When a destroyed subsystem was repaired, its submodels were only restored if the repair was done through a SEXP. A subsystem repaired by a support ship, by automatic repair, by a script, or through the debug console regained its hitpoints but not its submodels, so a ship could have a working turret with no model. Any -destroyed form also remained visible.
All of these now call restore_subsystem_submodels, which brings back the subsystem's submodels and hides the -destroyed submodel linked to a special-point subsystem. The -destroyed form of an ordinary submodel is hidden when the submodels are next replicated. The SEXP code uses the same function, with no change in its behavior.
Destruction outside do_subobj_destroyed_stuff now goes through a new blow_off_subsystem_submodels function, which do_subobj_destroyed_stuff also uses. It blows off both of the subsystem's submodels and shows any linked -destroyed submodel, unless the subsystem has "no disappear". This handles the cases where a subsystem appears destroyed without going through do_subobj_destroyed_stuff:
- the subsystem is destroyed at the start of the mission
- the subsystem was destroyed in the previous mission of a red-alert sequence
- the ship changes class while the subsystem's submodels are blown off
Previously the first two blew off only the first submodel, ignored "no disappear" for it, and never showed a linked -destroyed submodel; the third did nothing. For red alert, subsystems with no hitpoints at all are not treated as destroyed. For a class change, the visible state is carried over rather than inferred from hitpoints, so a subsystem set to zero without being blown up, or repaired without its submodels, keeps its appearance.
Red alert also restores the submodels of a subsystem that the mission file starts destroyed if the previous mission's data gives it hitpoints. Ship-copy-damage now shows or hides the -destroyed submodel linked to a special-point subsystem to match the copied state.
As before, the link between special-point subsystems and -destroyed submodels requires "$Link special-point subsystems to -destroyed submodels:" in game_settings.tbl.
The multiplayer hitpoint-update path is not changed. A client receives subsystem hitpoints separately from the notification that a subsystem was destroyed, so a hitpoint update sent just before the destruction could arrive after it and wrongly restore the submodels.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Report leaks after static destruction via _CRTDBG_LEAK_CHECK_DF instead of calling _CrtDumpMemoryLeaks() at the end of main(), which incorrectly reported every global container as a memory leak.
Also fix the three remaining real leaks which were discovered after all the noise was cleared up:
- Mc_point_list was never freed, because its atexit cleanup was only registered by code that no longer runs. But the list was only a temporary array of pointers used while building the collision tree, so remove it and have model_collide_parse_bsp_defpoints() copy the vertices directly into the tree's point list.
- model_batch_buffer's Mem_alloc copy was never freed. But the copy is actually not necessary, so remove it and pass Submodel_matrices directly.
- Goal_target_names strings were only freed when the next mission was parsed, so the last mission's names leaked at exit. Hold them in SCP_vm_unique_ptr so they are freed with the vector.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
FRED and qtFRED no longer overwrite a hand-edited arrival cue on the wing
containing the player start. The cue is editable when the wing has multiple
waves (where it governs the arrival of subsequent waves) and read-only
otherwise. Waves and threshold are now editable for the single-player
player wing, while the arrival delay remains locked; the arrival location
is editable in both editors.
Both error checkers flag a single-wave player wing with a custom cue as a
potential issue, and the engine now warns and aborts the mission load,
rather than tripping an Assert, if the player start ship was not created
at mission start.
Also fix an unguarded read of Ships[Player_start_shipnum] in both editors.
Co-authored-by: Claude Fable 5 <[email protected]>
`support_ship_info::rearm_weapon_pool` was a dense per-weapon-class array where -1 = unlimited (the default), 0 = not rearmable, and >0 = remaining stockpile. Convert it to a sparse per-team map:
`std::array<SCP_map<int, int>, MAX_TVT_TEAMS> rearm_weapon_pool;`
`int rearm_pool_default() const; // -1, or 0 under rearm_pool_from_loadout`
Mission-file format and save output are unchanged. Also tightens the two weapon-class bounds checks in ship.cpp. This removes another `MAX_WEAPON_TYPES` sized structure on the way to lifting the weapon class limit.
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
With every fixed-size ship-class-indexed structure now converted to dynamic containers, we can finally remove the `MAX_SHIP_CLASSES` limit!
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]>
- A parse flag to indicate when the mission is being parsed, and a new `ship_class_index()` accessor in the ship registry, both currently unused
- Make the ship registry more robust, especially in FRED: properly update it when ships are renamed or deleted
Following PR #7429, ships belonging to wings were hashed the same way as every other ship, instead of the original hackish format where the hash was juggled around the number. This was a mostly transparent upgrade, and ship names were automatically converted from the legacy format to the new format. However, missions that referenced ships *in subsequent waves* broke; the converter only handled ships in the first wave.
This PR adds compatibility checks to `eval_ship` and `ship_registry_get` to accommodate legacy names.
Convert the allowed-ships and allowed-weapons lists to sets, rather than naive vectors that stored a true/false for every class. Since these are ordered sets, iteration occurs in the same sequence as iteration of `Ship_info` or `Weapon_info`. Associated data structures such as `Granted_ships` and `Granted_weapons` are converted as well.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Refactor `Ship_class_names`, `Iff_info_names`, and `Ai_class_names` to be populated once at parse time, as opposed to ad-hoc and possibly many times.
Also make `Ai_classes` and `Spawn_names` proper SCP_vectors.
* avoid undefined behavior from UNREACHABLE on reachable code paths
UNREACHABLE expands to __assume(false) / __builtin_unreachable() in
release builds, which is undefined behavior if the code is ever actually
reached. Many UNREACHABLE sites only guard against unexpected-but-
possible runtime states (values from tables, mission/save files, network
packets, scripting, or user input), so reaching them should degrade
gracefully rather than invoke UB.
Replace UNREACHABLE with Assertion(false, ...) at all such sites. It
still pops an error in debug builds, but in release it is a harmless
no-op that simply falls through to the following code. Sites that are
genuinely unreachable by local invariants (e.g. a switch over a bounded
random number) are left as UNREACHABLE.
A few converted sites had no safe fall-through -- an uninitialized read,
null dereference, or null call would have followed -- so they also get a
minimal recovery: initialize the value, guard the dereference, return
early, or skip the offending item.
Where the converted site was a simple guard of the form
if (x) { Assertion(false, ...); return; }
hoist the check onto the invariant instead:
Assertion(!x, ...);
if (x) { return; }
This states the expectation directly and shows the offending value in
the debug popup, while keeping the same graceful runtime degradation.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* addendum
After consultation with Lafiel, use UNREACHABLE in situations that are effectively impossible even if not provably impossible. UNREACHABLE = "this shouldn't ever happen"; Assertion = "if this ever happens, it's a coding error". Also fix a few other sites and change a couple to Warnings.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
A sweep for the w_bank pattern (raw owning handles in types that are
copied, moved, or shifted by value) found four more instances:
p_object: the destructor frees dock_list, which suppresses the implicit
move operations, so Parse_objects growth copied elements -- safe only
because dock lists happen to be built after parsing finishes. Wrap
dock_list in the new util::reset_on_move<> (a handle whose move resets
the source) and explicitly default the rule of five (with a user-defined
move assignment that frees any existing dock list before the memberwise
transfer), so moves now transfer the dock list.
DecalDefinition: the defaulted moves copied the bitmap handles without
resetting the source, so destroying a moved-from element would
bm_release live handles -- safe only because DecalDefinitions grows
before loadBitmaps runs. Wrap the handles in reset_on_move<int, -1>.
cmission: FRED's remove_mission overwrote the removed slot's five
vm_strdup'd strings without freeing them (a leak on every removal) and
left the vacated slot aliasing the surviving copy (a double-free if the
slot is later reused without reassigning every string). Factor the
string cleanup out of mission_campaign_clear into
mission_campaign_free_mission_strings and call it before the overwrite,
nulling the vacated slot afterward.
Undo_stack: the destructor deletes the tracked items, but the implicit
shallow copy was used to transfer stacks into the undo system, kept safe
only by an immediately-following untrack() call. Delete the copy
operations, default the move constructor and implement move assignment
as clear-then-exchange, so an assigned-over stack deletes its own items
first and the source is guaranteed empty.
Co-Authored-By: Claude Fable 5 <[email protected]>
* long project will be long
* add shared foundation files
* unify some shared types across both editors
* sexptreemodel framework
* move opf functions to the model
* move shared functions into the model
* move variable/container stuff to the model
* sexptreeactions and ui interface
* wire the model into the ui
* move some shared logic to the model
* move more methods to the model
* move clipboard to model
* create new right click menu model
* begin connecting the new right click menu
* wire up the new right click menu in fred
* wire up the new right click menu in qtfred
* move help text and visuals to model
* move menu option enable disable to model
* wire up sexp_tree to the model
* rename sexp_tree files to sexp_tree_ui
* fix build errors
* fix warning
* remove all opf forwarders
* remove some forwarder methods
* connect qtfred events editor directly to model
* wite plan for unifying tree types
* replace legacy m_mode with treeflags
* fix qtfred campaign dialog
* fix qtfred ship/wing dialog trees
* build error
* documentation
* more documentation
* even more documentation
* move more opf functions to opf file
* clean up context menu function
* move default argument to opf file
* build errors
* fix qtfred dialogs and annoations
* new shared annotations model
* sexp_tree_ui to sexp_tree_view
* documentation pass
* document annotations model
* delete the plan
* move event annotations to the new model
* generic cleanup
* remove some old defines
* cleanup includes
* more cleanup
* parenthesis
* more parenthesis
* variable collisions
* too many parenthesis this time
* clang modernization
* variable names again
* more clang and assertions
* static cast
* clang again
* fix some bugs and issues
* Refactor sexp tree action duplication into SexpTreeActions
* fix a few more minor issues
* avoid clang false positive
* rebase tweaks
* fix annotations issue
* fix typo and two small bugs claude found
* fix event editor tree widget loading
* fix ship and wing sexp tree editors in qtfred
* fix qtfred briefing and debriefing trees
* fix a regression Claude found
* address feedback
* fix annotations for root items in qtfred by copying fred's method
* address feedback
* rescope variables
* clang
* framework for support ship rearm pool and flags
* scripting api
* FRED support
* QtFRED support
* do not save non-player-allowed weapons
* tvt and mission versioning
* fix variable name
* minor fixes and clarity
* oops
* add qtfred help docs
* fixes and cleanup
* clang and readability
* address feedback
* address feedback
* change to new fog system
* Fix fog conversion issues
* remove unused old neb values
* Update scripting API
* temporary old-value round trip
* Rewrite nebula fog with four explicit parameters
Replace the legacy `+Fog Near Mult` / `+Fog Far Mult` model — which
depended on fixed engine constants — with four parameters that map
directly to renderer behavior:
+Fog 1000m Visibility fraction of light surviving 1000m of fog
+Fog Near Distance meters from camera where fog begins
+Fog Skybox Clip Distance max render distance for the skybox in fog
+Fog Clip Distance max render distance for the scene in fog
The deferred-lighting fog shader, material system, model renderer,
object sort, and the Lua mission API all consume the new parameters
directly. The legacy `Neb2_fog_near_mult` / `Neb2_fog_far_mult`
globals are removed.
Mission parsing accepts either the legacy keys or the modern keys.
Legacy values are converted to the modern set at parse time, and a
per-mission flag (`Neb2_fog_use_legacy_values`) records which keys to
write back on save. The flag is cleared only when something actually
changes, so opening and OK'ing an unmodified legacy mission preserves
its on-disk format. Saving uses `optional_string_fred` to preserve
user comments through edit cycles.
FRED2 and QtFRED background editors both expose the four parameters
in place of the old multipliers, with friendly labels and tooltips.
Both editors validate input (visibility clamped to [0, 1]; near
distance must be > 0; skybox and scene clip distances may be 0 to
disable fog at that range) and use a snapshot-and-compare at close
time so that a no-op open-then-OK never spuriously rewrites a legacy
mission in the modern format.
FRED2's background dialog is also reorganized so that "Display bg
bitmaps in nebula", "Override nebula fog palette", the RGB row, and
the four fog parameters all sit inside the Nebula group box; the
"Old Nebula" group box is restored below it with its Pattern, Color,
Pitch, Bank, and Heading controls back inside the frame. The dialog
now overrides PreTranslateMessage so that the new fog tooltips —
and the previously broken "Save sun/bitmap angles in correct format"
tooltip — actually fire.
QtFRED's BackgroundEditor.ui adds four QDoubleSpinBox controls in
the existing nebula form layout, with per-field decimals chosen to
avoid scientific notation, and per-field min/max declaring the
validation rules. The QtFRED help page is updated to describe the
new fields.
* fix tooltips
* restore forward-renderer fog
* Clang Tidy
* address feedback
* Refine nebula fog rewrite
- Rename Neb2_fog_use_legacy_values to Neb2_fog_save_legacy_values
to reflect that the flag governs save format, not runtime
behavior.
- Decide both the save flag and the legacy-to-modern conversion
purely from whether the file contains modern fog keys, dropping
the prior heuristic based on required_fso_version. A mission
without modern keys (legacy- or retail-format) converts on load
and saves back in legacy format; missions with modern keys are
saved as modern.
- Snapshot the four fog values as floats at editor open and use
fl_equal for the no-change comparison in both FRED2 and QtFRED,
replacing the FRED2 CString comparison and the QtFRED strict
inequality.
- Document the on-disk invariant in missionsave: when not saving
legacy keys, the four modern keys must always be written (never
gated on "only if non-default"), since the parser uses their
presence to decide whether to run the conversion on next load.
---------
Co-authored-by: Goober5000 <[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
Some code refactoring to allow mission titles to be strings of any length. Titles that are too long to actually display (based on string width) will be visibly truncated using `font::force_fit_string`, but the underlying title will not be affected. Multiplayer is still subject to the 31-character length limit, so titles sent over the network will still have characters truncated.
Also removes some redundant calls to `gr_get_string_size`, since `font::force_fit_string` returns the fitted string width.
* new qtfred error checker
* fix some issues
* clang
* more clang
* clean up and fixes
* missed a comment
* add help documentation
* surface auto corrected issues more clearly
* some cleanup
* use std::move() for local variables that are copied but not reused
Addresses ~140 Coverity "Variable copied when it could be moved"
defects across 74 files. Every std::move() targets a local variable
whose value is consumed by push_back, emplace, assignment, lambda
capture, or a function call and is never read again afterward.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* clang: use std::make_shared
* clang: use std::make_unique
* address feedback
---------
Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
Three out-of-bounds/UB fixes for issue #6793:
- asteroid_create_all: skip field_debris_type[idx] == -1 entries when
building the DG_DEBRIS odds table and loading models; iterate only
over valid table slots during spawn. Matches the guard already present
in asteroid_frame.
- missionparse: bounds-check subtype before indexing colors[] in the
obsolete +Field Debris Type: parsing path.
- verify_asteroid_list: guard the asteroid_list[0] fallback access
against an empty list.
- asteroid_init: emit an mprintf when no asteroids are defined instead
of returning silently.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
* qtfred dockable objects list
* fix jumpnode rendering
* more ouline panel features
* fix for wings
* remember list expansions
* clean up wing selection and layer handling
* disable top and bottom docking
* give scene outliner some padding
* replace old selection dialog with the scene browser
* fix some minor issues
* right click for wings and waypoint paths
* clang
* clang again
* fix status bar message
* 25 1 checks
* clang
* more clang
* address feedback
Add coverity[GLOBAL_INIT_ORDER] annotations to ~50 global variables
across 27 files. These were all analyzed and confirmed safe:
- ~48 OptionBuilder::finish() globals use a Meyers singleton
(OptionsManager::instance()), which guarantees safe init order.
Destruction is also safe due to shared_ptr ref-counting.
- 6 default-constructed container globals (vectors, maps) have no
cross-TU dependencies at construction or destruction time.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
1. When loading and saving lighting profiles, treat an empty string as equivalent to the default name
2. Remove unused fields from the mission struct
3. Properly guard union initialization in `add_message`
Testing in FotG exposed a bug that dates back to retail. During mission parsing, arrival and departure anchors were represented by indexes into `Parse_names`. After parsing, they were converted to ship indexes -- but only in FRED. Since some functions are shared between FRED and FS2, this can produce inconsistent results, which can be hard to detect since parse indexes and ship indexes are usually the same.
This refactors arrival and departure anchors to be represented by ship registry indexes in all cases. It also adds conversion functions for the benefit of FRED and QtFRED since those dialogs were designed with ship indexes in mind.
The existing `cannot-perform-scan` flag prevents the cargo from being shown on the HUD, but some modders would like to see the cargo while still preventing a ship from being able to perform a scan. So, this renames the existing flag to `cannot-perform-scan-hide-cargo` and adds a new `cannot-perform-scan-show-cargo` flag. Compatibility code in both mission parsing and flag evaluation ensures that missions using the old flag will still work.
Note that if both `cannot-perform-scan-hide-cargo` and `cannot-perform-scan-show-cargo` are set, the former will take precedence.
Add a useful function for getting a ship_info pointer from a ship registry entry. Also rename `ship_has_dock_bay` to `ship_has_hangar_bay` for clarity, since docking bays and docking points are not the same thing. Finally, add a `model_has_hangar_bay` function.
Make sure that missions without props will still be compatible with earlier mission file formats. If props are included, bump the mission file version. Also, fix some newline behavior in the save code.