Compare commits

..
Author SHA1 Message Date
the-e 76286c5332 Remove unused graphics settings from EditorViewport
Eliminate redundant graphics preference fields (post-processing, shadow quality, MSAA, texture filtering, anisotropy, gamma) that were initialized but unused in `EditorViewport`. Clean up associated setting writes for consistency.
2026-11-07 19:46:00 +01:00
the-eandClaude Opus 5 e4546de24e Update the design notes and help for the resize rework
documentation/qtfred-post-processing-viewport-resize.md still described the
Gr_min_render_target_w/h floor, which no longer exists, and listed
reallocate-on-resize as rejected -- which is now the implemented approach.

Rewrite it around gf_resize_render_targets, and keep the two failed attempts as
history, because the reason they failed is the reason the current one works.
That document had already identified the prerequisite correctly: the scene
teardown left FBOs attached to stale texture handles, and draws to an incomplete
FBO go nowhere, hence the black viewport. Completing the teardown was what made
reallocation viable. Also records the two properties that are easy to get wrong
(clamp to the hardware limit before deciding whether to resize, or a viewport
past that limit rebuilds every frame; never resize mid-frame), the sites fixed
in the deferred and Vulkan paths, and why the volumetric nebula pass is
deliberately left unscaled.

Corrections to the Preferences help page from the same review:

 - Enabling post-processing does not by itself produce shadows. Shadow quality
   defaults to Disabled and needs a restart to change, so the page said the
   opposite of what a user will experience.
 - The anisotropy control also greys out when the GPU reports a maximum of 2x,
   not only when it lacks the feature outright.

The help keyword index is left alone: keywords there are page-level, with
multiple entries only ever used as synonyms for one page, so per-setting
keywords would be inventing a convention rather than following one.

Finally, note in the qtfred module guide that its viewport calls
gr_screen_resize() every frame -- the assumption whose violation caused all of
the above -- and where the graphics preferences now live.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-11-07 19:46:00 +01:00
the-eandClaude Opus 5 336f198594 Document the Graphics preferences in the help that ships
The Graphics tab was documented in help-src/doc/dialogs/PreferencesDialog.html,
which is not listed in doc/qtfred.qhp and so is never compiled into
qtfred_help.qch. It is a stale duplicate of general/PreferencesDialog.html --
the page the table of contents, the Preferences keyword, and Viewport.html all
point at -- and one of only two orphaned files under help-src/doc. None of the
new documentation reached the Help viewer.

Document the tab on the page that actually ships, and revert the edit to the
orphan so nothing is left stranded there. The orphan itself predates this
branch and is left alone; it should probably be deleted, but that is a separate
question from this branch.

The content is also corrected against what the settings now do:

 - Shadow quality requires a restart. The orphan did not say so, and the
   dropdown entries no longer carry a "(restart required)" suffix now that
   their labels come from the engine's own option definition.
 - Texture filtering and anisotropy default to the engine's own choice
   (the config default, and the hardware maximum) until the user picks one.
 - Anisotropy is disabled outright on hardware that does not support it.
 - Gamma documents its actual range and default.
 - The page's "changes take effect immediately" claim is qualified, since it
   is not true of the four restart-only settings.

Also notes the View > Enable Post Processing menu equivalent, which had no
documentation anywhere -- no help page covers the View menu's display toggles.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-11-07 19:46:00 +01:00
the-eandClaude Opus 5 5c7059b451 Give qtFRED's graphics preferences a single owner
The Preferences > Graphics settings were read from QSettings in two places --
management.cpp before gr_init(), and EditorViewport once the editor exists --
each with its own copy of the key strings and default values, so renaming a key
in one would silently stop the other from working. The split between settings
that apply live and settings that need a restart was recorded only in comments
that had to stay in sync across five files.

Move both into GraphicsSettings, which owns the keys, the defaults, and the two
apply paths (applyLive() and applyBeforeGrInit()). ViewSettings holds one of
these instead of seven loose fields.

Behaviour fixes that fall out of having one reader:

 - Values are range-checked before being cast to ShadowQuality/AntiAliasMode
   and validated against the MSAA list, matching what the neighbouring
   DataMenuStyle load already did. A hand-edited settings file no longer
   produces an out-of-range enum.
 - The two OptionsManager overrides are now consistent. Texture filtering was
   overridden unconditionally with a hardcoded default while anisotropy was
   guarded, so a fresh install masked the engine's own texture-filter default
   for no reason. Both now use a sentinel for "the user has not chosen" and
   leave the engine option alone until there is a real choice.
 - gr_set_gamma(3.0f) is no longer duplicated as a literal next to the struct
   default.

The dialog's value lists now come from the engine option definitions
(Graphics.Shadows, Graphics.AAMode, Graphics.TextureFilter) instead of the
hardcoded .ui items that duplicated them, and anisotropy uses the shared
gr_get_supported_anisotropy_levels() rather than a near-verbatim copy of the
engine's enumerator. Adding an AA mode upstream no longer silently desyncs
qtFRED's dropdown. Note that the shadow-quality entries therefore lose the
"(restart required)" suffix they carried in the .ui; the control's tooltip and
the help page still say it.

Populating combos now blocks signals: setupUi() has already run
connectSlotsByName(), so filling them would otherwise fire the change slots and
mark the model modified before the user touched anything.

Finally, render_frame() gets its three correlated EnablePostProcessing branches
replaced by a scoped ScenePostProcessing guard, and the shadow pass -- with the
HTL matrix-stack dance it needs -- moves into its own named function.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-11-07 19:45:48 +01:00
the-eandClaude Opus 5 4c0ff8414d Grow the scene/post render targets when the viewport does
The offscreen targets backing the scene texture and post-processing were
allocated once from gr_screen at gr_init() and never revisited. Anything drawn
while gr_screen was larger got clipped to their edge and stretched back over
the viewport. The game hits this on the SDL window-resize path
(osapi.cpp calls gr_screen_resize()); qtFRED hits it constantly, since its 3D
viewport is a resizable dock widget.

Add gf_resize_render_targets, called from gr_screen_resize(), and implement it
for OpenGL: grow the targets to cover the new gr_screen, rebuilding only the
resolution-dependent resources. The post-processing table, the compiled
shaders and the SMAA lookup textures are resolution-independent and stay
alive, which is what makes this cheap enough to run off a window drag. This
mirrors what the Vulkan backend already does in VulkanPostProcessor::resize();
Vulkan leaves the hook unset because recreateSwapChain() owns it there.

The allocation only ever grows: gr_screen_resize() is called every frame by
qtFRED and repeatedly by the briefing map widget, so tracking the high-water
mark avoids thrashing, and the shrunk state is already handled by
Scene_texture_u_scale/v_scale. The size is also clamped to the hardware limit
up front, so a viewport past that limit stops asking to be resized instead of
rebuilding every frame. If the larger allocation fails outright -- most likely
precisely when growing -- the resize stops before rebuilding the
post-processing targets on top of scene textures that no longer exist.

This replaces Gr_min_render_target_w/h, which sized the targets for the
largest attached display up front -- on a 4K display at 2x scaling that was
over a gigabyte of VRAM, allocated at launch, for a feature that is off by
default and may never be switched on.

Deletions now go through GL_state.Texture.Delete(): a freed texture name the
state cache still holds would make a later Enable() of the recycled name a
silent no-op. This mattered little when teardown only ran at shutdown. While
there, the scene teardown now releases everything setup allocates -- it was
leaking Scene_ldr/composite/luminance/Cockpit_depth and all six MSAA targets --
and post-processing shutdown releases the SMAA lookup textures.

Separately, fix the sampling extents that the above makes reachable:

 - deferred-f.sdr turns gl_FragCoord into a G-buffer coordinate using
   invScreenWidth/Height, which described gr_screen rather than the G-buffer.
   Fixed in both backends; it is currently a no-op under Vulkan, where
   resize() keeps the extent equal to gr_screen, but states the requirement
   instead of relying on that.
 - the MSAA scene-colour copy, the MSAA resolve and the fog pass sampled the
   full [0,1] range of targets that are only filled to the u/v scale.
 - fxaa-v.sdr derived its texcoord from vertPosition, ignoring the
   sub-rectangle the draw call asked for.

Those extents now come from opengl_draw_full_screen_scene_texture() rather
than being open-coded, so a new pass cannot quietly reintroduce the bug. The
volumetric nebula pass is deliberately left unscaled and commented: it uses
fragTexCoord both to reconstruct a ray direction and to sample, which needs a
shader change to separate.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-11-07 19:45:48 +01:00
the-eandClaude Opus 5 019c4bacea Fix texture filtering silently defaulting to bilinear
opengl_tcache_init() read GL_mipmap_filter out of TextureFilteringOption
before seeding it from the legacy config key. That option's default_func
returns GL_mipmap_filter itself, so with no persisted "Graphics.TextureFilter"
value getValue() fell through to a still-zero-initialized global -- bilinear,
where the config default is trilinear. Since in-game options are on by default,
that affected every player who had never explicitly set the option, and it
ignored the legacy TextureFilter key existing installs were configured through.

Seed from config first, then let the option override, matching the order the
anisotropy setting below already uses (its default_func queries the hardware
directly, which is why it was never affected).

Also route the option's value enumerator through the new shared
gr_get_supported_anisotropy_levels() rather than a private copy.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-11-07 19:45:48 +01:00
the-e e7d58d85cb Add configurable graphics settings to Preferences dialog
Introduce post-processing, shadow quality, anti-aliasing, anisotropic filtering, texture filtering, MSAA, and gamma controls to qtFRED's Preferences. Ensures real-time application of certain settings and requires restart for others. Adjust graphics initialization to support baked settings before `gr_init()`. Update documentation to reflect these additions.
2026-11-07 19:45:48 +01:00
the-eandClaude Opus 5 84fa0a622f Size qtFRED's post-processing targets for its resizable viewport
The offscreen render targets that back post-processing -- the scene
textures and the post-processing surfaces -- are allocated once from
gr_screen.max_w/max_h at renderer init and never resized. The game never
notices, because its window size is fixed after gr_init(). qtFRED does:
FredRenderer::render_frame() calls gr_screen_resize() every frame to match
a dockable, resizable widget, so growing the viewport past the startup size
clipped the render to the old, smaller texture and then stretched it back
over the new, larger viewport.

Add Gr_min_render_target_w/h as a floor those allocations respect, and have
qtFRED set it to the largest size its viewport can reach (the biggest screen
the window could be maximized onto, in device pixels) before calling
gr_init(). Left at 0 for the game, which keeps sizing them purely from
gr_screen.

