Commit Graph
19628 Commits
Author SHA1 Message Date
Taylor Richards 12a36dc1a3 fix various macOS/Xcode project issues (#7875)
Silence CMake build type warnings when using a multi-config generator.

Fix macOS deployment target specification. It must be set before
the first project() command to work properly, and needs to be a cached
value so that it can be overridden by the user. This in particular is
required for Xcode 27 where the minimum deployment target is 12.0 and
using anything less is a hard fail rather than a warning.

Fix Xcode scheme only containing qtFred as a buildable target.

Fix various Xcode warnings when a static library contains files without
any symbols.

Allow FSO/qtFRED cmdline arguments and working directory to be set via their
respective options (FSO_RUN_ARGUMENTS & FSO_FREESPACE_PATH) in Xcode.

Fix product identifier mismatch and unset minimum system version warnings
in Info.plist file.
2026-10-17 06:16:10 -04:00
Goober5000andClaude Opus 5.5 c28032a2af Make quickstart options quick, and fix several associated bugs (#7834)
* only strip the legacy M/S suffix from the last pilot when needed

player_select_get_last_pilot_info() chopped any trailing uppercase M or S
from the LastPlayer value, a holdover from the pre-pilot-file era.  Pilots
whose callsigns end in M or S (e.g. BOSS) were never found as the last
pilot.  Now the suffix is only removed when the full callsign is not a
pilot but the shortened one is.  This also avoids indexing before the
string when LastPlayer is empty.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* route all pilot selection through one commit function

player_select_close() and player_finish_select() each had their own copy
of the code that loads a pilot as the active Player, and the copies had
drifted apart.  Both now call player_select_commit_pilot().

This fixes two bugs:
* -pilot did not work with the retail pilot select screen.
  player_finish_select() loaded the requested pilot, but then
  player_select_close() loaded the highlighted pilot (the most recent
  one) over it.  The close handler now skips the load if a pilot has
  already been committed.
* player_finish_select() ignored its is_multi parameter when setting
  player_was_multi, so scripted selection of a multiplayer pilot saved
  it as single-player.

Scripted selection also now saves the pilot file, as the retail screen
already did.  The background bitmap handle starts out as -1 so that the
close handler doesn't release bitmap 0 when the screen was never set up.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* select pilots automatically at game init, through the normal commit path

-startgame, -connect and -almission (via Cmdline_use_last_pilot) used
player_select_get_last_pilot(), which loaded the last pilot directly
without most of the setup that a normal selection does.  It skipped
valid_pilot(), multi_options_init_globals() and saving the pilot file.
Because it never entered the pilot select state, it also turned off the
main hall's fallback for an unavailable campaign.  Meanwhile -pilot and
-benchmark_mode were handled on the pilot select screen itself, which
scripted UIs replace entirely.

All of these now go through player_select_try_auto_select() at
GS_EVENT_GAME_INIT.  It picks the -pilot callsign, else the last pilot
if it still exists, else the most recently saved pilot.  It shows the
startup warning popups, validates the pilot, and commits it through
player_select_commit_pilot().  The pilot is multiplayer if -startgame or
-connect was given, and single-player otherwise.  If there are no
pilots, or validation fails, the pilot select screen is shown as usual.

The main hall now allows the campaign fallback whenever a pilot has just
been committed, not only when coming from the pilot select state.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* select a pilot automatically for -start_mission

-start_mission skips the main hall, but it still waited on the pilot
select screen.  It now uses the same automatic pilot selection as the
other quickstart options, so the screen only appears if there are no
pilots.  The pilot is committed as single-player, consistent with the
main hall forcing single-player mode for -start_mission.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* skip the intro movie for quickstart options

When the command line asks to skip pilot selection (-start_mission,
-pilot, -startgame, -connect, -almission, -benchmark_mode), the intro
movie now doesn't play either.  It could take several minutes before the
game even reached the pilot.  The OnIntroAboutToPlay hook still runs, as
it does when the mod table disables the intro.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-16 20:57:07 -04:00
LuytenKy e76e844d02 Vulkan: Skip MSAA resolve when no deferred pass is active (#7854)
* stub check for msaa

* initialize viewport variables
2026-10-15 18:04:32 +02:00
BMagnu 88669e4518 Fix VR cursor issues and warn about crash hazard (#7868)
* Add warning for OpenXR toolkit

* Properly handle mouse position

* GCC 9 issue
2026-10-15 00:48:46 +00:00
BMagnu f6f1aac574 Fix fullscreen with resolutions not available for modesetting (#7867) 2026-10-15 02:01:11 +02:00
Goober5000andClaude Fable 5.1 b37cfda56d Fix a model refcount bug on loadout change, and harden model_unload against stale handles (#7851)
* 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]>
2026-10-13 02:19:48 -04:00
Goober5000 085ff7a70b Merge pull request #7852 from Goober5000/fix/loadout_wireframe_wipe
restore the wireframe wipe in the FS2 loadout effect
2026-10-12 23:32:32 -04:00
Goober5000andClaude Opus 5.5 2f6bca06f7 restore the wireframe wipe in the FS2 loadout effect
During phase 1 of the FS2 ship/weapon select animation, the wireframe
is supposed to be revealed as the scanline moves down.  That relied on
the clip plane set up for the grid carrying over to the wireframe
render.  90a2676f1 replaced the grid's clip plane with line trimming,
which left the phase 1 wireframe unclipped, so it popped in all at once.

Clip the wireframe in both phase 1 and phase 2.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-12 22:31:50 -04:00
Goober5000andClaude Opus 5.5 1af0eb82bf apply $fix ai target recovery's pursuit filter only to targets picked for pursuit
The actively-pursues filter added to "$fix ai target recovery:" in #7736 was applied to every target search.  But a ship's target is not only used for pursuit: it also drives the ship's turrets, other ships' guard reactions, and attacker counts.  In retail SM1-04, the friendly Knossos (which does not pursue fighters) was made to target the Behemoth instead of a Mara, which caused the Mara guards to react to the protected Knossos endlessly instead of rushing the player.

The filter now applies only where the target is chosen for maneuvering:
* find_enemy() and get_nearest_objnum() take a required for_pursuit parameter; automatic searches pass ai_targets_for_pursuit(), which is true only in AIM_CHASE or AIM_STRAFE
* guard reactions, retaliation (dynamic chase), and the retargets in ai_big_chase_attack() and ai_big_maybe_enter_strafe_mode() skip ships the attacker won't pursue, via the new ai_declines_pursuit()
* ai_attack_wing() prefers living wing members the ship will pursue; if there are none, it holds a member as an explicit order to attack an unpursued ship does
* get_nearest_objnum() holds an unpursued member of the enemy wing rather than leaving the wing

Since refused targets are no longer picked for pursuit, ai_chase() no longer needs to drop them.  Retail behavior is unchanged when the flag is disabled.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-12 21:26:53 -04:00
Goober5000 f64d940963 capitalization 2026-10-12 20:09:29 -04:00
Goober5000andClaude Fable 5.1 2052dcaeb0 reset stale AI order state and track previous_mode through dynamic chases
Several ai_info fields remember which order set them up (enemy_wing,
guard_objnum/signature/wingnum) or which mode a dynamic goal interrupted
(previous_mode, resume_goal_time), but nothing cleared them when a new
order took over.  A ship that once guarded a wing would be yanked back
into AIM_GUARD whenever a wingmate died; a stale previous_mode of
AIM_GUARD could make a former guard abandon a counterattack; a stale
enemy_wing biased every find_enemy() toward a long-ago target; and a
resume_goal_time left over from a dynamic chase suppressed target
acquisition for up to 20 seconds into the next order.

Add ai_reset_order_state(), called when ai_process_mission_orders()
takes up a goal and when ai_do_default_behavior() runs.  Each order's
own setup repopulates the fields it needs.  enemy_wing is reset under
either $fix stale ai order state or $fix ai target recovery, since it
affects target selection; the rest only under the new flag.

maybe_set_dynamic_chase() wrote aip->mode into previous_submode, a
retail typo that left previous_mode untracked for dynamic chases.  Under
the flag, save both fields properly, but not when a dynamic goal is
already in progress, so a nested interruption does not clobber the mode
to return to.  Apply the same guard to the strafe/chase transitions in
aibig.cpp.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-10-12 20:09:29 -04:00
Goober5000 a1250d7168 Merge pull request #7836 from Goober5000/cleanup/remove_glow_warning
Remove 3D shockwave glow map warning
2026-10-09 01:39:13 -04:00
Goober5000andClaude Sonnet 5.5 014e14af6f Remove 3D shockwave glow map warning
The warning fired for any 3D shockwave model without a glow map, but it turns out that situation is fine.  Drop the warning along with the cmdline include it required.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-09 00:38:27 -04:00
Mike Nelson 0c0320bb87 Null the multi SEXP packet getters' out-pointers on failure (#7828)
get_ship(), get_wing(), get_object() and get_parse_object() only assigned
their out-pointer on success. When a lookup failed (no bytes left, or a net
signature that no longer resolves, e.g. a ship destroyed before the packet
was processed) the caller's uninitialized local was left as stack garbage,
so its nullptr check could pass and the next line would write through it.
multi_sexp_set_camera_target() used the object without checking at all.

Set the out-pointer to nullptr up front, so every caller's existing nullptr
check works. This also clears the C4701/C4703 "potentially uninitialized"
warnings MSVC 2026 reports for the callers in sexp.cpp when it inlines these
getters at link time.
2026-10-09 10:54:31 +09:00
Goober5000andClaude Opus 5.5 5dd9f56deb release textures and model memory when a model load fails
The failure checks after parsing (bad detail levels, debris objects or
parents, and too many textures) run after the TXTR and GLOW chunks have
already loaded bitmaps, but model_load only deleted the polymodel, so
those bitmap slots were never released.  Page out the textures and free
the model through model_free, as the virtual POF cache already does for
models it loads.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 03:04:02 -04:00
Goober5000andClaude Opus 5.5 4ff6c8c447 accept a cross section count of -1 without a warning
Retail models (111 of them in sparky_fs2.vp) store -1 as their cross
section count, meaning none, which the original loader silently accepted.
Routing the count through pof_read_count made every such model warn on
load.  Read it directly and floor it at zero, as before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 03:02:24 -04:00
Goober5000andClaude Opus 5.5 2f131db53a propagate POF load failures through virtual POFs
A virtual POF ignored the load status of its base POF, so a base that
failed to load (missing, bad version, or rejected after parsing) was
still built upon and reported as a successful load.  Likewise, every
operation that copies from another POF used it even if that POF had
failed to load.

Return FAIL from read_virtual_model_file when the base POF fails, and
have the operations skip, with a warning, any source POF that failed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 03:02:24 -04:00
Goober5000andClaude Opus 5.5 7f71b169dd map appended-submodel textures before changing the virtual POF
When appending submodels would take the combined POF past
MAX_MODEL_TEXTURES, the limit was only noticed partway through, after the
submodel array had been grown, subsystems copied, and n_textures
incremented past the limit, which later loops over maps[] then overran.
Work out the texture mapping first, and give up before touching the model.

Also skip the -1 texture id of untextured flat polygons, which was being
assigned a texture slot, and which replace_textures_used then used to
index Num_verts and Num_polies.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 03:02:23 -04:00
Goober5000andClaude Opus 5.5 1d13fc2a83 reject models with too many textures once parsing is finished
Clamping the TXTR count keeps maps[] safe, but texture indices are baked
into the BSP data, so a model declaring more than MAX_MODEL_TEXTURES
textures cannot be built correctly.  Keep the clamp so that the chunk is
still read in full, then fail the load after parsing.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 03:02:23 -04:00
Goober5000andClaude Opus 5.5 2563a746aa reject out-of-range BSP texture indices when building polygon data
process_tmap and process_tmap2 used the texture index from the BSP data
to index Num_polies and Num_verts without checking it, so a corrupt
model wrote past both arrays while its vertex buffers were built.  The
collision tree parser already treats such an index as a fatal error, but
it runs afterward.  Raise the same error here, and skip the polygon in
case the error is continued past.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 03:02:23 -04:00
Goober5000andClaude Opus 5.5 604eb39519 close the model file on every exit path
The post-parse sanity checks and the bad-version check returned FAIL
without closing the POF.  Nothing reads the file after the chunk loop, so
close it there, and close it before the bad-version return too.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 03:02:23 -04:00
Goober5000 242eb19ef5 Merge pull request #7808 from Goober5000/cleanup/post_option_logging
log game settings after all tables have been parsed
2026-10-06 19:35:06 -04:00
Goober5000 6115df3e08 Merge pull request #7822 from Goober5000/fix/subsystem_dump
restore the debug subsystem dump
2026-10-06 19:34:49 -04:00
Goober5000andClaude Opus 5.5 571e63d659 value-initialize lights when adding them
Directional, point, and cone lights left local_vec, local_vec2, and some of
the type-specific fields uninitialized until something rotated or set them.
The sun copies in Static_light were never rotated at all until the previous
commit, so light_apply_rgb's sun term read uninitialized memory.  Zero every
field up front, which also covers what the tube light did by hand.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 00:17:02 -04:00
Goober5000andClaude Opus 5.5 d6c253bda3 light insignias using lights rotated into their frame of reference
light_apply_rgb works in the model's local frame, but nothing rotated the
lights into that frame before insignias were drawn: light_rotate_all() was
only called for the briefing and tech room lights, and even then it skipped
Static_light, the separate copies of the sun lights that light_apply_rgb
reads.  So the sun term used a stale, never-set direction and the point
lights were treated as if they sat at the model's origin.

Rotate the lights, including the sun copies, once the insignia's instance
is set up, and light each vertex at its actual position, which includes
the insignia's offset.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 00:17:02 -04:00
Goober5000andClaude Opus 5.5 89bad012a2 restore the debug subsystem dump
In debug builds, subsystems found in a model but missing from ships.tbl
are meant to be dumped to data/tables/<model>.subsystems as stub table
entries.  Since subsystem matching was deferred until after the model
file is read (#4590), the dump file was already closed by the time
do_new_subsystem ran, so it was always empty and deleted.  Worse, ss_fp
was left pointing at a closed CFILE slot, which another file could reuse
before do_new_subsystem wrote to it, and the dump file leaked whenever a
load failed.

Open the dump file on the first unmatched subsystem instead, and close it
once the deferred subsystems have been matched.  This also means no file
is created and deleted for models without unmatched subsystems.  Reset
the once-per-model warning flags there as well, rather than only when
the dump file opened successfully.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 14:42:50 -04:00
Goober5000andClaude Opus 5.5 52e5652624 store insignia normals per face, and light insignia faces with them
The insignia normal array was declared with the vertex limit, filled in by
face index, and read by vertex index.  An insignia with more than 81 faces
therefore wrote past the array, into the following insignia or, for the
sixth and final slot, past the insignia list into the rest of the
polymodel.  And the renderer lit each vertex with the normal of whichever
face shared its index, or with a zero normal (ambient light only) when
there was no such face.

A normal belongs to a face, so size the array by the face limit and light
all three corners of each face with that face's normal.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 13:14:32 -04:00
Goober5000 b483d30b20 Merge pull request #7804 from Goober5000/fix/misc
four miscellaneous fixes
2026-10-04 13:08:13 -04:00
Goober5000andClaude Opus 5.5 dd77f7afbf break submodel parent cycles when reading SOBJ chunks
A subobject that named itself, or one of its own descendants, as its parent
made the depth walk loop forever, and would have done the same to every
later walk up the hierarchy.  Since each SOBJ chunk adds only one link, any
cycle must lead back to the subobject being read, so detect that during the
walk and treat the subobject as parentless.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-03 23:48:51 -04:00
Goober5000andClaude Opus 5.5 b0d8862f18 Fix the fighterbay speed ramp on detoured departure paths
When a ship flying into a fighterbay has to route around the carrier,
create_model_path puts tangent and bisect points ahead of the bay path.
The departure speed ramp treated the first point of the AI path as the
start of the bay path: it started once the ship passed that point and
measured the path length from there.  On a detoured path the ramp
therefore began slowing the ship while it was still flying around the
hull, and the ship entered the bay slower than intended.  The ramp
fraction was also not clamped, so the target speed could exceed max
speed.

Under the new ai_profiles flag "$fix fighterbay speed ramp:"
(Fix_bay_speed_ramp), the departure ramp starts at the first point of
the bay path itself and the ramp fraction is clamped to [0, 1].  The
flag is enabled by default for mods targeting 26.2.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 21:16:46 -04:00
Goober5000 a93c808df3 Merge pull request #7814 from Goober5000/fix/model_path_refresh
Fix two issues with refreshing model paths in place
2026-10-01 21:13:06 -04:00
Goober5000andClaude Fable 5.1 048310ca25 log game settings after all tables have been parsed
Settings in game_settings.tbl were logged as they were parsed, so a setting was only logged if it appeared in a table.  A setting enabled through $Target Version was never logged, and a setting that appeared in more than one table was logged once per table, possibly with different values.

The settings are now logged by mod_table_log_settings, which runs at the end of mod_table_init.  The version-gated settings that previously had no log line now have one: framerate independent turning, scripted velocity, asteroid bounding box checks, the expensive turret target check, skybox depth consistency, zero-radius explosions, and insignias as decals.

Messages that describe the table text itself, such as deprecated options, removed file extensions, and out-of-range values, are still logged at parse time.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-01 20:05:09 -04:00
Goober5000andClaude Fable 5.1 d2f2374f5c fix the collision moment of inertia rotation, and flag the density fix
get_I_inv converts an object's inverse moment of inertia tensor from body to world coordinates for collision physics.  It was written as though vm_matrix_x_matrix(dest, a, b) computes a * b, but that function computes b * a, so the result was (R)(I_inv_body)(Rt) rather than the (Rt)(I_inv_body)(R) stated in the comment.  The collision response therefore used the tensor of an object facing the inverse of its actual orientation.  This dates back to retail.

The result is only correct for an isotropic tensor or an unrotated object.  Every retail ship has an anisotropic tensor, with a median principal-axis ratio of about 2.5 and as high as 13 for capital ships, so the spin imparted by a collision was typically about the wrong axis.

Since this changes collision behavior for every mod, the fix is controlled by the new game_settings.tbl option "$Fix collision moment of inertia:", which defaults to true for mods targeting 26.2 or later.  The retail path is otherwise unchanged.

PR #2575 fixed a related bug in physics_ship_init: retail scaled the inverse moment of inertia by $Density rather than by its inverse, so a denser ship was easier to rotate rather than harder.  That fix was merged without a flag.  For consistency it is now controlled by "$Fix density moment of inertia:", which defaults to true for mods targeting 21.0 or later.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-01 16:59:38 -04:00
Goober5000andClaude Opus 5.5 da08ae5b6e Fix bay path speed lookup reading paths[-1] on detoured paths
set_accel_for_docking took the model path from the first point of the
AI path. When a departing ship has to route around the carrier,
create_model_path prepends detour points whose path_num is -1, so the
path name was read from pm->paths[-1]. That usually yields junk and a
missed metadata lookup, but crashes when the memory before the paths
array is unmapped. Use aip->mp_index, which both bay path builders set
to the model path index.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 13:49:26 -04:00
Goober5000andClaude Opus 5.5 6fb9312982 Fix two issues with refreshing model paths in place
When the ship a path leads into moves, modify_model_path_points
re-globalizes the path's model points in place.

It always rewrote nverts points, but create_model_exit_path can build
a path with fewer, in which case the rewrite would run past the end of
the path into Path_points entries that may belong to other ships.  No
current caller builds such a path, but the rewrite now covers only the
points the path actually holds.

A subsystem path gives its second-to-last point a random offset when
created, but the refresh did not pass that point back in, so the
offset was lost the first time the target moved.  Record the
randomized vertex in ai_info and, under the new ai_profiles flag
"$fix subsystem path randomization on refresh:"
(Fix_model_path_refresh_randomization), reapply it on refresh.  The
flag is enabled by default for mods targeting 26.2.

Also rename a parameter in `get_base_path_info` (which is currently
unused) for clarity.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 13:47:07 -04:00
wookieejedi c23c69a538 Fix primary thruster glows rendering through geometry (#7805) 2026-10-01 11:18:58 -04:00
Goober5000 b37c6bcd93 Merge pull request #7811 from Goober5000/fix/subsys_path_bounds
fix off-by-one in the subsystem path bounds check
2026-10-01 10:53:00 -04:00
Goober5000 69e5017050 Merge pull request #7803 from Goober5000/fix/misc_loadout
two loadout-related fixes for FRED and QtFRED
2026-09-30 22:08:19 -04:00
Goober5000 4a633c86cb fix off-by-one in the subsystem path bounds check
Fix indexing of `pm->paths` in `ship_return_subsys_path_normal`.
2026-09-30 22:06:22 -04:00
Goober5000andClaude Opus 5.5 f7eb2ca419 restore subsystem sounds when a subsystem is repaired
A subsystem's looping sounds are now switched by ship_update_subsys_sounds, split out of ship_recalc_subsys_strength, which the support-ship and automatic repair paths now also call when a subsystem comes back from zero.  Previously those paths left the subsystem playing its dead sound.

Both the recalc and do_subobj_destroyed_stuff also cleared the dead-sound flag immediately after assigning the dead sound, so a revived subsystem never stopped it and a destroyed one gained another copy on each recalc.  They now set the flag.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 02:58:50 -04:00
Goober5000andClaude Fable 5.1 112ca2fc4b handle submodel repair/destruction consistently
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]>
2026-09-29 02:58:02 -04:00
Goober5000andClaude Opus 5 a159e67a10 share the loadout usage-list code between the two editors
Add generate_ship_usage_list_team alongside the weaponry twin that already existed in missioneditor/common, so both loadout editors can drop their TVT/non-TVT branches and simply iterate Num_teams.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-25 03:22:23 -04:00
Goober5000andClaude Opus 5.5 8c8620ef0f don't look up the operator of an empty SEXP list
An empty list such as `( )` leaves get_sexp() with no start node, but it still passed that -1 to get_operator_const() for the ai-chase-any-except compatibility check and, outside FRED, for preloading.  This tripped the out-of-range assertion in debug builds.  Return the empty list directly instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-25 02:28:24 -04:00
Goober5000 33c4bb0222 make is_special_sender static 2026-09-25 01:52:15 -04:00
Goober5000andClaude Opus 5 fd40df069c validate counts and indices when reading POF chunks
Element counts were taken straight from the file, so a corrupt POF could
supply a negative count (stored alongside a null array) or one larger than
the fixed-size array it feeds.  Add pof_read_count() to floor every count at
zero and clamp it to the destination's capacity, reading past the discarded
entries where more data follows in the same chunk.

The upper bounds that were checked at all were checked with debug-only
Asserts, or with an Error() that returns if the user picks Debug, so the
overflowing loop after it was reachable in both cases.

Also validate the submodel indices in SOBJ and TGUN/TMIS, the vertex indices
in insignia faces, and bounds-check the split plane array before writing to
it rather than after.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-25 01:52:15 -04:00
Goober5000andClaude Opus 5 c10df878ba remove Current_filename_sub and tidy up stop_parse
Current_filename_sub dates back to the use of *_ex() parsing functions for parsing .eff files, which were superseded by the Bookmarks feature.  Today it is entirely redundant to Current_filename.  A read that throws now leaves Current_filename naming the file that failed, rather than the file parsed before it, as it should.

Also clear Current_filename in stop_parse, so that a warning issued between the end of one parse and the start of the next no longer blames the last file parsed, and null out Mp there so it stops dangling into the freed parse buffer.  Also delete the vestigial Mp_save.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-25 01:52:15 -04:00
Goober5000andOpenAI Codex db02a71319 three QtFred fixes (#7780)
* Fix QtFRED root notifications when inserting nested operators

Only emit rootNodeFormulaChanged when the wrapped node is a formula
root. Inserting above a nested expression previously reported a child
as an event, goal, or cutscene root, causing assertions and out-of-bounds
accesses in the receiving dialog model.

Co-Authored-By: OpenAI Codex

* Give each QtFRED ship independently owned arrival and departure cues

Pass the tree model to the cue update handlers and serialize a separate
expression for each eligible ship. Sharing one formula across selected
ships caused double frees on subsequent edits and left other ships
referencing freed nodes when only one ship was changed.

This also avoids allocating unused expressions when cue updates are
disabled or no selected ships are eligible.

Co-Authored-By: OpenAI Codex

* Synchronize QtFRED item editability after SEXP node changes

Update native Qt editability alongside the shared model flags so
variable and container references cannot be edited as plain text.
Restore inline editing when a reference is replaced with ordinary data,
and preserve item flags when moving or copying branches.

Reject edit requests for protected items before setting the editing
state, preventing later programmatic changes from being treated as
user edits.

Co-Authored-By: OpenAI Codex
2026-09-24 13:23:23 +09:00
Goober5000andClaude Opus 5.5 04ba1b2015 type SEXP variable references by the variable's declared type (#7799)
When parsing a SEXP, a variable reference's node type was decided solely by whether it was quoted: "@Var" became a string variable and @Var a numeric variable, regardless of how the variable was declared.  A hand-edited SEXP with the wrong quoting therefore produced either a misleading "Not a number" error or a silently mistyped node that was saved back with the wrong quoting.

get_sexp now takes the node type from the variable's declared type, and flags a reference whose quoting contradicts it.  check_sexp_syntax reports such references (or any variable node whose type disagrees with its variable) as the new recoverable error SEXP_CHECK_VARIABLE_TYPE_MISMATCH: FRED flags it, and the game warns but still loads the mission, which runs correctly because the node type has been corrected.  Saving from FRED writes the corrected quoting.

Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-24 13:21:47 +09:00
BMagnu fcf9f569af safety checks for unused hud gauges (#7800) 2026-09-24 03:12:38 +00:00
Goober5000andClaude Opus 5 c342b6df61 Warn when a 3D shockwave model has no glow map (#7795)
Check each 3D shockwave model when it is loaded and warn if none of its textures has a glow map. The check is skipped when glow maps are disabled, since they are not loaded then.

Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
2026-09-24 11:23:11 +09:00