From b412d9303221273c96d82167dfccaac5c1f5bb66 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Thu, 14 Nov 2024 16:44:33 -0500 Subject: [PATCH 01/64] add $Substitute Music: for credits.tbl Similar to Substitute Music in other places, this allows the credits music to be changed if an optional music pack is present. --- code/menuui/credits.cpp | 23 +++++++++++++++++++---- code/menuui/credits.h | 3 ++- code/scripting/api/libs/ui.cpp | 12 +++++++++++- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/code/menuui/credits.cpp b/code/menuui/credits.cpp index c5e3c068048..f6debecaffb 100644 --- a/code/menuui/credits.cpp +++ b/code/menuui/credits.cpp @@ -205,6 +205,8 @@ static credits_screen_buttons Buttons[NUM_BUTTONS][GR_NUM_RESOLUTIONS] = { }; char Credits_music_name[NAME_LENGTH] = "Cinema"; +char Credits_substitute_music_name[NAME_LENGTH] = ""; + static int Credits_music_handle = -1; static UI_TIMESTAMP Credits_music_begin_timestamp; @@ -319,6 +321,10 @@ void credits_parse_table(const char* filename) { stuff_string(Credits_music_name, F_NAME, NAME_LENGTH); } + if (optional_string("$Substitute Music:")) + { + stuff_string(Credits_substitute_music_name, F_NAME, NAME_LENGTH); + } if (optional_string("$Number of Images:")) { int temp; @@ -499,12 +505,21 @@ void credits_init() Credits_artwork_index = Random::next(Credits_num_images); } - auto credits_wavfile_name = credits_get_music_filename(Credits_music_name); - if (credits_wavfile_name != nullptr) { + const char *credits_wavfile_name = nullptr; + + // try substitute music first + if (*Credits_substitute_music_name) + credits_wavfile_name = credits_get_music_filename(Credits_substitute_music_name); + + // fall back to regular music + if (!credits_wavfile_name) + credits_wavfile_name = credits_get_music_filename(Credits_music_name); + + // if we have something, play it + if (credits_wavfile_name) credits_load_music(credits_wavfile_name); - } - // Use this id to trigger the start of music playing on the briefing screen + // Use this id to trigger the start of music playing on the credits screen Credits_music_begin_timestamp = ui_timestamp(Credits_music_delay); Credits_frametime = 0; diff --git a/code/menuui/credits.h b/code/menuui/credits.h index 33a083832fb..5a6a5429f2e 100644 --- a/code/menuui/credits.h +++ b/code/menuui/credits.h @@ -12,7 +12,8 @@ #ifndef __CREDITS_H__ #define __CREDITS_H__ -extern char Credits_music_name[NAME_LENGTH]; +extern char Credits_music_name[]; +extern char Credits_substitute_music_name[]; extern int Credits_num_images; extern int Credits_artwork_index; extern float Credits_scroll_rate; diff --git a/code/scripting/api/libs/ui.cpp b/code/scripting/api/libs/ui.cpp index b4e2ee4c29c..8b1e7244e3f 100644 --- a/code/scripting/api/libs/ui.cpp +++ b/code/scripting/api/libs/ui.cpp @@ -1792,7 +1792,17 @@ ADE_VIRTVAR(Music, l_UserInterface_Credits, nullptr, "The credits music filename LuaError(L, "This property is read only."); } - return ade_set_args(L, "s", credits_get_music_filename(Credits_music_name)); + const char *credits_wavfile_name = nullptr; + + // try substitute music first + if (*Credits_substitute_music_name) + credits_wavfile_name = credits_get_music_filename(Credits_substitute_music_name); + + // fall back to regular music + if (!credits_wavfile_name) + credits_wavfile_name = credits_get_music_filename(Credits_music_name); + + return ade_set_args(L, "s", credits_wavfile_name); } ADE_VIRTVAR(NumImages, l_UserInterface_Credits, nullptr, "The total number of credits images", "number", "The number of images") From 6bdd80bed32af10b145b33417007d77d13241802 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Fri, 29 Nov 2024 02:19:47 -0500 Subject: [PATCH 02/64] fix ship:warpIn() behavior for docked ships As described in #6445, the `warpIn()` script function did not work correctly for docked ships because the dock leader flag was not set. So, set the flag, but make sure to set it only for the first ship for which `warpIn()` is called. And make sure the flag is removed after the ship finishes arriving. Also remove an `Int3()` in `shipfx_warpin_start()`. (This Int3() was not actually in the original Volition source code release.) Fixes #6445. --- code/object/objectdock.cpp | 9 +++++++++ code/object/objectdock.h | 3 +++ code/scripting/api/objs/ship.cpp | 11 +++++++++++ code/ship/shipfx.cpp | 4 ++-- 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/code/object/objectdock.cpp b/code/object/objectdock.cpp index 1d7cff6f817..c32763d69f3 100644 --- a/code/object/objectdock.cpp +++ b/code/object/objectdock.cpp @@ -844,6 +844,15 @@ void object_remove_arriving_stage2_ndl_flag_helper(object *objp, dock_function_i Ships[objp->instance].flags.remove(Ship::Ship_Flags::Arriving_stage_2_dock_follower); } +void dock_find_dock_leader_helper(object *objp, dock_function_info *infop) +{ + if (Ships[objp->instance].flags[Ship::Ship_Flags::Dock_leader]) + { + infop->maintained_variables.objp_value = objp; + infop->early_return_condition = true; + } +} + void dock_calc_total_moi_helper(object* objp, dock_function_info* infop) { matrix local_moi, unorient, temp, world_moi; diff --git a/code/object/objectdock.h b/code/object/objectdock.h index 67975ff0c54..2e85df15386 100644 --- a/code/object/objectdock.h +++ b/code/object/objectdock.h @@ -152,4 +152,7 @@ void object_remove_arriving_stage1_ndl_flag_helper(object *objp, dock_function_i void object_set_arriving_stage2_ndl_flag_helper(object *objp, dock_function_info * /*infop*/ ); void object_remove_arriving_stage2_ndl_flag_helper(object *objp, dock_function_info * /*infop*/ ); +// find any ship in this group that is the dock leader +void dock_find_dock_leader_helper(object *objp, dock_function_info *infop); + #endif // _OBJECT_DOCK_H diff --git a/code/scripting/api/objs/ship.cpp b/code/scripting/api/objs/ship.cpp index 78213008553..0dd52cd1110 100644 --- a/code/scripting/api/objs/ship.cpp +++ b/code/scripting/api/objs/ship.cpp @@ -2104,6 +2104,17 @@ ADE_FUNC(warpIn, l_Ship, NULL, "Warps ship in", "boolean", "True if successful, if(!objh->isValid()) return ADE_RETURN_NIL; + if (object_is_docked(objh->objp())) + { + // Ships that are docked need a designated dock leader to bring the entire group in. The dock leader is, de facto, the arriving ship; + // so by scripting a certain ship to warp in, the script author has designated it as the leader. That being said, if the script + // calls warpIn() multiple times on the same docked group, only set the flag on the first ship. + dock_function_info dfi; + dock_evaluate_all_docked_objects(objh->objp(), &dfi, dock_find_dock_leader_helper); + if (!dfi.maintained_variables.objp_value) + Ships[objh->objp()->instance].flags.set(Ship::Ship_Flags::Dock_leader); + } + shipfx_warpin_start(objh->objp()); return ADE_RETURN_TRUE; diff --git a/code/ship/shipfx.cpp b/code/ship/shipfx.cpp index e7cbf204391..4db160a41be 100644 --- a/code/ship/shipfx.cpp +++ b/code/ship/shipfx.cpp @@ -480,10 +480,11 @@ void shipfx_actually_warpin(ship *shipp, object *objp) // dock leader needs to handle dockees if (object_is_docked(objp)) { - Assertion(shipp->flags[Ship::Ship_Flags::Dock_leader], "The docked ship warping in (%s) should only be the dock leader at this point!\n", shipp->ship_name); + Assertion(shipp->flags[Ship::Ship_Flags::Dock_leader], "The docked ship warping in (%s) should be the dock leader at this point!\n", shipp->ship_name); dock_function_info dfi; dock_evaluate_all_docked_objects(objp, &dfi, object_remove_arriving_stage1_ndl_flag_helper); dock_evaluate_all_docked_objects(objp, &dfi, object_remove_arriving_stage2_ndl_flag_helper); + shipp->flags.remove(Ship::Ship_Flags::Dock_leader); // the dock leader flag is only used for arrival and could interfere with future scripted warpIn() calls } // let physics in on it too. @@ -527,7 +528,6 @@ void shipfx_warpin_start( object *objp ) if (shipp->is_arriving()) { mprintf(( "Ship '%s' is already arriving!\n", shipp->ship_name )); - Int3(); return; } From bcdcba7b8f1c51b8e516edbe337c5a746d8240fe Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Sun, 8 Dec 2024 23:34:58 -0500 Subject: [PATCH 03/64] default the connection speed to Fast even if it has been set to None A connection speed of "None" is really just the default used by launchers if the user hasn't explicitly set the speed yet. Additionally, `CONNECTION_SPEED_NONE` isn't really used by the engine except for error checking. So, fall back to the default connection speed even when the speed has been set to "None". Follow-up to #6402. --- code/network/multiutil.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/code/network/multiutil.cpp b/code/network/multiutil.cpp index 0adb5ee56ce..4bc1bd4bd33 100644 --- a/code/network/multiutil.cpp +++ b/code/network/multiutil.cpp @@ -2948,8 +2948,10 @@ int multi_get_connection_speed() cspeed = CONNECTION_SPEED_CABLE; } else if ( !stricmp(connection_speed, NOX("Fast")) ) { cspeed = CONNECTION_SPEED_T1; + } else if (!stricmp(connection_speed, NOX("None"))) { + cspeed = CONNECTION_SPEED_T1; // default to Fast, per the os_config_read_string() call; per #define in multi.h, None doesn't really mean anything } else { - cspeed = CONNECTION_SPEED_NONE; + cspeed = CONNECTION_SPEED_NONE; // this will now no longer be returned unless the connection speed string is invalid } return cspeed; From 3bcc6b8c656eeacee1ab1fceb9586cbbfae27cb9 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Thu, 22 Aug 2024 16:51:23 +0100 Subject: [PATCH 04/64] Load data --- qtfred/source_groups.cmake | 5 + .../ShipEditor/ShipCustomWarpDialogModel.cpp | 163 ++++++++++++++++ .../ShipEditor/ShipCustomWarpDialogModel.h | 64 ++++++ .../ShipEditor/ShipCustomWarpDialog.cpp | 119 ++++++++++++ .../dialogs/ShipEditor/ShipCustomWarpDialog.h | 31 +++ .../dialogs/ShipEditor/ShipEditorDialog.cpp | 7 +- .../ui/dialogs/ShipEditor/ShipEditorDialog.h | 1 + qtfred/ui/ShipCustomWarpDialog.ui | 183 ++++++++++++++++++ 8 files changed, 571 insertions(+), 2 deletions(-) create mode 100644 qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp create mode 100644 qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h create mode 100644 qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp create mode 100644 qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.h create mode 100644 qtfred/ui/ShipCustomWarpDialog.ui diff --git a/qtfred/source_groups.cmake b/qtfred/source_groups.cmake index c46b7415130..a4bd7250f6a 100644 --- a/qtfred/source_groups.cmake +++ b/qtfred/source_groups.cmake @@ -88,6 +88,8 @@ add_file_folder("Source/Mission/Dialogs/ShipEditor" src/mission/dialogs/ShipEditor/ShipTBLViewerModel.h src/mission/dialogs/ShipEditor/ShipPathsDialogModel.cpp src/mission/dialogs/ShipEditor/ShipPathsDialogModel.h + src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h + src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp ) add_file_folder("Source/UI" @@ -156,6 +158,8 @@ add_file_folder("Source/UI/Dialogs/ShipEditor" src/ui/dialogs/ShipEditor/ShipTBLViewer.cpp src/ui/dialogs/ShipEditor/ShipPathsDialog.h src/ui/dialogs/ShipEditor/ShipPathsDialog.cpp + src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.h + src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp ) add_file_folder("Source/UI/Util" @@ -206,6 +210,7 @@ add_file_folder("UI" ui/ShipTextureReplacementDialog.ui ui/ShipTBLViewer.ui ui/ShipPathsDialog.ui + ui/ShipCustomWarpDialog.ui ) add_file_folder("Resources" diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp new file mode 100644 index 00000000000..382263bfd78 --- /dev/null +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp @@ -0,0 +1,163 @@ +#include "ShipCustomWarpDialogModel.h" + +#include "ship/shipfx.h" +namespace fso { +namespace fred { +namespace dialogs { +ShipCustomWarpDialogModel::ShipCustomWarpDialogModel(QObject* parent, EditorViewport* viewport, bool departure) + : AbstractDialogModel(parent, viewport), _m_departure(departure) +{ + initializeData(); +} + +bool ShipCustomWarpDialogModel::apply() +{ + return false; +} + +void ShipCustomWarpDialogModel::reject() {} + +int ShipCustomWarpDialogModel::getType() const +{ + return _m_warp_type; +} + +SCP_string ShipCustomWarpDialogModel::getStartSound() const +{ + return _m_start_sound; +} + +SCP_string ShipCustomWarpDialogModel::getEndSound() const +{ + return _m_end_sound; +} + +float ShipCustomWarpDialogModel::getEngageTime() const +{ + return _m_warpout_engage_time; +} + +float ShipCustomWarpDialogModel::getSpeed() const +{ + return _m_speed; +} + +float ShipCustomWarpDialogModel::getTime() const +{ + return _m_time; +} + +float ShipCustomWarpDialogModel::getExponent() const +{ + return _m_accel_exp; +} + +float ShipCustomWarpDialogModel::getRadius() const +{ + return _m_radius; +} + +SCP_string ShipCustomWarpDialogModel::getAnim() const +{ + return _m_anim; +} + +bool ShipCustomWarpDialogModel::getSupercap() const +{ + return _m_supercap_warp_physics; +} + +float ShipCustomWarpDialogModel::getPlayerSpeed() const +{ + return _m_player_warpout_speed; +} + +bool ShipCustomWarpDialogModel::departMode() const +{ + return _m_departure; +} + +bool ShipCustomWarpDialogModel::isPlayer() const +{ + return _m_player; +} + +void ShipCustomWarpDialogModel::initializeData() +{ + // find the params of the first marked ship + WarpParams* params = nullptr; + for (object* objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) { + if ((objp->type == OBJ_SHIP) || (objp->type == OBJ_START)) { + if (objp->flags[Object::Object_Flags::Marked]) { + if (!_m_departure) { + params = &Warp_params[Ships[objp->instance].warpin_params_index]; + } else { + params = &Warp_params[Ships[objp->instance].warpout_params_index]; + } + if (objp->type = OBJ_START) { + _m_player = true; + } + break; + } + } + } + + if (params != nullptr) { + if (params->warp_type & WT_DEFAULT_WITH_FIREBALL) { + _m_warp_type = (params->warp_type & WT_FLAG_MASK) + Num_warp_types; + } else if (params->warp_type >= 0 && params->warp_type < Num_warp_types) { + _m_warp_type = params->warp_type; + } + + if (params->snd_start.isValid()) { + _m_start_sound = gamesnd_get_game_sound(params->snd_start)->name; + } + if (params->snd_end.isValid()) { + _m_end_sound = gamesnd_get_game_sound(params->snd_end)->name; + } + + if (params->warpout_engage_time > 0) { + _m_warpout_engage_time = i2fl(params->warpout_engage_time) / 1000.0f; + } + + if (params->speed > 0.0f) { + _m_speed = params->speed; + } + + if (params->time > 0.0f) { + _m_time = i2fl(params->time) / 1000.0f; + } + if (params->accel_exp > 0.0f) { + _m_accel_exp = params->accel_exp; + } + + if (params->radius > 0.0f) { + _m_radius = params->radius; + } + + if (strlen(params->anim) > 0) { + _m_anim = params->anim; + } + + _m_supercap_warp_physics = params->supercap_warp_physics ? true : false; + + if (params->warpout_player_speed > 0.0f) { + _m_player_warpout_speed = params->warpout_player_speed; + } + } +} + +void ShipCustomWarpDialogModel::set_modified() +{ + if (!_modified) { + _modified = true; + } +} + +bool ShipCustomWarpDialogModel::query_modified() const +{ + return _modified; +} +} // namespace dialogs +} // namespace fred +} // namespace fso \ No newline at end of file diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h new file mode 100644 index 00000000000..62d9d02bc12 --- /dev/null +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h @@ -0,0 +1,64 @@ +#pragma once +#include "../AbstractDialogModel.h" +namespace fso { +namespace fred { +namespace dialogs { +class ShipCustomWarpDialogModel : public AbstractDialogModel { + private: + void initializeData(); + template + void modify(T& a, const T& b); + bool _modified = false; + bool _m_departure; + + int _m_warp_type; + SCP_string _m_start_sound; + SCP_string _m_end_sound; + float _m_warpout_engage_time; + float _m_speed; + float _m_time; + float _m_accel_exp; + float _m_radius; + SCP_string _m_anim; + bool _m_supercap_warp_physics; + float _m_player_warpout_speed; + + bool _m_player = false; + void set_modified(); + + public: + ShipCustomWarpDialogModel(QObject* parent, EditorViewport* viewport, bool departure); + bool apply() override; + void reject() override; + + int getType() const; + SCP_string getStartSound() const; + SCP_string getEndSound() const; + float getEngageTime() const; + float getSpeed() const; + float getTime() const; + float getExponent() const; + float getRadius() const; + SCP_string getAnim() const; + bool getSupercap() const; + float getPlayerSpeed() const; + + bool departMode() const; + bool isPlayer() const; + + bool query_modified() const; +}; + +template +inline void ShipCustomWarpDialogModel::modify(T& a, const T& b) +{ + if (a != b) { + a = b; + set_modified(); + modelChanged(); + } +} + +} // namespace dialogs +} // namespace fred +} // namespace fso \ No newline at end of file diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp b/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp new file mode 100644 index 00000000000..60582c62710 --- /dev/null +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp @@ -0,0 +1,119 @@ +#include "ShipCustomWarpDialog.h" + +#include "ui_ShipCustomWarpDialog.h" + +#include +#include + +#include + +namespace fso { +namespace fred { +namespace dialogs { +ShipCustomWarpDialog::ShipCustomWarpDialog(QDialog* parent, EditorViewport* viewport, bool departure) + : QDialog(parent), ui(new Ui::ShipCustomWarpDialog()), + _model(new ShipCustomWarpDialogModel(this, viewport, departure)), _viewport(viewport) +{ + ui->setupUi(this); + connect(this, &QDialog::accepted, _model.get(), &ShipCustomWarpDialogModel::apply); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &ShipCustomWarpDialog::rejectHandler); + + updateUI(true); + + // Resize the dialog to the minimum size + resize(QDialog::sizeHint()); +} + +ShipCustomWarpDialog::~ShipCustomWarpDialog() = default; +void ShipCustomWarpDialog::closeEvent(QCloseEvent* e) +{ + if (_model->query_modified()) { + auto button = _viewport->dialogProvider->showButtonDialog(DialogType::Question, + "Changes detected", + "Do you want to keep your changes?", + {DialogButton::Yes, DialogButton::No, DialogButton::Cancel}); + + if (button == DialogButton::Cancel) { + e->ignore(); + return; + } + + if (button == DialogButton::Yes) { + accept(); + return; + } + if (button == DialogButton::No) { + _model->reject(); + } + } + + QDialog::closeEvent(e); +} +void ShipCustomWarpDialog::updateUI(const bool firstrun) +{ + util::SignalBlockers blockers(this); + if (firstrun) { + for (int i = 0; i < Num_warp_types; ++i) { + SCP_string name = Warp_types[i]; + if (name == "Hyperspace") { + name = "Star Wars"; + } + ui->comboBoxType->addItem(name.c_str()); + } + for (const auto& fi : Fireball_info) { + ui->comboBoxType->addItem(fi.unique_id); + } + ui->comboBoxType->setCurrentIndex(_model->getType()); + + ui->lineEditStartSound->setText(_model->getStartSound().c_str()); + ui->lineEditEndSound->setText(_model->getEndSound().c_str()); + if (!_model->departMode()) { + ui->doubleSpinBoxEngage->setVisible(false); + ui->labelEngageTime->setVisible(false); + } else { + ui->doubleSpinBoxEngage->setValue(_model->getEngageTime()); + } + + ui->doubleSpinBoxSpeed->setValue(_model->getSpeed()); + ui->doubleSpinBoxTime->setValue(_model->getTime()); + + if (_model->departMode()) { + ui->labelExponent->setText(tr("Deceleration Exponent:")); + } else { + ui->labelExponent->setText(tr("Aceleration Exponent:")); + } + ui->doubleSpinBoxExponent->setValue(_model->getExponent()); + ui->doubleSpinBoxRadius->setValue(_model->getRadius()); + ui->lineEditAnim->setText(_model->getAnim().c_str()); + if (_model->getSupercap()) { + ui->checkBoxSupercap->setChecked(true); + } + if (!_model->isPlayer() || !_model->departMode()) { + ui->labelPlayerSpeed->setVisible(false); + ui->doubleSpinBoxPlayerSpeed->setVisible(false); + } else { + ui->doubleSpinBoxPlayerSpeed->setValue(_model->getPlayerSpeed()); + } + } + + if (ui->comboBoxType->itemText(ui->comboBoxType->currentIndex()) == "Homeworld") + { + ui->lineEditAnim->setVisible(true); + ui->labelAnim->setVisible(true); + } else { + ui->lineEditAnim->setVisible(false); + ui->labelAnim->setVisible(false); + } + + if (ui->comboBoxType->itemText(ui->comboBoxType->currentIndex()) == "Star Wars") { + ui->doubleSpinBoxExponent->setVisible(true); + ui->labelExponent->setVisible(true); + } else { + ui->doubleSpinBoxExponent->setVisible(false); + ui->labelExponent->setVisible(false); + } +} +void ShipCustomWarpDialog::rejectHandler() {} +} // namespace dialogs +} // namespace fred +} // namespace fso \ No newline at end of file diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.h b/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.h new file mode 100644 index 00000000000..92d28cd17b1 --- /dev/null +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.h @@ -0,0 +1,31 @@ +#pragma once +#include + +#include + +namespace fso { +namespace fred { +namespace dialogs { +namespace Ui { +class ShipCustomWarpDialog; +} + +class ShipCustomWarpDialog : public QDialog { + Q_OBJECT + public: + explicit ShipCustomWarpDialog(QDialog* parent, EditorViewport* viewport, const bool departure = false); + ~ShipCustomWarpDialog() override; + + protected: + void closeEvent(QCloseEvent*) override; + + private: + std::unique_ptr ui; + std::unique_ptr _model; + EditorViewport* _viewport; + void updateUI(const bool firstRun = false); + void rejectHandler(); +}; +} // namespace dialogs +} // namespace fred +} // namespace fso \ No newline at end of file diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp b/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp index beab9983726..f06c39ea12c 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp @@ -879,7 +879,8 @@ void ShipEditorDialog::on_restrictArrivalPathsButton_clicked() } void ShipEditorDialog::on_customWarpinButton_clicked() { - // TODO:: Custom warp Dialog + auto dialog = new dialogs::ShipCustomWarpDialog(this, _viewport, false); + dialog->show(); } void ShipEditorDialog::on_restrictDeparturePathsButton_clicked() { @@ -888,7 +889,9 @@ void ShipEditorDialog::on_restrictDeparturePathsButton_clicked() dialog->show(); } void ShipEditorDialog::on_customWarpoutButton_clicked() -{ // TODO:: Custom warp Dialog +{ + auto dialog = new dialogs::ShipCustomWarpDialog(this, _viewport, true); + dialog->show(); } } // namespace dialogs } // namespace fred diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.h b/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.h index 058850915ef..03dc286a238 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.h +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.h @@ -12,6 +12,7 @@ #include "ShipTextureReplacementDialog.h" #include "ShipTBLViewer.h" #include "ShipPathsDialog.h" +#include "ShipCustomWarpDialog.h" #include #include diff --git a/qtfred/ui/ShipCustomWarpDialog.ui b/qtfred/ui/ShipCustomWarpDialog.ui new file mode 100644 index 00000000000..52d5767c477 --- /dev/null +++ b/qtfred/ui/ShipCustomWarpDialog.ui @@ -0,0 +1,183 @@ + + + fso::fred::dialogs::ShipCustomWarpDialog + + + Qt::WindowModal + + + + 0 + 0 + 333 + 356 + + + + Edit Parameters + + + true + + + + + + + + Warp Type: + + + + + + + + + + Start Sound: + + + + + + + 32 + + + + + + + End Sound: + + + + + + + 32 + + + + + + + Warpout Engage Time: + + + + + + + + + + Warping Speed + + + + + + + + + + Warping Time + + + + + + + + + + Deceleration Exponent: + + + + + + + + + + Radius: + + + + + + + + + + Animation: + + + + + + + 32 + + + + + + + Supercap Warp Physics: + + + + + + + + + + + + + + Player Warpout Speed: + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + From 59f66a9d248214a6bd002fcf40249daf7589bc60 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Tue, 27 Aug 2024 09:24:40 +0100 Subject: [PATCH 05/64] Finish Up --- .../ShipEditor/ShipCustomWarpDialogModel.cpp | 123 +++++++++++++++++- .../ShipEditor/ShipCustomWarpDialogModel.h | 14 ++ .../ShipEditor/ShipCustomWarpDialog.cpp | 101 +++++++++++++- .../dialogs/ShipEditor/ShipCustomWarpDialog.h | 4 + qtfred/ui/ShipCustomWarpDialog.ui | 35 ++++- 5 files changed, 270 insertions(+), 7 deletions(-) diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp index 382263bfd78..32f6a1a4761 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp @@ -12,7 +12,69 @@ ShipCustomWarpDialogModel::ShipCustomWarpDialogModel(QObject* parent, EditorView bool ShipCustomWarpDialogModel::apply() { - return false; + WarpParams params; + params.direction = _m_departure ? WarpDirection::WARP_OUT : WarpDirection::WARP_IN; + + if (_m_warp_type < Num_warp_types) { + params.warp_type = _m_warp_type; + } else { + params.warp_type = (_m_warp_type - Num_warp_types) | WT_DEFAULT_WITH_FIREBALL; + } + + if (!_m_start_sound.empty()) { + gamesnd_id id = gamesnd_get_by_name(_m_start_sound.c_str()); + if (id.value() == -1) { + Warning(LOCATION, "Game Sound \"%s\" does not exist. Skipping", _m_start_sound.c_str()); + } else { + params.snd_start = id; + } + } + + if (!_m_end_sound.empty()) { + gamesnd_id id = gamesnd_get_by_name(_m_end_sound.c_str()); + if (id.value() == -1) { + Warning(LOCATION, "Game Sound \"%s\" does not exist. Skipping", _m_end_sound.c_str()); + } else { + params.snd_end = id; + } + } + + if (_m_departure && _m_warpout_engage_time) { + params.warpout_engage_time = fl2i(_m_warpout_engage_time * 1000.0f); + } + if (_m_speed) { + params.speed = _m_speed; + } + if (_m_time) { + params.time = fl2i(_m_time * 1000.0f); + } + if (_m_accel_exp) { + params.accel_exp = _m_accel_exp; + } + if (_m_radius) { + params.accel_exp = _m_radius; + } + if (!_m_anim.empty()) { + strcpy_s(params.anim, _m_anim.c_str()); + } + params.supercap_warp_physics = (_m_supercap_warp_physics == true); + if (_m_departure && _m_player_warpout_speed) { + params.warpout_player_speed = _m_player_warpout_speed; + } + int index = find_or_add_warp_params(params); + + for (object* objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) { + if ((objp->type == OBJ_SHIP) || (objp->type == OBJ_START)) { + if (objp->flags[Object::Object_Flags::Marked]) { + if (!_m_departure) + Ships[objp->instance].warpin_params_index = index; + else + Ships[objp->instance].warpout_params_index = index; + } + } + } + _editor->missionChanged(); + return true; } void ShipCustomWarpDialogModel::reject() {} @@ -158,6 +220,65 @@ bool ShipCustomWarpDialogModel::query_modified() const { return _modified; } +void ShipCustomWarpDialogModel::setType(const int index) +{ + modify(_m_warp_type, index); +} +void ShipCustomWarpDialogModel::setStartSound(const SCP_string newSound) +{ + if (!newSound.empty()) { + modify(_m_start_sound, newSound); + } else { + _m_start_sound = ""; + set_modified(); + } +} +void ShipCustomWarpDialogModel::setEndSound(const SCP_string newSound) +{ + if (!newSound.empty()) { + modify(_m_end_sound, newSound); + } else { + _m_end_sound = ""; + set_modified(); + } +} +void ShipCustomWarpDialogModel::setEngageTime(const double newValue) +{ + modify(_m_warpout_engage_time, static_cast(newValue)); +} +void ShipCustomWarpDialogModel::setSpeed(const double newValue) +{ + modify(_m_speed, static_cast(newValue)); +} +void ShipCustomWarpDialogModel::setTime(const double newValue) +{ + modify(_m_time, static_cast(newValue)); +} +void ShipCustomWarpDialogModel::setExponent(const double newValue) +{ + modify(_m_accel_exp, static_cast(newValue)); +} +void ShipCustomWarpDialogModel::setRadius(const double newValue) +{ + modify(_m_radius, static_cast(newValue)); +} +void ShipCustomWarpDialogModel::setAnim(const SCP_string& newAnim) +{ + if (!newAnim.empty()) { + modify(_m_anim, newAnim); + } else { + _m_anim = ""; + set_modified(); + } +} +void ShipCustomWarpDialogModel::setSupercap(const bool checked) +{ + modify(_m_supercap_warp_physics, checked); +} +void ShipCustomWarpDialogModel::setPlayerSpeed(const double newValue) +{ + modify(_m_player_warpout_speed, static_cast(newValue)); +} } // namespace dialogs } // namespace fred } // namespace fso \ No newline at end of file diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h index 62d9d02bc12..a791a9e6880 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h @@ -31,6 +31,7 @@ class ShipCustomWarpDialogModel : public AbstractDialogModel { bool apply() override; void reject() override; + //Getters int getType() const; SCP_string getStartSound() const; SCP_string getEndSound() const; @@ -47,6 +48,19 @@ class ShipCustomWarpDialogModel : public AbstractDialogModel { bool isPlayer() const; bool query_modified() const; + + //Setters + void setType(const int index); + void setStartSound(const SCP_string); + void setEndSound(const SCP_string); + void setEngageTime(const double); + void setSpeed(const double); + void setTime(const double); + void setExponent(const double); + void setRadius(const double); + void setAnim(const SCP_string&); + void setSupercap(const bool); + void setPlayerSpeed(const double); }; template diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp b/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp index 60582c62710..c088d0eee49 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp @@ -18,8 +18,44 @@ ShipCustomWarpDialog::ShipCustomWarpDialog(QDialog* parent, EditorViewport* view connect(this, &QDialog::accepted, _model.get(), &ShipCustomWarpDialogModel::apply); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &ShipCustomWarpDialog::rejectHandler); - updateUI(true); + connect(_model.get(), &AbstractDialogModel::modelChanged, this, [this]() { updateUI(false); }); + + connect(ui->comboBoxType, + static_cast(&QComboBox::currentIndexChanged), + _model.get(), + &ShipCustomWarpDialogModel::setType); + connect(ui->lineEditStartSound, &QLineEdit::editingFinished, this, &ShipCustomWarpDialog::startSoundChanged); + connect(ui->lineEditEndSound, &QLineEdit::editingFinished, this, &ShipCustomWarpDialog::endSoundChanged); + connect(ui->doubleSpinBoxEngage, + qOverload(&QDoubleSpinBox::valueChanged), + _model.get(), + &ShipCustomWarpDialogModel::setEngageTime); + connect(ui->doubleSpinBoxSpeed, + qOverload(&QDoubleSpinBox::valueChanged), + _model.get(), + &ShipCustomWarpDialogModel::setSpeed); + connect(ui->doubleSpinBoxTime, + qOverload(&QDoubleSpinBox::valueChanged), + _model.get(), + &ShipCustomWarpDialogModel::setTime); + connect(ui->doubleSpinBoxExponent, + qOverload(&QDoubleSpinBox::valueChanged), + _model.get(), + &ShipCustomWarpDialogModel::setExponent); + connect(ui->doubleSpinBoxRadius, + qOverload(&QDoubleSpinBox::valueChanged), + _model.get(), + &ShipCustomWarpDialogModel::setRadius); + connect(ui->checkBoxSupercap, &QCheckBox::toggled, _model.get(), [=](bool param) { + static_cast(_model.get())->setSupercap(param); + }); + connect(ui->lineEditAnim, &QLineEdit::editingFinished, this, &ShipCustomWarpDialog::animChanged); + connect(ui->doubleSpinBoxPlayerSpeed, + qOverload(&QDoubleSpinBox::valueChanged), + _model.get(), + &ShipCustomWarpDialogModel::setPlayerSpeed); + updateUI(true); // Resize the dialog to the minimum size resize(QDialog::sizeHint()); } @@ -46,7 +82,6 @@ void ShipCustomWarpDialog::closeEvent(QCloseEvent* e) _model->reject(); } } - QDialog::closeEvent(e); } void ShipCustomWarpDialog::updateUI(const bool firstrun) @@ -96,8 +131,7 @@ void ShipCustomWarpDialog::updateUI(const bool firstrun) } } - if (ui->comboBoxType->itemText(ui->comboBoxType->currentIndex()) == "Homeworld") - { + if (ui->comboBoxType->itemText(ui->comboBoxType->currentIndex()) == "Homeworld") { ui->lineEditAnim->setVisible(true); ui->labelAnim->setVisible(true); } else { @@ -113,7 +147,64 @@ void ShipCustomWarpDialog::updateUI(const bool firstrun) ui->labelExponent->setVisible(false); } } -void ShipCustomWarpDialog::rejectHandler() {} +void ShipCustomWarpDialog::rejectHandler() +{ + if (_model->query_modified()) { + auto button = _viewport->dialogProvider->showButtonDialog(DialogType::Question, + "Changes detected", + "Do you want to keep your changes?", + {DialogButton::Yes, DialogButton::No, DialogButton::Cancel}); + + if (button == DialogButton::Cancel) { + return; + } + + if (button == DialogButton::Yes) { + accept(); + return; + } + if (button == DialogButton::No) { + _model->reject(); + QDialog::reject(); + } + } else { + _model->reject(); + QDialog::reject(); + } +} +void ShipCustomWarpDialog::startSoundChanged() +{ + // String wrangling reqired in order to avoid crashes when directly converting from Qstring to std::string on some + // enviroments + QString temp(ui->lineEditStartSound->text()); + if (!temp.isEmpty()) { + _model.get()->setStartSound(temp.toLatin1().constData()); + } else { + _model.get()->setStartSound(""); + } +} +void ShipCustomWarpDialog::endSoundChanged() +{ + // String wrangling reqired in order to avoid crashes when directly converting from Qstring to std::string on some + // enviroments + QString temp(ui->lineEditEndSound->text()); + if (!temp.isEmpty()) { + _model.get()->setEndSound(temp.toLatin1().constData()); + } else { + _model.get()->setEndSound(""); + } +} +void ShipCustomWarpDialog::animChanged() +{ + // String wrangling reqired in order to avoid crashes when directly converting from Qstring to std::string on some + // enviroments + QString temp(ui->lineEditAnim->text()); + if (!temp.isEmpty()) { + _model.get()->setAnim(temp.toLatin1().constData()); + } else { + _model.get()->setAnim(""); + } +} } // namespace dialogs } // namespace fred } // namespace fso \ No newline at end of file diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.h b/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.h index 92d28cd17b1..9cfa9c1ee63 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.h +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.h @@ -25,6 +25,10 @@ class ShipCustomWarpDialog : public QDialog { EditorViewport* _viewport; void updateUI(const bool firstRun = false); void rejectHandler(); + + void startSoundChanged(); + void endSoundChanged(); + void animChanged(); }; } // namespace dialogs } // namespace fred diff --git a/qtfred/ui/ShipCustomWarpDialog.ui b/qtfred/ui/ShipCustomWarpDialog.ui index 52d5767c477..f93ac3531ea 100644 --- a/qtfred/ui/ShipCustomWarpDialog.ui +++ b/qtfred/ui/ShipCustomWarpDialog.ui @@ -179,5 +179,38 @@ - + + + buttonBox + accepted() + fso::fred::dialogs::ShipCustomWarpDialog + accept() + + + 206 + 331 + + + 94 + 329 + + + + + buttonBox + rejected() + fso::fred::dialogs::ShipCustomWarpDialog + reject() + + + 251 + 333 + + + 37 + 326 + + + + From 5bbb74151ff5e4b7755dae59b18ac5f875404916 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Tue, 27 Aug 2024 10:26:48 +0100 Subject: [PATCH 06/64] Add documentation and fix gcc errors --- .../ShipEditor/ShipCustomWarpDialogModel.cpp | 2 +- .../ShipEditor/ShipCustomWarpDialogModel.h | 80 ++++++++++++++++++- .../ShipEditor/ShipCustomWarpDialog.cpp | 12 +-- .../dialogs/ShipEditor/ShipCustomWarpDialog.h | 30 ++++++- 4 files changed, 112 insertions(+), 12 deletions(-) diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp index 32f6a1a4761..723b19a4268 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp @@ -156,7 +156,7 @@ void ShipCustomWarpDialogModel::initializeData() } else { params = &Warp_params[Ships[objp->instance].warpout_params_index]; } - if (objp->type = OBJ_START) { + if (objp->type == OBJ_START) { _m_player = true; } break; diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h index a791a9e6880..c8b57e06daf 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h @@ -3,10 +3,19 @@ namespace fso { namespace fred { namespace dialogs { +/** + * @brief Model for QtFRED's Custom warp dialog + */ class ShipCustomWarpDialogModel : public AbstractDialogModel { private: + /** + * @brief Initialises data for the model + */ void initializeData(); template + /** + * @brief Copies rvalue to the lvalue while setting the modified variable. + */ void modify(T& a, const T& b); bool _modified = false; bool _m_departure; @@ -24,32 +33,95 @@ class ShipCustomWarpDialogModel : public AbstractDialogModel { float _m_player_warpout_speed; bool _m_player = false; + /** + * @brief Marks the model as modifed + */ void set_modified(); public: + /** + * @brief Constructor + * @param [in] parent The parent dialog. + * @param [in] viewport The viewport this dialog is attacted to. + * @param [in] departure Whether the dialog is changeing warp-in or warp-out. + */ ShipCustomWarpDialogModel(QObject* parent, EditorViewport* viewport, bool departure); bool apply() override; void reject() override; - //Getters + // Getters + /** + * @brief Getter + * @return Index of warp type + */ int getType() const; + /** + * @brief Getter + * @return Sound name + */ SCP_string getStartSound() const; + /** + * @brief Getter + * @return Sound name + */ SCP_string getEndSound() const; + /** + * @brief Getter + * @return Engage time in seconds + */ float getEngageTime() const; + /** + * @brief Getter + * @return ship speed + */ float getSpeed() const; + /** + * @brief Getter + * @return Time in seconds + */ float getTime() const; + /** + * @brief Getter + * @return Exponent + */ float getExponent() const; + /** + * @brief Getter + * @return Radius of effect + */ float getRadius() const; + /** + * @brief Getter + * @return anim name + */ SCP_string getAnim() const; + /** + * @brief Getter + * @return Supercap Physics + */ bool getSupercap() const; + /** + * @brief Getter + * @return Player Warpout Speed + */ float getPlayerSpeed() const; - + /** + * @brief Getter + * @return If the model is in depart mode. + */ bool departMode() const; + /** + * @brief Getter + * @return If the model is working on a player. + */ bool isPlayer() const; - + /** + * @brief Getter + * @return If the model has been modified. + */ bool query_modified() const; - //Setters + // Setters void setType(const int index); void setStartSound(const SCP_string); void setEndSound(const SCP_string); diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp b/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp index c088d0eee49..0c44e5d3bb4 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp @@ -27,23 +27,23 @@ ShipCustomWarpDialog::ShipCustomWarpDialog(QDialog* parent, EditorViewport* view connect(ui->lineEditStartSound, &QLineEdit::editingFinished, this, &ShipCustomWarpDialog::startSoundChanged); connect(ui->lineEditEndSound, &QLineEdit::editingFinished, this, &ShipCustomWarpDialog::endSoundChanged); connect(ui->doubleSpinBoxEngage, - qOverload(&QDoubleSpinBox::valueChanged), + QOverload::of(&QDoubleSpinBox::valueChanged), _model.get(), &ShipCustomWarpDialogModel::setEngageTime); connect(ui->doubleSpinBoxSpeed, - qOverload(&QDoubleSpinBox::valueChanged), + QOverload::of(&QDoubleSpinBox::valueChanged), _model.get(), &ShipCustomWarpDialogModel::setSpeed); connect(ui->doubleSpinBoxTime, - qOverload(&QDoubleSpinBox::valueChanged), + QOverload::of(&QDoubleSpinBox::valueChanged), _model.get(), &ShipCustomWarpDialogModel::setTime); connect(ui->doubleSpinBoxExponent, - qOverload(&QDoubleSpinBox::valueChanged), + QOverload::of(&QDoubleSpinBox::valueChanged), _model.get(), &ShipCustomWarpDialogModel::setExponent); connect(ui->doubleSpinBoxRadius, - qOverload(&QDoubleSpinBox::valueChanged), + QOverload::of(&QDoubleSpinBox::valueChanged), _model.get(), &ShipCustomWarpDialogModel::setRadius); connect(ui->checkBoxSupercap, &QCheckBox::toggled, _model.get(), [=](bool param) { @@ -51,7 +51,7 @@ ShipCustomWarpDialog::ShipCustomWarpDialog(QDialog* parent, EditorViewport* view }); connect(ui->lineEditAnim, &QLineEdit::editingFinished, this, &ShipCustomWarpDialog::animChanged); connect(ui->doubleSpinBoxPlayerSpeed, - qOverload(&QDoubleSpinBox::valueChanged), + QOverload::of(&QDoubleSpinBox::valueChanged), _model.get(), &ShipCustomWarpDialogModel::setPlayerSpeed); diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.h b/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.h index 9cfa9c1ee63..6bf5a2c54fd 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.h +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.h @@ -9,25 +9,53 @@ namespace dialogs { namespace Ui { class ShipCustomWarpDialog; } - +/** + * @brief QtFRED's Custom Warp Editor + */ class ShipCustomWarpDialog : public QDialog { Q_OBJECT public: + /** + * @brief Constructor + * @param [in] parent The parent dialog. + * @param [in] viewport The viewport this dialog is attacted to. + * @param [in] departure Whether the dialog is changeing warp-in or warp-out. + */ explicit ShipCustomWarpDialog(QDialog* parent, EditorViewport* viewport, const bool departure = false); ~ShipCustomWarpDialog() override; protected: + /** + * @brief Overides the Dialogs Close event to add a confermation dialog + * @param [in] *e The event. + */ void closeEvent(QCloseEvent*) override; private: std::unique_ptr ui; std::unique_ptr _model; EditorViewport* _viewport; + /** + * @brief Populates the UI + * @param [in] firstRun If this is the first run. + */ void updateUI(const bool firstRun = false); + /** + * @brief Check if the user meant to cancel + */ void rejectHandler(); + /** + * @brief Update model with the contents of the start sound text box + */ void startSoundChanged(); + /** + * @brief Update model with the contents of the end sound text box + */ void endSoundChanged(); + /** + * @brief Update model with the contents of the anim text box + */ void animChanged(); }; } // namespace dialogs From 3965e1f3272a0541ec1fb9e9e4805b86565fa5fd Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Tue, 27 Aug 2024 10:55:10 +0100 Subject: [PATCH 07/64] Appease Clang and fix cancelling --- .../ShipEditor/ShipCustomWarpDialogModel.cpp | 8 ++++---- .../ShipEditor/ShipCustomWarpDialogModel.h | 4 ++-- .../ShipEditor/ShipCustomWarpDialog.cpp | 13 +++++++------ qtfred/ui/ShipCustomWarpDialog.ui | 18 +----------------- 4 files changed, 14 insertions(+), 29 deletions(-) diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp index 723b19a4268..ac7097632b1 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp @@ -57,7 +57,7 @@ bool ShipCustomWarpDialogModel::apply() if (!_m_anim.empty()) { strcpy_s(params.anim, _m_anim.c_str()); } - params.supercap_warp_physics = (_m_supercap_warp_physics == true); + params.supercap_warp_physics = _m_supercap_warp_physics; if (_m_departure && _m_player_warpout_speed) { params.warpout_player_speed = _m_player_warpout_speed; } @@ -201,7 +201,7 @@ void ShipCustomWarpDialogModel::initializeData() _m_anim = params->anim; } - _m_supercap_warp_physics = params->supercap_warp_physics ? true : false; + _m_supercap_warp_physics = params->supercap_warp_physics; if (params->warpout_player_speed > 0.0f) { _m_player_warpout_speed = params->warpout_player_speed; @@ -224,7 +224,7 @@ void ShipCustomWarpDialogModel::setType(const int index) { modify(_m_warp_type, index); } -void ShipCustomWarpDialogModel::setStartSound(const SCP_string newSound) +void ShipCustomWarpDialogModel::setStartSound(const SCP_string& newSound) { if (!newSound.empty()) { modify(_m_start_sound, newSound); @@ -233,7 +233,7 @@ void ShipCustomWarpDialogModel::setStartSound(const SCP_string newSound) set_modified(); } } -void ShipCustomWarpDialogModel::setEndSound(const SCP_string newSound) +void ShipCustomWarpDialogModel::setEndSound(const SCP_string& newSound) { if (!newSound.empty()) { modify(_m_end_sound, newSound); diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h index c8b57e06daf..b97f01f044b 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h @@ -123,8 +123,8 @@ class ShipCustomWarpDialogModel : public AbstractDialogModel { // Setters void setType(const int index); - void setStartSound(const SCP_string); - void setEndSound(const SCP_string); + void setStartSound(const SCP_string&); + void setEndSound(const SCP_string&); void setEngageTime(const double); void setSpeed(const double); void setTime(const double); diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp b/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp index 0c44e5d3bb4..5c5835f3ab9 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp @@ -16,6 +16,7 @@ ShipCustomWarpDialog::ShipCustomWarpDialog(QDialog* parent, EditorViewport* view { ui->setupUi(this); connect(this, &QDialog::accepted, _model.get(), &ShipCustomWarpDialogModel::apply); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &ShipCustomWarpDialog::rejectHandler); connect(_model.get(), &AbstractDialogModel::modelChanged, this, [this]() { updateUI(false); }); @@ -178,9 +179,9 @@ void ShipCustomWarpDialog::startSoundChanged() // enviroments QString temp(ui->lineEditStartSound->text()); if (!temp.isEmpty()) { - _model.get()->setStartSound(temp.toLatin1().constData()); + _model->setStartSound(temp.toLatin1().constData()); } else { - _model.get()->setStartSound(""); + _model->setStartSound(""); } } void ShipCustomWarpDialog::endSoundChanged() @@ -189,9 +190,9 @@ void ShipCustomWarpDialog::endSoundChanged() // enviroments QString temp(ui->lineEditEndSound->text()); if (!temp.isEmpty()) { - _model.get()->setEndSound(temp.toLatin1().constData()); + _model->setEndSound(temp.toLatin1().constData()); } else { - _model.get()->setEndSound(""); + _model->setEndSound(""); } } void ShipCustomWarpDialog::animChanged() @@ -200,9 +201,9 @@ void ShipCustomWarpDialog::animChanged() // enviroments QString temp(ui->lineEditAnim->text()); if (!temp.isEmpty()) { - _model.get()->setAnim(temp.toLatin1().constData()); + _model->setAnim(temp.toLatin1().constData()); } else { - _model.get()->setAnim(""); + _model->setAnim(""); } } } // namespace dialogs diff --git a/qtfred/ui/ShipCustomWarpDialog.ui b/qtfred/ui/ShipCustomWarpDialog.ui index f93ac3531ea..630d27cfe89 100644 --- a/qtfred/ui/ShipCustomWarpDialog.ui +++ b/qtfred/ui/ShipCustomWarpDialog.ui @@ -3,7 +3,7 @@ fso::fred::dialogs::ShipCustomWarpDialog - Qt::WindowModal + Qt::NonModal @@ -196,21 +196,5 @@ - - buttonBox - rejected() - fso::fred::dialogs::ShipCustomWarpDialog - reject() - - - 251 - 333 - - - 37 - 326 - - - From b1e96bd49e76fa6ad73b9ac0bbce79f1997bb4db Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Tue, 10 Dec 2024 08:36:13 +0000 Subject: [PATCH 08/64] Address Review Spelling Fix, Add explanatory note, Change Dialog title based on Warp In/Out --- .../ShipEditor/ShipCustomWarpDialog.cpp | 31 ++++++----- qtfred/ui/ShipCustomWarpDialog.ui | 51 +++++++++++-------- 2 files changed, 47 insertions(+), 35 deletions(-) diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp b/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp index 5c5835f3ab9..6297c08e50b 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp @@ -56,6 +56,11 @@ ShipCustomWarpDialog::ShipCustomWarpDialog(QDialog* parent, EditorViewport* view _model.get(), &ShipCustomWarpDialogModel::setPlayerSpeed); + if (departure) { + this->setWindowTitle("Edit Warp-Out Parameters"); + } else { + this->setWindowTitle("Edit Warp-in Parameters"); + } updateUI(true); // Resize the dialog to the minimum size resize(QDialog::sizeHint()); @@ -104,8 +109,8 @@ void ShipCustomWarpDialog::updateUI(const bool firstrun) ui->lineEditStartSound->setText(_model->getStartSound().c_str()); ui->lineEditEndSound->setText(_model->getEndSound().c_str()); if (!_model->departMode()) { - ui->doubleSpinBoxEngage->setVisible(false); - ui->labelEngageTime->setVisible(false); + ui->doubleSpinBoxEngage->setEnabled(false); + ui->labelEngageTime->setEnabled(false); } else { ui->doubleSpinBoxEngage->setValue(_model->getEngageTime()); } @@ -116,7 +121,7 @@ void ShipCustomWarpDialog::updateUI(const bool firstrun) if (_model->departMode()) { ui->labelExponent->setText(tr("Deceleration Exponent:")); } else { - ui->labelExponent->setText(tr("Aceleration Exponent:")); + ui->labelExponent->setText(tr("Acceleration Exponent:")); } ui->doubleSpinBoxExponent->setValue(_model->getExponent()); ui->doubleSpinBoxRadius->setValue(_model->getRadius()); @@ -125,27 +130,27 @@ void ShipCustomWarpDialog::updateUI(const bool firstrun) ui->checkBoxSupercap->setChecked(true); } if (!_model->isPlayer() || !_model->departMode()) { - ui->labelPlayerSpeed->setVisible(false); - ui->doubleSpinBoxPlayerSpeed->setVisible(false); + ui->labelPlayerSpeed->setEnabled(false); + ui->doubleSpinBoxPlayerSpeed->setEnabled(false); } else { ui->doubleSpinBoxPlayerSpeed->setValue(_model->getPlayerSpeed()); } } if (ui->comboBoxType->itemText(ui->comboBoxType->currentIndex()) == "Homeworld") { - ui->lineEditAnim->setVisible(true); - ui->labelAnim->setVisible(true); + ui->lineEditAnim->setEnabled(true); + ui->labelAnim->setEnabled(true); } else { - ui->lineEditAnim->setVisible(false); - ui->labelAnim->setVisible(false); + ui->lineEditAnim->setEnabled(false); + ui->labelAnim->setEnabled(false); } if (ui->comboBoxType->itemText(ui->comboBoxType->currentIndex()) == "Star Wars") { - ui->doubleSpinBoxExponent->setVisible(true); - ui->labelExponent->setVisible(true); + ui->doubleSpinBoxExponent->setEnabled(true); + ui->labelExponent->setEnabled(true); } else { - ui->doubleSpinBoxExponent->setVisible(false); - ui->labelExponent->setVisible(false); + ui->doubleSpinBoxExponent->setEnabled(false); + ui->labelExponent->setEnabled(false); } } void ShipCustomWarpDialog::rejectHandler() diff --git a/qtfred/ui/ShipCustomWarpDialog.ui b/qtfred/ui/ShipCustomWarpDialog.ui index 630d27cfe89..b53ef907093 100644 --- a/qtfred/ui/ShipCustomWarpDialog.ui +++ b/qtfred/ui/ShipCustomWarpDialog.ui @@ -20,132 +20,139 @@ true + + + + Note: All these parameters are optional. + + + - + Warp Type: - + - + Start Sound: - + 32 - + End Sound: - + 32 - + Warpout Engage Time: - + - + Warping Speed - + - + Warping Time - + - + Deceleration Exponent: - + - + Radius: - + - + Animation: - + 32 - + Supercap Warp Physics: - + - + Player Warpout Speed: - + From 91221d7dcedafd36418a54a91c03a8acfe661245 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Tue, 10 Dec 2024 09:48:05 +0000 Subject: [PATCH 09/64] Fix new C++17 Clang issues --- .../dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp | 8 ++------ .../dialogs/ShipEditor/ShipCustomWarpDialogModel.h | 8 ++------ qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp | 8 ++------ qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.h | 8 ++------ 4 files changed, 8 insertions(+), 24 deletions(-) diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp index ac7097632b1..48e9dcf90a4 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp @@ -1,9 +1,7 @@ #include "ShipCustomWarpDialogModel.h" #include "ship/shipfx.h" -namespace fso { -namespace fred { -namespace dialogs { +namespace fso::fred::dialogs { ShipCustomWarpDialogModel::ShipCustomWarpDialogModel(QObject* parent, EditorViewport* viewport, bool departure) : AbstractDialogModel(parent, viewport), _m_departure(departure) { @@ -279,6 +277,4 @@ void ShipCustomWarpDialogModel::setPlayerSpeed(const double newValue) { modify(_m_player_warpout_speed, static_cast(newValue)); } -} // namespace dialogs -} // namespace fred -} // namespace fso \ No newline at end of file +} // namespace dialogs \ No newline at end of file diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h index b97f01f044b..1fa27fa4afd 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h @@ -1,8 +1,6 @@ #pragma once #include "../AbstractDialogModel.h" -namespace fso { -namespace fred { -namespace dialogs { +namespace fso::fred::dialogs { /** * @brief Model for QtFRED's Custom warp dialog */ @@ -145,6 +143,4 @@ inline void ShipCustomWarpDialogModel::modify(T& a, const T& b) } } -} // namespace dialogs -} // namespace fred -} // namespace fso \ No newline at end of file +} // namespace dialogs \ No newline at end of file diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp b/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp index 6297c08e50b..d0574e3842a 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp @@ -7,9 +7,7 @@ #include -namespace fso { -namespace fred { -namespace dialogs { +namespace fso::fred::dialogs { ShipCustomWarpDialog::ShipCustomWarpDialog(QDialog* parent, EditorViewport* viewport, bool departure) : QDialog(parent), ui(new Ui::ShipCustomWarpDialog()), _model(new ShipCustomWarpDialogModel(this, viewport, departure)), _viewport(viewport) @@ -211,6 +209,4 @@ void ShipCustomWarpDialog::animChanged() _model->setAnim(""); } } -} // namespace dialogs -} // namespace fred -} // namespace fso \ No newline at end of file +} // namespace fso::fred::dialogs \ No newline at end of file diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.h b/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.h index 6bf5a2c54fd..4d5e90f5fec 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.h +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.h @@ -3,9 +3,7 @@ #include -namespace fso { -namespace fred { -namespace dialogs { +namespace fso::fred::dialogs { namespace Ui { class ShipCustomWarpDialog; } @@ -58,6 +56,4 @@ class ShipCustomWarpDialog : public QDialog { */ void animChanged(); }; -} // namespace dialogs -} // namespace fred -} // namespace fso \ No newline at end of file +} // namespace fso::fred::dialogs \ No newline at end of file From a742b6c067d2c220b1a11c0701d8e281a257c5d5 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Tue, 10 Dec 2024 15:46:50 -0500 Subject: [PATCH 10/64] add missing newline --- freespace2/freespace.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freespace2/freespace.cpp b/freespace2/freespace.cpp index 5d39aa60fdd..4e1f1020bbc 100644 --- a/freespace2/freespace.cpp +++ b/freespace2/freespace.cpp @@ -1086,7 +1086,7 @@ void game_level_init() Campaign_ending_via_supernova = 0; load_gl_init = (time(nullptr) - load_gl_init); - mprintf(("Game_level_init took %ld seconds", load_gl_init)); + mprintf(("Game_level_init took %ld seconds\n", load_gl_init)); //WMC - Init multi players for level if (Game_mode & GM_MULTIPLAYER && Player != nullptr) { From 43c153c127e042631be8e615649bd021a2ee9981 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Tue, 10 Dec 2024 20:48:44 +0000 Subject: [PATCH 11/64] Address Feedback --- .../dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp | 4 ++-- qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp | 8 ++++---- qtfred/ui/ShipCustomWarpDialog.ui | 5 ++++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp index 48e9dcf90a4..25567dbb63d 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp @@ -61,7 +61,7 @@ bool ShipCustomWarpDialogModel::apply() } int index = find_or_add_warp_params(params); - for (object* objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) { + for (object* objp : list_range(&obj_used_list)) { if ((objp->type == OBJ_SHIP) || (objp->type == OBJ_START)) { if (objp->flags[Object::Object_Flags::Marked]) { if (!_m_departure) @@ -146,7 +146,7 @@ void ShipCustomWarpDialogModel::initializeData() { // find the params of the first marked ship WarpParams* params = nullptr; - for (object* objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) { + for (object* objp : list_range(&obj_used_list)) { if ((objp->type == OBJ_SHIP) || (objp->type == OBJ_START)) { if (objp->flags[Object::Object_Flags::Marked]) { if (!_m_departure) { diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp b/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp index d0574e3842a..7f8c86ebc80 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp @@ -57,7 +57,7 @@ ShipCustomWarpDialog::ShipCustomWarpDialog(QDialog* parent, EditorViewport* view if (departure) { this->setWindowTitle("Edit Warp-Out Parameters"); } else { - this->setWindowTitle("Edit Warp-in Parameters"); + this->setWindowTitle("Edit Warp-In Parameters"); } updateUI(true); // Resize the dialog to the minimum size @@ -94,7 +94,7 @@ void ShipCustomWarpDialog::updateUI(const bool firstrun) if (firstrun) { for (int i = 0; i < Num_warp_types; ++i) { SCP_string name = Warp_types[i]; - if (name == "Hyperspace") { + if (i == WT_HYPERSPACE) { name = "Star Wars"; } ui->comboBoxType->addItem(name.c_str()); @@ -135,7 +135,7 @@ void ShipCustomWarpDialog::updateUI(const bool firstrun) } } - if (ui->comboBoxType->itemText(ui->comboBoxType->currentIndex()) == "Homeworld") { + if (ui->comboBoxType->currentIndex() == WT_SWEEPER) { ui->lineEditAnim->setEnabled(true); ui->labelAnim->setEnabled(true); } else { @@ -143,7 +143,7 @@ void ShipCustomWarpDialog::updateUI(const bool firstrun) ui->labelAnim->setEnabled(false); } - if (ui->comboBoxType->itemText(ui->comboBoxType->currentIndex()) == "Star Wars") { + if (ui->comboBoxType->currentIndex() == WT_HYPERSPACE) { ui->doubleSpinBoxExponent->setEnabled(true); ui->labelExponent->setEnabled(true); } else { diff --git a/qtfred/ui/ShipCustomWarpDialog.ui b/qtfred/ui/ShipCustomWarpDialog.ui index b53ef907093..eb9d4512ade 100644 --- a/qtfred/ui/ShipCustomWarpDialog.ui +++ b/qtfred/ui/ShipCustomWarpDialog.ui @@ -23,7 +23,10 @@ - Note: All these parameters are optional. + All of these parameters are optional, and a mission designer will usually not need to override more than one or two. These parameters are the same as those found in ships.tbl. + + + true From 02cbfbc34703e1d72cd711c6b3d9c0a78d0e97fc Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Tue, 10 Dec 2024 20:00:25 -0500 Subject: [PATCH 12/64] enhance the add-goal and remove-goal sexps 1. Allow the goal priority to be an evaluated number, not just a literal number. This allows the goal priority to be obtained from operators or calculations. 2. Since the priority can now be evaluated, it can be NaN. In that case, default to the max priority. 3. Consolidate `CDR(CDR(...))` to `CDDR()` in the aigoal implementation 4. Clarify that `remove-goal` removes the first goal of a given type and priority, not necessarily the first fully matching goal. 5. Enhance `remove-goal` to remove all goals matching the constraints, not just the first goal. 6. Allow `remove-goal` to disregard priority when checking for matching goals. --- code/ai/aigoals.cpp | 152 ++++++++++++++++++++++++++++---------------- code/ai/aigoals.h | 2 +- code/parse/sexp.cpp | 37 +++++++---- 3 files changed, 124 insertions(+), 67 deletions(-) diff --git a/code/ai/aigoals.cpp b/code/ai/aigoals.cpp index 4953b42fc82..1240ac492c0 100644 --- a/code/ai/aigoals.cpp +++ b/code/ai/aigoals.cpp @@ -907,6 +907,7 @@ void ai_add_wing_goal_player( int type, int mode, int submode, const char *shipn void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, const char *actor_name ) { int node, dummy, op; + bool priority_is_nan = false, priority_is_nan_forever = false; Assert ( Sexp_nodes[sexp].first != -1 ); node = Sexp_nodes[sexp].first; @@ -932,7 +933,7 @@ void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, cons aigp->target_name = ai_get_goal_target_name(CTEXT(CDR(node)), &aigp->target_name_index); // waypoint path name; - aigp->priority = atoi( CTEXT(CDR(CDR(node))) ); + aigp->priority = eval_num(CDDR(node), priority_is_nan, priority_is_nan_forever); aigp->ai_mode = AI_GOAL_WAYPOINTS; if ( op == OP_AI_WAYPOINTS_ONCE ) aigp->ai_mode = AI_GOAL_WAYPOINTS_ONCE; @@ -950,9 +951,9 @@ void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, cons aigp->target_name = ai_get_goal_target_name( CTEXT(CDR(node)), &aigp->target_name_index ); // store the name of the subsystem in the docker.name field for now -- this field must get // fixed up when the goal is valid since we need to locate the subsystem on the ship's model - aigp->docker.name = ai_get_goal_target_name(CTEXT(CDR(CDR(node))), &dummy); + aigp->docker.name = ai_get_goal_target_name(CTEXT(CDDR(node)), &dummy); aigp->flags.set(AI::Goal_Flags::Subsys_needs_fixup); - aigp->priority = atoi( CTEXT(CDR(CDR(CDR(node)))) ); + aigp->priority = eval_num(CDDDR(node), priority_is_nan, priority_is_nan_forever); break; case OP_AI_DISABLE_SHIP: @@ -960,7 +961,7 @@ void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, cons aigp->ai_mode = (op == OP_AI_DISABLE_SHIP) ? AI_GOAL_DISABLE_SHIP : AI_GOAL_DISABLE_SHIP_TACTICAL; aigp->target_name = ai_get_goal_target_name( CTEXT(CDR(node)), &aigp->target_name_index ); aigp->ai_submode = -SUBSYSTEM_ENGINE; - aigp->priority = atoi( CTEXT(CDR(CDR(node))) ); + aigp->priority = eval_num(CDDR(node), priority_is_nan, priority_is_nan_forever); break; case OP_AI_DISARM_SHIP: @@ -968,27 +969,27 @@ void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, cons aigp->ai_mode = (op == OP_AI_DISARM_SHIP) ? AI_GOAL_DISARM_SHIP : AI_GOAL_DISARM_SHIP_TACTICAL; aigp->target_name = ai_get_goal_target_name( CTEXT(CDR(node)), &aigp->target_name_index ); aigp->ai_submode = -SUBSYSTEM_TURRET; - aigp->priority = atoi( CTEXT(CDR(CDR(node))) ); + aigp->priority = eval_num(CDDR(node), priority_is_nan, priority_is_nan_forever); break; case OP_AI_WARP_OUT: aigp->ai_mode = AI_GOAL_WARP; - aigp->priority = atoi( CTEXT(CDR(node)) ); + aigp->priority = eval_num(CDR(node), priority_is_nan, priority_is_nan_forever); break; // the following goal is obsolete, but here for compatibility case OP_AI_WARP: aigp->ai_mode = AI_GOAL_WARP; aigp->target_name = ai_get_goal_target_name(CTEXT(CDR(node)), &aigp->target_name_index); // waypoint path name; - aigp->priority = atoi( CTEXT(CDR(CDR(node))) ); + aigp->priority = eval_num(CDDR(node), priority_is_nan, priority_is_nan_forever); break; case OP_AI_UNDOCK: - aigp->priority = atoi( CTEXT(CDR(node)) ); + aigp->priority = eval_num(CDR(node), priority_is_nan, priority_is_nan_forever); // Goober5000 - optional undock with something - if (CDR(CDR(node)) != -1) - aigp->target_name = ai_get_goal_target_name( CTEXT(CDR(CDR(node))), &aigp->target_name_index ); + if (CDDR(node) != -1) + aigp->target_name = ai_get_goal_target_name( CTEXT(CDDR(node)), &aigp->target_name_index ); aigp->ai_mode = AI_GOAL_UNDOCK; aigp->ai_submode = AIS_UNDOCK_0; @@ -998,7 +999,7 @@ void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, cons { aigp->ai_mode = AI_GOAL_REARM_REPAIR; aigp->target_name = ai_get_goal_target_name(CTEXT(CDR(node)), &aigp->target_name_index); - aigp->priority = atoi( CTEXT(CDR(CDR(node))) ); + aigp->priority = eval_num(CDDR(node), priority_is_nan, priority_is_nan_forever); // this goal needs some extra setup // if this doesn't work, the goal will be immediately removed @@ -1016,36 +1017,36 @@ void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, cons case OP_AI_STAY_STILL: aigp->ai_mode = AI_GOAL_STAY_STILL; aigp->target_name = ai_get_goal_target_name(CTEXT(CDR(node)), &aigp->target_name_index); // waypoint path name; - aigp->priority = atoi( CTEXT(CDR(CDR(node))) ); + aigp->priority = eval_num(CDDR(node), priority_is_nan, priority_is_nan_forever); break; case OP_AI_DOCK: aigp->target_name = ai_get_goal_target_name( CTEXT(CDR(node)), &aigp->target_name_index ); - aigp->docker.name = ai_add_dock_name(CTEXT(CDR(CDR(node)))); - aigp->dockee.name = ai_add_dock_name(CTEXT(CDR(CDR(CDR(node))))); - aigp->priority = atoi( CTEXT(CDR(CDR(CDR(CDR(node))))) ); + aigp->docker.name = ai_add_dock_name(CTEXT(CDDR(node))); + aigp->dockee.name = ai_add_dock_name(CTEXT(CDDDR(node))); + aigp->priority = eval_num(CDDDDR(node), priority_is_nan, priority_is_nan_forever); aigp->ai_mode = AI_GOAL_DOCK; aigp->ai_submode = AIS_DOCK_0; // be sure to set the submode break; case OP_AI_CHASE_ANY: - aigp->priority = atoi( CTEXT(CDR(node)) ); + aigp->priority = eval_num(CDR(node), priority_is_nan, priority_is_nan_forever); aigp->ai_mode = AI_GOAL_CHASE_ANY; break; case OP_AI_PLAY_DEAD: - aigp->priority = atoi( CTEXT(CDR(node)) ); + aigp->priority = eval_num(CDR(node), priority_is_nan, priority_is_nan_forever); aigp->ai_mode = AI_GOAL_PLAY_DEAD; break; case OP_AI_PLAY_DEAD_PERSISTENT: - aigp->priority = atoi( CTEXT(CDR(node)) ); + aigp->priority = eval_num(CDR(node), priority_is_nan, priority_is_nan_forever); aigp->ai_mode = AI_GOAL_PLAY_DEAD_PERSISTENT; break; case OP_AI_KEEP_SAFE_DISTANCE: - aigp->priority = atoi( CTEXT(CDR(node)) ); + aigp->priority = eval_num(CDR(node), priority_is_nan, priority_is_nan_forever); aigp->ai_mode = AI_GOAL_KEEP_SAFE_DISTANCE; break; @@ -1055,7 +1056,7 @@ void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, cons bool is_nan, is_nan_forever; aigp->target_name = ai_get_goal_target_name( CTEXT(CDR(node)), &aigp->target_name_index ); - aigp->priority = atoi( CTEXT(CDDR(node)) ); + aigp->priority = eval_num(CDDR(node), priority_is_nan, priority_is_nan_forever); // distance from ship if ( CDDDR(node) < 0 ) @@ -1091,7 +1092,7 @@ void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, cons case OP_AI_IGNORE: case OP_AI_IGNORE_NEW: aigp->target_name = ai_get_goal_target_name( CTEXT(CDR(node)), &aigp->target_name_index ); - aigp->priority = atoi( CTEXT(CDR(CDR(node))) ); + aigp->priority = eval_num(CDDR(node), priority_is_nan, priority_is_nan_forever); if ( op == OP_AI_CHASE ) { aigp->ai_mode = AI_GOAL_CHASE; @@ -1141,7 +1142,7 @@ void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, cons localnode = CDR(localnode); } - aigp->priority = atoi( CTEXT(localnode) ); + aigp->priority = eval_num(localnode, priority_is_nan, priority_is_nan_forever); aigp->lua_ai_target = { std::move(target), luaAIMode->sexp.getSEXPArgumentList(CDR(localnode)) }; } @@ -1150,8 +1151,8 @@ void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, cons } } - if ( aigp->priority > MAX_GOAL_PRIORITY ) { - nprintf (("AI", "bashing sexpression priority of goal %s from %d to %d.\n", CTEXT(node), aigp->priority, MAX_GOAL_PRIORITY)); + if ( aigp->priority > MAX_GOAL_PRIORITY || priority_is_nan || priority_is_nan_forever ) { + nprintf (("AI", "bashing add-goal sexpression priority of goal %s from %d to %d.\n", Sexp_nodes[CAR(sexp)].text, aigp->priority, MAX_GOAL_PRIORITY)); aigp->priority = MAX_GOAL_PRIORITY; } @@ -1227,9 +1228,10 @@ int ai_find_goal_index( ai_goal* aigp, int mode, int submode, int priority ) /* Remove a goal from the given goals structure * Returns the index of the goal that it clears out. - * This is important so that if active_goal == index you can set AI_GOAL_NONE + * This is important so that if active_goal == index you can set AI_GOAL_NONE. + * NOTE: Callers should check the value of remove_more. If it is true, the function should be called again. */ -int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp ) +int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp, bool &remove_more ) { /* Sanity check */ Assert( Sexp_nodes[ sexp ].first != -1 ); @@ -1245,115 +1247,145 @@ int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp ) /* The operator to use */ int op = get_operator_const( node ); + // since this logic is common to all goals removed by the remove-goal sexp + auto eval_priority_et_seq = [sexp, &remove_more](int n, int priority_if_no_n = -1)->int + { + bool _priority_is_nan = false, _priority_is_nan_forever = false; + + int _priority = (n >= 0) ? eval_num(n, _priority_is_nan, _priority_is_nan_forever) : priority_if_no_n; + n = CDR(sexp); // we want the first node after the goal sub-tree + + if (_priority > MAX_GOAL_PRIORITY || _priority_is_nan || _priority_is_nan_forever) + { + nprintf(("AI", "bashing remove-goal sexpression priority of goal %s from %d to %d.\n", Sexp_nodes[CAR(sexp)].text, _priority, MAX_GOAL_PRIORITY)); + _priority = MAX_GOAL_PRIORITY; + } + + if (n >= 0) + { + remove_more = is_sexp_true(n); + n = CDR(n); + } + + if (n >= 0) + { + if (is_sexp_true(n)) + _priority = -1; + n = CDR(n); + } + + return _priority; + }; + /* We now need to determine what the mode and submode values are*/ switch( op ) { case OP_AI_WAYPOINTS_ONCE: goalmode = AI_GOAL_WAYPOINTS_ONCE; - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); break; case OP_AI_WAYPOINTS: goalmode = AI_GOAL_WAYPOINTS; - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); break; case OP_AI_DESTROY_SUBSYS: goalmode = AI_GOAL_DESTROY_SUBSYSTEM; - priority = ( CDR( CDR( CDR(node) ) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( CDR( node ) ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDDR(node)); break; case OP_AI_DISABLE_SHIP: case OP_AI_DISABLE_SHIP_TACTICAL: goalmode = (op == OP_AI_DISABLE_SHIP) ? AI_GOAL_DISABLE_SHIP : AI_GOAL_DISABLE_SHIP_TACTICAL; - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); break; case OP_AI_DISARM_SHIP: case OP_AI_DISARM_SHIP_TACTICAL: goalmode = (op == OP_AI_DISARM_SHIP) ? AI_GOAL_DISARM_SHIP : AI_GOAL_DISARM_SHIP_TACTICAL; - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); break; case OP_AI_WARP_OUT: goalmode = AI_GOAL_WARP; - priority = ( CDR(node) >= 0 ) ? atoi( CTEXT( CDR( node ) ) ) : -1; + priority = eval_priority_et_seq(CDR(node)); break; case OP_AI_WARP: goalmode = AI_GOAL_WARP; - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); break; case OP_AI_UNDOCK: goalmode = AI_GOAL_UNDOCK; goalsubmode = AIS_UNDOCK_0; - priority = ( CDR(node) >= 0 ) ? atoi( CTEXT( CDR( node ) ) ) : -1; + priority = eval_priority_et_seq(CDR(node)); break; case OP_AI_STAY_STILL: goalmode = AI_GOAL_STAY_STILL; - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); break; case OP_AI_DOCK: goalmode = AI_GOAL_DOCK; goalsubmode = AIS_DOCK_0; - priority = ( CDR( CDR( CDR( CDR(node) ) ) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( CDR( CDR( node ) ) ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDDDR(node)); break; case OP_AI_CHASE_ANY: goalmode = AI_GOAL_CHASE_ANY; - priority = ( CDR(node) >= 0 ) ? atoi( CTEXT( CDR( node ) ) ) : -1; + priority = eval_priority_et_seq(CDR(node)); break; case OP_AI_PLAY_DEAD: case OP_AI_PLAY_DEAD_PERSISTENT: goalmode = (op == OP_AI_PLAY_DEAD) ? AI_GOAL_PLAY_DEAD : AI_GOAL_PLAY_DEAD_PERSISTENT; - priority = ( CDR(node) >= 0 ) ? atoi( CTEXT( CDR( node ) ) ) : -1; + priority = eval_priority_et_seq(CDR(node)); break; case OP_AI_KEEP_SAFE_DISTANCE: - priority = ( CDR(node) >= 0 ) ? atoi( CTEXT( CDR( node ) ) ) : -1; + priority = eval_priority_et_seq(CDR(node)); goalmode = AI_GOAL_KEEP_SAFE_DISTANCE; break; case OP_AI_CHASE: - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); if ( wing_name_lookup( CTEXT( CDR( node ) ), 1 ) != -1 ) goalmode = AI_GOAL_CHASE_WING; else goalmode = AI_GOAL_CHASE; break; case OP_AI_GUARD: - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); if ( wing_name_lookup( CTEXT( CDR( node ) ), 1 ) != -1 ) goalmode = AI_GOAL_GUARD_WING; else goalmode = AI_GOAL_GUARD; break; case OP_AI_GUARD_WING: - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); goalmode = AI_GOAL_GUARD_WING; break; case OP_AI_CHASE_WING: - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); goalmode = AI_GOAL_CHASE_WING; break; case OP_AI_CHASE_SHIP_CLASS: - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); goalmode = AI_GOAL_CHASE_SHIP_CLASS; break; case OP_AI_EVADE_SHIP: - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); goalmode = AI_GOAL_EVADE_SHIP; break; case OP_AI_STAY_NEAR_SHIP: - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); goalmode = AI_GOAL_STAY_NEAR_SHIP; break; case OP_AI_IGNORE: case OP_AI_IGNORE_NEW: - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); goalmode = (op == OP_AI_IGNORE) ? AI_GOAL_IGNORE : AI_GOAL_IGNORE_NEW; break; case OP_AI_FORM_ON_WING: - priority = 99; + priority = eval_priority_et_seq(-1, 99); goalmode = AI_GOAL_FORM_ON_WING; break; case OP_AI_FLY_TO_SHIP: - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); goalmode = AI_GOAL_FLY_TO_SHIP; break; case OP_AI_REARM_REPAIR: - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); goalmode = AI_GOAL_REARM_REPAIR; break; default: @@ -1369,7 +1401,7 @@ int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp ) localnode = CDR(localnode); } - priority = localnode >= 0 ? atoi( CTEXT(localnode) ) : -1; + priority = eval_priority_et_seq(localnode); } else { UNREACHABLE("Invalid SEXP-OP %s (number %d) for an AI goal!", Sexp_nodes[node].text, op); @@ -1381,7 +1413,10 @@ int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp ) int goalindex = ai_find_goal_index( aigp, goalmode, goalsubmode, priority ); if ( goalindex == -1 ) + { + remove_more = false; return -1; /* no more to do; */ + } /* Clear out the contents of the goal. We can't use ai_remove_ship_goal since it needs ai_info and * we've only got ai_goals */ @@ -1395,6 +1430,7 @@ void ai_remove_wing_goal_sexp(int sexp, wing *wingp) { int i; int goalindex = -1; + bool remove_more = false; // remove the ai goal for any ship that is currently arrived in the game (only if fred isn't running) if ( !Fred_running ) { @@ -1402,9 +1438,13 @@ void ai_remove_wing_goal_sexp(int sexp, wing *wingp) int num = wingp->ship_index[i]; if ( num == -1 ) // ship must have been destroyed or departed continue; - goalindex = ai_remove_goal_sexp_sub( sexp, Ai_info[Ships[num].ai_index].goals ); - if ( Ai_info[Ships[num].ai_index].active_goal == goalindex ) - Ai_info[Ships[num].ai_index].active_goal = AI_GOAL_NONE; + auto aip = &Ai_info[Ships[num].ai_index]; + + do { + goalindex = ai_remove_goal_sexp_sub(sexp, aip->goals, remove_more); + if (aip->active_goal == goalindex) + aip->active_goal = AI_GOAL_NONE; + } while (remove_more); } } @@ -1412,7 +1452,9 @@ void ai_remove_wing_goal_sexp(int sexp, wing *wingp) // there are more waves to come if ((wingp->num_waves - wingp->current_wave > 0) || Fred_running) { - ai_remove_goal_sexp_sub( sexp, wingp->ai_goals ); + do { + ai_remove_goal_sexp_sub(sexp, wingp->ai_goals, remove_more); + } while (remove_more); } } diff --git a/code/ai/aigoals.h b/code/ai/aigoals.h index 9d8cd5475f3..f37bbbb2f74 100644 --- a/code/ai/aigoals.h +++ b/code/ai/aigoals.h @@ -173,7 +173,7 @@ extern void ai_add_ship_goal_sexp( int sexp, int type, ai_info *aip ); extern void ai_add_wing_goal_sexp( int sexp, int type, wing *wingp ); extern void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, const char *actor_name); -extern int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp ); +extern int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp, bool &remove_more ); extern void ai_remove_wing_goal_sexp( int sexp, wing *wingp ); // adds goals to ships/sings through player orders diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index f8ff3f7e702..7667c49d4dc 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -433,7 +433,7 @@ SCP_vector Operators = { //AI Control Sub-Category { "add-goal", OP_ADD_GOAL, 2, 2, SEXP_ACTION_OPERATOR, }, - { "remove-goal", OP_REMOVE_GOAL, 2, 2, SEXP_ACTION_OPERATOR, }, // Goober5000 + { "remove-goal", OP_REMOVE_GOAL, 2, 4, SEXP_ACTION_OPERATOR, }, // Goober5000 { "add-ship-goal", OP_ADD_SHIP_GOAL, 2, 2, SEXP_ACTION_OPERATOR, }, { "add-wing-goal", OP_ADD_WING_GOAL, 2, 2, SEXP_ACTION_OPERATOR, }, { "clear-goals", OP_CLEAR_GOALS, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, @@ -13139,12 +13139,16 @@ void sexp_remove_goal(int n) if (!ship_entry->has_shipp()) return; // ship not around anymore???? then forget it! - int goalindex = ai_remove_goal_sexp_sub(goal_node, Ai_info[ship_entry->shipp()->ai_index].goals); - if (goalindex >= 0) - { - if (Ai_info[ship_entry->shipp()->ai_index].active_goal == goalindex) - Ai_info[ship_entry->shipp()->ai_index].active_goal = AI_GOAL_NONE; - } + bool remove_more = false; + auto aip = &Ai_info[ship_entry->shipp()->ai_index]; + do { + int goalindex = ai_remove_goal_sexp_sub(goal_node, aip->goals, remove_more); + if (goalindex >= 0) + { + if (aip->active_goal == goalindex) + aip->active_goal = AI_GOAL_NONE; + } + } while (remove_more); return; } @@ -32167,12 +32171,19 @@ int query_operator_argument_type(int op, int argnum) return OPF_AI_GOAL; case OP_ADD_GOAL: - case OP_REMOVE_GOAL: if ( argnum == 0 ) return OPF_SHIP_WING; else return OPF_AI_GOAL; + case OP_REMOVE_GOAL: + if ( argnum == 0 ) + return OPF_SHIP_WING; + else if ( argnum == 1 ) + return OPF_AI_GOAL; + else + return OPF_BOOL; + case OP_COND: case OP_WHEN: case OP_EVERY_TIME: @@ -38609,10 +38620,14 @@ SCP_vector Sexp_help = { // Goober5000 { OP_REMOVE_GOAL, "Remove goal (Action operator)\r\n" - "\tRemoves a goal from a ship or wing.\r\n\r\n" - "Takes 2 arguments...\r\n" + "\tRemoves a goal from a ship or wing. Note that, by default, only the type of goal and the priority are matched. This operator " + "does not distinguish between, for example, two different waypoint goals.\r\n\r\n" + "Takes 2 to 4 arguments...\r\n" "\t1:\tName of ship or wing to remove goal from (ship/wing must be in-mission).\r\n" - "\t2:\tGoal to remove." }, + "\t2:\tGoal to remove.\r\n" + "\t3:\tWhether to remove all matching goals (optional; defaults to false; if false, only the first matching goal will be removed).\r\n" + "\t4:\tWhether to ignore the priority when matching a goal (optional).\r\n" + }, { OP_SABOTAGE_SUBSYSTEM, "Sabotage subystem (Action operator)\r\n" "\tReduces the specified subsystem integrity by the specified percentage." From 09552bb1952af872fa9f2342010a91f58e6a8f61 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Tue, 10 Dec 2024 20:46:48 -0500 Subject: [PATCH 13/64] bypass the SEXP caching code if running in FRED --- code/parse/sexp.cpp | 67 +++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index 7667c49d4dc..23fb4c2a209 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -5725,40 +5725,46 @@ wing *eval_wing(int node) /** * Returns a number parsed from the sexp node text. - * NOTE: sexp_atoi can only be used if CTEXT was used; i.e. atoi(CTEXT(n)) + * NOTE: sexp_atoi() should only replace atoi(CTEXT(n)) - it should not replace atoi(Sexp_nodes[node].text) - see commit 9923c87bc1 */ int sexp_atoi(int node) { - Assertion(!Fred_running, "This function relies on SEXP caching which is not set up to work in FRED!"); if (node < 0) return 0; - // check cache - if (Sexp_nodes[node].cache) + // SEXP caching is not set up to work in FRED, so bypass all the caching code in that case + if (!Fred_running) { - // have we cached something else? - if (Sexp_nodes[node].cache->sexp_node_data_type != OPF_NUMBER) - return 0; + // check cache + if (Sexp_nodes[node].cache) + { + // have we cached something else? + if (Sexp_nodes[node].cache->sexp_node_data_type != OPF_NUMBER) + return 0; - return Sexp_nodes[node].cache->numeric_literal; - } + return Sexp_nodes[node].cache->numeric_literal; + } - // maybe forward to a special-arg node - if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE) - { - auto current_argument = Sexp_replacement_arguments.back(); - int arg_node = current_argument.second; + // maybe forward to a special-arg node + if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE) + { + auto current_argument = Sexp_replacement_arguments.back(); + int arg_node = current_argument.second; - if (arg_node >= 0) - return sexp_atoi(arg_node); + if (arg_node >= 0) + return sexp_atoi(arg_node); + } } int num = atoi(CTEXT(node)); ensure_opf_positive_is_positive(node, num); - // cache the value if it can't change later - if (!is_node_value_dynamic(node)) - Sexp_nodes[node].cache = new sexp_cached_data(OPF_NUMBER, num, -1); + if (!Fred_running) + { + // cache the value if it can't change later + if (!is_node_value_dynamic(node)) + Sexp_nodes[node].cache = new sexp_cached_data(OPF_NUMBER, num, -1); + } return num; } @@ -5768,21 +5774,24 @@ int sexp_atoi(int node) */ bool sexp_can_construe_as_integer(int node) { - Assertion(!Fred_running, "This function relies on SEXP caching which is not set up to work in FRED!"); if (node < 0) return false; - if (Sexp_nodes[node].cache && Sexp_nodes[node].cache->sexp_node_data_type == OPF_NUMBER) - return true; - - // maybe forward to a special-arg node - if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE) + // SEXP caching is not set up to work in FRED, so bypass all the caching code in that case + if (!Fred_running) { - auto current_argument = Sexp_replacement_arguments.back(); - int arg_node = current_argument.second; + if (Sexp_nodes[node].cache && Sexp_nodes[node].cache->sexp_node_data_type == OPF_NUMBER) + return true; - if (arg_node >= 0) - return sexp_can_construe_as_integer(arg_node); + // maybe forward to a special-arg node + if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE) + { + auto current_argument = Sexp_replacement_arguments.back(); + int arg_node = current_argument.second; + + if (arg_node >= 0) + return sexp_can_construe_as_integer(arg_node); + } } return can_construe_as_integer(CTEXT(node)); From 58f90c22a8da8fc74569fb86393e7d1b63a4d6cf Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Thu, 12 Dec 2024 11:35:52 -0500 Subject: [PATCH 14/64] warn and adapt when adding and removing NaN priorities --- code/ai/aigoals.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/code/ai/aigoals.cpp b/code/ai/aigoals.cpp index 1240ac492c0..e419ce9242f 100644 --- a/code/ai/aigoals.cpp +++ b/code/ai/aigoals.cpp @@ -1151,7 +1151,11 @@ void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, cons } } - if ( aigp->priority > MAX_GOAL_PRIORITY || priority_is_nan || priority_is_nan_forever ) { + if ( priority_is_nan || priority_is_nan_forever ) { + Warning(LOCATION, "add-goal tried to add %s with a NaN priority; aborting...", Sexp_nodes[CAR(sexp)].text); + ai_goal_reset(aigp); + return; + } else if ( aigp->priority > MAX_GOAL_PRIORITY ) { nprintf (("AI", "bashing add-goal sexpression priority of goal %s from %d to %d.\n", Sexp_nodes[CAR(sexp)].text, aigp->priority, MAX_GOAL_PRIORITY)); aigp->priority = MAX_GOAL_PRIORITY; } @@ -1255,7 +1259,12 @@ int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp, bool &remove_more ) int _priority = (n >= 0) ? eval_num(n, _priority_is_nan, _priority_is_nan_forever) : priority_if_no_n; n = CDR(sexp); // we want the first node after the goal sub-tree - if (_priority > MAX_GOAL_PRIORITY || _priority_is_nan || _priority_is_nan_forever) + if (_priority_is_nan || _priority_is_nan_forever) + { + Warning(LOCATION, "remove-goal tried to remove %s with a NaN priority; the priority will not be used for goal comparison", Sexp_nodes[CAR(sexp)].text); + _priority = -1; + } + else if (_priority > MAX_GOAL_PRIORITY) { nprintf(("AI", "bashing remove-goal sexpression priority of goal %s from %d to %d.\n", Sexp_nodes[CAR(sexp)].text, _priority, MAX_GOAL_PRIORITY)); _priority = MAX_GOAL_PRIORITY; From 4d278097e3a9a8348e93b0c5042094d27375bec6 Mon Sep 17 00:00:00 2001 From: Asteroth Date: Thu, 12 Dec 2024 18:18:03 -0500 Subject: [PATCH 15/64] change reset to a constructor --- code/ship/ship.h | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/code/ship/ship.h b/code/ship/ship.h index 009b673d3b9..22614b54183 100644 --- a/code/ship/ship.h +++ b/code/ship/ship.h @@ -1076,19 +1076,14 @@ class rcs_thruster_info { vec3d pos, norm; - void reset() { - length = 0; + rcs_thruster_info() : length(0), radius (0.0f), tex_fps(0), tex_nframes(0), tex_id(-1) { norm.xyz.x = norm.xyz.y = norm.xyz.z = 0.0f; // I wanted to do norm = ZERO_VECTOR here, but apparently that breaks the MSVC 2015 compiler.... pos.xyz.x = pos.xyz.y = pos.xyz.z = 0.0f; - radius = 0.0f; - tex_fps = 0; - tex_nframes = 0; use_flags.reset(); start_snd = gamesnd_id(); loop_snd = gamesnd_id(); stop_snd = gamesnd_id(); - tex_id = -1; } }; From 326d8e5bdcccbe7e4380f9a7ed5181f8cf8b84c8 Mon Sep 17 00:00:00 2001 From: Asteroth Date: Thu, 12 Dec 2024 18:26:48 -0500 Subject: [PATCH 16/64] reorder init --- code/ship/ship.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/ship/ship.h b/code/ship/ship.h index 22614b54183..94c2a260f38 100644 --- a/code/ship/ship.h +++ b/code/ship/ship.h @@ -1076,7 +1076,7 @@ class rcs_thruster_info { vec3d pos, norm; - rcs_thruster_info() : length(0), radius (0.0f), tex_fps(0), tex_nframes(0), tex_id(-1) { + rcs_thruster_info() : tex_id(-1), tex_nframes(0), tex_fps(0), length(0), radius (0.0f) { norm.xyz.x = norm.xyz.y = norm.xyz.z = 0.0f; // I wanted to do norm = ZERO_VECTOR here, but apparently that breaks the MSVC 2015 compiler.... pos.xyz.x = pos.xyz.y = pos.xyz.z = 0.0f; use_flags.reset(); From 104847c4bdf2b67f600eac23541936616ccc2b8f Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Fri, 13 Dec 2024 09:19:40 +0000 Subject: [PATCH 17/64] Apply #6457 and #6458 to Ship sub-editors --- .../mission/dialogs/AbstractDialogModel.cpp | 14 ++++- .../src/mission/dialogs/AbstractDialogModel.h | 23 ++++++++ .../ShipEditor/PlayerOrdersDialogModel.cpp | 11 ---- .../ShipEditor/PlayerOrdersDialogModel.h | 18 ------ .../ShipEditor/ShipCustomWarpDialogModel.cpp | 11 ---- .../ShipEditor/ShipCustomWarpDialogModel.h | 22 -------- .../ShipEditor/ShipFlagsDialogModel.cpp | 11 ---- .../dialogs/ShipEditor/ShipFlagsDialogModel.h | 17 ------ .../ShipEditor/ShipGoalsDialogModel.cpp | 10 ---- .../dialogs/ShipEditor/ShipGoalsDialogModel.h | 16 +----- .../ShipInitialStatusDialogModel.cpp | 11 ---- .../ShipEditor/ShipInitialStatusDialogModel.h | 14 ----- .../ShipEditor/ShipPathsDialogModel.cpp | 4 -- .../dialogs/ShipEditor/ShipPathsDialogModel.h | 2 - .../ShipSpecialStatsDialogModel.cpp | 9 --- .../ShipEditor/ShipSpecialStatsDialogModel.h | 14 ----- .../ShipTextureReplacementDialogModel.cpp | 11 ---- .../ShipTextureReplacementDialogModel.h | 15 ----- qtfred/src/mission/util.cpp | 29 ++++++++++ qtfred/src/mission/util.h | 8 ++- .../src/ui/dialogs/AsteroidEditorDialog.cpp | 2 + .../dialogs/ShipEditor/PlayerOrdersDialog.cpp | 41 ++++---------- .../dialogs/ShipEditor/PlayerOrdersDialog.h | 7 +-- .../ShipEditor/ShipCustomWarpDialog.cpp | 49 +++------------- .../dialogs/ShipEditor/ShipEditorDialog.cpp | 56 +++++++++---------- .../ui/dialogs/ShipEditor/ShipEditorDialog.h | 16 ------ .../ui/dialogs/ShipEditor/ShipFlagsDialog.cpp | 34 +++-------- .../ui/dialogs/ShipEditor/ShipFlagsDialog.h | 3 +- .../ui/dialogs/ShipEditor/ShipGoalsDialog.cpp | 52 ++++------------- .../ui/dialogs/ShipEditor/ShipGoalsDialog.h | 10 +--- .../ShipEditor/ShipInitialStatusDialog.cpp | 42 ++++---------- .../ShipEditor/ShipInitialStatusDialog.h | 7 +-- .../ui/dialogs/ShipEditor/ShipPathsDialog.cpp | 50 ++--------------- .../ShipEditor/ShipSpecialStatsDialog.cpp | 34 +++-------- .../ShipEditor/ShipSpecialStatsDialog.h | 3 +- .../ui/dialogs/ShipEditor/ShipTBLViewer.cpp | 13 +---- .../src/ui/dialogs/ShipEditor/ShipTBLViewer.h | 7 +-- .../ShipTextureReplacementDialog.cpp | 41 +++++--------- .../ShipEditor/ShipTextureReplacementDialog.h | 7 +-- qtfred/ui/PlayerOrdersDialog.ui | 23 -------- qtfred/ui/ShipFlagsDialog.ui | 18 +----- qtfred/ui/ShipGoalsDialog.ui | 20 +------ qtfred/ui/ShipInitialStatus.ui | 16 ------ qtfred/ui/ShipSpecialStatsDialog.ui | 20 +------ qtfred/ui/ShipTextureReplacementDialog.ui | 16 ------ 45 files changed, 199 insertions(+), 658 deletions(-) diff --git a/qtfred/src/mission/dialogs/AbstractDialogModel.cpp b/qtfred/src/mission/dialogs/AbstractDialogModel.cpp index 94fec79e209..40a9bf4354f 100644 --- a/qtfred/src/mission/dialogs/AbstractDialogModel.cpp +++ b/qtfred/src/mission/dialogs/AbstractDialogModel.cpp @@ -6,13 +6,23 @@ namespace fso { namespace fred { namespace dialogs { - - AbstractDialogModel::AbstractDialogModel(QObject* parent, EditorViewport* viewport) : QObject(parent), _editor(viewport->editor), _viewport(viewport) { } +bool AbstractDialogModel::query_modified() const +{ + return _modified; +} + +void AbstractDialogModel::set_modified() +{ + if (!_modified) { + _modified = true; + } +} + } } } diff --git a/qtfred/src/mission/dialogs/AbstractDialogModel.h b/qtfred/src/mission/dialogs/AbstractDialogModel.h index b3d2e575d87..f39014391e0 100644 --- a/qtfred/src/mission/dialogs/AbstractDialogModel.h +++ b/qtfred/src/mission/dialogs/AbstractDialogModel.h @@ -25,6 +25,15 @@ class AbstractDialogModel: public QObject { Editor* _editor = nullptr; EditorViewport* _viewport = nullptr; + template + /** + * @brief Copies rvalue to the lvalue while setting the modified variable. + */ + void modify(T& a, const T& b); + + bool _modified = false; + void set_modified(); + public: AbstractDialogModel(QObject* parent, EditorViewport* viewport); @@ -42,6 +51,10 @@ class AbstractDialogModel: public QObject { */ virtual void reject() = 0; + + bool query_modified() const; + + signals: /** * @brief Signal emitted when the model has changed caused by an update operation @@ -51,6 +64,16 @@ class AbstractDialogModel: public QObject { void modelChanged(); }; +template +inline void AbstractDialogModel::modify(T& a, const T& b) +{ + if (a != b) { + a = b; + set_modified(); + modelChanged(); + } +} + } } } diff --git a/qtfred/src/mission/dialogs/ShipEditor/PlayerOrdersDialogModel.cpp b/qtfred/src/mission/dialogs/ShipEditor/PlayerOrdersDialogModel.cpp index 644fe6b9d64..c0161d79a0b 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/PlayerOrdersDialogModel.cpp +++ b/qtfred/src/mission/dialogs/ShipEditor/PlayerOrdersDialogModel.cpp @@ -53,11 +53,6 @@ namespace fso { { } - bool PlayerOrdersDialogModel::query_modified() const - { - return _modified; - } - SCP_vector PlayerOrdersDialogModel::getAcceptedOrders() const { return acceptedOrders; @@ -156,12 +151,6 @@ namespace fso { } modelChanged(); } - void PlayerOrdersDialogModel::set_modified() - { - if (!_modified) { - _modified = true; - } - } } } } \ No newline at end of file diff --git a/qtfred/src/mission/dialogs/ShipEditor/PlayerOrdersDialogModel.h b/qtfred/src/mission/dialogs/ShipEditor/PlayerOrdersDialogModel.h index fe4833a215f..dfed5632c2b 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/PlayerOrdersDialogModel.h +++ b/qtfred/src/mission/dialogs/ShipEditor/PlayerOrdersDialogModel.h @@ -9,13 +9,6 @@ namespace fso { class PlayerOrdersDialogModel : public AbstractDialogModel { private: - template - void modify(T& a, const T& b); - - bool _modified = false; - - void set_modified(); - bool m_multi; int m_num_checks_active; @@ -31,22 +24,11 @@ namespace fso { bool apply() override; void reject() override; - bool query_modified() const; - SCP_vector getAcceptedOrders() const; SCP_vector getOrderNames() const; SCP_vector getCurrentOrders() const; void setCurrentOrder(const int, const size_t); }; - template - inline void PlayerOrdersDialogModel::modify(T& a, const T& b) - { - if (a != b) { - a = b; - set_modified(); - modelChanged(); - } - } } } } \ No newline at end of file diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp index 25567dbb63d..7766a6c3cdc 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.cpp @@ -207,17 +207,6 @@ void ShipCustomWarpDialogModel::initializeData() } } -void ShipCustomWarpDialogModel::set_modified() -{ - if (!_modified) { - _modified = true; - } -} - -bool ShipCustomWarpDialogModel::query_modified() const -{ - return _modified; -} void ShipCustomWarpDialogModel::setType(const int index) { modify(_m_warp_type, index); diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h index 1fa27fa4afd..1b94dc3a295 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipCustomWarpDialogModel.h @@ -10,12 +10,6 @@ class ShipCustomWarpDialogModel : public AbstractDialogModel { * @brief Initialises data for the model */ void initializeData(); - template - /** - * @brief Copies rvalue to the lvalue while setting the modified variable. - */ - void modify(T& a, const T& b); - bool _modified = false; bool _m_departure; int _m_warp_type; @@ -34,7 +28,6 @@ class ShipCustomWarpDialogModel : public AbstractDialogModel { /** * @brief Marks the model as modifed */ - void set_modified(); public: /** @@ -113,11 +106,6 @@ class ShipCustomWarpDialogModel : public AbstractDialogModel { * @return If the model is working on a player. */ bool isPlayer() const; - /** - * @brief Getter - * @return If the model has been modified. - */ - bool query_modified() const; // Setters void setType(const int index); @@ -133,14 +121,4 @@ class ShipCustomWarpDialogModel : public AbstractDialogModel { void setPlayerSpeed(const double); }; -template -inline void ShipCustomWarpDialogModel::modify(T& a, const T& b) -{ - if (a != b) { - a = b; - set_modified(); - modelChanged(); - } -} - } // namespace dialogs \ No newline at end of file diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipFlagsDialogModel.cpp b/qtfred/src/mission/dialogs/ShipEditor/ShipFlagsDialogModel.cpp index 4a5ae85b032..c705f223791 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipFlagsDialogModel.cpp +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipFlagsDialogModel.cpp @@ -12,12 +12,6 @@ namespace fso { namespace fred { namespace dialogs { -void ShipFlagsDialogModel::set_modified() -{ - if (!_modified) { - _modified = true; - } -} int ShipFlagsDialogModel::tristate_set(int val, int cur_state) { if (val) { @@ -1127,11 +1121,6 @@ int ShipFlagsDialogModel::getNoSelfDestruct() const return m_no_disabled_self_destruct; } -bool ShipFlagsDialogModel::query_modified() -{ - return _modified; -} - void ShipFlagsDialogModel::initializeData() { object* objp; diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipFlagsDialogModel.h b/qtfred/src/mission/dialogs/ShipEditor/ShipFlagsDialogModel.h index 20c26a7f7b6..504d5d3beb4 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipFlagsDialogModel.h +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipFlagsDialogModel.h @@ -9,11 +9,6 @@ namespace dialogs { class ShipFlagsDialogModel : public AbstractDialogModel { private: - template - void modify(T& a, const T& b); - - bool _modified = false; - int m_red_alert_carry; int m_scannable; int m_reinforcement; @@ -61,7 +56,6 @@ class ShipFlagsDialogModel : public AbstractDialogModel { int m_escort_value; int m_respawn_priority; - void set_modified(); static int tristate_set(const int val, const int cur_state); void update_ship(const int); @@ -205,18 +199,7 @@ class ShipFlagsDialogModel : public AbstractDialogModel { void setNoSelfDestruct(const int); int getNoSelfDestruct() const; - bool query_modified(); }; - -template -inline void ShipFlagsDialogModel::modify(T& a, const T& b) -{ - if (a != b) { - a = b; - set_modified(); - modelChanged(); - } -} } // namespace dialogs } // namespace fred } // namespace fso \ No newline at end of file diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipGoalsDialogModel.cpp b/qtfred/src/mission/dialogs/ShipEditor/ShipGoalsDialogModel.cpp index d40c9749b4c..ee507a95ae1 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipGoalsDialogModel.cpp +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipGoalsDialogModel.cpp @@ -618,16 +618,6 @@ namespace fso { m_object[i] = data[i]; } } - void ShipGoalsDialogModel::set_modified() - { - if (!_modified) { - _modified = true; - } - } - bool ShipGoalsDialogModel::query_modified() - { - return _modified; - } void ShipGoalsDialogModel::setShip(const int ship) { self_ship = ship; diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipGoalsDialogModel.h b/qtfred/src/mission/dialogs/ShipEditor/ShipGoalsDialogModel.h index 3e6f822c0f9..8696216550d 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipGoalsDialogModel.h +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipGoalsDialogModel.h @@ -25,12 +25,7 @@ class ShipGoalsDialogModel : public AbstractDialogModel { void initialize(ai_goal* goals, int ship); void initialize_multi(); - template - void modify(T& a, const T& b); - bool _modified = false; - - void set_modified(); int self_ship, self_wing; int m_behavior[ED_MAX_GOALS]; @@ -53,7 +48,6 @@ class ShipGoalsDialogModel : public AbstractDialogModel { void initializeData(bool multi, int self_ship, int self_wing); bool apply() override; void reject() override; - bool query_modified(); void setShip(const int); int getShip() const; @@ -85,15 +79,7 @@ class ShipGoalsDialogModel : public AbstractDialogModel { void setPriority(const int, const int); int getPriority(const int) const; }; -template -inline void ShipGoalsDialogModel::modify(T& a, const T& b) -{ - if (a != b) { - a = b; - set_modified(); - modelChanged(); - } -} + } // namespace dialogs } // namespace fred } // namespace fso \ No newline at end of file diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipInitialStatusDialogModel.cpp b/qtfred/src/mission/dialogs/ShipEditor/ShipInitialStatusDialogModel.cpp index c517e79ff6f..651bd8dcc0d 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipInitialStatusDialogModel.cpp +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipInitialStatusDialogModel.cpp @@ -633,17 +633,6 @@ bool ShipInitialStatusDialogModel::apply() void ShipInitialStatusDialogModel::reject() {} -void ShipInitialStatusDialogModel::set_modified() -{ - if (!_modified) { - _modified = true; - } -} - - bool ShipInitialStatusDialogModel::query_modified() -{ - return _modified; -} void ShipInitialStatusDialogModel::setVelocity(const int value) { diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipInitialStatusDialogModel.h b/qtfred/src/mission/dialogs/ShipEditor/ShipInitialStatusDialogModel.h index 786cb50b326..a96449a0b24 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipInitialStatusDialogModel.h +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipInitialStatusDialogModel.h @@ -19,10 +19,7 @@ bool set_cue_to_false(int* cue); class ShipInitialStatusDialogModel : public AbstractDialogModel { private: - template - void modify(T& a, const T& b); - bool _modified = false; int m_ship; int cur_subsys = -1; @@ -48,7 +45,6 @@ class ShipInitialStatusDialogModel : public AbstractDialogModel { dockpoint_information* dockpoint_array; - void set_modified(); void update_docking_info(); void undock(object*, object*); @@ -72,7 +68,6 @@ class ShipInitialStatusDialogModel : public AbstractDialogModel { bool apply() override; void reject() override; - bool query_modified(); void setVelocity(const int); int getVelocity() const; @@ -146,15 +141,6 @@ static void handle_inconsistent_flag(flagset& flags, T flag, int value) flags.remove(flag); } } -template -inline void ShipInitialStatusDialogModel::modify(T& a, const T& b) -{ - if (a != b) { - a = b; - set_modified(); - modelChanged(); - } -} } // namespace dialogs } // namespace fred } // namespace fso \ No newline at end of file diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipPathsDialogModel.cpp b/qtfred/src/mission/dialogs/ShipEditor/ShipPathsDialogModel.cpp index 8b1d1a59a4e..a76cdb37602 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipPathsDialogModel.cpp +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipPathsDialogModel.cpp @@ -76,10 +76,6 @@ bool ShipPathsDialogModel::modify(const int index, const bool value) return false; } } -bool ShipPathsDialogModel::query_modified() const -{ - return _modified; -} SCP_vector ShipPathsDialogModel::getPathList() const { return m_path_list; diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipPathsDialogModel.h b/qtfred/src/mission/dialogs/ShipEditor/ShipPathsDialogModel.h index 9e1e5a149ea..55b71cb48d9 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipPathsDialogModel.h +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipPathsDialogModel.h @@ -13,7 +13,6 @@ class ShipPathsDialogModel : public AbstractDialogModel { SCP_vector m_path_list; int m_path_mask; int m_ship; - bool _modified = false; public: ShipPathsDialogModel(QObject* parent, EditorViewport* viewport, @@ -22,7 +21,6 @@ class ShipPathsDialogModel : public AbstractDialogModel { bool apply() override; void reject() override; bool modify(const int, const bool); - bool query_modified() const; SCP_vector getPathList() const; polymodel* getModel() const; }; diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipSpecialStatsDialogModel.cpp b/qtfred/src/mission/dialogs/ShipEditor/ShipSpecialStatsDialogModel.cpp index 48835bc7ebe..f3f82ad0292 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipSpecialStatsDialogModel.cpp +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipSpecialStatsDialogModel.cpp @@ -207,15 +207,6 @@ namespace fso { void ShipSpecialStatsDialogModel::reject() { } - void ShipSpecialStatsDialogModel::set_modified() - { - if (!_modified) { - _modified = true; - } - } - bool ShipSpecialStatsDialogModel::query_modified() { - return _modified; - } bool ShipSpecialStatsDialogModel::getSpecialExp() const { return m_special_exp; diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipSpecialStatsDialogModel.h b/qtfred/src/mission/dialogs/ShipEditor/ShipSpecialStatsDialogModel.h index e39112ab8f4..0656c4f0783 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipSpecialStatsDialogModel.h +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipSpecialStatsDialogModel.h @@ -7,9 +7,6 @@ namespace fso { namespace dialogs { class ShipSpecialStatsDialogModel : public AbstractDialogModel { private: - template - void modify(T& a, const T& b); - bool _modified = false; int m_ship; int num_selected_ships; @@ -31,14 +28,12 @@ namespace fso { int m_shields; int m_hull; - void set_modified(); public: ShipSpecialStatsDialogModel(QObject* parent, EditorViewport* viewport); void initializeData(); bool apply() override; void reject() override; - bool query_modified(); //Exp Get/Setters bool getSpecialExp() const; @@ -70,15 +65,6 @@ namespace fso { int getHull() const; void setHull(const int); }; - template - inline void ShipSpecialStatsDialogModel::modify(T& a, const T& b) - { - if (a != b) { - a = b; - set_modified(); - modelChanged(); - } - } } } } \ No newline at end of file diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipTextureReplacementDialogModel.cpp b/qtfred/src/mission/dialogs/ShipEditor/ShipTextureReplacementDialogModel.cpp index 1ef2cab98e0..c935d602d6e 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipTextureReplacementDialogModel.cpp +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipTextureReplacementDialogModel.cpp @@ -627,17 +627,6 @@ namespace fso { Assert(index < currentTextures.size()); modify(inheritMap[index][type], state); } - void ShipTextureReplacementDialogModel::set_modified() - { - if (!_modified) { - _modified = true; - } - } - - bool ShipTextureReplacementDialogModel::query_modified() const - { - return _modified;; - } } } } \ No newline at end of file diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipTextureReplacementDialogModel.h b/qtfred/src/mission/dialogs/ShipEditor/ShipTextureReplacementDialogModel.h index f15f397370e..d8a4ca4cf45 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipTextureReplacementDialogModel.h +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipTextureReplacementDialogModel.h @@ -10,11 +10,6 @@ namespace fso { private: void initSubTypes(polymodel* model, int); - template - void modify(T& a, const T& b); - bool _modified = false; - void set_modified(); - bool m_multi; //Used to dermeine what type of texutre a map has. SCP_vector> subTypesAvailable; @@ -50,18 +45,8 @@ namespace fso { void setReplace(const size_t index, const SCP_string& type, const bool state); void setInherit(const size_t index, const SCP_string& type, const bool state); - bool query_modified() const; }; - template - inline void ShipTextureReplacementDialogModel::modify(T& a, const T& b) - { - if (a != b) { - a = b; - set_modified(); - modelChanged(); - } - } } } } \ No newline at end of file diff --git a/qtfred/src/mission/util.cpp b/qtfred/src/mission/util.cpp index 3dfb5101cc0..87ef8542904 100644 --- a/qtfred/src/mission/util.cpp +++ b/qtfred/src/mission/util.cpp @@ -92,3 +92,32 @@ void time_to_mission_info_string(const std::tm* src, char* dest, size_t dest_max { std::strftime(dest, dest_max_len, "%x at %X", src); } + +bool rejectOrCloseHandler(QDialog* dialog, + fso::fred::dialogs::AbstractDialogModel* model, + fso::fred::EditorViewport* viewport) +{ + if (model->query_modified()) { + auto button = viewport->dialogProvider->showButtonDialog(fso::fred::DialogType::Question, + "Changes detected", + "Do you want to keep your changes?", + {fso::fred::DialogButton::Yes, fso::fred::DialogButton::No, fso::fred::DialogButton::Cancel}); + + if (button == fso::fred::DialogButton::Cancel) { + return false; + } + + if (button == fso::fred::DialogButton::Yes) { + model->apply(); + return true; + } + if (button == fso::fred::DialogButton::No) { + model->reject(); + return true; + } + return false; + } else { + model->reject(); + return true; + } +} \ No newline at end of file diff --git a/qtfred/src/mission/util.h b/qtfred/src/mission/util.h index c4ddd5ae818..3eb8400daaf 100644 --- a/qtfred/src/mission/util.h +++ b/qtfred/src/mission/util.h @@ -1,5 +1,7 @@ #pragma once - +#include +#include "dialogs/AbstractDialogModel.h" +#include "EditorViewport.h" void stuff_special_arrival_anchor_name(char *buf, int iff_index, int restrict_to_players, int retail_format); @@ -10,3 +12,7 @@ void generate_weaponry_usage_list_team(int team, int *arr); void generate_weaponry_usage_list_wing(int wing_num, int *arr); void time_to_mission_info_string(const std::tm* src, char* dest, size_t dest_max_len); + +bool rejectOrCloseHandler(QDialog* dialog, + fso::fred::dialogs::AbstractDialogModel* model, + fso::fred::EditorViewport* viewport); \ No newline at end of file diff --git a/qtfred/src/ui/dialogs/AsteroidEditorDialog.cpp b/qtfred/src/ui/dialogs/AsteroidEditorDialog.cpp index 10a3b4eef7c..39760412e82 100644 --- a/qtfred/src/ui/dialogs/AsteroidEditorDialog.cpp +++ b/qtfred/src/ui/dialogs/AsteroidEditorDialog.cpp @@ -331,6 +331,8 @@ void AsteroidEditorDialog::updateUI() ui->lineEdit_ibox_maxZ->setText(_model->AsteroidEditorDialogModel::getBoxText(AsteroidEditorDialogModel::_I_MAX_Z)); } + + void AsteroidEditorDialog::done(int r) { if(QDialog::Accepted == r) // ok was pressed diff --git a/qtfred/src/ui/dialogs/ShipEditor/PlayerOrdersDialog.cpp b/qtfred/src/ui/dialogs/ShipEditor/PlayerOrdersDialog.cpp index 0c128a41d66..fa8fed317de 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/PlayerOrdersDialog.cpp +++ b/qtfred/src/ui/dialogs/ShipEditor/PlayerOrdersDialog.cpp @@ -2,24 +2,20 @@ #include "ui_PlayerOrdersDialog.h" #include - +#include "mission/util.h" #include namespace fso { namespace fred { namespace dialogs { - PlayerOrdersDialog::PlayerOrdersDialog(QDialog* parent, EditorViewport* viewport) - : QDialog(parent), ui(new Ui::PlayerOrdersDialog()), + PlayerOrdersDialog::PlayerOrdersDialog(QDialog* parent, EditorViewport* viewport, bool editMultiple) + : QDialog(parent), ui(new Ui::PlayerOrdersDialog()), + _model(new PlayerOrdersDialogModel(this, viewport, editMultiple)), _viewport(viewport) { - parentDialog = dynamic_cast(parent); - Assert(parentDialog); - _model = std::unique_ptr(new PlayerOrdersDialogModel(this, - viewport, - parentDialog->getIfMultipleShips())); ui->setupUi(this); connect(this, &QDialog::accepted, _model.get(), &PlayerOrdersDialogModel::apply); - connect(this, &QDialog::rejected, _model.get(), &PlayerOrdersDialogModel::reject); + connect(ui->cancelButton, &QPushButton::clicked, this, &PlayerOrdersDialog::rejectHandler); for (size_t i = 0; i < _model->getAcceptedOrders().size(); i++) { //i == 0 check added to avoid culling first entry where getAcceptedOrders returns 0 if (_model->getAcceptedOrders()[i] || i == 0) { @@ -43,30 +39,15 @@ namespace fso { resize(QDialog::sizeHint()); } PlayerOrdersDialog::~PlayerOrdersDialog() = default; - void PlayerOrdersDialog::closeEvent(QCloseEvent* event) + void PlayerOrdersDialog::closeEvent(QCloseEvent* e) { - if (_model->query_modified()) { - auto button = _viewport->dialogProvider->showButtonDialog(DialogType::Question, "Changes detected", "Do you want to keep your changes?", - { DialogButton::Yes, DialogButton::No, DialogButton::Cancel }); - - if (button == DialogButton::Cancel) { - event->ignore(); - return; - } - - if (button == DialogButton::Yes) { - accept(); - return; - } - } - - QDialog::closeEvent(event); + if (!rejectOrCloseHandler(this, _model.get(), _viewport)) { + e->ignore(); + }; } - void PlayerOrdersDialog::showEvent(QShowEvent* e) + void PlayerOrdersDialog::rejectHandler() { - _model->initialiseData(parentDialog->getIfMultipleShips()); - - QDialog::showEvent(e); + this->close(); } void PlayerOrdersDialog::updateUI() { diff --git a/qtfred/src/ui/dialogs/ShipEditor/PlayerOrdersDialog.h b/qtfred/src/ui/dialogs/ShipEditor/PlayerOrdersDialog.h index 345de00fd34..d3f7d0403bc 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/PlayerOrdersDialog.h +++ b/qtfred/src/ui/dialogs/ShipEditor/PlayerOrdersDialog.h @@ -4,7 +4,6 @@ #include #include -#include namespace fso { namespace fred { @@ -13,24 +12,22 @@ namespace fso { namespace Ui { class PlayerOrdersDialog; } - class ShipEditorDialog; class PlayerOrdersDialog : public QDialog { Q_OBJECT public: - explicit PlayerOrdersDialog(QDialog* parent, EditorViewport* viewport); + explicit PlayerOrdersDialog(QDialog* parent, EditorViewport* viewport, bool editMultiple); ~PlayerOrdersDialog() override; protected: void closeEvent(QCloseEvent*) override; - void showEvent(QShowEvent*) override; + void rejectHandler(); private: std::unique_ptr ui; std::unique_ptr _model; EditorViewport* _viewport; - ShipEditorDialog* parentDialog; void updateUI(); diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp b/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp index 7f8c86ebc80..478f08612c1 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp @@ -2,6 +2,8 @@ #include "ui_ShipCustomWarpDialog.h" +#include "mission/util.h" + #include #include @@ -67,26 +69,9 @@ ShipCustomWarpDialog::ShipCustomWarpDialog(QDialog* parent, EditorViewport* view ShipCustomWarpDialog::~ShipCustomWarpDialog() = default; void ShipCustomWarpDialog::closeEvent(QCloseEvent* e) { - if (_model->query_modified()) { - auto button = _viewport->dialogProvider->showButtonDialog(DialogType::Question, - "Changes detected", - "Do you want to keep your changes?", - {DialogButton::Yes, DialogButton::No, DialogButton::Cancel}); - - if (button == DialogButton::Cancel) { - e->ignore(); - return; - } - - if (button == DialogButton::Yes) { - accept(); - return; - } - if (button == DialogButton::No) { - _model->reject(); - } - } - QDialog::closeEvent(e); + if (!rejectOrCloseHandler(this, _model.get(), _viewport)) { + e->ignore(); + }; } void ShipCustomWarpDialog::updateUI(const bool firstrun) { @@ -153,29 +138,9 @@ void ShipCustomWarpDialog::updateUI(const bool firstrun) } void ShipCustomWarpDialog::rejectHandler() { - if (_model->query_modified()) { - auto button = _viewport->dialogProvider->showButtonDialog(DialogType::Question, - "Changes detected", - "Do you want to keep your changes?", - {DialogButton::Yes, DialogButton::No, DialogButton::Cancel}); - - if (button == DialogButton::Cancel) { - return; - } - - if (button == DialogButton::Yes) { - accept(); - return; - } - if (button == DialogButton::No) { - _model->reject(); - QDialog::reject(); - } - } else { - _model->reject(); - QDialog::reject(); - } + this->close(); } + void ShipCustomWarpDialog::startSoundChanged() { // String wrangling reqired in order to avoid crashes when directly converting from Qstring to std::string on some diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp b/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp index f06c39ea12c..15fff8905f8 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp @@ -174,35 +174,30 @@ void ShipEditorDialog::showEvent(QShowEvent* e) { void ShipEditorDialog::on_miscButton_clicked() { - if (!flagsDialog) { - flagsDialog = std::unique_ptr(new dialogs::ShipFlagsDialog(this, _viewport)); - } - flagsDialog->show(); + auto dialog = new dialogs::ShipFlagsDialog(this, _viewport); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); } void ShipEditorDialog::on_initialStatusButton_clicked() { - if (!initialStatusDialog) { - initialStatusDialog = - std::unique_ptr(new dialogs::ShipInitialStatusDialog(this, _viewport)); - } - initialStatusDialog->show(); + auto dialog = new dialogs::ShipInitialStatusDialog(this, _viewport, getIfMultipleShips()); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); } void ShipEditorDialog::on_initialOrdersButton_clicked() { - if (!GoalsDialog) { - GoalsDialog = std::unique_ptr(new dialogs::ShipGoalsDialog(this, _viewport)); - } - GoalsDialog->show(); + auto dialog = new dialogs::ShipGoalsDialog(this, _viewport, getIfMultipleShips(), Ships[getSingleShip()].objnum, -1); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); } void ShipEditorDialog::on_tblInfoButton_clicked() { - if (!TBLViewer) { - TBLViewer = std::unique_ptr(new dialogs::ShipTBLViewer(this, _viewport)); - } - TBLViewer->show(); + auto dialog = new dialogs::ShipTBLViewer(this, _viewport, getShipClass()); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); } void ShipEditorDialog::update() @@ -801,11 +796,9 @@ void ShipEditorDialog::DepartureCueChanged(const bool value) void ShipEditorDialog::on_textureReplacementButton_clicked() { - if (!TextureReplacementDialog) { - TextureReplacementDialog = - std::unique_ptr(new dialogs::ShipTextureReplacementDialog(this, _viewport)); - } - TextureReplacementDialog->show(); + auto dialog = new dialogs::ShipTextureReplacementDialog(this, _viewport, getIfMultipleShips()); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); } void ShipEditorDialog::on_playerShipButton_clicked() @@ -838,18 +831,15 @@ void ShipEditorDialog::on_weaponsButton_clicked() } void ShipEditorDialog::on_playerOrdersButton_clicked() { - if (!playerOrdersDialog) { - playerOrdersDialog = std::unique_ptr(new dialogs::PlayerOrdersDialog(this, _viewport)); - } - playerOrdersDialog->show(); + auto dialog = new dialogs::PlayerOrdersDialog(this, _viewport, getIfMultipleShips()); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); } void ShipEditorDialog::on_specialStatsButton_clicked() { - if (!specialStatsDialog) { - specialStatsDialog = - std::unique_ptr(new dialogs::ShipSpecialStatsDialog(this, _viewport)); - } - specialStatsDialog->show(); + auto dialog = new dialogs::ShipSpecialStatsDialog(this, _viewport); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); } void ShipEditorDialog::on_hideCuesButton_clicked() { @@ -873,6 +863,7 @@ void ShipEditorDialog::on_restrictArrivalPathsButton_clicked() { int target_class = Ships[_model->getArrivalTarget()].ship_info_index; auto dialog = new dialogs::ShipPathsDialog(this, _viewport, _model->getSingleShip(), target_class, false); + dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); @@ -880,17 +871,20 @@ void ShipEditorDialog::on_restrictArrivalPathsButton_clicked() void ShipEditorDialog::on_customWarpinButton_clicked() { auto dialog = new dialogs::ShipCustomWarpDialog(this, _viewport, false); + dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); } void ShipEditorDialog::on_restrictDeparturePathsButton_clicked() { int target_class = Ships[_model->getDepartureTarget()].ship_info_index; auto dialog = new dialogs::ShipPathsDialog(this, _viewport, _model->getSingleShip(), target_class, true); + dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); } void ShipEditorDialog::on_customWarpoutButton_clicked() { auto dialog = new dialogs::ShipCustomWarpDialog(this, _viewport, true); + dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); } } // namespace dialogs diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.h b/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.h index 03dc286a238..39869539d36 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.h +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.h @@ -15,7 +15,6 @@ #include "ShipCustomWarpDialog.h" #include -#include namespace fso { @@ -26,13 +25,6 @@ namespace Ui { class ShipEditorDialog; } -class ShipTBLViewer; -class ShipGoalsDialog; -class ShipInitialStatusDialog; -class ShipFlagsDialog; -class ShipTextureReplacementDialog; -class PlayerOrdersDialog; - /** * @brief QTFred's Ship Editor */ @@ -137,14 +129,6 @@ class ShipEditorDialog : public QDialog, public SexpTreeEditorInterface { void departureDelayChanged(const int); void departureWarpChanged(const bool); void DepartureCueChanged(const bool); - - std::unique_ptr GoalsDialog = nullptr; - std::unique_ptr initialStatusDialog = nullptr; - std::unique_ptr flagsDialog = nullptr; - std::unique_ptr TextureReplacementDialog = nullptr; - std::unique_ptr playerOrdersDialog = nullptr; - std::unique_ptr specialStatsDialog = nullptr; - std::unique_ptr TBLViewer = nullptr; }; } // namespace dialogs } // namespace fred diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipFlagsDialog.cpp b/qtfred/src/ui/dialogs/ShipEditor/ShipFlagsDialog.cpp index d4bac2bad24..7272df4816c 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipFlagsDialog.cpp +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipFlagsDialog.cpp @@ -3,7 +3,7 @@ #include "ui_ShipFlagsDialog.h" #include - +#include #include namespace fso { @@ -17,6 +17,7 @@ ShipFlagsDialog::ShipFlagsDialog(QWidget* parent, EditorViewport* viewport) ui->setupUi(this); connect(this, &QDialog::accepted, _model.get(), &ShipFlagsDialogModel::apply); + connect(ui->cancelButton, &QPushButton::clicked, this, &ShipFlagsDialog::rejectHandler); connect(this, &QDialog::rejected, _model.get(), &ShipFlagsDialogModel::reject); @@ -100,35 +101,16 @@ ShipFlagsDialog::ShipFlagsDialog(QWidget* parent, EditorViewport* viewport) ShipFlagsDialog::~ShipFlagsDialog() = default; -void ShipFlagsDialog::closeEvent(QCloseEvent* event) +void ShipFlagsDialog::closeEvent(QCloseEvent* e) { - if (_model->query_modified()) { - auto button = _viewport->dialogProvider->showButtonDialog(DialogType::Question, - "Changes detected", - "Do you want to keep your changes?", - {DialogButton::Yes, DialogButton::No, DialogButton::Cancel}); - - if (button == DialogButton::Cancel) { - event->ignore(); - return; - } - - if (button == DialogButton::Yes) { - accept(); - return; - } - } - - QDialog::closeEvent(event); + if (!rejectOrCloseHandler(this, _model.get(), _viewport)) { + e->ignore(); + }; } - -void ShipFlagsDialog::showEvent(QShowEvent* event) +void ShipFlagsDialog::rejectHandler() { - _model->initializeData(); - - QDialog::showEvent(event); + this->close(); } - void ShipFlagsDialog::updateUI() { util::SignalBlockers blockers(this); diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipFlagsDialog.h b/qtfred/src/ui/dialogs/ShipEditor/ShipFlagsDialog.h index 2c88ed6a802..8a828457195 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipFlagsDialog.h +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipFlagsDialog.h @@ -21,7 +21,8 @@ class ShipFlagsDialog : public QDialog { protected: void closeEvent(QCloseEvent*) override; - void showEvent(QShowEvent*) override; + + void rejectHandler(); private: std::unique_ptr ui; diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipGoalsDialog.cpp b/qtfred/src/ui/dialogs/ShipEditor/ShipGoalsDialog.cpp index 7534cd1ce40..9691479537c 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipGoalsDialog.cpp +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipGoalsDialog.cpp @@ -5,14 +5,15 @@ #include #include #include - +#include "mission/util.h" #include namespace fso { namespace fred { namespace dialogs { -ShipGoalsDialog::ShipGoalsDialog(QWidget* parent, EditorViewport* viewport) - : QDialog(parent), ui(new Ui::ShipGoalsDialog()), +ShipGoalsDialog::ShipGoalsDialog(QWidget* parent, EditorViewport* viewport, bool editMultiple, int shipID, int wingID) + : QDialog(parent), ui(new Ui::ShipGoalsDialog()), _model(new ShipGoalsDialogModel(this, + viewport, editMultiple, shipID, wingID)), _viewport(viewport) { ui->setupUi(this); @@ -71,18 +72,7 @@ ShipGoalsDialog::ShipGoalsDialog(QWidget* parent, EditorViewport* viewport) priority[8] = ui->prioritySpinBox9; priority[9] = ui->prioritySpinBox10; connect(this, &QDialog::accepted, _model.get(), &ShipGoalsDialogModel::apply); - connect(this, &QDialog::rejected, _model.get(), &ShipGoalsDialogModel::reject); - parentDialog = dynamic_cast(parent); - if (parentDialog == nullptr) { - //TODO: Add wing editor Here - WingMode = true; - } else { - _model = std::unique_ptr(new ShipGoalsDialogModel(this, - viewport, - parentDialog->getIfMultipleShips(), - Ships[parentDialog->getSingleShip()].objnum, - -1)); - } + connect(ui->cancelButton, &QPushButton::clicked, this, &ShipGoalsDialog::rejectHandler); connect(_model.get(), &AbstractDialogModel::modelChanged, this, &ShipGoalsDialog::updateUI); for (int i = 0; i < ED_MAX_GOALS; i++) { @@ -116,35 +106,15 @@ ShipGoalsDialog::ShipGoalsDialog(QWidget* parent, EditorViewport* viewport) ShipGoalsDialog::~ShipGoalsDialog() = default; -void ShipGoalsDialog::showEvent(QShowEvent* e) +void ShipGoalsDialog::closeEvent(QCloseEvent* e) { - if (!WingMode) { - _model->initializeData(parentDialog->getIfMultipleShips(), Ships[parentDialog->getSingleShip()].objnum, -1); - } - - QDialog::showEvent(e); + if (!rejectOrCloseHandler(this, _model.get(), _viewport)) { + e->ignore(); + }; } - -void ShipGoalsDialog::closeEvent(QCloseEvent* event) +void ShipGoalsDialog::rejectHandler() { - if (_model->query_modified()) { - auto button = _viewport->dialogProvider->showButtonDialog(DialogType::Question, - "Changes detected", - "Do you want to keep your changes?", - {DialogButton::Yes, DialogButton::No, DialogButton::Cancel}); - - if (button == DialogButton::Cancel) { - event->ignore(); - return; - } - - if (button == DialogButton::Yes) { - accept(); - return; - } - } - - QDialog::closeEvent(event); + this->close(); } void ShipGoalsDialog::updateUI() diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipGoalsDialog.h b/qtfred/src/ui/dialogs/ShipEditor/ShipGoalsDialog.h index a767e20b25f..02bbe8dab43 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipGoalsDialog.h +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipGoalsDialog.h @@ -7,25 +7,23 @@ #include #include -#include "ShipEditorDialog.h" - namespace fso { namespace fred { namespace dialogs { namespace Ui { class ShipGoalsDialog; } -class ShipEditorDialog; class ShipGoalsDialog : public QDialog { Q_OBJECT public: - explicit ShipGoalsDialog(QWidget* parent, EditorViewport* viewport); + explicit ShipGoalsDialog(QWidget* parent, EditorViewport* viewport, bool editMultiple, int shipID, int wingID); ~ShipGoalsDialog() override; protected: void closeEvent(QCloseEvent*) override; - void showEvent(QShowEvent* e) override; + + void rejectHandler(); private: std::unique_ptr ui; @@ -39,8 +37,6 @@ class ShipGoalsDialog : public QDialog { QSpinBox* priority[ED_MAX_GOALS]; void updateUI(); - bool WingMode; - ShipEditorDialog* parentDialog = nullptr; }; } // namespace dialogs } // namespace fred diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipInitialStatusDialog.cpp b/qtfred/src/ui/dialogs/ShipEditor/ShipInitialStatusDialog.cpp index b4e3ec887da..47595309037 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipInitialStatusDialog.cpp +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipInitialStatusDialog.cpp @@ -9,24 +9,21 @@ #include #include #include - +#include "mission/util.h" #include namespace fso { namespace fred { namespace dialogs { - ShipInitialStatusDialog::ShipInitialStatusDialog(QDialog* parent, EditorViewport* viewport) - : QDialog(parent), ui(new Ui::ShipInitialStatusDialog()), _viewport(viewport) + ShipInitialStatusDialog::ShipInitialStatusDialog(QDialog* parent, EditorViewport* viewport, bool editMultiple) + : QDialog(parent), ui(new Ui::ShipInitialStatusDialog()), + _model(new ShipInitialStatusDialogModel(this, viewport, editMultiple)), _viewport(viewport) { ui->setupUi(this); - parentDialog = dynamic_cast(parent); - Assert(parentDialog); - _model = std::unique_ptr(new ShipInitialStatusDialogModel(this, - viewport, parentDialog->getIfMultipleShips())); connect(this, &QDialog::accepted, _model.get(), &ShipInitialStatusDialogModel::apply); - connect(this, &QDialog::rejected, _model.get(), &ShipInitialStatusDialogModel::reject); + connect(ui->cancelPushButton, &QPushButton::clicked, this, &ShipInitialStatusDialog::rejectHandler); connect(_model.get(), &AbstractDialogModel::modelChanged, this, &ShipInitialStatusDialog::updateUI); // Velocity @@ -84,30 +81,15 @@ namespace fso { ShipInitialStatusDialog::~ShipInitialStatusDialog() = default; - void ShipInitialStatusDialog::closeEvent(QCloseEvent* event) + void ShipInitialStatusDialog::closeEvent(QCloseEvent* e) { - if (_model->query_modified()) { - auto button = _viewport->dialogProvider->showButtonDialog(DialogType::Question, - "Changes detected", - "Do you want to keep your changes?", - { DialogButton::Yes, DialogButton::No, DialogButton::Cancel }); - - if (button == DialogButton::Cancel) { - event->ignore(); - return; - } - - if (button == DialogButton::Yes) { - accept(); - return; - } - } - - QDialog::closeEvent(event); + if (!rejectOrCloseHandler(this, _model.get(), _viewport)) { + e->ignore(); + }; } - void ShipInitialStatusDialog::showEvent(QShowEvent* event) { - _model->initializeData(parentDialog->getIfMultipleShips()); - QDialog::showEvent(event); + void ShipInitialStatusDialog::rejectHandler() + { + this->close(); } void ShipInitialStatusDialog::updateUI() { diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipInitialStatusDialog.h b/qtfred/src/ui/dialogs/ShipEditor/ShipInitialStatusDialog.h index 67fa0df219a..b0b4420492c 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipInitialStatusDialog.h +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipInitialStatusDialog.h @@ -2,7 +2,6 @@ #define SHIPINITIALSTATUSDIALOG_H #include -#include "ShipEditorDialog.h" #include #include @@ -13,24 +12,22 @@ namespace dialogs { namespace Ui { class ShipInitialStatusDialog; } -class ShipEditorDialog; class ShipInitialStatusDialog : public QDialog { Q_OBJECT public: - explicit ShipInitialStatusDialog(QDialog* parent, EditorViewport* viewport); + explicit ShipInitialStatusDialog(QDialog* parent, EditorViewport* viewport, bool editMultiple); ~ShipInitialStatusDialog() override; protected: void closeEvent(QCloseEvent*) override; - void showEvent(QShowEvent*) override; + void rejectHandler(); private: std::unique_ptr ui; std::unique_ptr _model; EditorViewport* _viewport; - ShipEditorDialog* parentDialog; void updateUI(); void updateFlags(); diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.cpp b/qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.cpp index 948eac6aca8..62e151db8ed 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.cpp +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.cpp @@ -3,6 +3,7 @@ #include #include +#include namespace fso { namespace fred { namespace dialogs { @@ -29,53 +30,14 @@ ShipPathsDialog::ShipPathsDialog(QWidget* parent, resize(QDialog::sizeHint()); } ShipPathsDialog::~ShipPathsDialog() = default; -void ShipPathsDialog::closeEvent(QCloseEvent* event) { - if (_model->query_modified()) { - auto button = _viewport->dialogProvider->showButtonDialog(DialogType::Question, - "Changes detected", - "Do you want to keep your changes?", - {DialogButton::Yes, DialogButton::No, DialogButton::Cancel}); - - if (button == DialogButton::Cancel) { - event->ignore(); - return; - } - - if (button == DialogButton::Yes) { - accept(); - return; - } - if (button == DialogButton::No) { - _model->reject(); - } - } - - QDialog::closeEvent(event); +void ShipPathsDialog::closeEvent(QCloseEvent* e) { + if (!rejectOrCloseHandler(this, _model.get(), _viewport)) { + e->ignore(); + }; } void ShipPathsDialog::rejectHandler() { - if (_model->query_modified()) { - auto button = _viewport->dialogProvider->showButtonDialog(DialogType::Question, - "Changes detected", - "Do you want to keep your changes?", - {DialogButton::Yes, DialogButton::No, DialogButton::Cancel}); - - if (button == DialogButton::Cancel) { - return; - } - - if (button == DialogButton::Yes) { - accept(); - return; - } - if (button == DialogButton::No) { - _model->reject(); - QDialog::reject(); - } - } else { - _model->reject(); - QDialog::reject(); - } + this->close(); } void ShipPathsDialog::updateUI() { util::SignalBlockers blockers(this); diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipSpecialStatsDialog.cpp b/qtfred/src/ui/dialogs/ShipEditor/ShipSpecialStatsDialog.cpp index 5d1fe5186ea..b6d570805bd 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipSpecialStatsDialog.cpp +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipSpecialStatsDialog.cpp @@ -3,7 +3,7 @@ #include "ui_ShipSpecialStatsDialog.h" #include - +#include #include namespace fso { @@ -17,7 +17,7 @@ namespace fso { ui->setupUi(this); connect(this, &QDialog::accepted, _model.get(), &ShipSpecialStatsDialogModel::apply); - connect(this, &QDialog::rejected, _model.get(), &ShipSpecialStatsDialogModel::reject); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &ShipSpecialStatsDialog::rejectHandler); connect(_model.get(), &AbstractDialogModel::modelChanged, this, &ShipSpecialStatsDialog::updateUI); connect(ui->explodeCheckBox, &QCheckBox::toggled, _model.get(), &ShipSpecialStatsDialogModel::setSpecialExp); @@ -43,32 +43,16 @@ namespace fso { ShipSpecialStatsDialog::~ShipSpecialStatsDialog() = default; - void ShipSpecialStatsDialog::closeEvent(QCloseEvent* event) + void ShipSpecialStatsDialog::closeEvent(QCloseEvent* e) { - if (_model->query_modified()) { - auto button = _viewport->dialogProvider->showButtonDialog(DialogType::Question, - "Changes detected", - "Do you want to keep your changes?", - { DialogButton::Yes, DialogButton::No, DialogButton::Cancel }); - - if (button == DialogButton::Cancel) { - event->ignore(); - return; - } - - if (button == DialogButton::Yes) { - accept(); - return; - } - } - - QDialog::closeEvent(event); + if (!rejectOrCloseHandler(this, _model.get(), _viewport)) { + e->ignore(); + }; } - void ShipSpecialStatsDialog::showEvent(QShowEvent* event) { - _model->initializeData(); - - QDialog::showEvent(event); + void ShipSpecialStatsDialog::rejectHandler() + { + this->close(); } void ShipSpecialStatsDialog::updateUI() diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipSpecialStatsDialog.h b/qtfred/src/ui/dialogs/ShipEditor/ShipSpecialStatsDialog.h index 1762678235e..94b7ddc2810 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipSpecialStatsDialog.h +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipSpecialStatsDialog.h @@ -21,7 +21,8 @@ namespace fso { protected: void closeEvent(QCloseEvent*) override; - void showEvent(QShowEvent*) override; + + void rejectHandler(); private: std::unique_ptr ui; diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipTBLViewer.cpp b/qtfred/src/ui/dialogs/ShipEditor/ShipTBLViewer.cpp index 4e54dbadb33..47351dd9edb 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipTBLViewer.cpp +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipTBLViewer.cpp @@ -9,12 +9,10 @@ namespace fso { namespace fred { namespace dialogs { -ShipTBLViewer::ShipTBLViewer(QWidget* parent, EditorViewport* viewport) - : QDialog(parent), ui(new Ui::ShipTBLViewer()), _viewport(viewport) +ShipTBLViewer::ShipTBLViewer(QWidget* parent, EditorViewport* viewport, int shipClass) + : QDialog(parent), ui(new Ui::ShipTBLViewer()), _model(new ShipTBLViewerModel(this, viewport, shipClass)), + _viewport(viewport) { - parentDialog = dynamic_cast(parent); - Assert(parentDialog); - _model = std::unique_ptr(new ShipTBLViewerModel(this, viewport, parentDialog->getShipClass())); ui->setupUi(this); @@ -31,11 +29,6 @@ void ShipTBLViewer::closeEvent(QCloseEvent* event) { QDialog::closeEvent(event); } -void ShipTBLViewer::showEvent(QShowEvent* e) { - _model->initializeData(parentDialog->getShipClass()); - - QDialog::showEvent(e); -} void ShipTBLViewer::updateUI() { util::SignalBlockers blockers(this); diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipTBLViewer.h b/qtfred/src/ui/dialogs/ShipEditor/ShipTBLViewer.h index b49fbf611f9..db314988ce2 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipTBLViewer.h +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipTBLViewer.h @@ -3,7 +3,6 @@ #include #include -#include "ShipEditorDialog.h" namespace fso { namespace fred { @@ -12,25 +11,21 @@ namespace dialogs { namespace Ui { class ShipTBLViewer; } -class ShipEditorDialog; class ShipTBLViewer : public QDialog { Q_OBJECT public: - explicit ShipTBLViewer(QWidget* parent, EditorViewport* viewport); + explicit ShipTBLViewer(QWidget* parent, EditorViewport* viewport, int shipClass); ~ShipTBLViewer() override; protected: void closeEvent(QCloseEvent*) override; - void showEvent(QShowEvent* e) override; private: std::unique_ptr ui; std::unique_ptr _model; EditorViewport* _viewport; - ShipEditorDialog* parentDialog = nullptr; - int sc; void updateUI(); diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipTextureReplacementDialog.cpp b/qtfred/src/ui/dialogs/ShipEditor/ShipTextureReplacementDialog.cpp index d108d9b06c6..e2440afc66e 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipTextureReplacementDialog.cpp +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipTextureReplacementDialog.cpp @@ -4,6 +4,7 @@ #include #include +#include namespace fso { namespace fred { namespace dialogs { @@ -35,19 +36,18 @@ namespace fso { } - ShipTextureReplacementDialog::ShipTextureReplacementDialog(QDialog* parent, EditorViewport* viewport) + ShipTextureReplacementDialog::ShipTextureReplacementDialog(QDialog* parent, EditorViewport* viewport, bool multiEdit) : QDialog(parent), ui(new Ui::ShipTextureReplacementDialog()), - _viewport(viewport) + _model(new ShipTextureReplacementDialogModel(this, viewport, multiEdit)), _viewport(viewport) { ui->setupUi(this); - parentDialog = dynamic_cast(parent); - Assert(parentDialog); - _model = std::unique_ptr(new ShipTextureReplacementDialogModel(this, - viewport, parentDialog->getIfMultipleShips())); connect(this, &QDialog::accepted, _model.get(), &ShipTextureReplacementDialogModel::apply); - connect(this, &QDialog::rejected, _model.get(), &ShipTextureReplacementDialogModel::reject); + connect(ui->buttonBox, + &QDialogButtonBox::rejected, + this, + &ShipTextureReplacementDialog::rejectHandler); listmodel = new MapModel(_model.get(), this); ui->TexturesList->setModel(listmodel); QItemSelectionModel* selectionModel = ui->TexturesList->selectionModel(); @@ -89,29 +89,16 @@ namespace fso { } ShipTextureReplacementDialog::~ShipTextureReplacementDialog() = default; - void ShipTextureReplacementDialog::closeEvent(QCloseEvent* event) + void ShipTextureReplacementDialog::closeEvent(QCloseEvent* e) { - if (_model->query_modified()) { - auto button = _viewport->dialogProvider->showButtonDialog(DialogType::Question, "Changes detected", "Do you want to keep your changes?", - { DialogButton::Yes, DialogButton::No, DialogButton::Cancel }); - - if (button == DialogButton::Cancel) { - event->ignore(); - return; - } - - if (button == DialogButton::Yes) { - accept(); - return; - } - } - - QDialog::closeEvent(event); + if (!rejectOrCloseHandler(this, _model.get(), _viewport)) { + e->ignore(); + }; } - void ShipTextureReplacementDialog::showEvent(QShowEvent* event) { - _model->initialiseData(parentDialog->getIfMultipleShips()); - QDialog::showEvent(event); + void ShipTextureReplacementDialog::rejectHandler() + { + this->close(); } void ShipTextureReplacementDialog::updateUI() diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipTextureReplacementDialog.h b/qtfred/src/ui/dialogs/ShipEditor/ShipTextureReplacementDialog.h index a4aecf6802f..7b16eef07f8 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipTextureReplacementDialog.h +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipTextureReplacementDialog.h @@ -3,7 +3,6 @@ #include #include #include -#include "ShipEditorDialog.h" #include namespace fso { @@ -13,7 +12,6 @@ namespace fso { namespace Ui { class ShipTextureReplacementDialog; } - class ShipEditorDialog; //Model for mapping data to listview in Texture Replace dialog class MapModel : public QAbstractListModel { @@ -30,20 +28,19 @@ namespace fso { Q_OBJECT public: - explicit ShipTextureReplacementDialog(QDialog* parent, EditorViewport* viewport); + explicit ShipTextureReplacementDialog(QDialog* parent, EditorViewport* viewport, bool multiEdit); ~ShipTextureReplacementDialog() override; protected: void closeEvent(QCloseEvent*) override; - void showEvent(QShowEvent*) override; private: std::unique_ptr ui; std::unique_ptr _model; EditorViewport* _viewport; - ShipEditorDialog* parentDialog; int row = 0; MapModel* listmodel; void updateUI(); void updateUIFull(); + void rejectHandler(); void setMain(); void setMisc(); diff --git a/qtfred/ui/PlayerOrdersDialog.ui b/qtfred/ui/PlayerOrdersDialog.ui index 0e37290f780..41a3918113d 100644 --- a/qtfred/ui/PlayerOrdersDialog.ui +++ b/qtfred/ui/PlayerOrdersDialog.ui @@ -54,13 +54,6 @@ - - - fso::fred::ShipFlagCheckbox - QCheckBox -
ui/widgets/ShipFlagCheckbox.h
-
-
@@ -81,21 +74,5 @@ - - cancelButton - clicked() - fso::fred::dialogs::PlayerOrdersDialog - reject() - - - 147 - 159 - - - 97 - 118 - - - diff --git a/qtfred/ui/ShipFlagsDialog.ui b/qtfred/ui/ShipFlagsDialog.ui index fbaeae46940..9ddb627b582 100644 --- a/qtfred/ui/ShipFlagsDialog.ui +++ b/qtfred/ui/ShipFlagsDialog.ui @@ -7,7 +7,7 @@ 0 0 421 - 664 + 745 @@ -709,21 +709,5 @@ - - cancelButton - clicked() - fso::fred::dialogs::ShipFlagsDialog - reject() - - - 389 - 563 - - - 375 - 515 - - - diff --git a/qtfred/ui/ShipGoalsDialog.ui b/qtfred/ui/ShipGoalsDialog.ui index e4568b56a4e..4608e38d05a 100644 --- a/qtfred/ui/ShipGoalsDialog.ui +++ b/qtfred/ui/ShipGoalsDialog.ui @@ -7,7 +7,7 @@ 0 0 597 - 344 + 374 @@ -322,7 +322,7 @@ - + okButton clicked() @@ -339,21 +339,5 @@ - - cancelButton - clicked() - fso::fred::dialogs::ShipGoalsDialog - reject() - - - 628 - 19 - - - 357 - 297 - - - diff --git a/qtfred/ui/ShipInitialStatus.ui b/qtfred/ui/ShipInitialStatus.ui index 1e07aa395c9..e84c7639183 100644 --- a/qtfred/ui/ShipInitialStatus.ui +++ b/qtfred/ui/ShipInitialStatus.ui @@ -421,21 +421,5 @@ - - cancelPushButton - clicked() - fso::fred::dialogs::ShipInitialStatusDialog - reject() - - - 389 - 563 - - - 375 - 515 - - - diff --git a/qtfred/ui/ShipSpecialStatsDialog.ui b/qtfred/ui/ShipSpecialStatsDialog.ui index d8f0b095305..423fda8bc0a 100644 --- a/qtfred/ui/ShipSpecialStatsDialog.ui +++ b/qtfred/ui/ShipSpecialStatsDialog.ui @@ -6,8 +6,8 @@ 0 0 - 518 - 287 + 542 + 322 @@ -320,21 +320,5 @@ - - buttonBox - rejected() - fso::fred::dialogs::ShipSpecialStatsDialog - reject() - - - 20 - 20 - - - 20 - 20 - - - diff --git a/qtfred/ui/ShipTextureReplacementDialog.ui b/qtfred/ui/ShipTextureReplacementDialog.ui index 2e82a1b08ab..957e0cb00f6 100644 --- a/qtfred/ui/ShipTextureReplacementDialog.ui +++ b/qtfred/ui/ShipTextureReplacementDialog.ui @@ -253,21 +253,5 @@ - - buttonBox - rejected() - fso::fred::dialogs::ShipTextureReplacementDialog - reject() - - - 296 - 179 - - - 200 - 100 - - - From 17706a0a6d7c355cffc624e554a63acba58c3bfe Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Sun, 15 Dec 2024 22:57:31 -0500 Subject: [PATCH 18/64] fix sexp_ship_deal_with_subsystem_flag() Due to broken node iteration, the `sexp_ship_deal_with_subsystem_flag()` function incorrectly treated the first sexp node as a subsystem, but previous versions of the function would fail silently and ignore it, and subsequent nodes would work as expected. With the new `process_ship_subsystems()` function, this error is properly flagged (and the incorrect node is again bypassed). So, fix the error by fixing the node iteration. Follow-up to #6221. --- code/parse/sexp.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index f8ff3f7e702..b565d353d01 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -18997,6 +18997,7 @@ void sexp_ship_deal_with_subsystem_flag(int op_node, int node, Ship::Subsystem_F return; } auto shipp = ship_entry->shipp(); + node = CDR(node); //replace or not // OP_SHIP_SUBSYS_TARGETABLE/UNTARGETABLE, OP_SHIP_SUBSYS_TARGETABLE and OP_TURRET_SUBSYS_TARGET_ENABLE/DISABLE @@ -19004,8 +19005,8 @@ void sexp_ship_deal_with_subsystem_flag(int op_node, int node, Ship::Subsystem_F // backward compatibility hack for older sexps if (!((ss_flag == Ship::Subsystem_Flags::Untargetable) || (ss_flag == Ship::Subsystem_Flags::No_SS_targeting))) { - node = CDR(node); setit = is_sexp_true(node); + node = CDR(node); } //multiplayer packet start From dfbf4da3fba02125b706d51a9460fab0685e2a37 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Mon, 16 Dec 2024 02:07:57 -0500 Subject: [PATCH 19/64] fix engine wash An idiosyncrasy of the original implementation of `timestamp_until()` is that "immediate" timestamps that expired would be negative. Only one remaining spot in the codebase relied on this: engine wash. When #6258 fixed `timestamp_until()` to return 0 for immediate timestamps, engine wash broke. Changing the comparison from `<` to `<=` fixes it. I searched the code and there are no similar uses of `timestamp_until()` or `timestamp_since()`. In the process of searching, though, I found two uses of `timestamp_since()` that were converted from `timestamp_until()` in PR #4286. These particular conversions more properly include the 0 rather than exclude it, so I changed these as well. --- code/ai/aiturret.cpp | 4 ++-- code/ship/shipfx.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/code/ai/aiturret.cpp b/code/ai/aiturret.cpp index c54d61da264..ee7ab26acca 100644 --- a/code/ai/aiturret.cpp +++ b/code/ai/aiturret.cpp @@ -2788,7 +2788,7 @@ void ai_turret_execute_behavior(const ship *shipp, ship_subsys *ss) { //something did fire, get the lowest valid timestamp // don't do this if we already set a turret timestamp previously in the function - if (timestamp_since(ss->turret_next_fire_stamp) > 0) + if (timestamp_since(ss->turret_next_fire_stamp) >= 0) { int minimum_stamp = -1; @@ -2797,7 +2797,7 @@ void ai_turret_execute_behavior(const ship *shipp, ship_subsys *ss) int stamp = (i < MAX_SHIP_PRIMARY_BANKS) ? swp->next_primary_fire_stamp[i] : swp->next_secondary_fire_stamp[i - MAX_SHIP_PRIMARY_BANKS]; // valid timestamps start at 2; stamp must be in the future - if (stamp < 2 || timestamp_since(stamp) > 0) + if (stamp < 2 || timestamp_since(stamp) >= 0) continue; // find minimum diff --git a/code/ship/shipfx.cpp b/code/ship/shipfx.cpp index e7cbf204391..5ef860645ec 100644 --- a/code/ship/shipfx.cpp +++ b/code/ship/shipfx.cpp @@ -2685,7 +2685,7 @@ void engine_wash_ship_process(ship *shipp) // is it time to check for engine wash int time_to_next_hit = timestamp_until(shipp->wash_timestamp); - if (time_to_next_hit < 0) { + if (time_to_next_hit <= 0) { if (time_to_next_hit < -ENGINE_WASH_CHECK_INTERVAL) { time_to_next_hit = 0; } From 1c9066485cefe8eb8ad0e73abfde0e17ab5b5204 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Mon, 16 Dec 2024 12:12:30 -0500 Subject: [PATCH 20/64] STL fixes and cleanup (#6455) * fix some usages of std::transform * change parse_string_flag_list to use a reference * add a variant of parse_string_flag_list that can use a SCP_set, and make a templated version of flag_def_list --- code/ai/aigoals.h | 2 +- code/globalincs/pstypes.h | 10 +++++++--- code/globalincs/vmallocator.h | 4 ++-- code/iff_defs/iff_defs.cpp | 2 +- code/mission/missionparse.cpp | 2 +- code/parse/parselo.cpp | 9 +++------ code/parse/parselo.h | 21 ++++++++++++++++++++- code/ship/ship.cpp | 8 ++++---- qtfred/src/ui/widgets/sexp_tree.cpp | 2 +- tools/embedfile/embedfile.cpp | 2 +- 10 files changed, 41 insertions(+), 21 deletions(-) diff --git a/code/ai/aigoals.h b/code/ai/aigoals.h index 9d8cd5475f3..fc5a1187696 100644 --- a/code/ai/aigoals.h +++ b/code/ai/aigoals.h @@ -147,7 +147,7 @@ typedef struct ai_goal { extern void ai_goal_reset(ai_goal *aigp, bool adding_goal = false, int ai_mode = AI_GOAL_NONE, int ai_submode = -1, int type = -1); -typedef flag_def_list ai_goal_list; +typedef flag_def_list_templated ai_goal_list; extern ai_goal_list Ai_goal_names[]; extern int Num_ai_goals; diff --git a/code/globalincs/pstypes.h b/code/globalincs/pstypes.h index 1c37563ca8e..b6c56fbfc06 100644 --- a/code/globalincs/pstypes.h +++ b/code/globalincs/pstypes.h @@ -220,15 +220,19 @@ struct particle_pnt { vec3d up; }; +// for compiler compatibility, even though C++17 supports omitting the template type... + //def_list -struct flag_def_list { +template +struct flag_def_list_templated { const char *name; - int def; + T def; ubyte var; }; +using flag_def_list = flag_def_list_templated; //A list of parse names for a flag enum -template +template struct flag_def_list_new { const char* name; // The parseable representation of this flag T def; // The flag definition for this flag diff --git a/code/globalincs/vmallocator.h b/code/globalincs/vmallocator.h index d04a53bf3a1..41398063337 100644 --- a/code/globalincs/vmallocator.h +++ b/code/globalincs/vmallocator.h @@ -52,11 +52,11 @@ typedef std::basic_string, std::allocator > S typedef std::basic_stringstream, std::allocator > SCP_stringstream; inline void SCP_tolower(SCP_string &str) { - std::transform(str.begin(), str.end(), str.begin(), [](char c) { return SCP_tolower(c); }); + std::for_each(str.begin(), str.end(), [](char &c) { c = SCP_tolower(c); }); } inline void SCP_toupper(SCP_string &str) { - std::transform(str.begin(), str.end(), str.begin(), [](char c) { return SCP_toupper(c); }); + std::for_each(str.begin(), str.end(), [](char &c) { c = SCP_toupper(c); }); } inline bool SCP_truncate(SCP_string &str, size_t c_str_size) { diff --git a/code/iff_defs/iff_defs.cpp b/code/iff_defs/iff_defs.cpp index 21794228d8a..948b357a26c 100644 --- a/code/iff_defs/iff_defs.cpp +++ b/code/iff_defs/iff_defs.cpp @@ -434,7 +434,7 @@ void parse_iff_table(const char* filename) } if (optional_string("$Radar Target ID Flags:")) { - parse_string_flag_list((int*)&radar_target_id_flags, rti_flags, Num_rti_flags); + parse_string_flag_list(radar_target_id_flags, rti_flags, Num_rti_flags); if (optional_string("+reset")) radar_target_id_flags = 0; } diff --git a/code/mission/missionparse.cpp b/code/mission/missionparse.cpp index 0b1350d9ccd..38a45b33e19 100644 --- a/code/mission/missionparse.cpp +++ b/code/mission/missionparse.cpp @@ -5301,7 +5301,7 @@ void parse_event(mission *pm) } if (optional_string("+Event Flags:")) { - parse_string_flag_list(&event->flags, Mission_event_flags, Num_mission_event_flags); + parse_string_flag_list(event->flags, Mission_event_flags, Num_mission_event_flags); } if( optional_string("+Event Log Flags:") ) { diff --git a/code/parse/parselo.cpp b/code/parse/parselo.cpp index f533b50ec4e..57cd95c804c 100644 --- a/code/parse/parselo.cpp +++ b/code/parse/parselo.cpp @@ -2975,10 +2975,8 @@ size_t stuff_token_list(T *listp, size_t list_max, F stuff_one_token, const char // If this data is going to be parsed multiple times (like for mission load), then the dest variable // needs to be set to zero in between parses, otherwise we keep bad data. // For tbm files, it must not be reset. -void parse_string_flag_list(int *dest, flag_def_list defs[], size_t defs_size) +void parse_string_flag_list(int &dest, flag_def_list defs[], size_t defs_size) { - Assert(dest!=NULL); //wtf? - SCP_vector slp; stuff_string_list(slp); @@ -2986,9 +2984,8 @@ void parse_string_flag_list(int *dest, flag_def_list defs[], size_t defs_size) { for (size_t j = 0; j < defs_size; j++) { - if (!stricmp(str.c_str(), defs[j].name)) { - (*dest) |= defs[j].def; - } + if (!stricmp(str.c_str(), defs[j].name)) + dest |= defs[j].def; } } } diff --git a/code/parse/parselo.h b/code/parse/parselo.h index 6689399479d..f864b20a946 100644 --- a/code/parse/parselo.h +++ b/code/parse/parselo.h @@ -162,7 +162,26 @@ extern int stuff_int_optional(int *i); extern int stuff_float_optional(float *f); extern void stuff_string_list(SCP_vector& slp); extern size_t stuff_string_list(char slp[][NAME_LENGTH], size_t max_strings); -extern void parse_string_flag_list(int *dest, flag_def_list defs[], size_t defs_size); +extern void parse_string_flag_list(int &dest, flag_def_list defs[], size_t defs_size); + +// If this data is going to be parsed multiple times (like for mission load), then the dest variable +// needs to be cleared in between parses, otherwise we keep bad data. +// For tbm files, it must not be reset. +template +void parse_string_flag_list(SCP_set &dest, flag_def_list_templated defs[], size_t defs_size) +{ + SCP_vector slp; + stuff_string_list(slp); + + for (auto &str : slp) + { + for (size_t j = 0; j < defs_size; j++) + { + if (!stricmp(str.c_str(), defs[j].name)) + dest.insert(defs[j].def); + } + } +} // A templated version of parse_string_flag_list, to go along with the templated flag_def_list_new. diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index 78bbc9b150c..e968849026f 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -5862,7 +5862,7 @@ static void parse_ship_type(const char *filename, const bool replace) if(optional_string("$AI:")) { if(optional_string("+Valid goals:")) { - parse_string_flag_list(&stp->ai_valid_goals, Ai_goal_names, Num_ai_goals); + parse_string_flag_list(stp->ai_valid_goals, Ai_goal_names, Num_ai_goals); } if(optional_string("+Accept Player Orders:")) { @@ -5905,11 +5905,11 @@ static void parse_ship_type(const char *filename, const bool replace) } if(optional_string("+Active docks:")) { - parse_string_flag_list(&stp->ai_active_dock, Dock_type_names, Num_dock_type_names); + parse_string_flag_list(stp->ai_active_dock, Dock_type_names, Num_dock_type_names); } if(optional_string("+Passive docks:")) { - parse_string_flag_list(&stp->ai_passive_dock, Dock_type_names, Num_dock_type_names); + parse_string_flag_list(stp->ai_passive_dock, Dock_type_names, Num_dock_type_names); } if(optional_string("+Ignored on cripple by:")) { @@ -20573,7 +20573,7 @@ void parse_armor_type() //rest of the parse data if (optional_string("$Flags:")) - parse_string_flag_list((int*)&tat.flags, Armor_flags, Num_armor_flags); + parse_string_flag_list(tat.flags, Armor_flags, Num_armor_flags); //Add it to global armor types Armor_types.push_back(tat); diff --git a/qtfred/src/ui/widgets/sexp_tree.cpp b/qtfred/src/ui/widgets/sexp_tree.cpp index 9bfcbed6cf1..0334b6e7b01 100644 --- a/qtfred/src/ui/widgets/sexp_tree.cpp +++ b/qtfred/src/ui/widgets/sexp_tree.cpp @@ -4568,7 +4568,7 @@ sexp_list_item* sexp_tree::get_listing_opf_ship_wing_shiponteam_point() { for (i = 0; i < (int)Iff_info.size(); i++) { SCP_string tmp; sprintf(tmp, "", Iff_info[i].iff_name); - std::transform(begin(tmp), end(tmp), begin(tmp), [](char c) { return (char)::tolower(c); }); + SCP_tolower(tmp); head.add_data(tmp.c_str()); } diff --git a/tools/embedfile/embedfile.cpp b/tools/embedfile/embedfile.cpp index a0e6f89ab89..ed943c54f60 100644 --- a/tools/embedfile/embedfile.cpp +++ b/tools/embedfile/embedfile.cpp @@ -171,7 +171,7 @@ void do_text_content(std::ifstream& file_in, std::ofstream& file_out, void write_header(std::ostream& out, const std::string& fieldName, bool text_content, bool wxWidgets_image) { std::string headerDefine(fieldName); - std::transform(fieldName.begin(), fieldName.end(), headerDefine.begin(), + std::transform(fieldName.begin(), fieldName.end(), std::back_inserter(headerDefine), [](char c) { return static_cast(::toupper(static_cast(c))); }); headerDefine = "SCP_" + headerDefine + "_H"; From 2a7806d69093e283555f9e59819034bb36276de4 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Tue, 17 Dec 2024 08:32:08 +0000 Subject: [PATCH 21/64] Slightly off topic but remove C style casts from selection dialog --- qtfred/src/mission/dialogs/SelectionDialogModel.cpp | 9 +++++---- qtfred/src/mission/dialogs/SelectionDialogModel.h | 4 ++-- qtfred/src/ui/dialogs/SelectionDialog.cpp | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/qtfred/src/mission/dialogs/SelectionDialogModel.cpp b/qtfred/src/mission/dialogs/SelectionDialogModel.cpp index d0dd4430c70..26c5a1cf12d 100644 --- a/qtfred/src/mission/dialogs/SelectionDialogModel.cpp +++ b/qtfred/src/mission/dialogs/SelectionDialogModel.cpp @@ -263,13 +263,14 @@ void SelectionDialogModel::setFilterWaypoints(bool filter_waypoints) { } } -bool SelectionDialogModel::isFilterIFFTeam(int team) const { - Assertion(team >= 0 && team < (int)Iff_info.size(), "Team index %d is invalid!", team); +bool SelectionDialogModel::isFilterIFFTeam(size_t team) const { + Assertion(team >= 0 && team < Iff_info.size(), "Team index %d is invalid!", team); return _filter_iff[team]; } -void SelectionDialogModel::setFilterIFFTeam(int team, bool filter) { - Assertion(team >= 0 && team < (int)Iff_info.size(), "Team index %d is invalid!", team); +void SelectionDialogModel::setFilterIFFTeam(size_t team, bool filter) +{ + Assertion(team >= 0 && team < Iff_info.size(), "Team index %d is invalid!", team); if (filter != _filter_iff[team]) { _filter_iff[team] = filter; diff --git a/qtfred/src/mission/dialogs/SelectionDialogModel.h b/qtfred/src/mission/dialogs/SelectionDialogModel.h index f686101eb88..48fb4e04f58 100644 --- a/qtfred/src/mission/dialogs/SelectionDialogModel.h +++ b/qtfred/src/mission/dialogs/SelectionDialogModel.h @@ -72,8 +72,8 @@ class SelectionDialogModel : public AbstractDialogModel { bool isFilterWaypoints() const; void setFilterWaypoints(bool filter_waypoints); - bool isFilterIFFTeam(int team) const; - void setFilterIFFTeam(int team, bool filter); + bool isFilterIFFTeam(size_t team) const; + void setFilterIFFTeam(size_t team, bool filter); /** * @brief Updates the selection status of the objects in the list diff --git a/qtfred/src/ui/dialogs/SelectionDialog.cpp b/qtfred/src/ui/dialogs/SelectionDialog.cpp index bf8ee8fe4dc..c1af891e5c2 100644 --- a/qtfred/src/ui/dialogs/SelectionDialog.cpp +++ b/qtfred/src/ui/dialogs/SelectionDialog.cpp @@ -38,7 +38,7 @@ SelectionDialog::SelectionDialog(FredView* parent, EditorViewport* viewport) : [this](int state) { _model->setFilterStarts(state == Qt::Checked); }); // Initialize IFF check boxes - for (auto i = 0; i < (int)Iff_info.size(); ++i) { + for (size_t i = 0; i < Iff_info.size(); ++i) { auto checkbox = new QCheckBox(QString::fromUtf8(Iff_info[i].iff_name), this); _iffCheckBoxes.push_back(checkbox); ui->iffSelectionContainer->addWidget(checkbox); @@ -73,7 +73,7 @@ void SelectionDialog::updateUI() { ui->checkPlayerStarts->setChecked(_model->isFilterStarts()); ui->checkWaypoints->setChecked(_model->isFilterWaypoints()); ui->checkShips->setChecked(_model->isFilterShips()); - for (auto i = 0; i < (int)Iff_info.size(); ++i) { + for (size_t i = 0; i < Iff_info.size(); ++i) { _iffCheckBoxes[i]->setChecked(_model->isFilterIFFTeam(i)); _iffCheckBoxes[i]->setEnabled(_model->isFilterShips()); } From f4bd6aa003de3d295707fa946a21b0c80d1431b3 Mon Sep 17 00:00:00 2001 From: BMagnu <6238428+BMagnu@users.noreply.github.com> Date: Tue, 17 Dec 2024 18:56:29 +0900 Subject: [PATCH 22/64] Correctly process ImGUI flags (#6468) --- code/osapi/osapi.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/code/osapi/osapi.cpp b/code/osapi/osapi.cpp index 8748f71fffe..e7c0a49ac64 100644 --- a/code/osapi/osapi.cpp +++ b/code/osapi/osapi.cpp @@ -741,9 +741,16 @@ static void handle_sdl_event(const SDL_Event& event) { bool imgui_processed_this = false; if ((gameseq_get_state() == GS_STATE_LAB) || (gameseq_get_state() == GS_STATE_INGAME_OPTIONS)) { - if (ImGui::GetIO().WantCaptureKeyboard || ImGui::GetIO().WantCaptureMouse) { - imgui_processed_this = ImGui_ImplSDL2_ProcessEvent(&event); - } + //In these states, we always need to forward inputs to ImGUI, and depending on the ImGUI state and the input type, we must consume it here instead of passing it to FSO. + ImGui_ImplSDL2_ProcessEvent(&event); + + imgui_processed_this = (ImGui::GetIO().WantCaptureKeyboard && + (event.type == SDL_EventType::SDL_KEYUP || + event.type == SDL_EventType::SDL_KEYDOWN)) || + (ImGui::GetIO().WantCaptureMouse && + (event.type == SDL_EventType::SDL_MOUSEBUTTONUP || + event.type == SDL_EventType::SDL_MOUSEBUTTONDOWN|| + event.type == SDL_EventType::SDL_MOUSEMOTION)); } if (!imgui_processed_this) { From 7f48375a6d816c52f2947fcb844b51c4fe88b506 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Wed, 18 Dec 2024 02:33:39 -0600 Subject: [PATCH 23/64] Add underlining as a custom decorator (#6399) * add underlining and corner border custom decorators * some cleanup * CLANG --- code/scpui/RocketDecorators.cpp | 248 +++++++++++++++++++++++ code/scpui/RocketDecorators.h | 81 ++++++++ code/scpui/RocketDecoratorsInstancer.cpp | 99 +++++++++ code/scpui/RocketDecoratorsInstancer.h | 43 ++++ code/scpui/rocket_ui.cpp | 13 ++ code/source_groups.cmake | 4 + 6 files changed, 488 insertions(+) create mode 100644 code/scpui/RocketDecorators.cpp create mode 100644 code/scpui/RocketDecorators.h create mode 100644 code/scpui/RocketDecoratorsInstancer.cpp create mode 100644 code/scpui/RocketDecoratorsInstancer.h diff --git a/code/scpui/RocketDecorators.cpp b/code/scpui/RocketDecorators.cpp new file mode 100644 index 00000000000..d6e30074cce --- /dev/null +++ b/code/scpui/RocketDecorators.cpp @@ -0,0 +1,248 @@ +#include "RocketDecorators.h" + +#include +#include + +namespace scpui { +namespace decorators { + +// ================================ +// Element Underline Implementation +// ================================ + +DecoratorUnderline::DecoratorUnderline(float thickness, LineStyle style, float length, float space, Rocket::Core::Colourb color, bool element_color) + : line_thickness(thickness), line_style(style), line_length(length), line_space(space), line_color(color), use_element_color(element_color) +{ +} + +DecoratorUnderline::~DecoratorUnderline() = default; + +// Generate per-element data, if needed (return NULL if not needed) +Rocket::Core::DecoratorDataHandle DecoratorUnderline::GenerateElementData(Rocket::Core::Element* /*element*/) +{ + return 0; // No per-element data needed +} + +// Release any element-specific data +void DecoratorUnderline::ReleaseElementData(Rocket::Core::DecoratorDataHandle /*element_data*/) +{ + // No element-specific data to release in this case +} + +void DecoratorUnderline::RenderElement(Rocket::Core::Element* element, + Rocket::Core::DecoratorDataHandle /*element_data*/) +{ + // Get the size of the element's content area + Rocket::Core::Vector2f element_size = element->GetBox().GetSize(Rocket::Core::Box::CONTENT); + + // Determine line rendering properties based on style + float dash_length = 5.0f; + float space_length = 3.0f; + + switch (line_style) { + case LineStyle::Dotted: + dash_length = line_thickness; // Dotted lines are small circles + space_length = line_space; + break; + case LineStyle::Dashed: + space_length = line_space; + dash_length = line_length; + break; + case LineStyle::Solid: + default: + space_length = 0; // No space for solid lines + break; + } + + // Calculate the starting position (bottom-left of the content area) + Rocket::Core::Vector2f start = element->GetAbsoluteOffset(Rocket::Core::Box::CONTENT); + float x_position = start.x; + float y_position = start.y + element_size.y - (line_thickness * 2); // Adjusted y-position using the thickness + + Rocket::Core::RenderInterface* render_interface = element->GetRenderInterface(); + + // Create space for vertices and indices for all dashes + std::vector vertices; + std::vector indices; + + int vertex_count = 0; + + Rocket::Core::Colourb render_color = line_color; + if (use_element_color) { + render_color = element->GetProperty("color"); + } + + // Loop through to create each dash as a rectangle (or dot) + while (x_position < start.x + element_size.x) { + float dash_end_x = std::min(x_position + dash_length, start.x + element_size.x); + + // Create four vertices for the rectangle (two triangles) + Rocket::Core::Vertex top_left, top_right, bottom_left, bottom_right; + + // Set vertex positions + top_left.position = Rocket::Core::Vector2f(x_position, y_position); + top_right.position = Rocket::Core::Vector2f(dash_end_x, y_position); + bottom_left.position = Rocket::Core::Vector2f(x_position, y_position + line_thickness); + bottom_right.position = Rocket::Core::Vector2f(dash_end_x, y_position + line_thickness); + + // Set the color + top_left.colour = render_color; + top_right.colour = render_color; + bottom_left.colour = render_color; + bottom_right.colour = render_color; + + // Set texture coordinates to 0 since we have no texture + top_left.tex_coord = Rocket::Core::Vector2f(0, 0); + top_right.tex_coord = Rocket::Core::Vector2f(0, 0); + bottom_left.tex_coord = Rocket::Core::Vector2f(0, 0); + bottom_right.tex_coord = Rocket::Core::Vector2f(0, 0); + + // Add the vertices to the list + vertices.push_back(top_left); + vertices.push_back(top_right); + vertices.push_back(bottom_left); + vertices.push_back(bottom_right); + + // Add indices for two triangles + indices.push_back(vertex_count); // First triangle + indices.push_back(vertex_count + 1); + indices.push_back(vertex_count + 2); + + indices.push_back(vertex_count + 1); // Second triangle + indices.push_back(vertex_count + 3); + indices.push_back(vertex_count + 2); + + // Move to the next dash position + x_position += dash_length + space_length; + + // Increment vertex count for the next set of triangles + vertex_count += 4; + } + + // Render the geometry + render_interface->RenderGeometry(vertices.data(), + (int)vertices.size(), + indices.data(), + (int)indices.size(), + 0, + Rocket::Core::Vector2f(0, 0)); +} + +// ============================= +// Corner Borders Implementation +// ============================= + +DecoratorCornerBorders::DecoratorCornerBorders(float thickness, float length_h, float length_v, Rocket::Core::Colourb color) + : border_thickness(thickness), border_length_h(length_h), border_length_v(length_v), border_color(color) +{ +} + +DecoratorCornerBorders::~DecoratorCornerBorders() = default; + +// Generate per-element data, if needed (return NULL if not needed) +Rocket::Core::DecoratorDataHandle DecoratorCornerBorders::GenerateElementData(Rocket::Core::Element* /*element*/) +{ + return 0; // No per-element data needed +} + +// Release any element-specific data +void DecoratorCornerBorders::ReleaseElementData(Rocket::Core::DecoratorDataHandle /*element_data*/) +{ + // No element-specific data to release in this case +} + +void DecoratorCornerBorders::RenderElement(Rocket::Core::Element* element, + Rocket::Core::DecoratorDataHandle /*element_data*/) +{ + // Get the size of the element's content area + Rocket::Core::Vector2f element_size = element->GetBox().GetSize(Rocket::Core::Box::CONTENT); + Rocket::Core::Vector2f start = element->GetAbsoluteOffset(Rocket::Core::Box::CONTENT); + + Rocket::Core::RenderInterface* render_interface = element->GetRenderInterface(); + + // Create space for vertices and indices for the corner borders + std::vector vertices; + std::vector indices; + + int vertex_count = 0; + + // Define the four corner positions, moved inward by thickness + Rocket::Core::Vector2f top_left = start + Rocket::Core::Vector2f(border_thickness, border_thickness); + Rocket::Core::Vector2f top_right = + start + Rocket::Core::Vector2f(element_size.x - border_thickness, border_thickness); + Rocket::Core::Vector2f bottom_left = + start + Rocket::Core::Vector2f(border_thickness, element_size.y - border_thickness); + Rocket::Core::Vector2f bottom_right = + start + Rocket::Core::Vector2f(element_size.x - border_thickness, element_size.y - border_thickness); + + // Function to add a line segment as two triangles + auto add_line_segment = [&](const Rocket::Core::Vector2f& start_pos, const Rocket::Core::Vector2f& end_pos, float thickness) { + // Vector direction of the line + Rocket::Core::Vector2f direction = end_pos - start_pos; + // Normalize direction and calculate the perpendicular vector for thickness + Rocket::Core::Vector2f perpendicular = + Rocket::Core::Vector2f(-direction.y, direction.x).Normalise() * thickness; + + // Define the four vertices of the line as a rectangle (two triangles) + Rocket::Core::Vertex v1, v2, v3, v4; + v1.position = start_pos - perpendicular; + v2.position = start_pos + perpendicular; + v3.position = end_pos - perpendicular; + v4.position = end_pos + perpendicular; + + // Set color + v1.colour = border_color; + v2.colour = border_color; + v3.colour = border_color; + v4.colour = border_color; + + // Add vertices + vertices.push_back(v1); + vertices.push_back(v2); + vertices.push_back(v3); + vertices.push_back(v4); + + // Add indices for two triangles + indices.push_back(vertex_count); + indices.push_back(vertex_count + 1); + indices.push_back(vertex_count + 2); + + indices.push_back(vertex_count + 1); + indices.push_back(vertex_count + 3); + indices.push_back(vertex_count + 2); + + vertex_count += 4; + }; + + // Define the lengths for horizontal and vertical borders + Rocket::Core::Vector2f horizontal(border_length_h, 0); + Rocket::Core::Vector2f vertical(0, border_length_v); + + // Add corner borders by adding two line segments for each corner, moved inward by thickness + // Top-left corner + add_line_segment(top_left, top_left + horizontal, border_thickness); // Horizontal line + add_line_segment(top_left, top_left + vertical, border_thickness); // Vertical line + + // Top-right corner + add_line_segment(top_right, top_right - horizontal, border_thickness); // Horizontal line + add_line_segment(top_right, top_right + vertical, border_thickness); // Vertical line + + // Bottom-left corner + add_line_segment(bottom_left, bottom_left + horizontal, border_thickness); // Horizontal line + add_line_segment(bottom_left, bottom_left - vertical, border_thickness); // Vertical line + + // Bottom-right corner + add_line_segment(bottom_right, bottom_right - horizontal, border_thickness); // Horizontal line + add_line_segment(bottom_right, bottom_right - vertical, border_thickness); // Vertical line + + // Render the geometry + render_interface->RenderGeometry(vertices.data(), + (int)vertices.size(), + indices.data(), + (int)indices.size(), + 0, + Rocket::Core::Vector2f(0, 0)); +} + +} // namespace decorators +} // namespace scpui diff --git a/code/scpui/RocketDecorators.h b/code/scpui/RocketDecorators.h new file mode 100644 index 00000000000..e9a5760ffd3 --- /dev/null +++ b/code/scpui/RocketDecorators.h @@ -0,0 +1,81 @@ +#pragma once + +// Our Assert conflicts with the definitions inside libRocket +#pragma push_macro("Assert") +#undef Assert + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wshadow" +#endif + +#include +#include +#include +#include +#include + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#pragma pop_macro("Assert") + +namespace scpui { +namespace decorators { + +enum class LineStyle { + Solid, + Dashed, + Dotted, + Num_styles +}; + +class DecoratorUnderline : public Rocket::Core::Decorator { + public: + // Constructor to accept properties + DecoratorUnderline(float line_thickness, LineStyle line_style, float line_length, float line_space, Rocket::Core::Colourb line_color, bool use_element_color); + ~DecoratorUnderline() override; + + // Called to generate per-element data for newly decorated elements. + Rocket::Core::DecoratorDataHandle GenerateElementData(Rocket::Core::Element* element) override; + + // Called to release element-specific data. + void ReleaseElementData(Rocket::Core::DecoratorDataHandle element_data) override; + + // Called to render the decorator on an element. + void RenderElement(Rocket::Core::Element* element, Rocket::Core::DecoratorDataHandle element_data) override; + + private: + float line_thickness; + LineStyle line_style; + float line_length; + float line_space; + Rocket::Core::Colourb line_color; + bool use_element_color; +}; + +class DecoratorCornerBorders : public Rocket::Core::Decorator { + public: + // Constructor to accept properties + DecoratorCornerBorders(float border_thickness, float border_length_h, float border_length_v, Rocket::Core::Colourb border_color); + ~DecoratorCornerBorders() override; + + // Called to generate per-element data for newly decorated elements. + Rocket::Core::DecoratorDataHandle GenerateElementData(Rocket::Core::Element* element) override; + + // Called to release element-specific data. + void ReleaseElementData(Rocket::Core::DecoratorDataHandle element_data) override; + + // Called to render the decorator on an element. + void RenderElement(Rocket::Core::Element* element, Rocket::Core::DecoratorDataHandle element_data) override; + + private: + float border_thickness; + float border_length_h; + float border_length_v; + Rocket::Core::Colourb border_color; +}; + +} // namespace decorators +} // namespace scpui diff --git a/code/scpui/RocketDecoratorsInstancer.cpp b/code/scpui/RocketDecoratorsInstancer.cpp new file mode 100644 index 00000000000..9da86df8246 --- /dev/null +++ b/code/scpui/RocketDecoratorsInstancer.cpp @@ -0,0 +1,99 @@ +#include "RocketDecoratorsInstancer.h" + +#include +#include +#include + +namespace scpui { +namespace decorators { + +// UnderlineDecoratorInstancer Implementation +UnderlineDecoratorInstancer::UnderlineDecoratorInstancer() +{ + RegisterProperty("thickness", "1.0").AddParser("number"); + RegisterProperty("style", "solid").AddParser("keyword", "solid, dashed, dotted"); + RegisterProperty("length", "5.0").AddParser("number"); + RegisterProperty("space", "3.0").AddParser("number"); + RegisterProperty("color-setting", "default").AddParser("keyword", "default, element"); + RegisterProperty("color", "white").AddParser("color"); + + RegisterShorthand("shorthand", "style, thickness, length, space"); +} + +Rocket::Core::Decorator* UnderlineDecoratorInstancer::InstanceDecorator(const Rocket::Core::String& /*name*/, + const Rocket::Core::PropertyDictionary& prop_dict) +{ + // librocket documentation says the best way to get a keyword is by index so get + // that and convert it for readability to an enum for downstream methods + int style_idx = prop_dict.GetProperty("style")->Get(); + LineStyle style; + if (style_idx < 0 || style_idx >= static_cast(LineStyle::Num_styles)) { + style = LineStyle::Solid; + } else { + style = static_cast(style_idx); + } + + auto thickness = prop_dict.GetProperty("thickness")->Get(); + auto length = prop_dict.GetProperty("length")->Get(); + auto space = prop_dict.GetProperty("space")->Get(); + Rocket::Core::Colourb color = prop_dict.GetProperty("color")->Get(); + bool use_element_color = prop_dict.GetProperty("color-setting")->Get(); + + return new DecoratorUnderline(thickness, style, length, space, color, use_element_color); +} + +void UnderlineDecoratorInstancer::ReleaseDecorator(Rocket::Core::Decorator* decorator) +{ + delete decorator; +} + +void UnderlineDecoratorInstancer::Release() +{ + delete this; +} + +// BorderDecoratorInstancer Implementation +BorderDecoratorInstancer::BorderDecoratorInstancer() +{ + // Register border properties + RegisterProperty("thickness", "1.0").AddParser("number"); + RegisterProperty("length", "10.0").AddParser("number"); + RegisterProperty("length-h", "10.0").AddParser("number"); + RegisterProperty("length-v", "10.0").AddParser("number"); + RegisterProperty("color", "white").AddParser("color"); + + RegisterShorthand("shorthand", "thickness, length-h, length-v"); +} + +Rocket::Core::Decorator* BorderDecoratorInstancer::InstanceDecorator(const Rocket::Core::String& /*name*/, + const Rocket::Core::PropertyDictionary& prop_dict) +{ + auto thickness = prop_dict.GetProperty("thickness")->Get(); + + // Check if horizontal or vertical lengths are defined, and fallback to "length" if needed + float length_h = prop_dict.GetProperty("length-h") + ? prop_dict.GetProperty("length-h")->Get() + : prop_dict.GetProperty("length")->Get(); + float length_v = prop_dict.GetProperty("length-v") + ? prop_dict.GetProperty("length-v")->Get() + : prop_dict.GetProperty("length")->Get(); + + // Fetch the border color, defaults to white if not set + Rocket::Core::Colourb color = prop_dict.GetProperty("color")->Get(); + + return new DecoratorCornerBorders(thickness, length_h, length_v, color); +} + + +void BorderDecoratorInstancer::ReleaseDecorator(Rocket::Core::Decorator* decorator) +{ + delete decorator; +} + +void BorderDecoratorInstancer::Release() +{ + delete this; +} + +} // namespace decorators +} // namespace scpui diff --git a/code/scpui/RocketDecoratorsInstancer.h b/code/scpui/RocketDecoratorsInstancer.h new file mode 100644 index 00000000000..833102e8224 --- /dev/null +++ b/code/scpui/RocketDecoratorsInstancer.h @@ -0,0 +1,43 @@ +#pragma once + +#include "RocketDecorators.h" + +#include + +namespace scpui { +namespace decorators { + +class UnderlineDecoratorInstancer : public Rocket::Core::DecoratorInstancer { + public: + UnderlineDecoratorInstancer(); + ~UnderlineDecoratorInstancer() override = default; + + // Instances the underline decorator + Rocket::Core::Decorator* InstanceDecorator(const Rocket::Core::String& name, + const Rocket::Core::PropertyDictionary& properties) override; + + // Releases the underline decorator + void ReleaseDecorator(Rocket::Core::Decorator* decorator) override; + + // Releases the instancer itself + void Release() override; +}; + +class BorderDecoratorInstancer : public Rocket::Core::DecoratorInstancer { + public: + BorderDecoratorInstancer(); + ~BorderDecoratorInstancer() override = default; + + // Instances the border decorator + Rocket::Core::Decorator* InstanceDecorator(const Rocket::Core::String& name, + const Rocket::Core::PropertyDictionary& properties) override; + + // Releases the border decorator + void ReleaseDecorator(Rocket::Core::Decorator* decorator) override; + + // Releases the instancer itself + void Release() override; +}; + +} // namespace decorators +} // namespace scpui diff --git a/code/scpui/rocket_ui.cpp b/code/scpui/rocket_ui.cpp index e0b935bdfc5..c12e34da79e 100644 --- a/code/scpui/rocket_ui.cpp +++ b/code/scpui/rocket_ui.cpp @@ -16,6 +16,7 @@ #include "mod_table/mod_table.h" #include "osapi/osapi.h" #include "scpui/IncludeNodeHandler.h" +#include "scpui/RocketDecoratorsInstancer.h" #include "scpui/RocketFileInterface.h" #include "scpui/RocketLuaSystemInterface.h" #include "scpui/RocketRenderingInterface.h" @@ -565,6 +566,18 @@ void initialize() XMLParser::RegisterNodeHandler("include", new IncludeNodeHandler())->RemoveReference(); + // Register custom underline decorator with its own instancer + Rocket::Core::DecoratorInstancer* underline_instancer = new scpui::decorators::UnderlineDecoratorInstancer(); + Rocket::Core::Factory::RegisterDecoratorInstancer("underline", underline_instancer); + + // Register custom corner-borders decorator with its own instancer + Rocket::Core::DecoratorInstancer* corner_borders_instancer = new scpui::decorators::BorderDecoratorInstancer(); + Rocket::Core::Factory::RegisterDecoratorInstancer("corner-borders", corner_borders_instancer); + + // Decrease the reference counts (as it is managed by libRocket after registration) + underline_instancer->RemoveReference(); + corner_borders_instancer->RemoveReference(); + // Setup the plugin a style sheet properties for the sound support Rocket::Core::RegisterPlugin(new SoundPlugin()); Rocket::Core::StyleSheetSpecification::RegisterParser("sound", new SoundPropertyParser()); diff --git a/code/source_groups.cmake b/code/source_groups.cmake index 6a15397581a..6797630651c 100644 --- a/code/source_groups.cmake +++ b/code/source_groups.cmake @@ -1219,6 +1219,10 @@ add_file_folder("ScpUi" scpui/IncludeNodeHandler.h scpui/rocket_ui.cpp scpui/rocket_ui.h + scpui/RocketDecorators.cpp + scpui/RocketDecorators.h + scpui/RocketDecoratorsInstancer.cpp + scpui/RocketDecoratorsInstancer.h scpui/RocketFileInterface.cpp scpui/RocketFileInterface.h scpui/RocketLuaSystemInterface.cpp From 32a71e6b5552daebb530075e011e052eb4c95d41 Mon Sep 17 00:00:00 2001 From: Daft Mugi Date: Tue, 17 Dec 2024 16:05:01 -0600 Subject: [PATCH 24/64] Add '-campaign' cmdline option to set current campaign This allows the current campaign to be set on FSO launch so that the player isn't interrupted by the "The currently active campaign cannot be found" dialog when changing mods. Example usage: fs2_open -campaign freespace -mod fsport --- code/cmdline/cmdline.cpp | 10 ++++++++ code/cmdline/cmdline.h | 1 + code/mission/missioncampaign.cpp | 39 +++++++++++++++++++++++++++++--- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/code/cmdline/cmdline.cpp b/code/cmdline/cmdline.cpp index 4ac499b0269..714ee1027a7 100644 --- a/code/cmdline/cmdline.cpp +++ b/code/cmdline/cmdline.cpp @@ -417,8 +417,10 @@ int Cmdline_no_enhanced_sound = 0; // MOD related cmdline_parm mod_arg("-mod", "List of folders to overwrite/add-to the default data", AT_STRING, true); // Cmdline_mod -- DTP modsupport +cmdline_parm campaign_arg("-campaign", "Set current campaign", AT_STRING); // Cmdline_campaign char *Cmdline_mod = NULL; //DTP for mod argument +char *Cmdline_campaign = nullptr; // for campaign argument // Multiplayer/Network related cmdline_parm almission_arg("-almission", "Autoload multiplayer mission", AT_STRING); // Cmdline_almission -- DTP for autoload Multi mission @@ -1874,6 +1876,14 @@ bool SetCmdlineParams() Cmdline_mod = modlist; } + if(campaign_arg.found()) { + Cmdline_campaign = campaign_arg.str(); + + // strip off blank space if it's there + if ( Cmdline_campaign[strlen(Cmdline_campaign)-1] == ' ' ) { + Cmdline_campaign[strlen(Cmdline_campaign)-1] = '\0'; + } + } if (fps_arg.found()) { diff --git a/code/cmdline/cmdline.h b/code/cmdline/cmdline.h index ba322e0629e..c05aa325dab 100644 --- a/code/cmdline/cmdline.h +++ b/code/cmdline/cmdline.h @@ -92,6 +92,7 @@ extern int Cmdline_no_enhanced_sound; // MOD related extern char *Cmdline_mod; // DTP for mod support +extern char *Cmdline_campaign; // for campaign support // Multiplayer/Network related extern char *Cmdline_almission; // DTP for autoload mission (for multi only) extern int Cmdline_ingamejoin; diff --git a/code/mission/missioncampaign.cpp b/code/mission/missioncampaign.cpp index 320677a1ada..91728e116ae 100644 --- a/code/mission/missioncampaign.cpp +++ b/code/mission/missioncampaign.cpp @@ -1554,14 +1554,47 @@ int mission_load_up_campaign(bool fall_back_from_current) auto pl = Player; // find best campaign to use: - // 1) last used - // 2) builtin - // 3) anything else + // 1) cmdline + // 2) last used + // 3) builtin + // 4) anything else // Note that in each step we only fall back when the error is benign, e.g. ignored or missing; // if there's some other real error with the campaign file, we report it. // Also note that if we *have* a current campaign, we shouldn't fall back *at all*, lest we repeatedly // reset what the current campaign is, *unless* we are starting a brand new session or loading a new pilot. + // cmdline... + if ( Cmdline_campaign != nullptr && strlen(Cmdline_campaign) ) { + char* campaign = Cmdline_campaign; + + // Clear cmdline value + // * Only set campaign once from cmdline. + // * Prevent subsequent load failures. + // * On success, campaign becomes "last used". + Cmdline_campaign = nullptr; + + bool has_last_used_campaign = strlen(pl->current_campaign) > 0; + bool campaign_already_set = has_last_used_campaign + && (stricmp(campaign, pl->current_campaign) == 0); + + if (has_last_used_campaign) { + mprintf(("Current campaign is '%s'\n", pl->current_campaign)); + } + + if (!campaign_already_set) { + rc = mission_campaign_load(campaign, nullptr, pl); + + if (rc == 0) { + // update pilot with the new current campaign (becomes "last used") + strcpy_s(pl->current_campaign, Campaign.filename); + mprintf(("Set current campaign to '%s'\n", campaign)); + return rc; + } else { + mprintf(("Failed to set current campaign to '%s'\n", campaign)); + } + } + } + // last used... if ( strlen(pl->current_campaign) ) { rc = mission_campaign_load(pl->current_campaign, nullptr, pl); From c7025f47e07dd55afaa2907c968650b5c529fc30 Mon Sep 17 00:00:00 2001 From: Daft Mugi Date: Fri, 20 Dec 2024 05:06:29 -0600 Subject: [PATCH 25/64] Add FSO_BUILD_WITH_OPENXR cmake option (#6477) * Windows and Linux default to ON * macOS defaults to OFF * User can override default on the command line --- CMakeLists.txt | 13 +++++++++++++ code/CMakeLists.txt | 10 +++++++--- code/graphics/opengl/gropenglopenxr.cpp | 6 +++--- code/graphics/openxr.cpp | 9 +++++---- code/graphics/openxr_internal.h | 4 ++-- lib/CMakeLists.txt | 4 ++-- 6 files changed, 32 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c1a33e53f61..3d461d5a2a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,6 +127,17 @@ OPTION(FSO_BUILD_WITH_OPENGL_DEBUG "Enables debug option for OpenGL" OFF) OPTION(FSO_BUILD_WITH_VULKAN "Enable compilation of the Vulkan renderer" OFF) +IF(NOT APPLE) + SET(OPENXR_BUILD_DEFAULT ON) +ELSE() + SET(OPENXR_BUILD_DEFAULT OFF) +ENDIF() +OPTION(FSO_BUILD_WITH_OPENXR "Build with OpenXR support" ${OPENXR_BUILD_DEFAULT}) +IF(FSO_BUILD_WITH_OPENXR AND APPLE) + MESSAGE(WARNING "FSO_BUILD_WITH_OPENXR is ON. Not supported on macOS - setting to OFF.") + SET(FSO_BUILD_WITH_OPENXR OFF CACHE BOOL "" FORCE) +ENDIF() + OPTION(FSO_BUILD_WITH_OPENXR_DEBUG "Enables debug option for OpenXR" OFF) OPTION(FSO_USE_LTO "Build using LTO (only for release builds)" ON) @@ -144,6 +155,7 @@ mark_as_advanced(FORCE FSO_BUILD_WITH_FFMPEG) mark_as_advanced(FORCE FSO_BUILD_WITH_OPENGL) mark_as_advanced(FORCE FSO_BUILD_WITH_OPENGL_DEBUG) mark_as_advanced(FORCE FSO_BUILD_WITH_VULKAN) +mark_as_advanced(FORCE FSO_BUILD_WITH_OPENXR) mark_as_advanced(FORCE FSO_BUILD_WITH_OPENXR_DEBUG) # Include cotire file from https://github.com/sakra/cotire/ @@ -233,3 +245,4 @@ message(STATUS "Release logging: ${FSO_RELEASE_LOGGING}") message(STATUS "With FFmpeg: ${FSO_BUILD_WITH_FFMPEG}") message(STATUS "With OpenGL: ${FSO_BUILD_WITH_OPENGL}") message(STATUS "With Vulkan: ${FSO_BUILD_WITH_VULKAN}") +message(STATUS "With OpenXR: ${FSO_BUILD_WITH_OPENXR}") diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index d5247164546..f6e743e7f1b 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -25,7 +25,11 @@ if (FSO_BUILD_WITH_OPENGL) endif() endif() -if(FSO_BUILD_WITH_OPENXR_DEBUG) +if(FSO_BUILD_WITH_OPENXR) + add_definitions(-DFS_OPENXR) +endif() + +if(FSO_BUILD_WITH_OPENXR_DEBUG AND FSO_BUILD_WITH_OPENXR) add_definitions(-DFS_OPENXR_DEBUG) endif() @@ -79,7 +83,7 @@ target_link_libraries(code PUBLIC hidapi::hidapi) target_link_libraries(code PUBLIC imgui) -IF(NOT APPLE) +IF(FSO_BUILD_WITH_OPENXR) target_link_libraries(code PUBLIC OpenXR::openxr_loader) target_include_directories(code PUBLIC OpenXR::Headers) ENDIF() @@ -139,4 +143,4 @@ set_target_properties(code PROPERTIES XCODE_ATTRIBUTE_STRIP_INSTALLED_PRODUCT[va set_target_properties(code PROPERTIES XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN[variant=Debug] "NO") # Prevent GCC warnings in third-party BitOp... see GitHub #4366 -suppress_file_warnings(scripting/lua/bitop/bit.c) \ No newline at end of file +suppress_file_warnings(scripting/lua/bitop/bit.c) diff --git a/code/graphics/opengl/gropenglopenxr.cpp b/code/graphics/opengl/gropenglopenxr.cpp index 44972df2ca8..2aa6a13893c 100644 --- a/code/graphics/opengl/gropenglopenxr.cpp +++ b/code/graphics/opengl/gropenglopenxr.cpp @@ -21,7 +21,7 @@ #include "graphics/opengl/ShaderProgram.h" #include "osapi/osapi.h" -#if defined __APPLE_CC__ +#ifndef FS_OPENXR //Not supported @@ -38,7 +38,7 @@ #include -#ifndef __APPLE_CC__ +#ifdef FS_OPENXR //SETUP FUNCTIONS OGL SCP_vector gr_opengl_openxr_get_extensions() { @@ -473,4 +473,4 @@ bool gr_opengl_openxr_acquire_swapchain_buffers() { return false; } bool gr_opengl_openxr_flip() { return false; } -#endif \ No newline at end of file +#endif diff --git a/code/graphics/openxr.cpp b/code/graphics/openxr.cpp index df227f561a2..e878ec7a16f 100644 --- a/code/graphics/openxr.cpp +++ b/code/graphics/openxr.cpp @@ -9,7 +9,7 @@ std::unique_ptr Stars_XRBuffer; -#ifndef __APPLE_CC__ +#ifdef FS_OPENXR #define XR_MAKE_VERSION_SHORT(major, minor, patch) \ ((((major) & 0x3ffU) << 20) | (((minor) & 0x3ffU) << 10) | ((patch) & 0x3ffU)) @@ -522,12 +522,13 @@ void openxr_start_frame() { } #else -//Stubs for Mac, as linking with OpenXR causes issues there. +// Stubs for when building without OpenXR support. +// NOTE: macOS has issues linking with OpenXR. void openxr_prepare(float hudscale) {} float openxr_preinit(float req_ar, float scale) { - mprintf(("Cannot create OpenXR session on macOS.\n")); + mprintf(("Cannot create OpenXR session. Not built with OpenXR support.\n")); return 0.0f; } @@ -545,4 +546,4 @@ bool openxr_requested() { return false; } OpenXRTrackingInfo openxr_start_stereo_frame() { return OpenXRTrackingInfo{}; } -#endif \ No newline at end of file +#endif diff --git a/code/graphics/openxr_internal.h b/code/graphics/openxr_internal.h index a8f261509c9..b9c3174a0e9 100644 --- a/code/graphics/openxr_internal.h +++ b/code/graphics/openxr_internal.h @@ -7,7 +7,7 @@ #include #include -#ifndef __APPLE_CC__ +#ifdef FS_OPENXR #include #include @@ -51,4 +51,4 @@ tl::optional> openxr_callExtensionFunc return func(std::forward(args)...); } -#endif \ No newline at end of file +#endif diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 5e00a549da3..d6321cb6316 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -61,6 +61,6 @@ add_subdirectory(hidapi) ADD_SUBDIRECTORY(imgui) -if(NOT APPLE) +if(FSO_BUILD_WITH_OPENXR) add_subdirectory(openxr) -endif() \ No newline at end of file +endif() From 6ca9555218723333cacb3a18939cf0ba3b5d4248 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Fri, 20 Dec 2024 08:04:32 -0600 Subject: [PATCH 26/64] Upgrade head ani handling (#6338) * null -> nullptr * always loop head anis feature * Add newer head ani choice method * properly handle death messages --- code/hud/hudmessage.cpp | 56 +++++++++++++++++----- code/mission/missionmessage.cpp | 84 +++++++++++++++++++++++---------- code/mission/missionmessage.h | 2 + 3 files changed, 104 insertions(+), 38 deletions(-) diff --git a/code/hud/hudmessage.cpp b/code/hud/hudmessage.cpp index 8bed5a9d39b..904eb760cc9 100644 --- a/code/hud/hudmessage.cpp +++ b/code/hud/hudmessage.cpp @@ -1140,7 +1140,7 @@ HudGauge(HUD_OBJECT_TALKING_HEAD, HUD_TALKING_HEAD, false, true, (VM_DEAD_VIEW | void HudGaugeTalkingHead::initialize() { - head_anim = NULL; + head_anim = nullptr; msg_id = -1; HudGauge::initialize(); @@ -1182,8 +1182,30 @@ void HudGaugeTalkingHead::render(float frametime) return; } - if(msg_id != -1 && head_anim != NULL) { - if(!head_anim->done_playing) { + if(msg_id != -1) { + + // Get our message data. Current max is 2 so this shouldn't be much of a performance hit + pmessage* cur_message = nullptr; + for (int j = 0; j < Num_messages_playing; ++j) { + if (Playing_messages[j].id == msg_id) { + cur_message = &Playing_messages[j]; + break; // only one head ani plays at a time + } + } + + // Should we play a frame or not? + bool play_frame = false; + if (head_anim != nullptr) { + // New method loops until the message audio is done + if (Always_loop_head_anis && cur_message != nullptr && (cur_message->builtin_type != MESSAGE_WINGMAN_SCREAM)) { + play_frame = cur_message->play_anim; + // Old method only plays once + } else if (head_anim != nullptr) { + play_frame = !head_anim->done_playing; + } + } + + if (play_frame) { // draw frame // hud_set_default_color(); setGaugeColor(); @@ -1235,24 +1257,34 @@ void HudGaugeTalkingHead::render(float frametime) // draw title renderString(position[0] + Header_offsets[0], position[1] + Header_offsets[1], XSTR("message", 217)); } else { - for (int j = 0; j < Num_messages_playing; ++j) { - if (Playing_messages[j].id == msg_id) { - Playing_messages[j].play_anim = false; - break; // only one head ani plays at a time + if (cur_message == nullptr) { + for (int j = 0; j < Num_messages_playing; ++j) { + if (Playing_messages[j].id == msg_id) { + Playing_messages[j].play_anim = false; + break; // only one head ani plays at a time + } } + } else { + cur_message->play_anim = false; } msg_id = -1; // allow repeated messages to display a new head ani - head_anim = NULL; // Nothing to see here anymore, move along + head_anim = nullptr; // Nothing to see here anymore, move along } } // check playing messages to see if we have any messages with talking animations that need to be created. for (int i = 0; i < Num_messages_playing; i++ ) { if(Playing_messages[i].play_anim && Playing_messages[i].id != msg_id ) { msg_id = Playing_messages[i].id; - if (Playing_messages[i].anim_data) - head_anim = Playing_messages[i].anim_data; - else - head_anim = NULL; + if (Playing_messages[i].anim_data) { + head_anim = Playing_messages[i].anim_data; + // If we're using the newer setup then choose a random starting frame + if (Use_newer_head_ani_suffix && (Playing_messages[i].builtin_type != MESSAGE_WINGMAN_SCREAM) && head_anim->num_frames > 0) { + int random_frame = rand() % head_anim->num_frames; // Generate random frame to start with + head_anim->anim_time = (float)random_frame * head_anim->total_time / head_anim->num_frames; + } + } else { + head_anim = nullptr; + } return; } diff --git a/code/mission/missionmessage.cpp b/code/mission/missionmessage.cpp index ec537ada3b6..7a2e1c4aaee 100644 --- a/code/mission/missionmessage.cpp +++ b/code/mission/missionmessage.cpp @@ -42,6 +42,8 @@ #include "utils/Random.h" bool Allow_generic_backup_messages = false; +bool Always_loop_head_anis = false; +bool Use_newer_head_ani_suffix = false; float Command_announces_enemy_arrival_chance = 0.25; #define DEFAULT_MOOD 0 @@ -757,6 +759,12 @@ void parse_msgtbl(const char* filename) // now we can start parsing parse_custom_message_types(false); // Already parsed, so skip it if (optional_string("#Message Settings")) { + if (optional_string("$Always loop head anis:")) { + stuff_boolean(&Always_loop_head_anis); + } + if (optional_string("$Use newer head ani suffix features:")) { + stuff_boolean(&Use_newer_head_ani_suffix); + } if (optional_string("$Allow Any Ship To Send Backup Messages:")) { stuff_boolean(&Allow_generic_backup_messages); } @@ -1392,41 +1400,63 @@ void message_play_anim( message_q *q ) // assigned, the logic will drop down below like it's supposed to if (persona_index >= 0) { - if ( Personas[persona_index].flags & (PERSONA_FLAG_WINGMAN | PERSONA_FLAG_SUPPORT) ) { - // get a random head - if ( q->builtin_type == MESSAGE_WINGMAN_SCREAM ) { - rand_index = MAX_WINGMAN_HEADS; // [0,MAX) are regular heads; MAX is always death head - is_death_scream = 1; + if (!Use_newer_head_ani_suffix) { + if (Personas[persona_index].flags & (PERSONA_FLAG_WINGMAN | PERSONA_FLAG_SUPPORT)) { + // get a random head + if (q->builtin_type == MESSAGE_WINGMAN_SCREAM) { + rand_index = MAX_WINGMAN_HEADS; // [0,MAX) are regular heads; MAX is always death head + is_death_scream = 1; + } else { + rand_index = ((int)Missiontime % MAX_WINGMAN_HEADS); + } + strcpy_s(temp, ani_name); + sprintf_safe(ani_name, "%s%c", temp, 'a' + rand_index); + subhead_selected = TRUE; + } else if (Personas[persona_index].flags & (PERSONA_FLAG_COMMAND | PERSONA_FLAG_LARGE)) { + // get a random head + // Goober5000 - *sigh*... if mission designers assign a command persona + // to a wingman head, they risk having the death ani play + if (!strnicmp(ani_name, "Head-TP", 7) || !strnicmp(ani_name, "Head-VP", 7)) { + mprintf(("message '%s' incorrectly assigns a command/largeship persona to a wingman animation!\n", m->name)); + rand_index = ((int)Missiontime % MAX_WINGMAN_HEADS); + } else { + rand_index = ((int)Missiontime % MAX_COMMAND_HEADS); + } + + strcpy_s(temp, ani_name); + sprintf_safe(ani_name, "%s%c", temp, 'a' + rand_index); + subhead_selected = TRUE; } else { - rand_index = ((int) Missiontime % MAX_WINGMAN_HEADS); + mprintf(("message '%s' uses an unrecognized persona type\n", m->name)); } - strcpy_s(temp, ani_name); - sprintf_safe(ani_name, "%s%c", temp, 'a'+rand_index); - subhead_selected = TRUE; - } else if ( Personas[persona_index].flags & (PERSONA_FLAG_COMMAND | PERSONA_FLAG_LARGE) ) { - // get a random head - // Goober5000 - *sigh*... if mission designers assign a command persona - // to a wingman head, they risk having the death ani play - if ( !strnicmp(ani_name, "Head-TP", 7) || !strnicmp(ani_name, "Head-VP", 7) ) { - mprintf(("message '%s' incorrectly assigns a command/largeship persona to a wingman animation!\n", m->name)); - rand_index = ((int) Missiontime % MAX_WINGMAN_HEADS); + } else { + // Explicitely allow death anims for large ships now. Only command can't have a death message. + if (!(Personas[persona_index].flags & PERSONA_FLAG_COMMAND) && q->builtin_type == MESSAGE_WINGMAN_SCREAM) { + strcpy_s(temp, ani_name); + sprintf_safe(ani_name, "%s-death", temp); + subhead_selected = TRUE; } else { - rand_index = ((int) Missiontime % MAX_COMMAND_HEADS); + strcpy_s(temp, ani_name); + sprintf_safe(ani_name, "%s-reg", temp); + subhead_selected = TRUE; } - + } + } else { + // In suffix mode if we don't have a persona AND the anim doesn't exist then append -reg + if (Use_newer_head_ani_suffix) { strcpy_s(temp, ani_name); - sprintf_safe(ani_name, "%s%c", temp, 'a'+rand_index); + sprintf_safe(ani_name, "%s-reg", temp); subhead_selected = TRUE; - } else { - mprintf(("message '%s' uses an unrecognized persona type\n", m->name)); } } if (!subhead_selected) { - // choose between a and b - rand_index = ((int) Missiontime % MAX_WINGMAN_HEADS); - strcpy_s(temp, ani_name); - sprintf_safe(ani_name, "%s%c", temp, 'a'+rand_index); + if (!Use_newer_head_ani_suffix) { + // choose between a and b + rand_index = ((int)Missiontime % MAX_WINGMAN_HEADS); + strcpy_s(temp, ani_name); + sprintf_safe(ani_name, "%s%c", temp, 'a' + rand_index); + } mprintf(("message '%s' with invalid head. Fix by assigning persona to the message.\n", m->name)); } nprintf(("Messaging", "playing head %s for %s\n", ani_name, q->who_from)); @@ -1462,7 +1492,9 @@ void message_play_anim( message_q *q ) return; } - anim_info->anim_data.direction = GENERIC_ANIM_DIRECTION_NOLOOP; + if (!Always_loop_head_anis) { + anim_info->anim_data.direction = GENERIC_ANIM_DIRECTION_NOLOOP; + } Playing_messages[Num_messages_playing].anim_data = &anim_info->anim_data; message_calc_anim_start_frame(Message_wave_duration, &anim_info->anim_data, is_death_scream); Playing_messages[Num_messages_playing].play_anim = true; diff --git a/code/mission/missionmessage.h b/code/mission/missionmessage.h index 83124295b94..e68dd76abc3 100644 --- a/code/mission/missionmessage.h +++ b/code/mission/missionmessage.h @@ -59,6 +59,8 @@ extern SCP_vector Message_waves; extern SCP_vector Builtin_moods; extern int Current_mission_mood; extern float Command_announces_enemy_arrival_chance; +extern bool Always_loop_head_anis; +extern bool Use_newer_head_ani_suffix; // Builtin messages From d81b48bd747ef1f0d29e444da0fc292a9724b995 Mon Sep 17 00:00:00 2001 From: Kestrellius <63537900+Kestrellius@users.noreply.github.com> Date: Fri, 20 Dec 2024 17:28:21 -0800 Subject: [PATCH 27/64] Weapon light bugfix (#6474) * Fixes weapon light bug. * Improves elegance. * Extends fix to missiles. * further elegance * more cleanup * optimization --- code/graphics/color.cpp | 10 ++++++++++ code/graphics/color.h | 1 + code/object/object.cpp | 13 +++++-------- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/code/graphics/color.cpp b/code/graphics/color.cpp index f959a7c2a3d..45e3601bd20 100644 --- a/code/graphics/color.cpp +++ b/code/graphics/color.cpp @@ -70,6 +70,16 @@ void hdr_color::set_rgb(int new_r, int new_g, int new_b) this->blue = i2fl(new_b) / 255.0f; } +/** + * @brief Sets RGB values from three 0.0-1.0 floats + */ +void hdr_color::set_rgb(float new_r, float new_g, float new_b) +{ + this->red = new_r; + this->green = new_g; + this->blue = new_b; +} + /** * @brief Sets RGBA values from an old style color object */ diff --git a/code/graphics/color.h b/code/graphics/color.h index 923fc3ea6c7..8b0909cbca3 100644 --- a/code/graphics/color.h +++ b/code/graphics/color.h @@ -18,6 +18,7 @@ class hdr_color{ void set_vecf(const SCP_vector& input); void set_rgb(const int new_r,const int new_g,const int new_b); + void set_rgb(const float new_r,const float new_g,const float new_b); void set_rgb(const color* const new_rgb); void set_rgb(const int* const new_rgb); diff --git a/code/object/object.cpp b/code/object/object.cpp index c5ed672e975..afd4cd1501a 100644 --- a/code/object/object.cpp +++ b/code/object/object.cpp @@ -1266,18 +1266,15 @@ void obj_move_all_post(object *objp, float frametime) // If there is no specific color set in the table, laser render weapons have a dynamic color. if (!wi->light_color_set && wi->render_type == WRT_LASER) { - // intensity is stored in the light color even if no user setting is done. - light_color = hdr_color(&wi->light_color); // Classic dynamic laser color is handled with an old color object color c; weapon_get_laser_color(&c, objp); - light_color.set_rgb(&c); - light_color.set_rgb(fl2i(i2fl(light_color.r()) * r_mult), fl2i(i2fl(light_color.g()) * g_mult), fl2i(i2fl(light_color.b()) * b_mult)); - light_color.i(light_color.i() * intensity_mult); + light_color.set_rgb((c.red/255.f) * r_mult, (c.green/255.f) * g_mult, (c.blue/255.f) * b_mult); + // intensity is stored in the light color even if no user setting is done. + light_color.i(wi->light_color.i() * intensity_mult); } else { // If not a laser then all default information needed is stored in the weapon light color - light_color = hdr_color(&wi->light_color); - light_color.set_rgb(fl2i(i2fl(light_color.r()) * r_mult), fl2i(i2fl(light_color.g()) * g_mult), fl2i(i2fl(light_color.b()) * b_mult)); + light_color.set_rgb(wi->light_color.r() * r_mult, wi->light_color.g() * g_mult, wi->light_color.b() * b_mult); } //handles both defaults and adjustments. float light_radius = wi->light_radius; @@ -1290,7 +1287,7 @@ void obj_move_all_post(object *objp, float frametime) source_radius *= 0.05f; light_radius = lp->missile_light_radius.handle(light_radius) * radius_mult; light_color.i(lp->missile_light_brightness.handle(light_color.i()) * intensity_mult); - light_color.set_rgb(fl2i(i2fl(light_color.r()) * r_mult), fl2i(i2fl(light_color.g()) * g_mult), fl2i(i2fl(light_color.b()) * b_mult)); + light_color.set_rgb(light_color.r() * r_mult, light_color.g() * g_mult, light_color.b() * b_mult); } if(light_radius > 0.0f && intensity_mult > 0.0f && light_color.i() > 0.0f) light_add_point(&objp->pos, light_radius, light_radius, &light_color, source_radius); From 440eed69d2e2159e6df92270793c5668d898c610 Mon Sep 17 00:00:00 2001 From: Daft Mugi Date: Thu, 19 Dec 2024 21:39:42 -0600 Subject: [PATCH 28/64] Ensure custom preferences path ends with path separator Example: Given: FSO_PREFERENCES_PATH='./config' fs2_open Path becomes: './config/' --- code/osapi/osapi.cpp | 65 ++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/code/osapi/osapi.cpp b/code/osapi/osapi.cpp index e7c0a49ac64..760a5e64eb7 100644 --- a/code/osapi/osapi.cpp +++ b/code/osapi/osapi.cpp @@ -32,7 +32,7 @@ namespace const char* ORGANIZATION_NAME = "HardLightProductions"; const char* APPLICATION_NAME = "FreeSpaceOpen"; - char* preferencesPath = nullptr; + SCP_string preferencesPath; bool checkedLegacyMode = false; bool legacyMode = false; @@ -41,38 +41,50 @@ namespace os::Viewport* mainViewPort = nullptr; SDL_Window* mainSDLWindow = nullptr; - const char* getPreferencesPath() + SCP_string getPreferencesPath() { // Lazily initialize the preferences path - if (!preferencesPath) { + if (preferencesPath.empty()) { //Check for a custom path set by env variable auto envPreferencesPath = getenv("FSO_PREFERENCES_PATH"); if (envPreferencesPath != nullptr && strlen(envPreferencesPath) > 0) { - preferencesPath = envPreferencesPath; + preferencesPath = SCP_string(envPreferencesPath); } else { - preferencesPath = SDL_GetPrefPath(ORGANIZATION_NAME, APPLICATION_NAME); + char* sdlPreferencesPath = SDL_GetPrefPath(ORGANIZATION_NAME, APPLICATION_NAME); + if (sdlPreferencesPath != nullptr) { + preferencesPath = SCP_string(sdlPreferencesPath); + SDL_free(sdlPreferencesPath); + sdlPreferencesPath = nullptr; + } + else { + // this section will at least tell the user if something is seriously wrong instead of just crashing without a message or debug log. + // It may crash later, especially when trying to load sound. But let's let it *try* to run in the current directory at least. + static bool sdl_is_borked_warning = false; + if (!sdl_is_borked_warning) { + ReleaseWarning(LOCATION, "%s\n\nSDL and Windows are unable to get the preferred path for the reason above. " + "Installing FSO, its executables and DLLs in another non-protected folder may fix the issue.\n\n" + "You may experience issues if you continue playing, and FSO may crash. Please report this error if it persists.\n\n" + "Report at www.hard-light.net or the hard-light discord.", SDL_GetError()); + sdl_is_borked_warning = true; + } + } } - // this section will at least tell the user if something is seriously wrong instead of just crashing without a message or debug log. - // It may crash later, especially when trying to load sound. But let's let it *try* to run in the current directory at least. - if (preferencesPath == nullptr) { - static bool sdl_is_borked_warning = false; - if (!sdl_is_borked_warning) { - ReleaseWarning(LOCATION, "%s\n\nSDL and Windows are unable to get the preferred path for the reason above. " - "Installing FSO, its executables and DLLs in another non-protected folder may fix the issue.\n\n" - "You may experience issues if you continue playing, and FSO may crash. Please report this error if it persists.\n\n" - "Report at www.hard-light.net or the hard-light discord.", SDL_GetError()); - sdl_is_borked_warning = true; - } - // No preferences path, try current directory. + // No preferences path, try current directory. + if (preferencesPath.empty()) { Cmdline_portable_mode = true; return "." DIR_SEPARATOR_STR; } + + // Ensure path ends with a path separator (slash) + if (!preferencesPath.empty() && (preferencesPath.back() != DIR_SEPARATOR_CHAR)) { + preferencesPath += DIR_SEPARATOR_CHAR; + } #ifdef WIN32 try { - auto current = preferencesPath; - const auto prefPathEnd = preferencesPath + strlen(preferencesPath); + auto current = preferencesPath.begin(); + const auto prefPathEnd = preferencesPath.end(); while (current != prefPathEnd) { const auto cp = utf8::next(current, prefPathEnd); if (cp > 127) { @@ -81,12 +93,12 @@ namespace const auto invalid_end = current; static bool force_portable_warning = false; if (!force_portable_warning) { - utf8::prior(current, preferencesPath); + utf8::prior(current, preferencesPath.begin()); ReleaseWarning(LOCATION, "Determined the preferences path as \"%s\". That path is not supported since it " "contains a Unicode character (%s). Using portable mode. Set -portable_mode in " "the commandline to avoid this message in the future.", - preferencesPath, std::string(current, invalid_end).c_str()); + preferencesPath.c_str(), std::string(current, invalid_end).c_str()); force_portable_warning = true; } Cmdline_portable_mode = true; @@ -94,13 +106,13 @@ namespace } } } catch (const std::exception& e) { - Error(LOCATION, "UTF-8 error while checking the preferences path \"%s\": %s", preferencesPath, + Error(LOCATION, "UTF-8 error while checking the preferences path \"%s\": %s", preferencesPath.c_str(), e.what()); } #endif } - if (preferencesPath) { + if (!preferencesPath.empty()) { return preferencesPath; } else { @@ -567,7 +579,7 @@ bool os_is_legacy_mode() if (legacyMode) { // Print a message for the people running it from the terminal fprintf(stdout, "FSO is running in legacy config mode. Please either update your launcher or" - " copy the configuration and pilot files to '%s' for better future compatibility.\n", getPreferencesPath()); + " copy the configuration and pilot files to '%s' for better future compatibility.\n", getPreferencesPath().c_str()); } checkedLegacyMode = true; @@ -584,11 +596,6 @@ void os_deinit() // Free the view ports os::closeAllViewports(); - if (preferencesPath) { - SDL_free(preferencesPath); - preferencesPath = nullptr; - } - SDL_Quit(); } From c20ec965c0a6f85a62fba6dc8407cdd7ad51a232 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Sat, 21 Dec 2024 12:43:41 -0500 Subject: [PATCH 29/64] fix a few bugs in the Vasudan heads cheat (#6426) There were several bugs and design issues in the Vasudan headz cheat, probably due to being coded in a hurry right before release: 1. Although the cheat activated on the main hall, it was not immediately apparent, requiring the player to switch screens and come back 2. The detection of whether the main hall is Vasudan did not work after the cheat was activated, due to the background filename changing (this one is actually not Volition's fault) 3. The implementation of the cheat was actually in the wrong section of code, resulting in some confusion These are now fixed. The cheat can be enabled or disabled from one function, the graphics are reloaded properly, and the main hall detection includes the cheat-active version of the main hall. Also, the door sounds have been slightly refactored to use a `std::pair` rather than a vector, since there are exactly two sounds, an open and a close. --- code/menuui/mainhallmenu.cpp | 132 +++++++++++++++++++++++++++-------- code/menuui/mainhallmenu.h | 2 +- 2 files changed, 102 insertions(+), 32 deletions(-) diff --git a/code/menuui/mainhallmenu.cpp b/code/menuui/mainhallmenu.cpp index ec5734c0ca0..afa7367879b 100644 --- a/code/menuui/mainhallmenu.cpp +++ b/code/menuui/mainhallmenu.cpp @@ -70,9 +70,6 @@ static int Main_hall_music_index = -1; bool Main_hall_poll_key = true; -int Vasudan_funny = 0; -int Vasudan_funny_plate = -1; - SCP_string Main_hall_cheat = ""; // ---------------------------------------------------------------------------- @@ -540,29 +537,6 @@ void main_hall_init(const SCP_string &main_hall_name) // init tooltip shader // nearly black gr_create_shader(&Main_hall_tooltip_shader, 5, 5, 5, 168); - // are we funny? - if (Vasudan_funny && main_hall_is_vasudan()) { - if (!stricmp(Main_hall->bitmap.c_str(), "vhall")) { - Main_hall->door_sounds.at(OPTIONS_REGION).at(0) = InterfaceSounds::VASUDAN_BUP; - Main_hall->door_sounds.at(OPTIONS_REGION).at(1) = InterfaceSounds::VASUDAN_BUP; - - // set head anim. hehe - Main_hall->door_anim_name.at(OPTIONS_REGION) = "vhallheads"; - - // set the background - Main_hall->bitmap = "vhallhead"; - } else if (!stricmp(Main_hall->bitmap.c_str(), "2_vhall")) { - Main_hall->door_sounds.at(OPTIONS_REGION).at(0) = InterfaceSounds::VASUDAN_BUP; - Main_hall->door_sounds.at(OPTIONS_REGION).at(1) = InterfaceSounds::VASUDAN_BUP; - - // set head anim. hehe - Main_hall->door_anim_name.at(OPTIONS_REGION) = "2_vhallheads"; - - // set the background - Main_hall->bitmap = "2_vhallhead"; - } - } - Main_hall_bitmap_w = -1; Main_hall_bitmap_h = -1; @@ -1488,7 +1462,7 @@ void main_hall_mouse_release_region(int region) snd_stop(sound_pair->second); } - auto sound = Main_hall->door_sounds.at(region).at(1); + auto sound = Main_hall->door_sounds.at(region).second; if (sound.isValid()) { @@ -1536,7 +1510,7 @@ void main_hall_mouse_grab_region(int region) } - auto sound = Main_hall->door_sounds.at(region).at(0); + auto sound = Main_hall->door_sounds.at(region).first; if (sound.isValid()) { @@ -2616,8 +2590,13 @@ void parse_one_main_hall(bool replace, int num_resolutions, int &hall_idx, int & } for (int idx = base_num; idx < m->num_door_animations; idx++) { + SCP_vector sounds; // door open and close sounds - parse_iface_sound_list("+Door sounds:", m->door_sounds.at(idx), "+Door sounds:", true); + parse_iface_sound_list("+Door sounds:", sounds, "+Door sounds:", true); + if (sounds.size() >= 2) { + m->door_sounds.at(idx).first = sounds.at(0); + m->door_sounds.at(idx).second = sounds.at(1); + } } for (int idx = base_num; idx < m->num_door_animations; idx++) { @@ -2737,12 +2716,102 @@ void parse_one_main_hall(bool replace, int num_resolutions, int &hall_idx, int & } } +// unload the current background and reload whatever the new one is +void main_hall_reload_background() +{ + // see main_hall_close() and main_hall_init() + + if (Main_hall_bitmap >= 0) + bm_release(Main_hall_bitmap); + + Main_hall_bitmap = bm_load(Main_hall->bitmap); + + if (Main_hall_bitmap < 0) + nprintf(("General", "WARNING! Couldn't load main hall background bitmap %s\n", Main_hall->bitmap.c_str())); +} + +// unload the current door animation and reload whatever the new one is +void main_hall_reload_door(int region) +{ + // see main_hall_close() and main_hall_init() + + auto &door_anim = Main_hall_door_anim.at(region); + auto &door_anim_name = Main_hall->door_anim_name.at(region); + + // save info for current animation + auto bg_type = door_anim.ani.bg_type; + int current_frame = door_anim.current_frame; + float anim_time = door_anim.anim_time; + auto direction = door_anim.direction; + + if (door_anim.num_frames > 0) + generic_anim_unload(&door_anim); + + generic_anim_init(&door_anim, door_anim_name); + door_anim.ani.bg_type = bg_type; + + if (generic_anim_stream(&door_anim) < 0) + nprintf(("General", "WARNING!, Could not load door anim %s in main hall\n", door_anim_name.c_str())); + + door_anim.current_frame = current_frame; + door_anim.anim_time = anim_time; + door_anim.direction = direction; +} + /** * Make the vasudan main hall funny */ void main_hall_vasudan_funny() { - Vasudan_funny = 1; + if (!Main_hall || !main_hall_is_vasudan()) + return; + + static bool Vasudan_funny = false; + static SCP_string serious_bitmap; + static SCP_vector> serious_door_info; + + Vasudan_funny = !Vasudan_funny; + + if (Vasudan_funny) { + // save stuff so we can restore it later + serious_bitmap = Main_hall->bitmap; + serious_door_info.clear(); + serious_door_info.emplace_back(OPTIONS_REGION, Main_hall->door_anim_name.at(OPTIONS_REGION), Main_hall->door_sounds.at(OPTIONS_REGION).first, Main_hall->door_sounds.at(OPTIONS_REGION).second); + + if (!stricmp(Main_hall->bitmap.c_str(), "vhall")) { + Main_hall->door_sounds.at(OPTIONS_REGION).first = InterfaceSounds::VASUDAN_BUP; + Main_hall->door_sounds.at(OPTIONS_REGION).second = InterfaceSounds::VASUDAN_BUP; + + // set head anim. hehe + Main_hall->door_anim_name.at(OPTIONS_REGION) = "vhallheads"; + + // set the background + Main_hall->bitmap = "vhallhead"; + } else if (!stricmp(Main_hall->bitmap.c_str(), "2_vhall")) { + Main_hall->door_sounds.at(OPTIONS_REGION).first = InterfaceSounds::VASUDAN_BUP; + Main_hall->door_sounds.at(OPTIONS_REGION).second = InterfaceSounds::VASUDAN_BUP; + + // set head anim. hehe + Main_hall->door_anim_name.at(OPTIONS_REGION) = "2_vhallheads"; + + // set the background + Main_hall->bitmap = "2_vhallhead"; + } + } else { + // time to get serious + Main_hall->bitmap = serious_bitmap; + for (const auto &info : serious_door_info) { + int door_region = std::get<0>(info); + Main_hall->door_anim_name.at(door_region) = std::get<1>(info); + Main_hall->door_sounds.at(door_region).first = std::get<2>(info); + Main_hall->door_sounds.at(door_region).second = std::get<3>(info); + } + } + + // reload anything we changed, whether on or off + main_hall_reload_background(); + for (const auto &info : serious_door_info) + main_hall_reload_door(std::get<0>(info)); } /** @@ -2758,7 +2827,8 @@ bool main_hall_is_vasudan(const main_hall_defines *hall) return false; } - return !stricmp(hall->bitmap.c_str(), "vhall") || !stricmp(hall->bitmap.c_str(), "2_vhall"); + return !stricmp(hall->bitmap.c_str(), "vhall") || !stricmp(hall->bitmap.c_str(), "2_vhall") + || !stricmp(hall->bitmap.c_str(), "vhallhead") || !stricmp(hall->bitmap.c_str(), "2_vhallhead"); // if the cheat is active } /** diff --git a/code/menuui/mainhallmenu.h b/code/menuui/mainhallmenu.h index fe38f6cec28..319b516d551 100644 --- a/code/menuui/mainhallmenu.h +++ b/code/menuui/mainhallmenu.h @@ -141,7 +141,7 @@ class main_hall_defines SCP_vector > door_anim_coords; // sounds for each region (open/close) - SCP_vector > door_sounds; + SCP_vector> door_sounds; // pan values for the door sounds SCP_vector door_sound_pan; From 2375ea380e1e6c5959a154df13aeb346106f62e6 Mon Sep 17 00:00:00 2001 From: Kestrellius <63537900+Kestrellius@users.noreply.github.com> Date: Sat, 21 Dec 2024 19:09:10 -0800 Subject: [PATCH 30/64] Cleans up weapon light casting. --- code/graphics/color.cpp | 33 +++++++++++++++++++++++---------- code/graphics/color.h | 3 ++- code/object/object.cpp | 38 +++++++++++++++++++++----------------- 3 files changed, 46 insertions(+), 28 deletions(-) diff --git a/code/graphics/color.cpp b/code/graphics/color.cpp index 45e3601bd20..48ad664d05a 100644 --- a/code/graphics/color.cpp +++ b/code/graphics/color.cpp @@ -70,16 +70,6 @@ void hdr_color::set_rgb(int new_r, int new_g, int new_b) this->blue = i2fl(new_b) / 255.0f; } -/** - * @brief Sets RGB values from three 0.0-1.0 floats - */ -void hdr_color::set_rgb(float new_r, float new_g, float new_b) -{ - this->red = new_r; - this->green = new_g; - this->blue = new_b; -} - /** * @brief Sets RGBA values from an old style color object */ @@ -97,6 +87,29 @@ void hdr_color::set_rgb(const int* const new_rgb) this->set_rgb(new_rgb[0], new_rgb[1], new_rgb[2]); } +/** + * @brief Sets RGBAI values from five 0.0-1.0 floats + */ +void hdr_color::set_rgbai(float new_r, float new_g, float new_b, float new_a, float new_i) +{ + this->red = new_r; + this->green = new_g; + this->blue = new_b; + this->alpha = new_a; + this->intensity = new_i; +} + +/** + * @brief Multiplies RGBAI values with five 0.0-1.0 floats + */ +void hdr_color::multiply_rgbai(float r_mult, float g_mult, float b_mult, float a_mult, float i_mult) +{ + this->red *= r_mult; + this->green *= g_mult; + this->blue *= b_mult; + this->alpha *= a_mult; + this->intensity *= i_mult; +} /** * @brief retreives unmultiplied 0.0f-1.0f color component. diff --git a/code/graphics/color.h b/code/graphics/color.h index 8b0909cbca3..386c0f53fba 100644 --- a/code/graphics/color.h +++ b/code/graphics/color.h @@ -18,9 +18,10 @@ class hdr_color{ void set_vecf(const SCP_vector& input); void set_rgb(const int new_r,const int new_g,const int new_b); - void set_rgb(const float new_r,const float new_g,const float new_b); void set_rgb(const color* const new_rgb); void set_rgb(const int* const new_rgb); + void set_rgbai(const float new_r,const float new_g,const float new_b,const float new_a = 1.f,const float new_i = 1.f); + void multiply_rgbai(const float r_mult,const float g_mult,const float b_mult,const float a_mult = 1.f,const float i_mult = 1.f); float r() const; float r(const float in); diff --git a/code/object/object.cpp b/code/object/object.cpp index afd4cd1501a..bc22037792c 100644 --- a/code/object/object.cpp +++ b/code/object/object.cpp @@ -1264,31 +1264,35 @@ void obj_move_all_post(object *objp, float frametime) float g_mult = wi->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LIGHT_G_MULT, *wp, &wp->modular_curves_instance); float b_mult = wi->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LIGHT_B_MULT, *wp, &wp->modular_curves_instance); + float source_radius = objp->radius; + float light_radius; + float light_brightness; + + // Handle differing adjustments depending on weapon type. + if (wi->render_type == WRT_LASER) { + light_radius = lp->laser_light_radius.handle(wi->light_radius) * radius_mult; + // intensity is stored in the light color even if no user setting is done. + light_brightness = lp->laser_light_brightness.handle(wi->light_color.i()); + } else { + // Missiles should typically not be treated as lights for their whole radius. TODO: make configurable. + source_radius *= 0.05f; + light_radius = lp->missile_light_radius.handle(wi->light_radius) * radius_mult; + light_brightness = lp->missile_light_brightness.handle(wi->light_color.i()); + } + // If there is no specific color set in the table, laser render weapons have a dynamic color. if (!wi->light_color_set && wi->render_type == WRT_LASER) { // Classic dynamic laser color is handled with an old color object color c; weapon_get_laser_color(&c, objp); - light_color.set_rgb((c.red/255.f) * r_mult, (c.green/255.f) * g_mult, (c.blue/255.f) * b_mult); - // intensity is stored in the light color even if no user setting is done. - light_color.i(wi->light_color.i() * intensity_mult); + light_color.set_rgbai((c.red/255.f), (c.green/255.f), (c.blue/255.f), 1.f, light_brightness); } else { // If not a laser then all default information needed is stored in the weapon light color - light_color.set_rgb(wi->light_color.r() * r_mult, wi->light_color.g() * g_mult, wi->light_color.b() * b_mult); - } - //handles both defaults and adjustments. - float light_radius = wi->light_radius; - float source_radius = objp->radius; - if (wi->render_type == WRT_LASER) { - light_radius = lp->laser_light_radius.handle(light_radius) * radius_mult; - light_color.i(lp->laser_light_brightness.handle(light_color.i()) * intensity_mult); - } else { - //Missiles should typically not be treated as lights for their whole radius. TODO: make configurable. - source_radius *= 0.05f; - light_radius = lp->missile_light_radius.handle(light_radius) * radius_mult; - light_color.i(lp->missile_light_brightness.handle(light_color.i()) * intensity_mult); - light_color.set_rgb(light_color.r() * r_mult, light_color.g() * g_mult, light_color.b() * b_mult); + light_color.set_rgbai(wi->light_color.r(), wi->light_color.g(), wi->light_color.b(), 1.f, light_brightness); } + + light_color.multiply_rgbai(r_mult, g_mult, b_mult, 1.f, intensity_mult); + if(light_radius > 0.0f && intensity_mult > 0.0f && light_color.i() > 0.0f) light_add_point(&objp->pos, light_radius, light_radius, &light_color, source_radius); } From 642ff82833de8742fbdd21322c7630b26dd01924 Mon Sep 17 00:00:00 2001 From: Daft Mugi Date: Sat, 21 Dec 2024 02:19:49 -0600 Subject: [PATCH 31/64] Add FSO_BUILD_WITH_DISCORD cmake option --- CMakeLists.txt | 4 ++++ code/CMakeLists.txt | 5 ++++- code/libs/discord/discord.cpp | 18 ++++++++++++++++++ code/mod_table/mod_table.cpp | 2 ++ lib/CMakeLists.txt | 4 +++- 5 files changed, 31 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d461d5a2a5..fd723372ffa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -122,6 +122,8 @@ option(FSO_RELEASE_LOGGING "Enable logging output for release builds" OFF) OPTION(FSO_BUILD_WITH_FFMPEG "Enable the usage of FFmpeg for sound and custscenes" ON) +OPTION(FSO_BUILD_WITH_DISCORD "Build with Discord support" ON) + OPTION(FSO_BUILD_WITH_OPENGL "Enable compilation of the OpenGL renderer" ON) OPTION(FSO_BUILD_WITH_OPENGL_DEBUG "Enables debug option for OpenGL" OFF) @@ -152,6 +154,7 @@ mark_as_advanced(FORCE FSO_INSTALL_DEBUG_FILES) mark_as_advanced(FORCE ENABLE_COTIRE) mark_as_advanced(FORCE FSO_RELEASE_LOGGING) mark_as_advanced(FORCE FSO_BUILD_WITH_FFMPEG) +mark_as_advanced(FORCE FSO_BUILD_WITH_DISCORD) mark_as_advanced(FORCE FSO_BUILD_WITH_OPENGL) mark_as_advanced(FORCE FSO_BUILD_WITH_OPENGL_DEBUG) mark_as_advanced(FORCE FSO_BUILD_WITH_VULKAN) @@ -243,6 +246,7 @@ message(STATUS "Building qtFRED: ${FSO_BUILD_QTFRED}") message(STATUS "Fatal warnings: ${FSO_FATAL_WARNINGS}") message(STATUS "Release logging: ${FSO_RELEASE_LOGGING}") message(STATUS "With FFmpeg: ${FSO_BUILD_WITH_FFMPEG}") +message(STATUS "With Discord: ${FSO_BUILD_WITH_DISCORD}") message(STATUS "With OpenGL: ${FSO_BUILD_WITH_OPENGL}") message(STATUS "With Vulkan: ${FSO_BUILD_WITH_VULKAN}") message(STATUS "With OpenXR: ${FSO_BUILD_WITH_OPENXR}") diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index f6e743e7f1b..052ccb9e605 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -52,7 +52,10 @@ TARGET_LINK_LIBRARIES(code PUBLIC compiler) target_link_libraries(code PUBLIC md5) -target_link_libraries(code PUBLIC discord-rpc) +if (FSO_BUILD_WITH_DISCORD) + target_link_libraries(code PUBLIC discord-rpc) + add_definitions(-DWITH_DISCORD) +endif() target_link_libraries(code PUBLIC libRocket) diff --git a/code/libs/discord/discord.cpp b/code/libs/discord/discord.cpp index f40a99c2a74..fbbfa2554df 100644 --- a/code/libs/discord/discord.cpp +++ b/code/libs/discord/discord.cpp @@ -8,6 +8,8 @@ #include "parse/parselo.h" #include "playerman/player.h" +#if WITH_DISCORD + #include "discord_rpc.h" namespace { @@ -284,3 +286,19 @@ void set_presence_gameplay() } // namespace discord } // namespace libs + +#else +// Stubs for building without Discord support. +namespace libs { +namespace discord { + +void init() { + mprintf(("Cannot create Discord session. Not built with Discord support.\n")); +} +void shutdown() {} +void set_presence_string(const SCP_string &text) {} +void set_presence_gameplay() {} + +} // namespace discord +} // namespace libs +#endif diff --git a/code/mod_table/mod_table.cpp b/code/mod_table/mod_table.cpp index 06f9202e4ab..67f7e4a98a6 100644 --- a/code/mod_table/mod_table.cpp +++ b/code/mod_table/mod_table.cpp @@ -157,6 +157,7 @@ bool Dont_show_callsigns_in_escort_list; bool Fix_scripted_velocity; color Overhead_line_colors[MAX_SHIP_SECONDARY_BANKS]; +#ifdef WITH_DISCORD static auto DiscordOption __UNUSED = options::OptionBuilder("Game.Discord", std::pair{"Discord Presence", 1754}, std::pair{"Toggle Discord Rich Presence", 1755}) @@ -179,6 +180,7 @@ static auto DiscordOption __UNUSED = options::OptionBuilder("Game.Discord" return true; }) .finish(); +#endif void mod_table_set_version_flags(); diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index d6321cb6316..4255846ed2c 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -33,7 +33,9 @@ if (FSO_BUILD_WITH_FFMPEG) include(FFmpeg.cmake) endif() -add_subdirectory(discord) +if (FSO_BUILD_WITH_DISCORD) + add_subdirectory(discord) +endif() include(libRocket.cmake) From 5c737a5bf91c78ce9cfd39be7ba52a5e951559aa Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Sun, 22 Dec 2024 16:31:58 -0500 Subject: [PATCH 32/64] improve campaign file extension handling (#6443) As discovered by WouterSmits, campaigns with periods in their filenames caused issues with FSO's campaign handling. Specifically, in The Babylon Project, the EA-Minbari war v1.0.fc2 campaign would not save progress because FSO expected EA-Minbari war v1.fc2 in certain places. This is because the `_splitpath()` function attempted to remove an extension from a filename that already had it removed. For most campaigns this did not matter, but WouterSmits found a campaign where it did. To solve this, there is now a new `util::get_file_part()` function to remove the path without removing the extension. This replaces `_splitpath()` for campaign files, as well as the similar `clean_filename()` and `GetFilePart()` functions in other places. All code paths were inspected for any chance of inadvertent extension adding, and Assertions were added to verify future code. The only potential problem area was `player:loadCampaignSavefile()` which now pre-removes the campaign extension if needed. --- code/exceptionhandler/exceptionhandler.cpp | 15 ++-------- code/globalincs/windebug.cpp | 14 ++------- code/menuui/readyroom.cpp | 8 ++--- code/mission/missioncampaign.cpp | 9 ++++-- code/model/modelread.cpp | 7 ++--- code/osapi/dialogs.cpp | 20 ++++--------- code/pilotfile/csg.cpp | 35 ++++++++++------------ code/scripting/api/objs/player.cpp | 13 ++++++++ code/utils/string_utils.h | 15 ++++++++++ code/windows_stub/stubs.cpp | 14 --------- 10 files changed, 65 insertions(+), 85 deletions(-) diff --git a/code/exceptionhandler/exceptionhandler.cpp b/code/exceptionhandler/exceptionhandler.cpp index 04c011e0fa0..78a1544cbf2 100644 --- a/code/exceptionhandler/exceptionhandler.cpp +++ b/code/exceptionhandler/exceptionhandler.cpp @@ -291,17 +291,6 @@ static const char *GetExceptionDescription(DWORD ExceptionCode) return "Unknown exception type"; } -static char* GetFilePart(char *source) -{ - char *result = strrchr(source, '\\'); - if (result) { - result++; - } else { - result = source; - } - return result; -} - // -------------------- // // External Functions @@ -338,7 +327,7 @@ int __cdecl RecordExceptionInfo(PEXCEPTION_POINTERS data, const char *Message) ModuleName[0] = 0; } - char *FilePart = GetFilePart(ModuleName); + char *FilePart = util::get_file_part(ModuleName); // Extract the file name portion and remove it's file extension. We'll // use that name shortly. @@ -371,7 +360,7 @@ int __cdecl RecordExceptionInfo(PEXCEPTION_POINTERS data, const char *Message) // code address, which is the same as the ModuleHandle. This can be used // to get the filename of the module that the crash happened in. if (VirtualQuery((void*)Context->Eip, &MemInfo, sizeof(MemInfo)) && GetModuleFileName((HINSTANCE)MemInfo.AllocationBase, CrashModulePathName, sizeof(CrashModulePathName)) > 0) { - CrashModuleFileName = GetFilePart(CrashModulePathName); + CrashModuleFileName = util::get_file_part(CrashModulePathName); } // Print out the beginning of the error log in a Win95 error window diff --git a/code/globalincs/windebug.cpp b/code/globalincs/windebug.cpp index 99d09821616..61baacd2699 100644 --- a/code/globalincs/windebug.cpp +++ b/code/globalincs/windebug.cpp @@ -34,6 +34,7 @@ #include "cmdline/cmdline.h" #include "parse/parselo.h" #include "debugconsole/console.h" +#include "utils/string_utils.h" #if defined( SHOW_CALL_STACK ) && defined( PDB_DEBUGGING ) # include "globalincs/mspdb_callstack.h" @@ -50,17 +51,6 @@ extern void gr_activate(int active); #endif #endif -const char *clean_filename( const char *name) -{ - const char *p = name+strlen(name)-1; - // Move p to point to first letter of EXE filename - while( (*p!='\\') && (*p!='/') && (*p!=':') && (p>= name) ) - p--; - p++; - - return p; -} - /* MSVC2005+ callstack support */ @@ -87,7 +77,7 @@ class SCP_DebugCallStack : public SCP_IDumpHandler UNREFERENCED_PARAMETER( address ); StackEntry entry; - entry.module = clean_filename( module ); + entry.module = util::get_file_part( module ); entry.symbol = symbol; m_stackFrames.push_back( entry ); } diff --git a/code/menuui/readyroom.cpp b/code/menuui/readyroom.cpp index f397534a7ac..899b427f828 100644 --- a/code/menuui/readyroom.cpp +++ b/code/menuui/readyroom.cpp @@ -1575,12 +1575,10 @@ void campaign_room_scroll_info_down() void campaign_reset(const SCP_string& campaign_file) { - auto filename = campaign_file + FS_CAMPAIGN_FILE_EXT; + // note: we do not toss all-time stats from player's performance in campaign up till now + mission_campaign_savefile_delete(campaign_file.c_str()); - // note: we do not toss all-time stats from player's performance in campaign up till now - mission_campaign_savefile_delete(filename.c_str()); - - const int load_status = mission_campaign_load(filename.c_str(), nullptr, nullptr, 1); + const int load_status = mission_campaign_load(campaign_file.c_str(), nullptr, nullptr, 1); // see if we successfully loaded this campaign if (load_status == 0) { diff --git a/code/mission/missioncampaign.cpp b/code/mission/missioncampaign.cpp index 320677a1ada..74b3bbeac0c 100644 --- a/code/mission/missioncampaign.cpp +++ b/code/mission/missioncampaign.cpp @@ -49,6 +49,7 @@ #include "ship/ship.h" #include "starfield/supernova.h" #include "ui/ui.h" +#include "utils/string_utils.h" #include "weapon/weapon.h" // campaign wasn't ended @@ -740,14 +741,16 @@ void mission_campaign_init() */ void mission_campaign_savefile_delete(const char* cfilename) { - char filename[_MAX_FNAME], base[_MAX_FNAME]; - - _splitpath( cfilename, NULL, NULL, base, NULL ); + char filename[_MAX_FNAME]; if ( Player->flags & PLAYER_FLAGS_IS_MULTI ) { return; // no such thing as a multiplayer campaign savefile } + auto base = util::get_file_part(cfilename); + // do a sanity check, but don't arbitrarily drop any extension in case the filename contains a period + Assertion(!stristr(base, FS_CAMPAIGN_FILE_EXT), "The campaign should not have an extension at this point!"); + // only support the new filename here - taylor sprintf_safe( filename, NOX("%s.%s.csg"), Player->callsign, base ); diff --git a/code/model/modelread.cpp b/code/model/modelread.cpp index a2f2178e8bb..dfa9ecbad64 100644 --- a/code/model/modelread.cpp +++ b/code/model/modelread.cpp @@ -1113,10 +1113,9 @@ void do_new_subsystem( int n_subsystems, model_subsystem *slist, int subobj_num, } } #ifndef NDEBUG - char bname[_MAX_FNAME]; + char bname[FILESPEC_LENGTH]; if ( !ss_warning_shown_mismatch) { - _splitpath(model_filename, NULL, NULL, bname, NULL); // Lets still give a comment about it and not just erase it Warning(LOCATION,"Not all subsystems in model \"%s\" have a record in ships.tbl.\nThis can cause game to crash.\n\nList of subsystems not found from table is in log file.\n", model_get(model_num)->filename ); mprintf(("Subsystem %s in model %s was not found in ships.tbl!\n", subobj_name, model_get(model_num)->filename)); @@ -1602,9 +1601,9 @@ modelread_status read_model_file_no_subsys(polymodel * pm, const char* filename, // code to get a filename to write out subsystem information for each model that // is read. This info is essentially debug stuff that is used to help get models // into the game quicker -#if 0 +#ifndef NDEBUG { - char bname[_MAX_FNAME]; + char bname[FILESPEC_LENGTH]; _splitpath(filename, NULL, NULL, bname, NULL); sprintf(debug_name, "%s.subsystems", bname); diff --git a/code/osapi/dialogs.cpp b/code/osapi/dialogs.cpp index 57235fd95f5..287880b94d6 100644 --- a/code/osapi/dialogs.cpp +++ b/code/osapi/dialogs.cpp @@ -5,6 +5,7 @@ #include "cmdline/cmdline.h" #include "graphics/2d.h" #include "scripting/ade.h" +#include "utils/string_utils.h" #include #include @@ -65,17 +66,6 @@ namespace return outStream.str(); } - const char* clean_filename(const char* filename) - { - auto separator = strrchr(filename, DIR_SEPARATOR_CHAR); - if (separator != nullptr) - { - filename = separator + 1; - } - - return filename; - } - void set_clipboard_text(const char* text) { // Make sure video is enabled @@ -106,7 +96,7 @@ namespace os void AssertMessage(const char * text, const char * filename, int linenum, const char * format, ...) { // We only want to display the file name - filename = clean_filename(filename); + filename = util::get_file_part(filename); SCP_stringstream msgStream; msgStream << "Assert: \"" << text << "\"\n"; @@ -274,7 +264,7 @@ namespace os void Error(const char * filename, int line, const char * format, ...) { SCP_string formatText; - filename = clean_filename(filename); + filename = util::get_file_part(filename); va_list args; va_start(args, format); @@ -354,7 +344,7 @@ namespace os // Actual implementation of the warning function. Used by the various warning functions void WarningImpl(const char* filename, int line, const SCP_string& text) { - filename = clean_filename(filename); + filename = util::get_file_part(filename); // output to the debug log before anything else (so that we have a complete record) mprintf(("WARNING: \"%s\" at %s:%d\n", text.c_str(), filename, line)); @@ -478,7 +468,7 @@ namespace os va_end(args); // Below is essentially a stripped down copy pasta of WarningImpl - filename = clean_filename(filename); + filename = util::get_file_part(filename); // output to the debug log before anything else (so that we have a complete record) mprintf(("INFO: \"%s\" at %s:%d\n", msg.c_str(), filename, line)); diff --git a/code/pilotfile/csg.cpp b/code/pilotfile/csg.cpp index d86167025dc..accb63dda6e 100644 --- a/code/pilotfile/csg.cpp +++ b/code/pilotfile/csg.cpp @@ -19,6 +19,7 @@ #include "ship/ship.h" #include "sound/audiostr.h" #include "stats/medals.h" +#include "utils/string_utils.h" #include "weapon/weapon.h" #define REDALERT_INTERNAL @@ -1595,8 +1596,7 @@ void pilotfile::csg_close() bool pilotfile::load_savefile(player *_p, const char *campaign) { - char base[_MAX_FNAME] = { '\0' }; - std::ostringstream buf; + SCP_string campaign_filename; if (Game_mode & GM_MULTIPLAYER) { return false; @@ -1610,18 +1610,18 @@ bool pilotfile::load_savefile(player *_p, const char *campaign) Assert( (Player_num >= 0) && (Player_num < MAX_PLAYERS) ); p = _p; - // build up filename for the savefile... - _splitpath((char*)campaign, NULL, NULL, base, NULL); - - buf << p->callsign << "." << base << ".csg"; + auto base = util::get_file_part(campaign); + // do a sanity check, but don't arbitrarily drop any extension in case the filename contains a period + Assertion(!stristr(base, FS_CAMPAIGN_FILE_EXT), "The campaign should not have an extension at this point!"); - filename = buf.str().c_str(); + // build up filename for the savefile... + sprintf(filename, NOX("%s.%s.csg"), p->callsign, base); // if campaign file doesn't exist, abort so we don't load irrelevant data - buf.str(std::string()); - buf << base << FS_CAMPAIGN_FILE_EXT; - if ( !cf_exists_full((char*)buf.str().c_str(), CF_TYPE_MISSIONS) ) { - mprintf(("CSG => Unable to find campaign file '%s'!\n", buf.str().c_str())); + campaign_filename = base; + campaign_filename += FS_CAMPAIGN_FILE_EXT; + if ( !cf_exists_full(campaign_filename.c_str(), CF_TYPE_MISSIONS) ) { + mprintf(("CSG => Unable to find campaign file '%s'!\n", campaign_filename.c_str())); return false; } @@ -1782,9 +1782,6 @@ bool pilotfile::load_savefile(player *_p, const char *campaign) bool pilotfile::save_savefile() { - char base[_MAX_FNAME] = { '\0' }; - std::ostringstream buf; - if (Game_mode & GM_MULTIPLAYER) { return false; } @@ -1797,12 +1794,12 @@ bool pilotfile::save_savefile() return false; } - // build up filename for the savefile... - _splitpath(Campaign.filename, NULL, NULL, base, NULL); - - buf << p->callsign << "." << base << ".csg"; + auto base = util::get_file_part(Campaign.filename); + // do a sanity check, but don't arbitrarily drop any extension in case the filename contains a period + Assertion(!stristr(base, FS_CAMPAIGN_FILE_EXT), "The campaign should not have an extension at this point!"); - filename = buf.str().c_str(); + // build up filename for the savefile... + sprintf(filename, NOX("%s.%s.csg"), p->callsign, base); // make sure that we can actually save this safely if (m_data_invalid) { diff --git a/code/scripting/api/objs/player.cpp b/code/scripting/api/objs/player.cpp index 269d07e1250..d3f8a586a1a 100644 --- a/code/scripting/api/objs/player.cpp +++ b/code/scripting/api/objs/player.cpp @@ -15,6 +15,7 @@ #include "scripting/api/objs/shipclass.h" #include "scripting/lua/LuaTable.h" #include "ship/ship.h" +#include "freespace.h" namespace scripting { namespace api { @@ -309,8 +310,20 @@ ADE_FUNC(loadCampaignSavefile, l_Player, "string campaign = \"\"", "Loa return ADE_RETURN_FALSE; } + char temp_buffer[MAX_FILENAME_LEN + 1]; // see current_campaign field in player class + if (savefile == nullptr) { savefile = plh->get()->current_campaign; + } else { + memset(temp_buffer, 0, sizeof(temp_buffer)); + strncpy(temp_buffer, savefile, MAX_FILENAME_LEN); + + // drop the extension if it exists + auto p = stristr(temp_buffer, FS_CAMPAIGN_FILE_EXT); + if (p) { + *p = '\0'; + savefile = temp_buffer; + } } pilotfile loader; diff --git a/code/utils/string_utils.h b/code/utils/string_utils.h index e1f3a57b034..7dd1a33b99b 100644 --- a/code/utils/string_utils.h +++ b/code/utils/string_utils.h @@ -18,4 +18,19 @@ void split_string(const SCP_string& s, char delim, Out result) std::vector split_string(const std::string& s, char delim); +// get a filename minus any leading path +template +T *get_file_part(T *path) +{ + T *p = path + strlen(path)-1; + + // Move p to point to first character of filename (check both types of dir separator) + while( (p >= path) && (*p != '\\') && (*p != '/') && (*p != ':') ) + p--; + + p++; + + return p; +} + } // namespace util diff --git a/code/windows_stub/stubs.cpp b/code/windows_stub/stubs.cpp index 02f15a5dbed..849a4b8060b 100644 --- a/code/windows_stub/stubs.cpp +++ b/code/windows_stub/stubs.cpp @@ -141,20 +141,6 @@ SCP_string dump_stacktrace() #endif } -// get a filename minus any leading path -char *clean_filename(char *name) -{ - char *p = name + strlen(name)-1; - - // Move p to point to first letter of EXE filename - while( (p > name) && (*p != '\\') && (*p != '/') && (*p != ':') ) - p--; - - p++; - - return p; -} - // retrieve the current working directory int _getcwd(char *out_buf, unsigned int len) { From 6487abd57f1ec171a643ad520ac467bc8823372f Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Sat, 30 Nov 2024 01:32:54 -0500 Subject: [PATCH 33/64] put operators in proper category; add newlines to sexp help --- code/parse/sexp.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index 74a1e120481..0dc9df40555 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -479,6 +479,9 @@ SCP_vector Operators = { { "ship-untag", OP_SHIP_UNTAG, 1, 1, SEXP_ACTION_OPERATOR, }, // Goober5000 { "set-arrival-info", OP_SET_ARRIVAL_INFO, 2, 8, SEXP_ACTION_OPERATOR, }, // Goober5000 { "set-departure-info", OP_SET_DEPARTURE_INFO, 2, 7, SEXP_ACTION_OPERATOR, }, // Goober5000 + { "alter-ship-flag", OP_ALTER_SHIP_FLAG, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma + { "alter-wing-flag", OP_ALTER_WING_FLAG, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 + { "cancel-future-waves", OP_CANCEL_FUTURE_WAVES, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // naomimyselfandi //Shields, Engines and Weapons Sub-Category { "set-weapon-energy", OP_SET_WEAPON_ENERGY, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma @@ -544,9 +547,6 @@ SCP_vector Operators = { { "ship-subsys-vanish", OP_SHIP_SUBSYS_VANISHED, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // FUBAR { "ship-subsys-ignore_if_dead", OP_SHIP_SUBSYS_IGNORE_IF_DEAD, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // FUBAR { "awacs-set-radius", OP_AWACS_SET_RADIUS, 3, 3, SEXP_ACTION_OPERATOR, }, - { "alter-ship-flag", OP_ALTER_SHIP_FLAG, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma - { "alter-wing-flag", OP_ALTER_WING_FLAG, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 - { "cancel-future-waves", OP_CANCEL_FUTURE_WAVES, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // naomimyselfandi //Cargo Sub-Category { "transfer-cargo", OP_TRANSFER_CARGO, 2, 2, SEXP_ACTION_OPERATOR, }, @@ -41733,60 +41733,60 @@ SCP_vector Sexp_help = { }, {OP_FORCE_GLIDE, "force-glide\r\n" - "\tForces a given ship into glide mode, provided it is capable of gliding. Note that the player will not be able to leave glide mode, and that a ship in glide mode cannot warp out or enter autopilot." + "\tForces a given ship into glide mode, provided it is capable of gliding. Note that the player will not be able to leave glide mode, and that a ship in glide mode cannot warp out or enter autopilot.\r\n" "Takes 2 Arguments...\r\n" "\t1:\tShip to force (ship must be in-mission)\r\n" "\t2:\tTrue to activate glide, False to deactivate\r\n" }, {OP_HUD_SET_DIRECTIVE, "hud-set-directive\r\n" - "\tSets the text of a given custom hud gauge to the provided text." + "\tSets the text of a given custom hud gauge to the provided text.\r\n" "Takes 2 Arguments...\r\n" "\t1:\tHUD Gauge name\r\n" "\t2:\tText that will be displayed. This text will be treated as directive text, meaning that references to mapped keys will be replaced with the user's preferences.\r\n" }, {OP_HUD_GAUGE_SET_ACTIVE, "hud-gauge-set-active (deprecated)\r\n" - "\tActivates or deactivates a given hud gauge. Works for custom and retail gauges." + "\tActivates or deactivates a given hud gauge. Works for custom and retail gauges.\r\n" "Takes 2 Arguments...\r\n" "\t1:\tHUD Gauge name\r\n" "\t2:\tBoolean, whether or not to display this gauge\r\n" }, {OP_HUD_CLEAR_MESSAGES, "hud-clear-messages\r\n" - "\tClears active messages displayed on the HUD." + "\tClears active messages displayed on the HUD.\r\n" "Takes no arguments\r\n" }, {OP_HUD_ACTIVATE_GAUGE_TYPE, "hud-activate-gauge-type (deprecated)\r\n" - "\tActivates or deactivates all hud gauges of a given type." + "\tActivates or deactivates all hud gauges of a given type.\r\n" "Takes 2 Arguments...\r\n" "\t1:\tGauge Type\r\n" "\t2:\tBoolean, whether or not to display this gauge\r\n" }, {OP_HUD_SET_CUSTOM_GAUGE_ACTIVE, "hud-set-custom-gauge-active\r\n" - "\tActivates or deactivates a custom hud gauge defined in hud_gauges.tbl." + "\tActivates or deactivates a custom hud gauge defined in hud_gauges.tbl.\r\n" "Takes 2 Arguments...\r\n" "\t1:\tBoolean, whether or not to display this gauge\r\n" "\tRest:\tHUD Gauge name\r\n" }, {OP_HUD_SET_BUILTIN_GAUGE_ACTIVE, "hud-set-builtin-gauge-active\r\n" - "\tActivates or deactivates a builtin hud gauge grouping." + "\tActivates or deactivates a builtin hud gauge grouping.\r\n" "Takes 2 Arguments...\r\n" "\t1:\tBoolean, whether or not to display this gauge\r\n" "\tRest:\tHUD Gauge Group name\r\n" }, {OP_HUD_FORCE_SENSOR_STATIC, "hud-force-sensor-static\r\n" - "\tActivates or deactivates hud static as if sensors are damaged." + "\tActivates or deactivates hud static as if sensors are damaged.\r\n" "Takes 1 Argument...\r\n" "\t1:\tBoolean, whether or not to enable sensor static\r\n" }, {OP_HUD_FORCE_EMP_EFFECT, "hud-force-emp-effect\r\n" - "\tActivates or deactivates emp effect for the player." + "\tActivates or deactivates emp effect for the player.\r\n" "Takes 2 or more Arguments...\r\n" "\t1:\tNumber, emp intensity (0 to 500)\r\n" "\t2:\tNumber, emp duration in milliseconds\r\n" From e58573158cc699d278e38caec57dff2ae6f52b47 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Sat, 30 Nov 2024 23:21:47 -0500 Subject: [PATCH 34/64] add set-squadron-wings, part 1 SEXP infrastructure and partial implementation, excluding refresh of ship position and HUD status for the new wings. --- code/parse/sexp.cpp | 66 +++++++++++++++++++++++++++++++++++++++++++++ code/parse/sexp.h | 1 + 2 files changed, 67 insertions(+) diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index 0dc9df40555..9d4d04afb33 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -698,6 +698,7 @@ SCP_vector Operators = { { "hud-set-max-targeting-range", OP_HUD_SET_MAX_TARGETING_RANGE, 1, 1, SEXP_ACTION_OPERATOR, }, // Goober5000 { "hud-force-sensor-static", OP_HUD_FORCE_SENSOR_STATIC, 1, 1, SEXP_ACTION_OPERATOR, }, // MjnMixael { "hud-force-emp-effect", OP_HUD_FORCE_EMP_EFFECT, 2, 3, SEXP_ACTION_OPERATOR, }, // MjnMixael + { "set-squadron-wings", OP_SET_SQUADRON_WINGS, 1, MAX_SQUADRON_WINGS, SEXP_ACTION_OPERATOR, }, // Goober5000 //Nav Sub-Category { "add-nav-waypoint", OP_NAV_ADD_WAYPOINT, 3, 4, SEXP_ACTION_OPERATOR, }, //kazan @@ -13663,6 +13664,54 @@ void multi_sexp_hud_display_gauge() } } +void sexp_set_squadron_wings(int node) +{ + // clear the ships that are currently in the squadron wings + for (int i = 0; i < MAX_SQUADRON_WINGS; i++) + { + if (Squadron_wings[i] < 0) + continue; + + auto wingp = &Wings[Squadron_wings[i]]; + for (int j = 0; j < MAX_SHIPS_PER_WING; j++) + { + int shipnum = wingp->ship_index[j]; + if (shipnum >= 0) + { + Ships[shipnum].wing_status_wing_index = -1; + Ships[shipnum].wing_status_wing_pos = -1; + } + } + } + + // get the new set of squadron wings + for (int i = 0, n = node; i < MAX_SQUADRON_WINGS; i++) + { + auto wing_name = ""; + int wing_num = -1; + + if (n >= 0) + { + auto wingp = eval_wing(n); + if (wingp) + { + wing_name = wingp->name; + wing_num = WING_INDEX(wingp); + } + n = CDR(n); + } + + strcpy_s(Squadron_wing_names[i], wing_name); + Squadron_wings[i] = wing_num; + } + + // set the ships in the new squadron wings + // TODO + + // refresh the HUD status + // TODO +} + // Goober5000 /** * Trigger whether player uses the game AI for stuff @@ -28589,6 +28638,11 @@ int eval_sexp(int cur_node, int referenced_node) sexp_val = SEXP_TRUE; break; + case OP_SET_SQUADRON_WINGS: + sexp_set_squadron_wings(node); + sexp_val = SEXP_TRUE; + break; + // Goober5000 case OP_PLAYER_USE_AI: case OP_PLAYER_NOT_USE_AI: @@ -31178,6 +31232,7 @@ int query_operator_return_type(int op) case OP_HUD_CLEAR_MESSAGES: case OP_HUD_FORCE_SENSOR_STATIC: case OP_HUD_FORCE_EMP_EFFECT: + case OP_SET_SQUADRON_WINGS: case OP_SHIP_CHANGE_ALT_NAME: case OP_SHIP_CHANGE_CALLSIGN: case OP_SET_DEATH_MESSAGE: @@ -32571,6 +32626,9 @@ int query_operator_argument_type(int op, int argnum) else return OPF_MESSAGE_OR_STRING; + case OP_SET_SQUADRON_WINGS: + return OPF_WING; + case OP_PLAYER_USE_AI: case OP_PLAYER_NOT_USE_AI: return OPF_NONE; @@ -36464,6 +36522,7 @@ int get_category(int op_id) case OP_TOGGLE_ASTEROID_FIELD: case OP_HUD_FORCE_SENSOR_STATIC: case OP_HUD_FORCE_EMP_EFFECT: + case OP_SET_SQUADRON_WINGS: case OP_SET_GRAVITY_ACCEL: case OP_FORCE_REARM: case OP_ABORT_REARM: @@ -36847,6 +36906,7 @@ int get_subcategory(int op_id) case OP_HUD_SET_MAX_TARGETING_RANGE: case OP_HUD_FORCE_SENSOR_STATIC: case OP_HUD_FORCE_EMP_EFFECT: + case OP_SET_SQUADRON_WINGS: return CHANGE_SUBCATEGORY_HUD; case OP_NAV_ADD_WAYPOINT: @@ -41793,6 +41853,12 @@ SCP_vector Sexp_help = { "\t3:\tString or message to display. \"none\" to display nothing. Defaults to \"Emp\"\r\n" }, + {OP_SET_SQUADRON_WINGS, "set-squadron-wings\r\n" + "\tSets the wings displayed on the squadron HUD display. By default these are Alpha, Beta, Gamma, Delta, and Epsilon.\r\n" + "Takes 1 to " SCP_TOKEN_TO_STR(MAX_SQUADRON_WINGS) " arguments...\r\n" + "\tAll:\tWing to display\r\n" + }, + {OP_ADD_TO_COLGROUP, "add-to-collision-group\r\n" "\tAdds a ship to the specified collision group(s). Note that there are 32 collision groups, " "and that an object may be in several collision groups at the same time\r\n" diff --git a/code/parse/sexp.h b/code/parse/sexp.h index fd34c51be68..3fda6beaa89 100644 --- a/code/parse/sexp.h +++ b/code/parse/sexp.h @@ -923,6 +923,7 @@ enum : int { OP_GOOD_PRIMARY_TIME, // plieblang OP_SET_SKYBOX_ALPHA, // Goober5000 OP_NEBULA_SET_RANGE, // Goober5000 + OP_SET_SQUADRON_WINGS, // Goober5000 // OP_CATEGORY_AI // defined for AI goals From 7af523bdf354008961ee1eea7524598d077c66dd Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Sun, 1 Dec 2024 01:59:56 -0500 Subject: [PATCH 35/64] convert `ignore` and `used` to bool --- code/hud/hudwingmanstatus.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/code/hud/hudwingmanstatus.cpp b/code/hud/hudwingmanstatus.cpp index f9019e14f6e..489dd524f8b 100644 --- a/code/hud/hudwingmanstatus.cpp +++ b/code/hud/hudwingmanstatus.cpp @@ -36,8 +36,8 @@ typedef struct Wingman_status { - int ignore; // set to 1 when we should ignore this item -- used in team v. team - int used; + bool ignore; // set to true when we should ignore this item -- used in team v. team + bool used; float hull[MAX_SHIPS_PER_WING]; // 0.0 -> 1.0 int status[MAX_SHIPS_PER_WING]; // HUD_WINGMAN_STATUS_* int dot_anim_override[MAX_SHIPS_PER_WING]; // optional wingmen dot status to use instead of default --wookieejedi @@ -80,10 +80,10 @@ void hud_set_wingman_status_none( int wing_index, int wing_pos) HUD_wingman_status[wing_index].status[wing_pos] = HUD_WINGMAN_STATUS_NONE; - int used = 0; + bool used = false; for ( i = 0; i < MAX_SHIPS_PER_WING; i++ ) { if ( HUD_wingman_status[wing_index].status[i] != HUD_WINGMAN_STATUS_NONE ) { - used = 1; + used = true; break; } } @@ -123,7 +123,7 @@ void hud_wingman_kill_multi_teams() if ( wing_index == -1 ) return; - HUD_wingman_status[wing_index].ignore = 1; + HUD_wingman_status[wing_index].ignore = true; } @@ -137,8 +137,8 @@ void hud_init_wingman_status_gauge() HUD_wingman_update_timer=timestamp(0); // update status right away for (i = 0; i < MAX_SQUADRON_WINGS; i++) { - HUD_wingman_status[i].ignore = 0; - HUD_wingman_status[i].used = 0; + HUD_wingman_status[i].ignore = false; + HUD_wingman_status[i].used = false; for ( j = 0; j < MAX_SHIPS_PER_WING; j++ ) { HUD_wingman_status[i].status[j] = HUD_WINGMAN_STATUS_NONE; HUD_wingman_status[i].dot_anim_override[j] = -1; @@ -192,7 +192,7 @@ void hud_wingman_status_update() if ( (wing_index >= 0) && (wing_pos >= 0) ) { - HUD_wingman_status[wing_index].used = 1; + HUD_wingman_status[wing_index].used = true; if (!(shipp->is_departing()) ) { HUD_wingman_status[wing_index].status[wing_pos] = HUD_WINGMAN_STATUS_ALIVE; } @@ -561,7 +561,7 @@ void HudGaugeWingmanStatus::render(float /*frametime*/) int i, count, num_wings_to_draw = 0; for (i = 0; i < MAX_SQUADRON_WINGS; i++) { - if ( (HUD_wingman_status[i].used > 0) && (HUD_wingman_status[i].ignore == 0) ) { + if ( (HUD_wingman_status[i].used) && !(HUD_wingman_status[i].ignore) ) { num_wings_to_draw++; } } @@ -578,7 +578,7 @@ void HudGaugeWingmanStatus::render(float /*frametime*/) count = 0; for (i = 0; i < MAX_SQUADRON_WINGS; i++) { - if ( (HUD_wingman_status[i].used <= 0) || (HUD_wingman_status[i].ignore == 1) ) { + if ( !(HUD_wingman_status[i].used) || (HUD_wingman_status[i].ignore) ) { continue; } From 123d68d94dc03ec06d5f253ce6cf83023f806493 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Sun, 1 Dec 2024 01:55:27 -0500 Subject: [PATCH 36/64] add set-squadron-wings, part 2 Refresh the ship position and HUD status for the new wings. --- code/hud/hudwingmanstatus.cpp | 66 ++++++++++++++++++++++------------- code/hud/hudwingmanstatus.h | 4 +++ code/math/floating.h | 2 ++ code/parse/sexp.cpp | 22 ++++++++++-- 4 files changed, 66 insertions(+), 28 deletions(-) diff --git a/code/hud/hudwingmanstatus.cpp b/code/hud/hudwingmanstatus.cpp index 489dd524f8b..337ce4e8c9e 100644 --- a/code/hud/hudwingmanstatus.cpp +++ b/code/hud/hudwingmanstatus.cpp @@ -174,23 +174,22 @@ void hud_init_wingman_status_gauge() void hud_wingman_status_update() { if ( timestamp_elapsed(HUD_wingman_update_timer) ) { - int wing_index,wing_pos; - ship_obj *so; - object *ship_objp; - ship *shipp; HUD_wingman_update_timer=timestamp(HUD_WINGMAN_UPDATE_STATUS_INTERVAL); - for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) { - ship_objp = &Objects[so->objnum]; - if (ship_objp->flags[Object::Object_Flags::Should_be_dead]) + for (int wing_index = 0; wing_index < MAX_SQUADRON_WINGS; wing_index++) { + if (Squadron_wings[wing_index] < 0) continue; - shipp = &Ships[ship_objp->instance]; - wing_index = shipp->wing_status_wing_index; - wing_pos = shipp->wing_status_wing_pos; + auto wingp = &Wings[Squadron_wings[wing_index]]; + for (int j = 0; j < MAX_SHIPS_PER_WING; j++) { + int shipnum = wingp->ship_index[j]; + if (shipnum < 0) + continue; - if ( (wing_index >= 0) && (wing_pos >= 0) ) { + auto shipp = &Ships[shipnum]; + auto ship_objp = &Objects[shipp->objnum]; + int wing_pos = shipp->wing_status_wing_pos; HUD_wingman_status[wing_index].used = true; if (!(shipp->is_departing()) ) { @@ -534,7 +533,7 @@ int hud_wingman_status_wingmen_exist(int num_wings_to_draw) break; case 1: for (i = 0; i < MAX_SQUADRON_WINGS; i++) { - if ( HUD_wingman_status[i].used > 0 ) { + if ( HUD_wingman_status[i].used ) { for ( j = 0; j < MAX_SHIPS_PER_WING; j++ ) { if ( HUD_wingman_status[i].status[j] != HUD_WINGMAN_STATUS_NONE ) { count++; @@ -667,22 +666,25 @@ bool HudGaugeWingmanStatus::maybeFlashStatus(int wing_index, int wing_pos) void hud_wingman_status_set_index(wing *wingp, ship *shipp, p_object *pobjp) { - int wing_index; - // Check for squadron wings - wing_index = ship_squadron_wing_lookup(wingp->name); - if ( wing_index < 0 ) + int squad_wing_index = ship_squadron_wing_lookup(wingp->name); + hud_wingman_status_set_index(squad_wing_index, wingp, shipp, pobjp); +} + +void hud_wingman_status_set_index(int squad_wing_index, wing *wingp, ship *shipp, p_object *pobjp) +{ + if ( squad_wing_index < 0 ) return; // this wing is shown on the squadron display - shipp->wing_status_wing_index = (char) wing_index; + shipp->wing_status_wing_index = i2ch(squad_wing_index); - // for the first wave, just use the parse object position - if (wingp->current_wave == 1) + // for the first wave, if we have a parse object, just use the parse object position + if (wingp->current_wave == 1 && pobjp != nullptr) { - shipp->wing_status_wing_pos = (char) pobjp->pos_in_wing; + shipp->wing_status_wing_pos = i2ch(pobjp->pos_in_wing); } - // for subsequent waves, find the first position not taken + // otherwise, find the first position not taken else { int i, pos, wing_bitfield = 0; @@ -700,7 +702,7 @@ void hud_wingman_status_set_index(wing *wingp, ship *shipp, p_object *pobjp) { if (!(wing_bitfield & (1<wing_status_wing_pos = (char) i; + shipp->wing_status_wing_pos = i2ch(i); break; } } @@ -711,14 +713,28 @@ void hud_wingman_status_set_index(wing *wingp, ship *shipp, p_object *pobjp) // this section accounts for ships that are not present at mission start // the wingmen dot override for ships present at mission start are set hud_init_wingman_status_gauge() int wing_pos = shipp->wing_status_wing_pos; - if ( (wing_index >= 0) && (wing_pos >= 0) ) { - int dot_override = Ship_info[pobjp->ship_class].wingmen_status_dot_override; + if ( (squad_wing_index >= 0) && (wing_pos >= 0) ) { + int dot_override = Ship_info[shipp->ship_info_index].wingmen_status_dot_override; if (dot_override >= 0) { - HUD_wingman_status[wing_index].dot_anim_override[wing_pos] = dot_override; + HUD_wingman_status[squad_wing_index].dot_anim_override[wing_pos] = dot_override; } } } +void hud_wingman_status_refresh() +{ + // basically reinitialize the gauge, except for the individual ship data which will be filled in by the update function + for (int wing_index = 0; wing_index < MAX_SQUADRON_WINGS; wing_index++) + { + HUD_wingman_status[wing_index].used = (Squadron_wings[wing_index] >= 0); + for (int wing_pos = 0; wing_pos < MAX_SHIPS_PER_WING; wing_pos++) + HUD_wingman_status[wing_index].status[wing_pos] = HUD_WINGMAN_STATUS_NONE; + } + + HUD_wingman_update_timer = timestamp(0); // update status right away + hud_wingman_status_update(); +} + void HudGaugeWingmanStatus::pageIn() { bm_page_in_aabitmap( Wingman_status_left.first_frame, Wingman_status_left.num_frames); diff --git a/code/hud/hudwingmanstatus.h b/code/hud/hudwingmanstatus.h index 316bd04b668..8566ce1aa63 100644 --- a/code/hud/hudwingmanstatus.h +++ b/code/hud/hudwingmanstatus.h @@ -28,6 +28,10 @@ void hud_set_wingman_status_none( int wing_index, int wing_pos); void hud_wingman_status_start_flash(int wing_index, int wing_pos); void hud_wingman_status_set_index(wing *wingp, ship *shipp, p_object *pobjp); +// for resetting the gauge via sexp +void hud_wingman_status_set_index(int squad_wing_index, wing *wingp, ship *shipp, p_object *pobjp); +void hud_wingman_status_refresh(); + class HudGaugeWingmanStatus: public HudGauge { protected: diff --git a/code/math/floating.h b/code/math/floating.h index d5bd895be61..79077f37e33 100644 --- a/code/math/floating.h +++ b/code/math/floating.h @@ -31,8 +31,10 @@ inline bool fl_is_nan(float fl) { #define fl_isqrt(fl) (1.0f/sqrtf(fl)) #define fl_abs(fl) fabsf(fl) #define i2fl(i) (static_cast(i)) // int to float +#define i2ch(i) (static_cast(i)) // int to char #define l2d(l) (static_cast(l)) // long to double #define fl2i(fl) (static_cast(fl)) // float to int +#define ch2i(ch) (static_cast(ch)) // char to int #define d2l(d) (static_cast(d)) // double to long #define fl2ir(fl) (static_cast(fl + (((fl) < 0.0f) ? -0.5f : 0.5f))) // float to int, rounding #define d2lr(d) (static_cast(d + (((d) < 0.0) ? -0.5 : 0.5))) // double to long, rounding diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index 9d4d04afb33..5aed8ac00dc 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -50,6 +50,7 @@ #include "hud/hudparse.h" #include "hud/hudshield.h" #include "hud/hudsquadmsg.h" // for the order sexp +#include "hud/hudwingmanstatus.h" #include "iff_defs/iff_defs.h" #include "io/keycontrol.h" #include "io/timer.h" @@ -13706,10 +13707,22 @@ void sexp_set_squadron_wings(int node) } // set the ships in the new squadron wings - // TODO + for (int i = 0; i < MAX_SQUADRON_WINGS; i++) + { + if (Squadron_wings[i] < 0) + continue; + + auto wingp = &Wings[Squadron_wings[i]]; + for (int j = 0; j < MAX_SHIPS_PER_WING; j++) + { + int shipnum = wingp->ship_index[j]; + if (shipnum >= 0) + hud_wingman_status_set_index(i, wingp, &Ships[shipnum], nullptr); + } + } // refresh the HUD status - // TODO + hud_wingman_status_refresh(); } // Goober5000 @@ -41854,7 +41867,10 @@ SCP_vector Sexp_help = { }, {OP_SET_SQUADRON_WINGS, "set-squadron-wings\r\n" - "\tSets the wings displayed on the squadron HUD display. By default these are Alpha, Beta, Gamma, Delta, and Epsilon.\r\n" + "\tSets the wings displayed on the squadron HUD display. By default these are Alpha, Beta, Gamma, Delta, and Epsilon. " + "The squadron status will be updated to the current status of the wings, but note that if any ships in those wings have " + "previously been destroyed, the HUD display may look different than expected. (Specifically, destroyed ships may be " + "indicated as missing or may cause other ships in the same wing to appear in different positions.)\r\n" "Takes 1 to " SCP_TOKEN_TO_STR(MAX_SQUADRON_WINGS) " arguments...\r\n" "\tAll:\tWing to display\r\n" }, From d73eaf08cab3ea74d555b934ec99ed803fb3bcbf Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Tue, 3 Dec 2024 02:24:54 -0500 Subject: [PATCH 37/64] add set-squadron-wings, part 3 Move a lot of the logic into hudwingmanstatus, and preserve the wing information that is common from one display to the next. --- code/hud/hudwingmanstatus.cpp | 113 +++++++++++++++++++++++++++++----- code/hud/hudwingmanstatus.h | 3 +- code/parse/sexp.cpp | 51 +++------------ 3 files changed, 107 insertions(+), 60 deletions(-) diff --git a/code/hud/hudwingmanstatus.cpp b/code/hud/hudwingmanstatus.cpp index 337ce4e8c9e..5cdf1637aa5 100644 --- a/code/hud/hudwingmanstatus.cpp +++ b/code/hud/hudwingmanstatus.cpp @@ -34,14 +34,14 @@ #define HUD_WINGMAN_STATUS_ALIVE 2 // wingman is in the mission #define HUD_WINGMAN_STATUS_NOT_HERE 3 // wingman hasn't arrived, or has departed -typedef struct Wingman_status +struct wingman_status { bool ignore; // set to true when we should ignore this item -- used in team v. team bool used; float hull[MAX_SHIPS_PER_WING]; // 0.0 -> 1.0 int status[MAX_SHIPS_PER_WING]; // HUD_WINGMAN_STATUS_* int dot_anim_override[MAX_SHIPS_PER_WING]; // optional wingmen dot status to use instead of default --wookieejedi -} wingman_status; +}; wingman_status HUD_wingman_status[MAX_SQUADRON_WINGS]; @@ -664,15 +664,9 @@ bool HudGaugeWingmanStatus::maybeFlashStatus(int wing_index, int wing_pos) return draw_bright; } -void hud_wingman_status_set_index(wing *wingp, ship *shipp, p_object *pobjp) -{ - // Check for squadron wings - int squad_wing_index = ship_squadron_wing_lookup(wingp->name); - hud_wingman_status_set_index(squad_wing_index, wingp, shipp, pobjp); -} - void hud_wingman_status_set_index(int squad_wing_index, wing *wingp, ship *shipp, p_object *pobjp) { + // Check for squadron wings if ( squad_wing_index < 0 ) return; @@ -721,16 +715,105 @@ void hud_wingman_status_set_index(int squad_wing_index, wing *wingp, ship *shipp } } -void hud_wingman_status_refresh() +void hud_wingman_status_set_index(wing *wingp, ship *shipp, p_object *pobjp) +{ + int squad_wing_index = ship_squadron_wing_lookup(wingp->name); + hud_wingman_status_set_index(squad_wing_index, wingp, shipp, pobjp); +} + +void hud_set_new_squadron_wings(const std::array &new_squad_wingnums) { - // basically reinitialize the gauge, except for the individual ship data which will be filled in by the update function - for (int wing_index = 0; wing_index < MAX_SQUADRON_WINGS; wing_index++) + // set up a map to tell where wings are going in the new arrangement + SCP_unordered_map new_squad_indexes; + for (int cur_squad_index = 0; cur_squad_index < MAX_SQUADRON_WINGS; cur_squad_index++) + { + int wingnum = Squadron_wings[cur_squad_index]; + if (wingnum >= 0) + { + auto loc = std::find(new_squad_wingnums.begin(), new_squad_wingnums.end(), wingnum); + if (loc != new_squad_wingnums.end()) + new_squad_indexes[wingnum] = static_cast(std::distance(new_squad_wingnums.begin(), loc)); + } + } + + // clear or reassign the ships that are currently in the squadron wings + for (int cur_squad_index = 0; cur_squad_index < MAX_SQUADRON_WINGS; cur_squad_index++) + { + int wingnum = Squadron_wings[cur_squad_index]; + if (wingnum < 0) + continue; + auto wingp = &Wings[wingnum]; + auto new_squad_index = new_squad_indexes.find(wingnum); + + for (int j = 0; j < MAX_SHIPS_PER_WING; j++) + { + int shipnum = wingp->ship_index[j]; + if (shipnum < 0) + continue; + auto shipp = &Ships[shipnum]; + + if (new_squad_index == new_squad_indexes.end()) + { + shipp->wing_status_wing_index = -1; + shipp->wing_status_wing_pos = -1; + } + else + shipp->wing_status_wing_index = i2ch(new_squad_index->second); + } + } + + // copy the old status to the new locations + wingman_status Backup_HUD_wingman_status[MAX_SQUADRON_WINGS]; + memcpy(Backup_HUD_wingman_status, HUD_wingman_status, sizeof(HUD_wingman_status)); + for (int cur_squad_index = 0; cur_squad_index < MAX_SQUADRON_WINGS; cur_squad_index++) + { + int wingnum = Squadron_wings[cur_squad_index]; + if (wingnum < 0) + continue; + + auto new_squad_index = new_squad_indexes.find(wingnum); + if (new_squad_index == new_squad_indexes.end()) + continue; + + memcpy(&HUD_wingman_status[new_squad_index->second], &Backup_HUD_wingman_status[cur_squad_index], sizeof(wingman_status)); + } + + // set the new squadron wings on the HUD + for (int new_squad_index = 0; new_squad_index < MAX_SQUADRON_WINGS; new_squad_index++) + { + int wingnum = new_squad_wingnums[new_squad_index]; + auto wing_name = wingnum < 0 ? "" : Wings[wingnum].name; + + strcpy_s(Squadron_wing_names[new_squad_index], wing_name); + Squadron_wings[new_squad_index] = wingnum; + + HUD_wingman_status[new_squad_index].used = (wingnum >= 0); + } + + // set the ships in the new squadron wings, but only for the ones that weren't copied + for (int new_squad_index = 0; new_squad_index < MAX_SQUADRON_WINGS; new_squad_index++) { - HUD_wingman_status[wing_index].used = (Squadron_wings[wing_index] >= 0); - for (int wing_pos = 0; wing_pos < MAX_SHIPS_PER_WING; wing_pos++) - HUD_wingman_status[wing_index].status[wing_pos] = HUD_WINGMAN_STATUS_NONE; + int wingnum = Squadron_wings[new_squad_index]; + if (wingnum < 0) + continue; + if (new_squad_indexes.find(wingnum) != new_squad_indexes.end()) + continue; + + // clear the status before we actually add any wingmen + for (int j = 0; j < MAX_SHIPS_PER_WING; j++) + HUD_wingman_status[new_squad_index].status[j] = HUD_WINGMAN_STATUS_NONE; + + // now add the existing wingmen + auto wingp = &Wings[wingnum]; + for (int j = 0; j < MAX_SHIPS_PER_WING; j++) + { + int shipnum = wingp->ship_index[j]; + if (shipnum >= 0) + hud_wingman_status_set_index(new_squad_index, wingp, &Ships[shipnum], nullptr); + } } + // refresh the HUD status for everybody HUD_wingman_update_timer = timestamp(0); // update status right away hud_wingman_status_update(); } diff --git a/code/hud/hudwingmanstatus.h b/code/hud/hudwingmanstatus.h index 8566ce1aa63..9f301be34cf 100644 --- a/code/hud/hudwingmanstatus.h +++ b/code/hud/hudwingmanstatus.h @@ -29,8 +29,7 @@ void hud_wingman_status_start_flash(int wing_index, int wing_pos); void hud_wingman_status_set_index(wing *wingp, ship *shipp, p_object *pobjp); // for resetting the gauge via sexp -void hud_wingman_status_set_index(int squad_wing_index, wing *wingp, ship *shipp, p_object *pobjp); -void hud_wingman_status_refresh(); +void hud_set_new_squadron_wings(const std::array &new_squad_wingnums); class HudGaugeWingmanStatus: public HudGauge { diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index 5aed8ac00dc..58e1c05fd87 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -13667,62 +13667,26 @@ void multi_sexp_hud_display_gauge() void sexp_set_squadron_wings(int node) { - // clear the ships that are currently in the squadron wings - for (int i = 0; i < MAX_SQUADRON_WINGS; i++) - { - if (Squadron_wings[i] < 0) - continue; - - auto wingp = &Wings[Squadron_wings[i]]; - for (int j = 0; j < MAX_SHIPS_PER_WING; j++) - { - int shipnum = wingp->ship_index[j]; - if (shipnum >= 0) - { - Ships[shipnum].wing_status_wing_index = -1; - Ships[shipnum].wing_status_wing_pos = -1; - } - } - } + std::array new_squad_wingnums; // get the new set of squadron wings for (int i = 0, n = node; i < MAX_SQUADRON_WINGS; i++) { - auto wing_name = ""; - int wing_num = -1; + int wingnum = -1; if (n >= 0) { auto wingp = eval_wing(n); if (wingp) - { - wing_name = wingp->name; - wing_num = WING_INDEX(wingp); - } + wingnum = WING_INDEX(wingp); + n = CDR(n); } - strcpy_s(Squadron_wing_names[i], wing_name); - Squadron_wings[i] = wing_num; - } - - // set the ships in the new squadron wings - for (int i = 0; i < MAX_SQUADRON_WINGS; i++) - { - if (Squadron_wings[i] < 0) - continue; - - auto wingp = &Wings[Squadron_wings[i]]; - for (int j = 0; j < MAX_SHIPS_PER_WING; j++) - { - int shipnum = wingp->ship_index[j]; - if (shipnum >= 0) - hud_wingman_status_set_index(i, wingp, &Ships[shipnum], nullptr); - } + new_squad_wingnums[i] = wingnum; } - // refresh the HUD status - hud_wingman_status_refresh(); + hud_set_new_squadron_wings(new_squad_wingnums); } // Goober5000 @@ -41870,7 +41834,8 @@ SCP_vector Sexp_help = { "\tSets the wings displayed on the squadron HUD display. By default these are Alpha, Beta, Gamma, Delta, and Epsilon. " "The squadron status will be updated to the current status of the wings, but note that if any ships in those wings have " "previously been destroyed, the HUD display may look different than expected. (Specifically, destroyed ships may be " - "indicated as missing or may cause other ships in the same wing to appear in different positions.)\r\n" + "indicated as missing or may cause other ships in the same wing to appear in different positions.) However, any wings " + "that are shared between one display and the next will be preserved.\r\n" "Takes 1 to " SCP_TOKEN_TO_STR(MAX_SQUADRON_WINGS) " arguments...\r\n" "\tAll:\tWing to display\r\n" }, From 08b24dba62df9a6cef757e4a8f52c88641c5e0e3 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Tue, 3 Dec 2024 21:01:25 -0500 Subject: [PATCH 38/64] use range-based `for` --- code/hud/hudwingmanstatus.cpp | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/code/hud/hudwingmanstatus.cpp b/code/hud/hudwingmanstatus.cpp index 5cdf1637aa5..12feae1d803 100644 --- a/code/hud/hudwingmanstatus.cpp +++ b/code/hud/hudwingmanstatus.cpp @@ -182,8 +182,7 @@ void hud_wingman_status_update() continue; auto wingp = &Wings[Squadron_wings[wing_index]]; - for (int j = 0; j < MAX_SHIPS_PER_WING; j++) { - int shipnum = wingp->ship_index[j]; + for (int shipnum: wingp->ship_index) { if (shipnum < 0) continue; @@ -725,9 +724,8 @@ void hud_set_new_squadron_wings(const std::array &new_s { // set up a map to tell where wings are going in the new arrangement SCP_unordered_map new_squad_indexes; - for (int cur_squad_index = 0; cur_squad_index < MAX_SQUADRON_WINGS; cur_squad_index++) + for (int wingnum: Squadron_wings) { - int wingnum = Squadron_wings[cur_squad_index]; if (wingnum >= 0) { auto loc = std::find(new_squad_wingnums.begin(), new_squad_wingnums.end(), wingnum); @@ -737,17 +735,15 @@ void hud_set_new_squadron_wings(const std::array &new_s } // clear or reassign the ships that are currently in the squadron wings - for (int cur_squad_index = 0; cur_squad_index < MAX_SQUADRON_WINGS; cur_squad_index++) + for (int wingnum: Squadron_wings) { - int wingnum = Squadron_wings[cur_squad_index]; if (wingnum < 0) continue; auto wingp = &Wings[wingnum]; auto new_squad_index = new_squad_indexes.find(wingnum); - for (int j = 0; j < MAX_SHIPS_PER_WING; j++) + for (int shipnum: wingp->ship_index) { - int shipnum = wingp->ship_index[j]; if (shipnum < 0) continue; auto shipp = &Ships[shipnum]; @@ -800,14 +796,13 @@ void hud_set_new_squadron_wings(const std::array &new_s continue; // clear the status before we actually add any wingmen - for (int j = 0; j < MAX_SHIPS_PER_WING; j++) - HUD_wingman_status[new_squad_index].status[j] = HUD_WINGMAN_STATUS_NONE; + for (int &status: HUD_wingman_status[new_squad_index].status) + status = HUD_WINGMAN_STATUS_NONE; // now add the existing wingmen auto wingp = &Wings[wingnum]; - for (int j = 0; j < MAX_SHIPS_PER_WING; j++) + for (int shipnum: wingp->ship_index) { - int shipnum = wingp->ship_index[j]; if (shipnum >= 0) hud_wingman_status_set_index(new_squad_index, wingp, &Ships[shipnum], nullptr); } From e3393c1beada62354c20316966989352b58f5989 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Tue, 24 Dec 2024 22:36:31 -0500 Subject: [PATCH 39/64] adjust-audio-volume adjustments Make some modifications to the adjust-audio-volume infrastructure: 1. Add an `aav_volume` array for ease of access to all three volume variables. Refactor `snd_adjust_audio_volume()` to use this array to avoid duplicating code. 2. Move `update_looping_sound_volumes()` to be handled in parallel with `audiostream_set_volume_all()`, and for the same reason: avoid setting the sound volume for every looping sound on every frame if the volume has not changed. 3. If the volume is to be adjusted immediately, apply the relevant changes without waiting for `snd_do_frame()` to be called on the next frame. Resolves #6291. 4. Fix some edge cases in `adjust_volume_on_frame()`. --- code/sound/sound.cpp | 90 +++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 52 deletions(-) diff --git a/code/sound/sound.cpp b/code/sound/sound.cpp index cae5f3af439..67a98006a21 100644 --- a/code/sound/sound.cpp +++ b/code/sound/sound.cpp @@ -117,10 +117,13 @@ SCP_list currentlyLoopingSoundInfos; SCP_list currentlyLooping3dSoundInfos; //For the adjust-audio-volume sexp -float aav_voice_volume = 1.0f; float aav_music_volume = 1.0f; +float aav_voice_volume = 1.0f; float aav_effect_volume = 1.0f; +// these must correspond to the order of the AAV_ #defines +static float* aav_volume[] = { &aav_music_volume, &aav_voice_volume, &aav_effect_volume }; + struct aav { float start_volume; float delta; @@ -1548,20 +1551,16 @@ int sound_env_supported() return ds_eax_is_inited(); } +void adjust_volume_on_frame(float* volume_now, aav* data); + // Called once per game frame // - -void adjust_volume_on_frame(float* volume_now, aav* data); -void update_looping_sound_volumes(SCP_list& looping_sounds); void snd_do_frame() { adjust_volume_on_frame(&aav_music_volume, &aav_data[AAV_MUSIC]); adjust_volume_on_frame(&aav_voice_volume, &aav_data[AAV_VOICE]); adjust_volume_on_frame(&aav_effect_volume, &aav_data[AAV_EFFECTS]); - update_looping_sound_volumes(currentlyLoopingSoundInfos); - update_looping_sound_volumes(currentlyLooping3dSoundInfos); - ds_do_frame(); } @@ -1574,40 +1573,25 @@ void update_looping_sound_volumes(SCP_list &looping_sounds) } } -void snd_adjust_audio_volume(int type, float percent, int time) +void snd_adjust_audio_volume(int aav_type, float percent, int delta_time) { - Assert( type >= 0 && type < 3 ); - - if ( type >= 0 && type < 3 ) { - switch (type) { - case AAV_MUSIC: - aav_data[type].start_volume = aav_music_volume; - if (percent < aav_music_volume) - aav_data[type].delta = (aav_music_volume - percent) * -1.0f; - else - aav_data[type].delta = percent - aav_music_volume; - break; - case AAV_VOICE: - aav_data[type].start_volume = aav_voice_volume; - if (percent < aav_voice_volume) - aav_data[type].delta = (aav_voice_volume - percent) * -1.0f; - else - aav_data[type].delta = percent - aav_voice_volume; - break; - case AAV_EFFECTS: - aav_data[type].start_volume = aav_effect_volume; - if (percent < aav_effect_volume) - aav_data[type].delta = (aav_effect_volume - percent) * -1.0f; - else - aav_data[type].delta = percent - aav_effect_volume; - break; - default: - Int3(); - } + Assertion(aav_type >= 0 && aav_type <= 2, "aav_type is out of range!"); + if (aav_type < 0 || aav_type > 2) + return; - aav_data[type].delta_time = time; - aav_data[type].start_time = (f2fl(Missiontime) * 1000); - } + aav_data[aav_type].start_volume = *aav_volume[aav_type]; + if (percent < *aav_volume[aav_type]) + aav_data[aav_type].delta = (*aav_volume[aav_type] - percent) * -1.0f; + else + aav_data[aav_type].delta = percent - *aav_volume[aav_type]; + + aav_data[aav_type].delta_time = delta_time; + aav_data[aav_type].start_time = f2fl(Missiontime) * MILLISECONDS_PER_SECOND; + + + // if the delta_time is 0 [immediate], make the relevant updates without waiting for the next frame's snd_do_frame() + if (delta_time <= 0) + adjust_volume_on_frame(aav_volume[aav_type], &aav_data[aav_type]); } void adjust_volume_on_frame(float* volume_now, aav* data) @@ -1619,29 +1603,31 @@ void adjust_volume_on_frame(float* volume_now, aav* data) if (*volume_now == (data->start_volume + data->delta)) return; - float msMissiontime = (f2fl(Missiontime) * 1000); - - if ( msMissiontime > ( data->start_time + data->delta_time) ) { - *volume_now = data->start_volume + data->delta; - return; - } + float msMissiontime = f2fl(Missiontime) * MILLISECONDS_PER_SECOND; - float done = 0.0f; //How much change do we need? - if (data->delta_time == 0) - done = 1.0f; + //if immediate, or if the time has elapsed, use the target volume + if (data->delta_time <= 0 || msMissiontime >= (data->start_time + data->delta_time)) + *volume_now = data->start_volume + data->delta; else - done =(float) (msMissiontime - data->start_time)/data->delta_time; + { + float done = (msMissiontime - data->start_time) / data->delta_time; - //apply change - *volume_now = data->start_volume + (data->delta * done); - CLAMP(*volume_now, 0.0f, 1.0f); + //apply change + *volume_now = data->start_volume + (data->delta * done); + CLAMP(*volume_now, 0.0f, 1.0f); + } // if setting music volume, trigger volume change in playing tracks // done here in order to avoid setting music volume in every frame regardless if it changed or not if (&aav_music_volume == volume_now) { audiostream_set_volume_all(Master_event_music_volume * aav_music_volume, ASF_EVENTMUSIC); } + // for similar reasons, if setting effects volume, trigger volume change here + else if (&aav_effect_volume == volume_now) { + update_looping_sound_volumes(currentlyLoopingSoundInfos); + update_looping_sound_volumes(currentlyLooping3dSoundInfos); + } } void snd_aav_init() From 04d36a31c81bd67eafbf70e1d00229713bda9a31 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Fri, 27 Dec 2024 01:03:22 -0500 Subject: [PATCH 40/64] add the FredOnMissionLoad hook (sync with original FRED) --- qtfred/src/mission/Editor.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/qtfred/src/mission/Editor.cpp b/qtfred/src/mission/Editor.cpp index 80eca8fd3cd..d4d31164966 100644 --- a/qtfred/src/mission/Editor.cpp +++ b/qtfred/src/mission/Editor.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "iff_defs/iff_defs.h" // iff_init #include "object/object.h" // obj_init @@ -381,6 +382,12 @@ bool Editor::loadMission(const std::string& mission_name, int flags) { missionLoaded(filepath); + // This hook will allow for scripts to know when a mission has been loaded + // which will then allow them to update any LuaEnums that may be related to sexps + if (scripting::hooks::FredOnMissionLoad->isActive()) { + scripting::hooks::FredOnMissionLoad->run(); + } + return true; } void Editor::unmark_all() { From 2f1874125893a71d84fbaaf7a955d61b4c30fad2 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Fri, 27 Dec 2024 17:07:47 -0500 Subject: [PATCH 41/64] add safeguards to the "immobile" flag migration When a mission is loaded in FRED the "immobile" flag is migrated to "don't-change-position" and "don't-change-orientation". So, notify the FREDder that this is happening, and add checks to the "potential issues" checker to guard against mixing the old and new flags. Follow-up to #6304. Resolves #6489. --- code/mission/missionparse.cpp | 5 +++ code/mission/missionparse.h | 3 ++ code/parse/sexp.cpp | 53 +++++++++++++++++++++++++++++ fred2/freddoc.cpp | 17 +++++++++ fred2/fredrender.cpp | 1 + fred2/fredrender.h | 1 + fred2/fredview.cpp | 5 +-- fred2/management.cpp | 1 + qtfred/src/mission/Editor.cpp | 26 ++++++++++++-- qtfred/src/mission/EditorViewport.h | 1 + 10 files changed, 109 insertions(+), 4 deletions(-) diff --git a/code/mission/missionparse.cpp b/code/mission/missionparse.cpp index 38a45b33e19..21a1ae499e3 100644 --- a/code/mission/missionparse.cpp +++ b/code/mission/missionparse.cpp @@ -167,6 +167,8 @@ SCP_vector Parse_names; SCP_vector Fred_texture_replacements; +SCP_unordered_set Fred_migrated_immobile_ships; + int Num_path_restrictions; path_restriction_t Path_restrictions[MAX_PATH_RESTRICTIONS]; @@ -2863,6 +2865,9 @@ void resolve_parse_flags(object *objp, flagset &par { objp->flags.set(Object::Object_Flags::Dont_change_position); objp->flags.set(Object::Object_Flags::Dont_change_orientation); + + // keep track of migrated ships + Fred_migrated_immobile_ships.insert(objp->instance); } else objp->flags.set(Object::Object_Flags::Immobile); diff --git a/code/mission/missionparse.h b/code/mission/missionparse.h index 02959103a4e..1d84c1772cd 100644 --- a/code/mission/missionparse.h +++ b/code/mission/missionparse.h @@ -332,6 +332,9 @@ typedef struct texture_replace { extern SCP_vector Fred_texture_replacements; +// which ships have had the "immobile" flag migrated to "don't-change-position" and "don't-change-orientation" +extern SCP_unordered_set Fred_migrated_immobile_ships; + typedef struct alt_class { int ship_class; int variable_index; // if set allows the class to be set by a variable diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index 74a1e120481..9fa875bde09 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -4241,6 +4241,59 @@ int check_sexp_potential_issues(int node, int *bad_node, SCP_string &issue_msg) break; } + // examine uses of alter-ship-flag and are-ship-flags-set with "immobile" + case OP_ALTER_SHIP_FLAG: + case OP_ARE_SHIP_FLAGS_SET: + { + Object::Object_Flags object_flag = Object::Object_Flags::NUM_VALUES; + Ship::Ship_Flags ship_flag = Ship::Ship_Flags::NUM_VALUES; + Mission::Parse_Object_Flags parse_obj_flag = Mission::Parse_Object_Flags::NUM_VALUES; + AI::AI_Flags ai_flag = AI::AI_Flags::NUM_VALUES; + bool immobile_used = false; + + // check to see the flag is specified by the sexp (don't check to see what the flag may be applied to) + if (op_num == OP_ALTER_SHIP_FLAG) + { + auto flag_name = CTEXT(first_arg_node); + sexp_check_flag_arrays(flag_name, object_flag, ship_flag, parse_obj_flag, ai_flag); + if (object_flag == Object::Object_Flags::Immobile || parse_obj_flag == Mission::Parse_Object_Flags::OF_Immobile) + immobile_used = true; + } + else + { + for (int n = CDR(first_arg_node); n >= 0; n = CDR(n)) + { + auto flag_name = CTEXT(n); + sexp_check_flag_arrays(flag_name, object_flag, ship_flag, parse_obj_flag, ai_flag); + if (object_flag == Object::Object_Flags::Immobile || parse_obj_flag == Mission::Parse_Object_Flags::OF_Immobile) + { + immobile_used = true; + break; + } + } + } + + // now check if any ships are created with any of the flags + if (immobile_used) + { + for (const auto so : list_range(&Ship_obj_list)) + { + const auto &obj = Objects[so->objnum]; + if (obj.flags[Object::Object_Flags::Immobile, Object::Object_Flags::Dont_change_position, Object::Object_Flags::Dont_change_orientation]) + { + issue_msg = "At least one ship ("; + issue_msg += Ships[obj.instance].ship_name; + issue_msg += ") has \"Does Not Change Position\" and/or \"Does Not Change Orientation\" checked, while this "; + issue_msg += Sexp_nodes[node].text; + issue_msg += " operator uses the \"immobile\" flag. Be aware that all three flags are independent and setting/checking one flag will not " + "set/check another. For convenience, the set-mobile and set-immobile operators will clear conflicting flags, but alter-ship-flag will not."; + return SEXP_CHECK_POTENTIAL_ISSUE; + } + } + } + break; + } + default: break; } diff --git a/fred2/freddoc.cpp b/fred2/freddoc.cpp index f71a7694e9b..4e61c451cf4 100644 --- a/fred2/freddoc.cpp +++ b/fred2/freddoc.cpp @@ -266,6 +266,23 @@ bool CFREDDoc::load_mission(const char *pathname, int flags) { Fred_view_wnd->MessageBox(msg.c_str()); } + // message 4: check for "immobile" flag migration + if (!Fred_migrated_immobile_ships.empty()) { + SCP_string msg = "The \"immobile\" ship flag has been superseded by the \"don't-change-position\", and \"don't-change-orientation\" flags. " + "All ships which previously had \"Does Not Move\" checked in the ship flags editor will now have both \"Does Not Change Position\" and " + "\"Does Not Change Orientation\" checked. After you close this dialog, the error checker will check for any potential issues, including " + "issues involving these flags.\n\nThe following ships have been migrated:"; + + for (int shipnum : Fred_migrated_immobile_ships) { + msg += "\n\t"; + msg += Ships[shipnum].ship_name; + } + + truncate_message_lines(msg, 30); + Fred_view_wnd->MessageBox(msg.c_str()); + Error_checker_checks_potential_issues_once = true; + } + obj_merge_created_list(); objp = GET_FIRST(&obj_used_list); while (objp != END_OF_LIST(&obj_used_list)) { diff --git a/fred2/fredrender.cpp b/fred2/fredrender.cpp index deff2f65f78..c744042fdd4 100644 --- a/fred2/fredrender.cpp +++ b/fred2/fredrender.cpp @@ -103,6 +103,7 @@ int Show_outlines = 0; bool Draw_outlines_on_selected_ships = true; bool Draw_outline_at_warpin_position = false; bool Error_checker_checks_potential_issues = true; +bool Error_checker_checks_potential_issues_once = false; int Show_stars = 1; int Single_axis_constraint = 0; int True_rw, True_rh; diff --git a/fred2/fredrender.h b/fred2/fredrender.h index 4c04cf35ffc..c5c5d99f1e3 100644 --- a/fred2/fredrender.h +++ b/fred2/fredrender.h @@ -23,6 +23,7 @@ extern int Show_outlines; //!< Bool. If nonzero, draw each object's me extern bool Draw_outlines_on_selected_ships; // If a ship is selected, draw mesh lines extern bool Draw_outline_at_warpin_position; // Project an outline at the place where the ship will arrive after warping in extern bool Error_checker_checks_potential_issues; // Error checker checks not only outright errors but also potential issues +extern bool Error_checker_checks_potential_issues_once; // Same as above, but only once, and independent of the selected option extern int Show_stars; //!< Bool. If nonzero, draw the starfield, nebulas, and suns. Might also handle skyboxes extern int Single_axis_constraint; //!< Bool. If nonzero, constrain movement to one axis extern int Show_distances; //!< Bool. If nonzero, draw lines between each object and display their distance on the middle of each line diff --git a/fred2/fredview.cpp b/fred2/fredview.cpp index 0f4a4b5627c..e5ce41f34ef 100644 --- a/fred2/fredview.cpp +++ b/fred2/fredview.cpp @@ -3406,7 +3406,7 @@ int CFREDView::fred_check_sexp(int sexp, int type, const char *location, ...) return 1; } - if (Error_checker_checks_potential_issues) + if (Error_checker_checks_potential_issues || Error_checker_checks_potential_issues_once) z = check_sexp_potential_issues(sexp, &faulty_node, issue_msg); if (z) { @@ -3417,11 +3417,12 @@ int CFREDView::fred_check_sexp(int sexp, int type, const char *location, ...) if (!bad_node_str.empty()) // the previous function adds a space at the end bad_node_str.pop_back(); - sprintf(error_buf, "Potential issue detected in %s:\n%s\n\n%s\n\n(Suspect node appears to be: %s)", location_buf.c_str(), issue_msg.c_str(), sexp_buf.c_str(), bad_node_str.c_str()); + sprintf(error_buf, "Potential issue detected in %s:\n\n%s\n\n%s\n\n(Suspect node appears to be: %s)", location_buf.c_str(), issue_msg.c_str(), sexp_buf.c_str(), bad_node_str.c_str()); if (Fred_main_wnd->MessageBox(error_buf.c_str(), "Warning", MB_OKCANCEL | MB_ICONINFORMATION) != IDOK) return 1; } + Error_checker_checks_potential_issues_once = false; return 0; } diff --git a/fred2/management.cpp b/fred2/management.cpp index 7c488da1ae3..feb3f33e1df 100644 --- a/fred2/management.cpp +++ b/fred2/management.cpp @@ -873,6 +873,7 @@ void clear_mission(bool fast_reload) set_physics_controls(); Event_annotations.clear(); + Fred_migrated_immobile_ships.clear(); // free memory from all parsing so far -- see also the stop_parse() in player_select_close() which frees all tbls found during game_init() stop_parse(); diff --git a/qtfred/src/mission/Editor.cpp b/qtfred/src/mission/Editor.cpp index d4d31164966..4b46b7afd80 100644 --- a/qtfred/src/mission/Editor.cpp +++ b/qtfred/src/mission/Editor.cpp @@ -272,6 +272,26 @@ bool Editor::loadMission(const std::string& mission_name, int flags) { { DialogButton::Ok }); } + // message 4: check for "immobile" flag migration + if (!Fred_migrated_immobile_ships.empty()) { + SCP_string msg = "The \"immobile\" ship flag has been superseded by the \"don't-change-position\", and \"don't-change-orientation\" flags. " + "All ships which previously had \"Does Not Move\" checked in the ship flags editor will now have both \"Does Not Change Position\" and " + "\"Does Not Change Orientation\" checked. After you close this dialog, the error checker will check for any potential issues, including " + "issues involving these flags.\n\nThe following ships have been migrated:"; + + for (int shipnum : Fred_migrated_immobile_ships) { + msg += "\n\t"; + msg += Ships[shipnum].ship_name; + } + + truncate_message_lines(msg, 30); + _lastActiveViewport->dialogProvider->showButtonDialog(DialogType::Information, + "Ship Flag Migration", + msg, + { DialogButton::Ok }); + _lastActiveViewport->Error_checker_checks_potential_issues_once = true; + } + obj_merge_created_list(); objp = GET_FIRST(&obj_used_list); while (objp != END_OF_LIST(&obj_used_list)) { @@ -537,6 +557,7 @@ void Editor::clearMission(bool fast_reload) { } Event_annotations.clear(); + Fred_migrated_immobile_ships.clear(); // free memory from all parsing so far -- see also the stop_parse() in player_select_close() which frees all tbls found during game_init() stop_parse(); @@ -2651,7 +2672,7 @@ int Editor::fred_check_sexp(int sexp, int type, const char* location, ...) { return 1; } - if (_lastActiveViewport->Error_checker_checks_potential_issues) + if (_lastActiveViewport->Error_checker_checks_potential_issues || _lastActiveViewport->Error_checker_checks_potential_issues_once) z = check_sexp_potential_issues(sexp, &faulty_node, issue_msg); if (z) { @@ -2662,11 +2683,12 @@ int Editor::fred_check_sexp(int sexp, int type, const char* location, ...) { if (!bad_node_str.empty()) // the previous function adds a space at the end bad_node_str.pop_back(); - sprintf(error_buf, "Potential issue detected in %s:\n%s\n\n%s\n\n(Suspect node appears to be: %s)", location_buf.c_str(), issue_msg.c_str(), sexp_buf.c_str(), bad_node_str.c_str()); + sprintf(error_buf, "Potential issue detected in %s:\n\n%s\n\n%s\n\n(Suspect node appears to be: %s)", location_buf.c_str(), issue_msg.c_str(), sexp_buf.c_str(), bad_node_str.c_str()); if (_lastActiveViewport->dialogProvider->showButtonDialog(DialogType::Warning, "Warning", error_buf.c_str(), { DialogButton::Ok, DialogButton::Cancel }) != DialogButton::Ok) return 1; } + _lastActiveViewport->Error_checker_checks_potential_issues_once = false; return 0; } diff --git a/qtfred/src/mission/EditorViewport.h b/qtfred/src/mission/EditorViewport.h index 7301787db8c..18d48e5edba 100644 --- a/qtfred/src/mission/EditorViewport.h +++ b/qtfred/src/mission/EditorViewport.h @@ -164,6 +164,7 @@ class EditorViewport { bool Lookat_mode = false; bool Move_ships_when_undocking = true; bool Error_checker_checks_potential_issues = true; + bool Error_checker_checks_potential_issues_once = false; Editor* editor = nullptr; FredRenderer* renderer = nullptr; From 08326677444df23c3a78216fe9d80382dde05cfa Mon Sep 17 00:00:00 2001 From: wookieejedi Date: Sat, 28 Dec 2024 08:37:07 -0500 Subject: [PATCH 42/64] Fixes Joystick Hat Labeling (#6486) * Fixes Joystick Hat Labeling Fixes mislabeling of joystick hat names that appeared in #6371. Tested and confirms this fixes the issue. * fix ordering oversight --- code/controlconfig/controlsconfigcommon.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/code/controlconfig/controlsconfigcommon.cpp b/code/controlconfig/controlsconfigcommon.cpp index 1717c058ec1..40e39ab6502 100644 --- a/code/controlconfig/controlsconfigcommon.cpp +++ b/code/controlconfig/controlsconfigcommon.cpp @@ -441,37 +441,37 @@ SCP_unordered_map old_text = { }; // Localization strings for hat positions. Back[0], Forward[1], Left[2], Right[3] -// Forward[0], Right[1], Backward[2], Left[3] +// This ordering is used to follow the same logic as hatBtnToEnum in joy-sdl.cpp const char* Joy_hat_text_german_u[JOY_NUM_HAT_POS] = { - "Hut Vorne", "Hut Rechts", "Hut Hinten", "Hut Links" + "Hut Hinten", "Hut Vorne", "Hut Links", "Hut Rechts" }; const char* Joy_hat_text_french_u[JOY_NUM_HAT_POS] = { - "Chapeau Avant", "Chapeau Droite", ("Chapeau Arri\xc3\xa8""re"), "Chapeau Gauche" + ("Chapeau Arri\xc3\xa8""re"), "Chapeau Avant", "Chapeau Gauche", "Chapeau Droite" }; const char* Joy_hat_text_polish_u[JOY_NUM_HAT_POS] = { - ("Hat Prz\xc3\xb3""d"), "Hat Prawo", "Hat Ty\xc5\x82", "Hat Lewo" + "Hat Ty\xc5\x82", ("Hat Prz\xc3\xb3""d"), "Hat Lewo", "Hat Prawo" }; const char* Joy_hat_text_english_u[JOY_NUM_HAT_POS] = { - "Hat Forward", "Hat Right", "Hat Back", "Hat Left" + "Hat Back", "Hat Forward", "Hat Left", "Hat Right" }; const char* Joy_hat_text_german[JOY_NUM_HAT_POS] = { - "Hut Vorne", "Hut Rechts", "Hut Hinten", "Hut Links" + "Hut Hinten", "Hut Vorne", "Hut Links", "Hut Rechts" }; const char* Joy_hat_text_french[JOY_NUM_HAT_POS] = { - "Chapeau Avant", "Chapeau Droite", "Chapeau Arri\x8Are", "Chapeau Gauche" + "Chapeau Arri\x8Are", "Chapeau Avant", "Chapeau Gauche", "Chapeau Droite" }; const char* Joy_hat_text_polish[JOY_NUM_HAT_POS] = { - "Hat Prz\xF3\x64", "Hat Prawo", "Hat Ty\xB3", "Hat Lewo" + "Hat Ty\xB3", "Hat Prz\xF3\x64", "Hat Lewo", "Hat Prawo" }; const char* Joy_hat_text_english[JOY_NUM_HAT_POS] = { - "Hat Forward", "Hat Right", "Hat Back", "Hat Left" + "Hat Back", "Hat Forward", "Hat Left", "Hat Right" }; //English scancodes are still needed eclusively for the scripting API, as we need to give generic and stable scan code names to the API that are neither translated nor localized to keyboard layout. From 7931a55744873d5fcd7fc371248ee72213790491 Mon Sep 17 00:00:00 2001 From: wookieejedi Date: Sat, 28 Dec 2024 08:37:22 -0500 Subject: [PATCH 43/64] Update JOY_NUM_BUTTONS to 128 (#6479) Follow-up to #6371 (and replaces #5960 since the goal of that PR was already taken care of with #6371 adding the procedural translation). Overall tagging this as 25.0 since multiple folks have asking about bumping the limit --- code/io/joy.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/io/joy.h b/code/io/joy.h index 7ee6e259f3b..ce4cd7cfca1 100644 --- a/code/io/joy.h +++ b/code/io/joy.h @@ -17,7 +17,7 @@ #include "SDL_joystick.h" // z64: Moved up here for compatibility. Bye bye, organization! -const int JOY_NUM_BUTTONS = 32; // Max number of buttons FSO can handle. OS max may differ. +const int JOY_NUM_BUTTONS = 128; // Max number of buttons FSO can handle. OS max may differ. const int JOY_NUM_HAT_POS = 4; const int JOY_TOTAL_BUTTONS = (JOY_NUM_BUTTONS + JOY_NUM_HAT_POS); From a26d52dcea7fceccdd6237088c86165185c49f22 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Sat, 28 Dec 2024 14:07:11 +0000 Subject: [PATCH 44/64] Revert "Slightly off topic but remove C style casts from selection dialog" This reverts commit 2a7806d69093e283555f9e59819034bb36276de4. --- qtfred/src/mission/dialogs/SelectionDialogModel.cpp | 9 ++++----- qtfred/src/mission/dialogs/SelectionDialogModel.h | 4 ++-- qtfred/src/ui/dialogs/SelectionDialog.cpp | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/qtfred/src/mission/dialogs/SelectionDialogModel.cpp b/qtfred/src/mission/dialogs/SelectionDialogModel.cpp index 26c5a1cf12d..d0dd4430c70 100644 --- a/qtfred/src/mission/dialogs/SelectionDialogModel.cpp +++ b/qtfred/src/mission/dialogs/SelectionDialogModel.cpp @@ -263,14 +263,13 @@ void SelectionDialogModel::setFilterWaypoints(bool filter_waypoints) { } } -bool SelectionDialogModel::isFilterIFFTeam(size_t team) const { - Assertion(team >= 0 && team < Iff_info.size(), "Team index %d is invalid!", team); +bool SelectionDialogModel::isFilterIFFTeam(int team) const { + Assertion(team >= 0 && team < (int)Iff_info.size(), "Team index %d is invalid!", team); return _filter_iff[team]; } -void SelectionDialogModel::setFilterIFFTeam(size_t team, bool filter) -{ - Assertion(team >= 0 && team < Iff_info.size(), "Team index %d is invalid!", team); +void SelectionDialogModel::setFilterIFFTeam(int team, bool filter) { + Assertion(team >= 0 && team < (int)Iff_info.size(), "Team index %d is invalid!", team); if (filter != _filter_iff[team]) { _filter_iff[team] = filter; diff --git a/qtfred/src/mission/dialogs/SelectionDialogModel.h b/qtfred/src/mission/dialogs/SelectionDialogModel.h index 48fb4e04f58..f686101eb88 100644 --- a/qtfred/src/mission/dialogs/SelectionDialogModel.h +++ b/qtfred/src/mission/dialogs/SelectionDialogModel.h @@ -72,8 +72,8 @@ class SelectionDialogModel : public AbstractDialogModel { bool isFilterWaypoints() const; void setFilterWaypoints(bool filter_waypoints); - bool isFilterIFFTeam(size_t team) const; - void setFilterIFFTeam(size_t team, bool filter); + bool isFilterIFFTeam(int team) const; + void setFilterIFFTeam(int team, bool filter); /** * @brief Updates the selection status of the objects in the list diff --git a/qtfred/src/ui/dialogs/SelectionDialog.cpp b/qtfred/src/ui/dialogs/SelectionDialog.cpp index c1af891e5c2..bf8ee8fe4dc 100644 --- a/qtfred/src/ui/dialogs/SelectionDialog.cpp +++ b/qtfred/src/ui/dialogs/SelectionDialog.cpp @@ -38,7 +38,7 @@ SelectionDialog::SelectionDialog(FredView* parent, EditorViewport* viewport) : [this](int state) { _model->setFilterStarts(state == Qt::Checked); }); // Initialize IFF check boxes - for (size_t i = 0; i < Iff_info.size(); ++i) { + for (auto i = 0; i < (int)Iff_info.size(); ++i) { auto checkbox = new QCheckBox(QString::fromUtf8(Iff_info[i].iff_name), this); _iffCheckBoxes.push_back(checkbox); ui->iffSelectionContainer->addWidget(checkbox); @@ -73,7 +73,7 @@ void SelectionDialog::updateUI() { ui->checkPlayerStarts->setChecked(_model->isFilterStarts()); ui->checkWaypoints->setChecked(_model->isFilterWaypoints()); ui->checkShips->setChecked(_model->isFilterShips()); - for (size_t i = 0; i < Iff_info.size(); ++i) { + for (auto i = 0; i < (int)Iff_info.size(); ++i) { _iffCheckBoxes[i]->setChecked(_model->isFilterIFFTeam(i)); _iffCheckBoxes[i]->setEnabled(_model->isFilterShips()); } From 24590dd548b355fefff0bf8bca615b6242a829da Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Sat, 28 Dec 2024 14:09:04 +0000 Subject: [PATCH 45/64] Update Fiction Viewer --- .../dialogs/FictionViewerDialogModel.h | 11 ------- qtfred/src/ui/FredView.cpp | 5 ++-- qtfred/src/ui/dialogs/FictionViewerDialog.cpp | 30 +++++++------------ qtfred/src/ui/dialogs/FictionViewerDialog.h | 1 + qtfred/ui/FictionViewerDialog.ui | 16 ---------- 5 files changed, 14 insertions(+), 49 deletions(-) diff --git a/qtfred/src/mission/dialogs/FictionViewerDialogModel.h b/qtfred/src/mission/dialogs/FictionViewerDialogModel.h index 9247d723b16..21c9350a33c 100644 --- a/qtfred/src/mission/dialogs/FictionViewerDialogModel.h +++ b/qtfred/src/mission/dialogs/FictionViewerDialogModel.h @@ -47,8 +47,6 @@ class FictionViewerDialogModel: public AbstractDialogModel { private: void initializeData(); - template - void modify(T &a, const T &b); SCP_string _storyFile; SCP_string _fontFile; @@ -59,15 +57,6 @@ class FictionViewerDialogModel: public AbstractDialogModel { int _maxStoryFileLength, _maxFontFileLength, _maxVoiceFileLength; }; - -template -inline void FictionViewerDialogModel::modify(T &a, const T &b) { - if (a != b) { - a = b; - modelChanged(); - } -} - } } } diff --git a/qtfred/src/ui/FredView.cpp b/qtfred/src/ui/FredView.cpp index dd51ff204e4..63df4bdbe64 100644 --- a/qtfred/src/ui/FredView.cpp +++ b/qtfred/src/ui/FredView.cpp @@ -1144,8 +1144,9 @@ void FredView::on_actionMission_Objectives_triggered(bool) { } void FredView::on_actionFiction_Viewer_triggered(bool) { - dialogs::FictionViewerDialog dialog(this, _viewport); - dialog.exec(); + auto dialog = new dialogs::FictionViewerDialog(this, _viewport); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); } } // namespace fred diff --git a/qtfred/src/ui/dialogs/FictionViewerDialog.cpp b/qtfred/src/ui/dialogs/FictionViewerDialog.cpp index 9d2a99978ba..3bf84192e3c 100644 --- a/qtfred/src/ui/dialogs/FictionViewerDialog.cpp +++ b/qtfred/src/ui/dialogs/FictionViewerDialog.cpp @@ -3,7 +3,7 @@ #include "ui/dialogs/FictionViewerDialog.h" #include "ui/util/SignalBlockers.h" #include "ui_FictionViewerDialog.h" - +#include "mission/util.h" namespace fso { namespace fred { namespace dialogs { @@ -23,7 +23,7 @@ FictionViewerDialog::FictionViewerDialog(FredView* parent, EditorViewport* viewp ui->voiceFileEdit->setMaxLength(_model->getMaxVoiceFileLength()); connect(this, &QDialog::accepted, _model.get(), &FictionViewerDialogModel::apply); - connect(this, &QDialog::rejected, _model.get(), &FictionViewerDialogModel::reject); + connect(ui->dialogButtonBox, &QDialogButtonBox::rejected, this, &FictionViewerDialog::rejectHandler); connect(_model.get(), &AbstractDialogModel::modelChanged, this, &FictionViewerDialog::updateUI); @@ -109,25 +109,15 @@ void FictionViewerDialog::keyPressEvent(QKeyEvent* event) { QDialog::keyPressEvent(event); } -void FictionViewerDialog::closeEvent(QCloseEvent* event) { - if (_model->query_modified()) { - auto button = _viewport->dialogProvider->showButtonDialog(DialogType::Question, "Changes detected", "Do you want to keep your changes?", - { DialogButton::Yes, DialogButton::No, DialogButton::Cancel }); - - if (button == DialogButton::Cancel) { - event->ignore(); - return; - } - - if (button == DialogButton::Yes) { - accept(); - return; - } - } - - QDialog::closeEvent(event); +void FictionViewerDialog::closeEvent(QCloseEvent* e) { + if (!rejectOrCloseHandler(this, _model.get(), _viewport)) { + e->ignore(); + }; +} +void FictionViewerDialog::rejectHandler() +{ + this->close(); } - } } } diff --git a/qtfred/src/ui/dialogs/FictionViewerDialog.h b/qtfred/src/ui/dialogs/FictionViewerDialog.h index ebadd07d490..09fcfd5ef09 100644 --- a/qtfred/src/ui/dialogs/FictionViewerDialog.h +++ b/qtfred/src/ui/dialogs/FictionViewerDialog.h @@ -29,6 +29,7 @@ class FictionViewerDialog : public QDialog { protected: void keyPressEvent(QKeyEvent* event) override; void closeEvent(QCloseEvent*) override; + void rejectHandler(); private: void updateMusicComboBox(); diff --git a/qtfred/ui/FictionViewerDialog.ui b/qtfred/ui/FictionViewerDialog.ui index 2e6384d9a9d..7d65057be29 100644 --- a/qtfred/ui/FictionViewerDialog.ui +++ b/qtfred/ui/FictionViewerDialog.ui @@ -100,21 +100,5 @@ - - dialogButtonBox - rejected() - fso::fred::dialogs::FictionViewerDialog - reject() - - - 138 - 134 - - - 114 - 78 - - - From 5b23d9d3b8aa84efd2109248e7c238fc99429615 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Sat, 28 Dec 2024 14:10:41 +0000 Subject: [PATCH 46/64] Update loadout dialog --- .../dialogs/LoadoutEditorDialogModel.cpp | 22 ++++++++++--------- qtfred/src/ui/FredView.cpp | 1 + qtfred/src/ui/dialogs/LoadoutDialog.cpp | 17 ++++++++++++-- qtfred/src/ui/dialogs/LoadoutDialog.h | 10 ++++++++- qtfred/ui/LoadoutDialog.ui | 16 -------------- 5 files changed, 37 insertions(+), 29 deletions(-) diff --git a/qtfred/src/mission/dialogs/LoadoutEditorDialogModel.cpp b/qtfred/src/mission/dialogs/LoadoutEditorDialogModel.cpp index ddc367241d8..13c1b883306 100644 --- a/qtfred/src/mission/dialogs/LoadoutEditorDialogModel.cpp +++ b/qtfred/src/mission/dialogs/LoadoutEditorDialogModel.cpp @@ -233,6 +233,7 @@ void LoadoutDialogModel::setPlayerEntryDelay(float delay) { _playerEntryDelay = delay; modelChanged(); + set_modified(); } @@ -721,7 +722,7 @@ void LoadoutDialogModel::setShipEnabled(const SCP_vector& list, bool } } } - + set_modified(); buildCurrentLists(); } @@ -754,7 +755,7 @@ void LoadoutDialogModel::setShipVariableEnabled(const SCP_vector& li item); } } - + set_modified(); buildCurrentLists(); } @@ -780,7 +781,7 @@ void LoadoutDialogModel::setWeaponEnabled(const SCP_vector& list, bo } } } - + set_modified(); buildCurrentLists(); } @@ -813,7 +814,7 @@ void LoadoutDialogModel::setWeaponVariableEnabled(const SCP_vector& item); } } - + set_modified(); buildCurrentLists(); } @@ -828,7 +829,7 @@ void LoadoutDialogModel::setExtraAllocatedShipCount(const SCP_vector } } } - + set_modified(); buildCurrentLists(); } @@ -842,7 +843,7 @@ void LoadoutDialogModel::setExtraAllocatedForShipVariablesCount(const SCP_vector } } } - + set_modified(); buildCurrentLists(); } @@ -856,7 +857,7 @@ void LoadoutDialogModel::setExtraAllocatedWeaponCount(const SCP_vector& list, c } } } - + set_modified(); buildCurrentLists(); } @@ -942,6 +943,7 @@ void LoadoutDialogModel::setSkipValidation(const bool skipIt) { // this is designed to be a global control, so turn this off in TvT, until we hear from someone otherwise. for (auto& team : _teams) { team.skipValidation = skipIt; + set_modified(); } } diff --git a/qtfred/src/ui/FredView.cpp b/qtfred/src/ui/FredView.cpp index 63df4bdbe64..747af1e4082 100644 --- a/qtfred/src/ui/FredView.cpp +++ b/qtfred/src/ui/FredView.cpp @@ -758,6 +758,7 @@ void FredView::on_actionReinforcements_triggered(bool) { } void FredView::on_actionLoadout_triggered(bool) { auto editorDialog = new dialogs::LoadoutDialog(this, _viewport); + editorDialog->setAttribute(Qt::WA_DeleteOnClose); editorDialog->show(); } DialogButton FredView::showButtonDialog(DialogType type, diff --git a/qtfred/src/ui/dialogs/LoadoutDialog.cpp b/qtfred/src/ui/dialogs/LoadoutDialog.cpp index 9e730551c21..8952ad3d317 100644 --- a/qtfred/src/ui/dialogs/LoadoutDialog.cpp +++ b/qtfred/src/ui/dialogs/LoadoutDialog.cpp @@ -6,6 +6,7 @@ #include #include #include +#include constexpr int TABLE_MODE = 0; constexpr int VARIABLE_MODE = 1; @@ -23,7 +24,7 @@ LoadoutDialog::LoadoutDialog(FredView* parent, EditorViewport* viewport) // Major Changes, like Applying the model, rejecting changes and updating the UI. connect(_model.get(), &AbstractDialogModel::modelChanged, this, &LoadoutDialog::updateUI); connect(this, &QDialog::accepted, _model.get(), &LoadoutDialogModel::apply); - connect(this, &QDialog::rejected, _model.get(), &LoadoutDialogModel::reject); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &LoadoutDialog::rejectHandler); // Ship and Weapon lists, selection changed. connect(ui->listShipsNotUsed, @@ -237,7 +238,19 @@ LoadoutDialog::LoadoutDialog(FredView* parent, EditorViewport* viewport) } // a result of competing CI requirements -LoadoutDialog::~LoadoutDialog(){} // NOLINT +LoadoutDialog::~LoadoutDialog() {} // NOLINT + +void LoadoutDialog::closeEvent(QCloseEvent* e) +{ + if (!rejectOrCloseHandler(this, _model.get(), _viewport)) { + e->ignore(); + }; +} + +void LoadoutDialog::rejectHandler() +{ + this->close(); +} void LoadoutDialog::onSwitchViewButtonPressed() { diff --git a/qtfred/src/ui/dialogs/LoadoutDialog.h b/qtfred/src/ui/dialogs/LoadoutDialog.h index 7c47b7861ff..5802642ca33 100644 --- a/qtfred/src/ui/dialogs/LoadoutDialog.h +++ b/qtfred/src/ui/dialogs/LoadoutDialog.h @@ -29,7 +29,15 @@ class LoadoutDialog : public QDialog explicit LoadoutDialog(FredView* parent, EditorViewport* viewport); ~LoadoutDialog() override; -private: + protected: + /** + * @brief Overides the Dialogs Close event to add a confermation dialog + * @param [in] *e The event. + */ + void closeEvent(QCloseEvent*) override; + void rejectHandler(); + + private: std::unique_ptr ui; std::unique_ptr _model; EditorViewport* _viewport; diff --git a/qtfred/ui/LoadoutDialog.ui b/qtfred/ui/LoadoutDialog.ui index a454de8ee09..a80e96f859c 100644 --- a/qtfred/ui/LoadoutDialog.ui +++ b/qtfred/ui/LoadoutDialog.ui @@ -957,21 +957,5 @@ - - buttonBox - rejected() - fso::fred::dialogs::LoadoutDialog - reject() - - - 309 - 625 - - - 309 - 321 - - - From 069a30c1905f3eb171ec9d4385a37fc4f148ad36 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Sat, 28 Dec 2024 14:16:27 +0000 Subject: [PATCH 47/64] Update Mission Spec dialog --- .../dialogs/MissionSpecDialogModel.cpp | 10 ------- .../mission/dialogs/MissionSpecDialogModel.h | 16 ---------- qtfred/src/ui/FredView.cpp | 1 + qtfred/src/ui/dialogs/MissionSpecDialog.cpp | 29 +++++++------------ qtfred/src/ui/dialogs/MissionSpecDialog.h | 1 + qtfred/ui/MissionSpecDialog.ui | 16 ---------- 6 files changed, 12 insertions(+), 61 deletions(-) diff --git a/qtfred/src/mission/dialogs/MissionSpecDialogModel.cpp b/qtfred/src/mission/dialogs/MissionSpecDialogModel.cpp index 8ce9fa26444..c5eeaa0cf06 100644 --- a/qtfred/src/mission/dialogs/MissionSpecDialogModel.cpp +++ b/qtfred/src/mission/dialogs/MissionSpecDialogModel.cpp @@ -367,16 +367,6 @@ SCP_string MissionSpecDialogModel::getDesignerNoteText() { return _m_mission_notes; } -void MissionSpecDialogModel::set_modified() { - if (!_modified) { - _modified = true; - } -} - -bool MissionSpecDialogModel::query_modified() { - return _modified; -} - } } } diff --git a/qtfred/src/mission/dialogs/MissionSpecDialogModel.h b/qtfred/src/mission/dialogs/MissionSpecDialogModel.h index 5d48d93fc67..ceaf9b05637 100644 --- a/qtfred/src/mission/dialogs/MissionSpecDialogModel.h +++ b/qtfred/src/mission/dialogs/MissionSpecDialogModel.h @@ -16,10 +16,6 @@ class MissionSpecDialogModel : public AbstractDialogModel { private: void initializeData(); - template - void modify(T &a, const T &b); - - bool _modified = false; SCP_string _m_created; SCP_string _m_modified; @@ -49,8 +45,6 @@ class MissionSpecDialogModel : public AbstractDialogModel { int _m_type; - void set_modified(); - public: MissionSpecDialogModel(QObject* parent, EditorViewport* viewport); @@ -122,18 +116,8 @@ class MissionSpecDialogModel : public AbstractDialogModel { void setDesignerNoteText(const SCP_string&); SCP_string getDesignerNoteText(); - bool query_modified(); }; -template -inline void MissionSpecDialogModel::modify(T &a, const T &b) { - if (a != b) { - a = b; - set_modified(); - modelChanged(); - } -} - } } } diff --git a/qtfred/src/ui/FredView.cpp b/qtfred/src/ui/FredView.cpp index 747af1e4082..0acb6a625d7 100644 --- a/qtfred/src/ui/FredView.cpp +++ b/qtfred/src/ui/FredView.cpp @@ -728,6 +728,7 @@ void FredView::on_actionBriefing_triggered(bool) { } void FredView::on_actionMission_Specs_triggered(bool) { auto missionSpecEditor = new dialogs::MissionSpecDialog(this, _viewport); + missionSpecEditor->setAttribute(Qt::WA_DeleteOnClose); missionSpecEditor->show(); } void FredView::on_actionWaypoint_Paths_triggered(bool) { diff --git a/qtfred/src/ui/dialogs/MissionSpecDialog.cpp b/qtfred/src/ui/dialogs/MissionSpecDialog.cpp index c35a6370715..8679c03c6c1 100644 --- a/qtfred/src/ui/dialogs/MissionSpecDialog.cpp +++ b/qtfred/src/ui/dialogs/MissionSpecDialog.cpp @@ -3,7 +3,7 @@ #include "ui_MissionSpecDialog.h" #include - +#include "mission/util.h" #include #include @@ -17,7 +17,7 @@ MissionSpecDialog::MissionSpecDialog(FredView* parent, EditorViewport* viewport) ui->setupUi(this); connect(this, &QDialog::accepted, _model.get(), &MissionSpecDialogModel::apply); - connect(this, &QDialog::rejected, _model.get(), &MissionSpecDialogModel::reject); + connect(ui->dialogButtonBox, &QDialogButtonBox::rejected, _model.get(), &MissionSpecDialogModel::reject); connect(_model.get(), &AbstractDialogModel::modelChanged, this, &MissionSpecDialog::updateUI); @@ -103,23 +103,14 @@ MissionSpecDialog::MissionSpecDialog(FredView* parent, EditorViewport* viewport) MissionSpecDialog::~MissionSpecDialog() { } -void MissionSpecDialog::closeEvent(QCloseEvent* event) { - if (_model->query_modified()) { - auto button = _viewport->dialogProvider->showButtonDialog(DialogType::Question, "Changes detected", "Do you want to keep your changes?", - { DialogButton::Yes, DialogButton::No, DialogButton::Cancel }); - - if (button == DialogButton::Cancel) { - event->ignore(); - return; - } - - if (button == DialogButton::Yes) { - accept(); - return; - } - } - - QDialog::closeEvent(event); +void MissionSpecDialog::closeEvent(QCloseEvent* e) { + if (!rejectOrCloseHandler(this, _model.get(), _viewport)) { + e->ignore(); + }; +} +void MissionSpecDialog::rejectHandler() +{ + this->close(); } void MissionSpecDialog::updateUI() { diff --git a/qtfred/src/ui/dialogs/MissionSpecDialog.h b/qtfred/src/ui/dialogs/MissionSpecDialog.h index 57c9f38ec6e..7c36fee6453 100644 --- a/qtfred/src/ui/dialogs/MissionSpecDialog.h +++ b/qtfred/src/ui/dialogs/MissionSpecDialog.h @@ -27,6 +27,7 @@ class MissionSpecDialog : public QDialog protected: void closeEvent(QCloseEvent*) override; + void rejectHandler(); private slots: void on_customWingNameButton_clicked(); diff --git a/qtfred/ui/MissionSpecDialog.ui b/qtfred/ui/MissionSpecDialog.ui index e26935a2e5b..1cd7c7554e1 100644 --- a/qtfred/ui/MissionSpecDialog.ui +++ b/qtfred/ui/MissionSpecDialog.ui @@ -1053,22 +1053,6 @@ - - dialogButtonBox - rejected() - fso::fred::dialogs::MissionSpecDialog - reject() - - - 628 - 19 - - - 357 - 297 - - - From 5892edaf5709004f67abd653c0fc233946f43c45 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Sat, 28 Dec 2024 14:17:41 +0000 Subject: [PATCH 48/64] Update Reinforcements dialog --- .../dialogs/ReinforcementsEditorDialogModel.cpp | 7 +++++++ qtfred/src/ui/FredView.cpp | 1 + .../ui/dialogs/ReinforcementsEditorDialog.cpp | 17 ++++++++++++++--- .../src/ui/dialogs/ReinforcementsEditorDialog.h | 1 + qtfred/ui/ReinforcementsDialog.ui | 16 ---------------- 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/qtfred/src/mission/dialogs/ReinforcementsEditorDialogModel.cpp b/qtfred/src/mission/dialogs/ReinforcementsEditorDialogModel.cpp index c60e2406f7d..9714c803890 100644 --- a/qtfred/src/mission/dialogs/ReinforcementsEditorDialogModel.cpp +++ b/qtfred/src/mission/dialogs/ReinforcementsEditorDialogModel.cpp @@ -147,6 +147,7 @@ void ReinforcementsDialogModel::addToReinforcements(const SCP_vector _listUpdateRequired = true; _numberLineEditUpdateRequired = true; modelChanged(); + set_modified(); } void ReinforcementsDialogModel::removeFromReinforcements(const SCP_vector& namesIn) @@ -170,6 +171,7 @@ void ReinforcementsDialogModel::removeFromReinforcements(const SCP_vector _numberLineEditUpdateRequired = true; modelChanged(); + set_modified(); } void ReinforcementsDialogModel::setUseCount(int count) @@ -257,6 +260,7 @@ void ReinforcementsDialogModel::setUseCount(int count) for (auto& reinforcement : _selectedReinforcementIndices) { std::get<1>(_reinforcementList[reinforcement]) = count; } + set_modified(); } void ReinforcementsDialogModel::setBeforeArrivalDelay(int delay) @@ -264,6 +268,7 @@ void ReinforcementsDialogModel::setBeforeArrivalDelay(int delay) for (auto& reinforcement : _selectedReinforcementIndices) { std::get<2>(_reinforcementList[reinforcement]) = delay; } + set_modified(); } void ReinforcementsDialogModel::moveReinforcementsUp() @@ -284,6 +289,7 @@ void ReinforcementsDialogModel::moveReinforcementsUp() updateSelectedIndices(); _listUpdateRequired = true; modelChanged(); + set_modified(); } } @@ -303,6 +309,7 @@ void ReinforcementsDialogModel::moveReinforcementsDown() updateSelectedIndices(); _listUpdateRequired = true; modelChanged(); + set_modified(); } } diff --git a/qtfred/src/ui/FredView.cpp b/qtfred/src/ui/FredView.cpp index 0acb6a625d7..eaef2795d55 100644 --- a/qtfred/src/ui/FredView.cpp +++ b/qtfred/src/ui/FredView.cpp @@ -755,6 +755,7 @@ void FredView::on_actionCommand_Briefing_triggered(bool) { } void FredView::on_actionReinforcements_triggered(bool) { auto editorDialog = new dialogs::ReinforcementsDialog(this, _viewport); + editorDialog->setAttribute(Qt::WA_DeleteOnClose); editorDialog->show(); } void FredView::on_actionLoadout_triggered(bool) { diff --git a/qtfred/src/ui/dialogs/ReinforcementsEditorDialog.cpp b/qtfred/src/ui/dialogs/ReinforcementsEditorDialog.cpp index a0c1bd47311..5021ae806fd 100644 --- a/qtfred/src/ui/dialogs/ReinforcementsEditorDialog.cpp +++ b/qtfred/src/ui/dialogs/ReinforcementsEditorDialog.cpp @@ -1,6 +1,6 @@ #include "ReinforcementsEditorDialog.h" #include "ui_ReinforcementsDialog.h" - +#include "mission/util.h" #include #include #include @@ -22,7 +22,9 @@ namespace dialogs { connect(_model.get(), &AbstractDialogModel::modelChanged, this, &ReinforcementsDialog::updateUI); connect(this, &QDialog::accepted, _model.get(), &ReinforcementsDialogModel::apply); - connect(this, &QDialog::rejected, _model.get(), &ReinforcementsDialogModel::reject); + connect(ui->okAndCancelButtonBox, + &QDialogButtonBox::rejected, + this, &ReinforcementsDialog::rejectHandler); connect(ui->delayLineEdit, @@ -197,7 +199,16 @@ namespace dialogs { ReinforcementsDialog::~ReinforcementsDialog() {} // NOLINT - void ReinforcementsDialog::closeEvent(QCloseEvent* ){} + void ReinforcementsDialog::closeEvent(QCloseEvent* e){ + if (!rejectOrCloseHandler(this, _model.get(), _viewport)) { + e->ignore(); + }; + } + + void ReinforcementsDialog::rejectHandler() + { + this->close(); + } } } diff --git a/qtfred/src/ui/dialogs/ReinforcementsEditorDialog.h b/qtfred/src/ui/dialogs/ReinforcementsEditorDialog.h index 8d07a6042b7..18a245d4a75 100644 --- a/qtfred/src/ui/dialogs/ReinforcementsEditorDialog.h +++ b/qtfred/src/ui/dialogs/ReinforcementsEditorDialog.h @@ -28,6 +28,7 @@ class ReinforcementsDialog : public QDialog { protected: void closeEvent(QCloseEvent*) override; + void rejectHandler(); private slots: void on_chosenShipsList_clicked(); diff --git a/qtfred/ui/ReinforcementsDialog.ui b/qtfred/ui/ReinforcementsDialog.ui index 37cce44e423..f01a24205e9 100644 --- a/qtfred/ui/ReinforcementsDialog.ui +++ b/qtfred/ui/ReinforcementsDialog.ui @@ -458,21 +458,5 @@ - - okAndCancelButtonBox - rejected() - fso::fred::dialogs::ReinforcementsDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - From d94da96ca403e61d9433851b1943049ceaf769f4 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Sat, 28 Dec 2024 14:22:16 +0000 Subject: [PATCH 49/64] Update Shield Dialog --- .../mission/dialogs/ShieldSystemDialogModel.h | 10 ------- qtfred/src/ui/FredView.cpp | 5 ++-- qtfred/src/ui/dialogs/ShieldSystemDialog.cpp | 30 +++++++------------ qtfred/src/ui/dialogs/ShieldSystemDialog.h | 5 +++- qtfred/ui/ShieldSystemDialog.ui | 16 ---------- 5 files changed, 17 insertions(+), 49 deletions(-) diff --git a/qtfred/src/mission/dialogs/ShieldSystemDialogModel.h b/qtfred/src/mission/dialogs/ShieldSystemDialogModel.h index 94875d6d66a..8eb37b68748 100644 --- a/qtfred/src/mission/dialogs/ShieldSystemDialogModel.h +++ b/qtfred/src/mission/dialogs/ShieldSystemDialogModel.h @@ -34,8 +34,6 @@ class ShieldSystemDialogModel: public AbstractDialogModel { private: void initializeData(); - template - void modify(T &a, const T &b); std::vector _shipTypeOptions; std::vector _teamOptions; @@ -45,14 +43,6 @@ class ShieldSystemDialogModel: public AbstractDialogModel { int _currType; }; -template -inline void ShieldSystemDialogModel::modify(T &a, const T &b) { - if (a != b) { - a = b; - modelChanged(); - } -} - } } } diff --git a/qtfred/src/ui/FredView.cpp b/qtfred/src/ui/FredView.cpp index eaef2795d55..e9d1d054bcb 100644 --- a/qtfred/src/ui/FredView.cpp +++ b/qtfred/src/ui/FredView.cpp @@ -1133,8 +1133,9 @@ void FredView::on_actionBackground_triggered(bool) { } void FredView::on_actionShield_System_triggered(bool) { - dialogs::ShieldSystemDialog dialog(this, _viewport); - dialog.exec(); + auto dialog = new dialogs::ShieldSystemDialog(this, _viewport); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); } void FredView::on_actionVoice_Acting_Manager_triggered(bool) { diff --git a/qtfred/src/ui/dialogs/ShieldSystemDialog.cpp b/qtfred/src/ui/dialogs/ShieldSystemDialog.cpp index 6fc2863781b..160f704380a 100644 --- a/qtfred/src/ui/dialogs/ShieldSystemDialog.cpp +++ b/qtfred/src/ui/dialogs/ShieldSystemDialog.cpp @@ -3,7 +3,7 @@ #include "ShieldSystemDialog.h" #include "ui/util/SignalBlockers.h" #include "ui_ShieldSystemDialog.h" - +#include "mission/util.h" namespace fso { namespace fred { @@ -17,7 +17,7 @@ ShieldSystemDialog::ShieldSystemDialog(FredView* parent, EditorViewport* viewpor ui->setupUi(this); connect(this, &QDialog::accepted, _model.get(), &ShieldSystemDialogModel::apply); - connect(this, &QDialog::rejected, _model.get(), &ShieldSystemDialogModel::reject); + connect(ui->dialogButtonBox, &QDialogButtonBox::rejected, this, &ShieldSystemDialog::rejectHandler); connect(_model.get(), &AbstractDialogModel::modelChanged, this, &ShieldSystemDialog::updateUI); @@ -113,25 +113,15 @@ void ShieldSystemDialog::keyPressEvent(QKeyEvent* event) { QDialog::keyPressEvent(event); } -void ShieldSystemDialog::closeEvent(QCloseEvent* event) { - if (_model->query_modified()) { - auto button = _viewport->dialogProvider->showButtonDialog(DialogType::Question, "Changes detected", "Do you want to keep your changes?", - { DialogButton::Yes, DialogButton::No, DialogButton::Cancel }); - - if (button == DialogButton::Cancel) { - event->ignore(); - return; - } - - if (button == DialogButton::Yes) { - accept(); - return; - } - } - - QDialog::closeEvent(event); +void ShieldSystemDialog::closeEvent(QCloseEvent* e) { + if (!rejectOrCloseHandler(this, _model.get(), _viewport)) { + e->ignore(); + }; +} +void ShieldSystemDialog::rejectHandler() +{ + this->close(); } - } } } diff --git a/qtfred/src/ui/dialogs/ShieldSystemDialog.h b/qtfred/src/ui/dialogs/ShieldSystemDialog.h index c0c870ec31d..dc7298cc4f5 100644 --- a/qtfred/src/ui/dialogs/ShieldSystemDialog.h +++ b/qtfred/src/ui/dialogs/ShieldSystemDialog.h @@ -24,7 +24,10 @@ class ShieldSystemDialog : public QDialog protected: void keyPressEvent(QKeyEvent* event) override; void closeEvent(QCloseEvent*) override; -private: + + void rejectHandler(); + + private: void updateUI(); void updateTeam(); void updateType(); diff --git a/qtfred/ui/ShieldSystemDialog.ui b/qtfred/ui/ShieldSystemDialog.ui index 11137c17e66..fc66fc7fe6d 100644 --- a/qtfred/ui/ShieldSystemDialog.ui +++ b/qtfred/ui/ShieldSystemDialog.ui @@ -130,22 +130,6 @@ - - dialogButtonBox - rejected() - fso::fred::dialogs::ShieldSystemDialog - reject() - - - 195 - 132 - - - 154 - 80 - - - From 2b1bf2c1bb62d42ef30587001361c8e8f75dbc19 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Sat, 28 Dec 2024 14:23:35 +0000 Subject: [PATCH 50/64] Update Mission Goals dialog --- qtfred/src/ui/FredView.cpp | 5 +-- qtfred/src/ui/dialogs/MissionGoalsDialog.cpp | 34 ++++++-------------- qtfred/src/ui/dialogs/MissionGoalsDialog.h | 1 + qtfred/ui/MissionGoalsDialog.ui | 16 --------- 4 files changed, 14 insertions(+), 42 deletions(-) diff --git a/qtfred/src/ui/FredView.cpp b/qtfred/src/ui/FredView.cpp index e9d1d054bcb..689c7d8b54f 100644 --- a/qtfred/src/ui/FredView.cpp +++ b/qtfred/src/ui/FredView.cpp @@ -1143,8 +1143,9 @@ void FredView::on_actionVoice_Acting_Manager_triggered(bool) { dialog.exec(); } void FredView::on_actionMission_Objectives_triggered(bool) { - dialogs::MissionGoalsDialog dialog(this, _viewport); - dialog.exec(); + auto dialog = new dialogs::MissionGoalsDialog(this, _viewport); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); } void FredView::on_actionFiction_Viewer_triggered(bool) { diff --git a/qtfred/src/ui/dialogs/MissionGoalsDialog.cpp b/qtfred/src/ui/dialogs/MissionGoalsDialog.cpp index a641153d558..022e2519570 100644 --- a/qtfred/src/ui/dialogs/MissionGoalsDialog.cpp +++ b/qtfred/src/ui/dialogs/MissionGoalsDialog.cpp @@ -2,7 +2,7 @@ #include "MissionGoalsDialog.h" #include "ui/util/SignalBlockers.h" - +#include "mission/util.h" #include "ui_MissionGoalsDialog.h" namespace fso { @@ -26,7 +26,7 @@ MissionGoalsDialog::MissionGoalsDialog(QWidget* parent, EditorViewport* viewport ui->helpTextBox->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); connect(this, &QDialog::accepted, _model.get(), &MissionGoalsDialogModel::apply); - connect(this, &QDialog::rejected, _model.get(), &MissionGoalsDialogModel::reject); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &MissionGoalsDialog::rejectHandler); connect(_model.get(), &MissionGoalsDialogModel::modelChanged, this, &MissionGoalsDialog::updateUI); @@ -198,29 +198,15 @@ void MissionGoalsDialog::changeGoalCategory(int type) { recreate_tree(); } } -void MissionGoalsDialog::closeEvent(QCloseEvent* event) { - if (_model->query_modified()) { - auto result = QMessageBox::question(this, - "Close", - "Do you want to keep your changes?", - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, - QMessageBox::Cancel); - - if (result == QMessageBox::Cancel) { - event->ignore(); - return; - } - - if (result == QMessageBox::Yes) { - accept(); - event->accept(); - return; - } - } - - QDialog::closeEvent(event); +void MissionGoalsDialog::closeEvent(QCloseEvent* e) { + if (!rejectOrCloseHandler(this, _model.get(), _viewport)) { + e->ignore(); + }; +} +void MissionGoalsDialog::rejectHandler() +{ + this->close(); } - } } } diff --git a/qtfred/src/ui/dialogs/MissionGoalsDialog.h b/qtfred/src/ui/dialogs/MissionGoalsDialog.h index 7f633033154..8cbe7a3d150 100644 --- a/qtfred/src/ui/dialogs/MissionGoalsDialog.h +++ b/qtfred/src/ui/dialogs/MissionGoalsDialog.h @@ -28,6 +28,7 @@ class MissionGoalsDialog : public QDialog, public SexpTreeEditorInterface protected: void closeEvent(QCloseEvent* event) override; + void rejectHandler(); private: void updateUI(); diff --git a/qtfred/ui/MissionGoalsDialog.ui b/qtfred/ui/MissionGoalsDialog.ui index 07dc8176d3f..3d41d59779e 100644 --- a/qtfred/ui/MissionGoalsDialog.ui +++ b/qtfred/ui/MissionGoalsDialog.ui @@ -255,22 +255,6 @@ - - buttonBox - rejected() - fso::fred::dialogs::MissionGoalsDialog - reject() - - - 506 - 119 - - - 286 - 274 - - - buttonBox accepted() From abdef2ca07ec93f2521217beebca7089601626e9 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Sat, 28 Dec 2024 14:26:45 +0000 Subject: [PATCH 51/64] Update Object Orientation dialog --- qtfred/src/ui/FredView.cpp | 3 +- .../ui/dialogs/ObjectOrientEditorDialog.cpp | 28 +++++++------------ .../src/ui/dialogs/ObjectOrientEditorDialog.h | 1 + qtfred/ui/ObjectOrientationDialog.ui | 16 ----------- 4 files changed, 13 insertions(+), 35 deletions(-) diff --git a/qtfred/src/ui/FredView.cpp b/qtfred/src/ui/FredView.cpp index 689c7d8b54f..3dafe37db9b 100644 --- a/qtfred/src/ui/FredView.cpp +++ b/qtfred/src/ui/FredView.cpp @@ -863,8 +863,9 @@ void FredView::mouseDoubleClickEvent(QMouseEvent* event) { } void FredView::orientEditorTriggered() { auto dialog = new dialogs::ObjectOrientEditorDialog(this, _viewport); + dialog->setAttribute(Qt::WA_DeleteOnClose); // This is a modal dialog - dialog->exec(); + dialog->show(); } void FredView::onUpdateEditorActions() { ui->actionObjects->setEnabled(query_valid_object(fred->currentObject)); diff --git a/qtfred/src/ui/dialogs/ObjectOrientEditorDialog.cpp b/qtfred/src/ui/dialogs/ObjectOrientEditorDialog.cpp index 4b5195e50f1..9e8c0e2b56b 100644 --- a/qtfred/src/ui/dialogs/ObjectOrientEditorDialog.cpp +++ b/qtfred/src/ui/dialogs/ObjectOrientEditorDialog.cpp @@ -6,7 +6,7 @@ #include "ui_ObjectOrientationDialog.h" #include - +#include "mission/util.h" #include namespace fso { @@ -19,7 +19,7 @@ ObjectOrientEditorDialog::ObjectOrientEditorDialog(FredView* parent, EditorViewp ui->setupUi(this); connect(this, &QDialog::accepted, _model.get(), &ObjectOrientEditorDialogModel::apply); - connect(this, &QDialog::rejected, _model.get(), &ObjectOrientEditorDialogModel::reject); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &ObjectOrientEditorDialog::rejectHandler); connect(_model.get(), &AbstractDialogModel::modelChanged, this, &ObjectOrientEditorDialog::updateUI); @@ -67,23 +67,15 @@ ObjectOrientEditorDialog::ObjectOrientEditorDialog(FredView* parent, EditorViewp ObjectOrientEditorDialog::~ObjectOrientEditorDialog() { } -void ObjectOrientEditorDialog::closeEvent(QCloseEvent* event) { - if (_model->query_modified()) { - auto button = _viewport->dialogProvider->showButtonDialog(DialogType::Question, "Changes detected", "Do you want to keep your changes?", - { DialogButton::Yes, DialogButton::No, DialogButton::Cancel }); - - if (button == DialogButton::Cancel) { - event->ignore(); - return; - } - - if (button == DialogButton::Yes) { - accept(); - return; - } - } +void ObjectOrientEditorDialog::closeEvent(QCloseEvent* e) { + if (!rejectOrCloseHandler(this, _model.get(), _viewport)) { + e->ignore(); + }; +} - QDialog::closeEvent(event); +void ObjectOrientEditorDialog::rejectHandler() +{ + this->close(); } void ObjectOrientEditorDialog::updateUI() { diff --git a/qtfred/src/ui/dialogs/ObjectOrientEditorDialog.h b/qtfred/src/ui/dialogs/ObjectOrientEditorDialog.h index 7bac4890026..b50a10057f8 100644 --- a/qtfred/src/ui/dialogs/ObjectOrientEditorDialog.h +++ b/qtfred/src/ui/dialogs/ObjectOrientEditorDialog.h @@ -22,6 +22,7 @@ class ObjectOrientEditorDialog : public QDialog { protected: void closeEvent(QCloseEvent*) override; + void rejectHandler(); private: std::unique_ptr ui; diff --git a/qtfred/ui/ObjectOrientationDialog.ui b/qtfred/ui/ObjectOrientationDialog.ui index 1a599168802..5b3bd355d34 100644 --- a/qtfred/ui/ObjectOrientationDialog.ui +++ b/qtfred/ui/ObjectOrientationDialog.ui @@ -292,21 +292,5 @@ - - buttonBox - rejected() - fso::fred::dialogs::ObjectOrientEditorDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - From 68c2fcc31c160e528128c7cdc8357b9c06a769a9 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Sat, 28 Dec 2024 14:28:32 +0000 Subject: [PATCH 52/64] Update Ship editor dialog --- .../dialogs/ShipEditor/ShipEditorDialogModel.h | 15 --------------- .../ui/dialogs/ShipEditor/ShipEditorDialog.cpp | 2 +- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipEditorDialogModel.h b/qtfred/src/mission/dialogs/ShipEditor/ShipEditorDialogModel.h index d864efcecce..5a2ac86f0a9 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipEditorDialogModel.h +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipEditorDialogModel.h @@ -15,9 +15,6 @@ namespace dialogs { class ShipEditorDialogModel : public AbstractDialogModel { private: - template - void modify(T& a, const T& b); - int _m_no_departure_warp; int _m_no_arrival_warp; bool _m_player_ship; @@ -76,7 +73,6 @@ class ShipEditorDialogModel : public AbstractDialogModel { public: ShipEditorDialogModel(QObject* parent, EditorViewport* viewport); void initializeData(); - bool _modified = false; bool apply() override; void reject() override; @@ -232,17 +228,6 @@ class ShipEditorDialogModel : public AbstractDialogModel { */ int getIfPlayerShip() const; }; - -template -inline void ShipEditorDialogModel::modify(T& a, const T& b) -{ - if (a != b) { - a = b; - set_modified(); - update_data(); - modelChanged(); - } -} } // namespace dialogs } // namespace fred } // namespace fso \ No newline at end of file diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp b/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp index 15fff8905f8..d69dc3a7b5d 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp @@ -203,7 +203,7 @@ void ShipEditorDialog::on_tblInfoButton_clicked() void ShipEditorDialog::update() { if (this->isVisible()) { - if (_model->getNumSelectedObjects() && _model->_modified) { + if (_model->getNumSelectedObjects() && _model->query_modified()) { _model->apply(); } _model->initializeData(); From 30bc3c78acf7cf195a22963562dad90efd8705f8 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Sat, 28 Dec 2024 14:29:11 +0000 Subject: [PATCH 53/64] Add delete on close to dialogs that don't need other changes --- qtfred/src/ui/FredView.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qtfred/src/ui/FredView.cpp b/qtfred/src/ui/FredView.cpp index 3dafe37db9b..1c7bac04cd9 100644 --- a/qtfred/src/ui/FredView.cpp +++ b/qtfred/src/ui/FredView.cpp @@ -704,6 +704,7 @@ void FredView::keyReleaseEvent(QKeyEvent* event) { } void FredView::on_actionEvents_triggered(bool) { auto eventEditor = new dialogs::EventEditorDialog(this, _viewport); + eventEditor->setAttribute(Qt::WA_DeleteOnClose); eventEditor->show(); } void FredView::on_actionSelectionLock_triggered(bool enabled) { @@ -733,6 +734,7 @@ void FredView::on_actionMission_Specs_triggered(bool) { } void FredView::on_actionWaypoint_Paths_triggered(bool) { auto editorDialog = new dialogs::WaypointEditorDialog(this, _viewport); + editorDialog->setAttribute(Qt::WA_DeleteOnClose); editorDialog->show(); } void FredView::on_actionShips_triggered(bool) @@ -981,7 +983,8 @@ bool FredView::showModalDialog(IBaseDialog* dlg) { void FredView::on_actionSelectionList_triggered(bool) { auto dialog = new dialogs::SelectionDialog(this, _viewport); // This is a modal dialog - dialog->exec(); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); } void FredView::on_actionOrbitSelected_triggered(bool enabled) { _viewport->Lookat_mode = enabled; From bb589059c55864b83faed03ec029e457327328e8 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Sat, 28 Dec 2024 14:37:22 +0000 Subject: [PATCH 54/64] Update Wing names dialog --- .../dialogs/CustomWingNamesDialogModel.h | 10 ------- .../src/ui/dialogs/CustomWingNamesDialog.cpp | 29 +++++++------------ qtfred/src/ui/dialogs/CustomWingNamesDialog.h | 2 ++ qtfred/src/ui/dialogs/MissionSpecDialog.cpp | 3 +- qtfred/ui/CustomWingNamesDialog.ui | 16 ---------- 5 files changed, 14 insertions(+), 46 deletions(-) diff --git a/qtfred/src/mission/dialogs/CustomWingNamesDialogModel.h b/qtfred/src/mission/dialogs/CustomWingNamesDialogModel.h index e9ba5f3c933..87b7d6340b8 100644 --- a/qtfred/src/mission/dialogs/CustomWingNamesDialogModel.h +++ b/qtfred/src/mission/dialogs/CustomWingNamesDialogModel.h @@ -24,22 +24,12 @@ class CustomWingNamesDialogModel : public AbstractDialogModel { private: void initializeData(); - template - void modify(T &a, T &b); SCP_string _m_starting[3]; SCP_string _m_squadron[5]; SCP_string _m_tvt[2]; }; -template -inline void CustomWingNamesDialogModel::modify(T & a, T & b) { - if (a != b) { - a = b; - modelChanged(); - } -} - } } } diff --git a/qtfred/src/ui/dialogs/CustomWingNamesDialog.cpp b/qtfred/src/ui/dialogs/CustomWingNamesDialog.cpp index bb69959db07..56deb21eb95 100644 --- a/qtfred/src/ui/dialogs/CustomWingNamesDialog.cpp +++ b/qtfred/src/ui/dialogs/CustomWingNamesDialog.cpp @@ -1,6 +1,7 @@ #include "CustomWingNamesDialog.h" #include "ui_CustomWingNamesDialog.h" +#include namespace fso { namespace fred { @@ -12,7 +13,7 @@ CustomWingNamesDialog::CustomWingNamesDialog(QWidget* parent, EditorViewport* vi ui->setupUi(this); connect(this, &QDialog::accepted, _model.get(), &CustomWingNamesDialogModel::apply); - connect(this, &QDialog::rejected, _model.get(), &CustomWingNamesDialogModel::reject); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &CustomWingNamesDialog::rejectHandler); connect(_model.get(), &AbstractDialogModel::modelChanged, this, &CustomWingNamesDialog::updateUI); @@ -41,25 +42,15 @@ CustomWingNamesDialog::CustomWingNamesDialog(QWidget* parent, EditorViewport* vi CustomWingNamesDialog::~CustomWingNamesDialog() { } -void CustomWingNamesDialog::closeEvent(QCloseEvent * event) { - if (_model->query_modified()) { - auto button = _viewport->dialogProvider->showButtonDialog(DialogType::Question, "Changes detected", "Do you want to keep your changes?", - { DialogButton::Yes, DialogButton::No, DialogButton::Cancel }); - - if (button == DialogButton::Cancel) { - event->ignore(); - return; - } - - if (button == DialogButton::Yes) { - accept(); - return; - } - } - - QDialog::closeEvent(event); +void CustomWingNamesDialog::closeEvent(QCloseEvent * e) { + if (!rejectOrCloseHandler(this, _model.get(), _viewport)) { + e->ignore(); + }; +} +void CustomWingNamesDialog::rejectHandler() +{ + this->close(); } - void CustomWingNamesDialog::updateUI() { // Update starting wings ui->startingWing_1->setText(_model->getStartingWing(0).c_str()); diff --git a/qtfred/src/ui/dialogs/CustomWingNamesDialog.h b/qtfred/src/ui/dialogs/CustomWingNamesDialog.h index f50f209aae2..338f9b76cb1 100644 --- a/qtfred/src/ui/dialogs/CustomWingNamesDialog.h +++ b/qtfred/src/ui/dialogs/CustomWingNamesDialog.h @@ -27,6 +27,8 @@ class CustomWingNamesDialog : public QDialog protected: void closeEvent(QCloseEvent*) override; + void rejectHandler(); + private: std::unique_ptr ui; std::unique_ptr _model; diff --git a/qtfred/src/ui/dialogs/MissionSpecDialog.cpp b/qtfred/src/ui/dialogs/MissionSpecDialog.cpp index 8679c03c6c1..30b68f25370 100644 --- a/qtfred/src/ui/dialogs/MissionSpecDialog.cpp +++ b/qtfred/src/ui/dialogs/MissionSpecDialog.cpp @@ -299,7 +299,8 @@ void MissionSpecDialog::squadronNameChanged(const QString & string) { void MissionSpecDialog::on_customWingNameButton_clicked() { CustomWingNamesDialog* dialog = new CustomWingNamesDialog(this, _viewport); - dialog->exec(); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); } void MissionSpecDialog::on_squadronLogoButton_clicked() { diff --git a/qtfred/ui/CustomWingNamesDialog.ui b/qtfred/ui/CustomWingNamesDialog.ui index 228b47be1d7..51f16a5f6ec 100644 --- a/qtfred/ui/CustomWingNamesDialog.ui +++ b/qtfred/ui/CustomWingNamesDialog.ui @@ -138,21 +138,5 @@ - - buttonBox - rejected() - fso::fred::dialogs::CustomWingNamesDialog - reject() - - - 603 - 50 - - - 324 - 50 - - - From 76abb2819dd80e294d142b37508ae3996559960f Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Sat, 28 Dec 2024 14:52:53 +0000 Subject: [PATCH 55/64] Update Command Briefing Dialog --- .../dialogs/CommandBriefingDialogModel.cpp | 17 +++++++++++++---- qtfred/src/ui/FredView.cpp | 1 + .../src/ui/dialogs/CommandBriefingDialog.cpp | 18 +++++++++++++----- qtfred/src/ui/dialogs/CommandBriefingDialog.h | 2 ++ qtfred/ui/CommandBriefingDialog.ui | 16 ---------------- 5 files changed, 29 insertions(+), 25 deletions(-) diff --git a/qtfred/src/mission/dialogs/CommandBriefingDialogModel.cpp b/qtfred/src/mission/dialogs/CommandBriefingDialogModel.cpp index 4c41327023a..14d2502bcbb 100644 --- a/qtfred/src/mission/dialogs/CommandBriefingDialogModel.cpp +++ b/qtfred/src/mission/dialogs/CommandBriefingDialogModel.cpp @@ -144,7 +144,7 @@ void CommandBriefingDialogModel::addStage() _wipCommandBrief.num_stages++; _currentStage = _wipCommandBrief.num_stages - 1; - + set_modified(); modelChanged(); } @@ -155,6 +155,7 @@ void CommandBriefingDialogModel::insertStage() if (_wipCommandBrief.num_stages >= CMD_BRIEF_STAGES_MAX) { _wipCommandBrief.num_stages = CMD_BRIEF_STAGES_MAX; + set_modified(); modelChanged(); // signal that the model has changed, in case of inexplicable invalid index. return; } @@ -164,6 +165,7 @@ void CommandBriefingDialogModel::insertStage() for (int i = _wipCommandBrief.num_stages - 1; i > _currentStage; i--) { _wipCommandBrief.stage[i] = _wipCommandBrief.stage[i - 1]; } + set_modified(); modelChanged(); } @@ -181,6 +183,7 @@ void CommandBriefingDialogModel::deleteStage() _wipCommandBrief.stage[0].wave = -1; memset(_wipCommandBrief.stage[0].wave_filename, 0, CF_MAX_FILENAME_LENGTH); memset(_wipCommandBrief.stage[0].ani_filename, 0, CF_MAX_FILENAME_LENGTH); + set_modified(); modelChanged(); return; } @@ -293,13 +296,15 @@ int CommandBriefingDialogModel::getSpeechInstanceNumber() void CommandBriefingDialogModel::setBriefingText(const SCP_string& briefingText) { - _wipCommandBrief.stage[_currentStage].text = briefingText; + _wipCommandBrief.stage[_currentStage].text = briefingText; + set_modified(); modelChanged(); } void CommandBriefingDialogModel::setAnimationFilename(const SCP_string& animationFilename) { - strcpy_s(_wipCommandBrief.stage[_currentStage].ani_filename, animationFilename.c_str()); + strcpy_s(_wipCommandBrief.stage[_currentStage].ani_filename, animationFilename.c_str()); + set_modified(); modelChanged(); } @@ -307,24 +312,28 @@ void CommandBriefingDialogModel::setSpeechFilename(const SCP_string& speechFilen { _soundTestUpdateRequired = true; strcpy_s(_wipCommandBrief.stage[_currentStage].wave_filename, speechFilename.c_str()); - setWaveID(); + setWaveID(); + set_modified(); modelChanged(); } void CommandBriefingDialogModel::setCurrentTeam(const ubyte& teamIn) { _currentTeam = teamIn; + set_modified(); }; // not yet fully supported void CommandBriefingDialogModel::setLowResolutionFilename(const SCP_string& lowResolutionFilename) { strcpy_s(_wipCommandBrief.background[0], lowResolutionFilename.c_str()); + set_modified(); modelChanged(); } void CommandBriefingDialogModel::setHighResolutionFilename(const SCP_string& highResolutionFilename) { strcpy_s(_wipCommandBrief.background[1], highResolutionFilename.c_str()); + set_modified(); modelChanged(); } diff --git a/qtfred/src/ui/FredView.cpp b/qtfred/src/ui/FredView.cpp index 1c7bac04cd9..67e722207ce 100644 --- a/qtfred/src/ui/FredView.cpp +++ b/qtfred/src/ui/FredView.cpp @@ -753,6 +753,7 @@ void FredView::on_actionObjects_triggered(bool) { } void FredView::on_actionCommand_Briefing_triggered(bool) { auto editorDialog = new dialogs::CommandBriefingDialog(this, _viewport); + editorDialog->setAttribute(Qt::WA_DeleteOnClose); editorDialog->show(); } void FredView::on_actionReinforcements_triggered(bool) { diff --git a/qtfred/src/ui/dialogs/CommandBriefingDialog.cpp b/qtfred/src/ui/dialogs/CommandBriefingDialog.cpp index 83ab998b6a9..ad7c46358d4 100644 --- a/qtfred/src/ui/dialogs/CommandBriefingDialog.cpp +++ b/qtfred/src/ui/dialogs/CommandBriefingDialog.cpp @@ -1,6 +1,6 @@ #include "CommandBriefingDialog.h" #include "ui_CommandBriefingDialog.h" - +#include "mission/util.h" #include #include #include @@ -22,7 +22,7 @@ namespace dialogs { connect(this, &QDialog::accepted, _model.get(), &CommandBriefingDialogModel::apply); connect(viewport->editor, &Editor::currentObjectChanged, _model.get(), &CommandBriefingDialogModel::apply); connect(viewport->editor, &Editor::objectMarkingChanged, _model.get(), &CommandBriefingDialogModel::apply); - connect(this, &QDialog::rejected, _model.get(), &CommandBriefingDialogModel::reject); + connect(ui->okAndCancelButtons, &QDialogButtonBox::rejected, this, &CommandBriefingDialog::rejectHandler); connect(ui->actionChangeTeams, static_cast(&QSpinBox::valueChanged), @@ -283,8 +283,16 @@ namespace dialogs { CommandBriefingDialog::~CommandBriefingDialog() {}; //NOLINT - void CommandBriefingDialog::closeEvent(QCloseEvent*){} - -} // dialogs + void CommandBriefingDialog::closeEvent(QCloseEvent* e) + { + if (!rejectOrCloseHandler(this, _model.get(), _viewport)) { + e->ignore(); + }; + } + void CommandBriefingDialog::rejectHandler() + { + this->close(); + } + } // dialogs } // fred } // fso \ No newline at end of file diff --git a/qtfred/src/ui/dialogs/CommandBriefingDialog.h b/qtfred/src/ui/dialogs/CommandBriefingDialog.h index 005aecb06f4..4b1c27ba01c 100644 --- a/qtfred/src/ui/dialogs/CommandBriefingDialog.h +++ b/qtfred/src/ui/dialogs/CommandBriefingDialog.h @@ -29,6 +29,8 @@ class CommandBriefingDialog : public QDialog { protected: void closeEvent(QCloseEvent*) override; + void rejectHandler(); + private slots: // where the buttons go void on_actionPrevStage_clicked(); void on_actionNextStage_clicked(); diff --git a/qtfred/ui/CommandBriefingDialog.ui b/qtfred/ui/CommandBriefingDialog.ui index d1a67643de2..a3d2171d896 100644 --- a/qtfred/ui/CommandBriefingDialog.ui +++ b/qtfred/ui/CommandBriefingDialog.ui @@ -352,21 +352,5 @@ - - okAndCancelButtons - rejected() - fso::fred::dialogs::CommandBriefingDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - From d6c9ff43d72cbe7ad443ad4748d471cde2cccf45 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Sat, 28 Dec 2024 15:07:29 +0000 Subject: [PATCH 56/64] Asteroid editor and add delete on close to a few more unchanged dialogs --- .../dialogs/AsteroidEditorDialogModel.cpp | 16 ------ .../dialogs/AsteroidEditorDialogModel.h | 16 ------ qtfred/src/ui/FredView.cpp | 19 +++++-- .../src/ui/dialogs/AsteroidEditorDialog.cpp | 57 +++++-------------- qtfred/src/ui/dialogs/AsteroidEditorDialog.h | 7 ++- qtfred/ui/AsteroidEditorDialog.ui | 16 ------ 6 files changed, 33 insertions(+), 98 deletions(-) diff --git a/qtfred/src/mission/dialogs/AsteroidEditorDialogModel.cpp b/qtfred/src/mission/dialogs/AsteroidEditorDialogModel.cpp index 74a86c03521..ca2ff87720a 100644 --- a/qtfred/src/mission/dialogs/AsteroidEditorDialogModel.cpp +++ b/qtfred/src/mission/dialogs/AsteroidEditorDialogModel.cpp @@ -26,7 +26,6 @@ AsteroidEditorDialogModel::AsteroidEditorDialogModel(QObject* parent, EditorView _field_type(FT_ACTIVE), _debris_genre(DG_ASTEROID), _bypass_errors(false), - _modified(false), _cur_field(0), _last_field(-1) { @@ -494,21 +493,6 @@ void AsteroidEditorDialogModel::update_init() _last_field = _cur_field; } -void AsteroidEditorDialogModel::set_modified() -{ - _modified = true; -} - -void AsteroidEditorDialogModel::unset_modified() -{ - _modified = false; -} - -bool AsteroidEditorDialogModel::get_modified() -{ - return _modified; -} - void AsteroidEditorDialogModel::showErrorDialogNoCancel(const SCP_string& message) { if (_bypass_errors) { diff --git a/qtfred/src/mission/dialogs/AsteroidEditorDialogModel.h b/qtfred/src/mission/dialogs/AsteroidEditorDialogModel.h index 0d65b6abd2b..ffe409d2521 100644 --- a/qtfred/src/mission/dialogs/AsteroidEditorDialogModel.h +++ b/qtfred/src/mission/dialogs/AsteroidEditorDialogModel.h @@ -61,13 +61,7 @@ Q_OBJECT void update_init(); bool validate_data(); - void set_modified(); - void unset_modified(); - bool get_modified(); - private: - template - void modify(T &a, const T &b); void showErrorDialogNoCancel(const SCP_string& message); void initializeData(); @@ -98,7 +92,6 @@ Q_OBJECT asteroid_field _a_field; // :v: had unfinished plans for multiple fields? bool _bypass_errors; - bool _modified; int _cur_field; int _last_field; @@ -110,15 +103,6 @@ Q_OBJECT std::unordered_map debris_inverse_idx_lookup; }; -template -inline void AsteroidEditorDialogModel::modify(T &a, const T &b) { - if (a != b) { - a = b; - set_modified(); - modelChanged(); - } -} - } // namespace dialogs } // namespace fred } // namespace fso diff --git a/qtfred/src/ui/FredView.cpp b/qtfred/src/ui/FredView.cpp index 67e722207ce..eb8d2893faa 100644 --- a/qtfred/src/ui/FredView.cpp +++ b/qtfred/src/ui/FredView.cpp @@ -721,10 +721,12 @@ void FredView::onShipClassSelected(int ship_class) { } void FredView::on_actionAsteroid_Field_triggered(bool) { auto asteroidFieldEditor = new dialogs::AsteroidEditorDialog(this, _viewport); + asteroidFieldEditor->setAttribute(Qt::WA_DeleteOnClose); asteroidFieldEditor->show(); } void FredView::on_actionBriefing_triggered(bool) { auto eventEditor = new dialogs::BriefingEditorDialog(this); + eventEditor->setAttribute(Qt::WA_DeleteOnClose); eventEditor->show(); } void FredView::on_actionMission_Specs_triggered(bool) { @@ -740,12 +742,14 @@ void FredView::on_actionWaypoint_Paths_triggered(bool) { void FredView::on_actionShips_triggered(bool) { auto editorDialog = new dialogs::ShipEditorDialog(this, _viewport); + editorDialog->setAttribute(Qt::WA_DeleteOnClose); editorDialog->show(); } void FredView::on_actionCampaign_triggered(bool) { //TODO: Save if Changes auto editorCampaign = new dialogs::CampaignEditorDialog(this, _viewport); + editorCampaign->setAttribute(Qt::WA_DeleteOnClose); editorCampaign->show(); } void FredView::on_actionObjects_triggered(bool) { @@ -1128,13 +1132,15 @@ void FredView::on_actionError_Checker_triggered(bool) { fred->global_error_check(); } void FredView::on_actionAbout_triggered(bool) { - dialogs::AboutDialog dialog(this); - dialog.exec(); + auto dialog = new dialogs::AboutDialog(this); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); } void FredView::on_actionBackground_triggered(bool) { - dialogs::BackgroundEditorDialog dialog(this, _viewport); - dialog.exec(); + auto dialog = new dialogs::BackgroundEditorDialog(this, _viewport); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); } void FredView::on_actionShield_System_triggered(bool) { @@ -1144,8 +1150,9 @@ void FredView::on_actionShield_System_triggered(bool) { } void FredView::on_actionVoice_Acting_Manager_triggered(bool) { - dialogs::VoiceActingManager dialog(this, _viewport); - dialog.exec(); + auto dialog = new dialogs::VoiceActingManager(this, _viewport); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); } void FredView::on_actionMission_Objectives_triggered(bool) { auto dialog = new dialogs::MissionGoalsDialog(this, _viewport); diff --git a/qtfred/src/ui/dialogs/AsteroidEditorDialog.cpp b/qtfred/src/ui/dialogs/AsteroidEditorDialog.cpp index 39760412e82..a49ec326639 100644 --- a/qtfred/src/ui/dialogs/AsteroidEditorDialog.cpp +++ b/qtfred/src/ui/dialogs/AsteroidEditorDialog.cpp @@ -4,6 +4,7 @@ #include #include "ui_AsteroidEditorDialog.h" +#include namespace fso { namespace fred { @@ -22,6 +23,8 @@ AsteroidEditorDialog::AsteroidEditorDialog(FredView *parent, EditorViewport* vie ui(new Ui::AsteroidEditorDialog()), _model(new AsteroidEditorDialogModel(this, viewport)) { + connect(this, &QDialog::accepted, _model.get(), &AsteroidEditorDialogModel::apply); + connect(ui->dialogButtonBox, &QDialogButtonBox::rejected, this, &AsteroidEditorDialog::rejectHandler); ui->setupUi(this); _model->update_init(); @@ -127,6 +130,18 @@ AsteroidEditorDialog::AsteroidEditorDialog(FredView *parent, EditorViewport* vie AsteroidEditorDialog::~AsteroidEditorDialog() = default; +void AsteroidEditorDialog::closeEvent(QCloseEvent* e) +{ + if (!rejectOrCloseHandler(this, _model.get(), _viewport)) { + e->ignore(); + }; +} + +void AsteroidEditorDialog::rejectHandler() +{ + this->close(); +} + QString & AsteroidEditorDialog::getBoxText(AsteroidEditorDialogModel::_box_line_edits type) { return _model->getBoxText(type); @@ -331,48 +346,6 @@ void AsteroidEditorDialog::updateUI() ui->lineEdit_ibox_maxZ->setText(_model->AsteroidEditorDialogModel::getBoxText(AsteroidEditorDialogModel::_I_MAX_Z)); } - - -void AsteroidEditorDialog::done(int r) -{ - if(QDialog::Accepted == r) // ok was pressed - { - // TODO consider moving the validation to when values are entered - // but just visually indicate that there's a problem at that time - // hard fail (by checking status boolean?) here instead - // i.e. let FREDers have temp bad values when they're changing stuff - // if they know what they're doing - if (_model->apply()) { - // all ok - QDialog::done(r); - _model->unset_modified(); - return; - } - else { - // leave dialog open - return; - } - } - else // cancel, close or exc was pressed - { - if (_model->get_modified()) { - // give FREDer a chance in case they cancelled by mistake - // although I wonder if we're better off with always saving & don't prompt - // ~philisophical~ - auto z = _viewport->dialogProvider->showButtonDialog(DialogType::Question, - "Question", - "You have unsaved changes, do you wish to discard them?", - { DialogButton::Ok, DialogButton::Cancel }); - if (z == DialogButton::Cancel) { - return; - } - } - QDialog::done(r); - _model->unset_modified(); - return; - } -} - } // namespace dialogs } // namespace fred } // namespace fso diff --git a/qtfred/src/ui/dialogs/AsteroidEditorDialog.h b/qtfred/src/ui/dialogs/AsteroidEditorDialog.h index a64a66f47fc..b8ace401067 100644 --- a/qtfred/src/ui/dialogs/AsteroidEditorDialog.h +++ b/qtfred/src/ui/dialogs/AsteroidEditorDialog.h @@ -18,10 +18,13 @@ class AsteroidEditorDialog : public QDialog Q_OBJECT public: AsteroidEditorDialog(FredView* parent, EditorViewport* viewport); - ~AsteroidEditorDialog() override; + ~AsteroidEditorDialog() override; + + protected: + void closeEvent(QCloseEvent* e); + void rejectHandler(); private: - void done(int r) override; void toggleEnabled(bool enabled); void toggleInnerBoxEnabled(bool enabled); diff --git a/qtfred/ui/AsteroidEditorDialog.ui b/qtfred/ui/AsteroidEditorDialog.ui index 1889c2672cb..25d11b03b28 100644 --- a/qtfred/ui/AsteroidEditorDialog.ui +++ b/qtfred/ui/AsteroidEditorDialog.ui @@ -378,22 +378,6 @@ - - dialogButtonBox - rejected() - fso::fred::dialogs::AsteroidEditorDialog - reject() - - - 337 - 317 - - - 337 - 169 - - - From 5b3df074730395a44aee9f887232cd8a7e680852 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Sat, 28 Dec 2024 15:39:05 +0000 Subject: [PATCH 57/64] And unused --- qtfred/src/mission/util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qtfred/src/mission/util.cpp b/qtfred/src/mission/util.cpp index 87ef8542904..2604e9c991e 100644 --- a/qtfred/src/mission/util.cpp +++ b/qtfred/src/mission/util.cpp @@ -93,7 +93,7 @@ void time_to_mission_info_string(const std::tm* src, char* dest, size_t dest_max std::strftime(dest, dest_max_len, "%x at %X", src); } -bool rejectOrCloseHandler(QDialog* dialog, +bool rejectOrCloseHandler(__UNUSED QDialog* dialog, fso::fred::dialogs::AbstractDialogModel* model, fso::fred::EditorViewport* viewport) { From 22ec235c225c9a9ada0a7d42f08be529bb605725 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Sat, 28 Dec 2024 15:53:36 +0000 Subject: [PATCH 58/64] Add missing override --- qtfred/src/ui/dialogs/AsteroidEditorDialog.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qtfred/src/ui/dialogs/AsteroidEditorDialog.h b/qtfred/src/ui/dialogs/AsteroidEditorDialog.h index b8ace401067..3ba6294c5ce 100644 --- a/qtfred/src/ui/dialogs/AsteroidEditorDialog.h +++ b/qtfred/src/ui/dialogs/AsteroidEditorDialog.h @@ -21,7 +21,7 @@ class AsteroidEditorDialog : public QDialog ~AsteroidEditorDialog() override; protected: - void closeEvent(QCloseEvent* e); + void closeEvent(QCloseEvent* e) override; void rejectHandler(); private: From f1271cde031cdf69366aaeb35d09f236359476a6 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Sun, 29 Dec 2024 11:01:59 -0600 Subject: [PATCH 59/64] fix warp flash option (#6494) --- code/fireball/warpineffect.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/code/fireball/warpineffect.cpp b/code/fireball/warpineffect.cpp index 01103f0f08a..3dddb14d971 100644 --- a/code/fireball/warpineffect.cpp +++ b/code/fireball/warpineffect.cpp @@ -34,6 +34,14 @@ static auto WarpFlashOption __UNUSED = options::OptionBuilder("Graphics.Wa .importance(65) .finish(); +bool warpin_show_flash() { + if (Using_in_game_options) { + return WarpFlashOption->getValue(); + } else { + return Fireball_warp_flash; + } +} + void warpin_batch_draw_face( int texture, vertex *v1, vertex *v2, vertex *v3 ) { vec3d norm; @@ -76,7 +84,7 @@ void warpin_queue_render(model_draw_list *scene, object *obj, matrix *orient, ve r *= (0.40f + Noise[noise_frame] * 0.30f); // Bobboau's warp thingie, toggled by cmdline - if (Fireball_warp_flash) { + if (warpin_show_flash()) { r += powf((2.0f * life_percent) - 1.0f, 24.0f) * max_radius * 1.5f; } @@ -159,7 +167,7 @@ void warpin_queue_render(model_draw_list *scene, object *obj, matrix *orient, ve warpin_batch_draw_face( texture_bitmap_num, &verts[0], &verts[3], &verts[4] ); } - if (warp_ball_bitmap >= 0 && Fireball_warp_flash) { + if (warp_ball_bitmap >= 0 && warpin_show_flash()) { flash_ball warp_ball(20, .1f,.25f, &orient->vec.fvec, pos, 4.0f, 0.5f); float adg = (2.0f * life_percent) - 1.0f; From cda02de4a06b32b888b216c85243f240e42ed4a3 Mon Sep 17 00:00:00 2001 From: Daft Mugi Date: Sun, 29 Dec 2024 15:44:37 -0600 Subject: [PATCH 60/64] Fix antlr4 CMake deprecation warnings The deprecation warning that required changes: * CMP0054: Only interpret if() arguments as variables/keywords when unquoted. The deprecation warnings that did NOT require changes as those features were not used: * CMP0026 * CMP0042 * CMP0045 * CMP0059 --- lib/antlr4-cpp-runtime/CMakeLists.txt | 29 +++++-------------- lib/antlr4-cpp-runtime/runtime/CMakeLists.txt | 6 ++-- 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/lib/antlr4-cpp-runtime/CMakeLists.txt b/lib/antlr4-cpp-runtime/CMakeLists.txt index 8100907423a..a26c9ec96b0 100644 --- a/lib/antlr4-cpp-runtime/CMakeLists.txt +++ b/lib/antlr4-cpp-runtime/CMakeLists.txt @@ -1,5 +1,5 @@ # -*- mode:cmake -*- -cmake_minimum_required (VERSION 2.8) +cmake_minimum_required (VERSION 3.7) # 2.8 needed because of ExternalProject # Detect build type, fallback to release and throw a warning if use didn't specify any @@ -25,20 +25,6 @@ option(WITH_STATIC_CRT "(Visual C++) Enable to statically link CRT, which avoids project(LIBANTLR4) -if(CMAKE_VERSION VERSION_EQUAL "3.0.0" OR - CMAKE_VERSION VERSION_GREATER "3.0.0") - CMAKE_POLICY(SET CMP0026 NEW) - CMAKE_POLICY(SET CMP0054 OLD) - CMAKE_POLICY(SET CMP0045 OLD) - CMAKE_POLICY(SET CMP0042 OLD) -endif() - -if(CMAKE_VERSION VERSION_EQUAL "3.3.0" OR - CMAKE_VERSION VERSION_GREATER "3.3.0") - CMAKE_POLICY(SET CMP0059 OLD) - CMAKE_POLICY(SET CMP0054 OLD) -endif() - if(CMAKE_SYSTEM_NAME MATCHES "Linux") #find_package(PkgConfig REQUIRED) #pkg_check_modules(UUID REQUIRED uuid) @@ -57,7 +43,7 @@ if(WITH_DEMO) message(FATAL_ERROR "Missing antlr4.jar location. You can specify it's path using: -DANTLR_JAR_LOCATION=") else() get_filename_component(ANTLR_NAME ${ANTLR_JAR_LOCATION} NAME_WE) - if(NOT EXISTS "${ANTLR_JAR_LOCATION}") + if(NOT EXISTS ANTLR_JAR_LOCATION) message(FATAL_ERROR "Unable to find ${ANTLR_NAME} in ${ANTLR_JAR_LOCATION}") else() message(STATUS "Found ${ANTLR_NAME}: ${ANTLR_JAR_LOCATION}") @@ -73,8 +59,9 @@ else() set(MY_CXX_WARNING_FLAGS " -Wall -pedantic -W") endif() + # Initialize CXXFLAGS. -if("${CMAKE_VERSION}" VERSION_GREATER 3.1.0) +if(CMAKE_VERSION VERSION_GREATER 3.1.0) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) else() @@ -99,20 +86,20 @@ else() endif() # Compiler-specific C++11 activation. -if("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Intel") +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Intel") execute_process( COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) # Just g++-5.0 and greater contain header. (test in ubuntu) if(NOT (GCC_VERSION VERSION_GREATER 5.0 OR GCC_VERSION VERSION_EQUAL 5.0)) message(FATAL_ERROR "${PROJECT_NAME} requires g++ 5.0 or greater.") endif () -elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" AND ANDROID) +elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND ANDROID) # Need -Os cflag and cxxflags here to work with exception handling on armeabi. # see https://github.com/android-ndk/ndk/issues/573 # and without -stdlib=libc++ cxxflags -elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" AND APPLE) +elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND APPLE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -stdlib=libc++") -elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" AND ( CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "FreeBSD") ) +elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND ( CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "FreeBSD") ) execute_process( COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE CLANG_VERSION) if(NOT (CLANG_VERSION VERSION_GREATER 4.2.1 OR CLANG_VERSION VERSION_EQUAL 4.2.1)) diff --git a/lib/antlr4-cpp-runtime/runtime/CMakeLists.txt b/lib/antlr4-cpp-runtime/runtime/CMakeLists.txt index afef4e72c31..8b83486a1fa 100644 --- a/lib/antlr4-cpp-runtime/runtime/CMakeLists.txt +++ b/lib/antlr4-cpp-runtime/runtime/CMakeLists.txt @@ -45,7 +45,7 @@ if(CMAKE_SYSTEM_NAME MATCHES "Linux") #target_link_libraries(antlr4_static ${UUID_LIBRARIES}) elseif(APPLE) # target_link_libraries(antlr4_shared ${COREFOUNDATION_LIBRARY}) - target_link_libraries(antlr4_static ${COREFOUNDATION_LIBRARY}) + target_link_libraries(antlr4_static PRIVATE ${COREFOUNDATION_LIBRARY}) endif() if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") @@ -55,9 +55,9 @@ else() endif() -if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") set(disabled_compile_warnings "${disabled_compile_warnings} -Wno-dollar-in-identifier-extension -Wno-four-char-constants") -elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Intel") +elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Intel") set(disabled_compile_warnings "${disabled_compile_warnings} -Wno-multichar") endif() From 77de5c0aa70086291cd8b16d96f0ab78b8a6f5d6 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Mon, 30 Dec 2024 17:37:05 +0000 Subject: [PATCH 61/64] Address Feedback --- qtfred/src/mission/dialogs/LoadoutEditorDialogModel.cpp | 1 + .../src/mission/dialogs/ReinforcementsEditorDialogModel.cpp | 2 ++ qtfred/src/ui/FredView.cpp | 4 ++-- qtfred/src/ui/dialogs/MissionSpecDialog.cpp | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/qtfred/src/mission/dialogs/LoadoutEditorDialogModel.cpp b/qtfred/src/mission/dialogs/LoadoutEditorDialogModel.cpp index 13c1b883306..b15d0d5d100 100644 --- a/qtfred/src/mission/dialogs/LoadoutEditorDialogModel.cpp +++ b/qtfred/src/mission/dialogs/LoadoutEditorDialogModel.cpp @@ -933,6 +933,7 @@ void LoadoutDialogModel::setRequiredWeapon(const SCP_vector& list, c } set_modified(); buildCurrentLists(); + modelChanged(); } bool LoadoutDialogModel::getSkipValidation() { diff --git a/qtfred/src/mission/dialogs/ReinforcementsEditorDialogModel.cpp b/qtfred/src/mission/dialogs/ReinforcementsEditorDialogModel.cpp index 9714c803890..32b01e37b50 100644 --- a/qtfred/src/mission/dialogs/ReinforcementsEditorDialogModel.cpp +++ b/qtfred/src/mission/dialogs/ReinforcementsEditorDialogModel.cpp @@ -260,6 +260,7 @@ void ReinforcementsDialogModel::setUseCount(int count) for (auto& reinforcement : _selectedReinforcementIndices) { std::get<1>(_reinforcementList[reinforcement]) = count; } + modelChanged(); set_modified(); } @@ -268,6 +269,7 @@ void ReinforcementsDialogModel::setBeforeArrivalDelay(int delay) for (auto& reinforcement : _selectedReinforcementIndices) { std::get<2>(_reinforcementList[reinforcement]) = delay; } + modelChanged(); set_modified(); } diff --git a/qtfred/src/ui/FredView.cpp b/qtfred/src/ui/FredView.cpp index eb8d2893faa..851a71faa1f 100644 --- a/qtfred/src/ui/FredView.cpp +++ b/qtfred/src/ui/FredView.cpp @@ -872,7 +872,7 @@ void FredView::orientEditorTriggered() { auto dialog = new dialogs::ObjectOrientEditorDialog(this, _viewport); dialog->setAttribute(Qt::WA_DeleteOnClose); // This is a modal dialog - dialog->show(); + dialog->exec(); } void FredView::onUpdateEditorActions() { ui->actionObjects->setEnabled(query_valid_object(fred->currentObject)); @@ -989,7 +989,7 @@ void FredView::on_actionSelectionList_triggered(bool) { auto dialog = new dialogs::SelectionDialog(this, _viewport); // This is a modal dialog dialog->setAttribute(Qt::WA_DeleteOnClose); - dialog->show(); + dialog->exec(); } void FredView::on_actionOrbitSelected_triggered(bool enabled) { _viewport->Lookat_mode = enabled; diff --git a/qtfred/src/ui/dialogs/MissionSpecDialog.cpp b/qtfred/src/ui/dialogs/MissionSpecDialog.cpp index 30b68f25370..1b9a87c8f52 100644 --- a/qtfred/src/ui/dialogs/MissionSpecDialog.cpp +++ b/qtfred/src/ui/dialogs/MissionSpecDialog.cpp @@ -300,7 +300,7 @@ void MissionSpecDialog::squadronNameChanged(const QString & string) { void MissionSpecDialog::on_customWingNameButton_clicked() { CustomWingNamesDialog* dialog = new CustomWingNamesDialog(this, _viewport); dialog->setAttribute(Qt::WA_DeleteOnClose); - dialog->show(); + dialog->exec(); } void MissionSpecDialog::on_squadronLogoButton_clicked() { From 369ed0f5c0c9b6d6329c99260b9d9b3686f2a82b Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Wed, 1 Jan 2025 08:54:32 -0600 Subject: [PATCH 62/64] prefer ship tech model if it exists (#6499) --- code/model/modelrender.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/code/model/modelrender.cpp b/code/model/modelrender.cpp index 2b60a60b2b3..8070a256bda 100644 --- a/code/model/modelrender.cpp +++ b/code/model/modelrender.cpp @@ -3137,7 +3137,11 @@ bool render_tech_model(tech_render_type model_type, int x1, int y1, int x2, int } // Make sure model is loaded - model_num = model_load(sip->pof_file, sip->n_subsystems, &sip->subsystems[0], 0); + if (VALID_FNAME(sip->pof_file_tech)) { + model_num = model_load(sip->pof_file_tech, sip->n_subsystems, &sip->subsystems[0], 0); + } else { + model_num = model_load(sip->pof_file, sip->n_subsystems, &sip->subsystems[0], 0); + } render_info.set_replacement_textures(model_num, sip->replacement_textures); break; From fe722cbab7c3a7918a7171b724c105c8c813dd56 Mon Sep 17 00:00:00 2001 From: wookieejedi Date: Thu, 2 Jan 2025 06:53:39 -0500 Subject: [PATCH 63/64] Fix `Mouse` XSTR Index in In-game options (#6501) In the In-game options for `mouse_mode` the XSTR index for "Mouse" was set to 1774, which is already used for "Glide When Pressed". Luckily "Mouse" XSTR already exists with Index 1373, so this PR changes it to that. --- code/io/mouse.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/io/mouse.cpp b/code/io/mouse.cpp index e0d3ab0c39c..5e3c5d12c10 100644 --- a/code/io/mouse.cpp +++ b/code/io/mouse.cpp @@ -70,7 +70,7 @@ static auto MouseSensitivityOption __UNUSED = options::OptionBuilder("Input bool Use_mouse_to_fly = false; -static SCP_string mouse_mode_display(bool mode) { return mode ? XSTR("Joy-0", 1699) : XSTR("Mouse", 1774); } +static SCP_string mouse_mode_display(bool mode) { return mode ? XSTR("Joy-0", 1699) : XSTR("Mouse", 1373); } static auto UseMouseOption __UNUSED = options::OptionBuilder("Input.UseMouse", std::pair{"Mouse", 1373}, From ca243a0a6a452ab5b019367486bd21f4462f2fac Mon Sep 17 00:00:00 2001 From: Asteroth Date: Thu, 2 Jan 2025 06:56:46 -0500 Subject: [PATCH 64/64] Make explicit shield hit radius for ships and weapons (#6478) * allow shield effect radius overrides * this should be a float * change variable name * skip rendering 0 radius --- code/object/collideshipweapon.cpp | 2 +- code/ship/shield.cpp | 75 ++++++++++++++++++------------- code/ship/ship.cpp | 7 +++ code/ship/ship.h | 3 +- code/weapon/beam.cpp | 2 +- code/weapon/weapon.h | 3 +- code/weapon/weapons.cpp | 5 +++ 7 files changed, 63 insertions(+), 34 deletions(-) diff --git a/code/object/collideshipweapon.cpp b/code/object/collideshipweapon.cpp index b369140b182..d88a94bb693 100644 --- a/code/object/collideshipweapon.cpp +++ b/code/object/collideshipweapon.cpp @@ -454,7 +454,7 @@ static int ship_weapon_check_collision(object *ship_objp, object *weapon_objp, f if (quadrant_num >= 0) { // do the hit effect if ( mc_shield.shield_hit_tri != -1 && (mc_shield.hit_dist*(flFrametime + time_limit) - flFrametime) < 0.0f ) { - add_shield_point(OBJ_INDEX(ship_objp), mc_shield.shield_hit_tri, &mc_shield.hit_point); + add_shield_point(OBJ_INDEX(ship_objp), mc_shield.shield_hit_tri, &mc_shield.hit_point, wip->shield_impact_effect_radius); } // if this weapon pierces the shield, then do the hit effect, but act like a shield collision never occurred; diff --git a/code/ship/shield.cpp b/code/ship/shield.cpp index 4b099adbeee..63fc2b5d54b 100644 --- a/code/ship/shield.cpp +++ b/code/ship/shield.cpp @@ -58,9 +58,10 @@ typedef struct gshield_tri { } gshield_tri; typedef struct shield_hit { - int start_time; // start time of this object + fix start_time; // start time of this object int type; // type, probably the weapon type, to indicate the bitmap to use int objnum; // Object index, needed to get current orientation, position. + float radius_override; // the weapon which caused the hit may adjust the size of the effect int num_tris; // Number of Shield_tris comprising this shield. int tri_list[MAX_TRIS_PER_HIT]; // Indices into Shield_tris, triangles for this shield hit. ubyte rgb[3]; // rgb colors @@ -76,6 +77,7 @@ typedef struct shield_point { int objnum; // Object that was hit. int shield_tri; // Triangle in shield mesh that took hit. vec3d hit_point; // Point in global 3-space of hit. + float radius_override; } shield_point; #define MAX_SHIELD_POINTS 100 @@ -425,14 +427,15 @@ void render_shield(int shield_num) object *objp; ship *shipp; ship_info *si; + shield_hit* sh = &Shield_hits[shield_num]; - if (Shield_hits[shield_num].type == SH_UNUSED) { + if (sh->type == SH_UNUSED) { return; } - Assert(Shield_hits[shield_num].objnum >= 0); + Assert(sh->objnum >= 0); - objp = &Objects[Shield_hits[shield_num].objnum]; + objp = &Objects[sh->objnum]; if (objp->flags[Object::Object_Flags::No_shields]) { return; @@ -440,13 +443,13 @@ void render_shield(int shield_num) // If this object didn't get rendered, don't render its shields. In fact, make the shield hit go away. if (!(objp->flags[Object::Object_Flags::Was_rendered])) { - Shield_hits[shield_num].type = SH_UNUSED; + sh->type = SH_UNUSED; return; } // At detail levels 1, 3, animations play at double speed to reduce load. if ( (Detail.shield_effects == 1) || (Detail.shield_effects == 3) ) { - Shield_hits[shield_num].start_time -= Frametime; + sh->start_time -= Frametime; } MONITOR_INC(NumShieldRend,1); @@ -457,18 +460,18 @@ void render_shield(int shield_num) // If this ship is in its deathroll, make the shield hit effects go away faster. if (shipp->flags[Ship::Ship_Flags::Dying]) { - Shield_hits[shield_num].start_time -= fl2f(2*flFrametime); + sh->start_time -= fl2f(2*flFrametime); } // Detail level stuff. When lots of shield hits, maybe make them go away faster. if (Poly_count > 50) { - if (Shield_hits[shield_num].start_time + (SHIELD_HIT_DURATION*50)/Poly_count < Missiontime) { - Shield_hits[shield_num].type = SH_UNUSED; + if (sh->start_time + (SHIELD_HIT_DURATION*50)/Poly_count < Missiontime) { + sh->type = SH_UNUSED; free_global_tri_records(shield_num); return; } - } else if ((Shield_hits[shield_num].start_time + SHIELD_HIT_DURATION) < Missiontime) { - Shield_hits[shield_num].type = SH_UNUSED; + } else if ((sh->start_time + SHIELD_HIT_DURATION) < Missiontime) { + sh->type = SH_UNUSED; free_global_tri_records(shield_num); return; } @@ -487,26 +490,26 @@ void render_shield(int shield_num) // don't try to draw if we don't have an ani if ( sa->first_frame >= 0 ) { - frame_num = bm_get_anim_frame(sa->first_frame, f2fl(Missiontime - Shield_hits[shield_num].start_time), f2fl(SHIELD_HIT_DURATION)); + frame_num = bm_get_anim_frame(sa->first_frame, f2fl(Missiontime - sh->start_time), f2fl(SHIELD_HIT_DURATION)); bitmap_id = sa->first_frame + frame_num; float alpha = 0.9999f; nebula_handle_alpha(alpha, centerp, Neb2_fog_visibility_shield); ubyte r, g, b; - r = (ubyte)(Shield_hits[shield_num].rgb[0] * alpha); - g = (ubyte)(Shield_hits[shield_num].rgb[1] * alpha); - b = (ubyte)(Shield_hits[shield_num].rgb[2] * alpha); + r = (ubyte)(sh->rgb[0] * alpha); + g = (ubyte)(sh->rgb[1] * alpha); + b = (ubyte)(sh->rgb[2] * alpha); if ( bitmap_id <= -1 ) { return; } if ( (Detail.shield_effects == 1) || (Detail.shield_effects == 2) ) { - shield_render_low_detail_bitmap(bitmap_id, alpha, &Global_tris[Shield_hits[shield_num].tri_list[0]], orient, centerp, r, g, b); + shield_render_low_detail_bitmap(bitmap_id, alpha, &Global_tris[sh->tri_list[0]], orient, centerp, r, g, b); } else if ( Detail.shield_effects < 4 ) { - for ( int i = 0; i < Shield_hits[shield_num].num_tris; i++ ) { - shield_render_triangle(bitmap_id, alpha, &Global_tris[Shield_hits[shield_num].tri_list[i]], orient, centerp, r, g, b); + for ( int i = 0; i < sh->num_tris; i++ ) { + shield_render_triangle(bitmap_id, alpha, &Global_tris[sh->tri_list[i]], orient, centerp, r, g, b); } } else { float hit_radius = pm->core_radius; @@ -514,9 +517,18 @@ void render_shield(int shield_num) hit_radius = pm->core_radius * 0.5f; } - color clr; - gr_init_alphacolor(&clr, r, g, b, fl2i(alpha * 255.0f)); - shield_render_decal(pm, orient, centerp, &Shield_hits[shield_num].hit_orient, &Shield_hits[shield_num].hit_pos, hit_radius, bitmap_id, &clr); + if (sh->radius_override >= 0.0f) + hit_radius = sh->radius_override; + + if (si->max_shield_impact_effect_radius >= 0.0f && hit_radius > si->max_shield_impact_effect_radius) { + hit_radius = si->max_shield_impact_effect_radius; + } + + if (hit_radius > 0.0) { + color clr; + gr_init_alphacolor(&clr, r, g, b, fl2i(alpha * 255.0f)); + shield_render_decal(pm, orient, centerp, &sh->hit_orient, &sh->hit_pos, hit_radius, bitmap_id, &clr); + } } } } @@ -629,7 +641,7 @@ void create_shield_from_triangle(int trinum, matrix *orient, shield_info *shield * We need to store vertex information in the global array since the vertex list * will not be available to us when we actually use the array. */ -void copy_shield_to_globals( int objnum, shield_info *shieldp, matrix *hit_orient, vec3d *hit_pos ) +void copy_shield_to_globals( int objnum, shield_info *shieldp, matrix *hit_orient, vec3d *hit_pos, float radius_override) { int i, j; int gi = 0; @@ -673,6 +685,7 @@ void copy_shield_to_globals( int objnum, shield_info *shieldp, matrix *hit_orien Shield_hits[shnum].objnum = objnum; Shield_hits[shnum].hit_orient = *hit_orient; Shield_hits[shnum].hit_pos = *hit_pos; + Shield_hits[shnum].radius_override = radius_override; Shield_hits[shnum].rgb[0] = 255; Shield_hits[shnum].rgb[1] = 255; @@ -746,14 +759,15 @@ void create_shield_low_detail(int objnum, int /*model_num*/, matrix * /*orient* // Output of above is a list of triangles with u,v coordinates. These u,v // coordinates will have to be clipped against the explosion texture bounds. -void create_shield_explosion(int objnum, int model_num, matrix *orient, vec3d *centerp, vec3d *tcp, int tr0) +void create_shield_explosion(int objnum, int model_num, vec3d *tcp, int tr0, float radius_override) { matrix tom; // Texture Orientation Matrix shield_info *shieldp; polymodel *pm; int i; + object* objp = &Objects[objnum]; - if (Objects[objnum].flags[Object::Object_Flags::No_shields]) + if (objp->flags[Object::Object_Flags::No_shields]) return; pm = model_get(model_num); @@ -764,7 +778,7 @@ void create_shield_explosion(int objnum, int model_num, matrix *orient, vec3d *c return; if ( (Detail.shield_effects == 1) || (Detail.shield_effects == 2) ) { - create_shield_low_detail(objnum, model_num, orient, centerp, tcp, tr0, shieldp); + create_shield_low_detail(objnum, model_num, &objp->orient, &objp->pos, tcp, tr0, shieldp); return; } @@ -781,12 +795,12 @@ void create_shield_explosion(int objnum, int model_num, matrix *orient, vec3d *c vm_vector_2_matrix(&tom, &shieldp->tris[tr0].norm, NULL, NULL); // Create the shield from the current triangle, as well as its neighbors. - create_shield_from_triangle(tr0, orient, shieldp, tcp, centerp, Objects[objnum].radius, &tom.vec.rvec, &tom.vec.uvec); + create_shield_from_triangle(tr0, &objp->orient, shieldp, tcp, &objp->pos, objp->radius, &tom.vec.rvec, &tom.vec.uvec); for (i=0; i<3; i++) - create_shield_from_triangle(shieldp->tris[tr0].neighbors[i], orient, shieldp, tcp, centerp, Objects[objnum].radius, &tom.vec.rvec, &tom.vec.uvec); + create_shield_from_triangle(shieldp->tris[tr0].neighbors[i], &objp->orient, shieldp, tcp, &objp->pos, objp->radius, &tom.vec.rvec, &tom.vec.uvec); - copy_shield_to_globals(objnum, shieldp, &tom, tcp); + copy_shield_to_globals(objnum, shieldp, &tom, tcp, radius_override); } MONITOR(NumShieldHits) @@ -794,7 +808,7 @@ MONITOR(NumShieldHits) /** * Add data for a shield hit. */ -void add_shield_point(int objnum, int tri_num, vec3d *hit_pos) +void add_shield_point(int objnum, int tri_num, vec3d *hit_pos, float radius_override) { if (Num_shield_points >= MAX_SHIELD_POINTS) return; @@ -806,6 +820,7 @@ void add_shield_point(int objnum, int tri_num, vec3d *hit_pos) Shield_points[Num_shield_points].objnum = objnum; Shield_points[Num_shield_points].shield_tri = tri_num; Shield_points[Num_shield_points].hit_point = *hit_pos; + Shield_points[Num_shield_points].radius_override = radius_override; Num_shield_points++; @@ -873,7 +888,7 @@ void create_shield_explosion_all(object *objp) for (i=0; iship_info_index].model_num, &objp->orient, &objp->pos, &Shield_points[i].hit_point, Shield_points[i].shield_tri); + create_shield_explosion(objnum, Ship_info[shipp->ship_info_index].model_num, &Shield_points[i].hit_point, Shield_points[i].shield_tri, Shield_points[i].radius_override); count--; if (count <= 0){ break; diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index e968849026f..9dbb9d032ec 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -1170,6 +1170,7 @@ void ship_info::clone(const ship_info& other) auto_shield_spread_bypass = other.auto_shield_spread_bypass; auto_shield_spread_from_lod = other.auto_shield_spread_from_lod; auto_shield_spread_min_span = other.auto_shield_spread_min_span; + max_shield_impact_effect_radius = other.max_shield_impact_effect_radius; // ...Hmm. A memcpy() seems slightly overkill here, but I've settled into the pattern of "array gets memcpy'd", so... -MageKing17 memcpy(shield_point_augment_ctrls, other.shield_point_augment_ctrls, sizeof(int) * 4); @@ -1510,6 +1511,7 @@ void ship_info::move(ship_info&& other) auto_shield_spread_bypass = other.auto_shield_spread_bypass; auto_shield_spread_from_lod = other.auto_shield_spread_from_lod; auto_shield_spread_min_span = other.auto_shield_spread_min_span; + max_shield_impact_effect_radius = other.max_shield_impact_effect_radius; std::swap(shield_point_augment_ctrls, other.shield_point_augment_ctrls); @@ -1950,6 +1952,8 @@ ship_info::ship_info() auto_shield_spread_from_lod = -1; auto_shield_spread_min_span = -1.0f; + max_shield_impact_effect_radius = -1.0f; + for (int i = 0; i < 4; i++) { shield_point_augment_ctrls[i] = -1; @@ -3937,6 +3941,9 @@ static void parse_ship_values(ship_info* sip, const bool is_template, const bool else sip->auto_shield_spread_from_lod = temp; } + + if (optional_string("+Max Shield Impact Effect Radius:")) + stuff_float(&sip->max_shield_impact_effect_radius); } if(optional_string("$Model Point Shield Controls:")) { diff --git a/code/ship/ship.h b/code/ship/ship.h index 94c2a260f38..e91aef14ea7 100644 --- a/code/ship/ship.h +++ b/code/ship/ship.h @@ -1331,6 +1331,7 @@ class ship_info bool auto_shield_spread_bypass; // Whether weapons fired up close can bypass shields int auto_shield_spread_from_lod; // Which LOD to project the shield from float auto_shield_spread_min_span; // Minimum distance weapons must travel until allowed to collide with the shield + float max_shield_impact_effect_radius; int shield_point_augment_ctrls[4]; // Re-mapping of shield augmentation controls for model point shields @@ -1782,7 +1783,7 @@ extern void create_shield_explosion(int objnum, int model_num, matrix *orient, v extern void shield_hit_init(); extern void create_shield_explosion_all(object *objp); extern void shield_frame_init(); -extern void add_shield_point(int objnum, int tri_num, vec3d *hit_pos); +extern void add_shield_point(int objnum, int tri_num, vec3d *hit_pos, float radius_override); extern void add_shield_point_multi(int objnum, int tri_num, vec3d *hit_pos); extern void shield_point_multi_setup(); extern void shield_hit_close(); diff --git a/code/weapon/beam.cpp b/code/weapon/beam.cpp index 539001c92e9..0e06d7aae4a 100644 --- a/code/weapon/beam.cpp +++ b/code/weapon/beam.cpp @@ -3259,7 +3259,7 @@ int beam_collide_ship(obj_pair *pair) // do the hit effect if (shield_collision) { if (mc_shield.shield_hit_tri != -1) { - add_shield_point(OBJ_INDEX(ship_objp), mc_shield.shield_hit_tri, &mc_shield.hit_point); + add_shield_point(OBJ_INDEX(ship_objp), mc_shield.shield_hit_tri, &mc_shield.hit_point, bwi->shield_impact_effect_radius); } } else { /* TODO */; diff --git a/code/weapon/weapon.h b/code/weapon/weapon.h index eee99a3af0c..c6700d3fba0 100644 --- a/code/weapon/weapon.h +++ b/code/weapon/weapon.h @@ -527,7 +527,8 @@ struct weapon_info char anim_filename[MAX_FILENAME_LEN]; // filename for animation that plays in weapon selection int selection_effect; - float shield_impact_explosion_radius; + float shield_impact_effect_radius; // shield surface effect radius + float shield_impact_explosion_radius; // shield-specific particle effect radius particle::ParticleEffectHandle impact_weapon_expl_effect; // Impact particle effect particle::ParticleEffectHandle dinky_impact_weapon_expl_effect; // Dinky impact particle effect diff --git a/code/weapon/weapons.cpp b/code/weapon/weapons.cpp index 14edd7ef103..1ea3d487649 100644 --- a/code/weapon/weapons.cpp +++ b/code/weapon/weapons.cpp @@ -2248,6 +2248,10 @@ int parse_weapon(int subtype, bool replace, const char *filename) } } + if (optional_string("$Shield Impact Effect Radius:")) { + stuff_float(&wip->shield_impact_effect_radius); + } + if (optional_string("$Dinky Impact Effect:")) { wip->dinky_impact_weapon_expl_effect = particle::util::parseEffect(wip->name); } else { @@ -9632,6 +9636,7 @@ void weapon_info::reset() memset(this->anim_filename, 0, sizeof(this->anim_filename)); this->selection_effect = Default_weapon_select_effect; + this->shield_impact_effect_radius = -1.0f; this->shield_impact_explosion_radius = 1.0f; this->impact_weapon_expl_effect = particle::ParticleEffectHandle::invalid();