That makes Scene_texture_u_scale/v_scale meaningful outside the game for the
first time, which turns up two latent bugs:

  - Seven post-processing passes passed Scene_texture_u_scale for both axes.
    Harmless while the scene texture matches the screen exactly (u == v),
    wrong the moment it doesn't.
  - The bloom bright pass hardcoded 1.0/1.0. Unlike the passes after it, it
    reads the scene texture directly rather than an already-cropped
    intermediate, so it has to confine itself to the sub-rectangle that was
    actually rendered into.

Finally, expose the pipeline in qtFRED at all: a View menu toggle brackets
the 3D scene in gr_scene_texture_begin/end, the same way game_render_frame()
brackets its own. Off by default, and it covers only the 3D world content --
the 2D overlays stay outside it.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-11-07 19:45:48 +01:00
the-eandClaude Sonnet 5.5 c5b761ca5d Apply review feedback on softer shadows
- Bind the GL raw-depth sampler on unit 11 in the deferred pass and
  unbind it after the pass.
- Enable RTAO only when shadows are on, and remove the RTAO option when
  ray tracing is not supported.
- Use every second Poisson tap in the PCSS blocker search.
- Use TLAS-relative ray origins for RTAO.
- Gate +AngularSize: at version 27.0.
- Measure an .eff sun from its first frame, and skip the measurement
  when shadows are off.
