diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index 99e6337d841..5347458abe5 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -11123,6 +11123,12 @@ void ai_do_objects_repairing_stuff( object *repaired_objp, object *repair_objp, aip->ai_flags.remove(AI::AI_Flags::Awaiting_repair); stamp = timestamp(-1); + if (repair_objp != nullptr && scripting::hooks::OnSupportRearmStarted->isActive()) { + scripting::hooks::OnSupportRearmStarted->run( + scripting::hook_param_list(scripting::hook_param("Support Ship", 'o', repair_objp), + scripting::hook_param("Target Ship", 'o', repaired_objp))); + } + // if this is a player ship, then subtract the repair penalty from this player's score if ( repaired_objp->flags[Object::Object_Flags::Player_ship] ) { if ( !(Game_mode & GM_MULTIPLAYER) ) { @@ -11195,6 +11201,12 @@ void ai_do_objects_repairing_stuff( object *repaired_objp, object *repair_objp, mission_log_add_entry(LOG_PLAYER_ABORTED_REARM, Ships[repaired_objp->instance].ship_name, NULL); } + if (repair_objp != nullptr && scripting::hooks::OnSupportRearmFinished->isActive()) { + scripting::hooks::OnSupportRearmFinished->run( + scripting::hook_param_list(scripting::hook_param("Support Ship", 'o', repair_objp), + scripting::hook_param("Target Ship", 'o', repaired_objp))); + } + stamp = timestamp((int) ((30 + 10*frand()) * 1000)); break; diff --git a/code/mission/mission_flags.h b/code/mission/mission_flags.h index 88c5dcd01b0..f92a6d84e09 100644 --- a/code/mission/mission_flags.h +++ b/code/mission/mission_flags.h @@ -36,6 +36,7 @@ namespace Mission { Neb2_fog_color_override, // Whether to use explicit fog colors instead of checking the palette - Goober5000 Fullneb_background_bitmaps, // Show background bitmaps despite fullneb Preload_subspace, // Preload the subspace tunnel for both the sexp and specs checkbox (for scripts) - MjnMixael + Limited_support_rearm_pool, // Support ships can only rearm weapons while mission-level pool is available - MjnMixael NUM_VALUES }; diff --git a/code/mission/missionparse.cpp b/code/mission/missionparse.cpp index 34a5c7e550f..95dbf7e98f7 100644 --- a/code/mission/missionparse.cpp +++ b/code/mission/missionparse.cpp @@ -385,7 +385,8 @@ flag_def_list_new Parse_mission_flags[] = { {"Toggle Starting in Chase View", Mission::Mission_Flags::Toggle_start_chase_view, true, false}, {"Nebula Fog Color Override", Mission::Mission_Flags::Neb2_fog_color_override, true, true}, {"Full Nebula Background Bitmaps", Mission::Mission_Flags::Fullneb_background_bitmaps, true, true}, - {"Preload Subspace Tunnel", Mission::Mission_Flags::Preload_subspace, true, false} + {"Preload Subspace Tunnel", Mission::Mission_Flags::Preload_subspace, true, false}, + {"Limit Support Rearm to Mission Pool", Mission::Mission_Flags::Limited_support_rearm_pool, true, true} }; parse_object_flag_description Parse_mission_flag_descriptions[] = { @@ -418,7 +419,8 @@ parse_object_flag_description Parse_mission_flag_descrip {Mission::Mission_Flags::Toggle_start_chase_view, "Toggles whether the player starts the mission in chase view"}, {Mission::Mission_Flags::Neb2_fog_color_override, "Whether to use explicit fog colors instead of checking the palette"}, {Mission::Mission_Flags::Fullneb_background_bitmaps, "Show background bitmaps despite full nebula"}, - {Mission::Mission_Flags::Preload_subspace, "Preload the subspace tunnel for both the sexp and specs checkbox"}, + {Mission::Mission_Flags::Preload_subspace, "Preload the subspace tunnel for both the sexp and specs checkbox"}, + {Mission::Mission_Flags::Limited_support_rearm_pool, "Support ships can only rearm from the mission weapon pool"}, }; const size_t Num_parse_mission_flags = sizeof(Parse_mission_flags) / sizeof(flag_def_list_new); @@ -882,6 +884,18 @@ void parse_mission_info(mission *pm, bool basic = false) pm->support_ships.max_support_ships = (temp > 0) ? 0 : -1; } + if (optional_string("+Disallow Support Rearm:")) { + int temp; + stuff_int(&temp); + pm->support_ships.disallow_rearm = (temp != 0); + } + + if (optional_string("+Allow Support Rearm Weapon Precedence:")) { + int temp; + stuff_int(&temp); + pm->support_ships.allow_rearm_weapon_precedence = (temp != 0); + } + if ( optional_string("+Hull Repair Ceiling:")) { float temp; @@ -904,6 +918,12 @@ void parse_mission_info(mission *pm, bool basic = false) } } + if (optional_string("+Support Rearm Pool From Loadout:")) { + int temp; + stuff_int(&temp); + pm->support_ships.rearm_pool_from_loadout = (temp != 0); + } + if (optional_string("+All Teams Attack")){ Mission_all_attack = 1; } @@ -1106,13 +1126,16 @@ void parse_player_info(mission *pm) void parse_player_info2(mission *pm) { int nt, i; - SCP_vector list, list2; + SCP_vector list, list2, support_rearm_list; team_data *ptr; if (OnLoadoutAboutToParseHook->isActive()) { OnLoadoutAboutToParseHook->run(); } + // initialize support rearm pool from mission loadout data (weapon index -> count) + memset(pm->support_ships.rearm_weapon_pool, 0, sizeof(pm->support_ships.rearm_weapon_pool)); + // read in a ship/weapon pool for each team. for ( nt = 0; nt < Num_teams; nt++ ) { int num_choices; @@ -1223,6 +1246,13 @@ void parse_player_info2(mission *pm) ptr->weaponry_pool[num_choices] = wc.index; ptr->weaponry_count[num_choices] = wc.count; + if (pm->support_ships.rearm_pool_from_loadout) { + if (Weapon_info[wc.index].disallow_rearm) { + pm->support_ships.rearm_weapon_pool[nt][wc.index] = 0; + } else if (wc.count > 0 && pm->support_ships.rearm_weapon_pool[nt][wc.index] >= 0) { + pm->support_ships.rearm_weapon_pool[nt][wc.index] += wc.count; + } + } // if the list isn't set by a variable leave the variable name empty if (wc.index_sexp_var == NOT_SET_BY_SEXP_VARIABLE) { @@ -1244,6 +1274,34 @@ void parse_player_info2(mission *pm) } ptr->num_weapon_choices = num_choices; + if (optional_string("+Support Rearm Pool:")) { + support_rearm_list.clear(); + stuff_loadout_list(support_rearm_list, ParseLookupType::MISSION_LOADOUT_WEAPON_LIST); + + if (!pm->support_ships.rearm_pool_from_loadout) { + for (auto& wc : support_rearm_list) { + if (wc.index < 0 || wc.index >= weapon_info_size()) { + continue; + } + + if (!(Weapon_info[wc.index].wi_flags[Weapon::Info_Flags::Player_allowed]) && !Fred_running) { + WarningEx(LOCATION, + "Weapon '%s' in support rearm pool isn't allowed on player loadout! Ignoring it ...\n", + Weapon_info[wc.index].name); + continue; + } + + if (Weapon_info[wc.index].disallow_rearm) { + pm->support_ships.rearm_weapon_pool[nt][wc.index] = 0; + } else if (wc.count < 0) { + pm->support_ships.rearm_weapon_pool[nt][wc.index] = -1; + } else if (wc.count > 0 && pm->support_ships.rearm_weapon_pool[nt][wc.index] >= 0) { + pm->support_ships.rearm_weapon_pool[nt][wc.index] += wc.count; + } + } + } + } + memset(ptr->weapon_required, 0, MAX_WEAPON_TYPES * sizeof(bool)); if (optional_string("+Required for mission:")) { @@ -7072,6 +7130,10 @@ void mission::Reset() support_ships.max_support_ships = -1; // infinite support_ships.max_concurrent_ships = 1; support_ships.ship_class = -1; + memset(&support_ships.rearm_weapon_pool, -1, sizeof(support_ships.rearm_weapon_pool)); + support_ships.disallow_rearm = false; + support_ships.allow_rearm_weapon_precedence = false; + support_ships.rearm_pool_from_loadout = false; // for each species, store whether support is available for (int species = 0; species < (int)Species_info.size(); species++) { @@ -9443,5 +9505,26 @@ bool check_for_24_3_data() bool check_for_25_1_data() { - return (count_items_with_value(Props) > 0); + if ((count_items_with_value(Props) > 0)) { + return true; + } + + if (The_mission.flags[Mission::Mission_Flags::Limited_support_rearm_pool]) { + return true; + } + + if (The_mission.support_ships.disallow_rearm || The_mission.support_ships.allow_rearm_weapon_precedence || + The_mission.support_ships.rearm_pool_from_loadout) { + return true; + } + + for (int team = 0; team < Num_teams; ++team) { + for (int pool_wep = 0; pool_wep < MAX_WEAPON_TYPES; ++pool_wep) { + if (The_mission.support_ships.rearm_weapon_pool[team][pool_wep] != -1) { + return true; + } + } + } + + return false; } diff --git a/code/mission/missionparse.h b/code/mission/missionparse.h index e6a8c69d89a..7459fdcff10 100644 --- a/code/mission/missionparse.h +++ b/code/mission/missionparse.h @@ -103,17 +103,21 @@ inline const std::vector> Mission_event_teams_tvt = [ // Goober5000 typedef struct support_ship_info { - ArrivalLocation arrival_location; // arrival location - int arrival_anchor; // arrival anchor - DepartureLocation departure_location; // departure location - int departure_anchor; // departure anchor - float max_hull_repair_val; // % of a ship's hull that can be repaired -C - float max_subsys_repair_val; // same thing, except for subsystems -C - int max_support_ships; // max number of consecutive support ships - int max_concurrent_ships; // max number of concurrent support ships in mission per team - int ship_class; // ship class of support ship - int tally; // number of support ships so far - int support_available_for_species; // whether support is available for a given species (this is a bitfield) + ArrivalLocation arrival_location; // arrival location + int arrival_anchor; // arrival anchor + DepartureLocation departure_location; // departure location + int departure_anchor; // departure anchor + float max_hull_repair_val; // % of a ship's hull that can be repaired -C + float max_subsys_repair_val; // same thing, except for subsystems -C + int max_support_ships; // max number of consecutive support ships + int max_concurrent_ships; // max number of concurrent support ships in mission per team + int ship_class; // ship class of support ship + int tally; // number of support ships so far + int support_available_for_species; // whether support is available for a given species (this is a bitfield) + bool disallow_rearm; // if true, support ships can only repair and will not rearm weapons + bool allow_rearm_weapon_precedence; // if true, support ships may swap to precedence weapons when rearm pool is empty + bool rearm_pool_from_loadout; // initialize rearm pool from mission loadout after filling starting loadout ships + int rearm_weapon_pool[MAX_TVT_TEAMS][MAX_WEAPON_TYPES]; // mission stockpile used to limit support ship rearming } support_ship_info; // movie type defines diff --git a/code/missioneditor/missionsave.cpp b/code/missioneditor/missionsave.cpp index ad02a50ba64..c8d0b7c3ae1 100644 --- a/code/missioneditor/missionsave.cpp +++ b/code/missioneditor/missionsave.cpp @@ -2757,6 +2757,24 @@ int Fred_mission_save::save_mission_info() // this is compatible with non-SCP variants - Goober5000 fout(" %d", (The_mission.support_ships.max_support_ships == 0) ? 1 : 0); + if (save_config.save_format != MissionFormat::RETAIL) { + if (optional_string_fred("+Disallow Support Rearm:")) { + parse_comments(2); + } else { + fout("\n+Disallow Support Rearm:"); + } + + fout(" %d", The_mission.support_ships.disallow_rearm ? 1 : 0); + + if (optional_string_fred("+Allow Support Rearm Weapon Precedence:")) { + parse_comments(2); + } else { + fout("\n+Allow Support Rearm Weapon Precedence:"); + } + + fout(" %d", The_mission.support_ships.allow_rearm_weapon_precedence ? 1 : 0); + } + // here be WMCoolmon's hull and subsys repair stuff if (save_config.save_format != MissionFormat::RETAIL) { if (optional_string_fred("+Hull Repair Ceiling:")) { @@ -2772,6 +2790,13 @@ int Fred_mission_save::save_mission_info() fout("\n+Subsystem Repair Ceiling:"); } fout(" %f", The_mission.support_ships.max_subsys_repair_val); + + if (optional_string_fred("+Support Rearm Pool From Loadout:")) { + parse_comments(1); + } else { + fout("\n+Support Rearm Pool From Loadout:"); + } + fout(" %d", The_mission.support_ships.rearm_pool_from_loadout ? 1 : 0); } if (Mission_all_attack) { @@ -4369,6 +4394,25 @@ int Fred_mission_save::save_players() fout(")"); + if (save_config.save_format != MissionFormat::RETAIL && !The_mission.support_ships.rearm_pool_from_loadout) { + if (optional_string_fred("+Support Rearm Pool:", "$Starting Shipname:")) { + parse_comments(2); + } else { + fout("\n\n+Support Rearm Pool:"); + } + + fout(" (\n"); + for (j = 0; j < weapon_info_size(); j++) { + if (!Weapon_info[j].wi_flags[Weapon::Info_Flags::Player_allowed]) { + continue; + } + if (The_mission.support_ships.rearm_weapon_pool[i][j] != 0) { + fout("\t\"%s\"\t%d\n", Weapon_info[j].name, The_mission.support_ships.rearm_weapon_pool[i][j]); + } + } + fout(")"); + } + // sanity check if (save_config.save_format == MissionFormat::RETAIL && wrote_fso_data) { // this is such an unlikely (and hard-to-fix) case that a warning should be sufficient diff --git a/code/scripting/api/libs/mission.cpp b/code/scripting/api/libs/mission.cpp index 034f7292b86..0d703ca8519 100644 --- a/code/scripting/api/libs/mission.cpp +++ b/code/scripting/api/libs/mission.cpp @@ -65,6 +65,7 @@ #include "scripting/api/objs/ship.h" #include "scripting/api/objs/shipclass.h" #include "scripting/api/objs/sound.h" +#include "scripting/api/objs/support_rearm_pool.h" #include "scripting/api/objs/team.h" #include "scripting/api/objs/vecmath.h" #include "scripting/api/objs/waypoint.h" @@ -261,6 +262,42 @@ ADE_FUNC(runSEXP, l_Mission, "string", "Runs the defined SEXP script within a `w return ADE_RETURN_FALSE; } +//****SUBLIBRARY: Mission/SupportRearmPools +ADE_LIB_DERIV(l_Mission_SupportRearmPools, + "SupportRearmPools", + nullptr, + "Per-team mission support rearm pools (team indexed).", + l_Mission); + +ADE_INDEXER(l_Mission_SupportRearmPools, + "number TeamIndex", + "Gets support rearm pool handle for a specific team.", + "support_rearm_pool_team", + "Support rearm pool team handle, or invalid handle if index is out of range.") +{ + int idx = -1; + if (!ade_get_args(L, "*i", &idx)) { + return ade_set_error(L, "o", l_SupportRearmPoolTeam.Set(-1)); + } + + idx--; // Lua to C++ index + if (idx < 0 || idx >= Num_teams || idx >= MAX_TVT_TEAMS) { + return ade_set_error(L, "o", l_SupportRearmPoolTeam.Set(-1)); + } + + return ade_set_args(L, "o", l_SupportRearmPoolTeam.Set(idx)); +} + +ADE_FUNC(__len, + l_Mission_SupportRearmPools, + nullptr, + "The number of support rearm pool teams.", + "number", + "The number of TVT/loadout teams with support rearm pools.") +{ + return ade_set_args(L, "i", MIN(Num_teams, MAX_TVT_TEAMS)); +} + //****SUBLIBRARY: Mission/Asteroids ADE_LIB_DERIV(l_Mission_Asteroids, "Asteroids", NULL, "Asteroids in the mission", l_Mission); diff --git a/code/scripting/api/objs/support_rearm_pool.cpp b/code/scripting/api/objs/support_rearm_pool.cpp new file mode 100644 index 00000000000..3d6bea08a4a --- /dev/null +++ b/code/scripting/api/objs/support_rearm_pool.cpp @@ -0,0 +1,79 @@ +// +// + +#include "support_rearm_pool.h" + +#include "mission/missionparse.h" +#include "scripting/api/objs/weaponclass.h" +#include "weapon/weapon.h" + +namespace scripting::api { + +//****HANDLE: support_rearm_pool_team +ADE_OBJ(l_SupportRearmPoolTeam, int, "support_rearm_pool_team", "Support rearm pool team handle"); + +ADE_INDEXER(l_SupportRearmPoolTeam, + "number/weaponclass IndexOrClass, number amount", + "Gets/sets support rearm pool value for a weapon class on this team.\n" + "Values: -1 = unlimited, 0 = unavailable, >0 = limited amount.\n" + "If a weapon has $Disallow Support Rearm, this always returns 0 and ignores writes.", + "number", + "Current pool value for the weapon class.") +{ + int team_idx = -1; + int idx = -1; + int amount; + if (lua_isnumber(L, 2)) { + if (!ade_get_args(L, "oi|i", l_SupportRearmPoolTeam.Get(&team_idx), &idx, &amount)) { + return ADE_RETURN_NIL; + } + + if (idx < 1 || idx > weapon_info_size()) { + return ADE_RETURN_NIL; + } + + idx--; // Lua to C++ index + } else { + if (!ade_get_args(L, "oo|i", l_SupportRearmPoolTeam.Get(&team_idx), l_Weaponclass.Get(&idx), &amount)) { + return ADE_RETURN_NIL; + } + + if (idx < 0 || idx >= weapon_info_size()) { + return ADE_RETURN_NIL; + } + } + + if (team_idx < 0 || team_idx >= Num_teams || team_idx >= MAX_TVT_TEAMS) { + return ADE_RETURN_NIL; + } + + if (ADE_SETTING_VAR) { + if (!Weapon_info[idx].disallow_rearm) { + if (amount < 0) { + The_mission.support_ships.rearm_weapon_pool[team_idx][idx] = -1; + } else { + The_mission.support_ships.rearm_weapon_pool[team_idx][idx] = amount; + } + } else { + The_mission.support_ships.rearm_weapon_pool[team_idx][idx] = 0; + } + } + + if (Weapon_info[idx].disallow_rearm) { + return ade_set_args(L, "i", 0); + } + + return ade_set_args(L, "i", The_mission.support_ships.rearm_weapon_pool[team_idx][idx]); +} + +ADE_FUNC(__len, + l_SupportRearmPoolTeam, + nullptr, + "The number of weapon classes in this support rearm pool.", + "number", + "The number of weapon classes.") +{ + return ade_set_args(L, "i", weapon_info_size()); +} + +} // namespace scripting::api diff --git a/code/scripting/api/objs/support_rearm_pool.h b/code/scripting/api/objs/support_rearm_pool.h new file mode 100644 index 00000000000..dc34cf43f70 --- /dev/null +++ b/code/scripting/api/objs/support_rearm_pool.h @@ -0,0 +1,12 @@ +#pragma once + +#include "globalincs/pstypes.h" + +#include "scripting/ade.h" +#include "scripting/ade_api.h" + +namespace scripting::api { + +DECLARE_ADE_OBJ(l_SupportRearmPoolTeam, int); + +} // namespace scripting::api diff --git a/code/scripting/global_hooks.cpp b/code/scripting/global_hooks.cpp index b5f8e673352..3b9e90f4cd4 100644 --- a/code/scripting/global_hooks.cpp +++ b/code/scripting/global_hooks.cpp @@ -132,6 +132,16 @@ const std::shared_ptr> OnAfterburnerEnd = Hook> OnSupportRearmStarted = Hook<>::Factory("On Support Rearm Started", + "Invoked when a support ship begins actively rearming/repairing a target ship.", + { { "Support Ship", "ship", "The support ship performing the service." }, + { "Target Ship", "ship", "The ship being serviced by the support ship." } }); + +const std::shared_ptr> OnSupportRearmFinished = Hook<>::Factory("On Support Rearm Finished", + "Invoked when a support ship finishes rearming/repairing a target ship.", + { { "Support Ship", "ship", "The support ship that performed the service." }, + { "Target Ship", "ship", "The ship that was serviced." } }); + const std::shared_ptr> OnWaypointsDone = Hook::Factory("On Waypoints Done", "Invoked whenever a ship stops using its afterburners", { diff --git a/code/scripting/global_hooks.h b/code/scripting/global_hooks.h index d797f54b2dc..f7acf27cf21 100644 --- a/code/scripting/global_hooks.h +++ b/code/scripting/global_hooks.h @@ -33,6 +33,8 @@ extern const std::shared_ptr> OnMouseReleased; extern const std::shared_ptr> OnAfterburnerStart; extern const std::shared_ptr> OnAfterburnerEnd; +extern const std::shared_ptr> OnSupportRearmStarted; +extern const std::shared_ptr> OnSupportRearmFinished; extern const std::shared_ptr> OnWaypointsDone; extern const std::shared_ptr> OnGoalsCleared; diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index 0bed76538e3..2ef41cfc698 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -167,6 +167,7 @@ static void ship_set_eye(object *obj, int eye_index); static void ship_start_targeting_laser(ship *shipp); static void ship_add_ship_type_kill_count(int ship_info_index); static int ship_info_lookup_sub(const char *token); +static int get_support_rearm_pool_tvt_team_index(int tvt_team); enum class LegacyShipParticleType : uint8_t {DAMAGE_SPEW, SPLIT_PARTICLES, OTHER}; static particle::ParticleEffectHandle default_ship_particle_effect(LegacyShipParticleType type, int n_high, int n_low, float max_rad, float min_rad, float max_life, float min_life, float max_vel, float min_vel, float variance, float range, int bitmap, float velocityInherit, bool useNormal = false); @@ -10963,6 +10964,14 @@ void ship_process_post(object * obj, float frametime) { shipp->weapons.secondary_bank_start_ammo[i] = shipp->weapons.secondary_bank_ammo[i]; } + + if (The_mission.support_ships.rearm_pool_from_loadout && shipp->flags[Ship_Flags::From_player_wing]) { + const int weapon_class = shipp->weapons.secondary_bank_weapons[i]; + if (weapon_class >= 0 && weapon_class < MAX_WEAPON_TYPES && The_mission.support_ships.rearm_weapon_pool[get_support_rearm_pool_tvt_team_index(shipp->team)][weapon_class] >= 0) { + The_mission.support_ships.rearm_weapon_pool[get_support_rearm_pool_tvt_team_index(shipp->team)][weapon_class] = + MAX(0, The_mission.support_ships.rearm_weapon_pool[get_support_rearm_pool_tvt_team_index(shipp->team)][weapon_class] - shipp->weapons.secondary_bank_ammo[i]); + } + } } for ( int i=0; iweapons.primary_bank_start_ammo[i] = shipp->weapons.primary_bank_ammo[i]; } + + const int weapon_class = shipp->weapons.primary_bank_weapons[i]; + if (The_mission.support_ships.rearm_pool_from_loadout && shipp->flags[Ship_Flags::From_player_wing] && + SCP_vector_inbounds(Weapon_info, weapon_class) && Weapon_info[weapon_class].wi_flags[Weapon::Info_Flags::Ballistic]) { + if (weapon_class < MAX_WEAPON_TYPES && The_mission.support_ships.rearm_weapon_pool[get_support_rearm_pool_tvt_team_index(shipp->team)][weapon_class] >= 0) { + The_mission.support_ships.rearm_weapon_pool[get_support_rearm_pool_tvt_team_index(shipp->team)][weapon_class] = + MAX(0, The_mission.support_ships.rearm_weapon_pool[get_support_rearm_pool_tvt_team_index(shipp->team)][weapon_class] - shipp->weapons.primary_bank_ammo[i]); + } + } } shipp->flags.set(Ship_Flags::Ammo_count_recorded); @@ -16074,7 +16092,139 @@ float ship_calculate_rearm_duration( object *objp ) return shield_rep_time + hull_rep_time + subsys_rep_time + prim_rearm_time + sec_rearm_time + 1.2f; } +static int get_support_rearm_pool_tvt_team_index(int tvt_team) +{ + // If not in TVT then all cases should return 0. + if (tvt_team >= 0 && tvt_team < Num_teams && tvt_team < MAX_TVT_TEAMS) { + return tvt_team; + } + + return 0; +} + +static int get_mission_rearm_pool_for_weapon(int weapon_class, int team) +{ + const auto team_index = get_support_rearm_pool_tvt_team_index(team); + if ((weapon_class < 0) || (weapon_class >= MAX_WEAPON_TYPES)) { + return 0; + } + + if (Weapon_info[weapon_class].disallow_rearm) { + return 0; + } + + if (!(The_mission.flags[Mission::Mission_Flags::Limited_support_rearm_pool])) { + return -1; + } + + return The_mission.support_ships.rearm_weapon_pool[team_index][weapon_class]; +} + +static bool weapon_allowed_for_current_game_type(int weapon_flags) +{ + if (MULTI_DOGFIGHT) { + return (weapon_flags & DOGFIGHT_WEAPON) != 0; + } + + return (weapon_flags & REGULAR_WEAPON) != 0; +} + +static bool support_rearm_bank_allows_weapon(const ship_info* sip, int bank_index, int weapon_class) +{ + if (!weapon_allowed_for_current_game_type(sip->allowed_weapons[weapon_class])) { + return false; + } + + if (bank_index >= 0 && !sip->restricted_loadout_flag.empty()) { + if (weapon_allowed_for_current_game_type(sip->restricted_loadout_flag[bank_index])) { + if (sip->allowed_bank_restricted_weapons.empty() || + !weapon_allowed_for_current_game_type(sip->allowed_bank_restricted_weapons[bank_index][weapon_class])) { + return false; + } + } + } + + return true; +} + +static int find_precedence_rearm_weapon(const ship* shipp, int bank_index, bool is_secondary_bank) +{ + Assertion(shipp != nullptr, "ship pointer cannot be null"); + const ship_info* sip = &Ship_info[shipp->ship_info_index]; + const int current_weapon = is_secondary_bank ? shipp->weapons.secondary_bank_weapons[bank_index] : shipp->weapons.primary_bank_weapons[bank_index]; + + for (const auto& weapon_id : Player_weapon_precedence) { + if (weapon_id < 0 || weapon_id >= weapon_info_size()) { + continue; + } + + if (weapon_id == current_weapon) { + continue; + } + + if (is_secondary_bank) { + if (Weapon_info[weapon_id].subtype != WP_MISSILE) { + continue; + } + } else { + if (!Weapon_info[weapon_id].wi_flags[Weapon::Info_Flags::Ballistic]) { + continue; + } + } + + if (!support_rearm_bank_allows_weapon(sip, (is_secondary_bank ? MAX_SHIP_PRIMARY_BANKS : 0) + bank_index, weapon_id)) { + continue; + } + + if (get_mission_rearm_pool_for_weapon(weapon_id, shipp->team) != 0) { + return weapon_id; + } + } + + return -1; +} + +static bool maybe_swap_to_precedence_rearm_weapon(ship* shipp, int bank_index, bool is_secondary_bank) +{ + const int replacement_weapon = find_precedence_rearm_weapon(shipp, bank_index, is_secondary_bank); + if (replacement_weapon < 0) { + return false; + } + if (is_secondary_bank) { + shipp->weapons.secondary_bank_weapons[bank_index] = replacement_weapon; + shipp->weapons.secondary_bank_start_ammo[bank_index] = + get_max_ammo_count_for_bank(shipp->ship_info_index, bank_index, replacement_weapon); + shipp->weapons.secondary_bank_ammo[bank_index] = 0; + } else { + shipp->weapons.primary_bank_weapons[bank_index] = replacement_weapon; + shipp->weapons.primary_bank_start_ammo[bank_index] = + get_max_ammo_count_for_primary_bank(shipp->ship_info_index, bank_index, replacement_weapon); + shipp->weapons.primary_bank_ammo[bank_index] = 0; + } + + return true; +} + +static void use_mission_rearm_pool_for_weapon(int weapon_class, int amount, int team) +{ + if (!(The_mission.flags[Mission::Mission_Flags::Limited_support_rearm_pool]) || amount <= 0) { + return; + } + + if ((weapon_class < 0) || (weapon_class >= MAX_WEAPON_TYPES)) { + return; + } + + const auto team_index = get_support_rearm_pool_tvt_team_index(team); + + if (The_mission.support_ships.rearm_weapon_pool[team_index][weapon_class] < 0) { + return; + } + + The_mission.support_ships.rearm_weapon_pool[team_index][weapon_class] = + MAX(0, The_mission.support_ships.rearm_weapon_pool[team_index][weapon_class] - amount); +} // ================================================================================== // ship_do_rearm_frame() @@ -16213,150 +16363,185 @@ int ship_do_rearm_frame( object *objp, float frametime ) // they can be rearmed. We can rearm multiple banks at once. banks_full = 0; primary_banks_full = 0; + + // Skip rearming entirely if the mission disallows it, but still count the banks as full so that the "rearm + // complete" state can be reached. + if (The_mission.support_ships.disallow_rearm) { + banks_full = swp->num_secondary_banks; + primary_banks_full = swp->num_primary_banks; + } if ( subsys_all_ok ) { - for (i = 0; i < swp->num_secondary_banks; i++ ) - { - // Actual loading of missiles is preceded by a sound effect which is the missile - // loading equipment moving into place - if ( aip->rearm_first_missile == TRUE ) - { - swp->secondary_bank_rearm_time[i] = timestamp((int)gamesnd_get_max_duration(gamesnd_get_game_sound(GameSounds::MISSILE_START_LOAD))); - } - - if ( swp->secondary_bank_ammo[i] < swp->secondary_bank_start_ammo[i] ) - { - float rearm_time; - - if ( objp == Player_obj ) - { - hud_gauge_popup_start(HUD_WEAPONS_GAUGE); + if (!The_mission.support_ships.disallow_rearm) { + for (i = 0; i < swp->num_secondary_banks; i++) { + // Actual loading of missiles is preceded by a sound effect which is the missile + // loading equipment moving into place + if (aip->rearm_first_missile == TRUE) { + swp->secondary_bank_rearm_time[i] = timestamp( + (int)gamesnd_get_max_duration(gamesnd_get_game_sound(GameSounds::MISSILE_START_LOAD))); } - if ( timestamp_elapsed(swp->secondary_bank_rearm_time[i]) ) - { - rearm_time = Weapon_info[swp->secondary_bank_weapons[i]].rearm_rate; - swp->secondary_bank_rearm_time[i] = timestamp((int)(rearm_time * 1000.0f)); - - snd_play_3d( gamesnd_get_game_sound(GameSounds::MISSILE_LOAD), &objp->pos, &View_position ); - if (objp == Player_obj) - joy_ff_play_reload_effect(); + if (swp->secondary_bank_ammo[i] < swp->secondary_bank_start_ammo[i]) { + float rearm_time; + const int weapon_class = swp->secondary_bank_weapons[i]; + const int rearm_pool = get_mission_rearm_pool_for_weapon(weapon_class, shipp->team); - swp->secondary_bank_ammo[i] += Weapon_info[swp->secondary_bank_weapons[i]].reloaded_per_batch; - if ( swp->secondary_bank_ammo[i] > swp->secondary_bank_start_ammo[i] ) - { - swp->secondary_bank_ammo[i] = swp->secondary_bank_start_ammo[i]; + if (rearm_pool == 0) { + if (The_mission.support_ships.allow_rearm_weapon_precedence && + swp->secondary_bank_ammo[i] == 0 && maybe_swap_to_precedence_rearm_weapon(shipp, i, true)) { + continue; + } + + banks_full++; + continue; } - } - else - { - } - } - else - { - banks_full++; - } - if ((aip->rearm_first_missile == TRUE) && (i == swp->num_secondary_banks - 1)) - { - if ((banks_full != swp->num_secondary_banks)) - snd_play_3d( gamesnd_get_game_sound(GameSounds::MISSILE_START_LOAD), &objp->pos, &View_position ); + if (objp == Player_obj) { + hud_gauge_popup_start(HUD_WEAPONS_GAUGE); + } - aip->rearm_first_missile = FALSE; - } - } // end for + if (timestamp_elapsed(swp->secondary_bank_rearm_time[i])) { + rearm_time = Weapon_info[weapon_class].rearm_rate; + swp->secondary_bank_rearm_time[i] = timestamp((int)(rearm_time * 1000.0f)); - // rearm ballistic primaries - Goober5000 - if ( aip->rearm_first_ballistic_primary == TRUE) - { - for (i = 0; i < swp->num_primary_banks; i++ ) - { - if ( Weapon_info[swp->primary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::Ballistic] ) - last_ballistic_idx = i; - } - } + int reload_amount = Weapon_info[weapon_class].reloaded_per_batch; + if (rearm_pool > 0) { + reload_amount = MIN(reload_amount, rearm_pool); + } + reload_amount = + MIN(reload_amount, swp->secondary_bank_start_ammo[i] - swp->secondary_bank_ammo[i]); + if (reload_amount <= 0) { + banks_full++; + continue; + } - for (i = 0; i < swp->num_primary_banks; i++ ) - { - if (Weapon_info[swp->primary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::Ballistic]) - { - // Actual loading of bullets is preceded by a sound effect which is the bullet - // loading equipment moving into place - if ( aip->rearm_first_ballistic_primary == TRUE ) - { - // Goober5000 - gamesnd_id sound_index; - if (gamesnd_game_sound_try_load(GameSounds::BALLISTIC_START_LOAD)) - sound_index = GameSounds::BALLISTIC_START_LOAD; - else - sound_index = GameSounds::MISSILE_START_LOAD; - - if (sound_index.isValid()) - swp->primary_bank_rearm_time[i] = timestamp((int)gamesnd_get_max_duration(gamesnd_get_game_sound(sound_index))); - else - swp->primary_bank_rearm_time[i] = timestamp(0); - } + snd_play_3d(gamesnd_get_game_sound(GameSounds::MISSILE_LOAD), &objp->pos, &View_position); + if (objp == Player_obj) + joy_ff_play_reload_effect(); - if ( swp->primary_bank_ammo[i] < swp->primary_bank_start_ammo[i] ) - { - float rearm_time; - - if ( objp == Player_obj ) - { - hud_gauge_popup_start(HUD_WEAPONS_GAUGE); + swp->secondary_bank_ammo[i] += reload_amount; + use_mission_rearm_pool_for_weapon(weapon_class, reload_amount, shipp->team); + if (swp->secondary_bank_ammo[i] > swp->secondary_bank_start_ammo[i]) { + swp->secondary_bank_ammo[i] = swp->secondary_bank_start_ammo[i]; + } + } else { } + } else { + banks_full++; + } - if ( timestamp_elapsed(swp->primary_bank_rearm_time[i]) ) - { - rearm_time = Weapon_info[swp->primary_bank_weapons[i]].rearm_rate; - swp->primary_bank_rearm_time[i] = timestamp( (int)(rearm_time * 1000.f) ); - + if ((aip->rearm_first_missile == TRUE) && (i == swp->num_secondary_banks - 1)) { + if ((banks_full != swp->num_secondary_banks)) + snd_play_3d(gamesnd_get_game_sound(GameSounds::MISSILE_START_LOAD), &objp->pos, &View_position); + + aip->rearm_first_missile = FALSE; + } + } // end for + + // rearm ballistic primaries - Goober5000 + if (aip->rearm_first_ballistic_primary == TRUE) { + for (i = 0; i < swp->num_primary_banks; i++) { + if (Weapon_info[swp->primary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::Ballistic]) + last_ballistic_idx = i; + } + } + + for (i = 0; i < swp->num_primary_banks; i++) { + if (Weapon_info[swp->primary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::Ballistic]) { + // Actual loading of bullets is preceded by a sound effect which is the bullet + // loading equipment moving into place + if (aip->rearm_first_ballistic_primary == TRUE) { // Goober5000 gamesnd_id sound_index; - if (gamesnd_game_sound_try_load(GameSounds::BALLISTIC_LOAD)) - sound_index = GameSounds::BALLISTIC_LOAD; + if (gamesnd_game_sound_try_load(GameSounds::BALLISTIC_START_LOAD)) + sound_index = GameSounds::BALLISTIC_START_LOAD; else - sound_index = GameSounds::MISSILE_LOAD; + sound_index = GameSounds::MISSILE_START_LOAD; if (sound_index.isValid()) - snd_play_3d( gamesnd_get_game_sound(sound_index), &objp->pos, &View_position ); - - swp->primary_bank_ammo[i] += Weapon_info[swp->primary_bank_weapons[i]].reloaded_per_batch; - if ( swp->primary_bank_ammo[i] > swp->primary_bank_start_ammo[i] ) - { - swp->primary_bank_ammo[i] = swp->primary_bank_start_ammo[i]; + swp->primary_bank_rearm_time[i] = + timestamp((int)gamesnd_get_max_duration(gamesnd_get_game_sound(sound_index))); + else + swp->primary_bank_rearm_time[i] = timestamp(0); + } + + if (swp->primary_bank_ammo[i] < swp->primary_bank_start_ammo[i]) { + float rearm_time; + const int weapon_class = swp->primary_bank_weapons[i]; + const int rearm_pool = get_mission_rearm_pool_for_weapon(weapon_class, shipp->team); + + if (rearm_pool == 0) { + if (The_mission.support_ships.allow_rearm_weapon_precedence && + swp->primary_bank_ammo[i] == 0 && + maybe_swap_to_precedence_rearm_weapon(shipp, i, false)) { + continue; + } + + primary_banks_full++; + continue; + } + + if (objp == Player_obj) { + hud_gauge_popup_start(HUD_WEAPONS_GAUGE); + } + + if (timestamp_elapsed(swp->primary_bank_rearm_time[i])) { + rearm_time = Weapon_info[weapon_class].rearm_rate; + swp->primary_bank_rearm_time[i] = timestamp((int)(rearm_time * 1000.f)); + + int reload_amount = Weapon_info[weapon_class].reloaded_per_batch; + if (rearm_pool > 0) { + reload_amount = MIN(reload_amount, rearm_pool); + } + reload_amount = + MIN(reload_amount, swp->primary_bank_start_ammo[i] - swp->primary_bank_ammo[i]); + if (reload_amount <= 0) { + primary_banks_full++; + continue; + } + + // Goober5000 + gamesnd_id sound_index; + if (gamesnd_game_sound_try_load(GameSounds::BALLISTIC_LOAD)) + sound_index = GameSounds::BALLISTIC_LOAD; + else + sound_index = GameSounds::MISSILE_LOAD; + + if (sound_index.isValid()) + snd_play_3d(gamesnd_get_game_sound(sound_index), &objp->pos, &View_position); + + swp->primary_bank_ammo[i] += reload_amount; + use_mission_rearm_pool_for_weapon(weapon_class, reload_amount, shipp->team); + if (swp->primary_bank_ammo[i] > swp->primary_bank_start_ammo[i]) { + swp->primary_bank_ammo[i] = swp->primary_bank_start_ammo[i]; + } } + } else { + primary_banks_full++; } } - else - { + // if the bank is not a ballistic + else { primary_banks_full++; } - } - // if the bank is not a ballistic - else - { - primary_banks_full++; - } - if ((aip->rearm_first_ballistic_primary == TRUE) && (i == last_ballistic_idx)) - { - if (primary_banks_full != swp->num_primary_banks) - { - // Goober5000 - gamesnd_id sound_index; - if (gamesnd_game_sound_try_load(GameSounds::BALLISTIC_START_LOAD)) - sound_index = GameSounds::BALLISTIC_START_LOAD; - else - sound_index = GameSounds::MISSILE_START_LOAD; - - if (sound_index.isValid()) - snd_play_3d( gamesnd_get_game_sound(sound_index), &objp->pos, &View_position ); - } + if ((aip->rearm_first_ballistic_primary == TRUE) && (i == last_ballistic_idx)) { + if (primary_banks_full != swp->num_primary_banks) { + // Goober5000 + gamesnd_id sound_index; + if (gamesnd_game_sound_try_load(GameSounds::BALLISTIC_START_LOAD)) + sound_index = GameSounds::BALLISTIC_START_LOAD; + else + sound_index = GameSounds::MISSILE_START_LOAD; - aip->rearm_first_ballistic_primary = FALSE; - } - } // end for + if (sound_index.isValid()) + snd_play_3d(gamesnd_get_game_sound(sound_index), &objp->pos, &View_position); + } + + aip->rearm_first_ballistic_primary = FALSE; + } + } // end for + } } // end if (subsys_all_ok) if ( banks_full == swp->num_secondary_banks ) diff --git a/code/source_groups.cmake b/code/source_groups.cmake index 29b19ca55a6..71a969229a1 100644 --- a/code/source_groups.cmake +++ b/code/source_groups.cmake @@ -1516,6 +1516,8 @@ add_file_folder("Scripting\\\\Api\\\\Objs" scripting/api/objs/sound.h scripting/api/objs/species.cpp scripting/api/objs/species.h + scripting/api/objs/support_rearm_pool.cpp + scripting/api/objs/support_rearm_pool.h scripting/api/objs/streaminganim.cpp scripting/api/objs/streaminganim.h scripting/api/objs/subsystem.cpp diff --git a/code/weapon/weapon.h b/code/weapon/weapon.h index 3c14686635d..99ddd8ffd62 100644 --- a/code/weapon/weapon.h +++ b/code/weapon/weapon.h @@ -459,6 +459,7 @@ struct weapon_info float cargo_size; // cargo space taken up by individual weapon (missiles only) float autoaim_fov; // the weapon specific auto-aim field of view float rearm_rate; // rate per second at which secondary weapons are loaded during rearming + bool disallow_rearm; // if true, support ships cannot rearm this weapon int reloaded_per_batch; // number of munitions rearmed per batch float weapon_range; // max range weapon can be effectively fired. (May be less than life * speed) float optimum_range; // causes ai fighters to prefer this distance when attacking with the weapon diff --git a/code/weapon/weapons.cpp b/code/weapon/weapons.cpp index 7ab06d2b58a..29a56cd25a3 100644 --- a/code/weapon/weapons.cpp +++ b/code/weapon/weapons.cpp @@ -1958,6 +1958,10 @@ int parse_weapon(int subtype, bool replace, const char *filename) wip->reloaded_per_batch = REARM_NUM_BALLISTIC_PRIMARIES_PER_BATCH; } } + + if (optional_string("$Disallow Support Rearm:")) { + stuff_boolean(&wip->disallow_rearm); + } if (optional_string("+Weapon Range:")) { stuff_float(&wip->weapon_range); @@ -9457,6 +9461,7 @@ void weapon_info::reset() this->cargo_size = 1.0f; this->autoaim_fov = 0.0f; this->rearm_rate = 1.0f; + this->disallow_rearm = false; this->reloaded_per_batch = -1; this->weapon_range = WEAPON_DEFAULT_TABLED_MAX_RANGE; // *Minimum weapon range, default is 0 -Et1 diff --git a/fred2/CMakeLists.txt b/fred2/CMakeLists.txt index 8d974199f53..621c2409207 100644 --- a/fred2/CMakeLists.txt +++ b/fred2/CMakeLists.txt @@ -134,6 +134,8 @@ set(FRED2_SOURCES shiptexturesdlg.h soundenvironmentdlg.cpp soundenvironmentdlg.h + supportrearmdlg.cpp + supportrearmdlg.h starfieldeditor.cpp starfieldeditor.h stdafx.cpp diff --git a/fred2/fred.rc b/fred2/fred.rc index 676ea0c46db..071b0436f09 100644 --- a/fred2/fred.rc +++ b/fred2/fred.rc @@ -930,16 +930,8 @@ BEGIN EDITTEXT IDC_LOADING_SCREEN640,47,225,97,14,ES_AUTOHSCROLL PUSHBUTTON "1024x768",IDC_LOADING_SCREEN_BUTTON1024,7,241,40,16 EDITTEXT IDC_LOADING_SCREEN1024,47,241,97,16,ES_AUTOHSCROLL - GROUPBOX "Support Ships",IDC_STATIC,160,6,152,71 - CONTROL "Disallow Support Ships",IDC_SUPPORT_ALLOWED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,164,16,95,10 - CONTROL "Support Ships Repair Hull",IDC_SUPPORT_REPAIRS_HULL, - "Button",BS_AUTOCHECKBOX | WS_TABSTOP,164,27,123,10 - LTEXT "Hull Repair Ceiling",IDC_STATIC,164,43,60,8 - EDITTEXT IDC_MAX_HULL_REPAIR_VAL,244,41,49,14,ES_AUTOHSCROLL - LTEXT "%",IDC_STATIC,298,46,8,8 - LTEXT "Subsytem Repair Ceiling",IDC_STATIC,164,64,75,8 - EDITTEXT IDC_MAX_SUBSYS_REPAIR_VAL,244,59,49,14,ES_AUTOHSCROLL - LTEXT "%",IDC_STATIC,298,60,8,8 + GROUPBOX "Support Ships",IDC_STATIC,160,6,152,40 + PUSHBUTTON "Support Options...",IDC_SUPPORT_REARM_OPTIONS,165,20,140,14 GROUPBOX "Ship Trails",IDC_STATIC,160,81,154,41 CONTROL "Toggle (off in nebula; on elsewhere)",IDC_SPECS_TOGGLE_TRAILS, "Button",BS_AUTOCHECKBOX | WS_TABSTOP,162,92,141,10 @@ -998,6 +990,40 @@ BEGIN PUSHBUTTON "Cancel",IDCANCEL,424,0,50,14 END +IDD_SUPPORT_REARM_OPTIONS DIALOGEX 0, 0, 356, 258 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Support Rearm Options" +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + CONTROL "Disallow Support Ships",IDC_DISALLOW_SUPPORT_SHIPS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,8,130,10 + CONTROL "Support Ships Repair Hull",IDC_SUPPORT_REPAIRS_HULL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,20,130,10 + LTEXT "Hull Repair Ceiling",IDC_STATIC,8,35,66,8 + EDITTEXT IDC_MAX_HULL_REPAIR_VAL,78,32,48,14,ES_AUTOHSCROLL + LTEXT "%",IDC_STATIC,130,35,8,8 + LTEXT "Subsystem Repair Ceiling",IDC_STATIC,8,52,80,8 + EDITTEXT IDC_MAX_SUBSYS_REPAIR_VAL,78,49,48,14,ES_AUTOHSCROLL + LTEXT "%",IDC_STATIC,130,52,8,8 + GROUPBOX "Rearm Behavior",IDC_STATIC,148,4,200,74 + CONTROL "Disallow Support Rearm",IDC_DISALLOW_SUPPORT_REARM,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,154,16,187,10 + CONTROL "Limit Rearm to Mission Pool",IDC_LIMIT_SUPPORT_REARM_TO_POOL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,154,28,187,10 + CONTROL "Set Rearm Pool from Loadout Pool surplus",IDC_SUPPORT_REARM_POOL_FROM_LOADOUT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,154,40,187,10 + CONTROL "Use Weapon Precedence when pool is empty",IDC_ALLOW_SUPPORT_REARM_PRECEDENCE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,154,52,187,10 + GROUPBOX "Support Rearm Pool",IDC_STATIC,4,80,346,158 + LTEXT "Pool Team",IDC_STATIC,10,94,29,8 + COMBOBOX IDC_SUPPORT_REARM_POOL_TEAM,42,90,82,84,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LISTBOX IDC_SUPPORT_REARM_WEAPON_LIST,10,106,226,120,LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + LTEXT "Pool Amount",IDC_STATIC,246,95,47,8 + EDITTEXT IDC_SUPPORT_REARM_POOL_AMOUNT,246,107,94,14,ES_AUTOHSCROLL + PUSHBUTTON "Set Amount",IDC_SUPPORT_REARM_SET_AMOUNT,246,127,94,14 + PUSHBUTTON "Set Unlimited (-1)",IDC_SUPPORT_REARM_SET_UNLIMITED,246,145,94,14 + PUSHBUTTON "Set Zero",IDC_SUPPORT_REARM_SET_ZERO,246,163,94,14 + PUSHBUTTON "Set ALL to Amount",IDC_SUPPORT_REARM_SET_ALL_AMOUNT,246,181,94,14 + PUSHBUTTON "Set ALL to Unlimited",IDC_SUPPORT_REARM_SET_ALL_UNLIMITED,246,199,94,14 + PUSHBUTTON "Set ALL to Zero",IDC_SUPPORT_REARM_SET_ALL_ZERO,246,217,94,14 + DEFPUSHBUTTON "OK",IDOK,245,240,50,14 + PUSHBUTTON "Cancel",IDCANCEL,300,240,50,14 +END + IDD_PREFERENCES DIALOG 0, 0, 333, 81 STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Preferences" diff --git a/fred2/missionnotesdlg.cpp b/fred2/missionnotesdlg.cpp index 4531c5ebfe3..53d27d47bab 100644 --- a/fred2/missionnotesdlg.cpp +++ b/fred2/missionnotesdlg.cpp @@ -21,6 +21,7 @@ #include "mission/missionparse.h" #include "mission/missionmessage.h" #include "scripting/global_hooks.h" +#include "supportrearmdlg.h" #ifdef _DEBUG #undef THIS_FILE @@ -58,13 +59,11 @@ CMissionNotesDlg::CMissionNotesDlg(CWnd* pParent /*=NULL*/) : CDialog(CMissionNo m_scramble = FALSE; m_num_respawns = 0; m_max_respawn_delay = -1; - m_disallow_support = 0; m_no_promotion = FALSE; m_no_builtin_msgs = FALSE; m_no_builtin_command_msgs = FALSE; m_no_traitor = FALSE; m_toggle_trails = FALSE; - m_support_repairs_hull = FALSE; m_beam_free_all_by_default = FALSE; m_player_start_using_ai = FALSE; m_toggle_start_chase_view = FALSE; @@ -77,8 +76,6 @@ CMissionNotesDlg::CMissionNotesDlg(CWnd* pParent /*=NULL*/) : CDialog(CMissionNo m_end_to_mainhall = FALSE; m_override_hashcommand = FALSE; m_preload_subspace = FALSE; - m_max_hull_repair_val = 0.0f; - m_max_subsys_repair_val = 100.0f; m_contrail_threshold = CONTRAIL_THRESHOLD_DEFAULT; m_contrail_threshold_flag = FALSE; //}}AFX_DATA_INIT @@ -112,13 +109,11 @@ void CMissionNotesDlg::DoDataExchange(CDataExchange* pDX) DDV_MinMaxUInt(pDX, m_num_respawns, 0, 99); DDX_Text(pDX, IDC_MAX_RESPAWN_DELAY, m_max_respawn_delay); DDV_MinMaxInt(pDX, m_max_respawn_delay, -1, 999); - DDX_Check(pDX, IDC_SUPPORT_ALLOWED, m_disallow_support); DDX_Check(pDX, IDC_NO_PROMOTION, m_no_promotion); DDX_Check(pDX, IDC_DISABLE_BUILTIN_MSGS, m_no_builtin_msgs); DDX_Check(pDX, IDC_DISABLE_BUILTIN_COMMAND_MSGS, m_no_builtin_command_msgs); DDX_Check(pDX, IDC_NO_TRAITOR, m_no_traitor); DDX_Check(pDX, IDC_SPECS_TOGGLE_TRAILS, m_toggle_trails); - DDX_Check(pDX, IDC_SUPPORT_REPAIRS_HULL, m_support_repairs_hull); DDX_Check(pDX, IDC_BEAM_FREE_ALL_BY_DEFAULT, m_beam_free_all_by_default); DDX_Check(pDX, IDC_PLAYER_START_AI, m_player_start_using_ai); DDX_Check(pDX, IDC_TOGGLE_START_CHASE, m_toggle_start_chase_view); @@ -131,10 +126,6 @@ void CMissionNotesDlg::DoDataExchange(CDataExchange* pDX) DDX_Check(pDX, IDC_END_TO_MAINHALL, m_end_to_mainhall); DDX_Check(pDX, IDC_OVERRIDE_HASHCOMMAND, m_override_hashcommand); DDX_Check(pDX, IDC_PRELOAD_SUBSPACE, m_preload_subspace); - DDX_Text(pDX, IDC_MAX_HULL_REPAIR_VAL, m_max_hull_repair_val); - DDV_MinMaxFloat(pDX, m_max_hull_repair_val, 0, 100); - DDX_Text(pDX, IDC_MAX_SUBSYS_REPAIR_VAL, m_max_subsys_repair_val); - DDV_MinMaxFloat(pDX, m_max_subsys_repair_val, 0, 100); DDX_Text(pDX, IDC_CONTRAIL_THRESHOLD, m_contrail_threshold); DDV_MinMaxInt(pDX, m_contrail_threshold, 0, 1000); DDX_Check(pDX, IDC_CONTRAIL_THRESHOLD_CHECK, m_contrail_threshold_flag); @@ -155,6 +146,7 @@ BEGIN_MESSAGE_MAP(CMissionNotesDlg, CDialog) ON_BN_CLICKED(IDC_SOUND_ENVIRONMENT_BUTTON, OnSoundEnvironment) ON_BN_CLICKED(IDC_OPEN_CUSTOM_DATA, OnCustomData) ON_BN_CLICKED(IDC_OPEN_CUSTOM_STRINGS, OnCustomStrings) + ON_BN_CLICKED(IDC_SUPPORT_REARM_OPTIONS, OnSupportRearmOptions) //}}AFX_MSG_MAP END_MESSAGE_MAP() @@ -232,9 +224,6 @@ void CMissionNotesDlg::OnOK() MODIFY(The_mission.game_type, new_m_type ); MODIFY(The_mission.num_respawns, (int)m_num_respawns ); MODIFY(The_mission.max_respawn_delay, m_max_respawn_delay ); - MODIFY(The_mission.support_ships.max_support_ships, (m_disallow_support) ? 0 : -1); - MODIFY(The_mission.support_ships.max_hull_repair_val, m_max_hull_repair_val); - MODIFY(The_mission.support_ships.max_subsys_repair_val, m_max_subsys_repair_val); flags = The_mission.flags; @@ -266,9 +255,6 @@ void CMissionNotesDlg::OnOK() The_mission.contrail_threshold = CONTRAIL_THRESHOLD_DEFAULT; } - //set support ship repairing flags - The_mission.flags.set(Mission::Mission_Flags::Support_repairs_hull, m_support_repairs_hull != 0); - // set default beam free The_mission.flags.set(Mission::Mission_Flags::Beam_free_all_by_default, m_beam_free_all_by_default != 0); @@ -399,13 +385,11 @@ BOOL CMissionNotesDlg::OnInitDialog() m_red_alert = (The_mission.flags[Mission::Mission_Flags::Red_alert]) ? 1 : 0; m_scramble = (The_mission.flags[Mission::Mission_Flags::Scramble]) ? 1 : 0; m_full_war = Mission_all_attack; - m_disallow_support = (The_mission.support_ships.max_support_ships == 0) ? 1 : 0; m_no_promotion = (The_mission.flags[Mission::Mission_Flags::No_promotion]) ? 1 : 0; m_no_builtin_msgs = (The_mission.flags[Mission::Mission_Flags::No_builtin_msgs]) ? 1 : 0; m_no_builtin_command_msgs = (The_mission.flags[Mission::Mission_Flags::No_builtin_command]) ? 1 : 0; m_no_traitor = (The_mission.flags[Mission::Mission_Flags::No_traitor]) ? 1 : 0; m_toggle_trails = (The_mission.flags[Mission::Mission_Flags::Toggle_ship_trails]) ? 1 : 0; - m_support_repairs_hull = (The_mission.flags[Mission::Mission_Flags::Support_repairs_hull]) ? 1 : 0; m_beam_free_all_by_default = (The_mission.flags[Mission::Mission_Flags::Beam_free_all_by_default]) ? 1 : 0; m_player_start_using_ai = (The_mission.flags[Mission::Mission_Flags::Player_start_ai]) ? 1 : 0; m_toggle_start_chase_view = (The_mission.flags[Mission::Mission_Flags::Toggle_start_chase_view]) ? 1 : 0; @@ -511,8 +495,6 @@ BOOL CMissionNotesDlg::OnInitDialog() m_max_respawn_delay_spin.SetRange(-1, 999); m_num_respawns = The_mission.num_respawns; m_max_respawn_delay = The_mission.max_respawn_delay; - m_max_hull_repair_val = The_mission.support_ships.max_hull_repair_val; - m_max_subsys_repair_val = The_mission.support_ships.max_subsys_repair_val; m_contrail_threshold = The_mission.contrail_threshold; m_contrail_threshold_flag = (m_contrail_threshold != CONTRAIL_THRESHOLD_DEFAULT); @@ -670,7 +652,13 @@ void CMissionNotesDlg::OnLoad640() } } - +void CMissionNotesDlg::OnSupportRearmOptions() +{ + CSupportRearmDlg dlg(this); + if (dlg.DoModal() == IDOK) { + set_modified(); + } +} void CMissionNotesDlg::OnEnChangeLoadingScreen641() { diff --git a/fred2/missionnotesdlg.h b/fred2/missionnotesdlg.h index d19752aea7a..644ac40aa32 100644 --- a/fred2/missionnotesdlg.h +++ b/fred2/missionnotesdlg.h @@ -48,13 +48,11 @@ class CMissionNotesDlg : public CDialog BOOL m_scramble; UINT m_num_respawns; int m_max_respawn_delay; - int m_disallow_support; BOOL m_no_promotion; BOOL m_no_builtin_msgs; BOOL m_no_builtin_command_msgs; BOOL m_no_traitor; BOOL m_toggle_trails; - BOOL m_support_repairs_hull; BOOL m_beam_free_all_by_default; BOOL m_player_start_using_ai; BOOL m_toggle_start_chase_view; @@ -67,8 +65,6 @@ class CMissionNotesDlg : public CDialog BOOL m_end_to_mainhall; BOOL m_override_hashcommand; BOOL m_preload_subspace; - float m_max_hull_repair_val; - float m_max_subsys_repair_val; BOOL m_contrail_threshold_flag; int m_contrail_threshold; //}}AFX_DATA @@ -105,6 +101,7 @@ class CMissionNotesDlg : public CDialog afx_msg void OnSoundEnvironment(); afx_msg void OnCustomData(); afx_msg void OnCustomStrings(); + afx_msg void OnSupportRearmOptions(); //}}AFX_MSG DECLARE_MESSAGE_MAP() public: diff --git a/fred2/resource.h b/fred2/resource.h index 0ba948f0bd9..9b62e3be23f 100644 --- a/fred2/resource.h +++ b/fred2/resource.h @@ -137,6 +137,7 @@ #define IDD_MUSIC_PLAYER 330 #define IDD_VOLUMETRICS 332 #define IDD_EDIT_CUSTOM_STRINGS 333 +#define IDD_SUPPORT_REARM_OPTIONS 334 #define IDC_SHIP_CLASS 1003 #define IDC_SHIP_WING 1004 #define IDC_SOUND_CLIP_NAME 1007 @@ -1271,6 +1272,21 @@ #define IDC_PROP_PREV 1710 #define IDC_PROP_NEXT 1711 #define IDC_PROP_FLAGS 1712 +#define IDC_SUPPORT_REARM_OPTIONS 1716 +#define IDC_DISALLOW_SUPPORT_SHIPS 1717 +#define IDC_LIMIT_SUPPORT_REARM_TO_POOL 1718 +#define IDC_DISALLOW_SUPPORT_REARM 1719 +#define IDC_ALLOW_SUPPORT_REARM_PRECEDENCE 1720 +#define IDC_SUPPORT_REARM_POOL_FROM_LOADOUT 1721 +#define IDC_SUPPORT_REARM_WEAPON_LIST 1722 +#define IDC_SUPPORT_REARM_POOL_AMOUNT 1723 +#define IDC_SUPPORT_REARM_SET_AMOUNT 1724 +#define IDC_SUPPORT_REARM_SET_UNLIMITED 1725 +#define IDC_SUPPORT_REARM_SET_ZERO 1726 +#define IDC_SUPPORT_REARM_SET_ALL_AMOUNT 1727 +#define IDC_SUPPORT_REARM_SET_ALL_UNLIMITED 1728 +#define IDC_SUPPORT_REARM_SET_ALL_ZERO 1729 +#define IDC_SUPPORT_REARM_POOL_TEAM 1730 #define IDC_SEXP_POPUP_LIST 32770 #define ID_FILE_MISSIONNOTES 32771 #define ID_DUPLICATE 32774 @@ -1581,7 +1597,7 @@ #define _APS_3D_CONTROLS 1 #define _APS_NEXT_RESOURCE_VALUE 335 #define _APS_NEXT_COMMAND_VALUE 33104 -#define _APS_NEXT_CONTROL_VALUE 1705 +#define _APS_NEXT_CONTROL_VALUE 1731 #define _APS_NEXT_SYMED_VALUE 105 #endif #endif diff --git a/fred2/supportrearmdlg.cpp b/fred2/supportrearmdlg.cpp new file mode 100644 index 00000000000..c5807f1f845 --- /dev/null +++ b/fred2/supportrearmdlg.cpp @@ -0,0 +1,408 @@ +#include "stdafx.h" +#include "FRED.h" +#include "FREDDoc.h" +#include "supportrearmdlg.h" +#include "mission/missionparse.h" +#include "weapon/weapon.h" + +#ifdef _DEBUG +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +CSupportRearmDlg::CSupportRearmDlg(CWnd* pParent) : CDialog(CSupportRearmDlg::IDD, pParent) +{ + m_disallow_support_ships = FALSE; + m_limit_rearm_to_pool = FALSE; + m_support_repairs_hull = FALSE; + m_disallow_support_rearm = FALSE; + m_allow_weapon_precedence = FALSE; + m_rearm_pool_from_loadout = FALSE; + m_max_hull_repair_val = 0.0f; + m_max_subsys_repair_val = 100.0f; + m_weapon_pool_amount = 0; + m_rearm_pool_team = 0; + memset(m_rearm_weapon_pool, 0, sizeof(m_rearm_weapon_pool)); +} + +void CSupportRearmDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + DDX_Check(pDX, IDC_DISALLOW_SUPPORT_SHIPS, m_disallow_support_ships); + DDX_Check(pDX, IDC_LIMIT_SUPPORT_REARM_TO_POOL, m_limit_rearm_to_pool); + DDX_Check(pDX, IDC_SUPPORT_REPAIRS_HULL, m_support_repairs_hull); + DDX_Check(pDX, IDC_DISALLOW_SUPPORT_REARM, m_disallow_support_rearm); + DDX_Check(pDX, IDC_ALLOW_SUPPORT_REARM_PRECEDENCE, m_allow_weapon_precedence); + DDX_Check(pDX, IDC_SUPPORT_REARM_POOL_FROM_LOADOUT, m_rearm_pool_from_loadout); + DDX_Text(pDX, IDC_MAX_HULL_REPAIR_VAL, m_max_hull_repair_val); + DDV_MinMaxFloat(pDX, m_max_hull_repair_val, 0.0f, 100.0f); + DDX_Text(pDX, IDC_MAX_SUBSYS_REPAIR_VAL, m_max_subsys_repair_val); + DDV_MinMaxFloat(pDX, m_max_subsys_repair_val, 0.0f, 100.0f); + DDX_Text(pDX, IDC_SUPPORT_REARM_POOL_AMOUNT, m_weapon_pool_amount); + DDX_CBIndex(pDX, IDC_SUPPORT_REARM_POOL_TEAM, m_rearm_pool_team); +} + +BEGIN_MESSAGE_MAP(CSupportRearmDlg, CDialog) +ON_LBN_SELCHANGE(IDC_SUPPORT_REARM_WEAPON_LIST, OnSelchangeWeaponList) +ON_BN_CLICKED(IDC_SUPPORT_REARM_SET_AMOUNT, OnSetPoolAmount) +ON_BN_CLICKED(IDC_SUPPORT_REARM_SET_UNLIMITED, OnSetPoolUnlimited) +ON_BN_CLICKED(IDC_SUPPORT_REARM_SET_ZERO, OnSetPoolZero) +ON_BN_CLICKED(IDC_SUPPORT_REARM_SET_ALL_AMOUNT, OnSetAllPoolAmount) +ON_BN_CLICKED(IDC_SUPPORT_REARM_SET_ALL_UNLIMITED, OnSetAllPoolUnlimited) +ON_BN_CLICKED(IDC_SUPPORT_REARM_SET_ALL_ZERO, OnSetAllPoolZero) +ON_CBN_SELCHANGE(IDC_SUPPORT_REARM_POOL_TEAM, OnSelchangePoolTeam) +ON_BN_CLICKED(IDC_DISALLOW_SUPPORT_SHIPS, OnOptionChanged) +ON_BN_CLICKED(IDC_SUPPORT_REPAIRS_HULL, OnOptionChanged) +ON_BN_CLICKED(IDC_DISALLOW_SUPPORT_REARM, OnOptionChanged) +ON_BN_CLICKED(IDC_LIMIT_SUPPORT_REARM_TO_POOL, OnOptionChanged) +ON_BN_CLICKED(IDC_SUPPORT_REARM_POOL_FROM_LOADOUT, OnOptionChanged) +ON_BN_CLICKED(IDC_ALLOW_SUPPORT_REARM_PRECEDENCE, OnOptionChanged) +ON_WM_DRAWITEM() +END_MESSAGE_MAP() + +BOOL CSupportRearmDlg::OnInitDialog() +{ + m_disallow_support_ships = (The_mission.support_ships.max_support_ships == 0) ? TRUE : FALSE; + m_limit_rearm_to_pool = The_mission.flags[Mission::Mission_Flags::Limited_support_rearm_pool] ? TRUE : FALSE; + m_support_repairs_hull = The_mission.flags[Mission::Mission_Flags::Support_repairs_hull] ? TRUE : FALSE; + m_disallow_support_rearm = The_mission.support_ships.disallow_rearm ? TRUE : FALSE; + m_allow_weapon_precedence = The_mission.support_ships.allow_rearm_weapon_precedence ? TRUE : FALSE; + m_rearm_pool_from_loadout = The_mission.support_ships.rearm_pool_from_loadout ? TRUE : FALSE; + m_max_hull_repair_val = The_mission.support_ships.max_hull_repair_val; + m_max_subsys_repair_val = The_mission.support_ships.max_subsys_repair_val; + memcpy(m_rearm_weapon_pool, The_mission.support_ships.rearm_weapon_pool, sizeof(m_rearm_weapon_pool)); + m_rearm_pool_team = 0; + + CDialog::OnInitDialog(); + + populate_team_list(); + populate_weapon_list(); + auto* weapon_list = (CListBox*)GetDlgItem(IDC_SUPPORT_REARM_WEAPON_LIST); + if (weapon_list != nullptr && weapon_list->GetCount() > 0) { + weapon_list->SetCurSel(0); + } + update_weapon_amount_display(); + UpdateData(FALSE); + update_control_states(); + + return TRUE; +} + +void CSupportRearmDlg::populate_team_list() +{ + auto* team_combo = (CComboBox*)GetDlgItem(IDC_SUPPORT_REARM_POOL_TEAM); + if (team_combo == nullptr) { + return; + } + + team_combo->ResetContent(); + for (int i = 0; i < Num_teams && i < MAX_TVT_TEAMS; ++i) { + CString text; + text.Format("Team %d", i + 1); + team_combo->AddString(text); + } + + if (team_combo->GetCount() > 0) { + team_combo->SetCurSel(m_rearm_pool_team); + } +} + +CString CSupportRearmDlg::format_weapon_pool_entry(int weapon_class) const +{ + CString text; + const auto& wi = Weapon_info[weapon_class]; + int amount = m_rearm_weapon_pool[m_rearm_pool_team][weapon_class]; + if (wi.disallow_rearm) { + text.Format("%s - 0 (disabled by weapon settings)", wi.name); + return text; + } + if (amount < 0) { + text.Format("%s - Unlimited", wi.name); + } else { + text.Format("%s - %d", wi.name, amount); + } + return text; +} + +void CSupportRearmDlg::populate_weapon_list() +{ + auto* weapon_list = (CListBox*)GetDlgItem(IDC_SUPPORT_REARM_WEAPON_LIST); + if (weapon_list == nullptr) { + return; + } + + weapon_list->ResetContent(); + for (int i = 0; i < weapon_info_size(); ++i) { + if (!Weapon_info[i].wi_flags[Weapon::Info_Flags::Player_allowed]) { + continue; + } + + int list_index = weapon_list->AddString(format_weapon_pool_entry(i)); + weapon_list->SetItemData(list_index, i); + } +} + +int CSupportRearmDlg::get_selected_weapon_class() const +{ + auto* weapon_list = (CListBox*)GetDlgItem(IDC_SUPPORT_REARM_WEAPON_LIST); + if (weapon_list == nullptr) { + return -1; + } + + int sel = weapon_list->GetCurSel(); + if (sel == LB_ERR) { + return -1; + } + + return (int)weapon_list->GetItemData(sel); +} + +void CSupportRearmDlg::update_weapon_amount_display() +{ + int weapon_class = get_selected_weapon_class(); + if (weapon_class < 0 || weapon_class >= weapon_info_size()) { + m_weapon_pool_amount = 0; + } else { + m_weapon_pool_amount = m_rearm_weapon_pool[m_rearm_pool_team][weapon_class]; + } + + UpdateData(FALSE); +} + +void CSupportRearmDlg::set_selected_weapon_amount(int amount) +{ + auto* weapon_list = (CListBox*)GetDlgItem(IDC_SUPPORT_REARM_WEAPON_LIST); + if (weapon_list == nullptr) { + return; + } + + const int sel = weapon_list->GetCurSel(); + const int weapon_class = get_selected_weapon_class(); + if (sel == LB_ERR || weapon_class < 0 || weapon_class >= weapon_info_size()) { + return; + } + + if (Weapon_info[weapon_class].disallow_rearm) { + amount = 0; + } else if (amount < 0) { + amount = -1; + } + + m_rearm_weapon_pool[m_rearm_pool_team][weapon_class] = amount; + weapon_list->DeleteString(sel); + weapon_list->InsertString(sel, format_weapon_pool_entry(weapon_class)); + weapon_list->SetItemData(sel, weapon_class); + weapon_list->SetCurSel(sel); + update_weapon_amount_display(); +} + +void CSupportRearmDlg::set_all_weapon_amount(int amount) +{ + const int normalized_amount = (amount < 0) ? -1 : amount; + auto* weapon_list = (CListBox*)GetDlgItem(IDC_SUPPORT_REARM_WEAPON_LIST); + if (weapon_list != nullptr) { + for (int i = 0; i < weapon_list->GetCount(); ++i) { + const int weapon_class = static_cast(weapon_list->GetItemData(i)); + if (weapon_class < 0 || weapon_class >= weapon_info_size()) { + continue; + } + + if (Weapon_info[weapon_class].disallow_rearm) { + m_rearm_weapon_pool[m_rearm_pool_team][weapon_class] = 0; + } else { + m_rearm_weapon_pool[m_rearm_pool_team][weapon_class] = normalized_amount; + } + } + + const int previous_sel = weapon_list->GetCurSel(); + populate_weapon_list(); + if (previous_sel != LB_ERR && previous_sel < weapon_list->GetCount()) { + weapon_list->SetCurSel(previous_sel); + } else if (weapon_list->GetCount() > 0) { + weapon_list->SetCurSel(0); + } + } + + update_weapon_amount_display(); +} + +void CSupportRearmDlg::update_control_states() +{ + const bool support_enabled = (m_disallow_support_ships == FALSE); + const bool rearm_allowed = support_enabled && (m_disallow_support_rearm == FALSE); + const bool pool_controls_enabled = + rearm_allowed && (m_limit_rearm_to_pool != FALSE) && (m_rearm_pool_from_loadout == FALSE); + const int selected_weapon_class = get_selected_weapon_class(); + const bool selected_weapon_disallow_rearm = + (selected_weapon_class >= 0 && selected_weapon_class < weapon_info_size() && + Weapon_info[selected_weapon_class].disallow_rearm); + const bool right_controls_enabled = pool_controls_enabled && !selected_weapon_disallow_rearm; + + auto enable = [this](int control_id, bool state) { + if (auto* ctrl = GetDlgItem(control_id)) { + ctrl->EnableWindow(state ? TRUE : FALSE); + } + }; + + enable(IDC_SUPPORT_REPAIRS_HULL, support_enabled); + enable(IDC_MAX_HULL_REPAIR_VAL, support_enabled); + enable(IDC_MAX_SUBSYS_REPAIR_VAL, support_enabled); + enable(IDC_DISALLOW_SUPPORT_REARM, support_enabled); + + const bool limited_pool_enabled = rearm_allowed && (m_limit_rearm_to_pool != FALSE); + + enable(IDC_LIMIT_SUPPORT_REARM_TO_POOL, rearm_allowed); + enable(IDC_SUPPORT_REARM_POOL_FROM_LOADOUT, limited_pool_enabled); + enable(IDC_ALLOW_SUPPORT_REARM_PRECEDENCE, limited_pool_enabled); + + enable(IDC_SUPPORT_REARM_WEAPON_LIST, pool_controls_enabled); + enable(IDC_SUPPORT_REARM_POOL_TEAM, pool_controls_enabled); + enable(IDC_SUPPORT_REARM_POOL_AMOUNT, right_controls_enabled); + enable(IDC_SUPPORT_REARM_SET_AMOUNT, right_controls_enabled); + enable(IDC_SUPPORT_REARM_SET_UNLIMITED, right_controls_enabled); + enable(IDC_SUPPORT_REARM_SET_ZERO, right_controls_enabled); + enable(IDC_SUPPORT_REARM_SET_ALL_AMOUNT, right_controls_enabled); + enable(IDC_SUPPORT_REARM_SET_ALL_UNLIMITED, right_controls_enabled); + enable(IDC_SUPPORT_REARM_SET_ALL_ZERO, right_controls_enabled); +} + +void CSupportRearmDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) +{ + if (nIDCtl != IDC_SUPPORT_REARM_WEAPON_LIST || lpDrawItemStruct == nullptr) { + CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct); + return; + } + + if (lpDrawItemStruct->itemID == static_cast(-1)) { + return; + } + + auto* weapon_list = (CListBox*)GetDlgItem(IDC_SUPPORT_REARM_WEAPON_LIST); + if (weapon_list == nullptr) { + return; + } + + CDC* dc = CDC::FromHandle(lpDrawItemStruct->hDC); + CRect rect(lpDrawItemStruct->rcItem); + + const bool selected = (lpDrawItemStruct->itemState & ODS_SELECTED) != 0; + const bool focus = (lpDrawItemStruct->itemState & ODS_FOCUS) != 0; + const int weapon_class = static_cast(weapon_list->GetItemData(lpDrawItemStruct->itemID)); + const bool disallow_rearm = + (weapon_class >= 0 && weapon_class < weapon_info_size()) ? Weapon_info[weapon_class].disallow_rearm : false; + + COLORREF bk = selected ? GetSysColor(COLOR_HIGHLIGHT) : GetSysColor(COLOR_WINDOW); + COLORREF text = selected ? GetSysColor(COLOR_HIGHLIGHTTEXT) : GetSysColor(COLOR_WINDOWTEXT); + if (disallow_rearm) { + text = GetSysColor(COLOR_GRAYTEXT); + } + + dc->FillSolidRect(&rect, bk); + dc->SetBkMode(TRANSPARENT); + dc->SetTextColor(text); + + CString item_text; + weapon_list->GetText(lpDrawItemStruct->itemID, item_text); + CRect text_rect = rect; + text_rect.DeflateRect(2, 0); + dc->DrawText(item_text, &text_rect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX); + + if (focus) { + dc->DrawFocusRect(&rect); + } +} + +void CSupportRearmDlg::OnOptionChanged() +{ + if (!UpdateData(TRUE)) { + return; + } + + update_control_states(); +} + +void CSupportRearmDlg::OnSelchangeWeaponList() +{ + update_weapon_amount_display(); + update_control_states(); +} + +void CSupportRearmDlg::OnSelchangePoolTeam() +{ + if (!UpdateData(TRUE)) { + return; + } + + populate_weapon_list(); + auto* weapon_list = (CListBox*)GetDlgItem(IDC_SUPPORT_REARM_WEAPON_LIST); + if (weapon_list != nullptr && weapon_list->GetCount() > 0) { + weapon_list->SetCurSel(0); + } + update_weapon_amount_display(); + update_control_states(); +} + +void CSupportRearmDlg::OnSetPoolAmount() +{ + if (!UpdateData(TRUE)) { + return; + } + + set_selected_weapon_amount(m_weapon_pool_amount); +} + +void CSupportRearmDlg::OnSetPoolUnlimited() +{ + set_selected_weapon_amount(-1); +} + +void CSupportRearmDlg::OnSetPoolZero() +{ + set_selected_weapon_amount(0); +} + +void CSupportRearmDlg::OnSetAllPoolAmount() +{ + if (!UpdateData(TRUE)) { + return; + } + + set_all_weapon_amount(m_weapon_pool_amount); +} + +void CSupportRearmDlg::OnSetAllPoolUnlimited() +{ + set_all_weapon_amount(-1); +} + +void CSupportRearmDlg::OnSetAllPoolZero() +{ + set_all_weapon_amount(0); +} + +void CSupportRearmDlg::OnOK() +{ + if (!UpdateData(TRUE)) { + return; + } + + The_mission.support_ships.max_support_ships = (m_disallow_support_ships != FALSE) ? 0 : -1; + The_mission.flags.set(Mission::Mission_Flags::Limited_support_rearm_pool, m_limit_rearm_to_pool != FALSE); + The_mission.flags.set(Mission::Mission_Flags::Support_repairs_hull, m_support_repairs_hull != FALSE); + The_mission.support_ships.disallow_rearm = (m_disallow_support_rearm != FALSE); + The_mission.support_ships.allow_rearm_weapon_precedence = (m_allow_weapon_precedence != FALSE); + The_mission.support_ships.rearm_pool_from_loadout = (m_rearm_pool_from_loadout != FALSE); + The_mission.support_ships.max_hull_repair_val = m_max_hull_repair_val; + The_mission.support_ships.max_subsys_repair_val = m_max_subsys_repair_val; + memcpy(The_mission.support_ships.rearm_weapon_pool, m_rearm_weapon_pool, sizeof(m_rearm_weapon_pool)); + + for (int i = 0; i < weapon_info_size(); ++i) { + if (Weapon_info[i].disallow_rearm || !Weapon_info[i].wi_flags[Weapon::Info_Flags::Player_allowed]) { + for (int team = 0; team < MAX_TVT_TEAMS; ++team) { + The_mission.support_ships.rearm_weapon_pool[team][i] = 0; + } + } + } + + CDialog::OnOK(); +} \ No newline at end of file diff --git a/fred2/supportrearmdlg.h b/fred2/supportrearmdlg.h new file mode 100644 index 00000000000..32d42d226a4 --- /dev/null +++ b/fred2/supportrearmdlg.h @@ -0,0 +1,48 @@ +#pragma once + +class CSupportRearmDlg : public CDialog { + public: + CSupportRearmDlg(CWnd* pParent = nullptr); + enum { + IDD = IDD_SUPPORT_REARM_OPTIONS + }; + + BOOL OnInitDialog() override; + void DoDataExchange(CDataExchange* pDX) override; + void OnOK() override; + + protected: + afx_msg void OnSelchangeWeaponList(); + afx_msg void OnSetPoolAmount(); + afx_msg void OnSetPoolUnlimited(); + afx_msg void OnSetPoolZero(); + afx_msg void OnSetAllPoolAmount(); + afx_msg void OnSetAllPoolUnlimited(); + afx_msg void OnSetAllPoolZero(); + afx_msg void OnSelchangePoolTeam(); + afx_msg void OnOptionChanged(); + afx_msg void OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct); + DECLARE_MESSAGE_MAP() + + private: + void update_control_states(); + void populate_team_list(); + void populate_weapon_list(); + void update_weapon_amount_display(); + int get_selected_weapon_class() const; + void set_selected_weapon_amount(int amount); + void set_all_weapon_amount(int amount); + CString format_weapon_pool_entry(int weapon_class) const; + + BOOL m_disallow_support_ships; + BOOL m_limit_rearm_to_pool; + BOOL m_support_repairs_hull; + BOOL m_disallow_support_rearm; + BOOL m_allow_weapon_precedence; + BOOL m_rearm_pool_from_loadout; + float m_max_hull_repair_val; + float m_max_subsys_repair_val; + int m_weapon_pool_amount; + int m_rearm_pool_team; + int m_rearm_weapon_pool[MAX_TVT_TEAMS][MAX_WEAPON_TYPES]; +}; diff --git a/qtfred/source_groups.cmake b/qtfred/source_groups.cmake index 4c8caee9e79..3670e58afe2 100644 --- a/qtfred/source_groups.cmake +++ b/qtfred/source_groups.cmake @@ -102,6 +102,8 @@ add_file_folder("Source/Mission/Dialogs/MissionSpecs" src/mission/dialogs/MissionSpecs/CustomWingNamesDialogModel.h src/mission/dialogs/MissionSpecs/SoundEnvironmentDialogModel.cpp src/mission/dialogs/MissionSpecs/SoundEnvironmentDialogModel.h + src/mission/dialogs/MissionSpecs/SupportRearmDialogModel.cpp + src/mission/dialogs/MissionSpecs/SupportRearmDialogModel.h ) add_file_folder("Source/Mission/Dialogs/ShipEditor" src/mission/dialogs/ShipEditor/ShipEditorDialogModel.h @@ -202,6 +204,8 @@ add_file_folder("Source/UI/Dialogs/MissionSpecs" src/ui/dialogs/MissionSpecs/CustomWingNamesDialog.h src/ui/dialogs/MissionSpecs/SoundEnvironmentDialog.cpp src/ui/dialogs/MissionSpecs/SoundEnvironmentDialog.h + src/ui/dialogs/MissionSpecs/SupportRearmDialog.cpp + src/ui/dialogs/MissionSpecs/SupportRearmDialog.h ) add_file_folder("Source/UI/Dialogs/ShipEditor" src/ui/dialogs/ShipEditor/ShipEditorDialog.h @@ -303,6 +307,7 @@ add_file_folder("UI" ui/SelectionDialog.ui ui/ShieldSystemDialog.ui ui/SoundEnvironmentDialog.ui + ui/SupportRearmDialog.ui ui/VoiceActingManager.ui ui/VolumetricNebulaDialog.ui ui/WaypointEditorDialog.ui diff --git a/qtfred/src/mission/dialogs/MissionSpecDialogModel.cpp b/qtfred/src/mission/dialogs/MissionSpecDialogModel.cpp index a928ba5c283..9a1e65356d6 100644 --- a/qtfred/src/mission/dialogs/MissionSpecDialogModel.cpp +++ b/qtfred/src/mission/dialogs/MissionSpecDialogModel.cpp @@ -67,6 +67,20 @@ void MissionSpecDialogModel::initializeData() { _m_max_hull_repair_val = The_mission.support_ships.max_hull_repair_val; _m_max_subsys_repair_val = The_mission.support_ships.max_subsys_repair_val; + _m_support_rearm_settings.disallowSupportShips = (The_mission.support_ships.max_support_ships == 0); + _m_support_rearm_settings.supportRepairsHull = The_mission.flags[Mission::Mission_Flags::Support_repairs_hull]; + _m_support_rearm_settings.maxHullRepair = The_mission.support_ships.max_hull_repair_val; + _m_support_rearm_settings.maxSubsysRepair = The_mission.support_ships.max_subsys_repair_val; + _m_support_rearm_settings.disallowSupportRearm = The_mission.support_ships.disallow_rearm; + _m_support_rearm_settings.limitRearmToPool = The_mission.flags[Mission::Mission_Flags::Limited_support_rearm_pool]; + _m_support_rearm_settings.rearmPoolFromLoadout = The_mission.support_ships.rearm_pool_from_loadout; + _m_support_rearm_settings.allowWeaponPrecedence = The_mission.support_ships.allow_rearm_weapon_precedence; + for (int team = 0; team < MAX_TVT_TEAMS; ++team) { + for (int i = 0; i < MAX_WEAPON_TYPES; ++i) { + _m_support_rearm_settings.rearmWeaponPool[team][i] = The_mission.support_ships.rearm_weapon_pool[team][i]; + } + } + _m_contrail_threshold = The_mission.contrail_threshold; _m_contrail_threshold_flag = (_m_contrail_threshold != CONTRAIL_THRESHOLD_DEFAULT); @@ -120,9 +134,20 @@ bool MissionSpecDialogModel::apply() { The_mission.num_respawns = _m_num_respawns; The_mission.max_respawn_delay = _m_max_respawn_delay; Entry_delay_time = fl2f(_m_player_entry_delay); - The_mission.support_ships.max_support_ships = (_m_disallow_support) ? 0 : -1; - The_mission.support_ships.max_hull_repair_val = _m_max_hull_repair_val; - The_mission.support_ships.max_subsys_repair_val = _m_max_subsys_repair_val; + The_mission.support_ships.max_support_ships = (_m_support_rearm_settings.disallowSupportShips) ? 0 : -1; + The_mission.support_ships.max_hull_repair_val = _m_support_rearm_settings.maxHullRepair; + The_mission.support_ships.max_subsys_repair_val = _m_support_rearm_settings.maxSubsysRepair; + The_mission.support_ships.disallow_rearm = _m_support_rearm_settings.disallowSupportRearm; + The_mission.support_ships.allow_rearm_weapon_precedence = _m_support_rearm_settings.allowWeaponPrecedence; + The_mission.support_ships.rearm_pool_from_loadout = _m_support_rearm_settings.rearmPoolFromLoadout; + for (int team = 0; team < MAX_TVT_TEAMS; ++team) { + for (int i = 0; i < weapon_info_size(); ++i) { + The_mission.support_ships.rearm_weapon_pool[team][i] = _m_support_rearm_settings.rearmWeaponPool[team][i]; + if (Weapon_info[i].disallow_rearm || !Weapon_info[i].wi_flags[Weapon::Info_Flags::Player_allowed]) { + The_mission.support_ships.rearm_weapon_pool[team][i] = 0; + } + } + } // Copy mission flags The_mission.flags = _m_flags; @@ -317,6 +342,7 @@ SCP_string MissionSpecDialogModel::getHighResLoadingScren() { void MissionSpecDialogModel::setDisallowSupport(bool m_disallow_support) { modify(_m_disallow_support, m_disallow_support); + _m_support_rearm_settings.disallowSupportShips = m_disallow_support; } bool MissionSpecDialogModel::getDisallowSupport() { @@ -325,6 +351,7 @@ bool MissionSpecDialogModel::getDisallowSupport() { void MissionSpecDialogModel::setHullRepairMax(float m_max_hull_repair_val) { modify(_m_max_hull_repair_val, m_max_hull_repair_val); + _m_support_rearm_settings.maxHullRepair = m_max_hull_repair_val; } int MissionSpecDialogModel::getHullRepairMax() { @@ -339,6 +366,21 @@ int MissionSpecDialogModel::getSubsysRepairMax() { return _m_max_subsys_repair_val; } +SupportRearmSettings MissionSpecDialogModel::getSupportRearmSettings() const +{ + return _m_support_rearm_settings; +} + +void MissionSpecDialogModel::setSupportRearmSettings(const SupportRearmSettings& settings) +{ + modify(_m_support_rearm_settings, settings); + _m_disallow_support = settings.disallowSupportShips; + _m_max_hull_repair_val = settings.maxHullRepair; + _m_max_subsys_repair_val = settings.maxSubsysRepair; + setMissionFlagDirect(Mission::Mission_Flags::Support_repairs_hull, settings.supportRepairsHull); + setMissionFlagDirect(Mission::Mission_Flags::Limited_support_rearm_pool, settings.limitRearmToPool); +} + void MissionSpecDialogModel::setTrailThresholdFlag(bool m_contrail_threshold_flag) { modify(_m_contrail_threshold_flag, m_contrail_threshold_flag); } diff --git a/qtfred/src/mission/dialogs/MissionSpecDialogModel.h b/qtfred/src/mission/dialogs/MissionSpecDialogModel.h index 63bce9417fd..29dda086af1 100644 --- a/qtfred/src/mission/dialogs/MissionSpecDialogModel.h +++ b/qtfred/src/mission/dialogs/MissionSpecDialogModel.h @@ -11,6 +11,32 @@ namespace fso::fred::dialogs { +struct SupportRearmSettings { + bool disallowSupportShips = false; + bool supportRepairsHull = true; + float maxHullRepair = 100.0f; + float maxSubsysRepair = 100.0f; + bool disallowSupportRearm = false; + bool limitRearmToPool = false; + bool rearmPoolFromLoadout = false; + bool allowWeaponPrecedence = false; + std::array, MAX_TVT_TEAMS> rearmWeaponPool{}; + + bool operator==(const SupportRearmSettings& rhs) const + { + return disallowSupportShips == rhs.disallowSupportShips && supportRepairsHull == rhs.supportRepairsHull && + maxHullRepair == rhs.maxHullRepair && maxSubsysRepair == rhs.maxSubsysRepair && + disallowSupportRearm == rhs.disallowSupportRearm && limitRearmToPool == rhs.limitRearmToPool && + rearmPoolFromLoadout == rhs.rearmPoolFromLoadout && allowWeaponPrecedence == rhs.allowWeaponPrecedence && + rearmWeaponPool == rhs.rearmWeaponPool; + } + + bool operator!=(const SupportRearmSettings& rhs) const + { + return !(*this == rhs); + } +}; + class MissionSpecDialogModel : public AbstractDialogModel { private: @@ -55,6 +81,7 @@ class MissionSpecDialogModel : public AbstractDialogModel { SCP_vector _m_squadLogoList; int _m_type; + SupportRearmSettings _m_support_rearm_settings; public: MissionSpecDialogModel(QObject* parent, EditorViewport* viewport); @@ -102,6 +129,9 @@ class MissionSpecDialogModel : public AbstractDialogModel { void setSubsysRepairMax(float); int getSubsysRepairMax(); + SupportRearmSettings getSupportRearmSettings() const; + void setSupportRearmSettings(const SupportRearmSettings& settings); + void setTrailThresholdFlag(bool); bool getTrailThresholdFlag(); void setTrailDisplaySpeed(int); diff --git a/qtfred/src/mission/dialogs/MissionSpecs/SupportRearmDialogModel.cpp b/qtfred/src/mission/dialogs/MissionSpecs/SupportRearmDialogModel.cpp new file mode 100644 index 00000000000..337ae6e0a36 --- /dev/null +++ b/qtfred/src/mission/dialogs/MissionSpecs/SupportRearmDialogModel.cpp @@ -0,0 +1,44 @@ +#include "SupportRearmDialogModel.h" + +#include "weapon/weapon.h" + +namespace fso::fred::dialogs { + +SupportRearmDialogModel::SupportRearmDialogModel(QObject* parent, EditorViewport* viewport) + : AbstractDialogModel(parent, viewport) +{ + for (int i = 0; i < weapon_info_size(); ++i) { + if (Weapon_info[i].wi_flags[Weapon::Info_Flags::Player_allowed]) { + _visibleWeaponClasses.push_back(i); + } + } +} + +bool SupportRearmDialogModel::apply() +{ + return true; +} + +void SupportRearmDialogModel::reject() {} + +void SupportRearmDialogModel::setInitial(const SupportRearmSettings& settings) +{ + _settings = settings; +} + +SupportRearmSettings& SupportRearmDialogModel::settings() +{ + return _settings; +} + +const SupportRearmSettings& SupportRearmDialogModel::settings() const +{ + return _settings; +} + +const SCP_vector& SupportRearmDialogModel::visibleWeaponClasses() const +{ + return _visibleWeaponClasses; +} + +} // namespace fso::fred::dialogs diff --git a/qtfred/src/mission/dialogs/MissionSpecs/SupportRearmDialogModel.h b/qtfred/src/mission/dialogs/MissionSpecs/SupportRearmDialogModel.h new file mode 100644 index 00000000000..ca61a3720d3 --- /dev/null +++ b/qtfred/src/mission/dialogs/MissionSpecs/SupportRearmDialogModel.h @@ -0,0 +1,26 @@ +#pragma once + +#include "mission/dialogs/AbstractDialogModel.h" +#include "mission/dialogs/MissionSpecDialogModel.h" + +namespace fso::fred::dialogs { + +class SupportRearmDialogModel : public AbstractDialogModel { + public: + SupportRearmDialogModel(QObject* parent, EditorViewport* viewport); + + bool apply() override; + void reject() override; + + void setInitial(const SupportRearmSettings& settings); + SupportRearmSettings& settings(); + const SupportRearmSettings& settings() const; + + const SCP_vector& visibleWeaponClasses() const; + + private: + SupportRearmSettings _settings; + SCP_vector _visibleWeaponClasses; +}; + +} // namespace fso::fred::dialogs diff --git a/qtfred/src/ui/dialogs/MissionSpecDialog.cpp b/qtfred/src/ui/dialogs/MissionSpecDialog.cpp index 63af4413d86..3f5b64122cd 100644 --- a/qtfred/src/ui/dialogs/MissionSpecDialog.cpp +++ b/qtfred/src/ui/dialogs/MissionSpecDialog.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "mission/util.h" #include @@ -88,11 +89,6 @@ void MissionSpecDialog::updateUi() { ui->lowResScreen->setText(_model->getLowResLoadingScren().c_str()); ui->highResScreen->setText(_model->getHighResLoadingScren().c_str()); - ui->toggleSupportShip->setChecked(_model->getDisallowSupport()); - ui->toggleHullRepair->setChecked(_model->getMissionFlag(Mission::Mission_Flags::Support_repairs_hull)); - ui->hullRepairMax->setValue(_model->getHullRepairMax()); - ui->subsysRepairMax->setValue(_model->getSubsysRepairMax()); - ui->toggleTrail->setChecked(_model->getMissionFlag(Mission::Mission_Flags::Toggle_ship_trails)); ui->toggleSpeedDisplay->setChecked(_model->getTrailThresholdFlag()); ui->minDisplaySpeed->setEnabled(_model->getTrailThresholdFlag()); @@ -355,6 +351,15 @@ void MissionSpecDialog::on_highResScreenButton_clicked() { } } +void MissionSpecDialog::on_supportRearmOptionsButton_clicked() +{ + SupportRearmDialog dlg(this, _viewport); + dlg.setInitial(_model->getSupportRearmSettings()); + if (dlg.exec() == QDialog::Accepted) { + _model->setSupportRearmSettings(dlg.settings()); + } +} + void MissionSpecDialog::on_toggleSupportShip_toggled(bool enabled) { _model->setDisallowSupport(enabled); } diff --git a/qtfred/src/ui/dialogs/MissionSpecDialog.h b/qtfred/src/ui/dialogs/MissionSpecDialog.h index 64884aa02be..a3a1b6c36db 100644 --- a/qtfred/src/ui/dialogs/MissionSpecDialog.h +++ b/qtfred/src/ui/dialogs/MissionSpecDialog.h @@ -47,6 +47,7 @@ private slots: void on_squadronLogoButton_clicked(); void on_lowResScreenButton_clicked(); void on_highResScreenButton_clicked(); + void on_supportRearmOptionsButton_clicked(); // Middle column void on_toggleSupportShip_toggled(bool checked); diff --git a/qtfred/src/ui/dialogs/MissionSpecs/SupportRearmDialog.cpp b/qtfred/src/ui/dialogs/MissionSpecs/SupportRearmDialog.cpp new file mode 100644 index 00000000000..ab0e2871992 --- /dev/null +++ b/qtfred/src/ui/dialogs/MissionSpecs/SupportRearmDialog.cpp @@ -0,0 +1,289 @@ +#include "SupportRearmDialog.h" + +#include "ui_SupportRearmDialog.h" + +#include "ui/util/SignalBlockers.h" +#include "weapon/weapon.h" + +#include + +namespace fso::fred::dialogs { + +SupportRearmDialog::SupportRearmDialog(QWidget* parent, EditorViewport* viewport) + : QDialog(parent), ui(new Ui::SupportRearmDialog()), _model(new SupportRearmDialogModel(this, viewport)), + _viewport(viewport) +{ + ui->setupUi(this); + ui->poolTeamCombo->clear(); + for (int i = 0; i < Num_teams && i < MAX_TVT_TEAMS; ++i) { + ui->poolTeamCombo->addItem(QString("Team %1").arg(i + 1), i); + } + _activePoolTeam = 0; + if (ui->poolTeamCombo->count() > 0) { + ui->poolTeamCombo->setCurrentIndex(_activePoolTeam); + } + populateWeaponList(); + if (ui->weaponList->count() > 0) { + ui->weaponList->setCurrentRow(0); + } + updateFromSelection(); + updateControlStates(); +} + +SupportRearmDialog::~SupportRearmDialog() = default; + +void SupportRearmDialog::setInitial(const SupportRearmSettings& settings) +{ + _model->setInitial(settings); + { + util::SignalBlockers blockers(this); + ui->supportEnabledCheck->setChecked(!settings.disallowSupportShips); + ui->repairHullCheck->setChecked(settings.supportRepairsHull); + ui->hullRepairSpin->setValue(settings.maxHullRepair); + ui->subsysRepairSpin->setValue(settings.maxSubsysRepair); + ui->disallowRearmCheck->setChecked(settings.disallowSupportRearm); + ui->limitPoolCheck->setChecked(settings.limitRearmToPool); + ui->fromLoadoutCheck->setChecked(settings.rearmPoolFromLoadout); + ui->precedenceCheck->setChecked(settings.allowWeaponPrecedence); + _activePoolTeam = 0; + if (ui->poolTeamCombo->count() > 0) { + ui->poolTeamCombo->setCurrentIndex(_activePoolTeam); + } + } + populateWeaponList(); + if (ui->weaponList->count() > 0) { + ui->weaponList->setCurrentRow(0); + } + updateFromSelection(); + updateControlStates(); +} + +SupportRearmSettings SupportRearmDialog::settings() const +{ + auto out = _model->settings(); + out.disallowSupportShips = !ui->supportEnabledCheck->isChecked(); + out.supportRepairsHull = ui->repairHullCheck->isChecked(); + out.maxHullRepair = static_cast(ui->hullRepairSpin->value()); + out.maxSubsysRepair = static_cast(ui->subsysRepairSpin->value()); + out.disallowSupportRearm = ui->disallowRearmCheck->isChecked(); + out.limitRearmToPool = ui->limitPoolCheck->isChecked(); + out.rearmPoolFromLoadout = ui->fromLoadoutCheck->isChecked(); + out.allowWeaponPrecedence = ui->precedenceCheck->isChecked(); + for (int team = 0; team < MAX_TVT_TEAMS; ++team) { + for (int i = 0; i < weapon_info_size(); ++i) { + if (Weapon_info[i].disallow_rearm) { + out.rearmWeaponPool[team][i] = 0; + } + } + } + return out; +} + +void SupportRearmDialog::accept() +{ + if (_model->apply()) { + QDialog::accept(); + } +} + +void SupportRearmDialog::reject() +{ + QDialog::reject(); +} + +void SupportRearmDialog::closeEvent(QCloseEvent* e) +{ + reject(); + e->ignore(); +} + +QString SupportRearmDialog::weaponEntryText(int weaponClass) const +{ + const auto& wi = Weapon_info[weaponClass]; + if (wi.disallow_rearm) { + return QString::fromStdString(SCP_string(wi.name) + " - 0 (disabled by weapon settings)"); + } + const int amount = _model->settings().rearmWeaponPool[_activePoolTeam][weaponClass]; + if (amount < 0) { + return QString::fromStdString(SCP_string(wi.name) + " - Unlimited"); + } + return QString::fromStdString(SCP_string(wi.name) + " - " + std::to_string(amount)); +} + +void SupportRearmDialog::populateWeaponList() +{ + auto prev = selectedWeaponClass(); + ui->weaponList->clear(); + for (auto cls : _model->visibleWeaponClasses()) { + auto* item = new QListWidgetItem(weaponEntryText(cls)); + item->setData(Qt::UserRole, cls); + if (Weapon_info[cls].disallow_rearm) { + item->setForeground(palette().color(QPalette::Disabled, QPalette::Text)); + } + ui->weaponList->addItem(item); + } + for (int i = 0; i < ui->weaponList->count(); ++i) { + if (ui->weaponList->item(i)->data(Qt::UserRole).toInt() == prev) { + ui->weaponList->setCurrentRow(i); + break; + } + } +} + +int SupportRearmDialog::selectedWeaponClass() const +{ + auto* item = ui->weaponList->currentItem(); + if (!item) + return -1; + return item->data(Qt::UserRole).toInt(); +} + +void SupportRearmDialog::updateFromSelection() +{ + const int cls = selectedWeaponClass(); + if (cls < 0 || cls >= weapon_info_size()) { + ui->poolAmountSpin->setValue(0); + } else if (Weapon_info[cls].disallow_rearm) { + ui->poolAmountSpin->setValue(0); + } else { + ui->poolAmountSpin->setValue(_model->settings().rearmWeaponPool[_activePoolTeam][cls]); + } +} + +void SupportRearmDialog::updateControlStates() +{ + const bool supportEnabled = ui->supportEnabledCheck->isChecked(); + const bool rearmAllowed = supportEnabled && !ui->disallowRearmCheck->isChecked(); + const bool poolEnabled = rearmAllowed && ui->limitPoolCheck->isChecked() && !ui->fromLoadoutCheck->isChecked(); + const int cls = selectedWeaponClass(); + const bool selectedDisallowed = (cls >= 0 && cls < weapon_info_size() && Weapon_info[cls].disallow_rearm); + const bool rightEnabled = poolEnabled && !selectedDisallowed; + + ui->repairHullCheck->setEnabled(supportEnabled); + ui->hullRepairSpin->setEnabled(supportEnabled); + ui->subsysRepairSpin->setEnabled(supportEnabled); + ui->disallowRearmCheck->setEnabled(supportEnabled); + + const bool limitedPoolEnabled = rearmAllowed && ui->limitPoolCheck->isChecked(); + + ui->limitPoolCheck->setEnabled(rearmAllowed); + ui->fromLoadoutCheck->setEnabled(limitedPoolEnabled); + ui->precedenceCheck->setEnabled(limitedPoolEnabled); + + ui->weaponList->setEnabled(poolEnabled); + ui->poolTeamCombo->setEnabled(poolEnabled); + ui->poolAmountSpin->setEnabled(rightEnabled); + ui->setAmountButton->setEnabled(rightEnabled); + ui->setUnlimitedButton->setEnabled(rightEnabled); + ui->setZeroButton->setEnabled(rightEnabled); + ui->setAllAmountButton->setEnabled(rightEnabled); + ui->setAllUnlimitedButton->setEnabled(rightEnabled); + ui->setAllZeroButton->setEnabled(rightEnabled); +} + +void SupportRearmDialog::setCurrentWeaponAmount(int amount) +{ + const int cls = selectedWeaponClass(); + if (cls < 0 || cls >= weapon_info_size()) + return; + if (Weapon_info[cls].disallow_rearm) { + _model->settings().rearmWeaponPool[_activePoolTeam][cls] = 0; + } else { + _model->settings().rearmWeaponPool[_activePoolTeam][cls] = (amount < 0) ? -1 : amount; + } + populateWeaponList(); + updateFromSelection(); + updateControlStates(); +} + +void SupportRearmDialog::setAllVisibleWeaponAmounts(int amount) +{ + for (auto cls : _model->visibleWeaponClasses()) { + if (Weapon_info[cls].disallow_rearm) { + _model->settings().rearmWeaponPool[_activePoolTeam][cls] = 0; + } else { + _model->settings().rearmWeaponPool[_activePoolTeam][cls] = (amount < 0) ? -1 : amount; + } + } + populateWeaponList(); + updateFromSelection(); + updateControlStates(); +} + +void SupportRearmDialog::on_okAndCancelButtons_accepted() +{ + accept(); +} +void SupportRearmDialog::on_okAndCancelButtons_rejected() +{ + reject(); +} +void SupportRearmDialog::on_weaponList_currentRowChanged(int) +{ + updateFromSelection(); + updateControlStates(); +} +void SupportRearmDialog::on_setAmountButton_clicked() +{ + setCurrentWeaponAmount(ui->poolAmountSpin->value()); +} +void SupportRearmDialog::on_setUnlimitedButton_clicked() +{ + setCurrentWeaponAmount(-1); +} +void SupportRearmDialog::on_setZeroButton_clicked() +{ + setCurrentWeaponAmount(0); +} +void SupportRearmDialog::on_setAllAmountButton_clicked() +{ + setAllVisibleWeaponAmounts(ui->poolAmountSpin->value()); +} +void SupportRearmDialog::on_setAllUnlimitedButton_clicked() +{ + setAllVisibleWeaponAmounts(-1); +} +void SupportRearmDialog::on_setAllZeroButton_clicked() +{ + setAllVisibleWeaponAmounts(0); +} +void SupportRearmDialog::on_supportEnabledCheck_toggled(bool) +{ + updateControlStates(); +} +void SupportRearmDialog::on_repairHullCheck_toggled(bool) {} +void SupportRearmDialog::on_disallowRearmCheck_toggled(bool) +{ + updateControlStates(); +} +void SupportRearmDialog::on_limitPoolCheck_toggled(bool) +{ + updateControlStates(); +} +void SupportRearmDialog::on_fromLoadoutCheck_toggled(bool) +{ + updateControlStates(); +} +void SupportRearmDialog::on_precedenceCheck_toggled(bool) +{ + updateControlStates(); +} +void SupportRearmDialog::on_hullRepairSpin_valueChanged(double) {} +void SupportRearmDialog::on_subsysRepairSpin_valueChanged(double) {} + +void SupportRearmDialog::on_poolTeamCombo_currentIndexChanged(int index) +{ + if (index < 0 || index >= ui->poolTeamCombo->count()) { + return; + } + + _activePoolTeam = ui->poolTeamCombo->itemData(index).toInt(); + populateWeaponList(); + if (ui->weaponList->count() > 0) { + ui->weaponList->setCurrentRow(0); + } + updateFromSelection(); + updateControlStates(); +} + +} // namespace fso::fred::dialogs diff --git a/qtfred/src/ui/dialogs/MissionSpecs/SupportRearmDialog.h b/qtfred/src/ui/dialogs/MissionSpecs/SupportRearmDialog.h new file mode 100644 index 00000000000..ae80a31aef1 --- /dev/null +++ b/qtfred/src/ui/dialogs/MissionSpecs/SupportRearmDialog.h @@ -0,0 +1,64 @@ +#pragma once + +#include "mission/dialogs/MissionSpecs/SupportRearmDialogModel.h" + +#include + +namespace fso::fred::dialogs { + +namespace Ui { +class SupportRearmDialog; +} + +class SupportRearmDialog : public QDialog { + Q_OBJECT + public: + explicit SupportRearmDialog(QWidget* parent, EditorViewport* viewport); + ~SupportRearmDialog() override; + + void setInitial(const SupportRearmSettings& settings); + SupportRearmSettings settings() const; + + void accept() override; + void reject() override; + + protected: + void closeEvent(QCloseEvent* e) override; + + private slots: + void on_okAndCancelButtons_accepted(); + void on_okAndCancelButtons_rejected(); + void on_weaponList_currentRowChanged(int currentRow); + void on_setAmountButton_clicked(); + void on_setUnlimitedButton_clicked(); + void on_setZeroButton_clicked(); + void on_setAllAmountButton_clicked(); + void on_setAllUnlimitedButton_clicked(); + void on_setAllZeroButton_clicked(); + void on_supportEnabledCheck_toggled(bool); + void on_repairHullCheck_toggled(bool); + void on_disallowRearmCheck_toggled(bool); + void on_limitPoolCheck_toggled(bool); + void on_fromLoadoutCheck_toggled(bool); + void on_precedenceCheck_toggled(bool); + void on_hullRepairSpin_valueChanged(double); + void on_subsysRepairSpin_valueChanged(double); + void on_poolTeamCombo_currentIndexChanged(int); + + private: // NOLINT(readability-redundant-access-specifiers) + void populateWeaponList(); + void updateFromSelection(); + void updateControlStates(); + int selectedWeaponClass() const; + void setCurrentWeaponAmount(int amount); + void setAllVisibleWeaponAmounts(int amount); + QString weaponEntryText(int weaponClass) const; + + std::unique_ptr ui; + std::unique_ptr _model; + EditorViewport* _viewport; + + int _activePoolTeam = 0; +}; + +} // namespace fso::fred::dialogs diff --git a/qtfred/ui/MissionSpecDialog.ui b/qtfred/ui/MissionSpecDialog.ui index b3517fa6f48..71a373b52f9 100644 --- a/qtfred/ui/MissionSpecDialog.ui +++ b/qtfred/ui/MissionSpecDialog.ui @@ -501,83 +501,12 @@ 6 - + - Disallow Support Ships + Support Options - - - - Support Ships Repair Hull - - - - - - - - - Hull Repair Ceiling - - - - - - - % - - - Qt::AlignCenter - - - - - - - Subsystem Repair - - - - - - - % - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - QAbstractSpinBox::NoButtons - - - 0 - - - - - - - QAbstractSpinBox::NoButtons - - - 0 - - - - - diff --git a/qtfred/ui/SupportRearmDialog.ui b/qtfred/ui/SupportRearmDialog.ui new file mode 100644 index 00000000000..6de78315ea5 --- /dev/null +++ b/qtfred/ui/SupportRearmDialog.ui @@ -0,0 +1,70 @@ + + + fso::fred::dialogs::SupportRearmDialog + + 00650420 + Support Options + + + + + + Allow Support Ships + Support Ships Repair Hull + + Hull Repair Ceiling + 0.0100.00 + Subsystem Repair Ceiling + 0.0100.00 + + + + + + Disallow Support Rearm + Limit Rearm to Mission Pool + Set Rearm Pool from Loadout Pool surplus + Use Weapon Precedence when pool is empty + + + + + + Support Rearm Pool + + + + + + + Pool Team + + + + + + + + + + + + + Pool Amount + -1100000 + Set Amount + Set Unlimited (-1) + Set Zero + Set ALL to Amount + Set ALL to Unlimited + Set ALL to Zero + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + +