Compare commits

...
Author SHA1 Message Date
Goober5000andClaude Opus 5.5 61ad242997 fix lightning glow offset and collision LOD clamp (#8012)
Section-start glows used the b_add of the most recently created bolt
type (Nebl_type) rather than the bolt being drawn, so concurrent bolts
of different types got the wrong glow offset.  The cap glow already
used the correct type.

Clamp $Collision LOD to num_detail_levels - 1, the highest valid
detail level.  The previous bound was harmless because the collision
code falls back to the lowest existing LOD, but it was off by one.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-07 19:58:34 -05:00
dustinspace217andDustinspace217 9f2a41032d Vulkan: apply Lighting changes without a restart (#7974)
Changing Lighting in the options recompiles every shader, but Vulkan kept drawing with the pipelines it had already
built, and a pipeline keeps the shader code it was created from. So the new lighting level only showed up after a
restart. OpenGL applies it at once.

After the recompile, mark the cached pipelines stale. At the start of the next frame, before anything is recorded,
wait for the GPU and drop them; the next draws build them again from the new shaders. The recompile can run in the
middle of a frame (from the options menu or a script), so the drop waits for the next frame instead of happening there.

The recompile also rebuilds the anti-aliasing family that is not in use, which cannot compile under the current mode.
Its cached pipeline used to hide that; now the failed variant is dropped so it compiles again when that mode returns.

Fixes #7967

Co-authored-by: Dustinspace217 <[email protected]>
2026-11-07 19:56:30 +01:00
Goober5000andClaude Opus 5.5 5b6579ac0a fix lightning glow offset and collision LOD clamp
Section-start glows used the b_add of the most recently created bolt
type (Nebl_type) rather than the bolt being drawn, so concurrent bolts
of different types got the wrong glow offset.  The cap glow already
used the correct type.

Clamp $Collision LOD to num_detail_levels - 1, the highest valid
detail level.  The previous bound was harmless because the collision
code falls back to the lowest existing LOD, but it was off by one.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:54:01 -05:00
Boutros Tawaifi 49464c56f6 Add an "On Ship Damage" script hook (#7894)
* Add an "On Ship Damage" script hook

Runs after resolved damage has been applied to a ship (lethal hits run before
the destruction callback). Hook variables: Target, Source, Attacker,
WeaponClass, WeaponSubtype, SourceKind (PROJECTILE, BEAM, SHOCKWAVE, COLLISION,
OTHER), DamageType, ShieldDamage, HullDamage, Quadrant, Ballistic, BeamWeapon
and LethalHit. Conditions can filter on the target ship. Nothing is computed
unless a script has registered the hook.

* Move the On Ship Damage hook call out of a lambda

ship_do_damage() now calls a file-level static function, ship_run_damage_hook(), that takes the values it needs as arguments instead of capturing the function state. Hook behaviour is unchanged.

* Report subsystem damage in the On Ship Damage hook

Adds SubsystemDamage, the total subsystem strength a damage application removed, so a hit absorbed by subsystems no longer looks like a small hull hit. The hook description now says it only runs where damage is applied, which excludes multiplayer clients.

* Address On Ship Damage review comments

- Remove WeaponClass, WeaponSubtype, Ballistic and BeamWeapon from the
  hook. Scripts read these from Source when it is a weapon.
- Add a "Weapon class" condition for the On Ship Damage hook. It matches
  only when the damage source is a weapon of that class; a weapon name
  that does not exist never matches.
- SubsystemDamage is now a table keyed by subsystem handle that lists only
  the subsystems that lost strength. The total is removed.
- Rename PROJECTILE to NON_BEAM_WEAPON.
- Classify SourceKind by damage source: COLLISION for ship, debris and
  asteroid collisions; EXPLOSION for ship-death and docked-ship blasts and
  asteroid blasts; WASH for engine wash; SELF for kills where the ship
  damages itself; OTHER for other ship-sourced damage.
- Reword the hook description.

* Key On Ship Damage SubsystemDamage by canonical name

Lua tables cannot use subsystem handles as keys in a usable way, so key
the SubsystemDamage table by each subsystem's CanonicalName instead.

* Tag ship-vs-prop damage as a collision and use in_bounds

The ship-vs-prop collision call didn't pass DamageSource::Collision, so
the On Ship Damage hook reported those hits as OTHER. Every other
collision path was already tagged.

Also use SCP_vector::in_bounds() for the Weapon_info and Damage_types
index checks in ship_run_damage_hook().
2026-11-06 18:41:29 -05:00
Taylor Richards 4dc0ca7878 fix VR in OpenGL on Windows (#8009) 2026-11-06 16:27:14 -05:00
2b1759e98e Vulkan: stop mirroring light shafts top to bottom (#8006)
The Vulkan light shaft pass reuses OpenGL's sun position formula, whose
texture V grows upward. The Vulkan fullscreen triangle in post-v.sdr has
V = 0 at the top of the screen, and ls-f.sdr steps from fragTexCoord
toward sun_pos in that space, so the shafts radiated from a point mirrored
vertically from the sun. Flip the vertical coordinate before upload.

Co-authored-by: Saga port <saga-port@invalid>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-06 20:32:01 +00:00
Shivansps 1d0dd687cd Android Build (#6992)
* android build support

* Update es_compatibility.h

* Support reporting flags in android

* include pref_path in json v2

* add vulkan headers

* shaderc includes

* remove headers

* force use prebuilt vulkan

* fix vulkan inverted screen

* fix find sdl3 precompiled android

* Update CMakeLists.txt

* replace sdl2 android function names for sdl3 versions

* change sdl includes in osapi

* fix sdl3 includes, again

* rename function to sdl3 name

* remove unused definition

* copy libshaderc_shared.so to output in android

* do not look for vulkan-loader in android

* revert changes to SDL2.cmake

* add comment to vulkan pretransform setup

* cleanup maxsize function

* rename new android functions in osapi

* android trap back button

* Remove calculate_max_resize_max_size

* touch ui opacity control

* change opacity control to int

* fix opacity slider

* Corrected android working folder fallback and set borderless mode as default

* Implement shared helper for JNI calls

* Add version tag and sha256 to android prebuilt libs

* vulkan android check eSuboptimal

* An already requested rebuild also avoids redundant Android surface queries

* add cache bool to cmakelist

* set string ids
2026-11-05 20:56:36 +01:00
Tyler Blount 705d115214 Merge pull request #7993 from MjnMixael/qtfred-classic-menu-layout
Add a classic menu layout option to QtFRED
2026-11-04 18:25:19 -07:00
Mike Nelson a3b4386b7a Add a classic menu layout option to QtFRED
FRED2 can switch to its classic menus, where every editor dialog is in a single
Editors menu. This adds the same option to QtFRED, as a Preferences checkbox on
the Appearance tab.

The classic Editors and Misc menus live in FredView.ui beside the grouped menus
and reuse the same actions, so shortcuts and checked states are shared. Turning
the option on hides the Objects, Environment, Mission, Narrative and Tools
menus and shows the classic ones.
2026-11-04 15:42:59 -06:00
dustinspace217andDustinspace217 246a6ee8f0 Vulkan: match OpenGL's depth bias in the shadow map pass (#7944)
OpenGL's shadow pass calls gr_zbias(-1024), which sets a polygon offset of
factor 1, units 1024 (gropengltnl.cpp, gropengl.cpp). The Vulkan shadow pass
set a constant bias of -1024 with no slope term: the opposite sign, on a
depth buffer that uses the same less-than test. Use OpenGL's values.

Co-authored-by: Dustinspace217 <[email protected]>
2026-11-04 18:21:55 +01:00
dustinspace217andDustinspace217 e66fe223bf Vulkan: use one rendering-finished semaphore per swap chain image (#7943)
Each VulkanRenderFrame signaled its own rendering-finished semaphore and
presented with it. The frame's fence only says the submit has finished, not
that the presentation engine has consumed the semaphore, so the next submit
on that frame could signal a semaphore that is still pending, which the
validation layer reports as VUID-vkQueueSubmit-pSignalSemaphores-00067.

Move the semaphore to the renderer, one per swap chain image, recreated with
the swap chain, and pass the one for the acquired image to submitAndPresent.
Acquiring an image again means its previous present has been consumed.

Co-authored-by: Dustinspace217 <[email protected]>
2026-11-04 18:21:32 +01:00
dustinspace217andDustinspace217 e5b591141c Vulkan: give the irradiance pass cube fallbacks for its cube binding (#7941)
irrmap-f.sdr declares the Material set's texture binding as a samplerCube,
but vulkan_calculate_irrmap filled the array with the 2D fallback and only
replaced element 0 with the environment map, which the validation layer
reports as VUID-vkCmdDraw-viewType-07752 on every irradiance pass. Fill it
with the cube fallback instead.

Co-authored-by: Dustinspace217 <[email protected]>
2026-11-04 18:21:15 +01:00
dustinspace217andDustinspace217 81b9edc2e3 Vulkan: sample the thruster distortion map with its repeat sampler (#7940)
VulkanDistortion creates a linear/repeat sampler for its noise textures,
matching OpenGL's GL_REPEAT on Distortion_texture, but getTextureInfo
handed out the post-processing linearSampler instead, which clamps. The
sampler that was built for it is now the one used.

Co-authored-by: Dustinspace217 <[email protected]>
2026-11-04 18:20:53 +01:00
dustinspace217andDustinspace217 259a58de60 Vulkan: apply each material's depth bias, like OpenGL (#7939)
Squad logos are drawn with a material depth bias of 1 so they sit on top
of the hull. Under Vulkan the bias was written to the dynamic state but
the pipeline was built with depth bias disabled, so it never applied.
applyMaterial now calls vulkan_zbias with the material's bias before
building the pipeline config, the same way opengl_tnl_set_material calls
gr_zbias.

vulkan_zbias also passed its two values in the wrong order:
glPolygonOffset takes (slope factor, constant units) while
setDepthBias here takes (constant, slope).

Co-authored-by: Dustinspace217 <[email protected]>
2026-11-04 18:20:24 +01:00
dustinspace217andDustinspace217 b7e7949413 Vulkan: keep the swap chain pass open when irradiance setup fails (#7938)
vulkan_calculate_irrmap ended the swap chain render pass first and then
had four early returns (pipeline, UBO create, UBO allocate, UBO map)
before resumeSwapChainPass, so any of those failures left the rest of
the frame recording draws outside a render pass. End the pass only after
the setup that can fail.

Co-authored-by: Dustinspace217 <[email protected]>
2026-11-04 18:20:02 +01:00
dustinspace217andDustinspace217 32db880e94 Vulkan: retire BLASes and the TLASes built from them safely (#7937)
A BLAS was destroyed immediately when its model was unloaded or when the
vertex heap grew and the BLAS had to be rebuilt, while the TLAS of a
frame still on the GPU could reference it by device address. Queue the
acceleration structure and its buffer on the deletion queue instead, the
same way ensureTlasCapacity already retires the TLAS. The deletion queue
is flushed after ray tracing shuts down, so shutdown defers too.

A built TLAS also kept pointing at retired BLASes for later frames: the
live shadow TLAS binding is only refreshed when a mission frame rebuilds
it, and buildTlas keeps the previous TLAS when it has no instances. With
ray-traced shadows on, flying a mission and then leaving and re-entering
the tech room (which frees every model on exit) bound such a TLAS on
every tech room frame. So when BLASes are retired, the built frame TLASes
are retired with them and the binding goes back to the empty fallback
TLAS until the next rebuild. onModelUnloaded reports whether it retired
anything, so the binding refresh only runs then.

Co-authored-by: Dustinspace217 <[email protected]>
2026-11-04 18:19:40 +01:00
dustinspace217andDustinspace217 03589b49bf Vulkan: reject truncated shader cache files (#7936)
The runtime shader compiler cached SPIR-V on disk without checking the
write, and accepted any cached file whose size was a multiple of four.
A file cut short by a crash or a full disk was handed to the driver as a
shader module. With half-length cache files in place, master crashes in
the NVIDIA driver on entering SM1-01 under -vulkan.

Cached files are now checked before use: the SPIR-V magic word, and the
instruction word counts must end exactly at the end of the file on an
OpFunctionEnd. A file that fails is logged, recompiled and rewritten.
Fresh compiler output is checked the same way before it is cached, so a
module the check would reject is never written, and the log says so.

New cache files are written to a temporary file unique to the writer and
renamed over the old .spv only after a complete write and close, so a
game crash, or a second game writing the same shader, never leaves a
partial .spv. A power loss still can; the read check catches that.
purgeOldCache now also removes old temporary files.

Co-authored-by: Dustinspace217 <[email protected]>
2026-11-04 18:19:18 +01:00
dustinspace217andDustinspace217 c58a0cd307 Vulkan: give the default material's clip plane the model matrix (#7935)
The default-material shader transforms each vertex by the model matrix
before testing it against the clip plane. Vulkan always passed the
identity, so a clip plane given in world space (for example the warp
plane on a squad logo) was tested against model-space positions. OpenGL
passes gr_model_matrix_stack.get_transform(); do the same here.

Co-authored-by: Dustinspace217 <[email protected]>
2026-11-04 18:18:59 +01:00
dustinspace217andDustinspace217 0b42d0b6e7 Vulkan: upload a mission's environment map as a cubemap (#7934)
Under Vulkan a mission's $Environment Map was never uploaded: the
irradiance and lighting passes only use ENVMAP when its texture slot
already has an image view, and nothing created one for a bitmap that was
only bm_load'ed. OpenGL uploads it the first time it binds it as a
cubemap.

Add VulkanTextureManager::ensureCubemapUploaded(), called from both
passes. It locks the map the way OpenGL does for TCACHE_TYPE_CUBEMAP.
The lock uploads through bm_data(), which now sends uncompressed DDS
cubemaps (bm_is_compressed() returns 0 for them; comp_type identifies
them) to uploadCubemap() as DXT ones already went. If the slot still
holds no cube after the lock, because the data was already resident, it
uploads it directly. A map that cannot be used is remembered by bitmap
signature (handles are reused across missions) and warned about once in
debug builds, and the irradiance pass is skipped instead of sampling a
non-cube view.

uploadCubemap() also assumed uncompressed cubemaps had one mip level and
read the six faces back to back. DDS stores face0[mip0..mipN],
face1[...], so faces 1-5 read the tail of face 0's mip chain. It now
uses the file's mip count, expands 24-bit data over the whole chain,
generates a chain for a one-level uncompressed cubemap as OpenGL does
(the lighting pass samples by roughness), copies mip 0 for every array
layer, cleans up the image when the staging map fails, and clears
isCubemap as soon as it retires the old image.

Co-authored-by: Dustinspace217 <[email protected]>
2026-11-04 18:18:40 +01:00
dustinspace217andDustinspace217 030bbaa793 Fall back to the generated environment map when a mission's is not a cubemap (#7930)
A mission's $Environment Map is used as a cubemap without checking. Naming
an ordinary 2D texture made OpenGL copy six faces out of a one-face image
and crash when the mission started (issue #2245). Check the loaded bitmap
with a new bm_is_cubemap(); if it is not a cubemap, warn, release it and
build the generated environment map, as for a mission that names none.

DX10 (BC7) DDS files carry no cube flag through the loader, so they are let
through unchecked, as before.

Co-authored-by: Dustinspace217 <[email protected]>
2026-11-04 18:18:02 +01:00
dustinspace217andDustinspace217 8ca1ad9bbc P3: Vulkan: draw decals, and draw them like OpenGL does (#7879)
* Vulkan: bind all four model-matrix attribute locations for decals

The Vulkan shader input mask had one bit per listed attribute, so the
pipeline dropped the MATRIX4 attributes at locations 9-11 and the decal
vertex shader read three of its four instance-matrix columns from unbound
inputs. No decal was ever drawn in Vulkan.

The mask now covers four locations for VATTRIB_MODEL_MATRIX. It is built
by shader_vertex_input_mask() in shader_types so both Vulkan call sites
share it and a unit test covers it without a Vulkan build.

Fixes #7866

* Vulkan: build the decal pipeline with the material's shader flags

The decal pipeline config set the shader type but not the flags, so the
decal shader variant was always compiled without USE_NORMAL_MAP and fell
back to screen-space derivative normals for angle rejection. Far more of
the decal volume passed the angle test than in OpenGL, which lit the
decals brighter and wider. The pipeline now takes the flags the material
reports, as the other draw paths already do.

* Review fixes: exact input-mask test, decal flag polarity test, mask width check

Pin the full mask per shader type instead of the presence of the matrix
bits, so an expansion of any other attribute fails; pin that a decal
without a normal map is the one that reads the scene normal buffer; add a
compile-time bound on the mask width; trim two comments.

---------

Co-authored-by: Dustinspace217 <[email protected]>
2026-11-04 18:17:31 +01:00
dustinspace217andDustinspace217 499b3fb38b P4: qtFRED: rotate around the centre of a multi-selection (#7843)
* qtFRED: rotate around the centre of a multi-selection

Rotate Around Object (Alt+R) and the orbit pivot both used the current
object's position, so with several ships marked the camera still
circled whichever one happened to be current. Add
EditorViewport::selectionCenter(), which returns the centroid of the
marked objects when more than one is marked and the given object's
position otherwise, and use it at the three places that chose the
point: the look-at camera update, orbitCameraGetPivot(), and the Alt+R
handler that turns the camera toward the target when the mode is
switched on.

orbitCameraGetPivot() also serves middle-mouse orbit and wheel zoom in
normal mode, so those pivot on the selection centre as well. Every
marked type counts at full weight, including waypoints and jump nodes.
With a single object marked nothing changes.

Fixes #7724

* Make EditorViewport::selectionCenter const

It only reads the marked-object list and the editor's marked count;
clang-tidy (readability-make-member-function-const) flagged it in CI.

* Mirror the multi-selection orbit pivot to FRED, trim comments

FRED gets the same selection_center() as qtFRED's selectionCenter(),
used at the same three places: the look-at camera update, the orbit
pivot, and the Rotate Around Object toggle. Also removes the qtFRED
comments flagged in review.

* Make the selection-center pivot optional and share object bounds

Look-at and orbit use the current object again unless the new option is
on (qtFRED: Preferences > Controls; FRED: View menu), and the selection
center is now the bounding-box midpoint. view_universe in both editors,
the pivot, and qtFRED's selection radius all use get_object_bounds() in
missioneditor/common.

* Reset the cached orbit pivot when the selection-center option changes

Wheel zoom reuses the orbit pivot until the camera moves, so without this
the option only took effect for scrolling after the next camera move.

* Address review: move and rename the selection-centroid option

- FRED: the option moves from the View menu to Settings (and Misc in the
  classic layout), under Move Ships When Undocking, with hover help, and is
  renamed Rotate Around Selection Centroid.
- qtFRED: the checkbox moves next to Move Ships When Undocking in the
  preferences dialog, with the same name.
- setOrbitSelectionCenter only resets the orbit when the value changes.
- get_object_bounds iterates with list_range.
- The bounds test resets the object system with obj_init and takes objects
  off the free list, so it leaves a valid state for other tests.

* Rename the option to Selection Midpoint, move it to qtFRED's Controls tab, and document it

* Fix overlapping Appearance rows in qtFRED Preferences, realign a settings line

The last commit renumbered the Viewport label size row of the Appearance
group from 4 to 3, which is the Long list menu style row, so the label
size pair fell out of the layout and its spin box drew over the group
title. Put it back at row 4.

---------

Co-authored-by: Dustinspace217 <[email protected]>
2026-11-04 01:17:00 -05:00
dustinspace217andDustinspace217 12bddb8366 P3: Add weapon_info::clone (#7847)
* Add weapon_info::clone

Copies every member of a weapon class, following ship_info::clone, so a
weapon can be created from another as ship templates do. The
CLONE_UNIQUE_PTR macro moves from ship.cpp to utils/string_utils.h so
both clone functions share it.

* Group weapon_info::clone fields as the struct declares them

---------

Co-authored-by: Dustinspace217 <[email protected]>
2026-11-04 01:16:41 -05:00
c47a8bf351 P4: Make cfread_string_len read no more than fits in the buffer (#7863)
* Make cfread_string_len read no more than fits in the buffer

The char* version only checked the stored length with an Assertion, so in
release builds a string longer than the buffer was read past its end, and a
negative length wrote before it. It now reads at most buffer_size - 1
characters, skips the rest so the next read stays aligned, always terminates
the buffer, and logs when characters are dropped.

The SCP_string version clamps a negative or past-the-end length to what the
file still holds, so a corrupt length can't cause a huge allocation.

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

* Warn when cfread_string_len cuts or rejects a string

The char* version now reads up to the full buffer, so a string that fills it
exactly without a terminator is caught rather than cut silently. Both versions
clamp the stored length to what the file still holds and check the read limit
before reading, since cfseek does not; inside a limited pilot section a corrupt
length, negative or too long, throws cfile::max_read_length so the loader can
drop the section. A cut string, a corrupt length or a failed read gets a
Warning, and a failed read no longer leaves the file position off by the bytes
it did read. A buffer size of 0 skips the string without writing to the buffer.

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

* Write the cfread_string_len warnings inline with format arguments

* Share one core between the cfread_string_len overloads

Both overloads now read the length, work out what is stored, read the
body and warn through the same three steps. A length that does not fit
the file throws inside a limited section even when the section ends at
the end of the file, a zero-size buffer still warns about a corrupt
length, and a cut-off length field warns (or throws inside a limited
section) instead of reading as an empty string.

* Share cfread's read-size check with cfread_string_len

The string reader had its own copy of the read-limit check, without the
clamp to end-of-file that cfread does first, so a truncated last section
threw where it used to load. Both now call cf_check_read_size. A negative
length still throws inside a limited section, a cut-off length field only
warns, the cut warning counts bytes, and the helpers sit in an anonymous
namespace.

* Keep cfread_string_len quiet at a clean end of file

A string read at a clean end of file now returns an empty string without a
warning, as cfread_int does; a length field cut partway still warns, so a
truncated pilot list no longer raises one warning per remaining entry. A
zero-size buffer warns when it drops a string, and an invalid file reads as an
empty string again. Adds tests for an empty string inside a limited section,
the one-short boundary, a length still past the limit after the clamp, a
length field cut by the limit, the string returned after a failed read, and
cfread at end of file.

* Warn when a length field is missing before the end of a file

A failed length read now counts as a clean end of file only when cfeof says
so, so a file whose recorded size is larger than its data still warns. Tests
now pin the read-limit check after the end-of-file clamp, cfread at the end of
file past the limit, a length field cut short on disk, the zero-buffer
Assertion in Debug, and an empty string into a zero-size buffer.

* Close the test file before removing it in the cfile temp-file guards

Windows cannot delete an open file, and a failed ASSERT returns with the
file still open, so the guard now closes it first.

---------

Co-authored-by: Dustinspace217 <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-04 01:15:49 -05:00
Goober5000 3489da442a Merge pull request #7957 from BTawaifi/feature/subsystem-eq
Add __eq to the subsystem scripting handle
2026-11-04 01:04:28 -05:00
Goober5000 abf796f75f Merge pull request #7991 from Goober5000/version_26_3
bump version to 26.3
2026-11-03 23:41:25 -05:00
Goober5000 89d69189c8 bump version to 26.3 2026-11-03 22:39:54 -05:00
Goober5000andDustinspace217 b8bd1daa6e Keep the mission's own ship when a quick start finds no last flown ship (#7962)
A new pilot, or one whose last ship isn't in the current mod, has no last
flown ship. create_default_player_ship() then shows a popup and returns an
error; release builds carry on with the mission's own player ship, but
debug builds asserted. Drop the assert so both builds behave the same.

Co-authored-by: Dustinspace217 <[email protected]>
2026-11-02 22:42:30 -05:00
LuytenKy bb69b1b9db Refactor: CC_preset (#7809)
* CC_preset: Prevent post-construction type modification and handle pst duplication

* remove redundant function

* CC_preset: remove default constructor, require explicit type

* remove duplicate
2026-11-02 22:40:54 -05:00
Goober5000 9fb692f12f Merge pull request #7896 from BTawaifi/feature/noninteractive-no-focus-pause
Don't pause on lost focus in noninteractive runs
2026-11-02 22:40:15 -05:00
Boutros Tawaifi 43ba8b859e Measure afterburner cooldowns on the mission clock (#7895)
* Measure afterburner cooldowns on the mission clock

afterburners_start()/afterburners_stop() timed the +Aburn Cooldown Time delay and
the player's 1.3 s re-engage delay with timer_get_milliseconds(), the real
clock. Under time compression a cooldown therefore lasted longer in mission
time than the table says, and it kept running while the game was paused. The
cooldown was added in 2020 and reused the retail re-engage check's real-clock
"now"; the rest of the file (ABURN_DECAY_TIME, the sound timers) already uses
mission timestamps.

Both delays now use the mission clock. The mission clock does not run outside
missions, so it can still be near zero when the first mission of a session
starts; the "last stopped" and "last started" times therefore start out
invalid instead of 0, so that no delay is in effect before the afterburner has
been used.

Without time compression or pausing, the delays behave as before.

* Drop stale afterburner comment and use fl2i for the cooldown cast

The comment described an earlier startup state rather than the current
behavior. fl2i is the project's float-to-int macro and truncates the same
way the old C-style cast did.

* Remove the remaining afterburner cooldown comment

Timestamps running on mission time is the common case, so it doesn't
need a comment.
2026-11-02 22:39:31 -05:00
Shivansps 49f4b1d9c7 Support GLES in internal shaders and Vulkan software clip distance fallback (#7489)
* GLES Shaders and Vulkan Clip fallback

* clip fallback: change defined name, set from engine side and invert usage

* Proposed changes and fixes

Fixed:
-The OpenGL ES option stays on when OpenGL is off (issue #7984)
-Missing precision in copy-f, volumetric-f and smaa-resolve-f
-msaa-f including z-compress before precision quantification
-missing float literals in volumetric-f
-Fixed decals blending
-Restored glBindFragDataLocation() for use in legacy external shaders
-Enabled GL_ARB_explicit_attrib_location ext for GLSL version below 330 in GL
-Guarded enabling/disabling of GL_CLIP_DISTANCE0_EXT and GL_DEPTH_CLAMP
-Restored PI in lighting.sdr
-removed "External shaders only" indication from cmakelists.txt
2026-11-03 12:20:21 +09:00
Shivansps 9f55626272 Fix incomplete implementation of ETC2 textures in Vulkan (#7699)
* finish etc2 textures in vulkan implementation

* add etcdec implementation

* Enable textureCompressionETC2 at vulkan device creation and fixed outdated comments
2026-11-03 12:20:11 +09:00
Dustinspace217 670df9b97e Keep the mission's own ship when a quick start finds no last flown ship
A new pilot, or one whose last ship isn't in the current mod, has no last
flown ship. create_default_player_ship() then shows a popup and returns an
error; release builds carry on with the mission's own player ship, but
debug builds asserted. Drop the assert so both builds behave the same.
2026-10-29 03:50:51 -07:00
btawaifi 9a70489dbc Add __eq to the subsystem scripting handle
Two subsystem handles are equal when both are valid and refer to the same
subsystem on the same ship. Invalid handles compare as not equal, matching
the other handle types.
2026-10-29 00:56:03 +03:00
BMagnu 8afa318428 Clamp requested particle spawn time in both directions (#7927) 2026-10-27 13:00:00 +09:00
Goober5000 cf9c5590e3 Merge pull request #7926 from Goober5000/fix/jumpnode_rename
Fix jump node renames being lost in FRED2
2026-10-26 23:00:31 -04:00
Goober5000andClaude Opus 5.5 9879110c1f Fix jump node renames being lost in FRED2
The jump node dialog only copied control text into its member variables
in OnClose(), so update_data() calls from selection changes, saves, and
the dialog's own node menu applied stale values and discarded the edit.
Call UpdateData(TRUE) inside update_data(), as the waypoint path dialog
does.  This regressed when the dialog was split out of the waypoint
dialog in 78b957982.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 19:15:00 -04:00
Goober5000 a8d5b3d6f9 Merge pull request #7921 from Goober5000/fix/vulkan_texture_addressing
honor material texture addressing in Vulkan
2026-10-24 03:10:07 -04:00
Goober5000andClaude Fable 5.1 acf613c13c Fix FastDebug builds linking the non-redistributable debug CRT (#7919)
* fix FastDebug QtFRED linking the debug CRT

FastDebug builds in the DLL-runtime tree (used whenever qtFRED is
enabled) compiled with /MDd and, since #7450, linked Qt's debug
libraries.  Both import ucrtbased.dll, MSVCP140D.dll and
VCRUNTIME140D.dll, which Microsoft does not license for redistribution,
so end users could not run the FastDebug qtFRED shipped in nightlies
and releases.  Static Qt is ruled out by the LGPL, so the only shippable
shape is the release CRT plus release Qt.

On MSVC, _DEBUG is the CRT's own contract: /MTd and /MDd define it
themselves, and the STL headers select msvcprtd.lib and
_ITERATOR_DEBUG_LEVEL=2 from it.  Stop force-defining it for FastDebug
on MSVC; the engine keys its debug behaviour on NDEBUG, and the few
_DEBUG guards in code/ are switched to NDEBUG so they keep working.
The static tree is unchanged: /MTd still defines _DEBUG there.

Fixes #7915

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* derive the MSVC runtime choice once

The "DLL runtime or not" condition was spelled out in the toolchain and
twice in fred2 with "keep in sync" comments, and gtest_force_shared_crt
and antlr4's WITH_STATIC_CRT had already drifted: they only looked at
MSVC_USE_RUNTIME_DLL, so a qtFRED tree with tests enabled would have
asked both for the static CRT.  That is harmless today only because our
flag strings carry no literal /MD for them to rewrite.

Compute FSO_MSVC_RUNTIME_DLL once in toolchain-msvc.cmake and use it
everywhere.  InstallRequiredSystemLibraries deliberately stays keyed on
MSVC_USE_RUNTIME_DLL: the qtFRED deploy ships no compiler runtime.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* remove FastDebug runtime leftovers

The /MD -> /MDd substitution on the FastDebug flag strings has done
nothing since the runtime moved to CMAKE_MSVC_RUNTIME_LIBRARY: the
Release flags it copies contain no runtime switch.  The commented-out
_ITERATOR_DEBUG_LEVEL=0 lines for gtest and the OpenXR loader belong to
the scheme #7450 removed from the toolchain.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* use the release CRT for all FastDebug builds

Microsoft's documentation marks the static debug libraries (libcmtd,
libucrtd, libvcruntimed) "Not redistributable" and states that debug
versions of an application may only be copied to machines used for
testing, and the license grants distribution rights only for code on
the REDIST list, which contains no debug library in any form.  So the
statically linked FastDebug game and FRED2 are no more shippable than
the /MDd qtFRED was.

Link every FastDebug build against the release runtime.  The engine's
debug behaviour keys on NDEBUG, which FastDebug still leaves undefined,
so asserts, warnings and mprintf are unaffected; only the debug CRT's
heap checking goes, and that remains available in Debug builds.  FRED2's
static MFC library follows the CRT: the debug variant requires the debug
runtime.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* keep the debug icon, version flag and FRED2 asserts without _DEBUG

The resource compiler only ever sees the explicit definitions, so with
_DEBUG no longer defined for FastDebug the game lost its debug icon and
both editors lost the VS_FF_DEBUG version flag.  Key those on NDEBUG,
which FastDebug still leaves undefined.

FRED2 also used MFC's ASSERT, VERIFY, ASSERT_VALID and TRACE0, which are
all compiled out without _DEBUG.  Convert them to the FSO equivalents
FRED2 already uses nearly everywhere else: Assert, Verify, a null check
in place of ASSERT_VALID (AssertValid only exists in debug MFC), and
mprintf.  The AssertValid/Dump overrides and DEBUG_NEW stay under
_DEBUG, as they must.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

---------

Co-authored-by: Claude Fable 5.1 <[email protected]>
2026-10-24 03:09:57 -04:00
Goober5000andClaude Opus 5.5 afd92bb3f9 honor material texture addressing in Vulkan
Vulkan never read the material's texture addressing, so MR_FORCE_CLAMP
skyboxes were sampled with a wrapping sampler, producing seams.  Set it
on every material draw, as OpenGL does in opengl_tnl_set_material().

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-22 23:47:54 -04:00
Taylor Richards 76219fd1e2 lower Vulkan requirement and fix shaderc includes (#7902)
Lower Vulkan version requirement to 1.4.304, which should be the lowest
version supported without requiring code changes. This should also offer
out of the box support on current Linux distros, such as Debian 13 (trixie)
and Ubuntu 25.10 (questing) & 26.04 (resolute).

Also make sure that the shaderc headers are available and added to the
include path on Linux when using system libraries.
2026-10-18 05:53:10 -04:00
btawaifi 52fb1bdc8d Don't pause on lost focus in noninteractive runs
The game pauses when its window loses focus. With in-game options on, that is
governed by the Game.UnfocusedPause option (default on), and the legacy
-no_unfocused_pause flag is ignored, as other legacy flags are. An unattended
run started with -noninteractive therefore stopped as soon as another window
was in front, for example when several test instances run side by side.

-noninteractive means nobody is at the keyboard to resume the game, so
pause_if_unfocused() now returns false for it. This covers all three focus
paths (game_poll, game_pause, game_unpause). Interactive runs are unchanged.
2026-10-18 00:44:52 +03:00
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
Taylor Richards b477dd5ca9 lower required SDL3 version to 3.2.8 (#7876)
This should be a safe minimum for us when system libraries are either
required or preferred. Most current Linux distros that have SDL3 should
support this version or better.
2026-10-16 04:36:51 -04:00
Goober5000 7b80cd4897 Merge pull request #7871 from notimaginative/prebuilt_update
bump prebuilt libs for SDL 3.4.18
2026-10-15 23:19:01 -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
Taylor Richards 2ab398b34a bump prebuilt libs for SDL 3.4.18 2026-10-15 03:47:06 -04: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
Goober5000 72d579e010 Merge pull request #7818 from Goober5000/fix/ai_target_recovery
apply $fix ai target recovery's pursuit filter only to targets picked for pursuit
2026-10-12 22:59:38 -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
Goober5000 904edfee44 Merge pull request #7842 from dustinspace217/qtfred/hidpi-font
qtFRED: scale viewport labels by the device pixel ratio
2026-10-12 21:35:46 -04:00
Goober5000 d9b4d81d93 Merge pull request #7844 from Goober5000/fix/mission_splash_scaling
Scale mission load screen backgrounds to fit the screen
2026-10-12 21:29:00 -04:00
Goober5000 4fcd470135 Merge pull request #7846 from scp-fs2open/dependabot/pip/ci/post/mako-1.4.2
Bump mako from 1.3.12 to 1.4.2 in /ci/post
2026-10-12 21:28:26 -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 6e469a2180 Merge pull request #7837 from Goober5000/fix/ai_mode_fields
reset stale AI order state and track previous_mode through dynamic chases
2026-10-12 21:24:31 -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
dependabot[bot] cba5d1822f Bump mako from 1.3.12 to 1.4.2 in /ci/post
Bumps [mako](https://github.com/sqlalchemy/mako) from 1.3.12 to 1.4.2.
- [Release notes](https://github.com/sqlalchemy/mako/releases)
- [Changelog](https://github.com/sqlalchemy/mako/blob/main/CHANGES)
- [Commits](https://github.com/sqlalchemy/mako/commits)

---
updated-dependencies:
- dependency-name: mako
  dependency-version: 1.4.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-10-12 05:34:09 +00:00
Goober5000 94dd3af03c Merge pull request #7832 from notimaginative/vs2026_workflow
switch Windows workflows to Visual Studio 2026
2026-10-12 01:33:04 -04:00
Dustinspace217 0966d94dc4 Trim comments 2026-10-11 22:11:02 -07:00
Goober5000andClaude Opus 5.5 6e4a55263e Scale mission load screen backgrounds to fit the screen
The load screen background was drawn in the fixed 1024x768 layout, so an
image larger than that overshot the screen and only a corner was visible.
Set the screen scale to the background's dimensions, as the main hall and
title splash already do, and draw the loading animation in the same
layout.

The loading animation is assumed to match the default loading background
(or the default layout, if there is none).  That reference is fit into
the mission's background the way menus are fit into the screen: scaled by
the limiting dimension and centered in the other.  So a mission can use a
background of any resolution or aspect ratio without the animation
changing size or position relative to it.  Retail assets behave as
before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 23:10:06 -04:00
Dustinspace217 d5ca49f916 qtFRED: scale viewport labels by the device pixel ratio
On a HiDPI monitor with OS scaling, every Qt widget in qtFRED scales
but the ship labels, waypoint labels and coordinate readout drawn by
the engine inside the viewport do not, because the engine draws them in
physical pixels. At 200% they come out half the size of everything
around them.

FredRenderer already receives the window's device pixel ratio as the
scale argument of render_frame(). Remember it there and multiply it into
the label font scale at every label site, so the user's Preferences
value stays "100% = normal size on screen" regardless of OS scaling.

Fixes #7727
2026-10-11 03:20:33 -07: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
Goober5000 82e7a0793f Merge pull request #7821 from Goober5000/fix/pof_parsing
Harden POF loading: texture limits, file handles, virtual POF failures
2026-10-08 12:06:50 -04: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
Goober5000andClaude Opus 5.5 9ffa5192b0 stay on the v143 toolset and install its ATL/MFC
MSVC 14.50 (v145) no longer targets Windows 7, 8, or 8.1, so building
with it would quietly drop those platforms.  The VS 2026 runner images
include the v143 compiler but ship ATL and MFC only for the latest
toolset, and both are needed: ATL by SAPI speech (sphelper.h) and MFC
by FRED2.  Install the 14.44 ATL/MFC components before configuring and
keep building with v143, matching the 26.0 release.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-07 13:02:32 -04:00
Taylor Richards 521ddcbcca bump platform toolset to 145 (Visual Studio 2026)
The Github runners do not currently include a ATL/MFC version for older
platform versions. As we still need MFC for FRED2 this is the easiest fix.
2026-10-06 23:46:02 -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
Taylor Richards f54b3fdc5c switch Windows workflows to Visual Studio 2026
All default Windows runners should now be moved over the Visual Studio 2026,
with the exception of windows-2022 (which may be retired before long). So
move to windows-2025 and update the CMake generator to use vs2026 as well.
2026-10-06 19:01:43 -04:00
Goober5000 45b9d31d76 Merge pull request #7820 from Goober5000/fix/insignia_lighting
light insignias using lights rotated into their frame of reference
2026-10-05 21:05:30 -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
Goober5000 05a3df4bdd Merge pull request #7819 from Goober5000/fix/norms_faces
store insignia normals per face, and light insignia faces with them
2026-10-05 00:16:01 -04:00
Goober5000 3c7dc9bb35 Merge pull request #7717 from JohnAFernandez/Resize-qtfred-Messages-properties
QTFRED: Event Editor, move Items to a grid layout to better utilize spacing
2026-10-04 15:23:01 -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
Goober5000 2838d25ab7 Merge pull request #7816 from TheForce172/fix/Qtfred-object-z-axis
QTFred: Object Dialog: Matches Z POS spinner limits with other axis
2026-10-04 13:21:01 -04:00
Goober5000 b666d7eebc Merge pull request #7817 from TheForce172/fix/7815
QTFRED: Fix #7815: Arrival/Departure Cue Update
2026-10-04 13:17:02 -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
Goober5000 1db8c586c5 Merge pull request #7813 from Goober5000/fix/bay_path_speed_ramp
Fix the fighterbay speed ramp on detoured departure paths
2026-10-02 21:42:03 -04:00
The Force fc626df89b Fix #7815 2026-10-02 20:52:51 +01:00
The Force d4b868daa0 Match Z Axis spinner limits with other axis 2026-10-02 20:30:16 +01: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
Goober5000 80652b13ea Merge pull request #7807 from Goober5000/fix/moment_of_inertia
fix the collision moment of inertia rotation
2026-10-01 20:02:11 -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
Goober5000 89dedeb08e Merge pull request #7812 from Goober5000/fix/bay_path_name
Fix bay path speed lookup reading paths[-1] on detoured paths
2026-10-01 14:54:57 -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 976edb1b0f Merge pull request #7810 from Goober5000/fix/destroyed_submodels
handle submodel repair/destruction consistently
2026-09-30 22:07:18 -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 e65145a6d6 fix qtFRED's team loadout combo in single-team multiplayer missions
qtFRED's team loadout dialog enabled its team combo and its copy-to-other-teams button on MISSION_TYPE_MULTI, which encompasses more than just Team-versus-Team.  Gate both on Num_teams, which is what FRED's own loadout editor uses, and build the team list from the teams the mission actually has.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-25 03:22:23 -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
BMagnu 90813e3716 Mirror scp-fs2open/nightlybuild#118 (#7802) 2026-09-24 08:08:36 +00: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
Goober5000 5d89c6cce7 Merge pull request #7796 from Goober5000/fix/3999
Stop the deferred light pass from lighting unlit models
2026-09-23 21:04:44 -04:00
Goober5000 d56e7f1bc1 Merge pull request #7797 from Goober5000/fix/shockwave_fade
Restore the 3D shockwave fade, and time animated base textures like the glow map
2026-09-23 21:04:12 -04:00
Goober5000 f51ae61551 Merge pull request #7792 from Goober5000/fix/offscreen_indicator
Fix the offscreen target indicator flickering when targeting a subsystem on a distant ship
2026-09-23 21:03:47 -04:00
Goober5000andClaude Fable 5.1 11781a5081 Offscreen indicator: read the clipped vertex before freeing it and fix the min-side clamps
The overflow flag and screen position were read from a temp vertex
already returned to the pool, which only worked because nothing
allocated in between. Copy them out first.

The clamps that keep the indicator away from the top and left of the
clip rectangle subtracted the clip origin instead of adding it, and
dropped it from the clamped value. Inert while the origin is zero, but
wrong for any other clip rectangle.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-23 00:07:19 -04:00
Goober5000andClaude Fable 5.1 b609a6857b Time an animated shockwave base texture like its glow map
A 3D shockwave's glow map already takes its frame from the shockwave's
elapsed time so the animation spans the shockwave's life.  Do the same
for an animated base texture, and skip the radius fade for it, since the
artist's frames then control the brightness the way they do for the glow.
A static base texture still fades with the radius.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-22 01:58:37 -04:00
Goober5000andClaude Fable 5.1 537ad2788d Stop the deferred light pass from lighting unlit models
Models drawn without lighting under deferred rendering copy their base
colour into the emissive buffer and zero the ambient occlusion factor,
but still write their real diffuse and specular data to the G-buffer.
Each direct light then adds diffuse and specular on top of the emissive
copy, and the environment map adds its specular term, so the "no
lighting" flag only appeared to work in dark scenes.

Zero the diffuse and specular outputs after the emissive copy.  The
specular alpha is the Fresnel factor, so clearing the whole vector also
removes the grazing-angle sheen.

Fixes #3999

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-22 01:46:29 -04:00
Goober5000andClaude Fable 5.1 1bfa46eabe Restore the fade on unlit additive model draws
3D shockwaves pass `1 - radius/outer_radius` as the warp alpha, which `model_render_determine_color` turns into the material colour for additive draws.  Since a481999a2 (SamuelCho's OpenGL Code commit in August 2016), the model fragment shader has not multiplied the diffuse sample by that colour in the unlit branch, so the base texture stayed at full brightness and vanished abruptly.  The same path carries the warp effect's radius fade and the alpha of unlit transparent weapons.

Clamp the alpha before converting it to a byte, as the pre-2016 code did; thrusters pass 1.2, and without the clamp they would now render dim.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-22 01:27:49 -04:00
Fabian Woltermann 115491f8a9 Refactor RT shadow handling and Vulkan depth operations (#7685)
- Introduced `shadow_ray_params` to simplify ray origin and cull mask management.
- Shifted shadow ray origins to TLAS-relative space for improved precision and consistency, especially for cockpit rendering.
- Refactored cascade bind functions, adding `shadow_cascade_params_bind_deferred()` for consolidated offset/count handling in deferred lighting.
- Expanded TLAS to include cockpits and additional object types, ensuring parity with rasterized shadows.
- Introduced per-instance cull masks to address cockpit self-shadowing issues.
- Improved Vulkan depth isolation for cockpit rendering via backup/restore mechanism, ensuring correct depth sampling in later passes.
- Extracted reusable helper functions for cockpit rendering state logic.
2026-09-21 05:12:55 +02:00
Goober5000andClaude Opus 5 0dcee2a6be Add scripting access for per-guarder guard ranges (#7794)
Follow-up to #7606.  Restore the MaxGuardRadius property, restore the ability to set specific ranges using set-guard-range, and add scripting functions for setting the specific ranges.

Fixes #7756

Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
2026-09-21 05:12:47 +02:00
Goober5000andClaude Opus 5 09fc7055b5 Fix FRED and qtFRED memory leaks at exit (#7784)
Neither editor had a teardown to match FSO's game_shutdown(), so the engine
data they set up was never freed.  Add editor_free_engine_data() to free it,
and call it from a new fred_shutdown() in FRED and from qtFRED's shutdown(),
before the graphics system closes.

Also:
- Delete FRED's modeless editors if they are still open at exit.
- Make FRED's dialog tooltips and sexp tree image lists members, since
  they were leaked or depended on OnDestroy() being called.
- Parent qtFRED's item models so they are deleted with their widgets.
- Enable the CRT leak report in qtFRED, which previously had none.
- In qtFRED, call shutdown() last in closeEvent(), since it destroys the
  window.
- Make the preferences path and user SID strings function-level statics,
  so MFC's leak dump no longer reports them as leaks.

Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
2026-09-21 05:12:35 +02:00
Goober5000 d841f0a08b Merge pull request #7791 from Goober5000/fix/bigendian_bsp_swap
Fix big-endian swapping of SORTNORM2 and TMAP2POLY chunks
2026-09-19 14:41:08 -04:00
wookieejedi eb558395fd Update freespace.cpp (#7788)
Bug:
"No matter what value I'm setting in default_settings table with the entry below, it doesn't affect any new pilots I create."

Cause:
The skill option's table parser only sets Game_skill_level directly. Every other place that asks for "the default" ignores it and uses the hard-coded DEFAULT_SKILL_LEVEL (1, which is "Easy"), so the tabled value gets overwritten twice:

At startup (freespace.cpp:1919-1920): default_settings_init() sets Game_skill_level from your table. Then loadInitialValues() runs right after. If Game.SkillLevel isn't saved in the ini yet, it falls back to default_func. That returned the constant 1, and bind_to wrote it back over your value.
When a pilot is created (managepilot.cpp:100): init_new_pilot() does Game_skill_level = game_get_default_skill_level(), which also just returned the constant.

This PR follows the same strategy of settings like sound volume to allow modders to specify a default setting for difficulty.
2026-09-19 15:54:22 +02:00
Goober5000andClaude Fable 5.1 12323ef3f6 Fix the offscreen target indicator drawing with uninitialized position and direction
HudGaugeOffscreen::calculatePosition could return without writing its
outputs, either when the eye vertex overflowed or when the clipped
point landed a pixel or two inside the clip rectangle. The caller drew
anyway, so the indicator appeared at a random spot with a random
orientation that changed every frame. This showed up when targeting a
subsystem on a distant ship, where the clip loses enough precision to
hit the second case.

calculatePosition now returns whether it found a position, and both
callers skip drawing when it did not. The inside case falls back to
the nearest screen edge, since the point is already known to be off
screen, so the indicator stays steady instead of blinking.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-18 00:50:39 -04:00
Goober5000andClaude Fable 5.1 0f15b1b637 Store the targeted subsystem position in the current target's display list entries
The projected point was the subsystem but the stored position was the
hull centre, so the offscreen indicator sized its triangles for a
different point than the one it clipped against.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-18 00:50:39 -04:00
Goober5000andClaude Fable 5.1 b519bc8945 Set both flags when g3_project_vertex hits a vertex at zero depth
The expression used & instead of |, so neither PF_OVERFLOW nor
PF_PROJECTED was ever set on that path.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-17 23:15:11 -04:00
Goober5000andClaude Opus 5 ea75eda29d Fix big-endian swapping of SORTNORM2 and TMAP2POLY chunks
swap_bsp_sortnorm2 swapped the bounding box at offsets 8 and 20, which
re-swapped the front list offset and left the real box (at 16 and 28)
unswapped.  swap_bsp_tmap2poly never swapped the chunk's bounding box
or normal.  Both corrupted models using these chunks on big-endian
builds.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-17 22:20:31 -04:00
Taylor Richards 573ed72d4d fix prebuilt lib requirement for vulkan (#7789)
If prebuilt Vulkan was disabled, but a system-wide Vulkan install wasn't
found, then a fallback was available using prebuilt libs. Unfortunately
this does ultimately require the prebuilt libs be available which isn't
always wanted (or possible), so drop the fallback and just require Vulkan
instead.

Fixes #7786
2026-09-16 05:26:07 -04:00
Goober5000 0c33d9b90a Merge pull request #7787 from wookieejedi/fix-forum-post
Update forum.py
2026-09-15 23:30:43 -04:00
wookieejedi 8fae311924 Update forum.py 2026-09-15 09:40:48 -04:00
Fabian Woltermann 74fc04f7af silly claude, that's not how shields work (#7785) 2026-09-15 06:28:06 +02:00
Goober5000 bd5972c0ec Merge pull request #7781 from Goober5000/fix/mem_leak
Fix three memory leaks and make the debug leak check accurate
2026-09-14 21:49:21 -04:00
Goober5000andClaude Opus 5 519e3aef21 Fix three memory leaks and make the debug leak check accurate
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]>
2026-09-13 14:25:01 -04:00
Goober5000 1020a19855 Merge pull request #7755 from Goober5000/feature/giveorder-dockpoints
Allow giveOrder to specify dockpoints for ORDER_DOCK
2026-09-13 12:03:21 -04:00
Goober5000 5d45ba4de9 Merge pull request #7777 from Goober5000/fix/Cockpit_active
Replace the Cockpit_active global with computed predicates
2026-09-12 03:04:29 -04:00
Kestrellius 996e2fef6b Option to disable main fireball when ship is destroyed (#7179)
* add toggle

* minimize diff

* rebase gore

* whitespace

* move line
2026-09-11 23:28:14 -07:00
BMagnu 1cd85cbd63 Reorder HID deinit (#7778)
Close spacemouse -> Close HID -> Close SDL
2026-09-12 03:57:27 +00:00
Goober5000andClaude Opus 5 e2be023f84 Replace the Cockpit_active global with computed predicates
Cockpit_active was latched inside ship_render_player_ship(), which only
runs when Viewer_obj is non-null.  External, chase, warp-chase, topdown,
free-camera and dead views all null Viewer_obj, so the flag froze at its
last cockpit-view value and cockpitinactive HUD gauges vanished.  It was
also read before being written each frame (by the show-ship check and the
shadow helpers), so those decisions lagged a frame on any transition.

Replace it with three functions computed on demand:
- ship_cockpit_enabled(sip): the ship has a cockpit model and cockpits
  aren't disabled; used by the Show_ship_only_if_cockpits_enabled checks
- ship_render_player_cockpit(sip): the above, outside topdown view;
  replaces three duplicated copies of that expression
- ship_render_player_cockpit_active(): whether the viewer's cockpit is
  being rendered this frame; used by the HUD cockpit-view-choice checks

With the predicate correct, the VM_CHASE special cases added for #4804
are no longer needed and are removed.  In chase view, cockpitinactive
gauges now also respect pop-up state, No_ets, and setRenderOverride,
which the early return in HudGauge::canRender() used to bypass.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-11 23:39:05 -04:00
Goober5000andClaude Opus 5 2addd90aed ci: run the unit tests against SDL's dummy video driver (#7779)
The Linux Debug legs fail memcheck on six "definitely lost" contexts
(480 bytes in 12 blocks), all allocated under the temporary
SDL_InitSubSystem(SDL_INIT_VIDEO) in resolution_default(). SDL probes its
real video backends, dlopens libX11, and keeps a few small blocks across
the matching SDL_QuitSubSystem().

These are the same six contexts #7657 suppressed via fun:X11_CreateDevice.
That suppression stopped matching when #7774 bumped the prebuilt libs:
62c89ef6 shipped libSDL3.so.0.2.14 with a full .symtab (433 distinct X11_*
symbols), b57636e0 ships a stripped libSDL3.so.0.4.14 (none). X11_CreateDevice
is a static function, so valgrind can no longer resolve it and every libSDL3
frame prints as ???. Every PR branch based on post-bump master fails this leg.

Rather than rewrite the suppression around object wildcards, stop allocating
the memory in the first place. The unit tests never open a window and the
runners are headless, so pin SDL_VIDEODRIVER=dummy: SDL skips backend probing
entirely, libX11 is never loaded, and the leaks do not happen. It also makes
the tests behave identically whether or not the runner has a display. No test
depends on the real desktop mode; the only resolution test sets an explicit
override.

The os_init suppression stays. That leak is from SDL_Init(SDL_INIT_EVENTS)
and SDL_hid_init(), not from video, so the dummy driver does not affect it.
The X11_CreateDevice block also stays, as a backstop for running memcheck by
hand, now with a comment recording why it is inert in CI.

Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
2026-09-12 03:09:54 +00:00
Goober5000andClaude Opus 5 1fdb67649c Clear cockpit depth mask each frame so lightshafts don't use a stale one (#7775)
Views that don't render the cockpit (external, chase, topdown, etc.) left
Cockpit_depth_texture holding the last cockpit-view frame, so the lightshaft
pass painted that silhouette.  Fixes #7568.

Co-authored-by: Claude Opus 5 <[email protected]>
2026-09-12 02:59:38 +02:00
Goober5000 aee08e2bc8 Merge pull request #7759 from Goober5000/feature/remove_weapon_class_limits
Remove the weapon class limit
2026-09-10 16:29:51 -04:00
Fabian WoltermannandClaude Opus 5 9ec157833b Add command-line option overrides to options system (#7561)
* Add command-line option overrides to options system

- Extend `OptionsManager` to support storing and displaying reasons for overrides.
- Update related code paths to prioritize command-line overrides, ensure correct option resolution, and display override details in the UI.
- Add unit tests for override behavior and round-trip validation.
- Remove unused legacy functions (`removeVSyncOption`, `removeWindowModeOption`) in favor of the new system.

* Add unit tests for `Graphics.Anisotropy` option validation with and without OpenGL

* Add support for associating command-line option names to `OptionsManager` overrides

- Update HDR, shadow rendering, and graphics API option overrides to include their respective command-line flag names.
- Refactor `Graphics.RenderAPI` to use the `GraphicsAPI` enum instead of raw integers for better type safety and clarity.
- Ensure Vulkan support logic and fallback behavior function correctly with the updated options system.
- Update unit tests to validate override functionality and enum-based API configuration.

* Remove unused `removeVSyncOption` function

* Apply the PR review feedback for the command-line option overrides

Delete the unit-test run artifacts that this branch committed by mistake, and
add .gitignore rules for the files that the unittests binary writes.

Give an override priority over an in-session edit again. The options UI now
shows a disabled control for an overridden option, and setConfigValue()
refuses an edit for an overridden key. Before this change, a save wrote the
edit to the config file and applied it to the bound global, but getValue()
kept returning the override. The game and the options menu then showed
different values for the rest of the session.

Add a setOverride() overload that takes a JSON value. Every command-line flag
now builds its override with json_integer, json_real, json_true, json_false
or json_pack, in place of a hand-formatted string.

Make Graphics.CockpitFOV and Graphics.CockpitFOVToggle independent of the
order in which the options manager loads them, and remove the forced override
on the player's toggle preference. The toggle listener also replaces an
identical bind_to() call that made the listener dead code.

Add 16 to the values of Graphics.MSAASamples. The -msaa flag accepts 16, so an
override with that value made printValues() report an invalid value.

Keep the render API that the caller gives to gr_init(). Only a Default value
now comes from the Graphics.RenderAPI option, so FRED and the unit tests keep
the backend that they ask for.

Make the default_func of Graphics.Shadows and Graphics.ShadowRenderMethod
return their global. A constant discarded the value that -enable_shadows and
the mod table set. Set Max_rt_shadow_local_lights to 4, because 3 is not one
of the values that the option offers.

Also: resolve -lang one time, share the render API name with version.cpp,
correct the stale comments, and add unit tests with a fixture that clears the
overrides after each test.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UGh4ZM2u4oUGSbrP122Uyr

* Fix the order-dependent crash in the MSAA option test

The test `MsaaOverrideAcceptsEverySampleCountTheFlagAllows` calls
`getValidValues()`. `Option<T>` makes a display string for each value, and the
display function of `Graphics.MSAASamples` calls `XSTR()`. `XSTR()` runs
`Int3()` if `lcl_xstr_init()` did not run before. Only `FSTestFixture` runs it,
and `Xstr_inited` stays set for the remainder of the process. Thus the shuffled
test order decided if the test passed or if it stopped the test binary with
SIGILL.

Move that one test to a new fixture that derives from `FSTestFixture`. The
fixture initializes localization, and it also clears the overrides after each
test. Refresh the comment that told that the test binary never runs
`lcl_init()`.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UGh4ZM2u4oUGSbrP122Uyr

---------

Co-authored-by: Claude Opus 5 <[email protected]>
2026-09-10 13:14:18 +02:00
Taylor Richards a6136507ff bump prebuilt version (#7774)
Updates SDL version (fixes #7710)
Updates MoltenVK to fix numerous Mac specific Vulkan issues
2026-09-10 11:23:16 +02:00
Goober5000andClaude Fable 5 ba2505c3ac preserve custom player wing arrival cues (#7160) (#7716)
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]>
2026-09-10 11:22:30 +02:00
wookieejedi 643576b967 Safeguard divide by 0 for extra collision avoidance (#7773)
* Safeguard divide by 0 for extra collision avoidance

Extra collision avoidance could have divided by zero, so just safeguard that possibility.

* one more div 0 failsafe
2026-09-09 12:40:38 -04:00
Goober5000andClaude Opus 4.7 190472f77e Remove the weapon class limit
With every fixed-size weapon-class-indexed structure now converted to dynamic containers, we can finally remove the `MAX_WEAPON_TYPES` limit!

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-09-08 13:20:36 -04:00
Goober5000andClaude Fable 5.1 37b0b6ea82 Standardize isValid across the Lua API by making the types auto-generate it (#7761)
* Automatically generate the isValid() Lua API function for handle types

If an ADE object's stored type has an isValid() member, ade_obj now registers an isValid function for it automatically.  An explicit ADE_FUNC(isValid, ...) declared after the ADE_OBJ replaces the generated one, so types with special semantics (e.g. sound) keep their own.  The 67 hand-written functions that merely delegated to the member are removed, and 26 types that previously exposed no isValid() gain one.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* Add ADE_OBJ_VALIDATOR for index-based Lua handles

Int-index handles (shipclass, weaponclass, team, etc.) can now declare their validity predicate with a single ADE_OBJ_VALIDATOR or ADE_OBJ_VALIDATOR_RANGE line after the ADE_OBJ.  The registered validator drives the generated isValid() function and is also honored by ade_set_args when the mod table asks for nil instead of invalid objects, which previously never applied to these handles.  The 19 hand-written range-check functions are replaced, and loadout_ship, default_primary, default_secondary, and support_rearm_pool_team gain isValid().  Add a validator to HudGauge while we're at it.

Also fully qualify the scripting namespace in LuaConvert.h so its templates can be instantiated from the unit tests.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

---------

Co-authored-by: Claude Fable 5.1 <[email protected]>
2026-09-08 10:50:53 -04:00
Goober5000andClaude Opus 4.7 a168f5a9c0 Store the support rearm pool sparsely (#7758)
`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]>
2026-09-08 10:50:22 -04:00
Shivansps 8e60b69922 Fix unused param warnings when compiling with openxr off (#7770) 2026-09-08 10:48:50 -04:00
BMagnu a73e2565c8 Add Vulkan + Wayland init logic for OpenXR (#7767)
* Add Vulkan + Wayland init logic for OpenXR

* typo

* clang tidy
2026-09-08 10:48:33 -04:00
wookieejedi 3ac8be273e fix jump in chase view (#7771)
Chase view deltas are not reset on mission init, so ensure they are reset to prevent the viewing jumps that can be seen at the start of mission in chase view. Also ensure perspective and slew lock are properly resotred.
2026-09-08 10:48:13 -04:00
Shivansps d8afb71d41 Clear bsp_data from bsp_info after it has been used (#7754)
* clear bsp_data from bsp_info after has been used

* fix redundant redeclaration

* remove continue on null bsp_data
2026-09-08 10:47:55 -04:00
Goober5000 8aa07a3e9a Merge pull request #7764 from Goober5000/fix/firingpoints_followup
fix weapon substitution bank attribution and turret ammo accounting
2026-09-06 14:53:14 -04:00
Goober5000 66d468d611 Merge pull request #7766 from Goober5000/fix/virtual_pof_glowpoints
fix copying multiple glow point banks into a virtual POF
2026-09-06 14:12:41 -04:00
BMagnu 95f63218d8 Also set custom gauges + dradis radar to slew (#7769) 2026-09-06 14:39:07 +00:00
Goober5000andClaude Opus 5 086c553339 fix copying multiple glow point banks into a virtual POF
Every copied bank was written to the same array slot, so all but the last
were lost, and the slots left behind kept their zero-initialized contents.
A zeroed bitmap handle is slot 0 rather than -1, so the paging code would
then page out an unrelated bitmap.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-06 00:49:33 -04:00
Goober5000andClaude Fable 5.1 04e698c29b fix weapon substitution bank attribution and turret ammo accounting
Follow-up to #7635.

Pass the firing bank explicitly through weapon_create() and beam_fire()
instead of searching the ship's banks by weapon class, which could
attribute substitution patterns to the wrong bank.  Factor the
substitution logic into get_weapon_substitution_tuple() and only apply
linked/dual-fire pairing to ship-level fire, not turret fire.  Skip
substitution on multiplayer clients, since the server already resolved it.

Derive turret bank indices from the weapon slot being fired rather than
the turret's bank cursor, so ammo and substitution state are tracked on
the bank that actually fired.  Previously a single-selection turret could
consume ammo from a bank it never fired, and non-ammo turrets (whose
cursors stay at -1) would have skipped substitution entirely.

Use the Player_ship object flag rather than Player_obj in
ship_secondary_bank_can_dual_fire() so the server and clients agree on
dual fire for remote players, and guard ship_secondary_has_ammo() against
a negative bank index.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-05 19:22:45 -04:00
Goober5000 1a48c2d2a6 Merge pull request #7760 from Goober5000/fix/fred_label_line_spacing
Scale font line and tab advance by the font scale factor
2026-09-03 15:49:25 -04:00
Goober5000andClaude Opus 4.8 f3817c80fa Scale font line and tab advance by the font scale factor
Multi-line and tabbed strings drawn with a scaleMultiplier != 1 overlapped
or misaligned because the font code advanced newlines and tabs by the
unscaled font height/tab width -- most visible in FRED's viewport labels
now that they honor an adjustable font scale.

Scale the newline and tab advance in both the rendering and measurement
paths of each backend: the NVG path (gr_string and NVGFont::getStringSize)
and the VFNT path (gr_string_old and VFNTFont::getStringSize), which also
gains tab handling.  The tab width is hoisted onto the shared FSFont base
so both backends share it.

While here, pass the FSFont wrapper to gr_string_old instead of a bag of
scalars, make its font pointer const, and consolidate the
get_char_width_old externs into a single declaration in font_internal.h.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-09-03 01:55:03 -04:00
Goober5000 7e91fd00f7 Merge pull request #7746 from Goober5000/feature/remove_ship_class_limits
Remove the ship class limit
2026-08-30 23:44:24 -04:00
Goober5000 b08ab184ff Merge pull request #7757 from Goober5000/fix/flagset_64bit
Fix 32-bit truncation in the flagset u64 round-trip
2026-08-30 23:36:54 -04:00
Goober5000andClaude Opus 4.7 f353ca7d18 Remove the ship class limit
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]>
2026-08-30 23:00:29 -04:00
Goober5000 3db90491ab Merge pull request #7739 from Goober5000/refactor/loadout_ui
Refactor selection screen data storage to remove fixed arrays
2026-08-30 20:21:40 -04:00
Goober5000 e4ea11b95c Merge pull request #7735 from Goober5000/cleanup/sexp_syntax_checking
clean up check_sexp_syntax and improve caching
2026-08-30 19:58:52 -04:00
Goober5000andClaude Opus 4.8 3f5cf1c87c Release all teams' select-screen icon handles
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]>
2026-08-30 19:08:55 -04:00
Goober5000andClaude Opus 4.7 3f335b2b89 Store selection-screen UI data sparsely
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]>
2026-08-30 19:08:55 -04:00
Goober5000andClaude Opus 4.7 ba7aa70794 Fix 32-bit truncation in the flagset u64 round-trip
Mission flags are saved to the mission file as the full 64-bit flagset value, but three issues prevented anything larger than 32 bits from being saved as intended:

 * `stuff_flagset` parsed the value with `stuff_long`, where `atol` clamps anything above `LONG_MAX`
 * `flagset::from_u64` cast the incoming value to `unsigned long`, truncating bits 32-63
 * `flagset::to_u64` used `bitset::to_ulong` where it should use `bitset::to_ullong`

Fix these issues and add `static_assert` in the flagset functions to defend against future issues.  Also use the proper UINT64_T_ARG where it should be used.

This was first triggered by `Limited_support_rearm_pool`, the first mission flag to occupy bit 31.  Only mission flags are affected; the other flagset serialization paths were audited and are safe.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-08-30 16:46:11 -04:00
Goober5000andClaude Opus 4.8 659cb18e07 Allow giveOrder to specify dockpoints for ORDER_DOCK
The giveOrder scripting function now accepts optional DockerPoint and
DockeePoint arguments for ORDER_DOCK, each given as a dockpoint name or
one-based index (matching the setDocked convention).  DockerPoint refers
to a dockpoint on the ordering ship and DockeePoint to one on the target.

If only one point is specified, a compatible point is chosen automatically
on the other ship using the same dock-type precedence as the existing
automatic dockpoint negotiation.  When neither is specified, behavior is
unchanged.

Fixes #7119

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-29 19:03:48 -04:00
Fabian Woltermann 68e9a30463 Fix a couple of oversights in prop collisions (#7753)
* Fix estimate_ship_speed_upper_limit to handle non-ship objects gracefully

* Add collision handling for OBJ_PROP objects

Implement OBJ_PROP-specific collision messaging and logic: update player death messages, fix incorrect argument usage in collision checks, and set killer attributes for PROP collisions.
2026-08-29 11:17:07 +02:00
Goober5000 98b416183b Merge pull request #7743 from Goober5000/refactor/alt_ship_class_dlg
Store alt-ship-class combobox indices as item data
2026-08-28 16:49:17 -04:00
Goober5000 6309515d3e Merge pull request #7742 from Goober5000/refactor/shield_sys_types
Store the shield-system editor types in a map
2026-08-28 16:49:01 -04:00
Goober5000 b53fde2a1d Merge pull request #7741 from Goober5000/refactor/weapon_classes
Remove MAX_WEAPON_TYPES from AI and ship-parse weapon buffers
2026-08-28 16:48:52 -04:00
Goober5000 1c817b5965 Merge pull request #7737 from Goober5000/refactor/team_data
refactor team loadout data structures in preparation for lifting the ship class limit
2026-08-28 16:48:39 -04:00
Goober5000andClaude Fable 5 d413e52b33 External weapon model refactor: consolidate per-bank state, fix design issues (#7634)
* consolidate per-bank external weapon model state into one struct

The state for a bank's external weapon model was scattered across two
structs and three naming schemes: the model instance and its weapon in
ship_weapon, the firing-point chain counter in a combined array indexed
with the bank + MAX_SHIP_PRIMARY_BANKS packing convention, and the
gatling spin rate/angle on ship itself.

Gather them into external_weapon_state, with one array for primary
banks and one for secondary banks in ship_weapon.  This removes the
index-packing convention (ship_get_external_model_fp_offset now takes
the bank's state directly), and ship_delete now also frees secondary
instances should a future weapon ever create one.

No behavior change.

Co-Authored-By: Claude Fable 5 <[email protected]>

* update external weapon model spin in per-frame processing

The gatling spin state was integrated inside ship_fire_primary (spin-up)
and ship_stop_fire_primary_bank (spin-down), which entangled a per-frame
visual/state update with the firing event paths and had two quirks:

- Spin-down only ran when something called the stop-fire path, so a bank
  could freeze mid-spin (e.g. AI ships that stop calling
  ship_fire_primary without going through ship_stop_fire_primary).
- The spin angle only advanced when the ship class set
  $Show Primary Models:, so identical weapon state evolved differently
  per ship class even though the rate (which gates firing) always ran.

Banks that try to fire now just request spin-up, and
update_external_weapon_spin() -- called from ship_process_post next to
update_reload_percent(), which was moved out of the physics code for
the same reason -- integrates the rate and angle every frame.  The
firing gate (no shots until the barrels reach full speed) is unchanged.

Co-Authored-By: Claude Fable 5 <[email protected]>

* make external_model_num mean what it says; gate firing offsets on drawn models

Since the original 2005 implementation, external_model_num was bashed
to model_num at parse time when no $External Model File: was given, so
every POF-rendered weapon appeared to have an external model.  All the
checks downstream therefore tested 'has any model' while reading as
'has an external model', and two paths leaked the fallback into
gameplay:

- ship_fire_secondary applied the external-model firing point offset
  without checking $Show Secondary Models: (unlike the primary path),
  so any missile whose own in-flight POF contained gun points would
  have its launch position offset on every ship in the game.
- The AI line-of-sight check, copied from ship_fire_secondary,
  applied the offset with no draw check for either bank type, so for
  primaries it could disagree with the actual firing path.

external_model_num now stays -1 unless $External Model File: was
given.  Rendering code (ship_render_weapon_models, the HUD hardpoints
gauge, and the gatling instance helper) falls back to the weapon's own
model at the point of use, preserving the existing display of missiles
on hardpoints.  Firing point offsets now require a dedicated external
model *and* the bank's models to actually be drawn, in all three
paths.  In practice this does not change firing behavior, because GPNT
chunks are never added to weapon models except when explicitly built
as external models.

Co-Authored-By: Claude Fable 5 <[email protected]>

* warn on external weapon model conventions that are silently ignored

Two load-bearing conventions of the external weapon model feature had
no diagnostics:

- Only the first gun bank of an external model supplies firing points;
  additional banks are silently ignored.  Warn when an external model
  has more than one gun bank.
- $Show Primary Models: / $Show Secondary Models: must list exactly
  one bool per weapon bank; missing entries silently default to not
  drawing.  Warn when the list length doesn't match the bank count.

Co-Authored-By: Claude Fable 5 <[email protected]>

* orient external weapon models along their slot's firing normal

External weapon models were always rendered pointing ship-forward, and
reloading missiles slid backward along fixed -Z, regardless of the gun
or missile point's normal -- so a bank angled 45 degrees outward fired
along its normal while its mounted model pointed dead ahead.  The
per-slot angle offset was also applied as a roll about ship-forward
rather than about the firing axis.

ship_get_weapon_model_slot_transform now builds the slot orientation
from the firing normal (identity for the usual dead-ahead normal, so
existing forward-facing mounts render exactly as before) with the
angle-offset roll applied about that axis, and slides reloading
missiles along the normal.

Correspondingly, ship_get_external_model_fp_offset now rotates the
external model's firing-point offset by the same slot transform, so
shots continue to emerge from the drawn barrels on angled mounts, and
now also respect the angle-offset roll (previously the offset was
added unrotated, mismatching rolled models).

Co-Authored-By: Claude Fable 5 <[email protected]>

* address review: same-frame spool-up gating; optional slot transform outputs

Move the update_external_weapon_spin() call from ship_process_post() to the top of ship_process_pre().  The update now happens before any firing attempt in the frame, so the spool-up firing gate sees the current frame's spin rate, as it did on master where the update happened inside the firing code.  This also makes the ordering consistent for the AI regardless of Framerate_independent_turning.

Also make both outputs of ship_get_weapon_model_slot_transform() optional.

Co-Authored-By: Claude Fable 5 <[email protected]>

---------

Co-authored-by: Claude Fable 5 <[email protected]>
2026-08-27 22:08:06 -07:00
Goober5000andClaude Opus 4.8 947d3d9bdd Fix qtFRED crash when opening ship texture dialog on placeholder slot (#7748)
The ship texture replacement dialog builds parallel per-slot arrays sized to pm->n_textures, but only populates the "main"/subtype map entries for real textures; blank and duplicate texture slots are left as empty-map placeholders (with an empty default name). The dialog constructor then force-selected row 0, and getMap()/setMap() raised a fatal error_display when "main" was absent, so any ship whose first texture slot is blank or a duplicate crashed the dialog on open.

getMap()/setMap() now treat an empty default name as a non-editable placeholder.  The constructor also selects the first real texture row rather than blindly row 0.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-27 19:26:09 +00:00
Goober5000 87189525e0 clean up check_sexp_syntax and improve caching
- Clean up `check_sexp_syntax` to use `eval_ship` (and incidentally `eval_wing` and `eval_prop`) rather than the old lookups.
- Disable SEXP node caching while FRED is running.  Also use smart pointers and fix a cache memory leak.
2026-08-27 14:21:30 -04:00
Goober5000 814b8592a2 Merge pull request #7734 from Goober5000/fix/ship_registry_rename
a few parsing and infrastructure upgrades
2026-08-27 14:20:26 -04:00
wookieejedi dd2be3dfbd Fix path that could lead to unnormalized vector (#7747)
* Fix path that could lead to unnormalized vector

TLDR:
Within `ai_chase` the value for `predicted_enemy_pos` could remain unitialized because not every else branch was accounted for. This could lead to downstream effects and trigger unnormalized vector asserts.

More Detail:

The trigger
`predicted_enemy_pos` is declared with no initializer — `aicode.cpp:8991`
The lead-prediction gate at `9110` falls through: needs `real_dot_to_enemy >= 0.25f and target_time >= 1.0f`, and not seek-lock-with-aspect.
`ai_get_weapon` returns `nullptr` — 9125 → 7113, which bails on `current_primary_bank < 0`
Every assignment lived inside `if (wip)`, and there was no else — so the variable stayed unwritten
First read of uninitialized stack memory at 9167
Passed to `ai_chase_attack` at 9198, becomes the goal point
Target beyond 500m selects the `rvec` branch, which routes into `vm_vector_2_matrix_norm` and the `fvec` assert

Commonly this uninitialized value would result in a plausible float. In that case nothing asserts and the ship just flies at a garbage point — so this was likely causing erratic no-primary AI behavior far more often than it was crashing. The assert only fired on the subset where the residue happened to be NaN, Inf, or astronomically large.

This PR adds that final else branch, though given it's an AI change it is via flag. The flag is auto-enabled for 26.2 and onward though.

Happy to discuss!

* fix typo

* no need for flag
2026-08-27 13:25:53 -04:00
Goober5000andClaude Opus 4.8 f235517a36 Remove MAX_WEAPON_TYPES from AI and ship-parse weapon buffers
Two unrelated weapon-indexed buffers dropped their MAX_WEAPON_TYPES dependency, by different means since they have different natural bounds:

 * ai_select_secondary_weapon and update_aspect_lock_information declared weapon_id_list / weapon_bank_list as [MAX_WEAPON_TYPES], but these are filled only by get_available_secondary_weapons, which appends at most one entry per secondary bank -- so they never hold more than MAX_SHIP_SECONDARY_BANKS!

 * parse_allowed_weapons' allowed_weapons[MAX_WEAPON_TYPES] is genuinely weapon-class-bounded, so it becomes an SCP_vector<int> filled by the vector overload of stuff_int_list

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-27 13:17:54 -04:00
Goober5000 a7999b1a00 Merge pull request #7719 from Goober5000/fix/events_in_campaign_editor
restore mission/goal/event listing in FRED2's campaign editor
2026-08-27 13:12:51 -04:00
Goober5000 66add9f9e8 Merge pull request #7745 from Goober5000/feature/fred_font_scaling
Add adjustable viewport label font size to FRED and QtFRED
2026-08-27 12:06:00 -04:00
Goober5000andClaude Opus 4.7 47b2f0b3ca Consolidate the starting-wing usage builders in missioneditor
FRED2, qtFRED, and the shared mission save code each carried their own copy of the loadout usage builders.  The weaponry pair already had shared versions in missioneditor/common; this converts those to the SCP_map<int,int> signature the editors now use, adds the missing ship builder alongside them, and deletes both editors' private copies.

FRED2's playerstarteditor/freddoc and qtFRED's Editor/ErrorChecker all call the shared functions now, following the update_custom_wing_indexes consolidation precedent.  missionsave's used_pool becomes a map as well, which removes its MAX_WEAPON_TYPES stack array.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-08-27 00:58:20 -04:00
Goober5000andClaude Opus 4.7 49a9141073 Convert team_data to sparse loadout entry vectors
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]>
2026-08-27 00:58:20 -04:00
Goober5000andClaude Fable 5 2b01cebd57 fix AI ships getting permanently stuck after losing or refusing a target (#7736)
Generalize the unreleased "$fix target updating with strafe:" ai_profiles flag
into "$fix ai target recovery:", which now also covers two more ways a ship
holding a standing chase order could park in AIM_NONE forever:

* a ship already in AIM_NONE with a standing chase order (attack-any etc.) now
  clears its active goal so the order is re-processed, closing the window where
  retargeting finds a new enemy before the strafe clause can fire
* target selection (find_enemy/get_nearest_objnum) now skips ships outside the
  attacker's actively-pursues list, and ai_chase() drops self-chosen targets it
  refuses, instead of holding a target it will never attack

Retail behavior is unchanged when the flag is disabled; explicit orders on
non-pursued targets are still declined as before.

Co-authored-by: Claude Fable 5 <[email protected]>
2026-08-26 19:30:13 -04:00
Goober5000andClaude Opus 4.8 e3674834a0 Add adjustable viewport label font size to FRED and QtFRED
The text labels drawn in the main editor viewport were always rendered at the fixed FONT1 size.  Add options to scale the font from 50% to 400%.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-25 19:57:47 -04:00
wookieejedi e0e3e9dff2 Fix CTD with ship-percent sexp edge case (#7738)
`sexp_percent_ships_arrive_depart_destroy_disarm_disable_scan` could have an edge case where the total value (ie the denominator) is 0 and thus crashes FSO. This could happen if `Cancel_future_waves_of_any_wing_launched_from_an_exited_ship` is used and the death or departure of a mothership triggers the total of a wing to change that is later checked in a sexp such a 'percent-destroyed` or other similar sexps.

Fix is just to add an early out if the total value is 0.
2026-08-25 15:11:40 -04:00
wookieejedi 4c48e180aa Fix do_view_track (#7731)
Root cause
Commit 87ea04fa5 "change atan2_safe to atan2" (July 2024) replaced FSO's custom atan2_safe with the standard atan2. The old function returned a different range:

range returned
old atan2_safe	(-PI/2, 3PI/2)
standard atan2	(-PI, PI]

do_view_track_target() in playercontrol.cpp:417 computes the padlock heading as a raw subtraction of two extracted headings:

chase_slew_angles.h = forward_angles.h - view_angles.h;

The forward vector rotated into the player's own frame is always (0,0,1), so forward_angles.h is always PI/2. With the old range that subtraction landed in (-PI, PI) — symmetric, and the ±120° neck clamp below applied evenly. With standard atan2 it lands in [-PI/2, 3PI/2) instead: -90° to the left, but +270° to the right.

Leftward travel dead-ends at 90° (straight abeam) instead of 120°, and the moment a target crosses just past the player's left shoulder the value wraps to a large positive number and gets clamped to +120° — the camera snaps hard right.

The fix
Rather than reverting atan2_safe (standard atan2 is correct, and other callers depend on the new range), this PR wraps the heading difference back into (-PI, PI] before the neck clamp, using the same "over-the-top correction" idiom already used by do_view_external() a few hundred lines down:

	// Do over-the-top correction.
	// Headings are extracted with atan2, so each one lies in (-PI, PI] and their difference can be
	// nearly a full circle in either direction.  Without wrapping it back into (-PI, PI], a target just
	// past the left shoulder reads as being almost all the way around to the right instead.
	if (chase_slew_angles.h > PI)
		chase_slew_angles.h -= PI2;
	else if (chase_slew_angles.h < -PI)
		chase_slew_angles.h += PI2;
2026-08-25 15:10:42 -04:00
Fabian Woltermann f241ddb3e3 Use the OS-specific path separator when saving missions (#7744) 2026-08-25 07:30:29 +00:00
Goober5000andClaude Opus 4.8 e677709247 Store alt-ship-class combobox indices as item data
FRED2's AltShipClassDlg kept two fixed-size parallel arrays mapping combobox rows back to engine indices:

  int ship_class_indices[MAX_SHIP_CLASSES]       // row -> Ship_info index
  int string_variable_indices[MAX_SEXP_VARIABLES] // row -> Sexp_variables index

Both are pure UI plumbing. Delete them and attach each index to its combobox row via CComboBox::SetItemData/GetItemData.  Besides removing the arrays, this drops the num_string_variables index offset math the parallel arrays forced and bounds the reverse lookups by the combobox item count instead of MAX_SHIP_CLASSES.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-25 01:37:46 -04:00
Goober5000andClaude Opus 4.8 8d94cb5417 Store the shield-system editor types in a map
Shield_sys_types (the per-ship-class shield status in the FRED2/qtFRED Shield System dialog) was a dense array/vector sized to MAX_SHIP_CLASSES, one of the last cap-blocking structures.  It is a poor fit for a dense container: it is derived, ephemeral editor state, and sparse in practice, since only the ship classes present in the mission ever get a meaningful value.  Its default (HasShields) is exactly the "absent" case.

Convert it to a map keyed by ship class:

  FRED2:  int Shield_sys_types[MAX_SHIP_CLASSES]            -> SCP_map<int, int>
  qtFRED: SCP_vector<GlobalShieldStatus> Shield_sys_types   -> SCP_map<int, GlobalShieldStatus>
          (and the ShieldSystemDialogModel::_types copy likewise)

No file-format or behavior change: shield status is still derived from and applied to the same ship flags.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-25 00:15:12 -04:00
Goober5000andClaude Fable 5 c99ee7c741 restore mission/goal/event listing in FRED2's campaign editor
The sexp tree refactor (#7301) moved the OPF listing logic into a shared
model that asks the editor for campaign-aware lists via virtual methods
on SexpTreeEditorInterface.  QtFRED's campaign editor got the necessary
overrides, but FRED2's never did, so its OPF_MISSION_NAME /
OPF_GOAL_NAME / OPF_EVENT_NAME dropdowns fell back to the currently
loaded mission's data instead of the campaign's.

Backport the overrides to FRED2, restoring its pre-refactor behavior
(including listing only missions at earlier campaign levels), and hoist
QtFRED's loadAndFindCampaignMission helper into missioneditor/common so
both editors share one copy.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-24 21:07:10 -04:00
Goober5000 a34788ef1a a few parsing and infrastructure upgrades
- 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
2026-08-23 22:24:21 -04:00
Goober5000 7f6fb5e1eb Merge pull request #7732 from Goober5000/fix/wing_hash_compatibility
wing name format compatibility
2026-08-23 22:22:10 -04:00
Goober5000 41884cb1a3 wing name format compatibility and other cleanup
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.
2026-08-23 21:38:52 -04:00
Goober5000 ee4b554dd4 standardize and update the various _INDEX macros 2026-08-23 21:38:52 -04:00
Fabian Woltermann 234219d88b Fix invalid model/instance access for OBJ_RAW_POF and OBJ_PROP (#7733)
Ensure proper checks for model_num and instance validity when accessing OBJ_RAW_POF and OBJ_PROP objects. Prevents crashes from out-of-bounds or missing data in Pof_objects and Props.
2026-08-23 13:34:44 +02:00
Goober5000 f9c136ad8b Merge pull request #7720 from Goober5000/feature/classic_menu_toggle
add a toggle to switch FRED2 between the classic and current menu layouts
2026-08-21 22:34:08 -04:00
John Fernandez 1b1e0af2a5 Fix Campaign Editor Crash (#7722)
And a little minor cleanup
2026-08-20 16:32:22 -04:00
Goober5000andClaude Opus 4.8 96e1b1a092 add a toggle to switch FRED2 between the classic and current menu layouts
PR #7082 rearranged the FRED2 menu layout to match qtFRED (see issue #6985),
but some users preferred the previous Editors/Misc layout.  This adds a
"Classic menu layout" toggle (under Settings in the current layout, and under
Misc in the classic layout) that swaps the whole menu bar on demand.  The
choice is persisted to the registry and reapplied on startup.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-18 03:20:19 -04:00
Goober5000 99bf34074a Merge pull request #7712 from Goober5000/fix/fred_ship_dialog
handle ship editor re-entrancy in FRED
2026-08-18 01:03:35 -04:00
Goober5000 5a67e8e952 Merge pull request #7711 from Goober5000/fix/sexp_parsing
patch a hole in SEXP syntax checking
2026-08-18 00:52:21 -04:00
John Fernandez 9b2fedbf65 Move Items to a grid layout for easy sizing 2026-08-17 14:18:05 -04:00
Goober5000 6684be3ee3 Merge pull request #7713 from Goober5000/7688_followup
tiny tweak fix to the comm menu
2026-08-16 15:55:46 -04:00
Goober5000 1ea98b19d5 tiny tweak fix to the comm menu
Reset the selected item when we open a new menu.  This keeps the display state and internal state in sync if the player mixes arrow presses and number presses.  Follow-up to #7688.
2026-08-16 15:10:35 -04:00
Goober5000 3f6a9f12fa Merge pull request #7688 from wookieejedi/fix-skip-hidden-menu-items
Fix COMMS_MENU_SELECT control usage with 'Hide  Rearm/Repair items' and cleanup selection visibility
2026-08-16 14:57:20 -04:00
Goober5000 860604a726 Merge pull request #7683 from wookieejedi/forward-speed-launch
Add weapon launch curve input 'Target Forward Speed'
2026-08-16 14:42:49 -04:00
Goober5000 a9f797d3f2 Merge pull request #7689 from wookieejedi/more-multi-fixes
Fix various multi bugs
2026-08-16 14:42:25 -04:00
Goober5000 cdd477fddf Merge pull request #7704 from wookieejedi/fix-ai-ramming-big-targets
More Fixes for AI Ramming into Stationary Targets
2026-08-16 13:20:59 -04:00
wookieejedi 4a1f16c453 update to new version 2026-08-16 13:06:10 -04:00
The Force 54caf6ec25 New guard-range sexp (#7606)
* New guard-range sexp

New guard-range sexp which allows different ranges for different guarders

* cast value

* Clang

* bugfixes

* fix CI

* Address Feedback

* address feedback

* typo
2026-08-16 17:53:41 +01:00
wookieejedi 0f3f80b03a Merge branch 'master' into fix-skip-hidden-menu-items 2026-08-16 08:56:40 -04:00
Kestrellius 1967daffd3 Vectorize MsgItems (#7687)
Convert MsgItems from an array to a vector, and remove the associated limit value and counter.

* vectorize msgitems

* appeasement

* leave me alone clang

* remove optional

* cleanup

* changes requested

* clang

* clang what is WRONG with you

* feedback

* remove flag in .h
2026-08-16 02:04:50 +00:00
Goober5000 9baddcb25a handle ship editor re-entrancy in FRED
Prevent the mission from being modified via the ship editor's window update when it is already being cleared or loaded.  This mirrors existing guards in `delete_object` and `delete_ship`.

No other FRED2 dialogs are vulnerable.  QtFRED uses a different ship editor mechanism which is not affected by this issue.
2026-08-15 17:06:27 -04:00
Goober5000 da4f4e70ac patch a hole in SEXP syntax checking
An operator that isn't enclosed in parentheses could trigger an UNREACHABLE, which crashes in Debug and is undefined behavior in Release.  Catch it in a more appropriate way.
2026-08-15 15:21:03 -04:00
Goober5000 a4e33109a0 Merge pull request #7707 from Goober5000/fix/local_ssm
remove an unneeded SSM assertion and fix some parent checks
2026-08-15 12:13:01 -04:00
Goober5000 7418bf5f42 Merge pull request #7702 from wookieejedi/prop-type-fix
Update type Prop to ensure it's on object list
2026-08-14 18:01:27 -04:00
Goober5000 d5d4a16664 remove an unneeded SSM assertion and fix some parent checks
Contrary to the comment, local SSMs do not actually need a parent.

Also fix two other places where a parent was assumed but may not exist.
2026-08-14 15:11:10 -04:00
Fabian Woltermann f840e545b9 Fix base angle calculation for ventral turrets without custom matrices (#7706) 2026-08-14 20:45:18 +02:00
wookieejedi d5c371c7a2 clang 2 2026-08-12 16:20:17 -04:00
wookieejedi e983dfbe15 clang 2026-08-12 16:18:55 -04:00
wookieejedi 891a63420c fix old copy paste bug and power usage 2026-08-12 15:43:19 -04:00
wookieejedi 0107e1b25f clang 2026-08-12 13:35:41 -04:00
wookieejedi 9295d1a796 More Fixes for AI Ramming into Stationary Targets
TLDR: This is a long description, but it boils down to exposing 2 hardcoded AI values and updating 1 value in the optional 'better collision avoidance' behavior allows mods to substantially fix fighters ramming into capital ships they are attacking.

AI accidentally ramming into big ships while trying to evade fire from a hostile turret or a dogfight makes some sense given a chaotic combat situation. What does not seem good though is AI not being able to attack a large stationary target that is not shooting back and has no fighters around. This is especially evident in mods that use the new strafing AI fields, where AI will fly to a distant point then turn around to do an attack run, then once close enough pick another strafing run to conduct. In these situations, AI will commonly not pull up from the attack run in time, especially for mods that use different speeds or AI damp values compared to retail.

Fortunately, there exposing two values to the AI profiles allows modders to tune these values, the strafe retreat time and strafe retreat distance. By default these values are 2 seconds and 100 meters, which is commonly far too short for mods with higher speeds or larger damp values. Exposing these values allows modders to make the AI far more effective and virtually eliminate ramming into stationary ships they are attacking. For example, on current 26.0 with FotG testing we register 2-4 rams of a stationary target per minute. Using the new exposed values in this branch eliminates the ramming (after 10 minutes of firing no ramming occurs across multiple runs of this and other test missions).

Furthermore, this PR helps `better_collision_avoidance_triggered` function (enabled by using `better collision avoidance` in the `ai_profiles.tbl` fix an edge case with predicting collisions. On current master the `delta_time` value used in this function was `0`, which was only ever used eventually downstream in the `will_collide_with_big_ship` function call. This `will_collide_with_big_ship` function had an early return on line 7620:

```
int will_collide_with_big_ship(object *objp, vec3d *goal_point, object *big_objp, vec3d *collision_point, float delta_time)
{
	float		radius;
	vec3d	end_pos;

	radius = big_objp->radius + delta_time * objp->phys_info.speed;

	if (vm_vec_dist_quick(&big_objp->pos, &objp->pos) > radius) {
		return 0;
	}
```

This early return and the passing of `0` to the `delta_time` from `better_collision_avoidance_triggered` meant that the early out radius distance was only the big object's radius, and did not project any forward time. In other words, better collision avoidance only actually checked or triggered when the small ship was within the radius distance of the large ship. Thus, with ships that had geometry reach right to the edge of the object radius, it could result in the small ship not having enough time to react to avoid a collision. Notably, all other calls to `will_collide_with_big_ship` use a `delta_time` of 5-10. As such, this PR also fixes that edge case by ensuring the better collision avoidance also incorporates some lead travel time by passing a non-zero value for delta time. Given `better_collision_avoidance_triggered` aggression factor is 3.5 by default, it fit well in testing to pass this value to `delta_time`. That aggression factor is already tuneable for mods, so also using it here provides extra adaptability.

Overall these values and updates were tested and substantially remove collision accidents, especially with ships with high speeds and/or higher damps. The PR boils down to exposing two values to the AI profiles table and updating values in the optional 'better collision avoidance' flag, so it will not affect retail. Happy to discuss as well, thanks!
2026-08-12 12:57:29 -04:00
wookieejedi 075e1226b7 Update type Prop to ensure it's on object list
Seems like props were missing from the object type list, so adding them in this relatively small PR. Happy to edit or do something differently if folks want though!
2026-08-10 22:15:32 -04:00
wookieejedi 35d8c6ea4e Fix two small issues (#7690)
* Fix two small issues

Fix copy-paste bug where setting `$Ask Help Hull Percent:` never properly got set. Fortunately this does not affect the AI, only when the AI sends a message to the player asking for help.

Fix edge-case issue: in `find_turret_enemy`, the `turret-tgt-ship-tgt` shortcut takes the parent ship's target and indexes Ships[] with it unconditionally, but `aip->target_objnum` could be a weapon or asteroid, which could be at an index higher than the ships index, and then trying to look up team could lead to bad data.

* comment formatting
2026-08-08 10:26:53 -04:00
Goober5000 745398e6b9 Merge pull request #7679 from Goober5000/refactor/loadout_pools
refactor loadout pools for vectorized ship and weapon classes
2026-08-07 22:09:26 -04:00
Mike Nelson 96d5182bb1 Add text-to-speech Lua API to briefing/debrief/tech room UI sections (#7629)
Bind the existing fsspeech_* wrappers into the ui.Briefing, ui.CommandBriefing,
ui.Debriefing, and ui.TechRoom scripting sections so Lua UIs (e.g. SCPUI, which
overrides the native screens) can drive synthesized speech again.

Each section gets playTextToSpeech, stopTextToSpeech, pauseTextToSpeech,
isTextToSpeechPlaying, and isTextToSpeechEnabled. Briefing/command-brief/debrief
use FSSPEECH_FROM_BRIEFING; the tech room uses FSSPEECH_FROM_TECHROOM. fsspeech_play
already self-gates on the Speech.* options, so these are no-ops when speech is off.
2026-08-07 16:42:05 -04:00
Goober5000 4d50a1bef8 un-consolidate model flags (#7693)
Restore the original flag values that were consolidated in #7588.  Since the flags are saved in the mission file as an integer, the original values must be preserved.

Since `MR_ATTACHED_MODEL` was removed in the original PR, that is renamed to `MR_EMPTY_SLOT6`.
2026-08-07 08:41:43 +02:00
Goober5000 0e7f1c4d74 Merge pull request #7678 from Goober5000/refactor/static_alias
clean up static alias lists used for name lookups
2026-08-06 15:18:26 -04:00
wookieejedi 2e712f8e2e Fix various multi bugs
PR addresses various small but longstanding mutli-related bugs. In draft status pending additional testing.
2026-08-06 12:52:24 -04:00
wookieejedi 23f4a1ca1f Fix COMMS_MENU_SELECT control usage with 'Hide Rearm/Repair items'
The game settings flag '$Hide main Rearm/Repair items in Comms Gauge:' hides the 'Rearm and Repair' lines in the HUD Comms menu from appearing, which is useful for mods that do not have repair ships and thus those lines confuse players. Unfortunately, the newer `COMMS_MENU_SELECT` control bindings do not properly account for hidden menu items, so using those controls on a comms menu with 'Rearm and Repair' hidden means the selection index gets off and also confuses the player.

This PR fixes that by allowing `hud_squadmsg_selection_move_down()` and `hud_squadmsg_selection_move_up()` to skip hidden menu items. Tested and works as expected.

For folks using the COMMS_MENU_SELECT controls, there is also a current UI confusion when the comms menu is first brought up because the first item is selected, but the `Display_selector` does not show. Thus, players are unsure what item is actively selected even though one is actively selected. In the original comms menu select PR this was set to on by default, but then switched to off by default by a different developer a bit later. This choice is especially useful for mods that have many players with gamepads such as Event Horizon. Alternatively, mods that already have many number keys utilized for other gameplay aspects seem to prefer to keep the comms menu visuals as is and only show the selection if the player uses those keys. Given there is clearly arguments for both reasons, ideally modders should be able to choose how it displays for their own mods (always or off for the first pop-up of the Comms menu).  Thus, this PR adds that as a cleanup game option, too. This also was tested and works as expected.
2026-08-06 11:06:41 -04:00
wookieejedi 50935e540e Fix scripting copy-paste bug and other minor optimizations (#7684)
* Fix scripting copy-paste bug

The iterators for secondary banks for lua access had a copy-paste bug from primaries, so we should fix that. The early == -1 break masks it whenever a ship uses 3 or fewer secondary banks, which is probably why it went unnoticed.

Also add some additional low-hanging fruit optimizations.

* radar distance and other cleanup

* clang
2026-08-05 23:09:57 -04:00
Goober5000 304dbb60c5 Merge pull request #7680 from Goober5000/refactor/dense_class_data
store campaign allowed-class lists in sets
2026-08-04 23:10:07 -04:00
Goober5000 b1ada89f8d Merge pull request #7630 from Goober5000/claude/extract-name-conflict-checker
Extract common name conflict checker for FRED2 and qtFRED
2026-08-04 22:24:05 -04:00
Goober5000andClaude Opus 4.7 30d81a83fb store campaign allowed-class lists in sets
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]>
2026-08-04 22:14:58 -04:00
wookieejediandGoober5000 2821eb8ccf Update code/network/multi.h
Co-authored-by: Goober5000 <[email protected]>
2026-08-04 21:55:08 -04:00
wookieejedi 08bb369e46 fix order and multi bump 2026-08-04 16:07:06 -04:00
Taylor Richards 6d3c2873c4 improve support for gamepads (#7655)
Use the SDL gamepad API to add full gamepad support. Adds rumble for supported
gamepads and changes the FF in-game options to take immediate effect instead
of requiring a restart.

Additionally this makes changes to allow gamepad triggers to be used as both
an axis and a button. This support is specific to gamepads since we can
explicitly identify the trigger with the API and act on it accordingly. FSO
control config doesn't currently support such an action directly however so
we have to hack it in just a bit. Due to this the control binding will show
the trigger axis name bound to a button control only while a gamepad is
connected, otherwise it shows a button name associated with the trigger.
2026-08-04 15:49:46 -04:00
wookieejedi acd868fa49 one more multi clang 2026-08-04 09:56:21 -04:00
wookieejedi 4c4cc44310 multi packets 2026-08-04 09:05:08 -04:00
wookieejedi 61b7c9f3f1 clang auto format typo 2026-08-04 07:56:44 -04:00
wookieejedi af7a2f37ed clang again 2026-08-04 07:36:38 -04:00
Fabian Woltermann 45dd750e0c Secure forum posting by adding authentication header support (#7676)
* Secure forum posting by adding authentication header support

* Add check for missing FORUM_SECRET environment variable in ci/post/main.py

* also error out when the forum secret is not supplied
2026-08-04 08:25:52 +02:00
Goober5000 cb8dec1fd6 Merge pull request #7656 from Goober5000/cleanup/rpavlik
clean up submodule references
2026-08-03 23:51:49 -04:00
wookieejedi 208cfdfed3 clang 2026-08-03 23:18:03 -04:00
wookieejedi 9ccfe9d9e3 Add weapon launch curve input 'Target Forward Speed'
Following style of 'target radius' and other useful weapon launch curve inputs, this PR adds 'Target Forward Speed' which can be useful to use as an input and then use FoF as an output to do things like make turrets have a tougher time having dynamic FoF depending on target speed.
2026-08-03 23:03:35 -04:00
Goober5000 3bfe71d834 Merge pull request #7670 from Goober5000/fix/msg_arg_count
send-message-list arg-count forgiveness, plus better error checking
2026-08-03 22:21:13 -04:00
BMagnu 09de3f1fb8 Fix deferred lighting on vulkan (#7682) 2026-08-03 15:52:34 +02:00
Goober5000andClaude Opus 4.7 77186c72fc 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) <[email protected]>
2026-08-03 01:28:28 -04:00
Goober5000 3afa3293c8 Merge pull request #7672 from Goober5000/enhancement/containsKey
add contains, at_or, and value_or to the SCP maps
2026-08-03 01:25:50 -04:00
Goober5000 6bc6426672 clean up static alias lists used for name lookups
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.
2026-08-02 18:26:47 -04:00
Fabian Woltermann cb284b7f13 Update shader logic for shadow handling and Vulkan large shader configurations (#7659)
Refactor shadow flag handling in shaders to improve compatibility with small-shader mode. Add support for Vulkan's `-no_large_shaders` switch to split model ubershaders for better driver compatibility. Adjust related comments and preprocessor checks for clarity.
2026-08-02 06:29:02 +02:00
Goober5000 10edea2291 add contains, at_or, and value_or to the SCP maps
Extend the various maps to add a `contains` function, just as several other containers already add `contains`.

Also add `at_or` and `value_or` functions to provide ways to query for map values without inserting anything by default, as `operator[]` does.  If the map does not contain the key, the default value is returned.  Use `at_or` to return a const reference and `value_or` to return a value.  (These are only added to `SCP_map` and `SCP_unordered_map`; a `SCP_multimap` equivalent isn't as clearly useful.)

Also add Class Template Argument Deduction (CTAD) guides, per existing precedent.  And delete function overloads that are intrinsic footguns.
2026-08-01 23:49:27 -04:00
BMagnu 9320ed7e19 Reasonably generic mouse position handling (#7674) 2026-08-01 19:46:57 +02:00
Mike Nelson 319e926bf4 Qtfred briefing map widget fixes (#7663)
* qtfred: render briefing map via offscreen target for an accurate view

The briefing map widget rendered directly into the window at the widget's
size, which broke at display scaling > 100% (the briefing filled only the
top-left 1/dpr of the surface) and never matched the in-game framing or
icon proportions.

Render the briefing once into an offscreen render target at the reference
resolution (Briefing_window_resolution, retail default 888x371) using the
same setup the game uses, then letterbox-blit that finished image into the
widget. Scaling a canonical image means icon sizes, grid, line thickness
and text all scale together, so the editor view is a faithful WYSIWYG copy
of the briefing at any widget size or DPI.

Mouse picking and dragging now map input into reference-resolution space
via the blit rectangle. Selection brackets are drawn as a crisp window-
space overlay after the blit (editor UI, not part of the briefing image).

* qtfred: make the briefing map a resizable plain widget

The briefing map used a native QWindow embedded via createWindowContainer.
Native child windows paint over their siblings and won't shrink inside a
constrained layout, so the map couldn't resize with the dialog without
overlapping the surrounding controls.

Now that the briefing renders into an off-screen render target, read the
finished frame back into a QImage and paint it (aspect-fit, letterboxed)
in an ordinary QWidget. Selection brackets are drawn as a QPainter overlay
and mouse input maps through the paint rectangle. The widget participates
in the layout like any other, so it clips and resizes cleanly.

Drop the map's aspect-ratio pin and minimum size (the image scales to any
size), leaving the left pane free to shrink so the dialog resizes in every
direction without clipping.

* qtfred: pin briefing map resolution to the retail map size

The briefing map was composed at Briefing_window_resolution (the "$FRED
Briefing window resolution" table setting). That setting is only consumed
by FRED2 (an icon-scale-vs-window-size hack) and is ignored by the game
and SCPUI, so honoring it in QtFRED could only make the editor preview
diverge from the shipped briefing.

Pin the compose/render resolution to Brief_grid_coords[GR_1024] instead --
the retail briefing map size, and what Lua's ui.drawBriefingMap() defaults
to -- so the editor is an unconditional WYSIWYG preview of the game and
follows retail automatically if that constant ever changes. BriefingViewport
getSize() is the single source of truth; renderFrame() reads it rather than
duplicating the lookup.

* qtfred: cycle through stacked briefing icons on repeated clicks

Clicking the briefing map always selected the top-most icon under the
cursor, so overlapping icons underneath were unreachable.

The hit-test now collects every icon under the cursor (top-most first).
A plain click advances to the next icon underneath the currently-selected
one, wrapping bottom to top, so repeated clicks roll through the whole
stack; if nothing in the stack is selected it takes the top-most.
Shift-click and empty-space clicks are unchanged.

* qtfred: themed checkerboard matte behind the briefing map

Fill the briefing map's letterbox/pillarbox bars with a subtle,
theme-appropriate grey checkerboard so the (pure-black) map's edges are
clearly distinguishable from the surrounding matte. Two close grey shades
per theme keep it quiet; the tile is cached and rebuilt only when the
editor theme changes.

Add a shared Theme::currentThemeIsDark() that returns the effective dark
state from the applied theme preference (resolving System against the OS
scheme) and adopt it here, in VariableDialog's row tints, and in the help
browser's isDarkMode() in place of three separate palette-lightness checks.
2026-08-01 08:21:20 +02:00
Goober5000andClaude Opus 4.8 41ce07b33a Extract common name conflict checker for FRED2 and qtFRED
The ship editor, wing editor, waypoint path dialog, and jump node
dialog all had near-identical code to check whether a new name
conflicts with an existing ship, wing, waypoint path, jump node, or
target priority group (or is empty, or begins with '<').  Both FRED2
and qtFRED carried their own copies of this logic.

Extract it into a single check_name_conflict() function in
code/missioneditor/common.cpp, shared by both editors, and replace the
duplicated validation at each call site with a one-line call:

  - FRED2:  shipeditordlg, wing_editor, waypointpathdlg, jumpnodedlg
  - qtFRED: ShipEditorDialogModel, WaypointEditorDialogModel,
            JumpNodeEditorDialogModel, and Editor::validate_wing_name

This also aligns a few small behavioral differences that had crept in
between the two editors: the waypoint and jump node dialogs now skip
empty wing slots (wave_count == 0) when checking for wing-name
collisions, and the qtFRED wing editor now rejects names beginning
with '<'.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-31 00:26:07 -04:00
John Fernandez 92384b3c60 More Search UI/UX Enhancements (#7612)
* Prioritize items that begin with search string

Items that begin with the search string should be prioritized before those that simply contain the search string.

* Add Search Operators option

* Add Safety to Action Handler

I didn't see any crashes, but this matches the pattern of other keyboard shortcuts.

* Allow search for numeric nodes

The working code existed already

* Limit matches on operator search

This limits matches on operator searches by setting a minimum size that a string match has to meet.  Function returns -1 on failure, and the warning has been removed to allow this behavior.

* Try this option name better

* Add keyboard shortcut for search

This allows ctrl+s (which can be listed instead of spacebar) to initiate search.  There is a new menu item added as well.

* Put this in the correct place

* Address Feedback

* Wrong state

We want the item to be inserted here, not ignored.
2026-07-30 12:59:33 -04:00
Goober5000 6180173fe8 Merge pull request #7487 from Goober5000/feature/format_classes
Convert CSG/PLR fixed ship/weapon arrays to SCP_vector
2026-07-30 03:23:56 -04:00
Goober5000andClaude Fable 5 5fa6674688 send-message-list arg-count forgiveness, plus better error checking
Due to a long-standing bug, the groups-of-4 argument count check for
send-message-list and send-message-chain never actually worked, so
missions through 25.0 could includes sexps with truncated argument lists
that recovered gracefully at runtime.  Now that the check works, don't
punish those missions: introduce SEXP_CHECK_BAD_ARG_COUNT_BENIGN, which
warns but does not prevent the mission from loading.

To make sexp tree validation more robust, check_sexp_syntax now defers
recoverable errors rather than returning them immediately; otherwise
a recoverable error could mask a fatal error deeper in the tree.

Also fix a stale-variable bug in FRED's fred_check_sexp that could
produce a bogus empty dialog.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-30 01:03:06 -04:00
Goober5000 bb1948abeb Merge pull request #7660 from Goober5000/fix/gtbc_disable
fix crash when a ship with no primary banks is ordered to disable/disarm
2026-07-29 22:50:17 -04:00
Goober5000 1971931649 Merge pull request #7666 from Goober5000/fix/subsystem_targets
fix player subsystem targeting from sexps and scripting
2026-07-29 22:36:25 -04:00
Goober5000 53e31c9f70 Merge pull request #7667 from Goober5000/fix/keycontrol
fix keycontrol unsetting dual-fire
2026-07-29 22:32:32 -04:00
Mike Nelson ec798feb5d Fix space mouse detection under SDL_hidapi (#7665)
Since #7646 switched from the bundled hidapi to SDL_hidapi, no space mouse is
found. SDL_hid_enumerate() nominally auto-inits HIDAPI, but in practice it only
reliably reports an already-connected device when SDL_hid_init() has been called
up front. Call SDL_hid_init() once in os_init() (before SDL_Init), balanced by
SDL_hid_exit() in os_deinit(). io::spacemouse (qtFRED's only consumer) then
enumerates the device correctly.
2026-07-29 21:03:35 -04:00
Mike Nelson bcf3c3182e qtfred: fix and align keyboard shortcuts with FRED2 (#7661)
Fix reported bug where pressing D did not toggle Show Distances: the menu
and toolbar actions both claimed D, producing an ambiguous shortcut
overload. Removed the duplicate from the toolbar action. Applied the same
fix to Space (Selection Lock), which was owned by both a QAction and the
configurable ControlBindings; ControlBindings now owns it exclusively.

Restore/align several FRED2 accelerators and reshuffle dialog keys:
- X = Rotate Locally (routed through the transform local-axes button)
- Ctrl+Shift+P/Shift+P = Props, Shift+L = Team Loadout
- Shift+V = Variables & Containers, Ctrl+Shift+V = Toggle Viewpoint
  (new action mirroring FRED2's OnToggleViewpoint)
- Shift+U = Volumetric Nebula, Shift+M = Shield System

Add Shift+F1 to toggle the sexp help pane for the session (without
changing the saved preference) in the Ship, Wing, Mission Events, Goals,
and Cutscenes editors.

Add F6 / Shift+F6 to cycle Next / Prev in the Ship, Wing, Waypoint,
Jump Node, and Prop editors.
2026-07-29 21:03:04 -04:00
Mike Nelson 2f8b4f2962 Clear stale Prop_info model handles when a model is unloaded (#7669)
Repro: create a prop, File -> New Mission & Discard, then create the same prop
again. Asserts in model_get with "Index collision between model <other>.pof and
requested model N".

model_unload() walks Ship_info and Weapon_info clearing any cached model_num
that pointed at the model being freed, but never did the same for Prop_info.
props_level_close() only clears the prop instances, so prop_info::model_num
survived the discard pointing at a freed slot. Placing that prop class again hit
EditorViewport::create_object_on_grid, which reads pip->model_num directly to
size the placement distance, and by then the slot had been reused by an
unrelated model.

Adds the matching Prop_info pass. Doing it in model_unload rather than
props_level_close mirrors how the two closest analogues are handled -- Ship_info
and Weapon_info are likewise class tables holding a cached handle for placeable
objects -- and covers any unload path, not just level close. (asteroid_level_close
solves the same problem for Asteroid_info from the other direction.)

This is not OFP-specific; SCP master has props and the same missing pass.
2026-07-29 21:02:37 -04:00
Mike Nelson dd1792f28b Fix erroneous "doesn't have a valid target" error on new waypoint orders (#7664)
New initial orders start with no committed target (_object == -1). The
target combo visually defaults to its first item, but with signals blocked
that selection is never pushed to the model, and re-picking the already-shown
item emits no change signal. Ship/player targets auto-commit their first
valid entry inline during population; waypoint paths, individual waypoints,
ship classes, and wings did not, so a new order of those types stayed
uncommitted and failed validation on apply.

Add a uniform fallback after the target combos are populated: if a new order
has no committed target, commit the currently-shown default. No-op for ships
(already committed) and existing orders (loaded with a target already set).
2026-07-29 12:48:49 -04:00
Goober5000andClaude Fable 5 d40fcd16c0 fix player subsystem targeting from sexps and scripting
Fix a regression from #7393: `last_targeted_subobject` must be updated when the player's target and target subsystem are changed, or the subsystem will be retargeted due to `hud_target_change_check`.

Also fix related issues:
- the `ship.Target` and `ship.TargetSubsystem` scripting setters now use the standard `set_target_objnum` function and also correctly handle the new target subsystem
- fix an indexing error in `order.TargetSubsystem`

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-28 23:09:24 -04:00
Goober5000 fb13f1aec5 fix keycontrol unsetting dual-fire
Quick follow-up to #7635.  Even though you can't set dual-fire when a non-dual-fire bank is active, you should be able to *un*set it.  Reorder the conditions so this is possible.
2026-07-28 22:57:54 -04:00
Goober5000 4e26511055 Merge pull request #7635 from Goober5000/fix/firingpoints
fix dual fire handling for banks that cannot use it
2026-07-28 19:42:24 -04:00
Goober5000andClaude Fable 5 60c554354c fix dual fire handling for banks that cannot use it
The Secondary_dual_fire flag is per-ship, but not every secondary bank
can dual fire.  Previously the HUD drew the dual fire link icon even
when the armed bank had only one firepoint, and firing such a bank
cleared the flag entirely, silently discarding the player's dual fire
preference for the other banks.

Extend the ignored-rather-than-cleared approach already established
for no-doublefire weapons to the single-firepoint case:

- add ship_secondary_bank_can_dual_fire() as the single source of truth
  for bank validity, No_doublefire, ai_profiles restrictions, and
  firepoint count
- ship_fire_secondary() now fires single and leaves the flag alone when
  the bank cannot dual fire, instead of removing the flag
- the weapons gauges and the hardpoints gauge consult the helper, so the
  link icon and second-firepoint highlight are no longer drawn for
  incapable banks
- the dual fire toggle on a single-firepoint bank now explains why it
  cannot dual fire and leaves the preference untouched
- restoring the saved dual fire preference no longer depends on bank 0's
  firepoint count, since incapable banks now simply ignore the flag
- key control and force feedback also consult the helper

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-28 01:08:07 -04:00
Fabian WoltermannandClaude Opus 5 6e6b5141f3 clang tidy pipeline fixes (#7657)
* ci: run clang-tidy on macOS and Linux clang builds

Install a version-matched clang-tidy on macOS and make the shared runner portable across both CI environments.

* ci: keep valgrind compatible with clang debug builds

Emit DWARF4 debug information and suppress SDL's one-time initialization allocation so memcheck can report actionable failures.

* ci: suppress SDL3/libX11 video backend leaks under valgrind

Now that the `[ "$CONFIGURATION" = "Debug" ]` typo is fixed, valgrind
actually runs on the Linux Debug legs again, and memcheck fails the build
on six "definitely lost" contexts (480 bytes) that all originate inside
SDL3's X11 backend at X11_CreateDevice, reached from SDL_VideoInit.

These aren't ours to fix: both call sites (os_init's SDL_Init and the
temporary video init in resolution_default(), reached via gr_init) already
pair their init with a matching quit, and the allocations are made and
kept by SDL/libX11 across SDL_VideoQuit. Collapsing the repeated init/quit
cycles would only reduce the block count, not the number of leak contexts,
so the build would still fail.

The suppression is truncated at X11_CreateDevice on purpose: it stays
caller-agnostic (covering both the os_init and gr_init paths) and avoids
matching on SDL_InitSubSystem_REAL.part.0, whose .part.0 suffix is a
partial-inlining artifact of how SDL3 happened to be built.

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

* ci: suppress glibc dlopen loader leaks under valgrind

The Linux clang-16 Debug leg fails memcheck on a single zero-byte block
lost in glibc's `_dl_find_object_update()`, reached from `dlopen()` while
SDL probes its video backends. It is loader bookkeeping that glibc keeps
by design, and it only shows up on some legs because it depends on how
many objects the loader has mapped by that point.

The file already carried three suppressions for this class, but each
pinned a full, exact glibc stack, so every new loader code path costs
another CI round. Replace them with a single block matching on
`_dl_open`: nothing of ours runs below that frame, only loader internals
and the static constructors of shared libraries the loader brings in, so
this gives up no coverage we care about while retiring the whole class.
All three removed blocks contain `_dl_open` themselves and are therefore
subsumed by it.

Both new suppressions match `definite,possible` rather than `definite`
alone, since `--errors-for-leak-kinds` defaults to `definite,possible`
and the same allocations would otherwise fail the build if valgrind ever
reported them as possibly lost instead.

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

---------

Co-authored-by: Claude Opus 5 <[email protected]>
2026-07-27 21:54:29 +02:00
Goober5000andClaude Opus 4.8 b7cd9765a1 fix crash when a ship with no primary banks is ordered to disable/disarm
Gunships and other custom ship classes may run through the primary weapon code despite having no primary banks.  Add a few guards to prevent this from causing crashes.

Fixes a crash reported in Causality: The Great War.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-27 12:12:10 -04:00
Goober5000andClaude Opus 4.8 5bbf00f327 remove rpavlik-cmake-modules submodule and dead cotire references
Vendor the CMake modules FSO actually used from the ~150-file rpavlik
submodule into cmake/, then drop the submodule:

  - EnableExtraCompilerWarnings.cmake (toolchain warning flags)
  - CreateLaunchers.cmake + its CleanDirectoryList.cmake dependency and
    launcher-templates/ (IDE launcher generation for the game and FREDs)
  - MSVCMultipleProcessCompile.cmake + its ListCombinations.cmake dependency

Also remove the dead cotire module-path entry and the vestigial
ENABLE_COTIRE option (cotire is never invoked and its directory no longer
exists), and repoint the submodule-initialized check at libRocket.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-26 15:54:07 -04:00
Taylor Richards 29116e7ba4 remove hidapi lib in favor of SDL_hidapi (#7646)
* remove hidapi lib in favor of SDL_hidapi

Funtionally identical under the hood, but offering maximum capability and
compatability without adding any system dependencies. The main difference on
our side is that hidapi functions and types will be prefixed with "SDL_". And
SDL will dynamically load in the hidapi features best supported by the running
operating system.

* remove residual SDL_hid_exit call
2026-07-26 13:57:57 -04:00
Mike Nelson e1ad0deaa2 expanded data list menu usage (#7645) 2026-07-26 09:34:05 -04:00
The Force da05081405 Fix C4709 Warnings In modern VS (#7576)
* 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
2026-07-26 14:33:00 +01:00
Mike Nelson 5227826357 align object dialog initialization (#7644) 2026-07-24 21:10:06 -04:00
Fabian Woltermann 09ab04193b Merge pull request #7651 from The-E/fix/resolution-initialization-on-empty-config
Fix resolution_default to handle uninitialized SDL video subsystem
2026-07-24 21:16:10 +02:00
Fabian Woltermann d0cefc87bd Fix inverted scpui icons and 3d ship/weapon select models rendering in the wrong place (#7647)
* Fix Vulkan render target readback orientation to match OpenGL behavior

* Add render target pass resumption after Vulkan shadow rendering

Ensure that Vulkan render targets resume rendering properly after shadow passes, particularly for off-screen targets like SCPUI's 3D previews, to prevent unintended content leakage onto the screen.

* Skip alpha writes for additive batched bitmaps in render targets to preserve coverage and prevent opaque artifacts.

* Add external subpass dependencies to Vulkan render passes to fix read/write hazards

Introduce explicit memory dependencies for color attachments sampled between passes to prevent flickering and graphical artifacts caused by read-after-write and write-after-read hazards.

* Support fractional alpha values in createColor function and preserve 0-255 compatibility
2026-07-24 21:10:13 +02:00
the-e 9eb11aec7f Fix resolution_default to handle uninitialized SDL video subsystem
Ensure SDL video is temporarily initialized if needed during startup to safely query desktop display mode, preventing reliance on a potentially dead subsystem and avoiding silent fallback to invalid resolutions.
2026-07-24 20:38:14 +02:00
Fabian Woltermann 8c0787016d Merge pull request #7642 from JohnAFernandez/Remove-Unused-File
Remove unused file
2026-07-24 17:43:44 +02:00
Fabian Woltermann 6ae666d10a Merge pull request #7643 from Shivansps/vulkan-etc2
ETC2 textures support in Vulkan
2026-07-24 17:43:16 +02:00
Fabian Woltermann a6e4ec8df5 Merge pull request #7648 from notimaginative/extra_log_info
add some helpful system info to debug log
2026-07-24 17:42:57 +02:00
Taylor Richards aaa48d4374 fix uniform type mismatch error in shadow_map shader (#7649) 2026-07-24 04:18:48 +00:00
Taylor Richards 826ca2758e add some helpful system info to debug log 2026-07-23 15:35:15 -04:00
Taylor Richards 039880491a port to SDL3 (#7220)
Basic port from SDL2 to SDL3. Also updates imgui to 1.92.5.
2026-07-23 15:52:27 +02:00
Fabian Woltermann 1abbea499b Merge pull request #7640 from The-E/fix/various-scpui-issues-in-vulkan
Fix various issues in SCPUI when using Vulkan
2026-07-23 09:12:20 +02:00
John Fernandez 2dbc6f79ad Change Tab Orders (#7641)
Quick audit of tab orders to improve usability
2026-07-22 20:31:56 -04:00
Mike Nelson e75f72456e QtFRED add object reorder dialog (#7636)
* Add Reorder Objects dialog to QtFRED

* clang

* clang again

* address feedback
2026-07-22 20:31:38 -04:00
Mike Nelson 19cd71d643 Add searchable submenu style in event editor (#7628) 2026-07-22 20:31:16 -04:00
Mike Nelson 91fc1fbf43 QtFRED Layer Updates (#7631)
* Add layer rename and bold current layer in Move to Layer menu

Bold the object's current layer in the Move to Layer context menus
instead of marking it with a checkbox.

Add a Rename Layer button to the Layer Manager dialog. Renaming
rewrites the per-object fred_layer string on every object assigned to
the layer. The Default layer cannot be renamed.

* Add layer rename to Scene Browser right-click menu

Right-clicking a layer row in the Scene Browser now shows a small
context menu with Rename Layer, which opens a rename box and applies
the rename through the same viewport path as the Layer Manager dialog.
The Default layer cannot be renamed.
2026-07-22 20:30:46 -04:00
Salvador Cipolla a40868497e ETC2 textures support in vulkan 2026-07-22 19:45:29 -03:00
Fabian Woltermann 2c3e052ec6 Fix Vulkan FXAA/SMAA handling and ensure proper Scene_ldr transitions (#7639)
Address FXAA/SMAA shader selection logic by aligning it with the current anti-aliasing mode. Fix undefined behavior for discarded non-edge pixels during the FXAA main pass by transitioning Scene_ldr to a load-preserving layout. Optimize compatibility across IMR and tiled GPUs, preventing visual artifacts.
2026-07-23 00:38:44 +02:00
the-e 1e5bf66c84 Remove unsupported graphics options for non-raytracing renderers
Disable raytraced-shadow and HDR settings in the options menu when unsupported by the current renderer, reducing UI clutter and ensuring only relevant options are displayed.
2026-07-22 20:41:24 +02:00
John Fernandez b65f7b1fc3 Remove unused file
Someone attempted to rename this file and then did not delete the old one, so just remove to avoid confusion.
2026-07-22 14:33:29 -04:00
Mike Nelson 28b8be69db QtFred add object select menu dropdown (#7627)
* add object select menu dropdown

* clang
2026-07-22 14:15:46 -04:00
the-e 0d64f7f237 Add load-variant render pass support for Vulkan render targets
Introduce load-variant render passes to Vulkan textures, enabling content preservation during mid-frame readbacks. Changes include creating `renderPassLoad`, flushing command buffers for readbacks, and resuming rendering into targets with `loadOp=eLoad` to avoid clearing previously rendered content.
2026-07-22 19:57:10 +02:00
the-e 334682c44d Add depth/stencil support to Vulkan render targets
This commit introduces optional depth/stencil attachments for Vulkan render targets, enabling z-testing and addressing earlier limitations where depth-tested geometry rendered in submission order without depth attachments. Changes include resource allocation, attachment handling, and layout transitions.
2026-07-22 19:18:01 +02:00
Fabian Woltermann 22eefdd0ec Merge pull request #7637 from The-E/fix/missing-include-in-voicerec
Include cmdline.h in voicerec.cpp for command-line argument handling
2026-07-21 20:56:23 +02:00
the-e 52fd215b3f Include cmdline.h in voicerec.cpp for command-line argument handling 2026-07-21 19:57:54 +02:00
b26b9cf743 Add Vulkan rendering backend, with HDR10 output and raytraced shadows (#7554)
* Shadow overhaul round 2: PCSS, dynamic cascades, cockpit/instanced shadows

Squashed snapshot of lafiel/shadow_overhaul_2's continuation work
(d3922aa87..010f9d2d9), which has no separate upstream PR of its own.

* Add Vulkan rendering backend

Squashed snapshot of the Vulkan backend as vendored into this branch
(ce2d1e85f..6ce539463, authored by Mara van der Laan with build/CI
fixes by Taylor Richards). This work has its own upstream PR #7553
(notimaginative:vulkan-pr-new) and is not otherwise original to true-hdr.

* Add true HDR output, RT shadows, and PCSS shadows for Vulkan

Squashed snapshot of true-hdr's own integration work: true HDR
framebuffer output, wiring the shadow overhaul into the Vulkan
backend (raytraced + cascaded shadow maps), logging refactors, log
filter UI, and CI/build fixes.

* refactor: update Vulkan logging calls to use `nprintf` for scoped logging

* fix: correct shadow cascade count in Vulkan logging calls

* fix: fall back to PATH-resolved ccache when CCACHE_PATH is stale

CI hardcodes /usr/local/bin/ccache, but macOS runners on Apple Silicon
hosts install Homebrew (and ccache) under /opt/homebrew instead,
causing all four Mac configs to fail with "No such file or directory".

* Fix vulkan texture readback to correctly handle transparent pipelines

* Put the pixel swizzles into a util file

* OOB safety check

* Fix fog handling

* Fix MSAA in Vulkan

* fix: enable SMAA for vulkan

* adapt the post-processing AA shaders to hdr

* fix clang-tidy warnings: concatenate nested namespaces, static methods

pixel_swizzle.{h,cpp}: collapse `namespace graphics { namespace util {`
into `namespace graphics::util {` (modernize-concat-nested-namespaces).

VulkanState.h/VulkanDraw.h: getClearColor()/setClearColor() only touch
gr_screen and other globals, no instance state, so make them static
(readability-convert-member-functions-to-static) and drop the
top-level const on getClearColor()'s return type
(readability-const-return-type).

* fix clang-tidy warning

* Extend raytraced shadows to all directional lights

RT shadows previously only shadowed the first directional light in the
scene, even though multi-sun missions push more than one directional
light into the engine. The TLAS raytraced shadows query against is
already light-agnostic, so this extends both the deferred lighting
pass and the forward MODEL shader to shadow every directional light
(capped by a new Max Raytraced Shadow Lights option, configurable via
the in-game Options menu or the Lab's Scene rendering options).

Cascaded shadow maps are untouched: they're built for a single light
only, so the deferred pass still limits itself to one shadowed
directional light whenever CSM (not raytracing) is the active method.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_016DA6JHJ3twz2oHGQcZ1Udc

* refactor: improve shader flag handling and render target finalization in Vulkan backend

- Consolidate shader flag setup logic in VulkanPostProcessingLighting to simplify configuration.
- Fix dead field usage in VulkanDraw by ensuring correct image view selection for environment maps.
- Add mip chain regeneration in VulkanRenderer::endRenderTarget to ensure correct mip levels for cubemaps.

* fix: ensure correct mip chain initialization in cubemap rendering

- Add mip chain generation to VulkanDraw for cubemaps with multiple mip levels.
- Fix potential issue with uninitialized mip levels above level 0 in deferred-f.sdr sampling paths.

* fix: don't crash on startup when environment locale is unavailable

std::locale("") pulls in the process's environment locale categories
(LC_CTYPE, LC_MONETARY, LC_PAPER, etc.), and throws std::runtime_error
if any of them names a locale not installed on the host (e.g.
mismatched regional-format overrides). Since SCP_default_locale is
constructed during static initialization, before main() and before any
of our own exception handling exists, an uncaught throw here aborted
the whole process before the game even started. Fall back to the
classic "C" locale instead, which is sufficient since this locale is
only ever used for ASCII case-folding (SCP_toupper/tolower/isdigit).

Co-Authored-By: Claude Sonnet 5 <[email protected]>

* `feat: add raytraced shadows for local lights and optimize TLAS management`

* Make the Commandline authoritative for -res and -vulkan / -opengl

* address BMagnu's comments on the original PR

* update prebuilt version hash to 62c89ef6

* refactor: standardize graphics API handling and document Vulkan limitations in qtFRED

- Updated `AboutDialogModel.cpp` to use `GraphicsAPI` enum for better clarity.
- Ensured qtFRED falls back to OpenGL when Vulkan is requested.
- Added detailed documentation in `qtfred/README.md` regarding Vulkan backend limitations and requirements.

* chore: include cmdline library in main and mission management modules

* docs: add known issue for blank render viewport under Wayland in README.md

* fix CI issues

* chore: add `GR_DEBUG_SCOPE` markers across Vulkan post-processing and rendering paths for improved debugging

* feat: improve Vulkan HDR pipeline with separate encode paths for SDR and HDR10

- Added an HDR10-specific output encode pass in `VulkanRenderer` and `VulkanPostProcessor` that applies PQ/BT.2020 transfer.
- Introduced an optimized SDR fallback path with direct blit support for devices that can skip shader draws.
- Updated `m_sceneLdrCompressed` to provide [0,1]-clamped proxies for HDR edge detection.
- Refactored `copyImageToImage` and added `blitImageToImage` to allow format conversions and optimized transfers.
- Replaced `hdr_mode` with explicit HDR10 flags and data structures for clearer state handling.

* refactor: encapsulate SMAA processing and refactor VulkanPostProcessor

- Moved SMAA-specific logic into `VulkanSMAA` class for better separation of concerns.
- Simplified `initSMAA`, `shutdownSMAA`, and `executeSMAA` logic by leveraging `VulkanLDR` and `PostProcessContext` abstractions.
- Improved initialization and cleanup code with clearer resource lifecycle management.
- Updated debug markers and messages to reference `VulkanSMAA`.
- Enhanced flexibility of fullscreen triangle rendering API with additional parameters (`sampleCount`, `bindGlobalSet`).

* Update Vulkan porting credits in AboutDialogModel

* fix CI issues again

* fix: correct Y-flip in viewport setup for Vulkan light volumes

- Adjusted viewport height to negative value to account for G-buffer render pass's negative Y-axis convention.
- Ensured proper alignment of point and tube light volumes with actual light positions.

* feat: add user-adjustable gamma correction to SDR and HDR10 output paths

- Added gamma correction to HDR10 path, applying user settings before PQ/BT.2020 encode.
- Replaced the SDR blit-only path with an encode pass that applies gamma correction, ensuring consistent behavior across devices.
- Refactored VulkanPostProcessor and associated shaders to support gamma slider adjustments for both SDR and HDR output.

* fix: ensure proper uniform and cull mode setup for Vulkan sphere rendering

- Added uniform buffer setup and default material uniforms in `vulkan_draw_sphere`.
- Disabled face culling in material definition for sphere rendering to align behavior with OpenGL.

* Ensure memory flushes after UBO updates and add explicit pipeline barrier for MoltenVK compatibility.

* fix: add clear pass for SMAA edge detection to prevent persistent artifacts

* feat: add Vulkan debug-utils messenger, opt-in sync validation, frame diagnostics

Groundwork for the renderer-review fix series (.agents/plans/
vulkan-renderer-review-fixes.md, Phase 0):

- Register a VK_EXT_debug_utils messenger (preferred) instead of only the
  deprecated VK_EXT_debug_report callback, which is kept as a fallback for
  loaders without debug-utils. The messenger config is also chained into
  instance creation so vkCreateInstance-time messages are captured.
  Validation errors/warnings now go to the main log via mprintf.
- New -gr_sync_validation cmdline flag (implies -gr_debug): chains
  VkValidationFeaturesEXT with synchronization validation into instance
  creation when VK_LAYER_KHRONOS_validation is available. This is the
  regression net for the synchronization fixes in the following commits.
- Frame stats now report descriptor sets/writes allocated per frame and the
  total pipeline count (descriptor-manager counters; DescriptorWriter::flush
  moved out-of-line to report into the manager).

Co-Authored-By: Claude Fable 5 <[email protected]>

* refactor: unify Vulkan render-pass begins, clamp in-pass clears, add per-frame UBO ring

Phase 1 of the renderer-review fix series: shared helpers that the
correctness fixes build on.

- New VulkanRenderer::beginTrackedRenderPass(PassBeginDesc): every render-pass
  begin on the frame command buffer now records vkCmdBeginRenderPass and
  updates the state tracker (pass, color attachment count, sample count,
  render area, viewport) in one place. Converted: setupFrame,
  beginSceneRendering (both branches), resumeSceneRendering,
  endSceneRendering, beginRenderTarget (viewport kept for gr_set_viewport),
  resumeSwapChainPass, plus new resumeScenePassAfterCopy() shared by
  copyEffectTexture/copySceneDepthForParticles and
  rebeginSwapChainPassAfterReadback() shared by both readbackFramebuffer
  exits (~200 lines of duplicated begin/clear/viewport blocks removed).
- The state tracker records the active render area; all four
  vkCmdClearAttachments sites clamp their rects against it and skip empty
  results. Previously gr_screen-sized clear rects could exceed a smaller
  off-screen render target's render area, which is invalid API usage.
- New PerFrameUboRing utility (VulkanPerFrameUbo.h/.cpp): a persistently
  mapped uniform buffer with one slot region per frame in flight and a
  self-flushing alloc(). Used by the next commit to fix the cross-frame
  UBO races.

Deliberate behavior deltas (all safe-direction): the helper always sets
colorAttachmentCount/sampleCount (some sites previously inherited stale
values), and resumeSwapChainPass/resumeSceneRendering now also reset the
standard flipped viewport instead of leaving the previous one.

Co-Authored-By: Claude Fable 5 <[email protected]>

* fix: eliminate Vulkan cross-frame UBO/buffer races, make gr_sync honest

Phase 2 of the renderer-review fix series: with two frames in flight, several
host-visible resources were rewritten by the CPU while the previous frame's
GPU work could still be reading them.

- The post-processing scratch UBO is now a PerFrameUboRing (per-frame-in-
  flight slot regions, persistently mapped, self-flushing alloc). All the
  per-pass map/unmap/cursor bookkeeping in bloom/LDR/fog/SMAA is deleted;
  the cursor resets exactly once per frame via the new
  VulkanPostProcessor::beginFrame() hook in setupFrame.
  This also fixes two latent bugs: (a) fog consumed scratch slots mid-scene
  and bloom's later same-frame cursor reset overwrote them before submission
  whenever fog+bloom were both active; (b) volumetric_fog_data (288 bytes)
  overflowed the 256-byte slots - the memcpy spilled into the neighboring
  slot and the shader's bound range ended before the aspect/fov/noise
  fields. Slots are now 512 bytes (static_assert at the fog call site) and
  the per-frame budget is 32 slots.
- The dedicated tonemap, output-encode, and MSAA-resolve UBOs (each a single
  instance rewritten per frame, i.e. racy; the MSAA one also never flushed)
  are deleted entirely - those passes build their structs on the stack and
  allocate from the scratch ring.
- Static-buffer rewrites orphan instead of racing: getVkBuffer() stamps a
  last-used frame number, and a full-content updateBufferData() on a Static
  buffer used within the last MAX_FRAMES_IN_FLIGHT frames defers the old
  VkBuffer to the deletion queue and writes a fresh one. Offset writes
  (GPU-heap appends to fresh regions) and PersistentMapping buffers (the
  engine holds their mapped pointer and syncs via gr_sync) are exempt.
- gr_sync_wait now honors its contract: VulkanRenderFrame::waitForFinish
  takes a timeout and reports expiry without consuming the fence;
  VulkanRenderer::waitForFrame returns false for fences taken during the
  still-recording frame (previously it waited on a stale or idle slot and
  reported success) and resolves the frame->slot mapping correctly. This
  makes UniformBufferManager's segment-fence throttling actually protective
  on Vulkan for the first time.

Co-Authored-By: Claude Fable 5 <[email protected]>

* Refactor framebuffer and target lifecycle: centralized creation/resizing logic, improved resource management across Vulkan post-processing subsystems.

* Improve Vulkan cross-frame sync with explicit access masks and validation updates

- Unified Vulkan validation flag handling in setup, separating engine debug output from synchronization validation.
- Specified access masks and stage flags to address cross-frame WRITE_AFTER_WRITE vulnerabilities, notably in G-buffer, post-processing, and MSAA resolve paths.
- Adjusted subpass dependencies and image layout transitions to prevent read-after-write and write-after-write hazards, detected using `-gr_sync_validation`.
- Fixed uniform buffer and barrier transitions to ensure proper ordering in render passes across frames.

* Refactor Vulkan render-pass dependencies and sync stages for clarity and future testing

- Clarified dependency logic for cross-pass and cross-frame synchronization, ensuring explicit stage and access flag comments match observed behavior.
- Adjusted subpass dependencies to decouple render-pass compatibility definitions from dependency assumptions.
- Highlighted MoltenVK-specific issues and retained workarounds with notes for re-testing on macOS hardware.
- Updated synchronization barriers and validation notes to address historical inconsistencies across platforms.

* Remove transfer queue support; consolidate all operations into the graphics queue

- Removed transfer queue handling, simplifying buffer and queue logic to use the graphics queue for all transfer operations.
- Updated buffer sharing modes and removed related code paths and parameters for transfer queue families.
- Introduced exclusive buffer access, noting async transfer and queue ownership transfers are future work.
- Limited enabled device features to only those actively used by the renderer, reducing unnecessary feature requests (e.g., robustBufferAccess).

* Refactor frame advancement and descriptor management in VulkanRenderer

- Removed `endFrame()` from `VulkanDescriptorManager`, replacing it with `setCurrentFrame(frameIndex)` for explicit frame synchronization.
- Centralized frame index advancement within `VulkanRenderer` to ensure consistency across components.
- Invalidated cached descriptor set bindings at render-pass boundaries to handle raw recorder staleness correctly.
- Optimized pipeline descriptor rebind logic by removing redundant clears during pipeline changes.

* Refactor `PipelineConfig::hash` and Global descriptor set management for improved hashing and memoization efficiency

- Replaced inefficient shift-based hashing in `PipelineConfig::hash` with a robust `hashCombine` strategy to minimize bucket collisions.
- Introduced `maskBits` helper to streamline bitmask creation for boolean fields.
- Implemented memoization for Global (Set 0) descriptor sets, reducing redundant rebuilds across frames and improving performance.
- Added dirty state tracking (`m_globalSetDirty`) and invalidation logic for Global set updates following relevant input changes (e.g., TLAS or UBO modifications).

* Refactor Vulkan post-processing output logic: extracted shared `encodeToSwapChainPass` method to streamline HDR and SDR output paths.

* Optimize Vulkan descriptor set management with per-frame memoization

- Added memoization for Material (Set 1) and PerDraw (Set 2) descriptor sets to reuse cached sets across frames if inputs remain unchanged.
- Implemented precise input tracking for Material and PerDraw descriptor sets to detect and rebuild only when necessary.
- Reduced redundant Vulkan descriptor allocations and writes, improving performance on repeated or batched draw calls.

* Fix Vulkan access masks for G-buffer and scene pass transitions

- Added `eColorAttachmentRead` to access masks in multiple barriers to ensure proper synchronization for load operations (e.g., `loadOp=eLoad`) across render passes.
- Prevented `READ_AFTER_WRITE` synchronization validation issues in deferred and multi-pass rendering scenarios.
- Updated comments to clarify the reasoning behind the access mask changes and load dependencies.

* Refactor Vulkan resource management and indexed draw logic

- Consolidated destruction queue methods in `VulkanDeletionQueue` using a shared `queueHandle` template to reduce redundancy.
- Introduced `ModelDrawParams` structure and `computeModelDrawParams` function to unify indexed draw parameters for models and shadows.
- Simplified draw logic by eliminating repeated calculation of indexed draw parameters and improving code reuse.

* Add `releaseAnimationSlotRef` for ref-count handling of shared animation texture arrays

- Introduced `releaseAnimationSlotRef` to manage reference counting for shared animation texture-array slots, preventing premature destruction of shared resources.
- Updated `flushTextures` and related methods to integrate `releaseAnimationSlotRef` for shared slot cleanup.
- Refactored staging buffer creation into `createStagingBuffer` for 3D texture uploads and shared path consistency.
- Enhanced texture upload logic with improved tracking and logging for re-upload cases, aiding preloading gap analysis.
- Added instrumentation for on-demand mid-frame texture uploads in `FrameStats`.

* Refactor Vulkan resource and texture management, extend debug logging

- Updated smart pointer usage for consistency across Vulkan resource managers (`std::make_unique`).
- Removed redundant `cubeImageView` member and related cleanup logic; unified sampling view management.
- Added per-instance debug log counters (avoiding static state) for Vulkan texture uploads and bindings, improving visibility during initialization and run-time.
- Optimized `VulkanRenderer` property/feature getters by caching physical device properties and features during device creation.
- Improved memory region flushing logic for out-of-date offsets, aligning with Vulkan Memory Allocator behavior.
- Refactored render pass state tracking to reapply dynamic state reliably after mid-frame render passes.
- Enhanced validation and error handling for mismatched update texture formats.

* fix CI build warning

* Fix on-demand YUV texture locking and set alpha uniform for movie rendering in Vulkan

- Corrected bitmap lock type from `BMP_TEX_OTHER` to `BMP_AABITMAP` for YUV textures during on-demand loading.
- Updated movie rendering to bind alpha from the `MovieData` UBO (set 2, binding 4) for Vulkan, aligning behavior with OpenGL implementation.

* revert inadvertent changes introduced by a squash commit

* address ci issues

* remove wip stuff from code comments that make no sense to people who aren't me

* Remove unused LD_LIBRARY_PATH export and fix shader shadow map sampling.

* Refactor batched rendering uniform setup in Vulkan

- Removed unused fields from `genericData` uniform block, simplifying shader definitions.
- Updated Vulkan material uniform handling to align with OpenGL's batched rendering logic.
- Introduced `batched_data` struct for streamlined UBO updates, improving maintainability and clarity.

* Add `-hdr` command-line option for HDR nits configuration

- Introduced `-hdr` parameter to set paper white and peak luminance in nits, overriding in-game HDR settings.
- Added input validation with error feedback for incorrect format.

* Remove unused `MODEL_SDR_FLAG_SHADOW_MAP` macro from shader flags

* clean up unrelated changes

* Invalidate Vulkan state tracker bindings after external command buffer modifications

- Added `invalidateExternalBindings()` to `VulkanStateTracker` to handle stale pipelines, descriptors, and dynamic states caused by external Vulkan commands (e.g., ImGui renders mid-pass).
- Updated ImGui rendering flow to call `invalidateExternalBindings()` post-draw, ensuring consistent pipeline state for subsequent engine renders.
- Adjusted image usage flags to account for `eTransferSrc` in texture readbacks.
- Fixed barrier `dstAccessMask` and pipeline stage flags to ensure proper synchronization for image transitions.

* Add a few more generically useful lines to the log

* Simplify Qt deployment logic and improve handling for system-installed Qt on Linux

* add cmdline headers to make qtFred compile

* use VK_KHR_synchronization2 for sync and barrier purposes

* Simplify and clean up Qt deployment logic in CMake for qtfred

* update get_bitmap_from_texture to match the new API

* Enhance `-hdr` command-line option to force HDR output and support optional nits configuration with improved input validation and overrides.

* Improve Vulkan robustness, add device feature checks, and refine memory/logging.

- Added depth clamping support gated by device feature checks, with fallback where unsupported.
- Improved deferred lighting UBO overflow handling with user-accessible warnings and buffer size adjustments.
- Enhanced Vulkan logging for validation, allocation, and debugging:
  - Automatic logging for descriptor pool exhaustion/fragmentation.
  - Vertex format and resource destruction checks.
  - Notifications for generic layout transitions and volumetric texture uploads.
- Consolidated shader logic to ensure Vulkan and OpenGL parity.

* Remove HDR10 encode shader and consolidate gamma correction logic for SDR and HDR10. Replace `Verify` assertions with `Assert`

* Add braces to conditional in object lighting logic for clarity

* Refine Vulkan memory allocation checks with `Verification` for improved error handling and messaging.

* Refine VulkanBuffer resize logic to preserve contents with CPU-side copy and add safeguards for non-coherent memory scenarios.

* Add detailed comments explaining descriptor array capacity constants in VulkanDescriptorManager

* Gate shadow support on depth clamp feature availability and clarify its necessity with improved comments.

---------

Co-authored-by: BMagnu <[email protected]>
Co-authored-by: Mara van der Laan <[email protected]>
Co-authored-by: Claude <[email protected]>
2026-07-21 16:58:21 +00:00
Goober5000 3e317d9734 Merge pull request #7515 from Goober5000/feature/reorder_ships_wings
add a Reorder Ships and Wings editor to FRED
2026-07-20 23:56:30 -04:00
Goober5000 47d5ccca35 Merge pull request #7588 from Goober5000/cleanup/external_weapons
External weapon model cleanup: consolidate duplicated logic, fix several longstanding bugs
2026-07-20 23:40:35 -04:00
Goober5000andClaude Fable 5 52763935c7 add a Reorder Ships and Wings editor to FRED
Accessible from the Tools menu, this dialog lists all ships or all
wings (selectable via a drop-down) and provides Move to top/up/down/
bottom buttons.  Moves are applied immediately via the slot
reassignment utilities in missioneditor/common.h, using adjacent swaps
so the relative order of other entries is preserved.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-20 23:15:16 -04:00
Goober5000andClaude Opus 4.7 175f414c03 add Wings[] slot reassignment utilities for FRED/qtFRED
reassign_wing_slot moves a wing between Wings[] slots, fixing up every
back-reference: Ships[i].wingnum, the Starting/Squadron/TVT_wings caches
(via update_custom_wing_indexes), the FRED-side parallel array
wing_objects, and cur_wing (passed via FredWingSlotConfig so non-FRED
callers can opt out).  swap_wing_slots wraps it as a three-leg swap
through a temporary empty slot.

Also consolidates update_custom_wing_indexes (previously duplicated
verbatim in fred2/management.cpp and qtfred Editor) into common.cpp so
the new utility — and any future caller — has one shared implementation.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-07-20 23:04:56 -04:00
Goober5000andClaude Opus 4.7 74b2776c00 add Ships[] slot reassignment utilities for FRED/qtFRED
reassign_ship_slot moves a ship between Ships[] slots, fixing up every
back-reference: Objects[].instance, Ai_info[].shipnum, Wings[].ship_index[],
Player_start_shipnum, Ship_registry's cached shipnum, and the FRED-side
parallel arrays Fred_alt_names/Fred_callsigns plus cur_ship (passed via
FredShipSlotConfig so non-FRED callers can opt out).  swap_ship_slots
wraps it as a three-leg swap through a temporary empty slot.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-07-20 23:04:56 -04:00
Goober5000andClaude Fable 5 7b241812ed make ship move-correct
Ship's implicit move operations compiled but were semantically broken in
two ways: moving copied the subsys_list sentinel's links verbatim,
leaving the bookend nodes of the intrusive subsystem list pointing at
the old sentinel address; and the raw owning warpin_effect /
warpout_effect pointers were copied without nulling the source, so a
vacated Ships[] slot aliased live WarpEffects that ship::clear() (on
slot reuse) or ship_close() (which walks every slot) would delete out
from under the destination ship.

Fix both at the member level so the compiler-generated moves are
correct by composition:

- Add ship_subsys_sentinel, derived from ship_subsys, whose move
  operations re-link the bookend nodes (handling the null-linked,
  self-referential-empty, and populated source states) and leave the
  moved-from sentinel as an initialized empty list.  Copying is
  deleted.  Public inheritance keeps &ship::subsys_list convertible to
  ship_subsys*, so list_init, GET_FIRST, and every other call site is
  unchanged.

- Convert warpin_effect and warpout_effect to std::unique_ptr.  The
  delete-and-null blocks in ship::clear(), ship_close(), and
  ship_set_warp_effects() collapse into reset(); call sites that borrow
  the pointer into a raw local now use get().

With the members handling themselves, declare ship's special member
functions: defaulted default constructor and moves, deleted copies
(previously implicit via the unique_ptr members, now stated intent).
The moves are deliberately not declared noexcept because members like
SCP_map and std::deque do not have standard-guaranteed nothrow moves;
the computed exception specification is used instead.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-20 23:04:56 -04:00
Mike Nelson 1281871694 Reduce mouse travel when naming new event-editor messages (#7633)
Move the New/Insert/Delete message buttons directly above the message
name field so creating a message and naming it no longer requires
travelling past the message text box.

Also focus and select the name field after New Msg and Insert Msg so
the placeholder name can be replaced by typing immediately, without an
extra click.
2026-07-20 22:57:46 -04:00
Mike Nelson 533aa66ac2 Enforce single instance for non-modal editor dialogs (#7632)
Menubar entries and keyboard shortcuts (e.g. Shift+E for the event
editor) created a new dialog instance on every activation. Route the
non-modal dialog slots through a showSingleInstanceDialog helper that
raises and focuses an already-open instance instead of creating a
second one.

Ship/Wing/Prop and the error checker keep their existing member-pointer
guards since other code references those open instances.
2026-07-20 22:56:39 -04:00
Mike Nelson b1c3022144 signal and slot cleanup (#7621) 2026-07-20 22:55:33 -04:00
Mike Nelson 25132986e0 use ship index, not objnum (#7625) 2026-07-20 22:55:22 -04:00
Mike Nelson ec7ea78c2a make width of orders dropdowns smarter (#7626) 2026-07-20 22:55:04 -04:00
Goober5000 820048bda8 Merge pull request #7608 from Goober5000/fix/vector_movable_collections
fix move semantics for data loaded from .tbl and .tbm files
2026-07-20 19:15:31 -04:00
Goober5000 cdb11f7357 patch mission format compatibility for new support features (#7624)
Although #7249 added the new support features to `check_for_25_1_data()`, the new fields were still written to the mission file in all formats except retail.  Use a version check when writing the fields too.
2026-07-20 16:51:46 -04:00
Goober5000 e8d9b5f77c Merge pull request #7432 from Goober5000/claude/CJumpNode_vector
refactor CJumpNode to use SCP_vector
2026-07-19 23:58:52 -04:00
Goober5000 4b28f92d42 Merge pull request #7622 from Goober5000/fix/port_to_fred
port several QtFRED fixes to FRED
2026-07-19 22:41:23 -04:00
Goober5000 b82fa2a996 refactor CJumpNode to use SCP_vector
Jump nodes previously used SCP_list; refactor to use SCP_vector.  Also improve resource handling and life cycle management.

Follow-up to #7431.
2026-07-19 22:25:33 -04:00
Mike Nelson 23efd03d1c save shine effect (#7620) 2026-07-19 21:50:49 -04:00
Goober5000 69c6ce3b5f Merge pull request #7617 from Goober5000/fix/texture_upload
Exempt user bitmaps from texture detail culling
2026-07-19 01:47:28 -04:00
Goober5000andClaude Fable 5 914c8b6f39 Handle props in the Object Orientation editor
The object list in orient_editor::OnInitDialog() predates props, so
every prop in the mission fired the "Unknown object type" warning when
the dialog opened, and props could not be selected as point-to targets.
Add them to the list like qtFRED does since #7402.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-19 00:19:57 -04:00
Goober5000andClaude Fable 5 2d5eb3f9b6 Render team colors on ships in the FRED viewport
render_one_model() never passed the ship's team color to the model
renderer, so ships with an assigned team color displayed with their
class defaults.  Set it the same way qtFRED does since #7403.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-19 00:19:57 -04:00
Goober5000andClaude Fable 5 b80895c75e Don't wipe a ship's team color when the Initial Status combo is disabled
The team color combo box is only populated and enabled in single-edit
mode for a ship class that uses team colors.  In multi-edit (or for a
class without team colors) the combo stays disabled, and OnOK() fell
into the unconditional else branch that reset the current ship's
team_name to none.  Only apply the combo selection when the combo is
actually enabled, matching the qtFRED fix in #7403.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-19 00:19:57 -04:00
Goober5000andClaude Fable 5 5d66e4ce61 Don't apply reinforcement or destroy-before-mission to player starts
The ship flags dialog disables these checkboxes only when the selection
consists solely of player starts.  In a mixed selection (player start plus
regular ship), OnOK() ran update_ship() on the marked player starts too,
and set_reinforcement() would emplace a reinforcement entry for the player
before the ship_name_lookup() (which skips player starts) failed, leaving
a bogus Reinforcements entry that persisted into the saved mission, plus
an Int3() in debug builds.  Skip both flags for OBJ_START, matching the
qtFRED fix in #7402.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-19 00:16:49 -04:00
Goober5000andClaude Fable 5 35312d379c Check texture bind results in the volumetric nebula pass
gr_opengl_tcache_set() leaves the previous binding active when it
fails, so the glTexParameteri calls that follow would modify the filter
state of an unrelated texture.  Only set the parameter when the volume
texture actually bound.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-19 00:12:21 -04:00
Goober5000andClaude Fable 5 61a217c462 Exempt user bitmaps from texture detail culling
At texture detail below maximum, opengl_create_texture() culls texture
dimensions on upload, but user bitmaps are streaming surfaces
(streamed animations, video) whose frames are updated through
gr_update_texture() with the bitmap's own dimensions.  Once culled, the
glTexSubImage3D update exceeds the texture's actual size, so OpenGL
rejects it with GL_INVALID_VALUE and the animation silently freezes on
its first, downsampled frame.  Culling them also saves nothing, since
full-size frame data is streamed every frame regardless.

Also assert in gr_opengl_update_texture() that the update dimensions
match the texture, so any future mismatch of this kind is caught in
debug builds instead of failing silently.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-19 00:12:21 -04:00
Goober5000 831f3a4c92 Merge pull request #7539 from Goober5000/feature/reorder_messages_events
Add event and message reordering to the FRED event editor
2026-07-18 23:59:13 -04:00
wookieejedi 47726f21d0 Fix AI Bug with Strafing (#7619)
* Fix AI Bug with Strafing

Bug:
When a fighter or bomber enters strafe mode attacking a large ship, that large ship may die or depart, which results in the fighter/bomber AI slowing down to 0 and then not moving until another order is issued. This is most apparent in mods that force strafe to be used more via the ai profile flags (such as FotG).

Cause:
This bug is caused by the following. A fighter/bomber whose strafe target is destroyed drops to AIM_NONE (see ai_execute_behavior) with its active_goal still set, so a standing order like attack-any is never re-processed and the ship slows to zero and remains still until another order is issued.

Fix:
If we're chasing or strafing against large ship but have lost our target, we need to clear the active goal to ensure ai_process_mission_orders() re-evaluates our orders next frame. Of course gate this behind a flag.

Tested and update fixes the issue. We are very much hoping this can be a part of a point-release since it is substantially needed for FotG missions and we want to release in the next month or so. Happy to answer any questions or discuss, too!

* target 26.2
2026-07-18 22:44:34 -04:00
Mike Nelson 3c63e756e7 Qtfred event/campaign editor enhancements (#7613)
* add move to top and move to bottom

* enable or disable log states based on selection

* add move to top and move to bottom to campaigns
2026-07-18 16:16:43 +01:00
Mike Nelson e182280d88 Qtfred minor fixes and tweaks (#7615)
* Fix window leaks

* Fix chain delay not saving

* add last saved to the status bar
2026-07-18 16:16:09 +01:00
Goober5000andClaude Fable 5 6f4825c45e Fix heap overrun in texture readback for the model transparency scan (#7616)
* Fix heap overrun in texture readback for the model transparency scan

gr_opengl_get_bitmap_from_texture() sized its readback buffer from the
texture cache slot dimensions and the bmpman frame count, but
glGetTexImage() returns whatever the bound GL texture actually contains.
When texture detail below maximum culls textures on upload, the two can
disagree, and the readback overruns the buffer (observed as heap
corruption when loading Zeus.pof with Texture Detail: Minimum).  Size
the readback from the dimensions and layer count OpenGL itself reports
for the texture, and pad the staging buffer to 4x4 block granularity in
case a driver writes tiny compressed mips back in whole blocks.

The readback now also allocates the returned buffer and reports the
dimensions of the data it returns, because the second half of the bug
was on the consumer side: bitmap_lookup indexed the readback data by
the bitmap's own dimensions, so with any culling in effect the
transparency scan read mostly uninitialized memory.  bitmap_lookup (and
its cache) now carries the actual readback dimensions, and it skips the
texture cleanly if the readback fails.

Also pairs the cache's vm_malloc allocations with vm_free instead of
free, and no longer ignores a failed tcache_set before reading back.

Co-Authored-By: Claude Fable 5 <[email protected]>

* Harden the transparency-scan texture readback

Return fully opaque from bitmap_lookup::get_channel_alpha() when the
bitmap has no alpha channel, since indexing channel 3 of a 3-channel
buffer reads out of bounds (currently unreachable because the only
caller checks bm_has_alpha_channel() first, but no longer a trap).

Per review, also assert at maximum texture detail, where no culling
occurs on upload, that the readback dimensions OpenGL reports match the
texture cache slot, so any future slot mismatch is caught immediately
in debug builds instead of surfacing as corruption.

Co-Authored-By: Claude Fable 5 <[email protected]>

* Fix inverted dimension check in opengl_export_render_target

The validation rejected the export only if both the width and the
height mismatched the texture cache slot, so a texture wrong in one
dimension passed through into opengl_get_texture, which trusts the
caller-supplied dimensions for its mipmap loop.

Co-Authored-By: Claude Fable 5 <[email protected]>

---------

Co-authored-by: Claude Fable 5 <[email protected]>
2026-07-18 09:50:46 +02:00
wookieejedi 86a4af4fc3 Fix fighter/bomber AI remaining stationary if engines repaired (#7609)
* draft both options

* cleanup

* authors

* whitespace
2026-07-17 20:42:14 -04:00
BMagnu 856e676e79 Add render path to dispatch Particles as Decals (#7507)
* Add path to dispatch particles as decals

* Testing and bugfixing

* Address feedback

* Fix Typo

* Fix obj attachment
2026-07-17 13:38:45 +09:00
Mike Nelson 446e8942ca fix volumetric related crash (#7610) 2026-07-15 18:53:00 -04:00
Fabian Woltermann ba002c5797 Merge pull request #7611 from MjnMixael/qtfred-theme-follow-system
QtFRED follow system theme mode
2026-07-15 23:22:55 +02:00
Mike Nelson 0b6647ff52 follow system theme mode 2026-07-15 15:22:05 -05:00
BMagnu fcaa9dd998 Allow trail particles to be parented to their spawner (#7528)
* Adapt particle at runtime

* Refactor EffectHosts

* fix issues

* cleanup

* Remove live transitive particles

* paticle hosts now do local correctly

* Fix effecthostparticle local mode

* again

* Set particle host orientation

* I don't like mixed local and global vectors

* agaiiin

* Fix validity check

* parent foo

* Fix bugs, hopefully finally

* refactor

* Flip new table option for intuitiveness

* Fix support for GCC9 due to issues with LWG3052

* Address feedback
2026-07-15 15:10:15 +09:00
The Force 7a69e3ec2f Show Table based texture replacements when adding ship in FRED (#7607)
* Load table Based texture replacements on ship create

* do FRED as well
2026-07-14 20:07:20 -04:00
Goober5000andClaude Fable 5 9bebf2592f give the "Set as Player Ship" button exclusive semantics in both editors (#7605)
In single-player missions, the "Set as Player Ship" button is supposed to make
the selected ship the one and only player start.  FRED did this correctly, but
qtFRED routed the button through the same setPlayer() toggle used by the
multiplayer "Player Ship" checkbox, which only promoted the selected ship and
never demoted the previous player start.  Since the checkbox is disabled in
single player, there was no way to demote the old start, so single-player
missions could accumulate multiple player starts.

Consolidate the exclusive logic into a shared helper,
set_single_player_start() in missioneditor/common.cpp, and call it from both
FRED's OnSetAsPlayerShip() and a new qtFRED model method,
makeSolePlayerStart(), which the button now uses.  This also avoids
setPlayer()'s early return on stale dialog state, which could cause the button
to silently do nothing.

Follow-up to #7600.

Co-authored-by: Claude Fable 5 <[email protected]>
2026-07-14 20:05:13 -04:00
John Fernandez 57be2eb333 Merge pull request #7603 from JohnAFernandez/Add-double-click-to-events
Allow double click to initiate search for sexp view items
2026-07-14 13:38:50 -04:00
Goober5000andClaude Fable 5 da24cdae38 store glowpoint override indices instead of pointers
glowpoint_bank_override_map stored raw pointers into the growable global glowpoint_bank_overrides vector.  Store indices instead, which survive vector reallocation.  The readers also lose a redundant second map lookup (they previously called find() and then operator[] on the same key), and prop_info's parallel map gets the same conversion.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 13:23:37 -04:00
Goober5000andClaude Fable 5 473630ed85 make ship_info's subsystems a unique_ptr and default the moves
The hand-written ~350-line memberwise move existed only to manage the
one raw owning pointer, and silently left any member it didn't mention
uninitialized in the destination.  With subsystems as a unique_ptr, the
defaulted moves and destructor are correct by composition.  Copies
become explicitly deleted (use clone()).  n_subsystems stays a plain
int because it is passed to varargs functions.  The moves are not
declared noexcept because libstdc++ computes a potentially-throwing
implicit specification; NOLINT suppresses clang-tidy accordingly.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 13:23:37 -04:00
Fabian WoltermannandClaude Fable 5 583192b9f8 Add intermediate framebuffer for gamma correction to prevent flickering (#7589)
* Add intermediate framebuffer for gamma correction to prevent flickering

* Fix frozen SCPUI briefing screen by resetting color mask before gamma pass

A masked draw pass inside the briefing map render leaves the GL color
mask fully disabled, and the Rocket UI material path never restores it.
Before cc63bfa7 this didn't matter: the frame was presented with a pure
glBlitFramebuffer, which ignores the color mask. The gamma-by-shader
change turned presentation into a regular draw, which respects the
mask, so every flip wrote nothing and the screen froze on stale
swapchain content while the engine kept rendering correctly underneath.
Hovering a briefing icon appended draws whose materials re-enabled
color writes, which is why the screen only updated while hovering.

Neutralize color mask and stencil state before the gamma draw, matching
the blend and depth-buffer neutralization already done there.

Fixes #7590

Co-Authored-By: Claude Fable 5 <[email protected]>

* Skip the gamma-correction pass when gamma is 1.0

The gamma shader is an identity transform at gamma 1.0, so present
Back_framebuffer directly via glBlitFramebuffer in that case (the same
present path used before the gamma shader was introduced), saving a
fullscreen draw per frame. The shader pass through GammaBlit_framebuffer
only runs when the effective gamma actually differs from 1.0.

Co-Authored-By: Claude Fable 5 <[email protected]>

* Reuse Scene_ldr_texture as the gamma-correction pass target

Scene_ldr_texture is dead scratch by flip time: all of its consumers
(tonemap, FXAA/SMAA, the final post-process pass) write before reading
within gr_opengl_post_process_end(), which completes before the flip,
and the gamma pass fully overwrites it before blitting it to the
window. Attaching it to GammaBlit_framebuffer instead of allocating a
dedicated texture saves a screen-sized RGBA8 allocation (~8 MB at
1080p, ~33 MB at 4K).

The dedicated allocation remains as a fallback for the case where the
scene textures were clamped to GL_max_renderbuffer_size and don't cover
the screen.

Co-Authored-By: Claude Fable 5 <[email protected]>

---------

Co-authored-by: Claude Fable 5 <[email protected]>
2026-07-14 07:28:41 +00:00
John Fernandez 9adfa66bba Allow double click to initiate search
This allows double click to initiate search when a non-parent item is double clicked.  Double clicking a parent will still expand/contract an item.  Spacebar will also still initiate edit.
2026-07-13 20:20:43 -04:00
Goober5000andClaude Fable 5 e51db2ad49 fix builtin_message's defaulted moves and copy-assignment
The defaulted moves copied the conditionally-owned name and its
ownership flag without clearing the source, so reallocation of
Builtin_messages (mods append custom types via #Message Types) freed
every strdup'd name out from under the surviving elements.  Hand-write
the moves to reset the source and free the destination's old name.
Also fix copy-assignment, which leaked the old name and was unsafe
under self-assignment.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-13 14:45:52 -04:00
Goober5000andClaude Fable 5 009529b235 guard against empty weapon banks in external model render paths
ship_render_weapon_models(), HudGaugeHardpoints::render(), and
ship_stop_fire_primary_bank() indexed Weapon_info[] with the bank's
weapon index without checking for an empty bank (-1).  Banks within
num_*_banks are normally populated, but other code that iterates banks
treats them as possibly empty and guards accordingly (e.g.
ship_do_cargo_hidden, ship_page_in), so these paths now do too.  In
the hardpoints gauge an empty primary bank still draws its firing
point circles.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-13 13:19:23 -04:00
Goober5000andClaude Fable 5 6578217008 revive $Weapon Model Draw Distance; fix attached weapon model LOD depth
The draw-distance cull for attached external weapon models checked
MR_ATTACHED_MODEL, but nothing has set that flag since commit
1ef7688fa1, so $Weapon Model Draw Distance: has been dead for years.
The check also needed the ship resolved from the render params' object
number, which is deliberately not set while weapon models render
(setting it would make model_render_queue clobber the explicitly
passed gatling model instance), and it compared against a depth
computed as if the weapon model's ship-local position were in world
space.

Carry the distance in model_render_params instead
(set_attached_model_draw_distance, negative = not an attached model),
set by ship_render_weapon_models from the ship's tabled value.  When
set, model_render_queue measures depth from the eye position expressed
in the parent's frame (model_draw_list::get_view_position), which is
correct under the pushed parent transform, culls against the tabled
distance, and uses that same corrected depth for detail level
selection -- previously attached weapon models picked their LOD from a
garbage depth (roughly the eye's distance from the world origin).

MR_ATTACHED_MODEL is removed; nothing else referenced it. The unused
flags are also removed and the remaining flags are consolidated.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-13 13:19:23 -04:00
Goober5000andClaude Fable 5 90ea116d0f recreate external weapon model instance when a bank's weapon changes
The model instance used to spin Gun_rotation submodels (gatling
barrels) was created lazily on first render, guarded by a
once-per-bank bool that nothing ever reset.  If a primary bank's
weapon changed mid-mission (set-primary-weapon SEXP, scripting, rearm
precedence swap, multiplayer sync), the old weapon's instance leaked
until the ship was deleted and was silently discarded at render time
by the model/instance mismatch guard, while the new weapon never got
an instance, so its barrels never spun.

Replace the bool with a record of which weapon the instance was
created for, and move the logic into
ship_get_external_weapon_model_instance(), which recreates the
instance whenever the bank's weapon differs.  This self-heals after
every swap path without needing hooks in each of them.  The HUD
hardpoints gauge now goes through the same function instead of reading
the possibly-stale instance array directly.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-13 13:19:23 -04:00
Goober5000andClaude Fable 5 368a6a9053 consolidate external weapon model slot placement; fix HUD launcher position
The per-slot placement math for rendered external weapon models (the
angle-offset yaw plus the reload slide-back for missiles) was
duplicated four times across ship_render_weapon_models() and
HudGaugeHardpoints::render().  Extract it into
ship_get_weapon_model_slot_transform() so that future changes (such as
submodel-attached weapon banks) only need to touch one place.

This fixes a bug in the hardpoints gauge: the launcher-style secondary
branch computed the view-rotated world position but then rendered the
model at the untransformed local point, misplacing launcher models in
the rotated top/front views.

Also: hoist per-bank invariants out of the slot loops, drop the
redundant render_flags parameter of ship_render_weapon_models() (it
always equaled the flags already set on the render params), remove
long-dead commented-out code, and read the reload percentage once per
slot.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-13 13:19:23 -04:00
Goober5000andClaude Fable 5 741ed0e0f1 consolidate external weapon model firing point selection; fix two bugs
The logic for offsetting a shot by the external weapon model's firing
points was duplicated in ship_fire_primary(), ship_fire_secondary(),
and the AI's line-of-sight check.  Extract it into
ship_get_external_model_fp_offset().

This fixes two bugs that were present in all three copies:

1. The chain counter for "chain external model fps" weapons was reset
   when it reached weapon_model->n_guns (the number of gun banks)
   rather than the number of firing points in the bank being indexed.
   For the typical model with one gun bank the counter reset after
   every shot, so chaining never advanced past the first firing point;
   for a model with several gun banks the counter could index past the
   end of the first bank's point array.

2. The AI line-of-sight check advanced the chain counter even though it
   is only a query, desyncing the counter from the shots actually
   fired.  It now reads the current firing point without advancing.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-13 13:19:23 -04:00
Goober5000andClaude Fable 5 ea0e4feafa consolidate duplicated per-weapon page-in code
weapons_page_in(), weapons_page_in_cheats(), and weapon_page_in() each
duplicated the same ~60-line block for loading a weapon's models and
graphics.  Extract it into a weapon_page_in_one() helper; the cheats
variant passes load_graphics = false to preserve its behavior of
loading only model geometry.

Also fix the tautological Assertion in the default render_type case:
the old condition was always true inside that branch, so it could never
fire.  It now asserts render_type == WRT_NONE, which is the only other
legitimate value (invisible weapons).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-13 13:19:22 -04:00
wookieejedi 2e57072b57 Skip particle parsing to speed up FRED loading (#7602)
FRED takes a long time opening mods that have a lot of particle entries in the particles table such as FotG (can take over a minute or more sometimes), so given FRED never renders or pages in particles, just skip the parsing.
2026-07-13 11:57:01 +09:00
wookieejedi 1ca6c6a188 Fix shockwave damage bug (#7601)
* Fix two shockwave bugs

Bug:
Ship and SEXP created shockwaves with 0 speed last forever. A shockwave speed <= 0 is an "instant" shockwave, but the speed is used as a denominator for total_time. Dividing by 0 that creates in infinite shockwave (frozen at its initial radius of 1.0).

Fix:
Thus, add a safeguard to protect against the infinite duration. Note, ship-death and sexp shockwave run shockwave straight to shockwave_create(), whereas weapon shockwaves run weapon_do_area_effect() if <= 0 speed.

Bug:
It is possible the shockwave may have lifetime shorter than one frame (e.g. high speed, small radius), which by default results in shockwave applying no damage.

Fix:
Provide optional fix to ensure shockwave is not killed until after this frame's damage pass has run.

Tested and work as expected.

* just the 1expire fix

* comment cleanup
2026-07-12 22:56:37 -04:00
Goober5000 071677cff5 Merge pull request #7578 from Goober5000/cleanup/list_range
clean up and extend the linked list iterator adapter
2026-07-12 21:36:35 -04:00
Goober5000 dab690eab8 Merge pull request #7600 from Goober5000/fix/ship_demotion
fix a bug with player ships being demoted to common ships
2026-07-12 20:15:54 -04:00
Goober5000andClaude Opus 4.8 7d5bab25e3 fix a bug with player ships being demoted to common ships
If a player ship is changed to a regular ship, be sure that
Player_start_shipnum does not still point to the old ship.

Consolidate the several near-duplicate copies of the "repoint
Player_start_shipnum to a valid player start" logic, in both FRED and
qtFRED, into a single shared helper, ensure_valid_player_start_shipnum()
in missioneditor/common.cpp.

Also add some guards in situations where Player_start_shipnum might
be -1.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-12 19:32:39 -04:00
Goober5000 5c17ab6352 Merge pull request #7599 from Goober5000/fix/misc
miscellaneous small fixes
2026-07-11 22:22:07 -04:00
Goober5000 b66582e6f2 Merge pull request #7597 from Goober5000/cleanup/sexp_tree_const
const-qualify read-only sexp tree methods
2026-07-11 21:46:11 -04:00
Goober5000 4b761447e3 Merge pull request #7598 from Goober5000/fix/prop_selection
fix prop selection using model geometry instead of bounding sphere in FRED
2026-07-11 21:45:30 -04:00
Goober5000 b4a321d456 Merge pull request #7591 from Goober5000/fix/icon_propagation
fix several bugs related to icon propagation, mostly in FRED
2026-07-11 21:43:44 -04:00
Goober5000 25139fded8 Off is 1286 2026-07-11 21:02:36 -04:00
Goober5000 50f62b3ccf fix an Assertion and a latent campaign option 2026-07-11 21:02:36 -04:00
Goober5000 39c806771e when disbanding a wing, delete its reinforcement entry
Also rename `remove_wing` to `disband_wing` for clarity.
2026-07-11 21:02:36 -04:00
Goober5000 b9e43f2907 when sorting by fuzzy cost, use a stable sort 2026-07-11 21:02:36 -04:00
Goober5000andClaude Sonnet 4.6 dd83a20984 fix prop selection using model geometry instead of bounding sphere in FRED
In both FRED2 and QtFRED, object_check_collision() only dispatched to
model_collide() for OBJ_SHIP and OBJ_START, leaving OBJ_PROP to fall
through to fvi_ray_sphere().  For flat, wide props the bounding sphere is
much larger than the visible geometry, making it impossible to
click-deselect without clicking far outside the model bounds.

Add an OBJ_PROP branch that calls model_collide() with the prop's model
and instance numbers, consistent with how OBJ_SHIP and OBJ_START are
handled, and move the mc.model_instance_num default initialisation above
the dispatch so the prop branch can override it with the real instance
number.  In FRED2, also add an early-out guard for unoccupied prop slots
(QtFRED already gates on Show_props).

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-07-11 20:57:29 -04:00
Goober5000andClaude Fable 5 fa9c20d6d7 clean up and extend the linked list iterator adapter
Clean up volition_linked_list / list_range in linklist.h:

* Merge the copy-pasted iterator and const_iterator classes into a
  single basic_iterator template.

* Make operator* and operator-> return the element pointer by value
  and const-qualify them.  Previously operator* returned a mutable
  reference to the iterator's own internal pointer, so writing through
  a dereference silently reseated the iterator rather than modifying
  the list, and a stored reference could dangle.

* Add the five iterator traits typedefs plus a default constructor so
  the iterators work with non-modifying <algorithm> functions such as
  std::find_if, std::distance, and std::reverse_iterator.

* Add the implicit iterator -> const_iterator conversion and allow
  iterator/const_iterator comparisons in either order, as the
  standard's container requirements specify.

* Deduce list_range's node type from the head's next pointer rather
  than from the head pointer itself (preserving the head's constness),
  so that a list headed by a type derived from the node type -- such
  as the self-relinking ship subsystem sentinel -- iterates as its
  node type.

* Make the constructors explicit, remove a stale <utility> include,
  and document that elements must not be removed from the list (nor
  freed) inside a range-for loop, since the iterator reads the current
  element's next pointer when it advances.

No behavior change at any existing call site; all existing loop
binding patterns compile unchanged.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-11 20:28:45 -04:00
Goober5000andClaude Fable 5 35e1686c2f const-qualify read-only sexp tree methods
Add const to query methods that the sexp_tree refactor left non-const:

- FRED2/qtFRED views: node, type, handle, and root lookups; label-edit
  eligibility; annotation key resolution and comment getters; qtFRED's
  validOperatorsForNode
- SexpTreeOPF: all instance get_listing_opf_* functions plus the
  dispatcher and get_default_value (the class holds only a reference
  to its model, so these are read-only by construction)
- SexpTreeModel: compute_context_menu_state and the two remaining
  ctx_* helpers, which write only their state out-parameter
- ISexpTreeUI: the two read-only navigation callbacks
  (ui_get_child_item, ui_has_children) in the interface and both
  frontend implementations

Also convert the four listing functions that use no editor state at all
(species, language, functional_when_eval_type, wing_formation) to
static, removing the NOLINT suppressions that had silenced clang-tidy's
convert-to-static warning since before the refactor.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-11 17:11:05 -04:00
Goober5000andClaude Opus 4.8 63b26ade93 Add event and message reordering to the FRED event editor
Add "Move to top / up / down / to bottom" controls -- vertical icon-button
strips beside both the event tree and the message list -- so events and
messages can be reordered in place.

Also add an "Insert Msg" button that inserts a message at the current
selection, mirroring the existing "Insert Event".

Tweak the message-area layout, drop the "Messages" label, and edit the
AFX_DIALOG_LAYOUT table for proper behavior when the dialog is resized.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-11 15:46:43 -04:00
John Fernandez 56dddef32a Merge pull request #7585 from MjnMixael/qtfred-sexp-tree-icons
Qtfred better sexp tree icons
2026-07-11 12:27:50 -04:00
John Fernandez add4f72179 Merge pull request #7581 from MjnMixael/qtfred-documentation-updates
Qtfred documentation updates
2026-07-11 12:26:51 -04:00
John Fernandez 1612233c01 Merge pull request #7594 from TheForce172/QTFred/missed-doc
Adds documentation for #7573
2026-07-11 12:25:10 -04:00
Goober5000andClaude Fable 5 50de61cf74 prevent zombie annotations and restore help box root-label comments
Root-label annotations (event-name comments/colors) that lose their
node — e.g. because their event was deleted — could survive
indefinitely and surface on the wrong items:

- saveToGlobal's root-key branch kept an unresolvable annotation in
  Event_annotations with an empty path instead of marking it for
  pruning, so once a zombie was persisted in a mission file it
  survived every save/load cycle.
- getByKey(-1) matched annotations whose node_index had been reset to
  the unresolved sentinel, so such a zombie's comment could display on
  any item that has no annotation key of its own.

Guard findByKey against the -1 sentinel, and prune any annotation
whose rebuilt path is empty at save time.  (The complementary
in-session fixes — removing a root-label annotation when its event is
deleted and re-keying it when insert-operator changes the formula
node — are handled in the shared model and actions by 'Keep event
annotations attached to their node across tree mutations'.)

Also restore root-label comments in the FRED2 help box: update_help
resolved comments by model node index, which is -1 for labels.  Route
it through a get_item_comment virtual that the event editor's tree
overrides with its annotation key lookup, matching pre-refactor
behavior.  (qtFRED renders comments per-item via NoteRole, which
already covers labels; its help box does not show comments by design.)

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-11 01:17:31 -04:00
Goober5000andClaude Fable 5 a7cb9bbf32 Restore pre-refactor on-disk format for event annotation paths
Annotation +Path: values written by pre-refactor FRED were built over
the UI tree, where each event's root item is the event-name label and
every element after the event index is a child position at successive
levels below it: [event] is the label, [event, 0] the top operator
(the label's only child), [event, 0, k...] its descendants.  The
refactored SexpAnnotationModel built paths over tree_nodes[], which
represents the label's single child directly as the formula node, so
paths came out one level shallower: existing missions' annotations
resolved one level too deep (and were re-saved wrong), and a
top-operator annotation produced the length-1 path that decodes as a
root-label key, silently migrating onto the event label.

Emit and consume the label-level child position in
buildPath/resolveFromPath to preserve the on-disk contract.  Child 0
resolves to the formula node and any other index fails resolution,
matching the old traverse_path behavior of walking off the label's
sibling list.  Annotations on the event label itself are unaffected:
they remain the length-1 path handled by the root-key branch.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-11 01:17:31 -04:00
Goober5000andClaude Fable 5 6825100f42 Keep event annotations attached to their node across tree mutations
Post-refactor successor of fix/annotations, rebuilt for the shared sexp
tree model introduced by the sexp_tree refactor (#7301).  The refactor
re-keyed annotations from UI item handles to tree_nodes[] indices, which
fixed most of the original decoupling by construction and already prunes
regular-node annotations in free_node2().  Two gaps remained, plus one
qtFRED regression:

- free_node2() only removed annotations keyed by node index, never a
  root-label annotation keyed rootKey(formula).  Deleting an event left
  that annotation live, so a later event whose formula reused the slot
  inherited the deleted event's annotation.  Remove the root key too;
  since the global Event_annotations are only rewritten at save, the
  removal still survives a Cancel.

- Inserting an operator above a labeled root changes the event's formula
  node, orphaning the root-label annotation keyed to the old index.
  Re-key it in insert_operator() so it follows the event.  (This is the
  only such flow: replace_operator() reuses the node in place.)

- qtFRED reorderByRootFormulaOrder() reloaded annotations from the
  global state, discarding any annotation edits made since the dialog
  was opened.  A root reorder changes neither node indices nor formulas,
  so drop the reload.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-11 01:17:31 -04:00
Goober5000 00c8b0f63b Merge pull request #7583 from Goober5000/fix/lua_secondary_bank_fields
fix Lua weapon bank setter writing primary fields for secondary banks
2026-07-10 23:53:17 -04:00
Goober5000 11debd5c0c Merge pull request #7596 from Goober5000/fix/various_sexp_tree_issues
Fix various sexp tree issues
2026-07-10 23:50:50 -04:00
Taylor Richards 309419b518 fix compile error in shadow shader (#7592)
A change introduced by #7529 caused a shader compile error on Mac due to an
attempted swizzle of texture(sampler2DArrayShadow,...), which returns a single
float value rather than a vector.
2026-07-11 02:46:18 +02:00
The Force 539eacf7d7 Adds documentation for #7573 2026-07-10 21:21:58 +01:00
Goober5000 3a1e802965 nullptr and SIZE_T_ARG 2026-07-10 13:42:41 -04:00
Goober5000andClaude Fable 5 8ab9f907b5 remove vestigial text_buf parameter from get_default_value()
The buffer round-trip was left over from the pre-refactor char-array
days: *item = *list already copies the text, and no caller reads the
buffer after the call. Removing it also removes the unbounded strcpy
into the callers' 256-byte stack buffers.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-10 13:42:41 -04:00
Goober5000andClaude Fable 5 c6e2603e19 fix move_branch() reparenting to root when parent index is 0
The only caller, SexpTreeActions::insert_operator(), passes a freshly
allocated tree_nodes index as the parent, and allocate_node() reuses
freed slots, so 0 is a valid parent index. The old 'if (parent)' test
(and qtFRED's 'parent > 0' copy of it) treated node 0 as attach-at-
tree-root, visually reparenting the wrapped branch to the root while
the model data placed it under node 0. Since the assertion already
requires parent >= 0, attach to the parent's handle unconditionally.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-10 13:42:41 -04:00
Goober5000andClaude Fable 5 1aeb6a6957 delete copy/move on the sexp tree model, OPF, and actions classes
SexpTreeOPF and SexpTreeActions hold references to their owning model
and UI, and SexpTreeModel owns an OPF member back-referencing itself,
so a copy would compile (copy construction was not implicitly deleted,
only copy assignment) but leave the copy's _opf silently bound to the
original model.  Nothing copies or moves these classes today; delete
the operations so nothing can.

The other sexp tree types were checked and are correct as-is:
SexpContextMenuState already deletes copy and implements move via
swap; SexpAnnotationModel and the item structs are deliberate value
types; sexp_list_item's shallow copy with manual destroy() is the
inherited idiom its callers rely on; the views are non-copyable via
their MFC/Qt base classes.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-10 13:42:41 -04:00
Taylor Richards 495e2965ce fix Qt6 fallback lib usage message (#7593)
When Qt6 falls back to using prebuilt libs force the option to enabled to
avoid an erroneous, but otherwise harmless, system lib usage message during
reconfigure.
2026-07-10 13:36:41 -04:00
Mike Nelson 0c48f7bd9e Update DESIGN_GUIDE.md 2026-07-10 08:49:53 -05:00
Mike Nelson 5ea93a0eb6 Update README.md 2026-07-10 08:43:34 -05:00
Mike Nelson 180af80b29 clean up preferences and add tooltips (#7582) 2026-07-10 08:13:59 -04:00
Mike Nelson e02af94e80 fix audio handle guards (#7575) 2026-07-10 08:13:34 -04:00
Goober5000andClaude Fable 5 b82a10a7ae Install a fallback editor interface in FRED2 sexp_tree_view
Only the event, goals, cutscenes, and campaign dialogs install a
SexpTreeEditorInterface; the ship, wing, message, briefing, and
debriefing editors left _model._interface null, and the shared OPF
code dereferences it unconditionally (getMessages, getMissionNames,
getMissionGoals, getMissionEvents).  Right-clicking e.g. the
event-name argument of is-event-true-delay in a ship arrival cue
crashed FRED.

Install a default-constructed interface at construction, mirroring
qtFRED initializeEditor.  The base implementations match the old
m_mode == 0 global fallbacks and the empty flag set keeps all
flag-gated paths behaving as before; dialogs that provide editor
context overwrite the fallback as they always did.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-10 00:11:24 -04:00
John Fernandez 98ddaea0b1 Merge pull request #7573 from TheForce172/QTFred/missing-warp-flag
QTFred: Add missing warp flag to ship editor
2026-07-09 22:12:28 -04:00
Goober5000andClaude Fable 5 978e3bea93 add briefing icon ID collision validation to qtFRED
FRED2 rejects an icon ID edit that would collide with another icon,
warning and reverting the field, but qtFRED's setIconId() applied the
value unconditionally. Two icons could be given the same ID in a stage
(making find_icon and forward propagation ambiguous), or a global rename
could merge two distinct icon chains by reusing a downstream ID.

setIconId() now mirrors FRED2:

- An ID is a single-icon concept, so it operates on the current icon
  rather than the whole selection (applying one ID to several selected
  icons would itself create a same-stage collision).

- Rejects an ID already used by another icon in the current stage.

- When propagating forward (Change Locally off), rejects an ID already
  used in a later stage, since the rename would merge two icons.

- Otherwise applies the change and renames the icon's forward copies by
  matching the old ID, respecting Change Locally.

It returns false when a change is rejected; the dialog resets the spin
box to the model's value. The spin box is switched to non-keyboard-
tracking so validation runs when the edit is committed (focus-out/Enter)
rather than on every keystroke, avoiding a modal warning on a transient
value mid-typing.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-09 00:12:35 -04:00
Goober5000andClaude Fable 5 48db13d883 fix minor briefing icon editor issues in FRED
- OnMakeIcon() left closeup_label uninitialized. The icon arrays are
  only cleared at mission reset, and delete_icon shifts structs down
  leaving stale copies behind, so a newly created icon could inherit a
  deleted icon's closeup label and then propagate/save it. Clear it,
  matching qtFRED's makeIcon.

- Case-only label edits (e.g. "alpha" to "Alpha") did not propagate
  forward: the change detection used stricmp, so a case-only change was
  treated as no change and applied only locally. Use strcmp for both
  label and closeup_label. qtFRED already compares case-sensitively.

- OnPropagateIcons() propagated a snapshot that could predate the text
  typed into the icon fields, since it never committed pending dialog
  edits first. Call update_data(1) before propagating.

- copy_stage() copied every stage field except draw_grid, so inserting,
  deleting, or reordering stages reset the grid-disabled flag. Copy it
  through (and default it to true for freshly created stages). qtFRED's
  copyStageData already preserves it.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-08 23:56:53 -04:00
Goober5000andClaude Fable 5 c6654dc3f7 prevent invalid briefing icon IDs in FRED and qtFRED
- Negative icon IDs are now rejected. FRED2 assigned the ID edit box
  value unconditionally even though it only validated collisions for
  non-negative values, so a negative ID could slip through and silently
  break find_icon()/propagation. It now reverts to the previous ID (with
  a warning, but only when the value was actually changed, to avoid a
  message-box loop on refresh). qtFRED gets a matching defensive guard in
  setIconId().

- OnMakeIcon() now skips IDs that are already in use. It previously used
  Cur_brief_id++, but Cur_brief_id is only advanced at mission parse, so
  a manual ID edit that raised an icon ID above it would cause the next
  new icon to silently collide. The new get_unused_briefing_icon_id()
  helper advances Cur_brief_id past any value already taken by an icon in
  the current briefing. qtFRED's makeIcon() already computes maxId + 1,
  which inherently skips existing IDs.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-08 23:43:01 -04:00
Goober5000andClaude Opus 4.8 c6c403ea54 fix briefing icon propagation bugs in FRED
- get_next_icon() used `stage_loop > num_stages` as its terminating
  check, allowing it to read one stage past the live range. On a full
  MAX_BRIEF_STAGES briefing this is a genuine out-of-bounds array
  access; below capacity it reads a non-live stage. Changed to `>=`.

- Icon scale_factor and the editable flag bits (highlight, mirror,
  wing, cargo) were always written locally, ignoring the "Change
  Locally" checkbox and never propagating forward like the other
  attributes. Route them through the same reset_icon_loop/get_next_icon
  propagation so they match id/label/type/team/ship_class (and qtFRED).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-08 23:18:55 -04:00
Goober5000andClaude Opus 4.8 bed228d264 avoid undefined behavior from UNREACHABLE on reachable code paths (#7555)
* 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]>
2026-07-09 11:20:25 +09:00
wookieejedi c05c06db95 Fix CockpitFOV and CockpitFOVToggle missing in default table (#7547)
* Update camera.cpp

* order cleanup
2026-07-09 11:20:16 +09:00
BMagnu b26c8e42f9 Double Tree Build for Windows Nightlies / Release to avoid issues with /MT /MD linkage (#7586) 2026-07-09 02:17:57 +00:00
John Fernandez 91dc178036 Merge pull request #7584 from MjnMixael/qtfred-ui-adjustments
Qtfred adjust ui based on feedback
2026-07-08 22:13:29 -04:00
John Fernandez 99dee5ab25 Merge pull request #7587 from MjnMixael/qtfred-sexp-tree-controls
Qtfred adjust tree controls
2026-07-08 22:12:50 -04:00
Goober5000 d7e1153c61 Merge pull request #7580 from Goober5000/remove_pending_messages
remove FRED's unused pending message mechanism
2026-07-08 21:28:24 -04:00
Goober5000 f143990fb2 Merge pull request #7579 from Goober5000/fix/spinning_orion
fix big ship waypoint completion (the "spinning Orion" bug)
2026-07-08 21:18:08 -04:00
Mike Nelson d94a895a1f adjust controls based on feedback 2026-07-08 19:04:38 -05:00
Mike Nelson fc6ce91106 clang 2026-07-08 18:52:18 -05:00
Mike Nelson e65d0d354b Better sexp tree icons for qtfred 2026-07-08 16:48:45 -05:00
Mike Nelson 39eab6fd92 adjust ui based on feedback 2026-07-08 12:14:22 -05:00
Goober5000andClaude Fable 5 da7e5b563b fix Lua weapon bank setter writing primary fields for secondary banks
The SWH_SECONDARY case of the ship_bank setter was copy-pasted from the
primary case and still assigned primary_bank_* fields; it now assigns
the secondary_bank_* fields.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-08 12:18:18 -04:00
Mike Nelson a8c68fb605 cleanup 2026-07-08 08:51:34 -05:00
Mike Nelson f578063833 add missing help text 2026-07-08 08:50:19 -05:00
Mike Nelson b1a6cc0866 update readme and add design guide 2026-07-08 08:50:07 -05:00
Goober5000andClaude Opus 4.8 48569f271c remove FRED's unused pending message mechanism
Never called from anywhere, even dating back to retail FRED.  It also
contained a latent bug: add_pending_message was missing a break, so it
would have filled every free slot in the buffer with the same message.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-08 03:05:21 -04:00
Goober5000andClaude Fable 5 9ea9f51ce1 fix big ship waypoint completion (the "spinning Orion" bug)
Since FS1 retail, a large ship could get permanently stuck circling its
final waypoint.  Waypoint completion requires the ship center to pass
within MIN_DIST_TO_WAYPOINT_GOAL + sqrt(radius) of the point (~37m for
a 1000m-radius capital ship), and the check only runs when the ship
covers 0.1m in a single frame (a framerate-dependent minimum speed).
At any speed that passes that gate, a capital ship turning at
2PI/(3*rotation_time) has a turning circle of hundreds of meters, so
once it overshoots a misaligned final leg it can never re-aim into the
completion bubble: an endless overshoot-turnaround-miss loop.

Add ai_profiles flag "$fix big ship waypoint completion:"
(Fix_big_ship_waypoint_completion), enabled by default for mods
targeting 26.2.  With the flag set:

- big/huge ships complete a waypoint anywhere within their own radius
  of it (matching what the docking path code has always done), while
  small ships keep the retail sqrt(radius) tolerance; and
- the completion check runs every frame instead of requiring 0.1m of
  travel per frame, removing the framerate-dependent minimum speed
  (which at 240fps is 24 m/s, enough to affect fighters too).

With the flag unset, behavior is unchanged from retail.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-08 00:48:24 -04:00
Goober5000 4fc444162e Merge pull request #7519 from Goober5000/move_fixes
several fixes to move semantics
2026-07-08 00:15:01 -04:00
Goober5000 2fc4ff10c0 Merge pull request #7557 from Goober5000/fix/brief_stage_move
make brief_stage non-copyable and non-movable; provide swap() and reset()
2026-07-08 00:14:46 -04:00
Mike Nelson 0d33ac8378 fix missing scene browser menu option (#7574) 2026-07-07 22:35:00 -04:00
Mike Nelson e1ac0e61bd fix docking mem leak (#7577) 2026-07-07 22:34:41 -04:00
The Force 07bd7f9d21 Add missing warp flag to ship editor 2026-07-07 18:24:33 +01:00
Goober5000 455ac462c0 fix translation/rotation bugs (#7544)
For standard translation offsets, the wrong axis was being used for translation.  Fix that.  Also fix a string offset when parsing a custom axis.

Follow-up to #4582 and #3056.
2026-07-07 14:47:44 +02:00
Goober5000andClaude Fable 5 22be26492e fix briefing lines when deleting an icon (#7556)
When an icon is deleted, remove any lines that reference it, and fix the
indexes of lines that reference the icons shifted down to fill the gap.
Previously stale lines silently reattached to the wrong icons.  This
matches the behavior of qtFRED's deleteCurrentIcon.

Co-authored-by: Claude Fable 5 <[email protected]>
2026-07-07 14:47:18 +02:00
Goober5000andClaude Fable 5 3dcd8e655b two loadout fixes (#7558)
* 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]>
2026-07-07 14:46:53 +02:00
Goober5000andClaude Fable 5 8f69542d4f call object_moved() in the FRED and qtFRED wing Align handlers (#7559)
The Align button repositions wing members by assigning objp->pos and objp->orient directly, skipping the post-move bookkeeping that the viewport drag and orient editor paths perform.  A wing member with a docked partner (e.g. an attached support ship) would be moved to its formation slot while the partner stayed behind with broken dock geometry.

Fix by calling object_moved() for each repositioned member, which resets the Docked_already_handled flags and drags docked partners along.  No-op for undocked ships.

Verified in FRED by docking a ship to a wing member and pressing Align.

Co-authored-by: Claude Fable 5 <[email protected]>
2026-07-07 14:46:46 +02:00
BMagnu e625f70a3b Move inline static to global static (#7572) 2026-07-07 14:46:26 +02:00
Goober5000 ccbbc0f33b fix wing display names in a single wave wing (#7565)
As noticed by @Achille44, wing display names were handled properly for multi-wave wings, but were inadvertently skipped for single-wave wings.
2026-07-07 14:44:37 +02:00
BMagnu d995b7d72e Ensure props survive mission restarts (#7571) 2026-07-07 14:44:26 +02:00
Goober5000 73fae42ede fix FRED indexing of Props (#7546)
Get the Prop class from the list, in the same way that a Ship class is gotten, which accounts for the no_fred flag.  Fixes #7545.
2026-07-07 14:44:06 +02:00
Goober5000andClaude Opus 4.8 d27582bbcb Fix FRED resetting the grid position after saving a mission (#7552)
When FRED2 saves a mission, OnSaveDocument() reloads it via
load_mission(MPF_FAST_RELOAD).  That reload calls clear_mission() ->
fred_render_init() -> create_default_grid(), which rebuilds The_grid as
the default origin-centered XZ plane, discarding any adjustments made in
the Adjust Grid dialog (e.g. the X-Z Plane Y Level).  The grid is editor
display state and is not stored in the mission file, so the reload had
nothing to restore it from.

The camera (view_pos/view_orient) is already preserved across this reload;
apply the same preserve-and-restore pattern to the grid by capturing its
orientation, center, dimensions, and square size before clear_mission()
and rebuilding it with create_grid() afterward.

qtfred is unaffected because its save path does not reload the mission.

Fixes #7548

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-07 14:43:54 +02:00
Asteroth 51aa9cf7eb change version (#7570) 2026-07-07 14:28:55 +09:00
Goober5000 4d2d4944ce fix "mesage" (#7569)
The CMake misspelling is a bug; the others are cosmetic; but might as well fix all of them.

Small follow-up to #7029.
2026-07-07 01:38:44 +00:00
John Fernandez 79279b26ff Merge pull request #7029 from TheForce172/QTFred/translate
Update To QT6
2026-07-06 20:17:46 -04:00
The Force 9477ca997e One more 2026-07-06 20:05:23 +01:00
Taylor Richards b050f985af fix cache workflow (#7567)
I somehow added an older, broken version of the cache-master workflow during
the recent workflow update. This should fix the architecture bug preventing
the Mac cache from working properly, as well as increasing the max cache size
from 500 MiB to 750 MiB.
2026-07-06 14:59:41 -04:00
The Force ecddd783f9 3 Clang Fixes 2026-07-06 19:43:20 +01:00
The Force 703bf7ac4f Merge pull request #8 from notimaginative/qtfred_at_night
fix PR checks and add QtFRED to nightly builds
2026-07-06 18:56:27 +01:00
Goober5000andClaude Fable 5 a733748615 fix geometry_batcher copy semantics and add move operations
clone() allocated fresh vert/radius_list buffers without freeing the
ones already owned, so assigning over a live batcher leaked both.  It
also never copied buffer_offset, and the copy constructor called it
with no member initialization at all -- so every copy-constructed
batcher carried an uninitialized buffer_offset.  Free the existing
buffers in clone(), copy buffer_offset, and delegate the copy
constructor to the default constructor so clone() sees initialized
members (which is also what makes its new frees safe on that path).

Also add noexcept move operations, which the user-declared copy
operations had suppressed, so by-value handling can transfer the
buffers instead of deep-copying them.

None of these defects were reachable from current callers (the static
batch maps mutate entries in place); this closes the latent hazards.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-06 13:33:49 -04:00
Goober5000 3e7e707b07 store special_message in a unique_ptr, which also makes the training_message_queue struct safe for moves 2026-07-06 13:33:49 -04:00
Goober5000 5749f1294e remove CombinedVariable code, which was never finished and is not called anywhere 2026-07-06 13:33:49 -04:00
Goober5000andClaude Fable 5 f90a0b3c70 fix four more shallow-ownership hazards in by-value types
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]>
2026-07-06 13:33:49 -04:00
Goober5000andClaude Fable 5 9bf5b6acf4 harden move operations in w_bank and LuaThread
w_bank: add a move constructor.  object_copy_including_array_member
returns by value, and without a move constructor the return fell back
to the shallow copy constructor whenever NRVO was not applied (e.g.
MSVC debug builds), after which the local's destructor deleted the
freshly allocated arrays -- leaving the stored bank dangling and
double-deleting at model unload.  Also add noexcept to the move
operations, replace the destructor call in move-assignment with
explicit delete[]s (using an object after its lifetime has ended is
undefined behavior), guard against self-move, and document that the
defaulted copy constructor is intentionally shallow for
object_copy_including_array_member's copy-then-replace pattern.

LuaThread: declare the defaulted move operations noexcept.  This
exposed that LuaTable was not movable at all (its user-declared
destructor suppressed the implicit moves), so LuaThread's implicit
move spec was formally throwing -- a hard error on gcc 9, which
still requires the spec on a defaulted redeclaration to match the
implicit one.  Restore LuaTable's rule of five and declare the
LuaValue and LuaFunction copy operations noexcept (they only copy a
shared_ptr, a raw pointer, and an enum), making the noexcept claims
hold throughout the hierarchy.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-06 13:33:49 -04:00
Taylor Richards ffd6a0ac7f address standard c++ lib issues
Disable static libstdc++/libc++ by default now that we've moved to using a
base of Ubuntu 22.04. Using a static c++ library also poses a possible issue
with our prebuilt Qt6 libs since they are dynamically linked.

Disable automatic use of libc++ when static c++ library is enabled. The
options can and should be used independently.

Disable using libc++ for clang by default since it conflicts with the prebuilt
Qt6 libs as well as the system Qt6 libs on most Linux distros. An message has
been added to QtFRED config to error/warn if this is likely to be a problem.
2026-07-06 10:54:11 -04:00
Goober5000 ae4c3495e0 Merge pull request #7520 from Goober5000/fix/std_exchange
use std::exchange in move ctors/assignment operators
2026-07-06 03:11:12 -04:00
Goober5000andClaude Opus 4.6 285e971aec Remove redundant m_pos from CJumpNode; add GetRadius() accessor (#7431)
CJumpNode stored a private m_pos field that duplicated the position
already held by its Objects[] entry.  The field was set once during
construction and never updated afterward, so any subsequent movement
(e.g. FRED dragging) would leave m_pos stale while Objects[m_objnum].pos
held the authoritative value.  Every external caller already used
GetSCPObject()->pos instead; GetPosition() had zero call sites.

- Remove the m_pos field and its initialization in both constructors,
  the move constructor, and the move-assignment operator.
- Pass the constructor's position argument directly to obj_create()
  instead of routing through m_pos.
- Repoint GetPosition() at Objects[m_objnum].pos so it always returns
  the live, up-to-date position.
- Add GetRadius() to expose the cached m_radius, and use it in
  jumpnode_get_which_in() instead of the redundant model_get_radius()
  call.

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
2026-07-06 01:49:10 +02:00
Taylor Richards 8dfc27afbf fix compile error 2026-07-05 17:39:33 -04:00
Taylor Richards ceb6cd89ef fix lib fallback 2026-07-05 16:42:36 -04:00
Taylor Richards 7186c4d415 set QtFRED option before toolchain is included 2026-07-05 15:34:02 -04:00
The Force cd8ab9f8f0 Clang Fix 2026-07-05 20:29:06 +01:00
Taylor Richards 5d4577acae add prebuilt fallback if system Qt not available 2026-07-05 14:46:14 -04:00
Taylor Richards 192b4ebe55 bump prebuilt version 2026-07-05 14:23:42 -04:00
Taylor Richards acba81535d fix prebuilt issue when used outside of /lib 2026-07-05 14:23:42 -04:00
Taylor Richards e458420612 use prebuilt Qt libs for CI 2026-07-05 14:23:42 -04:00
Taylor Richards 99b7368863 update prebuilt ref to Qt6 version 2026-07-05 14:23:42 -04:00
Taylor Richards 9b00a851ae enable QtFRED by default
Note that Win32 is not supported by Qt6 and so QtFRED is forcibly disabled
on 32-bit Windows.
2026-07-05 14:23:42 -04:00
The Force bb81f70d43 One more depreciation 2026-07-05 18:01:33 +01:00
The Force 69b4a7c273 Fix more deprecations that somehow managed to slip by. 2026-07-05 18:01:32 +01:00
The Force 00ae863a2a Reapply #7450 2026-07-05 18:01:31 +01:00
Taylor Richards 44bd129f14 remove broken imported config mapping 2026-07-05 18:01:30 +01:00
Taylor Richards 2e41035cf3 stop installation of runtime libs (KNet does it) 2026-07-05 18:01:28 +01:00
Taylor Richards 2e04b76bf5 fix cmake policy warning 2026-07-05 18:01:27 +01:00
Taylor Richards 824368656b stop install of things we don't need 2026-07-05 18:01:26 +01:00
Taylor Richards 9892726042 remove AppImage hack for broken Wayland in Qt5 2026-07-05 18:01:25 +01:00
Taylor Richards aabad874a8 add custom fonts for Linux and fix platform check 2026-07-05 18:01:23 +01:00
Taylor Richards 47939dda88 switch to universal deployment setup
Use Qt deployment cmake script for all platforms. This uses the platform
specific method where possible (windeployqt/macdeployqt) and a generic
deployment script on other platforms.

This also uses a crazy RPATH hack on Linux, which is necessary for it to
find the prebuilt Qt libs and deploy them. Qt does not appear to allow
passing tool options for the generic deployment script so we can't tell
the relavent cmake functions where to look for our libraries even though
they support such functionality.
2026-07-05 18:01:22 +01:00
Taylor Richards 6b7c4bd5dc add warning to not distribute builds using system Qt install 2026-07-05 18:01:21 +01:00
Taylor Richards 18efd09123 Mjn's help cache fix 2026-07-05 18:01:20 +01:00
Taylor Richards 221926346b fix binary identification in AppImage script
With the new QtFRED help changes it gets the filename wrong, including the
names of multiple files by mistake. This tweaks the find command to ignore
those other files.
2026-07-05 18:01:18 +01:00
Taylor Richards 8c1c2d3822 add Mac app bundle info for qtFRED 2026-07-05 18:01:17 +01:00
Taylor Richards bfc8d65c59 place help files in proper location on Mac 2026-07-05 18:01:16 +01:00
Taylor Richards aa45bd8e07 fix finding of qhelpgenerator on Mac/Linux 2026-07-05 18:01:15 +01:00
Taylor Richards f6c4b908d9 fix executable type for Windows/Mac 2026-07-05 18:01:13 +01:00
Taylor Richards 9e3da3bd63 add support for precompiled Qt libs 2026-07-05 18:01:12 +01:00
Taylor Richards 00335726a3 bump macOS deployment target to 10.15 2026-07-05 18:01:11 +01:00
The Force ed090fa858 Try and disable console on non dev machines 2026-07-05 18:01:10 +01:00
The Force 1ce15a6b45 supress dpi context warning 2026-07-05 18:01:09 +01:00
The Force 61b9546dff Try to fix briefing editor 2026-07-05 18:01:07 +01:00
Mike Nelson 28f0231629 fix qtfred help 2026-07-05 18:01:06 +01:00
The Force 4ef290957b Build qthelp for QT6 2026-07-05 18:01:05 +01:00
The Force 9e88c3bf02 Not entirely Sure why but VS studio debug needs this to run 2026-07-05 18:01:03 +01:00
The Force 405d1290e5 Qt6 Changes 2026-07-05 18:01:02 +01:00
The Force 5aab5a1cf9 Fix Merge Error 2026-07-05 18:01:01 +01:00
The Force f6fcbf9745 Fix Several Crashes on close
Move shutdown to run before Qt destroys the opengl context

disconnect visibility changes as it accidently runs after windows destruction due to QT weirdness
2026-07-05 18:00:59 +01:00
The Force 236ad7547e Fix more illegal Chars 2026-07-05 18:00:58 +01:00
The Force 8f762038a7 Build Fix and remove illegal chars 2026-07-05 18:00:56 +01:00
The Force 8976616ba4 Fix Deprecations 2026-07-05 18:00:55 +01:00
The Force 17c676ec28 Add back in RCC 2026-07-05 18:00:54 +01:00
The Force 9414f36a01 Fix Main Window For DPI Scaling 2026-07-05 18:00:52 +01:00
The Force 875a57097d Enable Qt6 2026-07-05 18:00:46 +01:00
Fabian Woltermann 7122489c59 Merge pull request #7563 from Baezon/kamikaze-no-collision-avoidance
No collision avoidance for kamikaze fighters
2026-07-05 18:51:17 +02:00
Fabian Woltermann da1b045ad2 Merge pull request #7472 from MjnMixael/qtfred-fix-image-picker-dds
QtFRED support dds files in the image picker
2026-07-05 18:45:28 +02:00
Fabian Woltermann 2cb9227012 Merge pull request #7564 from The-E/fix/ai-manage-bay-doors-stale-parent
Fix crash in ai_manage_bay_doors from stale destroyed parent ship
2026-07-05 13:43:50 +02:00
the-eandClaude Sonnet 5 90f29236c1 Fix crash in ai_manage_bay_doors from stale destroyed parent ship
If the carrier a fighter launched from was destroyed earlier in the
mission, its Ships[] entry retains stale bay door state while its
object and model instance are already freed (objnum/model_instance_num
== -1). When the fighter later departs, ai_manage_bay_doors would still
try to animate the dead carrier's bay doors, calling
model_get_instance(-1) and asserting/crashing.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-07-05 12:08:48 +02:00
Taylor Richards 7e6b8594e7 clean, fix, and update workflows (#7562)
* clean, fix, and update workflows

Add support for Windows ARM64 builds.

Try to rely on build defaults for various options so that workflow
modifications aren't required for basic feature changes.

Remove Vulkan and Qt setup from workflow, instead relying on new
prebuilt libs.

Update SFTP image and Linux build image to newer release.

Add ccache setup for macOS.

Update ccache action to a version compatible with Node v24.
2026-07-04 22:36:32 -04:00
Shivansps 30955e88b4 Support Android TTS (#7497)
* add tts support

* correct type

* more robust method binding and hide volume option

* missing ifndef

* thread safe jnienv

* check jni env before use it

* remove opt in volume option and cleanup

* cleanup
2026-07-05 04:15:32 +02:00
BMagnu ca5a8f54ab Add flag to limit particles during animation to a direction (#7511) 2026-07-05 04:14:49 +02:00
BMagnu 7da707de7f Add particles on death of particles (#7504) 2026-07-05 04:14:41 +02:00
Goober5000 598c5c71a3 clean up ship status after mission (#7523)
Create a `ship_level_close()` function and call it from `game_level_close()`.  Among other tasks, this clears out any PRESENT or DEATH_ROLL statuses to prevent SEXPs or scripts from obtaining references to ships that have been deleted.

Follow-up to #7522.
2026-07-05 04:13:06 +02:00
Asteroth 90d04903fe no collision avoidance for kamikaze 2026-07-04 22:04:26 -04:00
BMagnu 25eebd96de Shadow Overhaul (#7529)
* Extract shadows into own shader and render pass

# Conflicts:
#	code/def_files/data/effects/main-g.sdr

* Consolidate render queue code

* More cleanup

* Fix missing clear of shadow buffer inter frame

* Request capability for viewport layer rendering

* instanced shadows

* Fix issues

* Switch to PCF+PCSS and proper textures for it

* Smooth PCSS toggle

* Fix for windows

* Fix OGL error

* Cleanup of old flag

* Expose shadow smoothness

* Fix missing uniform

* Make number of shadow cascades dynamic

* Progress on cockpit shadow integration

* Wrangle Cockpit and show-ship shadows into correct behaviour

* Cleanup

* make player ship shadow table options more intuitive

* Cleanup

* Only read from correct set of shadow maps, also stretch first main-scene map to 0

* Remove now non-necessary validation

* Make shadow cascade buffer more typesafe and less wasteful

* Don't allocate cockpit cascades if not used

* Cleanup

* Final fixes

* Fix Warnings

* clang tidy

* Fix missing fallback for shader precaching

* That weird FOV multiplier that always ruins your day

* Avoid shadow geometry artifacts by using hardware depth clamping

* Correctly handle shadow cascade overrides with hardcoded length

* GCC9 and its sad state of C++17 support.

* Address Feedback

# Conflicts:
#	code/graphics/shadows.cpp
2026-07-04 16:08:41 +02:00
BMagnu cc63bfa7c1 Gamma by Shader (#7484)
* Replace SDL gamma with a blit shader gamma

* Window_res comment

* Fixes

* Gamma command line flag
2026-07-04 15:28:52 +02:00
Goober5000 8b80541046 Merge pull request #7560 from Goober5000/version_26_1
bump version to 26.1
2026-07-03 23:04:34 -04:00
Goober5000 fd5c6fe4ed bump version to 26.1 2026-07-03 22:38:41 -04:00
Mike Nelson 4d8538e5cc Qtfred sexp tree refactor (#7301)
* 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
2026-07-04 02:12:34 +02:00
Goober5000andClaude Fable 5 82750105c7 make brief_stage non-copyable and non-movable; provide swap() and reset()
brief_stage owns its icons and lines buffers through raw pointers, so the
implicitly generated copy/move operations silently alias those buffers
across two stages.  Delete them and provide a custom swap() that
exchanges members (keeping each slot with a distinct buffer), plus a
reset() that restores the default-constructed state.

Update brief_compact_stages, the only code that assigned brief_stage
objects: shift stages down by swapping (equivalent, since the removed
stage buffers were already freed and the tail is cleared afterward) and
clear the tail entries with reset().

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-02 16:23:26 -04:00
Goober5000andClaude Opus 4.8 09c19f7094 use std::exchange in move ctors/assignment operators
Replaces the copy-then-reset idiom with std::exchange in five
steal-and-reset move constructors and assignment operators.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-20 20:54:52 -04:00
Goober5000 d15cadc47b Merge pull request #7537 from Goober5000/fix/defensive_unicode
more graceful degradation of invalid unicode
2026-06-20 20:50:27 -04:00
BMagnu 21b8035e63 Fix possibly negative pspew value init (#7541)
* Fix possibly negative pspew value init

* fix math
2026-06-21 01:59:20 +02:00
Goober5000andClaude Opus 4.8 fd2ebaf941 allow -1 sound index in OPF_GAME_SND syntax check (#7540)
The numeric branch of the OPF_GAME_SND check in check_sexp_syntax never
actually ran until the type2 variable-overload bug was fixed, so missions
using a sound index of -1 ("no sound" / "default") loaded fine.  Now that
the check is live, explicitly allow -1, which the runtime already treats as
an invalid (unset) sound.  Also return the more descriptive
SEXP_CHECK_INVALID_GAME_SND / SEXP_CHECK_INVALID_FIREBALL error codes, and
let nested-operator (SEXP_ATOM_LIST) arguments fall through since their type
is already validated upstream.

Also route the Lua SEXP soundentry argument through sexp_get_sound_index so
it accepts a table index, an operator, or a name like the core SEXPs do,
instead of only a name.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-21 01:59:08 +02:00
Goober5000 43b4724240 more graceful degradation of invalid unicode
If invalid UTF-8 is encountered, return the standard Unicode replacement character 0xFFFD.  When incrementing or decrementing, make a best effort to follow the code's instructions.  It might produce one or more � characters, but that's better than an Error and a hard crash.

Also move character constants to standardized locations.
2026-06-20 16:22:21 -04:00
Taylor Richards d5093cf799 Node v24 workflow update (#7536)
* update workflows for Node v24 support

* fix safe-directory issue
2026-06-18 21:40:20 +02:00
Goober5000 e71e9494d8 "change atan2_safe to atan2" followup (#7531)
Follow-up to #6255.  Partially restore use of `atan2_safe` from 87ea04fa53: keep special case while delegating most cases to atan2.

Fixes #7530.
2026-06-18 10:34:16 +09:00
BMagnu b65a92f358 Fix misbehaviour of Volume Point Towards (#7535) 2026-06-18 10:33:22 +09:00
BMagnu 7afbed8a3b Fix shadow artifacts by employing hardware clamping (#7532) 2026-06-18 00:46:33 +02:00
Goober5000 a7a2b9c9b1 Merge pull request #7518 from Goober5000/cleanpup/waypoint_move
remove empty destructors so waypoints can be moved
2026-06-15 23:43:06 -04:00
Goober5000andClaude Opus 4.8 1c079a79c1 Fix FRED play/media button transparency and disabled state (#7524)
CButton::SetBitmap blits a bitmap verbatim and does not perform the
magic-color substitution MFC applies when loading toolbar bitmaps. So a
button bitmap's background -- the legacy C0C0C0 gray baked into play.bmp
et al., or a magenta transparency key -- was drawn literally, reading as
a dark/colored rectangle instead of matching the real button face
(~F0F0F0 on modern Windows), and a disabled bitmap button embossed its
whole opaque rectangle into a solid gray square.

Add load_button_icon() to management.{cpp,h}, which builds an icon from
a button bitmap by keying out a background color into a transparency
mask. Driven through BS_ICON/SetIcon, the OS blends it with the button
face and grays it correctly when disabled. Route every play/media button
through it (event editor, briefing, debriefing, command brief, music
player, sound environment). Also add load_btnface_mapped() -- a
toolbar-style color remap -- kept as a lighter alternative for
always-enabled buttons.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-15 22:33:37 -04:00
Goober5000andClaude Fable 5 9be8c62f82 add move semantics for message media references (#7517)
MMessage's avi_info/wave_info unions hold strdup'd filenames in FRED
(indices in the game), but moves copied the pointers without nulling
the source, so every vector shift on Messages left moved-from elements
aliasing live allocations -- one careless edit away from a double-free.

Wrap each union in a MessageMediaRef struct whose moves null the source
and whose copies stay shallow (the event editor's backup pattern copies
then re-strdups).  MMessage keeps its implicit copy and gains a correct
implicit move; access syntax at all use sites is unchanged.

Add a message_free_media_names() helper and use it at the FRED
delete/wipe sites.  Two fixes ride along: messages_init() now frees
mission messages' media names before discarding them, closing a leak
that fired on every mission load in FRED; and the message editor's
OnDelete now uses the move-based array_remove_slot instead of a
copy-shift that left a stale aliasing tail.

No destructor: the active union member depends on Fred_running, and
shallow copies alias, so freeing remains explicit.

Co-authored-by: Claude Fable 5 <[email protected]>
2026-06-15 22:33:30 -04:00
wookieejedi 43387caf4a Fix flickering turrets in techroom (#7527)
Turrets with initial animations flip animation angles every frame in the techroom.  The commit returns TriStateBool from model_get_cached_ui_render_instance specifically so callers can run setup only when a new instance is created, but only `render_tech_model` (the Lua path) actually checks it.

Since the instance is cached and persists across frames, every frame re-runs clearShipData + initial-animation start on an already-animated instance, re-applying the initial animations relative to the current (already-animated) pose. This is what seems to produces the per-frame flip/jitter.

This PR caches it to prevent this, and fixes #7526. Tests confirm it works as expected.
2026-06-15 07:30:07 -04:00
wookieejedi 3e41e5ba2d Fix center reticle firepoints swaying (#7525)
Center reticle does not move if player is looking around, but there is a bug where the drawn firepoints of the center reticle do impromperly sway when the player is looking around. This is b/c the live global Eye_matrix, which has the free-look rotation baked into it by `object_get_eye()` then `compute_slew_matrix(eye_orient, &Viewer_slew_angles)`

Thus, do note use `Eye_matrix` b/c firepoints will sway if looking around, instead just use 'object_get_eye' with the properly set values.

Tested and fire-points work as normal but also fix the issue when looking around in free-look or TrackIR.
2026-06-15 07:29:51 -04:00
Goober5000 741693aa84 Merge pull request #7522 from Goober5000/fix/modelinstance_teardown
more robust handling of modelinstance teardown
2026-06-13 01:42:40 -04:00
Goober5000 ee50cde0e1 more robust handling of modelinstance teardown
Set model instances to -1 when objects are deleted, and guard against null model instances in `modelinstance_replace_active_texture`.  This may fix a crash in Event Horizon.
2026-06-12 22:39:50 -04:00
Goober5000andClaude Opus 4.7 e4c7c730cc null out editor-dialog globals on DestroyWindow (#7512)
Several dialogs already null their global at the top of DestroyWindow, but a few do not: briefing, debriefing, event, background, message, cutscene, goal, and Fred_view_wnd.  OnClose nulls in those five cover the user-initiated close path, but any teardown that bypasses OnClose leaves a dangling pointer that could be dereferenced.

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
2026-06-12 07:45:21 -04:00
Goober5000andClaude Fable 5 9f27adc31d remove empty destructors so waypoints can be moved
A user-declared destructor, even an empty one, suppresses the implicit
move operations, so waypoint and waypoint_list were silently copy-only:
every Waypoint_lists erase, insert, and reallocation deep-copied entire
lists, and inserting a waypoint mid-path copied every shifted waypoint.
Removing the destructors restores the implicit moves (correct and
noexcept, since the members are vec3d/int and SCP_vector/SCP_string),
turning those shifts into pointer steals.  The implicit deep copies
remain available and unchanged.  Waypoint identity is index-encoded in
Objects[].instance and renumbered on insert/erase, so relocation
semantics are unaffected.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-12 04:05:57 -04:00
Goober5000 5c110b5a87 Merge pull request #7516 from Goober5000/fix/6529
fix copy-paste error
2026-06-12 02:14:30 -04:00
Goober5000 0b7ded2407 fix copy-paste error
Due to a copy-paste error, the event editor would always be marked modified.  Fix the comparison.

This bug originally appeared in QtFRED also, but no longer does.

Follow-up to #6529.
2026-06-12 01:43:41 -04:00
wookieejedi 0076cdb74d Fix 'ignore if dead' flag (#7509)
* Fix 'ignore if dead' flag

Fixes #7508.

The subsystem flag "ignore if dead" that many mods use is supposed to "prevent homing weapons from attempting to hit the now destroyed submodel and instead tells them to home on the main hull of the target."

Though, in actuality, the code makes the weapon dumb fire instead of home. This bug means if you destroy a subsystem with this flag, then lock onto it with an aspect weapon and fire the weapon, the weapon will just dumb fire.

With `wp->homing_subsys` cleared as this PR does, the homing code at `weapons.cpp`:5730 falls into its else branch and picks an attack point on the ship body (`ai_big_pick_attack_point / hobjp->pos`) — which is what the flag promises. Javelins, which can only home on engines, will still correctly try to re-acquire another engine in the block just below and drop lock if there's none.

This fix is tested and now the flag works as expected.

* fix for javelins
2026-06-09 12:15:29 -04:00
Goober5000 d4c21eca0a Merge pull request #7514 from Goober5000/fix/7513
invalidate UI render instance cache when freeing model instances
2026-06-08 22:51:56 -04:00
Goober5000andClaude Opus 4.7 96d1988a54 invalidate UI render instance cache when freeing model instances
Fixes a startup assertion failure in Star Fox: Event Horizon, where a Lua
call to gr.freeAllModels() wiped Polygon_model_instances but left the UI
render instance cache holding stale indices. The next state transition
called model_clear_cached_ui_render_instances() which asserted in
model_delete_instance().

Closes #7513

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-06-08 22:02:45 -04:00
Goober5000 60203b6bae std::move in a few more places (#7502)
Follow-up to #7308.
2026-06-05 11:36:50 +02:00
Goober5000andClaude Opus 4.7 f184997572 fix ship select roster scroll-up getting stuck after list shrinks (#7501)
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]>
2026-06-05 11:36:29 +02:00
Mike NelsonandJohn Fernandez 880bfb11a7 Upgrade lua enumeration handling (#7423)
* upgrade lua enumeration handling

* some cleanup

* add lua enum descriptions

* minor fixes

* Update code/scripting/api/objs/enums.cpp

Co-authored-by: John Fernandez <[email protected]>

* include name

---------

Co-authored-by: John Fernandez <[email protected]>
2026-06-05 11:33:42 +02:00
Mike Nelson f4d84ec47a Support Rearm Pool, Limits, and Other Features (#7249)
* 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
2026-06-05 11:33:19 +02:00
Mike Nelson 31f77e5a32 Mines weapon type & weapon proximity triggers (#7461)
* new mine weapon type

* some fixes and cleanup

* fix some parsing issues

* make sure mines get plotted

* make mine spawned objects can get a correct target

* some cleanup and additional features

* chasing mines

* some Lua tweaks

* generalize proximity detonations

* minor tweaks

* collapse namespace

* address feedback
2026-06-05 14:06:00 +09:00
Mike Nelson d17bb56d3c Qtfred ship texture replace refactor (#7441)
* ship texture replacement stylization pass

* refactor UI to be dynamic

* properly save textures and load them on apply

* clang

* address feedback

* clang
2026-06-05 14:05:22 +09:00
Goober5000 1dd48f7c84 Merge pull request #7498 from Goober5000/fix/ets_crash
set ETS indexes despite power level
2026-06-04 08:53:25 -04:00
Mike Nelson 03ea836f86 Qtfred autosave system (#7463)
* remove legacy undo system from QtFRED

* implement timed autosave feature

* clang

* undo comments change
2026-06-04 13:35:13 +02:00
Goober5000andClaude Opus 4.6 1240d2eb11 add orbit camera support to FRED2 and qtFRED (#7310)
Implement a spherical-coordinate orbit camera for both the MFC (FRED2)
and Qt (qtFRED) editors, providing intuitive 3D viewport navigation
similar to other 3D editing tools.

Controls:
- Middle mouse drag: orbit rotate around pivot point
- Shift + middle mouse drag: pan the pivot point
- Right mouse drag: orbit rotate (alternative to middle mouse)
- Shift + right mouse drag: pan (alternative to shift + middle mouse)
- Mouse wheel: zoom in/out
- Right click without drag: context menu (preserving existing feature)

Camera pivot selection:
- If an object is selected, orbit around that object
- Otherwise, intersect the camera forward ray with the current grid
  plane to find a natural pivot point
- Falls back to grid center if the camera is parallel to the grid

Grid-plane awareness:
- Orbit axes are derived from The_grid->gmatrix using vm_vec_rotate
  and vm_vec_unrotate, so the camera orbits correctly on all three
  grid planes (XZ, XY, YZ), not just the default XZ plane
- The look-at matrix uses the grid's up vector (uvec) rather than a
  hardcoded world-up direction

Robust zoom/pan scaling:
- Zoom uses an exponential formula (powf) so the distance multiplier
  is always positive regardless of physics_speed setting (1-500)
- Pan uses a clamped speed factor to avoid excessive movement at high
  physics_speed values

State management:
- Keyboard camera movement (process_controls) invalidates orbit state,
  so the next mouse drag re-initializes from the current view
- Orbit state is per-viewport in qtFRED (EditorViewport members) and
  global in FRED2 (matching existing conventions in each codebase)

Implementation:
- FRED2: orbit math in fredrender.cpp, mouse handlers in fredview.cpp
- qtFRED: orbit math in EditorViewport.cpp, mouse/wheel handlers in
  renderwidget.cpp; RenderWindow::event() updated to forward middle
  button and wheel events to RenderWidget

Also fix an AND vs OR bug in FRED's process_controls().

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
2026-06-04 13:34:44 +02:00
Goober5000 2a998f182f set ETS indexes despite power level
Fixes a crash of the player ship has a power level of 0.
2026-06-03 23:12:46 -04:00
Goober5000 343bc173bb Merge pull request #7496 from Goober5000/fix/7206
fix CTD when LuaSEXP evaluates a subsystem on an absent ship
2026-06-03 21:29:23 -04:00
Goober5000andClaude Opus 4.7 876a1d36e9 fix CTD when LuaSEXP evaluates a subsystem on an absent ship
The OPF_SUBSYSTEM argument-conversion path in LuaSEXP only verified
has_shipp() before calling ship_entry->objp().  A ship registry entry
retains its shipnum after the ship is destroyed or departed, but its
objnum is reset to -1, so objp() either asserts (debug) or dereferences
Objects[-1] (release) when the entry is looked up by Lua scripting
during, for example, an OnMissionAboutToEnd hook.

Tighten the check to has_objp() so absent ships short-circuit to an
empty handle, and correct the early-return type from l_Ship to
l_Subsystem.  Apply the matching type correction to OPF_DOCKER_POINT,
which has the same return-type mismatch but no CTD because it never
dereferences objp().

Fixes #7206.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-06-03 16:40:59 -04:00
Goober5000 8a174c5ea4 Merge pull request #7462 from Goober5000/fix/mongoose_crash
Fix crash when restarting mongoose server on Windows
2026-06-03 11:53:04 -04:00
Goober5000 7145b3cb7e Harden Lua matrix and ParticleEffectAction against non-orthonormal inputs (#7490)
Two related fixes flagged by an audit of vm_vector_2_matrix_norm callers:

ParticleEffectAction: locals.direction defaulted to vmd_zero_vector,
which would trip vm_vector_2_matrix_norm's unit-length assertion (and
divide by zero in release) when an action program emitted a particle
effect without first running a Set Direction action.  Default to
vmd_z_vector so the unset-direction path always feeds a unit vector.

matrix_h / Lua orientation type: the element indexer (m[1..9] = x)
wrote arbitrary floats straight into mtx.a1d[idx] and the resulting
non-orthonormal matrix could flow into obj->orient via the Orientation
virt-vars on object/modelinstance/subsystem, breaking downstream engine
code that assumes the basis vectors are unit length.  getInterpolated
had the same problem by design (pure linear interp, no normalization).

Adds a fourth MatrixState, NeedsOrthonormalize, which the indexer
setter and getInterpolated use to flag their output.  ValidateMatrix
and ValidateAngles now call vm_orthogonalize_matrix when they observe
that state, so any consumer that reads through GetMatrix/GetAngles
gets a valid orientation without each call site needing its own guard.

getInterpolated is also marked ADE_FUNC_DEPRECATED at FSO 26.0 in
favor of rotationalInterpolate; mods targeting older versions keep
the legacy linear blend (now safely orthonormalized on use).
2026-06-03 16:52:30 +02:00
BMagnu 4246b761e7 FIx shadow banding on cockpits (#7495) 2026-06-03 11:06:21 +09:00
Mike Nelson 05534f709e Qtfred & FRED fix object cloning (#7460)
* fix ship duplication

* prop and jump node duplication

* make sure objects on layers are properly registered

* waypoint duplication

* some cleanup

* address feedback

* assertion for clang
2026-06-02 17:30:38 -04:00
Mike Nelson 970912f76e shield system dialog ux clarity (#7473) 2026-06-02 17:30:11 -04:00
Goober5000 96a60eb0bc Merge pull request #7492 from Goober5000/fix/fred_ship_registry
fix ship registry updating in FRED
2026-06-02 11:12:10 -04:00
Goober5000 8114a4c9bd Merge pull request #7493 from Goober5000/fix/supernova_clip
clip the supernova whiteout to the letterbox
2026-06-02 11:11:59 -04:00
BMagnu 0856b8211e Fix Shadow "Halo" (#7494)
* Attempt at removing the 'shadow-halo'

* Tidy up and rename old variable

* Tune bias for cockpits
2026-06-02 16:09:09 +02:00
Goober5000andClaude Opus 4.7 49d6f7a528 extract dead-view letterbox helpers
Per @BMagnu's review feedback on #7493: factor the yborder calculation
and clip-rect application into two file-local helpers so future changes
to the letterbox proportions live in one place.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-06-02 02:27:21 -04:00
Goober5000andClaude Opus 4.7 31eb87becf clip the supernova whiteout to the letterbox
game_render_hud calls gr_reset_clip() before game_flash_diminish, so the
sunspot palette flash was painting over the black bars during the
supernova camera cut. Re-apply the letterbox clip around gr_flash when
it would otherwise overdraw the bars (supernova HIT+ or GM_DEAD),
matching the condition already used in game_set_view_clip.

Partial fix for #4152.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-06-02 01:56:06 -04:00
Goober5000 781bf0d7f2 fix ship registry updating in FRED
When a ship is renamed in FRED/QtFRED, its ship registry information should be updated.
2026-06-01 23:55:20 -04:00
Goober5000 d52ba4e5b7 Merge pull request #7491 from Goober5000/fix/freezespace
Move SDL joystick scanning off the main thread
2026-06-01 21:45:47 -04:00
Mike Nelson 434d4fb968 fix arrival distance checks in qtfred (#7470) 2026-06-01 09:02:47 -04:00
Mike Nelson e38bab2e4a add qtfred themes for checked push buttons (#7471) 2026-06-01 09:02:25 -04:00
Goober5000 870e567fe4 Move SDL joystick scanning off the main thread
Setting SDL_HINT_JOYSTICK_THREAD=1 before SDL_InitSubSystem(SDL_INIT_JOYSTICK)
tells SDL2 to run joystick polling and HID device-detection on a dedicated
background thread, rather than processing them inline inside SDL_PollEvent
on the main thread.

Without this hint, every WM_DEVICECHANGE broadcast (which Windows sends when
*any* HID device wakes, sleeps, or pulses the USB bus) causes SDL to walk
every HID device class via SetupAPI / cfgmgr32 from inside SDL_PollEvent,
stalling the frame loop for multiple seconds on machines with many HID
peripherals - especially wireless Logitech mice and keyboards that
periodically pulse the bus. Diagnosed via ProcMon stack capture on one
affected user's machine, with the slow frame's call chain ending in
SDL2.dll -> SETUPAPI.dll -> cfgmgr32.dll -> kernel registry queries against
HKLM\System\CurrentControlSet\Enum\HID\*.

With the hint enabled, that work happens on SDL's own thread and the main
thread just dequeues already-collected events.
2026-06-01 02:14:08 -04:00
Goober5000 f831251cbd Merge pull request #7321 from scp-fs2open/dependabot/pip/ci/post/requests-2.33.0
Bump requests from 2.32.4 to 2.33.0 in /ci/post
2026-05-31 16:41:49 -04:00
dependabot[bot] 107b6bb35b Bump requests from 2.32.4 to 2.33.0 in /ci/post
Bumps [requests](https://github.com/psf/requests) from 2.32.4 to 2.33.0.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.4...v2.33.0)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.33.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-05-30 23:55:54 +00:00
Goober5000 cb1cb5641d Merge pull request #7445 from scp-fs2open/dependabot/pip/ci/post/mako-1.3.12
Bump mako from 1.3.11 to 1.3.12 in /ci/post
2026-05-30 19:52:44 -04:00
BMagnu 6b43cabff6 Fix CTD when an interface sound is shared with a gameplay sound (#7488) 2026-05-30 23:45:22 +02:00
Goober5000 14b0edcb95 Merge pull request #7486 from Goober5000/fix/stats_alltime_kills
fix stats sync in multiplayer
2026-05-30 17:28:11 -04:00
Goober5000andClaude Opus 4.7 237516c08c Convert CSG/PLR fixed ship/weapon arrays to SCP_vector
Ship_info is already an SCP_vector but MAX_SHIP_CLASSES (500) is held in
place by fixed-size structures throughout the engine and editors. This
converts the seven file-format-adjacent arrays whose persistence touches
the .csg campaign save and .plr pilot file formats:

  campaign::ships_allowed         [MAX_SHIP_CLASSES] -> SCP_vector<ubyte>
  campaign::weapons_allowed       [MAX_WEAPON_TYPES] -> SCP_vector<ubyte>
  loadout_data::ship_pool         [MAX_SHIP_CLASSES] -> SCP_vector<int>
  loadout_data::weapon_pool       [MAX_WEAPON_TYPES] -> SCP_vector<int>
  scoring_struct::kills           [MAX_SHIP_CLASSES] -> SCP_vector<int>
  scoring_struct::m_kills         [MAX_SHIP_CLASSES] -> SCP_vector<int>
  scoring_struct::m_okKills       [MAX_SHIP_CLASSES] -> SCP_vector<int>

The CSG and PLR file formats already store explicit counts and key by
ship/weapon names, so no on-disk format change is needed. Existing
read/write loops already iterated by ship_info_size() / weapon_info_size()
and just compile through against the new vector types.

Vectors are sized to ship_info_size() / weapon_info_size() in
scoring_struct::init(), mission_campaign_clear(), player_loadout_init(),
and the campaign-pool reset paths in csg.cpp and multi_campaign.cpp.
All of these run after weapon_init() / ship_init(), so the sizing
invariant holds. scoring_struct globals constructed at static-init time
start with empty vectors; first index access is always post-table-parse.

Multiplayer stats sync (multimsgs.cpp) now derives offsets and counts
from the vector size; added USHRT_MAX assertions on the sender side
since the wire protocol packs offset/count as USHORT. The FSTracker
sync (multi_fstracker.cpp) clamps vmt->num_ships down to the local
mod's kills.size() before transmission and guards the receive-side
write so a tracker payload with more ship slots than this mod has won't
overrun the vector.

The MAX_SHIP_CLASSES cap itself stays in place at ship.cpp:2327; this
is one prerequisite toward lifting it. The other ~25 fixed-size
ship-class-indexed structures (team_data, Ss_pool_teams, Wl_ships,
FRED2 editor arrays, qtFRED Editor::_ship_usage, etc.) remain to be
addressed in follow-up PRs.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-30 02:21:22 -04:00
Shivansps c3f9ce37e4 Default TTS to be disabled (#7485) 2026-05-30 08:16:55 +02:00
Goober5000 10288c16ad fix stats sync in multiplayer
A quick bugfix, thanks to Claude.  Make sure the end of the loop is correctly offset from the start.
2026-05-29 23:59:47 -04:00
Mike Nelson 2fcf1ba686 Lab camera snapping widget (#7400)
* camera control widget with snapping

* fresh brain, fresh tweaks

* clang
2026-05-30 05:46:43 +02:00
Shivansps 3bd934ee96 Add glClampColor() and GL_DEPTH24_STENCIL8 (#7478) 2026-05-29 09:44:52 +09:00
BMagnuandGoober5000 20c8417e8b Fullneb Revamp (#7352)
* 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]>
2026-05-28 14:43:48 +09:00
Goober5000 74dad95054 Merge pull request #7479 from Goober5000/fix/more_misc_bugs
miscellaneous fixes
2026-05-28 01:42:51 -04:00
BMagnu a715542405 Make beam impacts trigger even without beam flash / pierce effects (#7480) 2026-05-27 09:50:48 -04:00
Goober5000 11a58f4600 miscellaneous fixes
-weapon_spew_stats terminology, cfile fix, trigger count fix
2026-05-26 22:08:04 -04:00
Mike Nelson 3af8fa801d QtFred Render volumetric (#7467)
* render volumetric in qtfred

* warn for bad model files
2026-05-27 10:43:41 +09:00
Goober5000 fddedb3f42 Merge pull request #7449 from Goober5000/feature/dynamic_reinforcements
make the reinforcements collection dynamic
2026-05-26 20:10:57 -04:00
Mike Nelson 474f62b21e address feedback 2026-05-26 16:10:33 -05:00
Goober5000 b532a7b855 properly initialize each reinforcement 2026-05-26 10:55:05 -04:00
Goober5000 81695b36db qtfred support 2026-05-26 10:55:05 -04:00
Goober5000 7545e5d035 make the reinforcements collection dynamic
As requested by a FREDder who has a plausible situation where more than 32 reinforcements might be handy. :)
2026-05-26 10:55:05 -04:00
Shivansps fe18b474a9 KTX1 / ETC2 Compressed textures support (#7070)
* Initial KTX1/ETC2 support

* Conditional glad include

* remove unused function

* define glenum for non opengl build

* remove unused param

* fix qtbuild

* change ktx format to enum

* use nullptr

* ktx: support the same etc2 formats available in amd compressonator

* add comments and missing include

* correct flag type

* clarify variable name for clang

* cleanup

* update glad loader to GL4.3
2026-05-26 12:21:43 +02:00
Goober5000 04f0410408 Merge pull request #7299 from Goober5000/fix/fred_subsys_bbox
FRED and qtFRED quality-of-life improvements for highlighted models
2026-05-25 23:58:36 -04:00
Goober5000 4e0ae62e13 FRED and qtFRED quality-of-life improvements for highlighted models
A few visual improvements requested on Discord:
1. Use a configurable LOD to draw the outline wireframe, defaulting to LOD 1
2. Draw subsystem names with a dark outline to make them more visible
3. Draw the subsystem boundary boxes with thicker lines
2026-05-25 23:33:32 -04:00
Mike Nelson 712e10f778 only change theme if the pref changed (#7469) 2026-05-25 22:06:55 -04:00
BMagnu b70657a190 Moveable Instant Finish (#7476)
* Expose functions to instantly finish current moveable

* Fix IK crash
2026-05-25 01:31:53 +02:00
Mike Nelson 85f00be6f0 Optimize UI model instance (#7246)
* UI model caching feature

* do this with frames instead of timestamp
2026-05-25 01:31:21 +02:00
Mike Nelson 0ffc33c50b fix clipping issues on mirrored bitmaps (#7465) 2026-05-25 01:31:08 +02:00
Shivansps ff892b2ca7 Add OpenGL ES Compatibility Layer (#7137)
* initial changes

* add GLAD_ES loader

* Implement basic es compatibility header

* re-add accidentally deleted break

* Add GLES BC7 extension to glad and improve extension support report

* fix some enum mappings

* Use the ARB name for geo shader check

* Add es espand shader includes

* Fix glbufferstorage mapping

* fix some gl errors on opengl es

* Enable GL_CLIP_DISTANCE

* Add format conversions

* add bgba 8888 to rgba 8888 converter

* disable GLAD_GL_ARB_timer_query

* Properly handle wgl + opengl es on windows

* initial enable imgui gles

* add GL_BGR to GL_RGBA conversion

* inform opengl es on menu

* small corrections to the es header

* small refactor of glTexSubImage3D

* remove expandincludes, existing code already did that better

* change default graphic values for opengl es

* compile imgui with opengl es 3 on linux

* allow qtfred to run on opengl es

* improve es compatibility header and remove shader precision quantification

* add compiler hints

* remove unused structs

* Fix imgui crashing on windows with gles, use angle lib

* Do not use MSAA under OpenGL ES, at least for now

MSAA will need an alternative path for GL ES using GL_EXT_multisampled_render_to_texture and GL_EXT_multisampled_render_to_texture2

* add missing profile handler

* cleanup

* revert color_attachment_4/5 fbo workarounds

* Adjustments to log output and enable GL_BLEND

* Implement glGetQueryObjectui64v on es layer

* Fix deferred and decals support

Also added glGetTexImage(), glQueryCounter() and some additional comments

* Revert changes to default values of graphics options

* Minor improvements to glTexSubImage3D()
2026-05-25 01:30:42 +02:00
Taylor Richards 312a31feda add missing fontconfig dependency for ccache workflow (#7475) 2026-05-23 10:59:27 -04:00
Goober5000 cc9b7ad678 Merge pull request #7474 from Goober5000/fix/length_limit
fix wing name length checks
2026-05-22 20:55:44 -04:00
Goober5000 53db82a55f FRED fix - don't automatically give the ship a display name when the wing has a display name 2026-05-22 17:14:43 -04:00
Goober5000 b842e1774e fix wing name length checks
Handle wing names with any ordinal digit length.  Also fix a bug in `multi_ts_get_shipname` which passed a 0-based index instead of a 1-based index.
2026-05-21 22:46:17 -04:00
Taylor Richards 15c4141a1d always use newest appimagetool version (#7468)
This will download the appimagetool utility as part of the install step so
that the newest version of both the tool and the runtime is always used. Moving
it out of the build image allows us to take advantage of newer capabilities,
such as no longer having an external dependency on libfuse2.
2026-05-21 00:32:59 -04:00
Goober5000 295291e4ce Merge pull request #7456 from Goober5000/fix/openal_underrun
fix audiostream cutouts from openal buffer underrun
2026-05-20 22:03:15 -04:00
Mike Nelson 65a5c1f54c trim the fat (#7457) 2026-05-20 15:53:22 -04:00
Goober5000 70c4010389 add wing display names (#7429)
* 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
2026-05-20 15:16:08 -04:00
Mike Nelson 5a2ffd9f9c alt2 path mode with shortcutting (#7322) 2026-05-20 03:21:44 +02:00
Mike Nelson 9a843f3420 support dds images in qtfred image picker 2026-05-19 11:47:25 -05:00
BMagnu c8ea9c6035 Model surface particles (#7447)
* Pick model surface particle by sampling random vertex

* Parse and keep buffers

* Add local position scaling

* Fix warning

* Fix warning 2

* Fix warning 3

* Fix warning 4

* Add scale parameter

* Incorporate feedback
2026-05-18 13:23:41 +09:00
wookieejedi 15e7528d41 Expose new Max Guard Range to scripting (#7451)
Follow-up to #7251, which added `set-guard-range`
This PR exposes that value to scripting, so scripters do not have to use the more expensive `runSEXP` to use it.

The value is initialized as -1 (`max_guard_radius = -1.0f;` line 7913 in ship.cpp), hence the default value for the script is -1.
2026-05-18 11:34:19 +09:00
Goober5000 a6dc9de647 Merge pull request #7464 from Goober5000/fix_6793
two fixes for issue 6793
2026-05-17 19:25:59 -04:00
Mike Nelson 9de7315b38 move waypoint and jump node to their own dropdown category (#7455) 2026-05-16 10:41:59 -04:00
Mike NelsonandThe Force b4c5c7b781 Qtfred ship weapons dialog (#7453)
* Release Build Safety

* Move to using standard models.

* Avoid using .indexes() to fix heap coruption

* More work on selection function

* major cleanup and refactor

* enable drag and drop

* multi edit support

* cleanup

* fix build issue on linux

* revert non-weapons dialog changes

* clang

* clang

* update help doc

* better model/dialog code split

* further cleanup

* static

* keep primary and secondary banks aligned

---------

Co-authored-by: The Force <[email protected]>
2026-05-16 10:41:48 -04:00
Goober5000 4bc45b3481 Merge pull request #7459 from Goober5000/fix/comm_node
fix the Shivan Comm Node
2026-05-15 22:17:34 -04:00
Goober5000 1b8a79c64e two fixes for issue 6793
1. more robust handling of missing fireball graphics
2. fix handling of error dialogs on Linux so that the engine doesn't silently continue on an error!

Fixes #6793.
2026-05-15 22:09:29 -04:00
wookieejedi 0018f55c55 Fix edge case in switching from None AA to MSAA (#7454)
If you set AA level to None, save, then close the game, then re-enter the game and switch the AA mode to an MSAA mode, then the screen will just be black until the game is restarted. This does not happen when switching from None to FXAA because FXAA works because its post-pass uses `Scene_luminance_texture` and `Scene_ldr_texture`, which are always created as part of standard scene setup.

There are two ways to fix this, either require AA mode to require a game restart (though that may complicate the lab) or allow resource allocation for SMAA always.

Honestly I like the idea of keeping the lower-end machines optimized and not allocating for SMAA but simply running it always but for this PR have just allowed it always run.

Happy to discuss whichever option folks think is best!
2026-05-15 08:46:28 -04:00
Mike Nelson 946d15c1e5 ship special stats stylization pass (#7440) 2026-05-15 07:33:53 -04:00
Mike Nelson b8a3c9cae0 QtFred Ship initial status stylization pass (#7439)
* ship initial status stylization pass

* suppress false positive
2026-05-15 07:33:46 -04:00
Mike Nelson 4741869bbe QtFRED Jump Node Dialog Upgrade (#7420)
* make jump node multi select and direct edit

* clang

* update qtfred help docs

* fixes
2026-05-15 07:32:59 -04:00
Goober5000 c3b8922fe3 address feedback 2026-05-14 23:35:00 -04:00
Goober5000 3455b0f983 Merge pull request #7458 from Goober5000/cleanup/coverity_and_claude
a bit of cleanup
2026-05-14 11:16:43 -04:00
Shivansps 20b55eb58c TTS Speech rework (#7357)
* add imgui speech options

* adapt existing windows sapi speech implementation

* adapt existing mac speech integration

* add speech linux stubs

* add speech support in linux

* Add array checks

* Use dlopen for speech-dispatcher

* corrrect lib name

* missing includes and static cast

* do not change mac file type

* fix clang tidy warnings 1

* set tts rate

* set localization ids

* fix clang tidy warnings 2

* correct symbol name

* Remove voice cache and fix win enumerate_voices overriding voice selection

* fix mac rate

Done by notimaginative

* requested changes

* re-add voice cache for linux

* Open connection for linux get flags

* fix missing }

* change voice option combobox to std::pair

* delete duplicated voice id sanitizer on windows set voice

* Use pairs for speech_enumerate_voices() and adapt linux speech

* use reference

* actually free vector memory

* Clear voice cache when ingame options closes

* Use extern to call fsspeech_options_cleanup()

* update localization ids

* Fixing some linux and window speech oversights

* Apply volume and rate after a voice change, correct voice, volume and rate apply order
2026-05-14 08:07:11 -04:00
Mike Nelson 6ddc6c519d qtfred always generates sexps file (#7452) 2026-05-14 07:55:47 -04:00
Mike Nelson 3d43ddbcef QtFred Ship goals stylization pass (#7438)
* ship goals stylization pass

* clang

* static access
2026-05-14 07:55:32 -04:00
Mike Nelson 17b08706c1 ship flags stylization pass (#7437) 2026-05-14 07:53:23 -04:00
Mike Nelson 05619eba9d ship custom warp stylization pass (#7436) 2026-05-14 07:53:14 -04:00
Mike Nelson 2c62cb2089 ship alt class stylization pass (#7435) 2026-05-14 07:53:05 -04:00
Goober5000 8f916174be clang 2026-05-14 03:30:53 -04:00
Goober5000 1245546bb7 clang-tidy 2026-05-14 01:51:10 -04:00
Goober5000 bed223c5c7 second part of comm node fix
Fix #4089, part 2:
- properly link special-point subsystems with -destroyed submodels, if such a relationship exists and a game_settings.tbl flag is set
- add some helper functions for -destroyed variants of submodels (and now subsystems)
2026-05-14 00:04:31 -04:00
Goober5000 246213a4be first part of comm node fix
Fix #4089, part 1:
- give the `my_replacement` and `i_replace` fields clearer names
- for submodels with the `Is_damaged` flag (representing -destroyed variants or debris), initialize them to blown-off (the true fix for #3078, fixing a regression introduced in the model instance refactor)
- roll back the de-parenting of live debris from #3089 (726ce953c7), because it was a wrong fix to the initialization bug and introduced its own subtle bug when positioning live debris
- roll back the `Can_move` flag skipping from #4138 (063d7afcfe), because it was another wrong fix to the initialization bug
- add some clarifying comments
2026-05-14 00:04:31 -04:00
Goober5000 f8fa1e4775 a bit of cleanup
Fix a few items flagged by Coverity and Claude:
- properly check for an unassigned ship node, rather than just Asserting, since this could be affected by mission data
- properly check for null containers for the same reason
- rename `ai_goal_find_empty_slot` to `ai_goal_allocate_slot` and edit comments to reflect its not-quite-side-effect-free role
2026-05-13 23:29:31 -04:00
Goober5000andClaude Opus 4.7 e4af84e79a fix audiostream cutouts from openal buffer underrun
When a streaming OpenAL source plays through all queued buffers it
transitions to AL_STOPPED. Per the spec, queueing more buffers after
that does not auto-resume playback -- alSourcePlay must be called
again. This is the likely cause of music and briefing voices cutting
out.

To fix this, check for underrun and call alSourcePlay if needed. Also
double MAX_STREAM_BUFFERS from 4 to 8, bumping the margin to ~2s
of queued audio.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-13 21:20:31 -04:00
Mike Nelson e6d8835b72 Qtfred Ship editor stylization pass (#7434)
* ship editor stylization pass

* proper enable/disable fixes
2026-05-13 07:41:03 -04:00
Mike Nelson 9b57cc7a26 wing editor stylization pass (#7433) 2026-05-13 07:40:54 -04:00
Mike Nelson ddb6c2eaed QtFRED Waypoint Dialog Upgrade (#7419)
* make waypoint multi select and direct edit

* clang

* update qtfred help docs

* fixes

* static

* address feedback
2026-05-13 07:40:44 -04:00
Goober5000 ce28df46ca Merge pull request #7448 from Goober5000/feature/unlimited_title
unlimited mission titles
2026-05-12 21:17:40 -04:00
Goober5000 ef73e2572d unlimited mission titles
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.
2026-05-12 11:31:20 -04:00
BMagnu 1cbfb34a43 add findWorld / findObject for objects (#7443) 2026-05-12 07:52:28 -04:00
Mike Nelson 975ce277a7 QtFRED large ship collision group mission flag (#7425)
* large ship collision group mission flag

* update help doc
2026-05-12 07:51:00 -04:00
Mike Nelson 6c0d490489 Qtfred error checker (#7389)
* 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
2026-05-12 07:49:27 -04:00
Goober5000andClaude Opus 4.7 1a34123f0c fix qtfred heap corruption in FastDebug builds (#7450)
FastDebug FSO compiled with /MDd (debug CRT) but imported Qt's Release
libraries via CMAKE_MAP_IMPORTED_CONFIG_FASTDEBUG = Release Debug. Qt
Release ships /MD, so any allocation crossing the qtfred <-> Qt boundary
went into one heap and came back out of the other, tripping
_CrtIsValidHeapPointer on a perfectly-valid std::string dtor. Latent for
ages because it only surfaces when an allocation actually crosses;
opening a mission file is one such path.

To paper over the resulting STL layout difference, FastDebug also forced
_ITERATOR_DEBUG_LEVEL=0 so qtfred's std::string matched Release Qt's.
Not needed once we link Debug Qt, and keeping it would introduce a fresh
mismatch (qtfred IDL=0 vs Debug Qt IDL=2).

Fix:
  - Map FastDebug imports to "Debug Release \"\"" in qtfred/CMakeLists.
    Debug picks Qt's debug libs; Release is a defensive fallback; the
    empty entry falls back to plain IMPORTED_LOCATION, required because
    Qt's host tools (uic/moc/rcc/qhelpgenerator) ship a single variant
    with no IMPORTED_LOCATION_<CONFIG>. Must precede find_package(Qt5):
    CMake snapshots the variable into each target's MAP_IMPORTED_CONFIG
    property at creation time. (Also why the prior "Release Debug" was
    silently a no-op -- set after find_package, ignored; CMake fell
    back to Release on its own.)
  - Extend the qwindows/qsqlite plugin POST_BUILD copies' "d"-suffix
    generator expression to match FastDebug as well as Debug, so the
    Debug Qt runtime finds qwindowsd.dll / qsqlited.dll alongside.
  - Drop the FastDebug-only _ITERATOR_DEBUG_LEVEL=0 from the MSVC
    toolchain file. Qt was the sole consumer; pure-C deps don't care.

Cost: FastDebug STL containers pick up MSVC's debug-iterator overhead.
Reasonable trade for a config named "Debug with optimizations".

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-12 07:48:27 -04:00
wookieejedi 2046f9b5ac Update ParticleEffect.h (#7446)
Curve input follow-up to #6926 and #6889, in that it adds two new curve input Nebula and Particle Detail Levels for use within the particle effects table. These new inputs will allow modders to develop effects that can properly be optimized per varying particle and nebula detail levels.
2026-05-11 07:55:15 -04:00
Mike Nelson 76c00dbc06 Add set-guard-range sexp (#7251)
* set-guard-range

* make sure old path remains unchanged

* address feedback again
2026-05-11 07:53:58 -04:00
Mike Nelson 5f6309be49 make prop dialog true direct edit (#7418) 2026-05-09 10:50:51 -04:00
Mike Nelson d6509d81ab QtFRED Camera Class (#7408) 2026-05-09 10:50:30 -04:00
Mike Nelson 9180ff364e icon closeup zoom and pos (#7398) 2026-05-09 10:50:03 -04:00
Mike Nelson f02c01a81e fix gauges not respecting table flags (#7426) 2026-05-08 12:58:57 -04:00
dependabot[bot] 5fa6cb1677 Bump mako from 1.3.11 to 1.3.12 in /ci/post
Bumps [mako](https://github.com/sqlalchemy/mako) from 1.3.11 to 1.3.12.
- [Release notes](https://github.com/sqlalchemy/mako/releases)
- [Changelog](https://github.com/sqlalchemy/mako/blob/main/CHANGES)
- [Commits](https://github.com/sqlalchemy/mako/commits)

---
updated-dependencies:
- dependency-name: mako
  dependency-version: 1.3.12
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-05-08 10:46:56 +00:00
BMagnu 4086db15d9 Fix randomrange init slowdown (#7444) 2026-05-08 17:43:40 +09:00
Goober5000 165c4d3e6b Merge pull request #7307 from Goober5000/debriefing_modeless
convert debriefing editor to modeless; add focus_sexp(); fix bugs
2026-05-08 04:35:18 -04:00
Goober5000 6885bdc523 Merge pull request #7442 from Goober5000/7334_followup
fix FRED2 MFC linkage when building qtFRED
2026-05-08 01:53:40 -04:00
Mike Nelson 98aac12213 fix unpause issue (#7427) 2026-05-08 14:38:28 +09:00
Goober5000 f4c76dcee1 fix FRED2 MFC linkage when building qtFRED
PR #7334 caused an unintended side effect when building both FRED and QtFRED.  With QtFRED enabled, FRED2 sources were compiled with _AFXDLL defined while still linking against static MFC, causing a crash on startup.  To fix this, keep the conditionals in sync between cmake/toolchain-msvc.cmake and fred2/CMakeLists.txt.

Also, do not explicitly add static MFC libraries if we are using dynamic linking.
2026-05-08 00:54:40 -04:00
Mike Nelson fdef3eaa38 Share texture type names from model.h (#7395)
* share texture type names from model.h

* signed unsigned mismatch

* clang

* stop yur whining
2026-05-07 08:03:27 -04:00
Goober5000andClaude Opus 4.6 3f85d6172a use std::move() for local variables that are copied but not reused (#7308)
* 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]>
2026-05-07 14:40:38 +09:00
Goober5000 0e1e05552a Merge pull request #7421 from Goober5000/fix/anchor_model
fix a few bugs in model loading
2026-05-06 22:41:26 -04:00
Goober5000 4b157fe992 Merge pull request #7424 from Goober5000/fix/cmake_toolchain
fix cmake warning
2026-05-06 22:41:16 -04:00
BMagnu 1b0e63d924 Add animation support for particle spawning (#7416)
* Add particle during animation

* cleanup
2026-05-07 09:54:07 +09:00
Goober5000 9129a5871b fix cmake warning
Per Claude:
> The `IF` opens with `(MSVC_USE_RUNTIME_DLL OR FSO_BUILD_QTFRED)` but the `ELSE` and `ENDIF` only reference `(MSVC_USE_RUNTIME_DLL)`. CMake requires the argument in `ELSE`/`ENDIF` to match the `IF` exactly (or be omitted).
2026-05-05 23:29:11 -04:00
Mike Nelson 811b41252f Make showing 2D radar icons a toggle-able option (#7409)
* Make showing 2D radar icons a toggle-able option

* better options
2026-05-05 07:39:03 -04:00
Goober5000 2b6e20360d assign sip->model_num from inside model_load, plus a few tweaks 2026-05-05 00:19:47 -04:00
Goober5000 0642dcceab fix a few bugs in model loading
Each time `model_load` is called with a `ship_info` pointer, which causes it to load subsystems, the model number MUST be assigned to the ship_info.
2026-05-05 00:19:47 -04:00
LuytenKy 09a848a224 Refactor player-set-target SEXP (#7393)
* Refactor of logic based on feedback

* resolve requested changes
2026-05-03 12:26:45 -04:00
Mike Nelson d8131ab901 QtFRED help dialog and documentation (#7380)
* help dialog and documentation

* compile help in headless mode

* scene browser help file

* attempt to get CI working for linux

* debug attempt

* Fix split method to use QString::SkipEmptyParts

* Revert "debug attempt"

This reverts commit cb046e1b3305577ff50e95d5b748f6d1dbf025fb.

* clang

* update help

* correct name

* Inject sexps.html into help and add find in page controls

* update help based on recent commits

* better tutorial asset loading

* no const

* update help again
2026-05-03 12:26:06 -04:00
Mike Nelson 1d63244753 add escort to ship flags (#7404) 2026-05-02 12:04:01 -04:00
Mike Nelson 4416dfc3b0 make sure we don't set modified during init (#7417) 2026-05-02 12:03:20 -04:00
Goober5000 2b48778307 Merge pull request #7410 from Goober5000/documentation/4148
add documentation for issue 4148
2026-05-01 21:08:06 -04:00
The Force 0e43b1970c Fix Heap Alloction issues in QTFred (#7334)
* Build using /MD flag if building QTFred and on windows

Fixes Multiple QTFred heap crashes due to mismatched runtime types.

* Actually Change Right value
2026-05-01 14:56:09 -04:00
Mike Nelson 636390b727 Fix assert for flag list and ship starts (#7415) 2026-05-01 08:37:12 -04:00
Mike Nelson 683b533467 Qtfred Upgrade checkbox dialog (#7405) 2026-05-01 08:36:45 -04:00
BMagnu 37ecbe439a Gate correct insignias behind flag / version (#7406)
* Gate correct insignias behind flag / version

* Move flag to graphics settings
2026-05-01 16:16:40 +09:00
Goober5000 3a556d9413 Merge pull request #7412 from Goober5000/fix/asteroids
fix asteroid field undefined behavior
2026-04-30 22:02:27 -04:00
Goober5000 29bb47402f Merge pull request #7411 from Goober5000/fix/7327_followup
small followup to 7327
2026-04-30 22:01:11 -04:00
Goober5000andClaude Sonnet 4.6 6e5e342575 fix asteroid field undefined behavior
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]>
2026-04-30 02:10:49 -04:00
Goober5000 4235e4f252 small followup to 7327
Submodel `collision_tree_index` should be reset to -1 when removed, not 0.
2026-04-30 01:35:07 -04:00
Goober5000 e2707549f4 add documentation for issue 4148
Per Claude:

The key things the new comments add that the old ones were missing:

1. Palette RGB colors are completely ignored — only the index value is used as alpha. This is the non-obvious invariant that @notimaginative described.
2. Exact mapping table — makes the * 18 / * 17 math immediately readable, and clarifies the ANI vs PCX difference (> 15 clamps to opaque in ANI but transparent in PCX).
3. The broken-palette consequence — explicitly states that a reversed palette causes a white square, linking to #4148 so future readers understand the context.
2026-04-30 00:35:19 -04:00
Goober5000 7e74e35240 Merge pull request #7258 from Goober5000/split_str_once_preliminary_cleanup
preliminary cleanup for split_str_once refactor
2026-04-29 21:53:12 -04:00
Goober5000 f0ed5c88b8 Merge pull request #7407 from Goober5000/fix/coerce_to_utf8
fix coerce_to_utf8
2026-04-28 22:39:14 -04:00
Goober5000 abc5e87914 Merge pull request #7365 from Goober5000/fix/sexp_type_checking
improve SEXP syntax checking
2026-04-28 22:37:10 -04:00
Goober5000 66eb4702e1 fix coerce_to_utf8
Fix a bug that Claude caught: conversion from Latin-1 would fall through, truncate, and emit a spurious warning.
2026-04-28 01:26:11 -04:00
Mike Nelson ad4424984a Qtfred generic fixes (#7403) 2026-04-26 08:25:05 -04:00
Mike Nelson 681e9b73c6 Qtfred misc ship editor fixes (#7402)
* fix hotkey not saving/loading

* fix docking setup flow

* fix all/none in flags list not updating ui

* fix tri-state flag issues with multi selection

* fix initial status checkboxes

* fix no warp effect

* more fixes

* fix flag hiding

* allow any flag to be skipped for player starts

* another fix for no warp effect

* smarter context menu

* fixes for object orient editor
2026-04-25 10:40:46 -04:00
Mike Nelson be6594d786 QtFred layer dropdowns in object dialogs (#7385)
* layer dropdowns in object dialogs

* fix rebase issue

* silence Claude
2026-04-24 07:31:14 -04:00
wookieejedi 46d136c548 Follow-up fix to 7360 (#7399)
Turns out 7360 causes cockpits to not render. That PR had updated `GL_DEPTH_ATTACHMENT` to `GL_DEPTH_STENCIL_ATTACHMENT` but missed two spots. This PR fixes that and tests show cockpits now work.  (Root cause was the save/restore zbuffer swap using the old GL_DEPTH_ATTACHMENT point — once the scene texture became a combined depth-stencil, re-binding just the depth slot left stencil dangling and made the FBO incomplete.)
2026-04-23 10:25:51 -04:00
Mike Nelson 7fa37f3542 make sure toolbars can't be hidden (#7397) 2026-04-23 07:54:44 -04:00
Mike Nelson 86281e52b8 Qtfred custom data scripting parity (#7396)
* mirror scripting behavior from fred2

* apply scripted default custom data on new mission
# Conflicts:
#	qtfred/src/mission/dialogs/MissionSpecDialogModel.cpp
#	qtfred/src/ui/dialogs/MissionSpecs/CustomDataDialog.cpp
2026-04-23 07:53:58 -04:00
Mike Nelson 68aad508fb QtFred Combined spooled music selector/player widget (#7377)
* combined spooled music selector/player widget

* clang

* private members

* case insensitive compare
2026-04-23 07:53:16 -04:00
Mike Nelson 3d18cf4e5e Qtfred quick action bars (#7375)
* Add quick action bars to qtfred FredView

* always use trimmed

* make sure the layer list stays updated
2026-04-22 09:40:06 -04:00
BMagnu 7e09961f01 Fix stencil attachment for RTT textures and enable depth/stencil for cockpit RTT (#7386) 2026-04-21 12:17:53 +09:00
BMagnu 7590eaa371 Fix dirty modelcollide state in shipfx and harden with assert (#7390) 2026-04-21 12:15:27 +09:00
Mike Nelson d98b117316 qtfred event msecs checkbox (#7388) 2026-04-20 07:52:33 -04:00
Mike Nelson 09029030c2 Qtfred unify flag lists (#7384)
* convert wing flags to the flags widget

* set tooltips on flags using flag descriptions

* add missing descriptions

* clang
2026-04-20 07:52:24 -04:00
Mike Nelson b1cefade1a Qtfred cargo title and scanning fields (#7387)
* implement newer cargo fields and clarify subsystem cargo settings

* static
2026-04-20 07:52:16 -04:00
Mike Nelson cf5b50fe4b Qtfred visual tweaks (#7381)
* color variable or container rows based on type

* add props and jump nodes to layer filters

* make goals dialog dynamic and fix bugs

* cleanup

* missed these
2026-04-19 12:05:23 -04:00
Taylor Richards bd6ae390e6 add missing libfontconfig dependency for PR test workflow (#7382)
Fix issue with our Linux build image where libfontconfig1 isn't installed, but
is needed by some Qt5 libs. Since aqt is used to install a more up-to-date
Qt5 version than what's provided by the distro we don't have the usual
dependency resolution needed to prevent this.

This is a stop-gap measure to fix testing of pull requests for QtFred, and
only applies to that specifc workflow. The arm64 Linux image also has a broken
Qt install so this workaround is useless with normal workflows. A proper fix
will require updating the Linux build image to fix the Qt dependency problem
and related issues.
2026-04-18 21:42:52 -04:00
Goober5000 ced4fd3ed9 Merge pull request #7370 from Goober5000/fix/repulse
Fix stale subsystem model_num references across mission boundaries
2026-04-18 21:21:46 -04:00
Goober5000 1c25ed7fab Merge pull request #7383 from scp-fs2open/dependabot/pip/ci/post/mako-1.3.11
Bump mako from 1.2.2 to 1.3.11 in /ci/post
2026-04-18 13:37:33 -04:00
Mike Nelson efb1a62e86 Qtfred fixes and tweaks (#7379)
* fix mission stats message count

* fix texture replace for retail textures

* fix heap issue in ship alt class

* fix props menu item

* some ui clarity

* unify up/down buttons to use our painted icons

* fixup ship and wing cues toggles

* rename and move Object Orientation menu item

* menu seperator styling for dark mode
2026-04-18 10:43:29 -04:00
Mike Nelson 86ef1ff8a7 cleanup and persist preferences (#7376) 2026-04-17 07:46:35 -04:00
dependabot[bot] 5b84cef369 Bump mako from 1.2.2 to 1.3.11 in /ci/post
Bumps [mako](https://github.com/sqlalchemy/mako) from 1.2.2 to 1.3.11.
- [Release notes](https://github.com/sqlalchemy/mako/releases)
- [Changelog](https://github.com/sqlalchemy/mako/blob/main/CHANGES)
- [Commits](https://github.com/sqlalchemy/mako/commits)

---
updated-dependencies:
- dependency-name: mako
  dependency-version: 1.3.11
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-04-16 21:56:10 +00:00
Mike Nelson 4084c75678 Qtfred Dockable object list (#7368)
* 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
2026-04-15 23:07:01 -04:00
LuytenKy 2894ddb1fa Feature: Implements the SEXPs that set the player's target and or clear it if necessary (#7349)
* draft set-target-player-sexp

* Missed a few entries and duplicates

* Missed another entry

* Yet another missed entry

* Fix subsystem not showing up being targeted

* Fix subsystem targeting in set-player-target SEXP

* remove reduntant return

* change sexp category to better suit feature
2026-04-15 11:57:40 -04:00
LuytenKy e9c86b1e0e OpenGL: Consolidate depth–stencil buffers (Strategy 2) (#7360)
* Remove redundant unused stencil code

* switch Back_depth_texture to GL_DEPTH24_STENCIL8

* manage the clang-tidy error

* Remove redundancies and possible handle shutdown leaks
2026-04-15 07:52:20 -04:00
Mike Nelson c941963972 Qtfred ships dialog direct edit (#7369)
* fix some issues and convert to direct edit

* fix some comments

* some fixes

* var clash

* loops

* re add missing warning
2026-04-15 07:47:15 -04:00
Mike Nelson abb9dfb783 lua copy color (#7374) 2026-04-13 01:30:31 +02:00
Mike Nelson 243dc9237b QtFRED better file dialog handling (#7364)
* better file dialog handling

* fix copy/paste error

* not static for a change
2026-04-12 11:35:16 -04:00
Mike Nelson ec3f5ccc54 QtFred viewport code cleanup (#7361) 2026-04-11 08:28:47 -04:00
Mike Nelson 076d3849af Qtfred dark mode (#7362) 2026-04-11 08:28:31 -04:00
Goober5000andClaude Opus 4.6 2dcf851236 Fix stale subsystem model_num references across mission boundaries (#3147)
Fixes the long-standing intermittent bug where the GTD Orion#Repulse in
Silent Threat: Reborn's "Forced Hand" mission spawns with all subsystems
unlinked, leaving the ship untargetable and undisableable.  The same root
cause has been reported since at least 2015 (HLP topic 89403) and was
provisionally closed in #3147 as fixed by #3858, but #3858 was an unrelated
parse_ship_values memory leak fix and never actually addressed this
behavior.  Recently the underlying check at ship.cpp ~8043 was promoted
from Warning to Error (#5591), which is why Release players are now
hitting a hard error dialog instead of just seeing missing subsystems.

Root cause
----------
model_unload() walks Ship_info and resets si.model_num = -1 on every
ship_info that referenced the model, but does not touch
si.subsystems[k].model_num.  Subsystem entries are left pointing at the
freed model id.  When a subsequent mission reloads the same pof under a
new id, ship variants that share the model rely on
ship_copy_subsystem_fixup() to re-link their subsystem arrays, but the
fixup matches by name via model_copy_subsystems() and any unmatched
destination subsystem stays at -1.  The mismatch case in
model_copy_subsystems() was guarded only by Int3(), which is a no-op in
Release builds, so the failure was completely silent until subsys_set()
errored out much later with no useful context.

The bug is intermittent because it depends on three independent ordering
factors: which prior mission was last played, which Orion variant that
mission used, and the iteration order of Ship_info during the next
mission's ship_page_in().

Changes
-------
* model_unload(): when clearing si.model_num for an unloaded model, also
  clear every si.subsystems[k].model_num that pointed at the same id.
  This is the structural fix and eliminates the entire class of stale
  subsystem pointers surviving across mission boundaries.

* model_copy_subsystems(): replace the silent Int3() in the name-mismatch
  branch with Error(LOCATION, ...) naming the unmatched subsystem.  This
  surfaces a failure mode that has been invisible in Release builds for
  over a decade and gives modders an actionable diagnostic when sibling
  ship classes that share a model have inconsistent subsystem names.

* ship.cpp: introduce verify_ship_subsystems_linked() helper that checks
  every model_subsystem on a ship_info is linked into the same model the
  ship is using, and fails with Error(LOCATION, ...) naming both
  polymodels if not.  This is the canonical post-condition for both
  ship_copy_subsystem_fixup() and model_load() (which is supposed to
  link subsystems via do_new_subsystem()).

* ship_create(): call verify_ship_subsystems_linked() after
  ship_copy_subsystem_fixup() so that any failure to link a subsystem
  is surfaced with full context (ship name, subsystem name, both model
  filenames) instead of propagating to a generic error in subsys_set()
  much later.  This closes a long-standing asymmetry where ship_page_in()
  had a guard (added by zookeeper in 2015) but ship_create() did not.

* ship_page_in(): three post-fixup verifications were gated by
  #ifndef NDEBUG with Warning() or Assertion() calls, both of which are
  no-ops in Release builds.  Replace each with a verify_ship_subsystems_linked()
  call so they fire in Release as well.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-04-11 02:10:01 -04:00
Cliff GordonandClaude Opus 4.6 035ff3e111 Fix standalone web UI crash when polling player data before ship selection (#7367)
playerGet() indexed Ship_info with ship_class without bounds checking.
Before ship selection, ship_class is -1, which as an unsigned index
causes an out-of-bounds access. Add bounds check using ship_info_size(),
matching the pattern used elsewhere in the codebase. Also update the
Windows standalone GUI to use ship_info_size() for consistency.

Fixes #7366

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
2026-04-10 12:41:45 -04:00
Mike Nelson 1dd8f76861 Qtfred bugfix pass (#7363) 2026-04-10 09:03:52 -04:00
Mike Nelson 35e7bcf23b Qtfred toolbar and image asset cleanup (#7356) 2026-04-10 09:03:34 -04:00
Mike Nelson 3ccec4b9c6 Qtfred right click menu (#7354) 2026-04-10 09:03:18 -04:00
Mike Nelson 35ce84ebff Qtfred briefing map (#7338) 2026-04-10 09:02:54 -04:00
Mike Nelson 187906549c QtFRED waypoint path generator tool (#7335) 2026-04-10 09:02:37 -04:00
Mike Nelson 18fd87062f Allow rendering tech models with destroyed subsystems (#7218)
* allow rendering tech models with destroyed subsystems

* fix copy/paste error
2026-04-09 08:10:14 -04:00
Goober5000 56341002c8 improve SEXP syntax checking
Clean up the syntax checker, using clearer variable names for each check, and most importantly unique variable names.  Fixes several bugs where syntax checking failed because the same variable was overwritten with incompatible type information.

Also fixes a bug in `check_operator_argument_count` where the count check never ran due to a mixup of `op_index` and `op_const`.
2026-04-08 23:09:20 -04:00
Goober5000 219f692660 Merge pull request #7339 from Goober5000/claude/waypoint_position
remove redundant waypoint position by delegating to the game object
2026-04-08 21:12:04 -04:00
Mike Nelson 36d05ba9d8 Implement Photo Mode (#7287)
* Implement Photo Mode

* fix gr string errors

* return raw char array

* control conflict groups and better photo param defines

* better param identification in real time

* make photo mode hude dynamically sized

* fix xstrs after all changes

* missed a few

* use modern loop
2026-04-08 10:33:51 -04:00
Mike Nelson bbf527bae3 grant lua access to closeup values (#7342) 2026-04-08 10:32:32 -04:00
Cliff GordonandClaude Opus 4.6 c8201660d5 Fix multi sync screen showing wrong player status for non-host players (#7359)
Net_player[idx] (pointer arithmetic from current player) was used instead
of Net_players[idx] (global array index), causing the status display to
read from wrong offsets. This coincidentally worked for the host at index 0
but showed incorrect status for all other players — most visibly on
standalone servers where no client is at index 0.

Fixes #7358

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
2026-04-07 13:23:57 -04:00
Mike Nelson 7826b96ed2 QtFRED Cleanup and parity pass on view and object menus (#7348)
* Cleanup and parity pass on view and object menus

* some more view menu cleanup

* fix outline rendering for ships

* clang
2026-04-07 09:37:46 -04:00
Mike Nelson 703988942c QtFRED - wire up file and edit menu controls (#7347)
* wire up file and edit menu controls

* linux

* more linux

* backup files don't go into the recent files list
2026-04-07 07:59:35 -04:00
57eec4d015 multithreaded collision fixes (#7311)
* rename misleading variable check_again to never_check_again

The first element of collision_result means "never check again" per the
typedef comment, but was destructured as check_again (opposite meaning).
Rename to never_check_again to match the actual semantics and prevent
future confusion.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* fix data races in ship-weapon collision threading

Two thread-safety bugs in ship_weapon_check_collision, which runs on
worker threads:

1. update_danger_weapon() wrote to Ai_info[] (danger_weapon_objnum,
   danger_weapon_signature) without synchronization. Multiple weapons
   targeting the same ship would race on the same Ai_info entry.

2. Homing missile detonation logic wrote wp->lifeleft and
   wp->weapon_flags directly on the Weapons[] global array.

Fix: defer both writes to the main-thread post-processing phase via
new flags in ship_weapon_collision_data. Also convert the collision
data from a 6-element tuple to a named struct for readability.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* fix data race in ship-ship collision submodel checking

ship_ship_check_collision() toggled pmi->submodel[].collision_checked
flags on the heavy ship's polymodel_instance to selectively enable/
disable submodel collision checks. When two collision pairs shared the
same heavy ship, worker threads would race on the same pmi entries.

Fix: add a collision_checked_override pointer to mc_info that lets
callers provide a thread-local array of collision_checked values.
model_collide() uses the override when set, falling back to the
pmi->submodel[] field otherwise. ship_ship_check_collision() now
builds a local SCP_vector<char> and passes it via the override,
completely avoiding writes to shared polymodel_instance state.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* Ensure threads have spun down before continuing non-threaded execution

* address review feedback for ship-weapon collision threading

- postproc flag now accumulates incrementally instead of being OR'd
  at the final return, ensuring all return paths respect it
- early return for predictive collisions now preserves the
  should_update_danger_weapon flag in the collision data
- moved nprintf detonation debug print to the main-thread process
  pass for thread safety

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* move collision_checked from pmi into mc_info for thread safety

collision_checked flags were transient per-collision-pass state that
didn't belong in the shared polymodel_instance. Now mc_info owns an
SCP_vector<char> that model_collide auto-initializes from pmi when
empty. All collision callers (ship-ship, asteroid, debris, prop)
updated to manipulate mc.collision_checked instead of pmi->submodel[].

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* Convert to CV and spinup counter

* Update comment

---------

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
Co-authored-by: BMagnu <[email protected]>
2026-04-07 11:24:32 +09:00
Mike Nelson affd625262 fix menubar on windows (#7351) 2026-04-06 19:18:51 -04:00
wookieejedi 43a475a7e6 Merge pull request #7346 from MjnMixael/qtfred-about-dialog
Qtfred about dialog
2026-04-05 12:39:11 -04:00
wookieejedi c69a1aaa05 Merge pull request #7306 from notimaginative/multi_interp_fixes
Collection of fixes for multiple issues with the multi code found by Claude AI (by way of Goober5000), plus additional issues noted by myself while checking the code. These fixes are primarily focused on the interpolation code.

These changes should not be breaking and are largely exclusive to server-side handling, but still need to be tested thoroughly before being merged. Any tests must be done with at least the server (master) using the fixes.
2026-04-05 11:10:09 -04:00
Mike Nelson 41a8b18fad Qtfred visibility layers (#7314)
* add visibility layers

* save/load object layers using the mission file

* use fill

* fix rebase error
2026-04-04 09:11:55 -04:00
Mike Nelson f991bf14a8 Qtfred cleanup pass (#7345)
* set line edit fields to max length in code

* fix some dialog window titles

* remove unused context help button on Windows

* address some easy todo items

* static access
2026-04-04 09:11:31 -04:00
Mike Nelson de07dc6042 Qtfred settings dialog (#7316)
* working preferences dialog

* some cleanup

* clang ignore

* fix rebase issue
2026-04-03 14:03:07 -04:00
Mike Nelson f70d07a41e Qtfred general table viewer dialog (#7344)
* qtfred reusable table viewer dialog

* better error messages

char
2026-04-03 14:02:36 -04:00
Cliff GordonandClaude Opus 4.6 cfd9e0087c Fix multiplayer standalone crash: client stats race + host accept code (#7319)
* Fix multiplayer standalone crash: client stats race + host accept code

Fixes a crash in multiplayer PXO/tracker games hosted on a standalone
server where clients crash at debrief close after scoring a kill:

  pilotfile.cpp:295: Ship kills of '<ship class>' not found,
  should have been added by pilotfile::update_stats

Two independent root causes were found, both of which must be fixed for
correct behavior:

--- Root cause 1: race condition in multi_sexp_end_mission() (sexp.cpp) ---

The previous code called send_debrief_event() before
multi_handle_end_mission_request() as a "hack" to skip the warp-out
sequence when an end-mission SEXP fires. On standalone servers this is
a race condition: scoring_eval_kill only runs on the master, and the
master hasn't broadcast m_okKills to clients yet at the point
multi_sexp_end_mission() is called. Entering debrief this early causes
scoring_level_close() to run with zeroed m_okKills, so
Pilot.update_stats() creates no kill entries for that session.
When debrief_close() later calls scoring_backout_accept() it cannot
find those entries and hits the UNREACHABLE.

Fix: Remove the send_debrief_event() call entirely. The normal flow
via multi_handle_end_mission_request() has the server broadcast stats
first and then send MISSION_END, which causes the client to enter
debrief with correct kill data already populated. The master enters
debrief through sexp_end_mission() -> send_debrief_event() as before.

The behavioral side-effect of removing this hack: clients that go
through the normal warp-out sequence will no longer skip it on
end-mission. Clients in a state where they cannot warp still receive
immediate debrief via multi_handle_sudden_mission_end().

--- Root cause 2: host accept code never set on PXO standalone (multiui.cpp) ---

In multi_debrief_accept_hit() and multi_debrief_esc_hit(), when the
game is a PXO/tracker game and the local player is the game host
(NETINFO_FLAG_GAME_HOST), the code calls send_store_stats_packet() to
tell clients to record their stats. It then sets
Multi_debrief_stats_accept_code = 1 via the per-client response path
inside the NETINFO_FLAG_AM_MASTER block.

On a standalone server the game host is NOT the master
(NETINFO_FLAG_AM_MASTER is false for the client-side host). The
NETINFO_FLAG_AM_MASTER block is skipped entirely, and
send_store_stats_packet() uses multi_io_send_to_all_reliable() which
skips the local player — so the host never receives its own packet.
Multi_debrief_stats_accept_code stays at -1 (unset).

When debrief_close() checks this value and sees it is not 1, it
assumes stats were tossed or the window was closed without accepting,
and runs scoring_backout_accept() unconditionally. With fix 1 in place
this no longer crashes (entries exist), but silently strips all session
kill credit from the host's pilot record.

Fix: After the send_store_stats_packet() block, call
multi_debrief_stats_accept() unconditionally for the game host so the
accept code is set locally regardless of whether the host is also the
standalone master.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

* Preserve end-mission warp-out skip via deferred debrief entry

The previous fix removed send_debrief_event() from
multi_sexp_end_mission() entirely, which was correct for the stats
race condition but had a side effect: clients would no longer skip the
warp-out sequence when an end-mission SEXP fires.  The warp-out skip
was an intentional feature — process_endgame_packet() checks whether
the client is already in debrief state and, if so, bypasses
multi_warpout_all_players().

Restore the original behavior by deferring the send_debrief_event()
call to process_endgame_packet() rather than eliminating it.
multi_sexp_end_mission() now sets Multi_sexp_end_mission_pending=true
before calling multi_handle_end_mission_request().  When the client
receives the MISSION_END packet, process_endgame_packet() checks the
flag, calls send_debrief_event() there, then falls through to the
debrief-state check which skips the warp-out exactly as before.

This is safe because send_endgame_packet() calls multi_broadcast_stats()
before sending MISSION_END.  Both use reliable delivery, so stats are
guaranteed to be received and applied before MISSION_END is processed,
meaning scoring_level_close() now sees correct kill data when
send_debrief_event() fires.

Alt-J-initiated mission ends are unaffected: the flag is never set,
so the warp-out proceeds normally for those paths.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

* Revert multiui.cpp accept code change — tracker master owns stats

The previous change added multi_debrief_stats_accept() calls for the
game host in multi_debrief_accept_hit() and multi_debrief_esc_hit() to
work around Multi_debrief_stats_accept_code staying -1 on a standalone
server.  This was based on incorrect analysis.

In PXO tracker games the standalone master is intentionally the sole
authority for stat storage (via multi_fs_std_tracker_store_stats() in
multi_standalone_postgame_close()).  The host and clients must not save
stats to their local pilot files independently.  Multi_debrief_stats_
accept_code remaining -1 and triggering scoring_backout_accept() on the
host is correct behavior: kills are backed out of the local pilot file
because the tracker holds the authoritative record, not the local file.

The race condition fix (deferred send_debrief_event()) is sufficient to
prevent the crash: Pilot.update_stats() now creates kill entries with
correct m_okKills data, so scoring_backout_accept() can find and remove
them cleanly rather than hitting the UNREACHABLE.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

* Move Multi_sexp_end_mission_pending from multiutil to multi and reset in multi_vars_init()

Addresses review feedback: the flag has no relation to multiutil, and
resetting it in multi_vars_init() ensures it is always cleared between
missions regardless of which code path was taken.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

---------

Co-authored-by: Claude Sonnet 4.6 <[email protected]>
2026-04-03 09:26:23 -04:00
Goober5000andClaude Opus 4.6 92a46c9dad Fix SEXP references for shifted waypoints after single waypoint deletion (#7337)
When a waypoint is deleted from a list, the remaining waypoints after it shift down (e.g. Path:3 becomes Path:2). SEXP and AI goal references were properly invalidated for the deleted waypoint, but were not updated for any subsequent waypoint to reflect the new numbering.

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
2026-04-03 08:04:07 -04:00
Mike Nelson bca2721791 lab orthographic projection (#7336) 2026-04-03 08:02:41 -04:00
Mike Nelson 5acc748c2d Qtfred head ani picker (#7312)
* add head animation picker dialog for event messages

* get animations working

all previewing all head anis

cleanup

* reorganize files

* make these static

* no const

* more static

* updated var name

* missed one
2026-04-03 08:02:22 -04:00
Mike Nelson 54f0b410e7 Qtfred waypoint features (#7328)
* waypoint drawing features

* use ints

* parse and save as ints
2026-04-03 08:02:06 -04:00
Mike Nelson 9caec00b21 this goes here, silly 2026-04-02 16:13:31 -05:00
Mike Nelson 977db8ffa7 clang 2026-04-02 15:42:30 -05:00
Mike Nelson 170a4a82cc Add mission templates to QtFRED (#7320)
* add mission templates

* fix for unique_ptr
2026-04-02 16:42:22 -04:00
Taylor Richards 3f265054b8 add options to control rollback and fps on standalone
Adds multi.cfg options to allow easy tweaking of standalone servers to better handle mods
with faster ships and/or weapons that don't behave reliably with rollback or lower fps.
2026-04-02 16:31:57 -04:00
Taylor Richards 7d1983e74d fix log spam erroneously triggered by packet index fixes 2026-04-02 16:31:57 -04:00
Taylor Richards aef736643a fix ship whack sync issue in multi
Put ships getting whacked by weapons or other objects into simulation mode to avoid odd
position changes when client-side whacks get overridden by server interoplation updates.
2026-04-02 16:31:57 -04:00
Taylor Richards a7b29f339a use proper elapsed time for non-homing multi weapons
Fixes timing bug that used the wrong timestamp for primary and secondary weapon shots on
clients. This resulted in a discrepancy between client and server timing which caused weapon
shots to bypass the rollback system and become unreliable.
2026-04-02 16:31:57 -04:00
Taylor Richards f6db96a543 fix inverted interpolation scale
Could cause ships to jump backwards during position updates.
2026-04-02 16:31:57 -04:00
Taylor Richards ad356891f0 use the correct frame in reinterpolate_previous() 2026-04-02 16:31:56 -04:00
Taylor Richards 4ed03ad1d6 add safety checks and index updates to interpolation packet handling
Add safety checks for signal updates to avoid array boundary issues. And be sure to reasses
packet indexes when a new packet arrives instead of waiting a frame for index updates.
2026-04-02 16:31:56 -04:00
Taylor Richards d999d72065 add safety check for interpolation packet replacement 2026-04-02 16:31:56 -04:00
Taylor Richards 5a5082b4e9 fix crash if system_info is null 2026-04-02 16:31:56 -04:00
Taylor Richards 78fe2b5179 make sure to update client info interpolation frame 2026-04-02 16:31:56 -04:00
Taylor Richards bcf290877d fix afterburn hack and document why it's there
Afterburn hack is to fix trails and model animations getting triggered so that clients and hosts
see them. The hack needs to be reset before multi_oo_unpack_client_data() though, not after.

Fixes behavior that was broken in #2752
2026-04-02 16:31:56 -04:00
Taylor Richards 8dc0b724df continue processing ship records when gaps exist
If a ship is destroyed we still need to continue processing any other ships in the list.
2026-04-02 16:31:56 -04:00
Taylor Richards 396e065ad4 remove dead code for ai modes
The check was expanded and moved below this, but the old version remained.
2026-04-02 16:31:56 -04:00
Taylor Richards 041c9db2c5 add interpolation frame check for support ships 2026-04-02 16:31:55 -04:00
Mike Nelson 3d4502359b this goes here 2026-04-02 15:11:43 -05:00
Mike Nelson 4824a5d79c clean up the credits and styling 2026-04-02 15:03:30 -05:00
Mike Nelson 93fb8abaf0 make about dialog match qtfred styling 2026-04-02 13:58:25 -05:00
Mike Nelson 409421a0e4 Shared table viewer (#7235)
* Unify table viewer text loading across lab and editor UIs

* add some error messages
2026-04-02 08:00:28 -04:00
Goober5000 185b0f3dd4 Merge pull request #7296 from Goober5000/cleanup/weapon_spew_stats_refactor
refactor -weapon_spew_stats
2026-04-01 19:24:16 -04:00
Goober5000 e173fe17c6 Merge pull request #7341 from Goober5000/fix/mission_loop_music
fix mainhall music continuing to play during mission loop brief
2026-04-01 19:23:23 -04:00
Goober5000andClaude Opus 4.6 4a4ca20481 Fix crash when restarting mongoose server on Windows
mg_start() calls InitializeCriticalSection(&global_log_file_lock) on
every invocation, but mg_stop() never calls DeleteCriticalSection.
Re-initializing an already-initialized critical section is undefined
behavior on Windows and corrupts the heap, causing the next allocation
to throw an exception.

Make the Win32 initialization (WSAStartup + InitializeCriticalSection)
one-shot via a static flag, and remove the asymmetric WSACleanup from
mg_stop so resources remain valid across mg_start/mg_stop cycles.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-04-01 13:25:36 -04:00
Mike Nelson 95a7537b3d QtFRED mission stats dialog (#7333)
* qtfred mission stats dialog

* clang

* fix float division
2026-04-01 09:13:05 -04:00
Goober5000andClaude Opus 4.6 c745925821 fix mainhall music continuing to play during mission loop brief
When a player enters the mission loop screen from the main hall, there was no code to handle the stopping of main hall music and ambient sound.  Add code to handle this specific case.

Also fix `Loop_sound` not being initialized to -1 `in loop_brief_init()`, which caused an assert when closing the loop brief screen if no branch brief sound was defined for the mission.

Fixes #7330.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-04-01 00:34:35 -04:00
Goober5000 cc26887774 Merge pull request #7300 from Goober5000/cleanup/enhance_utility_h
enhance utility functions
2026-03-31 21:22:23 -04:00
Goober5000andClaude Opus 4.6 3c6df26da2 remove redundant waypoint position by delegating to the game object
The waypoint class stored its position in m_position separately from the game object's pos field, requiring callers to manually keep both in sync. Now get_pos() and set_pos() delegate to the object position when the game object exists, falling back to the parsed position only if the object does not exist.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-03-31 20:08:59 -04:00
Mike Nelson bd3855570b fix some issues with qtfred's ship list dialog (#7329) 2026-03-31 14:25:24 -04:00
Mike Nelson 7f61517aae Unified briefing map render paths (#7309)
* merged map rendering methods

* merge init and close methods

* nullptr

* assertions and nullptr

* fix comments
2026-03-31 10:07:34 -04:00
BMagnu 1f5525e04f Fix virtual POF crashes (#7327)
* Ensure model data isn't deleted twice

* shared_ptr-ify model data

* fix virtual pof filename issue

* Fix tests

* remove redundant memsets

* Clang Tid
2026-03-31 10:02:24 -04:00
BMagnu 7361417050 Cache Animation submodel by model id (#7331) 2026-03-31 00:41:01 +02:00
Taylor Richards 99585310b6 fix memory error when culling active games list (#7332) 2026-03-30 17:34:34 -04:00
wookieejedi 150b0c2d15 Fix targest nearest attacking ship in multiplayer (#7326)
* Fix `targest nearest attacking ship` in multiplayer

Pressing 'Target Nearest Attacker` in multiplayer does not work when a ship has spontaneously targeted another ship. This is because within, `multi_oo_pack_data` `goals[0].target_name` only covers explicitly ordered goal targets and is empty for spontaneous IFF-based engagements, which means `target_signature` does not return the proper value. This PR adds an extra check for that to pefer the live target_objnum so clients know who is actually being attacked.

Tested and works as expected.

* comment cleanup

* comment cleanup
2026-03-30 10:20:32 -04:00
Taylor Richards 96af51b56c fix error handling in libpcp to prevent endless loop (#7313) 2026-03-30 09:49:46 -04:00
Mike Nelson d3a8759257 Campaign Lua functions for branching (#7298)
* campaign lua functions for branching

* clang

* address feedback
2026-03-30 09:16:20 -04:00
Mike NelsonandBirk Magnussen 812769df7f Fix models rendering incorrectly when using RTT (#7239)
* Fix models rendering incorrectly when using rtt

* Add condition to depth attachment

---------

Co-authored-by: Birk Magnussen <[email protected]>
2026-03-30 15:06:47 +09:00
Mike Nelson 97a6c6314b Match editor wave thresholds to in-game limits (#7243) 2026-03-29 23:01:05 -04:00
Mike Nelson f2e8df4c69 Lua access to puncture flag status (#7323)
* lua access to puncture flag status

* disallow setting flag

* and don't grab the value even

* fix
2026-03-29 12:07:01 -04:00
Mike Nelson afc1db7563 Lab camera controls upgrade (#7219)
* Add lab panning controls and align to pof tools

* pan based on camera view coords not world coords

* reset view option

* change scrolling to use mouse wheel

* Better ui for controls reference

* clang
2026-03-29 08:04:03 -04:00
Goober5000 73715b823e Merge pull request #7317 from Goober5000/coverity/siof
Suppress Coverity GLOBAL_INIT_ORDER false positives for safe globals
2026-03-26 11:25:07 -04:00
wookieejedi 10ff64eb1d Tune ups for standalones (#7315)
* Tune ups for standalones

Fixes two warnings from building in standalone and also provide, also adds needed function to actually tell if game is in standalone mode.

* better fix for armor flags
2026-03-25 17:42:45 -04:00
Mike Nelson 240a9123c0 make fred's sound environment dropdown a full list (#7303) 2026-03-23 10:29:21 -04:00
Goober5000andClaude Opus 4.6 fa86575606 Suppress Coverity GLOBAL_INIT_ORDER false positives for safe globals
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]>
2026-03-22 15:00:25 -04:00
Mike Nelson c2462d059a Qtfred viewport controls (#7304)
* fix viewport controls

* allow cancelling drag events

* reverse forward backward movement

* fix some bugs

* ensure main window retains focus

* combine namespaces
2026-03-22 10:52:15 +01:00
Goober5000 6a20c0f549 Merge pull request #7117 from Goober5000/anchor_refactor
refactor ship anchors
2026-03-22 02:59:47 -04:00
Goober5000 2690cb078f Merge pull request #7183 from Goober5000/fred_fractional_pbh
allow more granular background orientations
2026-03-22 02:59:37 -04:00
Mike Nelson 7adcf9fe14 track mission modified and prompt to save (#7305) 2026-03-21 11:46:21 -04:00
Mike Nelson ff28a9c38e fix HDR and Alpha issues in screenshots (#7294) 2026-03-21 11:41:45 -04:00
Goober5000 a56bded919 convert debriefing editor to modeless; add focus_sexp(); fix bugs
Convert `debriefing_editor_dlg` from a modal stack-allocated dialog to a modeless heap-allocated dialog, matching the pattern of the briefing editor.  Update FRED infrastructure accordingly.

Add `focus_sexp(int node)` to `debriefing_editor_dlg`, mirroring the existing method in `briefing_editor_dlg`.  Fix both `focus_sexp()` implementations to search all team debriefings/briefings instead of only the currently selected team, so nodes in non-zero teams are found correctly in TVT missions.

Fix `debriefing set_modified()` calls to only set the flag if the debriefing data actually changes, rather than every time the editor was opened.
2026-03-21 01:29:13 -04:00
Goober5000 77954055df allow more granular background orientations
Since bitmaps, suns, and skyboxes store their coordinates as floating-point numbers, we can allow mission designers to specify floating-point numbers in the FRED UI.  This PR modifies the dialogs in FRED and QtFRED to allow two decimal places for pitch, bank, and heading.

Implements #7170.
2026-03-20 15:02:03 -04:00
Goober5000 05054f89f6 Merge pull request #7222 from Goober5000/text_replacement_updates
consistent handling of text token replacement
2026-03-20 15:00:16 -04:00
Goober5000 b76e652e0c Merge pull request #7282 from Goober5000/const_char_lookup
assorted `const` fixes
2026-03-20 14:59:04 -04:00
Goober5000 a47d34c739 assorted const fixes
1. Change `ai_profile_lookup` and `multi_xfer_lookup` to use `const char *`, since all the other lookup functions do
2. Remove several unneeded `const_cast`s, some of which are now made possible by the non-const `data()` overload in C++17
3. Change `campaign_link` to use `const char*`
4. Use better string management in `test::FSTestFixture::init_cmdline()`
2026-03-20 11:33:54 -04:00
Goober5000 49f00019a7 enhance utility functions
1. Add optional `start_index` and `num_items` parameters to the find/count functions in utility.h.
2. Fix an accidental recursion bug.
3. Change appropriate parameters to `size_t` and cast appropriately.
4. Add two forms of `SCP_vector_contains_lcase`.
5. Enhance `sprintf` and add `sprintf_concat` for `SCP_string`.
2026-03-20 02:22:20 -04:00
Mike Nelson f01eec903a check, assert, and skip as needed (#7297) 2026-03-19 07:45:54 -04:00
wookieejedi 100b8971a1 Fix gr.drawOffscreenIndicator (#7295)
The scripted `gr.drawOffscreenIndicator` was missing a critical `resetClip();` which meant it was using whatever the previous stack's clipping distance was, and thus not draw in the actual correct location.

This 1 line PR fixes that and the fix works as intended.
2026-03-18 07:33:15 -04:00
Goober5000 ba1f11f80a Merge pull request #7280 from Goober5000/campaign_optional_string_fred
optional_string_fred fixes
2026-03-17 23:34:29 -04:00
Goober5000 7de18842ef Merge pull request #7291 from Goober5000/fix/misc-fixes
miscellaneous fixes
2026-03-17 23:34:16 -04:00
Mike Nelson b09f7a26ce Qtfred props support (#7240)
* Add props support and dialogs

* Enable prop navigation when no prop is selected

* no lint

* clang

* oops
2026-03-17 08:53:23 -04:00
Mike Nelson ca1634bb5d Lua isDockLeader function (#7286)
* lua isDockLeader function

* address nitpicks
2026-03-17 08:21:12 -04:00
Goober5000 50a4febe88 refactor -weapon_spew_stats
This splits out the stat calculation and MediaVP text generation into their own functions so that they can be used elsewhere.
2026-03-17 00:22:50 -04:00
Mike Nelson 7b4b544ee7 Unify parsing of this param between TTF and bitmap fonts (#7288)
* unify parsing

* allow both
2026-03-16 14:47:25 -04:00
Goober5000 fc48fac60e miscellaneous fixes
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`
2026-03-15 03:25:38 -04:00
Mike Nelson 8fb7a058e8 grant lua ability to force a ship to call for support (#7250) 2026-03-14 18:50:13 -04:00
Mike Nelson 6120f7e6d6 update librocket reference (#7283) 2026-03-14 13:53:28 -04:00
Kestrellius 2211945f2d Merge pull request #7281 from Kestrellius/det-range-optimization
Optimize detonation range and detonation radius checks
2026-03-13 23:41:14 -07:00
Goober5000 ce31a33029 fix persistent variables and containers (#7192) 2026-03-13 08:16:28 -04:00
Mike Nelson b4c98defc6 Fix incorrect param order (#7284) 2026-03-12 20:51:46 -04:00
Goober5000 52081e20e6 Merge pull request #7279 from Goober5000/fred_campaign_fixes
fix a few campaign editor bugs
2026-03-12 20:42:30 -04:00
Goober5000 5639662e12 Merge pull request #7273 from Goober5000/more_fred_fixes_b
more FRED fixes when dialogs pop up, part 2
2026-03-11 23:01:42 -04:00
Kestrellius df7413ad4f address review 2026-03-11 19:55:41 -07:00
Goober5000 0073532a70 more FRED fixes when dialogs pop up, part 2
Follow-up to #7272 to add props handling.  This allows the original PR to be merged separately to 25.0.1.
2026-03-11 22:25:10 -04:00
Goober5000 780de87c04 Merge pull request #7272 from Goober5000/more_fred_fixes
more FRED fixes when dialogs pop up
2026-03-11 22:22:08 -04:00
Kestrellius 1fc7dd1fc5 dist squared 2026-03-11 18:42:34 -07:00
Taylor Richards d6c1b36c56 fix issues with imgui backend/renderer cleanup (#7278)
If the graphics system fails to fully init for some reason then it's possible
for the imgui backend and/or renderer to not be initialized. Those subsystems
*must* be initialized before being shut down so we need to check for that to
avoid hitting assertions inside of imgui.

Also add a safety check for making sure that an imgui context exists before
attempting to initialize the backend/renderer. A simple assertion should work
for this as it would take a coder messing something up in order to trip it.
2026-03-11 21:06:03 -04:00
Goober5000 b9c0a1ddd6 fix a few campaign editor bugs
1. Properly initialize campaign links and elements so that stale values don't stick around from previously loaded campaigns.
2. Clear internal flag bits from campaign missions to prevent invalid state from being loaded from a campaign file.
3. Properly check the main hall when saving flags in retail format.
4. Clean up parsing and saving of mission loop data, which had been broken ever since retail.  Add compatibility checks for the old format.
2026-03-11 20:45:00 -04:00
Goober5000 cbb30f9d7e optional_string_fred fixes
Not using the return value from `optional_string_fred` triggers Coverity warnings, but it turns out those "consume token" blocks aren't actually needed.

While we're at it, make the token parsing a little more robust in campaignsave.cpp and clean up the newline behavior.
2026-03-11 13:20:37 -04:00
CRGorman 3c3a3850ca Apply a wait for named pipe after verification (#7275) 2026-03-10 04:38:40 +00:00
BMagnu 8438af20e9 Make window_res buffer have a stencil attachment (#7274) 2026-03-10 00:58:14 +01:00
Goober5000 fbf58703b4 Merge pull request #7271 from Goober5000/particle_fixes
particle fixes and subsystem speedup
2026-03-08 23:24:50 -04:00
Taylor Richards 78f60c6f15 fix AppImage setup for fso and qtfred (#7154)
- generate appimage for qtfred if it's available
 - separate fso and qtfred install roots
 - move fso appimage files out of global space
 - fix qtfred appimage launch script
 - prefer X11 Qt backend for qtfred
2026-03-08 23:11:12 -04:00
Goober5000 d8a6d68482 more FRED fixes when dialogs pop up
FRED can get into a weird state if a warning appears while a ship is being created, e.g. if the ship has a null moment of inertia.  So, prevent FRED from rendering a ship if it hasn't been fully initialized yet.
2026-03-08 22:50:13 -04:00
Goober5000 89e7fd91da particle fixes and subsystem speedup
1. Modify `ship_get_subsys_for_submodel` to use the subsystem indexer.  This changes the operation from O(n) to O(1) and also allows the function to work with turret gun barrels.
2. Add an `isValid` check to `ParticleSource::finishCreation` to prevent unknown state from being accessed if the source is not valid.
3. Tweak `EffectHostTurret` and make sure it works for fighter beams.
4. Prevent `createOnTurret` from running on submodels that are not turrets.
2026-03-08 20:15:02 -04:00
Goober5000 35ed88c57f make anchors type-safe, and adjust support_ship_info accordingly 2026-03-06 22:00:32 -05:00
Goober5000 33d5f7ed3c refactor ship anchors
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.
2026-03-06 22:00:32 -05:00
The Force 14a900e554 Properly Invert flag check (#7270)
Fixes #7269
2026-03-06 14:09:52 -05:00
Mike Nelson ad03fa5900 Capture ImGui in screenshots (#7236)
* capture imgui in screenshots

* revert buffer changes

* request a screenshot for end of frame

* full revert for cleanliness
2026-03-06 07:47:28 -05:00
Goober5000 785bd36986 make clang-tidy happy 2026-03-06 00:03:00 -05:00
Goober5000 3aff98e5e3 preliminary cleanup for split_str_once refactor
String processing enhancement prerequisites for the upcoming `split_str_once` PR.

1. Add overloads of some string processing functions with a length parameter, to allow processing of substrings of C-strings
2. Add overloads of gr_printf that work with string lengths
3. Add constructors to `HUD_message_data` and `line_node`
4. Rename "size" to "len" in `vm_strndup` for clarity
5. Fill in some string attribute annotations
6. Make the rest of the HUD render functions `const`
7. Removed unneeded circular #includes in code/globalincs/memory/
2026-03-06 00:03:00 -05:00
Mike Nelson 8e23ce837f Add feature to customize how CMs are launched (#7224) 2026-03-05 15:15:56 -05:00
Mike Nelson f6588f802e allow repositioning the mainhall version strings (#7257) 2026-03-05 07:48:13 -05:00
Mike Nelson 21a3c80b45 Lua access to volumetric params (#7262)
* Lua access to volumetric params

* lua documentation error

* address feedback

* clang

* typo
2026-03-05 07:47:57 -05:00
BMagnu 5fc84d533d Actually load model if necessary (#7265) 2026-03-04 12:04:09 +09:00
Goober5000 ffa1bb2903 Merge pull request #7261 from Goober5000/fix_assert
fix waypoint list assertions
2026-03-02 23:25:16 -05:00
Goober5000 baf2f9521a fix waypoint list assertions
Followup to #7104.
2026-03-02 23:00:29 -05:00
Goober5000 1869b00af4 Merge pull request #7242 from Goober5000/props_missionsave
mission save compatibility
2026-03-02 21:53:44 -05:00
Mike Nelson e1d67cebbc Allow defining a default profile to use in non-mission rendering (#7238)
* allow defining a default profile to use in non-mission rendering

* Correct comment typo in lighting_profiles.h
2026-03-01 23:11:02 -05:00
BMagnu 252655cee5 Don't magic a model instance out of thin air (#7259) 2026-03-02 02:34:43 +00:00
Goober5000 e39a900223 Merge pull request #7241 from Goober5000/fred2_fixes
fix a FRED2 crash
2026-02-28 19:46:11 -05:00
Goober5000 135d84ebac Merge pull request #7255 from Goober5000/7104_followup
fix some waypoint bugs
2026-02-28 17:31:07 -05:00
Goober5000 482c5a5cc0 fix some waypoint bugs
1. The `waypoint_stuff_name` function used the wrong length when building the waypoint name, causing the number to be written past the end of the string in most cases.  Not sure how this escaped testing.
2. Change two Assertions, which had no effect due to the comma operator, to Errors, which match their counterparts in their respective functions.
3. Make `internal_error` consistent between the campaign editor, the mission editor, and qtFRED, and prune an unnecessary string copy.

Follow-up to #7104.  Fixes #7254.
2026-02-28 16:59:23 -05:00
Taylor Richards 31ad18fe3f fix portfwd issue when dealing with server response failures (#7252)
An upstream bug in libpcpnatpmp caused it to not pass along ping failure
messages in the internal state which then triggered multiple issues with
both the library itself and FSO. This fixes the bug in the pcp library
and takes care of the portfwd log spam issues in FSO as well.
2026-02-27 23:57:34 -05:00
Mike Nelson 5c2e5a261b Stop select model rendering destroyed subsystems (#7225) 2026-02-26 10:10:26 -05:00
Asteroth f23d35c7ec add scripted flight cursor sensitivity (#7149) 2026-02-25 08:30:42 -05:00
wookieejedi ff11370c56 Fix forever flashing threat gauge (#7247)
This one would be good for the upcoming point release, as on Stable 25.0, if your threat gauge is flashing during a mission and then you quit the mission and go to the HUD Config screen, then the gauge remains flashing.

This PR fixes that by simply not flashing the gauge while in configure mode. Tested and fixes the bug as expected.
2026-02-25 08:28:50 -05:00
BMagnu 7412e14575 Add Z-Compression shader to better use position buffer's FP16 (#7245) 2026-02-25 12:59:30 +09:00
Mike Nelson 8e44f355f0 Make sure to delete any dead objects before processing (#7223) 2026-02-25 00:48:28 +01:00
Goober5000 bbca63da8f Merge pull request #7185 from Goober5000/cannot_perform_scan_show_cargo
show and hide cargo variants of cannot-perform-scan
2026-02-24 00:14:48 -05:00
Goober5000 849acf200e show and hide cargo variants of cannot-perform-scan
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.
2026-02-23 22:03:22 -05:00
Goober5000 2436ef2a3f Merge pull request #7129 from Goober5000/fix_scannable_ship_types
enhance and clean up scannable ship types
2026-02-23 22:00:34 -05:00
BMagnu 176330023c Add metatable to correctly handle FSO objects (#7190)
* Add metatable to correctly handle FSO objects

* Avoid infinite recursion
2026-02-23 09:56:15 -05:00
Mike Nelson 405de8d0f3 Allow setting custom gauges as message gauges (#7217) 2026-02-23 09:53:34 -05:00
Goober5000 512021d7f5 Merge pull request #7116 from Goober5000/ship_enhancements
some ship utility enhancements
2026-02-21 00:39:08 -05:00
Goober5000 c4efdfb77b Merge pull request #7104 from Goober5000/waypoint_stuff_name
add waypoint_stuff_name
2026-02-20 23:14:10 -05:00
Goober5000 418af70ea3 some ship utility enhancements
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.
2026-02-20 23:11:07 -05:00
Mara van der Laan 239e0274ee io: Fix crash on SDL joystick disconnect (#7216)
Check that `pJoystick[i]` is non-null before dereferencing it.
2026-02-20 09:12:08 -05:00
Goober5000 82e50f82c1 Merge pull request #7155 from notimaginative/prebuilt_download_fix
stop download of prebuilt libs when using override
2026-02-19 13:00:05 -05:00
Goober5000 0666749ab6 mission save compatibility
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.
2026-02-19 09:54:26 -05:00
Mike Nelson e05bf3c3c7 Make sure we actually set old_class when changing classes (#7221) 2026-02-19 08:17:53 -05:00
Mike Nelson 125377f362 do not reset lab state when going to options (#7226) 2026-02-19 08:17:22 -05:00
Mike Nelson 6ae95c663b fix edge cases for mission background loading in the lab (#7237) 2026-02-19 08:16:53 -05:00
Goober5000 3763a29b16 Merge pull request #7098 from Goober5000/utility_enhancements
several utility enhancements
2026-02-19 00:31:45 -05:00
Goober5000 1832fa0397 fix a FRED2 crash
Fix a crash caused in part by some missing UI calls in FRED2 and in part by subtle object life cycle issues.  Depending on whether FRED receives the focus during mission load (which can happen if the autosave dialog appears) it could access props that were in the process of being cleaned up but were still referenced.  This isn't specifically a props issue; it could happen to any FSO object stored in a SCP_vector, since the vector can be cleared.

The quick mission init is to prevent a whole bunch of unnecessary and risky processing during a transient loading state, but it has the nice side effect of making mission list loading noticeably faster.
2026-02-19 00:29:53 -05:00
BMagnu e827011113 Particle source duration curve + life remaining input (#7231)
* Add curve to control the duration of a particle source

* Also add fractional Life Left curve input for sources

* Actually register new particle curve output
2026-02-17 12:09:13 +01:00
Mike Nelson 4fbfa84c6e fix logic checking for invalid ship or prop (#7232) 2026-02-16 17:07:41 -05:00
Mike NelsonandAsteroth 070a8e18fa Adds Props as a new object type (#6836)
* First draft of prop object type

* Add prop editor dialog in FRED

* prop sexps

* lua table prop support

* lab support

* lua mission access to props

* prop collision support

* use std::optional so the object->instance id is always valid

* allow props to block suns

* minor cleanup and remove arbitrary limit. Still limited to 500 objects.

* Sort props by category

* cleanup

* implement collision hooks

* parse prop flags and further ensure props are inited and closed correctly

* Add Cyborg's multi suggestions

* fix missing header

* add props to empty slots if available

* add multi signature

* fix rebase issues and match collisions to the new multithread style

* appease Clang the Conquerer

* save/parse mission prop classes by name

* Clean up comments and finish TODOs

* fix string errors

* clang wants this to be static now

* address feedback

* address feedback

* Use model num from prop info instead

* merge prop and ship collision group sexps together

* fix rebase bug

* address feedback

* fix name shadowing

* ship heavy submodel

* clang

---------

Co-authored-by: Asteroth <[email protected]>
2026-02-16 14:41:34 +01:00
Mike Nelson 37df4ba479 QtFRED Generalized Mission/Campaign Save (#7045)
* Generalize mission save from original FRED

* make qtfred use the new generalized missionsave

* split campaign save into a sister class

* allow unlimited campaign description length to be saved

* have the campaign editor pass the links to save properly

* cleanup

* future todo

* fixes

* more fixes

* cleanup vars

* clang

* add missing guardian threshold and fix var name

* more clang

* conflict resolution error

* update qtfred to use the new campaign save
2026-02-15 07:38:55 +01:00
Goober5000 7277fc11ff Merge pull request #7186 from Goober5000/ship_class_display_name
make ship and weapon class display names SCP_string
2026-02-14 21:23:50 -05:00
Goober5000 2e6b38ee06 Merge pull request #7204 from Goober5000/asteroid_debris_iterators
add getAsteroidList and getDebrisList script API iterators
2026-02-14 21:23:40 -05:00
Goober5000 b341e85e55 fix a few SEXP tree problems with variables and containers (#7203)
1. Add default strings for variables and containers, providing something more appropriate than `<new default required!>`.
2. Replace the `Assert` in `add_default_operator` with a check for a valid variable index.  This prevents a crash if FRED adds an operator that takes a variable as an argument and there are no variables in the mission.
3. Add the correct variable type to `get_listing_opf_variable_names()` to fix SEXP tree construction in certain circumstances.

Apply the same fixes to QtFRED as well.
2026-02-13 08:53:57 -05:00
Goober5000 c193a920d0 several utility enhancements
These were coded for the `split_str` refactor, but are generally useful elsewhere as well.

1. Use SCP_string in some utility functions where appropriate
2. Avoid copying C-strings in some utility functions where it isn't necessary
3. Add concat functions to `SCP_vector`
4. Change return type `int` to `bool` on some common parselo functions
5. Add `find_white_space` and `find_gray_space` parselo functions
2026-02-13 01:10:01 -05:00
Goober5000 68075dfbd5 consistent handling of text token replacement
Just as special tokens are replaced in debriefings, replace them in command briefings and briefings as well.

Change `string_replace_tokens_with_keys` to `message_translate_tokens` (since it's the same thing) and add a `replaceContainers` script API function to complement `replaceVariables` and `replaceTokens`.

Also replace every category of token in `sexp_modify_variable` and add comments to places where token replacement might be expected but happens elsewhere.
2026-02-13 00:56:41 -05:00
Goober5000 c428a22150 Merge pull request #7097 from Goober5000/snprintf_size
fix the size parameter of snprintf and vsnprintf
2026-02-13 00:50:38 -05:00
Goober5000 f78ac499e2 Merge pull request #7195 from Goober5000/timestamp_elapsed_fix
fix two timestamp bugs
2026-02-12 00:23:41 -05:00
Asteroth 1a7ee078f9 assume beam and shockwave creation is falliable (#7174) 2026-02-11 07:54:03 -05:00
Goober5000 ddb50fbbc9 Merge pull request #7207 from Baezon/start-loop-end-snds-fix
Misc fixes to start/loop/end sounds
2026-02-11 00:59:09 -05:00
Goober5000 2d4ca569be Merge pull request #7113 from Goober5000/zero_rad_skip_fireball
add Zero_radius_explosions_skip_fireballs
2026-02-11 00:56:18 -05:00
Goober5000 a0b6e1a0e2 Merge pull request #7115 from Goober5000/configurable_sets
configurable sets
2026-02-11 00:55:52 -05:00
Mike Nelson c51c5a3b0c don't assert for scripted animations (#7209) 2026-02-10 12:18:14 -05:00
BMagnu 0f45fa8209 Fix change ship class edgecases (#7210)
* Clear Decals and Recreate Model instance

* Also reset the PMI if the new model has no cockpit
2026-02-10 12:17:39 -05:00
BMagnu 6c67f348ac Bump version to 25.1 (#7215) 2026-02-10 05:29:40 +00:00
Goober5000 38c28d2c6f Merge pull request #7099 from Goober5000/filtered_sexps_speedup
filtered SEXP operator speedup
2026-02-09 21:19:51 -05:00
Goober5000 cc82766a11 Merge pull request #7112 from Goober5000/splodeing_removal
remove the splodeing code
2026-02-09 21:18:22 -05:00
Goober5000 427ab77b0a Merge pull request #7107 from Goober5000/unicode_enhancements
upgrade Unicode `text_iterator` for C++17
2026-02-09 21:14:44 -05:00
Asteroth d313cf8ca6 copy paste error 2026-01-30 00:40:46 -05:00
Asteroth 42e85df9f9 use obj snds for start and end too 2026-01-30 00:14:27 -05:00
Asteroth c47e0826e7 misc fixes to start/loop/end snds 2026-01-29 19:28:17 -05:00
Goober5000 73dd6c04e6 remove unneeded detour into Objects array 2026-01-29 01:34:13 -05:00
Goober5000 72f57ffe93 add getAsteroidList and getDebrisList script API iterators
These are similar to the existing iterators for in-game objects.
2026-01-29 01:34:13 -05:00
wookieejedi 5eea353952 Fix Select All HUD Color Slider (#7202)
#6670 added support for custom gauges and also cleaned up the color setting of hud gauges when using 'Select All'. It seems two copy paste bug slipped through the cracks (ie the order of colors was copied from red to blue/green, when really the order should have been updated).

This PR restores the proper order and fixes the bug.
2026-01-26 23:05:08 -05:00
wookieejedi 319e573637 Fix #7199 (#7200) 2026-01-25 19:37:08 -05:00
BMagnu 83f08291af Rollback insignia fix (#7196)
* Revert "Move insignia post-processing to model load"

This reverts commit 665d7145b6.

* Revert "Cleanup of old insignia stuff"

This reverts commit fdf98a1f3a.

* Revert "render insignias as decals"

This reverts commit 24cdeb3c95.

* Isolate reverts to insignias

* Fix oversight

* clang tidy
2026-01-23 11:11:47 -05:00
Goober5000 66a07fe0e1 fix two timestamp bugs
A timestamp set to 1 means an immediate timestamp, and so the `int` version of `timestamp_elapsed` should account for that, just as it does in the `TIMESTAMP` and `UI_TIMESTAMP` versions.  Normally the number comparison means the check will still work, but during mission initialization the timestamp code is not running.  This caused destroy-before-mission ships to be added to the arrival list when they shouldn't have been.

Also fix logic in `timestamp_elapsed_safe`, where the `TIMESTAMP` and `UI_TIMESTAMP` versions were correct, but the `int` version had a bug dating back to retail that caused it to return true for uninitialized (zero) timestamps rather than false.  Practically speaking, this fix has no effect because the function is only used in multiplayer and none of the instances use integer-0 to indicate uninitialized, but this will at least fix any uses in the future.
2026-01-21 00:06:25 -05:00
Asteroth 2813ceb5e9 Fix for ships blowing up with live debris submodels (#7139) 2026-01-20 08:32:03 -05:00
wookieejedi a2c05009dc Fix asteroids toggling (#7188) 2026-01-15 23:15:31 -05:00
Daniel Cazares 2af0aefd91 fix(eye): guard missing model instance (#7180) 2026-01-15 23:15:17 -05:00
wookieejedi d9a6330d9e Fix regression from #7051 (#7191) 2026-01-15 23:14:23 -05:00
Mike Nelson 634eece877 make this check less stringent (#7182) 2026-01-15 23:13:43 -05:00
Goober5000 add9c46868 configurable sets
Add the ability to configure the `less` operator for `SCP_set`, `SCP_map`, and so forth.  Add comparison operators to the type-safe `ID` class so that it can be used with these data structures.
2026-01-10 13:10:05 -05:00
Goober5000 b4d9c3e9c4 make ship and weapon class display names SCP_string
All other display names are `SCP_string`, so make these `SCP_string` as well to be consistent and to allow arbitrary-length display names.

Also fix a few infelicities with German translation and the script API.
2026-01-05 23:13:35 -05:00
Daniel Cazares e5c4c58eba fix(graphics): measure ellipsis before shrinking in force_fit_string (#7165)
* fix(graphics): measure ellipsis before shrinking in force_fit_string

* chore(comments): clarify why ellipsis measurement is needed before shrinking
2026-01-03 19:33:40 -05:00
wookieejedi e567ae7c81 Fix warning of unbound keys for newer controls (#7173)
* Fix warning of unbound keys for newer controls

Under some circumstances, FSO will warn the player if a key is unbound if that they need to press to complete a training event. This logic though only works for keys with XSTRs from 507-619. Any control that was added with a different XSTR will not be picked up correctly, and thus the message displayed to the player will be incorrect, confusing, and not helpful.

To fix this `missiontraining.cpp` around line 757 needs to account for the newer controls as well.

This PR adds those needed checks, and this fixes the issue. This fix is especially needed for mods that use training missions with newer controls such as FotG. Note, we do ship with a full default controls table with all the needed keys bound, but we have found players will be players and simply change all their controls before even starting the training missions. Thus they unbind key training controls, and with this bug they are unable to correct their controls. With this fix they are properly able to update their controls as needed to complete the training missions.

Fixes #7172.

* var name update
2026-01-03 18:19:10 -05:00
Kestrellius 46d3e490bb Render reorder (#7177)
Move the rendering of ships with effects to immediately after rendering of ships. This fixes rendering in several edge cases where, currently, additively-rendered items, among others, are incorrectly obscured by ships with effects. This affects anything that doesn't render to the depth buffer -- nebula poofs, particles, lasers, beams, engine glows, etc.

Forward-rendered objects (due to transparency) still interact incorrectly with full nebula fog, but that's out of scope for this PR.
2026-01-03 15:37:28 -05:00
Daniel Cazares 4dfdddf4d2 fix(model): capture driver inputs by value (#7163) 2025-12-28 05:14:22 +09:00
Mike Nelson 1da50c738f medal display name should be distinct from name (#7148) 2025-12-26 19:14:36 +09:00
Daniel Cazares a8f4be1d34 fix(math): correct vector assignments in vm_vector_2_matrix_gen_vectors (#7162) 2025-12-26 19:14:24 +09:00
Goober5000 306f34a574 update force_fit_string and bounds in calling functions (#7161)
Rewrite `force_fit_string` to be more robust and to remove the potential for memory access violations.  Also audit all of the length parameters in the calling functions - some of them were the buffer size, not the length.  This should fix the mysterious crash in the mission log screen.
2025-12-25 08:08:46 +09:00
Taylor Richards fb52a8861e fix fullneb lightning glows (#7153) 2025-12-23 07:10:16 -05:00
Goober5000 587e6f4c21 Merge pull request #7150 from notimaginative/macos_ninja_ci_fix
fix macOS ci warning regarding ninja
2025-12-22 23:17:46 -05:00
Goober5000 06d1514a47 Merge pull request #7151 from notimaginative/cmake_path_variable_fix
replace FILEPATH with PATH where appropriate
2025-12-22 23:17:06 -05:00
Goober5000 f483a92329 Merge pull request #7152 from notimaginative/macos_debug_flag_fix
disable compiler optimizations on Mac debug builds
2025-12-22 23:15:09 -05:00
Goober5000 db69cb8395 Merge pull request #7157 from Baezon/uvec-dominant-frame-of-ref
Uvec dominant frame of ref for submodels
2025-12-22 23:14:08 -05:00
Asteroth e803d8a0e9 move some stuff 2025-12-21 23:36:25 -05:00
Asteroth 761b97529a comments and assertions 2025-12-21 22:12:28 -05:00
Asteroth 1cd45aa1b4 uvec dominant frame of ref for submodels 2025-12-20 20:53:54 -05:00
Taylor Richards 542b3d3643 stop download of prebuilt libs when using override
Fix bug where prebuilt libs were downloaded even when an override was used.
2025-12-19 03:09:20 -05:00
Goober5000 5f110962e3 use pm->autocenter when drawing weapon lines (#7147)
In the weapon loadout screen, the model's visual center should be taken into account when calculating coordinates for weapon banks.  Fixes #7145.
2025-12-19 17:01:16 +09:00
Taylor Richards 0dea8d2904 disable compiler optimizations on Mac debug builds
Clang treats -Og the same as -O1, which prevents the viewing of many variables
while debugging, so we need to use -O0 directly.
2025-12-19 02:46:18 -05:00
Taylor Richards 381eda672b replace FILEPATH with PATH where appropriate
Fix issue setting path cmake variables in the gui due to the incorrect
type being specified.
2025-12-19 02:44:21 -05:00
Taylor Richards cbf2693d33 fix macOS ci warning regarding ninja 2025-12-19 02:39:18 -05:00
Goober5000 6c96d12133 slight fix to "Always save display names" (#7146)
A ship which has a display name and belongs to a wing will display that name no matter what wave it appears in.  Since this should only be done when the mission designer deliberately wants it to be done, omit this case from the cases covered by the "Always save display names" feature.  (To always save a display name in this case would require saving one for the wing, but a wing display name feature does not yet exist.)

Fixes #7140.  Followup to #6810.
2025-12-15 15:50:09 -05:00
BMagnu cb3819309b Make sunglows respect detail boxes (#7141)
* Add flag to respect detailboxes in model collide checks

* Don't check detailboxes that are invisible for sunglare checks

* Use local coords

* Actualyl also run the check for cockpits & show ship

* Be less stupid
2025-12-13 11:15:23 +01:00
wookieejedi 8fb0cbe08c Fix Message Line Positioning (#7144)
Message Lines were not being rendered to the proper position for certain resolutions per issue #7143.

#6575 seemed to be the commit it broke on, and looking at that commit it showed that code added `x` and `y` to the render string positions, even though `x` and `y` did not exist there before.

This 1-line PR fixes that issue, and it is confirmed to fix the issue by KamiGaAtaeta. Also I checked and on more standard resolutions the message lines still display as expected in both mission and the HUD Config screen.
2025-12-12 23:16:57 +01:00
Kestrellius ce0fe088eb Fix 'on edge' parameter for ring volumes not doing anything (#7142) 2025-12-11 01:30:52 +00:00
wookieejedi 86ef459cf6 Fix shadow shader for Mac (#7138)
Turns out Mac shader does not support the cleaner bracket style
2025-12-07 18:30:55 -08:00
Goober5000 32c07ef847 enhance and clean up scannable ship types
The `$Scannable` field in objecttypes.tbl controls whether a ship can be targeted by the "target unscanned ship" key.  This is independent of which ship types can actually be *scanned*, which is not connected to any field in objecttypes.tbl.

So, this PR renames the `$Scannable` field to `$Targetable as unscanned:` to be clearer about what it actually does, and it adds the missing counterpart `$Scannable by default:` flag to specify which ship types can actually be scanned.

Note that this is only relevant for legacy scanning behavior.  Mods which use `$Unify scanning behavior:` do not need either of the ship type flags.
2025-11-29 13:48:46 -05:00
Goober5000 adfb8200a5 remove the splodeing code
This dates back to commit `300d5fc2c1`.  It probably did something at some point, but with various upgrades over the years, it no longer has any effect.

It looks like a lot of the model interp code (e.g. `model_interp_defpoints`, which isn't called anywhere) can be removed as well, but that's a bit more involved.
2025-11-12 03:47:28 -05:00
Goober5000 8d7a56c3a8 add Zero_radius_explosions_skip_fireballs
Addresses a behavior idiosyncrasy encountered by Solaris.  If this flag is enabled, explosions with a visual radius of 0 do not have fireballs created, and do not have the fireball lifetime added to the explosion lifetime.
2025-11-12 02:17:25 -05:00
Goober5000 395d434f59 upgrade Unicode text_iterator for C++17
Add member types, tweak function signatures, and add a couple functions
2025-11-05 00:13:29 -05:00
Goober5000 1dc68b28c2 add waypoint_stuff_name
A consistent friendly way to get a waypoint name.  Follow-up to #7103.
2025-11-02 01:00:57 -04:00
Goober5000 5b4a4dd5cc filtered SEXP operator speedup
Improve the speed of the SEXP filter by not redrawing the combo box until the box is fully populated.
2025-10-25 13:11:55 -04:00
Goober5000 5f8e2c9475 fix the size parameter of snprintf and vsnprintf
These functions accept a size parameter that *includes* the null terminator, so tweak function calls accordingly.
2025-10-23 12:41:44 -04:00
1665 changed files with 195843 additions and 84514 deletions

No files matched your search

+144 -109
View File
@@ -5,9 +5,6 @@ on:
tags:
- 'nightly_*'
env:
QT_VERSION: 5.12.12
jobs:
build_linux:
strategy:
@@ -21,24 +18,25 @@ jobs:
arch: arm64
name: Linux
runs-on: ${{ matrix.os }}
container: ghcr.io/scp-fs2open/linux_build:sha-71099c9
container: ghcr.io/scp-fs2open/linux_build:sha-ab87451
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Configure CMake
env:
CONFIGURATION: ${{ matrix.configuration }}
COMPILER: gcc-9
ENABLE_QTFRED: OFF
run: $GITHUB_WORKSPACE/ci/linux/configure_cmake.sh
- name: Compile
working-directory: ./build
env:
CONFIGURATION: ${{ matrix.configuration }}
run: |
LD_LIBRARY_PATH=$Qt5_DIR/lib:$LD_LIBRARY_PATH ninja -k 20 all
run: ninja -k 20 all
- name: Run Tests
working-directory: ./build
env:
@@ -51,7 +49,7 @@ jobs:
CONFIGURATION: ${{ matrix.configuration }}
run: $GITHUB_WORKSPACE/ci/linux/generate_appimage.sh $GITHUB_WORKSPACE/build/install
- name: Upload build result
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: linux-${{ matrix.configuration }}-${{ matrix.arch }}
path: ${{ github.workspace }}/build/install/*.AppImage
@@ -59,23 +57,26 @@ jobs:
name: Build Linux distribution zip
needs: build_linux
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-748b19d
container: ghcr.io/scp-fs2open/sftp_upload:sha-ab87451
strategy:
matrix:
arch: [x86_64, arm64]
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: '0'
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Download Release builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: linux-Release-${{ matrix.arch }}
path: builds
- name: Download FastDebug builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: linux-FastDebug-${{ matrix.arch }}
path: builds
@@ -101,119 +102,169 @@ jobs:
strategy:
matrix:
configuration: [FastDebug, Release]
arch: [Win32, x64]
simd: [SSE2]
os: [windows-2025, windows-11-arm]
arch: [Win32, x64, ARM64]
simd: [SSE2, ""]
exclude:
- arch: Win32
os: windows-11-arm
- arch: x64
os: windows-11-arm
- arch: ARM64
os: windows-2025
- os: windows-2025
simd: ""
- os: windows-11-arm
simd: SSE2
name: Windows
runs-on: windows-2022
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
# - name: Cache Qt
# id: cache-qt-win
# uses: actions/cache@v1
# with:
# path: ${{ github.workspace }}/../Qt
# key: ${{ runner.os }}-${{ matrix.arch }}-QtCache-${{ env.QT_VERSION }}
# - name: Install Qt (32 bit)
# uses: jurplel/install-qt-action@v2
# if: ${{ matrix.arch == 'Win32' }}
# with:
# version: ${{ env.QT_VERSION }}
# dir: ${{ github.workspace }}/..
# arch: win32_msvc2017
# cached: ${{ steps.cache-qt-win.outputs.cache-hit }}
# aqtversion: ==0.8
# - name: Install Qt (64 bit)
# uses: jurplel/install-qt-action@v2
# if: ${{ matrix.arch == 'x64' }}
# with:
# version: ${{ env.QT_VERSION }}
# dir: ${{ github.workspace }}/..
# arch: win64_msvc2017_64
# cached: ${{ steps.cache-qt-win.outputs.cache-hit }}
# aqtversion: ==0.8
- name: Prepare Vulkan SDK
uses: humbletim/[email protected]
with:
vulkan-query-version: 1.4.304.1
vulkan-components: Vulkan-Headers, Vulkan-Loader
vulkan-use-cache: true
- name: Configure CMake
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Install v143 ATL/MFC
shell: pwsh
run: ci/windows/install_v143_atlmfc.ps1
# Tree A: static CRT (/MT) for FSO + FRED2 (no Qt dependency needed)
- name: Configure CMake (static CRT /MT - FSO + FRED2)
env:
CONFIGURATION: ${{ matrix.configuration }}
ARCHITECTURE: ${{ matrix.arch }}
SIMD: ${{ matrix.simd }}
shell: bash
run: |
mkdir build
cd build
if [ "$ARCHITECTURE" = "Win32" ]; then
cmake -DCMAKE_INSTALL_PREFIX="$(pwd)/install" -DFSO_USE_SPEECH="ON" \
-DFSO_USE_VOICEREC="ON" -DFORCED_SIMD_INSTRUCTIONS="$SIMD" \
-DFSO_BUILD_QTFRED=OFF -DFSO_BUILD_TESTS=ON \
-DFSO_INSTALL_DEBUG_FILES="ON" -DFSO_BUILD_WITH_VULKAN="OFF" -A "$ARCHITECTURE" \
-G "Visual Studio 17 2022" -T "v143" -DCMAKE_BUILD_TYPE=$CONFIGURATION ..
else
cmake -DCMAKE_INSTALL_PREFIX="$(pwd)/install" -DFSO_USE_SPEECH="ON" \
-DFSO_USE_VOICEREC="ON" -DFORCED_SIMD_INSTRUCTIONS="$SIMD" \
-DFSO_BUILD_QTFRED=OFF -DFSO_BUILD_TESTS=ON \
-DFSO_INSTALL_DEBUG_FILES="ON" -A "$ARCHITECTURE" \
-G "Visual Studio 17 2022" -T "v143" -DCMAKE_BUILD_TYPE=$CONFIGURATION ..
fi
- name: Compile
working-directory: ./build
mkdir build-mt
cd build-mt
cmake -DCMAKE_INSTALL_PREFIX="$(pwd)/install" -DFSO_USE_SPEECH=ON \
-DFSO_USE_VOICEREC=ON -DFORCED_SIMD_INSTRUCTIONS="$SIMD" \
-DFSO_BUILD_TESTS=ON -DFSO_INSTALL_DEBUG_FILES=ON -A "$ARCHITECTURE" \
-DFSO_BUILD_QTFRED=OFF -G "Visual Studio 18 2026" -T "v143" \
-DCMAKE_BUILD_TYPE=$CONFIGURATION ..
- name: Compile (static CRT)
working-directory: ./build-mt
env:
CONFIGURATION: ${{ matrix.configuration }}
COMPILER: ${{ matrix.compiler }}
shell: bash
run: |
cmake --build . --config "$CONFIGURATION" --target INSTALL -- /verbosity:minimal
cmake --build . --config "$CONFIGURATION" -- /verbosity:minimal
cmake --install . --config "$CONFIGURATION"
ls -alR "$(pwd)/install"
# Tree B: dynamic CRT (/MD) for QtFRED only (matches Qt's prebuilt /MD runtime)
# Skipped on Win32: Qt6 has no 32-bit Windows support, so CMake force-disables
# FSO_BUILD_QTFRED there and the qtfred target would not exist
- name: Configure CMake (dynamic CRT /MD - QtFRED)
if: matrix.arch != 'Win32'
env:
CONFIGURATION: ${{ matrix.configuration }}
ARCHITECTURE: ${{ matrix.arch }}
SIMD: ${{ matrix.simd }}
shell: bash
run: |
mkdir build-md
cd build-md
cmake -DCMAKE_INSTALL_PREFIX="$(pwd)/install" -DFSO_USE_SPEECH=ON \
-DFSO_USE_VOICEREC=ON -DFORCED_SIMD_INSTRUCTIONS="$SIMD" \
-DFSO_BUILD_TESTS=OFF -DFSO_INSTALL_DEBUG_FILES=ON -A "$ARCHITECTURE" \
-DFSO_BUILD_QTFRED=ON -DFSO_BUILD_FRED2=OFF -DQT_USE_PRECOMPILED=ON \
-G "Visual Studio 18 2026" -T "v143" -DCMAKE_BUILD_TYPE=$CONFIGURATION ..
- name: Compile (dynamic CRT)
if: matrix.arch != 'Win32'
working-directory: ./build-md
env:
CONFIGURATION: ${{ matrix.configuration }}
COMPILER: ${{ matrix.compiler }}
shell: bash
run: |
cmake --build . --config "$CONFIGURATION" --target qtfred -- /verbosity:minimal
cmake --install . --config "$CONFIGURATION" --component qtFRED
ls -alR "$(pwd)/install"
# Merge both trees into the single install dir the zip job consumes
- name: Merge installs
shell: bash
run: |
mkdir -p build/install
# build-md does not exist on Win32 (no QtFRED there)
if [ -d build-md/install ]; then
cp -r build-md/install/. build/install/
fi
cp -r build-mt/install/. build/install/
ls -alR build/install
- name: Run Tests
working-directory: ./build
working-directory: ./build-mt
env:
CONFIGURATION: ${{ matrix.configuration }}
shell: bash
run: ./bin/$CONFIGURATION/unittests --gtest_shuffle
- name: Set artifact id
shell: bash
run: |
id=
if [ -n "${{ matrix.simd }}" ]; then
id="${{ matrix.configuration }}-${{ matrix.arch }}-${{ matrix.simd }}"
else
id="${{ matrix.configuration }}-${{ matrix.arch }}"
fi
echo "artifact_id=${id}" >> $GITHUB_ENV
- name: Upload build result
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: windows-${{ matrix.configuration }}-${{ matrix.arch }}-${{ matrix.simd }}
name: windows-${{ env.artifact_id }}
path: ${{ github.workspace }}/build/install/*
windows_zip:
name: Build Windows distribution zip
needs: build_windows
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-748b19d
container: ghcr.io/scp-fs2open/sftp_upload:sha-ab87451
strategy:
matrix:
arch: [Win32, x64]
simd: [SSE2]
arch: [Win32, x64, ARM64]
simd: [SSE2, ""]
exclude:
- arch: Win32
simd: ""
- arch: x64
simd: ""
- arch: ARM64
simd: SSE2
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: '0'
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Set artifact id
shell: bash
run: |
id=
if [ -n "${{ matrix.simd }}" ]; then
id="${{ matrix.arch }}-${{ matrix.simd }}"
else
id="${{ matrix.arch }}"
fi
echo "artifact_id=${id}" >> $GITHUB_ENV
- name: Download Release builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: windows-Release-${{ matrix.arch }}-${{ matrix.simd }}
name: windows-Release-${{ env.artifact_id }}
path: builds
- name: Download FastDebug builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: windows-FastDebug-${{ matrix.arch }}-${{ matrix.simd }}
name: windows-FastDebug-${{ env.artifact_id }}
path: builds
- name: Create Distribution package
working-directory: ./builds
shell: bash
env:
ARCH: ${{ matrix.arch }}
SIMD: ${{ matrix.simd }}
ID: ${{ env.artifact_id }}
run: $GITHUB_WORKSPACE/ci/linux/create_dist_pack.sh Windows
- name: Upload result package
working-directory: ./builds
@@ -234,32 +285,14 @@ jobs:
name: Mac
runs-on: macos-latest
steps:
# - name: Cache Qt
# id: cache-qt-mac
# uses: actions/cache@v1
# with:
# path: ${{ github.workspace }}/../Qt
# key: ${{ runner.os }}-QtCache-${{ env.QT_VERSION }}
# - name: Install Qt
# uses: jurplel/install-qt-action@v2
# with:
# version: ${{ env.QT_VERSION }}
# dir: ${{ github.workspace }}/..
# cached: ${{ steps.cache-qt-mac.outputs.cache-hit }}
# setup-python: 'false'
# aqtversion: ==1.1.3
# py7zrversion: '==0.19.*'
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: 0
- name: Prepare Vulkan SDK
uses: humbletim/[email protected]
with:
vulkan-query-version: 1.4.304.1
vulkan-components: Vulkan-Headers, Vulkan-Loader
vulkan-use-cache: true
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Set up test version
shell: bash
run: |
@@ -272,11 +305,10 @@ jobs:
COMPILER: ${{ matrix.compiler }}
ARCHITECTURE: ${{ matrix.arch }}
JOB_CMAKE_OPTIONS: ${{ matrix.cmake_options }}
ENABLE_QTFRED: OFF
run: $GITHUB_WORKSPACE/ci/linux/configure_cmake.sh
- name: Compile
working-directory: ./build
run: LD_LIBRARY_PATH=$Qt5_DIR/lib:$LD_LIBRARY_PATH ninja all
run: ninja all
- name: Run Tests
working-directory: ./build
env:
@@ -302,7 +334,7 @@ jobs:
# Ref: https://github.com/actions/runner-images/issues/2619
run: gtar -cvzf macos-build-${{ matrix.configuration }}-${{ matrix.arch }}.tgz *.app
- name: Upload build result
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: mac-${{ matrix.configuration }}-${{ matrix.arch }}
path: ${{ github.workspace }}/build/bin/macos-build-${{ matrix.configuration }}-${{ matrix.arch }}.tgz
@@ -310,24 +342,27 @@ jobs:
name: Build Mac distribution zip
needs: build_mac
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-748b19d
container: ghcr.io/scp-fs2open/sftp_upload:sha-ab87451
strategy:
matrix:
arch: [x86_64, arm64]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: '0'
ref: '${{ github.ref }}'
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Download Release builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: mac-Release-${{ matrix.arch }}
path: builds
- name: Download FastDebug builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: mac-FastDebug-${{ matrix.arch }}
path: builds
+152 -115
View File
@@ -5,9 +5,6 @@ on:
tags:
- 'release_*'
env:
QT_VERSION: 5.12.12
jobs:
create_release:
name: Create GitHub release
@@ -50,26 +47,27 @@ jobs:
name: Linux
needs: create_release # Don't run this job until create_release is done and successful
runs-on: ${{ matrix.os }}
container: ghcr.io/scp-fs2open/linux_build:sha-71099c9
container: ghcr.io/scp-fs2open/linux_build:sha-ab87451
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v7
# checks-out your repository under $GITHUB_WORKSPACE, so your workflow can access it.
name: Checkout
with:
submodules: true # `true` to checkout submodules, `recursive` to recursively checkout submodules
ref: '${{ github.ref }}'
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Configure CMake
env:
CONFIGURATION: ${{ matrix.configuration }}
COMPILER: gcc-9
ENABLE_QTFRED: OFF
run: $GITHUB_WORKSPACE/ci/linux/configure_cmake.sh
- name: Compile
working-directory: ./build
env:
CONFIGURATION: ${{ matrix.configuration }}
run: |
LD_LIBRARY_PATH=$Qt5_DIR/lib:$LD_LIBRARY_PATH ninja -k 20 all
run: ninja -k 20 all
- name: Run Tests
working-directory: ./build
env:
@@ -82,7 +80,7 @@ jobs:
CONFIGURATION: ${{ matrix.configuration }}
run: $GITHUB_WORKSPACE/ci/linux/generate_appimage.sh $GITHUB_WORKSPACE/build/install
- name: Upload build result
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
# Upload the url file for use with other runners
# upload-artifact.inputs:
# if-no-files-found=warn What to do if path fails to find any files.
@@ -99,26 +97,29 @@ jobs:
name: Build Linux distribution zip
needs: build_linux
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-748b19d
container: ghcr.io/scp-fs2open/sftp_upload:sha-ab87451
strategy:
matrix:
arch: [x86_64, arm64]
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: '0' # value 0 to fetch all history and tags for all branches
ref: '${{ github.ref }}'
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Download Release builds
# Grab the release builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: linux-Release-${{ matrix.arch }}
path: builds
- name: Download FastDebug builds
# Grab the debug builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: linux-FastDebug-${{ matrix.arch }}
path: builds
@@ -135,7 +136,7 @@ jobs:
run: $GITHUB_WORKSPACE/ci/linux/create_dist_pack.sh Linux
- name: Upload result package
# Stash the result to artifact filespace
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ steps.generate_package.outputs.package_name }}
path: ${{ steps.generate_package.outputs.package_path }}
@@ -151,123 +152,175 @@ jobs:
strategy:
matrix:
configuration: [FastDebug, Release]
arch: [Win32, x64]
simd: [SSE2, AVX]
os: [windows-2025, windows-11-arm]
arch: [Win32, x64, ARM64]
simd: [SSE2, AVX, ""]
exclude:
- arch: Win32
os: windows-11-arm
- arch: x64
os: windows-11-arm
- arch: ARM64
os: windows-2025
- os: windows-2025
simd: ""
- os: windows-11-arm
simd: SSE2
- os: windows-11-arm
simd: AVX
needs: create_release
name: Windows
runs-on: windows-2022
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v7
# Checkout repo
name: Checkout
with:
submodules: true
ref: '${{ github.ref }}'
# - name: Cache Qt
# # Cache Qt
# id: cache-qt-win
# uses: actions/cache@v1
# with:
# path: ${{ github.workspace }}/../Qt
# key: ${{ runner.os }}-${{ matrix.arch }}-QtCache-${{ env.QT_VERSION }}
# - name: Install Qt (32 bit)
# # Install Qt 32bit if matrix.arch == Win32
# uses: jurplel/install-qt-action@v2
# if: ${{ matrix.arch == 'Win32' }}
# with:
# version: ${{ env.QT_VERSION }}
# dir: ${{ github.workspace }}/..
# arch: win32_msvc2017
# cached: ${{ steps.cache-qt-win.outputs.cache-hit }}
# aqtversion: ==0.8
# - name: Install Qt (64 bit)
# # Install Qt 64bit if matrix.arch == x64
# uses: jurplel/install-qt-action@v2
# if: ${{ matrix.arch == 'x64' }}
# with:
# version: ${{ env.QT_VERSION }}
# dir: ${{ github.workspace }}/..
# arch: win64_msvc2017_64
# cached: ${{ steps.cache-qt-win.outputs.cache-hit }}
# aqtversion: ==0.8
- name: Prepare Vulkan SDK
uses: humbletim/[email protected]
with:
vulkan-query-version: 1.4.304.1
vulkan-components: Vulkan-Headers, Vulkan-Loader
vulkan-use-cache: true
- name: Configure CMake
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Install v143 ATL/MFC
shell: pwsh
run: ci/windows/install_v143_atlmfc.ps1
# Tree A: static CRT (/MT) for FSO + FRED2 (no Qt dependency needed)
- name: Configure CMake (static CRT /MT - FSO + FRED2)
env:
CONFIGURATION: ${{ matrix.configuration }}
ARCHITECTURE: ${{ matrix.arch }}
SIMD: ${{ matrix.simd }}
shell: bash
run: |
mkdir build
cd build
if [ "$ARCHITECTURE" = "Win32" ]; then
cmake -DCMAKE_INSTALL_PREFIX="$(pwd)/install" -DFSO_USE_SPEECH="ON" \
-DFSO_USE_VOICEREC="ON" -DFORCED_SIMD_INSTRUCTIONS="$SIMD" \
-DFSO_BUILD_QTFRED=OFF -DFSO_BUILD_TESTS=ON \
-DFSO_INSTALL_DEBUG_FILES="ON" -DFSO_BUILD_WITH_VULKAN="OFF" -A "$ARCHITECTURE" \
-G "Visual Studio 17 2022" -T "v143" -DCMAKE_BUILD_TYPE=$CONFIGURATION ..
else
cmake -DCMAKE_INSTALL_PREFIX="$(pwd)/install" -DFSO_USE_SPEECH="ON" \
-DFSO_USE_VOICEREC="ON" -DFORCED_SIMD_INSTRUCTIONS="$SIMD" \
-DFSO_BUILD_QTFRED=OFF -DFSO_BUILD_TESTS=ON \
-DFSO_INSTALL_DEBUG_FILES="ON" -A "$ARCHITECTURE" \
-G "Visual Studio 17 2022" -T "v143" -DCMAKE_BUILD_TYPE=$CONFIGURATION ..
fi
- name: Compile
working-directory: ./build
mkdir build-mt
cd build-mt
cmake -DCMAKE_INSTALL_PREFIX="$(pwd)/install" -DFSO_USE_SPEECH=ON \
-DFSO_USE_VOICEREC=ON -DFORCED_SIMD_INSTRUCTIONS="$SIMD" \
-DFSO_BUILD_TESTS=ON -DFSO_INSTALL_DEBUG_FILES=ON -A "$ARCHITECTURE" \
-DFSO_BUILD_QTFRED=OFF -G "Visual Studio 18 2026" -T "v143" \
-DCMAKE_BUILD_TYPE=$CONFIGURATION ..
- name: Compile (static CRT)
working-directory: ./build-mt
env:
CONFIGURATION: ${{ matrix.configuration }}
COMPILER: ${{ matrix.compiler }}
shell: bash
run: |
cmake --build . --config "$CONFIGURATION" --target INSTALL -- /verbosity:minimal
cmake --build . --config "$CONFIGURATION" -- /verbosity:minimal
cmake --install . --config "$CONFIGURATION"
ls -alR "$(pwd)/install"
# Tree B: dynamic CRT (/MD) for QtFRED only (matches Qt's prebuilt /MD runtime)
# Skipped on Win32: Qt6 has no 32-bit Windows support, so CMake force-disables
# FSO_BUILD_QTFRED there and the qtfred target would not exist
- name: Configure CMake (dynamic CRT /MD - QtFRED)
if: matrix.arch != 'Win32'
env:
CONFIGURATION: ${{ matrix.configuration }}
ARCHITECTURE: ${{ matrix.arch }}
SIMD: ${{ matrix.simd }}
shell: bash
run: |
mkdir build-md
cd build-md
cmake -DCMAKE_INSTALL_PREFIX="$(pwd)/install" -DFSO_USE_SPEECH=ON \
-DFSO_USE_VOICEREC=ON -DFORCED_SIMD_INSTRUCTIONS="$SIMD" \
-DFSO_BUILD_TESTS=OFF -DFSO_INSTALL_DEBUG_FILES=ON -A "$ARCHITECTURE" \
-DFSO_BUILD_QTFRED=ON -DFSO_BUILD_FRED2=OFF -DQT_USE_PRECOMPILED=ON \
-G "Visual Studio 18 2026" -T "v143" -DCMAKE_BUILD_TYPE=$CONFIGURATION ..
- name: Compile (dynamic CRT)
if: matrix.arch != 'Win32'
working-directory: ./build-md
env:
CONFIGURATION: ${{ matrix.configuration }}
COMPILER: ${{ matrix.compiler }}
shell: bash
run: |
cmake --build . --config "$CONFIGURATION" --target qtfred -- /verbosity:minimal
cmake --install . --config "$CONFIGURATION" --component qtFRED
ls -alR "$(pwd)/install"
# Merge both trees into the single install dir the zip job consumes
- name: Merge installs
shell: bash
run: |
mkdir -p build/install
# build-md does not exist on Win32 (no QtFRED there)
if [ -d build-md/install ]; then
cp -r build-md/install/. build/install/
fi
cp -r build-mt/install/. build/install/
ls -alR build/install
- name: Run Tests
working-directory: ./build
working-directory: ./build-mt
env:
CONFIGURATION: ${{ matrix.configuration }}
shell: bash
run: ./bin/$CONFIGURATION/unittests --gtest_shuffle
- name: Set artifact id
shell: bash
run: |
id=
if [ -n "${{ matrix.simd }}" ]; then
id="${{ matrix.configuration }}-${{ matrix.arch }}-${{ matrix.simd }}"
else
id="${{ matrix.configuration }}-${{ matrix.arch }}"
fi
echo "artifact_id=${id}" >> $GITHUB_ENV
- name: Upload build result
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: windows-${{ matrix.configuration }}-${{ matrix.arch }}-${{ matrix.simd }}
name: windows-${{ env.artifact_id }}
path: ${{ github.workspace }}/build/install/*
windows_zip:
name: Build Windows distribution zip
needs: build_windows
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-748b19d
container: ghcr.io/scp-fs2open/sftp_upload:sha-ab87451
strategy:
fail-fast: false # Run the other jobs in the matrix instead of failing them
matrix:
arch: [Win32, x64]
simd: [SSE2, AVX]
arch: [Win32, x64, ARM64]
simd: [SSE2, AVX, ""]
exclude:
- arch: Win32
simd: ""
- arch: x64
simd: ""
- arch: ARM64
simd: SSE2
- arch: ARM64
simd: AVX
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: '0'
ref: '${{ github.ref }}'
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Set artifact id
shell: bash
run: |
id=
if [ -n "${{ matrix.simd }}" ]; then
id="${{ matrix.arch }}-${{ matrix.simd }}"
else
id="${{ matrix.arch }}"
fi
echo "artifact_id=${id}" >> $GITHUB_ENV
- name: Download Release builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: windows-Release-${{ matrix.arch }}-${{ matrix.simd }}
name: windows-Release-${{ env.artifact_id }}
path: builds
- name: Download FastDebug builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: windows-FastDebug-${{ matrix.arch }}-${{ matrix.simd }}
name: windows-FastDebug-${{ env.artifact_id }}
path: builds
- name: Create Distribution package
@@ -275,18 +328,17 @@ jobs:
working-directory: ./builds
shell: bash
env:
ARCH: ${{ matrix.arch }}
SIMD: ${{ matrix.simd }}
ID: ${{ env.artifact_id }}
run: $GITHUB_WORKSPACE/ci/linux/create_dist_pack.sh Windows
- name: Upload result package
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ steps.generate_package.outputs.package_name }}
path: ${{ steps.generate_package.outputs.package_path }}
- name: Upload debug package
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ steps.generate_package.outputs.debug_name }}
path: ${{ steps.generate_package.outputs.debug_path }}
@@ -310,43 +362,25 @@ jobs:
needs: create_release
runs-on: macos-latest
steps:
# - name: Cache Qt
# id: cache-qt-mac
# uses: actions/cache@v1
# with:
# path: ${{ github.workspace }}/../Qt
# key: ${{ runner.os }}-QtCache-${{ env.QT_VERSION }}
# - name: Install Qt
# uses: jurplel/install-qt-action@v2
# with:
# version: ${{ env.QT_VERSION }}
# dir: ${{ github.workspace }}/..
# cached: ${{ steps.cache-qt-mac.outputs.cache-hit }}
# setup-python: 'false'
# aqtversion: ==1.1.3
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: 0
ref: '${{ github.ref }}'
- name: Prepare Vulkan SDK
uses: humbletim/[email protected]
with:
vulkan-query-version: 1.4.304.1
vulkan-components: Vulkan-Headers, Vulkan-Loader
vulkan-use-cache: true
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Configure CMake
env:
CONFIGURATION: ${{ matrix.configuration }}
COMPILER: ${{ matrix.compiler }}
ARCHITECTURE: ${{ matrix.arch }}
JOB_CMAKE_OPTIONS: ${{ matrix.cmake_options }}
ENABLE_QTFRED: OFF
run: $GITHUB_WORKSPACE/ci/linux/configure_cmake.sh
- name: Compile
working-directory: ./build
run: LD_LIBRARY_PATH=$Qt5_DIR/lib:$LD_LIBRARY_PATH ninja all
run: ninja all
- name: Run Tests
working-directory: ./build
env:
@@ -372,7 +406,7 @@ jobs:
# Ref: https://github.com/actions/runner-images/issues/2619
run: gtar -cvzf macos-build-${{ matrix.configuration }}-${{ matrix.arch }}.tgz *.app
- name: Upload build result
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: mac-${{ matrix.configuration }}-${{ matrix.arch }}
path: ${{ github.workspace }}/build/bin/macos-build-${{ matrix.configuration }}-${{ matrix.arch }}.tgz
@@ -380,24 +414,27 @@ jobs:
name: Build Mac distribution zip
needs: build_mac
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-748b19d
container: ghcr.io/scp-fs2open/sftp_upload:sha-ab87451
strategy:
matrix:
arch: [x86_64, arm64]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: '0'
ref: '${{ github.ref }}'
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Download Release builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: mac-Release-${{ matrix.arch }}
path: builds
- name: Download FastDebug builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: mac-FastDebug-${{ matrix.arch }}
path: builds
@@ -413,7 +450,7 @@ jobs:
ARCH: ${{ matrix.arch }}
run: $GITHUB_WORKSPACE/ci/linux/create_dist_pack.sh Mac
- name: Upload result package
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ steps.generate_package.outputs.package_name }}
path: ${{ steps.generate_package.outputs.package_path }}
+114 -106
View File
@@ -5,22 +5,28 @@ on:
branches:
- 'test/*'
env:
QT_VERSION: 5.12.12
jobs:
build_linux:
strategy:
matrix:
configuration: [FastDebug, Release]
os: [ubuntu-latest, ubuntu-24.04-arm]
include:
- os: ubuntu-latest
arch: x86_64
- os: ubuntu-24.04-arm
arch: arm64
name: Linux
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/linux_build:sha-71099c9
runs-on: ${{ matrix.os }}
container: ghcr.io/scp-fs2open/linux_build:sha-ab87451
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Set up test version
shell: bash
run: |
@@ -31,14 +37,12 @@ jobs:
env:
CONFIGURATION: ${{ matrix.configuration }}
COMPILER: gcc-9
ENABLE_QTFRED: OFF
run: $GITHUB_WORKSPACE/ci/linux/configure_cmake.sh
- name: Compile
working-directory: ./build
env:
CONFIGURATION: ${{ matrix.configuration }}
run: |
LD_LIBRARY_PATH=$Qt5_DIR/lib:$LD_LIBRARY_PATH ninja -k 20 all
run: ninja -k 20 all
- name: Run Tests
working-directory: ./build
env:
@@ -51,34 +55,46 @@ jobs:
CONFIGURATION: ${{ matrix.configuration }}
run: $GITHUB_WORKSPACE/ci/linux/generate_appimage.sh $GITHUB_WORKSPACE/build/install
- name: Upload build result
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: linux-${{ matrix.configuration }}
name: linux-${{ matrix.configuration }}-${{ matrix.arch }}
path: ${{ github.workspace }}/build/install/*.AppImage
linux_zip:
name: Build Linux distribution zip
needs: build_linux
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-748b19d
container: ghcr.io/scp-fs2open/sftp_upload:sha-ab87451
strategy:
matrix:
arch: [x86_64, arm64]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: '0'
ref: '${{ github.ref }}'
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Download Release builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: linux-Release
name: linux-Release-${{ matrix.arch }}
path: builds
- name: Download FastDebug builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: linux-FastDebug
name: linux-FastDebug-${{ matrix.arch }}
path: builds
- name: Fix permissions
working-directory: ./builds
shell: bash
run: chmod 755 *.AppImage
- name: Create Distribution package
working-directory: ./builds
env:
ARCH: ${{ matrix.arch }}
run: $GITHUB_WORKSPACE/ci/linux/create_dist_pack.sh Linux
- name: Upload result package
working-directory: ./builds
@@ -93,51 +109,39 @@ jobs:
strategy:
matrix:
configuration: [FastDebug, Release]
arch: [Win32, x64]
simd: [SSE2]
os: [windows-2025, windows-11-arm]
arch: [Win32, x64, ARM64]
simd: [SSE2, ""]
exclude:
- arch: Win32
os: windows-11-arm
- arch: x64
os: windows-11-arm
- arch: ARM64
os: windows-2025
- os: windows-2025
simd: ""
- os: windows-11-arm
simd: SSE2
name: Windows
runs-on: windows-2022
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Set up test version
shell: bash
run: |
[[ "${{ github.ref }}" =~ ^refs\/heads\/test\/(.*)$ ]]
# Override the revision string so that the builds are named correctly
echo "set(FSO_VERSION_REVISION_STR ${BASH_REMATCH[1]})" > "version_override.cmake"
# - name: Cache Qt
# id: cache-qt-win
# uses: actions/cache@v1
# with:
# path: ${{ github.workspace }}/../Qt
# key: ${{ runner.os }}-${{ matrix.arch }}-QtCache-${{ env.QT_VERSION }}
# - name: Install Qt (32 bit)
# uses: jurplel/install-qt-action@v2
# if: ${{ matrix.arch == 'Win32' }}
# with:
# version: ${{ env.QT_VERSION }}
# dir: ${{ github.workspace }}/..
# arch: win32_msvc2017
# cached: ${{ steps.cache-qt-win.outputs.cache-hit }}
# aqtversion: ==0.8
# - name: Install Qt (64 bit)
# uses: jurplel/install-qt-action@v2
# if: ${{ matrix.arch == 'x64' }}
# with:
# version: ${{ env.QT_VERSION }}
# dir: ${{ github.workspace }}/..
# arch: win64_msvc2017_64
# cached: ${{ steps.cache-qt-win.outputs.cache-hit }}
# aqtversion: ==0.8
- name: Prepare Vulkan SDK
uses: humbletim/[email protected]
with:
vulkan-query-version: 1.4.304.1
vulkan-components: Vulkan-Headers, Vulkan-Loader
vulkan-use-cache: true
- name: Install v143 ATL/MFC
shell: pwsh
run: ci/windows/install_v143_atlmfc.ps1
- name: Configure CMake
env:
CONFIGURATION: ${{ matrix.configuration }}
@@ -147,20 +151,11 @@ jobs:
run: |
mkdir build
cd build
if [ "$ARCHITECTURE" = "Win32" ]; then
cmake -DCMAKE_INSTALL_PREFIX="$(pwd)/install" -DFSO_USE_SPEECH="ON" \
-DFSO_USE_VOICEREC="ON" -DFORCED_SIMD_INSTRUCTIONS="$SIMD" \
-DFSO_BUILD_QTFRED=OFF -DFSO_BUILD_TESTS=ON \
-DFSO_INSTALL_DEBUG_FILES="ON" -DFSO_BUILD_WITH_VULKAN="OFF" -A "$ARCHITECTURE" \
-G "Visual Studio 17 2022" -T "v143" -DCMAKE_BUILD_TYPE=$CONFIGURATION ..
else
cmake -DCMAKE_INSTALL_PREFIX="$(pwd)/install" -DFSO_USE_SPEECH="ON" \
-DFSO_USE_VOICEREC="ON" -DFORCED_SIMD_INSTRUCTIONS="$SIMD" \
-DFSO_BUILD_QTFRED=OFF -DFSO_BUILD_TESTS=ON \
-DFSO_INSTALL_DEBUG_FILES="ON" -A "$ARCHITECTURE" \
-G "Visual Studio 17 2022" -T "v143" -DCMAKE_BUILD_TYPE=$CONFIGURATION ..
fi
cmake -DCMAKE_INSTALL_PREFIX="$(pwd)/install" -DFSO_USE_SPEECH=ON \
-DFSO_USE_VOICEREC=ON -DFORCED_SIMD_INSTRUCTIONS="$SIMD" \
-DFSO_BUILD_TESTS=ON -DFSO_INSTALL_DEBUG_FILES=ON -A "$ARCHITECTURE" \
-DQT_USE_PRECOMPILED=ON -G "Visual Studio 18 2026" -T "v143" \
-DCMAKE_BUILD_TYPE=$CONFIGURATION ..
- name: Compile
working-directory: ./build
env:
@@ -176,43 +171,72 @@ jobs:
CONFIGURATION: ${{ matrix.configuration }}
shell: bash
run: ./bin/$CONFIGURATION/unittests --gtest_shuffle
- name: Set artifact id
shell: bash
run: |
id=
if [ -n "${{ matrix.simd }}" ]; then
id="${{ matrix.configuration }}-${{ matrix.arch }}-${{ matrix.simd }}"
else
id="${{ matrix.configuration }}-${{ matrix.arch }}"
fi
echo "artifact_id=${id}" >> $GITHUB_ENV
- name: Upload build result
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: windows-${{ matrix.configuration }}-${{ matrix.arch }}-${{ matrix.simd }}
name: windows-${{ env.artifact_id }}
path: ${{ github.workspace }}/build/install/*
windows_zip:
name: Build Windows distribution zip
needs: build_windows
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-748b19d
container: ghcr.io/scp-fs2open/sftp_upload:sha-ab87451
strategy:
matrix:
arch: [Win32, x64]
simd: [SSE2]
arch: [Win32, x64, ARM64]
simd: [SSE2, ""]
exclude:
- arch: Win32
simd: ""
- arch: x64
simd: ""
- arch: ARM64
simd: SSE2
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: '0'
ref: '${{ github.ref }}'
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Set artifact id
shell: bash
run: |
id=
if [ -n "${{ matrix.simd }}" ]; then
id="${{ matrix.arch }}-${{ matrix.simd }}"
else
id="${{ matrix.arch }}"
fi
echo "artifact_id=${id}" >> $GITHUB_ENV
- name: Download Release builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: windows-Release-${{ matrix.arch }}-${{ matrix.simd }}
name: windows-Release-${{ env.artifact_id }}
path: builds
- name: Download FastDebug builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: windows-FastDebug-${{ matrix.arch }}-${{ matrix.simd }}
name: windows-FastDebug-${{ env.artifact_id }}
path: builds
- name: Create Distribution package
working-directory: ./builds
shell: bash
env:
ARCH: ${{ matrix.arch }}
SIMD: ${{ matrix.simd }}
ID: ${{ env.artifact_id }}
run: $GITHUB_WORKSPACE/ci/linux/create_dist_pack.sh Windows
- name: Upload result package
working-directory: ./builds
@@ -233,32 +257,14 @@ jobs:
name: Mac
runs-on: macos-latest
steps:
# - name: Cache Qt
# id: cache-qt-mac
# uses: actions/cache@v1
# with:
# path: ${{ github.workspace }}/../Qt
# key: ${{ runner.os }}-QtCache-${{ env.QT_VERSION }}
# - name: Install Qt
# uses: jurplel/install-qt-action@v2
# with:
# version: ${{ env.QT_VERSION }}
# dir: ${{ github.workspace }}/..
# cached: ${{ steps.cache-qt-mac.outputs.cache-hit }}
# setup-python: 'false'
# aqtversion: ==1.1.3
# py7zrversion: '==0.19.*'
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: 0
- name: Prepare Vulkan SDK
uses: humbletim/[email protected]
with:
vulkan-query-version: 1.4.304.1
vulkan-components: Vulkan-Headers, Vulkan-Loader
vulkan-use-cache: true
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Set up test version
shell: bash
run: |
@@ -270,11 +276,10 @@ jobs:
CONFIGURATION: ${{ matrix.configuration }}
COMPILER: ${{ matrix.compiler }}
ARCHITECTURE: ${{ matrix.arch }}
ENABLE_QTFRED: OFF
run: $GITHUB_WORKSPACE/ci/linux/configure_cmake.sh
- name: Compile
working-directory: ./build
run: LD_LIBRARY_PATH=$Qt5_DIR/lib:$LD_LIBRARY_PATH ninja all
run: ninja all
- name: Run Tests
working-directory: ./build
env:
@@ -300,7 +305,7 @@ jobs:
# Ref: https://github.com/actions/runner-images/issues/2619
run: gtar -cvzf macos-build-${{ matrix.configuration }}-${{ matrix.arch }}.tgz *.app
- name: Upload build result
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: mac-${{ matrix.configuration }}-${{ matrix.arch }}
path: ${{ github.workspace }}/build/bin/macos-build-${{ matrix.configuration }}-${{ matrix.arch }}.tgz
@@ -308,24 +313,27 @@ jobs:
name: Build Mac distribution zip
needs: build_mac
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-748b19d
container: ghcr.io/scp-fs2open/sftp_upload:sha-ab87451
strategy:
matrix:
arch: [x86_64, arm64]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: '0'
ref: '${{ github.ref }}'
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Download Release builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: mac-Release-${{ matrix.arch }}
path: builds
- name: Download FastDebug builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: mac-FastDebug-${{ matrix.arch }}
path: builds
+52 -10
View File
@@ -5,15 +5,13 @@ on:
branches:
- master
env:
QT_VERSION: 5.12.12
jobs:
build_linux:
strategy:
matrix:
configuration: [Debug, Release]
compiler: [gcc-9, gcc-13, clang-16]
arch: [x86_64]
cmake_options: [""]
include:
# Also include configurations that check if the code compiles without the graphics backends
@@ -25,16 +23,20 @@ jobs:
cmake_options: -DFSO_BUILD_WITH_OPENGL=OFF -DFSO_BUILD_WITH_VULKAN=OFF
name: Linux
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/linux_build:sha-71099c9
container: ghcr.io/scp-fs2open/linux_build:sha-ab87451
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: ccache
uses: hendrikmuhs/ccache-action@v1
uses: hendrikmuhs/ccache-action@v1.2.23
with:
key: ${{ runner.os }}-${{ matrix.configuration }}-${{ matrix.compiler }}
key: ${{ runner.os }}-${{ matrix.configuration }}-${{ matrix.compiler }}-${{ matrix.arch }}
max-size: 750MB
- name: Configure ccache
run: |
ccache --set-config=sloppiness=pch_defines,time_macros
@@ -46,11 +48,51 @@ jobs:
COMPILER: ${{ matrix.compiler }}
JOB_CMAKE_OPTIONS: ${{ matrix.cmake_options }}
CCACHE_PATH: /usr/local/bin/ccache
ENABLE_QTFRED: ON
Qt5_DIR: /qt/${{ env.QT_VERSION }}/gcc_64/lib/cmake/Qt5
run: $GITHUB_WORKSPACE/ci/linux/configure_cmake.sh
- name: Compile
working-directory: ./build
run: LD_LIBRARY_PATH=$Qt5_DIR/lib:$LD_LIBRARY_PATH ninja -k 20 all
run: ninja -k 20 all
- name: Show CCache statistics
run: ccache --show-stats
build_mac:
strategy:
matrix:
configuration: [Debug, Release]
compiler: [clang]
arch: [x86_64, arm64]
cmake_options: [""]
name: Mac
runs-on: macos-latest
steps:
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: ccache
uses: hendrikmuhs/[email protected]
with:
key: ${{ runner.os }}-${{ matrix.configuration }}-${{ matrix.arch }}
max-size: 750MB
- name: Configure ccache
run: |
ccache --set-config=sloppiness=pch_defines,time_macros
ccache -p
- name: Configure CMake
env:
CONFIGURATION: ${{ matrix.configuration }}
COMPILER: ${{ matrix.compiler }}
ARCHITECTURE: ${{ matrix.arch }}
JOB_CMAKE_OPTIONS: ${{ matrix.cmake_options }}
CCACHE_PATH: /usr/local/bin/ccache
run: $GITHUB_WORKSPACE/ci/linux/configure_cmake.sh
- name: Compile
working-directory: ./build
run: ninja -k 20 all
- name: Show CCache statistics
run: ccache --show-stats
+6 -2
View File
@@ -33,6 +33,7 @@ env:
NEBULA_PASSWORD: ${{ secrets.NEBULA_PASSWORD }}
HLP_API: ${{ secrets.HLP_API }}
HLP_KEY: ${{ secrets.HLP_KEY }}
FORUM_SECRET: ${{ secrets.FORUM_SECRET }}
RELEASE_TAG: ${{ inputs.releaseTag }}
jobs:
@@ -45,11 +46,13 @@ jobs:
run: echo "github.event_name= ${{ github.event_name }}"
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v7
with:
fetch-depth: '0'
ref: ${{ env.RELEASE_TAG }}
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Install Python dependencies
run: pip install -r ci/post/requirements.txt
@@ -67,6 +70,7 @@ jobs:
HLP_API: ${{ secrets.HLP_API }}
HLP_KEY: ${{ secrets.HLP_KEY }}
RELEASE_TAG: ${{ inputs.releaseTag }}
FORUM_SECRET: ${{ secrets.FORUM_SECRET }}
run: python ci/post/main.py release
- name: Post Builds (Manual trigger)
+50 -90
View File
@@ -2,9 +2,6 @@ name: Check a pull request
on: [pull_request]
env:
QT_VERSION: 5.12.12
jobs:
build_linux:
strategy:
@@ -20,23 +17,26 @@ jobs:
- configuration: Release
compiler: gcc-13
cmake_options: -DFSO_BUILD_WITH_OPENGL=OFF -DFSO_BUILD_WITH_VULKAN=OFF
- configuration: Debug
compiler: gcc-13
cmake_options: -DFSO_BUILD_WITH_OPENGL=OFF -DFSO_BUILD_WITH_VULKAN=ON
- configuration: Release
compiler: gcc-13
cmake_options: -DFSO_BUILD_WITH_OPENGL=OFF -DFSO_BUILD_WITH_VULKAN=ON
# - configuration: Debug
# compiler: gcc-13
# cmake_options: -DFSO_BUILD_WITH_OPENGL=OFF -DFSO_BUILD_WITH_VULKAN=ON
# - configuration: Release
# compiler: gcc-13
# cmake_options: -DFSO_BUILD_WITH_OPENGL=OFF -DFSO_BUILD_WITH_VULKAN=ON
name: Linux
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/linux_build:sha-71099c9
container: ghcr.io/scp-fs2open/linux_build:sha-ab87451
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: 0
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: ccache
uses: hendrikmuhs/ccache-action@v1
uses: hendrikmuhs/ccache-action@v1.2.23
with:
key: ${{ runner.os }}-${{ matrix.configuration }}-${{ matrix.compiler }}
save: false # Caches are created by a separate job and only restored for PRs
@@ -49,12 +49,10 @@ jobs:
COMPILER: ${{ matrix.compiler }}
JOB_CMAKE_OPTIONS: ${{ matrix.cmake_options }}
CCACHE_PATH: /usr/local/bin/ccache
ENABLE_QTFRED: ON
Qt5_DIR: /qt/${{ env.QT_VERSION }}/gcc_64/lib/cmake/Qt5
run: $GITHUB_WORKSPACE/ci/linux/configure_cmake.sh
- name: Compile
working-directory: ./build
run: LD_LIBRARY_PATH=$Qt5_DIR/lib:$LD_LIBRARY_PATH ninja -k 20 all
run: ninja -k 20 all
- name: Run Tests
working-directory: ./build
env:
@@ -80,44 +78,21 @@ jobs:
compiler: [MSVC]
arch: [Win32, x64]
name: Windows
runs-on: windows-2022
runs-on: windows-2025
steps:
- name: Prepare Environment
run: choco install ninja
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
# - name: Cache Qt
# id: cache-qt-win
# uses: actions/cache@v1
# with:
# path: ${{ github.workspace }}/../Qt
# key: ${{ runner.os }}-${{ matrix.arch }}-QtCache-${{ env.QT_VERSION }}
# - name: Install Qt (32 bit)
# uses: jurplel/install-qt-action@v2
# if: ${{ matrix.compiler == 'MSVC' && matrix.arch == 'Win32' }}
# with:
# version: ${{ env.QT_VERSION }}
# dir: ${{ github.workspace }}/..
# arch: win32_msvc2017
# cached: ${{ steps.cache-qt-win.outputs.cache-hit }}
# aqtversion: ==0.8
# - name: Install Qt (64 bit)
# uses: jurplel/install-qt-action@v2
# if: ${{ matrix.compiler == 'MSVC' && matrix.arch == 'x64' }}
# with:
# version: ${{ env.QT_VERSION }}
# dir: ${{ github.workspace }}/..
# arch: win64_msvc2017_64
# cached: ${{ steps.cache-qt-win.outputs.cache-hit }}
# aqtversion: ==0.8
- name: Prepare Vulkan SDK
uses: humbletim/[email protected]
with:
vulkan-query-version: 1.4.304.1
vulkan-components: Vulkan-Headers, Vulkan-Loader
vulkan-use-cache: true
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Install v143 ATL/MFC
if: matrix.compiler == 'MSVC'
shell: pwsh
run: ci/windows/install_v143_atlmfc.ps1
- name: Configure CMake
env:
CONFIGURATION: ${{ matrix.configuration }}
@@ -129,23 +104,12 @@ jobs:
cd build
if [ "$COMPILER" = "MinGW" ]; then
if [ "$ARCHITECTURE" = "Win32" ]; then
cmake -DFSO_USE_SPEECH="OFF" -DFSO_FATAL_WARNINGS="ON" -DFSO_USE_VOICEREC="OFF" -DFSO_BUILD_TESTS="ON" \
-DFSO_BUILD_FRED2="OFF" -DFSO_BUILD_WITH_VULKAN="OFF" -DCMAKE_BUILD_TYPE=$CONFIGURATION -G "Ninja" ..
else
cmake -DFSO_USE_SPEECH="OFF" -DFSO_FATAL_WARNINGS="ON" -DFSO_USE_VOICEREC="OFF" -DFSO_BUILD_TESTS="ON" \
-DFSO_BUILD_FRED2="OFF" -DCMAKE_BUILD_TYPE=$CONFIGURATION -G "Ninja" ..
fi
cmake -DFSO_USE_SPEECH=OFF -DFSO_FATAL_WARNINGS=ON -DFSO_USE_VOICEREC=OFF -DFSO_BUILD_TESTS=ON \
-DFSO_BUILD_FRED2=OFF -DQT_USE_PRECOMPILED=ON -DCMAKE_BUILD_TYPE=$CONFIGURATION -G "Ninja" ..
else
if [ "$ARCHITECTURE" = "Win32" ]; then
cmake -DFSO_USE_SPEECH="ON" -DFSO_FATAL_WARNINGS="ON" -DFSO_USE_VOICEREC="OFF" -DFSO_BUILD_TESTS="ON" \
-DFORCED_SIMD_INSTRUCTIONS=SSE2 -DFSO_BUILD_FRED2="ON" -DFSO_BUILD_WITH_VULKAN="OFF" -G "Visual Studio 17 2022" \
-DFSO_BUILD_QTFRED=OFF -T "v143" -A "$ARCHITECTURE" -DCMAKE_BUILD_TYPE=$CONFIGURATION ..
else
cmake -DFSO_USE_SPEECH="ON" -DFSO_FATAL_WARNINGS="ON" -DFSO_USE_VOICEREC="OFF" -DFSO_BUILD_TESTS="ON" \
-DFORCED_SIMD_INSTRUCTIONS=SSE2 -DFSO_BUILD_FRED2="ON" -G "Visual Studio 17 2022" \
-DFSO_BUILD_QTFRED=OFF -T "v143" -A "$ARCHITECTURE" -DCMAKE_BUILD_TYPE=$CONFIGURATION ..
fi
cmake -DFSO_USE_SPEECH=ON -DFSO_FATAL_WARNINGS=ON -DFSO_USE_VOICEREC=OFF -DFSO_BUILD_TESTS=ON \
-DFORCED_SIMD_INSTRUCTIONS=SSE2 -G "Visual Studio 18 2026" -T "v143" -A "$ARCHITECTURE" \
-DQT_USE_PRECOMPILED=ON -DCMAKE_BUILD_TYPE=$CONFIGURATION ..
fi
- name: Compile
working-directory: ./build
@@ -182,48 +146,32 @@ jobs:
name: Mac
runs-on: macos-latest
steps:
# - name: Cache Qt
# id: cache-qt-mac
# uses: actions/cache@v1
# with:
# path: ${{ github.workspace }}/../Qt
# key: ${{ runner.os }}-QtCache-${{ env.QT_VERSION }}
# - name: Install Qt
# uses: jurplel/install-qt-action@v2
# with:
# version: ${{ env.QT_VERSION }}
# dir: ${{ github.workspace }}/..
# cached: ${{ steps.cache-qt-mac.outputs.cache-hit }}
# setup-python: 'false'
# aqtversion: ==1.1.3
# py7zrversion: '==0.19.*'
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: 0
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: ccache
uses: hendrikmuhs/ccache-action@v1
uses: hendrikmuhs/ccache-action@v1.2.23
with:
key: ${{ runner.os }}-${{ matrix.configuration }}-${{ matrix.compiler }}-${{ matrix.arch }}
save: false # Caches are created by a separate job and only restored for PRs
- name: Prepare Vulkan SDK
uses: humbletim/[email protected]
with:
vulkan-query-version: 1.4.304.1
vulkan-components: Vulkan-Headers, Vulkan-Loader
vulkan-use-cache: true
- name: Configure ccache
run: ccache --set-config=sloppiness=pch_defines,time_macros
- name: Configure CMake
env:
CONFIGURATION: ${{ matrix.configuration }}
COMPILER: ${{ matrix.compiler }}
ARCHITECTURE: ${{ matrix.arch }}
JOB_CMAKE_OPTIONS: ${{ matrix.cmake_options }}
ENABLE_QTFRED: OFF
CCACHE_PATH: /usr/local/bin/ccache
run: $GITHUB_WORKSPACE/ci/linux/configure_cmake.sh
- name: Compile
working-directory: ./build
run: LD_LIBRARY_PATH=$Qt5_DIR/lib:$LD_LIBRARY_PATH ninja all
run: ninja all
- name: Run Tests
working-directory: ./build
env:
@@ -231,12 +179,24 @@ jobs:
ARCH: ${{ matrix.arch }}
XDG_RUNTIME_DIR: /root
run: $GITHUB_WORKSPACE/ci/linux/run_tests.sh
- name: Show CCache statistics
run: ccache --show-stats
- name: Install clang-tidy
# macOS runners don't ship clang-tidy; pin to the same version as the Linux
# container's clang-tidy-16 for consistent diagnostics. pyyaml is required
# too: clang-tidy-diff.py only accepts -export-fixes when it can import yaml.
# --break-system-packages is required since the runner's Homebrew Python
# is externally managed (PEP 668); the runner is ephemeral so this is safe.
if: startsWith(matrix.compiler, 'clang')
run: pip3 install --break-system-packages clang-tidy==16.0.4 pyyaml
- name: Run Clang Tidy
# Clang-tidy reuses the precompiled headers so this only makes sense for the clang compilers
if: startsWith(matrix.compiler, 'clang-')
if: startsWith(matrix.compiler, 'clang')
env:
CLANG_TIDY_BINARY: clang-tidy
run: $GITHUB_WORKSPACE/ci/linux/clang_tidy.sh ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }}
- name: Process clang-tidy warnings
if: startsWith(matrix.compiler, 'clang-')
if: startsWith(matrix.compiler, 'clang')
uses: asarium/clang-tidy-action@v1
with:
fixesFile: clang-fixes.yaml
+5 -4
View File
@@ -11,7 +11,6 @@ on:
- cron: "5 0 * * 5" # Run once per a week on Friday morning (midnight UTC, 4 AM EST), to avoid Coverity's submission limits
env:
QT_VERSION: 5.12.12
coverity_email: [email protected]
coverity_token: ${{ secrets.COVERITY_TOKEN }}
@@ -20,7 +19,7 @@ jobs:
name: Build FSO With Coverity Wrapper
if: github.repository == 'scp-fs2open/fs2open.github.com'
runs-on: ${{ matrix.config.os }}
container: ghcr.io/scp-fs2open/linux_build:sha-71099c9
container: ghcr.io/scp-fs2open/linux_build:sha-ab87451
strategy:
fail-fast: false
matrix:
@@ -29,9 +28,12 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@v7
with:
submodules: recursive # `true` to checkout submodules, `recursive` to recursively checkout submodules
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
# pipe results of wget to a log file because it's very verbose and rarely fails
- name: Install Coverity
@@ -49,7 +51,6 @@ jobs:
env:
CONFIGURATION: Release
COMPILER: gcc-9
ENABLE_QTFRED: OFF
run: $GITHUB_WORKSPACE/ci/linux/configure_cmake.sh
- name: Build FSO with Coverity Wrapper
+5
View File
@@ -262,3 +262,8 @@ CMakePresets.json
!/docker/build
CMakeFiles/cmake.check_cache
CMakeCache.txt
# Written by the unittests binary at runtime
test/test_data/**/*_settings.ini
test/test_data/data/players/presets/asdf.json
test/test_data/pilotfile/data/asdf.hdp
-3
View File
@@ -1,6 +1,3 @@
[submodule "cmake/external/rpavlik-cmake-modules"]
path = cmake/external/rpavlik-cmake-modules
url = https://github.com/rpavlik/cmake-modules.git
[submodule "lib/libRocket"]
path = lib/libRocket
url = https://github.com/scp-fs2open/libRocket.git
+68 -31
View File
@@ -29,33 +29,57 @@ ENDIF(NOT DEFINED CMAKE_INSTALL_PREFIX)
SET(TARGET_COPY_FILES "" CACHE INTERNAL "" FORCE)
SET(TARGET_COPY_DIRS "" CACHE INTERNAL "" FORCE)
### Define options and editable variables in the section below:
# This is only needed for single configuration generators so don't annoy others
if(NOT CMAKE_BUILD_TYPE AND NOT DEFINED CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Default build configuration used." FORCE)
message(WARNING "CMAKE_BUILD_TYPE was not specified, defaulting to Release configuration.")
endif(NOT CMAKE_BUILD_TYPE AND NOT DEFINED CMAKE_CONFIGURATION_TYPES)
get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
# Override standard CMake configurations
set(CMAKE_CONFIGURATION_TYPES Debug Release FastDebug CACHE STRING "Available configurations" FORCE)
set(FSO_BUILD_TYPES Debug Release FastDebug)
if(is_multi_config)
set(CMAKE_CONFIGURATION_TYPES "${FSO_BUILD_TYPES}" CACHE STRING "Configuration types" FORCE)
elseif(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Default build configuration used." FORCE)
message(WARNING "CMAKE_BUILD_TYPE was not specified, defaulting to Release configuration.")
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "${FSO_BUILD_TYPES}")
endif()
set(FSO_LANGUAGES C CXX)
if(APPLE)
set(FSO_LANGUAGES ${FSO_LANGUAGES} OBJC OBJCXX)
# 10.15 for c++17 std::filesystem
# NOTE: this must be set *before* the project() call to work properly!!
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "macOS deployment target")
# fix all targets in scheme (so it's not just qtfred)
set(CMAKE_XCODE_GENERATE_SCHEME TRUE)
endif()
PROJECT(FS2_Open LANGUAGES ${FSO_LANGUAGES})
if (ANDROID)
message(STATUS "Building for Android (NDK)")
set(FSO_BUILD_INCLUDED_LIBS ON CACHE BOOL "" FORCE)
set(USING_PREBUILT_LIBS ON CACHE BOOL "" FORCE)
set(SDL3_USE_PRECOMPILED ON CACHE BOOL "" FORCE)
set(OPENAL_USE_PRECOMPILED ON CACHE BOOL "" FORCE)
set(USING_PREBUILT_VULKAN ON CACHE BOOL "" FORCE)
endif()
# Check if the external modules exists
IF(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/cmake/external/rpavlik-cmake-modules/launcher-templates")
IF(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lib/libRocket/Build")
message(FATAL_ERROR "External submodules could not be found. Please make sure you have updated your submodules.")
endif()
LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/finder")
LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/external/rpavlik-cmake-modules")
LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/external/cotire")
# this must be set before include(toolchain)
if(WIN32 AND CMAKE_SIZEOF_VOID_P EQUAL 4)
set(FSO_BUILD_QTFRED OFF CACHE INTERNAL "" FORCE)
else()
OPTION(FSO_BUILD_QTFRED "Build qtFRED2 binary" ON)
endif()
INCLUDE(globals)
INCLUDE(toolchain)
@@ -66,7 +90,7 @@ IF(RESET_INSTALL_PREFIX)
IF(NOT IS_DIRECTORY $ENV{FS2PATH})
MESSAGE(WARNING "The path '$ENV{FS2PATH}' of the environment variable FS2PATH does not point to a directory! Install prefix wasn't set.")
ELSE(NOT IS_DIRECTORY $ENV{FS2PATH})
SET(CMAKE_INSTALL_PREFIX "$ENV{FS2PATH}" CACHE FILEPATH "Install path" FORCE)
SET(CMAKE_INSTALL_PREFIX "$ENV{FS2PATH}" CACHE PATH "Install path" FORCE)
FILE(TO_CMAKE_PATH ${CMAKE_INSTALL_PREFIX} CMAKE_INSTALL_PREFIX)
ENDIF(NOT IS_DIRECTORY $ENV{FS2PATH})
ELSE(NOT $ENV{FS2PATH} STREQUAL "")
@@ -74,9 +98,11 @@ IF(RESET_INSTALL_PREFIX)
ENDIF(NOT $ENV{FS2PATH} STREQUAL "")
ENDIF(RESET_INSTALL_PREFIX)
IF(WIN32 OR APPLE)
IF(WIN32 OR APPLE OR ANDROID OR CMAKE_SYSTEM_NAME STREQUAL "Linux")
OPTION(FSO_USE_SPEECH "Use text-to-speach libraries" ON)
ENDIF(WIN32 OR APPLE)
ELSE()
OPTION(FSO_USE_SPEECH "Use text-to-speach libraries" OFF)
ENDIF()
IF (WIN32)
OPTION(FSO_USE_VOICEREC "Enable voice recognition support" ON)
@@ -92,10 +118,8 @@ OPTION(FSO_BUILD_TESTS "Build unit tests" OFF)
OPTION(FSO_DEVELOPMENT_MODE "Generate binaries in development mode, only use if you know what you're doing!" OFF)
OPTION(FSO_BUILD_QTFRED "Build qtFRED2 binary" OFF)
IF(WIN32 OR APPLE)
# On windows and mac the default should be to always build the included libraries
IF(WIN32 OR APPLE OR ANDROID)
# On windows, mac and android the default should be to always build the included libraries
SET(FSO_BUILD_INCLUDED_LIBS_DEFAULT ON)
ELSE()
SET(FSO_BUILD_INCLUDED_LIBS_DEFAULT OFF)
@@ -109,14 +133,12 @@ OPTION(FSO_USE_OPENALSOFT "Download and build OpenAL Soft instead of using the s
OPTION(FSO_FATAL_WARNINGS "Determines if warnings in the build are considered fatal errors, primarily used for CI" OFF)
SET(FSO_FREESPACE_PATH "${CMAKE_INSTALL_PREFIX}" CACHE FILEPATH "The path of the FreeSpace directory you want to use. Please note that you will have to change CMAKE_INSTALL_PREFIX if you change this at some point.")
SET(FSO_FREESPACE_PATH "${CMAKE_INSTALL_PREFIX}" CACHE PATH "The path of the FreeSpace directory you want to use. Please note that you will have to change CMAKE_INSTALL_PREFIX if you change this at some point.")
SET(FSO_RUN_ARGUMENTS "" CACHE STRING "Additional arguments passed to a generated executable when run with the generated build files.")
option(FSO_INSTALL_DEBUG_FILES "Install some debug files (currently only PDB files on windows)" OFF)
option(ENABLE_COTIRE "Enable cotire for faster compilation. Enabled by default." ON)
option(FSO_RELEASE_LOGGING "Enable logging output for release builds" OFF)
OPTION(FSO_BUILD_WITH_FFMPEG "Enable the usage of FFmpeg for sound and custscenes" ON)
@@ -125,10 +147,25 @@ OPTION(FSO_BUILD_WITH_DISCORD "Build with Discord support" ON)
OPTION(FSO_BUILD_WITH_OPENGL "Enable compilation of the OpenGL renderer" ON)
OPTION(FSO_BUILD_WITH_OPENGL_DEBUG "Enables debug option for OpenGL" OFF)
OPTION(FSO_BUILD_WITH_OPENGL_ES "When building OpenGL, use OpenGL ES compatibility layer" OFF)
OPTION(FSO_BUILD_WITH_VULKAN "Enable compilation of the Vulkan renderer" OFF)
OPTION(FSO_BUILD_WITH_VULKAN "Enable compilation of the Vulkan renderer" ON)
IF(NOT APPLE)
if(NOT FSO_BUILD_WITH_OPENGL AND FSO_BUILD_WITH_OPENGL_ES)
# Disable GLES if not building with OpenGL
set(FSO_BUILD_WITH_OPENGL_ES OFF CACHE BOOL "" FORCE)
endif()
if(FSO_BUILD_WITH_OPENGL_ES)
add_definitions(-DUSE_OPENGL_ES)
IF(FSO_BUILD_WITH_OPENXR)
MESSAGE(WARNING "FSO_BUILD_WITH_OPENXR is ON. But not supported on OpenGL ES for now - setting to OFF.")
SET(FSO_BUILD_WITH_OPENXR OFF CACHE BOOL "" FORCE)
endif()
endif()
IF(NOT APPLE AND NOT FSO_BUILD_WITH_OPENGL_ES)
SET(OPENXR_BUILD_DEFAULT ON)
ELSE()
SET(OPENXR_BUILD_DEFAULT OFF)
@@ -150,12 +187,12 @@ MARK_AS_ADVANCED(FORCE FSO_USE_LUAJIT)
MARK_AS_ADVANCED(FORCE FSO_DEVELOPMENT_MODE)
MARK_AS_ADVANCED(FORCE FSO_FATAL_WARNINGS)
mark_as_advanced(FORCE FSO_INSTALL_DEBUG_FILES)
mark_as_advanced(FORCE ENABLE_COTIRE)
mark_as_advanced(FORCE FSO_RELEASE_LOGGING)
mark_as_advanced(FORCE FSO_BUILD_WITH_FFMPEG)
mark_as_advanced(FORCE FSO_BUILD_WITH_DISCORD)
mark_as_advanced(FORCE FSO_BUILD_WITH_OPENGL)
mark_as_advanced(FORCE FSO_BUILD_WITH_OPENGL_DEBUG)
mark_as_advanced(FORCE FSO_BUILD_WITH_OPENGL_ES)
mark_as_advanced(FORCE FSO_BUILD_WITH_VULKAN)
mark_as_advanced(FORCE FSO_BUILD_WITH_OPENXR)
mark_as_advanced(FORCE FSO_BUILD_WITH_OPENXR_DEBUG)
@@ -170,18 +207,19 @@ SET(GENERATED_SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated_source)
include(generateHeaders)
if(FSO_BUILD_APPIMAGE)
include(appimage)
SET(BINARY_DESTINATION "bin")
SET(LIBRAY_DESTINATION "lib")
endif()
include(clang-tidy)
include(CheckIPOSupported)
check_ipo_supported(RESULT LTO_AVAILABLE)
if (CMAKE_BUILD_TYPE STREQUAL "Release" AND FSO_USE_LTO AND LTO_AVAILABLE)
if (FSO_USE_LTO AND LTO_AVAILABLE)
set(CMAKE_POLICY_DEFAULT_CMP0069 NEW)
message("Enabled LTO")
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
endif (CMAKE_BUILD_TYPE STREQUAL "Release" AND FSO_USE_LTO AND LTO_AVAILABLE)
message("Enabled LTO for Release builds")
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
endif (FSO_USE_LTO AND LTO_AVAILABLE)
# This includes source code for some tools, either used in the build or also for something else
ADD_SUBDIRECTORY(tools)
@@ -226,9 +264,7 @@ include(package)
include(doxygen)
# Print used options to log
IF(WIN32 OR APPLE)
message(STATUS "Using text to speech: ${FSO_USE_SPEECH}")
ENDIF()
message(STATUS "Using text to speech: ${FSO_USE_SPEECH}")
IF (WIN32)
message(STATUS "Using voice recogition: ${FSO_USE_VOICEREC}")
message(STATUS "Building FRED2: ${FSO_BUILD_FRED2}")
@@ -242,5 +278,6 @@ message(STATUS "Release logging: ${FSO_RELEASE_LOGGING}")
message(STATUS "With FFmpeg: ${FSO_BUILD_WITH_FFMPEG}")
message(STATUS "With Discord: ${FSO_BUILD_WITH_DISCORD}")
message(STATUS "With OpenGL: ${FSO_BUILD_WITH_OPENGL}")
message(STATUS "With OpenGL ES: ${FSO_BUILD_WITH_OPENGL_ES}")
message(STATUS "With Vulkan: ${FSO_BUILD_WITH_VULKAN}")
message(STATUS "With OpenXR: ${FSO_BUILD_WITH_OPENXR}")
+7 -4
View File
@@ -18,13 +18,16 @@ fi
# branch
BASE_COMMIT=$(git merge-base $1 $2)
# Allow the caller to override the clang-tidy binary and job count, since neither
# /usr/bin/clang-tidy-16 nor nproc are available outside the Linux CI container
# (e.g. macOS runners install clang-tidy via pip and use sysctl for core count).
CLANG_TIDY_BINARY="${CLANG_TIDY_BINARY:-/usr/bin/clang-tidy-16}"
NUM_JOBS="${NUM_JOBS:-$( (command -v nproc >/dev/null 2>&1 && nproc) || sysctl -n hw.ncpu )}"
# Note: Manually passing in the Vulkan flags that are normally provided by cmake (but are not so, here), to ensure
# that the source files are checked with the actual configuration used.
echo "Running clang-tidy on changed files"
git diff -U0 --no-color "$BASE_COMMIT..$2" | \
$HERE/clang-tidy-diff.py -path "$(pwd)/build" -p1 \
-extra-arg="-DWITH_VULKAN" \
-extra-arg="-DVULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1" \
-extra-arg="-DVK_NO_PROTOTYPES" \
-regex '(code(?!((\/graphics\/shaders\/compiled)|(\/globalincs\/windebug)|(\/def_files\/data)))|freespace2|qtfred|test\/src|build|tools)\/.*\.(cpp|h)' \
-clang-tidy-binary /usr/bin/clang-tidy-16 -j$(nproc) -export-fixes "$(pwd)/clang-fixes.yaml"
-clang-tidy-binary "$CLANG_TIDY_BINARY" -j"$NUM_JOBS" -export-fixes "$(pwd)/clang-fixes.yaml"
+25 -10
View File
@@ -13,21 +13,32 @@ if [ "$COMPILER" = "clang-16" ]; then
export CXX=clang++-16
fi
LD_LIBRARY_PATH=$Qt5_DIR/lib:$LD_LIBRARY_PATH
if [ "$RUNNER_OS" = "macOS" ]; then
CXXFLAGS="-mtune=generic -pipe -Wno-unknown-pragmas"
CFLAGS="-mtune=generic -pipe -Wno-unknown-pragmas"
# TODO: Vulkan support is disabled on MacOS due to issues with the test suite not linking correctly
PLATFORM_CMAKE_OPTIONS="-DFSO_BUILD_WITH_VULKAN=OFF"
export CMAKE_OSX_ARCHITECTURES="$ARCHITECTURE"
else
PLATFORM_CMAKE_OPTIONS="-DFSO_BUILD_APPIMAGE=ON -DFORCED_SIMD_INSTRUCTIONS=SSE2 -DUSE_STATIC_LIBCXX=ON"
PLATFORM_CMAKE_OPTIONS="-DFSO_BUILD_APPIMAGE=ON -DFORCED_SIMD_INSTRUCTIONS=SSE2"
# Optionally use static libstdc++/libc++ on Linux, but default to off
# (Issue #5571 enabled it as a fix for 20.04 compatibility, but we've moved to 22.04)
PLATFORM_CMAKE_OPTIONS="$PLATFORM_CMAKE_OPTIONS -DUSE_STATIC_LIBCXX=${USE_STATIC_LIBCXX:-OFF}"
fi
CMAKE_OPTIONS="$JOB_CMAKE_OPTIONS"
if [[ "$COMPILER" =~ ^clang.*$ ]]; then
CMAKE_OPTIONS="$CMAKE_OPTIONS -DCLANG_USE_LIBCXX=ON"
# force clang to silently allow -static-libstdc++ flag
# Default to allowing system standard c++ library be used. On Linux, which
# typically uses libstdc++ by default, using libc++ causes a conflict with
# Qt6 libs. So an error/warning is in place to catch that case when QtFRED
# is enabled.
CMAKE_OPTIONS="$CMAKE_OPTIONS -DCLANG_USE_LIBCXX=${CLANG_USE_LIBCXX:-OFF}"
fi
if [ ! "$CCACHE_PATH" = "" ] && [ ! -x "$CCACHE_PATH" ]; then
# The configured path doesn't exist on this runner (e.g. macOS runners on Apple
# Silicon hosts install Homebrew under /opt/homebrew instead of /usr/local),
# so fall back to whatever ccache is actually on PATH.
CCACHE_PATH="$(command -v ccache || true)"
fi
if [ ! "$CCACHE_PATH" = "" ]; then
@@ -35,6 +46,10 @@ if [ ! "$CCACHE_PATH" = "" ]; then
CMAKE_OPTIONS="$CMAKE_OPTIONS -DCMAKE_C_COMPILER_LAUNCHER=$CCACHE_PATH -DCMAKE_CXX_COMPILER_LAUNCHER=$CCACHE_PATH"
fi
if [ -n "${ENABLE_QTFRED:-}" ]; then
CMAKE_OPTIONS="$CMAKE_OPTIONS -DFSO_BUILD_QTFRED=${ENABLE_QTFRED}"
fi
mkdir build
cd build
@@ -42,7 +57,7 @@ export CXXFLAGS
export CFLAGS
export LD_LIBRARY_PATH
if [ "$RUNNER_OS" = "macOS" ]; then
if [ "$RUNNER_OS" = "macOS" ] && [ ! -x "$(which ninja)" ]; then
brew install ninja
fi
@@ -50,6 +65,6 @@ fi
# we have some build rules that do not play nice with parallel invocation and that fixes these issues
cmake -G Ninja -DFSO_FATAL_WARNINGS=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON $CMAKE_OPTIONS $PLATFORM_CMAKE_OPTIONS \
-DCMAKE_INSTALL_PREFIX="$(pwd)/install" -DCMAKE_BUILD_TYPE=$CONFIGURATION \
-DFFMPEG_USE_PRECOMPILED=ON -DFSO_BUILD_TESTS=ON -DFSO_BUILD_INCLUDED_LIBS=ON -DFSO_BUILD_QTFRED=${ENABLE_QTFRED:-OFF} \
-DSHADERS_ENABLE_COMPILATION=ON -DCMAKE_JOB_POOLS=link=1 -DCMAKE_JOB_POOL_LINK=link ..
-DCMAKE_INSTALL_PREFIX="$(pwd)/install" -DCMAKE_BUILD_TYPE=$CONFIGURATION -DQT_USE_PRECOMPILED=ON \
-DFFMPEG_USE_PRECOMPILED=ON -DFSO_BUILD_TESTS=ON -DFSO_BUILD_INCLUDED_LIBS=ON \
-DCMAKE_JOB_POOLS=link=1 -DCMAKE_JOB_POOL_LINK=link ..
+8 -8
View File
@@ -14,17 +14,17 @@ if [ "$OS" = "Linux" ]; then
echo "package_name=$(get_package_name)-builds-Linux-$ARCH.tar.gz" >> $GITHUB_OUTPUT
echo "package_mime=$(file -b --mime-type "$(pwd)/$(get_package_name)-builds-Linux-$ARCH.tar.gz")" >> $GITHUB_OUTPUT
elif [ "$OS" = "Windows" ]; then
7z a -xr'!*.pdb' "$(get_package_name)-builds-$ARCH-$SIMD.zip" "*"
7z a -xr'!*.pdb' "$(get_package_name)-builds-$ID.zip" "*"
echo "package_path=$(pwd)/$(get_package_name)-builds-$ARCH-$SIMD.zip" >> $GITHUB_OUTPUT
echo "package_name=$(get_package_name)-builds-$ARCH-$SIMD.zip" >> $GITHUB_OUTPUT
echo "package_mime=$(file -b --mime-type "$(pwd)/$(get_package_name)-builds-$ARCH-$SIMD.zip")" >> $GITHUB_OUTPUT
echo "package_path=$(pwd)/$(get_package_name)-builds-$ID.zip" >> $GITHUB_OUTPUT
echo "package_name=$(get_package_name)-builds-$ID.zip" >> $GITHUB_OUTPUT
echo "package_mime=$(file -b --mime-type "$(pwd)/$(get_package_name)-builds-$ID.zip")" >> $GITHUB_OUTPUT
7z a "$(get_package_name)-debug-$ARCH-$SIMD.7z" "*.pdb"
7z a "$(get_package_name)-debug-$ID.7z" "*.pdb"
echo "debug_path=$(pwd)/$(get_package_name)-debug-$ARCH-$SIMD.7z" >> $GITHUB_OUTPUT
echo "debug_name=$(get_package_name)-debug-$ARCH-$SIMD.7z" >> $GITHUB_OUTPUT
echo "debug_mime=$(file -b --mime-type "$(pwd)/$(get_package_name)-debug-$ARCH-$SIMD.7z")" >> $GITHUB_OUTPUT
echo "debug_path=$(pwd)/$(get_package_name)-debug-$ID.7z" >> $GITHUB_OUTPUT
echo "debug_name=$(get_package_name)-debug-$ID.7z" >> $GITHUB_OUTPUT
echo "debug_mime=$(file -b --mime-type "$(pwd)/$(get_package_name)-debug-$ID.7z")" >> $GITHUB_OUTPUT
elif [ "$OS" = "Mac" ]; then
tar -cvzf "$(get_package_name)-builds-Mac-$ARCH.tar.gz" *.app
+39 -4
View File
@@ -2,13 +2,48 @@
INSTALL_FOLDER=$1
# safety check
if [ ! -d bin -o ! -f cmake_install.cmake ]; then
echo "ERROR! This script must be run from within the build root!"
exit 1
fi
if [ ! -x "$(which wget)" ]; then
echo "ERROR! Required utility is not available: wget"
exit 1
fi
# install newest appimagetool if it's not already available
if [ ! -x ./bin/appimagetool ]; then
APPIMAGE_TOOL_URL="https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-$(uname -m).AppImage"
wget -O ./bin/appimagetool "$APPIMAGE_TOOL_URL" || { echo "ERROR! Failed to get appimagetool!" && exit 1; }
chmod +x ./bin/appimagetool
fi
# This shouldn't be needed with newer runtimes, but they still generate an
# error if fusermount is missing even though it works. So to skip error msg
# and have max compatibility with runtimes and containers we'll use it.
export APPIMAGE_EXTRACT_AND_RUN=1
# Install Freespace2 targets
cmake -DCMAKE_INSTALL_PREFIX=$INSTALL_FOLDER -DCOMPONENT=Unspecified -P cmake_install.cmake
cmake -DCMAKE_INSTALL_PREFIX=$INSTALL_FOLDER -DCOMPONENT=Freespace2 -P cmake_install.cmake
cmake -DCMAKE_INSTALL_PREFIX=$INSTALL_FOLDER/Freespace2 -DCOMPONENT=Unspecified -P cmake_install.cmake
cmake -DCMAKE_INSTALL_PREFIX=$INSTALL_FOLDER/Freespace2 -DCOMPONENT=Freespace2 -P cmake_install.cmake
# We need to be a bit creative for determining the AppImage name since we don't want to hard-code the name
FILENAME="$(find $INSTALL_FOLDER/bin -name 'fs2_open_*' -type f -printf "%f\n").AppImage"
appimagetool -n $INSTALL_FOLDER "$INSTALL_FOLDER/$FILENAME"
FILENAME="$(find $INSTALL_FOLDER/Freespace2/bin -maxdepth 1 -name 'fs2_open_*' -type f -executable -printf "%f\n").AppImage"
./bin/appimagetool -n "$INSTALL_FOLDER/Freespace2" "$INSTALL_FOLDER/$FILENAME"
chmod +x "$INSTALL_FOLDER/$FILENAME"
# Maybe install qtFRED targets
if [ -f qtfred/cmake_install.cmake ]; then
cmake -DCMAKE_INSTALL_PREFIX=$INSTALL_FOLDER/qtFRED -DCOMPONENT=Unspecified -P cmake_install.cmake
cmake -DCMAKE_INSTALL_PREFIX=$INSTALL_FOLDER/qtFRED -DCOMPONENT=qtFRED -P cmake_install.cmake
# We need to be a bit creative for determining the AppImage name since we don't want to hard-code the name
FILENAME="$(find $INSTALL_FOLDER/qtFRED/bin -maxdepth 1 -iname 'qtfred_*' -type f -executable -printf "%f\n").AppImage"
./bin/appimagetool -n "$INSTALL_FOLDER/qtFRED" "$INSTALL_FOLDER/$FILENAME"
chmod +x "$INSTALL_FOLDER/$FILENAME"
fi
ls -al $INSTALL_FOLDER
+9 -1
View File
@@ -5,6 +5,14 @@ HERE=$(dirname "$SCRIPT")
export LD_LIBRARY_PATH=$(pwd)/bin/lib:$LD_LIBRARY_PATH
# The unit tests never open a window, but a few of them reach code that brings SDL's video
# subsystem up (os_init() and resolution_default(), the latter via gr_init()). Left to itself SDL
# probes its real backends and dlopens libX11, which leaks a handful of blocks per init/quit cycle
# that SDL_QuitSubSystem() does not reclaim -- those are the "definitely lost" contexts memcheck
# fails the Debug legs on. Pinning the dummy driver skips the probing entirely, so the leaks never
# happen, and it makes the tests behave the same way whether or not the runner has a display.
export SDL_VIDEODRIVER=dummy
# if on mac and building for architecture that doesn't match host
# then unittests will fail so we just need to skip it (for now)
if [ "$RUNNER_OS" = "macOS" ] && [ "$ARCH" != "$(uname -m)" ]; then
@@ -12,7 +20,7 @@ if [ "$RUNNER_OS" = "macOS" ] && [ "$ARCH" != "$(uname -m)" ]; then
exit 0
fi
if [ "$CONFIGURATION" = "Debug"] && [[ "$RUNNER_OS" != "macOS" ]] ; then
if [ "$CONFIGURATION" = "Debug" ] && [[ "$RUNNER_OS" != "macOS" ]] ; then
valgrind --leak-check=full --error-exitcode=1 --gen-suppressions=all \
--suppressions="$HERE/valgrind.supp" ./bin/unittests --gtest_shuffle
else
+18 -42
View File
@@ -8,24 +8,6 @@
obj:*
obj:*
}
{
Travis memleak
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
fun:_dl_signal_error
fun:_dl_open
fun:dlopen_doit
fun:_dl_catch_error
fun:_dlerror_run
fun:dlopen@@GLIBC_2.2.5
obj:/usr/lib/x86_64-linux-gnu/libmirclient.so.7
fun:call_init.part.0
fun:call_init
fun:_dl_init
fun:dl_open_worker
fun:_dl_catch_error
}
{
CLang Lua unitialized value error
Memcheck:Cond
@@ -127,37 +109,31 @@
fun:_ZN7testing8internal35HandleExceptionsInMethodIfSupportedINS_4TestEvEET0_PT_MS4_FS3_vEPKc
}
{
<insert_a_suppression_name_here>
SDL_Init one-time internal allocation via os_init
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
fun:_dl_signal_error
fun:_dl_open
fun:dlopen_doit
fun:_dl_catch_error
fun:_dlerror_run
fun:dlopen@@GLIBC_2.2.5
obj:*
obj:*
fun:call_init.part.0
fun:call_init
fun:_dl_init
fun:dl_open_worker
obj:*
obj:*
fun:_Z7os_initPKcS0_S0_
fun:_ZN4test13FSTestFixture5SetUpEv
}
# Kept only as a backstop for running memcheck by hand. run_tests.sh pins SDL_VIDEODRIVER=dummy, so
# the X11 backend is never created there; this block also cannot match the prebuilt libSDL3 anymore,
# since that library is now stripped and X11_CreateDevice is a static symbol.
{
SDL3 X11 video backend internal allocations, not freed by SDL_VideoQuit
Memcheck:Leak
match-leak-kinds: definite,possible
...
fun:X11_CreateDevice
}
{
dlopen leak
glibc loader bookkeeping and library constructor allocations retained across dlopen
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
fun:_dl_exception_create
fun:_dl_signal_error
fun:_dl_map_object
fun:dl_open_worker
fun:_dl_catch_exception
match-leak-kinds: definite,possible
...
fun:_dl_open
fun:dlopen_doit
fun:_dl_catch_exception
fun:_dl_catch_error
fun:_dlerror_run
fun:dlopen@@GLIBC_2.2.5
}
+5 -1
View File
@@ -149,6 +149,8 @@ def get_release_files(tag_name, config) -> Tuple[List[ReleaseFile], Dict[str, So
# x64 is the Visual Studio name but for consistency we need Win64
if platform == "x64":
platform = "Win64"
elif platform == "ARM64":
platform = "WinARM64"
binary_files.append(ReleaseFile(name, url, platform, group_match.group(3)))
else:
@@ -213,6 +215,8 @@ def get_ftp_files(build_type, tag_name, config) -> List[ReleaseFile] :
# x64 is the name Visual Studio uses but Win64 works better for us since that gets displayed in the nightly post
if "x64" in group_match:
group_match = group_match.replace("x64", "Win64")
elif "ARM64" in group_match:
group_match = group_match.replace("ARM64", "WinARM64")
# construct the download URL list for all mirrors. The first listed ftp location is taken as the Primary
for mirror in config["ftp"]["mirrors"]:
@@ -225,4 +229,4 @@ def get_ftp_files(build_type, tag_name, config) -> List[ReleaseFile] :
# Form the List[ReleaseFile] list with the download URL links
out_data.append(ReleaseFile(file, primary_url, group_match, None, mirrors))
return out_data
return out_data
+9 -6
View File
@@ -1,3 +1,4 @@
import os
from typing import List
import requests
@@ -33,12 +34,14 @@ class ForumAPI:
print("Post failed! No API or API_KEY given!")
return
resp = requests.post(self.config["hlp"]["api"], data={
"api_key": self.config["hlp"]["key"],
"board": str(board),
"subject": title,
"body": content
})
resp = requests.post(self.config["hlp"]["api"],
headers={"X-HLP-Bot": os.environ["FORUM_SECRET"]},
data={
"api_key": self.config["hlp"]["key"],
"board": str(board),
"subject": title,
"body": content
})
if resp.status_code != 200:
print("Post failed! Response: %s" %resp.text)
+5
View File
@@ -18,6 +18,7 @@
# Credentials into hard-light.net for forum post
# HLP_API:
# HLP_KEY:
# FORUM_SECRET:
# LINUX_RESULT: bool, if the linux builds were successfully uploaded
# WINDOWS_RESULT: bool, if the windows builds were successfully uploaded
@@ -124,6 +125,10 @@ def get_source_version(date_version: datetime, tag_name: str) -> semantic_versio
def main():
if "FORUM_SECRET" not in os.environ or not os.environ["FORUM_SECRET"]:
print(" ERROR: FORUM_SECRET environment variable not set")
sys.exit(1)
os.chdir(os.path.dirname(__file__)) # Change working directory to this file's directory
# Aggregate configuration data in a dictionary
+6 -2
View File
@@ -15,6 +15,7 @@ WIN32_SSE2_KEY = "Win32-SSE2"
WIN64_SSE2_KEY = "Win64-SSE2"
WIN32_AVX_KEY = "Win32-AVX"
WIN64_AVX_KEY = "Win64-AVX"
WINARM64_KEY = "WinARM64"
metadata = {
'type': 'engine',
@@ -42,7 +43,8 @@ platforms = {
WIN32_SSE2_KEY: 'windows',
WIN64_SSE2_KEY: 'windows',
WIN32_AVX_KEY: 'windows',
WIN64_AVX_KEY: 'windows'
WIN64_AVX_KEY: 'windows',
WINARM64_KEY: 'windows',
}
envs = {
@@ -53,7 +55,8 @@ envs = {
WIN32_SSE2_KEY: 'windows',
WIN64_SSE2_KEY: 'windows && x86_64',
WIN32_AVX_KEY: 'windows && avx',
WIN64_AVX_KEY: 'windows && avx && x86_64'
WIN64_AVX_KEY: 'windows && avx && x86_64',
WINARM64_KEY: 'windows && arm64'
}
subdirs = {
@@ -191,6 +194,7 @@ def render_nebula_release(version, stability, files, config):
"sse2": "SSE2" in fn or "AVX" in fn, # AVX implies SSE2
"avx": "AVX" in fn, # This conveniently also covers the AVX2 case since AVX2 implies AVX
"avx2": "AVX2" in fn,
"arm64": "arm64" in fn,
}
pkg['executables'].append({
+3 -1
View File
@@ -22,7 +22,7 @@ Windows: [url=http://scp.fsmods.net/builds/Launcher55g.zip]Launcher 5.5g[/url]
OS X: Soulstorm's [url=https://www.hard-light.net/forums/index.php/topic,51391.0.html]OS X Launcher 3.0[/url]
Linux: [url=https://www.hard-light.net/forums/index.php/topic,53206.0.html]YAL[/url] or [url=https://www.hard-light.net/wiki/index.php/Fs2_open_on_Linux/Graphics_Settings]by hand[/url] or whatever you can figure out.[/hidden]
[img]https://scp.indiegames.us/img/windows-icon.png[/img] [color=green][size=12pt]Windows (32/64-bit)[/size][/color]
[img]https://scp.indiegames.us/img/windows-icon.png[/img] [color=green][size=12pt]Windows (32/64-bit/ARM64)[/size][/color]
[size=8pt]Compiled using GitHub Actions on Windows Server 2019 (10.0.17763), Visual Studio Enterprise 2019[/size]
[b]64-bit:[/b] ${build(groups["Win64"].mainFile)}
@@ -30,6 +30,8 @@ Linux: [url=https://www.hard-light.net/forums/index.php/topic,53206.0.html]YAL[
[b]32-bit:[/b] ${build(groups["Win32"].mainFile)}
[size=8pt]This one is based on the SSE2 Optimizations from the MSVC Compiler.[/size]
[b]ARM64:[/b] ${build(groups["WinARM64"].mainFile)}
[hidden=Alternative builds]
[b]64-bit AVX:[/b] ${build(groups["Win64"].subFiles["AVX"])}
+2 -2
View File
@@ -1,3 +1,3 @@
Mako==1.2.2
requests==2.32.4
Mako==1.4.2
requests==2.33.0
semantic-version==2.8.5
+34
View File
@@ -0,0 +1,34 @@
# Installs ATL and MFC for the v143 toolset, which the Visual Studio 2026 runner images
# only ship for the latest toolset. ATL is needed by SAPI speech (sphelper.h) and MFC by FRED2.
$ErrorActionPreference = 'Stop'
$msvcVersion = '14.44'
$component = 'Microsoft.VisualStudio.Component.VC.14.44.17.14'
$suffix = if ($env:RUNNER_ARCH -eq 'ARM64') { '.ARM64' } else { '' }
$installerDir = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer"
$installPath = & "$installerDir\vswhere.exe" -version '[18.0,19.0)' -latest -property installationPath
if (-not $installPath) {
Write-Error 'Visual Studio 2026 was not found'
}
$arguments = @(
'/c', 'vs_installer.exe', 'modify',
'--installPath', "`"$installPath`"",
'--add', "$component.ATL$suffix",
'--add', "$component.MFC$suffix",
'--quiet', '--norestart', '--nocache'
)
$process = Start-Process -FilePath cmd.exe -ArgumentList $arguments -WorkingDirectory $installerDir -Wait -PassThru -NoNewWindow
# 3010 = success, reboot required
if ($process.ExitCode -notin 0, 3010) {
Write-Error "vs_installer exited with code $($process.ExitCode)"
}
# fail here rather than with a confusing compile error if the components didn't install
foreach ($header in 'atlbase.h', 'afxwin.h') {
if (-not (Test-Path "$installPath\VC\Tools\MSVC\$msvcVersion.*\atlmfc\include\$header")) {
Write-Error "$header for MSVC $msvcVersion is missing after install"
}
}
+48
View File
@@ -0,0 +1,48 @@
# - Removes duplicate entries and non-directories from a provided list
#
# clean_directory_list(<listvar> [<additional list items>...])
#
# Requires CMake 2.6 or newer (uses the 'function' command)
#
# Original Author:
# 2009-2010 Ryan Pavlik <[email protected]> <[email protected]>
# http://academic.cleardefinition.com
# Iowa State University HCI Graduate Program/VRAC
#
# Copyright Iowa State University 2009-2010.
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
if(__clean_directory_list)
return()
endif()
set(__clean_directory_list YES)
function(clean_directory_list _var)
# combine variable's current value with additional list items
set(_in ${${_var}} ${ARGN})
if(_in)
# Initial list cleaning
list(REMOVE_DUPLICATES _in)
# Grab the absolute path of each actual directory
set(_out)
foreach(_dir ${_in})
if(IS_DIRECTORY "${_dir}")
get_filename_component(_dir "${_dir}" ABSOLUTE)
file(TO_CMAKE_PATH "${_dir}" _dir)
list(APPEND _out "${_dir}")
endif()
endforeach()
if(_out)
# Clean up the output list now
list(REMOVE_DUPLICATES _out)
endif()
# return _out
set(${_var} "${_out}" PARENT_SCOPE)
endif()
endfunction()
+311
View File
@@ -0,0 +1,311 @@
# - Create launchers to set working directory, env. vars, etc.
#
# include(CreateLaunchers) - to make these available
# guess_runtime_library_dirs(<outputvarname> [<extralibrary> ...])
# create_default_target_launcher(<targetname>
# [ARGS <args...>]
# [FORWARD_ARGS]
# [RUNTIME_LIBRARY_DIRS <dir...>]
# [WORKING_DIRECTORY <dir>]
# [ENVIRONMENT <VAR=value> [<VAR=value>...]])
#
# create_target_launcher(<targetname>
# [ARGS <args...>]
# [FORWARD_ARGS]
# [RUNTIME_LIBRARY_DIRS <dir...>]
# [WORKING_DIRECTORY <dir>]
# [ENVIRONMENT <VAR=value> [<VAR=value>...]])
#
# create_generic_launcher(<launchername>
# [RUNTIME_LIBRARY_DIRS <dir...>]
# [WORKING_DIRECTORY <dir>]
# [ENVIRONMENT <VAR=value> [<VAR=value>...]])
# - sets GENERIC_LAUNCHER_COMMAND and GENERIC_LAUNCHER_FAIL_REGULAR_EXPRESSION
#
# Requires these CMake modules:
# ListFilter
# ProgramFilesGlob
# CleanDirectoryList
#
# Requires CMake 2.6 or newer (uses the 'function' command)
#
# Original Author:
# 2009-2010 Ryan Pavlik <[email protected]> <[email protected]>
# http://academic.cleardefinition.com
# Iowa State University HCI Graduate Program/VRAC
#
# Copyright Iowa State University 2009-2010.
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
if(__create_launchers)
return()
endif()
set(__create_launchers YES)
include(CleanDirectoryList)
# We must run the following at "include" time, not at function call time,
# to find the path to this module rather than the path to a calling list file
get_filename_component(_launchermoddir
${CMAKE_CURRENT_LIST_FILE}
PATH)
set(_launchermoddir "${_launchermoddir}/launcher-templates")
macro(_launcher_system_settings)
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(BITS 64)
else()
set(BITS 32)
endif()
if(WIN32)
# Find user and system name
set(SYSTEM_NAME $ENV{USERDOMAIN})
set(USER_NAME $ENV{USERNAME})
set(VCPROJ_TYPE vcproj)
set(USERFILE_EXTENSION ${SYSTEM_NAME}.${USER_NAME}.user)
set(LAUNCHER_LINESEP "&#x0A;")
if(MSVC90)
set(USERFILE_VC_VERSION 9.00)
elseif(MSVC80)
set(USERFILE_VC_VERSION 8.00)
elseif(MSVC71)
set(USERFILE_VC_VERSION 7.10)
elseif(MSVC10 OR (MSVC AND MSVC_VERSION GREATER 1600)) # 2010 or newer
set(LAUNCHER_LINESEP "\n")
set(USERFILE_VC_VERSION 10.00)
set(USERFILE_EXTENSION user)
set(VCPROJ_TYPE vcxproj)
endif()
if(BITS EQUAL 64)
set(USERFILE_PLATFORM x64)
else()
set(USERFILE_PLATFORM Win${BITS})
endif()
set(_pathdelim ";")
set(_suffix "cmd")
else()
set(_pathdelim ":")
set(USERFILE_PLATFORM ${CMAKE_SYSTEM_NAME}${BITS})
set(_suffix "sh")
find_package(GDB QUIET)
if(GDB_FOUND)
set(LAUNCHERS_GOT_GDB YES)
if(GDB_HAS_RETURN_CHILD_RESULT)
set(LAUNCHERS_GDB_ARG --return-child-result)
endif()
else()
set(LAUNCHERS_GOT_GDB)
endif()
endif()
if(WIN32 AND NOT USERFILE_REMOTE_MACHINE)
site_name(USERFILE_REMOTE_MACHINE)
mark_as_advanced(USERFILE_REMOTE_MACHINE)
endif()
endmacro()
macro(_launcher_process_args)
set(_nowhere)
set(_curdest _nowhere)
set(_val_args
ARGS
RUNTIME_LIBRARY_DIRS
WORKING_DIRECTORY
ENVIRONMENT)
set(_bool_args FORWARD_ARGS)
foreach(_arg ${_val_args} ${_bool_args})
set(${_arg})
endforeach()
foreach(_element ${ARGN})
list(FIND _val_args "${_element}" _val_arg_find)
list(FIND _bool_args "${_element}" _bool_arg_find)
if("${_val_arg_find}" GREATER "-1")
set(_curdest "${_element}")
elseif("${_bool_arg_find}" GREATER "-1")
set("${_element}" ON)
set(_curdest _nowhere)
else()
list(APPEND ${_curdest} "${_element}")
endif()
endforeach()
if(_nowhere)
message(FATAL_ERROR
"Syntax error in use of a function in CreateLaunchers!")
endif()
# Turn into a list of native paths
set(_runtime_lib_dirs)
foreach(_dlldir ${RUNTIME_LIBRARY_DIRS})
file(TO_NATIVE_PATH "${_dlldir}" _path)
set(_runtime_lib_dirs "${_runtime_lib_dirs}${_path}${_pathdelim}")
endforeach()
if(NOT WORKING_DIRECTORY)
set(WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
endif()
if(FORWARD_ARGS)
if(WIN32)
set(FWD_ARGS %*)
else()
set(FWD_ARGS $*)
endif()
else()
set(FWD_ARGS)
endif()
set(USERFILE_WORKING_DIRECTORY "${WORKING_DIRECTORY}")
set(USERFILE_COMMAND_ARGUMENTS "${ARGS}")
set(LAUNCHERSCRIPT_COMMAND_ARGUMENTS "${ARGS} ${FWD_ARGS}")
if(WIN32)
set(RUNTIME_LIBRARIES_ENVIRONMENT "PATH=${_runtime_lib_dirs};%PATH%")
file(READ
"${_launchermoddir}/launcher.env.cmd.in"
_cmdenv)
else()
if(APPLE)
set(RUNTIME_LIBRARIES_ENVIRONMENT
"DYLD_LIBRARY_PATH=${_runtime_lib_dirs}:$DYLD_LIBRARY_PATH")
else()
set(RUNTIME_LIBRARIES_ENVIRONMENT
"LD_LIBRARY_PATH=${_runtime_lib_dirs}:$LD_LIBRARY_PATH")
endif()
file(READ
"${_launchermoddir}/launcher.env.sh.in"
_cmdenv)
endif()
set(USERFILE_ENVIRONMENT "${RUNTIME_LIBRARIES_ENVIRONMENT}")
set(USERFILE_ENV_COMMANDS)
foreach(_arg "${RUNTIME_LIBRARIES_ENVIRONMENT}" ${ENVIRONMENT})
string(CONFIGURE
"@USERFILE_ENVIRONMENT@@LAUNCHER_LINESEP@@_arg@"
USERFILE_ENVIRONMENT
@ONLY)
string(CONFIGURE
"@USERFILE_ENV_COMMANDS@${_cmdenv}"
USERFILE_ENV_COMMANDS
@ONLY)
endforeach()
endmacro()
macro(_launcher_produce_vcproj_user)
if(MSVC)
file(READ
"${_launchermoddir}/perconfig.${VCPROJ_TYPE}.user.in"
_perconfig)
file(TO_NATIVE_PATH "${USERFILE_WORKING_DIRECTORY}" NATIVE_WORKING_DIRECTORY)
set(USERFILE_CONFIGSECTIONS)
foreach(USERFILE_CONFIGNAME ${CMAKE_CONFIGURATION_TYPES})
string(CONFIGURE "${_perconfig}" _temp @ONLY ESCAPE_QUOTES)
set(USERFILE_CONFIGSECTIONS "${USERFILE_CONFIGSECTIONS}${_temp}")
endforeach()
configure_file("${_launchermoddir}/${VCPROJ_TYPE}.user.in"
"${VCPROJNAME}.${VCPROJ_TYPE}.${USERFILE_EXTENSION}"
@ONLY)
endif()
endmacro()
macro(_launcher_configure_executable _src _tmp _target)
# get_filename_component(_targetname "${_target}" NAME)
get_filename_component(_targetpath "${_target}" PATH)
configure_file("${_src}"
"${_tmp}"
@ONLY)
file(COPY "${_tmp}"
DESTINATION "${_targetpath}"
FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_WRITE GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
endmacro()
macro(_launcher_create_target_launcher)
set(TARGET_NAME ${_targetname})
set(_fn "launch-${_targetname}-generator.${_suffix}")
configure_file("${_launchermoddir}/targetlauncher.${_suffix}.in"
"${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${_fn}"
@ONLY)
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/launch-${_targetname}-$<CONFIGURATION>.${_suffix}"
INPUT "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${_fn}")
endmacro()
function(create_default_target_launcher _targetname)
_launcher_system_settings()
_launcher_process_args(${ARGN})
set(VCPROJNAME "${CMAKE_BINARY_DIR}/ALL_BUILD")
_launcher_produce_vcproj_user()
_launcher_create_target_launcher()
endfunction()
function(create_target_launcher _targetname)
_launcher_system_settings()
_launcher_process_args(${ARGN})
set(VCPROJNAME "${CMAKE_CURRENT_BINARY_DIR}/${_targetname}")
_launcher_produce_vcproj_user()
_launcher_create_target_launcher()
endfunction()
function(create_generic_launcher _launchername)
_launcher_system_settings()
_launcher_process_args(${ARGN})
if(NOT IS_ABSOLUTE _launchername)
set(_launchername
"${CMAKE_CURRENT_BINARY_DIR}/${_launchername}.${_suffix}")
else()
set(_launchername "${_launchername}.${_suffix}")
endif()
if(WIN32)
set(GENERIC_LAUNCHER_COMMAND "${_launchername}" PARENT_SCOPE)
set(GENERIC_LAUNCHER_FAIL_REGULAR_EXPRESSION)
else()
set(GENERIC_LAUNCHER_COMMAND sh "${_launchername}" PARENT_SCOPE)
set(GENERIC_LAUNCHER_FAIL_REGULAR_EXPRESSION
"Program terminated with signal")
endif()
_launcher_configure_executable("${_launchermoddir}/genericlauncher.${_suffix}.in"
"${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/genericlauncher.${_suffix}.in"
"${_launchername}")
endfunction()
function(guess_runtime_library_dirs _var)
# Start off with the link directories of the calling listfile's directory
get_directory_property(_libdirs LINK_DIRECTORIES)
# Add additional libraries passed to the function
foreach(_lib ${ARGN})
get_filename_component(_libdir "${_lib}" PATH)
list(APPEND _libdirs "${_libdir}")
endforeach()
# Now, build a list of potential dll directories
set(_dlldirs)
foreach(_libdir ${_libdirs})
# Add the libdir itself
list(APPEND _dlldirs "${_libdir}")
# Look also in libdir/../bin since the dll might not be with the lib
get_filename_component(_libdir "${_libdir}/../bin" ABSOLUTE)
list(APPEND _dlldirs "${_libdir}")
endforeach()
# Only keep the valid, unique directories
clean_directory_list(_dlldirs)
# Return _dlldirs
set(${_var} "${_dlldirs}" PARENT_SCOPE)
endfunction()
+88
View File
@@ -0,0 +1,88 @@
# - Add flags to compile with extra warnings
#
# enable_extra_compiler_warnings(<targetname>)
# globally_enable_extra_compiler_warnings() - to modify CMAKE_CXX_FLAGS, etc
# to change for all targets declared after the command, instead of per-command
#
#
# Original Author:
# 2010 Ryan Pavlik <[email protected]> <[email protected]>
# http://academic.cleardefinition.com
# Iowa State University HCI Graduate Program/VRAC
#
# Copyright Iowa State University 2009-2010.
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
if(__enable_extra_compiler_warnings)
return()
endif()
set(__enable_extra_compiler_warnings YES)
macro(_enable_extra_compiler_warnings_flags)
set(_flags)
if(MSVC)
option(COMPILER_WARNINGS_EXTREME
"Use compiler warnings that are probably overkill."
off)
mark_as_advanced(COMPILER_WARNINGS_EXTREME)
set(_flags "/W4")
if(COMPILER_WARNINGS_EXTREME)
set(_flags "${_flags} /Wall /wd4619 /wd4668 /wd4820 /wd4571 /wd4710")
endif()
else()
include(CheckCXXCompilerFlag)
set(_flags)
check_cxx_compiler_flag(-W SUPPORTS_W_FLAG)
if(SUPPORTS_W_FLAG)
set(_flags "${_flags} -W")
endif()
check_cxx_compiler_flag(-Wall SUPPORTS_WALL_FLAG)
if(SUPPORTS_WALL_FLAG)
set(_flags "${_flags} -Wall")
endif()
check_cxx_compiler_flag(-Wextra SUPPORTS_WEXTRA_FLAG)
if(SUPPORTS_WEXTRA_FLAG)
set(_flags "${_flags} -Wextra")
endif()
if(SUPPORTS_WALL_FLAG)
# At least GCC includes -Wmaybe-uninitialized in -Wall, which
# unneccesarily whines about boost::optional (by it's nature
# it's a "maybe" warning - prone to noisy false-positives)
check_cxx_compiler_flag(-Wno-maybe-uninitialized SUPPORTS_WNO_MAYBE_UNINITIALIZED_FLAG)
if(SUPPORTS_WNO_MAYBE_UNINITIALIZED_FLAG)
set(_flags "${_flags} -Wno-maybe-uninitialized")
endif()
endif()
endif()
endmacro()
function(enable_extra_compiler_warnings _target)
_enable_extra_compiler_warnings_flags()
get_target_property(_origflags ${_target} COMPILE_FLAGS)
if(_origflags)
set_property(TARGET
${_target}
PROPERTY
COMPILE_FLAGS
"${_flags} ${_origflags}")
else()
set_property(TARGET
${_target}
PROPERTY
COMPILE_FLAGS
"${_flags}")
endif()
endfunction()
function(globally_enable_extra_compiler_warnings)
_enable_extra_compiler_warnings_flags()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${_flags}" PARENT_SCOPE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${_flags}" PARENT_SCOPE)
endfunction()
+53
View File
@@ -0,0 +1,53 @@
# - Combine lists of prefixes and suffixes in all combinations
#
# list_combinations(var PREFIXES listitems... SUFFIXES listitems...) -
# where var is the name of your desired output variable and PREFIXES
# and SUFFIXES are special arguments that indicate the start of your
# list of prefixes or suffixes respectively.
#
# Original Author:
# 2009-2010 Ryan Pavlik <[email protected]> <[email protected]>
# http://academic.cleardefinition.com
# Iowa State University HCI Graduate Program/VRAC
#
# Copyright Iowa State University 2009-2010.
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
if(__list_combinations)
return()
endif()
set(__list_combinations YES)
function(list_combinations var)
# Parse arguments
set(_prefixes)
set(_suffixes)
set(_nowhere)
set(_curdest _nowhere)
foreach(_element ${ARGN})
if("${_element}" STREQUAL "PREFIXES")
set(_curdest _prefixes)
elseif("${_element}" STREQUAL "SUFFIXES")
set(_curdest _suffixes)
else()
list(APPEND ${_curdest} "${_element}")
endif()
endforeach()
if(_nowhere)
message(STATUS "_prefixes ${_prefixes}")
message(STATUS "_prefixes ${_suffixes}")
message(STATUS "_prefixes ${_nowhere}")
message(FATAL_ERROR
"Syntax error in use of ${CMAKE_CURRENT_LIST_FILE}")
endif()
foreach(_prefix ${_prefixes})
foreach(_suffix ${_suffixes})
list(APPEND _out "${_prefix}${_suffix}")
endforeach()
endforeach()
set(${var} "${_out}" PARENT_SCOPE)
endfunction()
+31
View File
@@ -0,0 +1,31 @@
# - Compile with multiple processes on MSVC
#
# include(MSVCMultipleProcessCompile)
#
# Requires these CMake modules:
# ListCombinations.cmake
#
# Original Author:
# 2009-2010 Ryan Pavlik <[email protected]> <[email protected]>
# http://academic.cleardefinition.com
# Iowa State University HCI Graduate Program/VRAC
#
# Copyright Iowa State University 2009-2010.
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
if(MSVC AND NOT "${MSVC_VERSION}" LESS 1400)
# Only available in VS 2005 and newer
string(TOUPPER "${CMAKE_CONFIGURATION_TYPES}" _conftypesUC)
include(ListCombinations)
list_combinations(_varnames
PREFIXES
CMAKE_C_FLAGS_
CMAKE_CXX_FLAGS_
SUFFIXES
${_conftypesUC})
foreach(_var ${_varnames})
set(${_var} "${${_var}} /MP")
endforeach()
endif()
-21
View File
@@ -1,21 +0,0 @@
SET(BINARY_DESTINATION "bin")
SET(LIBRAY_DESTINATION "lib")
configure_file("${CMAKE_CURRENT_LIST_DIR}/AppRun.in" "${CMAKE_CURRENT_BINARY_DIR}/AppRun.gen" @ONLY)
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/AppRun-$<CONFIG>"
INPUT "${CMAKE_CURRENT_BINARY_DIR}/AppRun.gen")
install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/AppRun-$<CONFIG>" DESTINATION "." RENAME "AppRun"
COMPONENT "Freespace2")
configure_file("${CMAKE_CURRENT_LIST_DIR}/AppImage.desktop.in" "${CMAKE_CURRENT_BINARY_DIR}/AppImage.desktop.gen" @ONLY)
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/AppImage-$<CONFIG>.desktop"
INPUT "${CMAKE_CURRENT_BINARY_DIR}/AppImage.desktop.gen")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/AppImage-$<CONFIG>.desktop" DESTINATION "." RENAME "fso.desktop"
COMPONENT "Freespace2")
install(FILES "${CMAKE_SOURCE_DIR}/freespace2/resources/app_icon.png" DESTINATION "."
COMPONENT "Freespace2")
+4
View File
@@ -11,6 +11,10 @@ if (WIN32)
endif()
elseif(APPLE)
# it should just work
elseif(ANDROID)
# connects to a java TTS manager via SDL activity class
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
# uses speech-dispatcher with dlopen
else()
message(SEND_ERROR "Text to Speech is not supported on this platform!")
endif()
@@ -0,0 +1,14 @@
cd @USERFILE_WORKING_DIRECTORY@
@USERFILE_ENV_COMMANDS@
IF NOT [x%1]==[x--debugger] GOTO SkipDebuggingMess
ECHO Need to ditch the debugger!
SHIFT /1
%1 %2 %3 %4 %5 %6 %7 %8 %9
GOTO EOF
:SkipDebuggingMess
%*
:EOF
@@ -0,0 +1,18 @@
#!/bin/sh
bindir=$(pwd)
cd @USERFILE_WORKING_DIRECTORY@
@USERFILE_ENV_COMMANDS@
if test "x$1" = "x--debugger"; then
shift
cmd="$1"
shift
if test "x@LAUNCHERS_GOT_GDB@" = "xYES"; then
echo "r $@" > $bindir/gdbscript
echo "bt" >> $bindir/gdbscript
@GDB_COMMAND@ -batch -command=$bindir/gdbscript @LAUNCHERS_GDB_ARG@ $cmd
else
$cmd $@
fi
else
$@
fi
@@ -0,0 +1 @@
set @_arg@
@@ -0,0 +1 @@
export @_arg@
@@ -0,0 +1,28 @@
<Configuration
Name="@USERFILE_CONFIGNAME@|@USERFILE_PLATFORM@"
>
<DebugSettings
Command="${USERFILE_@USERFILE_CONFIGNAME@_COMMAND}"
WorkingDirectory="@USERFILE_WORKING_DIRECTORY@"
CommandArguments="@USERFILE_COMMAND_ARGUMENTS@"
Attach="false"
DebuggerType="3"
Remote="1"
RemoteMachine="@USERFILE_REMOTE_MACHINE@"
RemoteCommand=""
HttpUrl=""
PDBPath=""
SQLDebugging=""
Environment="@USERFILE_ENVIRONMENT@"
EnvironmentMerge="true"
DebuggerFlavor=""
MPIRunCommand=""
MPIRunArguments=""
MPIRunWorkingDirectory=""
ApplicationCommand=""
ApplicationArguments=""
ShimCommand=""
MPIAcceptMode=""
MPIAcceptFilter=""
/>
</Configuration>
@@ -0,0 +1,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='@USERFILE_CONFIGNAME@|@USERFILE_PLATFORM@'">
<LocalDebuggerEnvironment>@USERFILE_ENVIRONMENT@</LocalDebuggerEnvironment>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
<LocalDebuggerCommand>$(TargetPath)</LocalDebuggerCommand>
<LocalDebuggerCommandArguments>@USERFILE_COMMAND_ARGUMENTS@</LocalDebuggerCommandArguments>
<LocalDebuggerWorkingDirectory>@NATIVE_WORKING_DIRECTORY@</LocalDebuggerWorkingDirectory>
</PropertyGroup>
@@ -0,0 +1,8 @@
cd @USERFILE_WORKING_DIRECTORY@
@USERFILE_ENV_COMMANDS@
if [%1]==[--debugger] (
SHIFT
)
"@USERFILE_COMMAND@" @LAUNCHERSCRIPT_COMMAND_ARGUMENTS@
pause
@@ -0,0 +1,16 @@
#!/bin/sh
bindir=$(pwd)
cd @USERFILE_WORKING_DIRECTORY@
@USERFILE_ENV_COMMANDS@
if test "x$1" = "x--debugger"; then
shift
if test "x@LAUNCHERS_GOT_GDB@" = "xYES"; then
echo "r @LAUNCHERSCRIPT_COMMAND_ARGUMENTS@" > $bindir/gdbscript
echo "bt" >> $bindir/gdbscript
@GDB_COMMAND@ -batch -command=$bindir/gdbscript @LAUNCHERS_GDB_ARG@ @USERFILE_COMMAND@
else
"@USERFILE_COMMAND@" @LAUNCHERSCRIPT_COMMAND_ARGUMENTS@
fi
else
"@USERFILE_COMMAND@" @LAUNCHERSCRIPT_COMMAND_ARGUMENTS@
fi
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="Windows-1252"?>
<VisualStudioUserFile
ProjectType="Visual C++"
Version="@USERFILE_VC_VERSION@"
ShowAllFiles="false"
>
<Configurations>
@USERFILE_CONFIGSECTIONS@
</Configurations>
</VisualStudioUserFile>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
@USERFILE_CONFIGSECTIONS@
</Project>
-3
View File
@@ -11,9 +11,6 @@ endif()
target_link_libraries(platform INTERFACE ${COCOA_LIBRARY})
# To support C++17 we need to target at least 10.13. maybe 10.14 for some rare features
SET(CMAKE_OSX_DEPLOYMENT_TARGET "10.13")
SET(CMAKE_SKIP_RPATH FALSE)
SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
SET(CMAKE_INSTALL_RPATH @loader_path/../Frameworks/)
+1 -1
View File
@@ -15,4 +15,4 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wno-unused-variable -Wno-unused-parameter")
set(CMAKE_CXX_FLAGS_DEBUG "-Og -g -Wshadow")
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -Wshadow")
+4 -3
View File
@@ -43,7 +43,6 @@ endif()
if (USE_STATIC_LIBCXX)
set(CXX_BASE_FLAGS "${CXX_BASE_FLAGS} -static-libstdc++ -Qunused-arguments")
set(CLANG_USE_LIBCXX ON)
endif()
# For C and C++, the values can be overwritten independently
@@ -135,8 +134,10 @@ endif()
set(CXX_FLAGS_RELEASE "${CXX_OPT_FLAG_RELEASE} -Wno-unused-variable -Wno-unused-parameter")
set(C_FLAGS_RELEASE "${C_OPT_FLAG_RELEASE} -Wno-unused-variable -Wno-unused-parameter")
set(CXX_FLAGS_DEBUG "${CXX_OPT_FLAG_DEBUG} -g -Wshadow")
set(C_FLAGS_DEBUG "${C_OPT_FLAG_DEBUG} -g -Wshadow")
# Valgrind (as used by CI) does not understand the DWARF5 debug info that clang
# emits by default, so force DWARF4 to keep memcheck able to symbolize the binary.
set(CXX_FLAGS_DEBUG "${CXX_OPT_FLAG_DEBUG} -g -gdwarf-4 -Wshadow")
set(C_FLAGS_DEBUG "${C_OPT_FLAG_DEBUG} -g -gdwarf-4 -Wshadow")
# Always use the base flags and add our compiler flags at the back
set(CMAKE_CXX_FLAGS "${CXX_BASE_FLAGS} ${COMPILER_FLAGS}")
+13 -6
View File
@@ -13,6 +13,13 @@ option(MSVC_RELEASE_DEBUGGING "Set options that allow to debug release builds" O
MARK_AS_ADVANCED(FORCE MSVC_USE_RUNTIME_DLL)
MARK_AS_ADVANCED(FORCE MSVC_RELEASE_DEBUGGING)
# Qt is only available as DLL-runtime builds, so qtFRED forces the DLL runtime for the whole tree
if(MSVC_USE_RUNTIME_DLL OR FSO_BUILD_QTFRED)
set(FSO_MSVC_RUNTIME_DLL ON)
else()
set(FSO_MSVC_RUNTIME_DLL OFF)
endif()
# These are the warnings we disable
set(WARNING_FLAGS
/wd4100 # unreferenced formal parameters
@@ -71,12 +78,13 @@ if (MSVC_RELEASE_DEBUGGING)
endif()
endif()
IF(MSVC_USE_RUNTIME_DLL)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<NOT:$<CONFIG:Release>>:Debug>DLL")
# The debug runtime is not redistributable (statically linked or not), so only Debug may use it
IF(FSO_MSVC_RUNTIME_DLL)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
add_compile_definitions(_AFXDLL)
ELSE(MSVC_USE_RUNTIME_DLL)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<NOT:$<CONFIG:Release>>:Debug>")
ENDIF(MSVC_USE_RUNTIME_DLL)
ELSE()
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
ENDIF()
# Debug
set(CMAKE_C_FLAGS_DEBUG "/W4 /Gy /Zi /Od /RTC1 /Gd /Oy-")
@@ -113,7 +121,6 @@ if(IS_X86)
endif()
add_compile_definitions("$<$<CONFIG:FastDebug>:_ITERATOR_DEBUG_LEVEL=0>")
target_compile_definitions(compiler INTERFACE _CRT_SECURE_NO_DEPRECATE _CRT_SECURE_NO_WARNINGS _SECURE_SCL=0 NOMINMAX _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING)
if (FSO_FATAL_WARNINGS)
+7 -10
View File
@@ -3,7 +3,13 @@
add_library(compiler INTERFACE)
target_compile_definitions(compiler INTERFACE "$<$<CONFIG:Release>:NDEBUG>;$<$<CONFIG:Debug>:_DEBUG>;$<$<CONFIG:FastDebug>:_DEBUG>")
set(FSO_CONFIG_DEFINITIONS "$<$<CONFIG:Release>:NDEBUG>" "$<$<CONFIG:Debug>:_DEBUG>")
if(NOT MSVC)
# On MSVC the debug runtime flags (/MTd, /MDd) define _DEBUG themselves, and a FastDebug
# build on the release runtime must not have it
list(APPEND FSO_CONFIG_DEFINITIONS "$<$<CONFIG:FastDebug>:_DEBUG>")
endif()
target_compile_definitions(compiler INTERFACE ${FSO_CONFIG_DEFINITIONS})
set(FORCED_NATIVE_SIMD_INSTRUCTIONS ON CACHE BOOL "Override instruction set detection and compile with the maximum possible instructions for the current system.")
IF(IS_X86)
@@ -74,12 +80,3 @@ set(CMAKE_C_FLAGS_FASTDEBUG "${CMAKE_C_FLAGS_RELEASE}")
set(CMAKE_CXX_FLAGS_FASTDEBUG "${CMAKE_CXX_FLAGS_RELEASE}")
set(CMAKE_EXE_LINKER_FLAGS_FASTDEBUG "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
set(CMAKE_STATIC_LINKER_FLAGS_FASTDEBUG "${CMAKE_STATIC_LINKER_FLAGS_RELEASE}")
if (MSVC)
# Adjust runtime library
string(REPLACE "/MD" "/MDd" CMAKE_C_FLAGS_FASTDEBUG "${CMAKE_C_FLAGS_FASTDEBUG}")
string(REPLACE "/MT" "/MTd" CMAKE_C_FLAGS_FASTDEBUG "${CMAKE_C_FLAGS_FASTDEBUG}")
string(REPLACE "/MD" "/MDd" CMAKE_CXX_FLAGS_FASTDEBUG "${CMAKE_CXX_FLAGS_FASTDEBUG}")
string(REPLACE "/MT" "/MTd" CMAKE_CXX_FLAGS_FASTDEBUG "${CMAKE_CXX_FLAGS_FASTDEBUG}")
endif()
+34 -19
View File
@@ -66,33 +66,37 @@ ENDIF(EXISTS \"${CMAKE_CURRENT_BINARY_DIR}/${TARGET}/${FILE}\")
SET(${OUTVAR} "${CMAKE_COMMAND}" -P ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}/${NAME} PARENT_SCOPE)
ENDFUNCTION(EP_CHECK_FILE_EXISTS)
# Use rsync if possible for COPY_FILE_TO_TARGET macro in order to preserve
# symlinks as copy_if_different follows them which results in multiple copies
if(PLATFORM_UNIX AND NOT DEFINED RSYNC_BIN)
find_program(RSYNC_BIN rsync)
endif()
MACRO(COPY_FILE_TO_TARGET _target _file)
if (IS_DIRECTORY "${_file}")
get_filename_component(_dirName "${_file}" NAME)
if (PLATFORM_MAC AND ("${_file}" MATCHES ".framework$"))
# This is stupid, but it preserves symlinks, unlike copy_directory_if_different.
# Otherwise we end up creating duplicate files in the copied framework.
ADD_CUSTOM_COMMAND(
TARGET ${_target} POST_BUILD
COMMAND rsync -rlq "${_file}" "$<TARGET_FILE_DIR:${_target}>/${LIBRAY_DESTINATION}"
COMMENT "copying '${_file}'..."
VERBATIM
)
else()
if(RSYNC_BIN)
ADD_CUSTOM_COMMAND(
TARGET ${_target} POST_BUILD
COMMAND ${RSYNC_BIN} -rlq "${_file}" "$<TARGET_FILE_DIR:${_target}>/${LIBRAY_DESTINATION}"
COMMENT "copying '${_file}'..."
VERBATIM
)
else()
if (IS_DIRECTORY "${_file}")
get_filename_component(_dirName "${_file}" NAME)
ADD_CUSTOM_COMMAND(
TARGET ${_target} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory_if_different "${_file}" "$<TARGET_FILE_DIR:${_target}>/${LIBRAY_DESTINATION}/${_dirName}"
COMMENT "copying '${_file}'..."
VERBATIM
)
else()
ADD_CUSTOM_COMMAND(
TARGET ${_target} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_file}" "$<TARGET_FILE_DIR:${_target}>/${LIBRAY_DESTINATION}/"
COMMENT "copying '${_file}'..."
VERBATIM
)
endif()
else()
ADD_CUSTOM_COMMAND(
TARGET ${_target} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_file}" "$<TARGET_FILE_DIR:${_target}>/${LIBRAY_DESTINATION}/"
COMMENT "copying '${_file}'..."
VERBATIM
)
endif()
endmacro(COPY_FILE_TO_TARGET)
@@ -237,6 +241,17 @@ function(suppress_file_warnings)
endif()
endfunction(suppress_file_warnings)
# Suppress no symbols warnings in static libraries (on Xcode)
function(suppress_no_symbol_warnings _target)
if(XCODE)
get_target_property(target_type ${_target} TYPE)
if(target_type STREQUAL "STATIC_LIBRARY")
set_property(TARGET ${_target} APPEND PROPERTY
STATIC_LIBRARY_OPTIONS "-no_warning_for_no_symbols"
)
endif()
endif()
endfunction(suppress_no_symbol_warnings)
function(list_target_dependencies _target _out_var)
set(out_list)
+1 -1
View File
@@ -6,7 +6,7 @@ if (EXISTS "${PROJECT_SOURCE_DIR}/version_override.cmake")
include("${PROJECT_SOURCE_DIR}/version_override.cmake")
endif()
set_if_not_defined(FSO_VERSION_MAJOR 24)
set_if_not_defined(FSO_VERSION_MAJOR 26)
set_if_not_defined(FSO_VERSION_MINOR 3)
set_if_not_defined(FSO_VERSION_BUILD 0)
+7 -13
View File
@@ -39,7 +39,7 @@ TARGET_LINK_LIBRARIES(code PUBLIC ${PNG_LIBS})
TARGET_LINK_LIBRARIES(code PUBLIC ${JPEG_LIBS})
TARGET_LINK_LIBRARIES(code PUBLIC lz4)
TARGET_LINK_LIBRARIES(code PUBLIC sdl2)
TARGET_LINK_LIBRARIES(code PUBLIC sdl3)
if (FSO_BUILD_WITH_FFMPEG)
TARGET_LINK_LIBRARIES(code PUBLIC ffmpeg)
@@ -76,8 +76,6 @@ TARGET_LINK_LIBRARIES(code PUBLIC jansson)
target_link_libraries(code PUBLIC anl)
target_link_libraries(code PUBLIC hidapi::hidapi)
target_link_libraries(code PUBLIC imgui)
IF(FSO_BUILD_WITH_OPENXR)
@@ -113,16 +111,9 @@ if (FSO_BUILD_WITH_OPENGL)
target_compile_definitions(code PUBLIC WITH_OPENGL)
endif()
if (FSO_BUILD_WITH_VULKAN)
find_package(Vulkan REQUIRED)
if (Vulkan_FOUND)
target_compile_definitions(code PUBLIC WITH_VULKAN)
target_link_libraries(code PRIVATE Vulkan::Vulkan)
target_compile_definitions(code PUBLIC WITH_VULKAN VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1 VK_NO_PROTOTYPES)
else()
message(WARNING "FSO_BUILD_WITH_VULKAN was set, but the package was unable to be found. Forcing OFF.")
set(FSO_BUILD_WITH_VULKAN OFF CACHE BOOL "Enable compilation of the Vulkan renderer" FORCE)
endif()
target_compile_definitions(code PUBLIC WITH_VULKAN VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1 VK_NO_PROTOTYPES)
target_include_directories(code PUBLIC Vulkan::Headers)
target_link_libraries(code PUBLIC VulkanMemoryAllocator)
endif()
include(shaders.cmake)
@@ -139,5 +130,8 @@ set_target_properties(code PROPERTIES XCODE_ATTRIBUTE_COPY_PHASE_STRIP[variant=D
set_target_properties(code PROPERTIES XCODE_ATTRIBUTE_STRIP_INSTALLED_PRODUCT[variant=Debug] "NO")
set_target_properties(code PROPERTIES XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN[variant=Debug] "NO")
# Prevent warnings on files with no symbols on Mac
suppress_no_symbol_warnings(code)
# Prevent GCC warnings in third-party BitOp... see GitHub #4366
suppress_file_warnings(scripting/lua/bitop/bit.c)
+1 -1
View File
@@ -30,7 +30,7 @@ class BuiltinActionDefinition : public ActionDefinition {
// This should have been caught earlier
Assertion(paramIter != parameterExpressions.cend(), "Could not find built-in parameter!");
return std::unique_ptr<Action>(new TAction(paramIter->second.template asTyped<ActionValueType>()));
return std::make_unique<TAction>(paramIter->second.template asTyped<ActionValueType>());
}
};
+1 -1
View File
@@ -19,7 +19,7 @@ expression::ProgramVariables getDefaultTableVariables()
expression::ProgramVariables vars;
vars.setValue({"locals", "position"}, expression::Value(vmd_zero_vector));
vars.setValue({"locals", "direction"}, expression::Value(vmd_zero_vector));
vars.setValue({"locals", "direction"}, expression::Value(vmd_z_vector));
return vars;
}
+2 -2
View File
@@ -24,7 +24,7 @@ ActionExpression ActionExpression::parseFromTable(ValueType expectedReturnType,
stuff_string(expressionText, F_NAME);
ExpressionParser parser(expressionText);
ExpressionParser parser(std::move(expressionText));
auto expression = parser.parse(context);
@@ -45,7 +45,7 @@ ActionExpression ActionExpression::parseFromTable(ValueType expectedReturnType,
}
// Everything is valid
return ActionExpression(expression);
return ActionExpression(std::move(expression));
}
Value ActionExpression::execute(const ProgramVariables& variables) const
+1 -1
View File
@@ -6,7 +6,7 @@ namespace expression {
ProgramVariablesDefinition& ProgramVariablesDefinition::addScope(const SCP_string& name)
{
auto scope = std::unique_ptr<ProgramVariablesDefinition>(new ProgramVariablesDefinition());
auto scope = std::make_unique<ProgramVariablesDefinition>();
auto scopePtr = scope.get();
+1 -1
View File
@@ -38,7 +38,7 @@ const TypeDefinition& TypeDefinition::forValueType(ValueType type)
case ValueType::String:
return s_identifier;
default:
UNREACHABLE("Invalid value type!");
UNREACHABLE("Invalid value type %d!", static_cast<int>(type));
return s_integer; // Make compiler happy
}
}
+1 -1
View File
@@ -70,7 +70,7 @@ ActionResult MoveToSubmodel::execute(ProgramLocals& locals) const
std::unique_ptr<Action> MoveToSubmodel::clone() const
{
return std::unique_ptr<Action>(new MoveToSubmodel(*this));
return std::make_unique<MoveToSubmodel>(*this);
}
} // namespace types
+2 -2
View File
@@ -61,7 +61,7 @@ ActionResult ParticleEffectAction::execute(ProgramLocals& locals) const
matrix orientation;
vm_vector_2_matrix_norm(&orientation, &direction); // direction is normalized in SetDirectionAction::execute
source->setHost(make_unique<EffectHostObject>(locals.host.objp(), local_pos, orientation, true));
source->setHost(std::make_unique<EffectHostObject>(locals.host.objp(), local_pos, orientation, true));
source->finishCreation();
return ActionResult::Finished;
@@ -69,7 +69,7 @@ ActionResult ParticleEffectAction::execute(ProgramLocals& locals) const
std::unique_ptr<Action> ParticleEffectAction::clone() const
{
return std::unique_ptr<Action>(new ParticleEffectAction(*this));
return std::make_unique<ParticleEffectAction>(*this);
}
} // namespace types
+1 -1
View File
@@ -57,7 +57,7 @@ ActionResult PlaySoundAction::execute(ProgramLocals& locals) const
}
std::unique_ptr<Action> PlaySoundAction::clone() const
{
return std::unique_ptr<Action>(new PlaySoundAction(*this));
return std::make_unique<PlaySoundAction>(*this);
}
} // namespace types
} // namespace actions
+1 -1
View File
@@ -32,7 +32,7 @@ ActionResult SetDirectionAction::execute(ProgramLocals& locals) const
std::unique_ptr<Action> SetDirectionAction::clone() const
{
return std::unique_ptr<Action>(new SetDirectionAction(*this));
return std::make_unique<SetDirectionAction>(*this);
}
} // namespace types
+1 -1
View File
@@ -27,7 +27,7 @@ ActionResult SetPositionAction::execute(ProgramLocals& locals) const
std::unique_ptr<Action> SetPositionAction::clone() const
{
return std::unique_ptr<Action>(new SetPositionAction(*this));
return std::make_unique<SetPositionAction>(*this);
}
} // namespace types
+1 -1
View File
@@ -41,7 +41,7 @@ ActionResult WaitAction::execute(actions::ProgramLocals& locals) const
}
std::unique_ptr<Action> WaitAction::clone() const
{
return std::unique_ptr<Action>(new WaitAction(*this));
return std::make_unique<WaitAction>(*this);
}
} // namespace types
+7 -12
View File
@@ -16,9 +16,11 @@
#include "ai/ai.h"
#include "object/object.h"
#include "ship/ship.h"
#include "utils/string_utils.h"
SCP_vector<char *> Goal_target_names;
// heap-allocated rather than SCP_string, so that the pointers held by ai goals stay valid when the vector grows
SCP_vector<SCP_vm_unique_ptr<char>> Goal_target_names;
ai_info Ai_info[MAX_AI_INFO];
ai_info *Player_ai;
@@ -57,20 +59,13 @@ const char *ai_get_goal_target_name(const char *name, int *index)
Assertion(name != nullptr && index != nullptr, "Arguments cannot be null!");
for (int i = 0; i < static_cast<int>(Goal_target_names.size()); ++i) {
if (!stricmp(name, Goal_target_names[i])) {
if (!stricmp(name, Goal_target_names[i].get())) {
*index = i;
return Goal_target_names[i];
return Goal_target_names[i].get();
}
}
*index = static_cast<int>(Goal_target_names.size());
Goal_target_names.push_back(vm_strdup(name));
return Goal_target_names[*index];
}
void ai_clear_goal_target_names()
{
for (auto ptr : Goal_target_names)
vm_free(ptr);
Goal_target_names.clear();
Goal_target_names.push_back(util::vm_unique_copy(name, false));
return Goal_target_names.back().get();
}
+9 -5
View File
@@ -42,6 +42,7 @@ typedef struct ai_flag_description {
extern ai_flag_name Ai_flag_names[];
extern ai_flag_description Ai_flag_descriptions[];
extern const int Num_ai_flag_names;
extern const size_t Num_ai_flag_descriptions;
// dock_orient_and_approach() modes.
#define DOA_APPROACH 1 // Approach the current point on the path (aip->path_cur)
@@ -294,6 +295,7 @@ typedef struct ai_info {
vec3d path_create_pos; // Object's position at time of global path creation.
matrix path_create_orient; // Object's orientation at time of global path creation.
int mp_index; // Model path index. Index in polymodel:model_paths
int mp_randomized_vert; // Vertex of the model path that was given a random offset, or -1
fix path_next_check_time; // Last time checked to see if would collide with model.
int path_goal_dist; // minimum distance to first path point to consider path reached
int path_subsystem_next_check; // timestamp to next check if subsystem is still visible
@@ -493,8 +495,8 @@ extern int Mission_all_attack; // !0 means all teams attack all teams.
extern ai_info Ai_info[];
extern ai_info *Player_ai;
extern ai_class *Ai_classes;
extern char** Ai_class_names;
extern SCP_vector<ai_class> Ai_classes;
extern SCP_vector<const char*> Ai_class_names;
extern int Num_ai_classes;
extern int Ai_firing_enabled;
@@ -524,7 +526,6 @@ void ai_object_init(object * obj, int ai_index);
void ai_process( object * obj, int ai_index, float frametime );
const char *ai_get_goal_target_name(const char *name, int *index);
void ai_clear_goal_target_names();
extern void init_ai_system(void);
extern void ai_attack_object(object *attacker, object *attacked, int ship_info_index = -1, int class_type = -1);
@@ -534,6 +535,7 @@ extern void ai_ignore_wing(object *ignorer, int wingnum);
extern void ai_dock_with_object(object *docker, int docker_index, object *dockee, int dockee_index, int dock_type);
extern void ai_stay_still(object *still_objp, vec3d *view_pos);
extern void ai_do_default_behavior(object *obj);
extern void ai_reset_order_state(ai_info *aip);
extern void ai_start_waypoints(object *objp, int wl_index, int wp_flags, int start_index, bool force = false);
extern void ai_ship_hit(object *objp_ship, object *hit_objp, const vec3d *hit_normal);
extern void ai_ship_destroy(int shipnum);
@@ -596,7 +598,9 @@ extern int ai_maybe_fire_afterburner(object *objp, ai_info *aip);
extern void set_predicted_enemy_pos(vec3d *predicted_enemy_pos, object *pobjp, vec3d *enemy_pos, vec3d *enemy_vel, ai_info *aip);
extern int is_instructor(object *objp);
extern int find_enemy(int objnum, float range, int max_attackers, int ship_info_index = -1, int class_type = -1);
extern int find_enemy(int objnum, float range, int max_attackers, bool for_pursuit, int ship_info_index = -1, int class_type = -1);
extern bool ai_targets_for_pursuit(const ai_info *aip);
extern bool ai_declines_pursuit(const object *attacker_objp, const object *target_objp);
float ai_get_weapon_speed(const ship_weapon *swp);
void set_predicted_enemy_pos_turret(vec3d *predicted_enemy_pos, const vec3d *gun_pos, const object *pobjp, const vec3d *enemy_pos, const vec3d *enemy_vel, float weapon_speed, float time_enemy_in_range);
@@ -617,7 +621,7 @@ extern float dock_orient_and_approach(object *docker_objp, int docker_index, obj
void ai_set_mode_warp_out(object *objp, ai_info *aip);
// prototyped by Goober5000
int get_nearest_objnum(int objnum, int enemy_team_mask, int enemy_wing, float range, int max_attackers, int ship_info_index, int class_type = -1);
int get_nearest_objnum(int objnum, int enemy_team_mask, int enemy_wing, float range, int max_attackers, bool for_pursuit, int ship_info_index, int class_type = -1);
// moved to header file by Goober5000
void ai_announce_ship_dying(object *dying_objp);
+19
View File
@@ -132,6 +132,7 @@ namespace AI {
Smart_shield_management,
Smart_subsystem_targeting_for_turrets,
Strict_turret_tagged_only_targeting,
Ships_intercept_mines, // all AI-controlled ships autonomously engage hostile mines within mine_targetable_range of themselves
Support_dont_add_primaries, //Prevents support ship from equipping new primary as requested in https://scp.indiegames.us/mantis/view.php?id=3198
Turrets_ignore_target_radius,
Use_actual_primary_range,
@@ -175,6 +176,12 @@ namespace AI {
Dont_limit_change_in_speed_due_to_physics_whack,
Guards_ignore_protected_attackers,
Fix_standard_strafe,
Fix_ai_target_recovery, // a) strafing ships that lose their target re-process their orders, like chasing ships already do
// b) ships parked in AIM_NONE while holding a standing chase order (attack-any etc.) re-process their orders;
// c) target selection for pursuit (chasing, guard reactions, retaliation) skips ships outside the attacker's
// actively-pursues list; targets held while not chasing are unaffected, and explicitly ordered targets are
// still assigned but not chased or strafed;
// d) enemy_wing is reset when a new order takes over (shared with Fix_stale_ai_order_state)
Standard_strafe_used_more,
Unify_usage_ai_shield_manage_delay,
Fix_AI_shield_management_bug,
@@ -189,6 +196,18 @@ namespace AI {
Do_not_set_override_when_assigning_form_on_wing,
Purge_player_issued_form_on_wing_after_subsequent_order,
Cancel_future_waves_of_any_wing_launched_from_an_exited_ship,
Fix_ignore_if_dead_flag,
Kamikaze_no_collision_avoidance,
Fix_big_ship_waypoint_completion, // a) big ships complete a waypoint within their radius rather than sqrt(radius);
// b) completion no longer requires moving 0.1m in a single frame (framerate-dependent)
Fix_shockwave_expire_before_do_damage, // shockwaves whose lifetime is shorter than one frame apply their area damage at least once before expiring
Fix_small_ai_recover_after_engines_repaired, // ensure small ship AI can switch back to useful AI modes if engines get repaired
Fix_model_path_refresh_randomization, // a subsystem path keeps its randomized point when the path is refreshed because the target moved
Fix_bay_speed_ramp, // the fighterbay speed ramp covers only the bay path itself, not points that route a departing ship around the carrier, and never exceeds max speed
Fix_stale_ai_order_state, // a) enemy_wing, guard_*, previous_mode, resume_goal_time, and the targeted subsystem (with its path) are reset when a new order takes over (enemy_wing is also reset under Fix_ai_target_recovery);
// b) a dynamic chase saves previous_mode/previous_submode; it and strafe entry leave them alone during an ongoing dynamic goal (evading a weapon still overwrites them, which is harmless since the evade resume re-processes orders);
// c) resuming guard duty discards any subsystem target and path left over from the interruption;
// d) a ship ordered to guard itself, or to guard a wing it leads (including one that becomes leader mid-mission), treats that goal as unachievable instead of holding it forever
NUM_VALUES
};
+51 -7
View File
@@ -42,6 +42,7 @@ void set_flag(ai_profile_t *profile, const char *name, AI::Profile_Flags flag)
const char *AI_path_types[] = {
"normal",
"alt1",
"alt2",
};
int Num_ai_path_types = sizeof(AI_path_types)/sizeof(char*);
@@ -139,7 +140,7 @@ void parse_ai_profiles_tbl(const char *filename)
// don't create multiple profiles with the same name
if (previous_profile != NULL)
{
Warning(LOCATION, "An ai profile named '%s' already exists! The new one will not be created.\n", profile_name);
Warning(LOCATION, "An AI profile named '%s' already exists! The new one will not be created.\n", profile_name);
}
else
{
@@ -411,7 +412,7 @@ void parse_ai_profiles_tbl(const char *filename)
set_flag(profile, "$disable player secondary doublefire:", AI::Profile_Flags::Disable_player_secondary_doublefire);
set_flag(profile, "$disable ai secondary doublefire:", AI::Profile_Flags::Disable_ai_secondary_doublefire);
set_flag(profile, "$disable AI secondary doublefire:", AI::Profile_Flags::Disable_ai_secondary_doublefire);
set_flag(profile, "$disable weapon damage scaling:", AI::Profile_Flags::Disable_weapon_damage_scaling);
@@ -449,7 +450,7 @@ void parse_ai_profiles_tbl(const char *filename)
set_flag(profile, "$force beam turrets to use normal fov:", AI::Profile_Flags::Force_beam_turret_fov);
set_flag(profile, "$fix ai class bug:", AI::Profile_Flags::Fix_ai_class_bug);
set_flag(profile, "$fix AI class bug:", AI::Profile_Flags::Fix_ai_class_bug);
set_flag(profile, "$turrets ignore targets radius in range checks:", AI::Profile_Flags::Turrets_ignore_target_radius);
@@ -504,13 +505,13 @@ void parse_ai_profiles_tbl(const char *filename)
profile->ai_path_mode = j;
}
else {
Warning(LOCATION, "Invalid ai path mode '%s' specified", buf);
Warning(LOCATION, "Invalid AI path mode '%s' specified", buf);
}
}
set_flag(profile, "$no warp camera:", AI::Profile_Flags::No_warp_camera);
set_flag(profile, "$fix ai path order bug:", AI::Profile_Flags::Fix_ai_path_order_bug);
set_flag(profile, "$fix AI path order bug:", AI::Profile_Flags::Fix_ai_path_order_bug);
set_flag(profile, "$strict turret-tagged-only targeting:", AI::Profile_Flags::Strict_turret_tagged_only_targeting);
@@ -621,7 +622,7 @@ void parse_ai_profiles_tbl(const char *filename)
profile->ai_range_aware_secondary_select_mode = j;
}
else {
Warning(LOCATION, "Invalid ai secondary range awareness mode '%s' specified", buf);
Warning(LOCATION, "Invalid AI secondary range awareness mode '%s' specified", buf);
}
}
@@ -676,6 +677,8 @@ void parse_ai_profiles_tbl(const char *filename)
set_flag(profile, "$fix standard strafe:", AI::Profile_Flags::Fix_standard_strafe);
set_flag(profile, "$fix AI target recovery:", AI::Profile_Flags::Fix_ai_target_recovery);
set_flag(profile, "$standard strafe used more:", AI::Profile_Flags::Standard_strafe_used_more);
if (optional_string("$standard strafe triggers under this speed:")) {
@@ -690,6 +693,14 @@ void parse_ai_profiles_tbl(const char *filename)
stuff_float(&profile->strafe_max_unhit_time);
}
if (optional_string("$strafe retreat collide time:")) {
stuff_float(&profile->strafe_retreat_collide_time);
}
if (optional_string("$strafe retreat collide distance:")) {
stuff_float(&profile->strafe_retreat_collide_distance);
}
if (optional_string("$guard uses big-orbit for target radius above:")) {
stuff_float(&profile->guard_big_orbit_above_target_radius);
}
@@ -744,6 +755,23 @@ void parse_ai_profiles_tbl(const char *filename)
set_flag(profile, "$cancel future waves of any wing launched from an exited ship:", AI::Profile_Flags::Cancel_future_waves_of_any_wing_launched_from_an_exited_ship);
set_flag(profile, "$ships intercept mines:", AI::Profile_Flags::Ships_intercept_mines);
set_flag(profile, "$fix 'ignore if dead' flag:", AI::Profile_Flags::Fix_ignore_if_dead_flag);
set_flag(profile, "$no collision avoidance for kamikaze fighters:", AI::Profile_Flags::Kamikaze_no_collision_avoidance);
set_flag(profile, "$fix big ship waypoint completion:", AI::Profile_Flags::Fix_big_ship_waypoint_completion);
set_flag(profile, "$fix shockwave expiring before dealing damage:", AI::Profile_Flags::Fix_shockwave_expire_before_do_damage);
set_flag(profile, "$fix fighter/bomber AI recovers after engines repaired:", AI::Profile_Flags::Fix_small_ai_recover_after_engines_repaired);
set_flag(profile, "$fix subsystem path randomization on refresh:", AI::Profile_Flags::Fix_model_path_refresh_randomization);
set_flag(profile, "$fix fighterbay speed ramp:", AI::Profile_Flags::Fix_bay_speed_ramp);
set_flag(profile, "$fix stale AI order state:", AI::Profile_Flags::Fix_stale_ai_order_state);
// end of options ----------------------------------------
@@ -812,7 +840,7 @@ void ai_profiles_init()
Ai_profiles_initted = 1;
}
int ai_profile_lookup(char *name)
int ai_profile_lookup(const char *name)
{
for (int i = 0; i < Num_ai_profiles; i++)
if (!stricmp(name, Ai_profiles[i].profile_name))
@@ -844,6 +872,8 @@ void ai_profile_t::reset()
standard_strafe_when_below_speed = 3.0f;
strafe_retreat_box_dist = 300.0f;
strafe_max_unhit_time = 20.0f;
strafe_retreat_collide_time = 2.0f;
strafe_retreat_collide_distance = 100.0f;
guard_big_orbit_above_target_radius = 500.0f;
guard_big_orbit_max_speed_percent = 1.0f;
@@ -967,4 +997,18 @@ void ai_profile_t::reset()
flags.set(AI::Profile_Flags::Purge_player_issued_form_on_wing_after_subsequent_order);
flags.set(AI::Profile_Flags::Cancel_future_waves_of_any_wing_launched_from_an_exited_ship);
}
if (mod_supports_version(26, 0, 0)) {
flags.set(AI::Profile_Flags::Fix_ignore_if_dead_flag);
}
if (mod_supports_version(26, 2, 0)) {
flags.set(AI::Profile_Flags::Kamikaze_no_collision_avoidance);
flags.set(AI::Profile_Flags::Fix_big_ship_waypoint_completion);
flags.set(AI::Profile_Flags::Fix_shockwave_expire_before_do_damage);
flags.set(AI::Profile_Flags::Fix_small_ai_recover_after_engines_repaired);
flags.set(AI::Profile_Flags::Fix_standard_strafe);
flags.set(AI::Profile_Flags::Fix_ai_target_recovery);
flags.set(AI::Profile_Flags::Fix_model_path_refresh_randomization);
flags.set(AI::Profile_Flags::Fix_bay_speed_ramp);
flags.set(AI::Profile_Flags::Fix_stale_ai_order_state);
}
}
+5 -2
View File
@@ -17,6 +17,7 @@
// AI Path types
#define AI_PATH_MODE_NORMAL 0
#define AI_PATH_MODE_ALT1 1
#define AI_PATH_MODE_ALT2 2
#define AI_RANGE_AWARE_SEC_SEL_MODE_RETAIL 0
#define AI_RANGE_AWARE_SEC_SEL_MODE_AWARE 1
@@ -142,6 +143,8 @@ public:
float standard_strafe_when_below_speed; // Speed at which standard strafing large ships is possibly triggered
float strafe_retreat_box_dist; // Distance beyond the bounding box to retreat to strafing point
float strafe_max_unhit_time; // Maximum amount of time to stay in strafe mode if not hit
float strafe_retreat_collide_time; // When anticipated collision time is less than this, begin retreat from strafe
float strafe_retreat_collide_distance; // When perpendicular distance to *surface* is less than this, begin retreat from strafe
// AI guard options --wookieejedi
float guard_big_orbit_above_target_radius; // Radius of guardee that triggers ai_big_guard()
@@ -160,10 +163,10 @@ extern int Num_ai_profiles;
extern int Default_ai_profile;
extern ai_profile_t Ai_profiles[MAX_AI_PROFILES];
#define AI_PROFILES_INDEX(ai_p) ((int)((ai_p) - Ai_profiles))
#define AI_PROFILES_INDEX(ai_p) (static_cast<int>((ai_p)-Ai_profiles))
void ai_profiles_init();
int ai_profile_lookup(char *name);
int ai_profile_lookup(const char *name);
#endif
+90 -75
View File
@@ -40,9 +40,6 @@
// AI BIG MAGIC NUMBERS
// Select strafing options are now exposed to modders --wookieejedi
#define STRAFE_RETREAT_COLLIDE_TIME 2.0 // when anticipated collision time is less than this, begin retreat
#define STRAFE_RETREAT_COLLIDE_DIST 100 // when perpendicular distance to *surface* is less than this, begin retreat
#define EVADE_BOX_BASE_DISTANCE 300 // standard distance to end evade submode
#define EVADE_BOX_MIN_DISTANCE 200 // minimun distance to end evade submode, after long time
@@ -69,6 +66,16 @@ static bool ai_big_strafe_maybe_retreat(const vec3d *target_pos);
extern void compute_desired_rvec(vec3d *rvec, const vec3d *goal_pos, const vec3d *cur_pos);
extern void big_ship_collide_recover_start(const object *objp, const object *big_objp, const vec3d *collision_normal);
// Record the mode being left as the one to return to, unless a dynamic goal is already in progress
// (in which case previous_mode still refers to the mode that goal interrupted).
static void ai_big_save_previous_mode(ai_info *aip)
{
if (The_mission.ai_profile->flags[AI::Profile_Flags::Fix_stale_ai_order_state] && aip->active_goal == AI_ACTIVE_GOAL_DYNAMIC)
return;
aip->previous_mode = aip->mode;
}
// Called by ai_big_pick_attack_point.
// Generates a random attack point.
@@ -502,7 +509,7 @@ bool ai_big_maybe_follow_subsys_path(bool do_dot_check)
// for now, only follow the path to the first point
if ( aip->path_cur < (aip->path_start+aip->path_length-1) ) {
if ( aip->goal_objnum != aip->target_objnum ) {
aip->previous_mode = aip->mode;
ai_big_save_previous_mode(aip);
aip->mode = AIM_NONE;
aip->submode = -1;
aip->submode_start_time = Missiontime;
@@ -709,7 +716,8 @@ void ai_big_chase_attack(ai_info *aip, ship_info *sip, vec3d *enemy_pos, float d
if (vm_vec_dot(&in_vec, &objp->orient.vec.fvec) > 0.0f) {
dist = vm_vec_normalize(&in_vec);
if ((dist < 200.0f) && (vm_vec_dot(&in_vec, &objp->orient.vec.fvec) > 0.95f)) {
if ((Objects[objp->parent].signature == objp->parent_sig) && (vm_vec_dist_quick(&objp->pos, &Objects[objp->parent].pos) < 300.0f)) {
if ((Objects[objp->parent].signature == objp->parent_sig) && (vm_vec_dist_quick(&objp->pos, &Objects[objp->parent].pos) < 300.0f)
&& !ai_declines_pursuit(Pl_objp, &Objects[objp->parent])) {
set_target_objnum(aip, objp->parent);
aip->submode = SM_ATTACK;
aip->submode_start_time = Missiontime;
@@ -730,7 +738,7 @@ void ai_big_chase_attack(ai_info *aip, ship_info *sip, vec3d *enemy_pos, float d
// including ai_profile flag and if enemy fighters are near
if (Pl_objp->phys_info.speed < The_mission.ai_profile->standard_strafe_when_below_speed &&
(The_mission.ai_profile->flags[AI::Profile_Flags::Standard_strafe_used_more] || ai_big_maybe_start_strafe(aip, sip))) {
aip->previous_mode = aip->mode;
ai_big_save_previous_mode(aip);
aip->mode = AIM_STRAFE;
aip->submode_parm0 = Missiontime; // use parm0 as time strafe mode entered (i.e. MODE start time)
ai_big_strafe_position();
@@ -739,7 +747,7 @@ void ai_big_chase_attack(ai_info *aip, ship_info *sip, vec3d *enemy_pos, float d
//Maybe enter glide strafe (check every 8 seconds, on a different schedule for each ship)
if ((sip->can_glide == true) && !(aip->ai_flags[AI::AI_Flags::Kamikaze]) && static_randf((Missiontime + static_rand(aip->shipnum)) >> 19) < aip->ai_glide_strafe_percent) {
aip->previous_mode = aip->mode;
ai_big_save_previous_mode(aip);
aip->mode = AIM_STRAFE;
aip->submode_parm0 = Missiontime; // use parm0 as time strafe mode entered (i.e. MODE start time)
aip->submode = AIS_STRAFE_GLIDE_ATTACK;
@@ -1011,7 +1019,7 @@ static void ai_big_maybe_fire_weapons(float dist_to_enemy, float dot_to_enemy)
// switch ai ship into chase mode
void ai_big_switch_to_chase_mode(ai_info *aip)
{
aip->previous_mode = aip->mode;
ai_big_save_previous_mode(aip);
aip->mode = AIM_CHASE;
aip->submode = SM_ATTACK;
aip->submode_start_time = Missiontime;
@@ -1111,7 +1119,7 @@ void ai_big_chase()
} else if (En_objp->flags[Object::Object_Flags::Protected]) { // If protected and we're not attacking a subsystem, stop attacking!
update_aspect_lock_information(aip, &vec_to_enemy, dist_to_enemy - En_objp->radius, En_objp->radius);
aip->target_objnum = -1;
if (find_enemy(OBJ_INDEX(Pl_objp), MAX_ENEMY_DISTANCE, The_mission.ai_profile->max_attackers[Game_skill_level]) == -1) {
if (find_enemy(OBJ_INDEX(Pl_objp), MAX_ENEMY_DISTANCE, The_mission.ai_profile->max_attackers[Game_skill_level], ai_targets_for_pursuit(aip)) == -1) {
ai_do_default_behavior(Pl_objp);
return;
}
@@ -1198,70 +1206,78 @@ void ai_big_chase()
}
// If a collision is expected, pull out!
float dist_normal_to_enemy;
if (!(The_mission.ai_profile->flags[AI::Profile_Flags::Kamikaze_no_collision_avoidance] && aip->ai_flags[AI::AI_Flags::Kamikaze])) {
if (vm_vec_mag_squared(&aip->big_attack_surface_normal) > 0.9) {
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing])
dist_normal_to_enemy = fl_abs((dist_to_enemy / vm_vec_dot(&vec_to_enemy, &aip->big_attack_surface_normal)));
else // I'm fairly sure this retail code VVV meant to use / instead of *
dist_normal_to_enemy = fl_abs((dist_to_enemy * vm_vec_dot(&vec_to_enemy, &aip->big_attack_surface_normal)));
} else {
// don;t have normal so use a conservative value here
dist_normal_to_enemy = 0.3f * dist_to_enemy;
}
float dist_normal_to_enemy;
// float time_to_enemy = dist_normal_to_enemy / Pl_objp->phys_info.speed * fl_abs(vm_vec_dot(&Pl_objp->phys_info.vel, &aip->big_attack_surface_normal));
// if (Framecount % 30 == 1) {
// mprintf(("normal dist; %.1f, time: %.1f\n", dist_normal_to_enemy, time_to_enemy));
// }
// since we're not in strafe and we may get a bad normal, cap dist_normal_to_enemy as MIN(0.3*dist_to_enemy, self)
// this will allow us to get closer on a bad normal
dist_normal_to_enemy = MAX(0.3f*dist_to_enemy, dist_normal_to_enemy);
if (dist_to_enemy < ATTACK_COLLIDE_BASE_DIST) {
// within 50m or 1sec
float time_to_enemy;
if (vm_vec_mag_squared(&aip->big_attack_surface_normal) > 0.9) {
if (Pl_objp->phys_info.speed > 0.1) {
time_to_enemy = dist_normal_to_enemy / fl_abs(vm_vec_dot(&Pl_objp->phys_info.vel, &aip->big_attack_surface_normal));
} else {
// a big time
time_to_enemy = 100.0f;
}
} else {
if (Pl_objp->phys_info.speed > 0.1) {
time_to_enemy = dist_normal_to_enemy / Pl_objp->phys_info.speed;
} else {
// a big time
time_to_enemy = 100.0f;
}
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing])
dist_normal_to_enemy = fl_abs((dist_to_enemy / vm_vec_dot(&vec_to_enemy, &aip->big_attack_surface_normal)));
else // I'm fairly sure this retail code VVV meant to use / instead of *
dist_normal_to_enemy = fl_abs((dist_to_enemy * vm_vec_dot(&vec_to_enemy, &aip->big_attack_surface_normal)));
}
else {
// don;t have normal so use a conservative value here
dist_normal_to_enemy = 0.3f * dist_to_enemy;
}
float speed_dist = MAX(0.0f, (Pl_objp->phys_info.speed-50) * 2);
if ((dist_normal_to_enemy < ATTACK_COLLIDE_AVOID_DIST + speed_dist) || (time_to_enemy < ATTACK_COLLIDE_AVOID_TIME) ) {
// get away, simulate crsh recovery (don't use avoid)
// accelerate_ship(aip, -1.0f);
big_ship_collide_recover_start(Pl_objp, En_objp, nullptr);
// aip->submode = SM_AVOID;
// aip->submode_start_time = Missiontime;
} else if ((dist_normal_to_enemy < ATTACK_COLLIDE_SLOW_DIST) || (time_to_enemy < ATTACK_COLLIDE_SLOW_TIME) ) {
// slow down
accelerate_ship(aip, -1.0f);
}
}
// float time_to_enemy = dist_normal_to_enemy / Pl_objp->phys_info.speed * fl_abs(vm_vec_dot(&Pl_objp->phys_info.vel, &aip->big_attack_surface_normal));
// if (Framecount % 30 == 1) {
// mprintf(("normal dist; %.1f, time: %.1f\n", dist_normal_to_enemy, time_to_enemy));
// }
/*
if ((dot_to_enemy > 1.0f - 0.1f * En_objp->radius/(dist_to_enemy + 1.0f)) && (Pl_objp->phys_info.speed > dist_to_enemy/5.0f)) {
if (might_collide_with_ship(Pl_objp, En_objp, dot_to_enemy, dist_to_enemy, (aip->targeted_subsys == NULL)*2.0f + 1.5f)) {
if ((Missiontime - aip->last_hit_time > F1_0*4) && (dist_to_enemy < Pl_objp->radius*2 + En_objp->radius*2)) {
// since we're not in strafe and we may get a bad normal, cap dist_normal_to_enemy as MIN(0.3*dist_to_enemy, self)
// this will allow us to get closer on a bad normal
dist_normal_to_enemy = MAX(0.3f * dist_to_enemy, dist_normal_to_enemy);
if (dist_to_enemy < ATTACK_COLLIDE_BASE_DIST) {
// within 50m or 1sec
float time_to_enemy;
if (vm_vec_mag_squared(&aip->big_attack_surface_normal) > 0.9) {
if (Pl_objp->phys_info.speed > 0.1) {
time_to_enemy = dist_normal_to_enemy / fl_abs(vm_vec_dot(&Pl_objp->phys_info.vel, &aip->big_attack_surface_normal));
}
else {
// a big time
time_to_enemy = 100.0f;
}
}
else {
if (Pl_objp->phys_info.speed > 0.1) {
time_to_enemy = dist_normal_to_enemy / Pl_objp->phys_info.speed;
}
else {
// a big time
time_to_enemy = 100.0f;
}
}
float speed_dist = MAX(0.0f, (Pl_objp->phys_info.speed - 50) * 2);
if ((dist_normal_to_enemy < ATTACK_COLLIDE_AVOID_DIST + speed_dist) || (time_to_enemy < ATTACK_COLLIDE_AVOID_TIME)) {
// get away, simulate crsh recovery (don't use avoid)
// accelerate_ship(aip, -1.0f);
big_ship_collide_recover_start(Pl_objp, En_objp, nullptr);
// aip->submode = SM_AVOID;
// aip->submode_start_time = Missiontime;
}
else if ((dist_normal_to_enemy < ATTACK_COLLIDE_SLOW_DIST) || (time_to_enemy < ATTACK_COLLIDE_SLOW_TIME)) {
// slow down
accelerate_ship(aip, -1.0f);
} else {
aip->submode = SM_AVOID;
aip->submode_start_time = Missiontime;
}
}
} */
/*
if ((dot_to_enemy > 1.0f - 0.1f * En_objp->radius/(dist_to_enemy + 1.0f)) && (Pl_objp->phys_info.speed > dist_to_enemy/5.0f)) {
if (might_collide_with_ship(Pl_objp, En_objp, dot_to_enemy, dist_to_enemy, (aip->targeted_subsys == NULL)*2.0f + 1.5f)) {
if ((Missiontime - aip->last_hit_time > F1_0*4) && (dist_to_enemy < Pl_objp->radius*2 + En_objp->radius*2)) {
accelerate_ship(aip, -1.0f);
} else {
aip->submode = SM_AVOID;
aip->submode_start_time = Missiontime;
}
}
} */
}
}
switch (aip->submode) {
@@ -1444,8 +1460,8 @@ static bool ai_big_strafe_maybe_retreat(const vec3d *target_pos)
collide_time = false;
collide_distance = false;
} else {
collide_time = (dist_to_target / Pl_objp->phys_info.speed) < STRAFE_RETREAT_COLLIDE_TIME;
collide_distance = dist_to_target < (STRAFE_RETREAT_COLLIDE_DIST + speed_to_dist_penalty);
collide_time = (Pl_objp->phys_info.speed > 0.0f) && (dist_to_target / Pl_objp->phys_info.speed) < (The_mission.ai_profile->strafe_retreat_collide_time);
collide_distance = dist_to_target < ((The_mission.ai_profile->strafe_retreat_collide_distance) + speed_to_dist_penalty);
}
} else {
float dist_normal_to_target;
@@ -1455,8 +1471,8 @@ static bool ai_big_strafe_maybe_retreat(const vec3d *target_pos)
} else {
dist_normal_to_target = 0.2f * dist_to_target;
}
collide_time = (dist_normal_to_target / Pl_objp->phys_info.speed) < STRAFE_RETREAT_COLLIDE_TIME;
collide_distance = dist_normal_to_target < (STRAFE_RETREAT_COLLIDE_DIST + speed_to_dist_penalty);
collide_time = (Pl_objp->phys_info.speed > 0.0f) && (dist_normal_to_target / Pl_objp->phys_info.speed) < (The_mission.ai_profile->strafe_retreat_collide_time);
collide_distance = dist_normal_to_target < ((The_mission.ai_profile->strafe_retreat_collide_distance) + speed_to_dist_penalty);
}
//if ((dot_to_enemy > 1.0f - 0.1f * En_objp->radius/(dist_to_enemy + 1.0f)) && (Pl_objp->phys_info.speed > dist_to_enemy/5.0f))
@@ -1862,7 +1878,7 @@ void ai_big_strafe()
/*
if ( aip->goal_objnum != aip->target_objnum ) {
UNREACHABLE("The goal objnum does not match the target objnum in ai_big_strafe(), please report to the SCP!"); // what is going on here? - Get Alan
Assertion(false, "The goal objnum does not match the target objnum in ai_big_strafe(), please report to the SCP!"); // what is going on here? - Get Alan
aip->mode = AIM_NONE;
return;
}
@@ -1963,26 +1979,25 @@ int ai_big_maybe_enter_strafe_mode(const object *pl_objp, int weapon_objnum)
return 0;
}
if (ai_declines_pursuit(pl_objp, parent_objp)) {
return 0;
}
// Maybe the ship which fired the weapon isn't the current target
if ( OBJ_INDEX(parent_objp) != aip->target_objnum ) {
//JAS IMPOSSIBLE if (1) { // consider_target_only ) {
//JAS IMPOSSIBLE return 0;
//JAS IMPOSSIBLE } else {
// switch targets
sip = &Ship_info[Ships[parent_objp->instance].ship_info_index];
if ( !(sip->is_big_or_huge()) || (sip->flags[Ship::Info_Flags::Transport]) ) {
return 0;
}
set_target_objnum(aip, OBJ_INDEX(parent_objp));
//JAS IMPOSSIBLE }
}
ai_big_strafe_maybe_attack_turret(pl_objp, weapon_objp);
// if we've got this far, the weapon must have come from the player's target, and it is a
// big/capital ship... so enter strafe mode
aip->previous_mode = aip->mode;
ai_big_save_previous_mode(aip);
aip->mode = AIM_STRAFE;
aip->submode_parm0 = Missiontime; // use parm0 as time strafe mode entered (i.e. MODE start time)
aip->submode = AIS_STRAFE_AVOID;
+494 -179
View File
File diff suppressed because it is too large. Load diff
View File
Whitespace-only changes.
+72 -19
View File
@@ -825,7 +825,8 @@ void ai_add_goal_sub_player(ai_goal_type type, ai_goal_mode mode, int submode, c
// friendlies want to rearm at the same time. The support ship forgets what it's doing and flies
// off to repair somebody while still docked. I reproduced this with retail, so it's not a bug in
// my new docking code. :)
int ai_goal_find_empty_slot( ai_goal *goals, int active_goal )
// Note that this function is now where Purge_when_new_goal_added is checked to set the Purge flag.
int ai_goal_allocate_slot( ai_goal *goals, int active_goal )
{
int oldest_index = -1, first_empty_index = -1;
@@ -910,12 +911,29 @@ void ai_add_goal_sub_scripting(ai_goal_type type, ai_goal_mode mode, int submode
}
}
void ai_add_ship_goal_scripting(ai_goal_mode mode, int submode, int priority, const char *shipname, ai_info *aip, int int_data, float float_data)
// find a dockpoint on the given ship that is compatible with the specified dock-type flags (c.f. ai_goal_fixup_dockpoints)
static int ai_goal_find_compatible_dockpoint(int shipnum, int type_flags)
{
for (int i = 0; i < Num_dock_type_names; i++)
{
if (type_flags & Dock_type_names[i].def)
{
int dock_index = ai_goal_find_dockpoint(shipnum, Dock_type_names[i].def);
if (dock_index >= 0)
return dock_index;
}
}
// no compatible type-specific point found; fall back to a generic one
return ai_goal_find_dockpoint(shipnum, DOCK_TYPE_GENERIC);
}
void ai_add_ship_goal_scripting(ai_goal_mode mode, int submode, int priority, const char *shipname, ai_info *aip, int int_data, float float_data, int docker_index, int dockee_index)
{
int empty_index;
ai_goal *aigp;
empty_index = ai_goal_find_empty_slot(aip->goals, aip->active_goal);
empty_index = ai_goal_allocate_slot(aip->goals, aip->active_goal);
aigp = &aip->goals[empty_index];
ai_add_goal_sub_scripting(ai_goal_type::PLAYER_SHIP, mode, submode, priority, shipname, aigp, int_data, float_data);
@@ -926,7 +944,27 @@ void ai_add_ship_goal_scripting(ai_goal_mode mode, int submode, int priority, co
}
if ( (mode == AI_GOAL_REARM_REPAIR) || (mode == AI_GOAL_DOCK) ) {
ai_goal_fixup_dockpoints( aip, aigp );
// if the caller specified one or both dockpoints, honor them and choose the other side to match
if ( mode == AI_GOAL_DOCK && (docker_index >= 0 || dockee_index >= 0) ) {
int dockee_shipnum = ship_name_lookup(shipname);
Assertion(dockee_shipnum >= 0, "Couldn't find dock target '%s'; get a coder!", shipname);
// if only one point was given, choose the other to match its dock type
if ( docker_index < 0 ) {
polymodel *dockee_pm = model_get(Ship_info[Ships[dockee_shipnum].ship_info_index].model_num);
docker_index = ai_goal_find_compatible_dockpoint(aip->shipnum, dockee_pm->docking_bays[dockee_index].type_flags);
} else if ( dockee_index < 0 ) {
polymodel *docker_pm = model_get(Ship_info[Ships[aip->shipnum].ship_info_index].model_num);
dockee_index = ai_goal_find_compatible_dockpoint(dockee_shipnum, docker_pm->docking_bays[docker_index].type_flags);
}
aigp->docker.index = docker_index;
aigp->dockee.index = dockee_index;
aigp->flags.set(AI::Goal_Flags::Docker_index_valid);
aigp->flags.set(AI::Goal_Flags::Dockee_index_valid);
} else {
ai_goal_fixup_dockpoints( aip, aigp );
}
}
}
@@ -939,7 +977,7 @@ void ai_add_ship_goal_player(ai_goal_type type, ai_goal_mode mode, int submode,
int empty_index;
ai_goal *aigp;
empty_index = ai_goal_find_empty_slot( aip->goals, aip->active_goal );
empty_index = ai_goal_allocate_slot( aip->goals, aip->active_goal );
aigp = &aip->goals[empty_index];
ai_add_goal_sub_player( type, mode, submode, shipname, aigp, int_data, float_data, lua_target );
@@ -973,7 +1011,7 @@ void ai_add_wing_goal_player(ai_goal_type type, ai_goal_mode mode, int submode,
// add the sexpression index into the wing's list of goal sexpressions if
// there are more waves to come. We use the same method here as when adding a goal to
// a ship -- find the first empty entry. If none exists, take the oldest entry and replace it.
empty_index = ai_goal_find_empty_slot( wingp->ai_goals, -1 );
empty_index = ai_goal_allocate_slot( wingp->ai_goals, -1 );
ai_add_goal_sub_player( type, mode, submode, shipname, &wingp->ai_goals[empty_index], int_data, float_data, lua_target );
}
@@ -1252,7 +1290,7 @@ void ai_add_goal_sub_sexp( int sexp, ai_goal_type type, ai_info *aip, ai_goal *a
} else if ( op == OP_AI_IGNORE_NEW ) {
aigp->ai_mode = AI_GOAL_IGNORE_NEW;
} else
UNREACHABLE("Coding error: unhandled AI goal in ai_add_goal_sub_sexp!");
UNREACHABLE("Coding error: unhandled AI goal %d in ai_add_goal_sub_sexp!", op);
break;
@@ -1613,7 +1651,7 @@ void ai_add_ship_goal_sexp( int sexp, ai_goal_type type, ai_info *aip )
{
int gindex;
gindex = ai_goal_find_empty_slot( aip->goals, aip->active_goal );
gindex = ai_goal_allocate_slot( aip->goals, aip->active_goal );
ai_add_goal_sub_sexp( sexp, type, aip, &aip->goals[gindex], Ships[aip->shipnum].ship_name );
}
@@ -1637,7 +1675,7 @@ void ai_add_wing_goal_sexp(int sexp, ai_goal_type type, wing *wingp)
if ((wingp->num_waves - wingp->current_wave > 0) || Fred_running) {
int gindex;
gindex = ai_goal_find_empty_slot( wingp->ai_goals, -1 );
gindex = ai_goal_allocate_slot( wingp->ai_goals, -1 );
ai_add_goal_sub_sexp( sexp, type, nullptr, &wingp->ai_goals[gindex], wingp->name );
}
}
@@ -1660,7 +1698,7 @@ void ai_add_goal_ship_internal( ai_info *aip, int goal_type, char *name, int /*
Assertion(strcmp(name, Ships[aip->shipnum].ship_name) != 0, "The goals apply to the actor in ai_add_goal_ship_internal for ship %s, please report to the SCP!", name);
// find an empty slot to put this goal in.
gindex = ai_goal_find_empty_slot( aip->goals, aip->active_goal );
gindex = ai_goal_allocate_slot( aip->goals, aip->active_goal );
aigp = &(aip->goals[gindex]);
ai_goal_reset(aigp, true);
@@ -1765,6 +1803,20 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
auto target_ship_entry = aigp->target_name == nullptr ? nullptr : ship_registry_get(aigp->target_name);
// a ship can't guard itself, and the leader of a wing is the ship the rest of the wing guards
if (The_mission.ai_profile->flags[AI::Profile_Flags::Fix_stale_ai_order_state])
{
if (aigp->ai_mode == AI_GOAL_GUARD && target_ship_entry && target_ship_entry->has_shipp() && target_ship_entry->shipp() == shipp)
return ai_achievability::NOT_ACHIEVABLE;
if (aigp->ai_mode == AI_GOAL_GUARD_WING)
{
int wingnum = wing_name_lookup(aigp->target_name);
if (wingnum >= 0 && Wings[wingnum].ship_index[0] == objp->instance)
return ai_achievability::NOT_ACHIEVABLE;
}
}
// warp (depart) only achievable if there's somewhere to depart to
if (aigp->ai_mode == AI_GOAL_WARP)
{
@@ -1789,7 +1841,7 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
if (!target_ship_entry || !target_ship_entry->has_shipp())
return ai_achievability::NOT_ACHIEVABLE;
// the override flag is now set in the calling function, ai_mission_goal_achievable
// the override flag is now set in the calling function, validate_mission_goals
return ai_achievability::ACHIEVABLE;
}
@@ -2055,7 +2107,7 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
if (target_ship_entry->shipp()->subsys_info[SUBSYSTEM_TURRET].type_count == 0)
return ai_achievability::NOT_ACHIEVABLE;
} else {
UNREACHABLE("Target name %s is not an arrived ship!", aigp->target_name);
Assertion(false, "Target name %s is not an arrived ship!", aigp->target_name);
return ai_achievability::NOT_ACHIEVABLE; // force this goal to be invalid
}
}
@@ -2104,8 +2156,8 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
return ai_achievability::NOT_KNOWN;
// we must also determine if we're prevented from docking for any reason
Assertion(target_ship_entry && target_ship_entry->has_shipp(), "Target name %s is not an arrived ship!", aigp->target_name);
if (!target_ship_entry || !target_ship_entry->has_shipp()) {
UNREACHABLE("Target name %s is not an arrived ship!", aigp->target_name);
return ai_achievability::NOT_ACHIEVABLE; // force this goal to be invalid
}
auto goal_objp = target_ship_entry->objp();
@@ -2162,7 +2214,7 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
if (aip->goal_objnum != target_ship_entry->objnum)
return ai_achievability::NOT_KNOWN;
} else {
UNREACHABLE("Target name %s is not an arrived ship!", aigp->target_name);
Assertion(false, "Target name %s is not an arrived ship!", aigp->target_name);
return ai_achievability::NOT_ACHIEVABLE; // force this goal to be invalid
}
}
@@ -2176,14 +2228,14 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
aigp->ai_submode = ship_find_subsys( target_ship_entry->shipp(), aigp->docker.name );
aigp->flags.remove(AI::Goal_Flags::Subsys_needs_fixup);
} else {
UNREACHABLE("Target name %s is not an arrived ship!", aigp->target_name);
Assertion(false, "Target name %s is not an arrived ship!", aigp->target_name);
return ai_achievability::NOT_ACHIEVABLE; // force this goal to be invalid
}
}
} else if ( ((aigp->ai_mode == AI_GOAL_IGNORE) || (aigp->ai_mode == AI_GOAL_IGNORE_NEW)) && (status == SHIP_STATUS_ARRIVED) ) {
// for ignoring a ship, call the ai_ignore object function, then declare the goal satisfied
Assertion(target_ship_entry && target_ship_entry->has_objp(), "Target name %s is not an arrived ship!", aigp->target_name);
if (!target_ship_entry || !target_ship_entry->has_objp()) {
UNREACHABLE("Target name %s is not an arrived ship!", aigp->target_name);
return ai_achievability::NOT_ACHIEVABLE; // force this goal to be invalid
}
auto ignored = target_ship_entry->objp();
@@ -2232,16 +2284,16 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
{
// short circuit a couple of cases. Ship not arrived shouldn't happen. Ship gone means
// we mark the goal as not achievable.
Assertion(status != SHIP_STATUS_NOT_ARRIVED, "Ship %s cannot rearm a target %s that hasn't arrived; get Allender or a SCP member", shipp->ship_name, aigp->target_name); // get Allender. this shouldn't happen!!!
if ( status == SHIP_STATUS_NOT_ARRIVED ) {
UNREACHABLE("Ship %s cannot rearm a target %s that hasn't arrived; get Allender or a SCP member", shipp->ship_name, aigp->target_name); // get Allender. this shouldn't happen!!!
return ai_achievability::NOT_ACHIEVABLE;
}
if ( status == SHIP_STATUS_GONE )
return ai_achievability::NOT_ACHIEVABLE;
Assertion(target_ship_entry && target_ship_entry->has_shipp(), "Target name %s is not an arrived ship!", aigp->target_name);
if ( !target_ship_entry || !target_ship_entry->has_shipp() ) {
UNREACHABLE("Target name %s is not an arrived ship!", aigp->target_name);
return ai_achievability::NOT_ACHIEVABLE;
}
@@ -2497,8 +2549,9 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
if ( !(Player_use_ai) && (objp->flags[Object::Object_Flags::Player_ship]) && (current_goal->ai_mode != AI_GOAL_FORM_ON_WING) )
{
return;
}
}
ai_reset_order_state(aip);
std::unique_ptr<ai_goal> current_goal_backup;
auto current_goal_target_ship = current_goal->target_name ? ship_registry_get(current_goal->target_name) : nullptr;
+5 -1
View File
@@ -106,6 +106,10 @@ inline bool ai_goal_is_specific_chase(ai_goal_mode ai_mode)
{
return ai_mode == AI_GOAL_CHASE || ai_mode == AI_GOAL_CHASE_WING || ai_mode == AI_GOAL_CHASE_SHIP_CLASS || ai_mode == AI_GOAL_CHASE_SHIP_TYPE;
}
inline bool ai_goal_is_standing_chase(ai_goal_mode ai_mode)
{
return ai_mode == AI_GOAL_CHASE_ANY;
}
enum class ai_achievability { ACHIEVABLE, NOT_ACHIEVABLE, NOT_KNOWN, SATISFIED };
@@ -179,7 +183,7 @@ extern void ai_process_mission_orders( int objnum, ai_info *aip );
extern int ai_goal_num(ai_goal *goals);
// adds goals to ships/wing through sexpressions
extern void ai_add_ship_goal_scripting(ai_goal_mode mode, int submode, int priority, const char *shipname, ai_info *aip, int int_data, float float_data);
extern void ai_add_ship_goal_scripting(ai_goal_mode mode, int submode, int priority, const char *shipname, ai_info *aip, int int_data, float float_data, int docker_index = -1, int dockee_index = -1);
extern void ai_add_ship_goal_sexp(int sexp, ai_goal_type type, ai_info *aip);
extern void ai_add_wing_goal_sexp(int sexp, ai_goal_type type, wing *wingp);
extern void ai_add_goal_sub_sexp(int sexp, ai_goal_type type, ai_info *aip, ai_goal *aigp, const char *actor_name);
+60 -96
View File
@@ -350,6 +350,18 @@ const weapon_info *get_turret_weapon_wip(const ship_weapon *swp, int weapon_num)
return &Weapon_info[wi_index];
}
// similar to the above, since "weapon_num" is a bank index over multiple banks
static std::pair<int, int> get_turret_weapon_bank(int weapon_num)
{
int pbank = -1;
int sbank = -1;
if (weapon_num >= MAX_SHIP_PRIMARY_BANKS)
sbank = weapon_num - MAX_SHIP_PRIMARY_BANKS;
else
pbank = weapon_num;
return { pbank, sbank };
}
int get_turret_weapon_next_fire_stamp(const ship_weapon *swp, int weapon_num)
{
Assert(weapon_num < MAX_SHIP_WEAPONS);
@@ -479,6 +491,12 @@ int valid_turret_enemy(object *objp, object *turret_parent)
return 0;
}
// Mines are only targetable within their configured detection range
if (wip->is_mine()) {
if (vm_vec_dist(&objp->pos, &turret_parent->pos) > wip->mine_targetable_range)
return 0;
}
return 1;
}
@@ -1089,7 +1107,8 @@ int find_turret_enemy(const ship_subsys *turret_subsys, int objnum, const vec3d
int target_objnum = aip->target_objnum;
if (Objects[target_objnum].signature == aip->target_signature) {
ship* target_shipp = &Ships[Objects[target_objnum].instance];
// The parent's target can be a weapon or an asteroid, so make sure it's a ship before indexing Ships[]
ship* target_shipp = (Objects[target_objnum].type == OBJ_SHIP) ? &Ships[Objects[target_objnum].instance] : nullptr;
if (target_shipp && iff_matches_mask(target_shipp->team, enemy_team_mask)) {
if (!(Objects[target_objnum].flags[Object::Object_Flags::Protected])) { // check this flag as well
// nprintf(("AI", "Frame %i: Object %i resuming goal of object %i\n", AI_FrameCount, objnum,
@@ -1722,6 +1741,7 @@ bool turret_fire_weapon(int weapon_num,
auto wip = get_turret_weapon_wip(&turret->weapons, weapon_num);
if (!wip)
return false;
auto [turret_pbank, turret_sbank] = get_turret_weapon_bank(weapon_num);
bool in_lab = (gameseq_get_state() == GS_STATE_LAB);
@@ -1748,7 +1768,7 @@ bool turret_fire_weapon(int weapon_num,
parent_aip = &Ai_info[Ships[Objects[parent_objnum].instance].ai_index];
parent_ship = &Ships[Objects[parent_objnum].instance];
int turret_weapon_class = weapon_info_get_index(wip);
int turret_weapon_class = WEAPON_INFO_INDEX(wip);
#ifndef NDEBUG
// moved here from check_ok_to_fire
@@ -1832,6 +1852,7 @@ bool turret_fire_weapon(int weapon_num,
else
fire_info.target_subsys = nullptr;
fire_info.turret = turret;
fire_info.bank = turret_pbank;
fire_info.burst_seed = old_burst_seed;
fire_info.fire_method = BFM_TURRET_FIRED;
fire_info.per_burst_rotation = swp->per_burst_rot;
@@ -1844,7 +1865,6 @@ bool turret_fire_weapon(int weapon_num,
fire_info.target_pos1 = turret->last_aim_enemy_pos;
}
// fire a beam weapon
weapon_objnum = beam_fire(&fire_info);
@@ -1870,113 +1890,53 @@ bool turret_fire_weapon(int weapon_num,
}
// don't fire swam, but set up swarm info instead
else if ((wip->wi_flags[Weapon::Info_Flags::Swarm]) || (wip->wi_flags[Weapon::Info_Flags::Corkscrew])) {
if (swp->current_secondary_bank < 0) {
swp->current_secondary_bank = 0;
// swarm and corkscrew are not restricted to missiles, so check whichever bank is actually firing
if (!in_lab && (turret->system_info->flags[Model::Subsystem_Flags::Turret_use_ammo])) {
bool has_ammo = (turret_pbank >= 0)
? swp->primary_bank_ammo[turret_pbank] > 0
: ship_secondary_has_ammo(swp, turret_sbank);
if (!has_ammo)
return false;
}
int bank_to_fire = swp->current_secondary_bank;
if (!in_lab && (turret->system_info->flags[Model::Subsystem_Flags::Turret_use_ammo]) && !ship_secondary_has_ammo(swp, bank_to_fire)) {
if (!(turret->system_info->flags[Model::Subsystem_Flags::Use_multiple_guns])) {
swp->current_secondary_bank++;
if (swp->current_secondary_bank >= swp->num_secondary_banks) {
swp->current_secondary_bank = 0;
}
return false;
} else {
return false;
}
} else {
turret_swarm_set_up_info(parent_objnum, turret, wip, turret->turret_next_fire_pos, in_lab);
turret->flags.set(Ship::Subsystem_Flags::Has_fired); //set fired flag for scripting -nike
return true;
}
turret_swarm_set_up_info(parent_objnum, turret, wip, turret->turret_next_fire_pos, turret_pbank, turret_sbank, in_lab);
turret->flags.set(Ship::Subsystem_Flags::Has_fired); //set fired flag for scripting -nike
return true;
}
// now do anything else
else {
float shots_mult = wip->weapon_launch_curves.get_output(weapon_info::WeaponLaunchCurveOutputs::SHOTS_MULT, launch_curve_data);
int shots = fl2i(i2fl(wip->shots) * shots_mult);
// note that wip is reassigned to the created weapon's class inside the loop, so any
// property of the bank's own weapon must be read before we get there
bool no_ammo_needed = (turret_sbank >= 0) && Weapon_info[swp->secondary_bank_weapons[turret_sbank]].wi_flags[Weapon::Info_Flags::SecondaryNoAmmo];
for (int i = 0; i < shots; i++) {
if (!in_lab && turret->system_info->flags[Model::Subsystem_Flags::Turret_use_ammo]) {
int bank_to_fire, num_slots = turret->system_info->turret_num_firing_points;
if (wip->subtype == WP_LASER) {
if (turret_pbank >= 0) {
int points;
if (swp->num_primary_banks <= 0) {
return false;
}
if (swp->current_primary_bank < 0){
swp->current_primary_bank = 0;
return false;
}
int num_primary_banks = swp->num_primary_banks;
Assert(num_primary_banks > 0);
if (num_primary_banks < 1){
return false;
}
bank_to_fire = swp->current_primary_bank;
if (turret->system_info->flags[Model::Subsystem_Flags::Turret_salvo]) {
points = num_slots;
points = turret->system_info->turret_num_firing_points;
} else {
points = 1;
}
if (swp->primary_bank_ammo[bank_to_fire] >= points) {
swp->primary_bank_ammo[bank_to_fire] -= points;
} else if ((swp->primary_bank_ammo[bank_to_fire] >= 0) && !(The_mission.ai_profile->flags[AI::Profile_Flags::Prevent_negative_turret_ammo])) {
if (swp->primary_bank_ammo[turret_pbank] >= points) {
swp->primary_bank_ammo[turret_pbank] -= points;
} else if ((swp->primary_bank_ammo[turret_pbank] >= 0) && !(The_mission.ai_profile->flags[AI::Profile_Flags::Prevent_negative_turret_ammo])) {
// default behavior allowed ammo to be negative
swp->primary_bank_ammo[bank_to_fire] -= points;
} else if (!(turret->system_info->flags[Model::Subsystem_Flags::Use_multiple_guns]) && (swp->primary_bank_ammo[bank_to_fire] < 0)) {
swp->current_primary_bank++;
if (swp->current_primary_bank >= swp->num_primary_banks) {
swp->current_primary_bank = 0;
}
return false;
swp->primary_bank_ammo[turret_pbank] -= points;
} else {
return false;
}
}
else if (wip->subtype == WP_MISSILE) {
if (swp->num_secondary_banks <= 0) {
return false;
}
if (swp->current_secondary_bank < 0){
swp->current_secondary_bank = 0;
return false;
}
bank_to_fire = swp->current_secondary_bank;
int start_slot, end_slot;
start_slot = swp->secondary_next_slot[bank_to_fire];
end_slot = start_slot;
for (int j = start_slot; j <= end_slot; j++) {
swp->secondary_next_slot[bank_to_fire]++;
if (Weapon_info[swp->secondary_bank_weapons[bank_to_fire]].wi_flags[Weapon::Info_Flags::SecondaryNoAmmo])
continue;
if (swp->secondary_next_slot[bank_to_fire] > (num_slots - 1)){
swp->secondary_next_slot[bank_to_fire] = 0;
}
if (swp->secondary_bank_ammo[bank_to_fire] > 0) {
swp->secondary_bank_ammo[bank_to_fire]--;
} else if ((swp->secondary_bank_ammo[bank_to_fire] == 0) && !(The_mission.ai_profile->flags[AI::Profile_Flags::Prevent_negative_turret_ammo])) {
else if (turret_sbank >= 0) {
if (!no_ammo_needed) {
if (swp->secondary_bank_ammo[turret_sbank] > 0) {
swp->secondary_bank_ammo[turret_sbank]--;
} else if ((swp->secondary_bank_ammo[turret_sbank] == 0) && !(The_mission.ai_profile->flags[AI::Profile_Flags::Prevent_negative_turret_ammo])) {
// default behavior allowed ammo to be negative
swp->secondary_bank_ammo[bank_to_fire]--;
} else if (!(turret->system_info->flags[Model::Subsystem_Flags::Use_multiple_guns]) && (swp->secondary_bank_ammo[bank_to_fire] < 0)) {
swp->current_secondary_bank++;
if (swp->current_secondary_bank >= swp->num_secondary_banks) {
swp->current_secondary_bank = 0;
}
return false;
swp->secondary_bank_ammo[turret_sbank]--;
} else {
return false;
}
@@ -1991,7 +1951,7 @@ bool turret_fire_weapon(int weapon_num,
// so we need to get the position info separately for each shot
ship_get_global_turret_gun_info(&Objects[parent_objnum], turret, &firing_pos_buf, false, nullptr, true, nullptr);
weapon_objnum = weapon_create(firing_pos, &firing_orient, turret_weapon_class, parent_objnum, -1, true, false, 0.0f, turret, launch_curve_data);
weapon_objnum = weapon_create(firing_pos, &firing_orient, turret_weapon_class, parent_objnum, -1, true, false, swp, turret_pbank, turret_sbank, launch_curve_data);
weapon_set_tracking_info(weapon_objnum, parent_objnum, turret->turret_enemy_objnum, 1, turret->targeted_subsys);
//nprintf(("AI", "Turret_time_enemy_in_range = %7.3f\n", ss->turret_time_enemy_in_range));
@@ -2042,7 +2002,7 @@ bool turret_fire_weapon(int weapon_num,
}
//spawn particle effect
auto particleSource = particle::ParticleManager::get()->createSource(wip->muzzle_effect);
particleSource->setHost(make_unique<EffectHostTurret>(&Objects[parent_ship->objnum], turret->system_info->turret_gun_sobj, turret->turret_next_fire_pos));
particleSource->setHost(std::make_unique<EffectHostTurret>(&Objects[parent_ship->objnum], turret->system_info->turret_gun_sobj, turret->turret_next_fire_pos, false));
particleSource->setTriggerRadius(objp->radius * radius_mult);
particleSource->setTriggerVelocity(vm_vec_mag_quick(&objp->phys_info.vel));
particleSource->finishCreation();
@@ -2055,9 +2015,9 @@ bool turret_fire_weapon(int weapon_num,
subsys_index = ship_get_subsys_index(turret);
Assert( subsys_index != -1 );
if(wip->wi_flags[Weapon::Info_Flags::Flak]){
send_flak_fired_packet( parent_objnum, subsys_index, weapon_objnum, flak_range, launch_curve_data.distance_to_target, launch_curve_data.target_radius );
send_flak_fired_packet( parent_objnum, subsys_index, weapon_objnum, flak_range, launch_curve_data.distance_to_target, launch_curve_data.target_radius, launch_curve_data.target_forward_speed );
} else {
send_turret_fired_packet( parent_objnum, subsys_index, weapon_objnum, launch_curve_data.distance_to_target, launch_curve_data.target_radius );
send_turret_fired_packet( parent_objnum, subsys_index, weapon_objnum, launch_curve_data.distance_to_target, launch_curve_data.target_radius, launch_curve_data.target_forward_speed );
}
}
@@ -2131,10 +2091,11 @@ void turret_swarm_fire_from_turret(turret_swarm_info *tsi)
tsi->turret->system_info->turret_num_firing_points,
0.f,
0.f,
0.f,
};
// create weapon and homing info
weapon_objnum = weapon_create(&turret_pos, &firing_orient, tsi->weapon_class, tsi->parent_objnum, -1, true, false, 0.0f, tsi->turret, launch_curve_data);
weapon_objnum = weapon_create(&turret_pos, &firing_orient, tsi->weapon_class, tsi->parent_objnum, -1, true, false, &tsi->turret->weapons, tsi->swp_pbank, tsi->swp_sbank, launch_curve_data);
weapon_set_tracking_info(weapon_objnum, tsi->parent_objnum, tsi->target_objnum, 1, tsi->target_subsys);
// do other cool stuff if weapon is created.
@@ -2163,7 +2124,7 @@ void turret_swarm_fire_from_turret(turret_swarm_info *tsi)
if (Weapon_info[tsi->weapon_class].muzzle_effect.isValid()) {
//spawn particle effect
auto particleSource = particle::ParticleManager::get()->createSource(Weapon_info[tsi->weapon_class].muzzle_effect);
particleSource->setHost(make_unique<EffectHostTurret>(&Objects[tsi->parent_objnum], tsi->turret->system_info->turret_gun_sobj, tsi->turret->turret_next_fire_pos - 1));
particleSource->setHost(std::make_unique<EffectHostTurret>(&Objects[tsi->parent_objnum], tsi->turret->system_info->turret_gun_sobj, tsi->turret->turret_next_fire_pos - 1, false));
particleSource->setTriggerRadius(Objects[weapon_objnum].radius);
particleSource->finishCreation();
}
@@ -2182,7 +2143,7 @@ void turret_swarm_fire_from_turret(turret_swarm_info *tsi)
subsys_index = ship_get_subsys_index(tsi->turret);
Assert( subsys_index != -1 );
send_turret_fired_packet( tsi->parent_objnum, subsys_index, weapon_objnum, 0.f, 0.f);
send_turret_fired_packet( tsi->parent_objnum, subsys_index, weapon_objnum, 0.f, 0.f, 0.f);
}
}
}
@@ -2355,9 +2316,11 @@ void ai_turret_execute_behavior(const ship *shipp, ship_subsys *ss)
float turret_barrel_length = -1.0f;
float target_radius = 0.f;
float target_forward_speed = 0.f;
if (lep != nullptr) {
target_radius = lep->radius;
target_forward_speed = lep->phys_info.fspeed;
}
// grab the data for the launch curve inputs
@@ -2365,6 +2328,7 @@ void ai_turret_execute_behavior(const ship *shipp, ship_subsys *ss)
ss->system_info->turret_num_firing_points,
base_dist_to_enemy,
target_radius,
target_forward_speed,
};
//WMC - go through all valid weapons. Fire spawns if there are any.
+18 -10
View File
@@ -142,11 +142,15 @@ int unpack_pixel(anim_instance *ai, ubyte *data, ubyte pix, int aabitmap, int bp
bit_16 = (ushort)pix;
break;
case 8:
// 8 bit-per-pixel aa bitmaps are a bit special since they only use a palette index value in the range [0, 15]. These
// palette indexes must be remapped to alpha values between [0, 255] which is what graphics code expects. Palette
// range [0, 14] is a gradient from black to white, and palette index 15 is a special color which indicates the background
// area of a HUD gauge. Retail code uses the final alpha value for index 1 for this special index to give gauges a dark
// transparent background.
// 8-bit-per-pixel HUD gauge ANI aabitmaps use the palette index directly as an alpha value —
// the actual RGB colors stored in the palette are completely ignored. The valid range is [0, 15]:
// [0]: fully transparent (alpha 0)
// [1-14]: black-to-white gradient (alpha = index * 18)
// [15]: special HUD background (alpha 18, matching index 1, for a dark transparent tint)
// [>15]: clamped to fully opaque (alpha 255)
// Because only the index value matters, the palette in the source file must be ordered so that
// index 0 is the transparent end and index 14 is the opaque end. A reversed palette causes the
// gauge to render as a solid white square (see GitHub issue #4148).
if (pix > 15) {
bit_8 = 255;
}
@@ -240,11 +244,15 @@ int unpack_pixel_count(anim_instance *ai, ubyte *data, ubyte pix, int count = 0,
bit_16 = (ushort)pix;
break;
case 8 :
// 8 bit-per-pixel aa bitmaps are a bit special since they only use a palette index value in the range [0, 15]. These
// palette indexes must be remapped to alpha values between [0, 255] which is what graphics code expects. Palette
// range [0, 14] is a gradient from black to white, and palette index 15 is a special color which indicates the background
// area of a HUD gauge. Retail code uses the final alpha value for index 1 for this special index to give gauges a dark
// transparent background.
// 8-bit-per-pixel HUD gauge ANI aabitmaps use the palette index directly as an alpha value —
// the actual RGB colors stored in the palette are completely ignored. The valid range is [0, 15]:
// [0]: fully transparent (alpha 0)
// [1-14]: black-to-white gradient (alpha = index * 18)
// [15]: special HUD background (alpha 18, matching index 1, for a dark transparent tint)
// [>15]: clamped to fully opaque (alpha 255)
// Because only the index value matters, the palette in the source file must be ordered so that
// index 0 is the transparent end and index 14 is the opaque end. A reversed palette causes the
// gauge to render as a solid white square (see GitHub issue #4148).
if (pix > 15) {
bit_8 = 255;
}
+41 -20
View File
@@ -648,24 +648,30 @@ void asteroid_create_all()
int max_asteroids = Asteroid_field.num_initial_asteroids; // * (1.0f - 0.1f*(MAX_DETAIL_VALUE-Detail.asteroid_density)));
int num_debris_types = 0;
int num_valid = 0; // count of valid (non-(-1)) entries written into shipDebrisOddsTable
// get number of debris types
if (Asteroid_field.debris_genre == DG_DEBRIS) {
num_debris_types = static_cast<int>(Asteroid_field.field_debris_type.size());
// Calculate the odds table
// Calculate the odds table; skip invalid entries (field_debris_type[idx] == -1 means
// missionparse invalidated the entry because the type no longer exists in Asteroid_info)
for (idx=0; idx<num_debris_types; idx++) {
if (Asteroid_field.field_debris_type[idx] == -1)
continue;
float debris_weight = Asteroid_info[Asteroid_field.field_debris_type[idx]].spawn_weight;
shipDebrisOddsTable[idx].random_threshold = max_weighted_range + debris_weight;
shipDebrisOddsTable[idx].debris_type = Asteroid_field.field_debris_type[idx];
shipDebrisOddsTable[num_valid].random_threshold = max_weighted_range + debris_weight;
shipDebrisOddsTable[num_valid].debris_type = Asteroid_field.field_debris_type[idx];
max_weighted_range += debris_weight;
num_valid++;
}
}
// Load Asteroid/debris models
if (Asteroid_field.debris_genre == DG_DEBRIS) {
for (idx=0; idx<num_debris_types; idx++) {
asteroid_load(Asteroid_field.field_debris_type[idx], 0);
if (Asteroid_field.field_debris_type[idx] != -1)
asteroid_load(Asteroid_field.field_debris_type[idx], 0);
}
} else {
for (const auto& subtype : Asteroid_field.field_asteroid_type) {
@@ -690,7 +696,7 @@ void asteroid_create_all()
float rand_choice = frand() * max_weighted_range;
for (idx = 0; idx < static_cast<int>(Asteroid_field.field_debris_type.size()); idx++) {
for (idx = 0; idx < num_valid; idx++) {
// for ship debris, choose type according to odds table
if (rand_choice < shipDebrisOddsTable[idx].random_threshold) {
asteroid_create(&Asteroid_field, shipDebrisOddsTable[idx].debris_type, 0);
@@ -1077,8 +1083,10 @@ void asteroid_delete( object * obj )
asp = &Asteroids[num];
if (asp->model_instance_num >= 0)
if (asp->model_instance_num >= 0) {
model_delete_instance(asp->model_instance_num);
asp->model_instance_num = -1;
}
if (asp->target_objnum >= 0) {
for (asteroid_target& target : Asteroid_targets) {
@@ -1315,9 +1323,9 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
model_get_moving_submodel_list(submodel_vector, heavy_obj);
// turn off all moving submodels, collide against only 1 at a time.
// turn off collision detection for all moving submodels
mc.collision_checked.assign(pm->n_models, 0);
for (auto submodel: submodel_vector) {
pmi->submodel[submodel].collision_checked = true;
mc.collision_checked[submodel] = true;
}
// Only check single submodel now, since children of moving submodels are handled as moving as well
@@ -1325,10 +1333,8 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
// check each submodel in turn
for (auto submodel: submodel_vector) {
auto smi = &pmi->submodel[submodel];
// turn on just one submodel for collision test
smi->collision_checked = false;
mc.collision_checked[submodel] = false;
// find the start and end positions of the sphere in submodel RF
model_instance_global_to_local_point(&p0, &light_obj->last_pos, pm, pmi, submodel, &heavy_obj->last_orient, &heavy_obj->last_pos, true);
@@ -1360,9 +1366,12 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
}
}
// Don't look at this submodel again
smi->collision_checked = true;
mc.collision_checked[submodel] = true;
}
// Clear collision_checked before base model pass so it auto-inits fresh
mc.collision_checked.clear();
}
// Now complete base model collision checks that do not take into account rotating submodels.
@@ -1430,9 +1439,9 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
// everything above was calculated relative to the heavy's position
vec3d actual_world_hit_pos = mc.hit_point_world + heavy_obj->pos;
if ((shipp->is_arriving()) && (shipp->warpin_effect != nullptr))
warp_effect = shipp->warpin_effect;
warp_effect = shipp->warpin_effect.get();
else if ((shipp->flags[Ship::Ship_Flags::Depart_warp]) && (shipp->warpout_effect != nullptr))
warp_effect = shipp->warpout_effect;
warp_effect = shipp->warpout_effect.get();
if (warp_effect != nullptr && point_is_clipped_by_warp(&actual_world_hit_pos, warp_effect))
mc_ret_val = 0;
@@ -1607,8 +1616,13 @@ static float asteroid_create_explosion(object *objp)
}
fireball_scale_multiplier = asteroid_get_fireball_scale_multiplier(objp->instance);
auto fireball_radius = objp->radius * fireball_scale_multiplier;
if (Zero_radius_explosions_skip_fireballs && fl_near_zero(fireball_radius))
fireball_objnum = -1;
else
fireball_objnum = fireball_create( &objp->pos, fireball_type, FIREBALL_LARGE_EXPLOSION, OBJ_INDEX(objp), fireball_radius, false, &objp->phys_info.vel );
fireball_objnum = fireball_create( &objp->pos, fireball_type, FIREBALL_LARGE_EXPLOSION, OBJ_INDEX(objp), objp->radius*fireball_scale_multiplier, false, &objp->phys_info.vel );
if ( fireball_objnum > -1 ) {
explosion_life = fireball_lifeleft(&Objects[fireball_objnum]);
} else {
@@ -1679,7 +1693,7 @@ static void asteroid_do_area_effect(object *asteroid_objp)
if ( weapon_area_calc_damage(ship_objp, &asteroid_objp->pos, asip->inner_rad, asip->outer_rad, asip->blast, asip->damage, &blast, &damage, asip->outer_rad) == -1 )
continue;
ship_apply_global_damage(ship_objp, asteroid_objp, &asteroid_objp->pos, damage, asip->damage_type_idx);
ship_apply_global_damage(ship_objp, asteroid_objp, &asteroid_objp->pos, damage, asip->damage_type_idx, DamageSource::Explosion);
weapon_area_apply_blast(nullptr, ship_objp, &asteroid_objp->pos, blast, false);
} // end for
}
@@ -2220,7 +2234,7 @@ static void asteroid_parse_section()
}
return;
}
asteroid_list.push_back(asteroid_t);
asteroid_list.push_back(std::move(asteroid_t));
asteroid_p = &asteroid_list[asteroid_list.size() - 1];
}
@@ -2318,7 +2332,7 @@ static void asteroid_parse_section()
thisType.type_name.c_str(),
asteroid_p->name);
} else {
asteroid_p->subtypes.push_back(thisType);
asteroid_p->subtypes.push_back(std::move(thisType));
}
}
}
@@ -2607,7 +2621,7 @@ static void verify_asteroid_splits()
}
// Replace splits with only valid splits
Asteroid_info[i].split_info = splits_t;
Asteroid_info[i].split_info = std::move(splits_t);
}
}
@@ -2635,6 +2649,11 @@ static void verify_asteroid_list()
if (found)
continue;
if (asteroid_list.empty()) {
mprintf(("%s asteroid not found and no fallback available in asteroid_list.\n", asteroid_size[i].c_str()));
continue;
}
//Left this as a log print instead of a Warning because of retail-Mjn
mprintf(("%s asteroid not found. Using asteroid %s\n", asteroid_size[i].c_str(), asteroid_list[0].name));
asteroid_list[0].type = i;
@@ -2689,8 +2708,10 @@ void asteroid_init()
parse_modular_table("*-ast.tbm", asteroid_parse_tbl);
//No asteroids defined. Bail!
if (asteroid_list.empty())
if (asteroid_list.empty()) {
mprintf(("No asteroids defined in asteroid.tbl or any modular tables. Asteroid fields will not function.\n"));
return;
}
// now verify the asteroids were found and put them in the correct order
verify_asteroid_list();
+10 -9
View File
@@ -70,7 +70,7 @@ struct bitmap_entry {
// Stuff to keep track of usage
ubyte preloaded; //!< If set, then this was loaded from the lst file
int preload_count; //!< how many times this gets used in game, for unlocking
ushort used_flags; //!< What flags it was accessed thru
uint used_flags; //!< What flags it was accessed thru
int load_count;
bitmap bm; //!< Bitmap info
@@ -93,14 +93,15 @@ struct bitmap_slot {
};
// image specific lock functions
void bm_lock_ani( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags );
void bm_lock_dds( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags );
void bm_lock_png( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags );
void bm_lock_apng( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags );
void bm_lock_jpg( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags );
void bm_lock_pcx( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags );
void bm_lock_tga( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags );
void bm_lock_user( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags, bool convert = true );
void bm_lock_ani( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, uint flags );
void bm_lock_dds( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, uint flags );
void bm_lock_png( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, uint flags );
void bm_lock_apng( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, uint flags );
void bm_lock_jpg( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, uint flags );
void bm_lock_pcx( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, uint flags );
void bm_lock_tga( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, uint flags );
void bm_lock_user( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, uint flags, bool convert = true );
void bm_lock_ktx1(int handle, bitmap_slot* bs, bitmap* bmp, int bpp, uint flags);
const size_t BM_BLOCK_SIZE = 4096;
+170 -57
View File
@@ -13,6 +13,7 @@
#define WIN32_LEAN_AND_MEAN
#define BMPMAN_INTERNAL
#include "globalincs/pstypes.h"
#include "anim/animplay.h"
#include "anim/packunpack.h"
#include "bmpman/bm_internal.h"
@@ -32,6 +33,7 @@
#include "tgautils/tgautils.h"
#include "tracing/Monitor.h"
#include "tracing/tracing.h"
#include "ktxutils/ktxutils.h"
#include <cctype>
#include <climits>
@@ -59,8 +61,8 @@ size_t bm_texture_ram = 0;
int Bm_paging = 0;
// Extension type lists
const BM_TYPE bm_type_list[] = { BM_TYPE_DDS, BM_TYPE_TGA, BM_TYPE_PNG, BM_TYPE_JPG, BM_TYPE_PCX };
const char *bm_ext_list[] = { ".dds", ".tga", ".png", ".jpg", ".pcx" };
const BM_TYPE bm_type_list[] = { BM_TYPE_DDS, BM_TYPE_TGA, BM_TYPE_PNG, BM_TYPE_JPG, BM_TYPE_PCX, BM_TYPE_KTX };
const char* bm_ext_list[] = { ".dds", ".tga", ".png", ".jpg", ".pcx", ".ktx" };
const int BM_NUM_TYPES = sizeof(bm_type_list) / sizeof(bm_type_list[0]);
const BM_TYPE bm_ani_type_list[] = { BM_TYPE_EFF, BM_TYPE_ANI, BM_TYPE_PNG };
@@ -82,7 +84,12 @@ static bool bm_inited = false;
static uint Bm_next_signature = 0x1234;
static int Bm_low_mem = 0;
SCP_map<int,ubyte*> bm_lookup_cache;
struct bm_lookup_cache_entry {
ubyte* data;
int width;
int height;
};
SCP_map<int, bm_lookup_cache_entry> bm_lookup_cache;
/**
* How much RAM bmpman can use for textures.
@@ -143,30 +150,31 @@ bitmap_slot* bm_get_slot(int handle, bool separate_ani_frames) {
// Declaration of private functions and templates(declared as static type func(type param);)
bitmap_lookup::bitmap_lookup(int bitmap_num):
Bitmap_data(NULL)
Bitmap_data(nullptr),
Width(0),
Height(0),
Num_channels(3)
{
if ( !bm_is_valid(bitmap_num) ) return;
Num_channels = 3;
if ( bm_has_alpha_channel(bitmap_num) ) {
Num_channels = 4;
}
bitmap_entry *be = bm_get_entry(bitmap_num);
Width = be->bm.w;
Height = be->bm.h;
auto cache_search = bm_lookup_cache.find(bitmap_num);
if (cache_search == bm_lookup_cache.end()) {
Bitmap_data = (ubyte*)vm_malloc(Width * Height * Num_channels * sizeof(ubyte));
// The texture in graphics memory may be smaller than the bitmap itself, e.g. when mipmap levels
// are culled at lower texture detail settings, so the readback reports the dimensions of the
// data it actually returns.
Bitmap_data = gr_get_bitmap_from_texture(bitmap_num, &Width, &Height);
gr_get_bitmap_from_texture((void*)Bitmap_data, bitmap_num);
bm_lookup_cache.insert({bitmap_num, Bitmap_data});
if (Bitmap_data != nullptr) {
bm_lookup_cache.insert({bitmap_num, {Bitmap_data, Width, Height}});
}
} else {
Bitmap_data = cache_search->second;
Bitmap_data = cache_search->second.data;
Width = cache_search->second.width;
Height = cache_search->second.height;
}
}
@@ -189,6 +197,11 @@ float bitmap_lookup::get_channel_alpha(float u, float v)
{
Assert( Bitmap_data != NULL );
// without an alpha channel there is nothing to look up, and indexing channel 3 would read out of bounds
if ( Num_channels < 4 ) {
return 1.0f;
}
int x = fl2i(map_texture_address(u) * (Width-1));
int y = fl2i(map_texture_address(v) * (Height-1));
@@ -197,7 +210,7 @@ float bitmap_lookup::get_channel_alpha(float u, float v)
void clear_bm_lookup_cache() {
for(auto &iter: bm_lookup_cache) {
free(iter.second);
vm_free(iter.second.data);
}
bm_lookup_cache.clear();
}
@@ -205,7 +218,7 @@ void clear_bm_lookup_cache() {
/**
* Converts the bitmap referenced by bmp to the type specified by flags
*/
static void bm_convert_format(bitmap *bmp, ushort flags);
static void bm_convert_format(bitmap *bmp, uint flags);
/**
* Frees a bitmap's data if it can
@@ -598,8 +611,8 @@ int bm_create_3d(int bpp, int w, int h, int d, void* data) {
return -1;
// make sure that we have valid data
Assertion(data != nullptr, "No valid data received for 3D Bitmap creation!");
if (data == nullptr) {
UNREACHABLE("No valid data received for 3D Bitmap creation!");
return -1;
}
@@ -639,7 +652,7 @@ int bm_create_3d(int bpp, int w, int h, int d, void* data) {
return n;
}
void bm_convert_format(bitmap *bmp, ushort flags) {
void bm_convert_format(bitmap *bmp, uint flags) {
int idx;
// no transparency for 24 bpp images
@@ -901,7 +914,7 @@ void bm_get_frame_usage(int *ntotal, int *nnew) {
#endif
}
int bm_get_info(int handle, int *w, int * h, ushort* flags, int *nframes, int *fps) {
int bm_get_info(int handle, int *w, int * h, uint* flags, int *nframes, int *fps) {
bitmap * bmp;
if (!bm_inited) return -1;
@@ -1023,6 +1036,13 @@ void bm_init() {
bm_inited = true;
}
bool bm_is_cubemap(int handle)
{
const BM_TYPE type = bm_get_entry(handle)->comp_type;
return type == BM_TYPE_CUBEMAP_DDS || type == BM_TYPE_CUBEMAP_DXT1 || type == BM_TYPE_CUBEMAP_DXT3 ||
type == BM_TYPE_CUBEMAP_DXT5;
}
int bm_is_compressed(int num) {
auto entry = bm_get_entry(num);
BM_TYPE type = BM_TYPE_NONE;
@@ -1055,6 +1075,24 @@ int bm_is_compressed(int num) {
case BM_TYPE_CUBEMAP_DXT5:
return DDS_CUBEMAP_DXT5;
case BM_TYPE_ETC2_RGB:
return KTX_ETC2_RGB;
case BM_TYPE_ETC2_SRGB:
return KTX_ETC2_SRGB;
case BM_TYPE_ETC2_RGBA_EAC:
return KTX_ETC2_RGBA_EAC;
case BM_TYPE_ETC2_SRGBA_EAC:
return KTX_ETC2_SRGBA_EAC;
case BM_TYPE_ETC2_RGBA1:
return KTX_ETC2_RGB_A1;
case BM_TYPE_ETC2_SRGBA1:
return KTX_ETC2_SRGB_A1;
default:
return 0;
}
@@ -1148,6 +1186,16 @@ static int bm_load_info(BM_TYPE type, const char *filename, CFILE *img_cfp, int
return -1;
}
}
// its a KTX file
else if (type == BM_TYPE_KTX) {
int ktx_ct;
int ktx_error = ktx1_read_header(filename, img_cfp, w, h, bpp, &ktx_ct, mm_lvl, size);
if (ktx_error != KTX1_ERROR_NONE) {
mprintf(("ktx: could not open '%s'\n", filename));
return -1;
}
*c_type = (BM_TYPE)ktx_ct;
}
// if its a tga file
else if (type == BM_TYPE_TGA) {
int tga_error = targa_read_header(filename, img_cfp, w, h, bpp, NULL);
@@ -1181,7 +1229,7 @@ static int bm_load_info(BM_TYPE type, const char *filename, CFILE *img_cfp, int
}
}
else {
UNREACHABLE("Unknown file type specified! This is probably a coding error.");
UNREACHABLE("Unknown file type %d specified! This is probably a coding error.", type);
return -1;
}
@@ -1361,6 +1409,8 @@ bool bm_load_and_parse_eff(const char *filename, int dir_type, int *nframes, int
c_type = BM_TYPE_JPG;
} else if (!stricmp(NOX("pcx"), ext)) {
c_type = BM_TYPE_PCX;
} else if (!stricmp(NOX("ktx"), ext)) {
c_type = BM_TYPE_KTX;
} else {
mprintf(("BMPMAN: Unknown file type in EFF parse!\n"));
return false;
@@ -1390,7 +1440,7 @@ bool bm_load_and_parse_eff(const char *filename, int dir_type, int *nframes, int
/**
* Lock an image files data into memory
*/
static int bm_load_image_data(int handle, int bpp, ushort flags, bool nodebug)
static int bm_load_image_data(int handle, int bpp, uint flags, bool nodebug)
{
BM_TYPE c_type = BM_TYPE_NONE;
int true_bpp;
@@ -1482,6 +1532,10 @@ static int bm_load_image_data(int handle, int bpp, ushort flags, bool nodebug)
bm_lock_user(handle, bs, bmp, true_bpp, flags, false);
break;
case BM_TYPE_KTX:
bm_lock_ktx1(handle, bs, bmp, true_bpp, flags);
break;
default:
Warning(LOCATION, "Unsupported type in bm_lock -- %d\n", c_type);
return -1;
@@ -1941,7 +1995,7 @@ int bm_load_sub_slow(const char *real_filename, const int num_ext, const char **
return -1;
}
bitmap * bm_lock(int handle, int bpp, ushort flags, bool nodebug) {
bitmap * bm_lock(int handle, int bpp, uint flags, bool nodebug) {
bitmap *bmp;
Assertion(bm_inited, "bmpman must be initialized before this function can be called!");
@@ -2038,7 +2092,7 @@ bitmap * bm_lock(int handle, int bpp, ushort flags, bool nodebug) {
return bmp;
}
void bm_lock_ani(int /*handle*/, bitmap_slot *bs, bitmap* /*bmp*/, int bpp, ushort flags) {
void bm_lock_ani(int /*handle*/, bitmap_slot *bs, bitmap* /*bmp*/, int bpp, uint flags) {
anim *the_anim;
anim_instance *the_anim_instance;
bitmap *bm;
@@ -2164,7 +2218,7 @@ void bm_lock_ani(int /*handle*/, bitmap_slot *bs, bitmap* /*bmp*/, int bpp, usho
}
void bm_lock_apng(int /*handle*/, bitmap_slot *bs, bitmap *bmp, int bpp, ushort /*flags*/) {
void bm_lock_apng(int /*handle*/, bitmap_slot *bs, bitmap *bmp, int bpp, uint /*flags*/) {
auto be = &bs->entry;
int first_frame = be->info.ani.first_frame;
auto first_entry = bm_get_entry(first_frame);
@@ -2214,7 +2268,7 @@ void bm_lock_apng(int /*handle*/, bitmap_slot *bs, bitmap *bmp, int bpp, ushort
}
void bm_lock_dds(int handle, bitmap_slot *bs, bitmap *bmp, int /*bpp*/, ushort /*flags*/) {
void bm_lock_dds(int handle, bitmap_slot *bs, bitmap *bmp, int /*bpp*/, uint /*flags*/) {
ubyte *data = NULL;
int error;
ubyte dds_bpp = 0;
@@ -2278,7 +2332,7 @@ void bm_lock_dds(int handle, bitmap_slot *bs, bitmap *bmp, int /*bpp*/, ushort /
#endif
}
void bm_lock_jpg(int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort /*flags*/) {
void bm_lock_jpg(int handle, bitmap_slot *bs, bitmap *bmp, int bpp, uint /*flags*/) {
ubyte *data = NULL;
int d_size = 0;
int jpg_error = JPEG_ERROR_INVALID;
@@ -2325,7 +2379,7 @@ void bm_lock_jpg(int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort /*fla
#endif
}
void bm_lock_pcx(int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags) {
void bm_lock_pcx(int handle, bitmap_slot *bs, bitmap *bmp, int bpp, uint flags) {
ubyte *data;
int pcx_error;
char filename[MAX_FILENAME_LEN];
@@ -2370,7 +2424,7 @@ void bm_lock_pcx(int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags
bm_convert_format(bmp, flags);
}
void bm_lock_png(int handle, bitmap_slot *bs, bitmap *bmp, int /*bpp*/, ushort /*flags*/) {
void bm_lock_png(int handle, bitmap_slot *bs, bitmap *bmp, int /*bpp*/, uint /*flags*/) {
ubyte *data = NULL;
//assume 32 bit - libpng should expand everything
int d_size;
@@ -2415,7 +2469,7 @@ void bm_lock_png(int handle, bitmap_slot *bs, bitmap *bmp, int /*bpp*/, ushort /
#endif
}
void bm_lock_tga(int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags) {
void bm_lock_tga(int handle, bitmap_slot *bs, bitmap *bmp, int bpp, uint flags) {
ubyte *data = NULL;
int byte_size;
char filename[MAX_FILENAME_LEN];
@@ -2474,7 +2528,7 @@ void bm_lock_tga(int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags
bm_convert_format(bmp, flags);
}
void bm_lock_user(int /*handle*/, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags, bool convert) {
void bm_lock_user(int /*handle*/, bitmap_slot *bs, bitmap *bmp, int bpp, uint flags, bool convert) {
auto be = &bs->entry;
// Unload any existing data
@@ -2767,6 +2821,21 @@ void bm_page_in_texture(int bitmapnum, int nframes) {
frame_entry->used_flags = BMP_TEX_CUBEMAP;
continue;
case BM_TYPE_ETC2_RGB:
case BM_TYPE_ETC2_SRGB:
frame_entry->used_flags = BMP_TEX_ETC2_RGB8;
continue;
case BM_TYPE_ETC2_RGBA1:
case BM_TYPE_ETC2_SRGBA1:
frame_entry->used_flags = BMP_TEX_ETC2_RGBA1;
continue;
case BM_TYPE_ETC2_RGBA_EAC:
case BM_TYPE_ETC2_SRGBA_EAC:
frame_entry->used_flags = BMP_TEX_ETC2_RGBA8;
continue;
default:
continue;
}
@@ -2812,6 +2881,21 @@ void bm_page_in_xparent_texture(int bitmapnum, int nframes) {
entry->used_flags = BMP_TEX_CUBEMAP;
continue;
case BM_TYPE_ETC2_RGB:
case BM_TYPE_ETC2_SRGB:
entry->used_flags = BMP_TEX_ETC2_RGB8;
continue;
case BM_TYPE_ETC2_RGBA1:
case BM_TYPE_ETC2_SRGBA1:
entry->used_flags = BMP_TEX_ETC2_RGBA1;
continue;
case BM_TYPE_ETC2_RGBA_EAC:
case BM_TYPE_ETC2_SRGBA_EAC:
entry->used_flags = BMP_TEX_ETC2_RGBA8;
continue;
default:
continue;
}
@@ -3397,38 +3481,67 @@ bool bm_validate_filename(const SCP_string& file, bool single_frame, bool animat
}
return false;
}
SDL_Surface* bm_to_sdl_surface(int handle) {
SDL_Surface* bm_to_sdl_surface(int handle)
{
Assertion(bm_is_valid(handle), "%d is no valid bitmap handle!", handle);
int w;
int h;
bm_get_info(handle, &w, &h, nullptr, nullptr);
Uint32 rmask, gmask, bmask, amask;
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
rmask = 0x0000ff00;
gmask = 0x00ff0000;
bmask = 0xff000000;
amask = 0x000000ff;
#else
rmask = 0x00ff0000;
gmask = 0x0000ff00;
bmask = 0x000000ff;
amask = 0xff000000;
#endif
SDL_Surface* bitmapSurface = SDL_CreateRGBSurface(0, w, h, 32, rmask, gmask, bmask, amask);
if (SDL_LockSurface(bitmapSurface) < 0) {
return nullptr;
}
bitmap* bmp = bm_lock(handle, 32, BMP_TEX_XPARENT);
memcpy(bitmapSurface->pixels, reinterpret_cast<void*>(bmp->data), static_cast<size_t>(w * h * 4));
auto bitmapSurface = SDL_CreateSurface(bmp->w, bmp->h, SDL_PIXELFORMAT_BGRA32);
if (bitmapSurface) {
memcpy(bitmapSurface->pixels, reinterpret_cast<void *>(bmp->data),
bmp->w * bmp->h * (bmp->bpp >> 3));
}
bm_unlock(handle);
SDL_UnlockSurface(bitmapSurface);
bm_unload(handle);
return bitmapSurface;
}
// copied bm_lock_dds and adjusted for ktx. missing BIG_ENDIAN
void bm_lock_ktx1(int handle, bitmap_slot* bs, bitmap* bmp, int bpp, uint flags)
{
(void)bpp;
(void)flags;
ubyte* data = nullptr;
ubyte ktx_bpp = 0;
int ktx_error = KTX1_ERROR_NONE;
char filename[MAX_FILENAME_LEN];
auto be = &bs->entry;
bm_free_data(bs);
Assert(be->mem_taken > 0);
Assert(&be->bm == bmp);
data = (ubyte*)bm_malloc(handle, be->mem_taken);
if (!data)
return;
memset(data, 0, be->mem_taken);
// make sure we are using the correct filename in the case of an EFF.
// this will populate filename[] whether it's EFF or not
EFF_FILENAME_CHECK;
ktx_error = ktx1_read_bitmap(filename, data, &ktx_bpp);
if (ktx_error != KTX1_ERROR_NONE)
{
mprintf(("KTX: read_bitmap error=%d file='%s'\n", ktx_error, filename));
bm_free_data(bs);
return;
}
bmp->bpp = ktx_bpp;
bmp->data = (ptr_u)data;
bmp->flags = 0;
#ifdef BMPMAN_NDEBUG
Assert(be->data_size > 0);
#endif
}
+28 -5
View File
@@ -34,6 +34,7 @@
// Flag positions for bitmap.flags
// ***** NOTE: bitmap.flags is an 8-bit value, no more BMP_TEX_* flags can be added unless the type is changed!! ******
// Update: bitmap.flags type changed to 16-bit
#define BMP_AABITMAP (1<<0) //!< antialiased bitmap
#define BMP_TEX_XPARENT (1<<1) //!< transparent texture
#define BMP_TEX_OTHER (1<<2) //!< so we can identify all "normal" textures
@@ -43,9 +44,14 @@
#define BMP_TEX_BC7 (1<<6) //!< BC7 compressed 8r8g8b8a (32bit)
#define BMP_TEX_CUBEMAP (1<<7) //!< a texture made for cubic environment map
#define BMP_MASK_BITMAP (1<<8) //!< a bitmap that will be used for masking mouse interaction. Typically not used in render operations
#define BMP_TEX_ETC2_RGB8 (1 << 9) //!< ETC2 RGB compressed (24bit, no alpha)
#define BMP_TEX_ETC2_RGBA1 (1 << 10) //!< ETC2 RGBA1 compressed (24bit, 1 alpha)
#define BMP_TEX_ETC2_RGBA8 (1 << 11) //!< ETC2 RGBA8 compressed (32bit, 8 alpha)
// Combined flags
#define BMP_TEX_COMP ( BMP_TEX_DXT1 | BMP_TEX_DXT3 | BMP_TEX_DXT5 | BMP_TEX_BC7 ) //!< Compressed textures
#define BMP_TEX_COMP ( BMP_TEX_DXT1 | BMP_TEX_DXT3 | BMP_TEX_DXT5 | BMP_TEX_BC7 | BMP_TEX_ETC2_RGB8 |\
BMP_TEX_ETC2_RGBA1 | BMP_TEX_ETC2_RGBA8 ) //!< Compressed textures
#define BMP_TEX_NONCOMP ( BMP_TEX_XPARENT | BMP_TEX_OTHER ) //!< Non-compressed textures
#define BMP_TEX_ANY ( BMP_TEX_COMP | BMP_TEX_NONCOMP ) //!< Any texture
@@ -81,7 +87,15 @@ enum BM_TYPE
BM_TYPE_CUBEMAP_DXT3, //!< 32-bit cubemap (compressed cubemap surface)
BM_TYPE_CUBEMAP_DXT5, //!< 32-bit cubemap (compressed cubemap surface)
BM_TYPE_3D //!< 3D in-memory
BM_TYPE_3D, //!< 3D in-memory
BM_TYPE_KTX, //!< generic identifier for KTX1
BM_TYPE_ETC2_RGB, //!< 24 bit, no alpha
BM_TYPE_ETC2_SRGB, //!< 24 bit, no alpha, signed
BM_TYPE_ETC2_RGBA1, //!< 24 bit, 1 bit alpha
BM_TYPE_ETC2_SRGBA1, //!< 24 bit, 1 bit alpha, signed
BM_TYPE_ETC2_RGBA_EAC, //!< 32 bit, 8 bit alpha
BM_TYPE_ETC2_SRGBA_EAC //!< 32 bit, 8 bit alpha, signed
};
/**
@@ -96,7 +110,7 @@ struct bitmap
short rowsize; //!< What you need to add to go to next row
int bpp; //!< Requested bitdepth of each pixel. ( 7, 8, 15, 16, 24, 32)
int true_bpp; //!< The image's actual bitdepth
ushort flags; //!< Various texture type flags. @see BMPMAN_CONSTANTS
uint flags; //!< Various texture type flags. @see BMPMAN_CONSTANTS
ptr_u data; //!< Pointer to data, or maybe offset into VRAM.
ubyte *palette; /**< @brief Pointer to this bitmap's palette (if it has one).
* @details If BMP_NO_PALETTE_MAP flag is cleared, this palette just points to the screen palette. (gr_palette)
@@ -135,6 +149,7 @@ void bm_close();
#define BMP_FLAG_RENDER_TARGET_DYNAMIC (1<<1)
#define BMP_FLAG_CUBEMAP (1<<2)
#define BMP_FLAG_RENDER_TARGET_MIPMAP (1<<3)
#define BMP_FLAG_RENDER_TARGET_DEPTH_ATTACHMENT (1<<4)
/**
* @brief Allocates memory for the given handle.
@@ -344,7 +359,7 @@ int bm_load_either(const char *filename, int *nframes = NULL, int *fps = NULL, i
* @returns A pointer to the bitmap that's valid until bm_unlock is called if successful, or
* @returns NULL if unsuccessful
*/
bitmap* bm_lock(int handle, int bpp, ushort flags, bool nodebug = false);
bitmap* bm_lock(int handle, int bpp, uint flags, bool nodebug = false);
/**
* @brief Returns the image type of the given bitmap handle
@@ -387,7 +402,7 @@ int bm_is_valid(int handle);
* @returns The handle to the first frame on success, or
* @returns -1 on failure
*/
int bm_get_info(int handle, int *w = nullptr, int * h = nullptr, ushort* flags = nullptr, int *nframes = nullptr, int *fps = nullptr);
int bm_get_info(int handle, int *w = nullptr, int * h = nullptr, uint* flags = nullptr, int *nframes = nullptr, int *fps = nullptr);
/**
* @brief Gets 3D info on the bitmap indexed by handle.
@@ -579,6 +594,14 @@ void bm_get_components(ubyte *pixel, ubyte *r, ubyte *g, ubyte *b, ubyte *a);
*/
int bm_is_compressed(int handle);
/**
* @brief Returns true if the bitmap indexed by handle was loaded from a cubemap image file
*
* @note Render targets return false, even cubemap ones, and so do BC7 (DX10) DDS cubemaps, whose cube flag the DDS
* loader does not keep
*/
bool bm_is_cubemap(int handle);
/**
* @brief Gets the correct TCACHE_TYPE for compressed graphics (uncompressed are assumed TCACHE_TYPE_NORMAL)
*/
+46 -24
View File
@@ -56,6 +56,10 @@ APPLY_TO_FOV_T(-, sub)
// Used to set the default value for in-game options
static float fov_default = DEFAULT_FOV;
static float cockpit_fov_default = DEFAULT_FOV;
static bool cockpit_fov_toggle_default = false;
bool Use_cockpit_fov = false;
static SCP_string fov_display(float val)
{
@@ -73,6 +77,22 @@ static void parse_fov_func()
fov_default = value;
}
static void parse_cockpit_fov_func()
{
float value;
stuff_float(&value);
CLAMP(value, 0.436332f, 1.5708f);
cockpit_fov_default = value;
}
static void parse_cockpit_fov_toggle_func()
{
bool value;
stuff_boolean(&value);
cockpit_fov_toggle_default = value;
}
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
auto FovOption = options::OptionBuilder<float>("Graphics.FOV",
std::pair<const char*, int>{"Field Of View", 1703},
std::pair<const char*, int>{"The vertical field of view", 1704})
@@ -89,41 +109,42 @@ auto FovOption = options::OptionBuilder<float>("Graphics.FOV",
.parser(parse_fov_func)
.finish();
bool Use_cockpit_fov = false;
auto CockpitFOVToggleOption = options::OptionBuilder<bool>("Graphics.CockpitFOVToggle",
std::pair<const char*, int>{"Cockpit FOV Toggle", 1838},
std::pair<const char*, int>{"Whether or not to use a different FOV for cockpit rendering from normal rendering", 1839})
.category(std::make_pair("Graphics", 1825))
.default_val(false)
.change_listener([](bool val, bool) {
if (!val) {
COCKPIT_ZOOM_DEFAULT = VIEWER_ZOOM_DEFAULT;
}
return true; // This option will always persist so we never return false
})
.level(options::ExpertLevel::Advanced)
.bind_to(&Use_cockpit_fov)
.importance(61)
.finish();
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
auto CockpitFovOption = options::OptionBuilder<float>("Graphics.CockpitFOV",
std::pair<const char*, int>{"Cockpit Field Of View", 1840},
std::pair<const char*, int>{"The vertical field of view for cockpit rendering. Only works if cockpits are active and cockpit FOV toggle is turned on.", 1841})
.category(std::make_pair("Graphics", 1825))
.range(0.436332f, 1.5708f)
.change_listener([](const float& val, bool) {
if (Use_cockpit_fov){
COCKPIT_ZOOM_DEFAULT = val;
} else {
COCKPIT_ZOOM_DEFAULT = VIEWER_ZOOM_DEFAULT;
}
// Graphics.CockpitFOVToggle has its own listener that re-applies this value, so the
// result is the same whichever of the two options the manager happens to load first.
COCKPIT_ZOOM_DEFAULT = Use_cockpit_fov ? fov_t(val) : VIEWER_ZOOM_DEFAULT;
return true;
})
.display(fov_display)
.default_val(fov_default)
.default_func([]() { return cockpit_fov_default; })
.level(options::ExpertLevel::Advanced)
.importance(62)
.parser(parse_cockpit_fov_func)
.finish();
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
auto CockpitFOVToggleOption = options::OptionBuilder<bool>("Graphics.CockpitFOVToggle",
std::pair<const char*, int>{"Cockpit FOV Toggle", 1838},
std::pair<const char*, int>{"Whether or not to use a different FOV for cockpit rendering from normal rendering", 1839})
.category(std::make_pair("Graphics", 1825))
.default_func([]() {return cockpit_fov_toggle_default;})
// This does the work of bind_to(&Use_cockpit_fov) and then re-applies Graphics.CockpitFOV,
// because that option's listener needs Use_cockpit_fov to already hold its final value.
// Doing it here makes the pair of options independent of the order the manager loads them in.
.change_listener([](bool val, bool) {
Use_cockpit_fov = val;
COCKPIT_ZOOM_DEFAULT = val ? fov_t(CockpitFovOption->getValue()) : VIEWER_ZOOM_DEFAULT;
return true; // This option will always persist so we never return false
})
.level(options::ExpertLevel::Advanced)
.importance(61)
.parser(parse_cockpit_fov_toggle_func)
.finish();
//*************************CLASS: camera*************************
@@ -732,6 +753,7 @@ subtitle::subtitle(int in_x_pos, int in_y_pos, const char* in_text, const char*
text_buf = in_text;
sexp_replace_variable_names_with_values(text_buf);
sexp_container_replace_refs_with_values(text_buf);
// (message_translate_tokens is called when the subtitle is queued, so does not need to be called here)
in_text = text_buf.c_str();
}
+609
View File
@@ -0,0 +1,609 @@
#include "freespace.h"
#include "actions/Action.h"
#include "camera/camera.h"
#include "camera/photomode.h"
#include "controlconfig/controlsconfig.h"
#include "debugconsole/console.h"
#include "globalincs/alphacolors.h"
#include "graphics/2d.h"
#include "graphics/font.h"
#include "hud/hud.h"
#include "io/keycontrol.h"
#include "localization/localize.h"
#include "mission/missionmessage.h"
#include "object/object.h"
#include "parse/parselo.h"
#include "playerman/player.h"
#include "render/3d.h"
#include "scripting/global_hooks.h"
#include "sound/audiostr.h"
bool Photo_mode_active = false;
camid Photo_mode_id;
fix Photo_mode_saved_time_compression = F1_0;
bool Photo_mode_saved_lock_state = false;
bool Photo_mode_screenshot_queued_this_frame = false;
bool Photo_mode_allowed = true;
bool Photo_mode_audio_paused = false;
const float Photo_mode_turn_rate = PI_2; // radians/sec
constexpr float Photo_mode_move_speed = 90.0f;
constexpr float Photo_mode_boost_multiplier = 6.0f;
constexpr float Photo_mode_time_compression = 0.01f;
struct photo_mode_post_effect_state {
SCP_string name;
float intensity = 0.0f;
vec3d rgb = vmd_zero_vector;
int effect_idx = -1; // index into Post_processing_manager->getPostEffects()
};
enum class photo_mode_param_type {
INT_RANGE, // adjustable integer value with min/max
BOOL_TOGGLE // on/off toggle
};
enum class photo_mode_param_role {
NONE,
GRID_OVERLAY, // bool toggle that enables the rule of thirds grid
POST_EFFECT // drives a post processing effect by name
};
struct photo_mode_param {
const char* label; // display name
int xstr_id; // XSTR localization ID
photo_mode_param_type type;
photo_mode_param_role role;
// For POST_EFFECT role: post processing effect name, range, and values
const char* effect_name; // post processing effect to control (nullptr if role != POST_EFFECT)
int effect_idx; // index into Post_processing_manager->getPostEffects() (-1 if unresolved)
int min_val;
int max_val;
int value;
int saved_value;
// For BOOL_TOGGLE
bool bool_value;
};
// Parameter definitions
SCP_vector<photo_mode_param> Photo_mode_params = {
{"Grid", 1906, photo_mode_param_type::BOOL_TOGGLE, photo_mode_param_role::GRID_OVERLAY, nullptr, -1, 0, 0, 0, 0, false},
{"Saturation", 1903, photo_mode_param_type::INT_RANGE, photo_mode_param_role::POST_EFFECT, "saturation", -1, 0, 200, 100, 100, false},
{"Brightness", 1904, photo_mode_param_type::INT_RANGE, photo_mode_param_role::POST_EFFECT, "brightness", -1, 0, 200, 100, 100, false},
{"Contrast", 1905, photo_mode_param_type::INT_RANGE, photo_mode_param_role::POST_EFFECT, "contrast", -1, 0, 200, 100, 100, false},
};
SCP_vector<photo_mode_post_effect_state> Photo_mode_saved_post_effects;
int Photo_mode_selected_parameter = 0;
int Photo_mode_panel_width = 0;
int Photo_mode_panel_padding_w = 0;
void photo_mode_capture_post_effect_state()
{
Photo_mode_saved_post_effects.clear();
if (graphics::Post_processing_manager == nullptr) {
return;
}
const auto& post_effects = graphics::Post_processing_manager->getPostEffects();
Photo_mode_saved_post_effects.reserve(post_effects.size());
for (int i = 0; i < static_cast<int>(post_effects.size()); ++i) {
const auto& effect = post_effects[i];
photo_mode_post_effect_state state;
state.name = effect.name;
state.intensity = effect.intensity;
state.rgb = effect.rgb;
state.effect_idx = i;
Photo_mode_saved_post_effects.push_back(state);
}
// Resolve effect indices for each param.
// Done once here rather than on every frame or every key press.
for (auto& param : Photo_mode_params) {
if (param.role != photo_mode_param_role::POST_EFFECT) {
param.effect_idx = -1;
continue;
}
param.effect_idx = -1;
for (int j = 0; j < static_cast<int>(post_effects.size()); ++j) {
if (!stricmp(post_effects[j].name.c_str(), param.effect_name)) {
param.effect_idx = j;
break;
}
}
}
}
void photo_mode_apply_saved_post_effect_state(bool clear_saved_state)
{
if (graphics::Post_processing_manager == nullptr) {
if (clear_saved_state) {
Photo_mode_saved_post_effects.clear();
}
return;
}
const auto& post_effects = graphics::Post_processing_manager->getPostEffects();
for (const auto& saved_state : Photo_mode_saved_post_effects) {
if (saved_state.effect_idx < 0 || saved_state.effect_idx >= static_cast<int>(post_effects.size())) {
continue;
}
const auto& effect = post_effects[saved_state.effect_idx];
const int value = static_cast<int>(std::lround((saved_state.intensity - effect.add) * effect.div));
gr_post_process_set_effect(saved_state.name.c_str(), value, &saved_state.rgb);
}
if (clear_saved_state) {
Photo_mode_saved_post_effects.clear();
}
}
int photo_mode_get_saved_post_effect_value(int effect_idx)
{
int value = 100;
if (effect_idx < 0 || graphics::Post_processing_manager == nullptr) {
return value;
}
const auto& post_effects = graphics::Post_processing_manager->getPostEffects();
if (effect_idx >= static_cast<int>(post_effects.size())) {
return value;
}
for (const auto& saved_state : Photo_mode_saved_post_effects) {
if (saved_state.effect_idx != effect_idx) {
continue;
}
const auto& effect = post_effects[effect_idx];
value = static_cast<int>(std::lround((saved_state.intensity - effect.add) * effect.div));
break;
}
return value;
}
void photo_mode_sync_parameter_values_from_saved_state()
{
for (auto& param : Photo_mode_params) {
if (param.role == photo_mode_param_role::POST_EFFECT) {
param.saved_value = photo_mode_get_saved_post_effect_value(param.effect_idx);
param.value = param.saved_value;
} else if (param.type == photo_mode_param_type::BOOL_TOGGLE) {
param.bool_value = false;
}
}
Photo_mode_selected_parameter = 0;
}
void photo_mode_apply_parameter_values()
{
for (const auto& param : Photo_mode_params) {
if (param.role == photo_mode_param_role::POST_EFFECT) {
gr_post_process_set_effect(param.effect_name, param.value, nullptr);
}
}
}
SCP_string format_photo_mode_keybind(int action)
{
auto primary = Control_config[action].first.textify();
auto secondary = Control_config[action].second.textify();
if (primary.empty() && secondary.empty()) {
return XSTR("Unbound", 1907);
}
if (primary.empty()) {
return secondary;
}
if (secondary.empty()) {
return primary;
}
return primary + " / " + secondary;
}
void photo_mode_compute_hud_layout()
{
const auto old_font = font::get_current_fontnum();
font::set_font(font::FONT1);
int max_text_width = 0;
char buf[512];
auto measure = [&](const char* text) {
int w = 0;
gr_get_string_size(&w, nullptr, text);
if (w > max_text_width) max_text_width = w;
};
// Find the longest string
measure(XSTR("Photo Mode", 1892));
measure(XSTR("Controls", 1893));
measure(XSTR("Status", 1900));
measure(XSTR("Effects", 1914));
snprintf(buf, sizeof(buf), XSTR("Toggle: %s", 1894), format_photo_mode_keybind(TOGGLE_PHOTO_MODE).c_str());
measure(buf);
snprintf(buf, sizeof(buf), XSTR("Previous Filter: %s", 1895), format_photo_mode_keybind(PHOTO_MODE_FILTER_PREV).c_str());
measure(buf);
snprintf(buf, sizeof(buf), XSTR("Next Filter: %s", 1896), format_photo_mode_keybind(PHOTO_MODE_FILTER_NEXT).c_str());
measure(buf);
snprintf(buf, sizeof(buf), XSTR("Reset Filters: %s", 1897), format_photo_mode_keybind(PHOTO_MODE_FILTER_RESET).c_str());
measure(buf);
snprintf(buf, sizeof(buf), XSTR("Decrease Parameter: %s", 1898), format_photo_mode_keybind(PHOTO_MODE_PARAM_DECREASE).c_str());
measure(buf);
snprintf(buf, sizeof(buf), XSTR("Increase Parameter: %s", 1899), format_photo_mode_keybind(PHOTO_MODE_PARAM_INCREASE).c_str());
measure(buf);
// Use worst case placeholder values for the dynamic status strings so the
// panel width stays stable as these change during a session.
snprintf(buf, sizeof(buf), XSTR("Time Compression: %.2fx", 1901), 64.0f); measure(buf);
snprintf(buf, sizeof(buf), XSTR("Cam Pos: X %.1f Y %.1f Z %.1f", 1902), -99999.9f, -99999.9f, -99999.9f); measure(buf);
for (const auto& param : Photo_mode_params) {
if (param.type == photo_mode_param_type::INT_RANGE) {
snprintf(buf, sizeof(buf), "%s: %d", XSTR(param.label, param.xstr_id), param.max_val);
} else {
snprintf(buf, sizeof(buf), "%s: %s", XSTR(param.label, param.xstr_id), XSTR("Off", 1286));
}
measure(buf);
}
font::set_font(old_font);
Photo_mode_panel_padding_w = max_text_width / 10;
Photo_mode_panel_width = max_text_width + Photo_mode_panel_padding_w * 2;
}
void photo_mode_set_active(bool active)
{
if (active == Photo_mode_active) {
return;
}
if (active) {
if (!Photo_mode_allowed) {
return;
}
if ((Game_mode & GM_MULTIPLAYER) != 0 || Player_obj == nullptr || !(Game_mode & GM_IN_MISSION)) {
return;
}
if (Time_compression_locked) {
return;
}
if (Player_obj->type != OBJ_SHIP) {
return;
}
Photo_mode_saved_time_compression = Game_time_compression;
Photo_mode_saved_lock_state = Time_compression_locked;
Photo_mode_id = cam_create("Photo Mode", &Eye_position, &Eye_matrix);
if (!Photo_mode_id.isValid() || !cam_set_camera(Photo_mode_id)) {
cam_delete(Photo_mode_id);
Photo_mode_id = camid();
return;
}
set_time_compression(Photo_mode_time_compression, 0.0f);
lock_time_compression(true);
photo_mode_capture_post_effect_state();
photo_mode_sync_parameter_values_from_saved_state();
photo_mode_apply_parameter_values();
photo_mode_compute_hud_layout();
audiostream_pause_all();
message_pause_all();
Photo_mode_audio_paused = true;
Photo_mode_active = true;
mprintf(("Photo Mode enabled.\n"));
if (scripting::hooks::OnPhotoModeStarted->isActive()) {
scripting::hooks::OnPhotoModeStarted->run();
}
return;
}
cam_reset_camera();
cam_delete(Photo_mode_id);
Photo_mode_id = camid();
set_time_compression(f2fl(Photo_mode_saved_time_compression), 0.0f);
lock_time_compression(Photo_mode_saved_lock_state);
if (Photo_mode_audio_paused) {
audiostream_unpause_all();
message_resume_all();
Photo_mode_audio_paused = false;
}
photo_mode_apply_saved_post_effect_state(true);
for (auto& param : Photo_mode_params) {
if (param.type == photo_mode_param_type::INT_RANGE) {
param.value = 100;
param.saved_value = 100;
} else if (param.type == photo_mode_param_type::BOOL_TOGGLE) {
param.bool_value = false;
}
}
Photo_mode_selected_parameter = 0;
Photo_mode_active = false;
mprintf(("Photo Mode disabled.\n"));
if (scripting::hooks::OnPhotoModeEnded->isActive()) {
scripting::hooks::OnPhotoModeEnded->run();
}
}
void photo_mode_do_frame(float frame_time)
{
if (!Photo_mode_active || !Photo_mode_id.isValid()) {
return;
}
if (!Photo_mode_allowed) {
photo_mode_set_active(false);
return;
}
auto photo_mode = Photo_mode_id.getCamera();
if (photo_mode == nullptr) {
photo_mode_set_active(false);
return;
}
vec3d cam_pos = vmd_zero_vector;
matrix cam_orient = vmd_identity_matrix;
photo_mode->get_info(&cam_pos, &cam_orient);
angles delta_angles{};
float pitch = check_control_timef(PITCH_FORWARD) - check_control_timef(PITCH_BACK);
float heading = check_control_timef(YAW_RIGHT) - check_control_timef(YAW_LEFT);
float bank = check_control_timef(BANK_LEFT) - check_control_timef(BANK_RIGHT);
int axis[Action::NUM_VALUES] = {0};
control_get_axes_readings(axis, flRealframetime);
pitch += -f2fl(axis[Action::PITCH]);
heading += f2fl(axis[Action::HEADING]);
bank -= f2fl(axis[Action::BANK]);
CLAMP(pitch, -1.0f, 1.0f);
CLAMP(heading, -1.0f, 1.0f);
CLAMP(bank, -1.0f, 1.0f);
delta_angles.p = pitch * Photo_mode_turn_rate * frame_time;
delta_angles.h = heading * Photo_mode_turn_rate * frame_time;
delta_angles.b = bank * Photo_mode_turn_rate * frame_time;
matrix delta_orient = vmd_identity_matrix;
vm_angles_2_matrix(&delta_orient, &delta_angles);
vm_matrix_x_matrix(&cam_orient, &cam_orient, &delta_orient);
vm_fix_matrix(&cam_orient);
float speed = Photo_mode_move_speed;
if (check_control(AFTERBURNER)) {
speed *= Photo_mode_boost_multiplier;
}
float forward = check_control_timef(FORWARD_THRUST) - check_control_timef(REVERSE_THRUST);
float right = check_control_timef(RIGHT_SLIDE_THRUST) - check_control_timef(LEFT_SLIDE_THRUST);
float up = check_control_timef(UP_SLIDE_THRUST) - check_control_timef(DOWN_SLIDE_THRUST);
CLAMP(forward, -1.0f, 1.0f);
CLAMP(right, -1.0f, 1.0f);
CLAMP(up, -1.0f, 1.0f);
vm_vec_scale_add2(&cam_pos, &cam_orient.vec.fvec, forward * speed * frame_time);
vm_vec_scale_add2(&cam_pos, &cam_orient.vec.rvec, right * speed * frame_time);
vm_vec_scale_add2(&cam_pos, &cam_orient.vec.uvec, up * speed * frame_time);
photo_mode->set_rotation(&cam_orient);
photo_mode->set_position(&cam_pos);
}
void photo_mode_maybe_render_hud()
{
if (!Photo_mode_active || Photo_mode_screenshot_queued_this_frame || gr_is_screenshot_requested()) {
return;
}
if (!Photo_mode_allowed) {
photo_mode_set_active(false);
return;
}
auto photo_mode = Photo_mode_id.getCamera();
if (photo_mode == nullptr) {
return;
}
vec3d cam_pos = vmd_zero_vector;
matrix cam_orient = vmd_identity_matrix;
photo_mode->get_info(&cam_pos, &cam_orient);
const auto toggle_keybind = format_photo_mode_keybind(TOGGLE_PHOTO_MODE);
const auto prev_filter_keybind = format_photo_mode_keybind(PHOTO_MODE_FILTER_PREV);
const auto next_filter_keybind = format_photo_mode_keybind(PHOTO_MODE_FILTER_NEXT);
const auto reset_filter_keybind = format_photo_mode_keybind(PHOTO_MODE_FILTER_RESET);
const auto decrease_param_keybind = format_photo_mode_keybind(PHOTO_MODE_PARAM_DECREASE);
const auto increase_param_keybind = format_photo_mode_keybind(PHOTO_MODE_PARAM_INCREASE);
gr_set_color_fast(&Color_silver);
const auto old_font = font::get_current_fontnum();
font::set_font(font::FONT1);
const int line_height = gr_get_font_height();
const int num_param_lines = static_cast<int>(Photo_mode_params.size());
const int panel_padding_h = line_height / 2;
const int panel_padding_w = Photo_mode_panel_padding_w;
const int panel_width = Photo_mode_panel_width;
const int panel_height = panel_padding_h * 2 + line_height * (15 + num_param_lines);
const int panel_x = gr_screen.center_offset_x + (gr_screen.center_w / 4);
const int panel_y = gr_screen.center_offset_y + gr_screen.center_h - panel_height - (gr_screen.center_h / 6);
gr_line(panel_x, panel_y, panel_x + panel_width, panel_y, GR_RESIZE_NONE);
gr_line(panel_x, panel_y + panel_height, panel_x + panel_width, panel_y + panel_height, GR_RESIZE_NONE);
gr_line(panel_x, panel_y, panel_x, panel_y + panel_height, GR_RESIZE_NONE);
gr_line(panel_x + panel_width, panel_y, panel_x + panel_width, panel_y + panel_height, GR_RESIZE_NONE);
gr_line(panel_x,
panel_y + line_height + panel_padding_h + 1,
panel_x + panel_width,
panel_y + line_height + panel_padding_h + 1,
GR_RESIZE_NONE);
bool grid_enabled = false;
for (const auto& param : Photo_mode_params) {
if (param.role == photo_mode_param_role::GRID_OVERLAY) {
grid_enabled = param.bool_value;
break;
}
}
if (grid_enabled) {
const int x1 = gr_screen.center_offset_x + gr_screen.center_w / 3;
const int x2 = gr_screen.center_offset_x + (gr_screen.center_w * 2) / 3;
const int y1 = gr_screen.center_offset_y + gr_screen.center_h / 3;
const int y2 = gr_screen.center_offset_y + (gr_screen.center_h * 2) / 3;
gr_line(x1, gr_screen.center_offset_y, x1, gr_screen.center_offset_y + gr_screen.center_h, GR_RESIZE_NONE);
gr_line(x2, gr_screen.center_offset_y, x2, gr_screen.center_offset_y + gr_screen.center_h, GR_RESIZE_NONE);
gr_line(gr_screen.center_offset_x, y1, gr_screen.center_offset_x + gr_screen.center_w, y1, GR_RESIZE_NONE);
gr_line(gr_screen.center_offset_x, y2, gr_screen.center_offset_x + gr_screen.center_w, y2, GR_RESIZE_NONE);
}
int line = panel_y + panel_padding_h;
const int text_x = panel_x + panel_padding_w;
gr_printf_no_resize(text_x, line, "%s", XSTR("Photo Mode", 1892));
line += line_height;
line += line_height;
gr_printf_no_resize(text_x, line, "%s", XSTR("Controls", 1893));
line += line_height;
gr_printf_no_resize(text_x, line, XSTR("Toggle: %s", 1894), toggle_keybind.c_str());
line += line_height;
gr_printf_no_resize(text_x, line, XSTR("Previous Filter: %s", 1895), prev_filter_keybind.c_str());
line += line_height;
gr_printf_no_resize(text_x, line, XSTR("Next Filter: %s", 1896), next_filter_keybind.c_str());
line += line_height;
gr_printf_no_resize(text_x, line, XSTR("Reset Filters: %s", 1897), reset_filter_keybind.c_str());
line += line_height;
gr_printf_no_resize(text_x, line, XSTR("Decrease Parameter: %s", 1898), decrease_param_keybind.c_str());
line += line_height;
gr_printf_no_resize(text_x, line, XSTR("Increase Parameter: %s", 1899), increase_param_keybind.c_str());
line += line_height;
line += line_height;
gr_printf_no_resize(text_x, line, "%s", XSTR("Status", 1900));
line += line_height;
gr_printf_no_resize(text_x, line, XSTR("Time Compression: %.2fx", 1901), f2fl(Game_time_compression));
line += line_height;
gr_printf_no_resize(text_x,
line,
XSTR("Cam Pos: X %.1f Y %.1f Z %.1f", 1902),
cam_pos.xyz.x,
cam_pos.xyz.y,
cam_pos.xyz.z);
line += line_height;
line += line_height;
gr_set_color_fast(&Color_silver);
gr_printf_no_resize(text_x, line, "%s", XSTR("Effects", 1914));
line += line_height;
for (int i = 0; i < static_cast<int>(Photo_mode_params.size()); ++i) {
const auto& param = Photo_mode_params[i];
gr_set_color_fast(Photo_mode_selected_parameter == i ? &Color_bright_white : &Color_silver);
if (param.type == photo_mode_param_type::INT_RANGE) {
gr_printf_no_resize(text_x, line, "%s: %d", XSTR(param.label, param.xstr_id), param.value);
} else if (param.type == photo_mode_param_type::BOOL_TOGGLE) {
gr_printf_no_resize(text_x, line, "%s: %s", XSTR(param.label, param.xstr_id),
param.bool_value ? XSTR("On", 1285) : XSTR("Off", 1286));
}
line += line_height;
}
font::set_font(old_font);
}
void photo_mode_clear_screenshot_queued_flag()
{
Photo_mode_screenshot_queued_this_frame = false;
}
void photo_mode_set_screenshot_queued_flag()
{
Photo_mode_screenshot_queued_this_frame = true;
}
void game_toggle_photo_mode()
{
photo_mode_set_active(!Photo_mode_active);
}
void game_set_photo_mode_allowed(bool allowed)
{
Photo_mode_allowed = allowed && ((Game_mode & GM_MULTIPLAYER) == 0);
if (!Photo_mode_allowed && Photo_mode_active) {
photo_mode_set_active(false);
}
}
bool game_get_photo_mode_allowed()
{
return Photo_mode_allowed && ((Game_mode & GM_MULTIPLAYER) == 0);
}
bool game_is_photo_mode_active()
{
return Photo_mode_active;
}
void game_cycle_photo_mode_filter(int direction)
{
if (!Photo_mode_active) {
return;
}
Photo_mode_selected_parameter = std::clamp(Photo_mode_selected_parameter + direction, 0, static_cast<int>(Photo_mode_params.size()) - 1);
}
void game_reset_photo_mode_filters()
{
if (!Photo_mode_active) {
return;
}
for (auto& param : Photo_mode_params) {
if (param.type == photo_mode_param_type::INT_RANGE) {
param.value = param.saved_value;
} else if (param.type == photo_mode_param_type::BOOL_TOGGLE) {
param.bool_value = false;
}
}
photo_mode_apply_parameter_values();
}
void game_adjust_photo_mode_filter_parameter(int delta)
{
if (!Photo_mode_active) {
return;
}
auto& param = Photo_mode_params[Photo_mode_selected_parameter];
if (param.type == photo_mode_param_type::BOOL_TOGGLE) {
param.bool_value = (delta > 0);
return;
}
param.value = std::clamp(param.value + delta, param.min_val, param.max_val);
photo_mode_apply_parameter_values();
}
+16
View File
@@ -0,0 +1,16 @@
#pragma once
void photo_mode_set_active(bool active);
void photo_mode_do_frame(float frame_time);
void photo_mode_maybe_render_hud();
void photo_mode_clear_screenshot_queued_flag();
void photo_mode_set_screenshot_queued_flag();
// mission-level permission to allow/disallow Photo Mode
void game_toggle_photo_mode();
void game_set_photo_mode_allowed(bool allowed);
bool game_get_photo_mode_allowed();
bool game_is_photo_mode_active();
void game_cycle_photo_mode_filter(int direction);
void game_reset_photo_mode_filters();
void game_adjust_photo_mode_filter_parameter(int delta);
+104 -28
View File
@@ -33,7 +33,7 @@
#include "parse/encrypt.h"
#include "cfilesystem.h"
#include <algorithm>
#include <limits>
char Cfile_root_dir[CFILE_ROOT_DIRECTORY_LEN] = "";
@@ -52,7 +52,7 @@ cf_pathtype Pathtypes[CF_MAX_PATH_TYPES] = {
// Root must be index 1!!
{ CF_TYPE_ROOT, "", ".mve .ogg", CF_TYPE_ROOT },
{ CF_TYPE_DATA, "data", ".cfg .txt", CF_TYPE_ROOT },
{ CF_TYPE_MAPS, "data" DIR_SEPARATOR_STR "maps", ".pcx .ani .eff .tga .jpg .png .dds", CF_TYPE_DATA },
{ CF_TYPE_MAPS, "data" DIR_SEPARATOR_STR "maps", ".pcx .ani .eff .tga .jpg .png .dds .ktx", CF_TYPE_DATA },
{ CF_TYPE_TEXT, "data" DIR_SEPARATOR_STR "text", ".txt .net", CF_TYPE_DATA },
{ CF_TYPE_MODELS, "data" DIR_SEPARATOR_STR "models", ".pof", CF_TYPE_DATA },
{ CF_TYPE_TABLES, "data" DIR_SEPARATOR_STR "tables", ".tbl .tbm .lua", CF_TYPE_DATA },
@@ -66,26 +66,26 @@ cf_pathtype Pathtypes[CF_MAX_PATH_TYPES] = {
{ CF_TYPE_VOICE_TRAINING, "data" DIR_SEPARATOR_STR "voice" DIR_SEPARATOR_STR "training", ".wav .ogg", CF_TYPE_VOICE },
{ CF_TYPE_MUSIC, "data" DIR_SEPARATOR_STR "music", ".wav .ogg", CF_TYPE_DATA },
{ CF_TYPE_MOVIES, "data" DIR_SEPARATOR_STR "movies", ".mve .msb .ogg .mp4 .srt .webm .png",CF_TYPE_DATA },
{ CF_TYPE_INTERFACE, "data" DIR_SEPARATOR_STR "interface", ".pcx .ani .dds .tga .eff .png .jpg .rml .rcss", CF_TYPE_DATA },
{ CF_TYPE_INTERFACE, "data" DIR_SEPARATOR_STR "interface", ".pcx .ani .dds .tga .eff .png .jpg .rml .rcss .ktx", CF_TYPE_DATA },
{ CF_TYPE_FONT, "data" DIR_SEPARATOR_STR "fonts", ".vf .ttf .otf", CF_TYPE_DATA },
{ CF_TYPE_EFFECTS, "data" DIR_SEPARATOR_STR "effects", ".ani .eff .pcx .neb .tga .jpg .png .dds .sdr", CF_TYPE_DATA },
{ CF_TYPE_HUD, "data" DIR_SEPARATOR_STR "hud", ".pcx .ani .eff .tga .jpg .png .dds", CF_TYPE_DATA },
{ CF_TYPE_EFFECTS, "data" DIR_SEPARATOR_STR "effects", ".ani .eff .pcx .neb .tga .jpg .png .dds .ktx .sdr .vert .frag .glsl", CF_TYPE_DATA },
{ CF_TYPE_HUD, "data" DIR_SEPARATOR_STR "hud", ".pcx .ani .eff .tga .jpg .png .dds .ktx", CF_TYPE_DATA },
{ CF_TYPE_PLAYERS, "data" DIR_SEPARATOR_STR "players", ".hcf", /* DON'T add pilot files here!! */ CF_TYPE_DATA },
{ CF_TYPE_PLAYER_IMAGES, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "images", ".pcx .png .dds", CF_TYPE_PLAYERS },
{ CF_TYPE_SQUAD_IMAGES, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "squads", ".pcx .png .dds", CF_TYPE_PLAYERS },
{ CF_TYPE_PLAYER_IMAGES, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "images", ".pcx .png .dds .ktx", CF_TYPE_PLAYERS },
{ CF_TYPE_SQUAD_IMAGES, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "squads", ".pcx .png .dds .ktx", CF_TYPE_PLAYERS },
{ CF_TYPE_SINGLE_PLAYERS, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "single", ".pl2 .cs2 .plr .csg .css .json", CF_TYPE_PLAYERS },
{ CF_TYPE_MULTI_PLAYERS, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "multi", ".plr .json", CF_TYPE_PLAYERS },
{ CF_TYPE_PLAYER_BINDS, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "presets", ".json", CF_TYPE_PLAYERS },
{ CF_TYPE_CACHE, "data" DIR_SEPARATOR_STR "cache", ".clr .tmp .bx", CF_TYPE_DATA }, //clr=cached color
{ CF_TYPE_MULTI_CACHE, "data" DIR_SEPARATOR_STR "multidata", ".pcx .png .jpg .dds .fs2 .txt", CF_TYPE_DATA },
{ CF_TYPE_MULTI_CACHE, "data" DIR_SEPARATOR_STR "multidata", ".pcx .png .jpg .dds .fs2 .txt .ktx", CF_TYPE_DATA },
{ CF_TYPE_MISSIONS, "data" DIR_SEPARATOR_STR "missions", ".fs2 .fc2 .ntl .ssv", CF_TYPE_DATA },
{ CF_TYPE_CONFIG, "data" DIR_SEPARATOR_STR "config", ".cfg .tbl .tbm .xml .csv", CF_TYPE_DATA },
{ CF_TYPE_DEMOS, "data" DIR_SEPARATOR_STR "demos", ".fsd", CF_TYPE_DATA },
{ CF_TYPE_CBANIMS, "data" DIR_SEPARATOR_STR "cbanims", ".pcx .ani .eff .tga .jpg .png .dds", CF_TYPE_DATA },
{ CF_TYPE_INTEL_ANIMS, "data" DIR_SEPARATOR_STR "intelanims", ".pcx .ani .eff .tga .jpg .png .dds", CF_TYPE_DATA },
{ CF_TYPE_CBANIMS, "data" DIR_SEPARATOR_STR "cbanims", ".pcx .ani .eff .tga .jpg .png .dds .ktx", CF_TYPE_DATA },
{ CF_TYPE_INTEL_ANIMS, "data" DIR_SEPARATOR_STR "intelanims", ".pcx .ani .eff .tga .jpg .png .dds .ktx", CF_TYPE_DATA },
{ CF_TYPE_SCRIPTS, "data" DIR_SEPARATOR_STR "scripts", ".lua .lc .fnl", CF_TYPE_DATA },
{ CF_TYPE_FICTION, "data" DIR_SEPARATOR_STR "fiction", ".txt", CF_TYPE_DATA },
{ CF_TYPE_FREDDOCS, "data" DIR_SEPARATOR_STR "freddocs", ".html", CF_TYPE_DATA }
{ CF_TYPE_FREDDOCS, "data" DIR_SEPARATOR_STR "freddocs", ".html .css .png .jpg", CF_TYPE_DATA }
};
// clang-format on
@@ -185,7 +185,13 @@ int cfile_init(const char *exe_dir, const char *cdrom_dir)
char buf[CFILE_ROOT_DIRECTORY_LEN];
strncpy(buf, exe_dir, CFILE_ROOT_DIRECTORY_LEN - 1);
#ifndef __ANDROID__
strncpy(buf, exe_dir, CFILE_ROOT_DIRECTORY_LEN - 1);
#else
(void)exe_dir;
strncpy(buf, os_android_get_working_folder_path().c_str(), CFILE_ROOT_DIRECTORY_LEN - 1);
#endif
buf[CFILE_ROOT_DIRECTORY_LEN - 1] = '\0';
// are we in a root directory?
@@ -831,7 +837,7 @@ static int cfget_cfile_block()
mprintf(("Out of cfile blocks! Currently opened files:\n"));
dump_opened_files();
UNREACHABLE("There are no more free cfile blocks. This means that there are too many files opened by FSO.\n"
Warning(LOCATION, "There are no more free cfile blocks. This means that there are too many files opened by FSO.\n"
"This is probably caused by a programming or scripting error where a file does not get closed."); // out of free cfile blocks
return -1;
}
@@ -1104,28 +1110,98 @@ void cfread_string(char *buf, int n, CFILE *file)
} while (c != 0 );
}
void cfread_string_len(char *buf, int buffer_size, CFILE *file)
{
int len;
len = cfread_int(file);
Assertion( (len <= buffer_size), "String cannot fit in buffer, len: %i, buffer: %i", len, buffer_size);
if (len) {
cfread(buf, len, 1, file);
Assertion(len != buffer_size || buf[len - 1] == 0, "Unterminated string does not fit in buffer");
}
namespace {
struct string_len_header {
int len = 0;
int stored = 0;
int start = 0;
bool length_cut = false;
};
buf[MIN(len, buffer_size - 1)] = 0;
// a past-the-end length is clamped like any cfread, but a negative one has nothing to clamp to, so inside a
// limited section it throws and the loader skips the rest of the section
string_len_header read_string_len_header(CFILE* file)
{
string_len_header h;
if (!cf_is_valid(file)) {
return h;
}
int before = cftell(file);
h.length_cut = cfread(&h.len, sizeof(h.len), 1, file) != 1;
h.start = cftell(file);
if (h.length_cut) {
// a clean end of file reads as an empty string without a warning, as cfread_int does
h.length_cut = (h.start != before) || (cfeof(file) == 0);
return h;
}
h.len = INTEL_INT(h.len);
if ((h.len < 0) && (file->max_read_len != 0)) {
throw cfile::max_read_length("Attempted to read a string of negative length " + std::to_string(h.len));
}
h.stored = static_cast<int>(cf_check_read_size(file, static_cast<size_t>(std::max(h.len, 0))));
return h;
}
// reads count of the stored characters and leaves the file just past the whole string; false if the read failed
bool read_string_len_body(const string_len_header& h, char* dest, int count, CFILE* file)
{
bool ok = (count <= 0) || (cfread(dest, count, 1, file) == 1);
if (!ok || (h.stored > count)) {
cfseek(file, h.start + h.stored, CF_SEEK_SET);
}
return ok;
}
// warned last, so the file position is right even if a debug build stops here
void warn_string_len(const string_len_header& h, bool read_ok, bool cut, int buffer_size, CFILE* file)
{
if (h.length_cut) {
Warning(LOCATION, "cfread_string_len: cut-off string length field in %s", cf_get_filename(file));
} else if (!read_ok) {
Warning(LOCATION,
"cfread_string_len: could not read a string of %d characters in %s",
h.stored,
cf_get_filename(file));
} else if (h.len != h.stored) {
Warning(LOCATION, "cfread_string_len: corrupt string length %d in %s", h.len, cf_get_filename(file));
} else if (cut) {
Warning(LOCATION,
"cfread_string_len: a string of %d bytes was cut to fit a buffer of %d in %s",
h.len,
buffer_size,
cf_get_filename(file));
}
}
} // namespace
void cfread_string_len(char* buf, int buffer_size, CFILE* file)
{
Assertion(buffer_size > 0, "cfread_string_len needs room for at least the terminator, buffer: %i", buffer_size);
string_len_header h = read_string_len_header(file);
if (buffer_size <= 0) {
bool read_ok = read_string_len_body(h, buf, 0, file);
warn_string_len(h, read_ok, h.stored > 0, buffer_size, file);
return;
}
int count = std::min(h.stored, buffer_size);
bool read_ok = read_string_len_body(h, buf, count, file);
int kept = read_ok ? count : 0;
bool cut = (kept == buffer_size) && (memchr(buf, 0, buffer_size) == nullptr);
buf[std::min(kept, buffer_size - 1)] = 0;
warn_string_len(h, read_ok, cut, buffer_size, file);
}
SCP_string cfread_string_len(CFILE *file)
{
int len = cfread_int(file);
string_len_header h = read_string_len_header(file);
SCP_string str;
str.resize(len);
if (len)
cfread(&str[0], len, 1, file);
str.resize(h.stored);
bool read_ok = read_string_len_body(h, str.data(), h.stored, file);
if (!read_ok) {
str.clear();
}
warn_string_len(h, read_ok, false, 0, file);
return str;
}
+8 -2
View File
@@ -320,20 +320,26 @@ void cfread_string(char *buf,int n, CFILE *file);
/**
* @brief Read a fixed length string that is not null-terminated, with the length stored in file.
*
* If it is not null-terminated, the length of the string *must* be less than the size of the buffer (to make room for one)
*
* Keeps at most buffer_size - 1 characters and leaves the file just past the whole string, unless it throws.
*
* @param buf Pre-allocated array to store string
* @param buffer_size Size of pre-allocated array
* @param file File to read from
*
* @note Appends NULL character to string (buf)
* @note Bad data gets a warning; at a clean end of file, or with an invalid file, the string is empty and nothing is
* warned. Inside a limited section (cf_set_max_read_len), a negative length, or a read that still passes the limit
* once clamped to the end of the file, throws cfile::max_read_length.
*/
void cfread_string_len(char *buf,int buffer_size, CFILE *file);
/**
* @brief Read a string from the file where the length is stored in the file
*
* @param file The file to read the string from
* @return The string that was read
*
* @note Warns and throws like the char* overload, except that nothing is cut.
*/
SCP_string cfread_string_len(CFILE *file);
+24 -14
View File
@@ -193,21 +193,8 @@ int cfseek( CFILE *cfile, int offset, int where )
}
// cfread() reads from a file
//
// returns: returns the number of full elements read
//
//
int cfread(void *buf, int elsize, int nelem, CFILE *cfile)
size_t cf_check_read_size(CFILE* cfile, size_t size)
{
if(!cf_is_valid(cfile))
return 0;
size_t size = elsize*nelem;
if(buf == NULL || size <= 0)
return 0;
if ( (cfile->raw_position+size) > cfile->size ) {
Assertion(cfile->raw_position <= cfile->size, "Invalid raw_position value detected!");
size = cfile->size - cfile->raw_position;
@@ -226,6 +213,29 @@ int cfread(void *buf, int elsize, int nelem, CFILE *cfile)
}
}
return size;
}
// cfread() reads from a file
//
// returns: returns the number of full elements read
//
//
int cfread(void *buf, int elsize, int nelem, CFILE *cfile)
{
if(!cf_is_valid(cfile))
return 0;
size_t size = elsize*nelem;
if(buf == nullptr || size <= 0)
return 0;
size = cf_check_read_size(cfile, size);
if (size < 1) {
return 0;
}
size_t bytes_read;
if (cfile->data != nullptr) {
// This is a file from memory
+5
View File
@@ -62,4 +62,9 @@ void cf_check_compression(CFILE* cfile);
// Used to clear compression info data and free dynamic memory used by compression
void cf_clear_compression_info(CFILE* cfile);
// Clamps a read of size bytes to the end of the file and returns the clamped size, or 0 at the end of the file
// without checking the limit; throws cfile::max_read_length if the clamped read would pass the file's read limit
// (cf_set_max_read_len).
size_t cf_check_read_size(CFILE* cfile, size_t size);
#endif
+9 -7
View File
@@ -532,7 +532,7 @@ void cf_build_pack_list( cf_root *root )
s_root.roottype = CF_ROOTTYPE_PACK;
s_root.cf_type = i;
temp_roots_sort.push_back(s_root);
temp_roots_sort.push_back(std::move(s_root));
}
}
@@ -1083,7 +1083,7 @@ void cf_search_root_pack(int root_index)
file.pathtype = path_type;
file.offset = find.offset;
files.push_back(file);
files.push_back(std::move(file));
//mprintf(( "Found pack file '%s'\n", find.filename ));
}
@@ -1356,7 +1356,7 @@ CFileLocation cf_find_file_location(const char* filespec, int pathtype, uint32_t
fclose(fp);
res.offset = 0;
res.full_name = longname;
res.full_name = std::move(longname);
res.name_ext = filespec;
return res;
@@ -1549,8 +1549,8 @@ CFileLocationExt cf_find_file_location_ext(const char *filename, const int ext_n
fclose(fp);
res.offset = 0;
res.full_name = longname;
res.name_ext = filespec_ext;
res.full_name = std::move(longname);
res.name_ext = std::move(filespec_ext);
return res;
}
@@ -2437,7 +2437,9 @@ int cf_create_default_path_string(SCP_string& path, int pathtype, const char* fi
}
if (!root) {
Assert( filename != NULL );
if (filename == nullptr) {
return 0;
}
path.assign(filename);
return 1;
}
@@ -2480,7 +2482,7 @@ void cfile_spew_pack_file_crcs()
my_time = time(NULL);
memset( datetime, 0, sizeof(datetime) );
snprintf(datetime, sizeof(datetime)-1, "%s", ctime(&my_time));
snprintf(datetime, sizeof(datetime), "%s", ctime(&my_time));
// ctime() adds a newline char, so we have to strip it off
datetime[strlen(datetime)-1] = '\0';
+2 -2
View File
@@ -135,7 +135,7 @@ void parse_cheat_table(const char* filename) {
}
if (shipSpawn) {
std::unique_ptr<CustomCheat> shipCheat(new SpawnShipCheat(code, msg, requireCheats, shipClass, shipName));
std::unique_ptr<CustomCheat> shipCheat(new SpawnShipCheat(code, std::move(msg), requireCheats, std::move(shipClass), std::move(shipName)));
if(customCheats.count(code) == 1) {
Warning(LOCATION, "A cheat for code '%s' already exists. It will be replaced.", code.c_str());
@@ -144,7 +144,7 @@ void parse_cheat_table(const char* filename) {
customCheats.emplace(code, std::move(shipCheat));
}
} else {
std::unique_ptr<CustomCheat> cheat(new CustomCheat(code, msg, requireCheats));
std::unique_ptr<CustomCheat> cheat(new CustomCheat(code, std::move(msg), requireCheats));
if(customCheats.count(code) == 1) {
Warning(LOCATION, "A cheat for code '%s' already exists. It will be replaced.", code.c_str());
+1 -1
View File
@@ -35,7 +35,7 @@ class SpawnShipCheat : public CustomCheat {
SCP_string shipName;
public:
SpawnShipCheat(SCP_string cheat_code, SCP_string cheat_msg, bool require_cheats_enabled, SCP_string class_name, SCP_string ship_name) : CustomCheat(cheat_code, cheat_msg, require_cheats_enabled),
SpawnShipCheat(SCP_string cheat_code, SCP_string cheat_msg, bool require_cheats_enabled, SCP_string class_name, SCP_string ship_name) : CustomCheat(std::move(cheat_code), std::move(cheat_msg), require_cheats_enabled),
shipClassName(std::move(class_name)),
shipName(std::move(ship_name)) { }
+224 -73
View File
@@ -15,6 +15,7 @@
#include "globalincs/pstypes.h"
#include "globalincs/systemvars.h"
#include "globalincs/version.h"
#include "graphics/2d.h"
#include "graphics/openxr.h"
#include "graphics/shadows.h"
#include "hud/hudconfig.h"
@@ -74,6 +75,7 @@ const char *cmdline_arg_types[] =
enum class flag_output_type {
Binary,
Json_V1,
Json_V2,
};
// variables
@@ -356,6 +358,7 @@ cmdline_parm fb_explosions_arg("-fb_explosions", NULL, AT_NONE);
cmdline_parm fb_thrusters_arg("-fb_thrusters", NULL, AT_NONE);
cmdline_parm shadow_quality_arg("-shadow_quality", NULL, AT_INT);
cmdline_parm enable_shadows_arg("-enable_shadows", NULL, AT_NONE);
cmdline_parm rt_shadows_arg("-rt_shadows", nullptr, AT_NONE);
cmdline_parm no_deferred_lighting_arg("-no_deferred", NULL, AT_NONE); // Cmdline_no_deferred
cmdline_parm deferred_lighting_cockpit_arg("-deferred_cockpit", nullptr, AT_NONE);
cmdline_parm anisotropy_level_arg("-anisotropic_filter", NULL, AT_INT);
@@ -513,6 +516,7 @@ cmdline_parm deprecated_res_arg("-res", "Deprecated, resolution, formatted like
cmdline_parm render_res_arg("-render_res", "Resolution, formatted like 1600x900", AT_STRING);
cmdline_parm window_res_arg("-window_res", "Window resolution, formatted like 1600x900.", AT_STRING);
cmdline_parm center_res_arg("-center_res", "Resolution of center monitor, formatted like 1600x900", AT_STRING);
cmdline_parm hdr_nits_arg("-hdr", "Forces HDR output on, overriding the in-game HDR setting. Optionally takes paper white and peak luminance in nits, formatted like 400,1500, overriding the in-game HDR nits settings.", AT_STRING);
cmdline_parm verify_vps_arg("-verify_vps", NULL, AT_NONE); // Cmdline_verify_vps -- spew VP crcs to vp_crcs.txt
cmdline_parm parse_cmdline_only(PARSE_COMMAND_LINE_STRING, "Ignore any cmdline_fso.cfg files", AT_NONE);
cmdline_parm reparse_mainhall_arg("-reparse_mainhall", NULL, AT_NONE); //Cmdline_reparse_mainhall
@@ -527,6 +531,7 @@ cmdline_parm show_video_info("-show_video_info", NULL, AT_NONE); //Cmdline_show_
cmdline_parm frame_profile_arg("-profile_frame_time", NULL, AT_NONE); //Cmdline_frame_profile
cmdline_parm debug_window_arg("-debug_window", NULL, AT_NONE); // Cmdline_debug_window
cmdline_parm graphics_debug_output_arg("-gr_debug", nullptr, AT_NONE); // Cmdline_graphics_debug_output
cmdline_parm gr_sync_validation_arg("-gr_sync_validation", nullptr, AT_NONE); // Cmdline_gr_sync_validation (implies -gr_debug)
cmdline_parm log_to_stdout_arg("-stdout_log", nullptr, AT_NONE); // Cmdline_log_to_stdout
cmdline_parm slow_frames_ok_arg("-slow_frames_ok", nullptr, AT_NONE); // Cmdline_slow_frames_ok
cmdline_parm fixed_seed_rand("-seed", nullptr, AT_INT); // Cmdline_rng_seed,Cmdline_reuse_rng_seed;
@@ -534,6 +539,7 @@ cmdline_parm luadev_arg("-luadev", "Make lua errors non-fatal", AT_NONE); // Cmd
cmdline_parm override_arg("-override_data", "Enable override directory", AT_NONE); // Cmdline_override_data
cmdline_parm imgui_debug_arg("-imgui_debug", nullptr, AT_NONE);
cmdline_parm vulkan("-vulkan", nullptr, AT_NONE);
cmdline_parm opengl("-opengl", nullptr, AT_NONE);
cmdline_parm multithreading("-threads", nullptr, AT_INT);
char *Cmdline_start_mission = NULL;
@@ -550,7 +556,12 @@ int Cmdline_show_pos = 0;
int Cmdline_show_stats = 0;
int Cmdline_save_render_targets = 0;
bool Cmdline_window = false;
#ifdef __ANDROID__
// Default to borderless in the legacy window-mode path as well.
bool Cmdline_fullscreen_window = true;
#else
bool Cmdline_fullscreen_window = false;
#endif
std::optional<std::pair<uint16_t, uint16_t>>Cmdline_window_res = std::nullopt;
char *Cmdline_res = 0;
char *Cmdline_center_res = 0;
@@ -567,12 +578,13 @@ bool Cmdline_frame_profile = false;
bool Cmdline_show_video_info = false;
bool Cmdline_debug_window = false;
bool Cmdline_graphics_debug_output = false;
bool Cmdline_gr_sync_validation = false;
bool Cmdline_log_to_stdout = false;
bool Cmdline_slow_frames_ok = false;
bool Cmdline_lua_devmode = false;
bool Cmdline_override_data = false;
bool Cmdline_show_imgui_debug = false;
bool Cmdline_vulkan = false;
GraphicsAPI Cmdline_graphics_api = GraphicsAPI::Default;
int Cmdline_multithreading = 1;
// Other
@@ -1322,7 +1334,7 @@ static void handle_unix_modlist(char **modlist, size_t *len)
for (char *cur_mod = strtok(*modlist, ","); cur_mod != NULL; cur_mod = strtok(NULL, ",")) {
SCP_string path = get_real_mod_path(cur_mod);
mod_paths.push_back(path);
mod_paths.push_back(std::move(path));
}
// create new char[] to replace modlist
@@ -1352,51 +1364,58 @@ static void handle_unix_modlist(char **modlist, size_t *len)
// external entry point into this modules
static void json_add_version(json_t* root) {
auto version_obj = json_object();
json_object_set_new(version_obj, "full", json_string(FS_VERSION_FULL));
json_object_set_new(version_obj, "major", json_integer(FS_VERSION_MAJOR));
json_object_set_new(version_obj, "minor", json_integer(FS_VERSION_MINOR));
json_object_set_new(version_obj, "build", json_integer(FS_VERSION_BUILD));
json_object_set_new(version_obj, "has_revision", json_boolean(FS_VERSION_HAS_REVIS));
json_object_set_new(version_obj, "revision", json_integer(FS_VERSION_REVIS));
json_object_set_new(version_obj, "revision_str", json_string(FS_VERSION_REVIS_STR));
json_object_set_new(root, "version", version_obj);
}
static void json_add_easy_flags(json_t* root) {
auto easy_array = json_array();
for (auto& easy_flag : easy_flags) {
json_array_append_new(easy_array, json_string(easy_flag.name));
}
json_object_set_new(root, "easy_flags", easy_array);
}
static void json_add_flags(json_t* root) {
auto flags_array = json_array();
for (auto& flag : exe_params) {
auto flag_obj = json_object();
json_object_set_new(flag_obj, "name", json_string(flag.name));
json_object_set_new(flag_obj, "description", json_string(flag.desc));
json_object_set_new(flag_obj, "fso_only", json_boolean(flag.fso_only));
json_object_set_new(flag_obj, "on_flags", json_integer(flag.on_flags));
json_object_set_new(flag_obj, "off_flags", json_integer(flag.off_flags));
json_object_set_new(flag_obj, "type", json_string(flag.type));
json_object_set_new(flag_obj, "web_url", json_string(flag.web_url));
json_array_append_new(flags_array, flag_obj);
}
json_object_set_new(root, "flags", flags_array);
}
static json_t* json_get_v1() {
auto root = json_object();
{
auto version_obj = json_object();
json_add_version(root);
json_add_easy_flags(root);
json_add_flags(root);
json_object_set_new(version_obj, "full", json_string(FS_VERSION_FULL));
json_object_set_new(version_obj, "major", json_integer(FS_VERSION_MAJOR));
json_object_set_new(version_obj, "minor", json_integer(FS_VERSION_MINOR));
json_object_set_new(version_obj, "build", json_integer(FS_VERSION_BUILD));
json_object_set_new(version_obj, "has_revision", json_boolean(FS_VERSION_HAS_REVIS));
json_object_set_new(version_obj, "revision", json_integer(FS_VERSION_REVIS));
json_object_set_new(version_obj, "revision_str", json_string(FS_VERSION_REVIS_STR));
json_object_set_new(root, "version", version_obj);
}
{
auto easy_array = json_array();
for (auto& easy_flag : easy_flags) {
json_array_append_new(easy_array, json_string(easy_flag.name));
}
json_object_set_new(root, "easy_flags", easy_array);
}
{
auto flags_array = json_array();
for (auto& flag : exe_params) {
auto flag_obj = json_object();
json_object_set_new(flag_obj, "name", json_string(flag.name));
json_object_set_new(flag_obj, "description", json_string(flag.desc));
json_object_set_new(flag_obj, "fso_only", json_boolean(flag.fso_only));
json_object_set_new(flag_obj, "on_flags", json_integer(flag.on_flags));
json_object_set_new(flag_obj, "off_flags", json_integer(flag.off_flags));
json_object_set_new(flag_obj, "type", json_string(flag.type));
json_object_set_new(flag_obj, "web_url", json_string(flag.web_url));
json_array_append_new(flags_array, flag_obj);
}
json_object_set_new(root, "flags", flags_array);
}
{
auto caps_array = json_array();
@@ -1414,7 +1433,7 @@ static json_t* json_get_v1() {
auto voices = speech_enumerate_voices();
for (auto& voice : voices) {
json_array_append_new(voices_array, json_string(voice.c_str()));
json_array_append_new(voices_array, json_string(voice.second.c_str()));
}
json_object_set_new(root, "voices", voices_array);
@@ -1507,6 +1526,19 @@ static json_t* json_get_v1() {
return root;
}
// json_v2 reports version, supported flags, and the preferences path for launchers,
// without querying graphics, audio, or joystick devices.
static json_t* json_get_v2() {
auto root = json_object();
json_add_version(root);
json_add_easy_flags(root);
json_add_flags(root);
json_object_set_new(root, "pref_path", json_string(os_get_config_path().c_str()));
return root;
}
static void write_flags_file() {
FILE *fp = fopen("flags.lch","w");
@@ -1564,6 +1596,8 @@ static flag_output_type get_flags_output_type() {
return flag_output_type::Binary;
} else if (type == "json_v1") {
return flag_output_type::Json_V1;
} else if (type == "json_v2") {
return flag_output_type::Json_V2;
} else {
// This is supposed to make it easy for the launcher to recognize an unsupported type
printf("OUTPUT TYPE NOT SUPPORTED!\n");
@@ -1574,17 +1608,23 @@ static flag_output_type get_flags_output_type() {
static void write_flags() {
auto type = get_flags_output_type();
switch(type) {
case flag_output_type::Binary:
write_flags_file();
break;
case flag_output_type::Json_V1:
json_t* root = json_get_v1();
json_dumpf(root, stdout, JSON_INDENT(4));
json_decref(root);
break;
if (type == flag_output_type::Binary) {
write_flags_file();
return;
}
json_t* root = (type == flag_output_type::Json_V1) ? json_get_v1() : json_get_v2();
#ifdef __ANDROID__
char* dumped = json_dumps(root, JSON_INDENT(4));
os_android_set_flags_string(dumped ? dumped : "");
free(dumped);
#else
json_dumpf(root, stdout, JSON_INDENT(4));
#endif
json_decref(root);
}
bool SetCmdlineParams()
@@ -1775,38 +1815,45 @@ bool SetCmdlineParams()
}
// d3d windowed
// We need to set both the raw global (for the legacy !Using_in_game_options path) and the
// Option<T> override (for the modern path) since either backend may be the one actually consulted.
if(window_arg.found()) {
// We need to set both values since we don't know if we are going to use the new config system
//options::OptionsManager::instance()->setOverride("Graphics.WindowMode", "0");
options::OptionsManager::instance()->setOverride("Graphics.WindowMode",
std::unique_ptr<json_t>(json_integer(static_cast<int>(os::ViewportState::Windowed))), "-window");
Cmdline_window = true;
Cmdline_fullscreen_window = false;
}
if ( fullscreen_window_arg.found( ) )
{
//options::OptionsManager::instance()->setOverride("Graphics.WindowMode", "1");
options::OptionsManager::instance()->setOverride("Graphics.WindowMode",
std::unique_ptr<json_t>(json_integer(static_cast<int>(os::ViewportState::Borderless))),
"-fullscreen_window");
Cmdline_fullscreen_window = true;
Cmdline_window = false; /* Make sure no-one sets both */
}
if(render_res_arg.found() || deprecated_res_arg.found()){
const char* res_flag_name;
if (render_res_arg.found()) {
Cmdline_res = render_res_arg.str();
res_flag_name = "-render_res";
}
else {
Cmdline_res = deprecated_res_arg.str();
res_flag_name = "-res";
mprintf(("Deprecated -res argument. Use -render_res instead...\n"));
}
/*int width = 0;
int width = 0;
int height = 0;
if (sscanf(Cmdline_res, "%dx%d", &width, &height) == 2) {
SCP_string override;
sprintf(override, "{\"width\":%d,\"height\":%d}", width, height);
options::OptionsManager::instance()->setOverride("Graphics.Resolution", override);
options::OptionsManager::instance()->setOverride("Graphics.Resolution",
std::unique_ptr<json_t>(json_pack("{s:i, s:i}", "width", width, "height", height)), res_flag_name);
} else {
Warning(LOCATION, "Failed to parse -res parameter \"%s\". Must be in format \"<width>x<height>\".\n", Cmdline_res);
}*/
}
}
if(window_res_arg.found()){
int width = 0;
@@ -1822,6 +1869,24 @@ bool SetCmdlineParams()
if(center_res_arg.found()){
Cmdline_center_res = center_res_arg.str();
}
if (hdr_nits_arg.found()) {
// presence of the flag alone forces HDR output on, overriding the in-game HDR setting
options::OptionsManager::instance()->setOverride("Graphics.HDR",
std::unique_ptr<json_t>(json_true()), "-hdr");
if (hdr_nits_arg.has_param()) {
float paperwhite_nits = 0.0f, peak_nits = 0.0f;
if (sscanf(hdr_nits_arg.str(), "%f,%f", &paperwhite_nits, &peak_nits) == 2 && paperwhite_nits > 0.0f && peak_nits > 0.0f) {
// json_real keeps these JSON reals; json_unpack's "f" format rejects a bare integer like "400"
options::OptionsManager::instance()->setOverride("Graphics.HDRPaperWhite",
std::unique_ptr<json_t>(json_real(paperwhite_nits)), "-hdr");
options::OptionsManager::instance()->setOverride("Graphics.HDRPeakLuminance",
std::unique_ptr<json_t>(json_real(peak_nits)), "-hdr");
} else {
Warning(LOCATION, "Failed to parse -hdr parameter \"%s\". Must be in format \"<paperwhite_nits>,<peak_nits>\".\n", hdr_nits_arg.str());
}
}
}
if(almission_arg.found()){//DTP for autoload mission // developer oritentated
Cmdline_almission = almission_arg.str();
Cmdline_use_last_pilot = 1;
@@ -1914,17 +1979,20 @@ bool SetCmdlineParams()
if ( fov_arg.found() ) {
auto val = fov_arg.get_float();
if (val > 0.1) {
VIEWER_ZOOM_DEFAULT = val;
} else {
VIEWER_ZOOM_DEFAULT = DEFAULT_FOV;
if (val <= 0.1f) {
val = DEFAULT_FOV;
}
VIEWER_ZOOM_DEFAULT = val;
options::OptionsManager::instance()->setOverride("Graphics.FOV",
std::unique_ptr<json_t>(json_real(val)), "-fov");
}
if ( fov_cockpit_arg.found() ) {
auto val = fov_cockpit_arg.get_float();
if (val > 0.1) {
if (val > 0.1f) {
COCKPIT_ZOOM_DEFAULT = val;
options::OptionsManager::instance()->setOverride("Graphics.CockpitFOV",
std::unique_ptr<json_t>(json_real(val)), "-fov_cockpit");
}
else {
COCKPIT_ZOOM_DEFAULT = VIEWER_ZOOM_DEFAULT;
@@ -1947,13 +2015,18 @@ bool SetCmdlineParams()
Cmdline_fullscreen_window = false;
Cmdline_window = true; /* Make sure no-one sets both */
removeWindowModeOption();
// These replace the removeWindowModeOption()/removeVSyncOption() calls that used to hide these two
// options in VR. An override keeps them visible but disabled in the options menu, with the tooltip
// naming -vr as the reason, which tells the player more than the control simply not being there.
options::OptionsManager::instance()->setOverride("Graphics.WindowMode",
std::unique_ptr<json_t>(json_integer(static_cast<int>(os::ViewportState::Windowed))), "-vr");
// Turn off VSync, since we definitely don't want to be capped by the main monitor
// the OpenXR runtime should have a buffered wait which will behave, effectively,
// as a VSync to VR goggle driver
Gr_enable_vsync = false;
removeVSyncOption();
options::OptionsManager::instance()->setOverride("Graphics.VSync",
std::unique_ptr<json_t>(json_false()), "-vr");
openxr_prepare();
}
@@ -2008,6 +2081,8 @@ bool SetCmdlineParams()
if(no_vsync_arg.found() )
{
Gr_enable_vsync = false;
options::OptionsManager::instance()->setOverride("Graphics.VSync",
std::unique_ptr<json_t>(json_false()), "-no_vsync");
}
if ( normal_arg.found() ) {
@@ -2041,11 +2116,15 @@ bool SetCmdlineParams()
case 5:
Gr_aa_mode = AntiAliasMode::SMAA_High;
break;
case 6:
case 6:
Gr_aa_mode = AntiAliasMode::SMAA_Ultra;
break;
}
}
options::OptionsManager::instance()->setOverride("Graphics.AAMode",
std::unique_ptr<json_t>(json_integer(static_cast<int>(Gr_aa_mode))),
post_process_aa_preset_arg.found() ? "-aa_preset" : "-aa");
}
if (msaa_enabled_arg.found()) {
@@ -2061,6 +2140,9 @@ bool SetCmdlineParams()
Cmdline_msaa_enabled = 0;
break;
}
options::OptionsManager::instance()->setOverride("Graphics.MSAASamples",
std::unique_ptr<json_t>(json_integer(Cmdline_msaa_enabled)), "-msaa");
}
if ( glow_arg.found() )
@@ -2186,6 +2268,8 @@ bool SetCmdlineParams()
if ( softparticles_arg.found() )
{
Cmdline_softparticles = 1;
options::OptionsManager::instance()->setOverride("Graphics.SoftParticles",
std::unique_ptr<json_t>(json_true()), "-soft_particles");
}
if ( fb_explosions_arg.found() )
@@ -2193,14 +2277,31 @@ bool SetCmdlineParams()
Gr_framebuffer_effects.set(FramebufferEffects::Shockwaves, true);
}
if (fb_thrusters_arg.found())
if (fb_thrusters_arg.found())
{
Gr_framebuffer_effects.set(FramebufferEffects::Thrusters, true);
}
if (fb_explosions_arg.found() || fb_thrusters_arg.found()) {
SCP_string reason;
if (fb_explosions_arg.found()) {
reason = "-fb_explosions";
}
if (fb_thrusters_arg.found()) {
if (!reason.empty()) {
reason += " ";
}
reason += "-fb_thrusters";
}
options::OptionsManager::instance()->setOverride("Graphics.FramebufferEffects",
std::unique_ptr<json_t>(json_integer(static_cast<json_int_t>(Gr_framebuffer_effects.to_u64()))), reason);
}
if ( no_postprocess_arg.found() )
{
Gr_post_processing_enabled = false;
options::OptionsManager::instance()->setOverride("Graphics.PostProcessing",
std::unique_ptr<json_t>(json_false()), "-no_post_process");
}
if( reparse_mainhall_arg.found() )
@@ -2214,12 +2315,19 @@ bool SetCmdlineParams()
Shadow_quality_uses_mod_option = true;
Shadow_quality = ShadowQuality::Medium;
// Deliberately not calling setOverride() here: mod_table_init() (which runs after cmdline parsing but
// before OptionsManager::loadInitialValues()) may still adjust Shadow_quality via game_settings.tbl's
// "$Shadow Quality Default:" when Shadow_quality_uses_mod_option is set, and an override registered now
// would freeze in the Medium fallback and ignore that later adjustment. Graphics.Shadows' default_func
// reads Shadow_quality instead, so this value reaches the in-game options path that way, and the
// player keeps the ability to change it.
}
if( shadow_quality_arg.found() )
{
// set that we are not using default shadow quality level --wookieejedi
Shadow_quality_uses_mod_option = false;
Shadow_quality_uses_mod_option = false;
switch (shadow_quality_arg.get_int()) {
case 0:
@@ -2242,21 +2350,47 @@ bool SetCmdlineParams()
Shadow_quality = ShadowQuality::Disabled;
break;
}
// Unlike `-enable_shadows` alone, an explicit quality level is an unambiguous hard override that
// nothing else (mod defaults included) should be able to silently override.
options::OptionsManager::instance()->setOverride("Graphics.Shadows",
std::unique_ptr<json_t>(json_integer(static_cast<int>(Shadow_quality))), "-shadow_quality");
}
if( rt_shadows_arg.found() )
{
// Equivalent to picking "Raytraced" in the Shadow Method option. Silently
// has no effect if the renderer/hardware doesn't support
// CAPABILITY_RAYTRACED_SHADOWS -- the CSM path is used either way.
Shadow_render_method = ShadowRenderMethod::Raytraced;
// No need to gate this on shadows_raytracing_supported(): that answer is only meaningful after
// gr_init(), which runs later, and shadows_remove_unsupported_options() then removes this option
// altogether on hardware that cannot raytrace, so the override never reaches the options menu there.
options::OptionsManager::instance()->setOverride("Graphics.ShadowRenderMethod",
std::unique_ptr<json_t>(json_integer(static_cast<int>(ShadowRenderMethod::Raytraced))), "-rt_shadows");
}
if( no_deferred_lighting_arg.found() )
{
Cmdline_no_deferred_lighting = 1;
options::OptionsManager::instance()->setOverride("Graphics.DeferredLighting",
std::unique_ptr<json_t>(json_false()), "-no_deferred");
}
if( deferred_lighting_cockpit_arg.found() )
{
Cmdline_deferred_lighting_cockpit = true;
options::OptionsManager::instance()->setOverride("Graphics.DeferredCockpitLighting",
std::unique_ptr<json_t>(json_true()), "-deferred_cockpit");
}
if (anisotropy_level_arg.found())
if (anisotropy_level_arg.found())
{
Cmdline_aniso_level = anisotropy_level_arg.get_int();
options::OptionsManager::instance()->setOverride("Graphics.Anisotropy",
std::unique_ptr<json_t>(json_real(static_cast<double>(Cmdline_aniso_level))), "-anisotropic_filter");
}
if (frame_profile_write_file.found())
@@ -2307,6 +2441,16 @@ bool SetCmdlineParams()
Cmdline_graphics_debug_output = true;
}
if (gr_sync_validation_arg.found()) {
// Enables the Vulkan validation layer + debug messenger + synchronization
// validation feature (see VulkanRenderer::initializeInstance), but NOT the
// engine-level graphics debug output: -gr_debug additionally draws debug
// overlays (e.g. output_uniform_debug_data), which are unrelated to GPU
// sync validation and would otherwise appear as spurious on-screen
// artifacts. Keep this a pure GPU-validation switch.
Cmdline_gr_sync_validation = true;
}
if (log_to_stdout_arg.found()) {
Cmdline_log_to_stdout = true;
}
@@ -2332,7 +2476,14 @@ bool SetCmdlineParams()
}
if (vulkan.found()) {
Cmdline_vulkan = true;
Cmdline_graphics_api = GraphicsAPI::Vulkan;
options::OptionsManager::instance()->setOverride("Graphics.RenderAPI",
std::unique_ptr<json_t>(json_integer(static_cast<int>(GraphicsAPI::Vulkan))), "-vulkan");
}
else if (opengl.found()) {
Cmdline_graphics_api = GraphicsAPI::OpenGL;
options::OptionsManager::instance()->setOverride("Graphics.RenderAPI",
std::unique_ptr<json_t>(json_integer(static_cast<int>(GraphicsAPI::OpenGL))), "-opengl");
}
//Deprecated flags - CommanderDJ
+3 -3
View File
@@ -13,6 +13,7 @@
#define FS_CMDLINE_HEADER_FILE
#include <optional>
#include "graphics/2d.h"
int parse_cmdline(int argc, char *argv[]);
@@ -153,19 +154,18 @@ extern bool Cmdline_frame_profile;
extern bool Cmdline_show_video_info;
extern bool Cmdline_debug_window;
extern bool Cmdline_graphics_debug_output;
extern bool Cmdline_gr_sync_validation;
extern bool Cmdline_log_to_stdout;
extern bool Cmdline_slow_frames_ok;
extern bool Cmdline_lua_devmode;
extern bool Cmdline_override_data;
extern bool Cmdline_show_imgui_debug;
extern bool Cmdline_vulkan;
extern GraphicsAPI Cmdline_graphics_api;
extern int Cmdline_multithreading;
enum class WeaponSpewType { NONE = 0, STANDARD, ALL };
extern WeaponSpewType Cmdline_spew_weapon_stats;
extern void removeVSyncOption();
extern void removeWindowModeOption();
extern void removeResolutionOption();
extern void removeResolutionVROption();
+26 -12
View File
@@ -25,18 +25,33 @@ const float CMEASURE_DETONATE_DISTANCE = 40.0f;
//Used to set a countermeasure velocity after being launched from a ship as a countermeasure
//ie not as a primary or secondary.
void cmeasure_set_ship_launch_vel(object *objp, object *parent_objp, int arand)
void cmeasure_set_ship_launch_vel(object* objp, object* parent_objp, int arand)
{
vec3d vel, rand_vec;
vec3d vel;
vec3d rand_vec;
vec3d world_dir;
//Get cmeasure rear velocity in world
vm_vec_scale_add(&vel, &parent_objp->phys_info.vel, &parent_objp->orient.vec.fvec, -25.0f);
weapon* wp = &Weapons[objp->instance];
auto* wip = &Weapon_info[wp->weapon_info_index];
//Get random velocity vector
static_randvec(arand+1, &rand_vec);
// Start with parent velocity
vel = parent_objp->phys_info.vel;
//Add it to the rear velocity
vm_vec_scale_add2(&vel, &rand_vec, 2.0f);
// Normalize direction
vec3d local_dir = wip->cm_launch_vec;
if (vm_vec_mag_squared(&local_dir) > 0.0f) {
vm_vec_normalize(&local_dir);
}
// Convert from ship to world space
vm_vec_unrotate(&world_dir, &local_dir, &parent_objp->orient);
// Apply launch impulse
vm_vec_scale_add2(&vel, &world_dir, wip->cm_launch_speed);
// Add random variance
static_randvec(arand + 1, &rand_vec);
vm_vec_scale_add2(&vel, &rand_vec, wip->cm_launch_variance);
objp->phys_info.vel = vel;
@@ -44,17 +59,16 @@ void cmeasure_set_ship_launch_vel(object *objp, object *parent_objp, int arand)
vm_vec_zero(&objp->phys_info.rotvel);
vm_vec_zero(&objp->phys_info.max_vel);
vm_vec_zero(&objp->phys_info.max_rotvel);
// blow out his reverse thrusters. Or drag, same thing.
objp->phys_info.rotdamp = 10000.0f;
objp->phys_info.side_slip_time_const = 10000.0f;
objp->phys_info.max_vel.xyz.z = -25.0f;
vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, objp->phys_info.max_vel.xyz.z );
// Desired velocity should align with launch direction, not hardcoded backward
vm_vec_copy_scale(&objp->phys_info.desired_vel, &world_dir, wip->cm_launch_speed);
// if this cmeasure has a single segment trail, let the trail know since we just changed the velocity
// yeah this is hacky but that's what this function gets for CHANGING the velocity on objects with a ""CONSTANT VELOCITY""
weapon* wp = &Weapons[objp->instance];
if (wp->trail_ptr && wp->trail_ptr->single_segment) {
wp->trail_ptr->vel[0] = objp->phys_info.vel;
wp->trail_ptr->vel[1] = objp->phys_info.vel;
+33 -25
View File
@@ -10,6 +10,7 @@
#include "camera/photomode.h"
#include "cfile/cfile.h"
#include "cmdline/cmdline.h"
#include "controlconfig/controlsconfig.h"
@@ -620,6 +621,11 @@ void control_config_conflict_check()
continue;
}
// Skip conflict check between items in different conflict groups
if ((item_i.conflict_groups & item_j.conflict_groups) == 0) {
continue;
}
if (item_i.disabled && (item_i.has_first_conflict(item_j) || item_i.has_second_conflict(item_j))) {
// item_i conflicts with item_j and is disabled. Silently clear item_i
item_i.clear();
@@ -733,7 +739,7 @@ void control_config_bind(int i, const CC_bind &new_bind, selItem order, bool API
Undo_stack stack;
stack.save(Control_config[i].first);
stack.save(Control_config[i].second);
Undo_controls.save_stack(stack);
Undo_controls.save_stack(std::move(stack));
CCB old(Control_config[i]);
@@ -764,7 +770,7 @@ bool control_config_remove_binding(int ctrl, selItem item, bool API_Access)
stack.save(Control_config[ctrl].first);
stack.save(Control_config[ctrl].second);
Undo_controls.save_stack(stack);
Undo_controls.save_stack(std::move(stack));
Control_config[ctrl].first.clear();
Control_config[ctrl].second.clear();
@@ -798,7 +804,7 @@ bool control_config_remove_binding(int ctrl, selItem item, bool API_Access)
default:
// Coder forgot to add a case!
UNREACHABLE("Unhandled selItem case.");
UNREACHABLE("Unhandled selItem case: %i", static_cast<int>(item));
}
if (success)
@@ -867,7 +873,7 @@ bool control_config_clear_other(int ctrl, bool API_Access)
return false;
}
Undo_controls.save_stack(stack);
Undo_controls.save_stack(std::move(stack));
control_config_conflict_check();
if (!API_Access) {
@@ -905,7 +911,7 @@ bool control_config_clear_all(bool API_Access)
return false;
}
Undo_controls.save_stack(stack);
Undo_controls.save_stack(std::move(stack));
control_config_conflict_check();
if (!API_Access) {
@@ -969,7 +975,7 @@ bool control_config_do_reset(bool cycle, bool API_Access)
stack.save(item.first);
stack.save(item.second);
}
Undo_controls.save_stack(stack);
Undo_controls.save_stack(std::move(stack));
control_config_use_preset(Control_config_presets[Defaults_cycle_pos]);
@@ -1091,7 +1097,7 @@ bool control_config_toggle_modifier(int bit, int ctrl, bool API_Access)
Undo_stack stack;
stack.save(Control_config[ctrl].first);
stack.save(Control_config[ctrl].second);
Undo_controls.save_stack(stack);
Undo_controls.save_stack(std::move(stack));
Control_config[ctrl].take(CC_bind(CID_KEYBOARD, static_cast<short>(k ^ bit)), -1);
control_config_conflict_check();
@@ -1316,7 +1322,7 @@ bool control_config_accept(bool API_Access)
// Check if a hardcoded preset with name already exists. If so, complain to user and force retry
auto it = std::find_if(Control_config_presets.begin(), Control_config_presets.end(), [str](CC_preset& p) {
return ((p.name == str) && ((p.type == Preset_t::tbl) || (p.type == Preset_t::hardcode)));
return ((p.name == str) && ((p.get_type() == Preset_t::tbl) || (p.get_type() == Preset_t::hardcode)));
});
if (it != Control_config_presets.end()) {
@@ -1347,15 +1353,14 @@ bool control_config_accept(bool API_Access)
}
// Pack the current bindings into a preset, then save the file
CC_preset preset;
preset.name = std::move(str);
CC_preset preset(std::move(str), Preset_t::pst);
std::copy(Control_config.begin(), Control_config.end(), std::back_inserter(preset.bindings));
Control_config_presets.push_back(preset);
save_preset_file(preset, true);
// Reload the presets from file. Do this instead of just pushing the preset to the vector direct to get
// consistant ordering
Control_config_presets.resize(1);
Control_config_presets.erase(Control_config_presets.begin() + 1, Control_config_presets.end());
load_preset_files();
// finally, save the new preset so that changes will get saved to this preset
@@ -1772,7 +1777,7 @@ bool control_config_create_new_preset(const SCP_string& newName, bool overwrite)
// Check if a hardcoded preset with name already exists. If so, complain to user and force retry
auto it = std::find_if(Control_config_presets.begin(), Control_config_presets.end(), [newName](CC_preset& p) {
return (p.name == newName) && ((p.type == Preset_t::tbl) || (p.type == Preset_t::hardcode));
return (p.name == newName) && ((p.get_type() == Preset_t::tbl) || (p.get_type() == Preset_t::hardcode));
});
if (it != Control_config_presets.end()) {
@@ -1785,9 +1790,7 @@ bool control_config_create_new_preset(const SCP_string& newName, bool overwrite)
}
// Pack the current bindings into a preset, then save the file
CC_preset preset;
preset.name = newName;
preset.type = Preset_t::pst;
CC_preset preset(newName, Preset_t::pst);
std::copy(Control_config.begin(), Control_config.end(), std::back_inserter(preset.bindings));
// Done with the file
@@ -1799,7 +1802,7 @@ bool control_config_create_new_preset(const SCP_string& newName, bool overwrite)
save_preset_file(preset, true);
// Reload the presets from file.
Control_config_presets.resize(1);
Control_config_presets.erase(Control_config_presets.begin() + 1, Control_config_presets.end());
load_preset_files(newName);
// use the newly created preset
@@ -1807,10 +1810,9 @@ bool control_config_create_new_preset(const SCP_string& newName, bool overwrite)
return true;
} else if ((clone->name != preset.name) || (clone->type != Preset_t::pst)) {
} else if ((clone->name != preset.name) || (clone->get_type() != Preset_t::pst)) {
// Complain and ignore if the preset names or the type differs
return false;
}
return false; //should be unreachable, but just in case
@@ -1820,7 +1822,7 @@ bool control_config_clone_preset(const CC_preset& preset, const SCP_string& newN
// Check if a hardcoded preset with name already exists. If so, complain to user and force retry
auto it = std::find_if(Control_config_presets.begin(), Control_config_presets.end(), [newName](CC_preset& p) {
return (p.name == newName) && ((p.type == Preset_t::tbl) || (p.type == Preset_t::hardcode));
return (p.name == newName) && ((p.get_type() == Preset_t::tbl) || (p.get_type() == Preset_t::hardcode));
});
if (it != Control_config_presets.end()) {
@@ -1832,13 +1834,13 @@ bool control_config_clone_preset(const CC_preset& preset, const SCP_string& newN
return false;
}
CC_preset newPreset = preset;
newPreset.name = newName;
CC_preset newPreset(newName, Preset_t::pst);
newPreset.bindings = preset.bindings;
bool success = save_preset_file(newPreset, false);
// Reload the presets from file.
Control_config_presets.resize(1);
Control_config_presets.erase(Control_config_presets.begin() + 1, Control_config_presets.end());
load_preset_files(newName);
// use the newly cloned preset
@@ -2718,8 +2720,7 @@ void control_config_do_frame(float frametime)
strcpy_s(buf, Control_config[i].text.c_str());
}
font::force_fit_string(buf, 255, Conflict_wnd_coords[gr_screen.res][CONTROL_W_COORD]);
gr_get_string_size(&w, NULL, buf);
w = font::force_fit_string(buf, 255, Conflict_wnd_coords[gr_screen.res][CONTROL_W_COORD]);
gr_printf_menu(x - w / 2, y, "%s", buf);
} else if (*bound_string) {
@@ -2837,6 +2838,13 @@ int check_control_used(int id, int key)
if (item.disabled || item.locked)
return 0;
// Filter actions based on conflict groups and current mode
{
int active_group = game_is_photo_mode_active() ? CONFLICT_GROUP_PHOTO_MODE : CONFLICT_GROUP_DEFAULT;
if (!(item.conflict_groups & active_group))
return 0;
}
short z = item.get_btn(CID_KEYBOARD); // Get the key that's bound to this control
if (item.type == CC_TYPE_CONTINUOUS) {
@@ -3108,7 +3116,7 @@ void control_get_axes_readings(int *axis_v, float frame_time)
case CC_TYPE_AXIS_BTN_POS:
default:
//This should never happen, especially with the above Assertion. This is required as incomplete switches on an enum generate warnings
UNREACHABLE("Unhandled control item type");
UNREACHABLE("Unhandled control item type %d", static_cast<int>(item.type));
break;
}
}
+31 -11
View File
@@ -308,6 +308,13 @@ enum IoActionId : int {
CYCLE_PRIMARY_WEAPON_PATTERN,
TOGGLE_PHOTO_MODE,
PHOTO_MODE_FILTER_PREV,
PHOTO_MODE_FILTER_NEXT,
PHOTO_MODE_FILTER_RESET,
PHOTO_MODE_PARAM_DECREASE,
PHOTO_MODE_PARAM_INCREASE,
/*!
* This must always be below the last defined item
*/
@@ -565,26 +572,38 @@ public:
* A preset, a collection of bindings for use in Control_config with an associated name
*/
class CC_preset {
public:
public:
SCP_vector<CCB> bindings;
SCP_string name;
Preset_t type = Preset_t::hardcode;
public:
CC_preset() = default;
CC_preset(const CC_preset& A) = default;
private:
// No default value: the type is always set explicitly by the constructor
Preset_t type;
CC_preset& operator=(const CC_preset&);
/*!
* Checks if the given preset is a duplicate of this one
*/
bool is_duplicate_of(CC_preset&);
public:
// No default constructor: every preset must be created with an explicit type
CC_preset(SCP_string preset_name, Preset_t preset_type) : name(std::move(preset_name)), type(preset_type) {}
CC_preset(const CC_preset&) = default;
// Preserve target's preset type during copy assignment
CC_preset& operator=(const CC_preset& other);
Preset_t get_type() const
{
return type;
}
bool is_duplicate_of(const CC_preset& other) const;
};
/*!
* Control configuration item type.
* @detail Contains binding info, documentation, behavior, etc. for a single control
*/
// Conflict group bitmasks. Bindings only conflict if their groups overlap (bitwise AND != 0).
static const int CONFLICT_GROUP_DEFAULT = (1 << 0); //!< Standard gameplay bindings
static const int CONFLICT_GROUP_PHOTO_MODE = (1 << 1); //!< Bindings used within photo mode
class CCI : public CCB {
public:
// Inherited from CCB
@@ -593,6 +612,7 @@ public:
// Items Set in menu
char tab; //!< what tab (category) it belongs in
int conflict_groups = CONFLICT_GROUP_DEFAULT; //!< Bitmask of conflict groups. Bindings only conflict with others sharing at least one group.
int indexXSTR; //!< what string index we should use to translate this with an XSTR 0 = None, 1= Use item index + CONTROL_CONFIG_XSTR, 2 <= use CCI::indexXSTR directly
SCP_string text; //!< describes the action in the config screen
@@ -665,7 +685,7 @@ public:
* If the type is CC_TYPE_TRIGGER or CC_TYPE_CONTINOUS, the primary is assumed to be a key combo and secondary is assumed to be a joy button.
* If the type is CC_TYPE_AXIS_ABS, CC_TYPE_AXIS_REL, CC_TYPE_AXIS_BTN_POS, or CC_TYPE_AXIS_BTN_NEG, the primary is a Joy0 axis and the secondary is a Mouse axis
*/
CCI_builder& operator()(IoActionId action_id, short primary, short secondary, char tab, int indexXSTR, const char *text, CC_type type, bool disabled = false);
CCI_builder& operator()(IoActionId action_id, short primary, short secondary, char tab, int indexXSTR, const char *text, CC_type type, bool disabled = false, int conflict_groups = CONFLICT_GROUP_DEFAULT);
private:
CCI_builder(); // Only one builder per Control Config, so a default constructor is useless
+102 -45
View File
@@ -45,6 +45,7 @@ int Failed_key_index;
// Joystick configuration
int Joy_dead_zone_size = 10;
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
auto DeadZoneOption = options::OptionBuilder<int>("Input.JoystickDeadZone",
std::pair<const char*, int>{"Deadzone", 1377},
std::pair<const char*, int>{"The deadzone used for all joysticks", 1744})
@@ -59,6 +60,7 @@ auto DeadZoneOption = options::OptionBuilder<int>("Input.JoystickDeadZone",
int Joy_sensitivity = 9;
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
auto SensitivityOption = options::OptionBuilder<int>("Input.JoystickSensitivity",
std::pair<const char*, int>{"Sensitivity", 1745},
std::pair<const char*, int>{"The sensitivity used for all joysticks", 1746})
@@ -136,12 +138,12 @@ void control_config_common_init_bindings() {
(TARGET_PREV_BOMB, KEY_SHIFTED | KEY_B, -1, TARGET_TAB, 1, "Target Previous Hostile Bomb or Bomber", CC_TYPE_TRIGGER)
// flight controls (Rotation)
(BANK_LEFT, KEY_PAD7, -1, SHIP_TAB, 1, "Bank Left", CC_TYPE_CONTINUOUS)
(BANK_RIGHT, KEY_PAD9, -1, SHIP_TAB, 1, "Bank Right", CC_TYPE_CONTINUOUS)
(PITCH_FORWARD, KEY_PAD8, -1, SHIP_TAB, 1, "Pitch Forward", CC_TYPE_CONTINUOUS)
(PITCH_BACK, KEY_PAD2, -1, SHIP_TAB, 1, "Pitch Backward", CC_TYPE_CONTINUOUS)
(YAW_LEFT, KEY_PAD4, -1, SHIP_TAB, 1, "Yaw Left", CC_TYPE_CONTINUOUS)
(YAW_RIGHT, KEY_PAD6, -1, SHIP_TAB, 1, "Yaw Right", CC_TYPE_CONTINUOUS)
(BANK_LEFT, KEY_PAD7, -1, SHIP_TAB, 1, "Bank Left", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
(BANK_RIGHT, KEY_PAD9, -1, SHIP_TAB, 1, "Bank Right", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
(PITCH_FORWARD, KEY_PAD8, -1, SHIP_TAB, 1, "Pitch Forward", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
(PITCH_BACK, KEY_PAD2, -1, SHIP_TAB, 1, "Pitch Backward", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
(YAW_LEFT, KEY_PAD4, -1, SHIP_TAB, 1, "Yaw Left", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
(YAW_RIGHT, KEY_PAD6, -1, SHIP_TAB, 1, "Yaw Right", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
// flight controls (Throttle)
(ZERO_THROTTLE, KEY_BACKSP, -1, SHIP_TAB, 1, "Set Throttle to Zero", CC_TYPE_TRIGGER)
@@ -152,16 +154,16 @@ void control_config_common_init_bindings() {
(MINUS_5_PERCENT_THROTTLE, KEY_MINUS, -1, SHIP_TAB, 1, "Decrease Throttle 5 Percent", CC_TYPE_TRIGGER)
// flight controls (Thrust)
(FORWARD_THRUST, KEY_A, -1, SHIP_TAB, 1, "Forward Thrust", CC_TYPE_CONTINUOUS)
(REVERSE_THRUST, KEY_Z, -1, SHIP_TAB, 1, "Reverse Thrust", CC_TYPE_CONTINUOUS)
(RIGHT_SLIDE_THRUST, KEY_SHIFTED | KEY_3, -1, SHIP_TAB, 1, "Right Thrust", CC_TYPE_CONTINUOUS)
(LEFT_SLIDE_THRUST, KEY_SHIFTED | KEY_1, -1, SHIP_TAB, 1, "Left Thrust", CC_TYPE_CONTINUOUS)
(UP_SLIDE_THRUST, KEY_SHIFTED | KEY_PADPLUS, -1, SHIP_TAB, 1, "Up Thrust", CC_TYPE_CONTINUOUS)
(DOWN_SLIDE_THRUST, KEY_SHIFTED | KEY_PADENTER, -1, SHIP_TAB, 1, "Down Thrust", CC_TYPE_CONTINUOUS)
(FORWARD_THRUST, KEY_A, -1, SHIP_TAB, 1, "Forward Thrust", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
(REVERSE_THRUST, KEY_Z, -1, SHIP_TAB, 1, "Reverse Thrust", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
(RIGHT_SLIDE_THRUST, KEY_SHIFTED | KEY_3, -1, SHIP_TAB, 1, "Right Thrust", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
(LEFT_SLIDE_THRUST, KEY_SHIFTED | KEY_1, -1, SHIP_TAB, 1, "Left Thrust", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
(UP_SLIDE_THRUST, KEY_SHIFTED | KEY_PADPLUS, -1, SHIP_TAB, 1, "Up Thrust", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
(DOWN_SLIDE_THRUST, KEY_SHIFTED | KEY_PADENTER, -1, SHIP_TAB, 1, "Down Thrust", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
// flight controls (flight modes)
(BANK_WHEN_PRESSED, -1, -1, SHIP_TAB, 1, "Bank When Pressed", CC_TYPE_CONTINUOUS)
(AFTERBURNER, KEY_TAB, 5, SHIP_TAB, 1, "Afterburner", CC_TYPE_CONTINUOUS)
(AFTERBURNER, KEY_TAB, 5, SHIP_TAB, 1, "Afterburner", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
(GLIDE_WHEN_PRESSED, -1, -1, SHIP_TAB, 1774, "Glide When Pressed", CC_TYPE_CONTINUOUS)
(TOGGLE_GLIDING, KEY_ALTED | KEY_G, -1, SHIP_TAB, 1775, "Toggle Gliding", CC_TYPE_TRIGGER)
@@ -265,6 +267,12 @@ void control_config_common_init_bindings() {
(TOGGLE_HUD_CONTRAST, KEY_L, -1, COMPUTER_TAB, 1, "Toggle High HUD Contrast", CC_TYPE_TRIGGER)
(TOGGLE_HUD_SHADOWS, KEY_ALTED | KEY_L, -1, COMPUTER_TAB, 1781, "Toggle HUD Drop Shadows", CC_TYPE_TRIGGER)
(HUD_TARGETBOX_TOGGLE_WIREFRAME, KEY_ALTED | KEY_SHIFTED | KEY_Q, -1, COMPUTER_TAB, 1, "Toggle HUD Wireframe Target View", CC_TYPE_TRIGGER)
(TOGGLE_PHOTO_MODE, KEY_ALTED | KEY_1, -1, COMPUTER_TAB, 1908, "Toggle Photo Mode", CC_TYPE_TRIGGER, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
(PHOTO_MODE_FILTER_PREV, KEY_UP, -1, COMPUTER_TAB, 1909, "Photo Mode Previous Parameter", CC_TYPE_TRIGGER, false, CONFLICT_GROUP_PHOTO_MODE)
(PHOTO_MODE_FILTER_NEXT, KEY_DOWN, -1, COMPUTER_TAB, 1910, "Photo Mode Next Parameter", CC_TYPE_TRIGGER, false, CONFLICT_GROUP_PHOTO_MODE)
(PHOTO_MODE_FILTER_RESET, KEY_ALTED | KEY_2, -1, COMPUTER_TAB, 1911, "Photo Mode Reset Parameters", CC_TYPE_TRIGGER, false, CONFLICT_GROUP_PHOTO_MODE)
(PHOTO_MODE_PARAM_DECREASE, KEY_LEFT, -1, COMPUTER_TAB, 1912, "Photo Mode Decrease Selected Parameter", CC_TYPE_TRIGGER, false, CONFLICT_GROUP_PHOTO_MODE)
(PHOTO_MODE_PARAM_INCREASE, KEY_RIGHT, -1, COMPUTER_TAB, 1913, "Photo Mode Increase Selected Parameter", CC_TYPE_TRIGGER, false, CONFLICT_GROUP_PHOTO_MODE)
// Custom Controls
(CUSTOM_CONTROL_1, KEY_ALTED | KEY_SHIFTED | KEY_1, -1, COMPUTER_TAB, 1784, "Custom Control 1", CC_TYPE_TRIGGER, true)
@@ -284,10 +292,8 @@ void control_config_common_init_bindings() {
// init default preset
Control_config_presets.clear();
CC_preset preset;
CC_preset preset("default", Preset_t::hardcode);
preset.bindings.reserve(Control_config.size());
preset.name = "default";
preset.type = Preset_t::hardcode;
for (auto &item : Control_config) {
preset.bindings.push_back(CCB(item));
@@ -432,6 +438,12 @@ SCP_unordered_map<SCP_string, IoActionId> old_text = {
{"Up Thrust", UP_SLIDE_THRUST},
{"Down Thrust", DOWN_SLIDE_THRUST},
{"Toggle HUD Wireframe Target View", HUD_TARGETBOX_TOGGLE_WIREFRAME},
{"Toggle Photo Mode", TOGGLE_PHOTO_MODE},
{"Photo Mode Previous Parameter", PHOTO_MODE_FILTER_PREV},
{"Photo Mode Next Parameter", PHOTO_MODE_FILTER_NEXT},
{"Photo Mode Reset Parameters", PHOTO_MODE_FILTER_RESET},
{"Photo Mode Decrease Selected Parameter", PHOTO_MODE_PARAM_DECREASE},
{"Photo Mode Increase Selected Parameter", PHOTO_MODE_PARAM_INCREASE},
{"Top-Down View", VIEW_TOPDOWN},
{"Target Padlock View", VIEW_TRACK_TARGET},
@@ -668,7 +680,7 @@ const char *textify_scancode(int code)
}
SCP_string name;
unicode::convert_encoding(name, SDL_GetKeyName(SDL_GetKeyFromScancode(fs2_to_sdl(keycode))), unicode::Encoding::Encoding_utf8);
unicode::convert_encoding(name, SDL_GetKeyName(SDL_GetKeyFromScancode(fs2_to_sdl(keycode), SDL_KMOD_NONE, false)), unicode::Encoding::Encoding_utf8);
strcat_s(text, name.c_str());
return text;
}
@@ -1151,6 +1163,12 @@ void LoadEnumsIntoActionMap() {
ADD_ENUM_TO_ACTION_MAP(UP_SLIDE_THRUST)
ADD_ENUM_TO_ACTION_MAP(DOWN_SLIDE_THRUST)
ADD_ENUM_TO_ACTION_MAP(HUD_TARGETBOX_TOGGLE_WIREFRAME)
ADD_ENUM_TO_ACTION_MAP(TOGGLE_PHOTO_MODE)
ADD_ENUM_TO_ACTION_MAP(PHOTO_MODE_FILTER_PREV)
ADD_ENUM_TO_ACTION_MAP(PHOTO_MODE_FILTER_NEXT)
ADD_ENUM_TO_ACTION_MAP(PHOTO_MODE_FILTER_RESET)
ADD_ENUM_TO_ACTION_MAP(PHOTO_MODE_PARAM_DECREASE)
ADD_ENUM_TO_ACTION_MAP(PHOTO_MODE_PARAM_INCREASE)
ADD_ENUM_TO_ACTION_MAP(VIEW_TOPDOWN)
ADD_ENUM_TO_ACTION_MAP(VIEW_TRACK_TARGET)
@@ -1713,14 +1731,13 @@ size_t read_bind_1(CC_preset &preset) {
*/
void control_config_common_read_section(int s, bool first_override) {
Assertion((s == 0) || (s == 1), "Expected value of s to be either 0 or 1, found %i instead.", s);
CC_preset new_preset;
CC_preset new_preset("", Preset_t::tbl);
// Set references to the default preset and bindings
auto& default_preset = Control_config_presets[0];
auto& default_bindings = default_preset.bindings;
new_preset.bindings.clear();
new_preset.type = Preset_t::tbl;
if (s == 0) {
// #ControlConfigOverride
@@ -2368,7 +2385,7 @@ SCP_string ValToCID(CID id) {
if (it == mCIDNameToVal.cend()) {
// Shouldn't happen
Error(LOCATION, "Unknown CID value %i", id);
UNREACHABLE("Unknown CID value %i", id);
return "NONE";
} else {
@@ -2498,9 +2515,11 @@ SCP_string ValToJoy(const CC_bind &bind) {
if (it == mAxisNameToVal.end()) {
// should never happen
UNREACHABLE("Unknown error occured during reverse lookup of joy input string.");
} // else print out value
str = it->first;
str = "NONE";
} else {
// print out value
str = it->first;
}
/* } else if (bind.flags & CCF_HAT) {
// TODO Still currently encoded as buttons
@@ -2589,25 +2608,51 @@ void CC_bind::invert_toggle() {
flags ^= CCF_INVERTED;
}
static bool compare_btn(short cid, const CC_bind& A, char& A_flags, const CC_bind& B, char& B_flags)
{
auto A_btn = A.get_btn();
auto B_btn = B.get_btn();
auto current = io::joystick::getPlayerJoystick(cid);
if (current && current->isGamepad()) {
if (A_btn >= SDL_GAMEPAD_BUTTON_COUNT) {
A_btn = (A_btn - SDL_GAMEPAD_BUTTON_COUNT) + SDL_GAMEPAD_AXIS_LEFT_TRIGGER;
A_flags |= CCF_AXIS_BTN;
}
if (B_btn >= SDL_GAMEPAD_BUTTON_COUNT) {
B_btn = (B_btn - SDL_GAMEPAD_BUTTON_COUNT) + SDL_GAMEPAD_AXIS_LEFT_TRIGGER;
B_flags |= CCF_AXIS_BTN;
}
}
return (A_btn == B_btn);
}
bool CC_bind::conflicts_with(const CC_bind& B) const {
char A_flags = flags;
char B_flags = B.flags;
// Bail early if CID or btn are not the same
if ((cid != B.cid) || (btn != B.btn)) {
if ((cid != B.cid) || !compare_btn(cid, *this, A_flags, B, B_flags)) {
return false;
}
// Check if A is an Axis or Axis Button, and if B is an Axis or Axis Button
char mask = (CCF_AXIS_BTN | CCF_AXIS);
if ((flags & mask) && (B.flags & mask)) {
if ((A_flags & mask) && (B_flags & mask)) {
return true;
}
// Check if Hat
if (flags & B.flags & CCF_HAT) {
if (A_flags & B_flags & CCF_HAT) {
return true;
}
// Check if Ball
if (flags & B.flags & CCF_BALL) {
if (A_flags & B_flags & CCF_BALL) {
return true;
}
@@ -2619,7 +2664,7 @@ bool CC_bind::conflicts_with(const CC_bind& B) const {
// First off, check if A or B is NOT a button, as according to the mask. Buttons do not have a flag, so we check
// if they are any of the other input types
// Next, we return the inverse of the result. Negative of a Negative = Positive. Not Not a button = Is a button
return !((flags | B.flags) & mask);
return !((A_flags | B_flags) & mask);
}
bool CC_bind::is_inverted() const {
@@ -2710,10 +2755,19 @@ SCP_string CC_bind::textify() const {
case CID_JOY0:
case CID_JOY1:
case CID_JOY2:
case CID_JOY3:
Assert((btn >= 0) && (btn < JOY_TOTAL_BUTTONS));
retval = SCP_string(textify_button(btn));
case CID_JOY3: {
auto axis = joy_get_button_axis(cid, btn);
if (axis < 0) {
Assert((btn >= 0) && (btn < JOY_TOTAL_BUTTONS));
retval = SCP_string(textify_button(btn));
} else {
Assert((axis >= 0) && (axis < NUM_AXIS_TEXT));
retval = SCP_string(Axis_text[axis]);
}
break;
}
case CID_NONE:
default:
@@ -2818,6 +2872,7 @@ CCI& CCI::operator=(const CCI& A) {
locked = A.locked;
scriptEnabledByDefault = A.scriptEnabledByDefault;
continuous_ongoing = A.continuous_ongoing;
conflict_groups = A.conflict_groups;
return *this;
};
@@ -2900,7 +2955,7 @@ CCI_builder& CCI_builder::start() {
void CCI_builder::end() {};
CCI_builder& CCI_builder::operator()(IoActionId action_id, short primary, short secondary, char tab, int indexXSTR, const char *text, CC_type type, bool disabled) {
CCI_builder& CCI_builder::operator()(IoActionId action_id, short primary, short secondary, char tab, int indexXSTR, const char *text, CC_type type, bool disabled, int conflict_groups) {
Assert(action_id < CCFG_MAX);
CCI& item = ControlConfig[action_id];
@@ -2938,26 +2993,28 @@ CCI_builder& CCI_builder::operator()(IoActionId action_id, short primary, short
item.disabled = false;
}
// Assign the conflict group
item.conflict_groups = conflict_groups;
return *this;
}
CC_preset& CC_preset::operator=(const CC_preset& A) {
name = A.name;
type = A.type;
std::copy(A.bindings.begin(), A.bindings.end(), bindings.begin());
CC_preset& CC_preset::operator=(const CC_preset& other)
{
if (this != &other) {
bindings = other.bindings;
name = other.name;
// 'type' is intentionally preserved to maintain instance identity
}
return *this;
};
}
bool CC_preset::is_duplicate_of(CC_preset& A) {
for (size_t i = 0; i < A.bindings.size(); ++i) {
if (bindings[i] != A.bindings[i]) {
// Found a binding that's different. Thus, this preset is not a duplicate
bool CC_preset::is_duplicate_of(const CC_preset& other) const
{
for (size_t i = 0; i < other.bindings.size(); ++i) {
if (bindings[i] != other.bindings[i]) {
return false;
}
}
// Else, did not find any differences in the bindings. Thus, this preset is a duplicate
return true;
}
+3 -5
View File
@@ -74,9 +74,7 @@ void load_preset_files(SCP_string clone) {
// Start reading in data
CC_preset preset;
preset.name = file;
preset.type = Preset_t::pst;
CC_preset preset(file, Preset_t::pst);
preset.bindings.resize(Control_config.size());
size_t size;
@@ -134,7 +132,7 @@ void load_preset_files(SCP_string clone) {
if ((clone == preset.name) || (it == Control_config_presets.end())) {
Control_config_presets.push_back(preset);
} else if ((it->name != preset.name) || (it->type != Preset_t::pst)) {
} else if ((it->name != preset.name) || ((it->get_type() != Preset_t::pst))) {
if (gameseq_get_state() == GS_STATE_CONTROL_CONFIG) {
popup(PF_TITLE_WHITE | PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, "Preset '%s' is a duplicate of an existing preset, ignoring", preset.name.c_str());
} else {
@@ -173,7 +171,7 @@ bool delete_preset_file(CC_preset preset) {
cf_delete(filename.c_str(), CF_TYPE_PLAYER_BINDS, CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);
// Reload the presets from file.
Control_config_presets.resize(1);
Control_config_presets.erase(Control_config_presets.begin() + 1, Control_config_presets.end());
load_preset_files();
return true;
+3 -3
View File
@@ -49,7 +49,7 @@ VideoPresenter::VideoPresenter(const MovieProperties& props) : _properties(props
material_set_unlit(&_rgb_material, _planeTextureHandles[0], 1.0f, true, false);
break;
default:
UNREACHABLE("Unhandled enum value!");
UNREACHABLE("Unhandled enum value %d!", static_cast<int>(props.pixelFormat));
break;
}
}
@@ -86,7 +86,7 @@ void VideoPresenter::uploadVideoFrame(const VideoFramePtr& frame) {
bpp = 32;
break;
default:
UNREACHABLE("Unhandled enum value!");
UNREACHABLE("Unhandled enum value %d!", static_cast<int>(_properties.pixelFormat));
break;
}
@@ -145,7 +145,7 @@ void VideoPresenter::displayFrame(float x1, float y1, float x2, float y2, float
gr_render_primitives(&_rgb_material, PRIM_TYPE_TRISTRIP, &layout, 0, 4, gr_immediate_buffer_handle, offset);
break;
default:
UNREACHABLE("Unhandled enum value!");
UNREACHABLE("Unhandled enum value %d!", static_cast<int>(_properties.pixelFormat));
break;
}
}
+2 -1
View File
@@ -537,7 +537,8 @@ void cutscenes_screen_init()
int u = 0;
for (auto cut = Cutscenes.begin(); cut != Cutscenes.end(); ++cut, ++u)
{
if (cut->flags[Cutscene::Cutscene_Flags::Viewable, Cutscene::Cutscene_Flags::Always_viewable] && !cut->flags[Cutscene::Cutscene_Flags::Never_viewable])
if ((cut->flags.any_of(Cutscene::Cutscene_Flags::Viewable,Cutscene::Cutscene_Flags::Always_viewable)) &&
!cut->flags[Cutscene::Cutscene_Flags::Never_viewable])
{
Cutscene_list.push_back(u);
}
+4 -4
View File
@@ -14,8 +14,8 @@
#include <windows.h>
#endif
#include "freespace.h"
#include "globalincs/pstypes.h"
#include "freespace.h"
#include "globalincs/alphacolors.h"
#include "globalincs/systemvars.h"
#include "io/cursor.h"
@@ -208,7 +208,7 @@ void movie_display_loop(Player* player, PlaybackState* state) {
auto sleepTime = static_cast<std::uint64_t>((1. / (4. * player->getMovieProperties().fps)) * 1000.);
auto listener = [state](const SDL_Event& event) {
switch (event.key.keysym.sym) {
switch (event.key.key) {
case SDLK_ESCAPE:
case SDLK_KP_ENTER:
case SDLK_RETURN:
@@ -220,7 +220,7 @@ void movie_display_loop(Player* player, PlaybackState* state) {
}
};
// Use a dedicated listener here since we want to listen for the KEYUP event
auto key_handle = os::events::addEventListener(SDL_KEYUP, os::events::DEFAULT_LISTENER_WEIGHT - 10, listener);
auto key_handle = os::events::addEventListener(SDL_EVENT_KEY_UP, os::events::DEFAULT_LISTENER_WEIGHT - 10, listener);
// slight hack to make sure that game_set_frametime() doesn't also try to cap the framerate
auto fpsCapSave = Cmdline_NoFPSCap;
@@ -279,7 +279,7 @@ bool play(const char* filename, bool via_tech_room = false)
{
auto paramList = scripting::hook_param_list(scripting::hook_param("Filename", 's', filename), scripting::hook_param("ViaTechRoom", 'b', via_tech_room));
bool skip = scripting::hooks::OnMovieAboutToPlay->isOverride(paramList);
scripting::hooks::OnMovieAboutToPlay->run(paramList);
scripting::hooks::OnMovieAboutToPlay->run(std::move(paramList));
if (skip)
return false;
}
+3 -1
View File
@@ -4,6 +4,8 @@
#include "cutscene/VideoPresenter.h"
#include "cutscene/ffmpeg/FFMPEGDecoder.h"
#include "cmdline/cmdline.h"
#include "graphics/2d.h"
#include "globalincs/alphacolors.h"
@@ -52,7 +54,7 @@ void videoPlaybackInit(PlayerState* state) {
Assert(state != NULL);
if (gr_screen.mode != GR_STUB) {
if (gr_screen.mode != GraphicsAPI::Stub) {
// The video presenter is independent of the underlying graphics API
state->videoPresenter.reset(new VideoPresenter(state->props));
}
+116 -9
View File
@@ -1,13 +1,10 @@
#include "ddsutils/ddsutils.h"
#include "cfile/cfile.h"
#include "graphics/2d.h"
#include "osapi/osregistry.h"
#ifdef WITH_OPENGL
#include <glad/glad.h>
#else
static constexpr int GLAD_GL_EXT_texture_compression_s3tc = 0;
static constexpr int GLAD_GL_ARB_texture_compression_bptc = 0;
#ifdef USE_OPENGL_ES
#include "graphics/opengl/es_compatibility.h"
#endif
#define BCDEC_IMPLEMENTATION 1
@@ -57,11 +54,11 @@ static bool conversion_needed(const DDS_HEADER &dds_header)
case FOURCC_DXT1:
case FOURCC_DXT3:
case FOURCC_DXT5:
return !GLAD_GL_EXT_texture_compression_s3tc;
return !gr_is_capable(gr_capability::CAPABILITY_S3TC);
case FOURCC_DX10:
// anything other than BC7 will end up invalid
return !GLAD_GL_ARB_texture_compression_bptc;
return !gr_is_capable(gr_capability::CAPABILITY_BPTC);
default:
break;
@@ -223,7 +220,7 @@ static size_t compute_dds_size(const DDS_HEADER &dds_header, bool converting = f
if (dds_header.ddspf.dwFlags & DDPF_FOURCC) {
// size of data block (4x4)
d_size += ((d_width + 3) / 4) * ((d_height + 3) / 4) * d_depth * ((dds_header.ddspf.dwFourCC == FOURCC_DXT1) ? 8 : 16);
d_size += dds_compressed_mip_size(d_width, d_height, (dds_header.ddspf.dwFourCC == FOURCC_DXT1) ? 8 : 16) * d_depth;
} else {
d_size += d_width * d_height * d_depth * (dds_header.ddspf.dwRGBBitCount / 8);
}
@@ -549,6 +546,116 @@ int dds_read_bitmap(const char *filename, ubyte *data, ubyte *bpp, int cf_type)
return DDS_ERROR_NONE;
}
int dds_decompress_top_mip_bgra(const char *filename, int cf_type,
int *out_width, int *out_height,
SCP_vector<ubyte> &out_pixels)
{
Assert(filename != nullptr);
// normalize to a .dds extension, same as dds_read_bitmap
char real_name[MAX_FILENAME_LEN];
strcpy_s(real_name, filename);
char *p = strchr(real_name, '.');
if (p) { *p = 0; }
strcat_s(real_name, ".dds");
CFILE *cfp = cfopen(real_name, "rb", cf_type);
if (cfp == nullptr)
return DDS_ERROR_INVALID_FILENAME;
DDS_HEADER dds_header;
DDS_HEADER_DXT10 dx10_header;
int retval = _dds_read_header(cfp, dds_header, &dx10_header);
if (retval != DDS_ERROR_NONE) {
cfclose(cfp);
return retval;
}
// only 2D FOURCC-compressed images are supported here
if (!(dds_header.ddspf.dwFlags & DDPF_FOURCC) ||
(dds_header.dwCaps2 & (DDSCAPS2_CUBEMAP | DDSCAPS2_VOLUME)) ||
(dds_header.dwFlags & DDSD_DEPTH) ||
(dds_header.dwDepth > 1)) {
cfclose(cfp);
return DDS_ERROR_UNSUPPORTED;
}
void (*decode)(const void *, void *, int) = nullptr;
int block_size = 0;
switch (dds_header.ddspf.dwFourCC) {
case FOURCC_DXT1: decode = bcdec_bc1; block_size = BCDEC_BC1_BLOCK_SIZE; break;
case FOURCC_DXT3: decode = bcdec_bc2; block_size = BCDEC_BC2_BLOCK_SIZE; break;
case FOURCC_DXT5: decode = bcdec_bc3; block_size = BCDEC_BC3_BLOCK_SIZE; break;
case FOURCC_DX10:
if (!valid_dx10_format(dx10_header) ||
dx10_header.resourceDimension != D3D10_RESOURCE_DIMENSION::D3D10_RESOURCE_DIMENSION_TEXTURE2D ||
dx10_header.arraySize > 1) {
cfclose(cfp);
return DDS_ERROR_UNSUPPORTED;
}
decode = bcdec_bc7;
block_size = BCDEC_BC7_BLOCK_SIZE;
break;
default:
cfclose(cfp);
return DDS_ERROR_UNSUPPORTED;
}
const int w = static_cast<int>(dds_header.dwWidth);
const int h = static_cast<int>(dds_header.dwHeight);
if (w <= 0 || h <= 0) {
cfclose(cfp);
return DDS_ERROR_INVALID_FORMAT;
}
// BCn data is stored as ceil(w/4) * ceil(h/4) 4x4 blocks; dimensions
// don't have to be multiples of 4.
const int blocks_w = (w + 3) / 4;
const int blocks_h = (h + 3) / 4;
const int padded_w = blocks_w * 4;
const int padded_h = blocks_h * 4;
const size_t compressed_size = static_cast<size_t>(blocks_w) * blocks_h * block_size;
// _dds_read_header leaves the file positioned right after the header
// (including the DX10 sub-header if present), so the next read is the
// top mip's pixel data.
SCP_vector<ubyte> compressed(compressed_size);
const int got = cfread(compressed.data(), 1, static_cast<int>(compressed_size), cfp);
cfclose(cfp);
if (got != static_cast<int>(compressed_size))
return DDS_ERROR_INVALID_FORMAT;
// Decode into a padded buffer so edge blocks have room, then crop to w*h.
SCP_vector<ubyte> decoded(static_cast<size_t>(padded_w) * padded_h * 4);
const int dec_stride = padded_w * 4;
const ubyte *src = compressed.data();
for (int by = 0; by < blocks_h; ++by) {
for (int bx = 0; bx < blocks_w; ++bx) {
ubyte *blk_dst = decoded.data() + (by * 4) * dec_stride + (bx * 4) * 4;
decode(src, blk_dst, dec_stride);
src += block_size;
}
}
out_pixels.assign(static_cast<size_t>(w) * h * 4, 0);
const int dst_stride = w * 4;
for (int y = 0; y < h; ++y) {
std::memcpy(out_pixels.data() + static_cast<size_t>(y) * dst_stride,
decoded.data() + static_cast<size_t>(y) * dec_stride,
static_cast<size_t>(dst_stride));
}
// bcdec outputs RGBA byte-order; swap to BGRA
for (size_t x = 0; x < out_pixels.size(); x += 4) {
std::swap(out_pixels[x], out_pixels[x + 2]);
}
if (out_width) *out_width = w;
if (out_height) *out_height = h;
return DDS_ERROR_NONE;
}
// save some image data as a DDS image
// NOTE: we only support, uncompressed, 24-bit RGB and 32-bit RGBA images here!!
void dds_save_image(int width, int height, int bpp, int num_mipmaps, ubyte *data, int cubemap, const char *filename)
+30
View File
@@ -273,6 +273,27 @@ typedef struct {
} DDS_HEADER_DXT10;
#pragma pack()
// Block size in bytes for a 4x4 texel block of a compressed DDS format.
// comp_type is one of the DDS_DXT*/DDS_CUBEMAP_DXT* constants.
inline int dds_block_size(int comp_type) {
switch (comp_type) {
case DDS_DXT1:
case DDS_CUBEMAP_DXT1:
return 8;
case DDS_DXT3: case DDS_CUBEMAP_DXT3:
case DDS_DXT5: case DDS_CUBEMAP_DXT5:
case DDS_BC7:
return 16;
default:
return 0;
}
}
// Size in bytes of one mip level of a block-compressed texture.
inline size_t dds_compressed_mip_size(int w, int h, int block_size) {
return static_cast<size_t>(((w + 3) / 4) * ((h + 3) / 4) * block_size);
}
#define DDS_OFFSET 4+sizeof(DDS_HEADER) //place where the data starts -- should be 128
#define DX10_OFFSET DDS_OFFSET+sizeof(DDS_HEADER_DXT10) // Unless a DX10 header is present
@@ -285,6 +306,15 @@ int dds_read_header(const char *filename, CFILE *img_cfp = NULL, int *width = 0,
//size of the data it stored in size
int dds_read_bitmap(const char *filename, ubyte *data, ubyte *bpp = NULL, int cf_type = CF_TYPE_ANY);
// Decompress just the top mip of a 2D FOURCC-compressed DDS (DXT1/3/5, BC7)
// to 32-bpp BGRA, regardless of what the renderer's compression support is.
// Intended for tool/preview code that needs raw pixels and doesn't care
// about mipmaps or cubemap faces. On success, out_pixels is sized to
// width*height*4 in BGRA byte order.
int dds_decompress_top_mip_bgra(const char *filename, int cf_type,
int *out_width, int *out_height,
SCP_vector<ubyte> &out_pixels);
// writes a DDS file using given data
void dds_save_image(int width, int height, int bpp, int num_mipmaps, ubyte *data = NULL, int cubemap = 0, const char *filename = NULL);
+13 -11
View File
@@ -56,7 +56,7 @@ int Debris_num_submodels = 0;
particle::ParticleEffectHandle Debris_hit_particle;
#define DEBRIS_INDEX(dp) (int)(dp-Debris.data())
#define DEBRIS_INDEX(dp) (static_cast<int>((dp)-Debris.data()))
// Find the first available arc slot. If none is available, and no_create is false, add one.
debris_electrical_arc *debris_find_or_create_electrical_arc_slot(debris *db, bool no_create);
@@ -149,7 +149,7 @@ void debris_init()
particle::ParticleEffect::ShapeDirection::ALIGNED, //Particle direction
::util::UniformFloatRange(1.f), //Velocity Inherit
false, //Velocity Inherit absolute?
make_unique<particle::LegacyAACuboidVolume>(0.3f, 1.f, true), //Velocity volume
std::make_unique<particle::LegacyAACuboidVolume>(0.3f, 1.f, true), //Velocity volume
::util::UniformFloatRange(0.f, 10.f), //Velocity volume multiplier
particle::ParticleEffect::VelocityScaling::NONE, //Velocity directional scaling
std::nullopt, //Orientation-based velocity
@@ -248,6 +248,7 @@ void debris_delete( object * obj )
if (db->model_instance_num >= 0) {
model_delete_instance(db->model_instance_num);
db->model_instance_num = -1;
}
if ( db->is_hull ) {
@@ -336,7 +337,7 @@ void debris_process_post(object * obj, float frame_time)
break;
default:
UNREACHABLE("Unhandled case %d for electrical arc creation in debris_process_post()!", n);
Assertion(false, "Unhandled case %d for electrical arc creation in debris_process_post()!", n);
}
}
}
@@ -1030,9 +1031,9 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
model_get_moving_submodel_list(submodel_vector, heavy_obj);
// turn off all moving submodels, collide against only 1 at a time.
// turn off collision detection for all moving submodels
mc.collision_checked.assign(pm->n_models, 0);
for (auto submodel : submodel_vector) {
pmi->submodel[submodel].collision_checked = true;
mc.collision_checked[submodel] = true;
}
// Only check single submodel now, since children of moving submodels are handled as moving as well
@@ -1044,10 +1045,8 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
// check each submodel in turn
for (auto submodel: submodel_vector) {
auto smi = &pmi->submodel[submodel];
// turn on just one submodel for collision test
smi->collision_checked = false;
mc.collision_checked[submodel] = false;
// find the start and end positions of the sphere in submodel RF
model_instance_global_to_local_point(&p0, &light_obj->last_pos, pm, pmi, submodel, &heavy_obj->last_orient, &heavy_obj->last_pos, true);
@@ -1081,8 +1080,11 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
}
// Don't look at this submodel again
smi->collision_checked = true;
mc.collision_checked[submodel] = true;
}
// Clear collision_checked before base model pass so it auto-inits fresh
mc.collision_checked.clear();
}
// Now complete base model collision checks that do not take into account rotating submodels.
@@ -1150,9 +1152,9 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
// everything above was calculated relative to the heavy's position
vec3d actual_world_hit_pos = mc.hit_point_world + heavy_obj->pos;
if ((shipp->is_arriving()) && (shipp->warpin_effect != nullptr))
warp_effect = shipp->warpin_effect;
warp_effect = shipp->warpin_effect.get();
else if ((shipp->flags[Ship::Ship_Flags::Depart_warp]) && (shipp->warpout_effect != nullptr))
warp_effect = shipp->warpout_effect;
warp_effect = shipp->warpout_effect.get();
if (warp_effect != nullptr && point_is_clipped_by_warp(&actual_world_hit_pos, warp_effect))
mc_ret_val = 0;
Loaded 100 of 1665 files, more files were not shown because too many files have changed in this diff. Show more