- Guard the GL 3.3 sampler check for GLES builds.
- Restore the lab session overrides when the lab closes.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-07 19:45:48 +01:00
the-e 9443c4abe0 Add shadow contact hardening via PCSS blocker search
The shadow-mapped path's penumbra width was a fixed per-cascade value
(shadow_smoothness_scale()'s ceiling), so every occluder blurred its
shadow by the same amount regardless of how close it actually was to
the receiver -- unlike the raytraced path, which gets that for free
from traceShadowRayCone()'s real occluder distance. Close that gap
with PCSS: search nearby texels for occluders, then scale the filter
radius by how far away they were.

The blocker search (pcssBlockerSearch() in shadows.sdr) needs raw,
uncompared depth reads, which the existing PCF sampler can't do -- it's
bound with a compare mode for the hardware shadow-compare instructions
the filtered sampling itself relies on. So shadow maps now get a second
sampler, shadow_map_raw, bound to the same texture with compare mode
off; on OpenGL that needs a second sampler object and GL 3.3
(shadow_contact_hardening_supported()), Vulkan can always bind it.
Where the raw sampler isn't available, or the new Graphics.ShadowContactHardening
option is off, shadows_start_render() sets Shadow_penumbra_scale[cascade]
to -1 as a sentinel and pcssPenumbraRadius() (shadows.sdr) falls straight
back to the fixed radius, bit-identical to the pre-contact-hardening
behavior and skipping the extra texture reads.

When it is on, shadows_start_render() computes each cascade's penumbra
scale from its own frustum extents and the sun's tanθ; the shader turns
that plus the blocker search's average depth into a per-pixel radius,
clamped to [1 texel, the old fixed ceiling]. A blocker search that finds
nothing doesn't fall back to "no shadow" -- a sparse 8-tap search can
miss a thin occluder, so it instead runs the full filter at the hardest
(1-texel) radius, trading a touch of extra blur for not popping a real
shadow to nothing.

Drops RT_SHADOW_SUN_SIZE_CALIBRATION accordingly: that constant existed
only to fake a blocker-distance-driven penumbra on the shadow-mapped path
by pre-scaling the raytraced cone to match it, and the shadow-mapped path
now has a real blocker search of its own, so the raytraced cone goes back
to its physically correct size.
2026-11-07 19:45:48 +01:00
the-eandClaude Opus 5 5e745f7535 Add RTAO and unify raytraced/shadow-mapped shadow softness on sun angular size
Raytraced shadow penumbras needed a hand-authored $SunAngularSize: in
stars.tbl, which no shipped content sets -- so in practice nothing ever
got a penumbra, and the shadow-mapped path's $Shadow Smoothness Factor:
had no relation to it at all. Derive a size from the sun bitmap instead
and drive both methods from the same number.

g3_render_rect_screen_aligned_2d() sizes the sun quad so its rad is the
tangent of the half-angle it subtends, making tan(angular radius) equal
to 0.05 * the mission's +Scale: * the emitting disc's fraction of the
bitmap. That fraction is the area of the pixels at or above 90% of the
brightest one, converted to an equivalent radius. Across 90 sun bitmaps
from retail, the MediaVPs, Blue Planet and BtA it lands at a median of
0.261 (retail's chunkier art at 0.487), and the threshold acts as a
constant factor rather than a per-bitmap judgement call -- Spearman rho
0.96 between a 90% and a 50% cutoff.

Taken literally that yields suns 3.9-8.5x Sol's apparent diameter once
+Scale: is folded in (median 1.55, up to 5.0 in retail's own missions),
so the measurement is scaled by 0.25 and clamped to 1.5 degrees, putting
typical content near Sol. Derivation is the default: $SunAngularSize: 0
asks for hard shadows, an explicit value still wins, and missions can
also set +AngularSize: on an individual sun to override both, with FRED
and QtFRED UI (validation and fallback included) to author it. Bitmaps
that can't be read -- and the deliberately blank sun bitmaps mods ship
to get a light source with no visible disc -- fall back to hard shadows
rather than to the widest possible penumbra.

That angular size now sizes the raytraced penumbra cone directly
(traceShadowRayCone() in shadows.sdr), with sample count following the
same Shadow_quality tier that picks the shadow map's resolution (1 ray
at Low, up to 16 at Ultra) rather than being a separate setting. The
shadow-mapped path's tabled smoothness values were tuned by eye against
Sol-sized retail/MediaVPs art, so shadow_smoothness_scale() in
shadows.cpp scales them by the active sun's size relative to Sol,
clamped to what the shadow map can represent (shadow_clamp_smoothness()).
The two methods still disagree about occluder distance -- the raytracer
uses the real one, the shadow map's tabled values stand in for a blocker
distance pinned to the cascade's own extent -- so RT_SHADOW_SUN_SIZE_CALIBRATION
in shadows.sdr is a constant factor that makes a given $SunAngularSize:
read as roughly the same softness either way; it's the number to revisit
if the shadow map ever grows a real blocker search.

Also adds RTAO (raytraced ambient occlusion) sharing the same TLAS as
the shadow rays, with lighting-profile-driven radius/strength, and lab
controls for RT shadow quality (Low/High), the local light cap, RT
shadow samples, RTAO, and an angular-size override for comparing both
shadow methods against the same sun. All are safe to change live: they
only gate which lights are picked as shadow casters or scale existing
uniforms while filling the per-frame light data.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-11-07 19:45:47 +01:00
the-eandClaude Opus 5.5 c165b82996 Address third round of review feedback on the profiler overlay
- Move the overlay option strings to XSTR 1940 and 1941. The touch
  overlay on master uses 1936 to 1939.
- Give FrameProfiler the main thread ID when it is made. Before, it
  used the thread of the first event, which was a race.
- Let mouse motion go to the game while the pointer is over the
  overlay, so that mouse flight does not stop there.
- Find the dynamic bindings of the PerDraw key with
  dynamic_offset_slot(), as the other call sites do.
- Make disable_inside_open_scope count the GPU timestamp queries, so
  that it fails if end() does not close the GPU event.
- Make the copy test copy the category, not bind a reference.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 19:36:52 +01:00
Fabian WoltermannandClaude Opus 5.5 4f3a0d4cff Return size_t from Category::getCount() and tidy the registry code
- Use structured bindings and std::scoped_lock in the category registry.
- Return size_t from getCount() and update its callers and tests.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 19:36:42 +01:00
Fabian WoltermannandClaude Opus 5.5 8266392f00 Address second round of review feedback on the profiler overlay
- Make the category registry thread-safe and give categories with the
  same name one id, so the registry does not grow without limit.
- Add tests that toggle the profiler inside an open trace scope.
- Correct the next free XSTR id note to 1938.
- Use the existing Windows include block for psapi.h in osapi.cpp.
- Use list_range in ship_get_num_ships() and shorten some comments.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 19:36:42 +01:00
the-eandClaude Sonnet 5.5 68166ed419 Use wall-clock frame time and read the history without a copy
The Avg and FPS line used traced self-time, which reads too high.
Measure the time between overlay frames instead. Draw the graph with
PlotLineG so the history deque needs no per-frame copy.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-07 19:36:31 +01:00
the-eandClaude Sonnet 5.5 fa4b755b94 Address review feedback on the profiler overlay
Skip complete::end for a scope that did not start. This fixes a crash
when Lua turns the overlay on while a scope is open. Track the GPU begin
event so that turning the overlay off mid-scope still closes it.

Use XSTR 1936 and 1937 for the overlay option. Master now uses 1932
and 1933.

Remove the default value of dynamicOffsets in bindDescriptorSet. Use the
PerDraw binding constants in the PerDraw key.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-07 19:36:31 +01:00
the-e b5bcfeb8d1 Refactor profiler overlay to use snapshot-based frame timing 2026-11-07 19:36:31 +01:00
the-e b1c90ea5c5 Replace category pointers with IDs to prevent use-after-free
- Updated `trace_event` to store `category_id` instead of a pointer to prevent dangling references to category objects that may already be destroyed.
- Modified all references to category names to use `Category::getNameById()` instead of dereferencing pointers.
- Expanded memory statistics in the profiler overlay with new sound, Lua, and OS-level memory usage tracking.
- Added new tests to verify proper use of category IDs in frame profiling and event tracing.
- Improved memory safety and stability across tracing, profiling, and overlay systems.
2026-11-07 19:36:31 +01:00
the-eandClaude Opus 5 dd4a08925c Fix profiler overlay crash on a category that is not a static
The category registry kept a Category pointer for each id. The comment said that all categories
are global statics, but that is not correct. The Lua API tracing_category constructs a Category
and copies it into a script-owned object. The registry then kept a pointer to the dead temporary.
build_overlay_snapshot called getName() on that pointer, made an SCP_string from the garbage
char*, and the process stopped with SIGABRT.

The registry now keeps the name by value and never keeps a Category pointer. getById() becomes
getNameById(), which is all that build_overlay_snapshot needs. A copy of a Category keeps the id
of the original, which stays correct because the id only selects a name.

Add two tests: one shows that the name stays available after the Category object is dead, and one
drives the full path that failed, from processEvent to the overlay snapshot.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-11-07 19:36:13 +01:00
the-eandClaude Opus 5 647554e182 Add OpenGL memory statistics to the profiler overlay
The memory panel showed no texture bytes and no geometry bytes under
OpenGL. Only the depth renderbuffers went into the render target group.
All three groups now give a value.

Textures: GL_texture_bytes_used holds the bytes of every live texture
cache slot. opengl_texture_release_bytes() removes the bytes of a slot at
each site that drops its texture data. The bitmap render targets record
the bytes of their color texture.

Geometry: the unused GL_vertex_data_in global becomes
GL_geometry_bytes_used, which counts only vertex and index buffers. This
matches the geometry group of the Vulkan backend.
gr_opengl_delete_buffer() sets the size of the buffer to 0, because
opengl_destroy_all_buffers() calls it a second time for a buffer that the
game already deleted.

Render targets: three new functions add the scene textures, the
post-processing textures, and the shadow map to the group. They sum the
bytes at the moment of the call from the textures that exist, thus they
need no bookkeeping at the create and delete sites.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-11-07 19:36:13 +01:00
the-e 57c28798a7 fix ci issues 2026-11-07 19:36:13 +01:00
the-e 3c456a088c Add memory statistics and tracking to profiler overlay 2026-11-07 19:36:13 +01:00
the-e cd48b5b500 Replace exception with sentinel for non-dynamic Vulkan descriptor bindings
- Introduced `DYNAMIC_SLOT_INVALID` sentinel to replace the throw in `dynamic_offset_slot` for MSVC compatibility.
- Added `static_assert` checks to validate binding constants, ensuring compile-time validation for dynamic offsets.
- Updated comments to clarify design rationale and avoid potential misuse.
2026-11-07 19:36:13 +01:00
Fabian Woltermann f1812e212d Enable VK_KHR_portability_subset for compatibility 2026-11-07 19:36:13 +01:00
the-e 817f641478 Enable primitive restart for supported topologies in Vulkan pipeline
- Added `topologySupportsPrimitiveRestart` to determine restart eligibility for each topology.
- Updated `primitiveRestartEnable` to be conditionally set based on topology, addressing compatibility issues with Metal/MoltenVK.
- Prevented crash scenarios on macOS by ensuring pipelines are valid for strip/fan topologies requiring primitive restart.
2026-11-07 19:36:13 +01:00
the-e eec4a9ce3e Refactor Vulkan descriptor set binding to reduce dynamic offset churn
- Consolidated dynamic descriptor offset logic into `DescriptorWriter::bindSets`, replacing repetitive `vkCmdBindDescriptorSets` patterns across Vulkan modules.
- Eliminated redundant descriptor binding operations for Material and PerDraw sets by centralizing logic in `bindSets`.
- Simplified descriptors by directly splitting offset changes from descriptor contents, avoiding unnecessary updates.
- Updated comments and documentation to align with the new approach while maintaining reference consistency.
- Applied optimizations across Vulkan modules (`VulkanPostProcessing`, `VulkanDescriptorManager`, etc.) to reduce per-draw overhead and improve readability.
2026-11-07 19:36:13 +01:00
the-eandClaude Opus 5 b2de895055 Use dynamic uniform buffers to stop per-draw descriptor set churn in Vulkan
The Material and PerDraw descriptor sets were reallocated and fully rewritten on
essentially every draw call. model_draw_list::render_buffer rebinds ModelData with
a fresh uniform buffer offset for each queued draw, and that offset was part of the
Material set's memoization key, so the cache in VulkanDrawManager::applyMaterial
missed every time - taking a frame-pool allocation, a full template write, the
16-entry material texture resolution and a vkUpdateDescriptorSets with it.
GenericData/Matrices did the same to the PerDraw set. renderShadowDraw was worse
still: all three sets allocated and rewritten per shadow draw, with no memoization
at all.

A large asteroid field (Solaris m21) measured 2566 descriptor sets and 19107
descriptor writes per frame at 1606 model draws, and grew an extra descriptor pool
chunk every frame.

Declare the bindings whose offset moves per draw - MaterialBinding::ModelData,
MaterialBinding::ShadowMapData, PerDrawBinding::GenericData and
PerDrawBinding::Matrices - as eUniformBufferDynamic. DescriptorWriter::setBuffer
now splits a dynamic binding's offset out of the descriptor, writing
{buffer, 0, range} and stashing the offset for vkCmdBindDescriptorSets'
pDynamicOffsets, so an offset-only change leaves the set's contents identical and
the memoization caches start hitting. Shader sources are unchanged.

VulkanStateTracker::bindDescriptorSet now compares the dynamic offsets as well as
the set handle. This is required, not an optimization: the common case is now the
same set rebound with a new offset, and the old handle-only redundancy check would
have skipped that bind and fed every draw the previous draw's uniforms.

renderShadowDraw memoizes its three sets the way applyMaterial does. It does not
invalidate applyMaterial's caches - those record what a set contains, the shadow
pass allocates its own sets rather than overwriting theirs, and it binds through
the state tracker, so the next material cache hit still rebinds correctly.

Also switch allocateFrameSet to the non-allocating allocateDescriptorSets overload;
the vector-returning one heap-allocated once per set, thousands of times a frame.
MAX_SETS_PER_POOL stays at 1024 - the per-frame pool growth it used to log was a
symptom of the churn, and one chunk now covers the scene outright.

Measured on that mission in a Debug build, A/B on the same tree with and without
this change:

  descriptor sets/frame  @ ~1370 draws  2566   -> 250-345
  descriptor writes/frame                19107 -> ~1700-2300
  median frametime                       92.9/83.6 ms -> 72.0/66.5 ms
  Render Buffer (profiler overlay)       24.68 ms (30.8%) -> 12.73 ms (18.4%)
  Build Shadow Map                       5.44 ms -> off the top-5 entirely

Release builds already inlined most of this away and are unchanged, as expected.
Verified with the validation layers (sync validation enabled): no new VUIDs versus
the same build without the change.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-11-07 19:36:13 +01:00
the-e 7d4d61042e Refactor frame profiler to use per-category self-time accumulation
- Introduced `accumulate_self_times` for single-pass exclusive time computation, improving performance and accuracy.
- Updated `FrameProfiler` and overlay snapshot logic to leverage `Category`'s stable `getId` and `getCount`.
- Replaced pie chart with a stacked bar for frame budget visualization, optimizing rendering cost.
- Cached `get_tid` and `get_pid` with thread-local storage to minimize syscall overhead.
- Added support for unit testing frame profiling functionality and included new test cases.
- Improved code comments and clarified documentation.
2026-11-07 19:36:13 +01:00
the-e fd77b84c41 Update ImPlot to v1.1 WIP and improve internal consistency
- Adjust copyright notices for extended license coverage (2025-2026).
- Modernize constants with `constexpr` replacements, improving type safety and clarity.
- Enforce `nullptr` usage over `NULL` throughout, aligning with modern C++ practices.
- Add various enhancements to ImPlot legends, markers, and axes behavior.
- Introduce additional helper methods for time handling and timestamp calculations.
- Address UI and typo improvements in comments and documentation, refining accuracy.
2026-11-07 19:36:13 +01:00
the-e 2166d253a8 Add graphics and Vulkan debug stats to ImGui profiler overlay
- Integrated per-backend diagnostic counters into the ImGui frame profiler overlay, triggered by `-gr_debug`.
- Consolidated and cleaned up Vulkan rendering state and descriptor management for compatibility with ImGui's Vulkan pipeline.
- Enabled uniform buffer and draw statistics reporting for enhanced performance insights during debugging.
2026-11-07 19:36:13 +01:00
the-e f5b29e721b feat: add ImGui frame profiler overlay
Replaces the old text-dump frame profile display (-profile_frame_time)
with a runtime-toggleable ImGui/ImPlot overlay showing average/median
frametime, a scrolling frametime graph, and a pie chart of the top
contributors to frame time. Toggled via a new "Frame Profiler Overlay"
option, which also seeds from -profile_frame_time for backward
compatibility and now drives frame profiling collection at runtime
instead of only at startup.
2026-11-07 19:36:13 +01:00
the-e 9763c7a312 chore: vendor ImPlot library
Adds ImPlot as a build dependency, to be used for the upcoming ImGui
frame profiler overlay's frametime graph and pie chart.
2026-11-07 19:36:01 +01: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
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
Mike Nelson 474f62b21e address feedback 2026-05-26 16:10:33 -05:00
Mike Nelson 9a843f3420 support dds images in qtfred image picker 2026-05-19 11:47:25 -05:00
1247 changed files with 154528 additions and 59134 deletions

No files matched your search

+111 -94
View File
@@ -5,9 +5,6 @@ on:
tags:
- 'nightly_*'
env:
QT_VERSION: 5.12.12
jobs:
build_linux:
strategy:
@@ -21,7 +18,7 @@ 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@v7
name: Checkout
@@ -34,14 +31,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:
@@ -62,7 +57,7 @@ jobs:
name: Build Linux distribution zip
needs: build_linux
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-844131c
container: ghcr.io/scp-fs2open/sftp_upload:sha-ab87451
strategy:
matrix:
arch: [x86_64, arm64]
@@ -107,10 +102,22 @@ 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@v7
name: Checkout
@@ -119,88 +126,111 @@ jobs:
- 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: 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: 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@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-844131c
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@v7
name: Checkout
@@ -210,22 +240,31 @@ jobs:
- 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@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@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
@@ -246,21 +285,6 @@ 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@v7
name: Checkout
with:
@@ -269,12 +293,6 @@ jobs:
- 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: 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 up test version
shell: bash
run: |
@@ -287,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:
@@ -325,7 +342,7 @@ jobs:
name: Build Mac distribution zip
needs: build_mac
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-844131c
container: ghcr.io/scp-fs2open/sftp_upload:sha-ab87451
strategy:
matrix:
arch: [x86_64, arm64]
+115 -96
View File
@@ -5,9 +5,6 @@ on:
tags:
- 'release_*'
env:
QT_VERSION: 5.12.12
jobs:
create_release:
name: Create GitHub release
@@ -50,7 +47,7 @@ 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@v7
# checks-out your repository under $GITHUB_WORKSPACE, so your workflow can access it.
@@ -65,14 +62,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:
@@ -102,7 +97,7 @@ jobs:
name: Build Linux distribution zip
needs: build_linux
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-844131c
container: ghcr.io/scp-fs2open/sftp_upload:sha-ab87451
strategy:
matrix:
arch: [x86_64, arm64]
@@ -157,11 +152,25 @@ 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@v7
# Checkout repo
@@ -172,93 +181,115 @@ jobs:
- 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: 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: 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@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-844131c
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@v7
@@ -270,16 +301,26 @@ jobs:
- 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@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@v8
with:
name: windows-FastDebug-${{ matrix.arch }}-${{ matrix.simd }}
name: windows-FastDebug-${{ env.artifact_id }}
path: builds
- name: Create Distribution package
@@ -287,8 +328,7 @@ 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
@@ -322,20 +362,6 @@ 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@v7
name: Checkout
with:
@@ -345,23 +371,16 @@ jobs:
- 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: 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
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:
@@ -395,7 +414,7 @@ jobs:
name: Build Mac distribution zip
needs: build_mac
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-844131c
container: ghcr.io/scp-fs2open/sftp_upload:sha-ab87451
strategy:
matrix:
arch: [x86_64, arm64]
+81 -91
View File
@@ -5,17 +5,20 @@ 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@v7
name: Checkout
@@ -34,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:
@@ -56,13 +57,16 @@ jobs:
- name: Upload build result
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-844131c
container: ghcr.io/scp-fs2open/sftp_upload:sha-ab87451
strategy:
matrix:
arch: [x86_64, arm64]
steps:
- uses: actions/checkout@v7
name: Checkout
@@ -76,15 +80,21 @@ jobs:
- name: Download Release builds
uses: actions/download-artifact@v8
with:
name: linux-Release
name: linux-Release-${{ matrix.arch }}
path: builds
- name: Download FastDebug builds
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
@@ -99,10 +109,22 @@ 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@v7
name: Checkout
@@ -117,36 +139,9 @@ jobs:
[[ "${{ 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 }}
@@ -156,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:
@@ -185,20 +171,37 @@ 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@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-844131c
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@v7
name: Checkout
@@ -209,22 +212,31 @@ jobs:
- 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@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@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
@@ -245,21 +257,6 @@ 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@v7
name: Checkout
with:
@@ -268,12 +265,6 @@ jobs:
- 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: 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 up test version
shell: bash
run: |
@@ -285,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:
@@ -323,7 +313,7 @@ jobs:
name: Build Mac distribution zip
needs: build_mac
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-844131c
container: ghcr.io/scp-fs2open/sftp_upload:sha-ab87451
strategy:
matrix:
arch: [x86_64, arm64]
+48 -11
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,10 +23,8 @@ 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:
- name: Install Qt dependencies
run: apt-get -yq update && apt-get -yq install libfontconfig1
- uses: actions/checkout@v7
name: Checkout
with:
@@ -37,9 +33,10 @@ jobs:
# 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
@@ -51,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
@@ -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:
@@ -69,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)
+38 -89
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,18 +17,16 @@ 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:
- name: Install Qt dependencies
run: apt-get -yq update && apt-get -yq install libfontconfig1
- uses: actions/checkout@v7
name: Checkout
with:
@@ -41,7 +36,7 @@ jobs:
# 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
@@ -54,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:
@@ -85,7 +78,7 @@ jobs:
compiler: [MSVC]
arch: [Win32, x64]
name: Windows
runs-on: windows-2022
runs-on: windows-2025
steps:
- name: Prepare Environment
run: choco install ninja
@@ -96,36 +89,10 @@ jobs:
- 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: 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: Install v143 ATL/MFC
if: matrix.compiler == 'MSVC'
shell: pwsh
run: ci/windows/install_v143_atlmfc.ps1
- name: Configure CMake
env:
CONFIGURATION: ${{ matrix.configuration }}
@@ -137,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
@@ -190,21 +146,6 @@ 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@v7
name: Checkout
with:
@@ -214,27 +155,23 @@ jobs:
# 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:
@@ -242,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
+1 -3
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:
@@ -52,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
+44 -25
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)
@@ -74,7 +98,7 @@ IF(RESET_INSTALL_PREFIX)
ENDIF(NOT $ENV{FS2PATH} STREQUAL "")
ENDIF(RESET_INSTALL_PREFIX)
IF(WIN32 OR APPLE OR CMAKE_SYSTEM_NAME STREQUAL "Linux")
IF(WIN32 OR APPLE OR ANDROID OR CMAKE_SYSTEM_NAME STREQUAL "Linux")
OPTION(FSO_USE_SPEECH "Use text-to-speach libraries" ON)
ELSE()
OPTION(FSO_USE_SPEECH "Use text-to-speach libraries" OFF)
@@ -94,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)
@@ -117,8 +139,6 @@ SET(FSO_RUN_ARGUMENTS "" CACHE STRING "Additional arguments passed to a generate
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)
@@ -127,13 +147,13 @@ 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 (External shaders only!)" 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 FSO_BUILD_WITH_OPENGL AND FSO_BUILD_WITH_OPENGL_ES)
# Disable GLES if not building with OpenGL
set(FSO_BUILD_WITH_OPENGL_ES OFF FORCE)
set(FSO_BUILD_WITH_OPENGL_ES OFF CACHE BOOL "" FORCE)
endif()
@@ -167,7 +187,6 @@ 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)
@@ -196,11 +215,11 @@ 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)
+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"
+24 -9
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
@@ -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
+2 -2
View File
@@ -31,7 +31,7 @@ cmake -DCMAKE_INSTALL_PREFIX=$INSTALL_FOLDER/Freespace2 -DCOMPONENT=Unspecified
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/Freespace2/bin -name 'fs2_open_*' -type f -printf "%f\n").AppImage"
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"
@@ -41,7 +41,7 @@ if [ -f qtfred/cmake_install.cmake ]; then
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 -iname 'qtfred_*' -type f -printf "%f\n").AppImage"
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
+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"])}
+1 -1
View File
@@ -1,3 +1,3 @@
Mako==1.3.12
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()
+2
View File
@@ -11,6 +11,8 @@ 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()
@@ -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/)
+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}")
+11 -4
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,12 @@ if (MSVC_RELEASE_DEBUGGING)
endif()
endif()
# This should be kept in sync with the corresponding IF() in fred2/CMakeLists.txt
IF(MSVC_USE_RUNTIME_DLL OR FSO_BUILD_QTFRED)
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()
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<NOT:$<CONFIG:Release>>:Debug>")
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
ENDIF()
# Debug
+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)
+2 -2
View File
@@ -6,8 +6,8 @@ if (EXISTS "${PROJECT_SOURCE_DIR}/version_override.cmake")
include("${PROJECT_SOURCE_DIR}/version_override.cmake")
endif()
set_if_not_defined(FSO_VERSION_MAJOR 25)
set_if_not_defined(FSO_VERSION_MINOR 1)
set_if_not_defined(FSO_VERSION_MAJOR 26)
set_if_not_defined(FSO_VERSION_MINOR 3)
set_if_not_defined(FSO_VERSION_BUILD 0)
# A numeric revision e.g. the CI build number
+8 -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,9 +76,8 @@ 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)
target_link_libraries(code PUBLIC implot)
IF(FSO_BUILD_WITH_OPENXR)
target_link_libraries(code PUBLIC OpenXR::openxr_loader)
@@ -113,16 +112,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 +131,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
@@ -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
}
}
+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();
}
+8 -5
View File
@@ -295,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
@@ -494,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;
@@ -525,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);
@@ -535,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);
@@ -597,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);
@@ -618,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);
+17
View File
@@ -176,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,
@@ -191,6 +197,17 @@ namespace AI {
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
};
+42 -6
View File
@@ -140,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
{
@@ -412,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);
@@ -450,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);
@@ -505,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);
@@ -622,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);
}
}
@@ -677,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:")) {
@@ -691,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);
}
@@ -749,6 +759,19 @@ void parse_ai_profiles_tbl(const char *filename)
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 ----------------------------------------
@@ -849,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;
@@ -975,4 +1000,15 @@ void ai_profile_t::reset()
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);
}
}
+3 -1
View File
@@ -143,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()
@@ -161,7 +163,7 @@ 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();
+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;
+321 -166
View File
File diff suppressed because it is too large. Load diff
View File
Whitespace-only changes.
+63 -11
View File
@@ -911,7 +911,24 @@ 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;
@@ -927,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 );
}
}
}
@@ -1253,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;
@@ -1766,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)
{
@@ -2056,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
}
}
@@ -2105,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();
@@ -2163,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
}
}
@@ -2177,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();
@@ -2233,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;
}
@@ -2498,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);
+52 -94
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);
@@ -1095,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,
@@ -1728,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);
@@ -1754,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
@@ -1838,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;
@@ -1850,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);
@@ -1876,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;
}
@@ -1997,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));
@@ -2061,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 );
}
}
@@ -2137,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.
@@ -2188,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);
}
}
}
@@ -2361,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
@@ -2371,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.
+3 -3
View File
@@ -1439,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;
@@ -1693,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
}
+56 -42
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"
@@ -83,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.
@@ -144,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;
}
}
@@ -190,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));
@@ -198,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();
}
@@ -599,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;
}
@@ -871,6 +883,16 @@ const char *bm_get_filename(int handle) {
return entry->filename;
}
const char *bm_get_image_filename(int handle) {
auto entry = bm_get_entry(handle);
// an EFF entry stores the file of its individual image separately
if (entry->type == BM_TYPE_EFF)
return entry->info.ani.eff.filename;
return entry->filename;
}
void bm_get_filename(int bitmapnum, char *filename) {
if (!bm_is_valid(bitmapnum)) {
strcpy(filename, "");
@@ -1024,6 +1046,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;
@@ -1210,7 +1239,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;
}
@@ -3462,37 +3491,22 @@ 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;
+15
View File
@@ -436,6 +436,13 @@ void bm_get_filename(int bitmapnum, char *filename);
*/
const char *bm_get_filename(int handle);
/**
* @brief Gets the name of the file that holds the pixels of this bitmap
*
* This is the same as bm_get_filename(), except for an EFF. For an EFF it is the file of the individual image.
*/
const char *bm_get_image_filename(int handle);
/**
* @brief Unloads all used bitmaps, should only ever be called by game_shutdown()
*
@@ -594,6 +601,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)
*/
+43 -25
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,21 @@ 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},
@@ -90,25 +109,6 @@ auto FovOption = options::OptionBuilder<float>("Graphics.FOV",
.parser(parse_fov_func)
.finish();
bool Use_cockpit_fov = false;
// 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_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},
@@ -116,17 +116,35 @@ auto CockpitFovOption = options::OptionBuilder<float>("Graphics.CockpitFOV",
.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*************************
+1 -1
View File
@@ -249,7 +249,7 @@ void photo_mode_compute_hud_layout()
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", 1285));
snprintf(buf, sizeof(buf), "%s: %s", XSTR(param.label, param.xstr_id), XSTR("Off", 1286));
}
measure(buf);
}
+94 -19
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] = "";
@@ -85,7 +85,7 @@ cf_pathtype Pathtypes[CF_MAX_PATH_TYPES] = {
{ 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 .qch .css .png .jpg", CF_TYPE_DATA }
{ CF_TYPE_FREDDOCS, "data" DIR_SEPARATOR_STR "freddocs", ".html .css .png .jpg", CF_TYPE_DATA }
};
// clang-format on
@@ -185,7 +185,12 @@ 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';
@@ -832,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;
}
@@ -1105,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
+221 -71
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
@@ -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();
@@ -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,15 @@ 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 feeds extra
// stat groups into the ImGui frame profiler overlay (see gr_get_debug_stats),
// which are unrelated to GPU sync validation. 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 +2475,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();
+19 -23
View File
@@ -739,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]);
@@ -770,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();
@@ -804,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)
@@ -873,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) {
@@ -911,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) {
@@ -975,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]);
@@ -1097,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();
@@ -1322,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()) {
@@ -1353,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
@@ -1778,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()) {
@@ -1791,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
@@ -1805,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
@@ -1813,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
@@ -1826,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()) {
@@ -1838,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
@@ -3120,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;
}
}
+18 -10
View File
@@ -572,20 +572,28 @@ 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;
};
/*!
+64 -31
View File
@@ -292,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));
@@ -682,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;
}
@@ -1733,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
@@ -2388,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 {
@@ -2518,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
@@ -2609,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;
}
@@ -2639,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 {
@@ -2730,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:
@@ -2965,23 +2999,22 @@ CCI_builder& CCI_builder::operator()(IoActionId action_id, short primary, short
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);
}
+3 -3
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;
+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));
}
+114 -10
View File
@@ -1,18 +1,12 @@
#include "ddsutils/ddsutils.h"
#include "cfile/cfile.h"
#include "graphics/2d.h"
#include "osapi/osregistry.h"
#ifdef USE_OPENGL_ES
#include "graphics/opengl/es_compatibility.h"
#endif
#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;
#endif
#define BCDEC_IMPLEMENTATION 1
PUSH_SUPPRESS_WARNINGS
#include "ddsutils/bcdec.h"
@@ -60,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;
@@ -226,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);
}
@@ -552,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);
+4 -4
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);
@@ -337,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);
}
}
}
@@ -1152,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;
+9 -7
View File
@@ -489,13 +489,15 @@ void dc_putc(char c)
}
bool handle_textInputEvent(const SDL_Event& event) {
if (event.text.text[0] != '\0' && event.text.text[0] != '\1') {
for (char c : event.text.text) {
if (c < 32)
const char *p = event.text.text;
if (*p != '\0' && *p != '\1') {
for (; *p; ++p) {
if (*p < 32)
break;
if (dc_command_buf.size() < MAX_CLI_LEN) {
dc_command_buf.push_back(c);
dc_command_buf.push_back(*p);
}
}
}
@@ -515,8 +517,8 @@ void debug_console(void (*_func)(void))
dc_init();
}
auto textListener = os::events::addEventListener(SDL_TEXTINPUT, 10000, &handle_textInputEvent);
SDL_StartTextInput();
auto textListener = os::events::addEventListener(SDL_EVENT_TEXT_INPUT, 10000, &handle_textInputEvent);
SDL_StartTextInput(os::getSDLMainWindow());
dc_draw(TRUE);
@@ -618,7 +620,7 @@ void debug_console(void (*_func)(void))
dc_draw(TRUE);
}
SDL_StopTextInput();
SDL_StopTextInput(os::getSDLMainWindow());
os::events::removeEventListener(textListener);
while( key_inkey() ) {
+26 -4
View File
@@ -228,7 +228,7 @@ bool Decal::isValid() const {
return false;
}
} else {
Assertion(false, "Only ships are currently supported for decals!");
UNREACHABLE("Only ships are currently supported for decals!");
return false;
}
@@ -267,7 +267,7 @@ float smoothstep(float edge0, float edge1, float x) {
namespace decals {
void initialize() {
if (gr_screen.mode == GR_STUB) {
if (gr_screen.mode == GraphicsAPI::Stub) {
Decal_system_active = false;
return;
}
@@ -361,6 +361,10 @@ void pageInDecal(const creation_info& info) {
DecalDefinitions[info.definition_handle].pageIn();
}
bool decalSystemActive() {
return Decal_system_active && Decal_option_active && gr_is_capable(gr_capability::CAPABILITY_INSTANCED_RENDERING);
}
void initializeMission() {
active_decals.clear();
}
@@ -459,7 +463,7 @@ inline static void renderDecal(graphics::decal_draw_list& draw_list, const Decal
}
void renderAll() {
if (!Decal_system_active || !Decal_option_active || !gr_is_capable(gr_capability::CAPABILITY_INSTANCED_RENDERING)) {
if (!decalSystemActive()) {
return;
}
@@ -482,6 +486,24 @@ void renderAll() {
++iter;
}
for (auto iter = active_single_frame_decals.begin(); iter != active_single_frame_decals.end();) {
if (!iter->isValid()) {
// if we're sitting on the very last element, popping-back will invalidate the iterator!
if (iter + 1 == active_single_frame_decals.end()) {
active_single_frame_decals.pop_back();
break;
}
*iter = active_single_frame_decals.back();
active_single_frame_decals.pop_back();
continue;
}
// next decal, only increment the iterator if we found a valid value so nothing gets skipped
// otherwise we may skip a decal which can then get through to the draw_list loop while being invalid
++iter;
}
if (active_decals.empty() && active_single_frame_decals.empty()) {
return;
}
@@ -542,7 +564,7 @@ void addDecal(creation_info& info, const object* host, int submodel, const vec3d
}
void addSingleFrameDecal(Decal&& info) {
active_single_frame_decals.push_back(info);
active_single_frame_decals.push_back(std::move(info));
}
void invalidateForShip(const ship* shipp) {
+9 -3
View File
@@ -5,6 +5,7 @@
#include "object/object.h"
#include "utils/RandomRange.h"
#include "utils/reset_on_move.h"
namespace decals {
@@ -20,9 +21,12 @@ class DecalDefinition {
SCP_string _normalMapFilename;
bool _loopNormal = true;
int _diffuseBitmap = -1;
int _glowBitmap = -1;
int _normalBitmap = -1;
// reset_on_move so that the defaulted move operations below transfer the
// handles; the destructor bm_release()s them, so a moved-from element must
// not keep aliasing live handles
util::reset_on_move<int, -1> _diffuseBitmap;
util::reset_on_move<int, -1> _glowBitmap;
util::reset_on_move<int, -1> _normalBitmap;
public:
explicit DecalDefinition(SCP_string name);
@@ -129,6 +133,8 @@ void loadBitmaps(const creation_info& info);
*/
void pageInDecal(const creation_info& info);
bool decalSystemActive();
/**
* @brief Shut down the decal system. Must be called from game_shutdown
*/
+18 -2
View File
@@ -1,17 +1,33 @@
#ifdef GL_ES
precision highp float;
precision highp sampler2DArray;
#endif
#include "gamma.sdr"
layout(location=0) out vec4 fragOut0;
#ifdef VULKAN
layout (location = 0) in vec4 fragTexCoord;
layout (location = 1) in vec4 fragColor;
layout (set = 1, binding = 1) uniform sampler2DArray baseMap;
layout (set = 2, binding = 0, std140) uniform genericData {
vec4 color;
float intensity;
};
#else
in vec4 fragTexCoord;
in vec4 fragColor;
out vec4 fragOut0;
uniform sampler2DArray baseMap;
layout (std140) uniform genericData {
vec4 color;
float intensity;
};
#endif
void main()
{
+23
View File
@@ -1,3 +1,25 @@
#ifdef GL_ES
precision highp float;
#endif
#ifdef VULKAN
layout (location = 0) in vec4 vertPosition;
layout (location = 1) in vec4 vertColor;
layout (location = 2) in vec4 vertTexCoord;
layout (location = 0) out vec4 fragTexCoord;
layout (location = 1) out vec4 fragColor;
layout (set = 2, binding = 1, std140) uniform matrixData {
mat4 modelViewMatrix;
mat4 projMatrix;
};
layout (set = 2, binding = 0, std140) uniform genericData {
vec4 color;
float intensity;
};
#else
in vec4 vertPosition;
in vec4 vertTexCoord;
in vec4 vertColor;
@@ -14,6 +36,7 @@ layout (std140) uniform genericData {
vec4 color;
float intensity;
};
#endif
void main()
{
+17 -3
View File
@@ -1,11 +1,25 @@
#ifdef GL_ES
precision highp float;
precision highp sampler2D;
#endif
layout(location=0) out vec4 fragOut0;
#ifdef VULKAN
layout(location = 0) in vec2 fragTexCoord;
layout(set = 1, binding = 1) uniform sampler2D bloomed;
#else
in vec4 fragTexCoord;
out vec4 fragOut0;
uniform sampler2D bloomed;
#endif
layout (std140) uniform genericData {
#ifdef VULKAN
layout(std140, set = 2, binding = 0)
#else
layout(std140)
#endif
uniform genericData {
float bloom_intensity;
int levels;
};
+21 -5
View File
@@ -1,16 +1,32 @@
out vec4 fragOut0;
#ifdef GL_ES
precision highp float;
precision highp int;
precision highp sampler2D;
#endif
layout(location = 0) out vec4 fragOut0;
#ifdef VULKAN
layout(location = 0) in vec2 fragTexCoord;
layout(set = 1, binding = 1) uniform sampler2D tex;
#else
in vec4 fragTexCoord;
uniform sampler2D tex;
#endif
const float TapSize = 1.0;
layout (std140) uniform genericData {
#ifdef VULKAN
layout(set = 2, binding = 0, std140)
#else
layout(std140)
#endif
uniform genericData {
float texSize;
int level;
int pad[2];
};
const float TapSize = 1.0;
// Gaussian Blur
// 2 passes required
void main()
+13 -1
View File
@@ -1,6 +1,18 @@
#ifdef GL_ES
precision highp float;
precision highp sampler2D;
#endif
layout(location = 0) out vec4 fragOut0;
#ifdef VULKAN
layout(location = 0) in vec2 fragTexCoord;
layout(set = 1, binding = 1) uniform sampler2D tex;
#else
in vec4 fragTexCoord;
out vec4 fragOut0;
uniform sampler2D tex;
#endif
const float Luminance = 0.08;
const float fMiddleGray = 0.2;
const float fWhiteCutoff = 0.4;
+21 -3
View File
@@ -1,17 +1,35 @@
in vec4 fragTexCoord;
out vec4 fragOut0;
#ifdef GL_ES
precision highp float;
precision highp sampler2D;
precision highp sampler2DArray;
#endif
layout(location = 0) out vec4 fragOut0;
#ifdef VULKAN
layout(location = 0) in vec2 fragTexCoord;
layout(set = 1, binding = 1) uniform sampler2D tex;
#else
in vec4 fragTexCoord;
#ifdef COPY_ARRAY
uniform sampler2DArray tex;
#else
uniform sampler2D tex;
#endif
#endif
void main()
{
#ifdef COPY_ARRAY
#if defined(COPY_ARRAY) && !defined(VULKAN)
fragOut0 = texture(tex, vec3(fragTexCoord.xy, 0));
#else
fragOut0 = texture(tex, fragTexCoord.xy);
#endif
#ifdef CLAMP01
// Used to derive a [0,1]-bounded proxy from an extended-range HDR source,
// e.g. for FXAA/SMAA edge detection when the real output carries values
// above 1.0.
fragOut0 = min(fragOut0, vec4(1.0));
#endif
}
+77 -13
View File
@@ -1,3 +1,9 @@
#ifdef GL_ES
precision highp float;
precision highp int;
precision highp sampler2D;
precision highp sampler2DArray;
#endif
// NOTE: The technique and some of this code is based on this tutorial:
// http://martindevans.me/game-development/2015/02/27/Drawing-Stuff-On-Other-Stuff-With-Deferred-Screenspace-Decals/
@@ -6,9 +12,37 @@
#include "normals.sdr"
#include "gamma.sdr"
out vec4 fragOut0; // Diffuse buffer
out vec4 fragOut1; // Normal buffer
out vec4 fragOut2; // Emissive buffer
#ifdef VULKAN
// G-buffer: 6 attachments, 1/3/5 are write-masked by pipeline blend state
layout (location = 0) out vec4 fragOut0; // Color/Diffuse
layout (location = 1) out vec4 fragOut1; // Position (masked)
layout (location = 2) out vec4 fragOut2; // Normal
layout (location = 3) out vec4 fragOut3; // Specular (masked)
layout (location = 4) out vec4 fragOut4; // Emissive
layout (location = 5) out vec4 fragOut5; // Composite (masked)
layout (location = 0) flat in mat4 invModelMatrix; // locations 0-3
layout (location = 4) flat in vec3 decalDirection;
layout (location = 5) flat in float normal_angle_cutoff;
layout (location = 6) flat in float angle_fade_start;
layout (location = 7) flat in float alpha_scale;
layout (set = 1, binding = 1) uniform sampler2DArray decalTextures;
#define diffuseMap decalTextures
#define glowMap decalTextures
#define normalMap decalTextures
layout (set = 1, binding = 4) uniform sampler2D gDepthBuffer;
layout (set = 1, binding = 6) uniform sampler2D gNormalBuffer;
#else
layout (location=0) out vec4 fragOut0; // Diffuse buffer
#ifndef GL_ES
layout (location=1) out vec4 fragOut1; // Normal buffer
layout (location=2) out vec4 fragOut2; // Emissive buffer
#else
layout (location=2) out vec4 fragOut1; // Normal buffer -> COLOR_ATTACHMENT2
layout (location=4) out vec4 fragOut2; // Emissive buffer -> COLOR_ATTACHMENT4
#endif
flat in mat4 invModelMatrix;
flat in vec3 decalDirection;
@@ -22,8 +56,14 @@ uniform sampler2D gNormalBuffer;
uniform sampler2DArray diffuseMap;
uniform sampler2DArray glowMap;
uniform sampler2DArray normalMap;
#endif
layout (std140) uniform decalGlobalData {
#ifdef VULKAN
layout (set = 1, binding = 2, std140)
#else
layout (std140)
#endif
uniform decalGlobalData {
mat4 viewMatrix;
mat4 projMatrix;
mat4 invViewMatrix;
@@ -32,7 +72,12 @@ layout (std140) uniform decalGlobalData {
vec2 viewportSize;
};
layout (std140) uniform decalInfoData {
#ifdef VULKAN
layout (set = 2, binding = 3, std140)
#else
layout (std140)
#endif
uniform decalInfoData {
int diffuse_index;
int glow_index;
int normal_index;
@@ -45,8 +90,16 @@ vec3 computeViewPosition(vec2 textureCoord) {
vec4 clipSpaceLocation;
vec2 normalizedCoord = textureCoord / viewportSize;
#ifdef VULKAN
clipSpaceLocation.x = normalizedCoord.x * 2.0 - 1.0;
// Vulkan negative viewport inverts Y mapping
clipSpaceLocation.y = 1.0 - normalizedCoord.y * 2.0;
// Vulkan depth is [0,1] — use directly
clipSpaceLocation.z = texelFetch(gDepthBuffer, ivec2(textureCoord), 0).r;
#else
clipSpaceLocation.xy = normalizedCoord * 2.0f - 1.0f;
clipSpaceLocation.z = texelFetch(gDepthBuffer, ivec2(textureCoord), 0).r * 2.0f - 1.0f;
#endif
clipSpaceLocation.w = 1.0f;
vec4 homogenousLocation = invProjMatrix * clipSpaceLocation;
@@ -57,24 +110,24 @@ vec3 computeViewPosition(vec2 textureCoord) {
vec3 getPixelNormal(vec3 frag_position, vec2 tex_coord, inout float alpha, out vec3 binormal, out vec3 tangent) {
#ifdef USE_NORMAL_MAP
// If we can then we just use the existing normal buffer
vec3 normal = texelFetch(gNormalBuffer, ivec2(tex_coord), 0).xyz;
vec3 normal = texelFetch(gNormalBuffer, ivec2(tex_coord), 0).xyz;
// If we use the normal map then we don't really need these values so we don't need to compute them here
binormal = vec3(0.0);
tangent = vec3(0.0);
binormal = vec3(0.0);
tangent = vec3(0.0);
#else
// Use some fancy screen-space derivates to determine the normal of the current pixel by looking at the surrounding pixels
vec3 pos_dx = dFdx(frag_position);
vec3 pos_dy = dFdy(frag_position);
vec3 normal = normalize(cross(pos_dx, pos_dy));
vec3 normal = normalize(cross(pos_dx, pos_dy));
binormal = normalize(pos_dx);
tangent = normalize(pos_dy);
binormal = normalize(pos_dx);
tangent = normalize(pos_dy);
#endif
//Calculate angle between surface normal and decal direction
float angle = acos(dot(normal, decalDirection));
float angle = acos(clamp(dot(normal, decalDirection), -1.0, 1.0));
if (angle > normal_angle_cutoff) {
// The angle between surface normal and decal direction is too big
@@ -82,7 +135,7 @@ vec3 getPixelNormal(vec3 frag_position, vec2 tex_coord, inout float alpha, out v
}
// Make a smooth alpha transition leading up to an edge
alpha = alpha * (1 - smoothstep(angle_fade_start, normal_angle_cutoff, angle));
alpha = alpha * (1.0 - smoothstep(angle_fade_start, normal_angle_cutoff, angle));
return normal;
}
@@ -162,7 +215,18 @@ void main() {
normal_out = tangentToView * decalNormal * alpha;
}
#ifdef VULKAN
// 6-attachment G-buffer; attachments 1/3/5 are write-masked
fragOut0 = diffuse_out;
fragOut1 = vec4(0.0);
fragOut2 = vec4(normal_out, 0.0);
fragOut3 = vec4(0.0);
fragOut4 = emissive_out;
fragOut5 = vec4(0.0);
#else
// OpenGL: 3 draw buffers mapped to attachments 0/2/4
fragOut0 = diffuse_out;
fragOut1 = vec4(normal_out, 0.0);
fragOut2 = emissive_out;
#endif
}
+38 -2
View File
@@ -1,4 +1,24 @@
#ifdef GL_ES
precision highp float;
precision highp int;
#endif
#ifdef VULKAN
// Binding 0: box vertex positions
layout (location = 0) in vec4 vertPosition;
// Binding 1: per-instance model matrix (mat4 = 4 vec4s at locations 8-11)
layout (location = 8) in vec4 vertModelMatrix0;
layout (location = 9) in vec4 vertModelMatrix1;
layout (location = 10) in vec4 vertModelMatrix2;
layout (location = 11) in vec4 vertModelMatrix3;
layout (location = 0) flat out mat4 invModelMatrix; // locations 0-3
layout (location = 4) flat out vec3 decalDirection;
layout (location = 5) flat out float normal_angle_cutoff;
layout (location = 6) flat out float angle_fade_start;
layout (location = 7) flat out float alpha_scale;
#else
in vec4 vertPosition;
in mat4 vertModelMatrix;
@@ -7,8 +27,14 @@ flat out vec3 decalDirection;
flat out float normal_angle_cutoff;
flat out float angle_fade_start;
flat out float alpha_scale;
#endif
layout (std140) uniform decalGlobalData {
#ifdef VULKAN
layout (set = 1, binding = 2, std140)
#else
layout (std140)
#endif
uniform decalGlobalData {
mat4 viewMatrix;
mat4 projMatrix;
mat4 invViewMatrix;
@@ -17,7 +43,12 @@ layout (std140) uniform decalGlobalData {
vec2 viewportSize;
};
layout (std140) uniform decalInfoData {
#ifdef VULKAN
layout (set = 2, binding = 3, std140)
#else
layout (std140)
#endif
uniform decalInfoData {
int diffuse_index;
int glow_index;
int normal_index;
@@ -27,6 +58,11 @@ layout (std140) uniform decalInfoData {
};
void main() {
#ifdef VULKAN
// Reconstruct per-instance model matrix from 4 vec4 columns
mat4 vertModelMatrix = mat4(vertModelMatrix0, vertModelMatrix1, vertModelMatrix2, vertModelMatrix3);
#endif
normal_angle_cutoff = vertModelMatrix[0][3];
angle_fade_start = vertModelMatrix[1][3];
alpha_scale = vertModelMatrix[2][3];
@@ -0,0 +1,69 @@
#ifdef GL_ES
#extension GL_EXT_clip_cull_distance : enable
precision highp float;
precision highp int;
precision highp sampler2DArray;
#endif
#ifdef VULKAN
layout(set = 2, binding = 0, std140)
#else
layout(std140)
#endif
uniform genericData {
mat4 modelMatrix;
vec4 color;
vec4 clipEquation;
int baseMapIndex;
int alphaTexture;
int noTexturing;
int srgb;
float intensity;
float alphaThreshold;
uint clipEnabled;
};
layout(location = 0) out vec4 fragOut0;
#ifdef VULKAN
layout(set = 1, binding = 1) uniform sampler2DArray baseMap;
layout(location = 0) in vec4 fragTexCoord;
layout(location = 1) in vec4 fragColor;
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
layout(location = 2) in float vClipDist;
#endif
#else
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
in float vClipDist;
#endif
uniform sampler2DArray baseMap;
in vec4 fragTexCoord;
in vec4 fragColor;
#endif
#include "gamma.sdr"
void main()
{
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
if (clipEnabled != 0u && vClipDist < 0.0) {
discard;
}
#endif
vec4 baseColor = texture(baseMap, vec3(fragTexCoord.xy, float(baseMapIndex)));
if(alphaThreshold > baseColor.a) discard;
baseColor.rgb = (srgb == 1) ? srgb_to_linear(baseColor.rgb) : baseColor.rgb;
vec4 blendColor = (srgb == 1) ? vec4(srgb_to_linear(fragColor.rgb), fragColor.a) : fragColor;
fragOut0 = mix(mix(baseColor * blendColor, vec4(blendColor.rgb, baseColor.r * blendColor.a), float(alphaTexture)), blendColor, float(noTexturing)) * intensity;
}
@@ -0,0 +1,84 @@
#ifdef GL_ES
#extension GL_EXT_clip_cull_distance : enable
precision highp float;
precision highp int;
#endif
#ifdef VULKAN
layout(set = 2, binding = 0, std140)
#else
layout(std140)
#endif
uniform genericData {
mat4 modelMatrix;
vec4 color;
vec4 clipEquation;
int baseMapIndex;
int alphaTexture;
int noTexturing;
int srgb;
float intensity;
float alphaThreshold;
uint clipEnabled;
};
#ifdef VULKAN
layout(set = 2, binding = 1, std140)
#else
layout(std140)
#endif
uniform matrixData {
mat4 modelViewMatrix;
mat4 projMatrix;
};
#ifdef VULKAN
layout(location = 0) in vec4 vertPosition;
layout(location = 1) in vec4 vertColor;
layout(location = 2) in vec4 vertTexCoord;
layout(location = 0) out vec4 fragTexCoord;
layout(location = 1) out vec4 fragColor;
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
layout(location = 2) out float vClipDist;
#endif
#else
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
out float vClipDist;
#endif
out vec4 fragTexCoord;
in vec4 vertTexCoord;
out vec4 fragColor;
in vec4 vertColor;
in vec4 vertPosition;
#endif
void main()
{
fragTexCoord = vertTexCoord;
fragColor = vertColor * color;
gl_Position = projMatrix * modelViewMatrix * vertPosition;
if (clipEnabled != 0u) {
#ifndef SDR_FLAG_SW_CLIP_FALLBACK
gl_ClipDistance[0] = dot(clipEquation, modelMatrix * vertPosition);
#else
vClipDist = dot(clipEquation, modelMatrix * vertPosition);
#endif
} else {
#ifndef SDR_FLAG_SW_CLIP_FALLBACK
gl_ClipDistance[0] = 1.0;
#else
vClipDist = 1.0;
#endif
}
}
@@ -1,9 +1,14 @@
out vec4 fragOut0;
out vec4 fragOut1;
out vec4 fragOut2;
out vec4 fragOut3;
out vec4 fragOut4;
out vec4 fragOut5;
#ifdef GL_ES
precision highp float;
#endif
layout (location=0) out vec4 fragOut0;
layout (location=1) out vec4 fragOut1;
layout (location=2) out vec4 fragOut2;
layout (location=3) out vec4 fragOut3;
layout (location=4) out vec4 fragOut4;
layout (location=5) out vec4 fragOut5;
void main()
{
fragOut0 = vec4(0.0, 0.0, 0.0, 1.0); // color
+135 -36
View File
@@ -1,45 +1,110 @@
//? #version 150
#ifdef GL_ES
precision highp float;
precision highp int;
precision highp sampler2D;
precision highp samplerCube;
precision highp sampler2DArrayShadow;
#endif
#include "lighting.sdr" //! #include "lighting.sdr"
#include "gamma.sdr" //! #include "gamma.sdr"
#include "shadows.sdr" //! #include "shadows.sdr"
#include "z-compress.sdr" //! #include "z-compress.sdr"
out vec4 fragOut0;
#ifndef GL_ES
layout(location = 0) out vec4 fragOut0;
#else
layout(location = 5) out vec4 fragOut0; //COLOR_ATTACHMENTn is on location n in ES, here it uses COLOR_ATTACHMENT5
#endif
#ifdef VULKAN
layout(set = 1, binding = 1) uniform sampler2D sTextures[16];
#define ColorBuffer sTextures[0]
#define NormalBuffer sTextures[1]
#define PositionBuffer sTextures[2]
#define SpecBuffer sTextures[3]
layout(set = 0, binding = 2) uniform sampler2DArrayShadow shadow_map;
layout(set = 0, binding = 7) uniform sampler2DArray shadow_map_raw;
#ifdef ENV_MAP
layout(set = 0, binding = 3) uniform samplerCube sEnvmap;
layout(set = 0, binding = 4) uniform samplerCube sIrrmap;
#endif
#if defined(RT_SHADOWS) || defined(RTAO)
layout(set = 0, binding = 5) uniform accelerationStructureEXT shadow_tlas;
#endif
#else
uniform sampler2D ColorBuffer;
uniform sampler2D NormalBuffer;
uniform sampler2D PositionBuffer;
uniform sampler2D SpecBuffer;
uniform sampler2DArray shadow_map;
uniform sampler2DArrayShadow shadow_map;
uniform sampler2DArray shadow_map_raw;
#ifdef ENV_MAP
uniform samplerCube sEnvmap;
uniform samplerCube sIrrmap;
#endif
#endif
layout (std140) uniform globalDeferredData {
mat4 shadow_mv_matrix;
mat4 shadow_proj_matrix[4];
#ifdef VULKAN
layout(set = 0, binding = 1, std140)
#else
layout(std140)
#endif
uniform globalDeferredData {
mat4 inv_view_matrix;
float veryneardist;
float neardist;
float middist;
float fardist;
float invScreenWidth;
float invScreenHeight;
float nearPlane;
};
layout (std140) uniform matrixData {
#ifdef VULKAN
layout(set = 0, binding = 6, std140)
#else
layout(std140)
#endif
uniform shadowCascadeParams {
int cascade_offset;
int cascade_count;
float rtShadowBiasMin;
float rtShadowBiasMax;
int rtShadowSampleCount;
int rtaoSampleCount;
float rtaoRadius;
float rtaoStrength;
mat4 shadow_mv_matrix;
// See shadow_cascade_static_data (uniform_structs.h).
vec3 shadow_ray_view_origin;
int shadow_ray_cull_mask;
mat4 shadow_proj_matrix[NUM_SHADOW_CASCADES];
vec4 cascade_distances[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
vec4 smoothness_factors[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
vec4 penumbra_scale[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
};
#ifdef VULKAN
layout(set = 2, binding = 1, std140)
#else
layout(std140)
#endif
uniform matrixData {
mat4 modelViewMatrix;
mat4 projMatrix;
};
layout (std140) uniform lightData {
#ifdef VULKAN
layout(set = 0, binding = 0, std140)
#else
layout(std140)
#endif
uniform lightData {
vec3 diffuseLightColor;
float coneAngle;
@@ -47,18 +112,18 @@ layout (std140) uniform lightData {
float coneInnerAngle;
vec3 coneDir;
bool dualCone;
int dualCone;
vec3 scale;
float lightRadius;
int lightType;
bool enable_shadows;
int enable_shadows;
float sourceRadius;
};
// Nearest point sphere and tube light calculations taken from
// Nearest point sphere and tube light calculations taken from
// "Real Shading in Unreal Engine 4" by Brian Karis, Epic Games
// Part of SIGGRAPH 2013 Course: Physically Based Shading in Theory and Practice
@@ -66,7 +131,7 @@ vec3 ExpandLightSize(in vec3 lightDir, in vec3 reflectDir) {
// There's an extra max(...,sourceRadius) call here vs the version in the paper.
// This prevents the centerToRay calculation from choosing a point behind
// the reflection ray's origin (i.e. underneath the surface).
// this is necessary for situations where the fragment being shaded lies inside
// this is necessary for situations where the fragment being shaded lies inside
// the sourceRadius of the light.
// Instead, we choose a point suffciently far away from the reflection origin (hence max(...,sourceRadius))
// so that we have a gradual transition as the shaded fragments along the surface
@@ -74,12 +139,13 @@ vec3 ExpandLightSize(in vec3 lightDir, in vec3 reflectDir) {
vec3 centerToRay = max(dot(lightDir, reflectDir),sourceRadius) * reflectDir - lightDir;
return lightDir + centerToRay * clamp(sourceRadius/length(centerToRay), 0.0, 1.0);
}
void GetLightInfo(vec3 position, in float alpha, in vec3 reflectDir, out vec3 lightDirOut, out float attenuation, out float area_normalisation)
void GetLightInfo(vec3 position, in float alpha, in vec3 reflectDir, out vec3 lightDirOut, out float attenuation, out float area_normalisation, out float lightDist)
{
if (lightType == LT_DIRECTIONAL) {
lightDirOut = normalize(lightDir);
attenuation = 1.0;
area_normalisation = 1.0;
lightDist = RT_SHADOW_MAX_DISTANCE;
} else {
vec3 lightPosition = modelViewMatrix[3].xyz;
// Positional light source
@@ -92,8 +158,9 @@ void GetLightInfo(vec3 position, in float alpha, in vec3 reflectDir, out vec3 li
// move lightDirOut towards the ray defined by reflectDir
lightDirOut = ExpandLightSize(lightDirOut, reflectDir);
dist = length(lightDirOut);
lightDist = dist;
// Energy conservation term
float alpha_adjust = clamp(alpha + (sourceRadius/(2*dist)), 0.0, 1.0);
float alpha_adjust = clamp(alpha + (sourceRadius/(2.0*dist)), 0.0, 1.0);
area_normalisation = alpha/alpha_adjust;
area_normalisation *= area_normalisation;
//end chunk
@@ -134,8 +201,9 @@ void GetLightInfo(vec3 position, in float alpha, in vec3 reflectDir, out vec3 li
// Same principle as in LT_POINT, treat chosen location as a spherelight.
lightDirOut = ExpandLightSize(lightDirOut, reflectDir);
float dist = length(lightDirOut);
lightDist = dist;
// Energy conservation term
float alpha_adjust = min(alpha + (sourceRadius/(2*dist)), 1.0);
float alpha_adjust = min(alpha + (sourceRadius/(2.0*dist)), 1.0);
area_normalisation = alpha/alpha_adjust;
// don't need to square as it's a line rather than a sphere.
//end chunk
@@ -149,10 +217,11 @@ void GetLightInfo(vec3 position, in float alpha, in vec3 reflectDir, out vec3 li
lightDirOut = lightPosition - position.xyz;
float coneDot = dot(normalize(-lightDirOut), coneDir);
float dist = length(lightDirOut);
lightDist = dist;
attenuation = 1.0 - clamp(sqrt(dist / lightRadius), 0.0, 1.0);
area_normalisation = 1.0;
if(dualCone) {
if(dualCone != 0) {
if(abs(coneDot) < coneAngle) {
discard;
} else {
@@ -181,11 +250,11 @@ void ComputeEnvLight(float alpha, float ao, vec3 light_dir, vec3 eyeDir, vec3 no
// so let s = (2/alpha^2 - 2)
// 1/2 log(s + 1) = 1/2 log(2/alpha^2 -1)
const float ENV_REZ = 512; // Ideally this would be #define'd and shader recompiled with envmap rez changes
const float REZ_BIAS = log2(ENV_REZ * sqrt(3));
const float ENV_REZ = 512.0; // Ideally this would be #define'd and shader recompiled with envmap rez changes
const float REZ_BIAS = log2(ENV_REZ * sqrt(3.0));
float alphaSqr = alpha * alpha;
float rough_bias = 0.5 * log2(2/alphaSqr - 1);
float rough_bias = 0.5 * log2(2.0/alphaSqr - 1.0);
float mip_bias = REZ_BIAS - rough_bias;
// Sample light, using mip bias to blur it.
@@ -203,7 +272,7 @@ void ComputeEnvLight(float alpha, float ao, vec3 light_dir, vec3 eyeDir, vec3 no
// Pseudo-IBL, so use k_IBL
float k = alpha * alpha/ 2.0f;
float NdotL = max(dot(light_dir, normal),0);
float NdotL = max(dot(light_dir, normal),0.0);
float g1vNL = GeometrySchlickGGX(NdotL, k);
vec3 specEnvLighting = specEnvColour.rgb * fresnel * g1vNL;
@@ -241,6 +310,18 @@ void main()
if (lightType == LT_AMBIENT) {
float ao = position_buffer.w;
#ifdef RTAO
{
// Raytraced AO multiplies into the same ao term as the baked AO maps,
// so the env-map/IBL ambient below darkens consistently for free.
// Ray origin in TLAS-relative space, as for the shadow rays below.
vec3 worldPos = mat3(inv_view_matrix) * position + shadow_ray_view_origin;
vec3 worldNormal = normalize((inv_view_matrix * vec4(normal, 0.0)).xyz);
float rtaoBias = computeRtShadowBias(length(position), rtShadowBiasMin, rtShadowBiasMax);
ao *= traceAmbientOcclusion(shadow_tlas, worldPos, worldNormal,
rtaoRadius, rtaoStrength, rtaoSampleCount, rtaoBias, gl_FragCoord.xy);
}
#endif
fragmentColor.rgb = diffuseLightColor * diffColor * ao;
#ifdef ENV_MAP
@@ -255,24 +336,42 @@ void main()
vec3 lightDir;
float attenuation;
float area_normalisation;
GetLightInfo(position, alpha, reflectDir, lightDir, attenuation, area_normalisation);
float lightDist;
GetLightInfo(position, alpha, reflectDir, lightDir, attenuation, area_normalisation, lightDist);
if (enable_shadows) {
if (enable_shadows != 0) {
#ifdef RT_SHADOWS
// Ray origin in TLAS-relative space; see shadow_cascade_static_data (uniform_structs.h).
// Only the rotation of inv_view_matrix is used: its translation is the (large) camera
// world position, which shadow_ray_view_origin supplies already rebased onto the TLAS.
vec3 worldPos = mat3(inv_view_matrix) * position + shadow_ray_view_origin;
vec3 worldNormal = normalize((inv_view_matrix * vec4(normal, 0.0)).xyz);
vec3 worldLightDir = normalize((inv_view_matrix * vec4(lightDir, 0.0)).xyz);
float rtShadowBias = computeRtShadowBias(length(position), rtShadowBiasMin, rtShadowBiasMax);
// Penumbra cone: for directional lights sourceRadius IS the tangent of the
// sun's angular radius ($SunAngularSize); for local lights the subtended
// half-angle follows from the physical source radius and the light distance.
float coneTan = (lightType == LT_DIRECTIONAL)
? sourceRadius
: sourceRadius / max(lightDist, 1e-3);
attenuation *= traceShadowRayCone(shadow_tlas, worldPos, worldNormal, worldLightDir, lightDist,
rtShadowBias, uint(shadow_ray_cull_mask), coneTan, rtShadowSampleCount, gl_FragCoord.xy);
#else
vec4 fragShadowPos = shadow_mv_matrix * inv_view_matrix * vec4(position, 1.0);
vec4 fragShadowUV[4];
fragShadowUV[0] = transformToShadowMap(shadow_proj_matrix[0], 0, fragShadowPos);
fragShadowUV[1] = transformToShadowMap(shadow_proj_matrix[1], 1, fragShadowPos);
fragShadowUV[2] = transformToShadowMap(shadow_proj_matrix[2], 2, fragShadowPos);
fragShadowUV[3] = transformToShadowMap(shadow_proj_matrix[3], 3, fragShadowPos);
vec4 fragShadowUV[NUM_SHADOW_CASCADES];
for (int i = 0; i < NUM_SHADOW_CASCADES; i++) {
fragShadowUV[i] = transformToShadowMap(shadow_proj_matrix[i], i, fragShadowPos);
}
attenuation *= getShadowValue(shadow_map, -position.z, fragShadowPos.z, fragShadowUV, fardist, middist,
neardist, veryneardist);
attenuation *= getShadowValue(shadow_map, shadow_map_raw, -position.z, fragShadowUV, cascade_distances, smoothness_factors, penumbra_scale, cascade_offset, cascade_count);
#endif
}
vec3 halfVec = normalize(lightDir + eyeDir);
float NdotL = clamp(dot(normal, lightDir), 0.0, 1.0);
fragmentColor.rgb = computeLighting(specColor.rgb, diffColor, lightDir, normal.xyz, halfVec, eyeDir, roughness, fresnel, NdotL).rgb * diffuseLightColor * attenuation * area_normalisation;
}
fragOut0 = max(fragmentColor, vec4(0.0));
}
+28 -4
View File
@@ -1,14 +1,32 @@
#ifdef GL_ES
precision highp float;
precision highp int;
#endif
#include "lighting.sdr"
#ifdef VULKAN
layout(location = 0) in vec4 vertPosition;
#else
in vec4 vertPosition;
#endif
layout (std140) uniform matrixData {
#ifdef VULKAN
layout(set = 2, binding = 1, std140)
#else
layout(std140)
#endif
uniform matrixData {
mat4 modelViewMatrix;
mat4 projMatrix;
};
layout (std140) uniform lightData {
#ifdef VULKAN
layout(set = 0, binding = 0, std140)
#else
layout(std140)
#endif
uniform lightData {
vec3 diffuseLightColor;
float coneAngle;
@@ -16,13 +34,13 @@ layout (std140) uniform lightData {
float coneInnerAngle;
vec3 coneDir;
bool dualCone;
int dualCone;
vec3 scale;
float lightRadius;
int lightType;
bool enable_shadows;
int enable_shadows;
float sourceRadius;
};
@@ -30,7 +48,13 @@ layout (std140) uniform lightData {
void main()
{
if (lightType == LT_DIRECTIONAL || lightType == LT_AMBIENT) {
#ifdef VULKAN
// Fullscreen triangle from gl_VertexIndex (no vertex buffer)
vec2 pos = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
gl_Position = vec4(pos * 2.0 - 1.0, 0.0, 1.0);
#else
gl_Position = vec4(vertPosition.xyz, 1.0);
#endif
} else {
gl_Position = projMatrix * modelViewMatrix * vec4(vertPosition.xyz * scale, 1.0);
}
@@ -1,15 +1,37 @@
#ifdef GL_ES
precision highp float;
precision highp sampler2D;
precision highp sampler2DArray;
#endif
layout(location = 0) out vec4 fragOut0;
#ifdef VULKAN
layout(location = 0) in vec4 fragTexCoord;
layout(location = 1) in vec4 fragColor;
layout(location = 2) in float fragOffset;
layout(set = 1, binding = 1) uniform sampler2DArray baseMap;
layout(set = 1, binding = 4) uniform sampler2D depthMap;
layout(set = 1, binding = 5) uniform sampler2D frameBuffer;
layout(set = 1, binding = 6) uniform sampler2D distMap;
#else
in vec4 fragTexCoord;
in vec4 fragColor;
in float fragOffset;
out vec4 fragOut0;
uniform sampler2DArray baseMap;
uniform sampler2D depthMap;
uniform sampler2D distMap;
uniform sampler2D frameBuffer;
#endif
layout (std140) uniform genericData {
#ifdef VULKAN
layout(std140, set = 2, binding = 0)
#else
layout (std140)
#endif
uniform genericData {
float window_width;
float window_height;
float use_offset;
@@ -1,3 +1,17 @@
#ifdef GL_ES
precision highp float;
#endif
#ifdef VULKAN
layout(location = 0) in vec4 vertPosition;
layout(location = 1) in vec4 vertColor;
layout(location = 2) in vec4 vertTexCoord;
layout(location = 6) in float vertRadius;
layout(location = 0) out vec4 fragTexCoord;
layout(location = 1) out vec4 fragColor;
layout(location = 2) out float fragOffset;
#else
in vec4 vertPosition;
in vec4 vertTexCoord;
in vec4 vertColor;
@@ -5,13 +19,24 @@ in float vertRadius;
out vec4 fragTexCoord;
out vec4 fragColor;
out float fragOffset;
#endif
layout (std140) uniform matrixData {
#ifdef VULKAN
layout(std140, set = 2, binding = 1)
#else
layout (std140)
#endif
uniform matrixData {
mat4 modelViewMatrix;
mat4 projMatrix;
};
layout (std140) uniform genericData {
#ifdef VULKAN
layout(std140, set = 2, binding = 0)
#else
layout (std140)
#endif
uniform genericData {
float window_width;
float window_height;
float use_offset;
+31 -4
View File
@@ -1,18 +1,39 @@
#ifdef GL_ES
precision highp float;
precision highp int;
precision highp sampler2D;
precision highp sampler2DArray;
#endif
#include "gamma.sdr"
#include "z-compress.sdr"
layout(location = 0) out vec4 fragOut0;
#ifdef VULKAN
layout(location = 0) in vec4 fragTexCoord;
layout(location = 1) in vec4 fragColor;
layout(location = 2) in float fragRadius;
layout(location = 3) in vec4 fragPosition;
layout(set = 1, binding = 1) uniform sampler2DArray baseMap;
layout(set = 1, binding = 4) uniform sampler2D depthMap;
#else
in float fragRadius;
in vec4 fragPosition;
in vec4 fragTexCoord;
in vec4 fragColor;
out vec4 fragOut0;
uniform sampler2DArray baseMap;
uniform sampler2D depthMap;
#endif
layout (std140) uniform genericData {
#ifdef VULKAN
layout(std140, set = 2, binding = 0)
#else
layout(std140)
#endif
uniform genericData {
float window_width;
float window_height;
float nearZ;
@@ -39,7 +60,13 @@ void main()
float sceneDepthLinear;
float fragDepthLinear;
if ( linear_depth == 1 ) {
sceneDepthLinear = -uncompress_depth_value(sceneDepth.z);
// Position buffer .z == 0 means no deferred geometry was rendered at this pixel
// (G-buffer was cleared to zero). Treat as max distance so particles render fully.
if (sceneDepth.z == 0.0) {
sceneDepthLinear = farZ;
} else {
sceneDepthLinear = -uncompress_depth_value(sceneDepth.z);
}
fragDepthLinear = -fragPosition.z;
} else {
sceneDepthLinear = ( 2.0 * farZ * nearZ ) / ( farZ + nearZ - sceneDepth.x * (farZ-nearZ) );
Loaded 100 of 1247 files, more files were not shown because too many files have changed in this diff. Show more