* take a model reference when a ship changes class
ship_model_change() only loaded the model when the class had none, so a ship
switched to a class whose model had been loaded only by a select-screen
preview adopted that preview's single reference. Closing the screen then
freed the model from under the ship, and the first simulation frame asserted
in model_get(). This is what happened on a replayed mission with a restored
loadout: ship select was skipped, the weapon-select overhead loaded the new
class once, the commit switched the player to it, and the screen close
released it.
Load unconditionally, as ship_create() already does, so the ship holds its own
reference.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
* reject stale model ids in model_unload and release select-screen handles at level close
model_unload only used the slot part of the id it was given, so a handle kept
across missions would free whatever model had since been loaded into that slot.
Validate the id against the slot's occupant and warn instead.
Two holders could present such handles: the weapon-select icon models under a
scripted UI that overrides the state hooks (the native close never runs), and
the ship-select 3D model global, which was never reset between missions.
Release the select-screen handles from game_level_close, and unload the
ship-select model on screen close and on every selection change.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
* consolidate the select-screen resource release
The weapon side released its icons, anims, and ship class data from two
functions keyed on whether the screen was open, and the level-close path added
a third copy. Give the weapon side the same shape as the ship side: one
unconditional weapon_select_common_close(), with weapon_select_close() left as
pure UI teardown. common_select_level_close() now just calls the two common
closes, and common_select_close() calls it in turn. The ship-select 3D model
is released from ship_select_common_close() so both paths cover it.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
---------
Co-authored-by: Claude Fable 5.1 <[email protected]>
Fix bitmap and model handle leakage in the ship/weapon select screens, and make release symmetric with load:
* Initialize each team only once in `ship_select_common_init`
* Unload every team's map in `ss_unload_team_icons`, not just the active one.
* Add a per-team unload helper and use it in the load-all functions
Also drop the stray per-icon Cur_Anim unload, and call `wl_free_ship_class_data` before loading in `wl_init_ship_class_data`.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Convert the mission ship/weapon select screens' per-class UI state from dense MAX_SHIP_CLASSES / MAX_WEAPON_TYPES arrays to sparse containers keyed by loadout-pool membership, matching the Ss_pool / Wl_pool maps they shadow:
ss_icon_info Ss_icons_teams[][MAX_SHIP_CLASSES] -> SCP_map<int, ss_icon_info>[]
wl_icon_info Wl_icons_teams[][MAX_WEAPON_TYPES] -> SCP_map<int, wl_icon_info>[]
wl_ship_class_info Wl_ships[MAX_SHIP_CLASSES] -> SCP_map<int, wl_ship_class_info>
int Plist[] / Slist[] + manual size counters -> SCP_vector<int>
The ship-select active list is replaced outright by a vector of ship classes rebuilt on each interface sync, plus the existing scroll offset. active_list_remove() was dead code and is deleted.
Behavior fixes required by the conversion:
* wl_set_disabled_weapons now computes usability flags for the pool
weapons plus every weapon currently in a slot's banks, instead of all
weapon classes. (Bank weapons are not guaranteed pool members.)
* The two mouse-region handlers that read Plist[Plist_start+index] for
raw indices 0..3 now bounds-check against the list size (as does
ss_get_ship_class_from_list).
* wl_unload_icons and the ss unloaders early-return on a null team
pointer instead of asserting and dereferencing.
The ship side loads icons for every pool class including exhausted 0-count entries while the weapon side loads only positive counts, preserving the existing asymmetry.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
team_data was the last persistence-adjacent structure sized by MAX_SHIP_CLASSES: four parallel ship arrays plus four parallel weapon arrays (one slot per mission-file loadout line, holding a class or a sexp variable name, and a count or a sexp variable name) with manual num_*_choices counters, plus a dense per-class weapon_required flag array. Convert all of it to the sparse-container family:
ship_list / ship_list_variables / ship_count / ship_count_variables
-> SCP_vector<loadout_entry> ship_choices
weaponry_pool / weaponry_count / weaponry_pool_variable /
weaponry_amount_variable
-> SCP_vector<loadout_entry> weapon_choices
bool weapon_required[MAX_WEAPON_TYPES]
-> SCP_set<int> required_weapons
where loadout_entry is {class_index, count, class_variable, count_variable}, with empty variable strings meaning the value was given literally. num_ship_choices / num_weapon_choices become .size().
Intentional behavior fixes, all previously latent memory errors or
data bugs:
* is_ship_assignable stored an alt ship CLASS in a variable used as a
loadout list INDEX when default_to_this_class was false, then
swapped to ship_list[class] - garbage for any mission where the two
domains diverge. It now swaps to the alt class itself, per the
evident intent.
* The default-ship fallback read ship_list[0] even when a team had
zero ship choices; it now leaves default_ship at -1, and
ss_fixup_team_data guards its append of the default ship accordingly.
* FRED2's dumpstats indexed Ships[] with a ship class when printing
loadout entries; it now prints the entry's class or variable name
directly.
* Team parse now clears the previous mission's entries explicitly
rather than relying on overwrite-in-place.
This clears the largest remaining cluster of MAX_SHIP_CLASSES references on the way to lifting the ship class limit.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
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) <[email protected]>
* Fix C4709 Warnings on latest VS
* more
* suppress false positive
* Switch to using any/none_of
* Address Feedback
* Fix Line endings?
* Fix commits and strip out flag_combinator
* more fixes
* nolint
* fix stale color_to_draw causing draw_model_icon crash with model_id of -1
In draw_wing_block(), color_to_draw was initialized once outside the
slot loop while bitmap_to_draw was reset every iteration. Once any
slot in the wing took the model-icon path, color_to_draw stayed
non-NULL for all remaining slots. A later slot whose icon had no
model (model_index == -1) and no bitmap frame to draw (e.g. a carried
icon that has not moved yet, or an icon ani with missing frames) then
fell into the color branch and called draw_model_icon() with a model
index of -1, crashing in model_clear_instance().
Reset color_to_draw each iteration, and give draw_model_icon() the
same model_id guard that draw_model_rotating() already has.
Co-Authored-By: Claude Fable 5 <[email protected]>
* guard against NULL color_to_draw when an icon ani is missing frames
In draw_ship_icon_with_number() and wl_render_icon(), a bitmap-based
icon takes the bitmap branch initially, so color_to_draw is never
assigned. If a hot/selected/disabled state then swaps in an icon ani
frame that does not exist (e.g. a single-frame or truncated icon ani),
the frame index is -1, the final dispatch falls into the color/model
branch, and gr_set_color_fast() dereferences a NULL color.
Fall back to the normal icon color in that case.
Co-Authored-By: Claude Fable 5 <[email protected]>
---------
Co-authored-by: Claude Fable 5 <[email protected]>
After a drag-drop that consumed the last unit of a roster class, the
active list could shrink while SS_active_list_start was non-zero,
making items above the stuck offset (including default ships returned
to the pool, such as Seth fighters) unreachable: the up-arrow handler
short-circuits when size <= max_show without resetting the offset.
- Clamp the preserved scroll offset in ss_synch_interface() against
SS_active_list_size - MAX_ICONS_ON_SCREEN.
- Harden common_scroll_down_pressed() to reset *start to 0 when the
whole list fits on screen.
Fixes#6240
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
* add wing display names
Adds display name capability for wings, matching the pattern and infrastructure of display names for ships. As in #7234, wing display names take precedence over ship display names, if both are provided; because if ship display names took precedence, a ship would have the same display name in every wave.
All the relevant display locations are handled, including the HUD, escort list, message box, and mission log. Lua API support is provided as well.
Adds full support for both FRED and QtFRED, including the always-save-display-names setting.
Addresses #7189 and the last request from #1730.
* add help text from #7234
* Few Minor Visual Indicator Enhancements to Loadout
This PR adds a few visual indicator tune-ups to the loadout screens.
1.
Currently, if a mission is set as `scramble` then there is no visual way for the player to see that the Ship Selection and Weapon Loadout screens are locked, instead the player has to click on the buttons and hear the fail sounds or assess that the hover states are not triggering.
This PR adds a text line right below the words `Weapons Loadout` that states "Ships/Weapons Locked" (XSTR 749) if the mission is `scramble` thus the player can easily see this right away and not have to check the buttons on every mission.
2.
Currently there is a nice orange coloration of the player ship icon in the Ship Selection wing loadout. This immediately shows the player which ship is theirs, but this usefulness does not translate to the Weapon Loadout screen. The player must select another ship, besides the player ship, to have the player ship show up as orange.
This PR adds a bool `always_highlight_ply` which is specifically for the weapon `draw_wing_block` function when called as as part of the weapon loadout routine.
Both PRs are tested and work as expected. I currently have these PRs on without a flag, but if folks really do not want the PRs or would prefer them to be behind a flag, then I'm happy to do that, too.
* separate PRs
Use `std::unique_ptr` rather than `vm_malloc` and `vm_free` to handle optional strings in ship classes. This makes the code more robust and increases memory safety. This also allows several string variables to be consolidated in the ship class and weapon class script APIs.
Also add new `stuff_string()` overloads and a new `F_TRIMMED` string parsing type to handle the new fields and the string trimming done inside `stuff_malloc_string()`.
Add XSTR entries for "or", "None", and "Nothing".
Note that there is already an XSTR entry for "none", but it does not have the right capitalization.
Note that "None" is localized in `textify_scancode` but not `textify_scancode_universal`.
This makes "Nothing" cargo use appropriate localization.
Fixes#5227.
* Update `$POF file Techroom:` Usage
Currently `$POF file Techroom:` is only used in the weapons loadout screen (despite having techroom in the name). This PR expands the use of the `$POF file Techroom:` to shiploadout select and the actual techroom.
If it is desired I can put it behind a game settings flag, but IMO it would make the most sense for modders to have this just work as the actual name implies.
* clean up code with feedback
* typo fix in comment
* Add Subsystem Flag to Hide Turrets from Ship Loadout Stats
The statistics summary window in the ship loadout editor shows various aspects and stats of the selected ship. These can be toggled and customized through the ships table, but one line is actually autogenerated, `+Turrets`. This is unideal for certain ships, especially those which use turrets on ships in non-standard ways (for example having an AI controlled turret which shoots bombs). In these cases, the turrets are not actual turrets, and it is confusing for players seeing that there is a turret on the ship in the loadout editor. (I can give more detailed examples using FotG if needed).
This PR adds a subsystem flag which allows the modders to hide a turret subsystem so it is not counted in the auto generation of the `Turrets` summary line. (As a slight cleanup I also removed cleaned up the whitespace formatting on a nearby, but unrelated line).
* sync names
* fix renaming
Commit 6c2ebab01c (#3439) added a `max_line_length` argument to `split_str()`, but one call had the extra argument added in the wrong spot, and so a previously-pointless-but-harmless `ignored_char` of 0 became a `max_line_length` of 0, resulting (ultimately) in an exception in the unicode code when the iterator tried to decrement at the beginning of a ship's description. This gets rid of the argument entirely, rather than reordering them, because ignoring null characters doesn't really do anything anyway.
I've also added `Assertion`s to check `max_line_length` that should prevent this specific problem from occurring again in the future.
* Added a byte limit for line length in split function
* Fix call error
* Ádd missing limits import
* Change hardcoded values into defines
* Cleanup default argument
Several things going on here:
1) refactor `get_display_string()` to `get_display_name()` to be more precise
2) likewise, refactor `alt_name` to `display_name`
3) automatically generate display names when parsing the ship, ship class, and weapon class for the first time
4) remove the on-the-fly truncation of names with hash symbols
Basically this subsumes the `end_string_at_first_hash_symbol` logic into the display name. Rather than truncate the name every time it's displayed, truncate it once when it's parsed. This takes advantage of the previous display name infrastructure, since display names are a superset of the case of truncating the hash symbol.
This also has the benefit of now allowing hash symbols within display names. When updating Destiny of Peace, I noticed that "Lockheed Shuttle #4" would no longer display properly; and it was not previously possible to fix this even with display names.
I could pull out the `get_display_string()` and `alt_name` renaming into a separate PR if desired.
As noticed in Discord, in loop1-2 some of your wingmen have missing weapons, but as the ships are locked it's impossible for the player to modify them. These ships should not be part of the check.
Ships which do not have the maximum number of primary and secondary banks (3 and 4) will have gaps in the loadout slots, so be sure to only check the weapon slots that the ship class is actually equipped with.
Yet another obscure edge case from Aftermath: Reboot. The FREDder ought not to create this situation in the first place, but if he does, don't enter an endless loop.
This is the best way to address Mantis 2715. I took a look at allowing empty banks in the course of working on PR #2318, and it was completely intractable. But in order to prevent certain "funnies" such as violation of bank restrictions, it's a good idea to ensure all banks are populated.
This PR adds a new XSTR for a popup message, and incidentally adds another XSTR to a message that should have had one already but didn't.