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/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 72201544116..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"); @@ -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,42 +4480,42 @@ 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.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); } } } @@ -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/missionscreencommon.cpp b/code/missionui/missionscreencommon.cpp index f4e66df3ccd..2781eb05912 100644 --- a/code/missionui/missionscreencommon.cpp +++ b/code/missionui/missionscreencommon.cpp @@ -85,11 +85,6 @@ shader Icon_shaders[NUM_ICON_FRAMES]; loadout_data Player_loadout; // what the ship and weapon loadout is... used since we want to use the // same loadout if the mission is played again -//wss_unit Wss_slots[MAX_WSS_SLOTS]; // slot data struct -//int Wl_pool[MAX_WEAPON_TYPES]; // weapon pool -//int Ss_pool[MAX_SHIP_CLASSES]; // ship pool -//int Wss_num_wings; // number of player wings - wss_unit Wss_slots_teams[MAX_TVT_TEAMS][MAX_WSS_SLOTS]; SCP_map Wl_pool_teams[MAX_TVT_TEAMS]; SCP_map Ss_pool_teams[MAX_TVT_TEAMS]; diff --git a/code/missionui/missionshipchoice.cpp b/code/missionui/missionshipchoice.cpp index a3b90c3dcd4..5445fffed54 100644 --- a/code/missionui/missionshipchoice.cpp +++ b/code/missionui/missionshipchoice.cpp @@ -84,19 +84,26 @@ int anim_timer_start = 0; typedef struct ss_icon_info { int icon_bmaps[NUM_ICON_FRAMES]; - int current_icon_bitmap; - int model_index; + int current_icon_bitmap = -1; + int model_index = -1; generic_anim ss_anim; + + ss_icon_info() + { + for (int &bmap : icon_bmaps) + bmap = -1; + generic_anim_init(&ss_anim, nullptr); + } } ss_icon_info; -//ss_icon_info Ss_icons[MAX_SHIP_CLASSES]; // holds ui info on different ship icons //ss_wing_info Ss_wings[MAX_WING_BLOCKS]; // holds ui info for wings and wing slots ss_wing_info Ss_wings_teams[MAX_TVT_TEAMS][MAX_WING_BLOCKS]; ss_wing_info *Ss_wings = NULL; -ss_icon_info Ss_icons_teams[MAX_TVT_TEAMS][MAX_SHIP_CLASSES]; -ss_icon_info *Ss_icons = NULL; +// ui info for ship icons, keyed by ship class; an absent entry means no icon data is loaded for that class +SCP_map Ss_icons_teams[MAX_TVT_TEAMS]; +SCP_map *Ss_icons = nullptr; int Ss_mouse_down_on_region = -1; @@ -194,22 +201,11 @@ int Wing_icon_coords[GR_NUM_RESOLUTIONS][MAX_WSS_SLOTS][2] = { }; ////////////////////////////////////////////////////// -// Linked List of icons to show on ship selection list +// List of icons to show on ship selection list ////////////////////////////////////////////////////// -#define SS_ACTIVE_ITEM_USED (1<<0) -typedef struct ss_active_item -{ - ss_active_item *prev, *next; - int ship_class; - int flags; -} ss_active_item; - -static ss_active_item SS_active_head; -//static ss_active_item SS_active_items[MAX_WSS_SLOTS];//DTP commented out or else singleplayer will only have a max of MAX_WSS_SLOTS ships -static ss_active_item SS_active_items[MAX_SHIP_CLASSES];//DTP, now we have all ships in the TBL, as they can all be playerships +static SCP_vector SS_active_list; // ship classes with a positive pool count, in ascending class order static int SS_active_list_start; -static int SS_active_list_size; ////////////////////////////////////////////////////// // Background bitmaps data for ship_select @@ -322,8 +318,6 @@ void ss_init_pool(team_data *pteam); commit_pressed_status create_wings(); // loading/unloading -void ss_unload_all_icons(); -void ss_unload_all_anims(); void ss_init_units(); anim* ss_load_individual_animation(int ship_class); @@ -418,85 +412,19 @@ void ss_set_carried_icon(int from_slot, int ship_class) Ship_select_buttons[gr_screen.res][SS_BUTTON_DUMMY].button.capture_mouse(); } -// clear all active list items, and reset the flags inside the SS_active_items[] array -void clear_active_list() -{ - int i; - for ( i = 0; i < ship_info_size(); i++ ) { //DTP singleplayer ship choice fix - //for ( i = 0; i < MAX_WSS_SLOTS; i++ ) { - SS_active_items[i].flags = 0; - SS_active_items[i].ship_class = -1; - } - list_init(&SS_active_head); - - SS_active_list_start = 0; - SS_active_list_size = 0; -} - - -// get a free element from SS_active_items[] -ss_active_item *get_free_active_list_node() -{ - int i; - for ( i = 0; i < ship_info_size(); i++ ) { - //for ( i = 0; i < MAX_WSS_SLOTS; i++ ) { //DTP, ONLY MAX_WSS_SLOTS SHIPS ??? - if ( SS_active_items[i].flags == 0 ) { - SS_active_items[i].flags |= SS_ACTIVE_ITEM_USED; - return &SS_active_items[i]; - } - } - return NULL; -} - - -// add a ship into the active list -void active_list_add(int ship_class) -{ - ss_active_item *sai; - - sai = get_free_active_list_node(); - Assert(sai != NULL); - sai->ship_class = ship_class; - list_append(&SS_active_head, sai); -} - -// remove a ship from the active list -void active_list_remove(int ship_class) -{ - ss_active_item *sai, *temp; - - // next store players not assigned to wings - sai = GET_FIRST(&SS_active_head); - - while(sai != END_OF_LIST(&SS_active_head)){ - temp = GET_NEXT(sai); - if ( sai->ship_class == ship_class ) { - list_remove(&SS_active_head, sai); - sai->flags = 0; - } - sai = temp; - } -} - // Build up the ship selection active list, which is a list of all ships that the player // can choose from. void init_active_list() { - ss_active_item *sai; - Assert( Ss_pool != NULL ); - clear_active_list(); + SS_active_list.clear(); + SS_active_list_start = 0; - // build the active list + // build the active list (map iteration is in ascending class order, which is the display order) for ( const auto &[ship_class, count] : *Ss_pool ) { if ( count > 0 ) { - sai = get_free_active_list_node(); - if ( sai != NULL ) { - sai->ship_class = ship_class; - list_append(&SS_active_head, sai); - SS_active_list_size++; - } + SS_active_list.push_back(ship_class); } } } @@ -598,7 +526,7 @@ void ship_select_button_do(int i) if ( Current_screen != ON_SHIP_SELECT ) break; - if ( common_scroll_down_pressed(&SS_active_list_start, SS_active_list_size, MAX_ICONS_ON_SCREEN) ) { + if ( common_scroll_down_pressed(&SS_active_list_start, sz2i(SS_active_list.size()), MAX_ICONS_ON_SCREEN) ) { gamesnd_play_iface(InterfaceSounds::SCROLL); } else { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); @@ -609,7 +537,7 @@ void ship_select_button_do(int i) if ( Current_screen != ON_SHIP_SELECT ) break; - if ( common_scroll_up_pressed(&SS_active_list_start, SS_active_list_size, MAX_ICONS_ON_SCREEN) ) { + if ( common_scroll_up_pressed(&SS_active_list_start, sz2i(SS_active_list.size()), MAX_ICONS_ON_SCREEN) ) { gamesnd_play_iface(InterfaceSounds::SCROLL); } else { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); @@ -629,8 +557,6 @@ void ship_select_button_do(int i) // void ship_select_init() { -// SS_active_items = new ss_active_item[Num_ship_classes]; - common_set_interface_palette("ShipPalette"); common_flash_button_init(); @@ -739,29 +665,14 @@ void ship_select_init() // int ss_get_ship_class_from_list(int index) { - ss_active_item *sai; - int list_entry, i, count; - - i = 0; - count = 0; - list_entry = -1; - for ( sai = GET_FIRST(&SS_active_head); sai != END_OF_LIST(&SS_active_head); sai = GET_NEXT(sai) ) { - count++; - if ( count <= SS_active_list_start ) - continue; - - if ( i >= MAX_ICONS_ON_SCREEN ) - break; - - if ( i == index ) { - list_entry = sai->ship_class; - break; - } + if ( index < 0 || index >= MAX_ICONS_ON_SCREEN ) + return -1; - i++; - } + size_t list_index = SS_active_list_start + index; + if ( list_index >= SS_active_list.size() ) + return -1; - return list_entry; + return SS_active_list[list_index]; } // --------------------------------------------------------------------- @@ -1419,11 +1330,11 @@ void ship_select_do(float frametime) ship_select_redraw_pressed_buttons(); common_render_selected_screen_button(); } - if (!Use_3d_ship_select && ((Selected_ss_class >= 0) && (Ss_icons[Selected_ss_class].ss_anim.num_frames > 0))) + if (!Use_3d_ship_select && ((Selected_ss_class >= 0) && ((*Ss_icons)[Selected_ss_class].ss_anim.num_frames > 0))) { GR_DEBUG_SCOPE("Render ship animation"); - generic_anim_render(&Ss_icons[Selected_ss_class].ss_anim, (help_overlay_active(Ship_select_overlay_id)) ? 0 : frametime, Ship_anim_coords[gr_screen.res][0], Ship_anim_coords[gr_screen.res][1], true); + generic_anim_render(&(*Ss_icons)[Selected_ss_class].ss_anim, (help_overlay_active(Ship_select_overlay_id)) ? 0 : frametime, Ship_anim_coords[gr_screen.res][0], Ship_anim_coords[gr_screen.res][1], true); } else { GR_DEBUG_SCOPE("Render ship models"); // The new rendering code for 3D ships courtesy your friendly UnknownPlayer :) @@ -1491,17 +1402,17 @@ void ship_select_do(float frametime) mouse_get_pos_unscaled( &mouse_x, &mouse_y ); sx = mouse_x + Ss_delta_x; sy = mouse_y + Ss_delta_y; - if(Ss_icons[Carried_ss_icon.ship_class].icon_bmaps[ICON_FRAME_SELECTED] != -1) + if((*Ss_icons)[Carried_ss_icon.ship_class].icon_bmaps[ICON_FRAME_SELECTED] != -1) { - gr_set_bitmap(Ss_icons[Carried_ss_icon.ship_class].icon_bmaps[ICON_FRAME_SELECTED]); + gr_set_bitmap((*Ss_icons)[Carried_ss_icon.ship_class].icon_bmaps[ICON_FRAME_SELECTED]); gr_bitmap(sx, sy, GR_RESIZE_MENU); } else { ship_info *sip = &Ship_info[Carried_ss_icon.ship_class]; - if(Ss_icons[Carried_ss_icon.ship_class].model_index == -1) { - Ss_icons[Carried_ss_icon.ship_class].model_index = model_load(sip, true); - mprintf(("SL WARNING: Had to attempt to page in model for %s paged in manually! Result: %d\n", sip->name, Ss_icons[Carried_ss_icon.ship_class].model_index)); + if((*Ss_icons)[Carried_ss_icon.ship_class].model_index == -1) { + (*Ss_icons)[Carried_ss_icon.ship_class].model_index = model_load(sip, true); + mprintf(("SL WARNING: Had to attempt to page in model for %s paged in manually! Result: %d\n", sip->name, (*Ss_icons)[Carried_ss_icon.ship_class].model_index)); } gr_set_color_fast(&Icon_colors[ICON_FRAME_SELECTED]); @@ -1512,9 +1423,9 @@ void ship_select_do(float frametime) draw_brackets_square(&line_draw_list, sx, sy, sx + w, sy + h, GR_RESIZE_MENU); line_draw_list.flush(); - if(Ss_icons[Carried_ss_icon.ship_class].model_index != -1) + if((*Ss_icons)[Carried_ss_icon.ship_class].model_index != -1) { - draw_model_icon(Ss_icons[Carried_ss_icon.ship_class].model_index, MR_AUTOCENTER | MR_NO_FOGGING | MR_NO_LIGHTING, sx, sy, w, h, sip, nullptr, 0.8f, GR_RESIZE_MENU); + draw_model_icon((*Ss_icons)[Carried_ss_icon.ship_class].model_index, MR_AUTOCENTER | MR_NO_FOGGING | MR_NO_LIGHTING, sx, sy, w, h, sip, nullptr, 0.8f, GR_RESIZE_MENU); } } } @@ -1580,27 +1491,24 @@ void ship_select_close() // select screen has been closed and memory freed. This flag // is needed so we can know if ship_select_close() needs to called if // restoring a game from the Options screen invoked from ship select - -// delete[] SS_active_items; } -// ss_unload_icons() frees the bitmaps used for ship icons -void ss_unload_all_icons() +/** + * Release the bitmaps and animation held by one team's icon map, while leaving the map populated. + */ +static void ss_unload_team_icons(SCP_map &icons) { - int i,j; - ss_icon_info *icon; - - Assert( Ss_icons != NULL ); - - for ( i = 0; i < MAX_SHIP_CLASSES; i++ ) { - icon = &Ss_icons[i]; - - for ( j = 0; j < NUM_ICON_FRAMES; j++ ) { - if ( icon->icon_bmaps[j] >= 0 ) { - bm_release(icon->icon_bmaps[j]); - icon->icon_bmaps[j] = -1; + for ( auto &[ship_class, icon] : icons ) { + for ( int &bmap : icon.icon_bmaps ) { + if ( bmap >= 0 ) { + bm_release(bmap); + bmap = -1; } } + + if ( icon.ss_anim.num_frames ) { + generic_anim_unload(&icon.ss_anim); + } } } @@ -1608,21 +1516,12 @@ void ss_unload_all_icons() // draw_ship_icons() will request which icons to draw on screen. void draw_ship_icons() { - int i; - int count=0; - - ss_active_item *sai; - i = 0; - for ( sai = GET_FIRST(&SS_active_head); sai != END_OF_LIST(&SS_active_head); sai = GET_NEXT(sai) ) { - count++; - if ( count <= SS_active_list_start ) - continue; - - if ( i >= MAX_ICONS_ON_SCREEN ) + for ( int i = 0; i < MAX_ICONS_ON_SCREEN; i++ ) { + size_t list_index = SS_active_list_start + i; + if ( list_index >= SS_active_list.size() ) break; - draw_ship_icon_with_number(i, sai->ship_class); - i++; + draw_ship_icon_with_number(i, SS_active_list[list_index]); } } @@ -1645,7 +1544,7 @@ void draw_ship_icon_with_number(int screen_offset, int ship_class) Assert( screen_offset >= 0 && screen_offset <= 3 ); Assert( ship_class >= 0 ); Assert( (Ss_pool != NULL) && (Ss_icons != NULL) ); - ss_icon = &Ss_icons[ship_class]; + ss_icon = &(*Ss_icons)[ship_class]; num_x = Ship_list_coords[gr_screen.res][screen_offset][2]; num_y = Ship_list_coords[gr_screen.res][screen_offset][3]; @@ -1746,8 +1645,8 @@ void start_ship_animation(int ship_class, int /*play_sound*/) if ( Use_3d_ship_select || !strlen(sip->anim_filename) ) { //Unload Anim if one was playing - if(Ship_anim_class > 0 && Ss_icons[Ship_anim_class].ss_anim.num_frames > 0) { - generic_anim_unload(&Ss_icons[Ship_anim_class].ss_anim); + if(Ship_anim_class > 0 && (*Ss_icons)[Ship_anim_class].ss_anim.num_frames > 0) { + generic_anim_unload(&(*Ss_icons)[Ship_anim_class].ss_anim); Ship_anim_class = -1; } @@ -1775,8 +1674,8 @@ void start_ship_animation(int ship_class, int /*play_sound*/) } //unload the previous anim - if(Ship_anim_class > 0 && Ss_icons[Ship_anim_class].ss_anim.num_frames > 0) - generic_anim_unload(&Ss_icons[Ship_anim_class].ss_anim); + if(Ship_anim_class > 0 && (*Ss_icons)[Ship_anim_class].ss_anim.num_frames > 0) + generic_anim_unload(&(*Ss_icons)[Ship_anim_class].ss_anim); //load animation here, we now only have one loaded p = strchr(Ship_info[ship_class].anim_filename, '.' ); if(p) @@ -1789,13 +1688,13 @@ void start_ship_animation(int ship_class, int /*play_sound*/) strcpy_s(animation_filename, Ship_info[ship_class].anim_filename); } - generic_anim_init(&Ss_icons[ship_class].ss_anim, animation_filename); - Ss_icons[ship_class].ss_anim.ani.bg_type = bm_get_type(Ship_select_background_bitmap); - if(generic_anim_stream(&Ss_icons[ship_class].ss_anim) == -1) { + generic_anim_init(&(*Ss_icons)[ship_class].ss_anim, animation_filename); + (*Ss_icons)[ship_class].ss_anim.ani.bg_type = bm_get_type(Ship_select_background_bitmap); + if(generic_anim_stream(&(*Ss_icons)[ship_class].ss_anim) == -1) { //we've failed to load an animation, load an image and treat it like a 1 frame animation - Ss_icons[ship_class].ss_anim.first_frame = bm_load(Ship_info[ship_class].anim_filename); //if we fail here, the value is still -1 - if(Ss_icons[ship_class].ss_anim.first_frame != -1) { - Ss_icons[ship_class].ss_anim.num_frames = 1; + (*Ss_icons)[ship_class].ss_anim.first_frame = bm_load(Ship_info[ship_class].anim_filename); //if we fail here, the value is still -1 + if((*Ss_icons)[ship_class].ss_anim.first_frame != -1) { + (*Ss_icons)[ship_class].ss_anim.num_frames = 1; } } @@ -1807,17 +1706,6 @@ void start_ship_animation(int ship_class, int /*play_sound*/) // } } -void ss_unload_all_anims() -{ - Assert( Ss_icons != NULL ); - - for ( int i = 0; i < MAX_SHIP_CLASSES; i++ ) { - if ( Ss_icons[i].ss_anim.num_frames ) { - generic_anim_unload(&Ss_icons[i].ss_anim); - } - } -} - bool is_weapon_carried(int weapon_index) { for (int slot = 0; slot < MAX_WING_BLOCKS*MAX_WING_SLOTS; slot++) @@ -1897,7 +1785,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 +1820,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) { @@ -2191,7 +2076,7 @@ void draw_wing_block(int wb_num, int hot_slot, int selected_slot, int class_sele slot_index = wb_num*MAX_WING_SLOTS + i; if ( Wss_slots[slot_index].ship_class >= 0 ) { - icon = &Ss_icons[Wss_slots[slot_index].ship_class]; + icon = &(*Ss_icons)[Wss_slots[slot_index].ship_class]; } else { icon = NULL; } @@ -2369,7 +2254,7 @@ void ss_blit_ship_icon(int x,int y,int ship_class,int bmap_num) { Assert( Ss_icons != NULL ); - ss_icon_info *icon = &Ss_icons[ship_class]; + ss_icon_info *icon = &(*Ss_icons)[ship_class]; if(icon->icon_bmaps[bmap_num] != -1) { Assert(icon->icon_bmaps[bmap_num] != -1); @@ -2793,19 +2678,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 +2700,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 +2710,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; } } @@ -2900,7 +2748,7 @@ void ss_load_icons(int ship_class) Assert( Ss_icons != NULL ); - icon = &Ss_icons[ship_class]; + icon = &(*Ss_icons)[ship_class]; ship_info *sip = &Ship_info[ship_class]; if (!Use_3d_ship_icons && strlen(sip->icon_filename)) @@ -2929,21 +2777,15 @@ void ss_load_icons(int ship_class) // load all the icons for ships in the pool void ss_load_all_icons() { - int i, j; + Assert( (Ss_pool != nullptr) && (Ss_icons != nullptr) ); - Assert( (Ss_pool != NULL) && (Ss_icons != NULL) ); - - for ( i = 0; i < MAX_SHIP_CLASSES; i++ ) { - // clear out data - Ss_icons[i].current_icon_bitmap = -1; - for ( j = 0; j < NUM_ICON_FRAMES; j++ ) { - Ss_icons[i].icon_bmaps[j] = -1; - } - Ss_icons[i].model_index = -1; + // drop any icon data from a previous load of this team + ss_unload_team_icons(*Ss_icons); + Ss_icons->clear(); - if ( Ss_pool->contains(i) ) { - ss_load_icons(i); - } + // note: unlike the weapon pool, all pool members get icons, including exhausted 0-count classes + for ( const auto &[ship_class, count] : *Ss_pool ) { + ss_load_icons(ship_class); } } @@ -3207,7 +3049,7 @@ void ss_set_team_pointers(int team) Assert( (team >= 0) && (team < MAX_TVT_TEAMS) ); Ss_wings = Ss_wings_teams[team]; - Ss_icons = Ss_icons_teams[team]; + Ss_icons = &Ss_icons_teams[team]; } // reset the necessary pointers to defaults @@ -3219,7 +3061,7 @@ void ss_reset_team_pointers() return; Ss_wings = NULL; - Ss_icons = NULL; + Ss_icons = nullptr; } // initialize team specific stuff @@ -3261,22 +3103,19 @@ void ship_select_init_team_data(int team_num) // called when the briefing is entered void ship_select_common_init(bool API_Access) -{ - // initialize team critical data for all teams - int idx; +{ + // In team-vs-team, initialize every other team first; we always initialize our own team + // last so that the team pointers -- which common_set_team_pointers repoints to whichever + // team was initialized most recently -- are left pointing at our team for the rest of the screen. + if(MULTI_TEAM){ + for(int idx=0;idx Wl_ships; struct wl_icon_info { int icon_bmaps[NUM_ICON_FRAMES]; - int laser_bmap; - int model_index; - bool can_use_for_ship; - TriStateBool can_use_for_bank; + int laser_bmap = -1; + int model_index = -1; + bool can_use_for_ship = false; + TriStateBool can_use_for_bank = TriStateBool::UNKNOWN_; generic_anim animation; + + wl_icon_info() + { + for (int &bmap : icon_bmaps) + bmap = -1; + generic_anim_init(&animation, nullptr); + } }; -wl_icon_info Wl_icons_teams[MAX_TVT_TEAMS][MAX_WEAPON_TYPES]; -wl_icon_info *Wl_icons = NULL; +// ui info for weapon icons, keyed by weapon class; an absent entry means no icon data is loaded for that class +SCP_map Wl_icons_teams[MAX_TVT_TEAMS]; +SCP_map *Wl_icons = nullptr; -int Plist[MAX_WEAPON_TYPES]; // used to track scrolling of primary icon list -int Plist_start, Plist_size; +SCP_vector Plist; // weapon classes in the primary icon list, in pool order +int Plist_start; // scroll offset into Plist -int Slist[MAX_WEAPON_TYPES]; // used to track scrolling of primary icon list -int Slist_start, Slist_size; +SCP_vector Slist; // weapon classes in the secondary icon list, in pool order +int Slist_start; // scroll offset into Slist static int Selected_wl_slot = -1; // Currently selected ship slot static int Selected_wl_class = -1; // Class of weapon that is selected @@ -448,7 +462,7 @@ UI_XSTR Weapon_select_text[GR_NUM_RESOLUTIONS][WEAPON_SELECT_NUM_TEXT] = { /////////////////////////////////////////////////////////////////////// typedef struct carried_icon { - int weapon_class; // index Wl_icons[] for carried icon (-1 if carried from bank) + int weapon_class; // weapon class of the carried icon (-1 if carried from bank) int num; // number of units of weapon int from_bank; // bank index that icon came from (0..2 primary, 3..6 secondary). -1 if from list int from_slot; // ship slot that weapon is part of @@ -596,7 +610,7 @@ void weapon_button_do(int i) { switch ( i ) { case WL_BUTTON_SCROLL_PRIMARY_UP: - if ( common_scroll_up_pressed(&Plist_start, Plist_size, NUM_PRIMARY_MASK_REGIONS) ) { + if ( common_scroll_up_pressed(&Plist_start, sz2i(Plist.size()), NUM_PRIMARY_MASK_REGIONS) ) { gamesnd_play_iface(InterfaceSounds::SCROLL); } else { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); @@ -604,7 +618,7 @@ void weapon_button_do(int i) break; case WL_BUTTON_SCROLL_PRIMARY_DOWN: - if ( common_scroll_down_pressed(&Plist_start, Plist_size, NUM_PRIMARY_MASK_REGIONS) ) { + if ( common_scroll_down_pressed(&Plist_start, sz2i(Plist.size()), NUM_PRIMARY_MASK_REGIONS) ) { gamesnd_play_iface(InterfaceSounds::SCROLL); } else { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); @@ -612,7 +626,7 @@ void weapon_button_do(int i) break; case WL_BUTTON_SCROLL_SECONDARY_UP: - if ( common_scroll_up_pressed(&Slist_start, Slist_size, NUM_SECONDARY_MASK_REGIONS) ) { + if ( common_scroll_up_pressed(&Slist_start, sz2i(Slist.size()), NUM_SECONDARY_MASK_REGIONS) ) { gamesnd_play_iface(InterfaceSounds::SCROLL); } else { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); @@ -620,7 +634,7 @@ void weapon_button_do(int i) break; case WL_BUTTON_SCROLL_SECONDARY_DOWN: - if ( common_scroll_down_pressed(&Slist_start, Slist_size, NUM_SECONDARY_MASK_REGIONS) ) { + if ( common_scroll_down_pressed(&Slist_start, sz2i(Slist.size()), NUM_SECONDARY_MASK_REGIONS) ) { gamesnd_play_iface(InterfaceSounds::SCROLL); } else { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); @@ -1234,6 +1248,19 @@ int eval_weapon_flag_for_game_type(int weapon_flags) return rval; } +/** + * Return the weapon class shown at the given on-screen row of a scrolling icon list, + * or -1 if that row is past the end of the list. + */ +static int wl_get_weapon_class_from_list(const SCP_vector &list, int list_start, int row) +{ + size_t list_index = list_start + row; + if ( list_index >= list.size() ) + return -1; + + return list[list_index]; +} + /** * Go through the possible weapons to choose from, and flag some as disabled since * that ship class cannot use that kind of weapon. The weapon filter is specified @@ -1248,28 +1275,42 @@ void wl_set_disabled_weapons(int ship_class, int bank_index) Assert(ship_class >= 0 && ship_class < ship_info_size()); Assert(bank_index < MAX_SHIP_PRIMARY_BANKS + MAX_SHIP_SECONDARY_BANKS); - Assert( Wl_icons != NULL ); + Assert( (Wl_pool != nullptr) && (Wl_icons != nullptr) && (Wss_slots != nullptr) ); auto sip = &Ship_info[ship_class]; - int i = 0; - for ( auto &wi: Weapon_info ) + // Only weapons with UI entries can be displayed or dragged: the pool weapons, plus any weapon + // currently in a slot's banks (bank weapons are not guaranteed to be pool members, e.g. when a + // script assigns them directly). + SCP_set ui_weapons; + for ( const auto &[weapon_class, count] : *Wl_pool ) { + ui_weapons.insert(weapon_class); + } + for ( int slot = 0; slot < MAX_WSS_SLOTS; slot++ ) { + for ( int weapon_class : Wss_slots[slot].wep ) { + if ( weapon_class >= 0 ) + ui_weapons.insert(weapon_class); + } + } + + for ( int weapon_class : ui_weapons ) { - // Determine whether weapon #i is allowed on this ship class in the current type of mission. + auto &wi = Weapon_info[weapon_class]; + auto &icon = (*Wl_icons)[weapon_class]; + + // Determine whether this weapon is allowed on this ship class in the current type of mission. // As of 9/6/99, the only difference is dogfight missions have a different list of legal weapons. - Wl_icons[i].can_use_for_ship = eval_weapon_flag_for_game_type(sip->allowed_weapons[i]); + icon.can_use_for_ship = eval_weapon_flag_for_game_type(sip->allowed_weapons[weapon_class]); // Also determine whether the weapon can be used on this bank - if ( bank_index < 0 || !Wl_icons[i].can_use_for_ship ) - Wl_icons[i].can_use_for_bank = TriStateBool::UNKNOWN_; + if ( bank_index < 0 || !icon.can_use_for_ship ) + icon.can_use_for_bank = TriStateBool::UNKNOWN_; else if ( (wi.is_primary() && bank_index >= MAX_SHIP_PRIMARY_BANKS) || (wi.is_secondary() && bank_index < MAX_SHIP_PRIMARY_BANKS) ) - Wl_icons[i].can_use_for_bank = TriStateBool::UNKNOWN_; - else if ( !eval_weapon_flag_for_game_type(sip->restricted_loadout_flag[bank_index]) || eval_weapon_flag_for_game_type(sip->allowed_bank_restricted_weapons[bank_index][i]) ) - Wl_icons[i].can_use_for_bank = TriStateBool::TRUE_; + icon.can_use_for_bank = TriStateBool::UNKNOWN_; + else if ( !eval_weapon_flag_for_game_type(sip->restricted_loadout_flag[bank_index]) || eval_weapon_flag_for_game_type(sip->allowed_bank_restricted_weapons[bank_index][weapon_class]) ) + icon.can_use_for_bank = TriStateBool::TRUE_; else - Wl_icons[i].can_use_for_bank = TriStateBool::FALSE_; - - ++i; + icon.can_use_for_bank = TriStateBool::FALSE_; } } @@ -1317,9 +1358,9 @@ void maybe_select_new_weapon(int index) } if ( index < NUM_PRIMARY_MASK_REGIONS ) { - weapon_class = Plist[Plist_start+index]; + weapon_class = wl_get_weapon_class_from_list(Plist, Plist_start, index); } else { - weapon_class = Slist[Slist_start+index-NUM_PRIMARY_MASK_REGIONS]; + weapon_class = wl_get_weapon_class_from_list(Slist, Slist_start, index - NUM_PRIMARY_MASK_REGIONS); } if ( weapon_class >= 0 ) { @@ -1367,14 +1408,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 } } @@ -1399,7 +1438,7 @@ void wl_load_icons(int weapon_class) Assert( Wl_icons != NULL ); - icon = &Wl_icons[weapon_class]; + icon = &(*Wl_icons)[weapon_class]; if (!Use_3d_weapon_icons || (wip->render_type == WRT_LASER && !VALID_FNAME(wip->tech_model))) { @@ -1426,76 +1465,56 @@ void wl_load_icons(int weapon_class) } /** - * Load all the icons for weapons in the pool + * Release the bitmaps and models held by one team's weapon icon map, while leaving the map populated. */ -void wl_load_all_icons() +static void wl_unload_team_icons(SCP_map &icons) { - - int i, j; - - Assert( (Wl_icons != NULL) && (Wl_pool != NULL) ); - - for ( i = 0; i < MAX_WEAPON_TYPES; i++ ) { - // clear out data - generic_anim_init(&Wl_icons[i].animation, NULL); - for ( j = 0; j < NUM_ICON_FRAMES; j++ ) { - Wl_icons[i].icon_bmaps[j] = -1; + for ( auto &[weapon_class, icon] : icons ) { + for ( int &bmap : icon.icon_bmaps ) { + if ( bmap >= 0 ) { + bm_release(bmap); + bmap = -1; + } } - Wl_icons[i].model_index = -1; - Wl_icons[i].laser_bmap = -1; - if ( Wl_pool->value_or(i, 0) > 0 ) { - wl_load_icons(i); + if ( icon.model_index >= 0 ) { + model_unload(icon.model_index); + icon.model_index = -1; + } + if ( icon.laser_bmap >= 0 ) { + bm_unload(icon.laser_bmap); + icon.laser_bmap = -1; } } } /** - * Frees the bitmaps used for weapon icons + * Release and drop every team's weapon icon map */ -void wl_unload_icons() +static void wl_unload_all_teams_icons() { - int i,j; - wl_icon_info *icon; - - Assert( Wl_icons != NULL ); - - for ( i = 0; i < MAX_WEAPON_TYPES; i++ ) { - icon = &Wl_icons[i]; - - for ( j = 0; j < NUM_ICON_FRAMES; j++ ) { - if ( icon->icon_bmaps[j] >= 0 ) { - bm_release(icon->icon_bmaps[j]); - icon->icon_bmaps[j] = -1; - } - } - - if(icon->model_index >= 0) { - model_unload(icon->model_index); - icon->model_index = -1; - } - if(icon->laser_bmap >= 0) { - bm_unload(icon->laser_bmap); - icon->laser_bmap = -1; - } - if(Cur_Anim.num_frames > 0) - generic_anim_unload(&Cur_Anim); + for ( auto &team_icons : Wl_icons_teams ) { + wl_unload_team_icons(team_icons); + team_icons.clear(); } } /** - * init ship-class specific data + * Load all the icons for weapons in the pool */ -void wl_init_ship_class_data() +void wl_load_all_icons() { - int i; - wl_ship_class_info *wl_ship; + Assert( (Wl_icons != nullptr) && (Wl_pool != nullptr) ); + + // drop any icon data from a previous load of this team + wl_unload_team_icons(*Wl_icons); + Wl_icons->clear(); - for ( i = 0; i < ship_info_size(); i++ ) { - wl_ship = &Wl_ships[i]; - wl_ship->overhead_bitmap = -1; - wl_ship->model_num = -1; - generic_anim_init(&wl_ship->animation, NULL); + // note: unlike the ship pool, icons are only loaded for classes with ships remaining + for ( const auto &[weapon_class, count] : *Wl_pool ) { + if ( count > 0 ) { + wl_load_icons(weapon_class); + } } } @@ -1504,26 +1523,31 @@ void wl_init_ship_class_data() */ void wl_free_ship_class_data() { - int i; - wl_ship_class_info *wl_ship; - - for ( i = 0; i < ship_info_size(); i++ ) { - wl_ship = &Wl_ships[i]; - - if ( wl_ship->overhead_bitmap != -1 ) { - bm_release(wl_ship->overhead_bitmap); - wl_ship->overhead_bitmap = -1; + for ( auto &[ship_class, wl_ship] : Wl_ships ) { + if ( wl_ship.overhead_bitmap != -1 ) { + bm_release(wl_ship.overhead_bitmap); + wl_ship.overhead_bitmap = -1; } - if ( wl_ship->model_num != -1 ) { + if ( wl_ship.model_num != -1 ) { // this should unload the model from memory only if it's not used in the mission - model_unload(wl_ship->model_num); - wl_ship->model_num = -1; + model_unload(wl_ship.model_num); + wl_ship.model_num = -1; } - if(wl_ship->animation.num_frames) - generic_anim_unload(&wl_ship->animation); + if(wl_ship.animation.num_frames) + generic_anim_unload(&wl_ship.animation); } + + Wl_ships.clear(); +} + +/** + * init ship-class specific data + */ +void wl_init_ship_class_data() +{ + wl_free_ship_class_data(); } /** @@ -1606,19 +1630,15 @@ void wl_maybe_reset_selected_weapon_class() } // then check for a primary weapon in the pool - for ( i = 0; i < Plist_size; i++ ) { - if ( Plist[i] >= 0 ) { - Selected_wl_class = Plist[i]; - return; - } + if ( !Plist.empty() ) { + Selected_wl_class = Plist.front(); + return; } // finally, if no others found yet, check for a secondary weapon in the pool - for ( i = 0; i < Slist_size; i++ ) { - if ( Slist[i] >= 0 ) { - Selected_wl_class = Slist[i]; - return; - } + if ( !Slist.empty() ) { + Selected_wl_class = Slist.front(); + return; } } @@ -1852,25 +1872,12 @@ void wl_get_default_weapons(int ship_class, int slot_num, int *wep, int *wep_cou */ void wl_add_index_to_list(int wi_index) { - int i; if ( Weapon_info[wi_index].subtype == WP_MISSILE ) { - - for ( i=0; i 0 ) { if ( Weapon_info[weapon_class].subtype == WP_MISSILE ) { - Slist[Slist_size++] = weapon_class; + Slist.push_back(weapon_class); } else { - Plist[Plist_size++] = weapon_class; + Plist.push_back(weapon_class); } } } @@ -2024,8 +2024,8 @@ void wl_init_icon_lists() void wl_set_team_pointers(int team) { Assert( (team >= 0) && (team < MAX_TVT_TEAMS) ); - - Wl_icons = Wl_icons_teams[team]; + + Wl_icons = &Wl_icons_teams[team]; } /** @@ -2034,11 +2034,11 @@ void wl_set_team_pointers(int team) void wl_reset_team_pointers() { Assert( !Weapon_select_open ); - + if ( Weapon_select_open ) return; - - Wl_icons = NULL; + + Wl_icons = nullptr; } /** @@ -2066,7 +2066,7 @@ void weapon_select_close_team() if (Weapon_select_open) return; - wl_unload_icons(); + wl_unload_all_teams_icons(); wl_unload_all_anims(); } @@ -2076,21 +2076,18 @@ void weapon_select_close_team() */ void weapon_select_common_init(bool API_Access) { - int idx; - + // In team-vs-team, initialize every other team first; we always initialize our own team + // last so that the team pointers -- which common_set_team_pointers repoints to whichever + // team was initialized most recently -- are left pointing at our team for the rest of the screen. if(MULTI_TEAM){ - // initialize for all teams - for(idx=0;idxicon_bmaps[WEAPON_ICON_FRAME_SELECTED] != -1) { @@ -2947,7 +2944,7 @@ void weapon_select_do(float frametime) // check so see if this is really a legal weapon to carry away // (don't check can_use_for_bank here because we may drop the weapon on a bank that wasn't previously selected) - if ( !Wl_icons[Carried_wl_icon.weapon_class].can_use_for_ship ) + if ( !(*Wl_icons)[Carried_wl_icon.weapon_class].can_use_for_ship ) { int diffx, diffy; diffx = abs(Carried_wl_icon.from_x-mx); @@ -3027,7 +3024,7 @@ void weapon_select_close() // unload bitmaps bm_release(WeaponSelectMaskBitmap); - wl_unload_icons(); + wl_unload_all_teams_icons(); wl_unload_all_anims(); // ...must be last... wl_free_ship_class_data(); @@ -3068,7 +3065,7 @@ void wl_render_icon_count(int num, int x, int y) /** * Render icon * - * @param index index into Wl_icons[], identifying which weapon to draw + * @param index weapon class, identifying which weapon to draw * @param x x screen position to draw icon at * @param y y screen position to draw icon at * @param num count for weapon @@ -3086,7 +3083,7 @@ void wl_render_icon(int index, int x, int y, int num, int draw_num_flag, int hot if ( Selected_wl_slot == -1 ) return; - icon = &Wl_icons[index]; + icon = &(*Wl_icons)[index]; if ( icon->icon_bmaps[0] == -1 && icon->model_index == -1 && icon->laser_bmap == -1) { wl_load_icons(index); @@ -3237,7 +3234,7 @@ void draw_wl_icon_with_number(int list_count, int weapon_class) Assert( (Wl_icons != NULL) && (Wl_pool != NULL) ); - if ( Wl_icons[weapon_class].can_use_for_ship && Wl_icons[weapon_class].can_use_for_bank != TriStateBool::FALSE_ ) + if ( (*Wl_icons)[weapon_class].can_use_for_ship && (*Wl_icons)[weapon_class].can_use_for_bank != TriStateBool::FALSE_ ) { gr_set_color_fast(&Icon_colors[WEAPON_ICON_FRAME_NORMAL]); } @@ -3255,17 +3252,15 @@ void draw_wl_icon_with_number(int list_count, int weapon_class) */ void draw_wl_icons() { - int i, count; - - count=0; - for ( i = Plist_start; i < Plist_size; i++ ) { + int count=0; + for ( size_t i = Plist_start; i < Plist.size(); ++i ) { draw_wl_icon_with_number(count, Plist[i]); if ( ++count >= NUM_PRIMARY_MASK_REGIONS ) break; } count=0; - for ( i = Slist_start; i < Slist_size; i++ ) { + for ( size_t i = Slist_start; i < Slist.size(); ++i ) { draw_wl_icon_with_number(count+NUM_PRIMARY_MASK_REGIONS, Slist[i]); if ( ++count >= NUM_SECONDARY_MASK_REGIONS ) break; @@ -3296,13 +3291,13 @@ void wl_pick_icon_from_list(int index) } if ( index < NUM_PRIMARY_MASK_REGIONS ) { - weapon_class = Plist[Plist_start+index]; + weapon_class = wl_get_weapon_class_from_list(Plist, Plist_start, index); } else { - weapon_class = Slist[Slist_start+index-NUM_PRIMARY_MASK_REGIONS]; + weapon_class = wl_get_weapon_class_from_list(Slist, Slist_start, index - NUM_PRIMARY_MASK_REGIONS); } // there isn't a weapon there at all! - if ( weapon_class < 0 ) + if ( weapon_class < 0 ) return; Assert( Wl_pool != NULL ); @@ -3350,7 +3345,7 @@ void pick_from_ship_slot(int num) return; } - Assert(Wl_icons[wep[num]].can_use_for_ship); + Assert((*Wl_icons)[wep[num]].can_use_for_ship); // we can't Assert on can_use_for_bank here because we may have clicked on a bank that isn't selected wl_set_carried_icon(num, Selected_wl_slot, wep[num]); @@ -3527,7 +3522,7 @@ void start_weapon_animation(int weapon_class) Weapon_anim_class = weapon_class; - if ( Wl_icons[weapon_class].model_index >= 0 ) + if ( (*Wl_icons)[weapon_class].model_index >= 0 ) return; } 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..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" @@ -228,7 +229,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; @@ -348,30 +349,31 @@ 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++) { + 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 ((!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..1c8e86f9f57 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; inum_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]]++; - } - } - - 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); - } - } - } -} - -void generate_weaponry_usage_list(int team, int *arr) -{ - int i; - - for (i=0; i= 0 && team < MAX_TVT_TEAMS); - - 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 +295,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 +313,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 +430,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 +489,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 +544,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 +612,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 +670,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 +724,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 +773,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 +789,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 +808,6 @@ void player_start_editor::OnCancel() void player_start_editor::OnOK() { int i, idx; - int num_choices; UpdateData(); @@ -811,20 +816,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 +862,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 +931,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 +960,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..084d89f180d 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_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 ((!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,82 +1588,24 @@ void Editor::disband_wing(int wing_num) { missionChanged(); } -void Editor::generate_wing_weaponry_usage_list(int* arr, 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())) { - arr[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); - } - } - } -} -void Editor::generate_team_weaponry_usage_list(int team, int* arr) { - int i; - - for (i = 0; i < MAX_WEAPON_TYPES; i++) { - arr[i] = 0; - } - - 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]); - } - } else { - for (i = 0; i < MAX_STARTING_WINGS; i++) { - generate_wing_weaponry_usage_list(arr, Starting_wings[i]); - } - } -} -void Editor::generate_ship_usage_list(int* arr, int wing) { - int i; - - if (wing < 0) { - return; - } - - i = Wings[wing].wave_count; - while (i--) { - arr[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..8723a59bd5b 100644 --- a/qtfred/src/mission/Editor.h +++ b/qtfred/src/mission/Editor.h @@ -251,13 +251,17 @@ 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); - private slots: void performTimedAutosave(); @@ -282,9 +286,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,10 +333,6 @@ class Editor : public QObject { */ static int find_free_wing(); - void generate_wing_weaponry_usage_list(int* arr, int wing); - - void generate_ship_usage_list(int* arr, 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/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..6951c4cde29 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]; - _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; + SCP_map 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. + 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); } } }