From 77186c72fcc56b89657a9cba46bd9a8f3a201705 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Sun, 2 Aug 2026 03:00:58 -0400 Subject: [PATCH] refactor loadout pools for vectorized ship and weapon classes Convert ship/weapon loadout pools from naive per-class arrays to ordered maps that list loadout entries. Each entry represents a ship/weapon class and its count. An absent entry means the class is not in this mission's loadout. Since these are ordered maps, iteration of the map keys occurs in the same sequence as iteration of `Ship_info` or `Weapon_info`. Includes several bugfixes: - `csg_read_loadout` now clears both pools before reading. Previously the pools were only zeroed in `mission_campaign_init` - `restore_wss_data`: non-transmitted classes are now absent rather than memset to 0 - a copy-paste bug where the weapon pool entries were bounds-checked against MAX_SHIP_CLASSES - `wss_maybe_restore_loadout`: the pool write-back now preserves loadout membership instead of writing a count for every class - `ss_dump_to_list`/`ss_swap_list_slot`: returning a slot ship whose class is not in the pool now makes it available instead of silently losing it. Unreachable in retail but reachable via script - off-by-one errors in the Lua `Ship_Pool`/`Weapon_Pool` indexers Co-Authored-By: Claude Opus 4.7 (1M context) --- code/mission/missioncampaign.cpp | 4 +- code/missionui/missionscreencommon.cpp | 101 ++++++++++++++----------- code/missionui/missionscreencommon.h | 14 ++-- code/missionui/missionshipchoice.cpp | 48 +++++------- code/missionui/missionweaponchoice.cpp | 69 +++++++++-------- code/network/multiteamselect.cpp | 45 +++++------ code/pilotfile/csg.cpp | 20 +++-- code/scripting/api/libs/ui.cpp | 17 +++-- 8 files changed, 165 insertions(+), 153 deletions(-) diff --git a/code/mission/missioncampaign.cpp b/code/mission/missioncampaign.cpp index f4b853d5b09..0101f3daaa3 100644 --- a/code/mission/missioncampaign.cpp +++ b/code/mission/missioncampaign.cpp @@ -731,8 +731,8 @@ void player_loadout_init() memset(Player_loadout.filename, 0, sizeof(Player_loadout.filename)); memset(Player_loadout.last_modified, 0, sizeof(Player_loadout.last_modified)); - Player_loadout.ship_pool.assign(ship_info_size(), 0); - Player_loadout.weapon_pool.assign(weapon_info_size(), 0); + Player_loadout.ship_pool.clear(); + Player_loadout.weapon_pool.clear(); for ( i = 0; i < MAX_WSS_SLOTS; i++ ) { Player_loadout.unit_data[i].ship_class = -1; diff --git a/code/missionui/missionscreencommon.cpp b/code/missionui/missionscreencommon.cpp index fdd987afbc6..f4e66df3ccd 100644 --- a/code/missionui/missionscreencommon.cpp +++ b/code/missionui/missionscreencommon.cpp @@ -91,13 +91,13 @@ loadout_data Player_loadout; // what the ship and weapon loadout is... used sinc //int Wss_num_wings; // number of player wings wss_unit Wss_slots_teams[MAX_TVT_TEAMS][MAX_WSS_SLOTS]; -int Wl_pool_teams[MAX_TVT_TEAMS][MAX_WEAPON_TYPES]; -int Ss_pool_teams[MAX_TVT_TEAMS][MAX_SHIP_CLASSES]; +SCP_map Wl_pool_teams[MAX_TVT_TEAMS]; +SCP_map Ss_pool_teams[MAX_TVT_TEAMS]; int Wss_num_wings_teams[MAX_TVT_TEAMS]; wss_unit *Wss_slots = NULL; -int *Wl_pool = NULL; -int *Ss_pool = NULL; +SCP_map *Wl_pool = nullptr; +SCP_map *Ss_pool = nullptr; int Wss_num_wings; ////////////////////////////////////////////////////////////////// @@ -501,8 +501,8 @@ void common_set_team_pointers(int team) Assert( (team >= 0) && (team < MAX_TVT_TEAMS) ); Wss_slots = Wss_slots_teams[team]; - Ss_pool = Ss_pool_teams[team]; - Wl_pool = Wl_pool_teams[team]; + Ss_pool = &Ss_pool_teams[team]; + Wl_pool = &Wl_pool_teams[team]; ss_set_team_pointers(team); wl_set_team_pointers(team); @@ -517,8 +517,8 @@ void common_reset_team_pointers() // these are done last so that we can make use of the Assert()'s in the above // functions to make sure the screens are exited and this is safe Wss_slots = NULL; - Ss_pool = NULL; - Wl_pool = NULL; + Ss_pool = nullptr; + Wl_pool = nullptr; } // common_select_init() will load in animations and bitmaps that are common to the @@ -1100,14 +1100,10 @@ void wss_save_loadout() Assert( (Ss_pool != NULL) && (Wl_pool != NULL) && (Wss_slots != NULL) ); // save the ship pool - for ( i = 0; i < ship_info_size(); i++ ) { - Player_loadout.ship_pool[i] = Ss_pool[i]; - } + Player_loadout.ship_pool = *Ss_pool; // save the weapons pool - for ( i = 0; i < weapon_info_size(); i++ ) { - Player_loadout.weapon_pool[i] = Wl_pool[i]; - } + Player_loadout.weapon_pool = *Wl_pool; // save the ship class / weapons for each slot for ( i = 0; i < MAX_WSS_SLOTS; i++ ) { @@ -1174,21 +1170,25 @@ void wss_maybe_restore_loadout() // now compare the two, adding in what was left in the pools. If there are less of a ship or weapon class in the mission now // than there were last time, we can't restore and must abort. - for (i = 0; i < ship_info_size(); i++) { - if (Ss_pool[i] >= 1) { - this_loadout_ships[i] += Ss_pool[i]; + for (const auto &[ship_class, count] : *Ss_pool) { + if (count >= 1) { + this_loadout_ships[ship_class] += count; } + } + for (i = 0; i < ship_info_size(); i++) { if ( this_loadout_ships[i] < last_loadout_ships[i]) { - return; + return; } } - - for (i = 0; i < weapon_info_size(); i++) { - if (Wl_pool[i] >= 1) { - this_loadout_weapons[i] += Wl_pool[i]; + + for (const auto &[weapon_class, count] : *Wl_pool) { + if (count >= 1) { + this_loadout_weapons[weapon_class] += count; } + } + for (i = 0; i < weapon_info_size(); i++) { if ( this_loadout_weapons[i] < last_loadout_weapons[i]) { - return; + return; } } @@ -1215,14 +1215,25 @@ void wss_maybe_restore_loadout() } } - // restore the ship pool + // restore the ship pool. Update counts for classes already in the loadout, then add any class the + // previous runthrough used that isn't in it (two passes so we don't iterate over mid-loop inserts). + for (auto &[ship_class, count] : *Ss_pool) { + count = this_loadout_ships[ship_class]; + } for ( i = 0; i < ship_info_size(); i++ ) { - Ss_pool[i] = this_loadout_ships[i]; + if (this_loadout_ships[i] > 0 && !Ss_pool->contains(i)) { + (*Ss_pool)[i] = this_loadout_ships[i]; + } } // restore the weapons pool + for (auto &[weapon_class, count] : *Wl_pool) { + count = this_loadout_weapons[weapon_class]; + } for ( i = 0; i < weapon_info_size(); i++ ) { - Wl_pool[i] = this_loadout_weapons[i]; + if (this_loadout_weapons[i] > 0 && !Wl_pool->contains(i)) { + (*Wl_pool)[i] = this_loadout_weapons[i]; + } } } @@ -1381,10 +1392,10 @@ int store_wss_data(ubyte *data, __UNUSED const unsigned int max_size, interface_ if ( !(Game_mode & GM_MULTIPLAYER) ) return 0; - // write the ship pool + // write the ship pool (only positive counts; the pool map can also hold exhausted 0-count entries) pool_size = 0; - for (i = 0; i < ship_info_size(); i++) { - if (Ss_pool[i] > 0) { + for (const auto &[ship_class, count] : *Ss_pool) { + if (count > 0) { ++pool_size; } } @@ -1393,17 +1404,17 @@ int store_wss_data(ubyte *data, __UNUSED const unsigned int max_size, interface_ Assertion((((sizeof(short)+sizeof(short)) * pool_size) + packet_size) < max_size, "Size of ship pool exceeds max data size!"); - for (i = 0; i < ship_info_size(); i++) { - if (Ss_pool[i] > 0) { - ADD_SHORT(static_cast(i)); - ADD_SHORT(static_cast(Ss_pool[i])); + for (const auto &[ship_class, count] : *Ss_pool) { + if (count > 0) { + ADD_SHORT(static_cast(ship_class)); + ADD_SHORT(static_cast(count)); } } // write the weapon pool pool_size = 0; - for (i = 0; i < weapon_info_size(); i++) { - if (Wl_pool[i] > 0) { + for (const auto &[weapon_class, count] : *Wl_pool) { + if (count > 0) { ++pool_size; } } @@ -1412,10 +1423,10 @@ int store_wss_data(ubyte *data, __UNUSED const unsigned int max_size, interface_ Assertion((((sizeof(short)+sizeof(short)) * pool_size) + packet_size) < max_size, "Size of weapon pool exceeds max data size!"); - for (i = 0; i < weapon_info_size(); i++) { - if (Wl_pool[i] > 0) { - ADD_SHORT(static_cast(i)); - ADD_SHORT(static_cast(Wl_pool[i])); + for (const auto &[weapon_class, count] : *Wl_pool) { + if (count > 0) { + ADD_SHORT(static_cast(weapon_class)); + ADD_SHORT(static_cast(count)); } } @@ -1470,28 +1481,28 @@ int restore_wss_data(ubyte *data) return 0; // restore ship pool - memset(Ss_pool, 0, MAX_SHIP_CLASSES*sizeof(int)); + Ss_pool->clear(); GET_USHORT(pool_size); for (i = 0; i < pool_size; i++) { GET_SHORT(b1); GET_SHORT(b2); - if (b1 < MAX_SHIP_CLASSES) { - Ss_pool[b1] = b2; + if (Ship_info.in_bounds(b1)) { + (*Ss_pool)[b1] = b2; } } // restore weapons pool - memset(Wl_pool, 0, MAX_WEAPON_TYPES*sizeof(int)); + Wl_pool->clear(); GET_USHORT(pool_size); for (i = 0; i < pool_size; i++) { GET_SHORT(b1); GET_SHORT(b2); - if (b1 < MAX_SHIP_CLASSES) { - Wl_pool[b1] = b2; + if (Weapon_info.in_bounds(b1)) { + (*Wl_pool)[b1] = b2; } } diff --git a/code/missionui/missionscreencommon.h b/code/missionui/missionscreencommon.h index 28fac68c9ae..ed84fc7f0bf 100644 --- a/code/missionui/missionscreencommon.h +++ b/code/missionui/missionscreencommon.h @@ -190,14 +190,16 @@ extern int Wss_num_wings_teams[MAX_TVT_TEAMS]; ////////////////////////////////////////////// // Weapon pool ////////////////////////////////////////////// -extern int Wl_pool_teams[MAX_TVT_TEAMS][MAX_WEAPON_TYPES]; -extern int *Wl_pool; +// weapon class index -> count remaining; an absent entry means the class is not in this mission's loadout +extern SCP_map Wl_pool_teams[MAX_TVT_TEAMS]; +extern SCP_map *Wl_pool; ////////////////////////////////////////////// // Ship pool ////////////////////////////////////////////// -extern int Ss_pool_teams[MAX_TVT_TEAMS][MAX_SHIP_CLASSES]; -extern int *Ss_pool; +// ship class index -> count remaining; an absent entry means the class is not in this mission's loadout +extern SCP_map Ss_pool_teams[MAX_TVT_TEAMS]; +extern SCP_map *Ss_pool; ////////////////////////////////////////////// // Saving loadout @@ -207,8 +209,8 @@ typedef struct loadout_data char filename[MAX_FILENAME_LEN]; // mission filename char last_modified[DATE_TIME_LENGTH]; // when mission was last modified wss_unit unit_data[MAX_WSS_SLOTS]; // ship and weapon data - SCP_vector weapon_pool; // available weapons - SCP_vector ship_pool; // available ships + SCP_map weapon_pool; // available weapons: class index -> count; absent serialized as 0 + SCP_map ship_pool; // available ships: class index -> count; absent serialized as -1 (Ss_pool's not-in-loadout sentinel) } loadout_data; extern loadout_data Player_loadout; diff --git a/code/missionui/missionshipchoice.cpp b/code/missionui/missionshipchoice.cpp index 197fd027b21..a3b90c3dcd4 100644 --- a/code/missionui/missionshipchoice.cpp +++ b/code/missionui/missionshipchoice.cpp @@ -482,7 +482,6 @@ void active_list_remove(int ship_class) // can choose from. void init_active_list() { - int i; ss_active_item *sai; Assert( Ss_pool != NULL ); @@ -490,11 +489,11 @@ void init_active_list() clear_active_list(); // build the active list - for ( i = 0; i < ship_info_size(); i++ ) { - if ( Ss_pool[i] > 0 ) { + for ( const auto &[ship_class, count] : *Ss_pool ) { + if ( count > 0 ) { sai = get_free_active_list_node(); if ( sai != NULL ) { - sai->ship_class = i; + sai->ship_class = ship_class; list_append(&SS_active_head, sai); SS_active_list_size++; } @@ -1680,7 +1679,7 @@ void draw_ship_icon_with_number(int screen_offset, int ship_class) } } - if ( Ss_pool[ship_class] <= 0 ) { + if ( Ss_pool->value_or(ship_class, -1) <= 0 ) { return; } @@ -1713,7 +1712,7 @@ void draw_ship_icon_with_number(int screen_offset, int ship_class) } // blit the number - sprintf(buf, "%d", Ss_pool[ship_class] ); + sprintf(buf, "%d", Ss_pool->value_or(ship_class, -1) ); gr_set_color_fast(&Color_white); gr_string(num_x, num_y, buf, GR_RESIZE_MENU); } @@ -2058,14 +2057,13 @@ int pick_from_ship_list(int screen_offset, int ship_class) if ( ss_icon_being_carried() ) return rval; - if ( Ss_pool[ship_class] > 0 ) { + if ( Ss_pool->value_or(ship_class, -1) > 0 ) { int mouse_x, mouse_y; ss_set_carried_icon(-1, ship_class); mouse_get_pos_unscaled( &mouse_x, &mouse_y ); Ss_delta_x = Ship_list_coords[gr_screen.res][screen_offset][0] - mouse_x; Ss_delta_y = Ship_list_coords[gr_screen.res][screen_offset][1] - mouse_y; - Assert( Ss_pool[ship_class] >= 0 ); rval = 0; } @@ -2778,9 +2776,9 @@ void ss_reset_selected_ship() } // get the first ship class found in the pool - for ( i = 0; i < ship_info_size(); i++ ) { - if ( Ss_pool[i] > 0 ) { - Selected_ss_class = i; + for ( const auto &[ship_class, count] : *Ss_pool ) { + if ( count > 0 ) { + Selected_ss_class = ship_class; return; } } @@ -2886,16 +2884,12 @@ void ss_init_pool(team_data *pteam) Assert( Ss_pool != NULL ); - for ( i = 0; i < MAX_SHIP_CLASSES; i++ ) { - Ss_pool[i] = -1; - } + 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++ ) { - if (Ss_pool[pteam->ship_list[i]] == -1) { - Ss_pool[pteam->ship_list[i]] = 0; - } - Ss_pool[pteam->ship_list[i]] += pteam->ship_count[i]; + (*Ss_pool)[pteam->ship_list[i]] += pteam->ship_count[i]; } } @@ -2947,7 +2941,7 @@ void ss_load_all_icons() } Ss_icons[i].model_index = -1; - if ( Ss_pool[i] >= 0 ) { + if ( Ss_pool->contains(i) ) { ss_load_icons(i); } } @@ -3394,13 +3388,13 @@ int ss_dump_to_list(int from_slot, int to_list, interface_snd_id *sound) } // put ship back in list - Ss_pool[to_list]++; // return to list + (*Ss_pool)[to_list]++; // return to list slot->ship_class = -1; // remove from slot // put weapons back in list for ( i = 0; i < MAX_SHIP_WEAPONS; i++ ) { if ( (slot->wep[i] >= 0) && (slot->wep_count[i] > 0) ) { - Wl_pool[slot->wep[i]] += slot->wep_count[i]; + (*Wl_pool)[slot->wep[i]] += slot->wep_count[i]; slot->wep[i] = -1; slot->wep_count[i] = 0; } @@ -3421,7 +3415,7 @@ int ss_grab_from_list(int from_list, int to_slot, interface_snd_id *sound) slot = &Wss_slots[to_slot]; // ensure that pool has ship - if ( Ss_pool[from_list] <= 0 ) + if ( Ss_pool->value_or(from_list, -1) <= 0 ) { *sound=InterfaceSounds::ICON_DROP; return 0; @@ -3430,7 +3424,7 @@ int ss_grab_from_list(int from_list, int to_slot, interface_snd_id *sound) Assert(slot->ship_class < 0 ); // slot should be empty // take ship from list->slot - Ss_pool[from_list]--; + (*Ss_pool)[from_list]--; slot->ship_class = from_list; // take weapons from list->slot @@ -3455,7 +3449,7 @@ int ss_swap_list_slot(int from_list, int to_slot, interface_snd_id *sound) Assert( (Ss_pool != NULL) && (Wl_pool != NULL) && (Wss_slots != NULL) ); // ensure that pool has ship - if ( Ss_pool[from_list] <= 0 ) + if ( Ss_pool->value_or(from_list, -1) <= 0 ) { *sound=InterfaceSounds::ICON_DROP; return 0; @@ -3465,21 +3459,21 @@ int ss_swap_list_slot(int from_list, int to_slot, interface_snd_id *sound) Assert(slot->ship_class >= 0 ); // slot should be filled // put ship from slot->list - Ss_pool[Wss_slots[to_slot].ship_class]++; + (*Ss_pool)[Wss_slots[to_slot].ship_class]++; // put weapons from slot->list for ( i = 0; i < MAX_SHIP_WEAPONS; i++ ) { if ( (slot->wep[i] >= 0) && (slot->wep_count[i] > 0) ) { - Wl_pool[slot->wep[i]] += slot->wep_count[i]; + (*Wl_pool)[slot->wep[i]] += slot->wep_count[i]; slot->wep[i] = -1; slot->wep_count[i] = 0; } } // take ship from list->slot - Ss_pool[from_list]--; + (*Ss_pool)[from_list]--; slot->ship_class = from_list; // take weapons from list->slot diff --git a/code/missionui/missionweaponchoice.cpp b/code/missionui/missionweaponchoice.cpp index 4b857ee01bb..4c0a5ff760d 100644 --- a/code/missionui/missionweaponchoice.cpp +++ b/code/missionui/missionweaponchoice.cpp @@ -1371,12 +1371,10 @@ void wl_init_pool(team_data *td) Assert( Wl_pool != NULL ); - for ( i = 0; i < MAX_WEAPON_TYPES; i++ ) { - Wl_pool[i] = 0; - } + 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 + (*Wl_pool)[td->weaponry_pool[i]] += td->weaponry_count[i]; // read from mission } } @@ -1446,7 +1444,7 @@ void wl_load_all_icons() Wl_icons[i].model_index = -1; Wl_icons[i].laser_bmap = -1; - if ( Wl_pool[i] > 0 ) { + if ( Wl_pool->value_or(i, 0) > 0 ) { wl_load_icons(i); } } @@ -1889,8 +1887,9 @@ void wl_remove_weps_from_pool(int *wep, int *wep_count, int ship_class) for ( bank = 0; bank < MAX_SHIP_WEAPONS; bank++ ) { wi_index = wep[bank]; if ( wi_index >= 0 ) { - if ( (wep_count[bank] > 0) && ((Wl_pool[wi_index] - wep_count[bank]) >= 0) ) { - Wl_pool[wi_index] -= wep_count[bank]; + int pool_count = Wl_pool->value_or(wi_index, 0); + if ( (wep_count[bank] > 0) && ((pool_count - wep_count[bank]) >= 0) ) { + (*Wl_pool)[wi_index] -= wep_count[bank]; } else { // not enough weapons in pool // TEMP HACK: FRED doesn't fill in a weapons pool if there are no starting wings... so @@ -1899,12 +1898,12 @@ void wl_remove_weps_from_pool(int *wep, int *wep_count, int ship_class) wl_add_index_to_list(wi_index); } else { - if ( (Wl_pool[wi_index] <= 0) || (wep_count[bank] == 0) ) { + if ( (pool_count <= 0) || (wep_count[bank] == 0) ) { // fresh out of this weapon, pick an alternate pool weapon if we can for (const auto &new_index : Player_weapon_precedence) { Assertion(new_index >= 0, "Somehow, a negative index (%d) got into Player_weapon_precedence; this should not happen. Get a coder!", new_index); - if ( Wl_pool[new_index] <= 0 ) { + if ( Wl_pool->value_or(new_index, 0) <= 0 ) { continue; } @@ -1943,10 +1942,14 @@ void wl_remove_weps_from_pool(int *wep, int *wep_count, int ship_class) new_wep_count = wl_calc_missile_fit(wi_index, si.secondary_bank_ammo_capacity[secondary_bank_index]); } - wep_count[bank] = MIN(new_wep_count, Wl_pool[wi_index]); + // re-read the count, since the precedence loop may have picked a different weapon + pool_count = Wl_pool->value_or(wi_index, 0); + wep_count[bank] = MIN(new_wep_count, pool_count); Assert(wep_count[bank] >= 0); - Wl_pool[wi_index] -= wep_count[bank]; - if ( wep_count[bank] <= 0 ) { + if ( wep_count[bank] > 0 ) { + (*Wl_pool)[wi_index] -= wep_count[bank]; + } else { + // nothing to take wep[bank] = -1; } } @@ -2004,12 +2007,12 @@ void wl_init_icon_lists() Slist[i] = -1; } - for ( i = 0; i < weapon_info_size(); i++ ) { - if ( Wl_pool[i] > 0 ) { - if ( Weapon_info[i].subtype == WP_MISSILE ) { - Slist[Slist_size++] = i; + for ( const auto &[weapon_class, count] : *Wl_pool ) { + if ( count > 0 ) { + if ( Weapon_info[weapon_class].subtype == WP_MISSILE ) { + Slist[Slist_size++] = weapon_class; } else { - Plist[Plist_size++] = i; + Plist[Plist_size++] = weapon_class; } } } @@ -3244,7 +3247,7 @@ void draw_wl_icon_with_number(int list_count, int weapon_class) } wl_render_icon(weapon_class, Wl_weapon_icon_coords[gr_screen.res][list_count][0], Wl_weapon_icon_coords[gr_screen.res][list_count][1], - Wl_pool[weapon_class], 1, list_count, -1, weapon_class); + Wl_pool->value_or(weapon_class, 0), 1, list_count, -1, weapon_class); } /** @@ -3305,7 +3308,7 @@ void wl_pick_icon_from_list(int index) Assert( Wl_pool != NULL ); // no weapons left of that class - if ( Wl_pool[weapon_class] <= 0 ) { + if ( Wl_pool->value_or(weapon_class, 0) <= 0 ) { return; } @@ -3653,7 +3656,7 @@ void wl_saturate_bank(int ship_slot, int bank) slot->wep_count[bank] -= overflow; // add overflow back to pool - Wl_pool[slot->wep[bank]] += overflow; + (*Wl_pool)[slot->wep[bank]] += overflow; } } @@ -3726,7 +3729,7 @@ int wl_swap_slot_slot(int from_bank, int to_bank, int ship_slot, interface_snd_i // so return the "to" to the list and just move the "from" // put to_bank back into list - Wl_pool[slot->wep[to_bank]] += slot->wep_count[to_bank]; // return to list + (*Wl_pool)[slot->wep[to_bank]] += slot->wep_count[to_bank]; // return to list slot->wep[to_bank] = -1; // remove from slot slot->wep_count[to_bank] = 0; *sound=InterfaceSounds::ICON_DROP; // unless it changes later @@ -3737,7 +3740,7 @@ int wl_swap_slot_slot(int from_bank, int to_bank, int ship_slot, interface_snd_i if ( class_mismatch_flag ) { // put from_bank back into list - Wl_pool[slot->wep[from_bank]] += slot->wep_count[from_bank]; // return to list + (*Wl_pool)[slot->wep[from_bank]] += slot->wep_count[from_bank]; // return to list slot->wep[from_bank] = -1; // remove from slot slot->wep_count[from_bank] = 0; *sound=InterfaceSounds::ICON_DROP; @@ -3812,7 +3815,7 @@ int wl_dump_to_list(int from_bank, int to_list, int ship_slot, interface_snd_id } // put weapon bank to the list - Wl_pool[to_list] += slot->wep_count[from_bank]; // return to list + (*Wl_pool)[to_list] += slot->wep_count[from_bank]; // return to list slot->wep[from_bank] = -1; // remove from slot slot->wep_count[from_bank] = 0; *sound=InterfaceSounds::ICON_DROP; @@ -3852,7 +3855,7 @@ int wl_grab_from_list(int from_list, int to_bank, int ship_slot, interface_snd_i Assert(slot->wep[to_bank] < 0); // ensure that pool has weapon - if ( Wl_pool[from_list] <= 0 ) { + if ( Wl_pool->value_or(from_list, 0) <= 0 ) { return 0; } @@ -3885,11 +3888,12 @@ int wl_grab_from_list(int from_list, int to_bank, int ship_slot, interface_snd_i } // take weapon from list - if ( Wl_pool[from_list] < max_fit ) { - max_fit = Wl_pool[from_list]; + int pool_count = Wl_pool->value_or(from_list, 0); + if ( pool_count < max_fit ) { + max_fit = pool_count; update=2; } - Wl_pool[from_list] -= max_fit; + (*Wl_pool)[from_list] -= max_fit; // put on the slot slot->wep[to_bank] = from_list; @@ -3929,7 +3933,7 @@ int wl_swap_list_slot(int from_list, int to_bank, int ship_slot, interface_snd_i Assert(slot->wep[to_bank] >= 0); // ensure that pool has weapon - if ( Wl_pool[from_list] <= 0 ) { + if ( Wl_pool->value_or(from_list, 0) <= 0 ) { return 0; } @@ -3952,7 +3956,7 @@ int wl_swap_list_slot(int from_list, int to_bank, int ship_slot, interface_snd_i } // dump slot weapon back into list - Wl_pool[slot->wep[to_bank]] += slot->wep_count[to_bank]; + (*Wl_pool)[slot->wep[to_bank]] += slot->wep_count[to_bank]; slot->wep_count[to_bank] = 0; slot->wep[to_bank] = -1; @@ -3966,10 +3970,11 @@ int wl_swap_list_slot(int from_list, int to_bank, int ship_slot, interface_snd_i } // take weapon from list - if ( Wl_pool[from_list] < max_fit ) { - max_fit = Wl_pool[from_list]; + int pool_count = Wl_pool->value_or(from_list, 0); + if ( pool_count < max_fit ) { + max_fit = pool_count; } - Wl_pool[from_list] -= max_fit; + (*Wl_pool)[from_list] -= max_fit; // put on the slot slot->wep[to_bank] = from_list; diff --git a/code/network/multiteamselect.cpp b/code/network/multiteamselect.cpp index 9c9e188acde..e1a3f87b627 100644 --- a/code/network/multiteamselect.cpp +++ b/code/network/multiteamselect.cpp @@ -784,8 +784,8 @@ void multi_ts_sync_interface() // item 1 - determine how many ship types are available in the ship pool Multi_ts_avail_count = 0; - for(idx = 0; idx < ship_info_size(); idx++) { - if(Ss_pool[idx] > 0){ + for(const auto &[ship_class, count] : *Ss_pool) { + if(count > 0){ Multi_ts_avail_count++; } } @@ -1307,23 +1307,23 @@ void multi_ts_blit_wing_callsigns() // blit the ships on the avail list void multi_ts_blit_avail_ships() { - int display_count,ship_count,idx; + int display_count,ship_count; char count[6]; // blit the availability of all ship counts display_count = 0; ship_count = 0; - for(idx = 0; idx < ship_info_size(); idx++) { - if(Ss_pool[idx] > 0){ + for(const auto &[ship_class, pool_count] : *Ss_pool) { + if(pool_count > 0){ // if our starting display index is after this, then skip it if(ship_count < Multi_ts_avail_start){ ship_count++; } else { - // blit the icon - ss_blit_ship_icon(Multi_ts_avail_coords[display_count][gr_screen.res][MULTI_TS_X_COORD],Multi_ts_avail_coords[display_count][gr_screen.res][MULTI_TS_Y_COORD],idx,multi_ts_avail_bmap_num(display_count)); + // blit the icon + ss_blit_ship_icon(Multi_ts_avail_coords[display_count][gr_screen.res][MULTI_TS_X_COORD],Multi_ts_avail_coords[display_count][gr_screen.res][MULTI_TS_Y_COORD],ship_class,multi_ts_avail_bmap_num(display_count)); // blit the ship count available - sprintf(count,"%d",Ss_pool[idx]); + sprintf(count,"%d",pool_count); gr_set_color_fast(&Color_normal); gr_string(Multi_ts_avail_coords[display_count][gr_screen.res][MULTI_TS_X_COORD] - 20,Multi_ts_avail_coords[display_count][gr_screen.res][MULTI_TS_Y_COORD],count,GR_RESIZE_MENU); @@ -1820,7 +1820,7 @@ void multi_ts_handle_mouse() if(ship_class == -1){ region_empty = 1; } else { - region_empty = (Ss_pool[ship_class] > 0) ? 0 : 1; + region_empty = (Ss_pool->value_or(ship_class, -1) > 0) ? 0 : 1; } break; case MULTI_TS_SLOT_LIST: @@ -1984,7 +1984,7 @@ int multi_ts_can_perform(int from_type,int from_index,int to_type,int to_index,i switch(op_type){ case TS_GRAB_FROM_LIST: // if there are no more of this ship class, its no go - if(Ss_pool_teams[pl->p_info.team][ship_class] <= 0){ + if(Ss_pool_teams[pl->p_info.team].value_or(ship_class, -1) <= 0){ return 0; } @@ -2001,7 +2001,7 @@ int multi_ts_can_perform(int from_type,int from_index,int to_type,int to_index,i case TS_SWAP_LIST_SLOT: // if there are no more of this ship class, its no go - if(Ss_pool_teams[pl->p_info.team][ship_class] <= 0){ + if(Ss_pool_teams[pl->p_info.team].value_or(ship_class, -1) <= 0){ return 0; } @@ -2388,22 +2388,17 @@ int multi_ts_move_player(int from_index,int to_index,interface_snd_id *sound,int // get the ship class of the current index in the avail list or -1 if none exists int multi_ts_get_avail_ship_class(int index) { - int ship_count,class_index; - - ship_count = index + Multi_ts_avail_start; - class_index = 0; - while((ship_count >= 0) && (class_index < ship_info_size())){ - if(Ss_pool[class_index] > 0){ + int ship_count = index + Multi_ts_avail_start; + + // find the Nth class with ships still available (the map iterates in ascending class + // order, which is the same order the avail list is rendered in) + for(const auto &[ship_class, count] : *Ss_pool){ + if(count > 0){ + if(ship_count == 0){ + return ship_class; + } ship_count--; } - - if(ship_count >= 0){ - class_index++; - } - } - - if(ship_count < 0){ - return class_index; } return -1; diff --git a/code/pilotfile/csg.cpp b/code/pilotfile/csg.cpp index 6f8a0f8af5b..c4f9ca62b04 100644 --- a/code/pilotfile/csg.cpp +++ b/code/pilotfile/csg.cpp @@ -567,22 +567,26 @@ void pilotfile::csg_read_loadout() cfread_string_len(Player_loadout.filename, MAX_FILENAME_LEN, cfp); cfread_string_len(Player_loadout.last_modified, DATE_TIME_LENGTH, cfp); - // ship pool + // clear out any values from a previously loaded CSG + Player_loadout.ship_pool.clear(); + Player_loadout.weapon_pool.clear(); + + // ship pool (-1 means the class is not in the loadout, which is the same as absent) list_size = ship_list.size(); for (idx = 0; idx < list_size; idx++) { count = cfread_int(cfp); - if (ship_list[idx].index >= 0) { + if (ship_list[idx].index >= 0 && count != -1) { Player_loadout.ship_pool[ship_list[idx].index] = count; } } - // weapon pool + // weapon pool (0 means the class is not in the loadout, which is the same as absent) list_size = weapon_list.size(); for (idx = 0; idx < list_size; idx++) { count = cfread_int(cfp); - if (weapon_list[idx].index >= 0) { + if (weapon_list[idx].index >= 0 && count != 0) { Player_loadout.weapon_pool[weapon_list[idx].index] = count; } } @@ -677,14 +681,14 @@ void pilotfile::csg_write_loadout() cfwrite_string_len(Player_loadout.filename, cfp); cfwrite_string_len(Player_loadout.last_modified, cfp); - // ship pool + // ship pool (absent classes are not in the loadout, i.e. -1) for (idx = 0; idx < ship_info_size(); idx++) { - cfwrite_int(Player_loadout.ship_pool[idx], cfp); + cfwrite_int(Player_loadout.ship_pool.value_or(idx, -1), cfp); } - // weapon pool + // weapon pool (absent classes are not in the loadout, i.e. 0) for (idx = 0; idx < weapon_info_size(); idx++) { - cfwrite_int(Player_loadout.weapon_pool[idx], cfp); + cfwrite_int(Player_loadout.weapon_pool.value_or(idx, 0), cfp); } // play ship loadout diff --git a/code/scripting/api/libs/ui.cpp b/code/scripting/api/libs/ui.cpp index 5f97d18499b..25da4f575dc 100644 --- a/code/scripting/api/libs/ui.cpp +++ b/code/scripting/api/libs/ui.cpp @@ -1521,7 +1521,7 @@ ADE_INDEXER(l_Ship_Pool, if (!ade_get_args(L, "*i|i", &idx, &amount)) return ADE_RETURN_NIL; - if (idx < 0 || idx > ship_info_size()) { + if (idx < 1 || idx > ship_info_size()) { return ADE_RETURN_NIL; }; @@ -1533,13 +1533,14 @@ ADE_INDEXER(l_Ship_Pool, return ADE_RETURN_NIL; } if (amount < 0) { - Ss_pool[idx] = 0; + (*Ss_pool)[idx] = 0; } else { - Ss_pool[idx] = amount; + (*Ss_pool)[idx] = amount; } } - return ade_set_args(L, "i", Ss_pool[idx]); + // an absent entry means the class is not in this mission's loadout, which scripts see as -1 + return ade_set_args(L, "i", Ss_pool->value_or(idx, -1)); } ADE_FUNC(__len, l_Ship_Pool, nullptr, "The number of ship classes in the pool", "number", "The number of ship classes.") @@ -1559,7 +1560,7 @@ ADE_INDEXER(l_Weapon_Pool, if (!ade_get_args(L, "*i|i", &idx, &amount)) return ADE_RETURN_NIL; - if (idx < 0 || idx > weapon_info_size()) { + if (idx < 1 || idx > weapon_info_size()) { return ADE_RETURN_NIL; }; @@ -1571,13 +1572,13 @@ ADE_INDEXER(l_Weapon_Pool, return ADE_RETURN_NIL; } if (amount < 0) { - Wl_pool[idx] = 0; + (*Wl_pool)[idx] = 0; } else { - Wl_pool[idx] = amount; + (*Wl_pool)[idx] = amount; } } - return ade_set_args(L, "i", Wl_pool[idx]); + return ade_set_args(L, "i", Wl_pool->value_or(idx, 0)); } ADE_FUNC(__len,