From 9f1fca9ffcd598891612ccda02785d14b5f7092a Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Mon, 10 Aug 2026 03:28:36 -0400 Subject: [PATCH 1/2] Convert team_data to sparse loadout entry vectors team_data was the last persistence-adjacent structure sized by MAX_SHIP_CLASSES: four parallel ship arrays plus four parallel weapon arrays (one slot per mission-file loadout line, holding a class or a sexp variable name, and a count or a sexp variable name) with manual num_*_choices counters, plus a dense per-class weapon_required flag array. Convert all of it to the sparse-container family: ship_list / ship_list_variables / ship_count / ship_count_variables -> SCP_vector ship_choices weaponry_pool / weaponry_count / weaponry_pool_variable / weaponry_amount_variable -> SCP_vector weapon_choices bool weapon_required[MAX_WEAPON_TYPES] -> SCP_set required_weapons where loadout_entry is {class_index, count, class_variable, count_variable}, with empty variable strings meaning the value was given literally. num_ship_choices / num_weapon_choices become .size(). Intentional behavior fixes, all previously latent memory errors or data bugs: * is_ship_assignable stored an alt ship CLASS in a variable used as a loadout list INDEX when default_to_this_class was false, then swapped to ship_list[class] - garbage for any mission where the two domains diverge. It now swaps to the alt class itself, per the evident intent. * The default-ship fallback read ship_list[0] even when a team had zero ship choices; it now leaves default_ship at -1, and ss_fixup_team_data guards its append of the default ship accordingly. * FRED2's dumpstats indexed Ships[] with a ship class when printing loadout entries; it now prints the entry's class or variable name directly. * Team parse now clears the previous mission's entries explicitly rather than relying on overwrite-in-place. This clears the largest remaining cluster of MAX_SHIP_CLASSES references on the way to lifting the ship class limit. Co-Authored-By: Claude Opus 4.7 (1M context) --- code/lab/manager/lab_manager.cpp | 19 +- code/mission/missionparse.cpp | 183 +++---- code/mission/missionparse.h | 34 +- code/missioneditor/missionsave.cpp | 72 ++- code/missioneditor/sexp_tree_model.cpp | 15 +- code/missionui/missionshipchoice.cpp | 102 ++-- code/missionui/missionweaponchoice.cpp | 6 +- code/scripting/api/objs/weaponclass.cpp | 2 +- code/weapon/weapons.cpp | 4 +- fred2/dumpstats.cpp | 16 +- fred2/freddoc.cpp | 35 +- fred2/management.cpp | 55 +-- fred2/management.h | 6 +- fred2/playerstarteditor.cpp | 457 ++++++++---------- fred2/playerstarteditor.h | 26 +- qtfred/src/mission/Editor.cpp | 105 ++-- qtfred/src/mission/Editor.h | 19 +- .../dialogs/TeamLoadoutDialogModel.cpp | 120 ++--- qtfred/src/ui/util/ErrorChecker.cpp | 34 +- 19 files changed, 565 insertions(+), 745 deletions(-) diff --git a/code/lab/manager/lab_manager.cpp b/code/lab/manager/lab_manager.cpp index 24560967a3a..038785750b0 100644 --- a/code/lab/manager/lab_manager.cpp +++ b/code/lab/manager/lab_manager.cpp @@ -71,24 +71,21 @@ LabManager::LabManager() { team_data* teamp = &Team_data[0]; // In the lab, all ships are valid + teamp->ship_choices.clear(); for (size_t i = 0; i < Ship_info.size(); ++i) { - teamp->ship_list[i] = static_cast(i); - strcpy_s(teamp->ship_list_variables[i], ""); - teamp->ship_count[i] = 1; - teamp->loadout_total += 1; - strcpy_s(teamp->ship_count_variables[i], ""); + auto &entry = teamp->ship_choices.emplace_back(); + entry.class_index = sz2i(i); + entry.count = 1; } teamp->default_ship = 0; - teamp->num_ship_choices = static_cast(Ship_info.size()); // you want guns? you get guns. + teamp->weapon_choices.clear(); for (size_t i = 0; i < Weapon_info.size(); ++i) { - teamp->weaponry_pool[i] = static_cast(i); - teamp->weaponry_count[i] = 640; // should be enough for everyone - strcpy_s(teamp->weaponry_amount_variable[i], ""); - strcpy_s(teamp->weaponry_pool_variable[i], ""); + auto &entry = teamp->weapon_choices.emplace_back(); + entry.class_index = sz2i(i); + entry.count = 640; // should be enough for everyone } - teamp->num_weapon_choices = static_cast(Weapon_info.size()); Game_mode |= GM_LAB; diff --git a/code/mission/missionparse.cpp b/code/mission/missionparse.cpp index 94aa7836c6c..46ffb47f716 100644 --- a/code/mission/missionparse.cpp +++ b/code/mission/missionparse.cpp @@ -1228,9 +1228,13 @@ void parse_player_info2(mission *pm) // read in a ship/weapon pool for each team. for ( nt = 0; nt < Num_teams; nt++ ) { - int num_choices; - ptr = &Team_data[nt]; + + // clear anything from a previously parsed mission + ptr->ship_choices.clear(); + ptr->weapon_choices.clear(); + ptr->required_weapons.clear(); + // get the shipname for single player missions // MWA -- make this required later!!!! if ( optional_string("$Starting Shipname:") ) @@ -1245,8 +1249,6 @@ void parse_player_info2(mission *pm) required_string("$Ship Choices:"); stuff_loadout_list(list, ParseLookupType::MISSION_LOADOUT_SHIP_LIST); - num_choices = 0; - // check ship class loadout entries for (auto &sc : list) { if (!Ship_info.in_bounds(sc.index)) @@ -1259,43 +1261,30 @@ void parse_player_info2(mission *pm) continue; } - // if the list isn't set by a variable leave the variable name empty - if (sc.index_sexp_var == NOT_SET_BY_SEXP_VARIABLE) { - strcpy_s(ptr->ship_list_variables[num_choices], "") ; - } - else { - strcpy_s(ptr->ship_list_variables[num_choices], Sexp_variables[sc.index_sexp_var].variable_name); - } - - ptr->ship_list[num_choices] = sc.index; - ptr->ship_count[num_choices] = sc.count; - ptr->loadout_total += sc.count; + auto &entry = ptr->ship_choices.emplace_back(); + entry.class_index = sc.index; + entry.count = sc.count; - // if the list isn't set by a variable leave the variable name empty - if (sc.count_sexp_var == NOT_SET_BY_SEXP_VARIABLE) { - strcpy_s(ptr->ship_count_variables[num_choices], ""); - } - else { - strcpy_s(ptr->ship_count_variables[num_choices], Sexp_variables[sc.count_sexp_var].variable_name); - } - - num_choices++; + // if the entry isn't set by a variable leave the variable name empty + if (sc.index_sexp_var != NOT_SET_BY_SEXP_VARIABLE) + entry.class_variable = Sexp_variables[sc.index_sexp_var].variable_name; + if (sc.count_sexp_var != NOT_SET_BY_SEXP_VARIABLE) + entry.count_variable = Sexp_variables[sc.count_sexp_var].variable_name; } - ptr->num_ship_choices = num_choices; ptr->default_ship = -1; if (optional_string("+Default_ship:")) { char str[NAME_LENGTH]; stuff_string(str, F_NAME, NAME_LENGTH); ptr->default_ship = ship_info_lookup(str); - if (-1 == ptr->default_ship) { - WarningEx(LOCATION, "Mission: %s\nUnknown default ship %s! Defaulting to %s.", pm->name.c_str(), str, Ship_info[ptr->ship_list[0]].name ); - ptr->default_ship = ptr->ship_list[0]; // default to 1st in list + if (-1 == ptr->default_ship && !ptr->ship_choices.empty()) { + WarningEx(LOCATION, "Mission: %s\nUnknown default ship %s! Defaulting to %s.", pm->name.c_str(), str, Ship_info[ptr->ship_choices.front().class_index].name ); + ptr->default_ship = ptr->ship_choices.front().class_index; // default to 1st in list } // see if the player's default ship is an allowable ship (campaign only). If not, then what // do we do? choose the first allowable one? if (Game_mode & GM_CAMPAIGN_MODE || (MULTIPLAYER_CLIENT)) { - if ( !Campaign.ships_allowed.contains(ptr->default_ship) ) { + if ( ptr->default_ship >= 0 && !Campaign.ships_allowed.contains(ptr->default_ship) ) { for (i = 0; i < ship_info_size(); i++ ) { if ( Campaign.ships_allowed.contains(i) ) { ptr->default_ship = i; @@ -1307,14 +1296,12 @@ void parse_player_info2(mission *pm) } } - if (ptr->default_ship == -1) // invalid or not specified, make first in list - ptr->default_ship = ptr->ship_list[0]; + if (ptr->default_ship == -1 && !ptr->ship_choices.empty()) // invalid or not specified, make first in list + ptr->default_ship = ptr->ship_choices.front().class_index; required_string("+Weaponry Pool:"); stuff_loadout_list(list2, ParseLookupType::MISSION_LOADOUT_WEAPON_LIST); - num_choices = 0; - // When seeding the rearm pool from the loadout, reset this team's row to 0 so the per-weapon // loadout counts below accumulate from a clean baseline (rather than the -1 "unlimited" default). if (pm->support_ships.rearm_pool_from_loadout) { @@ -1343,8 +1330,9 @@ void parse_player_info2(mission *pm) continue; } - ptr->weaponry_pool[num_choices] = wc.index; - ptr->weaponry_count[num_choices] = wc.count; + auto &entry = ptr->weapon_choices.emplace_back(); + entry.class_index = wc.index; + entry.count = wc.count; if (pm->support_ships.rearm_pool_from_loadout) { if (Weapon_info[wc.index].disallow_rearm) { @@ -1354,25 +1342,12 @@ void parse_player_info2(mission *pm) } } - // if the list isn't set by a variable leave the variable name empty - if (wc.index_sexp_var == NOT_SET_BY_SEXP_VARIABLE) { - strcpy_s(ptr->weaponry_pool_variable[num_choices], ""); - } - else { - strcpy_s(ptr->weaponry_pool_variable[num_choices], Sexp_variables[wc.index_sexp_var].variable_name); - } - - // if the list isn't set by a variable leave the variable name empty - if (wc.count_sexp_var == NOT_SET_BY_SEXP_VARIABLE) { - strcpy_s(ptr->weaponry_amount_variable[num_choices], ""); - } - else { - strcpy_s(ptr->weaponry_amount_variable[num_choices], Sexp_variables[wc.count_sexp_var].variable_name); - } - - num_choices++; + // if the entry isn't set by a variable leave the variable name empty + if (wc.index_sexp_var != NOT_SET_BY_SEXP_VARIABLE) + entry.class_variable = Sexp_variables[wc.index_sexp_var].variable_name; + if (wc.count_sexp_var != NOT_SET_BY_SEXP_VARIABLE) + entry.count_variable = Sexp_variables[wc.count_sexp_var].variable_name; } - ptr->num_weapon_choices = num_choices; if (optional_string("+Support Rearm Pool:")) { support_rearm_list.clear(); @@ -1408,15 +1383,13 @@ void parse_player_info2(mission *pm) } } - memset(ptr->weapon_required, 0, MAX_WEAPON_TYPES * sizeof(bool)); if (optional_string("+Required for mission:")) { - int num_weapons; - int weapon_list_buf[MAX_WEAPON_TYPES]; - num_weapons = sz2i(stuff_int_list(weapon_list_buf, MAX_WEAPON_TYPES, ParseLookupType::WEAPON_LIST_TYPE)); + SCP_vector weapon_list_buf; + stuff_int_list(weapon_list_buf, ParseLookupType::WEAPON_LIST_TYPE); - for (i = 0; i < num_weapons; i++) - ptr->weapon_required[weapon_list_buf[i]] = true; + for (int weapon_class : weapon_list_buf) + ptr->required_weapons.insert(weapon_class); } } @@ -4269,17 +4242,17 @@ void parse_common_object_data(p_object *p_objp) /** * Checks if any ships of a certain ship class are still available in the team loadout - * @return The index of the ship in team_data->ship_list if found or -1 if it isn't + * @return The index of the entry in team_data->ship_choices if found or -1 if it isn't */ -int get_reassigned_index(team_data *current_team, int ship_class) +int get_reassigned_index(team_data *current_team, int ship_class) { // Search through the available ships to see if there is a matching ship class in the loadout - for (int i=0; i < current_team->num_ship_choices; i++) + for (size_t i = 0; i < current_team->ship_choices.size(); ++i) { - if (ship_class == current_team->ship_list[i]) + if (ship_class == current_team->ship_choices[i].class_index) { - if (current_team->ship_count[i] > 0) { - return i; + if (current_team->ship_choices[i].count > 0) { + return sz2i(i); } else { return -1; @@ -4291,65 +4264,60 @@ int get_reassigned_index(team_data *current_team, int ship_class) } /** - * Updates the loadout quanities for a ship class. + * Takes one ship of this loadout entry, if any remain. */ -void update_loadout_totals(team_data *current_team, int loadout_index) +void take_ship_from_loadout(team_data *current_team, int loadout_index) { - // Fix the loadout variables to show that the class has less available if there are still ships available - if (current_team->ship_count[loadout_index] > 0) + // Fix the loadout entry to show that the class has less available if there are still ships available + if (current_team->ship_choices[loadout_index].count > 0) { - Assert (current_team->loadout_total > 0); - - current_team->ship_count[loadout_index]--; - current_team->loadout_total--; + current_team->ship_choices[loadout_index].count--; } } /** * Attempts to set the class of this ship based which ship classes still remain unassigned in the ship loadout - * The ship class specified by the mission file itself is tested first. Followed by the list of alt classes. + * The ship class specified by the mission file itself is tested first, followed by the list of alt classes. * If an alt class flagged as default_to_this_class is reached the ship will be assigned to that class. - * If the class can't be assigned because no ships of that class remain the function returns false. + * If the class can't be assigned because no ships of that class remain, the function returns false. */ bool is_ship_assignable(p_object *p_objp) { - int loadout_index = -1; - team_data *data_for_team = &Team_data[p_objp->team]; // First lets check if the ship specified in the mission file is of an assignable class - loadout_index = get_reassigned_index(data_for_team, p_objp->ship_class); + int loadout_index = get_reassigned_index(data_for_team, p_objp->ship_class); if (loadout_index != -1 ) { - Assert (data_for_team->loadout_total > 0); + take_ship_from_loadout(data_for_team, loadout_index); - update_loadout_totals(data_for_team, loadout_index); - // Since the ship in the mission file matched one available in the loadout we need go no further return true; } // Now we check the alt_classes (if there are any) - for (SCP_vector::iterator pac = p_objp->alt_classes.begin(); pac != p_objp->alt_classes.end(); ++pac) { + int assigned_class = -1; + for (auto &pac : p_objp->alt_classes) { // we don't check availability unless we are asked to - if (pac->default_to_this_class == false) { - loadout_index = pac->ship_class; + if (!pac.default_to_this_class) { + assigned_class = pac.ship_class; break; } else { - loadout_index = get_reassigned_index(data_for_team, pac->ship_class); + loadout_index = get_reassigned_index(data_for_team, pac.ship_class); if (loadout_index != -1 ) { - update_loadout_totals(data_for_team, loadout_index); + take_ship_from_loadout(data_for_team, loadout_index); + assigned_class = pac.ship_class; break; } } } // If we managed to assign a class we'd may need to actually swap to it - if (loadout_index != -1 ) { - if (p_objp->ship_class != data_for_team->ship_list[loadout_index]) + if (assigned_class != -1 ) { + if (p_objp->ship_class != assigned_class) { - swap_parse_object(p_objp, data_for_team->ship_list[loadout_index]); + swap_parse_object(p_objp, assigned_class); } return true; } @@ -4379,43 +4347,28 @@ void process_loadout_objects() } } - // Now we go though the ships we were unable to assign earlier and reassign them on a first come first + // Now we go though the ships we were unable to assign earlier and reassign them on a first come first // served basis. for (size_t m=0; m < reassignments.size(); m++) { p_object *p_objp = &Parse_objects[reassignments[m]]; team_data *current_team = &Team_data[p_objp->team]; - bool loadout_assigned = false; Assert(p_objp->flags[Mission::Parse_Object_Flags::SF_Set_class_dynamically]); - // First thing to check is whether we actually have any ships left to assign - if (current_team->loadout_total == 0) + // Go through the loadout until we find an unassigned ship. If no ships remain + // anywhere in the loadout, the ship in the mission file is used as-is. + for (auto &entry : current_team->ship_choices) { - // If there is nothing left to assign we should use the ship in the mission file - loadout_assigned = true; - } - // We do have ships left in the team loadout that we can assign - else - { - // Go through the loadout until we find an unassigned ship - for (int j=0; j < current_team->num_ship_choices; j++) + if (entry.count > 0) { - if (current_team->ship_count[j] > 0) - { - update_loadout_totals(current_team, j); - // We will need to assign a new class too (if a p_object the same class was available - // it should have been assigned by attempt_loadout_assignation_from_defaults() - Assert (p_objp->ship_class != current_team->ship_list[j]); - swap_parse_object(p_objp, current_team->ship_list[j]); - - loadout_assigned = true; - break ; - } + entry.count--; + // We will need to assign a new class too (if a p_object the same class was available + // it should have been assigned by attempt_loadout_assignation_from_defaults() + Assert (p_objp->ship_class != entry.class_index); + swap_parse_object(p_objp, entry.class_index); + break; } } - - // We should never reach here with an unassigned loadout - Assert (loadout_assigned); } } diff --git a/code/mission/missionparse.h b/code/mission/missionparse.h index 40dbf55614e..2bf62fc8190 100644 --- a/code/mission/missionparse.h +++ b/code/mission/missionparse.h @@ -547,25 +547,27 @@ extern SCP_vector Parse_objects; extern p_object Support_ship_pobj, *Arriving_support_ship; extern p_object Ship_arrival_list; -typedef struct team_data { +// one line of a team loadout: a ship or weapon class (given literally or via a sexp variable) +// and how many of it are available (likewise literal or via a variable) +struct loadout_entry +{ + int class_index = -1; // resolved ship or weapon class + int count = 0; + SCP_string class_variable; // sexp variable name; empty = class was given literally + SCP_string count_variable; // sexp variable name; empty = count was given literally +}; + +struct team_data +{ // ships - int default_ship; // default ship type for player start point (recommended choice) - int num_ship_choices; // number of ship choices inside ship_list - int loadout_total; // Total number of ships available of all classes - int ship_list[MAX_SHIP_CLASSES]; - char ship_list_variables[MAX_SHIP_CLASSES][TOKEN_LENGTH]; - int ship_count[MAX_SHIP_CLASSES]; - char ship_count_variables[MAX_SHIP_CLASSES][TOKEN_LENGTH]; + int default_ship = -1; // default ship type for player start point (recommended choice) + SCP_vector ship_choices; // ship classes (and counts) available in the loadout // weapons - int num_weapon_choices; - bool do_not_validate; - int weaponry_pool[MAX_WEAPON_TYPES]; - int weaponry_count[MAX_WEAPON_TYPES]; - char weaponry_pool_variable[MAX_WEAPON_TYPES][TOKEN_LENGTH]; - char weaponry_amount_variable[MAX_WEAPON_TYPES][TOKEN_LENGTH]; - bool weapon_required[MAX_WEAPON_TYPES]; -} team_data; + bool do_not_validate = false; + SCP_vector weapon_choices; // weapon classes (and counts) available in the loadout + SCP_set required_weapons; // weapon classes that cannot be removed in weapon select +}; #define MAX_P_WINGS 16 #define MAX_SHIP_LIST 16 diff --git a/code/missioneditor/missionsave.cpp b/code/missioneditor/missionsave.cpp index 72201544116..ead80baf9d9 100644 --- a/code/missioneditor/missionsave.cpp +++ b/code/missioneditor/missionsave.cpp @@ -4415,51 +4415,53 @@ int Fred_mission_save::save_players() parse_comments(); fout(" (\n"); - int num_dogfight_weapons = 0; - SCP_vector dogfight_ships; + SCP_vector ships_without_dogfight_weapons; - for (j = 0; j < Team_data[i].num_ship_choices; j++) { + for (const auto &sc : Team_data[i].ship_choices) { // Check to see if a variable name should be written for the class rather than a number - if (strlen(Team_data[i].ship_list_variables[j])) { - var_idx = get_index_sexp_variable_name(Team_data[i].ship_list_variables[j]); + if (!sc.class_variable.empty()) { + var_idx = get_index_sexp_variable_name(sc.class_variable.c_str()); Assert(var_idx > -1 && var_idx < MAX_SEXP_VARIABLES); wrote_fso_data = true; fout("\t@%s\t", Sexp_variables[var_idx].variable_name); } else { - fout("\t\"%s\"\t", Ship_info[Team_data[i].ship_list[j]].name); + fout("\t\"%s\"\t", Ship_info[sc.class_index].name); } // Now check if we should write a variable or a number for the amount of ships available - if (strlen(Team_data[i].ship_count_variables[j])) { - var_idx = get_index_sexp_variable_name(Team_data[i].ship_count_variables[j]); + if (!sc.count_variable.empty()) { + var_idx = get_index_sexp_variable_name(sc.count_variable.c_str()); Assert(var_idx > -1 && var_idx < MAX_SEXP_VARIABLES); wrote_fso_data = true; fout("@%s\n", Sexp_variables[var_idx].variable_name); } else { - fout("%d\n", Team_data[i].ship_count[j]); + fout("%d\n", sc.count); } // Check the weapons pool for at least one dogfight weapon for this ship type - if (IS_MISSION_MULTI_DOGFIGHT) { - for (int wepCount = 0; wepCount < Team_data[i].num_weapon_choices; wepCount++) { - if (Ship_info[Team_data[i].ship_list[j]].allowed_weapons[Team_data[i].weaponry_pool[wepCount]] & - DOGFIGHT_WEAPON) { - num_dogfight_weapons++; + // (variable-specified classes can't be resolved at save time, so skip them) + if (IS_MISSION_MULTI_DOGFIGHT && sc.class_index >= 0) { + bool found_dogfight_weapon = false; + for (const auto &wc : Team_data[i].weapon_choices) { + if (wc.class_index >= 0 && + (Ship_info[sc.class_index].allowed_weapons[wc.class_index] & DOGFIGHT_WEAPON)) { + found_dogfight_weapon = true; break; - } else { - dogfight_ships.push_back(Ship_info[Team_data[i].ship_list[j]].name); } } + if (!found_dogfight_weapon) { + ships_without_dogfight_weapons.emplace_back(Ship_info[sc.class_index].name); + } } } fout(")"); // make sure we have at least one dogfight weapon for each ship type in a dogfight mission - if (IS_MISSION_MULTI_DOGFIGHT && (num_dogfight_weapons != Team_data[i].num_ship_choices)) { - for (const auto& d_ship : dogfight_ships) { + if (IS_MISSION_MULTI_DOGFIGHT && !ships_without_dogfight_weapons.empty()) { + for (const auto& d_ship : ships_without_dogfight_weapons) { mprintf(("Warning: Ship %s has no dogfight weapons allowed\n", d_ship.c_str())); } SCP_string msg = @@ -4478,33 +4480,33 @@ int Fred_mission_save::save_players() fout(" (\n"); generate_weaponry_usage_list_team(i, used_pool); - for (j = 0; j < Team_data[i].num_weapon_choices; j++) { + for (const auto &wc : Team_data[i].weapon_choices) { // first output the weapon name or a variable that sets it - if (strlen(Team_data[i].weaponry_pool_variable[j])) { - var_idx = get_index_sexp_variable_name(Team_data[i].weaponry_pool_variable[j]); + if (!wc.class_variable.empty()) { + var_idx = get_index_sexp_variable_name(wc.class_variable.c_str()); Assert(var_idx > -1 && var_idx < MAX_SEXP_VARIABLES); wrote_fso_data = true; fout("\t@%s\t", Sexp_variables[var_idx].variable_name); } else { - fout("\t\"%s\"\t", Weapon_info[Team_data[i].weaponry_pool[j]].name); + fout("\t\"%s\"\t", Weapon_info[wc.class_index].name); } // now output the amount of this weapon or a variable that sets it. If this weapon is in the used pool and // isn't set by a variable we should add the amount of weapons used by the wings to it and zero the entry so // we know that we have dealt with it - if (strlen(Team_data[i].weaponry_amount_variable[j])) { - var_idx = get_index_sexp_variable_name(Team_data[i].weaponry_amount_variable[j]); + if (!wc.count_variable.empty()) { + var_idx = get_index_sexp_variable_name(wc.count_variable.c_str()); Assert(var_idx > -1 && var_idx < MAX_SEXP_VARIABLES); wrote_fso_data = true; fout("@%s\n", Sexp_variables[var_idx].variable_name); } else { - if (strlen(Team_data[i].weaponry_pool_variable[j])) { - fout("%d\n", Team_data[i].weaponry_count[j]); + if (!wc.class_variable.empty()) { + fout("%d\n", wc.count); } else { - fout("%d\n", Team_data[i].weaponry_count[j] + used_pool[Team_data[i].weaponry_pool[j]]); - used_pool[Team_data[i].weaponry_pool[j]] = 0; + fout("%d\n", wc.count + used_pool[wc.class_index]); + used_pool[wc.class_index] = 0; } } } @@ -4568,23 +4570,15 @@ int Fred_mission_save::save_players() } // Goober5000 - mjn.mixael's required weapon feature - bool uses_required_weapon = false; - for (j = 0; j < weapon_info_size(); j++) { - if (Team_data[i].weapon_required[j]) { - uses_required_weapon = true; - break; - } - } - if (save_config.save_format != MissionFormat::RETAIL && uses_required_weapon) { + if (save_config.save_format != MissionFormat::RETAIL && !Team_data[i].required_weapons.empty()) { if (optional_string_fred("+Required for mission:", "$Starting Shipname:")) parse_comments(2); else fout("\n+Required for mission:"); fout(" ("); - for (j = 0; j < weapon_info_size(); j++) { - if (Team_data[i].weapon_required[j]) - fout(" \"%s\"", Weapon_info[j].name); + for (int weapon_class : Team_data[i].required_weapons) { + fout(" \"%s\"", Weapon_info[weapon_class].name); } fout(" )"); } diff --git a/code/missioneditor/sexp_tree_model.cpp b/code/missioneditor/sexp_tree_model.cpp index e9827a81c55..7e45afc5b89 100644 --- a/code/missioneditor/sexp_tree_model.cpp +++ b/code/missioneditor/sexp_tree_model.cpp @@ -1149,25 +1149,24 @@ int SexpTreeModel::get_loadout_variable_count(int var_index) // we shouldn't be being passed the index of variables that do not exist Assertion(var_index >= 0 && var_index < MAX_SEXP_VARIABLES, "Invalid variable index"); - int idx; int count = 0; + const char *var_name = Sexp_variables[var_index].variable_name; for (auto& team_datum : Team_data) { - for (idx = 0; idx < team_datum.num_ship_choices; idx++) { - if (!strcmp(team_datum.ship_list_variables[idx], Sexp_variables[var_index].variable_name)) { + for (auto& entry : team_datum.ship_choices) { + if (entry.class_variable == var_name) { count++; } - - if (!strcmp(team_datum.ship_count_variables[idx], Sexp_variables[var_index].variable_name)) { + if (entry.count_variable == var_name) { count++; } } - for (idx = 0; idx < team_datum.num_weapon_choices; idx++) { - if (!strcmp(team_datum.weaponry_pool_variable[idx], Sexp_variables[var_index].variable_name)) { + for (auto& entry : team_datum.weapon_choices) { + if (entry.class_variable == var_name) { count++; } - if (!strcmp(team_datum.weaponry_amount_variable[idx], Sexp_variables[var_index].variable_name)) { + if (entry.count_variable == var_name) { count++; } } diff --git a/code/missionui/missionshipchoice.cpp b/code/missionui/missionshipchoice.cpp index a3b90c3dcd4..8109b455d77 100644 --- a/code/missionui/missionshipchoice.cpp +++ b/code/missionui/missionshipchoice.cpp @@ -1897,7 +1897,7 @@ bool check_for_gaps_in_weapon_slots() // select screens is pressed. The ship selected is created, and the interface music is stopped. commit_pressed_status commit_pressed(bool API_Access) { - int j, player_ship_info_index; + int player_ship_info_index; if ( Wss_num_wings > 0 ) { if(!(Game_mode & GM_MULTIPLAYER)){ @@ -1932,20 +1932,17 @@ commit_pressed_status commit_pressed(bool API_Access) int num_required_weapons = 0; int num_satisfied_weapons = 0; SCP_string weapon_list; - for (j=0; j 1) - weapon_list.append(1, EOLN); - weapon_list.append(Weapon_info[j].get_display_name()); + // add it to the message list + num_required_weapons++; + if (num_required_weapons > 1) + weapon_list.append(1, EOLN); + weapon_list.append(Weapon_info[weapon_class].get_display_name()); - // see if it's carried by any ship - if (is_weapon_carried(j)) - num_satisfied_weapons++; - } + // see if it's carried by any ship + if (is_weapon_carried(weapon_class)) + num_satisfied_weapons++; } if (num_satisfied_weapons < num_required_weapons) { @@ -2793,19 +2790,21 @@ void ss_reset_selected_ship() // There may be ships that are in wings but not in Team_data[0]. Since we still want to show those // icons in the ship selection list, the code below checks for these cases. If a ship is found in -// a wing, and is not in Team_data[0], it is appended to the end of the ship_count[] and ship_list[] arrays -// that are in Team_data[0] +// a wing, and is not in Team_data[0], it is appended to the end of the ship_choices list +// that is in Team_data[0] // // exit: number of distinct ship classes available to choose from int ss_fixup_team_data(team_data *tdata) { - int i, j, k, ship_in_parse_player, list_size; + int i, j; p_object *p_objp; - team_data *p_team_data; + team_data *p_team_data = tdata; - p_team_data = tdata; - ship_in_parse_player = 0; - list_size = p_team_data->num_ship_choices; + auto append_class = [p_team_data](int ship_class) { + auto &entry = p_team_data->ship_choices.emplace_back(); + entry.class_index = ship_class; + entry.count = 0; + }; for ( i = 0; i < MAX_STARTING_WINGS; i++ ) { wing *wp; @@ -2813,21 +2812,9 @@ int ss_fixup_team_data(team_data *tdata) continue; wp = &Wings[Starting_wings[i]]; for ( j = 0; j < wp->current_count; j++ ) { - ship_in_parse_player = 0; - - for ( k = 0; k < p_team_data->num_ship_choices; k++ ) { - Assert( p_team_data->ship_count[k] >= 0 ); - if ( p_team_data->ship_list[k] == Ships[wp->ship_index[j]].ship_info_index ) { - ship_in_parse_player = 1; - break; - } - } // end for, go to next item in parse player - - if ( !ship_in_parse_player ) { - p_team_data->ship_count[list_size] = 0; - p_team_data->ship_list[list_size] = Ships[wp->ship_index[j]].ship_info_index; - p_team_data->num_ship_choices++; - list_size++; + if ( !std::any_of(p_team_data->ship_choices.begin(), p_team_data->ship_choices.end(), + [&](const auto &e) { return e.class_index == Ships[wp->ship_index[j]].ship_info_index; }) ) { + append_class(Ships[wp->ship_index[j]].ship_info_index); } } // end for, go get next ship in wing @@ -2835,61 +2822,34 @@ int ss_fixup_team_data(team_data *tdata) for ( p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp) ) { if ( p_objp->wingnum == WING_INDEX(wp) ) { - ship_in_parse_player = 0; - - for ( k = 0; k < p_team_data->num_ship_choices; k++ ) { - Assert( p_team_data->ship_count[k] >= 0 ); - if ( p_team_data->ship_list[k] == p_objp->ship_class ) { - ship_in_parse_player = 1; - break; - } - } // end for, go to next item in parse player - - if ( !ship_in_parse_player ) { - p_team_data->ship_count[list_size] = 0; - p_team_data->ship_list[list_size] = p_objp->ship_class; - p_team_data->num_ship_choices++; - list_size++; + if ( !std::any_of(p_team_data->ship_choices.begin(), p_team_data->ship_choices.end(), + [&](const auto &e) { return e.class_index == p_objp->ship_class; }) ) { + append_class(p_objp->ship_class); } } } } } // end for, go to next wing - if ( list_size == 0 ) { - // ensure that the default player ship is in the ship_list too - ship_in_parse_player = 0; - for ( k = 0; k < p_team_data->num_ship_choices; k++ ) { - Assert( p_team_data->ship_count[k] >= 0 ); - if ( p_team_data->ship_list[k] == p_team_data->default_ship ) { - ship_in_parse_player = 1; - break; - } - } - if ( !ship_in_parse_player ) { - p_team_data->ship_count[list_size] = 0; - p_team_data->ship_list[list_size] = p_team_data->default_ship; - p_team_data->num_ship_choices++; - list_size++; - } + if ( p_team_data->ship_choices.empty() && p_team_data->default_ship >= 0 ) { + // ensure that the default player ship is in the choices too + append_class(p_team_data->default_ship); } - return list_size; + return sz2i(p_team_data->ship_choices.size()); } // set numbers of ships in pool to default values void ss_init_pool(team_data *pteam) { - int i; - Assert( Ss_pool != NULL ); Ss_pool->clear(); // set number of available ships based on counts in team_data // (auto-insert starts new entries at 0, so classes listed with a count of 0 stay in the pool as exhausted) - for ( i = 0; i < pteam->num_ship_choices; i++ ) { - (*Ss_pool)[pteam->ship_list[i]] += pteam->ship_count[i]; + for ( auto &entry : pteam->ship_choices ) { + (*Ss_pool)[entry.class_index] += entry.count; } } diff --git a/code/missionui/missionweaponchoice.cpp b/code/missionui/missionweaponchoice.cpp index 4c0a5ff760d..9a910fdad82 100644 --- a/code/missionui/missionweaponchoice.cpp +++ b/code/missionui/missionweaponchoice.cpp @@ -1367,14 +1367,12 @@ void maybe_select_new_ship_weapon(int index) */ void wl_init_pool(team_data *td) { - int i; - Assert( Wl_pool != NULL ); Wl_pool->clear(); - for ( i = 0; i < td->num_weapon_choices; i++ ) { - (*Wl_pool)[td->weaponry_pool[i]] += td->weaponry_count[i]; // read from mission + for ( auto &entry : td->weapon_choices ) { + (*Wl_pool)[entry.class_index] += entry.count; // read from mission } } diff --git a/code/scripting/api/objs/weaponclass.cpp b/code/scripting/api/objs/weaponclass.cpp index d4a159c5e8c..6fb8dcdf3f1 100644 --- a/code/scripting/api/objs/weaponclass.cpp +++ b/code/scripting/api/objs/weaponclass.cpp @@ -1371,7 +1371,7 @@ ADE_FUNC(isWeaponRequired, //This could be requested before Common_team has been initialized, so let's check. if (Common_select_inited) { - return ade_set_args(L, "b", Team_data[Common_team].weapon_required[idx]); + return ade_set_args(L, "b", Team_data[Common_team].required_weapons.contains(idx)); } else { return ADE_RETURN_NIL; } diff --git a/code/weapon/weapons.cpp b/code/weapon/weapons.cpp index 221fc65069f..be62e849e7f 100644 --- a/code/weapon/weapons.cpp +++ b/code/weapon/weapons.cpp @@ -8857,8 +8857,8 @@ void weapons_page_in() // for weapons in weaponry pool for (i = 0; i < Num_teams; i++) { - for (j = 0; j < Team_data[i].num_weapon_choices; j++) { - used_weapons[Team_data[i].weaponry_pool[j]] += Team_data[i].weaponry_count[j]; + for (auto &entry : Team_data[i].weapon_choices) { + used_weapons[entry.class_index] += entry.count; } } diff --git a/fred2/dumpstats.cpp b/fred2/dumpstats.cpp index 53545264944..1b1f5d50065 100644 --- a/fred2/dumpstats.cpp +++ b/fred2/dumpstats.cpp @@ -505,7 +505,7 @@ void DumpStats::get_objectives_and_goals(CString &buffer) void DumpStats::get_ship_weapon_selection(CString &buffer) { CString temp; - int i,j; + int i; buffer += "\r\nSHIP WEAPON/SELECTION\r\n"; buffer += "Reported numbers are in addition to assigned ships and their default weapons\r\n"; @@ -515,11 +515,12 @@ void DumpStats::get_ship_weapon_selection(CString &buffer) buffer += temp; // ships - for (j=0; j 0) - temp.Format("\tWeapon name: %s, count %d\r\n", Weapon_info[Team_data[i].weaponry_pool[j]].name, Team_data[i].weaponry_count[j]); + for (const auto &entry : Team_data[i].weapon_choices) { + const char *entry_name = entry.class_variable.empty() ? Weapon_info[entry.class_index].name : entry.class_variable.c_str(); + temp.Format("\tWeapon name: %s, count %d\r\n", entry_name, entry.count); buffer += temp; - } } diff --git a/fred2/freddoc.cpp b/fred2/freddoc.cpp index 15aafbfa631..a5282f149ac 100644 --- a/fred2/freddoc.cpp +++ b/fred2/freddoc.cpp @@ -228,7 +228,7 @@ bool CFREDDoc::load_mission(const char *pathname, int flags) { char name[512], *old_name; int i, j, ob; - int used_pool[MAX_WEAPON_TYPES]; + SCP_map used_pool; object *objp; Parse_viewer_pos = view_pos; @@ -349,29 +349,30 @@ bool CFREDDoc::load_mission(const char *pathname, int flags) { for (i = 0; i < Num_teams; i++) { generate_weaponry_usage_list(i, used_pool); - for (j = 0; j < Team_data[i].num_weapon_choices; j++) { + for (auto &entry : Team_data[i].weapon_choices) { // The amount used in wings is always set by a static loadout entry so skip any that were set by Sexp variables - if ((!strlen(Team_data[i].weaponry_pool_variable[j])) && (!strlen(Team_data[i].weaponry_amount_variable[j]))) { - // convert weaponry_pool to be extras available beyond the current ships weapons - Team_data[i].weaponry_count[j] -= used_pool[Team_data[i].weaponry_pool[j]]; - if (Team_data[i].weaponry_count[j] < 0) { - Team_data[i].weaponry_count[j] = 0; + if (entry.class_variable.empty() && entry.count_variable.empty()) { + // convert weaponry pool to be extras available beyond the current ships weapons + entry.count -= used_pool.value_or(entry.class_index, 0); + if (entry.count < 0) { + entry.count = 0; } // zero the used pool entry - used_pool[Team_data[i].weaponry_pool[j]] = 0; + used_pool.erase(entry.class_index); } } // double check the used pool is empty - for (j = 0; j < weapon_info_size(); j++) { - if (!Team_data[i].do_not_validate && used_pool[j] != 0) { - Warning(LOCATION, "%s is used in wings of team %d but was not in the loadout. Fixing now", Weapon_info[j].name, i + 1); - - // add the weapon as a new entry - Team_data[i].weaponry_pool[Team_data[i].num_weapon_choices] = j; - Team_data[i].weaponry_count[Team_data[i].num_weapon_choices] = used_pool[j]; - strcpy_s(Team_data[i].weaponry_amount_variable[Team_data[i].num_weapon_choices], ""); - strcpy_s(Team_data[i].weaponry_pool_variable[Team_data[i].num_weapon_choices++], ""); + if (!Team_data[i].do_not_validate) { + for (const auto &[weapon_class, count] : used_pool) { + if (count != 0) { + Warning(LOCATION, "%s is used in wings of team %d but was not in the loadout. Fixing now", Weapon_info[weapon_class].name, i + 1); + + // add the weapon as a new entry + auto &entry = Team_data[i].weapon_choices.emplace_back(); + entry.class_index = weapon_class; + entry.count = count; + } } } } diff --git a/fred2/management.cpp b/fred2/management.cpp index ff5bd36a424..cdb0de9fad4 100644 --- a/fred2/management.cpp +++ b/fred2/management.cpp @@ -888,7 +888,7 @@ void reset_mission() void clear_mission(bool fast_reload) { char *str; - int i, j, count; + int i, j; CTime t; // clean up everything we need to before we reset back to defaults. @@ -954,34 +954,24 @@ void clear_mission(bool fast_reload) // set up the default ship types for all teams. For now, this is the same class // of ships for all teams for (i=0; i &usage, int wing) { - int i; + int i; if (wing < 0) { return; } - + i = Wings[wing].wave_count; while (i--) { - arr[Ships[Wings[wing].ship_index[i]].ship_info_index]++; + usage[Ships[Wings[wing].ship_index[i]].ship_info_index]++; } } -void generate_weaponry_usage_list(int *arr, int wing) +void generate_weaponry_usage_list(SCP_map &usage, int wing) { int i, j; ship_weapon *swp; @@ -2459,36 +2449,35 @@ void generate_weaponry_usage_list(int *arr, int wing) j = swp->num_primary_banks; while (j--) { if (swp->primary_bank_weapons[j] >= 0 && swp->primary_bank_weapons[j] < weapon_info_size()) { - arr[swp->primary_bank_weapons[j]]++; + usage[swp->primary_bank_weapons[j]]++; } } j = swp->num_secondary_banks; while (j--) { if (swp->secondary_bank_weapons[j] >=0 && swp->secondary_bank_weapons[j] < weapon_info_size()) { - arr[swp->secondary_bank_weapons[j]] += (int) floor((swp->secondary_bank_ammo[j] * swp->secondary_bank_capacity[j] / 100.0f / Weapon_info[swp->secondary_bank_weapons[j]].cargo_size) + 0.5f); + usage[swp->secondary_bank_weapons[j]] += (int) floor((swp->secondary_bank_ammo[j] * swp->secondary_bank_capacity[j] / 100.0f / Weapon_info[swp->secondary_bank_weapons[j]].cargo_size) + 0.5f); } } } } -void generate_weaponry_usage_list(int team, int *arr) +void generate_weaponry_usage_list(int team, SCP_map &usage) { int i; - for (i=0; i= 0 && team < MAX_TVT_TEAMS); for (i=0; i& usage); +void generate_weaponry_usage_list(SCP_map& usage, int wing); +void generate_ship_usage_list(SCP_map& usage, int wing); CJumpNode* jumpnode_get_by_name(const CString& name); diff --git a/fred2/playerstarteditor.cpp b/fred2/playerstarteditor.cpp index 9ca5d9bb502..bbf660a4712 100644 --- a/fred2/playerstarteditor.cpp +++ b/fred2/playerstarteditor.cpp @@ -87,79 +87,68 @@ END_MESSAGE_MAP() // player_start_editor message handlers -BOOL player_start_editor::OnInitDialog() +BOOL player_start_editor::OnInitDialog() { int i, j; - int idx; // initialize ship pool data - memset(static_ship_pool, -1, sizeof(int) * MAX_TVT_TEAMS * MAX_SHIP_CLASSES); - memset(dynamic_ship_pool, -1, sizeof(int) * MAX_TVT_TEAMS * MAX_SEXP_VARIABLES); - memset(static_ship_variable_pool, -1, sizeof(int) * MAX_TVT_TEAMS * MAX_SHIP_CLASSES); - memset(dynamic_ship_variable_pool, -1, sizeof(int) * MAX_TVT_TEAMS * MAX_SEXP_VARIABLES); for(i=0; i 0) || - (dynamic_ship_variable_pool[selected_team][i] != -1)) + if((dynamic_ship_pool[selected_team].value_or(i, -1) > 0) || + (dynamic_ship_variable_pool[selected_team].contains(i))) { m_ship_variable_list.SetCheck(current_entry, TRUE); } - else + else { m_ship_variable_list.SetCheck(current_entry, FALSE); } // and now for weapons - if((dynamic_weapon_pool[selected_team][i] > 0) || - (dynamic_weapon_variable_pool[selected_team][i] != -1)) + if((dynamic_weapon_pool[selected_team].value_or(i, -1) > 0) || + (dynamic_weapon_variable_pool[selected_team].contains(i))) { m_weapon_variable_list.SetCheck(current_entry, TRUE); } - else + else { m_weapon_variable_list.SetCheck(current_entry, FALSE); } @@ -309,7 +294,7 @@ void player_start_editor::reset_controls() m_ship_list.AddString(it->name); // if the ship currently has pool entries or was set by a variable, check it - if ((static_ship_pool[selected_team][i] > 0) || (static_ship_variable_pool[selected_team][i] != -1)) { + if ((static_ship_pool[selected_team].value_or(i, -1) > 0) || (static_ship_variable_pool[selected_team].contains(i))) { m_ship_list.SetCheck(ct, TRUE); } else { @@ -327,22 +312,22 @@ void player_start_editor::reset_controls() for (i=0; i 0) || (static_weapon_variable_pool[selected_team][i] != -1)){ + if((static_weapon_pool[selected_team].value_or(i, -1) > 0) || (static_weapon_variable_pool[selected_team].contains(i))){ m_weapon_list.SetCheck(ct, TRUE); } else { m_weapon_list.SetCheck(ct, FALSE); } ct++; - } else if (static_weapon_pool[selected_team][i] > 0 || (static_weapon_variable_pool[selected_team][i] != -1)) { + } else if (static_weapon_pool[selected_team].value_or(i, -1) > 0 || (static_weapon_variable_pool[selected_team].contains(i))) { // not sure if this should be a verbal warning or not, so I'm adding both and making it verbal for now Warning(LOCATION, "Weapon '%s' in weapon pool isn't allowed on player loadout! Resetting count to 0...\n", Weapon_info[i].name); - static_weapon_pool[selected_team][i] = 0; - static_weapon_variable_pool[selected_team][i] = -1; + static_weapon_pool[selected_team].erase(i); + static_weapon_variable_pool[selected_team].erase(i); } - } + } m_validation_toggle = validation_toggle[selected_team]; @@ -444,36 +429,37 @@ void player_start_editor::OnSelchangeShipList() // if we have a valid ship type if(si_index >= 0){ // if this item is checked - if(m_ship_list.GetCheck(selected)) { - if (static_ship_variable_pool[selected_team][si_index] == -1) { - if (static_ship_pool[selected_team][si_index] <= 0){ + if(m_ship_list.GetCheck(selected)) { + if (!static_ship_variable_pool[selected_team].contains(si_index)) { + if (static_ship_pool[selected_team].value_or(si_index, -1) <= 0){ static_ship_pool[selected_team][si_index] = 5; } m_ship_pool = static_ship_pool[selected_team][si_index]; // Set the ship variable ComboBox to reflect that we are not using variables for this ship - m_ship_quantity_variable.SetCurSel(0); + m_ship_quantity_variable.SetCurSel(0); } // If the number of ships was set by a variable else { - Assert (Sexp_variables[static_ship_variable_pool[selected_team][si_index]].type & SEXP_VARIABLE_NUMBER); + int count_var_index = static_ship_variable_pool[selected_team][si_index]; + Assert (Sexp_variables[count_var_index].type & SEXP_VARIABLE_NUMBER); - m_ship_pool = atoi(Sexp_variables[static_ship_variable_pool[selected_team][si_index]].text); - int selected_variable = sexp_variable_typed_count(static_ship_variable_pool[selected_team][si_index], SEXP_VARIABLE_NUMBER); + m_ship_pool = atoi(Sexp_variables[count_var_index].text); + int selected_variable = sexp_variable_typed_count(count_var_index, SEXP_VARIABLE_NUMBER); m_ship_quantity_variable.SetCurSel(selected_variable + 1); } - } + } // otherwise zero the count else { - static_ship_pool[selected_team][si_index] = 0; - static_ship_variable_pool[selected_team][si_index] = -1; + static_ship_pool[selected_team].erase(si_index); + static_ship_variable_pool[selected_team].erase(si_index); m_ship_pool = 0; m_ship_quantity_variable.SetCurSel(0); } - + // set the number used in wings - sprintf(ship_usage_buff, "%d", ship_usage[selected_team][si_index]); - m_ships_used_in_wings.SetWindowText(ship_usage_buff); - + sprintf(ship_usage_buff, "%d", ship_usage[selected_team].value_or(si_index, 0)); + m_ships_used_in_wings.SetWindowText(ship_usage_buff); + } else { Int3(); } @@ -502,28 +488,29 @@ void player_start_editor::OnSelchangeShipVariablesList() if (sexp_index > -1) { Assert(selection == sexp_variable_typed_count(sexp_index, SEXP_VARIABLE_STRING)); - // Is this item checked? + // Is this item checked? if (m_ship_variable_list.GetCheck(selection)) { - if (dynamic_ship_variable_pool[selected_team][sexp_index] == -1) { - if (dynamic_ship_pool[selected_team][sexp_index] <= 0) { + if (!dynamic_ship_variable_pool[selected_team].contains(sexp_index)) { + if (dynamic_ship_pool[selected_team].value_or(sexp_index, -1) <= 0) { dynamic_ship_pool[selected_team][sexp_index] = 5; } m_ship_pool = dynamic_ship_pool[selected_team][sexp_index]; - m_ship_quantity_variable.SetCurSel(0); + m_ship_quantity_variable.SetCurSel(0); } else { - Assert (Sexp_variables[dynamic_ship_variable_pool[selected_team][sexp_index]].type & SEXP_VARIABLE_NUMBER); - m_ship_pool = atoi(Sexp_variables[dynamic_ship_variable_pool[selected_team][sexp_index]].text); - int selected_variable = sexp_variable_typed_count(dynamic_ship_variable_pool[selected_team][sexp_index], SEXP_VARIABLE_NUMBER); + int count_var_index = dynamic_ship_variable_pool[selected_team][sexp_index]; + Assert (Sexp_variables[count_var_index].type & SEXP_VARIABLE_NUMBER); + m_ship_pool = atoi(Sexp_variables[count_var_index].text); + int selected_variable = sexp_variable_typed_count(count_var_index, SEXP_VARIABLE_NUMBER); m_ship_quantity_variable.SetCurSel(selected_variable + 1); } } // We've unselected the tickbox, reset everything else { - dynamic_ship_pool[selected_team][sexp_index] = -1; - dynamic_ship_variable_pool[selected_team][sexp_index] = -1; + dynamic_ship_pool[selected_team].erase(sexp_index); + dynamic_ship_variable_pool[selected_team].erase(sexp_index); m_ship_pool = 0; - m_ship_quantity_variable.SetCurSel(0); + m_ship_quantity_variable.SetCurSel(0); } // It might be nice to have FRED work out if any ships of the class represented by the variable are in the wings @@ -556,17 +543,23 @@ void player_start_editor::OnSelchangeShipVariablesCombo() Assert ((sexp_index > -1) || (!strcmp("Don't Use Variables", variable_name))); // See if the ship_list was selected - int ship_index = GetSelectedShipListIndex(); + int ship_index = GetSelectedShipListIndex(); if (ship_index >= 0) { - static_ship_variable_pool[selected_team][ship_index] = sexp_index; - update_static_pool = true; + if (sexp_index >= 0) + static_ship_variable_pool[selected_team][ship_index] = sexp_index; + else + static_ship_variable_pool[selected_team].erase(ship_index); + update_static_pool = true; } - + // Maybe it's the ship_variables_list that is actually selected int ship_variable_index = GetSelectedShipVariableListIndex(); if (ship_variable_index >= 0 ) { - dynamic_ship_variable_pool[selected_team][ship_variable_index] = sexp_index; - update_dynamic_pool = true; + if (sexp_index >= 0) + dynamic_ship_variable_pool[selected_team][ship_variable_index] = sexp_index; + else + dynamic_ship_variable_pool[selected_team].erase(ship_variable_index); + update_dynamic_pool = true; } // Somethings gone wrong if they're both marked as true @@ -618,36 +611,37 @@ void player_start_editor::OnSelchangeWeaponList() // if we have a valid ship type if(wi_index >= 0){ // if this item is checked - if(m_weapon_list.GetCheck(selected)) { - if (static_weapon_variable_pool[selected_team][wi_index] == -1) { - if (static_weapon_pool[selected_team][wi_index] <= 0){ + if(m_weapon_list.GetCheck(selected)) { + if (!static_weapon_variable_pool[selected_team].contains(wi_index)) { + if (static_weapon_pool[selected_team].value_or(wi_index, -1) <= 0){ static_weapon_pool[selected_team][wi_index] = 100; } m_weapon_pool = static_weapon_pool[selected_team][wi_index]; // Set the combo reflect that we are not using variables for this weapon - m_weapon_quantity_variable.SetCurSel(0); + m_weapon_quantity_variable.SetCurSel(0); } // If the number of ships was set by a variable else { - Assert (Sexp_variables[static_weapon_variable_pool[selected_team][wi_index]].type & SEXP_VARIABLE_NUMBER); + int count_var_index = static_weapon_variable_pool[selected_team][wi_index]; + Assert (Sexp_variables[count_var_index].type & SEXP_VARIABLE_NUMBER); - m_weapon_pool = atoi(Sexp_variables[static_weapon_variable_pool[selected_team][wi_index]].text); - int selected_variable = sexp_variable_typed_count(static_weapon_variable_pool[selected_team][wi_index], SEXP_VARIABLE_NUMBER); + m_weapon_pool = atoi(Sexp_variables[count_var_index].text); + int selected_variable = sexp_variable_typed_count(count_var_index, SEXP_VARIABLE_NUMBER); m_weapon_quantity_variable.SetCurSel(selected_variable + 1); } - } + } // otherwise zero the count else { - static_weapon_pool[selected_team][wi_index] = 0; - static_weapon_variable_pool[selected_team][wi_index] = -1; + static_weapon_pool[selected_team].erase(wi_index); + static_weapon_variable_pool[selected_team].erase(wi_index); m_weapon_pool = 0; - m_weapon_quantity_variable.SetCurSel(0); + m_weapon_quantity_variable.SetCurSel(0); } - + // set the number used in wings - sprintf(weapon_usage_buff, "%d", weapon_usage[selected_team][wi_index]); - m_weapons_used_in_wings.SetWindowText(weapon_usage_buff); - + sprintf(weapon_usage_buff, "%d", weapon_usage[selected_team].value_or(wi_index, 0)); + m_weapons_used_in_wings.SetWindowText(weapon_usage_buff); + } else { Int3(); } @@ -675,28 +669,29 @@ void player_start_editor::OnSelchangeWeaponVariablesList() if (sexp_index > -1) { Assert(selection == sexp_variable_typed_count(sexp_index, SEXP_VARIABLE_STRING)); - // Is this item checked? + // Is this item checked? if (m_weapon_variable_list.GetCheck(selection)) { - if (dynamic_weapon_variable_pool[selected_team][sexp_index] == -1) { - if (dynamic_weapon_pool[selected_team][sexp_index] <= 0) { + if (!dynamic_weapon_variable_pool[selected_team].contains(sexp_index)) { + if (dynamic_weapon_pool[selected_team].value_or(sexp_index, -1) <= 0) { dynamic_weapon_pool[selected_team][sexp_index] = 5; } m_weapon_pool = dynamic_weapon_pool[selected_team][sexp_index]; - m_weapon_quantity_variable.SetCurSel(0); + m_weapon_quantity_variable.SetCurSel(0); } else { - Assert (Sexp_variables[dynamic_weapon_variable_pool[selected_team][sexp_index]].type & SEXP_VARIABLE_NUMBER); - m_weapon_pool = atoi(Sexp_variables[dynamic_weapon_variable_pool[selected_team][sexp_index]].text); - int selected_variable = sexp_variable_typed_count(dynamic_weapon_variable_pool[selected_team][sexp_index], SEXP_VARIABLE_NUMBER); + int count_var_index = dynamic_weapon_variable_pool[selected_team][sexp_index]; + Assert (Sexp_variables[count_var_index].type & SEXP_VARIABLE_NUMBER); + m_weapon_pool = atoi(Sexp_variables[count_var_index].text); + int selected_variable = sexp_variable_typed_count(count_var_index, SEXP_VARIABLE_NUMBER); m_weapon_quantity_variable.SetCurSel(selected_variable + 1); } } // We've unselected the tickbox, reset everything else { - dynamic_weapon_pool[selected_team][sexp_index] = -1; - dynamic_weapon_variable_pool[selected_team][sexp_index] = -1; + dynamic_weapon_pool[selected_team].erase(sexp_index); + dynamic_weapon_variable_pool[selected_team].erase(sexp_index); m_weapon_pool = 0; - m_weapon_quantity_variable.SetCurSel(0); + m_weapon_quantity_variable.SetCurSel(0); } // It might be nice to have FRED work out if any weapons of this type are in the wings @@ -728,17 +723,23 @@ void player_start_editor::OnSelchangeWeaponVariablesCombo() Assert ((sexp_index > -1) || (!strcmp("Don't Use Variables", variable_name))); // See if the weapon_list was selected - int weapon_index = GetSelectedWeaponListIndex(); + int weapon_index = GetSelectedWeaponListIndex(); if (weapon_index >= 0) { - static_weapon_variable_pool[selected_team][weapon_index] = sexp_index; - update_static_pool = true; + if (sexp_index >= 0) + static_weapon_variable_pool[selected_team][weapon_index] = sexp_index; + else + static_weapon_variable_pool[selected_team].erase(weapon_index); + update_static_pool = true; } - + // Maybe it's the weapon_variables_list that is actually selected int weapon_variable_index = GetSelectedWeaponVariableListIndex(); if (weapon_variable_index >= 0 ) { - dynamic_weapon_variable_pool[selected_team][weapon_variable_index] = sexp_index; - update_dynamic_pool = true; + if (sexp_index >= 0) + dynamic_weapon_variable_pool[selected_team][weapon_variable_index] = sexp_index; + else + dynamic_weapon_variable_pool[selected_team].erase(weapon_variable_index); + update_dynamic_pool = true; } // Somethings gone wrong if they're both marked as true @@ -771,12 +772,12 @@ void player_start_editor::OnRequiredWeapons() // create a list of options with just the weapons that are in the static pool SCP_vector weapon_indexes; SCP_vector> options; - for (int i = 0; i < MAX_WEAPON_TYPES; i++) + for (const auto &[weapon_class, count] : static_weapon_pool[selected_team]) { - if (static_weapon_pool[selected_team][i] > 0) + if (count > 0) { - weapon_indexes.push_back(i); - options.emplace_back(Weapon_info[i].name, weapon_is_required[selected_team][i]); + weapon_indexes.push_back(weapon_class); + options.emplace_back(Weapon_info[weapon_class].name, weapon_is_required[selected_team].contains(weapon_class)); } } @@ -787,8 +788,12 @@ void player_start_editor::OnRequiredWeapons() dlg.DoModal(); // reassign required weapons - for (int i = 0; i < (int)options.size(); i++) - weapon_is_required[selected_team][weapon_indexes[i]] = dlg.IsChecked(i); + for (int i = 0; i < sz2i(options.size()); i++) { + if (dlg.IsChecked(i)) + weapon_is_required[selected_team].insert(weapon_indexes[i]); + else + weapon_is_required[selected_team].erase(weapon_indexes[i]); + } } // cancel @@ -802,7 +807,6 @@ void player_start_editor::OnCancel() void player_start_editor::OnOK() { int i, idx; - int num_choices; UpdateData(); @@ -811,20 +815,21 @@ void player_start_editor::OnOK() // store player entry time delay Entry_delay_time = i2f(m_delay); - // store ship pools + // store ship pools for(i=0; isecond].type & SEXP_VARIABLE_NUMBER); - strcpy_s(Team_data[i].ship_count_variables[num_choices], Sexp_variables[dynamic_ship_variable_pool[i][idx]].variable_name); - Team_data[i].ship_count[num_choices] = atoi(Sexp_variables[dynamic_ship_variable_pool[i][idx]].text); + entry.count_variable = Sexp_variables[count_var->second].variable_name; + entry.count = atoi(Sexp_variables[count_var->second].text); } - - num_choices++; } } @@ -881,44 +861,41 @@ void player_start_editor::OnOK() for (idx = 0; idx < ship_info_size(); idx++) { // if we have ships here - if(static_ship_pool[i][idx] > 0 || static_ship_variable_pool[i][idx] > -1) { - Team_data[i].ship_list[num_choices] = idx; - strcpy_s(Team_data[i].ship_list_variables[num_choices], ""); + if(static_ship_pool[i].value_or(idx, -1) > 0 || static_ship_variable_pool[i].contains(idx)) { + auto &entry = Team_data[i].ship_choices.emplace_back(); + entry.class_index = idx; // Now set the number of this class available - if (static_ship_variable_pool[i][idx] == -1) { - Team_data[i].ship_count[num_choices] = static_ship_pool[i][idx]; - strcpy_s(Team_data[i].ship_count_variables[num_choices], ""); + auto count_var = static_ship_variable_pool[i].find(idx); + if (count_var == static_ship_variable_pool[i].end()) { + entry.count = static_ship_pool[i][idx]; } else { - Assert (Sexp_variables[static_ship_variable_pool[i][idx]].type & SEXP_VARIABLE_NUMBER); - - strcpy_s(Team_data[i].ship_count_variables[num_choices], Sexp_variables[static_ship_variable_pool[i][idx]].variable_name); - Team_data[i].ship_count[num_choices] = atoi(Sexp_variables[static_ship_variable_pool[i][idx]].text); - } + Assert (Sexp_variables[count_var->second].type & SEXP_VARIABLE_NUMBER); - num_choices++; + entry.count_variable = Sexp_variables[count_var->second].variable_name; + entry.count = atoi(Sexp_variables[count_var->second].text); + } } } - Team_data[i].num_ship_choices = num_choices; } // store weapon pools - for(i=0; isecond].type & SEXP_VARIABLE_NUMBER); - strcpy_s(Team_data[i].weaponry_amount_variable[num_choices], Sexp_variables[dynamic_weapon_variable_pool[i][idx]].variable_name); - Team_data[i].weaponry_count[num_choices] = atoi(Sexp_variables[dynamic_weapon_variable_pool[i][idx]].text); + entry.count_variable = Sexp_variables[count_var->second].variable_name; + entry.count = atoi(Sexp_variables[count_var->second].text); } - - num_choices++; } } @@ -954,29 +930,26 @@ void player_start_editor::OnOK() for(idx=0; idx 0 || static_weapon_variable_pool[i][idx] > -1) + if(static_weapon_pool[i].value_or(idx, -1) > 0 || static_weapon_variable_pool[i].contains(idx)) { - Team_data[i].weaponry_pool[num_choices] = idx; - strcpy_s(Team_data[i].weaponry_pool_variable[num_choices], ""); + auto &entry = Team_data[i].weapon_choices.emplace_back(); + entry.class_index = idx; // Now set the number of this class available - if (static_weapon_variable_pool[i][idx] == -1) + auto count_var = static_weapon_variable_pool[i].find(idx); + if (count_var == static_weapon_variable_pool[i].end()) { - Team_data[i].weaponry_count[num_choices] = static_weapon_pool[i][idx]; - strcpy_s(Team_data[i].weaponry_amount_variable[num_choices], ""); + entry.count = static_weapon_pool[i][idx]; } - else + else { - Assert (Sexp_variables[static_weapon_variable_pool[i][idx]].type & SEXP_VARIABLE_NUMBER); - - strcpy_s(Team_data[i].weaponry_amount_variable[num_choices], Sexp_variables[static_weapon_variable_pool[i][idx]].variable_name); - Team_data[i].weaponry_count[num_choices] = atoi(Sexp_variables[static_weapon_variable_pool[i][idx]].text); - } + Assert (Sexp_variables[count_var->second].type & SEXP_VARIABLE_NUMBER); - num_choices++; + entry.count_variable = Sexp_variables[count_var->second].variable_name; + entry.count = atoi(Sexp_variables[count_var->second].text); + } } } - Team_data[i].num_weapon_choices = num_choices; } // store the loadout padding toggle @@ -986,18 +959,16 @@ void player_start_editor::OnOK() // store required weapons for (i = 0; i < MAX_TVT_TEAMS; i++) { - for (idx = 0; idx < weapon_info_size(); idx++) { - Team_data[i].weapon_required[idx] = false; - - if (weapon_is_required[i][idx]) { - if (static_weapon_pool[i][idx] > 0) { - Team_data[i].weapon_required[idx] = true; - } else { - SCP_string buffer = "Cannot require a weapon ("; - buffer += Weapon_info[idx].name; - buffer += ") that is not in the static weaponry pool! This weapon will be skipped."; - MessageBox(buffer.c_str()); - } + Team_data[i].required_weapons.clear(); + + for (int weapon_class : weapon_is_required[i]) { + if (static_weapon_pool[i].value_or(weapon_class, -1) > 0) { + Team_data[i].required_weapons.insert(weapon_class); + } else { + SCP_string buffer = "Cannot require a weapon ("; + buffer += Weapon_info[weapon_class].name; + buffer += ") that is not in the static weaponry pool! This weapon will be skipped."; + MessageBox(buffer.c_str()); } } } diff --git a/fred2/playerstarteditor.h b/fred2/playerstarteditor.h index 80682227d9d..e77eb0980c3 100644 --- a/fred2/playerstarteditor.h +++ b/fred2/playerstarteditor.h @@ -81,23 +81,23 @@ class player_start_editor : public CDialog bool autobalance; - // ship pool info - int static_ship_pool[MAX_TVT_TEAMS][MAX_SHIP_CLASSES]; // Holds the number of ships of a class that was set by the team loadout - int dynamic_ship_pool[MAX_TVT_TEAMS][MAX_SEXP_VARIABLES]; - int static_ship_variable_pool[MAX_TVT_TEAMS][MAX_SHIP_CLASSES]; - int dynamic_ship_variable_pool[MAX_TVT_TEAMS][MAX_SEXP_VARIABLES]; + // ship pool info; an absent map entry means "not in the loadout" / "no variable" (the old -1 sentinel) + SCP_map static_ship_pool[MAX_TVT_TEAMS]; // ship class -> count set by the team loadout + SCP_map dynamic_ship_pool[MAX_TVT_TEAMS]; // string sexp variable index -> count + SCP_map static_ship_variable_pool[MAX_TVT_TEAMS]; // ship class -> number sexp variable index supplying its count + SCP_map dynamic_ship_variable_pool[MAX_TVT_TEAMS]; // string sexp variable index -> number sexp variable index supplying its count // weapon pool info - int static_weapon_pool[MAX_TVT_TEAMS][MAX_WEAPON_TYPES]; - int dynamic_weapon_pool[MAX_TVT_TEAMS][MAX_SEXP_VARIABLES]; - int static_weapon_variable_pool[MAX_TVT_TEAMS][MAX_WEAPON_TYPES]; - int dynamic_weapon_variable_pool[MAX_TVT_TEAMS][MAX_SEXP_VARIABLES]; + SCP_map static_weapon_pool[MAX_TVT_TEAMS]; + SCP_map dynamic_weapon_pool[MAX_TVT_TEAMS]; + SCP_map static_weapon_variable_pool[MAX_TVT_TEAMS]; + SCP_map dynamic_weapon_variable_pool[MAX_TVT_TEAMS]; - // ship and weapon usage pools - int ship_usage[MAX_TVT_TEAMS][MAX_SHIP_CLASSES]; - int weapon_usage[MAX_TVT_TEAMS][MAX_WEAPON_TYPES]; + // ship and weapon usage pools (class -> number used in starting wings) + SCP_map ship_usage[MAX_TVT_TEAMS]; + SCP_map weapon_usage[MAX_TVT_TEAMS]; - bool weapon_is_required[MAX_TVT_TEAMS][MAX_WEAPON_TYPES]; + SCP_set weapon_is_required[MAX_TVT_TEAMS]; bool validation_toggle[MAX_TVT_TEAMS]; diff --git a/qtfred/src/mission/Editor.cpp b/qtfred/src/mission/Editor.cpp index b4f147c9d1e..931c4dcde2e 100644 --- a/qtfred/src/mission/Editor.cpp +++ b/qtfred/src/mission/Editor.cpp @@ -354,19 +354,20 @@ bool Editor::loadMission(const std::string& mission_name, int flags) { } for (i = 0; i < Num_teams; i++) { - generate_team_weaponry_usage_list(i, _weapon_usage[i]); - for (j = 0; j < Team_data[i].num_weapon_choices; j++) { + SCP_map used_pool; + generate_team_weaponry_usage_list(i, used_pool); + for (auto &entry : Team_data[i].weapon_choices) { // The amount used in wings is always set by a static loadout entry so skip any that were set by Sexp variables - if ((!strlen(Team_data[i].weaponry_pool_variable[j])) - && (!strlen(Team_data[i].weaponry_amount_variable[j]))) { - // convert weaponry_pool to be extras available beyond the current ships weapons - Team_data[i].weaponry_count[j] -= _weapon_usage[i][Team_data[i].weaponry_pool[j]]; - if (Team_data[i].weaponry_count[j] < 0) { - Team_data[i].weaponry_count[j] = 0; + if (entry.class_variable.empty() && entry.count_variable.empty()) { + // convert the weaponry pool to be extras available beyond the current ships weapons + entry.count -= used_pool.value_or(entry.class_index, 0); + if (entry.count < 0) { + entry.count = 0; } - // zero the used pool entry - _weapon_usage[i][Team_data[i].weaponry_pool[j]] = 0; + // remove the used pool entry, so that a duplicate loadout entry for the same class + // doesn't subtract the wing usage twice (matches FRED2's load_mission) + used_pool.erase(entry.class_index); } } // Weapons used in wings but missing from the loadout pool are flagged by the error checker. @@ -554,34 +555,24 @@ void Editor::clearMission(bool fast_reload) { // set up the default ship types for all teams. For now, this is the same class // of ships for all teams for (auto i = 0; i < MAX_TVT_TEAMS; i++) { - auto count = 0; + Team_data[i].ship_choices.clear(); for (auto j = 0; j < static_cast(Ship_info.size()); j++) { if (Ship_info[j].flags[Ship::Info_Flags::Default_player_ship]) { - Team_data[i].ship_list[count] = j; - strcpy_s(Team_data[i].ship_list_variables[count], ""); - Team_data[i].ship_count[count] = 5; - strcpy_s(Team_data[i].ship_count_variables[count], ""); - count++; + auto &entry = Team_data[i].ship_choices.emplace_back(); + entry.class_index = j; + entry.count = 5; } } - Team_data[i].num_ship_choices = count; - count = 0; + Team_data[i].weapon_choices.clear(); + Team_data[i].required_weapons.clear(); for (auto j = 0; j < static_cast(Weapon_info.size()); j++) { if (Weapon_info[j].wi_flags[Weapon::Info_Flags::Default_player_weapon]) { - if (Weapon_info[j].subtype == WP_LASER) { - Team_data[i].weaponry_count[count] = 16; - } else { - Team_data[i].weaponry_count[count] = 500; - } - Team_data[i].weaponry_pool[count] = j; - strcpy_s(Team_data[i].weaponry_pool_variable[count], ""); - strcpy_s(Team_data[i].weaponry_amount_variable[count], ""); - count++; + auto &entry = Team_data[i].weapon_choices.emplace_back(); + entry.class_index = j; + entry.count = (Weapon_info[j].subtype == WP_LASER) ? 16 : 500; } - Team_data[i].weapon_required[j] = false; } - Team_data[i].num_weapon_choices = count; } unmark_all(); @@ -1597,7 +1588,7 @@ void Editor::disband_wing(int wing_num) { missionChanged(); } -void Editor::generate_wing_weaponry_usage_list(int* arr, int wing) { +void Editor::generate_wing_weaponry_usage_list(SCP_map& usage, int wing) { int i, j; ship_weapon* swp; @@ -1611,41 +1602,39 @@ void Editor::generate_wing_weaponry_usage_list(int* arr, int wing) { j = swp->num_primary_banks; while (j--) { if (swp->primary_bank_weapons[j] >= 0 && swp->primary_bank_weapons[j] < static_cast(Weapon_info.size())) { - arr[swp->primary_bank_weapons[j]]++; + usage[swp->primary_bank_weapons[j]]++; } } j = swp->num_secondary_banks; while (j--) { if (swp->secondary_bank_weapons[j] >= 0 && swp->secondary_bank_weapons[j] < static_cast(Weapon_info.size())) { - arr[swp->secondary_bank_weapons[j]] += (int) floor( + usage[swp->secondary_bank_weapons[j]] += (int) floor( (swp->secondary_bank_ammo[j] * swp->secondary_bank_capacity[j] / 100.0f / Weapon_info[swp->secondary_bank_weapons[j]].cargo_size) + 0.5f); } } } } -void Editor::generate_team_weaponry_usage_list(int team, int* arr) { +void Editor::generate_team_weaponry_usage_list(int team, SCP_map& usage) { int i; - for (i = 0; i < MAX_WEAPON_TYPES; i++) { - arr[i] = 0; - } + usage.clear(); if (The_mission.game_type & MISSION_TYPE_MULTI_TEAMS) { Assert (team >= 0 && team < MAX_TVT_TEAMS); for (i = 0; i < MAX_TVT_WINGS_PER_TEAM; i++) { - generate_wing_weaponry_usage_list(arr, TVT_wings[(team * MAX_TVT_WINGS_PER_TEAM) + i]); + generate_wing_weaponry_usage_list(usage, TVT_wings[(team * MAX_TVT_WINGS_PER_TEAM) + i]); } } else { for (i = 0; i < MAX_STARTING_WINGS; i++) { - generate_wing_weaponry_usage_list(arr, Starting_wings[i]); + generate_wing_weaponry_usage_list(usage, Starting_wings[i]); } } } -void Editor::generate_ship_usage_list(int* arr, int wing) { - int i; +void Editor::generate_ship_usage_list(SCP_map& usage, int wing) { + int i; if (wing < 0) { return; @@ -1653,26 +1642,27 @@ void Editor::generate_ship_usage_list(int* arr, int wing) { i = Wings[wing].wave_count; while (i--) { - arr[Ships[Wings[wing].ship_index[i]].ship_info_index]++; + usage[Ships[Wings[wing].ship_index[i]].ship_info_index]++; } } void Editor::updateStartingWingLoadoutUseCounts() { - memset(_ship_usage, 0, sizeof(int) * MAX_TVT_TEAMS * MAX_SHIP_CLASSES); + _loadout_usage.clear(); + _loadout_usage.resize(MAX_TVT_TEAMS); - if (The_mission.game_type & MISSION_TYPE_MULTI_TEAMS) { + if (The_mission.game_type & MISSION_TYPE_MULTI_TEAMS) { for (int i = 0; i"; } -SCP_vector Editor::getStartingWingLoadoutUseCounts() { +const SCP_vector &Editor::getStartingWingLoadoutUseCounts() { // update before sending so that we have the most up to date info. updateStartingWingLoadoutUseCounts(); - SCP_vector out; - - for (int i = 0; i < MAX_TVT_TEAMS; i++) { - for (auto& entry : _ship_usage[i]) { - out.push_back(entry); - } - } - for (int i = 0; i < MAX_TVT_TEAMS; i++) { - for (auto& entry : _weapon_usage[i]) { - out.push_back(entry); - } - } - - return out; + return _loadout_usage; } diff --git a/qtfred/src/mission/Editor.h b/qtfred/src/mission/Editor.h index 6294913f2ba..b7b6908a757 100644 --- a/qtfred/src/mission/Editor.h +++ b/qtfred/src/mission/Editor.h @@ -251,12 +251,18 @@ class Editor : public QObject { static void pad_with_newline(SCP_string& str, size_t max_size); static SCP_string get_display_name_for_text_box(const SCP_string &orig_name); - SCP_vector getStartingWingLoadoutUseCounts(); + // per-team ship and weapon usage (class index -> count used in starting wings) + struct LoadoutUseCounts { + SCP_map ships; + SCP_map weapons; + }; + + const SCP_vector &getStartingWingLoadoutUseCounts(); static const ai_goal_list* getAi_goal_list(); static int getAigoal_list_size(); - void generate_team_weaponry_usage_list(int team, int* arr); + void generate_team_weaponry_usage_list(int team, SCP_map& usage); private slots: void performTimedAutosave(); @@ -282,9 +288,8 @@ class Editor : public QObject { bool already_deleting_wing = false; - // ship and weapon usage pools - int _ship_usage[MAX_TVT_TEAMS][MAX_SHIP_CLASSES]; - int _weapon_usage[MAX_TVT_TEAMS][MAX_WEAPON_TYPES]; + // ship and weapon usage pools, one entry per team + SCP_vector _loadout_usage; int common_object_delete(int obj); @@ -330,9 +335,9 @@ class Editor : public QObject { */ static int find_free_wing(); - void generate_wing_weaponry_usage_list(int* arr, int wing); + void generate_wing_weaponry_usage_list(SCP_map& usage, int wing); - void generate_ship_usage_list(int* arr, int wing); + void generate_ship_usage_list(SCP_map& usage, int wing); int get_visible_sub_system_count(ship* shipp); diff --git a/qtfred/src/mission/dialogs/TeamLoadoutDialogModel.cpp b/qtfred/src/mission/dialogs/TeamLoadoutDialogModel.cpp index ee6388a4599..fcda48d634f 100644 --- a/qtfred/src/mission/dialogs/TeamLoadoutDialogModel.cpp +++ b/qtfred/src/mission/dialogs/TeamLoadoutDialogModel.cpp @@ -35,14 +35,11 @@ bool TeamLoadoutDialogModel::apply() auto& out = Team_data[t]; // reset per team outputs - out.num_ship_choices = 0; - out.num_weapon_choices = 0; - for (auto& w : out.weapon_required) { - w = false; - } + out.ship_choices.clear(); + out.weapon_choices.clear(); + out.required_weapons.clear(); // Ships - int s = 0; // var ships first for (const auto& it : in.varShips) { @@ -55,41 +52,33 @@ bool TeamLoadoutDialogModel::apply() continue; // Skip invalid entries } - out.ship_list[s] = -1; + auto &entry = out.ship_choices.emplace_back(); + entry.class_index = -1; // enabling var name - strcpy_s(out.ship_list_variables[s], Sexp_variables[it.infoIndex].variable_name); + entry.class_variable = Sexp_variables[it.infoIndex].variable_name; // count: number-var or literal if (it.varCountIndex >= 0) { - strcpy_s(out.ship_count_variables[s], Sexp_variables[it.varCountIndex].variable_name); - out.ship_count[s] = 0; + entry.count_variable = Sexp_variables[it.varCountIndex].variable_name; } else { - out.ship_count_variables[s][0] = '\0'; - out.ship_count[s] = it.extraAllocated; + entry.count = it.extraAllocated; } - ++s; } } // static ships for (const auto& it : in.ships) { if (present(it)) { - out.ship_list[s] = it.infoIndex; - out.ship_list_variables[s][0] = '\0'; + auto &entry = out.ship_choices.emplace_back(); + entry.class_index = it.infoIndex; if (it.varCountIndex >= 0) { - strcpy_s(out.ship_count_variables[s], Sexp_variables[it.varCountIndex].variable_name); - out.ship_count[s] = 0; + entry.count_variable = Sexp_variables[it.varCountIndex].variable_name; } else { - out.ship_count_variables[s][0] = '\0'; - out.ship_count[s] = it.extraAllocated; + entry.count = it.extraAllocated; } - ++s; } } - out.num_ship_choices = s; - // Weapons - int w = 0; // var weapons first for (const auto& it : in.varWeapons) { @@ -102,42 +91,34 @@ bool TeamLoadoutDialogModel::apply() continue; // Skip invalid entries } - out.weaponry_pool[w] = -1; - strcpy_s(out.weaponry_pool_variable[w], Sexp_variables[it.infoIndex].variable_name); + auto &entry = out.weapon_choices.emplace_back(); + entry.class_index = -1; + entry.class_variable = Sexp_variables[it.infoIndex].variable_name; if (it.varCountIndex >= 0) { - strcpy_s(out.weaponry_amount_variable[w], Sexp_variables[it.varCountIndex].variable_name); - out.weaponry_count[w] = 0; + entry.count_variable = Sexp_variables[it.varCountIndex].variable_name; } else { - out.weaponry_amount_variable[w][0] = '\0'; - out.weaponry_count[w] = it.extraAllocated; + entry.count = it.extraAllocated; } - ++w; } } // static weapons for (const auto& it : in.weapons) { if (present(it)) { - out.weaponry_pool[w] = it.infoIndex; - out.weaponry_pool_variable[w][0] = '\0'; - + auto &entry = out.weapon_choices.emplace_back(); + entry.class_index = it.infoIndex; if (it.varCountIndex >= 0) { - strcpy_s(out.weaponry_amount_variable[w], Sexp_variables[it.varCountIndex].variable_name); - out.weaponry_count[w] = 0; + entry.count_variable = Sexp_variables[it.varCountIndex].variable_name; } else { - out.weaponry_amount_variable[w][0] = '\0'; - out.weaponry_count[w] = it.extraAllocated; + entry.count = it.extraAllocated; } - ++w; } } - out.num_weapon_choices = w; - // required weapons for (const auto& it : in.weapons) - if (present(it) && it.required && it.infoIndex >= 0 && it.infoIndex < MAX_WEAPON_TYPES) { - out.weapon_required[it.infoIndex] = true; + if (present(it) && it.required && Weapon_info.in_bounds(it.infoIndex)) { + out.required_weapons.insert(it.infoIndex); } out.do_not_validate = in.skipValidation; @@ -180,10 +161,7 @@ void TeamLoadoutDialogModel::initializeData() _teams.push_back(defaultEntry); } - // this is basically raw data, so we have to make sure to calculate the indices correctly. - SCP_vector usage = _editor->getStartingWingLoadoutUseCounts(); - - Assertion(usage.size() == (MAX_SHIP_CLASSES + MAX_WEAPON_TYPES) * MAX_TVT_TEAMS, "Starting wing loadout usage is unexpected size!"); + const auto &usage = _editor->getStartingWingLoadoutUseCounts(); for (int i = 0; i < Num_teams; i++) { auto& team = _teams[i]; @@ -191,9 +169,9 @@ void TeamLoadoutDialogModel::initializeData() // First we get the ship pool for (int j = 0; j < static_cast(Ship_info.size()); j++) { const auto& ship = Ship_info[j]; - + if (ship.flags[Ship::Info_Flags::Player_ship]) { - int countInWings = usage.at((MAX_SHIP_CLASSES * i) + j); + int countInWings = usage[i].ships.value_or(j, 0); LoadoutItem item( j, // ship class index @@ -218,7 +196,7 @@ void TeamLoadoutDialogModel::initializeData() const auto& weapon = Weapon_info[j]; if (weapon.wi_flags[Weapon::Info_Flags::Player_allowed]) { - int countInWings = usage.at((MAX_SHIP_CLASSES * MAX_TVT_TEAMS) + (MAX_WEAPON_TYPES * i) + j); + int countInWings = usage[i].weapons.value_or(j, 0); LoadoutItem item( j, // weapon index @@ -239,19 +217,19 @@ void TeamLoadoutDialogModel::initializeData() const auto& teamData = Team_data[i]; // first the ships - for (int j = 0; j < teamData.num_ship_choices; j++) { + for (const auto &sc : teamData.ship_choices) { // if it has an enabling variable, add it to the correct vector. - if (strlen(teamData.ship_list_variables[j])) { + if (!sc.class_variable.empty()) { LoadoutItem varItem( - get_index_sexp_variable_name(teamData.ship_list_variables[j]), // variable index + get_index_sexp_variable_name(sc.class_variable.c_str()), // variable index true, false, true, 0, // 0 until proven otherwise in-game. - teamData.ship_count[j], - (strlen(teamData.ship_count_variables[j])) ? get_index_sexp_variable_name(teamData.ship_count_variables[j]) : -1, - SCP_string(teamData.ship_list_variables[j]) + sc.count, + (!sc.count_variable.empty()) ? get_index_sexp_variable_name(sc.count_variable.c_str()) : -1, + sc.class_variable ); if (varItem.extraAllocated == 0) { @@ -263,11 +241,11 @@ void TeamLoadoutDialogModel::initializeData() // if it doesn't, enable the matching item. } else { for (auto& item : team.ships) { - if (teamData.ship_list[j] == item.infoIndex) { + if (sc.class_index == item.infoIndex) { item.enabled = true; - item.extraAllocated = teamData.ship_count[j]; - if (strlen(teamData.ship_count_variables[j])) { - item.varCountIndex = get_index_sexp_variable_name(teamData.ship_count_variables[j]); + item.extraAllocated = sc.count; + if (!sc.count_variable.empty()) { + item.varCountIndex = get_index_sexp_variable_name(sc.count_variable.c_str()); } else { item.varCountIndex = -1; } @@ -288,19 +266,19 @@ void TeamLoadoutDialogModel::initializeData() } // then the weapons - for (int j = 0; j < teamData.num_weapon_choices; j++) { + for (const auto &wc : teamData.weapon_choices) { // if it has an enabling variable, add it to the correct vector. - if (strlen(teamData.weaponry_pool_variable[j])) { + if (!wc.class_variable.empty()) { LoadoutItem varItem( - get_index_sexp_variable_name(teamData.weaponry_pool_variable[j]), // variable index + get_index_sexp_variable_name(wc.class_variable.c_str()), // variable index true, - false, // was teamData.weapon_required[j]... I don't think variables can be required + false, // variables can't be required true, 0, // 0 until proven otherwise in-game. - teamData.weaponry_count[j], - (strlen(teamData.weaponry_amount_variable[j])) ? get_index_sexp_variable_name(teamData.weaponry_amount_variable[j]) : -1, - SCP_string(teamData.weaponry_pool_variable[j]) + wc.count, + (!wc.count_variable.empty()) ? get_index_sexp_variable_name(wc.count_variable.c_str()) : -1, + wc.class_variable ); // it's impossible for this type to tell if it's secondary or its cargo size, so this default allows for a good number. @@ -312,12 +290,12 @@ void TeamLoadoutDialogModel::initializeData() // if it doesn't, enable the matching item. } else { for (auto& item : team.weapons) { - if (teamData.weaponry_pool[j] == item.infoIndex) { + if (wc.class_index == item.infoIndex) { item.enabled = true; - item.required = teamData.weapon_required[item.infoIndex]; - item.extraAllocated = teamData.weaponry_count[j]; - if (strlen(teamData.weaponry_amount_variable[j])) { - item.varCountIndex = get_index_sexp_variable_name(teamData.weaponry_amount_variable[j]); + item.required = teamData.required_weapons.contains(item.infoIndex); + item.extraAllocated = wc.count; + if (!wc.count_variable.empty()) { + item.varCountIndex = get_index_sexp_variable_name(wc.count_variable.c_str()); } else { item.varCountIndex = -1; } diff --git a/qtfred/src/ui/util/ErrorChecker.cpp b/qtfred/src/ui/util/ErrorChecker.cpp index e1ab1e68bd0..9694adbbf5a 100644 --- a/qtfred/src/ui/util/ErrorChecker.cpp +++ b/qtfred/src/ui/util/ErrorChecker.cpp @@ -1206,35 +1206,31 @@ int ErrorChecker::checkTeamLoadout() { continue; // Build a fresh usage list for this team's starting wings. - int usage[MAX_WEAPON_TYPES]; + SCP_map usage; _viewport->editor->generate_team_weaponry_usage_list(i, usage); - // Zero out weapons that are accounted for in the loadout pool, so that - // only weapons missing from the pool remain non-zero. - for (int j = 0; j < Team_data[i].num_weapon_choices; j++) { - int wi = Team_data[i].weaponry_pool[j]; - if (wi >= 0 && wi < MAX_WEAPON_TYPES) - usage[wi] = 0; + // Remove weapons that are accounted for in the loadout pool, so that + // only weapons missing from the pool remain. + for (const auto &entry : Team_data[i].weapon_choices) { + if (entry.class_index >= 0) + usage.erase(entry.class_index); } - // Any non-zero entry is a weapon used in wings but absent from the loadout. - for (int j = 0; j < MAX_WEAPON_TYPES; j++) { - if (usage[j] <= 0) + // Any remaining entry is a weapon used in wings but absent from the loadout. + for (const auto &[weapon_class, count] : usage) { + if (count <= 0) continue; - if (_viewport->Error_checker_apply_auto_corrections && Team_data[i].num_weapon_choices < MAX_WEAPON_TYPES) { + if (_viewport->Error_checker_apply_auto_corrections) { // Add the missing weapon to the pool so the mission is structurally valid. - int slot = Team_data[i].num_weapon_choices; - Team_data[i].weaponry_pool[slot] = j; - Team_data[i].weaponry_count[slot] = usage[j]; - strcpy_s(Team_data[i].weaponry_amount_variable[slot], ""); - strcpy_s(Team_data[i].weaponry_pool_variable[slot], ""); - Team_data[i].num_weapon_choices++; + auto &entry = Team_data[i].weapon_choices.emplace_back(); + entry.class_index = weapon_class; + entry.count = count; warning("Weapon \"%s\" is used in wings of team %d but was not in the team loadout pool — added automatically.", - Weapon_info[j].name, i + 1); + Weapon_info[weapon_class].name, i + 1); } else { warning("Weapon \"%s\" is used in wings of team %d but is not in the team loadout pool — can be auto-corrected by adding it.", - Weapon_info[j].name, i + 1); + Weapon_info[weapon_class].name, i + 1); } } } From 1f9722e0046ef15badc49556bc776b6b478e746d Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Mon, 10 Aug 2026 12:13:38 -0400 Subject: [PATCH 2/2] Consolidate the starting-wing usage builders in missioneditor FRED2, qtFRED, and the shared mission save code each carried their own copy of the loadout usage builders. The weaponry pair already had shared versions in missioneditor/common; this converts those to the SCP_map signature the editors now use, adds the missing ship builder alongside them, and deletes both editors' private copies. FRED2's playerstarteditor/freddoc and qtFRED's Editor/ErrorChecker all call the shared functions now, following the update_custom_wing_indexes consolidation precedent. missionsave's used_pool becomes a map as well, which removes its MAX_WEAPON_TYPES stack array. Co-Authored-By: Claude Opus 4.7 (1M context) --- code/missioneditor/common.cpp | 39 ++++++++++------- code/missioneditor/common.h | 9 ++-- code/missioneditor/missionsave.cpp | 12 +++--- fred2/freddoc.cpp | 3 +- fred2/management.cpp | 61 -------------------------- fred2/management.h | 3 -- fred2/playerstarteditor.cpp | 9 ++-- qtfred/src/mission/Editor.cpp | 67 +++-------------------------- qtfred/src/mission/Editor.h | 6 --- qtfred/src/ui/util/ErrorChecker.cpp | 2 +- 10 files changed, 47 insertions(+), 164 deletions(-) diff --git a/code/missioneditor/common.cpp b/code/missioneditor/common.cpp index ce3293e10a5..f128b780bd5 100644 --- a/code/missioneditor/common.cpp +++ b/code/missioneditor/common.cpp @@ -114,28 +114,40 @@ void update_custom_wing_indexes() TVT_wings[i] = wing_name_lookup(TVT_wing_names[i], 1); } -void generate_weaponry_usage_list_team(int team, int* arr) +void generate_ship_usage_list_wing(int wing_num, SCP_map& usage) { int i; - for (i = 0; i < MAX_WEAPON_TYPES; i++) { - arr[i] = 0; + if (wing_num < 0) { + return; + } + + i = Wings[wing_num].wave_count; + while (i--) { + usage[Ships[Wings[wing_num].ship_index[i]].ship_info_index]++; } +} + +void generate_weaponry_usage_list_team(int team, SCP_map& usage) +{ + int i; + + usage.clear(); if (The_mission.game_type & MISSION_TYPE_MULTI_TEAMS) { Assert(team >= 0 && team < MAX_TVT_TEAMS); for (i = 0; i < MAX_TVT_WINGS_PER_TEAM; i++) { - generate_weaponry_usage_list_wing(TVT_wings[(team * MAX_TVT_WINGS_PER_TEAM) + i], arr); + generate_weaponry_usage_list_wing(TVT_wings[(team * MAX_TVT_WINGS_PER_TEAM) + i], usage); } } else { for (i = 0; i < MAX_STARTING_WINGS; i++) { - generate_weaponry_usage_list_wing(Starting_wings[i], arr); + generate_weaponry_usage_list_wing(Starting_wings[i], usage); } } } -void generate_weaponry_usage_list_wing(int wing_num, int* arr) +void generate_weaponry_usage_list_wing(int wing_num, SCP_map& usage) { int i, j; ship_weapon* swp; @@ -149,20 +161,17 @@ void generate_weaponry_usage_list_wing(int wing_num, int* arr) swp = &Ships[Wings[wing_num].ship_index[i]].weapons; j = swp->num_primary_banks; while (j--) { - if (swp->primary_bank_weapons[j] >= 0 && - swp->primary_bank_weapons[j] < static_cast(Weapon_info.size())) { - arr[swp->primary_bank_weapons[j]]++; + if (Weapon_info.in_bounds(swp->primary_bank_weapons[j])) { + usage[swp->primary_bank_weapons[j]]++; } } j = swp->num_secondary_banks; while (j--) { - if (swp->secondary_bank_weapons[j] >= 0 && - swp->secondary_bank_weapons[j] < static_cast(Weapon_info.size())) { - arr[swp->secondary_bank_weapons[j]] += - (int)floor((swp->secondary_bank_ammo[j] * swp->secondary_bank_capacity[j] / 100.0f / - Weapon_info[swp->secondary_bank_weapons[j]].cargo_size) + - 0.5f); + if (Weapon_info.in_bounds(swp->secondary_bank_weapons[j])) { + usage[swp->secondary_bank_weapons[j]] += + sz2i(floor((swp->secondary_bank_ammo[j] * swp->secondary_bank_capacity[j] / 100.0f / + Weapon_info[swp->secondary_bank_weapons[j]].cargo_size) + 0.5f)); } } } diff --git a/code/missioneditor/common.h b/code/missioneditor/common.h index 6622afcece0..58a0124738d 100644 --- a/code/missioneditor/common.h +++ b/code/missioneditor/common.h @@ -46,13 +46,12 @@ int anchor_to_target(anchor_t anchor); anchor_t target_to_anchor(int target); // Rebuild Starting_wings[], Squadron_wings[], TVT_wings[] from their parallel -// name arrays via wing_name_lookup. Consolidated from FRED's and qtFRED's -// previously-duplicated copies. +// name arrays via wing_name_lookup. void update_custom_wing_indexes(); -void generate_weaponry_usage_list_team(int team, int* arr); - -void generate_weaponry_usage_list_wing(int wing_num, int* arr); +void generate_ship_usage_list_wing(int wing_num, SCP_map& usage); +void generate_weaponry_usage_list_team(int team, SCP_map& usage); +void generate_weaponry_usage_list_wing(int wing_num, SCP_map& usage); // If Player_start_shipnum no longer refers to a valid player start ship, repoint it to the // first remaining player start in the mission (or -1 if there are none). Call this after diff --git a/code/missioneditor/missionsave.cpp b/code/missioneditor/missionsave.cpp index ead80baf9d9..0e8aaca9bf8 100644 --- a/code/missioneditor/missionsave.cpp +++ b/code/missioneditor/missionsave.cpp @@ -4328,7 +4328,7 @@ int Fred_mission_save::save_players() bool wrote_fso_data = false; int i, j; int var_idx; - int used_pool[MAX_WEAPON_TYPES]; + SCP_map used_pool; if (optional_string_fred("#Alternate Types:")) { // Make sure the parser doesn't get out of sync required_string_fred("#end"); @@ -4505,17 +4505,17 @@ int Fred_mission_save::save_players() if (!wc.class_variable.empty()) { fout("%d\n", wc.count); } else { - fout("%d\n", wc.count + used_pool[wc.class_index]); - used_pool[wc.class_index] = 0; + fout("%d\n", wc.count + used_pool.value_or(wc.class_index, 0)); + used_pool.erase(wc.class_index); } } } // now we add anything left in the used pool as a static entry if (!Team_data[i].do_not_validate) { - for (j = 0; j < weapon_info_size(); j++) { - if (used_pool[j] > 0) { - fout("\t\"%s\"\t%d\n", Weapon_info[j].name, used_pool[j]); + for (const auto &[weapon_class, count] : used_pool) { + if (count > 0) { + fout("\t\"%s\"\t%d\n", Weapon_info[weapon_class].name, count); } } } diff --git a/fred2/freddoc.cpp b/fred2/freddoc.cpp index a5282f149ac..db444485253 100644 --- a/fred2/freddoc.cpp +++ b/fred2/freddoc.cpp @@ -38,6 +38,7 @@ #include "mission/missiongoals.h" #include "mission/missiongrid.h" #include "mission/missionparse.h" +#include "missioneditor/common.h" #include "object/object.h" #include "render/3d.h" #include "ship/ship.h" @@ -348,7 +349,7 @@ bool CFREDDoc::load_mission(const char *pathname, int flags) { } for (i = 0; i < Num_teams; i++) { - generate_weaponry_usage_list(i, used_pool); + generate_weaponry_usage_list_team(i, used_pool); for (auto &entry : Team_data[i].weapon_choices) { // The amount used in wings is always set by a static loadout entry so skip any that were set by Sexp variables if (entry.class_variable.empty() && entry.count_variable.empty()) { diff --git a/fred2/management.cpp b/fred2/management.cpp index cdb0de9fad4..1c8e86f9f57 100644 --- a/fred2/management.cpp +++ b/fred2/management.cpp @@ -2421,67 +2421,6 @@ int query_whole_wing_marked(int wing) return 0; } -void generate_ship_usage_list(SCP_map &usage, int wing) -{ - int i; - - if (wing < 0) { - return; - } - - i = Wings[wing].wave_count; - while (i--) { - usage[Ships[Wings[wing].ship_index[i]].ship_info_index]++; - } -} - -void generate_weaponry_usage_list(SCP_map &usage, int wing) -{ - int i, j; - ship_weapon *swp; - - if (wing < 0) - return; - - i = Wings[wing].wave_count; - while (i--) { - swp = &Ships[Wings[wing].ship_index[i]].weapons; - j = swp->num_primary_banks; - while (j--) { - if (swp->primary_bank_weapons[j] >= 0 && swp->primary_bank_weapons[j] < weapon_info_size()) { - usage[swp->primary_bank_weapons[j]]++; - } - } - - j = swp->num_secondary_banks; - while (j--) { - if (swp->secondary_bank_weapons[j] >=0 && swp->secondary_bank_weapons[j] < weapon_info_size()) { - usage[swp->secondary_bank_weapons[j]] += (int) floor((swp->secondary_bank_ammo[j] * swp->secondary_bank_capacity[j] / 100.0f / Weapon_info[swp->secondary_bank_weapons[j]].cargo_size) + 0.5f); - } - } - } -} - -void generate_weaponry_usage_list(int team, SCP_map &usage) -{ - int i; - - usage.clear(); - - if (The_mission.game_type & MISSION_TYPE_MULTI_TEAMS) { - Assert (team >= 0 && team < MAX_TVT_TEAMS); - - for (i=0; i& usage); -void generate_weaponry_usage_list(SCP_map& usage, int wing); -void generate_ship_usage_list(SCP_map& usage, int wing); CJumpNode* jumpnode_get_by_name(const CString& name); diff --git a/fred2/playerstarteditor.cpp b/fred2/playerstarteditor.cpp index bbf660a4712..31cf3db6448 100644 --- a/fred2/playerstarteditor.cpp +++ b/fred2/playerstarteditor.cpp @@ -14,6 +14,7 @@ #include "FREDDoc.h" #include "PlayerStartEditor.h" #include "mission/missionparse.h" +#include "missioneditor/common.h" #include "object/object.h" #include "Management.h" #include "weapon/weapon.h" @@ -167,15 +168,15 @@ BOOL player_start_editor::OnInitDialog() if (The_mission.game_type & MISSION_TYPE_MULTI_TEAMS) { for (i=0; i used_pool; - generate_team_weaponry_usage_list(i, used_pool); + generate_weaponry_usage_list_team(i, used_pool); for (auto &entry : Team_data[i].weapon_choices) { // The amount used in wings is always set by a static loadout entry so skip any that were set by Sexp variables if (entry.class_variable.empty() && entry.count_variable.empty()) { @@ -1588,63 +1588,6 @@ void Editor::disband_wing(int wing_num) { missionChanged(); } -void Editor::generate_wing_weaponry_usage_list(SCP_map& usage, int wing) { - int i, j; - ship_weapon* swp; - - if (wing < 0) { - return; - } - - i = Wings[wing].wave_count; - while (i--) { - swp = &Ships[Wings[wing].ship_index[i]].weapons; - j = swp->num_primary_banks; - while (j--) { - if (swp->primary_bank_weapons[j] >= 0 && swp->primary_bank_weapons[j] < static_cast(Weapon_info.size())) { - usage[swp->primary_bank_weapons[j]]++; - } - } - - j = swp->num_secondary_banks; - while (j--) { - if (swp->secondary_bank_weapons[j] >= 0 && swp->secondary_bank_weapons[j] < static_cast(Weapon_info.size())) { - usage[swp->secondary_bank_weapons[j]] += (int) floor( - (swp->secondary_bank_ammo[j] * swp->secondary_bank_capacity[j] / 100.0f - / Weapon_info[swp->secondary_bank_weapons[j]].cargo_size) + 0.5f); - } - } - } -} -void Editor::generate_team_weaponry_usage_list(int team, SCP_map& usage) { - int i; - - usage.clear(); - - if (The_mission.game_type & MISSION_TYPE_MULTI_TEAMS) { - Assert (team >= 0 && team < MAX_TVT_TEAMS); - - for (i = 0; i < MAX_TVT_WINGS_PER_TEAM; i++) { - generate_wing_weaponry_usage_list(usage, TVT_wings[(team * MAX_TVT_WINGS_PER_TEAM) + i]); - } - } else { - for (i = 0; i < MAX_STARTING_WINGS; i++) { - generate_wing_weaponry_usage_list(usage, Starting_wings[i]); - } - } -} -void Editor::generate_ship_usage_list(SCP_map& usage, int wing) { - int i; - - if (wing < 0) { - return; - } - - i = Wings[wing].wave_count; - while (i--) { - usage[Ships[Wings[wing].ship_index[i]].ship_info_index]++; - } -} void Editor::updateStartingWingLoadoutUseCounts() { _loadout_usage.clear(); _loadout_usage.resize(MAX_TVT_TEAMS); @@ -1652,16 +1595,16 @@ void Editor::updateStartingWingLoadoutUseCounts() { if (The_mission.game_type & MISSION_TYPE_MULTI_TEAMS) { for (int i = 0; i& usage); - private slots: void performTimedAutosave(); @@ -335,10 +333,6 @@ class Editor : public QObject { */ static int find_free_wing(); - void generate_wing_weaponry_usage_list(SCP_map& usage, int wing); - - void generate_ship_usage_list(SCP_map& usage, int wing); - int get_visible_sub_system_count(ship* shipp); int get_next_visible_subsys(ship* shipp, ship_subsys** next_subsys); diff --git a/qtfred/src/ui/util/ErrorChecker.cpp b/qtfred/src/ui/util/ErrorChecker.cpp index 9694adbbf5a..6951c4cde29 100644 --- a/qtfred/src/ui/util/ErrorChecker.cpp +++ b/qtfred/src/ui/util/ErrorChecker.cpp @@ -1207,7 +1207,7 @@ int ErrorChecker::checkTeamLoadout() { // Build a fresh usage list for this team's starting wings. SCP_map usage; - _viewport->editor->generate_team_weaponry_usage_list(i, usage); + generate_weaponry_usage_list_team(i, usage); // Remove weapons that are accounted for in the loadout pool, so that // only weapons missing from the pool remain.