Compare commits

...
Author SHA1 Message Date
SirKnightly c9bb68c124 Automated build commit 2026-07-03 23:22:47 +00:00
Goober5000 d15cadc47b Merge pull request #7537 from Goober5000/fix/defensive_unicode
more graceful degradation of invalid unicode
2026-06-20 20:50:27 -04:00
BMagnu 21b8035e63 Fix possibly negative pspew value init (#7541)
* Fix possibly negative pspew value init

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Fixes #7508.

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

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

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

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

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

Closes #7513

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-06-08 22:02:45 -04:00
Goober5000 60203b6bae std::move in a few more places (#7502)
Follow-up to #7308.
2026-06-05 11:36:50 +02:00
Goober5000andClaude Opus 4.7 f184997572 fix ship select roster scroll-up getting stuck after list shrinks (#7501)
After a drag-drop that consumed the last unit of a roster class, the
active list could shrink while SS_active_list_start was non-zero,
making items above the stuck offset (including default ships returned
to the pool, such as Seth fighters) unreachable: the up-arrow handler
short-circuits when size <= max_show without resetting the offset.

- Clamp the preserved scroll offset in ss_synch_interface() against
  SS_active_list_size - MAX_ICONS_ON_SCREEN.
- Harden common_scroll_down_pressed() to reset *start to 0 when the
  whole list fits on screen.

Fixes #6240

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
2026-06-05 11:36:29 +02:00
Mike NelsonandJohn Fernandez 880bfb11a7 Upgrade lua enumeration handling (#7423)
* upgrade lua enumeration handling

* some cleanup

* add lua enum descriptions

* minor fixes

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

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

* include name

---------

Co-authored-by: John Fernandez <[email protected]>
2026-06-05 11:33:42 +02:00
Mike Nelson f4d84ec47a Support Rearm Pool, Limits, and Other Features (#7249)
* framework for support ship rearm pool and flags

* scripting api

* FRED support

* QtFRED support

* do not save non-player-allowed weapons

* tvt and mission versioning

* fix variable name

* minor fixes and clarity

* oops

* add qtfred help docs

* fixes and cleanup

* clang and readability

* address feedback

* address feedback
2026-06-05 11:33:19 +02:00
Mike Nelson 31f77e5a32 Mines weapon type & weapon proximity triggers (#7461)
* new mine weapon type

* some fixes and cleanup

* fix some parsing issues

* make sure mines get plotted

* make mine spawned objects can get a correct target

* some cleanup and additional features

* chasing mines

* some Lua tweaks

* generalize proximity detonations

* minor tweaks

* collapse namespace

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

* refactor UI to be dynamic

* properly save textures and load them on apply

* clang

* address feedback

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

* implement timed autosave feature

* clang

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

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

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

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

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

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

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

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

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

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

Fixes #7206.

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

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

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

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

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

* prop and jump node duplication

* make sure objects on layers are properly registered

* waypoint duplication

* some cleanup

* address feedback

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

* Tidy up and rename old variable

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

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

Partial fix for #4152.

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

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

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

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

Signed-off-by: dependabot[bot] <[email protected]>
2026-05-30 23:55:54 +00:00
Goober5000 cb1cb5641d Merge pull request #7445 from scp-fs2open/dependabot/pip/ci/post/mako-1.3.12
Bump mako from 1.3.11 to 1.3.12 in /ci/post
2026-05-30 19:52:44 -04:00
BMagnu 6b43cabff6 Fix CTD when an interface sound is shared with a gameplay sound (#7488) 2026-05-30 23:45:22 +02:00
Goober5000 14b0edcb95 Merge pull request #7486 from Goober5000/fix/stats_alltime_kills
fix stats sync in multiplayer
2026-05-30 17:28:11 -04:00
Shivansps c3f9ce37e4 Default TTS to be disabled (#7485) 2026-05-30 08:16:55 +02:00
Goober5000 10288c16ad fix stats sync in multiplayer
A quick bugfix, thanks to Claude.  Make sure the end of the loop is correctly offset from the start.
2026-05-29 23:59:47 -04:00
Mike Nelson 2fcf1ba686 Lab camera snapping widget (#7400)
* camera control widget with snapping

* fresh brain, fresh tweaks

* clang
2026-05-30 05:46:43 +02:00
Shivansps 3bd934ee96 Add glClampColor() and GL_DEPTH24_STENCIL8 (#7478) 2026-05-29 09:44:52 +09:00
BMagnuandGoober5000 20c8417e8b Fullneb Revamp (#7352)
* change to new fog system

* Fix fog conversion issues

* remove unused old neb values

* Update scripting API

* temporary old-value round trip

* Rewrite nebula fog with four explicit parameters

Replace the legacy `+Fog Near Mult` / `+Fog Far Mult` model — which
depended on fixed engine constants — with four parameters that map
directly to renderer behavior:

  +Fog 1000m Visibility    fraction of light surviving 1000m of fog
  +Fog Near Distance       meters from camera where fog begins
  +Fog Skybox Clip Distance  max render distance for the skybox in fog
  +Fog Clip Distance       max render distance for the scene in fog

The deferred-lighting fog shader, material system, model renderer,
object sort, and the Lua mission API all consume the new parameters
directly. The legacy `Neb2_fog_near_mult` / `Neb2_fog_far_mult`
globals are removed.

Mission parsing accepts either the legacy keys or the modern keys.
Legacy values are converted to the modern set at parse time, and a
per-mission flag (`Neb2_fog_use_legacy_values`) records which keys to
write back on save. The flag is cleared only when something actually
changes, so opening and OK'ing an unmodified legacy mission preserves
its on-disk format. Saving uses `optional_string_fred` to preserve
user comments through edit cycles.

FRED2 and QtFRED background editors both expose the four parameters
in place of the old multipliers, with friendly labels and tooltips.
Both editors validate input (visibility clamped to [0, 1]; near
distance must be > 0; skybox and scene clip distances may be 0 to
disable fog at that range) and use a snapshot-and-compare at close
time so that a no-op open-then-OK never spuriously rewrites a legacy
mission in the modern format.

FRED2's background dialog is also reorganized so that "Display bg
bitmaps in nebula", "Override nebula fog palette", the RGB row, and
the four fog parameters all sit inside the Nebula group box; the
"Old Nebula" group box is restored below it with its Pattern, Color,
Pitch, Bank, and Heading controls back inside the frame. The dialog
now overrides PreTranslateMessage so that the new fog tooltips —
and the previously broken "Save sun/bitmap angles in correct format"
tooltip — actually fire.

QtFRED's BackgroundEditor.ui adds four QDoubleSpinBox controls in
the existing nebula form layout, with per-field decimals chosen to
avoid scientific notation, and per-field min/max declaring the
validation rules. The QtFRED help page is updated to describe the
new fields.

* fix tooltips

* restore forward-renderer fog

* Clang Tidy

* address feedback

* Refine nebula fog rewrite

- Rename Neb2_fog_use_legacy_values to Neb2_fog_save_legacy_values
  to reflect that the flag governs save format, not runtime
  behavior.
- Decide both the save flag and the legacy-to-modern conversion
  purely from whether the file contains modern fog keys, dropping
  the prior heuristic based on required_fso_version. A mission
  without modern keys (legacy- or retail-format) converts on load
  and saves back in legacy format; missions with modern keys are
  saved as modern.
- Snapshot the four fog values as floats at editor open and use
  fl_equal for the no-change comparison in both FRED2 and QtFRED,
  replacing the FRED2 CString comparison and the QtFRED strict
  inequality.
- Document the on-disk invariant in missionsave: when not saving
  legacy keys, the four modern keys must always be written (never
  gated on "only if non-default"), since the parser uses their
  presence to decide whether to run the conversion on next load.

---------

Co-authored-by: Goober5000 <[email protected]>
2026-05-28 14:43:48 +09:00
Goober5000 74dad95054 Merge pull request #7479 from Goober5000/fix/more_misc_bugs
miscellaneous fixes
2026-05-28 01:42:51 -04:00
BMagnu a715542405 Make beam impacts trigger even without beam flash / pierce effects (#7480) 2026-05-27 09:50:48 -04:00
Goober5000 11a58f4600 miscellaneous fixes
-weapon_spew_stats terminology, cfile fix, trigger count fix
2026-05-26 22:08:04 -04:00
Mike Nelson 3af8fa801d QtFred Render volumetric (#7467)
* render volumetric in qtfred

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

* Conditional glad include

* remove unused function

* define glenum for non opengl build

* remove unused param

* fix qtbuild

* change ktx format to enum

* use nullptr

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

* add comments and missing include

* correct flag type

* clarify variable name for clang

* cleanup

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

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

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

* add GLAD_ES loader

* Implement basic es compatibility header

* re-add accidentally deleted break

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

* fix some enum mappings

* Use the ARB name for geo shader check

* Add es espand shader includes

* Fix glbufferstorage mapping

* fix some gl errors on opengl es

* Enable GL_CLIP_DISTANCE

* Add format conversions

* add bgba 8888 to rgba 8888 converter

* disable GLAD_GL_ARB_timer_query

* Properly handle wgl + opengl es on windows

* initial enable imgui gles

* add GL_BGR to GL_RGBA conversion

* inform opengl es on menu

* small corrections to the es header

* small refactor of glTexSubImage3D

* remove expandincludes, existing code already did that better

* change default graphic values for opengl es

* compile imgui with opengl es 3 on linux

* allow qtfred to run on opengl es

* improve es compatibility header and remove shader precision quantification

* add compiler hints

* remove unused structs

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

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

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

* add missing profile handler

* cleanup

* revert color_attachment_4/5 fbo workarounds

* Adjustments to log output and enable GL_BLEND

* Implement glGetQueryObjectui64v on es layer

* Fix deferred and decals support

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

* Revert changes to default values of graphics options

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

Adds display name capability for wings, matching the pattern and infrastructure of display names for ships.  As in #7234, wing display names take precedence over ship display names, if both are provided; because if ship display names took precedence, a ship would have the same display name in every wave.

All the relevant display locations are handled, including the HUD, escort list, message box, and mission log.  Lua API support is provided as well.

Adds full support for both FRED and QtFRED, including the always-save-display-names setting.

Addresses #7189 and the last request from #1730.

* add help text from #7234
2026-05-20 15:16:08 -04:00
Mike Nelson 5a2ffd9f9c alt2 path mode with shortcutting (#7322) 2026-05-20 03:21:44 +02:00
BMagnu c8ea9c6035 Model surface particles (#7447)
* Pick model surface particle by sampling random vertex

* Parse and keep buffers

* Add local position scaling

* Fix warning

* Fix warning 2

* Fix warning 3

* Fix warning 4

* Add scale parameter

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

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

* Move to using standard models.

* Avoid using .indexes() to fix heap coruption

* More work on selection function

* major cleanup and refactor

* enable drag and drop

* multi edit support

* cleanup

* fix build issue on linux

* revert non-weapons dialog changes

* clang

* clang

* update help doc

* better model/dialog code split

* further cleanup

* static

* keep primary and secondary banks aligned

---------

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

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

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

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

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

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

* clang

* update qtfred help docs

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

* adapt existing windows sapi speech implementation

* adapt existing mac speech integration

* add speech linux stubs

* add speech support in linux

* Add array checks

* Use dlopen for speech-dispatcher

* corrrect lib name

* missing includes and static cast

* do not change mac file type

* fix clang tidy warnings 1

* set tts rate

* set localization ids

* fix clang tidy warnings 2

* correct symbol name

* Remove voice cache and fix win enumerate_voices overriding voice selection

* fix mac rate

Done by notimaginative

* requested changes

* re-add voice cache for linux

* Open connection for linux get flags

* fix missing }

* change voice option combobox to std::pair

* delete duplicated voice id sanitizer on windows set voice

* Use pairs for speech_enumerate_voices() and adapt linux speech

* use reference

* actually free vector memory

* Clear voice cache when ingame options closes

* Use extern to call fsspeech_options_cleanup()

* update localization ids

* Fixing some linux and window speech oversights

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

* clang

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

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

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

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

* clang

* update qtfred help docs

* fixes

* static

* address feedback
2026-05-13 07:40:44 -04:00
Goober5000 ce28df46ca Merge pull request #7448 from Goober5000/feature/unlimited_title
unlimited mission titles
2026-05-12 21:17:40 -04:00
Goober5000 ef73e2572d unlimited mission titles
Some code refactoring to allow mission titles to be strings of any length.  Titles that are too long to actually display (based on string width) will be visibly truncated using `font::force_fit_string`, but the underlying title will not be affected.  Multiplayer is still subject to the 31-character length limit, so titles sent over the network will still have characters truncated.

Also removes some redundant calls to `gr_get_string_size`, since `font::force_fit_string` returns the fitted string width.
2026-05-12 11:31:20 -04:00
BMagnu 1cbfb34a43 add findWorld / findObject for objects (#7443) 2026-05-12 07:52:28 -04:00
Mike Nelson 975ce277a7 QtFRED large ship collision group mission flag (#7425)
* large ship collision group mission flag

* update help doc
2026-05-12 07:51:00 -04:00
Mike Nelson 6c0d490489 Qtfred error checker (#7389)
* new qtfred error checker

* fix some issues

* clang

* more clang

* clean up and fixes

* missed a comment

* add help documentation

* surface auto corrected issues more clearly

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

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

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

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

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

* make sure old path remains unchanged

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

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

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

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

* signed unsigned mismatch

* clang

* stop yur whining
2026-05-07 08:03:27 -04:00
Goober5000andClaude Opus 4.6 3f85d6172a use std::move() for local variables that are copied but not reused (#7308)
* use std::move() for local variables that are copied but not reused

Addresses ~140 Coverity "Variable copied when it could be moved"
defects across 74 files.  Every std::move() targets a local variable
whose value is consumed by push_back, emplace, assignment, lambda
capture, or a function call and is never read again afterward.

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

* clang: use std::make_shared

* clang: use std::make_unique

* address feedback

---------

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
2026-05-07 14:40:38 +09:00
Goober5000 0e1e05552a Merge pull request #7421 from Goober5000/fix/anchor_model
fix a few bugs in model loading
2026-05-06 22:41:26 -04:00
Goober5000 4b157fe992 Merge pull request #7424 from Goober5000/fix/cmake_toolchain
fix cmake warning
2026-05-06 22:41:16 -04:00
BMagnu 1b0e63d924 Add animation support for particle spawning (#7416)
* Add particle during animation

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

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

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

* compile help in headless mode

* scene browser help file

* attempt to get CI working for linux

* debug attempt

* Fix split method to use QString::SkipEmptyParts

* Revert "debug attempt"

This reverts commit cb046e1b3305577ff50e95d5b748f6d1dbf025fb.

* clang

* update help

* correct name

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

* update help based on recent commits

* better tutorial asset loading

* no const

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

Fixes Multiple QTFred heap crashes due to mismatched runtime types.

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

* Move flag to graphics settings
2026-05-01 16:16:40 +09:00
Goober5000 3a556d9413 Merge pull request #7412 from Goober5000/fix/asteroids
fix asteroid field undefined behavior
2026-04-30 22:02:27 -04:00
Goober5000 29bb47402f Merge pull request #7411 from Goober5000/fix/7327_followup
small followup to 7327
2026-04-30 22:01:11 -04:00
Goober5000andClaude Sonnet 4.6 6e5e342575 fix asteroid field undefined behavior
Three out-of-bounds/UB fixes for issue #6793:

- asteroid_create_all: skip field_debris_type[idx] == -1 entries when
  building the DG_DEBRIS odds table and loading models; iterate only
  over valid table slots during spawn. Matches the guard already present
  in asteroid_frame.
- missionparse: bounds-check subtype before indexing colors[] in the
  obsolete +Field Debris Type: parsing path.
- verify_asteroid_list: guard the asteroid_list[0] fallback access
  against an empty list.
- asteroid_init: emit an mprintf when no asteroids are defined instead
  of returning silently.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-30 02:10:49 -04:00
Goober5000 4235e4f252 small followup to 7327
Submodel `collision_tree_index` should be reset to -1 when removed, not 0.
2026-04-30 01:35:07 -04:00
Goober5000 e2707549f4 add documentation for issue 4148
Per Claude:

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

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

* fix docking setup flow

* fix all/none in flags list not updating ui

* fix tri-state flag issues with multi selection

* fix initial status checkboxes

* fix no warp effect

* more fixes

* fix flag hiding

* allow any flag to be skipped for player starts

* another fix for no warp effect

* smarter context menu

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

* fix rebase issue

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

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

* clang

* private members

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

* always use trimmed

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

* set tooltips on flags using flag descriptions

* add missing descriptions

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

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

* add props and jump nodes to layer filters

* make goals dialog dynamic and fix bugs

* cleanup

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

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

* fix texture replace for retail textures

* fix heap issue in ship alt class

* fix props menu item

* some ui clarity

* unify up/down buttons to use our painted icons

* fixup ship and wing cues toggles

* rename and move Object Orientation menu item

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

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

Signed-off-by: dependabot[bot] <[email protected]>
2026-04-16 21:56:10 +00:00
Mike Nelson 4084c75678 Qtfred Dockable object list (#7368)
* qtfred dockable objects list

* fix jumpnode rendering

* more ouline panel features

* fix for wings

* remember list expansions

* clean up wing selection and layer handling

* disable top and bottom docking

* give scene outliner some padding

* replace old selection dialog with the scene browser

* fix some minor issues

* right click for wings and waypoint paths

* clang

* clang again

* fix status bar message

* 25 1 checks

* clang

* more clang

* address feedback
2026-04-15 23:07:01 -04:00
LuytenKy 2894ddb1fa Feature: Implements the SEXPs that set the player's target and or clear it if necessary (#7349)
* draft set-target-player-sexp

* Missed a few entries and duplicates

* Missed another entry

* Yet another missed entry

* Fix subsystem not showing up being targeted

* Fix subsystem targeting in set-player-target SEXP

* remove reduntant return

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

* switch Back_depth_texture to GL_DEPTH24_STENCIL8

* manage the clang-tidy error

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

* fix some comments

* some fixes

* var clash

* loops

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

* fix copy/paste error

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

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

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

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

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

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

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

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

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

Fixes #7366

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

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

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

* fix gr string errors

* return raw char array

* control conflict groups and better photo param defines

* better param identification in real time

* make photo mode hude dynamically sized

* fix xstrs after all changes

* missed a few

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

Fixes #7358

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

* some more view menu cleanup

* fix outline rendering for ships

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

* linux

* more linux

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

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

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

* fix data races in ship-weapon collision threading

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

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

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

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

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

* fix data race in ship-ship collision submodel checking

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

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

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

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

* address review feedback for ship-weapon collision threading

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

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

* move collision_checked from pmi into mc_info for thread safety

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

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

* Convert to CV and spinup counter

* Update comment

---------

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

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

* save/load object layers using the mission file

* use fill

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

* fix some dialog window titles

* remove unused context help button on Windows

* address some easy todo items

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

* some cleanup

* clang ignore

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

* better error messages

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---------

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

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

* get animations working

all previewing all head anis

cleanup

* reorganize files

* make these static

* no const

* more static

* updated var name

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

* use ints

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

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

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

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

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

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

* clang

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

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

Fixes #7330.

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

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

* merge init and close methods

* nullptr

* assertions and nullptr

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

* shared_ptr-ify model data

* fix virtual pof filename issue

* Fix tests

* remove redundant memsets

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

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

Tested and works as expected.

* comment cleanup

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

* clang

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

* Add condition to depth attachment

---------

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

* disallow setting flag

* and don't grab the value even

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

* pan based on camera view coords not world coords

* reset view option

* change scrolling to use mouse wheel

* Better ui for controls reference

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

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

* better fix for armor flags
2026-03-25 17:42:45 -04:00
Mike Nelson 240a9123c0 make fred's sound environment dropdown a full list (#7303) 2026-03-23 10:29:21 -04:00
Goober5000andClaude Opus 4.6 fa86575606 Suppress Coverity GLOBAL_INIT_ORDER false positives for safe globals
Add coverity[GLOBAL_INIT_ORDER] annotations to ~50 global variables
across 27 files. These were all analyzed and confirmed safe:

- ~48 OptionBuilder::finish() globals use a Meyers singleton
  (OptionsManager::instance()), which guarantees safe init order.
  Destruction is also safe due to shared_ptr ref-counting.
- 6 default-constructed container globals (vectors, maps) have no
  cross-TU dependencies at construction or destruction time.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-03-22 15:00:25 -04:00
Mike Nelson c2462d059a Qtfred viewport controls (#7304)
* fix viewport controls

* allow cancelling drag events

* reverse forward backward movement

* fix some bugs

* ensure main window retains focus

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

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

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

Implements #7170.
2026-03-20 15:02:03 -04:00
Goober5000 05054f89f6 Merge pull request #7222 from Goober5000/text_replacement_updates
consistent handling of text token replacement
2026-03-20 15:00:16 -04:00
Goober5000 b76e652e0c Merge pull request #7282 from Goober5000/const_char_lookup
assorted `const` fixes
2026-03-20 14:59:04 -04:00
Goober5000 a47d34c739 assorted const fixes
1. Change `ai_profile_lookup` and `multi_xfer_lookup` to use `const char *`, since all the other lookup functions do
2. Remove several unneeded `const_cast`s, some of which are now made possible by the non-const `data()` overload in C++17
3. Change `campaign_link` to use `const char*`
4. Use better string management in `test::FSTestFixture::init_cmdline()`
2026-03-20 11:33:54 -04:00
Goober5000 49f00019a7 enhance utility functions
1. Add optional `start_index` and `num_items` parameters to the find/count functions in utility.h.
2. Fix an accidental recursion bug.
3. Change appropriate parameters to `size_t` and cast appropriately.
4. Add two forms of `SCP_vector_contains_lcase`.
5. Enhance `sprintf` and add `sprintf_concat` for `SCP_string`.
2026-03-20 02:22:20 -04:00
Mike Nelson f01eec903a check, assert, and skip as needed (#7297) 2026-03-19 07:45:54 -04:00
wookieejedi 100b8971a1 Fix gr.drawOffscreenIndicator (#7295)
The scripted `gr.drawOffscreenIndicator` was missing a critical `resetClip();` which meant it was using whatever the previous stack's clipping distance was, and thus not draw in the actual correct location.

This 1 line PR fixes that and the fix works as intended.
2026-03-18 07:33:15 -04:00
Goober5000 ba1f11f80a Merge pull request #7280 from Goober5000/campaign_optional_string_fred
optional_string_fred fixes
2026-03-17 23:34:29 -04:00
Goober5000 7de18842ef Merge pull request #7291 from Goober5000/fix/misc-fixes
miscellaneous fixes
2026-03-17 23:34:16 -04:00
Mike Nelson b09f7a26ce Qtfred props support (#7240)
* Add props support and dialogs

* Enable prop navigation when no prop is selected

* no lint

* clang

* oops
2026-03-17 08:53:23 -04:00
Mike Nelson ca1634bb5d Lua isDockLeader function (#7286)
* lua isDockLeader function

* address nitpicks
2026-03-17 08:21:12 -04:00
Goober5000 50a4febe88 refactor -weapon_spew_stats
This splits out the stat calculation and MediaVP text generation into their own functions so that they can be used elsewhere.
2026-03-17 00:22:50 -04:00
Mike Nelson 7b4b544ee7 Unify parsing of this param between TTF and bitmap fonts (#7288)
* unify parsing

* allow both
2026-03-16 14:47:25 -04:00
Goober5000 fc48fac60e miscellaneous fixes
1. When loading and saving lighting profiles, treat an empty string as equivalent to the default name
2. Remove unused fields from the mission struct
3. Properly guard union initialization in `add_message`
2026-03-15 03:25:38 -04:00
Mike Nelson 8fb7a058e8 grant lua ability to force a ship to call for support (#7250) 2026-03-14 18:50:13 -04:00
Mike Nelson 6120f7e6d6 update librocket reference (#7283) 2026-03-14 13:53:28 -04:00
Kestrellius 2211945f2d Merge pull request #7281 from Kestrellius/det-range-optimization
Optimize detonation range and detonation radius checks
2026-03-13 23:41:14 -07:00
Goober5000 ce31a33029 fix persistent variables and containers (#7192) 2026-03-13 08:16:28 -04:00
Mike Nelson b4c98defc6 Fix incorrect param order (#7284) 2026-03-12 20:51:46 -04:00
Goober5000 52081e20e6 Merge pull request #7279 from Goober5000/fred_campaign_fixes
fix a few campaign editor bugs
2026-03-12 20:42:30 -04:00
Goober5000 5639662e12 Merge pull request #7273 from Goober5000/more_fred_fixes_b
more FRED fixes when dialogs pop up, part 2
2026-03-11 23:01:42 -04:00
Kestrellius df7413ad4f address review 2026-03-11 19:55:41 -07:00
Goober5000 0073532a70 more FRED fixes when dialogs pop up, part 2
Follow-up to #7272 to add props handling.  This allows the original PR to be merged separately to 25.0.1.
2026-03-11 22:25:10 -04:00
Goober5000 780de87c04 Merge pull request #7272 from Goober5000/more_fred_fixes
more FRED fixes when dialogs pop up
2026-03-11 22:22:08 -04:00
Kestrellius 1fc7dd1fc5 dist squared 2026-03-11 18:42:34 -07:00
Taylor Richards d6c1b36c56 fix issues with imgui backend/renderer cleanup (#7278)
If the graphics system fails to fully init for some reason then it's possible
for the imgui backend and/or renderer to not be initialized. Those subsystems
*must* be initialized before being shut down so we need to check for that to
avoid hitting assertions inside of imgui.

Also add a safety check for making sure that an imgui context exists before
attempting to initialize the backend/renderer. A simple assertion should work
for this as it would take a coder messing something up in order to trip it.
2026-03-11 21:06:03 -04:00
Goober5000 b9c0a1ddd6 fix a few campaign editor bugs
1. Properly initialize campaign links and elements so that stale values don't stick around from previously loaded campaigns.
2. Clear internal flag bits from campaign missions to prevent invalid state from being loaded from a campaign file.
3. Properly check the main hall when saving flags in retail format.
4. Clean up parsing and saving of mission loop data, which had been broken ever since retail.  Add compatibility checks for the old format.
2026-03-11 20:45:00 -04:00
Goober5000 cbb30f9d7e optional_string_fred fixes
Not using the return value from `optional_string_fred` triggers Coverity warnings, but it turns out those "consume token" blocks aren't actually needed.

While we're at it, make the token parsing a little more robust in campaignsave.cpp and clean up the newline behavior.
2026-03-11 13:20:37 -04:00
CRGorman 3c3a3850ca Apply a wait for named pipe after verification (#7275) 2026-03-10 04:38:40 +00:00
BMagnu 8438af20e9 Make window_res buffer have a stencil attachment (#7274) 2026-03-10 00:58:14 +01:00
Goober5000 fbf58703b4 Merge pull request #7271 from Goober5000/particle_fixes
particle fixes and subsystem speedup
2026-03-08 23:24:50 -04:00
Taylor Richards 78f60c6f15 fix AppImage setup for fso and qtfred (#7154)
- generate appimage for qtfred if it's available
 - separate fso and qtfred install roots
 - move fso appimage files out of global space
 - fix qtfred appimage launch script
 - prefer X11 Qt backend for qtfred
2026-03-08 23:11:12 -04:00
Goober5000 d8a6d68482 more FRED fixes when dialogs pop up
FRED can get into a weird state if a warning appears while a ship is being created, e.g. if the ship has a null moment of inertia.  So, prevent FRED from rendering a ship if it hasn't been fully initialized yet.
2026-03-08 22:50:13 -04:00
Goober5000 89e7fd91da particle fixes and subsystem speedup
1. Modify `ship_get_subsys_for_submodel` to use the subsystem indexer.  This changes the operation from O(n) to O(1) and also allows the function to work with turret gun barrels.
2. Add an `isValid` check to `ParticleSource::finishCreation` to prevent unknown state from being accessed if the source is not valid.
3. Tweak `EffectHostTurret` and make sure it works for fighter beams.
4. Prevent `createOnTurret` from running on submodels that are not turrets.
2026-03-08 20:15:02 -04:00
Goober5000 35ed88c57f make anchors type-safe, and adjust support_ship_info accordingly 2026-03-06 22:00:32 -05:00
Goober5000 33d5f7ed3c refactor ship anchors
Testing in FotG exposed a bug that dates back to retail.  During mission parsing, arrival and departure anchors were represented by indexes into `Parse_names`.  After parsing, they were converted to ship indexes -- but only in FRED.  Since some functions are shared between FRED and FS2, this can produce inconsistent results, which can be hard to detect since parse indexes and ship indexes are usually the same.

This refactors arrival and departure anchors to be represented by ship registry indexes in all cases.  It also adds conversion functions for the benefit of FRED and QtFRED since those dialogs were designed with ship indexes in mind.
2026-03-06 22:00:32 -05:00
The Force 14a900e554 Properly Invert flag check (#7270)
Fixes #7269
2026-03-06 14:09:52 -05:00
Mike Nelson ad03fa5900 Capture ImGui in screenshots (#7236)
* capture imgui in screenshots

* revert buffer changes

* request a screenshot for end of frame

* full revert for cleanliness
2026-03-06 07:47:28 -05:00
Goober5000 785bd36986 make clang-tidy happy 2026-03-06 00:03:00 -05:00
Goober5000 3aff98e5e3 preliminary cleanup for split_str_once refactor
String processing enhancement prerequisites for the upcoming `split_str_once` PR.

1. Add overloads of some string processing functions with a length parameter, to allow processing of substrings of C-strings
2. Add overloads of gr_printf that work with string lengths
3. Add constructors to `HUD_message_data` and `line_node`
4. Rename "size" to "len" in `vm_strndup` for clarity
5. Fill in some string attribute annotations
6. Make the rest of the HUD render functions `const`
7. Removed unneeded circular #includes in code/globalincs/memory/
2026-03-06 00:03:00 -05:00
Mike Nelson 8e23ce837f Add feature to customize how CMs are launched (#7224) 2026-03-05 15:15:56 -05:00
Mike Nelson f6588f802e allow repositioning the mainhall version strings (#7257) 2026-03-05 07:48:13 -05:00
Mike Nelson 21a3c80b45 Lua access to volumetric params (#7262)
* Lua access to volumetric params

* lua documentation error

* address feedback

* clang

* typo
2026-03-05 07:47:57 -05:00
BMagnu 5fc84d533d Actually load model if necessary (#7265) 2026-03-04 12:04:09 +09:00
Goober5000 ffa1bb2903 Merge pull request #7261 from Goober5000/fix_assert
fix waypoint list assertions
2026-03-02 23:25:16 -05:00
Goober5000 baf2f9521a fix waypoint list assertions
Followup to #7104.
2026-03-02 23:00:29 -05:00
Goober5000 1869b00af4 Merge pull request #7242 from Goober5000/props_missionsave
mission save compatibility
2026-03-02 21:53:44 -05:00
Mike Nelson e1d67cebbc Allow defining a default profile to use in non-mission rendering (#7238)
* allow defining a default profile to use in non-mission rendering

* Correct comment typo in lighting_profiles.h
2026-03-01 23:11:02 -05:00
BMagnu 252655cee5 Don't magic a model instance out of thin air (#7259) 2026-03-02 02:34:43 +00:00
Goober5000 e39a900223 Merge pull request #7241 from Goober5000/fred2_fixes
fix a FRED2 crash
2026-02-28 19:46:11 -05:00
Goober5000 135d84ebac Merge pull request #7255 from Goober5000/7104_followup
fix some waypoint bugs
2026-02-28 17:31:07 -05:00
Goober5000 482c5a5cc0 fix some waypoint bugs
1. The `waypoint_stuff_name` function used the wrong length when building the waypoint name, causing the number to be written past the end of the string in most cases.  Not sure how this escaped testing.
2. Change two Assertions, which had no effect due to the comma operator, to Errors, which match their counterparts in their respective functions.
3. Make `internal_error` consistent between the campaign editor, the mission editor, and qtFRED, and prune an unnecessary string copy.

Follow-up to #7104.  Fixes #7254.
2026-02-28 16:59:23 -05:00
Taylor Richards 31ad18fe3f fix portfwd issue when dealing with server response failures (#7252)
An upstream bug in libpcpnatpmp caused it to not pass along ping failure
messages in the internal state which then triggered multiple issues with
both the library itself and FSO. This fixes the bug in the pcp library
and takes care of the portfwd log spam issues in FSO as well.
2026-02-27 23:57:34 -05:00
Mike Nelson 5c2e5a261b Stop select model rendering destroyed subsystems (#7225) 2026-02-26 10:10:26 -05:00
Asteroth f23d35c7ec add scripted flight cursor sensitivity (#7149) 2026-02-25 08:30:42 -05:00
wookieejedi ff11370c56 Fix forever flashing threat gauge (#7247)
This one would be good for the upcoming point release, as on Stable 25.0, if your threat gauge is flashing during a mission and then you quit the mission and go to the HUD Config screen, then the gauge remains flashing.

This PR fixes that by simply not flashing the gauge while in configure mode. Tested and fixes the bug as expected.
2026-02-25 08:28:50 -05:00
BMagnu 7412e14575 Add Z-Compression shader to better use position buffer's FP16 (#7245) 2026-02-25 12:59:30 +09:00
Mike Nelson 8e44f355f0 Make sure to delete any dead objects before processing (#7223) 2026-02-25 00:48:28 +01:00
Goober5000 bbca63da8f Merge pull request #7185 from Goober5000/cannot_perform_scan_show_cargo
show and hide cargo variants of cannot-perform-scan
2026-02-24 00:14:48 -05:00
Goober5000 849acf200e show and hide cargo variants of cannot-perform-scan
The existing `cannot-perform-scan` flag prevents the cargo from being shown on the HUD, but some modders would like to see the cargo while still preventing a ship from being able to perform a scan.  So, this renames the existing flag to `cannot-perform-scan-hide-cargo` and adds a new `cannot-perform-scan-show-cargo` flag.  Compatibility code in both mission parsing and flag evaluation ensures that missions using the old flag will still work.

Note that if both `cannot-perform-scan-hide-cargo` and `cannot-perform-scan-show-cargo` are set, the former will take precedence.
2026-02-23 22:03:22 -05:00
Goober5000 2436ef2a3f Merge pull request #7129 from Goober5000/fix_scannable_ship_types
enhance and clean up scannable ship types
2026-02-23 22:00:34 -05:00
BMagnu 176330023c Add metatable to correctly handle FSO objects (#7190)
* Add metatable to correctly handle FSO objects

* Avoid infinite recursion
2026-02-23 09:56:15 -05:00
Mike Nelson 405de8d0f3 Allow setting custom gauges as message gauges (#7217) 2026-02-23 09:53:34 -05:00
Goober5000 512021d7f5 Merge pull request #7116 from Goober5000/ship_enhancements
some ship utility enhancements
2026-02-21 00:39:08 -05:00
Goober5000 c4efdfb77b Merge pull request #7104 from Goober5000/waypoint_stuff_name
add waypoint_stuff_name
2026-02-20 23:14:10 -05:00
Goober5000 418af70ea3 some ship utility enhancements
Add a useful function for getting a ship_info pointer from a ship registry entry.  Also rename `ship_has_dock_bay` to `ship_has_hangar_bay` for clarity, since docking bays and docking points are not the same thing.  Finally, add a `model_has_hangar_bay` function.
2026-02-20 23:11:07 -05:00
Mara van der Laan 239e0274ee io: Fix crash on SDL joystick disconnect (#7216)
Check that `pJoystick[i]` is non-null before dereferencing it.
2026-02-20 09:12:08 -05:00
Goober5000 82e50f82c1 Merge pull request #7155 from notimaginative/prebuilt_download_fix
stop download of prebuilt libs when using override
2026-02-19 13:00:05 -05:00
Goober5000 0666749ab6 mission save compatibility
Make sure that missions without props will still be compatible with earlier mission file formats.  If props are included, bump the mission file version.  Also, fix some newline behavior in the save code.
2026-02-19 09:54:26 -05:00
Mike Nelson e05bf3c3c7 Make sure we actually set old_class when changing classes (#7221) 2026-02-19 08:17:53 -05:00
Mike Nelson 125377f362 do not reset lab state when going to options (#7226) 2026-02-19 08:17:22 -05:00
Mike Nelson 6ae95c663b fix edge cases for mission background loading in the lab (#7237) 2026-02-19 08:16:53 -05:00
Goober5000 3763a29b16 Merge pull request #7098 from Goober5000/utility_enhancements
several utility enhancements
2026-02-19 00:31:45 -05:00
Goober5000 1832fa0397 fix a FRED2 crash
Fix a crash caused in part by some missing UI calls in FRED2 and in part by subtle object life cycle issues.  Depending on whether FRED receives the focus during mission load (which can happen if the autosave dialog appears) it could access props that were in the process of being cleaned up but were still referenced.  This isn't specifically a props issue; it could happen to any FSO object stored in a SCP_vector, since the vector can be cleared.

The quick mission init is to prevent a whole bunch of unnecessary and risky processing during a transient loading state, but it has the nice side effect of making mission list loading noticeably faster.
2026-02-19 00:29:53 -05:00
BMagnu e827011113 Particle source duration curve + life remaining input (#7231)
* Add curve to control the duration of a particle source

* Also add fractional Life Left curve input for sources

* Actually register new particle curve output
2026-02-17 12:09:13 +01:00
Mike Nelson 4fbfa84c6e fix logic checking for invalid ship or prop (#7232) 2026-02-16 17:07:41 -05:00
Mike NelsonandAsteroth 070a8e18fa Adds Props as a new object type (#6836)
* First draft of prop object type

* Add prop editor dialog in FRED

* prop sexps

* lua table prop support

* lab support

* lua mission access to props

* prop collision support

* use std::optional so the object->instance id is always valid

* allow props to block suns

* minor cleanup and remove arbitrary limit. Still limited to 500 objects.

* Sort props by category

* cleanup

* implement collision hooks

* parse prop flags and further ensure props are inited and closed correctly

* Add Cyborg's multi suggestions

* fix missing header

* add props to empty slots if available

* add multi signature

* fix rebase issues and match collisions to the new multithread style

* appease Clang the Conquerer

* save/parse mission prop classes by name

* Clean up comments and finish TODOs

* fix string errors

* clang wants this to be static now

* address feedback

* address feedback

* Use model num from prop info instead

* merge prop and ship collision group sexps together

* fix rebase bug

* address feedback

* fix name shadowing

* ship heavy submodel

* clang

---------

Co-authored-by: Asteroth <[email protected]>
2026-02-16 14:41:34 +01:00
Mike Nelson 37df4ba479 QtFRED Generalized Mission/Campaign Save (#7045)
* Generalize mission save from original FRED

* make qtfred use the new generalized missionsave

* split campaign save into a sister class

* allow unlimited campaign description length to be saved

* have the campaign editor pass the links to save properly

* cleanup

* future todo

* fixes

* more fixes

* cleanup vars

* clang

* add missing guardian threshold and fix var name

* more clang

* conflict resolution error

* update qtfred to use the new campaign save
2026-02-15 07:38:55 +01:00
Goober5000 7277fc11ff Merge pull request #7186 from Goober5000/ship_class_display_name
make ship and weapon class display names SCP_string
2026-02-14 21:23:50 -05:00
Goober5000 2e6b38ee06 Merge pull request #7204 from Goober5000/asteroid_debris_iterators
add getAsteroidList and getDebrisList script API iterators
2026-02-14 21:23:40 -05:00
Goober5000 b341e85e55 fix a few SEXP tree problems with variables and containers (#7203)
1. Add default strings for variables and containers, providing something more appropriate than `<new default required!>`.
2. Replace the `Assert` in `add_default_operator` with a check for a valid variable index.  This prevents a crash if FRED adds an operator that takes a variable as an argument and there are no variables in the mission.
3. Add the correct variable type to `get_listing_opf_variable_names()` to fix SEXP tree construction in certain circumstances.

Apply the same fixes to QtFRED as well.
2026-02-13 08:53:57 -05:00
Goober5000 c193a920d0 several utility enhancements
These were coded for the `split_str` refactor, but are generally useful elsewhere as well.

1. Use SCP_string in some utility functions where appropriate
2. Avoid copying C-strings in some utility functions where it isn't necessary
3. Add concat functions to `SCP_vector`
4. Change return type `int` to `bool` on some common parselo functions
5. Add `find_white_space` and `find_gray_space` parselo functions
2026-02-13 01:10:01 -05:00
Goober5000 68075dfbd5 consistent handling of text token replacement
Just as special tokens are replaced in debriefings, replace them in command briefings and briefings as well.

Change `string_replace_tokens_with_keys` to `message_translate_tokens` (since it's the same thing) and add a `replaceContainers` script API function to complement `replaceVariables` and `replaceTokens`.

Also replace every category of token in `sexp_modify_variable` and add comments to places where token replacement might be expected but happens elsewhere.
2026-02-13 00:56:41 -05:00
Goober5000 c428a22150 Merge pull request #7097 from Goober5000/snprintf_size
fix the size parameter of snprintf and vsnprintf
2026-02-13 00:50:38 -05:00
Goober5000 f78ac499e2 Merge pull request #7195 from Goober5000/timestamp_elapsed_fix
fix two timestamp bugs
2026-02-12 00:23:41 -05:00
Asteroth 1a7ee078f9 assume beam and shockwave creation is falliable (#7174) 2026-02-11 07:54:03 -05:00
Goober5000 ddb50fbbc9 Merge pull request #7207 from Baezon/start-loop-end-snds-fix
Misc fixes to start/loop/end sounds
2026-02-11 00:59:09 -05:00
Goober5000 2d4ca569be Merge pull request #7113 from Goober5000/zero_rad_skip_fireball
add Zero_radius_explosions_skip_fireballs
2026-02-11 00:56:18 -05:00
Goober5000 a0b6e1a0e2 Merge pull request #7115 from Goober5000/configurable_sets
configurable sets
2026-02-11 00:55:52 -05:00
Mike Nelson c51c5a3b0c don't assert for scripted animations (#7209) 2026-02-10 12:18:14 -05:00
BMagnu 0f45fa8209 Fix change ship class edgecases (#7210)
* Clear Decals and Recreate Model instance

* Also reset the PMI if the new model has no cockpit
2026-02-10 12:17:39 -05:00
BMagnu 6c67f348ac Bump version to 25.1 (#7215) 2026-02-10 05:29:40 +00:00
Goober5000 38c28d2c6f Merge pull request #7099 from Goober5000/filtered_sexps_speedup
filtered SEXP operator speedup
2026-02-09 21:19:51 -05:00
Goober5000 cc82766a11 Merge pull request #7112 from Goober5000/splodeing_removal
remove the splodeing code
2026-02-09 21:18:22 -05:00
Goober5000 427ab77b0a Merge pull request #7107 from Goober5000/unicode_enhancements
upgrade Unicode `text_iterator` for C++17
2026-02-09 21:14:44 -05:00
Asteroth d313cf8ca6 copy paste error 2026-01-30 00:40:46 -05:00
Asteroth 42e85df9f9 use obj snds for start and end too 2026-01-30 00:14:27 -05:00
Asteroth c47e0826e7 misc fixes to start/loop/end snds 2026-01-29 19:28:17 -05:00
Goober5000 73dd6c04e6 remove unneeded detour into Objects array 2026-01-29 01:34:13 -05:00
Goober5000 72f57ffe93 add getAsteroidList and getDebrisList script API iterators
These are similar to the existing iterators for in-game objects.
2026-01-29 01:34:13 -05:00
wookieejedi 5eea353952 Fix Select All HUD Color Slider (#7202)
#6670 added support for custom gauges and also cleaned up the color setting of hud gauges when using 'Select All'. It seems two copy paste bug slipped through the cracks (ie the order of colors was copied from red to blue/green, when really the order should have been updated).

This PR restores the proper order and fixes the bug.
2026-01-26 23:05:08 -05:00
wookieejedi 319e573637 Fix #7199 (#7200) 2026-01-25 19:37:08 -05:00
BMagnu 83f08291af Rollback insignia fix (#7196)
* Revert "Move insignia post-processing to model load"

This reverts commit 665d7145b6.

* Revert "Cleanup of old insignia stuff"

This reverts commit fdf98a1f3a.

* Revert "render insignias as decals"

This reverts commit 24cdeb3c95.

* Isolate reverts to insignias

* Fix oversight

* clang tidy
2026-01-23 11:11:47 -05:00
Goober5000 66a07fe0e1 fix two timestamp bugs
A timestamp set to 1 means an immediate timestamp, and so the `int` version of `timestamp_elapsed` should account for that, just as it does in the `TIMESTAMP` and `UI_TIMESTAMP` versions.  Normally the number comparison means the check will still work, but during mission initialization the timestamp code is not running.  This caused destroy-before-mission ships to be added to the arrival list when they shouldn't have been.

Also fix logic in `timestamp_elapsed_safe`, where the `TIMESTAMP` and `UI_TIMESTAMP` versions were correct, but the `int` version had a bug dating back to retail that caused it to return true for uninitialized (zero) timestamps rather than false.  Practically speaking, this fix has no effect because the function is only used in multiplayer and none of the instances use integer-0 to indicate uninitialized, but this will at least fix any uses in the future.
2026-01-21 00:06:25 -05:00
Asteroth 2813ceb5e9 Fix for ships blowing up with live debris submodels (#7139) 2026-01-20 08:32:03 -05:00
wookieejedi a2c05009dc Fix asteroids toggling (#7188) 2026-01-15 23:15:31 -05:00
Daniel Cazares 2af0aefd91 fix(eye): guard missing model instance (#7180) 2026-01-15 23:15:17 -05:00
wookieejedi d9a6330d9e Fix regression from #7051 (#7191) 2026-01-15 23:14:23 -05:00
Mike Nelson 634eece877 make this check less stringent (#7182) 2026-01-15 23:13:43 -05:00
Goober5000 add9c46868 configurable sets
Add the ability to configure the `less` operator for `SCP_set`, `SCP_map`, and so forth.  Add comparison operators to the type-safe `ID` class so that it can be used with these data structures.
2026-01-10 13:10:05 -05:00
Goober5000 b4d9c3e9c4 make ship and weapon class display names SCP_string
All other display names are `SCP_string`, so make these `SCP_string` as well to be consistent and to allow arbitrary-length display names.

Also fix a few infelicities with German translation and the script API.
2026-01-05 23:13:35 -05:00
Daniel Cazares e5c4c58eba fix(graphics): measure ellipsis before shrinking in force_fit_string (#7165)
* fix(graphics): measure ellipsis before shrinking in force_fit_string

* chore(comments): clarify why ellipsis measurement is needed before shrinking
2026-01-03 19:33:40 -05:00
wookieejedi e567ae7c81 Fix warning of unbound keys for newer controls (#7173)
* Fix warning of unbound keys for newer controls

Under some circumstances, FSO will warn the player if a key is unbound if that they need to press to complete a training event. This logic though only works for keys with XSTRs from 507-619. Any control that was added with a different XSTR will not be picked up correctly, and thus the message displayed to the player will be incorrect, confusing, and not helpful.

To fix this `missiontraining.cpp` around line 757 needs to account for the newer controls as well.

This PR adds those needed checks, and this fixes the issue. This fix is especially needed for mods that use training missions with newer controls such as FotG. Note, we do ship with a full default controls table with all the needed keys bound, but we have found players will be players and simply change all their controls before even starting the training missions. Thus they unbind key training controls, and with this bug they are unable to correct their controls. With this fix they are properly able to update their controls as needed to complete the training missions.

Fixes #7172.

* var name update
2026-01-03 18:19:10 -05:00
Kestrellius 46d3e490bb Render reorder (#7177)
Move the rendering of ships with effects to immediately after rendering of ships. This fixes rendering in several edge cases where, currently, additively-rendered items, among others, are incorrectly obscured by ships with effects. This affects anything that doesn't render to the depth buffer -- nebula poofs, particles, lasers, beams, engine glows, etc.

Forward-rendered objects (due to transparency) still interact incorrectly with full nebula fog, but that's out of scope for this PR.
2026-01-03 15:37:28 -05:00
Daniel Cazares 4dfdddf4d2 fix(model): capture driver inputs by value (#7163) 2025-12-28 05:14:22 +09:00
Mike Nelson 1da50c738f medal display name should be distinct from name (#7148) 2025-12-26 19:14:36 +09:00
Daniel Cazares a8f4be1d34 fix(math): correct vector assignments in vm_vector_2_matrix_gen_vectors (#7162) 2025-12-26 19:14:24 +09:00
Goober5000 306f34a574 update force_fit_string and bounds in calling functions (#7161)
Rewrite `force_fit_string` to be more robust and to remove the potential for memory access violations.  Also audit all of the length parameters in the calling functions - some of them were the buffer size, not the length.  This should fix the mysterious crash in the mission log screen.
2025-12-25 08:08:46 +09:00
Taylor Richards fb52a8861e fix fullneb lightning glows (#7153) 2025-12-23 07:10:16 -05:00
Goober5000 587e6f4c21 Merge pull request #7150 from notimaginative/macos_ninja_ci_fix
fix macOS ci warning regarding ninja
2025-12-22 23:17:46 -05:00
Goober5000 06d1514a47 Merge pull request #7151 from notimaginative/cmake_path_variable_fix
replace FILEPATH with PATH where appropriate
2025-12-22 23:17:06 -05:00
Goober5000 f483a92329 Merge pull request #7152 from notimaginative/macos_debug_flag_fix
disable compiler optimizations on Mac debug builds
2025-12-22 23:15:09 -05:00
Goober5000 db69cb8395 Merge pull request #7157 from Baezon/uvec-dominant-frame-of-ref
Uvec dominant frame of ref for submodels
2025-12-22 23:14:08 -05:00
Asteroth e803d8a0e9 move some stuff 2025-12-21 23:36:25 -05:00
Asteroth 761b97529a comments and assertions 2025-12-21 22:12:28 -05:00
Asteroth 1cd45aa1b4 uvec dominant frame of ref for submodels 2025-12-20 20:53:54 -05:00
Taylor Richards 542b3d3643 stop download of prebuilt libs when using override
Fix bug where prebuilt libs were downloaded even when an override was used.
2025-12-19 03:09:20 -05:00
Goober5000 5f110962e3 use pm->autocenter when drawing weapon lines (#7147)
In the weapon loadout screen, the model's visual center should be taken into account when calculating coordinates for weapon banks.  Fixes #7145.
2025-12-19 17:01:16 +09:00
Taylor Richards 0dea8d2904 disable compiler optimizations on Mac debug builds
Clang treats -Og the same as -O1, which prevents the viewing of many variables
while debugging, so we need to use -O0 directly.
2025-12-19 02:46:18 -05:00
Taylor Richards 381eda672b replace FILEPATH with PATH where appropriate
Fix issue setting path cmake variables in the gui due to the incorrect
type being specified.
2025-12-19 02:44:21 -05:00
Taylor Richards cbf2693d33 fix macOS ci warning regarding ninja 2025-12-19 02:39:18 -05:00
Goober5000 6c96d12133 slight fix to "Always save display names" (#7146)
A ship which has a display name and belongs to a wing will display that name no matter what wave it appears in.  Since this should only be done when the mission designer deliberately wants it to be done, omit this case from the cases covered by the "Always save display names" feature.  (To always save a display name in this case would require saving one for the wing, but a wing display name feature does not yet exist.)

Fixes #7140.  Followup to #6810.
2025-12-15 15:50:09 -05:00
BMagnu cb3819309b Make sunglows respect detail boxes (#7141)
* Add flag to respect detailboxes in model collide checks

* Don't check detailboxes that are invisible for sunglare checks

* Use local coords

* Actualyl also run the check for cockpits & show ship

* Be less stupid
2025-12-13 11:15:23 +01:00
wookieejedi 8fb0cbe08c Fix Message Line Positioning (#7144)
Message Lines were not being rendered to the proper position for certain resolutions per issue #7143.

#6575 seemed to be the commit it broke on, and looking at that commit it showed that code added `x` and `y` to the render string positions, even though `x` and `y` did not exist there before.

This 1-line PR fixes that issue, and it is confirmed to fix the issue by KamiGaAtaeta. Also I checked and on more standard resolutions the message lines still display as expected in both mission and the HUD Config screen.
2025-12-12 23:16:57 +01:00
Kestrellius ce0fe088eb Fix 'on edge' parameter for ring volumes not doing anything (#7142) 2025-12-11 01:30:52 +00:00
wookieejedi 86ef459cf6 Fix shadow shader for Mac (#7138)
Turns out Mac shader does not support the cleaner bracket style
2025-12-07 18:30:55 -08:00
Kestrellius cb19fecc36 Fix culling for particles that have length (#7136)
* fix culling

* further check

* rotate check

* fix error

* bithackery
2025-12-05 13:29:56 +09:00
wookieejedi af7bb154d3 Fix shadow shader update oversight (#7135) 2025-12-04 21:03:46 +01:00
wookieejedi 937091ee77 Fix haloing in cockpit shadows (#7134)
* Fix haloing in cockpit shadows

Cockpits can use shadows, but the small geometry of cockpits and the current shadow pass methods result in bad looking shadow halos and poor quality. The issue is that small scale geometry, like cockpit struts/pillars, cast overlapping shadows--and these overlapping shadows need to use a different shadowing method then the default larger external shadows.

I made a small edit the to the shadows file to fix it for small scale shadows (external shadows, IE those at longer ranges, remain using their method since that method works better at longer ranges). In FotG we have been using this update for years and it works very well.

KamiGaAtaeta was having these halo issues on the FS Ship Interiors project, and this shader update fixed those issues. Thus, per suggestion from Lafiel, I'm PRing this fix so everyone can easily use it.

* remove dead code
2025-12-03 23:59:38 +01:00
BMagnu dd0add946b load single frame motion debris from the correct file name (#7133) 2025-12-03 14:03:59 +00:00
BMagnu 2e4d97ce4e Properly initialize max_life when no lifetime is available (#7131) 2025-12-02 12:15:36 +09:00
Goober5000 32c07ef847 enhance and clean up scannable ship types
The `$Scannable` field in objecttypes.tbl controls whether a ship can be targeted by the "target unscanned ship" key.  This is independent of which ship types can actually be *scanned*, which is not connected to any field in objecttypes.tbl.

So, this PR renames the `$Scannable` field to `$Targetable as unscanned:` to be clearer about what it actually does, and it adds the missing counterpart `$Scannable by default:` flag to specify which ship types can actually be scanned.

Note that this is only relevant for legacy scanning behavior.  Mods which use `$Unify scanning behavior:` do not need either of the ship type flags.
2025-11-29 13:48:46 -05:00
Goober5000 88c3cea753 fix scannable ship types (#7130)
The `$Scannable` field in objecttypes.tbl controls whether a ship can be targeted by the "target unscanned ship" key.  In retail, these were cargo containers and freighters.  This is independent of which ship types can actually be *scanned*, which in retail were cargo containers and *transports*.  Since the targetability is meaningless without the ability to scan, this fixes the retail inconsistency by moving the targetability from Freighter to Transport.

Note that this is only relevant for legacy scanning behavior.  Mods which use `$Unify scanning behavior:` do not experience the inconsistency.
2025-11-29 12:02:13 +01:00
wookieejedi 4d184d501a Two 1-line fixes for new particle lights (#7125)
* Two 1-line fixes for new particle lights

This PR provides 2 semi-fixes for the particle lights with 2 simple conditionals.

------------
First, it fixes #7124. In sum, it fixes the issue that new particle lights not affected by light quality slider. This fix is done by adding a conditional check so that particle lights only render when weapon lights do, at the highest lighting setting. This ensures that lower end machines can properly turn off particle lights to help with performance.

------------
Second, this PR adds an extra conditional to ensure that the system does not attempt to render particle lights if the radii is <= 0.

Light emitters logically do not cast light if the light emit radius is <= 0. In 2015, there are assertations in each of the `light_add` functions that trigger if the radii are <= 0.

With the new particle lights and addition of curves for particle lights, this can make result in light radii <= 0. This approach is useful to allow things like hit effects to only give off light if the hit effect is large enough (IE if it hits a large ship scale the radii to be larger, and if it is too far away or a small ship scale the radii to 0). So, in those situations of <= 0 light radii, the modder would expect that the light point is just not shown and simply not attempted to be rendered. Unfortunately, this is not quite the case, as any light radii <= for particle lights will trigger the assertation within the `light_add` functions.

The fix for this is simply to have early conditional checks for light radii. There is already precent for this in the code in 3 locations--1) within the weapon light code in `object.cpp` line `1365`; 2) within the beam light code in `beam.cpp` at line `2009`; and 3) within the fireball light code in `object.cpp` line `1457`. Note these sections also early out if the light intensity is <= 0 as an optimization step, which also makes sense.

This PR simply follows that precedence for particle lights by adding an early out if the light radii are 0. This allows modders to properly use a range of radii and helps ensure internal consistency with the code mentioned above that already does this early out for weapon and beam lights.

----------
Given the benefits listed above, having this PR in before stable would be fantastic (or a relatively quick point release after stable).

Tested and fixes the issue as expected. Happy to answer any questions and/or discuss!

* comment signpost
2025-11-26 22:38:26 +01:00
BMagnu 702e8f430d Fix rendertargets being incorrectly invalidated (#7126) 2025-11-26 22:38:17 +01:00
Goober5000 5941d5ed8b Merge pull request #7123 from Goober5000/ship_cleanup_tweak
tweak ship_cleanup
2025-11-21 20:05:13 -05:00
Goober5000 879b1f43ad Merge pull request #7121 from Goober5000/fix_font_crash
fix a font crash
2025-11-21 20:04:56 -05:00
Goober5000 fff480e101 tweak ship_cleanup
The object and ship indexes held by the ship registry do not actually need to be cleared until `ship_cleanup` completes.  This will allow the ship to be referenced in any functions (via script hooks or otherwise) that are called during cleanup.

Tweak related script documentation while we're at it.

Follow-up to #7120.  Fixes #7122.
2025-11-21 02:32:20 -05:00
Goober5000 406f8e58ca fix a font crash
The `setAutoScaleBehavior` function call should go after the null check to prevent crashing FSO if the font fails to load.

Also, if a font fails to load, skip over it so that subsequent fonts can be parsed.
2025-11-20 21:16:22 -05:00
Goober5000 2af19b6605 Merge pull request #7120 from Goober5000/scripting_ship_access
remove ship_name_lookup in scripting code
2025-11-16 13:10:57 -05:00
Goober5000 59e8742c84 remove ship_name_lookup in scripting code
Use the ship registry rather than `ship_name_lookup`.  Also, be sure to check that ships, targets, and subsystems exist.

Fixes #7109.
2025-11-16 02:41:24 -05:00
Goober5000 849a4f6d4e Merge pull request #7118 from Goober5000/reuse_sound_handles
reuse sexp audio handles
2025-11-15 14:42:51 -05:00
Goober5000 8c4849aaee reuse sexp audio handles
The `play-sound-from-file` sexp did not have any way to automatically recycle audio handles, instead relying on other sexps to explicitly close them.  It is more modder-friendly to implicitly free any handles of streams that have finished playing, so this PR adds that capability.  It also adds a Warning in the event that an unused audio handle cannot be found.

Fixes #7108.
2025-11-14 18:02:32 -05:00
Goober5000 a06da31c6a Merge pull request #7114 from Goober5000/quick_anchor_fix
quick fix to arrival anchor bug
2025-11-12 22:27:47 -05:00
Goober5000 77c7569f68 quick fix to arrival anchor bug
Fix the wing arrival anchor check when ships are cleaned up.  This resolves bug #7110.  A more comprehensive refactor will be done post-25.0.
2025-11-12 21:42:22 -05:00
Goober5000 adfb8200a5 remove the splodeing code
This dates back to commit `300d5fc2c1`.  It probably did something at some point, but with various upgrades over the years, it no longer has any effect.

It looks like a lot of the model interp code (e.g. `model_interp_defpoints`, which isn't called anywhere) can be removed as well, but that's a bit more involved.
2025-11-12 03:47:28 -05:00
Goober5000 8d7a56c3a8 add Zero_radius_explosions_skip_fireballs
Addresses a behavior idiosyncrasy encountered by Solaris.  If this flag is enabled, explosions with a visual radius of 0 do not have fireballs created, and do not have the fireball lifetime added to the explosion lifetime.
2025-11-12 02:17:25 -05:00
BMagnu fb229ccbb1 Fix texture slot 0 incorrectly being considered invalid in a lot of cases (#7106) 2025-11-06 09:34:03 +09:00
Goober5000 395d434f59 upgrade Unicode text_iterator for C++17
Add member types, tweak function signatures, and add a couple functions
2025-11-05 00:13:29 -05:00
Goober5000 1dc68b28c2 add waypoint_stuff_name
A consistent friendly way to get a waypoint name.  Follow-up to #7103.
2025-11-02 01:00:57 -04:00
Goober5000 be2045013b Merge pull request #7103 from Goober5000/fred_waypoint_names
fix FRED and QtFRED handling of waypoint names
2025-11-02 01:00:12 -04:00
Goober5000 5659243766 Merge pull request #7100 from Goober5000/message_log_add_seg_fix
fix multi-line display in mission log
2025-10-31 22:18:48 -04:00
Goober5000 6d03ce1c13 make clang happy 2025-10-31 21:12:49 -04:00
Goober5000 cb3a17a404 fix FRED and QtFRED handling of waypoint names
First, fix a crash reported in Discord: prevent FRED or QtFRED from giving a waypoint list a name that is too long.

Also fix the updating of references, including for the constituent points, if a waypoint list is renamed.
2025-10-29 00:24:09 -04:00
Goober5000 f4a3a885dd fix multi-line display in mission log
At some point the mission log became unable to display entries that wrapped around to subsequent lines.  Fix that by keeping track of how many lines each entry occupies and adjusting the display accordingly.

This will correctly display mission log segments on multiple lines, but it uses the current string splitting technique which does not split properly in all cases.  The splitting will work properly once the `split_str_once` refactor is merged.
2025-10-26 00:13:37 -04:00
Goober5000 5b4a4dd5cc filtered SEXP operator speedup
Improve the speed of the SEXP filter by not redrawing the combo box until the box is fully populated.
2025-10-25 13:11:55 -04:00
BMagnu 1fe175b523 Make FSO not crash if invalid options get displayed (#7096)
* Make FSO not crash if invalid options get displayed

* Add release warning
2025-10-24 00:45:23 +02:00
Goober5000 5f8e2c9475 fix the size parameter of snprintf and vsnprintf
These functions accept a size parameter that *includes* the null terminator, so tweak function calls accordingly.
2025-10-23 12:41:44 -04:00
Goober5000 2ceb523954 Merge pull request #7089 from z64555/issue_7088
Adds doxy and warnings to control_used() and check_control_used()
2025-10-17 12:36:04 -04:00
wookieejedi 349b353bbe Fix 'has parent' case for apparent size input curve (#7094)
Thanks for lafiel for catching this: the "has parent" case of that #7090 needs to account for the parents rotation
2025-10-17 12:30:02 +00:00
Kestrellius c0cc43f558 add input (#7090) 2025-10-17 07:43:50 -04:00
Asteroth c3980b7aa6 patch split_str_once (#7093) 2025-10-17 15:26:29 +09:00
Asteroth 5d78e3ee63 Temporary perpetual shudder (#7092)
* perpetual shudder should only last for the mission

* reset other variables too
2025-10-17 00:44:41 +02:00
wookieejedi 38acf9ae15 Allow mods to easily tune 'Target Hostile Bomb or Bomber' control (#7048)
* Allow mods to easily tune 'Target Hostile Bomb or Bomber' control

The `Target Hostile Bomb or Bomber` control targets bombs or bombers, though in mods may want a way to easily set exactly what this control does while still leveraging the utility of the 'bomb' flag for weapons and/or 'bomber' flag for ships (both of which affect AI behavior in various ways).

For example, in Solaris, Darius wants a way a way to allow players to only target bombs, while still allowing ships to be classified as as 'bomber'. In this case, the 'only bombs' option can be used. This PR adds a `$Behavior for pressing 'Target Hostile Bomb or Bomber' control:` to allow mods to set the behavior to `default` `only bombs` or `only bombers`.

For example, in FotG we want to allow players to target just bombers, as bombs themselves are hard to shoot down (critically though we still want the player to be able to manually target a bomb via 'target object in reticle'. This allows players to prioritize and easily target bombers while allowing skilled pilots to retain the option of manually shooting down bombs if they want an extra challenge. In this case the 'only bomber' option can be used.

Tested and works as expected in all cases.

* update using cleaned up function

* extra alt option for default case

* warning cleanup

* update default message

* whitespace clang

* appease clang with brackets
2025-10-15 16:47:45 -04:00
z64555 10cdf2d367 Add some more doxy, since we're here 2025-10-15 09:08:31 -05:00
z64555 d84600356a Adds doxy and warnings to control_used() and check_control_used() 2025-10-15 08:52:44 -05:00
wookieejedi a0b4ae1a2e Mouse Wheel Cleanup Option (#7079)
* Adds an optional bool to `mouse_down()` for `must_be_mousewheel` to only process mousewheel events.
* Used within `controlsconfig.cpp::check_control_used()` to handle a special case with the mousewheel to allow using it for `CC_TYPE_TRIGGER` controls.
2025-10-15 08:27:35 -05:00
Taylor Richards 6a9b6a38e9 fix several uninitialized variable warnings (#7087)
Initialize structs as appropriate in PXO and mdns code that were reported by
gcc/clang/coverity.
2025-10-14 13:51:53 -04:00
Goober5000 ea985b6455 Merge pull request #7081 from Goober5000/wing_destruction_updates
add flag to automatically cancel future waves if a mothership is destroyed
2025-10-13 22:51:04 -04:00
wookieejedi 6b5f9c9be7 Cleanup and fix hud_target_missile (#7051)
* Cleanup and fix `hud_target_missile`

The function `hud_target_missile` is what runs when the `Target Hostile Bomb or Bomber` control is pressed. Notably, it does not work if there is only 1 hostile bomber. The issue is that the loop within `hud_target_missile` is designed to search for the "next" target, but when there's only one valid target, the loop logic skips over it. But, `hud_target_missile` correctly will target if there is only 1 bomb though, so the fix is to just update the ship bomber logic similarly. This PR updates the logic of the bomber search so it matches the bombs search, that way the bomber search can target a bomber if there is only 1 present in the mission (just as how the existing bomb search will let you target 1 bomb if there is only 1 bomb in the mission currently).

This PR also takes the opportunity to cleanup the function with more clear variable and function naming, along with two new internal bools which will be very helpful for later planned PRs.

Fixes #7050. Tested and works as expected.

* rework hud_target_hostile_bomb_or_bomber

* appease clang

* additional cleanup and modernization

* more cleanup for select previous
2025-10-13 21:51:50 -04:00
Goober5000 eca894e732 add flag to automatically cancel future waves if a mothership is destroyed
Infamously, in FSO, a wing whose mothership is destroyed will not, itself, be considered destroyed if it has more waves remaining, because wing cleanup and logging typically only happen when the last ship in the last wave exits the mission.  Instead, with no mothership to arrive from, waves will simply no longer arrive.  Events that check to see if the wing is destroyed or departed will never fire.

This PR fixes that behavior, ensuring that the wing is cleaned up and logged properly, by using the same logic as the enhanced `cancel-future-waves` SEXP operator.  This is tied to a new `$cancel future waves of any wing launched from an exited ship:` AI profiles flag which is enabled by default on 25.0+.
2025-10-13 21:00:14 -04:00
Goober5000 beaeafeb97 Merge pull request #7086 from Goober5000/vectorize_subsys_hits
vectorize subsystem damage/heal list
2025-10-13 20:58:15 -04:00
Goober5000 d77f413f39 vectorize subsystem damage/heal list
When checking for subsystems within range of a hit, more than one subsystem is unusual, and 32 (the original `MAX_SUBSYS_LIST` value) is so unlikely as to be impossible.  So bumping it to 200 was not needed here -- DTP might have confused it with one of the other subsystem limits.  But rather than reducing the limit again, we can simply make the array into a vector.
2025-10-11 19:13:18 -04:00
BMagnu a243052f4c Fix Particles Hosted on Particles Which Themselves Are Parented (#7085)
* Allow particles serving as particles hosts to provide their own parent to their children

* Fix parent particles with parents
2025-10-11 23:12:46 +02:00
Goober5000 05bf43bfcc Merge pull request #7082 from Goober5000/fred_menus
rearrange FRED2 menus to match qtFRED
2025-10-11 13:05:45 -04:00
LuytenKy 1b8f920529 Implement a new ai_profiles.tbl field that allows the maximum number of allies allowed to be rearming to be modified (#7065) 2025-10-11 11:41:53 -04:00
Goober5000 ea1a3707ad Merge pull request #7084 from Goober5000/initialize_fields
initialize some fields
2025-10-10 19:18:55 -04:00
Goober5000 cd2ff46541 initialize some fields
Handle some uninitialized fields recently flagged by Coverity.
2025-10-10 18:52:49 -04:00
Goober5000 17feb28d29 rearrange FRED2 menus to match qtFRED
See issue #6985 and PR #7064.
2025-10-10 01:29:27 -04:00
Goober5000 897429e239 Merge pull request #6630 from Goober5000/form_on_wing_and_stay_still_updates
form-on-wing and stay-still updates
2025-10-09 22:48:58 -04:00
Goober5000 712491696f Merge pull request #7080 from Goober5000/patch_cancel_future_waves
patch cancel-future-waves to handle absent waves
2025-10-08 22:48:58 -04:00
Goober5000 b8fa52097c patch cancel-future-waves to handle absent waves
If the `cancel-future-waves` sexp is called when a wave is not present, it would previously not handle wing cleanup, such as setting flags and updating the mission log.
2025-10-08 21:56:42 -04:00
wookieejedi 7eff7900a6 Allow mods to tune generic debris generation (#7027)
* Allow 'No Impact Debris' to also apply to explosions

The ship/subsystem flag `no impact debris` prevents the default debris shards from spawning, which is very valuable especially now that impact effects can be completely customized via the -particles table. Now that particle effects are also an option for ship and subsystems explosions, it is also invaluable to have the ability to turn off the spawning of shards when either of those explosions happen.

This PR uses the same flag of `no impact debris` and simply does not spawn the default shards for ship explosions or subsystem explosions of that flag is enabled. I thought about adding two new flags, but that would add to the flag clutter, and most folks who use these flags to turn off impacts I would estimate also want them off for actual explosions. Also, a new flag called `no explosion debris` would likely be confusing, so if a new flag were to be added it should be called something like `no default explosion debris shards` or something. Overall I've opted with the simpler and cleaner solution to just have it all under the impact flags. If two new flags are desired though I can easily add them.

Tested and works as expected.

* throw clang a bone

* use two different flags

* update flag name

* typo and logic fix

* allow custom generic debris to always work

* update to better methods

* cleanup logic

* clang
2025-10-07 07:42:44 -04:00
BMagnu 2808606b84 Fix Cross Compilation (#7077)
* Fix a few libraries for functional cross compilation

* include timeval
2025-10-06 14:12:40 +00:00
BMagnu 84d3cdf847 Fix anim executables when animations wrap around (#7076) 2025-10-06 13:53:44 +00:00
Asteroth b1e4bd7ef8 flight cursor aiming for secondaries (#7075) 2025-10-06 15:49:16 +02:00
Goober5000 f087c8adbb Merge pull request #7072 from Goober5000/colour_followup
fix required_string_either bug
2025-10-03 13:55:11 -04:00
Goober5000 8a8d7222f2 fix required_string_either bug
`optional_string_either` advances the parser by default, but `required_string_either` does not advance the parser by default.  So be sure to advance the parser here.

Followup to #7069.
2025-10-03 12:42:38 -04:00
Goober5000 9c353e71da Merge pull request #7067 from Goober5000/fireball_parse_lookup
Updates to fireball parsing
2025-10-01 23:36:55 -04:00
Goober5000 5a3cfa5f19 add ability to parse fireball type using its ID 2025-10-01 21:47:03 -04:00
Goober5000 afcb4871a3 upgrade stuff_int_list with enum class types
For type safety, use enum class rather than int.

Also make a few miscellaneous tweaks such as adding a new casting macro and making detail level settings a bit cleaner.
2025-10-01 21:47:01 -04:00
Goober5000 2589d8c542 Merge pull request #7069 from Goober5000/color_colour
color/colour updates
2025-10-01 21:42:53 -04:00
Mike Nelson 2374da528f Fix Asteroid Table Parsing (#7068)
* Zero initialize the string

* Initialize Radius
2025-09-30 13:56:30 +09:00
Goober5000 3d8174d0d8 color/colour updates
Add Colour as a recognized parsing option in several more places where Color is used
2025-09-29 02:05:49 -04:00
BMagnu dc50aaa86b Particle Scripting API (#7061)
* Expose particle effects to scripting

* Allow creation of particle sources

* Add API for particle sources

* bugfixes
2025-09-28 10:40:46 +02:00
BMagnu 2c04313c59 Fix copy and paste error (#7066) 2025-09-27 12:55:22 +00:00
Mike Nelson e6e551121f Modernize QtFRED menus (#7064) 2025-09-26 15:22:50 +00:00
Mike Nelson aadf1c5f8b QtFRED Briefing Dialog (#7032)
* initial cleanup

* rough in more code by copying from debrief

* make buildable

* prep the briefing data model

* completed data model

* activate the ui

* unused param

* stub this

* cmake list is important

* set max cam transition higher

* static cast
2025-09-26 08:56:28 -04:00
Mike Nelson 6cb49fa245 QtFRED Campaign Editor (#7030)
* refactored campaign dialog model

* begin setting up the ui code

* work on available missions list

* available missions filter

* working state

* better available missions list

* add tooltip

* handle the retail file format

* add custom data button

* prep for mission graph

* prep for campaign graph widget

* basic functioning nodes

* Basic edges implementation

* path arrows and names

* graph view upgrades

* fixup mode handling

* clean comments

* add some styling

* drag to connect

* add and remove mission nodes

* minor cleanup

* edge emphasis based on selection

* style tweak

* cleanup some graph and tree interactions

* clean out old code

* add getters for lists

* implement the rest of the detail controls

* custom data

* cleanup

* add branch manipulation controls

* branch move buttons

* set as first command

* linux is soooo sensitive (to case)

* more case

* unused param

* fixes

* more terminator checks

* namespaces are fun for the whole family

* Clang

* Private, you're fired!
2025-09-26 08:56:10 -04:00
Mike Nelson d0737926bd QtFRED Variables Dialog (#7020)
* refactor variables/containers model and variables ui

* get containers working

* switch to tab view and add filters

* fix the filters

* fixes

* ooo

* static casts

* more static cast

* unused

* clang
2025-09-26 08:55:43 -04:00
Goober5000 cfc1088ed0 Merge pull request #7056 from Goober5000/docked_wing_fixes
for docked wings, initialize some data before the wing is created
2025-09-25 13:17:29 -04:00
Mike Nelson 0d4c222a97 QtFRED Debriefing Dialog (#7018)
* qtfred debriefing dialog

* explicit flags

* clang
2025-09-25 07:51:08 -04:00
BMagnu 22b5f8148d Fix Beam Particles (#7057) 2025-09-25 07:49:49 -04:00
Goober5000 e3ff08ea46 for docked wings, initialize some data before the wing is created
Docked wings are created with a sort of reverse-bootstrapped initialization sequence, since normally wings are created first and the wing creates the ships.  Issue #7041 found a hole in this sequence, as the mission log entry was added before the IFF was assigned.  Fix this bug, and potential future bugs, by moving the assignment of certain fields to immediately before the docked wing is brought in.  AI can't be moved that easily, so fix AI setup by calling a separate function.

Fixes #7041.
2025-09-24 23:47:14 -04:00
Goober5000 e6da70a0ff Merge pull request #7053 from Goober5000/check_for_hangar_bay
check missions for missing hangar bays
2025-09-22 22:49:51 -04:00
Goober5000 ac03c1a612 Merge pull request #7046 from Goober5000/nebula_poofs_sexps
add more advanced sexps for nebula poofs
2025-09-22 22:49:28 -04:00
BMagnu 5903dc7e9d Filter out invalid particles (#7055) 2025-09-23 04:06:09 +02:00
Mike Nelson a5dc72c56c localize language names for SCPUI (#7052) 2025-09-22 07:50:15 -04:00
Goober5000 c1444781e8 check missions for missing hangar bays
It's possible for ships to be designated as arrival or departure anchors when these ships do not actually have hangar bays.  This cannot normally happen in the interface but can happen when missions are edited in a text editor, or are imported, or the modpack is changed.  So add warnings and checks for this situation.
2025-09-22 00:37:38 -04:00
Goober5000 300cd8a1cf Merge pull request #7049 from Goober5000/fix_anchor_error_handling
fix handling of invalid arrival/departure anchors
2025-09-21 15:36:07 -04:00
Goober5000 60b8876eb0 fix handling of invalid arrival/departure anchors
Missions with invalid anchors would previously cause crashes, so update the anchor code to fail gracefully:
1. Negative anchors (especially -1) could trip up the bitwise-AND in the mission saving code, so check for negative anchors *before* checking the special-anchor flag
2. In the event a mission runs with a negative anchor (which shouldn't happen because even `<error>` is added as a recognized anchor), handle the situation with if()s as well as Asserts
2025-09-20 23:59:34 -04:00
BMagnu 3c939f5cc4 Particle Curves with access to host object & ship properties (#7047)
* Add parent / host input curves

* reorder some includes to allow particles access to ship.h

* Allow modular_curves to deal with functions returning optionals

* Add convenience function to return an optional from an objp and an instance type

* Add a bunch of particle curve inputs

* Fix internal linkage warning

* Add hitpoint and shield fraction (albeit limited to ships)
2025-09-21 02:52:22 +02:00
The Force db4f9e4370 QTFred: Ship Editor Modernization 2 Part 2 (#7026)
* Switch to using signals/slots rather than connect

* Adds coloured persona box

Adds coloured persona box/ fixes persona loading, and reduces amount of data reset on model change

* Update To new flags widget

Updates To new flags widget this reqires moving several non flag items to other dialogs, which also lead to adding a new feature that lets a custom guardian threshold be set in editor

* Switch to size_t

* Make Mission Spec work with changes to Flaglist

* Remove Duplicate member

* Clang Appeasement

* Make static

* Properly initialise guardian threshold

* Convert Player Orders dialog to Checklist box

Unfortunately also formats Shipeditordialogmodel

* Update Custom warp to modern style

* Update Initial status to modern style

* Missed a file

* Update Ship Flags Closing

* Remove QT Code from model

* Update Ship Goals Closing

* Update Special Stats Closing

* Minor Changes

* Replace Paths Dialog With generic checkbox dialog

* Update Texture Replace to modern style

* Cast to int

* Initialize then append

* Clang Fixes

* Two didn't save

* More Clang

* Tighten NOLINT's

* Remove dead code

* Change to built in buttons
2025-09-20 08:18:40 -04:00
Goober5000 fa250c5ff6 add more advanced sexps for nebula poofs
1. Add new SEXP operators for setting and fading poofs that will work with one or more arguments, or no arguments (which will affect all poofs at once).
2. Update SEXP help, including clarification that the three `nebula-change-` operators require a nebula to be active to have any effect.
2025-09-19 22:55:09 -04:00
Goober5000 0c736b7bca address feedback 2025-09-18 21:30:45 -04:00
Goober5000 440c6fda4c restore original squad-commanded priority by removing lines added in 09d116f 2025-09-18 21:30:45 -04:00
Goober5000 aad6c84684 add $purge player-issued form-on-wing after subsequent order: flag 2025-09-18 21:30:45 -04:00
Goober5000 7f8674c532 enhance goals with additional parameters 2025-09-18 21:30:43 -04:00
Goober5000 40b4294b13 add Clear_all_goals_first goal flag, allow it to be used with any goal, and adapt goal assignment accordingly 2025-09-18 21:27:43 -04:00
Goober5000 54ee4a30d6 Preliminary cleanup of goal code. 2025-09-18 21:27:41 -04:00
Goober5000 a3691ed481 Merge pull request #7043 from Goober5000/disable_form_on_wing_at_mission_start
add ai_profiles flag to disable form-on-wing at mission start
2025-09-18 21:25:14 -04:00
BMagnu 973bd5570e Small particle copy-paste fixes (#7044)
* Use correct curve

* Properly init animation data
2025-09-18 12:49:46 +00:00
BMagnu 15445408f9 Particles can now emit light (#7040)
* Add backend for paticles to emit light

* Parse new light options

* Modular Curves account for light

* Actually apply curves

* remove comment

* Add cone mode
2025-09-18 13:39:12 +02:00
Goober5000 337b0402ce add ai_profiles flag to disable form-on-wing at mission start
By default, the player's wing will be issued a form-on-wing order at the beginning of the mission if the wing has no other initial goals.  Wings in the "starting wings" list that are not the player's wing will also be issued this goal if they arrive after mission start and have no other initial goals.  This PR adds a flag to disable this behavior.
2025-09-17 23:19:36 -04:00
Mike Nelson 8ee96781b3 QtFRED Team Loadout Dialog (#7016)
* unify naming and some ui cleanup

* sidetracked by a typo

* another sidetrack

* show team list as dropdown like other dialogs

* This is a mess

* cleanup model init and apply

* tweaks

* clear string vars

* validate variables

* some cleanup

* refactor data model

* finish the model and begin the ui

* start getting the ship list working

* fix multi select issues

* activate all 4 lists

* activate teams, filters, selection stuff

* fixes

* clang

* one more cleanup
2025-09-17 16:39:55 -04:00
wookieejedi 58ff297495 Update to #7023 (#7042)
From Sept 15 onward in fast debug I hit this error when using weapon create
ASSERTION: "cur_frame < part->nframes" at particle.cpp:371

Assert: "cur_frame < part->nframes"
File: particle.cpp
Line: 371

Thanks to lafiel, `that assert should have an `|| (part->nframes == 0 && cur_frame == 0) appended beaause while we need to stay inbounds for animations, frame 0 for both is valid for static images`
2025-09-17 15:16:24 +09:00
BMagnu 7875e5f7c7 Supply normals for edge hits (#7039)
* Pass out normals for edge hits

* Remove now-obsolete comments

* Also add third instance of disabled particle normal
2025-09-16 13:45:12 +09:00
Shivansps 8057b52a04 Do not set IS_X86 when target cpu is armv7 (#7033) 2025-09-16 13:38:02 +09:00
Mike Nelson ce3597f5a6 QtFRED Relative Coordinates Dialog (#7031)
* qtfred relative coords dialog

* clang
2025-09-15 15:56:58 -04:00
wookieejedi d42789decd Add new option for 'better combat collision avoidance' (#7036)
The `$better combat collision avoidance for fightercraft:` flag is incredibly useful in preventing AI from hitting large ships while attacking, and the ability to tune the collision avoidance aggression factor further helps mods tune behavior based on their ship speeds (tuning that value is via `+combat collision avoidance aggression for fightercraft:` ). Nevertheless, the improve collision avoidance still is not performed for that actual fightercraft's actual target, so there can be situations where a fast fighter attacks a large ship with high speed, and then when that fighter break off from the attack they turn directly into the large ship's hull. Allowing the target to be incorporated into the `better_collision_avoidance_triggered` check prevents this from happening. To allow the target to be a part of the checks we just simply need to not pass an 'ignor_ship` argument to `maybe_avoid_big_ship`.

This PR adds that ability via flag. This new flag is tested and works as expected. For example, without the flag a squadron of TIEs in FotG would literally ram themselves to death attacking some of our larger capital ships because they would break off attack too late, but with this flag they no longer accidentally collide at all. In discussion with Asteroth it was decided to make this enhanced behavior behind a flag, too.

This PR also fixes a small oversight from the original #2810 where `next_check_time` was never actually used within the `else` block of `maybe_avoid_big_ship`.
2025-09-15 07:31:02 -04:00
Goober5000 a32c3187de Merge pull request #7037 from Goober5000/save_fix
fix load/save mismatch in missions
2025-09-14 23:12:11 -04:00
BMagnu 1e7431a044 Give Particles Access to Their Spawner Effect (#7034)
* Make particles aware of the effect that spawned them

* Allow modular curves to call global functions from submember inputs

* Add more in and outputs for particle lifetime curves

* Allow post-curve velocity as a curve input

* fix warning and incorrect assig

* Disable CheckTriviallyCopyableMove

* Clang Tidy
2025-09-15 04:42:06 +02:00
Goober5000 b0166dd6a6 fix null vector assertion in slash beam collisions (#7038)
Beams in general, and slash beams most likely, can collide with the edge of a model, which will cause a non-normalized vector assertion when particle effects are created.  So comment out the normal assignment until a more comprehensive fix can be coded.
2025-09-15 04:42:00 +02:00
Goober5000 a023a1a82f Merge pull request #7014 from Goober5000/cinematic_followup
modular table fix for cinematic warp
2025-09-14 19:43:53 -04:00
Goober5000 bf51f8ee91 fix load/save mismatch in missions
When `$Contrail Speed Threshold:` and `+Volumetric Nebula:` are used in a mission, builds since 23.1 would throw an error, since the fields were loaded in a different order than they were saved.  It turned out that the save order was the correct order, so this fixes the load order.
2025-09-14 16:50:11 -04:00
Goober5000 f89cd69ad9 Merge pull request #7024 from Goober5000/event_double_quotes
restore double quote conversion in event editor
2025-09-08 22:04:07 -04:00
Taylor Richards 9d3ba25606 fix various issues with DDS decompression (#7022)
Fix heap corruption with smaller mipmaps due to decompression output
always being a 4x4 block rather than actual mipmap size. It's necessary to
pad the allocated data size to allow for the overflow.

Fix heap corruption with DXT1 due to using improper data offsets for the type.

Fix data offsets not always taking the depth value into account.

Adjust code for slight increase in decode performance.

Update bcdec to 0.98
2025-09-08 21:49:48 -04:00
Goober5000 0ee8c4b01a restore double quote conversion in event editor
The double quote conversions added in #7008 were removed in #6961, probably inadvertently due to the branch merge.  This adds them back.
2025-09-07 23:23:02 -04:00
Goober5000 fc102056b8 make legacy particle spews more robust (#7023)
If a pspew specifies a single-frame image, be sure that it is loaded correctly.  Fixes a particle crash in Inferno.
2025-09-07 02:32:07 +00:00
wookieejedi 877373f257 Fix for modern Thruster Particles (#7021)
Allows `$Thruster Effect:` by fixing copy-paste bug. Tested and fix confirms to work.
2025-09-06 06:31:55 +02:00
combatlombax 31d73b7ed5 Removing block that prevented volumetric and background nebulae from rendering simultaneously (#6853)
* Removing block to allow volumetric and background nebulas to render at the same time

* Updating code from feedback on pull request #6853

* What the... did I miss a line?

* Dual fog layering should now work when both nebula types are active
2025-09-06 00:00:06 +02:00
Goober5000 e4bba0c7f2 Merge pull request #7010 from Baezon/direct-fire-lead-target
Add 'direct fire lead target' beam flag
2025-09-03 23:27:27 -04:00
Goober5000 0d0574df0f Merge pull request #7013 from vmedea/vulkan-build-fixes
Fix build issues with Vulkan SDK 1.3.301+
2025-09-03 23:27:19 -04:00
Goober5000 f2bc71c13a small optimization 2025-09-03 22:45:31 -04:00
Goober5000 f88e5c475e modular table fix for cinematic warp
Small and subtle fix: move the CINEMATIC options inside the preceding if() block in the same way that the flare style options are inside their preceding if() block.  This prevents the CINEMATIC options from being reset to their default values in subsequent modular table parsing.  Followup to #6500.
2025-09-03 22:04:48 -04:00
Mara Huldra 5848df31dd Fix build issues with Vulkan SDK 1.3.301+
This fixes two build issues:

- `vk::PFN_DebugReportCallbackEXT` takes `vk::DebugReport*EXT` not C
  type `VkDebugReport*EXT` since version 1.3.304.
  (KhronosGroup/Vulkan-Hpp@d5a18dc87e)
- `vk::ObjectDestroy` was moved to namespace `vk::detail` in version
  1.3.301.
  (KhronosGroup/Vulkan-Hpp@6e5489fcd9)

Guard them with version checks, so that compiling with older verions
of Vulkan keeps working.

Closes #6416.
2025-09-03 20:26:29 +00:00
wookieejedi 2820532ab3 Expose value of idle circling with 'attack-any' (#7011)
* Expose value of idle circling with 'attack-any'

The attack-any order is extremely useful and utilized order, and there was one aspect that was ripe for exposure to modders: the `CHASE_CIRCLE_DIST` that was used to determine how big of a circle to fly around while the AI waited for new hostiles to arrive. For ships with high speeds, like FotG and others, the default radius of 200 m was a bit on the small side, which lead to ships flying in tight circles. This was was counterproductive to goals of tuning the AI to have a more cinematic style, such as where AI would fly around in large distances while waiting for enemies to arrive. Fortunately, this value is easy to expose and allow modders to tune with just a new ai_profiles value.

Tested and works as expected.

* improve name and use correct distance
2025-09-02 22:55:03 -04:00
Goober5000 32084cfe59 Merge pull request #7012 from Shivansps/check-mod-nullptr
Fix crashing if mod uses custom hud gauge and no -mod cmdline is passed
2025-09-02 20:59:26 -04:00
Salvador Cipolla 7a75aaa172 Check cmdline_mod for nullptr before using it 2025-09-02 19:17:16 -03:00
John Fernandez c50cffae17 Merge pull request #6998 from MjnMixael/qtfred_background_dialog
QtFRED Background Editor Dialog
2025-09-01 22:50:12 -04:00
Mike Nelson b7aa53dd79 address feedback and fix some ui issues 2025-09-01 18:54:15 -05:00
Asteroth 90e1f18fa2 add recoil for secondaries (#7007) 2025-08-31 10:01:31 -04:00
John Fernandez 19a6a1aa97 Merge pull request #7000 from MjnMixael/qtfred_volumetric_nebula_dialog
QtFRED Volumetric Nebula Dialog
2025-08-31 09:09:56 -04:00
Asteroth 730b294d7b add 'direct fire lead target' 2025-08-29 20:39:06 -04:00
The Force 42eae002d2 Fix Permanent Guardian (#7009)
* Switch to using signals/slots rather than connect

* Adds coloured persona box

Adds coloured persona box/ fixes persona loading, and reduces amount of data reset on model change

* Update To new flags widget

Updates To new flags widget this reqires moving several non flag items to other dialogs, which also lead to adding a new feature that lets a custom guardian threshold be set in editor

* Switch to size_t

* Make Mission Spec work with changes to Flaglist

* Remove Duplicate member

* Clang Appeasement

* Make static

* Properly initialise guardian threshold
2025-08-29 15:01:11 +00:00
Mike Nelson b3db62cf25 QtFRED Mission Events Editor (#6961)
* Match file framework of mission events to the rest

* first pass at splitting up the model and ui layers

* fix up message side of event editor

* fix event selection

* fix lots of event bugs

* Get event annotations working

* type to search for operators

* control annotations

* Add event dragging and fix event deletion

* cleanup

* Event and Msg order manipulation

* implement some remaining message todos

* minor cleanup

* prep for browse ani dialog picker

* basic file browser for now

* cleanup

* unused var

* more unused

* clang

* static member access

* rebase error

* fix rebase error

* address feedback

* missing parenthesis
2025-08-29 09:05:11 -04:00
Mike Nelson 689d184869 commit error 2025-08-28 22:54:35 -05:00
Mike Nelson 772920b727 static method 2025-08-28 22:54:34 -05:00
Mike Nelson 2b2320ffe7 volumetric nebula dialog 2025-08-28 22:54:34 -05:00
Mike Nelson b9229cd713 more static 2025-08-28 22:54:12 -05:00
Mike Nelson 886b54c298 more clang 2025-08-28 22:54:12 -05:00
Mike Nelson ca4540a821 clang 2025-08-28 22:54:12 -05:00
Mike Nelson 0f73a59155 clean 2025-08-28 22:54:12 -05:00
Mike Nelson 1d1ed1905b minor fix 2025-08-28 22:54:11 -05:00
Mike Nelson 917b4547ec save swap with index 2025-08-28 22:54:11 -05:00
Mike Nelson 614f510d0a misc group stuff 2025-08-28 22:54:11 -05:00
Mike Nelson aa7fc6dafd skybox section 2025-08-28 22:54:11 -05:00
Mike Nelson cb4e132df2 ambient controls 2025-08-28 22:54:11 -05:00
Mike Nelson fe17997ca3 nebula groups 2025-08-28 22:54:11 -05:00
Mike Nelson f691966675 sun section 2025-08-28 22:54:10 -05:00
Mike Nelson a531fd9fed wire up the bitmap section 2025-08-28 22:54:10 -05:00
Goober5000 9c65e1c9b5 Merge pull request #7008 from Goober5000/more_fred_replace_stuff
add additional double quote conversion to FRED and qtFRED
2025-08-28 19:11:03 -04:00
Goober5000 69dfe8194d Merge pull request #7006 from Goober5000/parse_error_fixes
fix some parse error handling
2025-08-28 19:10:50 -04:00
Goober5000 2d37a24b4d fix formatting of alt names and callsigns when saving a mission 2025-08-28 01:20:51 -04:00
Goober5000 b970e22410 add additional double quote conversion to FRED and qtFRED
Any place in FRED that uses XSTR is susceptible to double quotes interfering with the standard XSTR format, not just the expected briefing, debriefing, messages, etc.  This PR adds `lcl_fred_replace_stuff()` - which handles not only quotes but semicolons and slashes as well - to the other places where XSTR is used.

(Note that quotes are handled just fine in non-XSTR fields.  Semicolons will still truncate non-XSTR fields, but addressing that is beyond the scope of this PR as it would require editing every text field in FRED.  Fortunately, errant semicolons merely modify the text as opposed to corrupting the mission file.)

Fixes #7005 in both FRED and qtFRED; tested in both.  Caveat aedificator: the briefing editor is not yet implemented in qtFRED, so the relevant fixes to briefing icon labels and closeup labels cannot yet be ported over.
2025-08-28 01:02:06 -04:00
Goober5000 289de86eb9 fix some parse error handling
Parse errors that are actually errors should use `error_display(1, ...)`, rather than merely logging them (which doesn't abort parsing) or using `Error` (which omits the line number and file name).

Fixes #7002.  Tested with a modified script file that produces the same error.
2025-08-27 22:48:39 -04:00
Kestrellius 1d561a1f1e Merge pull request #6999 from Kestrellius/cockpit-brightness-adjust
Cockpit-specific light intensity adjustment for lighting profiles
2025-08-27 17:59:56 -07:00
The Force 1310c38642 QTFRED Ship editor Modernisation 2 Part 1 (#6989)
* Switch to using signals/slots rather than connect

* Adds coloured persona box

Adds coloured persona box/ fixes persona loading, and reduces amount of data reset on model change

* Update To new flags widget

Updates To new flags widget this reqires moving several non flag items to other dialogs, which also lead to adding a new feature that lets a custom guardian threshold be set in editor

* Switch to size_t

* Make Mission Spec work with changes to Flaglist

* Remove Duplicate member

* Clang Appeasement

* Make static
2025-08-27 12:38:43 -04:00
John Fernandez b090e5d09c Merge pull request #7004 from MjnMixael/qtfred_fix_loadout_again
Fix QtFRED Weapon usage for real
2025-08-27 12:37:43 -04:00
Mike Nelson e9311ec828 use correct array throughout 2025-08-27 10:58:04 -05:00
wookieejedi eaafd8cb13 Merge branch 'master' into cockpit-brightness-adjust 2025-08-26 18:02:29 -04:00
wookieejedi 5f899634c2 Fix new deferred check (#7001) 2025-08-26 17:33:40 -04:00
Kestrellius 7918797eec more bugfixing 2025-08-26 00:37:30 -07:00
Kestrellius d279e477b3 bugfixing 2025-08-25 21:33:00 -07:00
Kestrellius d2f842494d setup 2025-08-25 21:00:05 -07:00
Kestrellius a6f9337d5a add check (#6997) 2025-08-25 22:48:13 -04:00
Asteroth 1a1d3f040c fix interpolate matrices (#6996) 2025-08-25 17:17:52 +00:00
Mike Nelson 109c2b5a81 QtFRED Music Player Dialog (#6994)
* qtfred music player dialog

* cleanup

* int comparisons

* clang wins
2025-08-25 09:59:16 -04:00
Mike Nelson c2b92410aa QtFRED Global Ship Flags Dialog (#6993)
* Global Ship Flags Dialog

* modernize old fred code
2025-08-25 07:44:07 -04:00
Mike Nelson 16926a4395 QtFRED Shield Sys Dialog (#6991)
* refactor global shield sys dialog

* I win this time, Clang

* Or not...
2025-08-25 07:43:53 -04:00
Mike Nelson 76440ddfd1 QtFRED Reinforcements Dialog (#6990)
* cleanup reinforcements dialog

* unused
2025-08-25 07:43:41 -04:00
Mike Nelson 08a73c1ed7 QtFRED Voice Acting Manager Dialog (#6962)
* revise Voice Acting Manager dialog

* fixes

* clang

* make window modal
2025-08-25 07:43:10 -04:00
Mike Nelson 9f30d3001c QtFRED Command Briefing Dialog (#6968)
* refactor the qtfred command briefing dialog

* clang
2025-08-24 10:10:52 -04:00
Mike Nelson 6c101fe52a QtFRED Fiction Viewer Dialog (#6965)
* refactory qtfred fiction viewer dialog

* do this more efficiently
2025-08-24 10:10:40 -04:00
Mike Nelson bc083dc61b QtFRED Mission Specs Dialog (#6945)
* refactor mission specs dialog

* clang

* Stub in all the sub dialog files

* custom strings dialog

* custom strings dialog cleanup

* custom data dialog

* sound environment dialog

* move custom wing names into mission specs subfolder

* custom wings dialog

* unused

* clang tidy

* clean return

* refactor mission specs dialog

* move custom wing names into mission specs subfolder

* custom wings dialog

* rebase error

* remove a couple empty functions

* select after creating
2025-08-24 10:10:22 -04:00
Taylor Richards 9755d44c5f update mdns to v1.4.3 (#3973) 2025-08-23 20:32:18 -04:00
wookieejedi 9502af6f20 Fix gr.createParticle with parent velocity (#6970) 2025-08-23 17:40:21 -04:00
wookieejedi 5eb80f55bc Fix conversion of old muzzle flash to new type (#6972)
Fixes the updated name of `Pixel Size At Emitter`
2025-08-23 16:31:39 +00:00
Mike Nelson 4b9ccfc68f QtFRED Asteroid Dialog (#6908)
* begin asteroid dialog rewrite

* activate fields

* clean up connections

* clean up and rearrange files for consistency

* use constructor to zero out the field when needed

* fix checks errors

* remove duplicate lines

* add missing z
2025-08-23 15:20:38 +00:00
Mike Nelson 9db567ea40 QtFRED Mission Goals Dialog Cleanup (#6951)
* cleanup and bugfixes of mission goals editor

* redundant specifier

* static casts
2025-08-23 10:58:14 -04:00
Mike Nelson 7d8b437847 Qtfred Wing Editor Dialog (#6939)
* wing editor dialog base functionality

* flags and path masks plus some cleanup

* squad logo selection

* custom warp params using the ship editor version

* some cleanup

* some finalization

* cleanup, fixes, and clang

* remove duplicate lines after rebase

* merge namespaces
2025-08-23 10:57:54 -04:00
Mike Nelson e9d4f322d0 QtFRED Waypoint Path Dialog (#6935)
* cleanup qtfred waypoint dialog

* range based loops

* typo
2025-08-23 10:57:35 -04:00
Mike Nelson 080aa65ec8 QtFRED Object Orientation Editor Dialog (#6932)
* refactor and cleanup the qtfred object dialog

* clang is being pedantic

* BRAAAACE FOR IMPACT

* clang

* switch statement
2025-08-23 10:57:18 -04:00
Kestrellius 1207dfcae9 Merge pull request #6946 from Kestrellius/asteroid-effects
Particle effects for asteroid death
2025-08-23 07:00:43 -07:00
Kestrellius 8db0a44d7d Merge pull request #6938 from Kestrellius/deathroll-effects
Particle effect hooks for ship deathroll explosions, pre-death explosions, and propagating explosions
2025-08-23 07:00:32 -07:00
Goober5000 9ba88ca163 fix error in error reporting routine (#6969)
The `_s` functions throw an exception if the string's length is exceeded.  But in an error reporting routine, we are far more interested in the string than the exception, and we'd rather get a partial error message than a crash.  Plus this bit of code is already set up to truncate the string anyway.
2025-08-23 09:37:47 -04:00
John Fernandez c451a82f87 Merge pull request #6966 from MjnMixael/fix_qtfred_variables_strings
QtFRED Fix variables heap corruption crash
2025-08-22 22:00:05 -04:00
Mike Nelson eb36b56f1d fix 2025-08-22 12:10:52 -05:00
Mike Nelson c39d6b290f convert tostdstring to const data 2025-08-22 11:31:19 -05:00
John Fernandez 56a3ddd5d3 Merge pull request #6150 from JohnAFernandez/qtFRED-variables-editor
qtFRED variables editor
2025-08-22 10:44:39 -04:00
John Fernandez 89219df6af And move this 2025-08-22 09:40:49 -04:00
John Fernandez fa80fc3278 APPEND THIS 2025-08-22 09:36:19 -04:00
John Fernandez 84897636cd Hopefully last of clang warnings 2025-08-22 09:14:07 -04:00
Kestrellius 9e618f6632 Particle effect hooks for subsystem destruction (#6937)
* add particle hook

* partial bugfixing

* bugfixing

* appeasement

* syntax fix
2025-08-22 07:19:15 -04:00
Taylor Richards 43e85dd8fd update libpcp to lastest main (#6963)
This should fix build issues on ARM64 Windows.

Note that the project name changed from libpcp to libpcpnatpmp and is in
active development again after a multi-year period of inactivity. There
are currently no versioned releases. This is hash b5fe5e2.
2025-08-22 07:18:48 -04:00
Taylor Richards a12c1de012 fix memory access error with active games list (#6964)
During maintenance of the active games list it's possible for the list to be
cleared before a stale item is removed leading to a access violation. So we
need to confirm the list isn't empty when attemping to erase the stale  entry.

This also fixes a bug where the list entry index is incremented even when an
entry is removed which can cause an improper selection index being used.
2025-08-22 07:18:38 -04:00
The Force b1247042ac QTFred: Alt Ships Class dialogs (#6959)
* Add Files and UI

* Initial Setup of dialog

* Finish Dialog

* Obey Rule of 3(Possibly)

Why Cant the compiler do this?

add const

* Fix use of default keyword

* bit more

* Remove unused Vars

* Missed one

* Clang work

* Remove future proofing as the compiler doesnt like it

* Make static/ Apply de morgans

* Lets try this one

* Revert to inital and supress warning

Also switch to Assertions
2025-08-22 07:18:30 -04:00
John Fernandez 3725f27a5c Match header 2025-08-21 23:54:18 -04:00
John Fernandez 55efbe73b3 Forgot that VS doesn't have auto save 2025-08-21 23:41:48 -04:00
John Fernandez 40b9924892 More clangy 2025-08-21 23:39:40 -04:00
John Fernandez c21cbb3067 Handle shadowing 2025-08-21 11:57:06 -04:00
John Fernandez 7b9b237a66 No clever commit message this time
How long must I attempt to satisfy the endless hunger of clang
2025-08-20 23:45:42 -04:00
John Fernandez f15a6bc3e8 Handle more clang warnings 2025-08-20 23:37:11 -04:00
John Fernandez 3a7269275e blarg 2025-08-20 22:58:10 -04:00
John Fernandez 81656e8dff Add back missing closing markup 2025-08-20 22:37:04 -04:00
John Fernandez 7c2deb6761 Merge branch 'qtFRED-variables-editor' of https://github.com/JohnAFernandez/fs2open into qtFRED-variables-editor 2025-08-20 22:29:56 -04:00
John Fernandez 552817b8e1 Missing Parentheses 2025-08-20 22:29:30 -04:00
John Fernandez 555a413d7e Merge branch 'master' into qtFRED-variables-editor 2025-08-20 22:26:57 -04:00
John Fernandez 0241c9f4b9 Scare off DeMorgan 2025-08-20 22:25:34 -04:00
John Fernandez c93c91013c Do what clang says or else 2025-08-20 22:17:36 -04:00
John Fernandez 81d08cd6f3 More Clang 2025-08-20 22:13:16 -04:00
John Fernandez 4efcd82940 Make sure to use empty 2025-08-20 22:05:52 -04:00
John Fernandez a4014ea985 According to the internet this should work 2025-08-20 22:02:40 -04:00
wookieejedi 83dee2eab4 Spare future modders (#6960) 2025-08-20 18:56:16 -04:00
Mike Nelson 39f80289c2 QtFRED Mission Cutscenes Dialog (#6956)
* Mission cutscenes dialog

* fixes

* getter for now private member

* missed some cleanup

* assertion
2025-08-20 22:51:53 +00:00
Mike Nelson cbd491e277 QtFRED Jump Node Editor Dialog (#6940)
* qtfred jumpnode editor dialog

* unused function

* clang

* and and use SCP_trim
2025-08-20 18:08:52 -04:00
John Fernandez 1a60eff319 And try this 2025-08-20 17:31:20 -04:00
John Fernandez 7713e1b09b Don't overzealously mark this 2025-08-20 17:17:33 -04:00
John Fernandez c85876d0d3 don't forget headers 2025-08-20 14:46:33 -04:00
John Fernandez 6cadc350b1 Start fixing myriad clang issues 2025-08-20 14:40:10 -04:00
Kestrellius 806bb77298 Particle effect hooks for subsystem debris trails and debris explosions (#6944)
* add hooks

* parsing

* add particle hooks

* appease clang

* readability
2025-08-20 09:49:00 -04:00
Taylor Richards e5fccc0b19 fix conversion of Missiontime to milliseconds (#6957)
Broken in #6948 but the original didn't work properly either due to conversion
to int32_t and the resulting size limit.

Big assist from Goober5000 for noticing the bug and helping to sort out the
wonky values.
2025-08-19 12:12:55 -04:00
Kestrellius 0bf632ceae Add apparent size input for particle source curve (#6947)
* functional method

* add input
2025-08-18 22:36:17 -04:00
Kestrellius bd9ea61e9d appease clang 2025-08-18 19:17:18 -07:00
Kestrellius 836d7d4f2d switch include 2025-08-18 19:02:07 -07:00
Taylor Richards 5ea4a4c014 remove extra config bind on joystick axes (#6955)
Fixes fail sound due to the control set not changing between calls.
2025-08-18 21:50:15 -04:00
Taylor Richards dd71838715 fix array index bug in ship_get_subsys_index (#6954) 2025-08-18 21:49:35 -04:00
Taylor Richards b16854796a fix custom mouse cursors (#6953)
UI elements with custom mouse cursors wouldn't show them due to the cursor
being reset at the start of each frame. To fix this we need to be sure that
we only reset when the element is no longer active.
2025-08-18 21:48:51 -04:00
Taylor Richards 1142e7d9bc force proper ordering of joystick options (#6952) 2025-08-18 21:48:24 -04:00
John Fernandez 212c91c72f Merge pull request #6942 from MjnMixael/qtfred_flag_list_widget
QtFRED Flag List Widget
2025-08-18 18:39:02 -04:00
Kestrellius 4e75114479 Fix parented line sprite particles appearing in the wrong place (#6950)
* account for parenting

* fix p1 position
2025-08-18 16:45:17 -04:00
Taylor Richards e1ae938553 Merge pull request #6948 from notimaginative/compile_warnings
address various build warnings
2025-08-18 16:36:08 -04:00
Taylor Richards af833f059c fix avcodec deprecation warnings 2025-08-18 12:01:00 -04:00
Taylor Richards d9b7710f92 properly set some macOS bundle properties 2025-08-18 12:00:40 -04:00
Taylor Richards fc7389f95a fix various compiler warnings 2025-08-18 11:59:22 -04:00
Taylor Richards 908f306985 fix some abs() type conversion warnings 2025-08-18 11:56:39 -04:00
Taylor Richards 21c9b65c99 add _WIN32 guard to trackir/freetrack 2025-08-18 11:56:26 -04:00
Goober5000 f92bc15fc6 Merge pull request #6941 from Goober5000/6882_followup
restore inadvertently deleted code
2025-08-17 21:53:17 -04:00
Kestrellius 6539fc9aeb clean up control flow 2025-08-17 16:06:32 -07:00
Kestrellius a0730f601c add particle hook 2025-08-17 15:36:38 -07:00
Taylor Richards 59a362ed1d fix end-mission sexp in multi (#6943)
A previous attempt to fix the end-mission sexp in multi such that it wouldn't
trigger the jump out sequence was horribly broken. Notable safety checks, state
changes and server/client syncing was skipped over. This caused, as one
particular bug, a standalone server to stop communicating with the game host.
When this happened stats couldn't be accepted/tossed and you were stuck at the
debrief screen, being forced to disconnect from the standalone in order to
continue.

A proper fix for this is to use multi sexps to tell clients to enter a debrief
state before the standard end mission request is handled. This will allow it
the bypass the actual warp out animation for players while still doing the
normal end of mission handling.

NOTE: This is a breaking, but not fatal, multi change and should be safe
considering the recent version bump in early June. As such an additional
version bump is not required with this for 25.0.
2025-08-16 22:14:51 -04:00
Mike Nelson dadb91889f clang 2025-08-16 15:10:54 -05:00
Mike Nelson 41dc187456 add flag list widget 2025-08-16 14:51:32 -05:00
wookieejedi 7780769ba5 Add 'Nebula Usage Score' curve input (#6926)
Background:
Many mods, especially Event Horizon, have highlighted how versatile and useful using nebula are for creating a wide variety of immersive effects beyond just nebula, including weather, volcanic ash, specialty debris falling from the sky, and more. In addition volumetric nebula have opened another realm of possibilities for modders to creatively utilize. Furthermore, modders can not leverage the powerful updates to the particle system to create a wide diversity of effects, which can be also utilized within nebula (or any mission) by making weapons with those effects and then spawning those weapons with scripts/sexps. Just one creative example is exploding rock debris from a volcano (like in Event Horizon). The visuals of nebula poof though are of course spawned in density according to the `Nebula Detail Level` graphics slider in the Graphics tab of the Options menu. Thus, what the player sets will affect visuals of the nebula. As a modder creating visuals, this can have the effect where particle effects spawned from weapons used in nebula may look different in less dense fields. Ideally there could be a way for the modder to incorporate this Nebula slider detail into the development of their effects, to ensure that the effect visuals properly reflect and integrate with the nebula density that the player might see. The solution to this problem is luckily not difficult, as PR #6889 shows how it can be dealt with. That PR added a particle curve input `Particle Usage Score` which is incredibly useful to allow particle effects to scale visually according to the `Particle Detail Level` that the player has set in the Graphics tab of the Options menu. As such this utility could also be extended to the Nebula detail level to allow modders to develop weapon spawned effects for nebula missions that properly visually integrate with the nebula detail level.

This PR
This PR follows and extends the relatively simple approach of #6889, in that it adds a new curve input `Nebula Usage Score` for use within the particle effects table. This new input will allow modders to develop effects that can properly mesh with varying nebula detail levels--all with the aim to further expand the visuals and range of nebula utility for environmental effects.
2025-08-16 12:03:00 +02:00
wookieejedi c535108708 Allow background option to completely work (VR useful) (#6930)
From discussion with VR players, it appears that being able to toggle off the skybox/starfield/planet background is very useful to some players to prevent motion sickness. Currently, FSO has the toggle in the Graphics Options window for `Planets/Backgrounds` but it only turns off planet bitmaps. This PR suggests it should be expanded into an accessibility role for players, and properly turn off all background elements--so planets, plus skybox rendering (especially now since most mods also use skyboxes as backgrounds, whereas in early days this was not primarily the case).

Happy to discuss and/or add as a flag if folks think that would be needed.
2025-08-16 12:02:46 +02:00
Goober5000 dde16a54c6 restore inadvertently deleted code
This has an interesting history.  In the original Volition source release, global damage was set to originate from the shockwave position, which would have avoided the fireball issue fixed by #6882.  (Technically, though, this was only done for huge ships which would have still left the problem for smaller ships.)  In c2716d4ee8, this code was changed to run in non-HTL mode in an attempt to fix a bug.  Then in 70529dbd5a, as part of the removal of non-HTL mode, the code was entirely deleted.  So, this restores the code.

The #6882 fix is still valid, both because it works for non-huge ships, and because shockwaves may not necessarily originate from the subobject being destroyed.  The #6882 fix also subsumes the effect of the restored code, but it's still good to restore the code in case future development may depend on it.
2025-08-16 01:14:45 -04:00
Kestrellius 09b6888bc6 fix normals 2025-08-15 20:45:05 -07:00
Kestrellius be4d4eb1d5 add hooks 2025-08-15 19:09:12 -07:00
John Fernandez f0ecd88da9 Merge pull request #6934 from MjnMixael/qtfred_loadout_bug
Fix QtFRED weapon quantities
2025-08-14 22:35:36 -04:00
John Fernandez 6d9e590996 Merge branch 'master' into qtFRED-variables-editor 2025-08-14 22:32:35 -04:00
John Fernandez fe3dce092a Merge pull request #6169 from TheForce172/feature/weaponsDialog
Weapons Editor Dialog and Table Viewer
2025-08-14 22:31:05 -04:00
Mike Nelson 0b6150e961 Qt fred misc bugs (#6933)
* fix main window title

* Fix loadout dialog crashing
2025-08-14 22:14:27 -04:00
John Fernandez ebe2d235da Merge pull request #6931 from MjnMixael/qtfred_utils
Merge Recent QtFRED Generalized Dialogs
2025-08-14 21:53:43 -04:00
Mike Nelson 9ecc158aee initialize used_pool 2025-08-13 16:53:54 -05:00
Mike Nelson 4d8dbcb457 Cherry pick recent qtfred generalized dialogs 2025-08-13 08:49:13 -05:00
Mike Nelson 966ac4d878 Allow testing bay paths in the lab (#6902)
* allow testing bay paths in the lab

* unused param

* static member methods
2025-08-13 08:36:53 -04:00
Mike Nelson 1ffcaafc89 Allow customizing the fs2 selection effect (#6868)
* allow customizing the fs2 selection effect

* undo color color shift
2025-08-13 07:26:55 -04:00
John Fernandez 06f576bfa6 Merge pull request #6925 from wookieejedi/fix-skybox-zbuffer
Fix/enhance skybox submodel rendering
2025-08-12 10:36:18 -04:00
Taylor Richards d0c5fe6bcc fix series of bugs due to improper multi targeting (#6916)
Multiple issues were discovered to be from improper targeting data in multi
tracing back to the addition of multi-lock. This was due to differences in
how targeting works in single/multi and de-sync of this info between server
and client. Collecting accurate targeting data on the server and making sure
that the client will use that data resolves the tracking and sync issues.
2025-08-11 11:06:46 -04:00
wookieejedi f903180411 Fix/enhance skybox submodel rendering
Background:
Currently, skybox models can be set in FRED to have `No Z-Buffer` on or off. It is off by default, which is good because it ensures ships and other game objects cannot be rendered behind a skybox. Critically though, skybox models may have sub-models, especially now that animated sub-models are a feature (thanks to BMagnu!). If a skybox is using sub-models and `No Z-Buffer` is turned off, then there will be no proper model occlusion for the skybox and there will be ugly z fighting. Overall, BMangu stated "we could clear the depth buffer after the skybox with another flag".

Update
This PR fixes that issue by adding a new FRED game settings flag, `$Skybox internal depth consistency:` which when set to yes properly ensures there is internally consistency z-sorting with the skybox and any submodels it may have while at the same time ensuring that if `No Z-Buffer` is set in FRED that the skybox still correctly is always drawn behind mission objects like ships/debris.

Many thanks to @Bmagnu for the discussion and help in identifying and creating a proper fix for this issue that is clean, surgical, and supports all existing uses of `No Z-Buffer`.

Tested and works as expected. Fixes #6913.
2025-08-11 10:56:28 -04:00
The Force 726c954e37 Fix Crash On selecting different ship types (#6920) 2025-08-10 13:25:02 -04:00
The Force 1a72471543 Last One 2025-08-09 19:31:04 +01:00
The Force a7ec8d958b And now Clang 2025-08-09 18:35:57 +01:00
The Force c1c16c2fcf GCC Apeasment 2025-08-09 17:14:14 +01:00
The Force ec3fc3477d Refresh UI on Drag Drop 2025-08-09 16:59:27 +01:00
The Force 3524679c9e Fix Copy/Paste Error 2025-08-09 16:11:15 +01:00
The Force cf4f1b2ac6 Missed this 2025-08-09 16:09:14 +01:00
The Force 95fea193ae Couple More 2025-08-09 16:05:00 +01:00
The Force 8bf7f66483 Adress Feedback and other bug fixes 2025-08-09 15:53:58 +01:00
The Force 38f7479500 Appease GCC / Clang
Appease GCC

This is a silly error

AAAAAGH

Fix Model

Sick of this

Fix Supposed Shadowing

Hopefully the last

Or maybe not

Please
2025-08-09 15:53:56 +01:00
The Force f5b2b1bbd0 Add Comments 2025-08-09 15:53:54 +01:00
The Force 1a1d4bb057 Add TBL Viewer 2025-08-09 15:53:53 +01:00
The Force 858c116b3e Add Weapons Dialog
Move Ship Files To there own folder for legibility
Set File stucture

Create Layout

Rough outline established

Ship data fully loading

limit multiselection to one type

Add in missing conditon

Add weaponlist loading and move Bank model to its own file

Delete Missed file

Fix Cmake file

Change to size_ts

Fix Arrivals

Partial Drag and drop / better multi-select

Finish drag and drop / add ammo editing

Add Saving

Feature Complete
2025-08-09 15:53:09 +01:00
Mike Nelson b8b24a67e0 pass an initialized var instead of a temp var (#6917) 2025-08-09 13:16:52 +02:00
Kestrellius df0279869c Allow shockwave orientations to be relative to their parents (#6919)
* initial setup

* various orientations
2025-08-09 13:14:22 +02:00
Taylor Richards 85b37e098e remove "safe" versions of FD_SET/FD_ISSET (#6918)
The socket is a value to be assigned, not an index to be used. As such there
is no safety check to be done here and doing so just breaks things.

Fixes networking on Windows.
2025-08-08 20:43:34 -04:00
Taylor Richards 515beef61a fix cleanup of empty model.subsystems files (#6915) 2025-08-08 20:43:11 -04:00
Taylor Richards 7281f71da4 clear screen going to PXO lobby (#6914)
Widescreen mainhalls can still be visible on the sides when going to the PXO
lobby so we need to be sure the screen is cleared.
2025-08-08 20:42:47 -04:00
Mike Nelson 7c29656c7c let the model decide if the apply was successful and should close (#6912) 2025-08-08 10:36:19 -04:00
Mike Nelson 3900faac87 Fix issue where QtFred could have multiple Ship Dialogs open (#6909) 2025-08-08 07:30:37 -04:00
Kestrellius 5cd3b3a308 Extend particle interpolation to lifetime (#6910)
* the naive method

* the correct method
2025-08-08 07:29:59 -04:00
Mike Nelson 75b61efbd1 Custom Data for Campaigns (#6906)
* campaign custom data

* Assertion instead of assert
2025-08-07 09:56:52 -04:00
LuytenKyandwookieejedi b8c3c560d2 Implementation for an AI order to chase ship types -Second Attempt- (#6828)
* Back in business hopefully

* Refactor

* Resolve CI issue in aigoals.cpp

* Updating order.cpp

* Whoops

* Updated objecttypes.tbl to incorporate attack ship type

* Fix clang-tidy issue

* Forgot one other instance

---------

Co-authored-by: wookieejedi <[email protected]>
2025-08-07 09:56:18 -04:00
John Fernandez 94744e9d69 Merge pull request #6899 from MjnMixael/lab_detail_level
Show current detail level in the lab
2025-08-05 17:14:10 -04:00
John Fernandez 04c2913f07 Merge pull request #6903 from MjnMixael/lab_misc_fixes
Misc Lab Fixes
2025-08-05 17:13:37 -04:00
John Fernandez 75a7fc5755 Merge pull request #6901 from wookieejedi/debris-cleanup-2
Debris Cleanup
2025-08-05 17:12:26 -04:00
wookieejedi 6132f8bfc1 Another particle fix for standalones (#6905)
* Another particle fix for standalones

Fix more standalone crashes from particles by "allowing the code to create particles, and just silently quit if in standalone. I.e., accept an invalid effect handle if in standalone and that'll just quit out the source on its first frame of processing." Thanks to @Bmagnu for the fix! Putting here to allow taylor and chief to more easily test.

* add needed include

* and another fix
2025-08-05 16:25:16 -04:00
wookieejedi 32183f5e71 update comments 2025-08-04 18:43:06 -04:00
Mike Nelson 3f0c33046c unused params 2025-08-04 15:17:28 -05:00
Mike Nelson 1c482d76df default case 2025-08-04 14:55:11 -05:00
Mike Nelson e5a5c30fbf misc lab fixes 2025-08-04 14:24:03 -05:00
wookieejedi 4ddcaec027 Debris Cleanup
In current master the debris creation logic was really showing some rust, which resulted in inconsistent behavior when using `Lifetime Min` and `Lifetime Max` for debris in the `ships.tbl`. Specifically, by default debris can randomly be set as `DoNotExpire` if the check `(Random::next() < (Random::MAX_VALUE / 6))` is false. This was set *before* the code that set the lifetimes from `Min` and `Max` from the table though, which could randomly result in debris with infinite lifetimes even if a min and max were specified (and the ship was small, since large ships trigger infinite debris by default always).

Also, by default, ships with a radius larger than 50 meters always had infinite debris lifetimes because of `MIN_RADIUS_FOR_PERSISTENT_DEBRIS`. Thus, this PR also allows modders to set that value in the game settings table.

Tested and works as expected.
2025-08-04 12:05:54 -04:00
John Fernandez a68b22a9d8 Merge pull request #6900 from notimaginative/fixup_client_multi_data
cleanup and fix issues with multi client data
2025-08-04 00:49:44 -04:00
Taylor Richards d7a531d449 properly this time 2025-08-04 00:18:09 -04:00
John Fernandez 8d2895d3c2 Merge pull request #6898 from MjnMixael/fix_lab_animations
Still allow subsystem animations in the lab
2025-08-03 23:52:50 -04:00
Taylor Richards 255f3773c1 address clang-tidy issues 2025-08-03 22:57:03 -04:00
Taylor Richards 8748109eaf cleanup and fix issues with multi client data
Fix use of incorrect data macro
Fix packet size safety check
Fix sequence order issue with client data packet (Cyborg)
Cleanup code to address readability and maintence issues
2025-08-03 20:08:25 -04:00
Mike Nelson 6abfddfea5 show current detail level in the lab 2025-08-02 18:59:45 -05:00
Mike Nelson 32905ac841 still allow subsystem animations in the lab 2025-08-02 16:36:27 -05:00
Taylor Richards 671a7586e7 fix nullptr crash when debris collides with ship (#6896) 2025-08-02 10:10:31 -04:00
Taylor Richards 9718e7008c fix mipmap resizing bug (#6895)
The if statement wouldn't trigger as defined which caused the incorrect data
offsets to be used. This resulted in corrupted textures and invalid memory
reads of uncompressed textures if the hardware texture detail slider was set
to anything other than max.
2025-08-01 22:36:54 -04:00
Taylor Richards ab438116d3 remove old os_sleep() hack for Mac (#6894)
SDL 1.2 had an issue using SDL_Delay() on Mac where it caused bad stuttering
and freezes. A hack was added to avoid this, but it causes 100% CPU usage when
it should be a sleep state, so it wasn't ideal.

With SDL 2 this issue was resolved with the underlying code being unified for
all *NIX variants and that problematic Mac-specific code was removed. So now
we can remove the hack to offer a little better performance on Macs.
2025-08-01 22:35:43 -04:00
wookieejedi dce9e1c031 Allow mods to cap rotational velocity from collisions (#6890)
The `+Rotation Factor:` value in the ship collide section of the ships table has been around for many years, but raising that value beyond the default of 0.2 results in ship collisions that can result in the colliding ship spinning at ludicrous rotational velocities that last for over 10 seconds (this is especially evident with long ships with high speeds).

This PR adds a field after `+Rotation Factor:` called `+Rotation Magnitude Maximum:` which serves as a maximum for the resulting rotational velocity from a ship collision in degrees per second. By default this is -1 and thus disabled.

Tested and works as expected, and now properly allows mods to have ships spin out if they have collision while not spinning out at wild speeds that look immersion breaking.
2025-08-01 22:31:40 -04:00
wookieejedi b5f3edc019 Fix engine not updating with change-ship (#6893)
* Fix engine not updating with change-ship

Yet another caveat of the change-ship function was discovered by Darius, who found that that both the 3D assigned engine sounds and the 2D throttle/cockpit engine sounds do not properly updated when a ship type is changed via sexp/script.

This PR fixes this by properly calling the respective clearing functions and it also resets the throttle sound check timestamp to ensure the 2D sounds are indeed updated on the next frame.

Tested and works as expected. Fixes #6892.

* cleanup for safety
2025-08-01 22:31:09 -04:00
BMagnu e28b3bb592 Remove unused enum in particle code (#6891)
* Remove unused enum

* remove comment as well
2025-08-01 01:33:29 +00:00
BMagnu 0a7fac447d Add particle curve input for number of existing particles (#6889)
* Add code to process global starts for modular curves

* Add particle curve input for number of existing particles
2025-08-01 02:05:08 +02:00
Taylor Richards 4c7e4feaf3 fix double dumbfire missiles for multi clients (#6888)
Dumbfire missiles are client fired for rollback purposes. However players would see
their missiles fire twice due to the server also sending a fire packet back to the player.
So we need to prevent the player that fired the missile in question from getting that
extra packet.
2025-07-31 15:09:30 -04:00
Taylor Richards bf84dbf34f fix invalid array index crash in debug message (#6887) 2025-07-31 15:08:56 -04:00
Taylor Richards b19c3e565b fix more particle issues with standalone (#6886) 2025-07-31 15:06:11 -04:00
Mike Nelson ffcf078f03 Preload asteroids and debris when config-field sexps are used (#6874) 2025-07-30 22:45:38 +02:00
Mike Nelson e52fa1edeb No need to display debris label on objects in the Debris list (#6877)
* No need to display debris label on objects in the Debris list

* remove the unnecessary loop
2025-07-30 14:04:04 -04:00
Mike Nelson 44952eb2d6 Fix some minor hud config color errors (#6881) 2025-07-30 14:03:34 -04:00
BMagnu 95d21a4085 Minor Particle Fixes (#6884)
* Fix scripted particle velocity

* Add error if no bitmaps were defined
2025-07-30 20:53:26 +09:00
Kestrellius 8f835fe34b check for global (#6882) 2025-07-30 07:52:46 -04:00
wookieejedi c26b0caeb8 Ensure Aspect_invulnerability_fix always works (#6879)
#6248 added an extra subtraction line to calculating when a bomb can be shot down `extra_buggy_time`, but that value should only be calculated if  `Aspect_invulnerability_fix` is off. Specifically, the `else if` checks on lines 79 and 97 of `collideweaponweapon.cpp` were getting hit even when `Aspect_invulnerability_fix` was enabled, so ensure that if that flag is on that it correctly keeps the `extra_buggy_time` at 0.

Fix works as expected, happy to edit or tune however.
2025-07-30 20:17:54 +09:00
Mike Nelson 85ebd316e1 Only show one warning for skipped fonts (#6880) 2025-07-30 20:17:09 +09:00
Kestrellius abc82bd323 Handle impacts for obscure code paths (#6848)
* handle healing weapons

* incorporate shield threshold features

* fix piercing sign error

* use function
2025-07-30 20:12:35 +09:00
BMagnu f374eadb45 Fix load order guarantees (#6883) 2025-07-29 18:24:51 -04:00
Kestrellius 3e77e1370d fix copy-paste error (#6878) 2025-07-29 15:24:32 -04:00
Mike Nelson 2ce4bdd55c get font scale options for lua (#6876) 2025-07-29 07:41:45 -04:00
Mike Nelson bbb838a332 custom data for personas (#6861) 2025-07-28 19:21:14 -04:00
wookieejedi 7a655e44da Fix p-spew with homing weapons (#6875)
#6808 converted all Pspews to use the new particle effects, though that in turn lead to a fun little visual bug. Overall, particles on weapons will only be rendered if they are valid, and the is valid check included a `weapon_state == m_weaponstate` check (IE states had to match between what the weapon object held and what the particle held. When a weapon switches to homing that particle state was never updated, b/c before 6808 it never needed to be. This lead to the particle effects from pspews not rendering when a missile was in homing mode.

This PR fixes that issue by also updating the particle host status whenever the weapon state changes (uses the same logic as when the particle spawns the first time).

Tested and works as expected.
2025-07-28 22:20:40 +00:00
Goober5000 2ed608b303 add smoothing field to volumetrics dialog (#6865)
FRED support for the smoothing field added in #6854.
2025-07-26 17:46:10 +02:00
John Fernandez b49dbfeaa4 Merge pull request #6832 from MjnMixael/team_colors_followup
Get, set, browse team colors with Lua
2025-07-26 07:54:21 -04:00
Asteroth bfacc16a51 Fix for single segment trails (#6864)
* fix single segmen trails when interrupted on both ends

* cleaner case handling

* remove wip stuff
2025-07-26 12:46:10 +02:00
BMagnu f96c73faac Fix unlit shader flag (#6873) 2025-07-26 01:22:44 +00:00
Taylor Richards 5fd06b40fe fix multi crash loading campaigns (#6871)
A minor bug in #6077 resulted in a crash loading campaigns on the multi ui.
The bug itself was not fatal, however it triggered a cascade of poor error
handling which led to a crash. A lot of these issues date back to retail.
This fixes the minor bug, and hopefully adds enough error handling to
preemptively squash a repeat of the crash problem.
2025-07-25 21:15:31 -04:00
Taylor Richards 21e35d0ded fix issue with turret/flak multi packets (#6870)
Normalized vectors sent over multi are never guaranteed to still be normalized
on the other side.
2025-07-25 21:14:39 -04:00
Taylor Richards 9dc6eb7248 fix particle related standalone error (#6869)
Standalone doesn't create particles so these checks will always fail.
2025-07-25 21:14:09 -04:00
BMagnu fcb257db59 Fix calculation of apparent size (#6872) 2025-07-26 03:04:44 +02:00
BMagnu fa5ee72770 Consider current RTT state for screenToBlob (#6867) 2025-07-26 02:53:44 +02:00
BMagnu 72ed863700 Prevent lua from garbage collecting possibly-stale subframes (#6866) 2025-07-25 22:06:00 +02:00
Kestrellius d6cb9892de account for add state (#6858) 2025-07-24 19:01:05 -04:00
Mike Nelson 021eb8e49a Fix two HUD Config preset issues (#6830)
* always use exact match

* custom gauges should use the type for colors with old preset files
2025-07-24 16:51:45 -04:00
Mike Nelson 487cac6456 Lua func to check if in game options are enabled (#6862) 2025-07-24 16:51:14 -04:00
wookieejedi f76bf1b8c4 Fix 'show-ship` not working (#6860)
Small one-line fix that follows up #6849. That PR removed the `!(Viewer_mode & VM_EXTERNAL))` check within a section of `ship_render_player_ship` to optimize insignia rendering, but turns out that check is needed to ensure `show-ship` properly works. This PR restores that check to fix this bug, and ideally the thoughts about slightly optimizing insignias can be considered later.

Tested and works as expected.
2025-07-24 22:33:52 +09:00
BMagnu 755c4c4e29 Fix Volumetric Nebula Bugs and Add Smoothing (#6854)
* Fix 3d tex generation

* Fix blending and emissive

* Add Smoothing

* Add smoothing parameter

* Correctly calculate fractional smoothing
2025-07-24 22:33:37 +09:00
wookieejedi 168b3310ab Cleanup of Shield Hitpoint Threshold (#6851)
* Cleanup of Shield Hitpoint Threshold

Overall cleanup that does a few things:

1) Changes `ship_is_shield_up` from integer to bool.

2) `ship_is_shield_up` was only ever called to look at one quadrant, never for all quadrants, so took the opportunity to update the 'all-quadrant' block to account for ships with a non-standard number of shield quadrants.

3) ` MAX(2.0f, Shield_percent_skips_damage * shield_get_max_quad(ship_objp)` was used many times throughout the code (and will be used at least 2 more times with #6848), so simply consolidated all of those uses into a new function called `ship_shield_hitpoint_threshold`. Happy to edit the name however.

4) Removed un-needed redundant comments from `ship_is_shield_up` definition in `ship.h` since those comments were already present in the actual function within `shield.cpp`, and all the other functions used comments in that file instead.

Again happy to tune or edit with any other choices.

* use proper default arg style

* actually commit the updated files

* appease modern clang

* clang round 2
2025-07-24 08:39:58 -04:00
Kestrellius 365d9c57e3 gate hull and subsys impacts (#6855) 2025-07-23 08:07:42 -04:00
John Fernandez 1d5fa5eef1 Merge pull request #6852 from MjnMixael/fix_rocket_ani_assertion
Remove rocket ani assertion
2025-07-22 21:15:35 -04:00
John Fernandez 05624b5092 Merge pull request #6856 from MjnMixael/fix_get_mod_root_name
Prevent string ops on nullptr char*
2025-07-22 20:50:50 -04:00
Mike Nelson 8be619db7e prevent string ops on nullptr char* 2025-07-22 19:19:31 -05:00
BMagnu 9b82daeefc Proper Ambient Light (#6849)
* Remove ambient handling from amin / decal pass

* Add ambient light to deferred pass

* Make decals work and fix double insignia rendering

* Fix decal vertex shader

* render insignias as decals

* Cleanup of old insignia stuff

* Move insignia post-processing to model load

* perform envmap lighting in deferred shader

* Remove Envmapping from main shader

* Fix MSVC warning

* exclude def_files from clang tidy
2025-07-22 00:17:22 +02:00
Mike Nelson c53daa325c Remove rocket ani assertion 2025-07-21 15:32:25 -05:00
wookieejedi 86622e1ce0 Fix Edge-case with $Preload briefing icon models: (#6850)
Found a very interesting edge case bug with `$Preload briefing icon models:.`

Situation: Mission 1 has a briefing which has a ship class A as the class (and thus loads the model), but the ship class A is not actually in that mission so no subsystems are loaded. Then in mission 2, the ship class A is actually in the mission but FSO tries to reuse the slot that was loaded, but realizes no subsystems were loaded and makes an error message. I that is because the preload code for the briefing icons on `missionparse.cpp` line 6664 just calls `model_load` but does not specify the subsystem argument.

 I've also attached a retail mod reproducible campaign (2 missions, first is 5 seconds long). When the second mission loads the incorrect subsystem warning appears.

This PR properly loads the subsystems and fixes the bug. Tested and works as expected.
2025-07-20 15:00:47 -04:00
Kestrellius 199c7b79ef Make sure global damage doesn't spawn impact effects (#6842)
* check whether damage is global

* use static

* remove extraneous arguments
2025-07-20 07:45:27 -04:00
wookieejedi 26e9ecab0e Allow mods to set threshold at which shield stops taking damage (#6847)
* Allow mods to set threshold at which shield stops taking damage

FSO logic dictates that shields collisions and any related sound or visuals related to shields are skipped if the shields are under 10%. This is rather unintuitive especially for mods, so this PR creates a game setting value that allows mods to set this value.

Also, this PR fixes a bug where the HUD shield gauge would try and incorporate this feature but did not do it correctly. Specifically, the gauge skips shield segment rendering if the raw shield strength was 0.1 not the percent. Incorrectly using the raw value results in HUD shields being rendered (albeit faintly) even if the damage was skipping the shield and directly damaging the hull. In other words, if the shield segment was 8%, the shield segment would show faintly even the shield is skipping damage.

Tested and works as expected. Also happy to discuss or answer any questions!

* update local variable name for clarity

* rename `ship_quadrant_shield_strength`

* cleanup with updated get percent function

* more cleanup

* wording and safety tuning

* use correct default number
2025-07-19 18:24:49 -04:00
Goober5000 d746ae4f1a Merge pull request #6844 from Goober5000/modular_poofs
allow modular nebula tables to not define bitmaps
2025-07-18 00:46:33 -04:00
Goober5000 5fbce060e6 Merge pull request #6845 from Goober5000/check_for_bools
make the `check_for_*` functions return bool
2025-07-18 00:46:25 -04:00
Kestrellius cb20a05967 Fix inverted piercing impact logic (#6843)
* fix piercing sign error

* more robust solution
2025-07-17 10:59:01 -04:00
Goober5000 07db2aabf5 allow modular nebula tables to not define bitmaps
This allows the first half of a modular -neb.tbm to be skipped, allowing just poofs to be specified without the need for a preceding empty bitmap section.  It's a follow-up to #5128 which allowed just bitmaps to be specified without the need for a subsequent empty poof section.
2025-07-16 22:57:23 -04:00
Goober5000 be46706081 make the check_for_* functions return bool
Small cleanup.
2025-07-16 22:56:34 -04:00
BMagnu 83282b6427 Actually take the power of 3 (#6837) 2025-07-16 03:06:54 +02:00
Goober5000 a4eeaa706e Merge pull request #6831 from Goober5000/fix_play_sound_from_file
fix play-sound-from-file with multiple variable-controlled streams
2025-07-14 20:19:12 -04:00
wookieejedi a224736543 Slots for custom post processing effects (#6827)
* Slots for custom post processing effects

Post processing effects such as tint or dithering have their uniforms hardcoded, which means the only want to create new post-process effects for a mod is to overwrite an existing effect.

Requested for DEM2, this PR creates 4 blank slots for mods to create new post processing effects--2 floats and 2 vec3. This approach to create blank slots for modders follows the successful strategy used for Custom_Controls 1-5.

Note, this PR also adds `tint` to the default `post_processing.tbl` which had it missing (even though tint is a valid effect with a valid uniform buffer already).

Happy to discuss more or edit however.

* fix ordering
2025-07-14 13:44:18 -04:00
Taylor Richards d055d03e53 get proper number of physical cpu cores on macOS (#6835)
* get proper number of physical cpu cores on macOS

macOS has an easy way to get the physical cpu count so let's use it. Also with
Apple Silicon there are performance and efficiency cores so we need to make
sure we ignore those low-perf cores when getting the count.

* improve error handling
2025-07-14 13:41:36 -04:00
BMagnu 4c80ad7243 Fix Thread Count Detection on Windows (#6834) 2025-07-14 14:01:24 +00:00
BMagnu 53394093a4 Limit the number of threads spawned (#6833)
* Limit the number of threads spawned

* Conversion on msvc
2025-07-14 22:14:40 +09:00
Mike Nelson ef631f9fe0 nested namespaces 2025-07-13 19:44:44 -05:00
Mike Nelson d58d924f3e Get, set, browse team colors 2025-07-13 19:23:02 -05:00
Goober5000 8375b33998 Merge pull request #6810 from Goober5000/display_name_update
update handling of display names
2025-07-13 20:11:09 -04:00
Goober5000 3be99b2480 fix play-sound-from-file with multiple variable-controlled streams
Since this sexp was implemented, there has been a bug that prevented multiple variable-controlled audio streams from working.  One default and one variable-controlled stream could coexist, but any subsequent variable-controlled stream would reuse a previous stream's handle if the variable's current value was a valid handle.  This adjusts the code to properly create a new handle for every invocation of the sexp with a variable.

The following cases have been tested and now all work properly:
1. The default handle by itself
2. One variable handle by itself
3. One variable handle with one default handle
4. Multiple variable handles
5. Multiple variable handles with one default handle

Follow-up to #2115.
2025-07-13 20:02:06 -04:00
Taylor Richards 7811207ca9 fix decal shader compile error (#6829)
On macOS the new decal shaders (#6813) fail to compile with the following
message: "interpolation qualifier 'flat' must precede storage qualifiers".
2025-07-13 23:19:32 +02:00
Goober5000 dd72784f28 Merge pull request #6826 from Goober5000/sexp_help_newlines
ensure SEXP help has appropriate newlines or spaces
2025-07-12 19:40:04 -04:00
Goober5000 22feeddc6f ensure SEXP help has appropriate newlines or spaces
Some SEXP help did not have newlines separating what should be separate lines of text, or spaces separating what should be separate words.  Add newlines or spaces where appropriate.  (Regexes are A-1 SUPAR.)
2025-07-12 18:43:17 -04:00
BMagnu 6c35dac535 Allow autodetection of number of cores for threading (#6824)
* Allow autodetection of threads

* cleanup
2025-07-12 22:19:23 +02:00
BMagnu 8e64845cf1 Make sure to close the g3 frame (#6825) 2025-07-12 19:02:40 +00:00
BMagnu bdde909fa6 Threading Framework & Threaded Collisions (#6814)
* Dispatch ship weapon collisions and defer handling

* multithread ship weapon collision

* properly spin

* proper memory order

* externalize generic threadpool settings

* Wait for threads to shut down

* Add result pipelining

* cleanup

* incorrect nullopt type

* randomrange todo

* parallelize ship ship collisions

* bogus debug return

* remove warnings

* clang tidy
2025-07-12 11:41:30 +02:00
Kestrellius 6921f550c1 Enhanced functionality for conditional impacts (#6785)
* add new conditions

* don't worry about it

* implement new conditions

* bugfixing

* handle pokethrough differently

* clang for the clang god

* appeasement

* to the last i grapple with thee

* from hell's heart etc

* bugfixing

* support hits while dying

* fix shield math, cleanup

* fix memory error
2025-07-12 11:40:53 +02:00
Goober5000 06eaf94cb3 Merge pull request #6804 from Goober5000/stably_sorted_map
sort map keys before saving a container
2025-07-12 00:42:05 -04:00
Goober5000 d7fdf7b079 Merge pull request #6820 from Goober5000/fix_newlines_on_comments
fix broken newline behavior when FRED saves missions
2025-07-11 23:56:43 -04:00
Mike Nelson b9b64fe49d Show team colors with Lua rendering methods (#6802)
* get set team colors with lua's rendering methods

* fix for checks

* tweak

* add includes
2025-07-11 22:01:33 -04:00
BMagnu 218d8c09be Rely on bound vai's rather than try to retrieve them (#6822) 2025-07-11 16:13:25 -04:00
BMagnu bda54ffe83 Fix some things in the particle code coverity flaggedf (#6821) 2025-07-11 14:35:07 +00:00
Goober5000 b03301897e Merge pull request #6816 from Goober5000/model_memory_safety
memory safety for model parsing
2025-07-10 22:02:07 -04:00
Goober5000 32fcb8ccba fix broken newline behavior when FRED saves missions
Ever since retail, when encountering a comment, FRED would add an extra newline and mix `\r` with `\r\n`, often causing other bugs.  The reason for this is subtle.  C and C++ library functions will automatically convert `\n` to `\r\n` and back again when reading and writing in text mode, but FRED mission files are read in binary mode and written in text mode.  Normally the parsing code can accommodate reading raw newlines of any type, but comments are copied verbatim from what is read to what is written.  A comment ending in `\r\n` will therefore be written as `\r\r\n` which will add an extra incompatible newline.

At one point (in commit a988f5eab2) a fix was made for this, but it only applied to multiline comments, not all comments.  Therefore, this PR moves the fix to a common function and uses it for all comment styles.
2025-07-10 21:57:17 -04:00
Goober5000 86851fe0d2 make other compilers happy by only using the comma trick on debug mode 2025-07-10 15:10:19 -04:00
Goober5000 a565c8e76d memory safety for model parsing
Add assertions to the model parsing macros to guard against accessing out-of-bounds memory.  Patch the model parsing routines to avoid going out of bounds, even if the out of bounds situations are temporary and benign.
2025-07-10 15:10:19 -04:00
BMagnu 44ed242aee Make pspew warning a bit less annyoing (#6819) 2025-07-10 21:26:12 +09:00
Goober5000 145a34eeac Merge pull request #6806 from Goober5000/script_eval_multiline
make script-eval-num and script-eval-bool multiline
2025-07-09 20:38:18 -04:00
Goober5000 1e27a6bed3 Merge pull request #6817 from Goober5000/fix_qtfred_crash
fix qtfred memory corruption
2025-07-09 20:37:20 -04:00
Goober5000 84905f0789 Merge pull request #6818 from Goober5000/qtfred_cleanup
qtfred cleanup
2025-07-09 20:36:48 -04:00
BMagnu a52f31f748 Instanced Decals (#6813)
* prepare instancing

* Render single decal with instance-like buffer

* encode per-instance data in the instance matrix

* Actually render decals instanced

* Update todo commit

* actually add stubs

* Gate decal preparation behind GPU capability flag

* Fix MSVC warnigns

* remove explicit layout

* Fix MSVC warnigns 2

* Clang Tidy
2025-07-10 00:51:04 +02:00
Kestrellius 62c8260d26 add flag (#6815) 2025-07-09 12:08:53 -04:00
BMagnu 82dfdbc962 Cleanup of Level 0 Particles (#6808)
* Make scripting use the full-blown particle system

* Add reverse option

* Add animation reversal as an option to modern particles

* Start implementation of beam particles

* Add multi-emission particle source API for legacy particles

* Attach beam muzzle particles to the correct host

* Reorder code in order to facilitate apparent visual size as modular curve input for particles

* fix lua api

* Typo

* Fix scripting and beam muzzle particles

* Remove muzzleflash code, prepare to replace with particles

* Begin mflash effect conversion

* Pass final parameters to mflashes and fix curve

* Remove deprecated particle function

* Add debris flame particles

* remove weapon_explosion table

* Convert shield explosion particles

* Fix modular curve full inputs and convert particle curve inputs accordingly

* Allow particle volume offset and rotation with curve-based rot around fvec

* Add Ring and Point Volumes

* Convert PSPEW to new system

* Make particle curve timing aware of interp

* Update todos

* get rid of performance waste

* Add last parameter for converted pspews

* Fix pspew bugs

* Fix direction for default pspew

* Fix order for voluem rotation

* particle vel interp

* particle vel interp 2

* Make beam muzzle stay parented to the firing ship, as it's likely the more commonly intended variamnt

* fix scripting particle lifetime

* Allow beam muzzle particle override

* Skip invalid pspews

* remove pspew dcf code

* Fix sparkler stretch

* Correct sparkler bia

* General sparkler weirdness

* Tune distribution of sparklers

* Convert Asteroid particles

* Fix conversion warning

* Fix conversion warning again

* Fix conversion warning again again

* Really clang?
2025-07-09 16:47:14 +09:00
Goober5000 220ff6752b Merge pull request #6805 from Goober5000/warn_about_bitmap_fonts
change this WarningEx to a Warning
2025-07-09 02:40:11 -04:00
Goober5000 bd767ece09 qtfred cleanup
Various cleanup associated with the `toStdString()` function in qtFRED.  Now that `lcase_equal` exists, it is no longer necessary to compare the C strings.  Additionally, QString has its own `isEmpty()` function.
2025-07-08 23:06:20 -04:00
Goober5000 f60fe6dfc2 Merge pull request #6807 from Goober5000/subsystem_enhancements
subsystem scripting enhancements
2025-07-08 23:01:30 -04:00
Goober5000 342958b99f fix qtfred memory corruption
Fix a qtFRED memory access bug when checking for autosaved files.  Passing a temporary variable to a function as a reference, then returning it, led to a dangling reference access.  Rewrite the `maybeUseAutosave` function to avoid this.
2025-07-08 22:31:56 -04:00
BMagnu 497b834766 Speed up random range creation (#6812) 2025-07-08 14:26:43 +02:00
Kestrellius 8989b6e925 make change (#6809) 2025-07-08 11:05:26 +02:00
BMagnu 650b8d4f07 Disable uneccesary raycast (#6811) 2025-07-08 11:05:11 +02:00
Goober5000 672aa7f81f apply display name updates to jump nodes too 2025-07-07 03:25:35 -04:00
Goober5000 d99c4ca172 add display name text field to ship editor 2025-07-07 03:25:35 -04:00
Goober5000 fe1540c8a4 add 'Always save display names' option 2025-07-07 00:17:24 -04:00
Goober5000 df1126923d alphabetize accelerator keys, sort editor menu, sort ship dialog 2025-07-07 00:17:24 -04:00
Goober5000 8dc03b0fff additional submodel scripting enhancements
4. `__eq` functions for models and submodels
5. Store model and submodel IDs, not pointers
2025-07-05 16:05:19 -04:00
Goober5000 1b75fea06c subsystem scripting enhancements
Add several API features to improve scripting support for subsystems.

1. A `CanonicalName` virtvar to provide a clear way to get subsystem names for indexing and referencing
2. `Submodel` and `SubmodelInstance` virtvars to get the submodel information associated with the subsystem, if it exists
3. A `getSubsystemList()` function to provide a subsystem iterator
2025-07-05 16:05:19 -04:00
Goober5000 87021dd1d2 make script-eval-num and script-eval-bool multiline
These sexp operators can be enhanced to allow multiline scripts while retaining backwards compatibility, so do so.
2025-07-02 01:39:51 -04:00
Goober5000 3a380a05c9 change this WarningEx to a Warning
I just finished helping a modder debug his mod, and it would have saved him a lot of time and trouble if this WarningEx had been a Warning.
2025-07-01 23:20:18 -04:00
Goober5000 2e7f37ef7a sort map keys before saving a container
Applies a stable sort to the keys of a map container before saving the container to the mission file.  The underlying container is not modified.  Implements #6803.

(Lists are not sorted because the mission designer might have a particular order in mind for them.)
2025-07-01 21:57:04 -04:00
wookieejedi dbe0af1a24 Allow Custom Gauges to fully use parent type colors (#6799)
* Allow Custom Gauges to fully use parent type colors

#6670 allowed for custom coloring of HUD gauges, so this PR follows that up with fixing a few edge cases where mods with custom gauges that are linked to parent gauge types accurately reflect the color of their parent gauge type in both the new saved HUD file and the in-mission HUD.

This ability to link color to parent gauges for custom gauges the player cannot edit is what the behavior has always been, so this PR does not change any expected behavior it simply ensures that the new HUD updates and various combinations of table entries for `Config` are all robustly accounted for. For example, in FotG we have multiple non-configurable custom gauges all tied to the center reticle, so if the player changes the color of the center reticle all the custom gauges set to that type also automatically change color.

Tested and works as expected.

* appease clang

* one more clang

* clang needs to calm down

* instruct clang to be calm

* fix comment typo
2025-06-30 12:53:13 -04:00
wookieejedi f95ae0b1bd Prevent Potential Memory Problems with squadmsg hook function (#6801)
* Prevent Potential Memory Problems with squadmsg hook function

Within `hud_squadmsg_run_order_issued_hook` it is possible for the calling code to pass `nullptr` as the target parameter, which would cause memory problems with the several instances of `Objects[target->objnum]`. This PR mitigates that potential while testing of  `hud_squadmsg_run_order_issued_hook` still works as expected.

* add more safety
2025-06-30 12:14:28 -04:00
wookieejedi 94dac6b3d2 Prevent Bad Preset Names by Tuning Allowed Characters (#6800)
On current master players have the ability to name their control preset `...` which creates issues with Windows. This 1-line PR allows that the only non-alphanumeric characters allowed in the control preset name are `-` or `_`.

Tested and works as expected.
2025-06-30 08:22:27 -04:00
Goober5000 11a33fe42e Merge pull request #6347 from Goober5000/electrical_arc_enhancements
Enhancements to the FSO electrical arc system
2025-06-29 23:38:24 -04:00
Goober5000 821e48d188 Merge pull request #6798 from Goober5000/message_memory_fix
fix builtin_message memory usage
2025-06-29 22:49:56 -04:00
Goober5000 c68f8d71e4 Merge pull request #6796 from Goober5000/comm_menu_update
a few refinements to the new comm node selection feature
2025-06-29 17:11:18 -04:00
Goober5000 5380d88680 small optimization to get_builtin_message_sender() 2025-06-29 16:32:03 -04:00
Goober5000 3605f66e6d fix builtin_message memory usage
Since `builtin_message` might store strdup'd names, we might need to free them when the struct is destroyed.  Add a destructor for this purpose, and add corresponding constructors per the Rule of 5.

(Use `vm_strdup` rather than `strdup` for enhanced memory tracking.)
2025-06-29 16:32:03 -04:00
Goober5000 329974b4bf a few refinements to the new comm node selection feature
1. Don't display the selection arrows until the selection is actually moved, to avoid interfering with the standard selection feature
2. Use static variables and the new `TIMESTAMP` data type
3. Minor cleanup

Follow-up to #6736.
2025-06-29 00:05:48 -04:00
BMagnu 06227bbc80 fix cmake for debugging (#6795) 2025-06-27 12:24:35 +00:00
BMagnu a918ef0b6f Allow slot 0 hud RTT (#6794) 2025-06-27 14:07:31 +02:00
wookieejedi 9e731279eb AI Cleanup and Flag Fixes (#6788)
* AI Cleanup and Flag Fixes

This PR does 3 things.

1) Cleanup `ship_subsystem_in_sight` so it properly returns a bool, which is the return type of the function. Also update `ai_big_maybe_follow_subsys_path` to be a bool instead of int.

2) Adds `+subsystem attack pathing extra distance:` for use within `"$improved subsystem attack pathing:"`. FotG found, especially with the new strafing values we use, could really use the ability to set at what distance `ai_new_maybe_reposition_attack_subsys()` determines if it wants to pick a new point or not. By default the value was 2000.

3) In FotG we started using the `standard_strafe` mode more now thanks to #6511, but we have now discovered some inherit issues with it, namely that it will prematurely trigger `ai_big_strafe_maybe_retreat` because the distance it uses for determining if a collision is going to occur is `dist_normal_to_target`, which commonly resulted in the AI flying to a point to start a strafe, then thinking it would collide with the target and then retreating from strafe mode, only to realize it was now far enough to re-engage strafe mode, which commonly resulted in AI just flying around not accomplishing much. This PR adds a flag which instead uses the `distance_to_target` and a check to see if the ship is even facing the target.

Overall all are tested and work as expected. Happy to change or tune however requested, especially regarding point 3.

* clang cleanup

* cleanup copy paste name

* remove un-needed exposed float
2025-06-26 08:05:31 -04:00
wookieejedi 742dd074ee Allow Display Name to be used in Kills Stats by Ship Class (#6791)
* Allow Display Name to be used in Kills Stats by Ship Class

Currently, the display that is shown for kills by ship type does not use display name of the ship class, which makes the ship classes look different than the other areas where they are displayed throughout the game. This PR fixes that by using display name when rendering the kill stats. Note, this PR does not edit the pilot file structure at all, it simply just tunes how the names are displayed/rendered.

Importantly though, display names do not have to be unique, while the ship class names do. Thus, this PR also consolidates the number of kills of ship classes with the same display name (For example, FotG uses classes like `X-wing#Red` and `X-wing#Blue`, but both have the same display name of `X-wing`, so this consolidation means FSO will simple display `X-wing` with the sum. Overall, mods not using display name will be unaffected, while mods that do use display name will now have it properly work in the barracks and debrief rendering.

Tested and works as expected.

* remove flag for now

* move assert

* use handy get_display_name
2025-06-26 08:01:15 -04:00
wookieejedi f1a25ab47d Per Request add flag for $Disabled or disrupted engines are silent: (#6792)
* Per Request add flag for `$Disabled engines are silent:`

Follow-up to #6781 as there are modders who use engine sound as ambient sound. Also cleans up the sound engine disabled logic.

* cleanup name

* update flag name
2025-06-25 07:50:46 -04:00
wookieejedi d3db542640 Expand collision avoidance to strafing (#6789)
Currently, the `$better combat collision avoidance for fightercraft:` flag only checks for possible big ship collisions when in normal chase mode (`AIM_CHASE`), but strafing a big ship is a separate AI mode (`AIM_STRAFE`). Per discussion with Asteroth, strafe attacking should also check for better collision avoidance, as the modder likely would expect that to fall under this AI flag. Also took the chance to make the collision avoidance check into a function to improve maintenance and usability.
2025-06-24 23:01:47 -04:00
Goober5000 74beab5177 Merge pull request #6790 from Goober5000/order_fixes
clean up Target virtvar in order.cpp
2025-06-24 21:56:06 -04:00
Goober5000 2a62f04d72 clean up Target virtvar in order.cpp
Several refinements:
1. Make the case labels consistent between the setting and getting of the virtvar, for ease of comparison and troubleshooting.
2. Consolidate CHASE_WING and GUARD_WING for setting, like they already are for getting.
3. Consolidate STAY_STILL for getting, using one simple trick.
4. Remove the KEEP_SAFE_DISTANCE goal, since it has no target. Fixes #6761
5. Fix CHASE_SHIP_CLASS which put the getting case in the setting section, and consequently did not handle setting.
2025-06-24 02:14:20 -04:00
wookieejedi acf367e66a Edge case fix for Always_show_directive_value_count (#6787) 2025-06-23 20:26:22 -04:00
Goober5000 c5d5a93766 address feedback 2025-06-22 01:37:54 -04:00
Goober5000 f0ee1e2f7c electrical arc upgrade, stage 4: add a new ModifyElectricArcPoints function with a handy valueToVec3d utility function 2025-06-22 00:46:14 -04:00
Goober5000 47025a79d7 electrical arc upgrade, stage 3: add support for customizing segment depth and using persistent arc points 2025-06-22 00:46:14 -04:00
Goober5000 305af2a1ce electrical arc upgrade, stage 2: make arrays dynamic 2025-06-22 00:46:14 -04:00
Goober5000 5732183471 rename debris spark timestamps, per Asteroth 2025-06-22 00:46:14 -04:00
Goober5000 611de4d060 fix scope of ship arc sound generation
The code for generating spark sounds was incorrectly copied from debris to ships, dating all the way back to retail.  The sound should be played once per generation event, not once for every electrical arc active on the ship.  This fixes a bug that caused overlapping electrical arc sounds.
2025-06-22 00:46:14 -04:00
Goober5000 8270a38505 electrical arc upgrade, stage 1: refactor data structures 2025-06-22 00:46:14 -04:00
Goober5000 73e82d9440 Merge pull request #6786 from Goober5000/schroedinger_goals
schroedinger AI goals
2025-06-22 00:43:33 -04:00
Mike Nelson 6a1ae81bcb Add method to get a font's font family (#6782)
* Add method to get a font's font family

* nullptr

* add more safety checks

* pass font data instead of font data members

* return an empty string instead of unknown
2025-06-20 14:27:34 -04:00
Goober5000 035334878b schroedinger AI goals
The new enum class collection of AI goal modes did not handle the superposition of states that could occur when multiple ships were selected in FRED.  Add a special FRED-only state for this purpose.
2025-06-20 03:40:30 -04:00
wookieejedi 54c00ce205 Two Sound Updates (#6781)
* Two Sound Updates

On current master, destroyed engines still make noise and the `Engine Minimum Volume` is not used by big ships. This PR makes two new game settings flags which allow mods to mitigate those issues (`$Unify minimum engine sound:` and `$Disabled engines are silent:`).

Tested and works as expected.

I also added both of these to the 25.0 default, but happy to alter however.

* disabled eng snd does not need flag fix

* ensure retail large ships still sound loud
2025-06-19 22:38:12 -04:00
John Fernandez a9664ff22f Merge pull request #6752 from Goober5000/stay_near_target_fix
fix STAY_NEAR_TARGET_ITEM
2025-06-19 19:44:55 -04:00
Mike Nelson b5f4942cd3 fix hud config sliders (#6784) 2025-06-19 16:41:45 +00:00
wookieejedi 9141f1e9ed Allow Mouse Wheel to work with trigger type controls (#6779) 2025-06-18 17:35:28 -04:00
wookieejedi 26477276da Allow modders to tune Comms Styling (#6776)
This PR adds two new localization strings, which allow modders to change how the numbers in the comms menu are displayed, and it incorporates styling for the comms selection controls from #6736, too.

Tested and works as expected.
2025-06-17 22:58:45 -04:00
Asteroth 821d16192f guard against no model viewer object (#6780) 2025-06-16 07:57:49 -04:00
Mateusz Uberna 5864ec4e7e Comms menu controls (#6736)
* Added new keybinds, keycontrol handlers,  hudsquadmsg methods for alternate communication menu control scheme

* .gitignore update. CMake in VStudio places builds in [Oo]ut/ folder by default

* Added support for menu pages

* Small formatting cleanup

* code cleanup

* removed unecessary variable

* Updates after code review

* Updated comments

* XSTR_SIZE correction
2025-06-14 13:27:01 +00:00
wookieejedi c31c258679 Allow +Debris Density: (#6775)
* Allow `+Debris Density:`

Looks like when `+Debris Density` got added it never accounted for a colon like the other fields

* comment
2025-06-13 22:14:48 +00:00
wookieejedi c307ef7600 Allow ship debris pieces hitpoints depend on radius (#6771)
* Allow ship debris pieces hitpoints depend on radius

Currently, ship debris pieces have their hitpoints set via a Max and Min range. This constricts modders by needing all debris pieces to have somewhat the same size, otherwise the uniform random hitpoints assignment will could make small pieces much stronger than big pieces.

This PR adds a new field `+Hitpoints Radius Multiplier:` which is just a value that multiplies the debris radius, this value, and the debris hitpoints to calculate a final hitpoints value for the spawned debris piece.

This multiplier thus allows modders to have debris pieces that are very large have much strong hitpoints then debris pieces that are very small.

* incorporate feedback fix

* match other logical checks
2025-06-13 12:43:08 -04:00
Kestrellius 2701544822 Improve 'laser anim state add' curve output (#6773) 2025-06-12 21:33:29 -04:00
Mike Nelson 4ad015881d Lab dock tester (#6715)
* Add docking and undocking test to the Lab

* address feedback
2025-06-12 14:46:20 -04:00
Mike Nelson 43b36fad20 fix fred view not rendering (#6772) 2025-06-12 14:32:26 -04:00
John Fernandez 3dd2c38cef Merge pull request #6764 from scp-fs2open/dependabot/pip/ci/post/requests-2.32.4
Bump requests from 2.32.0 to 2.32.4 in /ci/post
2025-06-11 21:42:50 -04:00
BMagnu cfd3b716ba Fix FastDbg builds (#6770)
* Actually pr-test fastdbg

* Set embedfile msvc runtime

* Restructure toolchain files so that embedfile is also built accordingly

* Conditional AFXDLL

* Set iterator level globally
2025-06-11 22:21:51 +09:00
BMagnu d76f38320a Properly adapt the other workflow runs as well (#6769) 2025-06-11 06:41:46 -04:00
BMagnuandTyler Blount da58556787 Library updates, Win2022 CI, CMake 4 Compat (#6766)
* Update nightly build to use windows 2022

* Update build-release.yaml

* Update build-test.yaml

* Update test-pull_request.yaml

* Upgrade to CMake 3.21+

temp commit

* remove cotire due to incompatibilities

* Update dependencies to compile on CMake 4 with no warnings

* Update ANTLR4

* Update GTest

* Fix Windows CI

* Fix case sensitive ANTLR include

* Fix naming issues and disable shared ANTLR+

* Include Mess for MSVC

* fix new windows signed/unsigned compare warning

* FRED2 linking settings

* Don't clang tidy gtest

---------

Co-authored-by: Tyler Blount <[email protected]>
2025-06-11 02:00:23 +02:00
Mike Nelson 78d6a4b881 Ensure lua context is used for image drawing and string sizes (#6765) 2025-06-10 19:04:06 -04:00
BMagnu 1180f8762a Fix them oopsies (#6768) 2025-06-11 00:23:33 +02:00
BMagnu f342925763 Always enable window_res (#6767) 2025-06-11 00:23:25 +02:00
wookieejedi 2ec1553898 Fix few small bugs (#6755)
* Fix two small visual-based bugs

PR fixes/cleans up two small visual bugs/clean-up aspects.

1) If a mod uses the autoaim frame feature of the center reticle (IE the center reticle switches to a second bitmap if a target is within the autoaim cone), then there is currently an edge case bug where if the player's target dies while in the autoaim cone, the value tracking whether or not to use the autoaim frame is never reset to false. This results in the autoaim frame remaining even when the target is dead and long destroyed. This PR properly resets the autoaim bool to false if the target is not valid, and overall works now as expected.

2) #6555 moved warp options to tables instead of via in-game options, but there was a line of code that still referenced `WarpFlash`, where that option was forced off in standalones. This PR properly removes that line and instead puts the standalone check as part of the input warp queue function.

* also cleanup techroom subsystem loading
2025-06-10 14:45:50 -04:00
dependabot[bot] bfb8c70831 Bump requests from 2.32.0 to 2.32.4 in /ci/post
Bumps [requests](https://github.com/psf/requests) from 2.32.0 to 2.32.4.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.0...v2.32.4)

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

Signed-off-by: dependabot[bot] <[email protected]>
2025-06-10 08:18:07 +00:00
BMagnu e8bb54c36d Add "Move End Position by target velocity" (#6760)
* add "Move End Position by target velocity"

* Enable the functionality for slashbeams as well
2025-06-10 03:29:36 +02:00
Mike Nelson 6115a34d13 fix hudmessage rand (#6758) 2025-06-09 17:06:33 -04:00
Mike Nelson cc7f067e0f Use RAII guard to ensure lua context is always closed (#6757) 2025-06-09 17:06:18 -04:00
Goober5000 939d6a50c2 additional fix for STAY_NEAR_TARGET_ITEM
When issuing the stay-near-target order via hud squad message, default to a distance of 300, the same as the sexp.
2025-06-07 00:09:36 -04:00
Goober5000 3783cb0d1d fix STAY_NEAR_TARGET_ITEM
1. Allow "Stay near me" and "Stay near my target" to work even if the teams do not match
2. Allow "Stay near my target" to be issued regardless of team
3. Avoid divide-by-zero errors when running `ai_stay_near()`
2025-06-07 00:06:19 -04:00
JohnAFernandez 23254c993e Allow the loadout dialog to open varaible dialog 2024-05-22 01:09:14 -04:00
JohnAFernandez 29fa8c4f9e Final Fixes
Implements container contents reading, as well
2024-05-18 23:26:21 -04:00
JohnAFernandez ab7aa63569 Whoops 2024-05-18 16:31:09 -04:00
JohnAFernandez 77062e2571 Make sure check eternal boxes are properly enabled 2024-05-18 16:17:32 -04:00
JohnAFernandez 7229fdc423 Fix variable read and write 2024-05-18 15:58:19 -04:00
John Fernandez d8c7a63af7 This should actually fix it. 2024-05-17 18:04:08 -04:00
JohnAFernandez 2cf24cb27b Trying to fix this mystifying error 2024-05-17 17:58:48 -04:00
JohnAFernandez 768f88b135 Linter Fixes 2024-05-17 17:44:38 -04:00
John Fernandez d1829a9f10 Fix Eternal flags being set with no persistence 2024-05-17 17:29:40 -04:00
John Fernandez 860e28fbdb Finish applying data from model to mission 2024-05-17 15:33:11 -04:00
John Fernandez 07a389a5e8 Typo 2024-05-16 15:45:53 -04:00
John Fernandez 87367d6b84 Make sure Keys and Items are proper length 2024-05-16 15:40:56 -04:00
John Fernandez ad7e713692 Override the virtual reject function 2024-05-16 14:40:38 -04:00
John Fernandez e5e2729157 Add non-numeric keys to warning messages 2024-05-16 11:38:01 -04:00
John Fernandez 039866d526 Linter Fixes 2024-05-16 11:24:03 -04:00
John Fernandez d432f9b1e9 Make to loop over the right constraint 2024-05-16 11:21:14 -04:00
John Fernandez c51bc08d1d linter fix 2024-05-16 11:11:07 -04:00
JohnAFernandez 3be4d0b15a Fixes based on testing and linters 2024-05-16 00:47:46 -04:00
John Fernandez 7da79c174b Add sorting to other function calls 2024-05-15 13:01:13 -04:00
John Fernandez 62a43512fd Finish safe to alter variable functions
And sorting function.
2024-05-15 12:31:13 -04:00
John Fernandez 492683db2a Add A check to safeToAlter functions
And Add more notes to the notes column in the UI
2024-05-14 13:18:11 -04:00
John Fernandez c53e6b1487 Finish Safe to Alter Options in Model Update 2024-05-14 00:14:48 -04:00
John Fernandez e7f6fa3c91 Upgrade Container Copying 2024-05-13 22:31:30 -04:00
John Fernandez 455d2c9681 Add to Apply()
And start safeguarding changes that would not be safe to make without cleaning references.
2024-05-13 20:57:39 -04:00
John Fernandez b053131e71 Connect the x with prereject instead of reject.
Because the x is not connected to the cancel button, it has to be connected to preReject first.
2024-05-13 10:18:29 -04:00
John Fernandez e502cdfe76 Finish the validation function 2024-05-13 09:48:40 -04:00
JohnAFernandez e855fc81e9 Check for conversion availability with same algo 2024-05-12 01:50:45 -04:00
JohnAFernandez df77615473 Various Fixes Based on Testing 2024-05-12 01:42:36 -04:00
John Fernandez 20f992fe54 Fix Not being able to add map items by value 2024-05-11 00:43:36 -04:00
JohnAFernandez d557a6eff4 More Bug fixes 2024-05-11 00:19:18 -04:00
JohnAFernandez e66803af09 Further Bug fixes based on testing 2024-05-10 23:17:54 -04:00
JohnAFernandez cd3464d331 Make sure string items can be deleted 2024-05-10 22:52:56 -04:00
JohnAFernandez bc5f986d76 Bug fixes from testing 2024-05-10 22:18:54 -04:00
JohnAFernandez 621030a720 Fix various issues 2024-05-05 13:30:24 -04:00
JohnAFernandez 9b29e754ce Fix overflow return values
Previously on windows it was returning 0 because std::stol has a poor windows implementation.
2024-05-05 11:13:03 -04:00
JohnAFernandez e4886db4d2 Fix shift up and down buttons 2024-05-05 00:58:24 -04:00
JohnAFernandez b9b36d2a76 Fix Shift up and down buttons 2024-05-05 00:19:01 -04:00
JohnAFernandez 87fd99f9f6 Finish implementation of type formatting option 2024-05-05 00:18:47 -04:00
JohnAFernandez 03083d1a91 Implement a few missing container item features 2024-05-04 23:25:09 -04:00
JohnAFernandez 3120d6dda1 Fix the Addition of variables by typing
And start adding container Items by typing.
2024-05-04 16:57:30 -04:00
JohnAFernandez 8dde6d47cb More spacing adjustments and text changes 2024-05-04 01:19:42 -04:00
JohnAFernandez 98e78ec086 Fix Containers Not Restoring Deleted Items
And Change Deletion Text for size savings
2024-05-04 01:03:34 -04:00
JohnAFernandez e1b9832faf Fixes Based on Testing 2024-05-04 00:51:39 -04:00
JohnAFernandez c9e11d4657 Fixes Based On Testing 2024-05-04 00:35:46 -04:00
JohnAFernandez d7c4c544e6 Bug fixes according to testing 2024-05-03 23:54:07 -04:00
JohnAFernandez 50762246cb Fixes based on linter
And size adjustments
2024-05-03 23:22:27 -04:00
John Fernandez d04ce617fe Add swap keys and values button 2024-05-01 00:53:45 -04:00
John Fernandez 8340427388 Make sure shift buttons apply the model 2024-04-30 22:15:31 -04:00
John Fernandez eda645dcfc Make sure delete button changes text
The button should now say restore when the item has already been deleted.  Also, this expands how well we warn about deletions and will dynamically decide if we need to warn about deletion.
2024-04-30 22:03:50 -04:00
John Fernandez 72b13631c8 Implement sift up and down buttons 2024-04-30 20:56:04 -04:00
John Fernandez 824b5bbb37 Write addNewVariable overload for typed items 2024-04-30 18:59:24 -04:00
John Fernandez 2a8ec9cc45 Write setContainerKeyType 2024-04-30 18:51:29 -04:00
John Fernandez 237bd53b6d Rename this variable for clarity
And add a comment
2024-04-30 17:47:55 -04:00
JohnAFernandez ba263d6889 Trying to fixup The map/list switch function 2024-04-30 00:50:55 -04:00
JohnAFernandez d333694a09 Adding Button for Key/Value Swap
And Add more width for table views
2024-04-30 00:50:26 -04:00
John Fernandez cf356ed3df Finish writing change from list to map 2024-04-29 17:26:57 -04:00
John Fernandez 4677fb8801 Start Applying List values to Keys, etc. 2024-04-29 14:02:13 -04:00
JohnAFernandez 5ee211203a Fix Containers not deleting 2024-04-29 01:06:04 -04:00
JohnAFernandez 898f8a9963 Increase size of dialog to store info better
And ... more Fixes Based On Testing
2024-04-29 00:54:02 -04:00
JohnAFernandez c5b033235a More Fixes Based On Testing 2024-04-29 00:03:02 -04:00
JohnAFernandez f1dd86477a More Fixes Based on testing 2024-04-28 23:30:23 -04:00
JohnAFernandez a8cc7d2896 Appease the linter 2024-04-28 14:44:38 -04:00
JohnAFernandez e902f6d075 More change based on testing 2024-04-28 13:38:40 -04:00
JohnAFernandez 0c2d1a7a10 Save Changes 2024-04-28 01:52:30 -04:00
JohnAFernandez c984faeda1 Add Accept and Reject Signal Reactions 2024-04-28 01:37:29 -04:00
JohnAFernandez dac5fbeb82 Fix more Container issues based on testing 2024-04-26 20:11:40 -04:00
JohnAFernandez 94dbd95530 Fix nullptr on copy 2024-04-26 19:10:24 -04:00
JohnAFernandez 6e6b4098f3 Fix add container and add item rows
They were not appearing.
2024-04-26 19:03:22 -04:00
JohnAFernandez fc11ddcd45 Fixes Based On Linter and Testing 2024-04-26 18:15:59 -04:00
John Fernandez ec4e50481b Hopefully fix Variable Persistence Not saving 2024-04-26 17:21:18 -04:00
John Fernandez d143add82d Fixes to container options updates
And other fixes and adjustments
2024-04-26 17:17:10 -04:00
John Fernandez ed3fcc8aca Switch Key Options and Data Options
The data type options are going to be used much more regularly and always enabled, and so should be higher on the layout.
2024-04-26 17:16:34 -04:00
John Fernandez c24457ff6d Add Clamping Function
And more cleanup for trimIntegerString
2024-04-26 16:41:13 -04:00
John Fernandez 4bb8456f69 Further Fix and Rename function for clarity
It turns out that an empty string is not the only possible desired value if the filtered string is "-".  "-0" should return "0", but would return "" before this.
2024-04-26 16:06:58 -04:00
John Fernandez 5503aec7b4 Fix filtering out zero by accident 2024-04-26 15:57:33 -04:00
JohnAFernandez 4324b41109 More Fixes and Tweaks based on testing 2024-04-26 01:02:22 -04:00
JohnAFernandez 6134a42ef0 Ton of fixes from testing 2024-04-26 00:01:27 -04:00
John Fernandez 2a4894d696 start implementing unified values in containers
And write map key changing function.

Revert "start implementing unified values in containers"

This reverts commit 25eeae42413373bc7f12831285f8f639c8a93904.
2024-04-26 00:01:27 -04:00
John Fernandez 55380f7e8f Start on SetContainerListOrMap 2024-04-23 13:02:48 -04:00
John Fernandez 3a5a2ab43c Linter errors from recent changes 2024-04-23 12:01:17 -04:00
John Fernandez 4f0f57e711 Clean up the model in a few places 2024-04-22 16:46:04 -04:00
John Fernandez 8801672301 Fix more linter errors 2024-04-22 13:03:22 -04:00
John Fernandez a18ac34c03 Fix Linter errors 2024-04-22 12:43:26 -04:00
John Fernandez dd44d7d13a Fixing more issues 2024-04-22 01:40:46 -04:00
John Fernandez 84cad8c188 More Fixes and polish 2024-04-22 01:09:44 -04:00
John Fernandez 9dfc6d7c68 Fixes for UI bugs 2024-04-22 01:02:17 -04:00
JohnAFernandez 3c7639a9e3 And one more apparently 2024-04-22 00:33:16 -04:00
JohnAFernandez 7f1e72ecca Some final ui adjustments 2024-04-22 00:32:51 -04:00
JohnAFernandez d147330739 Fix null item crash 2024-04-21 23:38:14 -04:00
JohnAFernandez beaadbbc16 Merge branch 'qtFRED-variables-editor' of https://github.com/JohnAFernandez/fs2open into qtFRED-variables-editor 2024-04-21 23:03:34 -04:00
John Fernandez f38dbae310 Save changes to transfer over 2024-04-21 22:50:51 -04:00
John Fernandez 7f0666cc33 More Fixes, but Requires Linter For more progress 2024-04-21 16:46:57 -04:00
John Fernandez 91723be9c5 And stub another function for now
There are at least two stubbed functions left on the model
2024-04-21 16:05:12 -04:00
John Fernandez 4e669f9ce4 don't forget to change name. 2024-04-21 16:02:00 -04:00
John Fernandez 209e905a05 Add copy Container Function 2024-04-21 16:00:43 -04:00
John Fernandez a82db02b41 Fix missing function 2024-04-21 15:56:47 -04:00
John Fernandez 22663cb297 Finish Dialog Code
Need to make more changes to the model
2024-04-21 15:54:11 -04:00
John Fernandez 3698cbe09f Hopefully Final Linter mistakes 2024-04-21 15:00:44 -04:00
John Fernandez 29754cafc2 More Linter Fixes 2024-04-21 14:39:45 -04:00
John Fernandez 474527ef83 More Linter Fixes 2024-04-21 14:15:35 -04:00
John Fernandez ce32ef3022 Linter Fixes 2024-04-21 13:41:23 -04:00
John Fernandez 792cf0a0ed More fixes 2024-04-21 01:11:57 -04:00
John Fernandez 314ba92782 Fix Compiler Errors 2024-04-21 00:41:38 -04:00
John Fernandez 6264656097 As far as I can tell populating items is done 2024-04-21 00:37:46 -04:00
John Fernandez 6bd3b43cd8 Don't forget extra line left by accident 2024-04-21 00:17:01 -04:00
John Fernandez 3bec6d8cfc No const here 2024-04-21 00:14:19 -04:00
John Fernandez 72271be1f9 Getting closer to completion of basics 2024-04-20 23:50:54 -04:00
John Fernandez bb838ea4dd Place getCurrentContainerRow in more places 2024-04-20 23:38:32 -04:00
John Fernandez ad74c21b82 Edit onVariablesTableUpdated
And Apply GetCurrentContainerRow
2024-04-20 23:31:17 -04:00
John Fernandez e7b0f61870 Start applying currentVariableRow 2024-04-20 19:04:14 -04:00
John Fernandez 7c37628e1a Add get item rows to reduce duplicate code 2024-04-20 18:52:36 -04:00
John Fernandez 2a337133cb Fixes and add a couple more UI functions 2024-04-20 18:36:30 -04:00
John Fernandez a06cb0c688 More progress on updateContainerDataOptions 2024-04-18 18:03:37 -04:00
John Fernandez e4254636dd Finish addMapItem and upgrade getcontainernames
This has an upgrade for displaying List and Map info in a configurable manner.
2024-04-18 16:52:45 -04:00
John Fernandez f61649e911 Add type text to container Table 2024-04-18 15:14:25 -04:00
John Fernandez 83fde5acfc Actually, upgrade the lambda
This handles the leading negative no matter where it is.
2024-04-18 14:45:20 -04:00
John Fernandez 7d7b6eeeb8 Prevent crash from empty source string 2024-04-18 14:25:18 -04:00
John Fernandez 94057c5ef2 Clean up container data updates
And move the trimString method.
2024-04-18 14:24:01 -04:00
John Fernandez 6fe5ab7dc7 Fix No Persistence not updating
And other fixes
2024-04-17 16:29:11 -04:00
JohnAFernandez a57949c747 getting closer to correct width 2024-04-17 00:27:22 -04:00
JohnAFernandez 24310690f6 more progress 2024-04-17 00:26:42 -04:00
JohnAFernandez bd75eff4e4 Make sure that tables have contents
And fixes and progress
2024-04-17 00:05:36 -04:00
JohnAFernandez 364e39cedf more fixes 2024-04-16 23:24:26 -04:00
JohnAFernandez e1662d8501 Fix many typos from lack of linter 2024-04-16 00:22:40 -04:00
John Fernandez 7938169730 Don't forget lookup by name functions
This helps prevent name collisions
2024-04-15 22:50:05 -04:00
John Fernandez 3d5c43e45d Fix model functions by using index
Instead of name.
2024-04-15 22:43:45 -04:00
John Fernandez 1581d9dc1f Save progress 2024-04-14 12:34:20 -04:00
John Fernandez d4f8706208 Add contents to UpdateContainerOptions 2024-04-14 12:00:32 -04:00
John Fernandez 8d40952bf5 Finish adding Variable change options 2024-04-13 00:51:20 -04:00
John Fernandez 4cac920943 Add more dialog functions 2024-04-12 23:56:00 -04:00
John Fernandez 2ad0571b22 Make sure that setVariableType signals failure 2024-04-12 23:55:22 -04:00
John Fernandez 6e10ee64f8 Add TrimNumberString function
And other things
2024-04-12 21:56:27 -04:00
John Fernandez c9f4982d08 Add apply trigger on validated data
And clean up dialog model slightly
2024-04-12 21:48:50 -04:00
John Fernandez bc059bdb86 Add Variable Editor Trigger To Loadout Dialog 2024-04-12 21:47:42 -04:00
John Fernandez e60cf65036 Write part of onVariablesTableUpdated 2024-04-12 21:04:20 -04:00
John Fernandez 2934e99ddc Progress on applyModel 2024-04-12 17:26:14 -04:00
John Fernandez 11fac90617 Add missing functions 2024-04-12 15:27:24 -04:00
John Fernandez 9ef56797fe Create the Validate Function
And correct the Apply function some.
2024-04-12 15:26:58 -04:00
JohnAFernandez e1ed5fdd78 Fixes to allow building
Edits were previously done on a machine that could not build or use a linter.
2024-04-12 00:59:29 -04:00
JohnAFernandez a125b8b3ce Fixup the Variables Dialog
Add it to the editor menu (with shortcut), and fix it showing up when started
2024-04-12 00:58:37 -04:00
John Fernandez de54dea61a save progress 2024-04-11 22:29:21 -04:00
John Fernandez d665c618c1 Starting progress on initializeData
And container functions
2024-04-11 15:12:16 -04:00
John Fernandez fa27b8d133 MOAR FUNCTIONS! 2024-04-10 01:38:48 -04:00
John Fernandez aea39664ba Add logic for addcontainer
and a few other functions
2024-04-08 14:18:14 -04:00
John Fernandez 5575ab4c95 Finish setContainerValueType
And add stubs for other model functions we'll need
2024-04-07 23:37:25 -04:00
John Fernandez 2827e93b2d Continue writing editor model functions
Also, add a few declarations for List and Map contents editing.
2024-04-07 16:36:29 -04:00
John Fernandez cad4f6980d Save progress 2024-04-07 09:51:00 -04:00
John Fernandez 286a96e2c3 save progress 2024-04-06 22:25:48 -04:00
John Fernandez 7c15adec1a Finish Loop for saving variables 2024-01-06 18:38:12 -05:00
John Fernandez 52cc1f182e Make sure flags are included 2024-01-06 18:13:19 -05:00
John Fernandez 64b54d1022 Start writing apply and reject methods 2024-04-01 17:58:07 -04:00
John Fernandez 5d0ee564ac fix support structs 2023-12-29 17:50:39 -05:00
John Fernandez e7583ddab8 start creating Model and helper structs 2023-12-22 18:41:06 -05:00
JohnAFernandez 98a5ff4a23 Initial Dialog
Not building yet.  Some sort of linking error
2024-03-31 12:43:08 -04:00
1570 changed files with 135616 additions and 71996 deletions

No files matched your search

+2
View File
@@ -48,5 +48,7 @@ CheckOptions:
value: 'std::vector;std::deque;std::list;SCP_vector;SCP_deque;SCP_list'
- key: 'readability-braces-around-statements.ShortStatementLines'
value: '4' # Avoid flagging simple if (...) return false; statements
- key: 'performance-move-const-arg.CheckTriviallyCopyableMove' # This isn't actually performance relevant, but using move on trivially copyable types can well indicate that the variable should not be used anymore, such as when passing a builder type to the finialization function
value: 'false'
...
+39 -21
View File
@@ -23,10 +23,13 @@ jobs:
runs-on: ${{ matrix.os }}
container: ghcr.io/scp-fs2open/linux_build:sha-71099c9
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Configure CMake
env:
CONFIGURATION: ${{ matrix.configuration }}
@@ -51,7 +54,7 @@ jobs:
CONFIGURATION: ${{ matrix.configuration }}
run: $GITHUB_WORKSPACE/ci/linux/generate_appimage.sh $GITHUB_WORKSPACE/build/install
- name: Upload build result
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: linux-${{ matrix.configuration }}-${{ matrix.arch }}
path: ${{ github.workspace }}/build/install/*.AppImage
@@ -59,23 +62,26 @@ jobs:
name: Build Linux distribution zip
needs: build_linux
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-748b19d
container: ghcr.io/scp-fs2open/sftp_upload:sha-844131c
strategy:
matrix:
arch: [x86_64, arm64]
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: '0'
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Download Release builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: linux-Release-${{ matrix.arch }}
path: builds
- name: Download FastDebug builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: linux-FastDebug-${{ matrix.arch }}
path: builds
@@ -104,12 +110,15 @@ jobs:
arch: [Win32, x64]
simd: [SSE2]
name: Windows
runs-on: windows-2019
runs-on: windows-2022
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
# - name: Cache Qt
# id: cache-qt-win
# uses: actions/cache@v1
@@ -155,13 +164,13 @@ jobs:
-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 16 2019" -T "v142" ..
-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 16 2019" -T "v142" ..
-G "Visual Studio 17 2022" -T "v143" -DCMAKE_BUILD_TYPE=$CONFIGURATION ..
fi
- name: Compile
working-directory: ./build
@@ -179,7 +188,7 @@ jobs:
shell: bash
run: ./bin/$CONFIGURATION/unittests --gtest_shuffle
- name: Upload build result
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: windows-${{ matrix.configuration }}-${{ matrix.arch }}-${{ matrix.simd }}
path: ${{ github.workspace }}/build/install/*
@@ -187,24 +196,27 @@ jobs:
name: Build Windows distribution zip
needs: build_windows
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-748b19d
container: ghcr.io/scp-fs2open/sftp_upload:sha-844131c
strategy:
matrix:
arch: [Win32, x64]
simd: [SSE2]
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: '0'
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Download Release builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: windows-Release-${{ matrix.arch }}-${{ matrix.simd }}
path: builds
- name: Download FastDebug builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: windows-FastDebug-${{ matrix.arch }}-${{ matrix.simd }}
path: builds
@@ -249,11 +261,14 @@ jobs:
# setup-python: 'false'
# aqtversion: ==1.1.3
# py7zrversion: '==0.19.*'
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: 0
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Prepare Vulkan SDK
uses: humbletim/[email protected]
with:
@@ -302,7 +317,7 @@ jobs:
# Ref: https://github.com/actions/runner-images/issues/2619
run: gtar -cvzf macos-build-${{ matrix.configuration }}-${{ matrix.arch }}.tgz *.app
- name: Upload build result
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: mac-${{ matrix.configuration }}-${{ matrix.arch }}
path: ${{ github.workspace }}/build/bin/macos-build-${{ matrix.configuration }}-${{ matrix.arch }}.tgz
@@ -310,24 +325,27 @@ jobs:
name: Build Mac distribution zip
needs: build_mac
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-748b19d
container: ghcr.io/scp-fs2open/sftp_upload:sha-844131c
strategy:
matrix:
arch: [x86_64, arm64]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: '0'
ref: '${{ github.ref }}'
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Download Release builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: mac-Release-${{ matrix.arch }}
path: builds
- name: Download FastDebug builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: mac-FastDebug-${{ matrix.arch }}
path: builds
+43 -25
View File
@@ -52,12 +52,15 @@ jobs:
runs-on: ${{ matrix.os }}
container: ghcr.io/scp-fs2open/linux_build:sha-71099c9
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v7
# checks-out your repository under $GITHUB_WORKSPACE, so your workflow can access it.
name: Checkout
with:
submodules: true # `true` to checkout submodules, `recursive` to recursively checkout submodules
ref: '${{ github.ref }}'
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Configure CMake
env:
CONFIGURATION: ${{ matrix.configuration }}
@@ -82,7 +85,7 @@ jobs:
CONFIGURATION: ${{ matrix.configuration }}
run: $GITHUB_WORKSPACE/ci/linux/generate_appimage.sh $GITHUB_WORKSPACE/build/install
- name: Upload build result
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
# Upload the url file for use with other runners
# upload-artifact.inputs:
# if-no-files-found=warn What to do if path fails to find any files.
@@ -99,26 +102,29 @@ jobs:
name: Build Linux distribution zip
needs: build_linux
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-748b19d
container: ghcr.io/scp-fs2open/sftp_upload:sha-844131c
strategy:
matrix:
arch: [x86_64, arm64]
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: '0' # value 0 to fetch all history and tags for all branches
ref: '${{ github.ref }}'
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Download Release builds
# Grab the release builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: linux-Release-${{ matrix.arch }}
path: builds
- name: Download FastDebug builds
# Grab the debug builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: linux-FastDebug-${{ matrix.arch }}
path: builds
@@ -135,7 +141,7 @@ jobs:
run: $GITHUB_WORKSPACE/ci/linux/create_dist_pack.sh Linux
- name: Upload result package
# Stash the result to artifact filespace
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ steps.generate_package.outputs.package_name }}
path: ${{ steps.generate_package.outputs.package_path }}
@@ -155,14 +161,17 @@ jobs:
simd: [SSE2, AVX]
needs: create_release
name: Windows
runs-on: windows-2019
runs-on: windows-2022
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v7
# Checkout repo
name: Checkout
with:
submodules: true
ref: '${{ github.ref }}'
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
# - name: Cache Qt
# # Cache Qt
# id: cache-qt-win
@@ -211,13 +220,13 @@ jobs:
-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 16 2019" -T "v142" ..
-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 16 2019" -T "v142" ..
-G "Visual Studio 17 2022" -T "v143" -DCMAKE_BUILD_TYPE=$CONFIGURATION ..
fi
- name: Compile
working-directory: ./build
@@ -235,7 +244,7 @@ jobs:
shell: bash
run: ./bin/$CONFIGURATION/unittests --gtest_shuffle
- name: Upload build result
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: windows-${{ matrix.configuration }}-${{ matrix.arch }}-${{ matrix.simd }}
path: ${{ github.workspace }}/build/install/*
@@ -244,7 +253,7 @@ jobs:
name: Build Windows distribution zip
needs: build_windows
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-748b19d
container: ghcr.io/scp-fs2open/sftp_upload:sha-844131c
strategy:
fail-fast: false # Run the other jobs in the matrix instead of failing them
matrix:
@@ -252,20 +261,23 @@ jobs:
simd: [SSE2, AVX]
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: '0'
ref: '${{ github.ref }}'
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Download Release builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: windows-Release-${{ matrix.arch }}-${{ matrix.simd }}
path: builds
- name: Download FastDebug builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: windows-FastDebug-${{ matrix.arch }}-${{ matrix.simd }}
path: builds
@@ -280,13 +292,13 @@ jobs:
run: $GITHUB_WORKSPACE/ci/linux/create_dist_pack.sh Windows
- name: Upload result package
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ steps.generate_package.outputs.package_name }}
path: ${{ steps.generate_package.outputs.package_path }}
- name: Upload debug package
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ steps.generate_package.outputs.debug_name }}
path: ${{ steps.generate_package.outputs.debug_path }}
@@ -324,12 +336,15 @@ jobs:
# cached: ${{ steps.cache-qt-mac.outputs.cache-hit }}
# setup-python: 'false'
# aqtversion: ==1.1.3
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: 0
ref: '${{ github.ref }}'
- name: 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:
@@ -372,7 +387,7 @@ jobs:
# Ref: https://github.com/actions/runner-images/issues/2619
run: gtar -cvzf macos-build-${{ matrix.configuration }}-${{ matrix.arch }}.tgz *.app
- name: Upload build result
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: mac-${{ matrix.configuration }}-${{ matrix.arch }}
path: ${{ github.workspace }}/build/bin/macos-build-${{ matrix.configuration }}-${{ matrix.arch }}.tgz
@@ -380,24 +395,27 @@ jobs:
name: Build Mac distribution zip
needs: build_mac
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-748b19d
container: ghcr.io/scp-fs2open/sftp_upload:sha-844131c
strategy:
matrix:
arch: [x86_64, arm64]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: '0'
ref: '${{ github.ref }}'
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Download Release builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: mac-Release-${{ matrix.arch }}
path: builds
- name: Download FastDebug builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: mac-FastDebug-${{ matrix.arch }}
path: builds
@@ -413,7 +431,7 @@ jobs:
ARCH: ${{ matrix.arch }}
run: $GITHUB_WORKSPACE/ci/linux/create_dist_pack.sh Mac
- name: Upload result package
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ steps.generate_package.outputs.package_name }}
path: ${{ steps.generate_package.outputs.package_path }}
+39 -21
View File
@@ -17,10 +17,13 @@ jobs:
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/linux_build:sha-71099c9
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Set up test version
shell: bash
run: |
@@ -51,7 +54,7 @@ jobs:
CONFIGURATION: ${{ matrix.configuration }}
run: $GITHUB_WORKSPACE/ci/linux/generate_appimage.sh $GITHUB_WORKSPACE/build/install
- name: Upload build result
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: linux-${{ matrix.configuration }}
path: ${{ github.workspace }}/build/install/*.AppImage
@@ -59,21 +62,24 @@ jobs:
name: Build Linux distribution zip
needs: build_linux
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-748b19d
container: ghcr.io/scp-fs2open/sftp_upload:sha-844131c
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: '0'
ref: '${{ github.ref }}'
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Download Release builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: linux-Release
path: builds
- name: Download FastDebug builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: linux-FastDebug
path: builds
@@ -96,12 +102,15 @@ jobs:
arch: [Win32, x64]
simd: [SSE2]
name: Windows
runs-on: windows-2019
runs-on: windows-2022
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Set up test version
shell: bash
run: |
@@ -153,13 +162,13 @@ jobs:
-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 16 2019" -T "v142" ..
-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 16 2019" -T "v142" ..
-G "Visual Studio 17 2022" -T "v143" -DCMAKE_BUILD_TYPE=$CONFIGURATION ..
fi
- name: Compile
working-directory: ./build
@@ -177,7 +186,7 @@ jobs:
shell: bash
run: ./bin/$CONFIGURATION/unittests --gtest_shuffle
- name: Upload build result
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: windows-${{ matrix.configuration }}-${{ matrix.arch }}-${{ matrix.simd }}
path: ${{ github.workspace }}/build/install/*
@@ -185,25 +194,28 @@ jobs:
name: Build Windows distribution zip
needs: build_windows
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-748b19d
container: ghcr.io/scp-fs2open/sftp_upload:sha-844131c
strategy:
matrix:
arch: [Win32, x64]
simd: [SSE2]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: '0'
ref: '${{ github.ref }}'
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Download Release builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: windows-Release-${{ matrix.arch }}-${{ matrix.simd }}
path: builds
- name: Download FastDebug builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: windows-FastDebug-${{ matrix.arch }}-${{ matrix.simd }}
path: builds
@@ -248,11 +260,14 @@ jobs:
# setup-python: 'false'
# aqtversion: ==1.1.3
# py7zrversion: '==0.19.*'
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: 0
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Prepare Vulkan SDK
uses: humbletim/[email protected]
with:
@@ -300,7 +315,7 @@ jobs:
# Ref: https://github.com/actions/runner-images/issues/2619
run: gtar -cvzf macos-build-${{ matrix.configuration }}-${{ matrix.arch }}.tgz *.app
- name: Upload build result
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: mac-${{ matrix.configuration }}-${{ matrix.arch }}
path: ${{ github.workspace }}/build/bin/macos-build-${{ matrix.configuration }}-${{ matrix.arch }}.tgz
@@ -308,24 +323,27 @@ jobs:
name: Build Mac distribution zip
needs: build_mac
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/sftp_upload:sha-748b19d
container: ghcr.io/scp-fs2open/sftp_upload:sha-844131c
strategy:
matrix:
arch: [x86_64, arm64]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: '0'
ref: '${{ github.ref }}'
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Download Release builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: mac-Release-${{ matrix.arch }}
path: builds
- name: Download FastDebug builds
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: mac-FastDebug-${{ matrix.arch }}
path: builds
+6 -1
View File
@@ -27,10 +27,15 @@ jobs:
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/linux_build:sha-71099c9
steps:
- uses: actions/checkout@v1
- name: Install Qt dependencies
run: apt-get -yq update && apt-get -yq install libfontconfig1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: ccache
uses: hendrikmuhs/ccache-action@v1
with:
+4 -2
View File
@@ -45,11 +45,13 @@ jobs:
run: echo "github.event_name= ${{ github.event_name }}"
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v7
with:
fetch-depth: '0'
ref: ${{ env.RELEASE_TAG }}
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Install Python dependencies
run: pip install -r ci/post/requirements.txt
+20 -9
View File
@@ -30,11 +30,16 @@ jobs:
runs-on: ubuntu-latest
container: ghcr.io/scp-fs2open/linux_build:sha-71099c9
steps:
- uses: actions/checkout@v1
- name: Install Qt dependencies
run: apt-get -yq update && apt-get -yq install libfontconfig1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: 0
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: ccache
uses: hendrikmuhs/ccache-action@v1
with:
@@ -76,18 +81,21 @@ jobs:
build_windows:
strategy:
matrix:
configuration: [Debug, Release]
configuration: [Debug, FastDebug, Release]
compiler: [MSVC]
arch: [Win32, x64]
name: Windows
runs-on: windows-2019
runs-on: windows-2022
steps:
- name: Prepare Environment
run: choco install ninja
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
# - name: Cache Qt
# id: cache-qt-win
# uses: actions/cache@v1
@@ -139,12 +147,12 @@ jobs:
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 16 2019" \
-DFSO_BUILD_QTFRED=OFF -T "v142" -A "$ARCHITECTURE" ..
-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 16 2019" \
-DFSO_BUILD_QTFRED=OFF -T "v142" -A "$ARCHITECTURE" ..
-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
fi
- name: Compile
@@ -197,11 +205,14 @@ jobs:
# setup-python: 'false'
# aqtversion: ==1.1.3
# py7zrversion: '==0.19.*'
- uses: actions/checkout@v1
- uses: actions/checkout@v7
name: Checkout
with:
submodules: true
fetch-depth: 0
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: ccache
uses: hendrikmuhs/ccache-action@v1
with:
+4 -1
View File
@@ -29,9 +29,12 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@v7
with:
submodules: recursive # `true` to checkout submodules, `recursive` to recursively checkout submodules
- name: Set workspace as safe
# This appears to be broken in current actions, so do it manually
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
# pipe results of wget to a log file because it's very verbose and rarely fails
- name: Install Coverity
+3
View File
@@ -42,6 +42,7 @@ _ReSharper*/
#Project files
[Bb]uild/
[Oo]ut/
#Subversion files
.svn
@@ -259,3 +260,5 @@ CMakePresets.json
!/code/scripting/lua/LuaConvert.h
!/code/debugconsole/
!/docker/build
CMakeFiles/cmake.check_cache
CMakeCache.txt
+33 -16
View File
@@ -1,5 +1,5 @@
CMAKE_MINIMUM_REQUIRED(VERSION 3.9)
CMAKE_MINIMUM_REQUIRED(VERSION 3.21)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
# Make sure the source and binary directory are not the sample
@@ -8,12 +8,12 @@ if ("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}")
endif()
include(cmake/util.cmake)
include(cmake/embed_file.cmake)
# Set a few policies
set_policy(CMP0051 NEW)
set_policy(CMP0053 NEW)
set_policy(CMP0054 NEW)
set_policy(CMP0091 NEW) # Enable use of CMAKE_MSVC_RUNTIME_LIBRARY
SET(FSO_PROJECT_DESCRIPTION "Open source project based on the original FreeSpace 2 source code.")
@@ -57,12 +57,16 @@ 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")
INCLUDE(globals)
INCLUDE(toolchain)
include(embed_file)
IF(RESET_INSTALL_PREFIX)
IF(NOT $ENV{FS2PATH} STREQUAL "")
IF(NOT IS_DIRECTORY $ENV{FS2PATH})
MESSAGE(WARNING "The path '$ENV{FS2PATH}' of the environment variable FS2PATH does not point to a directory! Install prefix wasn't set.")
ELSE(NOT IS_DIRECTORY $ENV{FS2PATH})
SET(CMAKE_INSTALL_PREFIX "$ENV{FS2PATH}" CACHE FILEPATH "Install path" FORCE)
SET(CMAKE_INSTALL_PREFIX "$ENV{FS2PATH}" CACHE PATH "Install path" FORCE)
FILE(TO_CMAKE_PATH ${CMAKE_INSTALL_PREFIX} CMAKE_INSTALL_PREFIX)
ENDIF(NOT IS_DIRECTORY $ENV{FS2PATH})
ELSE(NOT $ENV{FS2PATH} STREQUAL "")
@@ -70,9 +74,11 @@ IF(RESET_INSTALL_PREFIX)
ENDIF(NOT $ENV{FS2PATH} STREQUAL "")
ENDIF(RESET_INSTALL_PREFIX)
IF(WIN32 OR APPLE)
IF(WIN32 OR APPLE OR CMAKE_SYSTEM_NAME STREQUAL "Linux")
OPTION(FSO_USE_SPEECH "Use text-to-speach libraries" ON)
ENDIF(WIN32 OR APPLE)
ELSE()
OPTION(FSO_USE_SPEECH "Use text-to-speach libraries" OFF)
ENDIF()
IF (WIN32)
OPTION(FSO_USE_VOICEREC "Enable voice recognition support" ON)
@@ -105,7 +111,7 @@ OPTION(FSO_USE_OPENALSOFT "Download and build OpenAL Soft instead of using the s
OPTION(FSO_FATAL_WARNINGS "Determines if warnings in the build are considered fatal errors, primarily used for CI" OFF)
SET(FSO_FREESPACE_PATH "${CMAKE_INSTALL_PREFIX}" CACHE FILEPATH "The path of the FreeSpace directory you want to use. Please note that you will have to change CMAKE_INSTALL_PREFIX if you change this at some point.")
SET(FSO_FREESPACE_PATH "${CMAKE_INSTALL_PREFIX}" CACHE PATH "The path of the FreeSpace directory you want to use. Please note that you will have to change CMAKE_INSTALL_PREFIX if you change this at some point.")
SET(FSO_RUN_ARGUMENTS "" CACHE STRING "Additional arguments passed to a generated executable when run with the generated build files.")
@@ -121,10 +127,25 @@ OPTION(FSO_BUILD_WITH_DISCORD "Build with Discord support" ON)
OPTION(FSO_BUILD_WITH_OPENGL "Enable compilation of the OpenGL renderer" ON)
OPTION(FSO_BUILD_WITH_OPENGL_DEBUG "Enables debug option for OpenGL" OFF)
OPTION(FSO_BUILD_WITH_OPENGL_ES "When building OpenGL, use OpenGL ES compatibility layer (External shaders only!)" OFF)
OPTION(FSO_BUILD_WITH_VULKAN "Enable compilation of the Vulkan renderer" OFF)
IF(NOT APPLE)
if(NOT FSO_BUILD_WITH_OPENGL AND FSO_BUILD_WITH_OPENGL_ES)
# Disable GLES if not building with OpenGL
set(FSO_BUILD_WITH_OPENGL_ES OFF FORCE)
endif()
if(FSO_BUILD_WITH_OPENGL_ES)
add_definitions(-DUSE_OPENGL_ES)
IF(FSO_BUILD_WITH_OPENXR)
MESSAGE(WARNING "FSO_BUILD_WITH_OPENXR is ON. But not supported on OpenGL ES for now - setting to OFF.")
SET(FSO_BUILD_WITH_OPENXR OFF CACHE BOOL "" FORCE)
endif()
endif()
IF(NOT APPLE AND NOT FSO_BUILD_WITH_OPENGL_ES)
SET(OPENXR_BUILD_DEFAULT ON)
ELSE()
SET(OPENXR_BUILD_DEFAULT OFF)
@@ -152,15 +173,11 @@ mark_as_advanced(FORCE FSO_BUILD_WITH_FFMPEG)
mark_as_advanced(FORCE FSO_BUILD_WITH_DISCORD)
mark_as_advanced(FORCE FSO_BUILD_WITH_OPENGL)
mark_as_advanced(FORCE FSO_BUILD_WITH_OPENGL_DEBUG)
mark_as_advanced(FORCE FSO_BUILD_WITH_OPENGL_ES)
mark_as_advanced(FORCE FSO_BUILD_WITH_VULKAN)
mark_as_advanced(FORCE FSO_BUILD_WITH_OPENXR)
mark_as_advanced(FORCE FSO_BUILD_WITH_OPENXR_DEBUG)
# Include cotire file from https://github.com/sakra/cotire/
include(cotire)
INCLUDE(globals)
INCLUDE(toolchain)
INCLUDE(platforms)
include(version)
@@ -171,7 +188,8 @@ SET(GENERATED_SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated_source)
include(generateHeaders)
if(FSO_BUILD_APPIMAGE)
include(appimage)
SET(BINARY_DESTINATION "bin")
SET(LIBRAY_DESTINATION "lib")
endif()
include(clang-tidy)
@@ -227,9 +245,7 @@ include(package)
include(doxygen)
# Print used options to log
IF(WIN32 OR APPLE)
message(STATUS "Using text to speech: ${FSO_USE_SPEECH}")
ENDIF()
message(STATUS "Using text to speech: ${FSO_USE_SPEECH}")
IF (WIN32)
message(STATUS "Using voice recogition: ${FSO_USE_VOICEREC}")
message(STATUS "Building FRED2: ${FSO_BUILD_FRED2}")
@@ -243,5 +259,6 @@ message(STATUS "Release logging: ${FSO_RELEASE_LOGGING}")
message(STATUS "With FFmpeg: ${FSO_BUILD_WITH_FFMPEG}")
message(STATUS "With Discord: ${FSO_BUILD_WITH_DISCORD}")
message(STATUS "With OpenGL: ${FSO_BUILD_WITH_OPENGL}")
message(STATUS "With OpenGL ES: ${FSO_BUILD_WITH_OPENGL_ES}")
message(STATUS "With Vulkan: ${FSO_BUILD_WITH_VULKAN}")
message(STATUS "With OpenXR: ${FSO_BUILD_WITH_OPENXR}")
+1 -1
View File
@@ -26,5 +26,5 @@ git diff -U0 --no-color "$BASE_COMMIT..$2" | \
-extra-arg="-DWITH_VULKAN" \
-extra-arg="-DVULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1" \
-extra-arg="-DVK_NO_PROTOTYPES" \
-regex '(code(?!((\/graphics\/shaders\/compiled)|(\/globalincs\/windebug)))|freespace2|qtfred|test|build|tools)\/.*\.(cpp|h)' \
-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"
+1 -1
View File
@@ -42,7 +42,7 @@ export CXXFLAGS
export CFLAGS
export LD_LIBRARY_PATH
if [ "$RUNNER_OS" = "macOS" ]; then
if [ "$RUNNER_OS" = "macOS" ] && [ ! -x "$(which ninja)" ]; then
brew install ninja
fi
+39 -4
View File
@@ -2,13 +2,48 @@
INSTALL_FOLDER=$1
# safety check
if [ ! -d bin -o ! -f cmake_install.cmake ]; then
echo "ERROR! This script must be run from within the build root!"
exit 1
fi
if [ ! -x "$(which wget)" ]; then
echo "ERROR! Required utility is not available: wget"
exit 1
fi
# install newest appimagetool if it's not already available
if [ ! -x ./bin/appimagetool ]; then
APPIMAGE_TOOL_URL="https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-$(uname -m).AppImage"
wget -O ./bin/appimagetool "$APPIMAGE_TOOL_URL" || { echo "ERROR! Failed to get appimagetool!" && exit 1; }
chmod +x ./bin/appimagetool
fi
# This shouldn't be needed with newer runtimes, but they still generate an
# error if fusermount is missing even though it works. So to skip error msg
# and have max compatibility with runtimes and containers we'll use it.
export APPIMAGE_EXTRACT_AND_RUN=1
# Install Freespace2 targets
cmake -DCMAKE_INSTALL_PREFIX=$INSTALL_FOLDER -DCOMPONENT=Unspecified -P cmake_install.cmake
cmake -DCMAKE_INSTALL_PREFIX=$INSTALL_FOLDER -DCOMPONENT=Freespace2 -P cmake_install.cmake
cmake -DCMAKE_INSTALL_PREFIX=$INSTALL_FOLDER/Freespace2 -DCOMPONENT=Unspecified -P cmake_install.cmake
cmake -DCMAKE_INSTALL_PREFIX=$INSTALL_FOLDER/Freespace2 -DCOMPONENT=Freespace2 -P cmake_install.cmake
# We need to be a bit creative for determining the AppImage name since we don't want to hard-code the name
FILENAME="$(find $INSTALL_FOLDER/bin -name 'fs2_open_*' -type f -printf "%f\n").AppImage"
appimagetool -n $INSTALL_FOLDER "$INSTALL_FOLDER/$FILENAME"
FILENAME="$(find $INSTALL_FOLDER/Freespace2/bin -name 'fs2_open_*' -type f -printf "%f\n").AppImage"
./bin/appimagetool -n "$INSTALL_FOLDER/Freespace2" "$INSTALL_FOLDER/$FILENAME"
chmod +x "$INSTALL_FOLDER/$FILENAME"
# Maybe install qtFRED targets
if [ -f qtfred/cmake_install.cmake ]; then
cmake -DCMAKE_INSTALL_PREFIX=$INSTALL_FOLDER/qtFRED -DCOMPONENT=Unspecified -P cmake_install.cmake
cmake -DCMAKE_INSTALL_PREFIX=$INSTALL_FOLDER/qtFRED -DCOMPONENT=qtFRED -P cmake_install.cmake
# We need to be a bit creative for determining the AppImage name since we don't want to hard-code the name
FILENAME="$(find $INSTALL_FOLDER/qtFRED/bin -iname 'qtfred_*' -type f -printf "%f\n").AppImage"
./bin/appimagetool -n "$INSTALL_FOLDER/qtFRED" "$INSTALL_FOLDER/$FILENAME"
chmod +x "$INSTALL_FOLDER/$FILENAME"
fi
ls -al $INSTALL_FOLDER
+2 -2
View File
@@ -1,3 +1,3 @@
Mako==1.2.2
requests==2.32.0
Mako==1.3.12
requests==2.33.0
semantic-version==2.8.5
-21
View File
@@ -1,21 +0,0 @@
SET(BINARY_DESTINATION "bin")
SET(LIBRAY_DESTINATION "lib")
configure_file("${CMAKE_CURRENT_LIST_DIR}/AppRun.in" "${CMAKE_CURRENT_BINARY_DIR}/AppRun.gen" @ONLY)
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/AppRun-$<CONFIG>"
INPUT "${CMAKE_CURRENT_BINARY_DIR}/AppRun.gen")
install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/AppRun-$<CONFIG>" DESTINATION "." RENAME "AppRun"
COMPONENT "Freespace2")
configure_file("${CMAKE_CURRENT_LIST_DIR}/AppImage.desktop.in" "${CMAKE_CURRENT_BINARY_DIR}/AppImage.desktop.gen" @ONLY)
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/AppImage-$<CONFIG>.desktop"
INPUT "${CMAKE_CURRENT_BINARY_DIR}/AppImage.desktop.gen")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/AppImage-$<CONFIG>.desktop" DESTINATION "." RENAME "fso.desktop"
COMPONENT "Freespace2")
install(FILES "${CMAKE_SOURCE_DIR}/freespace2/resources/app_icon.png" DESTINATION "."
COMPONENT "Freespace2")
-4054
View File
File diff suppressed because it is too large. Load diff
+2
View File
@@ -11,6 +11,8 @@ if (WIN32)
endif()
elseif(APPLE)
# it should just work
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
# uses speech-dispatcher with dlopen
else()
message(SEND_ERROR "Text to Speech is not supported on this platform!")
endif()
+6 -1
View File
@@ -9,14 +9,19 @@ else()
endif()
set(IS_ARM64 FALSE)
set(IS_ARMV7A FALSE)
if (NOT "${CMAKE_GENERATOR_PLATFORM}" STREQUAL "") # needed to cover Visual Studio generator
if(CMAKE_GENERATOR_PLATFORM MATCHES "^(aarch64|arm64|ARM64)")
set(IS_ARM64 TRUE)
elseif(CMAKE_GENERATOR_PLATFORM MATCHES "^(armv7)")
set(IS_ARMV7A TRUE)
endif()
else()
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)")
set(IS_ARM64 TRUE)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(armv7)")
set(IS_ARMV7A TRUE)
endif()
endif()
@@ -33,6 +38,6 @@ else()
endif()
set(IS_X86 FALSE)
if (NOT IS_ARM64 AND NOT IS_RISCV)
if (NOT IS_ARM64 AND NOT IS_RISCV AND NOT IS_ARMV7A)
set(IS_X86 TRUE)
endif()
+1 -1
View File
@@ -15,4 +15,4 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wno-unused-variable -Wno-unused-parameter")
set(CMAKE_CXX_FLAGS_DEBUG "-Og -g -Wshadow")
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -Wshadow")
+2 -2
View File
@@ -119,14 +119,14 @@ string(REGEX MATCH "-O[a-zA-Z|0-9]+" C_OPT_FLAG ${C_BASE_FLAGS})
# If no user-set opt flag, set -O3 and -Og
if ("${CXX_OPT_FLAG}" STREQUAL "")
set(CXX_OPT_FLAG_RELEASE "-O3")
set(CXX_OPT_FLAG_DEBUG "-Og")
set(CXX_OPT_FLAG_DEBUG "-O0")
else()
set(CXX_OPT_FLAG_RELEASE "${CXX_OPT_FLAG}")
set(CXX_OPT_FLAG_DEBUG "${CXX_OPT_FLAG}")
endif()
if ("${C_OPT_FLAG}" STREQUAL "")
set(C_OPT_FLAG_RELEASE "-O3")
set(C_OPT_FLAG_DEBUG "-Og")
set(C_OPT_FLAG_DEBUG "-O0")
else()
set(C_OPT_FLAG_RELEASE "${C_OPT_FLAG}")
set(C_OPT_FLAG_DEBUG "${C_OPT_FLAG}")
+2 -2
View File
@@ -161,14 +161,14 @@ string(REGEX MATCH "-O[a-zA-Z|0-9]+" C_OPT_FLAG ${C_BASE_FLAGS})
# If no user-set opt flag, set -O3 and -Og
if ("${CXX_OPT_FLAG}" STREQUAL "")
set(CXX_OPT_FLAG_RELEASE "-O3")
set(CXX_OPT_FLAG_DEBUG "-Og")
set(CXX_OPT_FLAG_DEBUG "-O0")
else()
set(CXX_OPT_FLAG_RELEASE "${CXX_OPT_FLAG}")
set(CXX_OPT_FLAG_DEBUG "${CXX_OPT_FLAG}")
endif()
if ("${C_OPT_FLAG}" STREQUAL "")
set(C_OPT_FLAG_RELEASE "-O3")
set(C_OPT_FLAG_DEBUG "-Og")
set(C_OPT_FLAG_DEBUG "-O0")
else()
set(C_OPT_FLAG_RELEASE "${C_OPT_FLAG}")
set(C_OPT_FLAG_DEBUG "${C_OPT_FLAG}")
+8 -17
View File
@@ -71,27 +71,19 @@ if (MSVC_RELEASE_DEBUGGING)
endif()
endif()
IF(MSVC_USE_RUNTIME_DLL)
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /MD")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MD")
ELSE(MSVC_USE_RUNTIME_DLL)
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /MT")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")
ENDIF(MSVC_USE_RUNTIME_DLL)
# 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")
add_compile_definitions(_AFXDLL)
ELSE()
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<NOT:$<CONFIG:Release>>:Debug>")
ENDIF()
# Debug
set(CMAKE_C_FLAGS_DEBUG "/W4 /Gy /Zi /Od /RTC1 /Gd /Oy-")
set(CMAKE_CXX_FLAGS_DEBUG "/W4 /Gy /Zi /Od /RTC1 /Gd /Oy-")
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG /INCREMENTAL:NO /MAPINFO:EXPORTS /NODEFAULTLIB:libcmt.lib")
IF(MSVC_USE_RUNTIME_DLL)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /MDd")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MDd")
ELSE(MSVC_USE_RUNTIME_DLL)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /MTd")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")
ENDIF(MSVC_USE_RUNTIME_DLL)
INCLUDE(MSVCMultipleProcessCompile)
# Visual Studio supports compiling for multiple vector instruction sets
@@ -122,8 +114,7 @@ if(IS_X86)
endif()
target_compile_definitions(compiler INTERFACE _CRT_SECURE_NO_DEPRECATE _CRT_SECURE_NO_WARNINGS _SECURE_SCL=0 NOMINMAX
"$<$<CONFIG:FastDebug>:_ITERATOR_DEBUG_LEVEL=0>" _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING)
target_compile_definitions(compiler INTERFACE _CRT_SECURE_NO_DEPRECATE _CRT_SECURE_NO_WARNINGS _SECURE_SCL=0 NOMINMAX _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING)
if (FSO_FATAL_WARNINGS)
# Make warnings fatal if the right variable is set
+11 -21
View File
@@ -158,27 +158,17 @@ function(set_precompiled_header _target _headerPath)
target_compile_definitions(${_target} PRIVATE CMAKE_PCH)
target_precompile_headers(${_target} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${_headerPath}>")
else()
if (ENABLE_COTIRE)
message("${_headerPath}")
set_target_properties(${_target} PROPERTIES COTIRE_CXX_PREFIX_HEADER_INIT "${_headerPath}")
# Disable unity build as it doesn't work well for us
set_target_properties(${_target} PROPERTIES COTIRE_ADD_UNITY_BUILD FALSE)
cotire(${_target})
else()
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
target_compile_options(${_target} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:-include${_headerPath}>")
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
target_compile_options(${_target} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:/FI ${_headerPath}>")
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
target_compile_options(${_target} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:-include${_headerPath}>")
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang")
target_compile_options(${_target} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:-include${_headerPath}>")
ELSE()
MESSAGE("Unknown compiler for global includes. This will probably break the build.")
ENDIF()
endif()
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
target_compile_options(${_target} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:-include${_headerPath}>")
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
target_compile_options(${_target} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:/FI ${_headerPath}>")
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
target_compile_options(${_target} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:-include${_headerPath}>")
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang")
target_compile_options(${_target} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:-include${_headerPath}>")
ELSE()
MESSAGE("Unknown compiler for global includes. This will probably break the build.")
ENDIF()
endif()
endfunction()
+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 24)
set_if_not_defined(FSO_VERSION_MINOR 3)
set_if_not_defined(FSO_VERSION_MAJOR 25)
set_if_not_defined(FSO_VERSION_MINOR 1)
set_if_not_defined(FSO_VERSION_BUILD 0)
# A numeric revision e.g. the CI build number
+1 -1
View File
@@ -59,7 +59,7 @@ endif()
target_link_libraries(code PUBLIC libRocket)
target_link_libraries(code PUBLIC pcp)
target_link_libraries(code PUBLIC pcpnatpmp)
target_link_libraries(code PUBLIC parsers)
+1 -1
View File
@@ -30,7 +30,7 @@ class BuiltinActionDefinition : public ActionDefinition {
// This should have been caught earlier
Assertion(paramIter != parameterExpressions.cend(), "Could not find built-in parameter!");
return std::unique_ptr<Action>(new TAction(paramIter->second.template asTyped<ActionValueType>()));
return std::make_unique<TAction>(paramIter->second.template asTyped<ActionValueType>());
}
};
+1 -1
View File
@@ -19,7 +19,7 @@ expression::ProgramVariables getDefaultTableVariables()
expression::ProgramVariables vars;
vars.setValue({"locals", "position"}, expression::Value(vmd_zero_vector));
vars.setValue({"locals", "direction"}, expression::Value(vmd_zero_vector));
vars.setValue({"locals", "direction"}, expression::Value(vmd_z_vector));
return vars;
}
+2 -2
View File
@@ -24,7 +24,7 @@ ActionExpression ActionExpression::parseFromTable(ValueType expectedReturnType,
stuff_string(expressionText, F_NAME);
ExpressionParser parser(expressionText);
ExpressionParser parser(std::move(expressionText));
auto expression = parser.parse(context);
@@ -45,7 +45,7 @@ ActionExpression ActionExpression::parseFromTable(ValueType expectedReturnType,
}
// Everything is valid
return ActionExpression(expression);
return ActionExpression(std::move(expression));
}
Value ActionExpression::execute(const ProgramVariables& variables) const
+8 -8
View File
@@ -51,8 +51,8 @@ class ExpressionBuilderVisitor : public ActionExpressionVisitor {
operatorText = context->MINUS()->getText();
}
const auto leftExpr = visit(context->expression(0)).as<std::shared_ptr<nodes::AbstractExpression>>();
const auto rightExpr = visit(context->expression(1)).as<std::shared_ptr<nodes::AbstractExpression>>();
const auto leftExpr = std::any_cast<std::shared_ptr<nodes::AbstractExpression>>(visit(context->expression(0)));
const auto rightExpr = std::any_cast<std::shared_ptr<nodes::AbstractExpression>>(visit(context->expression(1)));
auto operatorExpression = std::make_shared<nodes::FunctionCallExpression>(operatorTok,
true,
@@ -88,8 +88,8 @@ class ExpressionBuilderVisitor : public ActionExpressionVisitor {
antlrcpp::Any visitRandom_range_expression(ActionExpressionParser::Random_range_expressionContext* context) override
{
auto expression = std::make_shared<nodes::RandomRangeExpression>(context->RAND_L_PAREN()->getSymbol(),
visit(context->expression(0)).as<std::shared_ptr<nodes::AbstractExpression>>(),
visit(context->expression(1)).as<std::shared_ptr<nodes::AbstractExpression>>());
std::any_cast<std::shared_ptr<nodes::AbstractExpression>>(visit(context->expression(0))),
std::any_cast<std::shared_ptr<nodes::AbstractExpression>>(visit(context->expression(1))));
return std::static_pointer_cast<nodes::AbstractExpression>(expression);
}
antlrcpp::Any visitLiteral_expression(ActionExpressionParser::Literal_expressionContext* context) override
@@ -122,9 +122,9 @@ class ExpressionBuilderVisitor : public ActionExpressionVisitor {
}
antlrcpp::Any visitVec3d_constructor(ActionExpressionParser::Vec3d_constructorContext* context) override
{
const auto xExpression = visit(context->expression(0)).as<std::shared_ptr<nodes::AbstractExpression>>();
const auto yExpression = visit(context->expression(1)).as<std::shared_ptr<nodes::AbstractExpression>>();
const auto zExpression = visit(context->expression(2)).as<std::shared_ptr<nodes::AbstractExpression>>();
const auto xExpression = std::any_cast<std::shared_ptr<nodes::AbstractExpression>>(visit(context->expression(0)));
const auto yExpression = std::any_cast<std::shared_ptr<nodes::AbstractExpression>>(visit(context->expression(1)));
const auto zExpression = std::any_cast<std::shared_ptr<nodes::AbstractExpression>>(visit(context->expression(2)));
auto expression = std::make_shared<nodes::VectorConstructorExpression>(context->L_PAREN()->getSymbol(),
xExpression,
@@ -157,7 +157,7 @@ std::shared_ptr<nodes::AbstractExpression> ExpressionParser::parse(const ParseCo
if (errListener.diagnostics.empty()) {
// Only do semantic checking if syntax is valid
ExpressionBuilderVisitor builder;
exprReturn = tree->accept(&builder).as<std::shared_ptr<nodes::AbstractExpression>>();
exprReturn = std::any_cast<std::shared_ptr<nodes::AbstractExpression>>(tree->accept(&builder));
if (!exprReturn->validate(&parser, context)) {
exprReturn = nullptr;
+1 -1
View File
@@ -6,7 +6,7 @@ namespace expression {
ProgramVariablesDefinition& ProgramVariablesDefinition::addScope(const SCP_string& name)
{
auto scope = std::unique_ptr<ProgramVariablesDefinition>(new ProgramVariablesDefinition());
auto scope = std::make_unique<ProgramVariablesDefinition>();
auto scopePtr = scope.get();
+1 -1
View File
@@ -70,7 +70,7 @@ ActionResult MoveToSubmodel::execute(ProgramLocals& locals) const
std::unique_ptr<Action> MoveToSubmodel::clone() const
{
return std::unique_ptr<Action>(new MoveToSubmodel(*this));
return std::make_unique<MoveToSubmodel>(*this);
}
} // namespace types
+2 -2
View File
@@ -61,7 +61,7 @@ ActionResult ParticleEffectAction::execute(ProgramLocals& locals) const
matrix orientation;
vm_vector_2_matrix_norm(&orientation, &direction); // direction is normalized in SetDirectionAction::execute
source->setHost(make_unique<EffectHostObject>(locals.host.objp(), local_pos, orientation, true));
source->setHost(std::make_unique<EffectHostObject>(locals.host.objp(), local_pos, orientation, true));
source->finishCreation();
return ActionResult::Finished;
@@ -69,7 +69,7 @@ ActionResult ParticleEffectAction::execute(ProgramLocals& locals) const
std::unique_ptr<Action> ParticleEffectAction::clone() const
{
return std::unique_ptr<Action>(new ParticleEffectAction(*this));
return std::make_unique<ParticleEffectAction>(*this);
}
} // namespace types
+1 -1
View File
@@ -57,7 +57,7 @@ ActionResult PlaySoundAction::execute(ProgramLocals& locals) const
}
std::unique_ptr<Action> PlaySoundAction::clone() const
{
return std::unique_ptr<Action>(new PlaySoundAction(*this));
return std::make_unique<PlaySoundAction>(*this);
}
} // namespace types
} // namespace actions
+1 -1
View File
@@ -32,7 +32,7 @@ ActionResult SetDirectionAction::execute(ProgramLocals& locals) const
std::unique_ptr<Action> SetDirectionAction::clone() const
{
return std::unique_ptr<Action>(new SetDirectionAction(*this));
return std::make_unique<SetDirectionAction>(*this);
}
} // namespace types
+1 -1
View File
@@ -27,7 +27,7 @@ ActionResult SetPositionAction::execute(ProgramLocals& locals) const
std::unique_ptr<Action> SetPositionAction::clone() const
{
return std::unique_ptr<Action>(new SetPositionAction(*this));
return std::make_unique<SetPositionAction>(*this);
}
} // namespace types
+1 -1
View File
@@ -41,7 +41,7 @@ ActionResult WaitAction::execute(actions::ProgramLocals& locals) const
}
std::unique_ptr<Action> WaitAction::clone() const
{
return std::unique_ptr<Action>(new WaitAction(*this));
return std::make_unique<WaitAction>(*this);
}
} // namespace types
+5 -4
View File
@@ -42,6 +42,7 @@ typedef struct ai_flag_description {
extern ai_flag_name Ai_flag_names[];
extern ai_flag_description Ai_flag_descriptions[];
extern const int Num_ai_flag_names;
extern const size_t Num_ai_flag_descriptions;
// dock_orient_and_approach() modes.
#define DOA_APPROACH 1 // Approach the current point on the path (aip->path_cur)
@@ -527,7 +528,7 @@ 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);
extern void ai_attack_object(object *attacker, object *attacked, int ship_info_index = -1, int class_type = -1);
extern void ai_evade_object(object *evader, object *evaded);
extern void ai_ignore_object(object *ignorer, object *ignored, int ignore_new);
extern void ai_ignore_wing(object *ignorer, int wingnum);
@@ -559,7 +560,7 @@ extern void ai_set_guard_wing(object *objp, int wingnum);
extern void ai_warp_out(object *objp, vec3d *vp);
extern void ai_attack_wing(object *attacker, int wingnum);
extern void ai_deathroll_start(object *ship_obj);
extern int set_target_objnum(ai_info *aip, int objnum);
extern int set_target_objnum(ai_info* aip, int objnum);
extern void ai_form_on_wing(object *objp, object *goal_objp);
extern void ai_do_stay_near(object *objp, object *other_obj, float dist, int additional_data);
extern ship_subsys *set_targeted_subsys(ai_info *aip, ship_subsys *new_subsys, int parent_objnum);
@@ -596,7 +597,7 @@ 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);
extern int find_enemy(int objnum, float range, int max_attackers, int ship_info_index = -1, int class_type = -1);
float ai_get_weapon_speed(const ship_weapon *swp);
void set_predicted_enemy_pos_turret(vec3d *predicted_enemy_pos, const vec3d *gun_pos, const object *pobjp, const vec3d *enemy_pos, const vec3d *enemy_vel, float weapon_speed, float time_enemy_in_range);
@@ -617,7 +618,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 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);
// moved to header file by Goober5000
void ai_announce_ship_dying(object *dying_objp);
+14 -1
View File
@@ -44,12 +44,15 @@ namespace AI {
Goal_on_hold, // when set, this goal cannot currently be satisfied, although it could be in the future
Subsys_needs_fixup, // when set, the subsystem index (for a destroy subsystem goal) is invalid and must be gotten from the subsys name stored in docker.name field!!
Goal_override, // paired with ai_goal_type::DYNAMIC to mean this goal overrides any other goal
Want_override, // a goal should set this flag if Goal_override should be assigned when the goal is achievable
Purge, // purge this goal next time we process
Goals_purged, // this goal has already caused other goals to get purged
Purge_when_new_goal_added, // this goal is perpetual until any other goal is added, at which time it sets its own Purge flag
Goals_purged, // this goal has already caused other goals to get purged (because it is something like ai-disarm that renders other goals invalid)
Depart_sound_played,// Goober5000 - replacement for AL's hack ;)
Target_own_team, // this attack goal is allowed to target friendlies
Afterburn_hard, // afterburn as hard as possible to the goal
Waypoints_in_reverse, // decrement instead of increment
Clear_all_goals_first, // this goal wipes all goals (including itself) before it runs (handled separately, and in a different place, from purging invalid goals)
NUM_VALUES
};
@@ -129,6 +132,7 @@ namespace AI {
Smart_shield_management,
Smart_subsystem_targeting_for_turrets,
Strict_turret_tagged_only_targeting,
Ships_intercept_mines, // all AI-controlled ships autonomously engage hostile mines within mine_targetable_range of themselves
Support_dont_add_primaries, //Prevents support ship from equipping new primary as requested in https://scp.indiegames.us/mantis/view.php?id=3198
Turrets_ignore_target_radius,
Use_actual_primary_range,
@@ -142,6 +146,7 @@ namespace AI {
Fightercraft_nonshielded_ships_can_manage_ets,
Ships_playing_dead_dont_manage_ets,
Better_combat_collision_avoidance,
Better_combat_collision_avoid_includes_target,
Better_guard_collision_avoidance,
Require_exact_los,
Improved_missile_avoidance,
@@ -170,6 +175,7 @@ namespace AI {
Debris_respects_big_damage,
Dont_limit_change_in_speed_due_to_physics_whack,
Guards_ignore_protected_attackers,
Fix_standard_strafe,
Standard_strafe_used_more,
Unify_usage_ai_shield_manage_delay,
Fix_AI_shield_management_bug,
@@ -178,6 +184,13 @@ namespace AI {
Freespace_1_missile_behavior,
ETS_uses_power_output,
ETS_energy_same_regardless_of_system_presence,
Dont_form_on_wing_at_mission_start,
Do_not_clear_goals_when_running_form_on_wing,
Do_not_clear_goals_when_running_stay_still,
Do_not_set_override_when_assigning_form_on_wing,
Purge_player_issued_form_on_wing_after_subsequent_order,
Cancel_future_waves_of_any_wing_launched_from_an_exited_ship,
Fix_ignore_if_dead_flag,
NUM_VALUES
};
+48 -1
View File
@@ -42,6 +42,7 @@ void set_flag(ai_profile_t *profile, const char *name, AI::Profile_Flags flag)
const char *AI_path_types[] = {
"normal",
"alt1",
"alt2",
};
int Num_ai_path_types = sizeof(AI_path_types)/sizeof(char*);
@@ -477,6 +478,9 @@ void parse_ai_profiles_tbl(const char *filename)
set_flag(profile, "$support don't add primaries:", AI::Profile_Flags::Support_dont_add_primaries);
if (optional_string("$max allies rearming threshold:"))
stuff_int(&profile->max_allies_rearming_threshold);
set_flag(profile, "$firing requires exact los:", AI::Profile_Flags::Require_exact_los);
if (optional_string("$exact los minimum detection radius:")) {
@@ -588,6 +592,8 @@ void parse_ai_profiles_tbl(const char *filename)
stuff_float(&profile->better_collision_avoid_aggression_combat);
}
set_flag(profile, "+combat collision avoidance for fightercraft includes target:", AI::Profile_Flags::Better_combat_collision_avoid_includes_target);
set_flag(profile, "$better guard collision avoidance for fightercraft:", AI::Profile_Flags::Better_guard_collision_avoidance);
if (optional_string("+guard collision avoidance aggression for fightercraft:")) {
@@ -669,6 +675,8 @@ void parse_ai_profiles_tbl(const char *filename)
set_flag(profile, "$fix avoid-shockwave bugs:", AI::Profile_Flags::Fix_avoid_shockwave_bugs);
set_flag(profile, "$fix standard strafe:", AI::Profile_Flags::Fix_standard_strafe);
set_flag(profile, "$standard strafe used more:", AI::Profile_Flags::Standard_strafe_used_more);
if (optional_string("$standard strafe triggers under this speed:")) {
@@ -697,6 +705,10 @@ void parse_ai_profiles_tbl(const char *filename)
}
}
if (optional_string("$attack-any idle circle distance:")) {
stuff_float(&profile->attack_any_idle_circle_distance);
}
set_flag(profile, "$unify usage of AI Shield Manage Delay:", AI::Profile_Flags::Unify_usage_ai_shield_manage_delay);
set_flag(profile, "$fix AI shield management bug:", AI::Profile_Flags::Fix_AI_shield_management_bug);
@@ -711,6 +723,32 @@ void parse_ai_profiles_tbl(const char *filename)
set_flag(profile, "$ETS energy same regardless of system presence:", AI::Profile_Flags::ETS_energy_same_regardless_of_system_presence);
set_flag(profile, "$don't issue form-on-wing goals at mission start:", AI::Profile_Flags::Dont_form_on_wing_at_mission_start);
if (optional_string("$default form-on-wing priority:")) {
int priority;
stuff_int(&priority);
if (priority > 0) {
profile->default_form_on_wing_priority = priority;
} else {
mprintf(("Warning: $default form-on-wing priority: should be > 0 (read %d). Value will not be used.\n", priority));
}
}
set_flag(profile, "$do not clear goals when running form-on-wing:", AI::Profile_Flags::Do_not_clear_goals_when_running_form_on_wing);
set_flag(profile, "$do not clear goals when running stay-still:", AI::Profile_Flags::Do_not_clear_goals_when_running_stay_still);
set_flag(profile, "$do not set override when assigning form-on-wing:", AI::Profile_Flags::Do_not_set_override_when_assigning_form_on_wing);
set_flag(profile, "$purge player-issued form-on-wing after subsequent order:", AI::Profile_Flags::Purge_player_issued_form_on_wing_after_subsequent_order);
set_flag(profile, "$cancel future waves of any wing launched from an exited ship:", AI::Profile_Flags::Cancel_future_waves_of_any_wing_launched_from_an_exited_ship);
set_flag(profile, "$ships intercept mines:", AI::Profile_Flags::Ships_intercept_mines);
set_flag(profile, "$fix 'ignore if dead' flag:", AI::Profile_Flags::Fix_ignore_if_dead_flag);
// end of options ----------------------------------------
@@ -779,7 +817,7 @@ void ai_profiles_init()
Ai_profiles_initted = 1;
}
int ai_profile_lookup(char *name)
int ai_profile_lookup(const char *name)
{
for (int i = 0; i < Num_ai_profiles; i++)
if (!stricmp(name, Ai_profiles[i].profile_name))
@@ -803,6 +841,7 @@ void ai_profile_t::reset()
ai_range_aware_secondary_select_mode = AI_RANGE_AWARE_SEC_SEL_MODE_RETAIL;
turret_target_recheck_time = 2000.0f;
rot_fac_multiplier_ply_collisions = 0.0f;
max_allies_rearming_threshold = 2;
better_collision_avoid_aggression_combat = 3.5f;
better_collision_avoid_aggression_guard = 3.5f;
@@ -813,6 +852,9 @@ void ai_profile_t::reset()
guard_big_orbit_above_target_radius = 500.0f;
guard_big_orbit_max_speed_percent = 1.0f;
attack_any_idle_circle_distance = 100.0f;
default_form_on_wing_priority = 99; // as originally assigned in ai_add_goal_sub_sexp()
for (int i = 0; i < NUM_SKILL_LEVELS; ++i) {
max_incoming_asteroids[i] = 0;
@@ -927,5 +969,10 @@ void ai_profile_t::reset()
}
if (mod_supports_version(25, 0, 0)) {
flags.set(AI::Profile_Flags::Fix_avoid_shockwave_bugs);
flags.set(AI::Profile_Flags::Purge_player_issued_form_on_wing_after_subsequent_order);
flags.set(AI::Profile_Flags::Cancel_future_waves_of_any_wing_launched_from_an_exited_ship);
}
if (mod_supports_version(26, 0, 0)) {
flags.set(AI::Profile_Flags::Fix_ignore_if_dead_flag);
}
}
+10 -1
View File
@@ -17,6 +17,7 @@
// AI Path types
#define AI_PATH_MODE_NORMAL 0
#define AI_PATH_MODE_ALT1 1
#define AI_PATH_MODE_ALT2 2
#define AI_RANGE_AWARE_SEC_SEL_MODE_RETAIL 0
#define AI_RANGE_AWARE_SEC_SEL_MODE_AWARE 1
@@ -101,6 +102,9 @@ public:
float detail_distance_mult[MAX_DETAIL_VALUE + 1]; //MAX_DETAIL_VALUE really needs to be 4
// max number of allies allowed to be rearming at the same time
int max_allies_rearming_threshold;
// minimum radius for the line-of-sight (los) detection --wookieejedi
float los_min_detection_radius;
@@ -144,6 +148,11 @@ public:
float guard_big_orbit_above_target_radius; // Radius of guardee that triggers ai_big_guard()
float guard_big_orbit_max_speed_percent; // Max percent of forward speed that is used in ai_big_guard()
// AI attack any option --wookieejedi
float attack_any_idle_circle_distance; // Radius that AI circles around a point while waiting for new enemies in attack-any mode
int default_form_on_wing_priority; // the priority used if not specified in the sexp
void reset();
};
@@ -156,6 +165,6 @@ extern ai_profile_t Ai_profiles[MAX_AI_PROFILES];
void ai_profiles_init();
int ai_profile_lookup(char *name);
int ai_profile_lookup(const char *name);
#endif
+40 -19
View File
@@ -62,9 +62,9 @@
void ai_big_evade_ship();
void ai_big_chase_attack(ai_info *aip, ship_info *sip, vec3d *enemy_pos, float dist_to_enemy, int modelnum);
void ai_big_avoid_ship();
int ai_big_maybe_follow_subsys_path(int do_dot_check=1);
bool ai_big_maybe_follow_subsys_path(bool do_dot_check=true);
void ai_big_strafe_position();
static int ai_big_strafe_maybe_retreat(const vec3d *target_pos);
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);
@@ -146,6 +146,10 @@ done_1:
*attack_point = best_point;
//The following raycast tends to make up 10%(!) of total AI-frametime for basically no benefit, especially in the common case for turrets where the normal isn't even queried.
if (surface_normal == nullptr && Disable_expensive_turret_target_check)
return;
// Cast from attack_objp_pos to local_attack_pos and check for nearest collision.
// If no collision, cast to (0,0,0) [center of big ship]** [best_point initialized to 000]
@@ -347,10 +351,10 @@ void ai_big_subsys_path_cleanup(ai_info *aip)
}
// Maybe Pl_objp needs to follow a path to get in line-of-sight to a subsystem
// input: do_dot_check => default value 0, flag to indicate whether check should be done to ensure
// input: do_dot_check => default value true, flag to indicate whether check should be done to ensure
// subsystem is within certain field of view. We don't want to check fov when
// strafing, since ship is weaving to avoid turret fire
int ai_big_maybe_follow_subsys_path(int do_dot_check)
bool ai_big_maybe_follow_subsys_path(bool do_dot_check)
{
ai_info *aip;
float dot = 1.0f, min_dot;
@@ -864,7 +868,7 @@ void ai_big_chase_ct()
extern bool ai_select_secondary_weapon(object *objp, ship_weapon *swp, int priority1 = -1, int priority2 = -1);
extern float set_secondary_fire_delay(ai_info *aip, ship *shipp, weapon_info *swip, bool burst);
extern bool ai_choose_secondary_weapon(object *objp, ai_info *aip, object *en_objp);
extern int maybe_avoid_big_ship(object *objp, object *ignore_objp, ai_info *aip, vec3d *goal_point, float delta_time, float time_scale = 1.f);
extern bool maybe_avoid_big_ship(object *objp, object *ignore_objp, ai_info *aip, vec3d *goal_point, float delta_time, float time_scale = 1.f);
extern void maybe_cheat_fire_synaptic(object *objp);
@@ -1416,7 +1420,7 @@ void ai_big_attack_get_data(vec3d *enemy_pos, float *dist_to_enemy, float *dot_t
// check to see if Pl_objp has gotten too close to attacking point.. if so, break off by entering
// AIS_STRAFE_RETREAT
static int ai_big_strafe_maybe_retreat(const vec3d *target_pos)
static bool ai_big_strafe_maybe_retreat(const vec3d *target_pos)
{
ai_info *aip;
aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
@@ -1424,25 +1428,42 @@ static int ai_big_strafe_maybe_retreat(const vec3d *target_pos)
vec3d vec_to_target;
vm_vec_sub(&vec_to_target, target_pos, &Pl_objp->pos);
float dist_to_target, dist_normal_to_target, time_to_target;
dist_to_target = vm_vec_mag_quick(&vec_to_target);
if (vm_vec_mag_quick(&aip->big_attack_surface_normal) > 0.9) {
dist_normal_to_target = -vm_vec_dot(&vec_to_target, &aip->big_attack_surface_normal);
} else {
dist_normal_to_target = 0.2f * vm_vec_mag_quick(&vec_to_target);
}
dist_normal_to_target = MAX(0.2f*dist_to_target, dist_normal_to_target);
time_to_target = dist_normal_to_target / Pl_objp->phys_info.speed;
float dist_to_target = vm_vec_mag(&vec_to_target);
// add distance penalty for going too fast
float speed_to_dist_penalty = MAX(0.0f, (Pl_objp->phys_info.speed-50));
bool collide_time;
bool collide_distance;
if (The_mission.ai_profile->flags[AI::Profile_Flags::Fix_standard_strafe]) {
// check if ship facing target, as likely will only collide if facing
vec3d vec_to_tpos;
vm_vec_normalized_dir(&vec_to_tpos, target_pos, &Pl_objp->pos);
float dot = vm_vec_dot(&vec_to_tpos, &Pl_objp->orient.vec.fvec);
if (dot <= 0.0f) {
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);
}
} else {
float dist_normal_to_target;
if (vm_vec_mag(&aip->big_attack_surface_normal) > 0.9) {
dist_normal_to_target =
MAX(0.2f * dist_to_target, -vm_vec_dot(&vec_to_target, &aip->big_attack_surface_normal));
} 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);
}
//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))
// Inside 2 sec retreat, setting goal point to box point + strafe_retreat_box_dist
// If collision, use std collision resolution.
if ( !(aip->ai_flags[AI::AI_Flags::Kamikaze]) && ((aip->ai_flags[AI::AI_Flags::Target_collision]) || (time_to_target < STRAFE_RETREAT_COLLIDE_TIME) || (dist_normal_to_target < STRAFE_RETREAT_COLLIDE_DIST + speed_to_dist_penalty)) ) {
if ( !(aip->ai_flags[AI::AI_Flags::Kamikaze]) && ((aip->ai_flags[AI::AI_Flags::Target_collision]) || (collide_time) || (collide_distance)) ) {
if (aip->ai_flags[AI::AI_Flags::Target_collision]) {
// use standard collision resolution
aip->ai_flags.remove(AI::AI_Flags::Target_collision);
@@ -1486,7 +1507,7 @@ void ai_big_strafe_attack()
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing]) {
if (ai_new_maybe_reposition_attack_subsys())
return;
} else if (ai_big_maybe_follow_subsys_path(0))
} else if (ai_big_maybe_follow_subsys_path(false))
return;
ai_big_attack_get_data(&target_pos, &target_dist, &target_dot);
@@ -1606,7 +1627,7 @@ void ai_big_strafe_glide_attack()
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing]) {
if (ai_new_maybe_reposition_attack_subsys())
return;
} else if (ai_big_maybe_follow_subsys_path(0))
} else if (ai_big_maybe_follow_subsys_path(false))
return;
//Gets a point on the target ship to attack, as well as distance and the angle between the nose of the attacker and that point.
+305 -82
View File
@@ -223,9 +223,11 @@ ai_flag_description Ai_flag_descriptions[] = {
{AI::AI_Flags::No_dynamic, "Will stop allowing the AI to pursue dynamic goals (eg: chasing ships it was not ordered to)."},
{AI::AI_Flags::Free_afterburner_use, "Will allow AI to use afterburners when attacking a big ship, flying to a target position, guarding a ship, and flying in formation."},
{AI::AI_Flags::Waypoints_no_formation, "Ship will not form up with its wingmates while running waypoints with them." },
{AI::AI_Flags::Kamikaze, "Ship will attack big ships by colliding with them and exploding."},
};
extern const int Num_ai_flag_names = sizeof(Ai_flag_names) / sizeof(ai_flag_name);
extern const size_t Num_ai_flag_descriptions = sizeof(Ai_flag_descriptions) / sizeof(ai_flag_description);
const char *Skill_level_names(int level, int translate)
{
@@ -321,6 +323,18 @@ void ai_cleanup_dock_mode_objective(object *objp);
// the "autopilot"
object *Autopilot_flight_leader = NULL;
static inline float ai_guard_threshold(const object* guarded_objp, float threshold)
{
if (guarded_objp != nullptr && guarded_objp->type == OBJ_SHIP && guarded_objp->instance >= 0) {
const float configured = Ships[guarded_objp->instance].max_guard_radius;
if (configured > 0.0f) {
return configured;
}
}
return threshold;
}
/**
* Sets the timestamp used to tell is it is a good time for this team to rearm.
* Ends a 'bad rearm time'
@@ -542,6 +556,19 @@ void garbage_collect_path_points()
}
void reset_ai_path_points()
{
memset(Path_points, 0, sizeof(Path_points)); // optional: zero out for safety
Ppfp = Path_points;
// Also clear any AI path state
for (int i = 0; i < MAX_SHIPS; ++i) {
Ai_info[i].path_start = -1;
Ai_info[i].path_cur = -1;
Ai_info[i].path_length = 0;
}
}
/**
* Hash two values together, return result.
* Hash function: curval shifted right circular by one, newval xored in.
@@ -2216,6 +2243,7 @@ typedef struct eval_nearest_objnum {
object *trial_objp;
int enemy_team_mask;
int enemy_ship_info_index;
int enemy_class_type;
int enemy_wing;
float range;
int max_attackers;
@@ -2249,6 +2277,10 @@ void evaluate_object_as_nearest_objnum(eval_nearest_objnum *eno)
if ((eno->enemy_ship_info_index >= 0) && (shipp->ship_info_index != eno->enemy_ship_info_index))
return;
// If only supposed to attack ships of a certain ship type, don't attack other ships.
if ((eno->enemy_class_type >= 0) && (Ship_info[shipp->ship_info_index].class_type != eno->enemy_class_type))
return;
// Don't keep firing at a ship that is in its death throes.
if (shipp->flags[Ship::Ship_Flags::Dying])
return;
@@ -2337,8 +2369,9 @@ void evaluate_object_as_nearest_objnum(eval_nearest_objnum *eno)
* @param range Ship must be within range "range".
* @param max_attackers Don't attack a ship that already has at least max_attackers attacking it.
* @param ship_info_index If >=0, the enemy object must be of the specified ship class
* @param class_type If >=0, the enemy object must be of the specified ship type
*/
int get_nearest_objnum(int objnum, int enemy_team_mask, int enemy_wing, float range, int max_attackers, int ship_info_index)
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)
{
object *danger_weapon_objp;
ai_info *aip;
@@ -2348,6 +2381,7 @@ int get_nearest_objnum(int objnum, int enemy_team_mask, int enemy_wing, float ra
eval_nearest_objnum eno;
eno.enemy_team_mask = enemy_team_mask;
eno.enemy_ship_info_index = ship_info_index;
eno.enemy_class_type = class_type;
eno.enemy_wing = enemy_wing;
eno.max_attackers = max_attackers;
eno.objnum = objnum;
@@ -2393,7 +2427,7 @@ int get_nearest_objnum(int objnum, int enemy_team_mask, int enemy_wing, float ra
// If only looking for target in certain wing and couldn't find anything in
// that wing, look for any object.
if ((eno.nearest_objnum == -1) && (enemy_wing != -1)) {
return get_nearest_objnum(objnum, enemy_team_mask, -1, range, max_attackers, ship_info_index);
return get_nearest_objnum(objnum, enemy_team_mask, -1, range, max_attackers, ship_info_index, class_type);
}
return eno.nearest_objnum;
@@ -2497,13 +2531,15 @@ int get_enemy_timestamp()
/**
* Return objnum if enemy found, else return -1;
*
* @param objnum Object number
* @param range Range within which to look
* @param max_attackers Don't attack a ship that already has at least max_attackers attacking it.
* @param objnum Object number
* @param range Range within which to look
* @param max_attackers Don't attack a ship that already has at least max_attackers attacking it.
* @param ship_info_index If specified, restrict the search to enemies with this ship class
* @param class_type If specified, restrict the search to enemies with this ship type
*/
int find_enemy(int objnum, float range, int max_attackers, int ship_info_index)
int find_enemy(int objnum, float range, int max_attackers, int ship_info_index, int class_type)
{
int enemy_team_mask;
int enemy_team_mask;
if (objnum < 0)
return -1;
@@ -2511,19 +2547,25 @@ int find_enemy(int objnum, float range, int max_attackers, int ship_info_index)
enemy_team_mask = iff_get_attackee_mask(obj_team(&Objects[objnum]));
// if target_objnum != -1, use that as goal.
ai_info *aip = &Ai_info[Ships[Objects[objnum].instance].ai_index];
ai_info* aip = &Ai_info[Ships[Objects[objnum].instance].ai_index];
if (timestamp_elapsed(aip->choose_enemy_timestamp)) {
aip->choose_enemy_timestamp = timestamp(get_enemy_timestamp());
if (aip->target_objnum != -1) {
int target_objnum = aip->target_objnum;
int target_objnum = aip->target_objnum;
// DKA don't undo object as target in nebula missions.
// This could cause attack on ship on fringe on nebula to stop if attackee moves our of nebula range. (BAD)
if ( Objects[target_objnum].signature == aip->target_signature ) {
if (iff_matches_mask(Ships[Objects[target_objnum].instance].team, enemy_team_mask)) {
if (ship_info_index < 0 || ship_info_index == Ships[Objects[target_objnum].instance].ship_info_index) {
if (!(Objects[target_objnum].flags[Object::Object_Flags::Protected])) {
return target_objnum;
// This could cause attack on ship on fringe on nebula to stop if attackee moves out of nebula range. (BAD)
if (Objects[target_objnum].signature == aip->target_signature) {
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 (ship_info_index < 0 || ship_info_index == target_shipp->ship_info_index) {
if (class_type < 0 || (target_shipp->ship_info_index >= 0 &&
class_type == Ship_info[target_shipp->ship_info_index].class_type)) {
if (!(Objects[target_objnum].flags[Object::Object_Flags::Protected])) {
return target_objnum;
}
}
}
}
@@ -2532,9 +2574,8 @@ int find_enemy(int objnum, float range, int max_attackers, int ship_info_index)
aip->target_signature = -1;
}
}
return get_nearest_objnum(objnum, enemy_team_mask, aip->enemy_wing, range, max_attackers, ship_info_index);
return get_nearest_objnum(objnum, enemy_team_mask, aip->enemy_wing, range, max_attackers, ship_info_index, class_type);
} else {
aip->target_objnum = -1;
aip->target_signature = -1;
@@ -2575,7 +2616,7 @@ void force_avoid_player_check(object *objp, ai_info *aip)
* If attacked == NULL, then attack any enemy object.
* Attack point *rel_pos on object. This is for supporting attacking subsystems.
*/
void ai_attack_object(object* attacker, object* attacked, int ship_info_index)
void ai_attack_object(object* attacker, object* attacked, int ship_info_index, int class_type)
{
int temp;
ai_info* aip;
@@ -2608,7 +2649,7 @@ void ai_attack_object(object* attacker, object* attacked, int ship_info_index)
if (attacked == nullptr) {
aip->choose_enemy_timestamp = timestamp(0);
// nebula safe
set_target_objnum(aip, find_enemy(OBJ_INDEX(attacker), 99999.9f, 4, ship_info_index));
set_target_objnum(aip, find_enemy(OBJ_INDEX(attacker), 99999.9f, 4, ship_info_index, class_type));
} else {
// check if we can see attacked in nebula
if (aip->target_objnum != OBJ_INDEX(attacked)) {
@@ -3650,6 +3691,14 @@ void ai_form_on_wing(object *objp, object *goal_objp)
aip->ai_flags.remove(AI::AI_Flags::Formation_wing);
aip->ai_flags.set(AI::AI_Flags::Formation_object);
// if this goal is going to stick around for a while, change the mode
if (The_mission.ai_profile->flags[AI::Profile_Flags::Do_not_clear_goals_when_running_form_on_wing])
{
aip->mode = AIM_NONE;
aip->submode = -1;
aip->submode_start_time = Missiontime;
}
aip->goal_objnum = OBJ_INDEX(goal_objp);
ai_formation_object_recalculate_slotnums(aip->goal_objnum);
@@ -4258,7 +4307,7 @@ float ai_path_0()
// --------------------------------------------------------------------------
// Alternate version of ai_path
// 1.
float ai_path_1()
float ai_path_1_or_2(bool allow_path_shortcut)
{
int num_points;
float dot, dist_to_goal, dist_to_next, dot_to_next;
@@ -4313,6 +4362,25 @@ float ai_path_1()
vm_vec_scale_add(&next_vec, cvp, &delvec, 10.0f);
nvp = &next_vec;
}
if (allow_path_shortcut) {
// See if can reach next point (as opposed to current point)
// However, don't do this if docking and next point is last point.
if ((aip->mode != AIM_DOCK) || ((aip->path_cur - aip->path_start) < num_points - 2)) {
if ((aip->path_cur + aip->path_dir > aip->path_start) &&
(aip->path_cur + aip->path_dir < aip->path_start + num_points - 2)) {
if (timestamp_elapsed(aip->path_next_check_time)) {
aip->path_next_check_time = timestamp(3000);
if (!pp_collide(&Pl_objp->pos, nvp, gobjp, 1.1f * Pl_objp->radius)) {
cvp = nvp;
aip->path_cur += aip->path_dir;
nvp = &Path_points[aip->path_cur].pos;
}
}
}
}
}
// Set pvp to the previous vertex of interest on the path (if there is one)
int prev_point = aip->path_cur - aip->path_dir;
if (prev_point >= aip->path_start && prev_point <= aip->path_start + num_points)
@@ -4429,7 +4497,10 @@ float ai_path()
return ai_path_0();
break;
case AI_PATH_MODE_ALT1:
return ai_path_1();
return ai_path_1_or_2(false);
break;
case AI_PATH_MODE_ALT2:
return ai_path_1_or_2(true);
break;
default:
Error(LOCATION, "Invalid path mode found: %d\n", The_mission.ai_profile->ai_path_mode);
@@ -5080,9 +5151,10 @@ int maybe_resume_previous_mode(object *objp, ai_info *aip)
// If guarding ship is far away from guardee and enemy is far away from guardee,
// then stop chasing and resume guarding.
if (dist > (MAX_GUARD_DIST + guard_objp->radius) * 6) {
if (dist > ai_guard_threshold(guard_objp, (MAX_GUARD_DIST + guard_objp->radius) * 6))
{
if ((En_objp != NULL) && (En_objp->type == OBJ_SHIP)) {
if (vm_vec_dist_quick(&guard_objp->pos, &En_objp->pos) > (MAX_GUARD_DIST + guard_objp->radius) * 6) {
if (vm_vec_dist_quick(&guard_objp->pos, &En_objp->pos) > ai_guard_threshold(guard_objp, (MAX_GUARD_DIST + guard_objp->radius) * 6)) {
Assert(aip->previous_mode == AIM_GUARD);
aip->mode = aip->previous_mode;
aip->submode = AIS_GUARD_PATROL;
@@ -6434,6 +6506,11 @@ bool ai_select_secondary_weapon(object *objp, ship_weapon *swp, flagset<Weapon::
ignore_mask.set(Weapon::Info_Flags::Bomber_plus);
}
// Ignore mines in normal combat... they must be explicitly prioritized via SEXP
if (!(prio1[Weapon::Info_Flags::Mine] || prio2[Weapon::Info_Flags::Mine])) {
ignore_mask.set(Weapon::Info_Flags::Mine);
}
#ifndef NDEBUG
for (i=0; i<MAX_WEAPON_TYPES; i++) {
weapon_id_list[i] = -1;
@@ -7397,6 +7474,21 @@ void attack_set_accel(ai_info *aip, ship_info *sip, float dist_to_enemy, float d
if (wip != nullptr && wip->optimum_range > 0)
optimal_range = wip->optimum_range;
// Standoff for mines: keep at least 1.2x the target mine's proximity radius
// so we shoot it from outside its detonation zone.
if (En_objp->type == OBJ_WEAPON) {
weapon_info *target_wip = &Weapon_info[Weapons[En_objp->instance].weapon_info_index];
if (target_wip->is_mine() && target_wip->proximity_radius > 0.0f) {
float standoff = target_wip->proximity_radius * 1.2f;
// Don't stand off farther than our weapon can actually reach, or we'd hold
// position outside firing range and never destroy the mine. If the weapon
// can't outrange the detonation zone, close in and accept the risk.
if (wip != nullptr)
standoff = MIN(standoff, MIN(wip->max_speed * wip->lifetime, wip->weapon_range));
optimal_range = MAX(optimal_range, standoff);
}
}
if (dist_to_enemy > optimal_range + vm_vec_mag_quick(&En_objp->phys_info.vel) * dot_from_enemy + Pl_objp->phys_info.speed * speed_ratio) {
if (dist_to_enemy > optimal_range + 600.0f) {
if (ai_willing_to_afterburn_hard(aip)) {
@@ -7665,7 +7757,7 @@ void mabs_pick_goal_point(object *objp, object *big_objp, vec3d *collision_point
/**
* Return true if a large ship is being ignored.
*/
int maybe_avoid_big_ship(object *objp, object *ignore_objp, ai_info *aip, vec3d *goal_point, float delta_time, float time_scale = 1.f)
bool maybe_avoid_big_ship(object *objp, object *ignore_objp, ai_info *aip, vec3d *goal_point, float delta_time, float time_scale = 1.f)
{
if (timestamp_elapsed(aip->avoid_check_timestamp)) {
float distance;
@@ -7684,7 +7776,7 @@ int maybe_avoid_big_ship(object *objp, object *ignore_objp, ai_info *aip, vec3d
aip->ai_flags.remove(AI::AI_Flags::Avoiding_small_ship);
aip->avoid_ship_num = -1;
next_check_time = (int) (1500 * time_scale);
aip->avoid_check_timestamp = timestamp(1500);
aip->avoid_check_timestamp = timestamp(next_check_time);
}
}
@@ -7700,10 +7792,30 @@ int maybe_avoid_big_ship(object *objp, object *ignore_objp, ai_info *aip, vec3d
if (ai_willing_to_afterburn_hard(aip) && dot > 0.99f)
ai_afterburn_hard(Pl_objp, aip);
return 1;
return true;
}
return 0;
return false;
}
/**
* Return true if small ship and it will likely collide with large ship
* developed by Asteroth
*/
bool better_collision_avoidance_triggered(bool flag_to_check, float avoidance_aggression, object* pl_objp, object* ignore_objp)
{
ship* shipp = &Ships[pl_objp->instance];
ship_info* sip = &Ship_info[shipp->ship_info_index];
if ((flag_to_check) && sip->is_small_ship()) {
vec3d collide_vec = pl_objp->phys_info.vel * (avoidance_aggression / (PI2 / sip->srotation_time));
float radius_contribution = (pl_objp->phys_info.speed + pl_objp->radius) / pl_objp->phys_info.speed;
collide_vec *= radius_contribution;
collide_vec += pl_objp->pos;
return (maybe_avoid_big_ship(pl_objp, ignore_objp, &Ai_info[shipp->ai_index], &collide_vec, 0.f, 0.1f));
}
return false;
}
/**
@@ -8928,14 +9040,12 @@ void ai_chase()
return;
}
if ((The_mission.ai_profile->flags[AI::Profile_Flags::Better_combat_collision_avoidance]) && sip->is_small_ship()) {
vec3d collide_vec = Pl_objp->phys_info.vel * (The_mission.ai_profile->better_collision_avoid_aggression_combat / (PI2 / sip->srotation_time));
float radius_contribution = (Pl_objp->phys_info.speed + Pl_objp->radius) / Pl_objp->phys_info.speed;
collide_vec *= radius_contribution;
collide_vec += Pl_objp->pos;
if (maybe_avoid_big_ship(Pl_objp, En_objp, aip, &collide_vec, 0.f, 0.1f))
return;
if (better_collision_avoidance_triggered(
The_mission.ai_profile->flags[AI::Profile_Flags::Better_combat_collision_avoidance],
The_mission.ai_profile->better_collision_avoid_aggression_combat,
Pl_objp,
The_mission.ai_profile->flags[AI::Profile_Flags::Better_combat_collision_avoid_includes_target] ? nullptr : En_objp)) {
return;
}
if (enemy_sip_flags.any_set()) {
@@ -10429,10 +10539,65 @@ void maybe_update_guard_object(object *hit_objp, object *hitter_objp)
}
}
// Scan missile list looking for bombs homing on guarded_objp
// return 1 if bomb is found (and targeted by guarding_objp), otherwise return 0
// Find the closest hostile mine threatening 'against_objp', ranked by distance from 'from_objp'.
// If 'against_objp' is null, defaults to 'from_objp' (i.e. find mines threatening the searcher itself).
// imminent_only: also requires the mine to be within proximity_radius * 1.5 of against_objp;
// used to preempt a current target when the mine is about to detonate.
static object *ai_find_nearby_mine_threat(object *from_objp, object *against_objp = nullptr, bool imminent_only = false)
{
if (against_objp == nullptr)
against_objp = from_objp;
object *closest_mine = nullptr;
float closest_dist_from = std::numeric_limits<float>::max();
for (const auto *mo : list_range(&Missile_obj_list)) {
Assert(mo->objnum >= 0 && mo->objnum < MAX_OBJECTS);
object *mine_objp = &Objects[mo->objnum];
if (mine_objp->flags[Object::Object_Flags::Should_be_dead])
continue;
weapon *wp = &Weapons[mine_objp->instance];
weapon_info *wip = &Weapon_info[wp->weapon_info_index];
if (!wip->is_mine())
continue;
// Only engage destructible mines
if (!wip->wi_flags[Weapon::Info_Flags::Fighter_Interceptable])
continue;
// Only engage mines hostile to the threatened ship
if (!iff_x_attacks_y(wp->team, obj_team(against_objp)))
continue;
float dist_against = vm_vec_dist(&mine_objp->pos, &against_objp->pos);
// Mine must be within its own targetable range of the threatened ship
if (dist_against > wip->mine_targetable_range)
continue;
// Imminent gate: only consider mines about to detonate on the threatened ship.
// proximity_radius is always > 0 for mines (parser enforces).
if (imminent_only && dist_against > wip->proximity_radius * 1.5f)
continue;
float dist_from = (from_objp == against_objp) ? dist_against : vm_vec_dist(&mine_objp->pos, &from_objp->pos);
if (dist_from < closest_dist_from) {
closest_dist_from = dist_from;
closest_mine = mine_objp;
}
}
return closest_mine;
}
// Scan the missile list for a threat to guarded_objp and, if one is found, target it with guarding_objp.
// Bombs homing on guarded_objp take priority; if none are found, fall back to the closest hostile mine
// threatening guarded_objp (mines are stationary so they can't home, hence the lower priority).
// Returns 1 if a threat was found and targeted, otherwise 0.
int ai_guard_find_nearby_bomb(object *guarding_objp, object *guarded_objp)
{
{
missile_obj *mo;
object *bomb_objp, *closest_bomb_objp=NULL;
float dist, dist_to_guarding_obj,closest_dist_to_guarding_obj=999999.0f;
@@ -10462,7 +10627,7 @@ int ai_guard_find_nearby_bomb(object *guarding_objp, object *guarded_objp)
dist = vm_vec_dist_quick(&bomb_objp->pos, &guarded_objp->pos);
if (dist < (MAX_GUARD_DIST + guarded_objp->radius)*3) {
if (dist < ai_guard_threshold(guarded_objp, (MAX_GUARD_DIST + guarded_objp->radius) * 3)) {
dist_to_guarding_obj = vm_vec_dist_quick(&bomb_objp->pos, &guarding_objp->pos);
if ( dist_to_guarding_obj < closest_dist_to_guarding_obj ) {
closest_dist_to_guarding_obj = dist_to_guarding_obj;
@@ -10476,6 +10641,13 @@ int ai_guard_find_nearby_bomb(object *guarding_objp, object *guarded_objp)
return 1;
}
// No homing threat found... check for mines threatening the guarded ship.
// Mines are stationary so they won't home; handle them as a lower priority threat.
if (object *closest_mine_objp = ai_find_nearby_mine_threat(guarding_objp, guarded_objp)) {
guard_object_was_hit(guarding_objp, closest_mine_objp);
return 1;
}
return 0;
}
@@ -10506,11 +10678,11 @@ void ai_guard_find_nearby_ship(object *guarding_objp, object *guarded_objp)
if (Ship_info[eshipp->ship_info_index].class_type >= 0 && (Ship_types[Ship_info[eshipp->ship_info_index].class_type].flags[Ship::Type_Info_Flags::AI_guards_attack]))
{
dist = vm_vec_dist_quick(&enemy_objp->pos, &guarded_objp->pos);
if (dist < (MAX_GUARD_DIST + guarded_objp->radius)*3)
if (dist < ai_guard_threshold(guarded_objp, (MAX_GUARD_DIST + guarded_objp->radius) * 3))
{
guard_object_was_hit(guarding_objp, enemy_objp);
}
else if ((dist < 3000.0f) && (Ai_info[eshipp->ai_index].target_objnum == guarding_aip->guard_objnum))
} else if ((dist < ai_guard_threshold(guarded_objp, 3000.0f)) &&
(Ai_info[eshipp->ai_index].target_objnum == guarding_aip->guard_objnum))
{
guard_object_was_hit(guarding_objp, enemy_objp);
}
@@ -10537,7 +10709,7 @@ void ai_guard_find_nearby_asteroid(object *guarding_objp, object *guarded_objp)
if ( asteroid_objp->type == OBJ_ASTEROID ) {
// Attack asteroid if near guarded ship
dist = vm_vec_dist_quick(&asteroid_objp->pos, &guarded_objp->pos);
if ( dist < (MAX_GUARD_DIST + guarded_objp->radius)*2) {
if (dist < ai_guard_threshold(guarded_objp, (MAX_GUARD_DIST + guarded_objp->radius) * 2)) {
dist_to_self = vm_vec_dist_quick(&asteroid_objp->pos, &guarding_objp->pos);
if ( OBJ_INDEX(guarded_objp) == asteroid_collide_objnum(asteroid_objp) ) {
if( dist_to_self < closest_danger_asteroid_dist ) {
@@ -10586,6 +10758,13 @@ void ai_guard_find_nearby_object()
if ( (aip->target_objnum == -1) && asteroid_count() ) {
ai_guard_find_nearby_asteroid(Pl_objp, guardobjp);
}
// if still not attacking anything and flag is set, engage mines near the guarding ship itself
if (aip->target_objnum == -1 && The_mission.ai_profile->flags[AI::Profile_Flags::Ships_intercept_mines]) {
object *mine_objp = ai_find_nearby_mine_threat(Pl_objp);
if (mine_objp)
guard_object_was_hit(Pl_objp, mine_objp);
}
}
}
@@ -10849,16 +11028,12 @@ void ai_guard()
return;
}
ship_info* sip = &Ship_info[shipp->ship_info_index];
if ((The_mission.ai_profile->flags[AI::Profile_Flags::Better_guard_collision_avoidance]) && sip->is_small_ship()) {
vec3d collide_vec = Pl_objp->phys_info.vel * (The_mission.ai_profile->better_collision_avoid_aggression_guard / (PI2 / sip->srotation_time));
float radius_contribution = (Pl_objp->phys_info.speed + Pl_objp->radius) / Pl_objp->phys_info.speed;
collide_vec *= radius_contribution;
collide_vec += Pl_objp->pos;
if (maybe_avoid_big_ship(Pl_objp, En_objp, aip, &collide_vec, 0.f, 0.1f))
return;
if (better_collision_avoidance_triggered(
The_mission.ai_profile->flags[AI::Profile_Flags::Better_guard_collision_avoidance],
The_mission.ai_profile->better_collision_avoid_aggression_guard,
Pl_objp,
En_objp)) {
return;
}
// handler for guard object with BIG radius
@@ -11074,6 +11249,12 @@ void ai_do_objects_repairing_stuff( object *repaired_objp, object *repair_objp,
aip->ai_flags.remove(AI::AI_Flags::Awaiting_repair);
stamp = timestamp(-1);
if (repair_objp != nullptr && scripting::hooks::OnSupportRearmStarted->isActive()) {
scripting::hooks::OnSupportRearmStarted->run(
scripting::hook_param_list(scripting::hook_param("Support Ship", 'o', repair_objp),
scripting::hook_param("Target Ship", 'o', repaired_objp)));
}
// if this is a player ship, then subtract the repair penalty from this player's score
if ( repaired_objp->flags[Object::Object_Flags::Player_ship] ) {
if ( !(Game_mode & GM_MULTIPLAYER) ) {
@@ -11146,6 +11327,12 @@ void ai_do_objects_repairing_stuff( object *repaired_objp, object *repair_objp,
mission_log_add_entry(LOG_PLAYER_ABORTED_REARM, Ships[repaired_objp->instance].ship_name, NULL);
}
if (repair_objp != nullptr && scripting::hooks::OnSupportRearmFinished->isActive()) {
scripting::hooks::OnSupportRearmFinished->run(
scripting::hook_param_list(scripting::hook_param("Support Ship", 'o', repair_objp),
scripting::hook_param("Target Ship", 'o', repaired_objp)));
}
stamp = timestamp((int) ((30 + 10*frand()) * 1000));
break;
@@ -11476,7 +11663,7 @@ void ai_stay_near()
else if (aip->submode_parm0 != 0) {
vec3d rand_vec, center_to_goal, center_to_objp;
auto goal_objp = &Objects[goal_objnum];
float max_dist = aip->submode_float0;
float max_dist = std::max(aip->submode_float0, 1.0f);
// Figure out a goal point to fly to
// Make not all ships pursue same point.
@@ -11507,7 +11694,7 @@ void ai_stay_near()
else {
vec3d rand_vec, goal_pos, vec_to_goal;
auto goal_objp = &Objects[goal_objnum];
float max_dist = aip->submode_float0;
float max_dist = std::max(aip->submode_float0, 1.0f);
// Make not all ships pursue same point.
// Calculate the seed from both shipnums so the same Pl_objp doesn't always choose the same spot
@@ -12100,9 +12287,11 @@ void ai_process_subobjects(int objnum)
bool in_lab = gameseq_get_state() == GS_STATE_LAB;
// non-player ships that are playing dead do not process subsystems or turrets
if ((!(objp->flags[Object::Object_Flags::Player_ship]) || Player_use_ai) && aip->mode == AIM_PLAY_DEAD)
return;
// non-player ships that are playing dead do not process subsystems or turrets unless we're in the lab
if (gameseq_get_state() != GS_STATE_LAB) {
if ((!(objp->flags[Object::Object_Flags::Player_ship]) || Player_use_ai) && aip->mode == AIM_PLAY_DEAD)
return;
}
polymodel_instance *pmi = model_get_instance(shipp->model_instance_num);
polymodel *pm = model_get(pmi->model_num);
@@ -13203,8 +13392,6 @@ static void ai_preprocess_ignore_objnum(ai_info *aip)
aip->target_objnum = -1;
}
#define CHASE_CIRCLE_DIST 100.0f
void ai_chase_circle(object *objp)
{
float dist_to_goal;
@@ -13225,11 +13412,11 @@ void ai_chase_circle(object *objp)
if (aip->ignore_objnum == UNUSED_OBJNUM) {
dist_to_goal = vm_vec_dist_quick(&aip->goal_point, &objp->pos);
if (dist_to_goal > 2*CHASE_CIRCLE_DIST) {
if (dist_to_goal > 2 * The_mission.ai_profile->attack_any_idle_circle_distance) {
vec3d vec_to_goal;
// Too far from circle goal, create a new goal point.
vm_vec_normalized_dir(&vec_to_goal, &aip->goal_point, &objp->pos);
vm_vec_scale_add(&aip->goal_point, &objp->pos, &vec_to_goal, CHASE_CIRCLE_DIST);
vm_vec_scale_add(&aip->goal_point, &objp->pos, &vec_to_goal, The_mission.ai_profile->attack_any_idle_circle_distance);
}
goal_point = aip->goal_point;
@@ -13656,7 +13843,7 @@ int ai_acquire_emerge_path(object *pl_objp, int parent_objnum, int allowed_path_
ship_info* parent_sip = &Ship_info[parent_shipp->ship_info_index];
polymodel *pm = model_get( parent_sip->model_num );
ship_bay *bay = pm->ship_bay;
const auto& bay = pm->ship_bay;
if ( bay == nullptr ) {
Warning(LOCATION, "Ship %s was set to arrive from fighter bay on object %s, but no fighter bay exists on that ships' model (%s).\n", shipp->ship_name, parent_shipp->ship_name, pm->filename);
@@ -13871,9 +14058,8 @@ int ai_find_closest_depart_path(ai_info *aip, polymodel *pm, int allowed_path_ma
float dist, min_dist, min_free_dist;
vec3d *source;
model_path *mp;
ship_bay *bay;
bay = pm->ship_bay;
const auto& bay = pm->ship_bay;
best_free_path = best_path = -1;
min_free_dist = min_dist = 1e20f;
@@ -13950,9 +14136,9 @@ int ai_acquire_depart_path(object *pl_objp, int parent_objnum, int allowed_path_
object *parent_objp = &Objects[parent_objnum];
polymodel *pm = model_get(Ship_info[Ships[parent_objp->instance].ship_info_index].model_num );
ship_bay *bay = pm->ship_bay;
const auto& bay = pm->ship_bay;
if ( bay == NULL )
if ( bay == nullptr )
return -1;
if ( bay->num_paths <= 0 )
return -1;
@@ -14007,20 +14193,28 @@ void ai_bay_depart()
return;
}
ship* shipp;
// check if parent ship valid; if not, abort depart
auto anchor_ship_entry = ship_registry_get(Parse_names[Ships[Pl_objp->instance].departure_anchor]);
if (!anchor_ship_entry || !ship_useful_for_departure(anchor_ship_entry->shipnum, Ships[Pl_objp->instance].departure_path_mask))
{
mprintf(("Aborting bay departure!\n"));
aip->mode = AIM_NONE;
Ships[Pl_objp->instance].flags.remove(Ship::Ship_Flags::Depart_dockbay);
return;
if (gameseq_get_state() != GS_STATE_LAB) {
auto anchor_ship_entry = ship_registry_get(Ships[Pl_objp->instance].departure_anchor);
if (!anchor_ship_entry ||
!ship_useful_for_departure(anchor_ship_entry->shipnum, Ships[Pl_objp->instance].departure_path_mask)) {
mprintf(("Aborting bay departure!\n"));
aip->mode = AIM_NONE;
Ships[Pl_objp->instance].flags.remove(Ship::Ship_Flags::Depart_dockbay);
return;
}
shipp = anchor_ship_entry->shipp();
} else {
shipp = &Ships[Objects[aip->goal_objnum].instance];
}
ai_manage_bay_doors(Pl_objp, aip, false);
if ( anchor_ship_entry->shipp()->bay_doors_status != MA_POS_READY )
if ( shipp->bay_doors_status != MA_POS_READY )
return;
// follow the path to the final point
@@ -14035,11 +14229,10 @@ void ai_bay_depart()
// Volition bay code
polymodel *pm;
ship_bay *bay;
pm = model_get(Ship_info[Ships[Objects[aip->goal_objnum].instance].ship_info_index].model_num);
bay = pm->ship_bay;
if ( bay != NULL ) {
const auto& bay = pm->ship_bay;
if ( bay != nullptr ) {
bay->depart_flags &= ~(1<<aip->submode_parm0);
}
@@ -14140,7 +14333,13 @@ void ai_execute_behavior(ai_info *aip)
case AIM_STRAFE:
if (En_objp) {
Assert(En_objp->type == OBJ_SHIP);
ai_big_strafe(); // strafe a big ship
if (!(better_collision_avoidance_triggered(
The_mission.ai_profile->flags[AI::Profile_Flags::Better_combat_collision_avoidance],
The_mission.ai_profile->better_collision_avoid_aggression_combat,
Pl_objp,
The_mission.ai_profile->flags[AI::Profile_Flags::Better_combat_collision_avoid_includes_target] ? nullptr : En_objp))) {
ai_big_strafe(); // strafe a big ship
}
} else {
aip->mode = AIM_NONE;
}
@@ -14322,7 +14521,7 @@ int maybe_request_support(object *objp)
try_to_rearm = true;
} else if (ai_bad_time_to_rearm(objp)) {
try_to_rearm = false;
} else if (num_allies_rearming(objp) < 2) {
} else if (num_allies_rearming(objp) < The_mission.ai_profile->max_allies_rearming_threshold) {
if (desire >= 8) { // guarantees disabled will cause repair request
try_to_rearm = true;
} else if (desire >= 3) { // >= 3 means having a single subsystem fully blown will cause repair.
@@ -15207,6 +15406,22 @@ void ai_frame(int objnum)
ai_maybe_depart(Pl_objp);
// Imminent-mine override: even if we have a current target, swap to a mine
// that is about to detonate on us (within proximity_radius * 1.5).
// Skip departing ships (AIM_WARP_OUT/AIM_BAY_DEPART): ai_maybe_depart() ran just above and may
// have committed this ship to leaving, so we must not divert it back to a mine here.
if (The_mission.ai_profile->flags[AI::Profile_Flags::Ships_intercept_mines]
&& aip->mode != AIM_GUARD && aip->mode != AIM_EVADE_WEAPON
&& aip->mode != AIM_WARP_OUT && aip->mode != AIM_BAY_DEPART
&& Ship_info[shipp->ship_info_index].class_type > -1
&& Ship_types[Ship_info[shipp->ship_info_index].class_type].flags[Ship::Type_Info_Flags::AI_auto_attacks]) {
object *imminent_mine = ai_find_nearby_mine_threat(Pl_objp, nullptr, true);
if (imminent_mine != nullptr && (target_objnum < 0 || &Objects[target_objnum] != imminent_mine)) {
aip->aspect_locked_time = 0.0f;
target_objnum = set_target_objnum(aip, OBJ_INDEX(imminent_mine));
}
}
// Find an enemy if don't already have one.
En_objp = NULL;
if ( ai_need_new_target(Pl_objp, target_objnum) ) {
@@ -15226,6 +15441,14 @@ void ai_frame(int objnum)
{
En_objp = &Objects[target_objnum];
}
} else if (aip->mode != AIM_GUARD && The_mission.ai_profile->flags[AI::Profile_Flags::Ships_intercept_mines]) {
// No enemy found so check for nearby hostile mines within their targetable range
object *mine_objp = ai_find_nearby_mine_threat(Pl_objp);
if (mine_objp) {
target_objnum = set_target_objnum(aip, OBJ_INDEX(mine_objp));
if (target_objnum >= 0)
En_objp = &Objects[target_objnum];
}
}
}
}
+274 -116
View File
@@ -40,28 +40,43 @@ struct ship_registry_entry;
// dynamic goals are not handled here.
// defines for player issued goal priorities
#define PLAYER_PRIORITY_MIN 90
#define PLAYER_PRIORITY_SHIP 100
#define PLAYER_PRIORITY_WING 95
#define PLAYER_PRIORITY_SUPPORT_LOW 10
constexpr int PLAYER_PRIORITY_MIN = 90;
constexpr int PLAYER_PRIORITY_SHIP = 100;
constexpr int PLAYER_PRIORITY_WING = 95;
constexpr int PLAYER_PRIORITY_SUPPORT_LOW = 10;
#define MAX_GOAL_PRIORITY 200
constexpr int MAX_GOAL_PRIORITY = 200;
constexpr int MIN_GOAL_PRIORITY = 1;
// note: "purging" is not the same as "clearing" here
// purging: any goal that no longer makes sense (i.e. is "invalid") in light of a new goal is removed
// clearing: every goal is removed, including, ironically, the goal that caused the clearing
// define for which goals cause other goals to get purged
// Goober5000 - okay, this seems really stupid. If any ship in the mission is assigned a goal
// in PURGE_GOALS_ALL_SHIPS, *every* other ship will have certain goals purged. So I added
// PURGE_GOALS_ONE_SHIP for goals which should only purge other goals in the one ship.
// Goober5000 - note that the new disable and disarm goals (AI_GOAL_DISABLE_SHIP_TACTICAL and
// AI_GOAL_DISARM_SHIP_TACTICAL) do not purge ANY goals, not even the ones in the one ship
inline bool purge_goals_all_ships(ai_goal_mode ai_mode)
[[nodiscard]] bool causes_invalid_goal_purge_all_ships(ai_goal_mode ai_mode)
{
return ai_mode == AI_GOAL_IGNORE || ai_mode == AI_GOAL_DISABLE_SHIP || ai_mode == AI_GOAL_DISARM_SHIP;
}
inline bool purge_goals_one_ship(ai_goal_mode ai_mode)
[[nodiscard]] bool causes_invalid_goal_purge_one_ship(ai_goal_mode ai_mode)
{
return ai_mode == AI_GOAL_IGNORE_NEW;
}
// function for which goals cause other goals to be cleared -- see comments above on purging vs clearing
// NOTE: this function is only evaluated in ai_add_goal_sub_player() and ai_add_goal_sub_scripting(); for
// ai_add_goal_sub_sexp(), the goal clearing is integrated into the logic of the relevant sexp operators
[[nodiscard]] bool causes_goal_clearing(ai_goal_mode ai_mode)
{
return ((ai_mode == AI_GOAL_STAY_STILL && !The_mission.ai_profile->flags[AI::Profile_Flags::Do_not_clear_goals_when_running_stay_still])
|| (ai_mode == AI_GOAL_FORM_ON_WING && !The_mission.ai_profile->flags[AI::Profile_Flags::Do_not_clear_goals_when_running_form_on_wing])
|| (ai_mode == AI_GOAL_PLAY_DEAD));
}
// goals given from the player to other ships in the game are also handled in this
// code
@@ -100,6 +115,7 @@ ai_goal_list Ai_goal_names[] =
{ "Attack weapon", AI_GOAL_CHASE_WEAPON, 0 },
{ "Fly to ship", AI_GOAL_FLY_TO_SHIP, 0 },
{ "Attack ship class", AI_GOAL_CHASE_SHIP_CLASS, 0 },
{ "Attack ship type", AI_GOAL_CHASE_SHIP_TYPE, 0 },
};
int Num_ai_goals = sizeof(Ai_goal_names) / sizeof(ai_goal_list);
@@ -114,6 +130,7 @@ const char *Ai_goal_text(ai_goal_mode goal, int submode)
case AI_GOAL_CHASE:
case AI_GOAL_CHASE_WING:
case AI_GOAL_CHASE_SHIP_CLASS:
case AI_GOAL_CHASE_SHIP_TYPE:
return XSTR( "attack ", 474);
case AI_GOAL_DOCK:
return XSTR( "dock ", 475);
@@ -195,12 +212,15 @@ void ai_maybe_add_form_goal(wing* wingp)
return;
}
int j;
// we may simply not want the mission to do this
if (The_mission.ai_profile->flags[AI::Profile_Flags::Dont_form_on_wing_at_mission_start]) {
return;
}
// iterate through the ship_index list of this wing and check for orders. We will do
// this for all ships in the wing instead of on a wing only basis in case some ships
// in the wing actually have different orders than others
for (j = 0; j < wingp->current_count; j++) {
for (int j = 0; j < wingp->current_count; j++) {
ai_info* aip;
Assert(wingp->ship_index[j] != -1); // get Allender
@@ -212,7 +232,8 @@ void ai_maybe_add_form_goal(wing* wingp)
}
// need to add a form on my wing goal here. Ships are always forming on the player's wing.
// it is sufficient enough to check the first goal entry to see if it has a valid goal
// it is sufficient enough to check the first goal entry to see if it has a valid goal.
// Note: Although the player didn't explicitly order this, it is treated as a player-issued order
if (aip->goals[0].ai_mode == AI_GOAL_NONE) {
// Need to have a more specific target in multi, or they may end up trying to target standalone placeholder.
// So form on their team leader. In dogfight, all player-slot ai die, so just exclude.
@@ -513,13 +534,25 @@ void ai_goal_purge_invalid_goals( ai_goal *aigp, ai_goal *goal_list, ai_info *ai
if ( purge_goal->target_name == NULL )
continue;
// goals operating on ship classes are handled slightly differently
// goals operating on ship classes and ship types are handled slightly differently
if ( purge_ai_mode == AI_GOAL_CHASE_SHIP_CLASS ) {
// if the target of the purge goal is the same class of ship we are concerned about, then we have a match;
// if it is not, then we can continue (see standard ship check below)
if ( stricmp(purge_goal->target_name, Ship_info[Ships[ship_index].ship_info_index].name) != 0 )
continue;
}
else if (purge_ai_mode == AI_GOAL_CHASE_SHIP_TYPE) {
// Get the ship type of the ship we're concerned about
int ship_type = Ship_info[Ships[ship_index].ship_info_index].class_type;
// If the ship type is invalid, we can't match it, so continue
if (ship_type < 0)
continue;
// Check if the target name of the purge goal matches the ship type name
if (stricmp(purge_goal->target_name, Ship_types[ship_type].name) != 0)
continue;
}
// standard goals operating on either wings or ships
else {
// determine if the purge goal is acting either on the ship or the ship's wing.
@@ -588,8 +621,8 @@ void ai_goal_purge_all_invalid_goals(ai_goal *aigp)
int i;
ship_obj *sop;
// only purge goals if a new goal is one of the types in next statement
if (!purge_goals_all_ships(aigp->ai_mode))
// only purge goals if a new goal is one of the types that require purging
if (!causes_invalid_goal_purge_all_ships(aigp->ai_mode))
return;
for (sop = GET_FIRST(&Ship_obj_list); sop != END_OF_LIST(&Ship_obj_list); sop = GET_NEXT(sop))
@@ -732,12 +765,14 @@ void ai_goal_fixup_dockpoints(ai_info *aip, ai_goal *aigp)
// from the mission goals (i.e. those goals which come from events) in that we don't
// use sexpressions for goals from the player...so we enumerate all the parameters
void ai_add_goal_sub_player(ai_goal_type type, ai_goal_mode mode, int submode, const char *target_name, ai_goal *aigp, const ai_lua_parameters& lua_target )
void ai_add_goal_sub_player(ai_goal_type type, ai_goal_mode mode, int submode, const char *target_name, ai_goal *aigp, int int_data, float float_data, const ai_lua_parameters& lua_target )
{
Assert ( (type == ai_goal_type::PLAYER_WING) || (type == ai_goal_type::PLAYER_SHIP) );
ai_goal_reset(aigp, true, mode, submode, type);
aigp->int_data = int_data;
aigp->float_data = float_data;
aigp->lua_ai_target = lua_target;
if ( mode == AI_GOAL_WARP ) {
@@ -755,6 +790,18 @@ void ai_add_goal_sub_player(ai_goal_type type, ai_goal_mode mode, int submode, c
if ( target_name != NULL )
aigp->target_name = ai_get_goal_target_name( target_name, &aigp->target_name_index );
// set up the clear-goals flag for certain goals
if (causes_goal_clearing(mode))
aigp->flags.set(AI::Goal_Flags::Clear_all_goals_first);
// also set up the override, since it's no longer done automatically in ai_mission_goal_achievable
if (mode == AI_GOAL_FORM_ON_WING && !The_mission.ai_profile->flags[AI::Profile_Flags::Do_not_set_override_when_assigning_form_on_wing])
aigp->flags.set(AI::Goal_Flags::Want_override);
// generally, players want ships to form-on-wing only until they need to do something else
if (mode == AI_GOAL_FORM_ON_WING && The_mission.ai_profile->flags[AI::Profile_Flags::Purge_player_issued_form_on_wing_after_subsequent_order])
aigp->flags.set(AI::Goal_Flags::Purge_when_new_goal_added);
if (The_mission.ai_profile->flags[AI::Profile_Flags::Player_orders_afterburn_hard])
aigp->flags.set(AI::Goal_Flags::Afterburn_hard);
@@ -766,10 +813,6 @@ void ai_add_goal_sub_player(ai_goal_type type, ai_goal_mode mode, int submode, c
if ( (mode == AI_GOAL_STAY_NEAR_SHIP) || (mode == AI_GOAL_KEEP_SAFE_DISTANCE) )
aigp->priority = PLAYER_PRIORITY_SUPPORT_LOW;
// Goober5000 - same with form-on-wing, since it's a type of staying near
else if ( mode == AI_GOAL_FORM_ON_WING )
aigp->priority = PLAYER_PRIORITY_SUPPORT_LOW;
else if ( aigp->type == ai_goal_type::PLAYER_WING ) // NOLINT(readability-braces-around-statements)
aigp->priority = PLAYER_PRIORITY_WING; // player wing goals not as high as ship goals
else
@@ -782,19 +825,25 @@ void ai_add_goal_sub_player(ai_goal_type type, ai_goal_mode mode, int submode, c
// friendlies want to rearm at the same time. The support ship forgets what it's doing and flies
// off to repair somebody while still docked. I reproduced this with retail, so it's not a bug in
// my new docking code. :)
int ai_goal_find_empty_slot( ai_goal *goals, int active_goal )
// Note that this function is now where Purge_when_new_goal_added is checked to set the Purge flag.
int ai_goal_allocate_slot( ai_goal *goals, int active_goal )
{
int gindex, oldest_index;
int oldest_index = -1, first_empty_index = -1;
oldest_index = -1;
for ( gindex = 0; gindex < MAX_AI_GOALS; gindex++ )
for ( int gindex = 0; gindex < MAX_AI_GOALS; gindex++ )
{
// get the index for the first unused goal
if (goals[gindex].ai_mode == AI_GOAL_NONE)
return gindex;
{
if (first_empty_index < 0)
first_empty_index = gindex;
}
// if any goal needs to be purged when we add a goal, set the flag
else if (goals[gindex].flags[AI::Goal_Flags::Purge_when_new_goal_added])
goals[gindex].flags.set(AI::Goal_Flags::Purge);
// if this is the active goal, don't consider it for pre-emption!!
if (gindex == active_goal)
// if this is the active goal, and a real goal, don't consider it for pre-emption!!
if (gindex == active_goal && goals[gindex].ai_mode != AI_GOAL_NONE)
continue;
// store the index of the oldest goal
@@ -804,6 +853,10 @@ int ai_goal_find_empty_slot( ai_goal *goals, int active_goal )
oldest_index = gindex;
}
// try to use the first empty slot
if (first_empty_index >= 0)
return first_empty_index;
// if we didn't find an empty slot, use the oldest goal's slot
return oldest_index;
}
@@ -836,9 +889,26 @@ void ai_add_goal_sub_scripting(ai_goal_type type, ai_goal_mode mode, int submode
if ( target_name != NULL )
aigp->target_name = ai_get_goal_target_name( target_name, &aigp->target_name_index );
// set up the clear-goals flag for certain goals
if (causes_goal_clearing(mode))
aigp->flags.set(AI::Goal_Flags::Clear_all_goals_first);
// also set up the override, since it's no longer done automatically in ai_mission_goal_achievable
if (mode == AI_GOAL_FORM_ON_WING && !The_mission.ai_profile->flags[AI::Profile_Flags::Do_not_set_override_when_assigning_form_on_wing])
aigp->flags.set(AI::Goal_Flags::Want_override);
aigp->priority = priority;
aigp->int_data = int_data;
aigp->float_data = float_data;
// range check
if ( aigp->priority > MAX_GOAL_PRIORITY ) {
nprintf (("AI", "bashing scripting priority of goal %d from %d to %d.\n", mode, aigp->priority, MAX_GOAL_PRIORITY));
aigp->priority = MAX_GOAL_PRIORITY;
} else if ( aigp->priority < MIN_GOAL_PRIORITY ) {
nprintf (("AI", "bashing scripting priority of goal %d from %d to %d.\n", mode, aigp->priority, MIN_GOAL_PRIORITY));
aigp->priority = MIN_GOAL_PRIORITY;
}
}
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)
@@ -846,7 +916,7 @@ void ai_add_ship_goal_scripting(ai_goal_mode mode, int submode, int priority, co
int empty_index;
ai_goal *aigp;
empty_index = ai_goal_find_empty_slot(aip->goals, aip->active_goal);
empty_index = ai_goal_allocate_slot(aip->goals, aip->active_goal);
aigp = &aip->goals[empty_index];
ai_add_goal_sub_scripting(ai_goal_type::PLAYER_SHIP, mode, submode, priority, shipname, aigp, int_data, float_data);
@@ -865,14 +935,14 @@ void ai_add_ship_goal_scripting(ai_goal_mode mode, int submode, int priority, co
// is issued to ship or wing (from player), mode is AI_GOAL_*. submode is the submode the
// ship should go into. shipname is the object of the action. aip is the ai_info pointer
// of the ship receiving the order
void ai_add_ship_goal_player(ai_goal_type type, ai_goal_mode mode, int submode, const char *shipname, ai_info *aip, const ai_lua_parameters& lua_target)
void ai_add_ship_goal_player(ai_goal_type type, ai_goal_mode mode, int submode, const char *shipname, ai_info *aip, int int_data, float float_data, const ai_lua_parameters& lua_target)
{
int empty_index;
ai_goal *aigp;
empty_index = ai_goal_find_empty_slot( aip->goals, aip->active_goal );
empty_index = ai_goal_allocate_slot( aip->goals, aip->active_goal );
aigp = &aip->goals[empty_index];
ai_add_goal_sub_player( type, mode, submode, shipname, aigp, lua_target );
ai_add_goal_sub_player( type, mode, submode, shipname, aigp, int_data, float_data, lua_target );
// if the goal is to dock, then we must determine which dock points on the two ships to use.
// If the target of the dock is a cargo type container, then we should use DOCK_TYPE_CARGO
@@ -886,7 +956,7 @@ void ai_add_ship_goal_player(ai_goal_type type, ai_goal_mode mode, int submode,
// adds a goal from the player to the given wing (which in turn will add it to the proper
// ships in the wing
void ai_add_wing_goal_player(ai_goal_type type, ai_goal_mode mode, int submode, const char *shipname, int wingnum, const ai_lua_parameters& lua_target)
void ai_add_wing_goal_player(ai_goal_type type, ai_goal_mode mode, int submode, const char *shipname, int wingnum, int int_data, float float_data, const ai_lua_parameters& lua_target)
{
int i, empty_index;
wing *wingp = &Wings[wingnum];
@@ -897,15 +967,15 @@ void ai_add_wing_goal_player(ai_goal_type type, ai_goal_mode mode, int submode,
int num = wingp->ship_index[i];
if ( num == -1 ) // ship must have been destroyed or departed
continue;
ai_add_ship_goal_player( type, mode, submode, shipname, &Ai_info[Ships[num].ai_index], lua_target );
ai_add_ship_goal_player( type, mode, submode, shipname, &Ai_info[Ships[num].ai_index], int_data, float_data, lua_target );
}
}
// add the sexpression index into the wing's list of goal sexpressions if
// there are more waves to come. We use the same method here as when adding a goal to
// a ship -- find the first empty entry. If none exists, take the oldest entry and replace it.
empty_index = ai_goal_find_empty_slot( wingp->ai_goals, -1 );
ai_add_goal_sub_player( type, mode, submode, shipname, &wingp->ai_goals[empty_index], lua_target );
empty_index = ai_goal_allocate_slot( wingp->ai_goals, -1 );
ai_add_goal_sub_player( type, mode, submode, shipname, &wingp->ai_goals[empty_index], int_data, float_data, lua_target );
}
@@ -938,11 +1008,9 @@ void ai_add_goal_sub_sexp( int sexp, ai_goal_type type, ai_info *aip, ai_goal *a
// for achievability.
aigp->target_name = ai_get_goal_target_name(CTEXT(CDR(node)), &aigp->target_name_index); // waypoint path name;
aigp->priority = eval_num(CDDR(node), priority_is_nan, priority_is_nan_forever);
aigp->ai_mode = AI_GOAL_WAYPOINTS;
if ( op == OP_AI_WAYPOINTS_ONCE )
aigp->ai_mode = AI_GOAL_WAYPOINTS_ONCE;
aigp->ai_mode = (op == OP_AI_WAYPOINTS_ONCE) ? AI_GOAL_WAYPOINTS_ONCE : AI_GOAL_WAYPOINTS;
if (CDDDDR(node) < 0)
aigp->int_data = 0; // handle optional node separately because we don't subtract 1 here
else
@@ -1021,10 +1089,27 @@ void ai_add_goal_sub_sexp( int sexp, ai_goal_type type, ai_info *aip, ai_goal *a
}
case OP_AI_STAY_STILL:
{
int n = CDR(node);
aigp->target_name = ai_get_goal_target_name(CTEXT(n), &aigp->target_name_index); // waypoint path name;
n = CDR(n);
aigp->priority = eval_num(n, priority_is_nan, priority_is_nan_forever);
n = CDR(n);
bool clear_goals = !The_mission.ai_profile->flags[AI::Profile_Flags::Do_not_clear_goals_when_running_stay_still];
if (n >= 0)
{
clear_goals = is_sexp_true(n);
n = CDR(n);
}
if (clear_goals)
aigp->flags.set(AI::Goal_Flags::Clear_all_goals_first);
aigp->ai_mode = AI_GOAL_STAY_STILL;
aigp->target_name = ai_get_goal_target_name(CTEXT(CDR(node)), &aigp->target_name_index); // waypoint path name;
aigp->priority = eval_num(CDDR(node), priority_is_nan, priority_is_nan_forever);
break;
}
case OP_AI_DOCK:
aigp->target_name = ai_get_goal_target_name( CTEXT(CDR(node)), &aigp->target_name_index );
@@ -1042,13 +1127,12 @@ void ai_add_goal_sub_sexp( int sexp, ai_goal_type type, ai_info *aip, ai_goal *a
break;
case OP_AI_PLAY_DEAD:
aigp->priority = eval_num(CDR(node), priority_is_nan, priority_is_nan_forever);
aigp->ai_mode = AI_GOAL_PLAY_DEAD;
break;
// we don't clear out ship goals for the "persistent" goal variant
aigp->flags.set(AI::Goal_Flags::Clear_all_goals_first);
FALLTHROUGH;
case OP_AI_PLAY_DEAD_PERSISTENT:
aigp->priority = eval_num(CDR(node), priority_is_nan, priority_is_nan_forever);
aigp->ai_mode = AI_GOAL_PLAY_DEAD_PERSISTENT;
aigp->ai_mode = (op == OP_AI_PLAY_DEAD) ? AI_GOAL_PLAY_DEAD : AI_GOAL_PLAY_DEAD_PERSISTENT;
break;
case OP_AI_KEEP_SAFE_DISTANCE:
@@ -1084,14 +1168,53 @@ void ai_add_goal_sub_sexp( int sexp, ai_goal_type type, ai_info *aip, ai_goal *a
}
case OP_AI_FORM_ON_WING:
aigp->priority = 99;
aigp->target_name = ai_get_goal_target_name(CTEXT(CDR(node)), &aigp->target_name_index);
{
int n = CDR(node);
aigp->target_name = ai_get_goal_target_name(CTEXT(n), &aigp->target_name_index);
n = CDR(n);
if (n >= 0)
{
aigp->priority = eval_num(n, priority_is_nan, priority_is_nan_forever);
n = CDR(n);
}
else
aigp->priority = The_mission.ai_profile->default_form_on_wing_priority;
bool clear_goals = !The_mission.ai_profile->flags[AI::Profile_Flags::Do_not_clear_goals_when_running_form_on_wing];
if (n >= 0)
{
clear_goals = is_sexp_true(n);
n = CDR(n);
}
if (clear_goals)
aigp->flags.set(AI::Goal_Flags::Clear_all_goals_first);
bool set_override = !The_mission.ai_profile->flags[AI::Profile_Flags::Do_not_set_override_when_assigning_form_on_wing];
if (n >= 0)
{
set_override = is_sexp_true(n);
n = CDR(n);
}
if (set_override)
aigp->flags.set(AI::Goal_Flags::Want_override);
if (n >= 0)
{
if (is_sexp_true(n))
aigp->flags.set(AI::Goal_Flags::Purge_when_new_goal_added);
n = CDR(n);
}
aigp->ai_mode = AI_GOAL_FORM_ON_WING;
break;
}
case OP_AI_CHASE:
case OP_AI_CHASE_WING:
case OP_AI_CHASE_SHIP_CLASS:
case OP_AI_CHASE_SHIP_TYPE:
case OP_AI_GUARD:
case OP_AI_GUARD_WING:
case OP_AI_EVADE_SHIP:
@@ -1123,6 +1246,8 @@ void ai_add_goal_sub_sexp( int sexp, ai_goal_type type, ai_info *aip, ai_goal *a
aigp->ai_mode = AI_GOAL_CHASE_WING;
} else if (op == OP_AI_CHASE_SHIP_CLASS) {
aigp->ai_mode = AI_GOAL_CHASE_SHIP_CLASS;
} else if (op == OP_AI_CHASE_SHIP_TYPE) {
aigp->ai_mode = AI_GOAL_CHASE_SHIP_TYPE;
} else if ( op == OP_AI_IGNORE ) {
aigp->ai_mode = AI_GOAL_IGNORE;
} else if ( op == OP_AI_IGNORE_NEW ) {
@@ -1164,10 +1289,13 @@ void ai_add_goal_sub_sexp( int sexp, ai_goal_type type, ai_info *aip, ai_goal *a
} else if ( aigp->priority > MAX_GOAL_PRIORITY ) {
nprintf (("AI", "bashing add-goal sexpression priority of goal %s from %d to %d.\n", Sexp_nodes[CAR(sexp)].text, aigp->priority, MAX_GOAL_PRIORITY));
aigp->priority = MAX_GOAL_PRIORITY;
} else if ( aigp->priority < MIN_GOAL_PRIORITY ) {
nprintf (("AI", "bashing add-goal sexpression priority of goal %s from %d to %d.\n", Sexp_nodes[CAR(sexp)].text, aigp->priority, MIN_GOAL_PRIORITY));
aigp->priority = MIN_GOAL_PRIORITY;
}
// Goober5000 - we now have an extra optional chase argument to allow chasing our own team
if ( op == OP_AI_CHASE || op == OP_AI_CHASE_WING || op == OP_AI_CHASE_SHIP_CLASS
if (op == OP_AI_CHASE || op == OP_AI_CHASE_WING || op == OP_AI_CHASE_SHIP_CLASS || op == OP_AI_CHASE_SHIP_TYPE
|| op == OP_AI_DISABLE_SHIP || op == OP_AI_DISABLE_SHIP_TACTICAL || op == OP_AI_DISARM_SHIP || op == OP_AI_DISARM_SHIP_TACTICAL ) {
if (is_sexp_true(CDDDR(node)))
aigp->flags.set(AI::Goal_Flags::Target_own_team);
@@ -1192,6 +1320,7 @@ void ai_add_goal_sub_sexp( int sexp, ai_goal_type type, ai_info *aip, ai_goal *a
if (op == OP_AI_CHASE ||
op == OP_AI_CHASE_WING ||
op == OP_AI_CHASE_SHIP_CLASS ||
op == OP_AI_CHASE_SHIP_TYPE ||
op == OP_AI_DISABLE_SHIP ||
op == OP_AI_DISABLE_SHIP_TACTICAL ||
op == OP_AI_DISARM_SHIP ||
@@ -1275,6 +1404,11 @@ int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp, bool &remove_more )
nprintf(("AI", "bashing remove-goal sexpression priority of goal %s from %d to %d.\n", Sexp_nodes[CAR(sexp)].text, _priority, MAX_GOAL_PRIORITY));
_priority = MAX_GOAL_PRIORITY;
}
else if (_priority < MIN_GOAL_PRIORITY)
{
nprintf(("AI", "bashing remove-goal sexpression priority of goal %s from %d to %d.\n", Sexp_nodes[CAR(sexp)].text, _priority, MIN_GOAL_PRIORITY));
_priority = MIN_GOAL_PRIORITY;
}
if (n >= 0)
{
@@ -1295,12 +1429,9 @@ int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp, bool &remove_more )
/* We now need to determine what the mode and submode values are*/
switch( op )
{
case OP_AI_WAYPOINTS_ONCE:
goalmode = AI_GOAL_WAYPOINTS_ONCE;
priority = eval_priority_et_seq(CDDR(node));
break;
case OP_AI_WAYPOINTS:
goalmode = AI_GOAL_WAYPOINTS;
case OP_AI_WAYPOINTS_ONCE:
goalmode = (op == OP_AI_WAYPOINTS_ONCE) ? AI_GOAL_WAYPOINTS_ONCE : AI_GOAL_WAYPOINTS;
priority = eval_priority_et_seq(CDDR(node));
break;
case OP_AI_DESTROY_SUBSYS:
@@ -1378,6 +1509,10 @@ int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp, bool &remove_more )
priority = eval_priority_et_seq(CDDR(node));
goalmode = AI_GOAL_CHASE_SHIP_CLASS;
break;
case OP_AI_CHASE_SHIP_TYPE:
priority = eval_priority_et_seq(CDDR(node));
goalmode = AI_GOAL_CHASE_SHIP_TYPE;
break;
case OP_AI_EVADE_SHIP:
priority = eval_priority_et_seq(CDDR(node));
goalmode = AI_GOAL_EVADE_SHIP;
@@ -1392,7 +1527,7 @@ int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp, bool &remove_more )
goalmode = (op == OP_AI_IGNORE) ? AI_GOAL_IGNORE : AI_GOAL_IGNORE_NEW;
break;
case OP_AI_FORM_ON_WING:
priority = eval_priority_et_seq(-1, 99);
priority = eval_priority_et_seq(CDDR(node), The_mission.ai_profile->default_form_on_wing_priority);
goalmode = AI_GOAL_FORM_ON_WING;
break;
case OP_AI_FLY_TO_SHIP:
@@ -1479,7 +1614,7 @@ void ai_add_ship_goal_sexp( int sexp, ai_goal_type type, ai_info *aip )
{
int gindex;
gindex = ai_goal_find_empty_slot( aip->goals, aip->active_goal );
gindex = ai_goal_allocate_slot( aip->goals, aip->active_goal );
ai_add_goal_sub_sexp( sexp, type, aip, &aip->goals[gindex], Ships[aip->shipnum].ship_name );
}
@@ -1503,7 +1638,7 @@ void ai_add_wing_goal_sexp(int sexp, ai_goal_type type, wing *wingp)
if ((wingp->num_waves - wingp->current_wave > 0) || Fred_running) {
int gindex;
gindex = ai_goal_find_empty_slot( wingp->ai_goals, -1 );
gindex = ai_goal_allocate_slot( wingp->ai_goals, -1 );
ai_add_goal_sub_sexp( sexp, type, nullptr, &wingp->ai_goals[gindex], wingp->name );
}
}
@@ -1526,7 +1661,7 @@ void ai_add_goal_ship_internal( ai_info *aip, int goal_type, char *name, int /*
Assertion(strcmp(name, Ships[aip->shipnum].ship_name) != 0, "The goals apply to the actor in ai_add_goal_ship_internal for ship %s, please report to the SCP!", name);
// find an empty slot to put this goal in.
gindex = ai_goal_find_empty_slot( aip->goals, aip->active_goal );
gindex = ai_goal_allocate_slot( aip->goals, aip->active_goal );
aigp = &(aip->goals[gindex]);
ai_goal_reset(aigp, true);
@@ -1655,7 +1790,7 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
if (!target_ship_entry || !target_ship_entry->has_shipp())
return ai_achievability::NOT_ACHIEVABLE;
aigp->flags.set(AI::Goal_Flags::Goal_override);
// the override flag is now set in the calling function, validate_mission_goals
return ai_achievability::ACHIEVABLE;
}
@@ -1687,7 +1822,20 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
}
return ai_achievability::NOT_KNOWN;
}
// and similarly for chasing all ships of a certain ship type
if (aigp->ai_mode == AI_GOAL_CHASE_SHIP_TYPE) {
for (auto so : list_range(&Ship_obj_list)) {
auto type_objp = &Objects[so->objnum];
if (type_objp->type != OBJ_SHIP || type_objp->flags[Object::Object_Flags::Should_be_dead])
continue;
int ship_info_idx = Ships[type_objp->instance].ship_info_index;
int class_type = Ship_info[ship_info_idx].class_type;
if (class_type >= 0 && !strcmp(aigp->target_name, Ship_types[class_type].name)) {
return ai_achievability::ACHIEVABLE;
}
}
return ai_achievability::NOT_KNOWN;
}
return_val = ai_achievability::SATISFIED;
@@ -1917,15 +2065,15 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
// Goober5000 - see note at PURGE_GOALS_ALL_SHIPS... this is bizarre
if ((status == SHIP_STATUS_ARRIVED) && !(aigp->flags[AI::Goal_Flags::Goals_purged]))
{
if (purge_goals_all_ships(aigp->ai_mode)) {
if (causes_invalid_goal_purge_all_ships(aigp->ai_mode)) {
ai_goal_purge_all_invalid_goals(aigp);
aigp->flags.set(AI::Goal_Flags::Goals_purged);
}
else if (purge_goals_one_ship(aigp->ai_mode)) {
else if (causes_invalid_goal_purge_one_ship(aigp->ai_mode)) {
ai_goal_purge_invalid_goals(aigp, aip->goals, aip, -1);
aigp->flags.set(AI::Goal_Flags::Goals_purged);
}
}
}
// if we are docking, validate the docking indices on both ships. We might have to change names to indices.
// only enter this calculation if the ship we are docking with has arrived. If the ship is gone, then
@@ -2225,6 +2373,10 @@ void validate_mission_goals(int objnum, ai_info *aip)
continue;
}
// if the status is achievable, and we want to set the override, set it
if ( (state == ai_achievability::ACHIEVABLE) && (aigp->flags[AI::Goal_Flags::Want_override]))
aigp->flags.set(AI::Goal_Flags::Goal_override);
// if the status is achievable, and the on_hold flag is set, clear the flagb
if ( (state == ai_achievability::ACHIEVABLE) && (aigp->flags[AI::Goal_Flags::Goal_on_hold]) )
aigp->flags.remove(AI::Goal_Flags::Goal_on_hold);
@@ -2274,7 +2426,7 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
object *objp = &Objects[objnum];
object *other_obj;
ai_goal *current_goal;
int wingnum, shipnum;
int wingnum;
int original_signature;
/* if (!stricmp(Ships[objp->instance].ship_name, "gtt comet")) {
@@ -2349,14 +2501,34 @@ void ai_process_mission_orders( int objnum, ai_info *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;
if (current_goal->flags[AI::Goal_Flags::Clear_all_goals_first]) {
// save the current goal before we wipe it out by clearing everything
current_goal_backup.reset(new ai_goal(*current_goal));
current_goal = current_goal_backup.get();
// stay-still, form-on-wing, and play-dead all clear their goals here...
//
// clear out the object's goals. Seems to me that if a ship is staying still for a purpose
// then we need to clear everything out since there is not a real way to get rid of this goal
//
// for form on wing, we need to clear out all goals for this ship, and then call the form on wing AI code
//
// if a ship is playing dead, MWA says that it shouldn't try to do anything else.
//
// clearing out goals is okay here since we are now what mode to set this AI object to.
ai_clear_ship_goals(aip);
}
switch ( current_goal->ai_mode ) {
case AI_GOAL_CHASE:
if ( current_goal->target_name ) {
shipnum = ship_name_lookup( current_goal->target_name );
Assert (shipnum != -1 ); // shouldn't get here if this is false!!!!
other_obj = &Objects[Ships[shipnum].objnum];
if (current_goal->target_name) {
Assert(current_goal_target_ship && current_goal_target_ship->has_objp()); // shouldn't get here if this is false!!!!
other_obj = current_goal_target_ship->objp();
} else
other_obj = NULL; // we get this case when we tell ship to engage enemy!
@@ -2373,9 +2545,8 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
break;
case AI_GOAL_GUARD:
shipnum = ship_name_lookup( current_goal->target_name );
Assert (shipnum != -1 ); // shouldn't get here if this is false!!!!
other_obj = &Objects[Ships[shipnum].objnum];
Assert(current_goal_target_ship && current_goal_target_ship->has_objp()); // shouldn't get here if this is false!!!!
other_obj = current_goal_target_ship->objp();
// shipnum and other_obj are the shipnumber and object pointer of the object that you should
// guard.
if (objp != other_obj) {
@@ -2406,9 +2577,8 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
}
case AI_GOAL_DOCK: {
shipnum = ship_name_lookup( current_goal->target_name );
Assert (shipnum != -1 ); // shouldn't get here if this is false!!!!
other_obj = &Objects[Ships[shipnum].objnum];
Assert(current_goal_target_ship && current_goal_target_ship->has_objp()); // shouldn't get here if this is false!!!!
other_obj = current_goal_target_ship->objp();
// be sure that we have indices for docking points here! If we ever had names, they should
// get fixed up in goal_achievable so that the points can be checked there for validity
@@ -2418,24 +2588,22 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
}
case AI_GOAL_UNDOCK:
// try to find the object which which this object is docked with. Use that object as the
// try to find the object with which this object is docked. Use that object as the
// "other object" for the undocking proceedure. If "other object" isn't found, then the undock
// goal cannot continue. Spit out a warning and remove the goal.
// Goober5000 - do we have a specific ship to undock from?
if ( current_goal->target_name != NULL )
if (current_goal->target_name)
{
shipnum = ship_name_lookup( current_goal->target_name );
// hmm, perhaps he was destroyed
if (shipnum == -1)
if (!current_goal_target_ship || !current_goal_target_ship->has_objp())
{
other_obj = NULL;
}
// he exists... let's undock from him
else
{
other_obj = &Objects[Ships[shipnum].objnum];
other_obj = current_goal_target_ship->objp();
}
}
// no specific ship
@@ -2488,16 +2656,15 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
case AI_GOAL_DISABLE_SHIP_TACTICAL:
case AI_GOAL_DISARM_SHIP:
case AI_GOAL_DISARM_SHIP_TACTICAL: {
shipnum = ship_name_lookup( current_goal->target_name );
Assert( shipnum >= 0 );
other_obj = &Objects[Ships[shipnum].objnum];
Assert(current_goal_target_ship && current_goal_target_ship->has_objp());
other_obj = current_goal_target_ship->objp();
ai_attack_object( objp, other_obj);
ai_set_attack_subsystem( objp, current_goal->ai_submode ); // submode stored the subsystem type
// don't protect-ship for tactical goals
if (current_goal->ai_mode != AI_GOAL_DESTROY_SUBSYSTEM && current_goal->ai_mode != AI_GOAL_DISABLE_SHIP_TACTICAL && current_goal->ai_mode != AI_GOAL_DISARM_SHIP_TACTICAL) {
if (aip->target_objnum != -1) {
int class_type = Ship_info[Ships[shipnum].ship_info_index].class_type;
int class_type = Ship_info[current_goal_target_ship->shipp()->ship_info_index].class_type;
// Only protect if _not_ a capital ship. We don't want the Lucifer accidentally getting protected.
if (class_type >= 0 && Ship_types[class_type].flags[Ship::Type_Info_Flags::AI_protected_on_cripple])
Objects[aip->target_objnum].flags.set(Object::Object_Flags::Protected);
@@ -2521,10 +2688,20 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
// chase-ship-class is chase-any but restricted to a subset of ships
case AI_GOAL_CHASE_SHIP_CLASS:
shipnum = ship_info_lookup( current_goal->target_name );
Assertion( shipnum >= 0, "The target of AI_GOAL_CHASE_SHIP_CLASS must refer to a valid ship class!" );
ai_attack_object( objp, nullptr, shipnum );
{
int ship_info_index = ship_info_lookup(current_goal->target_name);
Assertion(ship_info_index >= 0, "The target of AI_GOAL_CHASE_SHIP_CLASS must refer to a valid ship class!");
ai_attack_object(objp, nullptr, ship_info_index);
break;
}
// similarly for chase-ship-type
case AI_GOAL_CHASE_SHIP_TYPE:
{
int class_type = ship_type_name_lookup(current_goal->target_name);
Assertion(class_type >= 0, "The target of AI_GOAL_CHASE_SHIP_TYPE must refer to a valid ship type!");
ai_attack_object(objp, nullptr, -1, class_type);
break;
}
case AI_GOAL_WARP: {
mission_do_departure( objp, true );
@@ -2532,46 +2709,29 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
}
case AI_GOAL_EVADE_SHIP:
shipnum = ship_name_lookup( current_goal->target_name );
Assert( shipnum >= 0 );
other_obj = &Objects[Ships[shipnum].objnum];
Assert(current_goal_target_ship && current_goal_target_ship->has_objp()); // shouldn't get here if this is false!!!!
other_obj = current_goal_target_ship->objp();
ai_evade_object( objp, other_obj);
break;
case AI_GOAL_STAY_STILL:
// NOTE: goal clearing moved above this enclosing switch block
// for now, ignore any other parameters!!!!
// clear out the object's goals. Seems to me that if a ship is staying still for a purpose
// then we need to clear everything out since there is not a real way to get rid of this goal
// clearing out goals is okay here since we are now what mode to set this AI object to.
ai_clear_ship_goals( aip );
ai_stay_still( objp, NULL );
break;
case AI_GOAL_PLAY_DEAD:
// if a ship is playing dead, MWA says that it shouldn't try to do anything else.
// clearing out goals is okay here since we are now what mode to set this AI object to.
ai_clear_ship_goals( aip );
aip->mode = AIM_PLAY_DEAD;
aip->submode = -1;
aip->submode_start_time = Missiontime;
break;
case AI_GOAL_PLAY_DEAD_PERSISTENT:
// same as above, but we don't clear out ship goals
// NOTE: goal clearing moved above this enclosing switch block
aip->mode = AIM_PLAY_DEAD;
aip->submode = -1;
aip->submode_start_time = Missiontime;
break;
case AI_GOAL_FORM_ON_WING:
// get the ship first, since we're going to wipe it out next
shipnum = ship_name_lookup( current_goal->target_name );
Assert( shipnum >= 0 );
other_obj = &Objects[Ships[shipnum].objnum];
// for form on wing, we need to clear out all goals for this ship, and then call the form
// on wing AI code
// clearing out goals is okay here since we are now what mode to set this AI object to.
ai_clear_ship_goals( aip );
// NOTE: goal clearing moved above this enclosing switch block
Assert(current_goal_target_ship && current_goal_target_ship->has_objp()); // shouldn't get here if this is false!!!!
other_obj = current_goal_target_ship->objp();
ai_form_on_wing( objp, other_obj );
break;
@@ -2580,9 +2740,8 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
case AI_GOAL_STAY_NEAR_SHIP:
case AI_GOAL_FLY_TO_SHIP:
{
shipnum = ship_name_lookup( current_goal->target_name );
Assert( shipnum >= 0 );
other_obj = &Objects[Ships[shipnum].objnum];
Assert(current_goal_target_ship && current_goal_target_ship->has_objp()); // shouldn't get here if this is false!!!!
other_obj = current_goal_target_ship->objp();
float dist = current_goal->float_data; // How far away to stay from ship. Should be set in SEXP?
int additional_data = current_goal->int_data; // Whether to target a particular point as if escorting
ai_do_stay_near(objp, other_obj, dist, additional_data);
@@ -2597,9 +2756,8 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
break;
case AI_GOAL_REARM_REPAIR:
shipnum = ship_name_lookup( current_goal->target_name );
Assert( shipnum >= 0 );
other_obj = &Objects[Ships[shipnum].objnum];
Assert(current_goal_target_ship && current_goal_target_ship->has_objp()); // shouldn't get here if this is false!!!!
other_obj = current_goal_target_ship->objp();
ai_rearm_repair( objp, current_goal->docker.index, other_obj, current_goal->dockee.index );
break;
+9 -6
View File
@@ -47,9 +47,9 @@ enum class ai_goal_type
enum ai_goal_mode : uint8_t
{
AI_GOAL_NONE = 0,
AI_GOAL_PLACEHOLDER_1,
AI_GOAL_SCHROEDINGER, // used for FRED when multiple ships are selected with different orders
AI_GOAL_CHASE, // per the original #define list, AI_GOAL_CHASE started at 2 (1<<1)
AI_GOAL_CHASE,
AI_GOAL_DOCK, // used for undocking as well
AI_GOAL_WAYPOINTS,
AI_GOAL_WAYPOINTS_ONCE,
@@ -80,7 +80,8 @@ enum ai_goal_mode : uint8_t
AI_GOAL_FLY_TO_SHIP,
AI_GOAL_IGNORE_NEW,
AI_GOAL_CHASE_SHIP_CLASS,
AI_GOAL_PLAY_DEAD_PERSISTENT,
AI_GOAL_CHASE_SHIP_TYPE,
AI_GOAL_PLAY_DEAD_PERSISTENT, // Disables subsystem rotation/translation among other things but there is a carveout for that in the lab in ship_move_subsystems() and ai_process_subobjects()
AI_GOAL_LUA,
AI_GOAL_DISARM_SHIP_TACTICAL,
AI_GOAL_DISABLE_SHIP_TACTICAL,
@@ -103,7 +104,7 @@ inline bool ai_goal_is_disable_or_disarm(ai_goal_mode ai_mode)
}
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;
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;
}
enum class ai_achievability { ACHIEVABLE, NOT_ACHIEVABLE, NOT_KNOWN, SATISFIED };
@@ -152,6 +153,8 @@ typedef struct ai_goal {
extern void ai_goal_reset(ai_goal *aigp, bool adding_goal = false, ai_goal_mode ai_mode = AI_GOAL_NONE, int ai_submode = -1, ai_goal_type type = ai_goal_type::INVALID);
// Reset all path points. Used in the ship lab. Missions clean up path points with the garbage collector in garbage_collect_path_points()
extern void reset_ai_path_points();
typedef flag_def_list_templated<ai_goal_mode> ai_goal_list;
@@ -185,8 +188,8 @@ extern int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp, bool &remove_more )
extern void ai_remove_wing_goal_sexp( int sexp, wing *wingp );
// adds goals to ships/sings through player orders
extern void ai_add_ship_goal_player(ai_goal_type type, ai_goal_mode mode, int submode, const char* shipname, ai_info* aip, const ai_lua_parameters& lua_target = { object_ship_wing_point_team(), luacpp::LuaValueList{} });
extern void ai_add_wing_goal_player(ai_goal_type type, ai_goal_mode mode, int submode, const char* shipname, int wingnum, const ai_lua_parameters& lua_target = { object_ship_wing_point_team(), luacpp::LuaValueList{} });
extern void ai_add_ship_goal_player(ai_goal_type type, ai_goal_mode mode, int submode, const char* shipname, ai_info* aip, int int_data = 0, float float_data = 0.0f, const ai_lua_parameters& lua_target = { object_ship_wing_point_team(), luacpp::LuaValueList{} });
extern void ai_add_wing_goal_player(ai_goal_type type, ai_goal_mode mode, int submode, const char* shipname, int wingnum, int int_data = 0, float float_data = 0.0f, const ai_lua_parameters& lua_target = { object_ship_wing_point_team(), luacpp::LuaValueList{} });
extern void ai_remove_ship_goal( ai_info *aip, int index );
extern void ai_clear_ship_goals( ai_info *aip );
+17 -13
View File
@@ -479,6 +479,12 @@ int valid_turret_enemy(object *objp, object *turret_parent)
return 0;
}
// Mines are only targetable within their configured detection range
if (wip->is_mine()) {
if (vm_vec_dist(&objp->pos, &turret_parent->pos) > wip->mine_targetable_range)
return 0;
}
return 1;
}
@@ -1089,9 +1095,11 @@ 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) {
if (iff_matches_mask(Ships[Objects[target_objnum].instance].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, target_objnum));
ship* target_shipp = &Ships[Objects[target_objnum].instance];
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,
// target_objnum));
return target_objnum;
}
}
@@ -1559,7 +1567,9 @@ void turret_set_next_fire_timestamp(int weapon_num, const weapon_info *wip, ship
float burst_shots_mult = wip->weapon_launch_curves.get_output(weapon_info::WeaponLaunchCurveOutputs::BURST_SHOTS_MULT, launch_curve_data);
int burst_shots = MAX(fl2i(i2fl(base_burst_shots) * burst_shots_mult) - 1, 0);
if (burst_shots > turret->weapons.burst_counter[weapon_num]) {
bool burst = burst_shots > turret->weapons.burst_counter[weapon_num];
if (burst) {
wait *= wip->burst_delay;
wait *= wip->weapon_launch_curves.get_output(weapon_info::WeaponLaunchCurveOutputs::BURST_DELAY_MULT, launch_curve_data);
turret->weapons.burst_counter[weapon_num]++;
@@ -1659,7 +1669,7 @@ void turret_set_next_fire_timestamp(int weapon_num, const weapon_info *wip, ship
wait *= frand_range(0.9f, 1.1f);
}
if(turret->rof_scaler != 1.0f)
if(turret->rof_scaler != 1.0f && !(burst && turret->system_info->flags[Model::Subsystem_Flags::Burst_ignores_RoF_Mult]))
wait /= get_adjusted_turret_rof(turret);
(*fs_dest) = timestamp((int)wait);
@@ -2038,14 +2048,11 @@ bool turret_fire_weapon(int weapon_num,
}
//spawn particle effect
auto particleSource = particle::ParticleManager::get()->createSource(wip->muzzle_effect);
particleSource->setHost(make_unique<EffectHostTurret>(&Objects[parent_ship->objnum], turret->system_info->turret_gun_sobj, turret->turret_next_fire_pos));
particleSource->setHost(std::make_unique<EffectHostTurret>(&Objects[parent_ship->objnum], turret->system_info->turret_gun_sobj, turret->turret_next_fire_pos, false));
particleSource->setTriggerRadius(objp->radius * radius_mult);
particleSource->setTriggerVelocity(vm_vec_mag_quick(&objp->phys_info.vel));
particleSource->finishCreation();
}
else if (wip->muzzle_flash >= 0) {
mflash_create(firing_pos, firing_vec, &Objects[parent_ship->objnum].phys_info, wip->muzzle_flash);
}
// in multiplayer (and the master), then send a turret fired packet.
if ( MULTIPLAYER_MASTER && (weapon_objnum != -1) ) {
@@ -2162,13 +2169,10 @@ void turret_swarm_fire_from_turret(turret_swarm_info *tsi)
if (Weapon_info[tsi->weapon_class].muzzle_effect.isValid()) {
//spawn particle effect
auto particleSource = particle::ParticleManager::get()->createSource(Weapon_info[tsi->weapon_class].muzzle_effect);
particleSource->setHost(make_unique<EffectHostTurret>(&Objects[tsi->parent_objnum], tsi->turret->system_info->turret_gun_sobj, tsi->turret->turret_next_fire_pos - 1));
particleSource->setHost(std::make_unique<EffectHostTurret>(&Objects[tsi->parent_objnum], tsi->turret->system_info->turret_gun_sobj, tsi->turret->turret_next_fire_pos - 1, false));
particleSource->setTriggerRadius(Objects[weapon_objnum].radius);
particleSource->finishCreation();
}
else if (Weapon_info[tsi->weapon_class].muzzle_flash >= 0) {
mflash_create(&turret_pos, &turret_fvec, &Objects[tsi->parent_objnum].phys_info, Weapon_info[tsi->weapon_class].muzzle_flash);
}
// maybe sound
if ( Weapon_info[tsi->weapon_class].launch_snd.isValid() ) {
+18 -10
View File
@@ -142,11 +142,15 @@ int unpack_pixel(anim_instance *ai, ubyte *data, ubyte pix, int aabitmap, int bp
bit_16 = (ushort)pix;
break;
case 8:
// 8 bit-per-pixel aa bitmaps are a bit special since they only use a palette index value in the range [0, 15]. These
// palette indexes must be remapped to alpha values between [0, 255] which is what graphics code expects. Palette
// range [0, 14] is a gradient from black to white, and palette index 15 is a special color which indicates the background
// area of a HUD gauge. Retail code uses the final alpha value for index 1 for this special index to give gauges a dark
// transparent background.
// 8-bit-per-pixel HUD gauge ANI aabitmaps use the palette index directly as an alpha value —
// the actual RGB colors stored in the palette are completely ignored. The valid range is [0, 15]:
// [0]: fully transparent (alpha 0)
// [1-14]: black-to-white gradient (alpha = index * 18)
// [15]: special HUD background (alpha 18, matching index 1, for a dark transparent tint)
// [>15]: clamped to fully opaque (alpha 255)
// Because only the index value matters, the palette in the source file must be ordered so that
// index 0 is the transparent end and index 14 is the opaque end. A reversed palette causes the
// gauge to render as a solid white square (see GitHub issue #4148).
if (pix > 15) {
bit_8 = 255;
}
@@ -240,11 +244,15 @@ int unpack_pixel_count(anim_instance *ai, ubyte *data, ubyte pix, int count = 0,
bit_16 = (ushort)pix;
break;
case 8 :
// 8 bit-per-pixel aa bitmaps are a bit special since they only use a palette index value in the range [0, 15]. These
// palette indexes must be remapped to alpha values between [0, 255] which is what graphics code expects. Palette
// range [0, 14] is a gradient from black to white, and palette index 15 is a special color which indicates the background
// area of a HUD gauge. Retail code uses the final alpha value for index 1 for this special index to give gauges a dark
// transparent background.
// 8-bit-per-pixel HUD gauge ANI aabitmaps use the palette index directly as an alpha value —
// the actual RGB colors stored in the palette are completely ignored. The valid range is [0, 15]:
// [0]: fully transparent (alpha 0)
// [1-14]: black-to-white gradient (alpha = index * 18)
// [15]: special HUD background (alpha 18, matching index 1, for a dark transparent tint)
// [>15]: clamped to fully opaque (alpha 255)
// Because only the index value matters, the palette in the source file must be ordered so that
// index 0 is the transparent end and index 14 is the opaque end. A reversed palette causes the
// gauge to render as a solid white square (see GitHub issue #4148).
if (pix > 15) {
bit_8 = 255;
}
+157 -79
View File
@@ -32,8 +32,9 @@
#include "object/objcollide.h"
#include "object/object.h"
#include "parse/parselo.h"
#include "particle/ParticleEffect.h"
#include "scripting/global_hooks.h"
#include "particle/particle.h"
#include "particle/hosts/EffectHostVector.h"
#include "render/3d.h"
#include "ship/ship.h"
#include "ship/shipfx.h"
@@ -63,8 +64,7 @@ asteroid Asteroids[MAX_ASTEROIDS];
asteroid_field Asteroid_field;
static int Asteroid_impact_explosion_ani;
static float Asteroid_impact_explosion_radius;
static particle::ParticleEffectHandle Asteroid_impact_explosion_ani;
char Asteroid_icon_closeup_model[NAME_LENGTH];
vec3d Asteroid_icon_closeup_position;
float Asteroid_icon_closeup_zoom;
@@ -648,24 +648,30 @@ void asteroid_create_all()
int max_asteroids = Asteroid_field.num_initial_asteroids; // * (1.0f - 0.1f*(MAX_DETAIL_VALUE-Detail.asteroid_density)));
int num_debris_types = 0;
int num_valid = 0; // count of valid (non-(-1)) entries written into shipDebrisOddsTable
// get number of debris types
if (Asteroid_field.debris_genre == DG_DEBRIS) {
num_debris_types = static_cast<int>(Asteroid_field.field_debris_type.size());
// Calculate the odds table
// Calculate the odds table; skip invalid entries (field_debris_type[idx] == -1 means
// missionparse invalidated the entry because the type no longer exists in Asteroid_info)
for (idx=0; idx<num_debris_types; idx++) {
if (Asteroid_field.field_debris_type[idx] == -1)
continue;
float debris_weight = Asteroid_info[Asteroid_field.field_debris_type[idx]].spawn_weight;
shipDebrisOddsTable[idx].random_threshold = max_weighted_range + debris_weight;
shipDebrisOddsTable[idx].debris_type = Asteroid_field.field_debris_type[idx];
shipDebrisOddsTable[num_valid].random_threshold = max_weighted_range + debris_weight;
shipDebrisOddsTable[num_valid].debris_type = Asteroid_field.field_debris_type[idx];
max_weighted_range += debris_weight;
num_valid++;
}
}
// Load Asteroid/debris models
if (Asteroid_field.debris_genre == DG_DEBRIS) {
for (idx=0; idx<num_debris_types; idx++) {
asteroid_load(Asteroid_field.field_debris_type[idx], 0);
if (Asteroid_field.field_debris_type[idx] != -1)
asteroid_load(Asteroid_field.field_debris_type[idx], 0);
}
} else {
for (const auto& subtype : Asteroid_field.field_asteroid_type) {
@@ -690,7 +696,7 @@ void asteroid_create_all()
float rand_choice = frand() * max_weighted_range;
for (idx = 0; idx < static_cast<int>(Asteroid_field.field_debris_type.size()); idx++) {
for (idx = 0; idx < num_valid; idx++) {
// for ship debris, choose type according to odds table
if (rand_choice < shipDebrisOddsTable[idx].random_threshold) {
asteroid_create(&Asteroid_field, shipDebrisOddsTable[idx].debris_type, 0);
@@ -824,18 +830,7 @@ void asteroid_create_debris_field(int num_asteroids, int asteroid_speed, SCP_vec
*/
void asteroid_level_init()
{
Asteroid_field.num_initial_asteroids = 0; // disable asteroid field by default.
Asteroid_field.speed = 0.0f;
vm_vec_make(&Asteroid_field.min_bound, -1000.0f, -1000.0f, -1000.0f);
vm_vec_make(&Asteroid_field.max_bound, 1000.0f, 1000.0f, 1000.0f);
vm_vec_make(&Asteroid_field.inner_min_bound, -500.0f, -500.0f, -500.0f);
vm_vec_make(&Asteroid_field.inner_max_bound, 500.0f, 500.0f, 500.0f);
Asteroid_field.has_inner_bound = false;
Asteroid_field.field_type = FT_ACTIVE;
Asteroid_field.debris_genre = DG_ASTEROID;
Asteroid_field.field_debris_type.clear();
Asteroid_field.field_asteroid_type.clear();
Asteroid_field.target_names.clear();
Asteroid_field = {};
if (!Fred_running)
{
@@ -1088,8 +1083,10 @@ void asteroid_delete( object * obj )
asp = &Asteroids[num];
if (asp->model_instance_num >= 0)
if (asp->model_instance_num >= 0) {
model_delete_instance(asp->model_instance_num);
asp->model_instance_num = -1;
}
if (asp->target_objnum >= 0) {
for (asteroid_target& target : Asteroid_targets) {
@@ -1326,9 +1323,9 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
model_get_moving_submodel_list(submodel_vector, heavy_obj);
// turn off all moving submodels, collide against only 1 at a time.
// turn off collision detection for all moving submodels
mc.collision_checked.assign(pm->n_models, 0);
for (auto submodel: submodel_vector) {
pmi->submodel[submodel].collision_checked = true;
mc.collision_checked[submodel] = true;
}
// Only check single submodel now, since children of moving submodels are handled as moving as well
@@ -1336,10 +1333,8 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
// check each submodel in turn
for (auto submodel: submodel_vector) {
auto smi = &pmi->submodel[submodel];
// turn on just one submodel for collision test
smi->collision_checked = false;
mc.collision_checked[submodel] = false;
// find the start and end positions of the sphere in submodel RF
model_instance_global_to_local_point(&p0, &light_obj->last_pos, pm, pmi, submodel, &heavy_obj->last_orient, &heavy_obj->last_pos, true);
@@ -1371,9 +1366,12 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos
}
}
// Don't look at this submodel again
smi->collision_checked = true;
mc.collision_checked[submodel] = true;
}
// Clear collision_checked before base model pass so it auto-inits fresh
mc.collision_checked.clear();
}
// Now complete base model collision checks that do not take into account rotating submodels.
@@ -1618,8 +1616,13 @@ static float asteroid_create_explosion(object *objp)
}
fireball_scale_multiplier = asteroid_get_fireball_scale_multiplier(objp->instance);
auto fireball_radius = objp->radius * fireball_scale_multiplier;
if (Zero_radius_explosions_skip_fireballs && fl_near_zero(fireball_radius))
fireball_objnum = -1;
else
fireball_objnum = fireball_create( &objp->pos, fireball_type, FIREBALL_LARGE_EXPLOSION, OBJ_INDEX(objp), fireball_radius, false, &objp->phys_info.vel );
fireball_objnum = fireball_create( &objp->pos, fireball_type, FIREBALL_LARGE_EXPLOSION, OBJ_INDEX(objp), objp->radius*fireball_scale_multiplier, false, &objp->phys_info.vel );
if ( fireball_objnum > -1 ) {
explosion_life = fireball_lifeleft(&Objects[fireball_objnum]);
} else {
@@ -1705,37 +1708,57 @@ static void asteroid_do_area_effect(object *asteroid_objp)
*/
void asteroid_hit( object * pasteroid_obj, object * other_obj, vec3d * hitpos, float damage, vec3d* force )
{
float explosion_life;
asteroid *asp;
asp = &Asteroids[pasteroid_obj->instance];
asteroid *asp = &Asteroids[pasteroid_obj->instance];
asteroid_info *asip = &Asteroid_info[Asteroids[pasteroid_obj->instance].asteroid_type];
if (pasteroid_obj->flags[Object::Object_Flags::Should_be_dead]){
return;
}
if ( MULTIPLAYER_MASTER ){
send_asteroid_hit( pasteroid_obj, other_obj, hitpos, damage, force );
}
if (hitpos && force && The_mission.ai_profile->flags[AI::Profile_Flags::Whackable_asteroids]) {
vec3d rel_hit_pos = *hitpos - pasteroid_obj->pos;
physics_calculate_and_apply_whack(force, &rel_hit_pos, &pasteroid_obj->phys_info, &pasteroid_obj->orient, &pasteroid_obj->phys_info.I_body_inv);
pasteroid_obj->phys_info.desired_vel = pasteroid_obj->phys_info.vel;
}
pasteroid_obj->hull_strength -= damage;
if (pasteroid_obj->hull_strength < 0.0f) {
if ( !asp->final_death_time.isValid() ) {
int play_loud_collision = 0;
float explosion_life = 1.f;
int breakup_timestamp;
Assertion(!asip->end_particles.isValid() || asip->breakup_delay.has_value(), "Asteroid %s has end particles but no breakup delay. Parsing should not have allowed this!", asip->name);
explosion_life = asteroid_create_explosion(pasteroid_obj);
if (asip->end_particles.isValid()) {
auto source = particle::ParticleManager::get()->createSource(asip->end_particles);
// Use the position since the asteroid is going to be invalid soon
auto host = std::make_unique<EffectHostVector>(pasteroid_obj->pos, pasteroid_obj->orient, pasteroid_obj->phys_info.vel);
host->setRadius(pasteroid_obj->radius);
source->setHost(std::move(host));
source->setNormal(pasteroid_obj->orient.vec.uvec);
source->finishCreation();
} else {
explosion_life = asteroid_create_explosion(pasteroid_obj);
}
if (asip->breakup_delay.has_value()) {
breakup_timestamp = fl2i(*asip->breakup_delay * MILLISECONDS_PER_SECOND);
} else {
breakup_timestamp = fl2i((explosion_life * MILLISECONDS_PER_SECOND) / 5.f);
}
asteroid_explode_sound(pasteroid_obj, asp->asteroid_type, play_loud_collision);
asteroid_do_area_effect(pasteroid_obj);
asp->final_death_time = _timestamp( fl2i(explosion_life*MILLISECONDS_PER_SECOND)/5 ); // Wait till 30% of vclip time before breaking the asteroid up.
asp->final_death_time = _timestamp( breakup_timestamp ); // Wait till 20% of vclip time before breaking the asteroid up, or use the specified delay
if ( hitpos ) {
asp->death_hit_pos = *hitpos;
} else {
@@ -1751,8 +1774,10 @@ void asteroid_hit( object * pasteroid_obj, object * other_obj, vec3d * hitpos, f
weapon_info *wip;
wip = &Weapon_info[Weapons[other_obj->instance].weapon_info_index];
// If the weapon didn't play any impact animation, play custom asteroid impact animation
if (!wip->impact_weapon_expl_effect.isValid()) {
particle::create( hitpos, &vmd_zero_vector, 0.0f, Asteroid_impact_explosion_radius, Asteroid_impact_explosion_ani );
if (!wip->impact_weapon_expl_effect.isValid() && Asteroid_impact_explosion_ani.isValid()) {
auto source = particle::ParticleManager::get()->createSource(Asteroid_impact_explosion_ani);
source->setHost(std::make_unique<EffectHostVector>(*hitpos, vmd_identity_matrix, vmd_zero_vector));
source->finishCreation();
}
}
}
@@ -2209,7 +2234,7 @@ static void asteroid_parse_section()
}
return;
}
asteroid_list.push_back(asteroid_t);
asteroid_list.push_back(std::move(asteroid_t));
asteroid_p = &asteroid_list[asteroid_list.size() - 1];
}
@@ -2307,13 +2332,13 @@ static void asteroid_parse_section()
thisType.type_name.c_str(),
asteroid_p->name);
} else {
asteroid_p->subtypes.push_back(thisType);
asteroid_p->subtypes.push_back(std::move(thisType));
}
}
}
if (optional_string("$Detail distance:")) {
asteroid_p->num_detail_levels = (int)stuff_int_list(asteroid_p->detail_distance, MAX_ASTEROID_DETAIL_LEVELS, RAW_INTEGER_TYPE);
asteroid_p->num_detail_levels = sz2i(stuff_int_list(asteroid_p->detail_distance, MAX_ASTEROID_DETAIL_LEVELS, ParseLookupType::RAW_INTEGER_TYPE));
}
if (optional_string("$Max Speed:")) {
@@ -2330,13 +2355,25 @@ static void asteroid_parse_section()
asteroid_p->damage_type_idx_sav = damage_type_add(buf);
asteroid_p->damage_type_idx = asteroid_p->damage_type_idx_sav;
}
if(optional_string("$Explosion Animations:")){
stuff_fireball_index_list(asteroid_p->explosion_bitmap_anims, asteroid_p->name);
if (optional_string("$Explosion Effect:")) {
asteroid_p->end_particles = particle::util::parseEffect(asteroid_p->name);
} else {
if(optional_string("$Explosion Animations:")){
stuff_fireball_index_list(asteroid_p->explosion_bitmap_anims, asteroid_p->name);
}
if (optional_string("$Explosion Radius Mult:")) {
stuff_float(&asteroid_p->fireball_radius_multiplier);
}
}
if (optional_string("$Explosion Radius Mult:")) {
stuff_float(&asteroid_p->fireball_radius_multiplier);
if (optional_string("$Breakup Delay:")) {
stuff_float(&asteroid_p->breakup_delay.emplace());
}
if (asteroid_p->end_particles.isValid() && !asteroid_p->breakup_delay.has_value()) {
error_display(0, "Asteroid %s has an explosion effect but no breakup delay!", asteroid_p->name);
}
if (optional_string("$Expl inner rad:")){
@@ -2461,18 +2498,52 @@ static void asteroid_parse_tbl(const char* filename)
required_string("#End");
if (optional_string("$Impact Explosion:")) {
char impact_ani_file[MAX_FILENAME_LEN];
stuff_string(impact_ani_file, F_NAME, MAX_FILENAME_LEN);
if (VALID_FNAME(impact_ani_file)) {
int num_frames;
Asteroid_impact_explosion_ani = bm_load_animation(impact_ani_file, &num_frames, nullptr, nullptr, nullptr, true);
}
if (optional_string("$Impact Explosion Effect:")) {
Asteroid_impact_explosion_ani = particle::util::parseEffect();
}
else {
char impact_ani_file[MAX_FILENAME_LEN] = {0};
float Asteroid_impact_explosion_radius = 0.0f;
int num_frames;
if (optional_string("$Impact Explosion Radius:")) {
stuff_float(&Asteroid_impact_explosion_radius);
if (optional_string("$Impact Explosion:")) {
stuff_string(impact_ani_file, F_NAME, MAX_FILENAME_LEN);
}
if (optional_string("$Impact Explosion Radius:")) {
stuff_float(&Asteroid_impact_explosion_radius);
}
if(VALID_FNAME(impact_ani_file)) {
Asteroid_impact_explosion_ani = particle::ParticleManager::get()->addEffect(particle::ParticleEffect(
"", //Name
::util::UniformFloatRange(1.f), //Particle num
particle::ParticleEffect::Duration::ONETIME, //Single Particle Emission
::util::UniformFloatRange(), //No duration
::util::UniformFloatRange(-1.f), //Single particle only
particle::ParticleEffect::ShapeDirection::ALIGNED, //Particle direction
::util::UniformFloatRange(), //Velocity Inherit
false, //Velocity Inherit absolute?
nullptr, //Velocity volume
::util::UniformFloatRange(), //Velocity volume multiplier
particle::ParticleEffect::VelocityScaling::NONE, //Velocity directional scaling
std::nullopt, //Orientation-based velocity
std::nullopt, //Position-based velocity
nullptr, //Position volume
particle::ParticleEffectHandle::invalid(), //Trail
1.f, //Chance
false, //Affected by detail
-1.f, //Culling range multiplier
false, //Disregard Animation Length. Must be true for everything using particle::Anim_bitmap_X
false, //Don't reverse animation
false, //parent local
false, //ignore velocity inherit if parented
false, //position velocity inherit absolute?
std::nullopt, //Local velocity offset
std::nullopt, //Local offset
::util::UniformFloatRange(-1.f), //Lifetime
::util::UniformFloatRange(Asteroid_impact_explosion_radius), //Radius
bm_load_animation(impact_ani_file, &num_frames, nullptr, nullptr, nullptr, true))); //Bitmap
}
}
if (optional_string("$Briefing Icon Closeup Model:")) {
@@ -2507,24 +2578,21 @@ int get_asteroid_index(const char* asteroid_name)
return -1;
}
// For FRED. Gets a list of unique asteroid subtype names
SCP_vector<SCP_string> get_list_valid_asteroid_subtypes()
// Returns the list of unique asteroid subtype names.
// List is cached after the first call since Asteroid_info cannot change during an engine instance.
const SCP_vector<SCP_string>& get_list_valid_asteroid_subtypes()
{
SCP_vector<SCP_string> list;
static SCP_vector<SCP_string> list;
for (const auto& this_asteroid : Asteroid_info) {
if (this_asteroid.type != ASTEROID_TYPE_DEBRIS) {
for (const auto& subtype : this_asteroid.subtypes) {
bool exists = false;
for (const auto& entry : list) {
if (subtype.type_name == entry) {
exists = true;
if (list.empty()) {
for (const auto& this_asteroid : Asteroid_info) {
if (this_asteroid.type != ASTEROID_TYPE_DEBRIS) {
for (const auto& subtype : this_asteroid.subtypes) {
// Only add unique names
if (std::find(list.begin(), list.end(), subtype.type_name) == list.end()) {
list.push_back(subtype.type_name);
}
}
if (!exists) {
list.push_back(subtype.type_name);
}
}
}
}
@@ -2532,6 +2600,7 @@ SCP_vector<SCP_string> get_list_valid_asteroid_subtypes()
return list;
}
static void verify_asteroid_splits()
{
@@ -2552,7 +2621,7 @@ static void verify_asteroid_splits()
}
// Replace splits with only valid splits
Asteroid_info[i].split_info = splits_t;
Asteroid_info[i].split_info = std::move(splits_t);
}
}
@@ -2580,6 +2649,11 @@ static void verify_asteroid_list()
if (found)
continue;
if (asteroid_list.empty()) {
mprintf(("%s asteroid not found and no fallback available in asteroid_list.\n", asteroid_size[i].c_str()));
continue;
}
//Left this as a log print instead of a Warning because of retail-Mjn
mprintf(("%s asteroid not found. Using asteroid %s\n", asteroid_size[i].c_str(), asteroid_list[0].name));
asteroid_list[0].type = i;
@@ -2622,8 +2696,7 @@ static void verify_asteroid_list()
*/
void asteroid_init()
{
Asteroid_impact_explosion_ani = -1;
Asteroid_impact_explosion_radius = 20.0; // retail value
Asteroid_impact_explosion_ani = particle::ParticleEffectHandle::invalid();
Asteroid_icon_closeup_model[0] = '\0';
vm_vec_make(&Asteroid_icon_closeup_position, 0.0f, 0.0f, -334.0f); // magic numbers from retail
Asteroid_icon_closeup_zoom = 0.5f; // magic number from retail
@@ -2635,8 +2708,10 @@ void asteroid_init()
parse_modular_table("*-ast.tbm", asteroid_parse_tbl);
//No asteroids defined. Bail!
if (asteroid_list.empty())
if (asteroid_list.empty()) {
mprintf(("No asteroids defined in asteroid.tbl or any modular tables. Asteroid fields will not function.\n"));
return;
}
// now verify the asteroids were found and put them in the correct order
verify_asteroid_list();
@@ -2644,8 +2719,11 @@ void asteroid_init()
// now that Asteroid_info is filled in we can verify the asteroid splits and set their indecies
verify_asteroid_splits();
if (Asteroid_impact_explosion_ani == -1) {
Error(LOCATION, "Missing valid asteroid impact explosion definition in asteroid.tbl!");
if (!Asteroid_impact_explosion_ani.isValid()) {
// this will always be missing on a standalone
if ( !Is_standalone ) {
Error(LOCATION, "Missing valid asteroid impact explosion definition in asteroid.tbl!");
}
}
if (Asteroid_icon_closeup_model[0] == '\0')
+27 -5
View File
@@ -16,6 +16,8 @@
#include "globalincs/pstypes.h"
#include "object/object_flags.h"
#include "io/timer.h"
#include "particle/ParticleSource.h"
#include "math/vecmat.h"
class object;
class polymodel;
@@ -79,7 +81,9 @@ public:
float initial_asteroid_strength; // starting strength of asteroid
SCP_vector< asteroid_split_info > split_info;
SCP_vector<int> explosion_bitmap_anims;
float fireball_radius_multiplier; // the model radius is multiplied by this to determine the fireball size
float fireball_radius_multiplier; // the model radius is multiplied by this to determine the fireball size
particle::ParticleEffectHandle end_particles;
std::optional<float> breakup_delay;
SCP_string display_name; // only used for hud targeting display and for debris
float spawn_weight; // debris only, relative proportion to spawn compared to other types in its asteroid field
float gravity_const; // multiplier for mission gravity
@@ -90,7 +94,7 @@ public:
rotational_vel_multiplier(1), damage_type_idx(0),
damage_type_idx_sav( -1 ), inner_rad( 0 ), outer_rad( 0 ),
damage( 0 ), blast( 0 ), initial_asteroid_strength( 0 ),
fireball_radius_multiplier( -1 ), spawn_weight( 1 ), gravity_const( 0 )
fireball_radius_multiplier( -1 ), end_particles( particle::ParticleEffectHandle::invalid() ), breakup_delay( std::nullopt ), spawn_weight( 1 ), gravity_const( 0 )
{
name[ 0 ] = 0;
display_name = "";
@@ -130,7 +134,7 @@ typedef enum {
FT_PASSIVE
} field_type_t;
typedef struct asteroid_field {
struct asteroid_field {
vec3d min_bound; // Minimum range of field.
vec3d max_bound; // Maximum range of field.
float bound_rad;
@@ -147,7 +151,24 @@ typedef struct asteroid_field {
bool enhanced_visibility_checks; // if true then range checks are overridden for spawning and wrapping asteroids in the field
SCP_vector<SCP_string> target_names; // default retail behavior is to just throw at the first big ship in the field
} asteroid_field;
asteroid_field()
{
num_initial_asteroids = 0; // disable the field by default
speed = 0.0f;
vm_vec_make(&min_bound, -1000.0f, -1000.0f, -1000.0f);
vm_vec_make(&max_bound, 1000.0f, 1000.0f, 1000.0f);
vm_vec_make(&inner_min_bound, -500.0f, -500.0f, -500.0f);
vm_vec_make(&inner_max_bound, 500.0f, 500.0f, 500.0f);
has_inner_bound = false;
field_type = FT_ACTIVE;
debris_genre = DG_ASTEROID;
enhanced_visibility_checks = false;
bound_rad = 0.0f;
vel = ZERO_VECTOR;
// the vectors default-construct to empty
}
};
extern SCP_vector< asteroid_info > Asteroid_info;
extern asteroid Asteroids[MAX_ASTEROIDS];
@@ -179,7 +200,8 @@ void asteroid_show_brackets();
void asteroid_target_closest_danger();
void asteroid_add_target(object* objp);
int get_asteroid_index(const char* asteroid_name);
SCP_vector<SCP_string> get_list_valid_asteroid_subtypes();
const SCP_vector<SCP_string>& get_list_valid_asteroid_subtypes();
int get_asteroid_subtype_index_by_name(const SCP_string& name, int asteroid_idx);
// extern for the lab
void asteroid_load(int asteroid_info_index, int asteroid_subtype);
+10 -9
View File
@@ -70,7 +70,7 @@ struct bitmap_entry {
// Stuff to keep track of usage
ubyte preloaded; //!< If set, then this was loaded from the lst file
int preload_count; //!< how many times this gets used in game, for unlocking
ushort used_flags; //!< What flags it was accessed thru
uint used_flags; //!< What flags it was accessed thru
int load_count;
bitmap bm; //!< Bitmap info
@@ -93,14 +93,15 @@ struct bitmap_slot {
};
// image specific lock functions
void bm_lock_ani( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags );
void bm_lock_dds( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags );
void bm_lock_png( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags );
void bm_lock_apng( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags );
void bm_lock_jpg( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags );
void bm_lock_pcx( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags );
void bm_lock_tga( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags );
void bm_lock_user( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags, bool convert = true );
void bm_lock_ani( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, uint flags );
void bm_lock_dds( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, uint flags );
void bm_lock_png( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, uint flags );
void bm_lock_apng( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, uint flags );
void bm_lock_jpg( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, uint flags );
void bm_lock_pcx( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, uint flags );
void bm_lock_tga( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, uint flags );
void bm_lock_user( int handle, bitmap_slot *bs, bitmap *bmp, int bpp, uint flags, bool convert = true );
void bm_lock_ktx1(int handle, bitmap_slot* bs, bitmap* bmp, int bpp, uint flags);
const size_t BM_BLOCK_SIZE = 4096;
+124 -15
View File
@@ -32,6 +32,7 @@
#include "tgautils/tgautils.h"
#include "tracing/Monitor.h"
#include "tracing/tracing.h"
#include "ktxutils/ktxutils.h"
#include <cctype>
#include <climits>
@@ -59,8 +60,8 @@ size_t bm_texture_ram = 0;
int Bm_paging = 0;
// Extension type lists
const BM_TYPE bm_type_list[] = { BM_TYPE_DDS, BM_TYPE_TGA, BM_TYPE_PNG, BM_TYPE_JPG, BM_TYPE_PCX };
const char *bm_ext_list[] = { ".dds", ".tga", ".png", ".jpg", ".pcx" };
const BM_TYPE bm_type_list[] = { BM_TYPE_DDS, BM_TYPE_TGA, BM_TYPE_PNG, BM_TYPE_JPG, BM_TYPE_PCX, BM_TYPE_KTX };
const char* bm_ext_list[] = { ".dds", ".tga", ".png", ".jpg", ".pcx", ".ktx" };
const int BM_NUM_TYPES = sizeof(bm_type_list) / sizeof(bm_type_list[0]);
const BM_TYPE bm_ani_type_list[] = { BM_TYPE_EFF, BM_TYPE_ANI, BM_TYPE_PNG };
@@ -205,7 +206,7 @@ void clear_bm_lookup_cache() {
/**
* Converts the bitmap referenced by bmp to the type specified by flags
*/
static void bm_convert_format(bitmap *bmp, ushort flags);
static void bm_convert_format(bitmap *bmp, uint flags);
/**
* Frees a bitmap's data if it can
@@ -639,7 +640,7 @@ int bm_create_3d(int bpp, int w, int h, int d, void* data) {
return n;
}
void bm_convert_format(bitmap *bmp, ushort flags) {
void bm_convert_format(bitmap *bmp, uint flags) {
int idx;
// no transparency for 24 bpp images
@@ -901,7 +902,7 @@ void bm_get_frame_usage(int *ntotal, int *nnew) {
#endif
}
int bm_get_info(int handle, int *w, int * h, ushort* flags, int *nframes, int *fps) {
int bm_get_info(int handle, int *w, int * h, uint* flags, int *nframes, int *fps) {
bitmap * bmp;
if (!bm_inited) return -1;
@@ -1055,6 +1056,24 @@ int bm_is_compressed(int num) {
case BM_TYPE_CUBEMAP_DXT5:
return DDS_CUBEMAP_DXT5;
case BM_TYPE_ETC2_RGB:
return KTX_ETC2_RGB;
case BM_TYPE_ETC2_SRGB:
return KTX_ETC2_SRGB;
case BM_TYPE_ETC2_RGBA_EAC:
return KTX_ETC2_RGBA_EAC;
case BM_TYPE_ETC2_SRGBA_EAC:
return KTX_ETC2_SRGBA_EAC;
case BM_TYPE_ETC2_RGBA1:
return KTX_ETC2_RGB_A1;
case BM_TYPE_ETC2_SRGBA1:
return KTX_ETC2_SRGB_A1;
default:
return 0;
}
@@ -1148,6 +1167,16 @@ static int bm_load_info(BM_TYPE type, const char *filename, CFILE *img_cfp, int
return -1;
}
}
// its a KTX file
else if (type == BM_TYPE_KTX) {
int ktx_ct;
int ktx_error = ktx1_read_header(filename, img_cfp, w, h, bpp, &ktx_ct, mm_lvl, size);
if (ktx_error != KTX1_ERROR_NONE) {
mprintf(("ktx: could not open '%s'\n", filename));
return -1;
}
*c_type = (BM_TYPE)ktx_ct;
}
// if its a tga file
else if (type == BM_TYPE_TGA) {
int tga_error = targa_read_header(filename, img_cfp, w, h, bpp, NULL);
@@ -1361,6 +1390,8 @@ bool bm_load_and_parse_eff(const char *filename, int dir_type, int *nframes, int
c_type = BM_TYPE_JPG;
} else if (!stricmp(NOX("pcx"), ext)) {
c_type = BM_TYPE_PCX;
} else if (!stricmp(NOX("ktx"), ext)) {
c_type = BM_TYPE_KTX;
} else {
mprintf(("BMPMAN: Unknown file type in EFF parse!\n"));
return false;
@@ -1390,7 +1421,7 @@ bool bm_load_and_parse_eff(const char *filename, int dir_type, int *nframes, int
/**
* Lock an image files data into memory
*/
static int bm_load_image_data(int handle, int bpp, ushort flags, bool nodebug)
static int bm_load_image_data(int handle, int bpp, uint flags, bool nodebug)
{
BM_TYPE c_type = BM_TYPE_NONE;
int true_bpp;
@@ -1482,6 +1513,10 @@ static int bm_load_image_data(int handle, int bpp, ushort flags, bool nodebug)
bm_lock_user(handle, bs, bmp, true_bpp, flags, false);
break;
case BM_TYPE_KTX:
bm_lock_ktx1(handle, bs, bmp, true_bpp, flags);
break;
default:
Warning(LOCATION, "Unsupported type in bm_lock -- %d\n", c_type);
return -1;
@@ -1941,7 +1976,7 @@ int bm_load_sub_slow(const char *real_filename, const int num_ext, const char **
return -1;
}
bitmap * bm_lock(int handle, int bpp, ushort flags, bool nodebug) {
bitmap * bm_lock(int handle, int bpp, uint flags, bool nodebug) {
bitmap *bmp;
Assertion(bm_inited, "bmpman must be initialized before this function can be called!");
@@ -2038,7 +2073,7 @@ bitmap * bm_lock(int handle, int bpp, ushort flags, bool nodebug) {
return bmp;
}
void bm_lock_ani(int /*handle*/, bitmap_slot *bs, bitmap* /*bmp*/, int bpp, ushort flags) {
void bm_lock_ani(int /*handle*/, bitmap_slot *bs, bitmap* /*bmp*/, int bpp, uint flags) {
anim *the_anim;
anim_instance *the_anim_instance;
bitmap *bm;
@@ -2164,7 +2199,7 @@ void bm_lock_ani(int /*handle*/, bitmap_slot *bs, bitmap* /*bmp*/, int bpp, usho
}
void bm_lock_apng(int /*handle*/, bitmap_slot *bs, bitmap *bmp, int bpp, ushort /*flags*/) {
void bm_lock_apng(int /*handle*/, bitmap_slot *bs, bitmap *bmp, int bpp, uint /*flags*/) {
auto be = &bs->entry;
int first_frame = be->info.ani.first_frame;
auto first_entry = bm_get_entry(first_frame);
@@ -2214,7 +2249,7 @@ void bm_lock_apng(int /*handle*/, bitmap_slot *bs, bitmap *bmp, int bpp, ushort
}
void bm_lock_dds(int handle, bitmap_slot *bs, bitmap *bmp, int /*bpp*/, ushort /*flags*/) {
void bm_lock_dds(int handle, bitmap_slot *bs, bitmap *bmp, int /*bpp*/, uint /*flags*/) {
ubyte *data = NULL;
int error;
ubyte dds_bpp = 0;
@@ -2278,7 +2313,7 @@ void bm_lock_dds(int handle, bitmap_slot *bs, bitmap *bmp, int /*bpp*/, ushort /
#endif
}
void bm_lock_jpg(int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort /*flags*/) {
void bm_lock_jpg(int handle, bitmap_slot *bs, bitmap *bmp, int bpp, uint /*flags*/) {
ubyte *data = NULL;
int d_size = 0;
int jpg_error = JPEG_ERROR_INVALID;
@@ -2325,7 +2360,7 @@ void bm_lock_jpg(int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort /*fla
#endif
}
void bm_lock_pcx(int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags) {
void bm_lock_pcx(int handle, bitmap_slot *bs, bitmap *bmp, int bpp, uint flags) {
ubyte *data;
int pcx_error;
char filename[MAX_FILENAME_LEN];
@@ -2370,7 +2405,7 @@ void bm_lock_pcx(int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags
bm_convert_format(bmp, flags);
}
void bm_lock_png(int handle, bitmap_slot *bs, bitmap *bmp, int /*bpp*/, ushort /*flags*/) {
void bm_lock_png(int handle, bitmap_slot *bs, bitmap *bmp, int /*bpp*/, uint /*flags*/) {
ubyte *data = NULL;
//assume 32 bit - libpng should expand everything
int d_size;
@@ -2415,7 +2450,7 @@ void bm_lock_png(int handle, bitmap_slot *bs, bitmap *bmp, int /*bpp*/, ushort /
#endif
}
void bm_lock_tga(int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags) {
void bm_lock_tga(int handle, bitmap_slot *bs, bitmap *bmp, int bpp, uint flags) {
ubyte *data = NULL;
int byte_size;
char filename[MAX_FILENAME_LEN];
@@ -2474,7 +2509,7 @@ void bm_lock_tga(int handle, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags
bm_convert_format(bmp, flags);
}
void bm_lock_user(int /*handle*/, bitmap_slot *bs, bitmap *bmp, int bpp, ushort flags, bool convert) {
void bm_lock_user(int /*handle*/, bitmap_slot *bs, bitmap *bmp, int bpp, uint flags, bool convert) {
auto be = &bs->entry;
// Unload any existing data
@@ -2767,6 +2802,21 @@ void bm_page_in_texture(int bitmapnum, int nframes) {
frame_entry->used_flags = BMP_TEX_CUBEMAP;
continue;
case BM_TYPE_ETC2_RGB:
case BM_TYPE_ETC2_SRGB:
frame_entry->used_flags = BMP_TEX_ETC2_RGB8;
continue;
case BM_TYPE_ETC2_RGBA1:
case BM_TYPE_ETC2_SRGBA1:
frame_entry->used_flags = BMP_TEX_ETC2_RGBA1;
continue;
case BM_TYPE_ETC2_RGBA_EAC:
case BM_TYPE_ETC2_SRGBA_EAC:
frame_entry->used_flags = BMP_TEX_ETC2_RGBA8;
continue;
default:
continue;
}
@@ -2812,6 +2862,21 @@ void bm_page_in_xparent_texture(int bitmapnum, int nframes) {
entry->used_flags = BMP_TEX_CUBEMAP;
continue;
case BM_TYPE_ETC2_RGB:
case BM_TYPE_ETC2_SRGB:
entry->used_flags = BMP_TEX_ETC2_RGB8;
continue;
case BM_TYPE_ETC2_RGBA1:
case BM_TYPE_ETC2_SRGBA1:
entry->used_flags = BMP_TEX_ETC2_RGBA1;
continue;
case BM_TYPE_ETC2_RGBA_EAC:
case BM_TYPE_ETC2_SRGBA_EAC:
entry->used_flags = BMP_TEX_ETC2_RGBA8;
continue;
default:
continue;
}
@@ -3432,3 +3497,47 @@ SDL_Surface* bm_to_sdl_surface(int handle) {
return bitmapSurface;
}
// copied bm_lock_dds and adjusted for ktx. missing BIG_ENDIAN
void bm_lock_ktx1(int handle, bitmap_slot* bs, bitmap* bmp, int bpp, uint flags)
{
(void)bpp;
(void)flags;
ubyte* data = nullptr;
ubyte ktx_bpp = 0;
int ktx_error = KTX1_ERROR_NONE;
char filename[MAX_FILENAME_LEN];
auto be = &bs->entry;
bm_free_data(bs);
Assert(be->mem_taken > 0);
Assert(&be->bm == bmp);
data = (ubyte*)bm_malloc(handle, be->mem_taken);
if (!data)
return;
memset(data, 0, be->mem_taken);
// make sure we are using the correct filename in the case of an EFF.
// this will populate filename[] whether it's EFF or not
EFF_FILENAME_CHECK;
ktx_error = ktx1_read_bitmap(filename, data, &ktx_bpp);
if (ktx_error != KTX1_ERROR_NONE)
{
mprintf(("KTX: read_bitmap error=%d file='%s'\n", ktx_error, filename));
bm_free_data(bs);
return;
}
bmp->bpp = ktx_bpp;
bmp->data = (ptr_u)data;
bmp->flags = 0;
#ifdef BMPMAN_NDEBUG
Assert(be->data_size > 0);
#endif
}
+20 -5
View File
@@ -34,6 +34,7 @@
// Flag positions for bitmap.flags
// ***** NOTE: bitmap.flags is an 8-bit value, no more BMP_TEX_* flags can be added unless the type is changed!! ******
// Update: bitmap.flags type changed to 16-bit
#define BMP_AABITMAP (1<<0) //!< antialiased bitmap
#define BMP_TEX_XPARENT (1<<1) //!< transparent texture
#define BMP_TEX_OTHER (1<<2) //!< so we can identify all "normal" textures
@@ -43,9 +44,14 @@
#define BMP_TEX_BC7 (1<<6) //!< BC7 compressed 8r8g8b8a (32bit)
#define BMP_TEX_CUBEMAP (1<<7) //!< a texture made for cubic environment map
#define BMP_MASK_BITMAP (1<<8) //!< a bitmap that will be used for masking mouse interaction. Typically not used in render operations
#define BMP_TEX_ETC2_RGB8 (1 << 9) //!< ETC2 RGB compressed (24bit, no alpha)
#define BMP_TEX_ETC2_RGBA1 (1 << 10) //!< ETC2 RGBA1 compressed (24bit, 1 alpha)
#define BMP_TEX_ETC2_RGBA8 (1 << 11) //!< ETC2 RGBA8 compressed (32bit, 8 alpha)
// Combined flags
#define BMP_TEX_COMP ( BMP_TEX_DXT1 | BMP_TEX_DXT3 | BMP_TEX_DXT5 | BMP_TEX_BC7 ) //!< Compressed textures
#define BMP_TEX_COMP ( BMP_TEX_DXT1 | BMP_TEX_DXT3 | BMP_TEX_DXT5 | BMP_TEX_BC7 | BMP_TEX_ETC2_RGB8 |\
BMP_TEX_ETC2_RGBA1 | BMP_TEX_ETC2_RGBA8 ) //!< Compressed textures
#define BMP_TEX_NONCOMP ( BMP_TEX_XPARENT | BMP_TEX_OTHER ) //!< Non-compressed textures
#define BMP_TEX_ANY ( BMP_TEX_COMP | BMP_TEX_NONCOMP ) //!< Any texture
@@ -81,7 +87,15 @@ enum BM_TYPE
BM_TYPE_CUBEMAP_DXT3, //!< 32-bit cubemap (compressed cubemap surface)
BM_TYPE_CUBEMAP_DXT5, //!< 32-bit cubemap (compressed cubemap surface)
BM_TYPE_3D //!< 3D in-memory
BM_TYPE_3D, //!< 3D in-memory
BM_TYPE_KTX, //!< generic identifier for KTX1
BM_TYPE_ETC2_RGB, //!< 24 bit, no alpha
BM_TYPE_ETC2_SRGB, //!< 24 bit, no alpha, signed
BM_TYPE_ETC2_RGBA1, //!< 24 bit, 1 bit alpha
BM_TYPE_ETC2_SRGBA1, //!< 24 bit, 1 bit alpha, signed
BM_TYPE_ETC2_RGBA_EAC, //!< 32 bit, 8 bit alpha
BM_TYPE_ETC2_SRGBA_EAC //!< 32 bit, 8 bit alpha, signed
};
/**
@@ -96,7 +110,7 @@ struct bitmap
short rowsize; //!< What you need to add to go to next row
int bpp; //!< Requested bitdepth of each pixel. ( 7, 8, 15, 16, 24, 32)
int true_bpp; //!< The image's actual bitdepth
ushort flags; //!< Various texture type flags. @see BMPMAN_CONSTANTS
uint flags; //!< Various texture type flags. @see BMPMAN_CONSTANTS
ptr_u data; //!< Pointer to data, or maybe offset into VRAM.
ubyte *palette; /**< @brief Pointer to this bitmap's palette (if it has one).
* @details If BMP_NO_PALETTE_MAP flag is cleared, this palette just points to the screen palette. (gr_palette)
@@ -135,6 +149,7 @@ void bm_close();
#define BMP_FLAG_RENDER_TARGET_DYNAMIC (1<<1)
#define BMP_FLAG_CUBEMAP (1<<2)
#define BMP_FLAG_RENDER_TARGET_MIPMAP (1<<3)
#define BMP_FLAG_RENDER_TARGET_DEPTH_ATTACHMENT (1<<4)
/**
* @brief Allocates memory for the given handle.
@@ -344,7 +359,7 @@ int bm_load_either(const char *filename, int *nframes = NULL, int *fps = NULL, i
* @returns A pointer to the bitmap that's valid until bm_unlock is called if successful, or
* @returns NULL if unsuccessful
*/
bitmap* bm_lock(int handle, int bpp, ushort flags, bool nodebug = false);
bitmap* bm_lock(int handle, int bpp, uint flags, bool nodebug = false);
/**
* @brief Returns the image type of the given bitmap handle
@@ -387,7 +402,7 @@ int bm_is_valid(int handle);
* @returns The handle to the first frame on success, or
* @returns -1 on failure
*/
int bm_get_info(int handle, int *w = nullptr, int * h = nullptr, ushort* flags = nullptr, int *nframes = nullptr, int *fps = nullptr);
int bm_get_info(int handle, int *w = nullptr, int * h = nullptr, uint* flags = nullptr, int *nframes = nullptr, int *fps = nullptr);
/**
* @brief Gets 3D info on the bitmap indexed by handle.
+7
View File
@@ -73,6 +73,7 @@ static void parse_fov_func()
fov_default = value;
}
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
auto FovOption = options::OptionBuilder<float>("Graphics.FOV",
std::pair<const char*, int>{"Field Of View", 1703},
std::pair<const char*, int>{"The vertical field of view", 1704})
@@ -91,6 +92,7 @@ auto FovOption = options::OptionBuilder<float>("Graphics.FOV",
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})
@@ -107,6 +109,7 @@ auto CockpitFOVToggleOption = options::OptionBuilder<bool>("Graphics.CockpitFOVT
.importance(61)
.finish();
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
auto CockpitFovOption = options::OptionBuilder<float>("Graphics.CockpitFOV",
std::pair<const char*, int>{"Cockpit Field Of View", 1840},
std::pair<const char*, int>{"The vertical field of view for cockpit rendering. Only works if cockpits are active and cockpit FOV toggle is turned on.", 1841})
@@ -732,6 +735,7 @@ subtitle::subtitle(int in_x_pos, int in_y_pos, const char* in_text, const char*
text_buf = in_text;
sexp_replace_variable_names_with_values(text_buf);
sexp_container_replace_refs_with_values(text_buf);
// (message_translate_tokens is called when the subtitle is queued, so does not need to be called here)
in_text = text_buf.c_str();
}
@@ -1294,6 +1298,9 @@ float cam_get_bbox_dist(const object* viewer_obj, float preferred_distance, cons
return preferred_distance;
polymodel* pm = object_get_model(viewer_obj);
if (pm == nullptr)
return preferred_distance;
vec3d adjusted_bbox;
const vec3d* cam_vec = &cam_orient->vec.fvec;
+609
View File
@@ -0,0 +1,609 @@
#include "freespace.h"
#include "actions/Action.h"
#include "camera/camera.h"
#include "camera/photomode.h"
#include "controlconfig/controlsconfig.h"
#include "debugconsole/console.h"
#include "globalincs/alphacolors.h"
#include "graphics/2d.h"
#include "graphics/font.h"
#include "hud/hud.h"
#include "io/keycontrol.h"
#include "localization/localize.h"
#include "mission/missionmessage.h"
#include "object/object.h"
#include "parse/parselo.h"
#include "playerman/player.h"
#include "render/3d.h"
#include "scripting/global_hooks.h"
#include "sound/audiostr.h"
bool Photo_mode_active = false;
camid Photo_mode_id;
fix Photo_mode_saved_time_compression = F1_0;
bool Photo_mode_saved_lock_state = false;
bool Photo_mode_screenshot_queued_this_frame = false;
bool Photo_mode_allowed = true;
bool Photo_mode_audio_paused = false;
const float Photo_mode_turn_rate = PI_2; // radians/sec
constexpr float Photo_mode_move_speed = 90.0f;
constexpr float Photo_mode_boost_multiplier = 6.0f;
constexpr float Photo_mode_time_compression = 0.01f;
struct photo_mode_post_effect_state {
SCP_string name;
float intensity = 0.0f;
vec3d rgb = vmd_zero_vector;
int effect_idx = -1; // index into Post_processing_manager->getPostEffects()
};
enum class photo_mode_param_type {
INT_RANGE, // adjustable integer value with min/max
BOOL_TOGGLE // on/off toggle
};
enum class photo_mode_param_role {
NONE,
GRID_OVERLAY, // bool toggle that enables the rule of thirds grid
POST_EFFECT // drives a post processing effect by name
};
struct photo_mode_param {
const char* label; // display name
int xstr_id; // XSTR localization ID
photo_mode_param_type type;
photo_mode_param_role role;
// For POST_EFFECT role: post processing effect name, range, and values
const char* effect_name; // post processing effect to control (nullptr if role != POST_EFFECT)
int effect_idx; // index into Post_processing_manager->getPostEffects() (-1 if unresolved)
int min_val;
int max_val;
int value;
int saved_value;
// For BOOL_TOGGLE
bool bool_value;
};
// Parameter definitions
SCP_vector<photo_mode_param> Photo_mode_params = {
{"Grid", 1906, photo_mode_param_type::BOOL_TOGGLE, photo_mode_param_role::GRID_OVERLAY, nullptr, -1, 0, 0, 0, 0, false},
{"Saturation", 1903, photo_mode_param_type::INT_RANGE, photo_mode_param_role::POST_EFFECT, "saturation", -1, 0, 200, 100, 100, false},
{"Brightness", 1904, photo_mode_param_type::INT_RANGE, photo_mode_param_role::POST_EFFECT, "brightness", -1, 0, 200, 100, 100, false},
{"Contrast", 1905, photo_mode_param_type::INT_RANGE, photo_mode_param_role::POST_EFFECT, "contrast", -1, 0, 200, 100, 100, false},
};
SCP_vector<photo_mode_post_effect_state> Photo_mode_saved_post_effects;
int Photo_mode_selected_parameter = 0;
int Photo_mode_panel_width = 0;
int Photo_mode_panel_padding_w = 0;
void photo_mode_capture_post_effect_state()
{
Photo_mode_saved_post_effects.clear();
if (graphics::Post_processing_manager == nullptr) {
return;
}
const auto& post_effects = graphics::Post_processing_manager->getPostEffects();
Photo_mode_saved_post_effects.reserve(post_effects.size());
for (int i = 0; i < static_cast<int>(post_effects.size()); ++i) {
const auto& effect = post_effects[i];
photo_mode_post_effect_state state;
state.name = effect.name;
state.intensity = effect.intensity;
state.rgb = effect.rgb;
state.effect_idx = i;
Photo_mode_saved_post_effects.push_back(state);
}
// Resolve effect indices for each param.
// Done once here rather than on every frame or every key press.
for (auto& param : Photo_mode_params) {
if (param.role != photo_mode_param_role::POST_EFFECT) {
param.effect_idx = -1;
continue;
}
param.effect_idx = -1;
for (int j = 0; j < static_cast<int>(post_effects.size()); ++j) {
if (!stricmp(post_effects[j].name.c_str(), param.effect_name)) {
param.effect_idx = j;
break;
}
}
}
}
void photo_mode_apply_saved_post_effect_state(bool clear_saved_state)
{
if (graphics::Post_processing_manager == nullptr) {
if (clear_saved_state) {
Photo_mode_saved_post_effects.clear();
}
return;
}
const auto& post_effects = graphics::Post_processing_manager->getPostEffects();
for (const auto& saved_state : Photo_mode_saved_post_effects) {
if (saved_state.effect_idx < 0 || saved_state.effect_idx >= static_cast<int>(post_effects.size())) {
continue;
}
const auto& effect = post_effects[saved_state.effect_idx];
const int value = static_cast<int>(std::lround((saved_state.intensity - effect.add) * effect.div));
gr_post_process_set_effect(saved_state.name.c_str(), value, &saved_state.rgb);
}
if (clear_saved_state) {
Photo_mode_saved_post_effects.clear();
}
}
int photo_mode_get_saved_post_effect_value(int effect_idx)
{
int value = 100;
if (effect_idx < 0 || graphics::Post_processing_manager == nullptr) {
return value;
}
const auto& post_effects = graphics::Post_processing_manager->getPostEffects();
if (effect_idx >= static_cast<int>(post_effects.size())) {
return value;
}
for (const auto& saved_state : Photo_mode_saved_post_effects) {
if (saved_state.effect_idx != effect_idx) {
continue;
}
const auto& effect = post_effects[effect_idx];
value = static_cast<int>(std::lround((saved_state.intensity - effect.add) * effect.div));
break;
}
return value;
}
void photo_mode_sync_parameter_values_from_saved_state()
{
for (auto& param : Photo_mode_params) {
if (param.role == photo_mode_param_role::POST_EFFECT) {
param.saved_value = photo_mode_get_saved_post_effect_value(param.effect_idx);
param.value = param.saved_value;
} else if (param.type == photo_mode_param_type::BOOL_TOGGLE) {
param.bool_value = false;
}
}
Photo_mode_selected_parameter = 0;
}
void photo_mode_apply_parameter_values()
{
for (const auto& param : Photo_mode_params) {
if (param.role == photo_mode_param_role::POST_EFFECT) {
gr_post_process_set_effect(param.effect_name, param.value, nullptr);
}
}
}
SCP_string format_photo_mode_keybind(int action)
{
auto primary = Control_config[action].first.textify();
auto secondary = Control_config[action].second.textify();
if (primary.empty() && secondary.empty()) {
return XSTR("Unbound", 1907);
}
if (primary.empty()) {
return secondary;
}
if (secondary.empty()) {
return primary;
}
return primary + " / " + secondary;
}
void photo_mode_compute_hud_layout()
{
const auto old_font = font::get_current_fontnum();
font::set_font(font::FONT1);
int max_text_width = 0;
char buf[512];
auto measure = [&](const char* text) {
int w = 0;
gr_get_string_size(&w, nullptr, text);
if (w > max_text_width) max_text_width = w;
};
// Find the longest string
measure(XSTR("Photo Mode", 1892));
measure(XSTR("Controls", 1893));
measure(XSTR("Status", 1900));
measure(XSTR("Effects", 1914));
snprintf(buf, sizeof(buf), XSTR("Toggle: %s", 1894), format_photo_mode_keybind(TOGGLE_PHOTO_MODE).c_str());
measure(buf);
snprintf(buf, sizeof(buf), XSTR("Previous Filter: %s", 1895), format_photo_mode_keybind(PHOTO_MODE_FILTER_PREV).c_str());
measure(buf);
snprintf(buf, sizeof(buf), XSTR("Next Filter: %s", 1896), format_photo_mode_keybind(PHOTO_MODE_FILTER_NEXT).c_str());
measure(buf);
snprintf(buf, sizeof(buf), XSTR("Reset Filters: %s", 1897), format_photo_mode_keybind(PHOTO_MODE_FILTER_RESET).c_str());
measure(buf);
snprintf(buf, sizeof(buf), XSTR("Decrease Parameter: %s", 1898), format_photo_mode_keybind(PHOTO_MODE_PARAM_DECREASE).c_str());
measure(buf);
snprintf(buf, sizeof(buf), XSTR("Increase Parameter: %s", 1899), format_photo_mode_keybind(PHOTO_MODE_PARAM_INCREASE).c_str());
measure(buf);
// Use worst case placeholder values for the dynamic status strings so the
// panel width stays stable as these change during a session.
snprintf(buf, sizeof(buf), XSTR("Time Compression: %.2fx", 1901), 64.0f); measure(buf);
snprintf(buf, sizeof(buf), XSTR("Cam Pos: X %.1f Y %.1f Z %.1f", 1902), -99999.9f, -99999.9f, -99999.9f); measure(buf);
for (const auto& param : Photo_mode_params) {
if (param.type == photo_mode_param_type::INT_RANGE) {
snprintf(buf, sizeof(buf), "%s: %d", XSTR(param.label, param.xstr_id), param.max_val);
} else {
snprintf(buf, sizeof(buf), "%s: %s", XSTR(param.label, param.xstr_id), XSTR("Off", 1285));
}
measure(buf);
}
font::set_font(old_font);
Photo_mode_panel_padding_w = max_text_width / 10;
Photo_mode_panel_width = max_text_width + Photo_mode_panel_padding_w * 2;
}
void photo_mode_set_active(bool active)
{
if (active == Photo_mode_active) {
return;
}
if (active) {
if (!Photo_mode_allowed) {
return;
}
if ((Game_mode & GM_MULTIPLAYER) != 0 || Player_obj == nullptr || !(Game_mode & GM_IN_MISSION)) {
return;
}
if (Time_compression_locked) {
return;
}
if (Player_obj->type != OBJ_SHIP) {
return;
}
Photo_mode_saved_time_compression = Game_time_compression;
Photo_mode_saved_lock_state = Time_compression_locked;
Photo_mode_id = cam_create("Photo Mode", &Eye_position, &Eye_matrix);
if (!Photo_mode_id.isValid() || !cam_set_camera(Photo_mode_id)) {
cam_delete(Photo_mode_id);
Photo_mode_id = camid();
return;
}
set_time_compression(Photo_mode_time_compression, 0.0f);
lock_time_compression(true);
photo_mode_capture_post_effect_state();
photo_mode_sync_parameter_values_from_saved_state();
photo_mode_apply_parameter_values();
photo_mode_compute_hud_layout();
audiostream_pause_all();
message_pause_all();
Photo_mode_audio_paused = true;
Photo_mode_active = true;
mprintf(("Photo Mode enabled.\n"));
if (scripting::hooks::OnPhotoModeStarted->isActive()) {
scripting::hooks::OnPhotoModeStarted->run();
}
return;
}
cam_reset_camera();
cam_delete(Photo_mode_id);
Photo_mode_id = camid();
set_time_compression(f2fl(Photo_mode_saved_time_compression), 0.0f);
lock_time_compression(Photo_mode_saved_lock_state);
if (Photo_mode_audio_paused) {
audiostream_unpause_all();
message_resume_all();
Photo_mode_audio_paused = false;
}
photo_mode_apply_saved_post_effect_state(true);
for (auto& param : Photo_mode_params) {
if (param.type == photo_mode_param_type::INT_RANGE) {
param.value = 100;
param.saved_value = 100;
} else if (param.type == photo_mode_param_type::BOOL_TOGGLE) {
param.bool_value = false;
}
}
Photo_mode_selected_parameter = 0;
Photo_mode_active = false;
mprintf(("Photo Mode disabled.\n"));
if (scripting::hooks::OnPhotoModeEnded->isActive()) {
scripting::hooks::OnPhotoModeEnded->run();
}
}
void photo_mode_do_frame(float frame_time)
{
if (!Photo_mode_active || !Photo_mode_id.isValid()) {
return;
}
if (!Photo_mode_allowed) {
photo_mode_set_active(false);
return;
}
auto photo_mode = Photo_mode_id.getCamera();
if (photo_mode == nullptr) {
photo_mode_set_active(false);
return;
}
vec3d cam_pos = vmd_zero_vector;
matrix cam_orient = vmd_identity_matrix;
photo_mode->get_info(&cam_pos, &cam_orient);
angles delta_angles{};
float pitch = check_control_timef(PITCH_FORWARD) - check_control_timef(PITCH_BACK);
float heading = check_control_timef(YAW_RIGHT) - check_control_timef(YAW_LEFT);
float bank = check_control_timef(BANK_LEFT) - check_control_timef(BANK_RIGHT);
int axis[Action::NUM_VALUES] = {0};
control_get_axes_readings(axis, flRealframetime);
pitch += -f2fl(axis[Action::PITCH]);
heading += f2fl(axis[Action::HEADING]);
bank -= f2fl(axis[Action::BANK]);
CLAMP(pitch, -1.0f, 1.0f);
CLAMP(heading, -1.0f, 1.0f);
CLAMP(bank, -1.0f, 1.0f);
delta_angles.p = pitch * Photo_mode_turn_rate * frame_time;
delta_angles.h = heading * Photo_mode_turn_rate * frame_time;
delta_angles.b = bank * Photo_mode_turn_rate * frame_time;
matrix delta_orient = vmd_identity_matrix;
vm_angles_2_matrix(&delta_orient, &delta_angles);
vm_matrix_x_matrix(&cam_orient, &cam_orient, &delta_orient);
vm_fix_matrix(&cam_orient);
float speed = Photo_mode_move_speed;
if (check_control(AFTERBURNER)) {
speed *= Photo_mode_boost_multiplier;
}
float forward = check_control_timef(FORWARD_THRUST) - check_control_timef(REVERSE_THRUST);
float right = check_control_timef(RIGHT_SLIDE_THRUST) - check_control_timef(LEFT_SLIDE_THRUST);
float up = check_control_timef(UP_SLIDE_THRUST) - check_control_timef(DOWN_SLIDE_THRUST);
CLAMP(forward, -1.0f, 1.0f);
CLAMP(right, -1.0f, 1.0f);
CLAMP(up, -1.0f, 1.0f);
vm_vec_scale_add2(&cam_pos, &cam_orient.vec.fvec, forward * speed * frame_time);
vm_vec_scale_add2(&cam_pos, &cam_orient.vec.rvec, right * speed * frame_time);
vm_vec_scale_add2(&cam_pos, &cam_orient.vec.uvec, up * speed * frame_time);
photo_mode->set_rotation(&cam_orient);
photo_mode->set_position(&cam_pos);
}
void photo_mode_maybe_render_hud()
{
if (!Photo_mode_active || Photo_mode_screenshot_queued_this_frame || gr_is_screenshot_requested()) {
return;
}
if (!Photo_mode_allowed) {
photo_mode_set_active(false);
return;
}
auto photo_mode = Photo_mode_id.getCamera();
if (photo_mode == nullptr) {
return;
}
vec3d cam_pos = vmd_zero_vector;
matrix cam_orient = vmd_identity_matrix;
photo_mode->get_info(&cam_pos, &cam_orient);
const auto toggle_keybind = format_photo_mode_keybind(TOGGLE_PHOTO_MODE);
const auto prev_filter_keybind = format_photo_mode_keybind(PHOTO_MODE_FILTER_PREV);
const auto next_filter_keybind = format_photo_mode_keybind(PHOTO_MODE_FILTER_NEXT);
const auto reset_filter_keybind = format_photo_mode_keybind(PHOTO_MODE_FILTER_RESET);
const auto decrease_param_keybind = format_photo_mode_keybind(PHOTO_MODE_PARAM_DECREASE);
const auto increase_param_keybind = format_photo_mode_keybind(PHOTO_MODE_PARAM_INCREASE);
gr_set_color_fast(&Color_silver);
const auto old_font = font::get_current_fontnum();
font::set_font(font::FONT1);
const int line_height = gr_get_font_height();
const int num_param_lines = static_cast<int>(Photo_mode_params.size());
const int panel_padding_h = line_height / 2;
const int panel_padding_w = Photo_mode_panel_padding_w;
const int panel_width = Photo_mode_panel_width;
const int panel_height = panel_padding_h * 2 + line_height * (15 + num_param_lines);
const int panel_x = gr_screen.center_offset_x + (gr_screen.center_w / 4);
const int panel_y = gr_screen.center_offset_y + gr_screen.center_h - panel_height - (gr_screen.center_h / 6);
gr_line(panel_x, panel_y, panel_x + panel_width, panel_y, GR_RESIZE_NONE);
gr_line(panel_x, panel_y + panel_height, panel_x + panel_width, panel_y + panel_height, GR_RESIZE_NONE);
gr_line(panel_x, panel_y, panel_x, panel_y + panel_height, GR_RESIZE_NONE);
gr_line(panel_x + panel_width, panel_y, panel_x + panel_width, panel_y + panel_height, GR_RESIZE_NONE);
gr_line(panel_x,
panel_y + line_height + panel_padding_h + 1,
panel_x + panel_width,
panel_y + line_height + panel_padding_h + 1,
GR_RESIZE_NONE);
bool grid_enabled = false;
for (const auto& param : Photo_mode_params) {
if (param.role == photo_mode_param_role::GRID_OVERLAY) {
grid_enabled = param.bool_value;
break;
}
}
if (grid_enabled) {
const int x1 = gr_screen.center_offset_x + gr_screen.center_w / 3;
const int x2 = gr_screen.center_offset_x + (gr_screen.center_w * 2) / 3;
const int y1 = gr_screen.center_offset_y + gr_screen.center_h / 3;
const int y2 = gr_screen.center_offset_y + (gr_screen.center_h * 2) / 3;
gr_line(x1, gr_screen.center_offset_y, x1, gr_screen.center_offset_y + gr_screen.center_h, GR_RESIZE_NONE);
gr_line(x2, gr_screen.center_offset_y, x2, gr_screen.center_offset_y + gr_screen.center_h, GR_RESIZE_NONE);
gr_line(gr_screen.center_offset_x, y1, gr_screen.center_offset_x + gr_screen.center_w, y1, GR_RESIZE_NONE);
gr_line(gr_screen.center_offset_x, y2, gr_screen.center_offset_x + gr_screen.center_w, y2, GR_RESIZE_NONE);
}
int line = panel_y + panel_padding_h;
const int text_x = panel_x + panel_padding_w;
gr_printf_no_resize(text_x, line, "%s", XSTR("Photo Mode", 1892));
line += line_height;
line += line_height;
gr_printf_no_resize(text_x, line, "%s", XSTR("Controls", 1893));
line += line_height;
gr_printf_no_resize(text_x, line, XSTR("Toggle: %s", 1894), toggle_keybind.c_str());
line += line_height;
gr_printf_no_resize(text_x, line, XSTR("Previous Filter: %s", 1895), prev_filter_keybind.c_str());
line += line_height;
gr_printf_no_resize(text_x, line, XSTR("Next Filter: %s", 1896), next_filter_keybind.c_str());
line += line_height;
gr_printf_no_resize(text_x, line, XSTR("Reset Filters: %s", 1897), reset_filter_keybind.c_str());
line += line_height;
gr_printf_no_resize(text_x, line, XSTR("Decrease Parameter: %s", 1898), decrease_param_keybind.c_str());
line += line_height;
gr_printf_no_resize(text_x, line, XSTR("Increase Parameter: %s", 1899), increase_param_keybind.c_str());
line += line_height;
line += line_height;
gr_printf_no_resize(text_x, line, "%s", XSTR("Status", 1900));
line += line_height;
gr_printf_no_resize(text_x, line, XSTR("Time Compression: %.2fx", 1901), f2fl(Game_time_compression));
line += line_height;
gr_printf_no_resize(text_x,
line,
XSTR("Cam Pos: X %.1f Y %.1f Z %.1f", 1902),
cam_pos.xyz.x,
cam_pos.xyz.y,
cam_pos.xyz.z);
line += line_height;
line += line_height;
gr_set_color_fast(&Color_silver);
gr_printf_no_resize(text_x, line, "%s", XSTR("Effects", 1914));
line += line_height;
for (int i = 0; i < static_cast<int>(Photo_mode_params.size()); ++i) {
const auto& param = Photo_mode_params[i];
gr_set_color_fast(Photo_mode_selected_parameter == i ? &Color_bright_white : &Color_silver);
if (param.type == photo_mode_param_type::INT_RANGE) {
gr_printf_no_resize(text_x, line, "%s: %d", XSTR(param.label, param.xstr_id), param.value);
} else if (param.type == photo_mode_param_type::BOOL_TOGGLE) {
gr_printf_no_resize(text_x, line, "%s: %s", XSTR(param.label, param.xstr_id),
param.bool_value ? XSTR("On", 1285) : XSTR("Off", 1286));
}
line += line_height;
}
font::set_font(old_font);
}
void photo_mode_clear_screenshot_queued_flag()
{
Photo_mode_screenshot_queued_this_frame = false;
}
void photo_mode_set_screenshot_queued_flag()
{
Photo_mode_screenshot_queued_this_frame = true;
}
void game_toggle_photo_mode()
{
photo_mode_set_active(!Photo_mode_active);
}
void game_set_photo_mode_allowed(bool allowed)
{
Photo_mode_allowed = allowed && ((Game_mode & GM_MULTIPLAYER) == 0);
if (!Photo_mode_allowed && Photo_mode_active) {
photo_mode_set_active(false);
}
}
bool game_get_photo_mode_allowed()
{
return Photo_mode_allowed && ((Game_mode & GM_MULTIPLAYER) == 0);
}
bool game_is_photo_mode_active()
{
return Photo_mode_active;
}
void game_cycle_photo_mode_filter(int direction)
{
if (!Photo_mode_active) {
return;
}
Photo_mode_selected_parameter = std::clamp(Photo_mode_selected_parameter + direction, 0, static_cast<int>(Photo_mode_params.size()) - 1);
}
void game_reset_photo_mode_filters()
{
if (!Photo_mode_active) {
return;
}
for (auto& param : Photo_mode_params) {
if (param.type == photo_mode_param_type::INT_RANGE) {
param.value = param.saved_value;
} else if (param.type == photo_mode_param_type::BOOL_TOGGLE) {
param.bool_value = false;
}
}
photo_mode_apply_parameter_values();
}
void game_adjust_photo_mode_filter_parameter(int delta)
{
if (!Photo_mode_active) {
return;
}
auto& param = Photo_mode_params[Photo_mode_selected_parameter];
if (param.type == photo_mode_param_type::BOOL_TOGGLE) {
param.bool_value = (delta > 0);
return;
}
param.value = std::clamp(param.value + delta, param.min_val, param.max_val);
photo_mode_apply_parameter_values();
}
+16
View File
@@ -0,0 +1,16 @@
#pragma once
void photo_mode_set_active(bool active);
void photo_mode_do_frame(float frame_time);
void photo_mode_maybe_render_hud();
void photo_mode_clear_screenshot_queued_flag();
void photo_mode_set_screenshot_queued_flag();
// mission-level permission to allow/disallow Photo Mode
void game_toggle_photo_mode();
void game_set_photo_mode_allowed(bool allowed);
bool game_get_photo_mode_allowed();
bool game_is_photo_mode_active();
void game_cycle_photo_mode_filter(int direction);
void game_reset_photo_mode_filters();
void game_adjust_photo_mode_filter_parameter(int delta);
+12 -11
View File
@@ -52,7 +52,7 @@ cf_pathtype Pathtypes[CF_MAX_PATH_TYPES] = {
// Root must be index 1!!
{ CF_TYPE_ROOT, "", ".mve .ogg", CF_TYPE_ROOT },
{ CF_TYPE_DATA, "data", ".cfg .txt", CF_TYPE_ROOT },
{ CF_TYPE_MAPS, "data" DIR_SEPARATOR_STR "maps", ".pcx .ani .eff .tga .jpg .png .dds", CF_TYPE_DATA },
{ CF_TYPE_MAPS, "data" DIR_SEPARATOR_STR "maps", ".pcx .ani .eff .tga .jpg .png .dds .ktx", CF_TYPE_DATA },
{ CF_TYPE_TEXT, "data" DIR_SEPARATOR_STR "text", ".txt .net", CF_TYPE_DATA },
{ CF_TYPE_MODELS, "data" DIR_SEPARATOR_STR "models", ".pof", CF_TYPE_DATA },
{ CF_TYPE_TABLES, "data" DIR_SEPARATOR_STR "tables", ".tbl .tbm .lua", CF_TYPE_DATA },
@@ -66,26 +66,26 @@ cf_pathtype Pathtypes[CF_MAX_PATH_TYPES] = {
{ CF_TYPE_VOICE_TRAINING, "data" DIR_SEPARATOR_STR "voice" DIR_SEPARATOR_STR "training", ".wav .ogg", CF_TYPE_VOICE },
{ CF_TYPE_MUSIC, "data" DIR_SEPARATOR_STR "music", ".wav .ogg", CF_TYPE_DATA },
{ CF_TYPE_MOVIES, "data" DIR_SEPARATOR_STR "movies", ".mve .msb .ogg .mp4 .srt .webm .png",CF_TYPE_DATA },
{ CF_TYPE_INTERFACE, "data" DIR_SEPARATOR_STR "interface", ".pcx .ani .dds .tga .eff .png .jpg .rml .rcss", CF_TYPE_DATA },
{ CF_TYPE_INTERFACE, "data" DIR_SEPARATOR_STR "interface", ".pcx .ani .dds .tga .eff .png .jpg .rml .rcss .ktx", CF_TYPE_DATA },
{ CF_TYPE_FONT, "data" DIR_SEPARATOR_STR "fonts", ".vf .ttf .otf", CF_TYPE_DATA },
{ CF_TYPE_EFFECTS, "data" DIR_SEPARATOR_STR "effects", ".ani .eff .pcx .neb .tga .jpg .png .dds .sdr", CF_TYPE_DATA },
{ CF_TYPE_HUD, "data" DIR_SEPARATOR_STR "hud", ".pcx .ani .eff .tga .jpg .png .dds", CF_TYPE_DATA },
{ CF_TYPE_EFFECTS, "data" DIR_SEPARATOR_STR "effects", ".ani .eff .pcx .neb .tga .jpg .png .dds .ktx .sdr .vert .frag .glsl", CF_TYPE_DATA },
{ CF_TYPE_HUD, "data" DIR_SEPARATOR_STR "hud", ".pcx .ani .eff .tga .jpg .png .dds .ktx", CF_TYPE_DATA },
{ CF_TYPE_PLAYERS, "data" DIR_SEPARATOR_STR "players", ".hcf", /* DON'T add pilot files here!! */ CF_TYPE_DATA },
{ CF_TYPE_PLAYER_IMAGES, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "images", ".pcx .png .dds", CF_TYPE_PLAYERS },
{ CF_TYPE_SQUAD_IMAGES, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "squads", ".pcx .png .dds", CF_TYPE_PLAYERS },
{ CF_TYPE_PLAYER_IMAGES, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "images", ".pcx .png .dds .ktx", CF_TYPE_PLAYERS },
{ CF_TYPE_SQUAD_IMAGES, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "squads", ".pcx .png .dds .ktx", CF_TYPE_PLAYERS },
{ CF_TYPE_SINGLE_PLAYERS, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "single", ".pl2 .cs2 .plr .csg .css .json", CF_TYPE_PLAYERS },
{ CF_TYPE_MULTI_PLAYERS, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "multi", ".plr .json", CF_TYPE_PLAYERS },
{ CF_TYPE_PLAYER_BINDS, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "presets", ".json", CF_TYPE_PLAYERS },
{ CF_TYPE_CACHE, "data" DIR_SEPARATOR_STR "cache", ".clr .tmp .bx", CF_TYPE_DATA }, //clr=cached color
{ CF_TYPE_MULTI_CACHE, "data" DIR_SEPARATOR_STR "multidata", ".pcx .png .jpg .dds .fs2 .txt", CF_TYPE_DATA },
{ CF_TYPE_MULTI_CACHE, "data" DIR_SEPARATOR_STR "multidata", ".pcx .png .jpg .dds .fs2 .txt .ktx", CF_TYPE_DATA },
{ CF_TYPE_MISSIONS, "data" DIR_SEPARATOR_STR "missions", ".fs2 .fc2 .ntl .ssv", CF_TYPE_DATA },
{ CF_TYPE_CONFIG, "data" DIR_SEPARATOR_STR "config", ".cfg .tbl .tbm .xml .csv", CF_TYPE_DATA },
{ CF_TYPE_DEMOS, "data" DIR_SEPARATOR_STR "demos", ".fsd", CF_TYPE_DATA },
{ CF_TYPE_CBANIMS, "data" DIR_SEPARATOR_STR "cbanims", ".pcx .ani .eff .tga .jpg .png .dds", CF_TYPE_DATA },
{ CF_TYPE_INTEL_ANIMS, "data" DIR_SEPARATOR_STR "intelanims", ".pcx .ani .eff .tga .jpg .png .dds", CF_TYPE_DATA },
{ CF_TYPE_CBANIMS, "data" DIR_SEPARATOR_STR "cbanims", ".pcx .ani .eff .tga .jpg .png .dds .ktx", CF_TYPE_DATA },
{ CF_TYPE_INTEL_ANIMS, "data" DIR_SEPARATOR_STR "intelanims", ".pcx .ani .eff .tga .jpg .png .dds .ktx", CF_TYPE_DATA },
{ CF_TYPE_SCRIPTS, "data" DIR_SEPARATOR_STR "scripts", ".lua .lc .fnl", CF_TYPE_DATA },
{ CF_TYPE_FICTION, "data" DIR_SEPARATOR_STR "fiction", ".txt", CF_TYPE_DATA },
{ CF_TYPE_FREDDOCS, "data" DIR_SEPARATOR_STR "freddocs", ".html", CF_TYPE_DATA }
{ CF_TYPE_FREDDOCS, "data" DIR_SEPARATOR_STR "freddocs", ".html .qch .css .png .jpg", CF_TYPE_DATA }
};
// clang-format on
@@ -186,6 +186,7 @@ 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);
buf[CFILE_ROOT_DIRECTORY_LEN - 1] = '\0';
// are we in a root directory?
@@ -902,7 +903,7 @@ static CFILE *cf_open_fill_cfblock(const char* source, int line, const char* ori
cfp->source_file = source;
cfp->line_num = line;
int pos = ftell(fp);
auto pos = ftell(fp);
if(pos == -1L)
pos = 0;
cf_init_lowlevel_read_code(cfp,0,filelength(fileno(fp)), 0 );
+1 -1
View File
@@ -143,7 +143,7 @@ void lz41_load_offsets(CFILE* cf)
int* offsets_ptr = cf->compression_info.offsets;
/* Seek to the first offset position, remember to consider the trailing ints */
fso_fseek(cf, ( ( sizeof(int) * cf->compression_info.num_offsets ) * -1 ) - (sizeof(int)*3 ), SEEK_END);
fso_fseek(cf, static_cast<int>( ( sizeof(int) * cf->compression_info.num_offsets ) * -1 ) - (sizeof(int)*3 ), SEEK_END);
for (block = 0; block < cf->compression_info.num_offsets; ++block)
{
auto bytes_read = fread(offsets_ptr++, sizeof(int), 1, cf->fp);
+9 -7
View File
@@ -532,7 +532,7 @@ void cf_build_pack_list( cf_root *root )
s_root.roottype = CF_ROOTTYPE_PACK;
s_root.cf_type = i;
temp_roots_sort.push_back(s_root);
temp_roots_sort.push_back(std::move(s_root));
}
}
@@ -1083,7 +1083,7 @@ void cf_search_root_pack(int root_index)
file.pathtype = path_type;
file.offset = find.offset;
files.push_back(file);
files.push_back(std::move(file));
//mprintf(( "Found pack file '%s'\n", find.filename ));
}
@@ -1356,7 +1356,7 @@ CFileLocation cf_find_file_location(const char* filespec, int pathtype, uint32_t
fclose(fp);
res.offset = 0;
res.full_name = longname;
res.full_name = std::move(longname);
res.name_ext = filespec;
return res;
@@ -1549,8 +1549,8 @@ CFileLocationExt cf_find_file_location_ext(const char *filename, const int ext_n
fclose(fp);
res.offset = 0;
res.full_name = longname;
res.name_ext = filespec_ext;
res.full_name = std::move(longname);
res.name_ext = std::move(filespec_ext);
return res;
}
@@ -2437,7 +2437,9 @@ int cf_create_default_path_string(SCP_string& path, int pathtype, const char* fi
}
if (!root) {
Assert( filename != NULL );
if (filename == nullptr) {
return 0;
}
path.assign(filename);
return 1;
}
@@ -2480,7 +2482,7 @@ void cfile_spew_pack_file_crcs()
my_time = time(NULL);
memset( datetime, 0, sizeof(datetime) );
snprintf(datetime, sizeof(datetime)-1, "%s", ctime(&my_time));
snprintf(datetime, sizeof(datetime), "%s", ctime(&my_time));
// ctime() adds a newline char, so we have to strip it off
datetime[strlen(datetime)-1] = '\0';
+2 -2
View File
@@ -135,7 +135,7 @@ void parse_cheat_table(const char* filename) {
}
if (shipSpawn) {
std::unique_ptr<CustomCheat> shipCheat(new SpawnShipCheat(code, msg, requireCheats, shipClass, shipName));
std::unique_ptr<CustomCheat> shipCheat(new SpawnShipCheat(code, std::move(msg), requireCheats, std::move(shipClass), std::move(shipName)));
if(customCheats.count(code) == 1) {
Warning(LOCATION, "A cheat for code '%s' already exists. It will be replaced.", code.c_str());
@@ -144,7 +144,7 @@ void parse_cheat_table(const char* filename) {
customCheats.emplace(code, std::move(shipCheat));
}
} else {
std::unique_ptr<CustomCheat> cheat(new CustomCheat(code, msg, requireCheats));
std::unique_ptr<CustomCheat> cheat(new CustomCheat(code, std::move(msg), requireCheats));
if(customCheats.count(code) == 1) {
Warning(LOCATION, "A cheat for code '%s' already exists. It will be replaced.", code.c_str());
+1 -1
View File
@@ -35,7 +35,7 @@ class SpawnShipCheat : public CustomCheat {
SCP_string shipName;
public:
SpawnShipCheat(SCP_string cheat_code, SCP_string cheat_msg, bool require_cheats_enabled, SCP_string class_name, SCP_string ship_name) : CustomCheat(cheat_code, cheat_msg, require_cheats_enabled),
SpawnShipCheat(SCP_string cheat_code, SCP_string cheat_msg, bool require_cheats_enabled, SCP_string class_name, SCP_string ship_name) : CustomCheat(std::move(cheat_code), std::move(cheat_msg), require_cheats_enabled),
shipClassName(std::move(class_name)),
shipName(std::move(ship_name)) { }
+8 -2
View File
@@ -534,6 +534,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 multithreading("-threads", nullptr, AT_INT);
char *Cmdline_start_mission = NULL;
int Cmdline_dis_collisions = 0;
@@ -572,6 +573,7 @@ bool Cmdline_lua_devmode = false;
bool Cmdline_override_data = false;
bool Cmdline_show_imgui_debug = false;
bool Cmdline_vulkan = false;
int Cmdline_multithreading = 1;
// Other
cmdline_parm get_flags_arg(GET_FLAGS_STRING, "Output the launcher flags file", AT_STRING);
@@ -1320,7 +1322,7 @@ static void handle_unix_modlist(char **modlist, size_t *len)
for (char *cur_mod = strtok(*modlist, ","); cur_mod != NULL; cur_mod = strtok(NULL, ",")) {
SCP_string path = get_real_mod_path(cur_mod);
mod_paths.push_back(path);
mod_paths.push_back(std::move(path));
}
// create new char[] to replace modlist
@@ -1412,7 +1414,7 @@ static json_t* json_get_v1() {
auto voices = speech_enumerate_voices();
for (auto& voice : voices) {
json_array_append_new(voices_array, json_string(voice.c_str()));
json_array_append_new(voices_array, json_string(voice.second.c_str()));
}
json_object_set_new(root, "voices", voices_array);
@@ -2407,6 +2409,10 @@ bool SetCmdlineParams()
}
}
if (multithreading.found()) {
Cmdline_multithreading = abs(multithreading.get_int());
}
return true;
}
+1
View File
@@ -159,6 +159,7 @@ extern bool Cmdline_lua_devmode;
extern bool Cmdline_override_data;
extern bool Cmdline_show_imgui_debug;
extern bool Cmdline_vulkan;
extern int Cmdline_multithreading;
enum class WeaponSpewType { NONE = 0, STANDARD, ALL };
extern WeaponSpewType Cmdline_spew_weapon_stats;
+26 -12
View File
@@ -25,18 +25,33 @@ const float CMEASURE_DETONATE_DISTANCE = 40.0f;
//Used to set a countermeasure velocity after being launched from a ship as a countermeasure
//ie not as a primary or secondary.
void cmeasure_set_ship_launch_vel(object *objp, object *parent_objp, int arand)
void cmeasure_set_ship_launch_vel(object* objp, object* parent_objp, int arand)
{
vec3d vel, rand_vec;
vec3d vel;
vec3d rand_vec;
vec3d world_dir;
//Get cmeasure rear velocity in world
vm_vec_scale_add(&vel, &parent_objp->phys_info.vel, &parent_objp->orient.vec.fvec, -25.0f);
weapon* wp = &Weapons[objp->instance];
auto* wip = &Weapon_info[wp->weapon_info_index];
//Get random velocity vector
static_randvec(arand+1, &rand_vec);
// Start with parent velocity
vel = parent_objp->phys_info.vel;
//Add it to the rear velocity
vm_vec_scale_add2(&vel, &rand_vec, 2.0f);
// Normalize direction
vec3d local_dir = wip->cm_launch_vec;
if (vm_vec_mag_squared(&local_dir) > 0.0f) {
vm_vec_normalize(&local_dir);
}
// Convert from ship to world space
vm_vec_unrotate(&world_dir, &local_dir, &parent_objp->orient);
// Apply launch impulse
vm_vec_scale_add2(&vel, &world_dir, wip->cm_launch_speed);
// Add random variance
static_randvec(arand + 1, &rand_vec);
vm_vec_scale_add2(&vel, &rand_vec, wip->cm_launch_variance);
objp->phys_info.vel = vel;
@@ -44,17 +59,16 @@ void cmeasure_set_ship_launch_vel(object *objp, object *parent_objp, int arand)
vm_vec_zero(&objp->phys_info.rotvel);
vm_vec_zero(&objp->phys_info.max_vel);
vm_vec_zero(&objp->phys_info.max_rotvel);
// blow out his reverse thrusters. Or drag, same thing.
objp->phys_info.rotdamp = 10000.0f;
objp->phys_info.side_slip_time_const = 10000.0f;
objp->phys_info.max_vel.xyz.z = -25.0f;
vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, objp->phys_info.max_vel.xyz.z );
// Desired velocity should align with launch direction, not hardcoded backward
vm_vec_copy_scale(&objp->phys_info.desired_vel, &world_dir, wip->cm_launch_speed);
// if this cmeasure has a single segment trail, let the trail know since we just changed the velocity
// yeah this is hacky but that's what this function gets for CHANGING the velocity on objects with a ""CONSTANT VELOCITY""
weapon* wp = &Weapons[objp->instance];
if (wp->trail_ptr && wp->trail_ptr->single_segment) {
wp->trail_ptr->vel[0] = objp->phys_info.vel;
wp->trail_ptr->vel[1] = objp->phys_info.vel;
+42 -7
View File
@@ -10,6 +10,7 @@
#include "camera/photomode.h"
#include "cfile/cfile.h"
#include "cmdline/cmdline.h"
#include "controlconfig/controlsconfig.h"
@@ -620,6 +621,11 @@ void control_config_conflict_check()
continue;
}
// Skip conflict check between items in different conflict groups
if ((item_i.conflict_groups & item_j.conflict_groups) == 0) {
continue;
}
if (item_i.disabled && (item_i.has_first_conflict(item_j) || item_i.has_second_conflict(item_j))) {
// item_i conflicts with item_j and is disabled. Silently clear item_i
item_i.clear();
@@ -1298,7 +1304,8 @@ bool control_config_accept(bool API_Access)
cstr = popup_input(flags,
XSTR( "Confirm new custom preset name.\n\nThe name must not be empty or a default preset.\n\n Press [Enter] to accept, [Esc] to abort to config menu.", 1867),
32 - 6,
default_string.c_str());
default_string.c_str(),
"-_");
if (cstr == nullptr) {
// Abort
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
@@ -2084,7 +2091,6 @@ int control_config_bind_key_on_frame(int ctrl, selItem item, bool API_Access)
if (!done && bind) {
if (!Axis_override.empty()) {
control_config_bind(ctrl, Axis_override, item, API_Access);
control_config_bind(ctrl, Axis_override, item, API_Access);
done = true;
strcpy_s(bound_string, Axis_override.textify().c_str());
@@ -2718,8 +2724,7 @@ void control_config_do_frame(float frametime)
strcpy_s(buf, Control_config[i].text.c_str());
}
font::force_fit_string(buf, 255, Conflict_wnd_coords[gr_screen.res][CONTROL_W_COORD]);
gr_get_string_size(&w, NULL, buf);
w = font::force_fit_string(buf, 255, Conflict_wnd_coords[gr_screen.res][CONTROL_W_COORD]);
gr_printf_menu(x - w / 2, y, "%s", buf);
} else if (*bound_string) {
@@ -2802,8 +2807,22 @@ void control_check_indicate()
Control_check_count = 0;
}
/**
* @brief Checks if a control is being used by the player this frame by checking the bound control input bindings.
*
* @param[in] id The IoActionId of the control to check
* @param[in] key DO NOT USE. Key combination acquired from game_poll(), or -1 to check against a previous poll
*
* @returns 0 If the control was disabled by a mod, or
* @returns 0 If locked by a script, or
* @returns 0 If controls need to be ignored for multiplayer, or
* @returns 0 If the control wasn't used, or
* @returns 1 If the control was used
*/
int check_control_used(int id, int key)
{
// Make sure mouse_down() is only called once during any logic path,
// or the mousewheel may be decayed multiple times!!
int mask;
static int last_key = 0;
auto & item = Control_config[id];
@@ -2823,12 +2842,19 @@ int check_control_used(int id, int key)
if (item.disabled || item.locked)
return 0;
// Filter actions based on conflict groups and current mode
{
int active_group = game_is_photo_mode_active() ? CONFLICT_GROUP_PHOTO_MODE : CONFLICT_GROUP_DEFAULT;
if (!(item.conflict_groups & active_group))
return 0;
}
short z = item.get_btn(CID_KEYBOARD); // Get the key that's bound to this control
if (item.type == CC_TYPE_CONTINUOUS) {
// this is awful, need to make a reverse lookup table to do button -> control instead of this control -> button
// nonsense.
// this is awful, need to make a reverse lookup table to do
// button -> control instead of this control -> button nonsense.
if ((joy_down(item.first) || joy_down_count(item.first, 1)) ||
(joy_down(item.second) || joy_down_count(item.second, 1))) {
// Joy button bound to this control was pressed, control activated
@@ -2838,7 +2864,7 @@ int check_control_used(int id, int key)
if ((mouse_down(item.first) || mouse_down_count(item.first, 1)) ||
(mouse_down(item.second) || mouse_down_count(item.second, 1))) {
// Joy button bound to this control was pressed, control activated
// Mouse button bound to this control was pressed, control activated
control_used(id);
return 1;
}
@@ -2884,6 +2910,15 @@ int check_control_used(int id, int key)
return 1;
}
// special case to allow actual mouse wheel to work with trigger controls --wookieejedi
if (item.type == CC_TYPE_TRIGGER) {
if ( mouse_down(item.first, true) || mouse_down(item.second, true) ) {
// Mouse wheel bound to this trigger control was pressed, control activated
control_used(id);
return 1;
}
}
return 0;
}
+25 -2
View File
@@ -297,10 +297,24 @@ enum IoActionId : int {
JOY_ABS_THROTTLE_AXIS = 127,
JOY_REL_THROTTLE_AXIS = 128,
//!< @n
//! Communication menu controls
//! ----------------------------
COMMS_MENU_MOVE_UP,
COMMS_MENU_MOVE_DOWN,
COMMS_MENU_SELECT,
TOGGLE_HUD_SHADOWS,
CYCLE_PRIMARY_WEAPON_PATTERN,
TOGGLE_PHOTO_MODE,
PHOTO_MODE_FILTER_PREV,
PHOTO_MODE_FILTER_NEXT,
PHOTO_MODE_FILTER_RESET,
PHOTO_MODE_PARAM_DECREASE,
PHOTO_MODE_PARAM_INCREASE,
/*!
* This must always be below the last defined item
*/
@@ -578,6 +592,10 @@ public:
* Control configuration item type.
* @detail Contains binding info, documentation, behavior, etc. for a single control
*/
// Conflict group bitmasks. Bindings only conflict if their groups overlap (bitwise AND != 0).
static const int CONFLICT_GROUP_DEFAULT = (1 << 0); //!< Standard gameplay bindings
static const int CONFLICT_GROUP_PHOTO_MODE = (1 << 1); //!< Bindings used within photo mode
class CCI : public CCB {
public:
// Inherited from CCB
@@ -586,6 +604,7 @@ public:
// Items Set in menu
char tab; //!< what tab (category) it belongs in
int conflict_groups = CONFLICT_GROUP_DEFAULT; //!< Bitmask of conflict groups. Bindings only conflict with others sharing at least one group.
int indexXSTR; //!< what string index we should use to translate this with an XSTR 0 = None, 1= Use item index + CONTROL_CONFIG_XSTR, 2 <= use CCI::indexXSTR directly
SCP_string text; //!< describes the action in the config screen
@@ -658,7 +677,7 @@ public:
* If the type is CC_TYPE_TRIGGER or CC_TYPE_CONTINOUS, the primary is assumed to be a key combo and secondary is assumed to be a joy button.
* If the type is CC_TYPE_AXIS_ABS, CC_TYPE_AXIS_REL, CC_TYPE_AXIS_BTN_POS, or CC_TYPE_AXIS_BTN_NEG, the primary is a Joy0 axis and the secondary is a Mouse axis
*/
CCI_builder& operator()(IoActionId action_id, short primary, short secondary, char tab, int indexXSTR, const char *text, CC_type type, bool disabled = false);
CCI_builder& operator()(IoActionId action_id, short primary, short secondary, char tab, int indexXSTR, const char *text, CC_type type, bool disabled = false, int conflict_groups = CONFLICT_GROUP_DEFAULT);
private:
CCI_builder(); // Only one builder per Control Config, so a default constructor is useless
@@ -937,8 +956,12 @@ float check_control_timef(int id);
* @brief Wrapper for check_control_used. Allows the game to ignore the key if told to do so by the ignore-key SEXP.
*
* @param[in] id The IoActionId of the control to check
* @param[in] key The key combo to check against the control. If -1, re-check the last key passed to this function
* @param[in] key DO NOT USE OUTSIDE OF THE MAIN LOOP. Key combination acquired from game_poll(), or -1 to check against a previous poll
*
* @returns 0 If the control was disabled by a mod, or
* @returns 0 If locked by a script, or
* @returns 0 If controls need to be ignored for multiplayer, or
* @returns 0 If a script is handling the control execution, or
* @returns 0 If the control wasn't used, or
* @returns 1 If the control was used
*
+52 -14
View File
@@ -45,6 +45,7 @@ int Failed_key_index;
// Joystick configuration
int Joy_dead_zone_size = 10;
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
auto DeadZoneOption = options::OptionBuilder<int>("Input.JoystickDeadZone",
std::pair<const char*, int>{"Deadzone", 1377},
std::pair<const char*, int>{"The deadzone used for all joysticks", 1744})
@@ -59,6 +60,7 @@ auto DeadZoneOption = options::OptionBuilder<int>("Input.JoystickDeadZone",
int Joy_sensitivity = 9;
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
auto SensitivityOption = options::OptionBuilder<int>("Input.JoystickSensitivity",
std::pair<const char*, int>{"Sensitivity", 1745},
std::pair<const char*, int>{"The sensitivity used for all joysticks", 1746})
@@ -136,12 +138,12 @@ void control_config_common_init_bindings() {
(TARGET_PREV_BOMB, KEY_SHIFTED | KEY_B, -1, TARGET_TAB, 1, "Target Previous Hostile Bomb or Bomber", CC_TYPE_TRIGGER)
// flight controls (Rotation)
(BANK_LEFT, KEY_PAD7, -1, SHIP_TAB, 1, "Bank Left", CC_TYPE_CONTINUOUS)
(BANK_RIGHT, KEY_PAD9, -1, SHIP_TAB, 1, "Bank Right", CC_TYPE_CONTINUOUS)
(PITCH_FORWARD, KEY_PAD8, -1, SHIP_TAB, 1, "Pitch Forward", CC_TYPE_CONTINUOUS)
(PITCH_BACK, KEY_PAD2, -1, SHIP_TAB, 1, "Pitch Backward", CC_TYPE_CONTINUOUS)
(YAW_LEFT, KEY_PAD4, -1, SHIP_TAB, 1, "Yaw Left", CC_TYPE_CONTINUOUS)
(YAW_RIGHT, KEY_PAD6, -1, SHIP_TAB, 1, "Yaw Right", CC_TYPE_CONTINUOUS)
(BANK_LEFT, KEY_PAD7, -1, SHIP_TAB, 1, "Bank Left", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
(BANK_RIGHT, KEY_PAD9, -1, SHIP_TAB, 1, "Bank Right", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
(PITCH_FORWARD, KEY_PAD8, -1, SHIP_TAB, 1, "Pitch Forward", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
(PITCH_BACK, KEY_PAD2, -1, SHIP_TAB, 1, "Pitch Backward", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
(YAW_LEFT, KEY_PAD4, -1, SHIP_TAB, 1, "Yaw Left", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
(YAW_RIGHT, KEY_PAD6, -1, SHIP_TAB, 1, "Yaw Right", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
// flight controls (Throttle)
(ZERO_THROTTLE, KEY_BACKSP, -1, SHIP_TAB, 1, "Set Throttle to Zero", CC_TYPE_TRIGGER)
@@ -152,16 +154,16 @@ void control_config_common_init_bindings() {
(MINUS_5_PERCENT_THROTTLE, KEY_MINUS, -1, SHIP_TAB, 1, "Decrease Throttle 5 Percent", CC_TYPE_TRIGGER)
// flight controls (Thrust)
(FORWARD_THRUST, KEY_A, -1, SHIP_TAB, 1, "Forward Thrust", CC_TYPE_CONTINUOUS)
(REVERSE_THRUST, KEY_Z, -1, SHIP_TAB, 1, "Reverse Thrust", CC_TYPE_CONTINUOUS)
(RIGHT_SLIDE_THRUST, KEY_SHIFTED | KEY_3, -1, SHIP_TAB, 1, "Right Thrust", CC_TYPE_CONTINUOUS)
(LEFT_SLIDE_THRUST, KEY_SHIFTED | KEY_1, -1, SHIP_TAB, 1, "Left Thrust", CC_TYPE_CONTINUOUS)
(UP_SLIDE_THRUST, KEY_SHIFTED | KEY_PADPLUS, -1, SHIP_TAB, 1, "Up Thrust", CC_TYPE_CONTINUOUS)
(DOWN_SLIDE_THRUST, KEY_SHIFTED | KEY_PADENTER, -1, SHIP_TAB, 1, "Down Thrust", CC_TYPE_CONTINUOUS)
(FORWARD_THRUST, KEY_A, -1, SHIP_TAB, 1, "Forward Thrust", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
(REVERSE_THRUST, KEY_Z, -1, SHIP_TAB, 1, "Reverse Thrust", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
(RIGHT_SLIDE_THRUST, KEY_SHIFTED | KEY_3, -1, SHIP_TAB, 1, "Right Thrust", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
(LEFT_SLIDE_THRUST, KEY_SHIFTED | KEY_1, -1, SHIP_TAB, 1, "Left Thrust", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
(UP_SLIDE_THRUST, KEY_SHIFTED | KEY_PADPLUS, -1, SHIP_TAB, 1, "Up Thrust", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
(DOWN_SLIDE_THRUST, KEY_SHIFTED | KEY_PADENTER, -1, SHIP_TAB, 1, "Down Thrust", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
// flight controls (flight modes)
(BANK_WHEN_PRESSED, -1, -1, SHIP_TAB, 1, "Bank When Pressed", CC_TYPE_CONTINUOUS)
(AFTERBURNER, KEY_TAB, 5, SHIP_TAB, 1, "Afterburner", CC_TYPE_CONTINUOUS)
(AFTERBURNER, KEY_TAB, 5, SHIP_TAB, 1, "Afterburner", CC_TYPE_CONTINUOUS, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
(GLIDE_WHEN_PRESSED, -1, -1, SHIP_TAB, 1774, "Glide When Pressed", CC_TYPE_CONTINUOUS)
(TOGGLE_GLIDING, KEY_ALTED | KEY_G, -1, SHIP_TAB, 1775, "Toggle Gliding", CC_TYPE_TRIGGER)
@@ -265,6 +267,12 @@ void control_config_common_init_bindings() {
(TOGGLE_HUD_CONTRAST, KEY_L, -1, COMPUTER_TAB, 1, "Toggle High HUD Contrast", CC_TYPE_TRIGGER)
(TOGGLE_HUD_SHADOWS, KEY_ALTED | KEY_L, -1, COMPUTER_TAB, 1781, "Toggle HUD Drop Shadows", CC_TYPE_TRIGGER)
(HUD_TARGETBOX_TOGGLE_WIREFRAME, KEY_ALTED | KEY_SHIFTED | KEY_Q, -1, COMPUTER_TAB, 1, "Toggle HUD Wireframe Target View", CC_TYPE_TRIGGER)
(TOGGLE_PHOTO_MODE, KEY_ALTED | KEY_1, -1, COMPUTER_TAB, 1908, "Toggle Photo Mode", CC_TYPE_TRIGGER, false, CONFLICT_GROUP_DEFAULT | CONFLICT_GROUP_PHOTO_MODE)
(PHOTO_MODE_FILTER_PREV, KEY_UP, -1, COMPUTER_TAB, 1909, "Photo Mode Previous Parameter", CC_TYPE_TRIGGER, false, CONFLICT_GROUP_PHOTO_MODE)
(PHOTO_MODE_FILTER_NEXT, KEY_DOWN, -1, COMPUTER_TAB, 1910, "Photo Mode Next Parameter", CC_TYPE_TRIGGER, false, CONFLICT_GROUP_PHOTO_MODE)
(PHOTO_MODE_FILTER_RESET, KEY_ALTED | KEY_2, -1, COMPUTER_TAB, 1911, "Photo Mode Reset Parameters", CC_TYPE_TRIGGER, false, CONFLICT_GROUP_PHOTO_MODE)
(PHOTO_MODE_PARAM_DECREASE, KEY_LEFT, -1, COMPUTER_TAB, 1912, "Photo Mode Decrease Selected Parameter", CC_TYPE_TRIGGER, false, CONFLICT_GROUP_PHOTO_MODE)
(PHOTO_MODE_PARAM_INCREASE, KEY_RIGHT, -1, COMPUTER_TAB, 1913, "Photo Mode Increase Selected Parameter", CC_TYPE_TRIGGER, false, CONFLICT_GROUP_PHOTO_MODE)
// Custom Controls
(CUSTOM_CONTROL_1, KEY_ALTED | KEY_SHIFTED | KEY_1, -1, COMPUTER_TAB, 1784, "Custom Control 1", CC_TYPE_TRIGGER, true)
@@ -272,6 +280,13 @@ void control_config_common_init_bindings() {
(CUSTOM_CONTROL_3, KEY_ALTED | KEY_SHIFTED | KEY_3, -1, COMPUTER_TAB, 1786, "Custom Control 3", CC_TYPE_TRIGGER, true)
(CUSTOM_CONTROL_4, KEY_ALTED | KEY_SHIFTED | KEY_4, -1, COMPUTER_TAB, 1787, "Custom Control 4", CC_TYPE_TRIGGER, true)
(CUSTOM_CONTROL_5, KEY_ALTED | KEY_SHIFTED | KEY_5, -1, COMPUTER_TAB, 1788, "Custom Control 5", CC_TYPE_TRIGGER, true)
//Comms Controls
(COMMS_MENU_MOVE_UP, -1, -1, COMPUTER_TAB, 1883, "Communication Menu Move Up", CC_TYPE_TRIGGER)
(COMMS_MENU_MOVE_DOWN, -1, -1, COMPUTER_TAB, 1884, "Communication Menu Move Down", CC_TYPE_TRIGGER)
(COMMS_MENU_SELECT, -1, -1, COMPUTER_TAB, 1885, "Communication Menu Select", CC_TYPE_TRIGGER)
.end(); // Builder
// init default preset
@@ -425,6 +440,12 @@ SCP_unordered_map<SCP_string, IoActionId> old_text = {
{"Up Thrust", UP_SLIDE_THRUST},
{"Down Thrust", DOWN_SLIDE_THRUST},
{"Toggle HUD Wireframe Target View", HUD_TARGETBOX_TOGGLE_WIREFRAME},
{"Toggle Photo Mode", TOGGLE_PHOTO_MODE},
{"Photo Mode Previous Parameter", PHOTO_MODE_FILTER_PREV},
{"Photo Mode Next Parameter", PHOTO_MODE_FILTER_NEXT},
{"Photo Mode Reset Parameters", PHOTO_MODE_FILTER_RESET},
{"Photo Mode Decrease Selected Parameter", PHOTO_MODE_PARAM_DECREASE},
{"Photo Mode Increase Selected Parameter", PHOTO_MODE_PARAM_INCREASE},
{"Top-Down View", VIEW_TOPDOWN},
{"Target Padlock View", VIEW_TRACK_TARGET},
@@ -438,6 +459,9 @@ SCP_unordered_map<SCP_string, IoActionId> old_text = {
{"Custom Control 3", CUSTOM_CONTROL_3},
{"Custom Control 4", CUSTOM_CONTROL_4},
{"Custom Control 5", CUSTOM_CONTROL_5},
{"Communication Menu Move Up", COMMS_MENU_MOVE_UP},
{"Communication Menu Move Down", COMMS_MENU_MOVE_DOWN},
{"Communication Menu Select", COMMS_MENU_SELECT},
};
// Localization strings for hat positions. Back[0], Forward[1], Left[2], Right[3]
@@ -1141,6 +1165,12 @@ void LoadEnumsIntoActionMap() {
ADD_ENUM_TO_ACTION_MAP(UP_SLIDE_THRUST)
ADD_ENUM_TO_ACTION_MAP(DOWN_SLIDE_THRUST)
ADD_ENUM_TO_ACTION_MAP(HUD_TARGETBOX_TOGGLE_WIREFRAME)
ADD_ENUM_TO_ACTION_MAP(TOGGLE_PHOTO_MODE)
ADD_ENUM_TO_ACTION_MAP(PHOTO_MODE_FILTER_PREV)
ADD_ENUM_TO_ACTION_MAP(PHOTO_MODE_FILTER_NEXT)
ADD_ENUM_TO_ACTION_MAP(PHOTO_MODE_FILTER_RESET)
ADD_ENUM_TO_ACTION_MAP(PHOTO_MODE_PARAM_DECREASE)
ADD_ENUM_TO_ACTION_MAP(PHOTO_MODE_PARAM_INCREASE)
ADD_ENUM_TO_ACTION_MAP(VIEW_TOPDOWN)
ADD_ENUM_TO_ACTION_MAP(VIEW_TRACK_TARGET)
@@ -1164,6 +1194,10 @@ void LoadEnumsIntoActionMap() {
ADD_ENUM_TO_ACTION_MAP(JOY_ABS_THROTTLE_AXIS)
ADD_ENUM_TO_ACTION_MAP(JOY_REL_THROTTLE_AXIS)
ADD_ENUM_TO_ACTION_MAP(COMMS_MENU_MOVE_UP)
ADD_ENUM_TO_ACTION_MAP(COMMS_MENU_MOVE_DOWN)
ADD_ENUM_TO_ACTION_MAP(COMMS_MENU_SELECT)
#undef ADD_ENUM_TO_ACTION_MAP
@@ -2804,6 +2838,7 @@ CCI& CCI::operator=(const CCI& A) {
locked = A.locked;
scriptEnabledByDefault = A.scriptEnabledByDefault;
continuous_ongoing = A.continuous_ongoing;
conflict_groups = A.conflict_groups;
return *this;
};
@@ -2886,7 +2921,7 @@ CCI_builder& CCI_builder::start() {
void CCI_builder::end() {};
CCI_builder& CCI_builder::operator()(IoActionId action_id, short primary, short secondary, char tab, int indexXSTR, const char *text, CC_type type, bool disabled) {
CCI_builder& CCI_builder::operator()(IoActionId action_id, short primary, short secondary, char tab, int indexXSTR, const char *text, CC_type type, bool disabled, int conflict_groups) {
Assert(action_id < CCFG_MAX);
CCI& item = ControlConfig[action_id];
@@ -2924,6 +2959,9 @@ CCI_builder& CCI_builder::operator()(IoActionId action_id, short primary, short
item.disabled = false;
}
// Assign the conflict group
item.conflict_groups = conflict_groups;
return *this;
}
+6 -3
View File
@@ -12,9 +12,10 @@ DecoderStatus::~DecoderStatus() {
videoCodec = nullptr;
if (videoCodecCtx != nullptr) {
avcodec_close(videoCodecCtx);
#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(57, 24, 255)
avcodec_free_context(&videoCodecCtx);
#else
avcodec_close(videoCodecCtx);
#endif
videoCodecCtx = nullptr;
}
@@ -24,9 +25,10 @@ DecoderStatus::~DecoderStatus() {
audioCodec = nullptr;
if (audioCodecCtx != nullptr) {
avcodec_close(audioCodecCtx);
#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(57, 24, 255)
avcodec_free_context(&audioCodecCtx);
#else
avcodec_close(audioCodecCtx);
#endif
audioCodecCtx = nullptr;
}
@@ -36,9 +38,10 @@ DecoderStatus::~DecoderStatus() {
subtitleCodec = nullptr;
if (subtitleCodecCtx != nullptr) {
avcodec_close(subtitleCodecCtx);
#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(57, 24, 255)
avcodec_free_context(&subtitleCodecCtx);
#else
avcodec_close(subtitleCodecCtx);
#endif
subtitleCodecCtx = nullptr;
}
+1 -1
View File
@@ -279,7 +279,7 @@ bool play(const char* filename, bool via_tech_room = false)
{
auto paramList = scripting::hook_param_list(scripting::hook_param("Filename", 's', filename), scripting::hook_param("ViaTechRoom", 'b', via_tech_room));
bool skip = scripting::hooks::OnMovieAboutToPlay->isOverride(paramList);
scripting::hooks::OnMovieAboutToPlay->run(paramList);
scripting::hooks::OnMovieAboutToPlay->run(std::move(paramList));
if (skip)
return false;
}
+191 -31
View File
@@ -1,4 +1,4 @@
/* bcdec.h - v0.96
/* bcdec.h - v0.97
provides functions to decompress blocks of BC compressed images
written by Sergii "iOrange" Kudlai in 2022
@@ -23,6 +23,10 @@
For more info, issues and suggestions please visit https://github.com/iOrange/bcdec
Configuration:
#define BCDEC_BC4BC5_PRECISE:
enables more precise but slower BC4/BC5 decoding + signed/unsigned mode
CREDITS:
Aras Pranckevicius (@aras-p) - BC1/BC3 decoders optimizations (up to 3x the speed)
- BC6H/BC7 bits pulling routines optimizations
@@ -30,6 +34,11 @@
- Split BC6H decompression function into 'half' and
'float' variants
Michael Schmidt (@RunDevelopment) - Found better "magic" coefficients for integer interpolation
of reference colors in BC1 color block, that match with
the floating point interpolation. This also made it faster
than integer division by 3!
bugfixes:
@linkmauve
@@ -39,6 +48,9 @@
#ifndef BCDEC_HEADER_INCLUDED
#define BCDEC_HEADER_INCLUDED
#define BCDEC_VERSION_MAJOR 0
#define BCDEC_VERSION_MINOR 98
/* if BCDEC_STATIC causes problems, try defining BCDECDEF to 'inline' or 'static inline' */
#ifndef BCDECDEF
#ifdef BCDEC_STATIC
@@ -90,12 +102,20 @@
BCDECDEF void bcdec_bc1(const void* compressedBlock, void* decompressedBlock, int destinationPitch);
BCDECDEF void bcdec_bc2(const void* compressedBlock, void* decompressedBlock, int destinationPitch);
BCDECDEF void bcdec_bc3(const void* compressedBlock, void* decompressedBlock, int destinationPitch);
#ifndef BCDEC_BC4BC5_PRECISE
BCDECDEF void bcdec_bc4(const void* compressedBlock, void* decompressedBlock, int destinationPitch);
BCDECDEF void bcdec_bc5(const void* compressedBlock, void* decompressedBlock, int destinationPitch);
#else
BCDECDEF void bcdec_bc4(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned);
BCDECDEF void bcdec_bc5(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned);
BCDECDEF void bcdec_bc4_float(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned);
BCDECDEF void bcdec_bc5_float(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned);
#endif
BCDECDEF void bcdec_bc6h_float(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned);
BCDECDEF void bcdec_bc6h_half(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned);
BCDECDEF void bcdec_bc7(const void* compressedBlock, void* decompressedBlock, int destinationPitch);
#endif /* BCDEC_HEADER_INCLUDED */
#ifdef BCDEC_IMPLEMENTATION
@@ -110,35 +130,44 @@ static void bcdec__color_block(const void* compressedBlock, void* decompressedBl
c0 = ((unsigned short*)compressedBlock)[0];
c1 = ((unsigned short*)compressedBlock)[1];
/* Expand 565 ref colors to 888 */
r0 = (((c0 >> 11) & 0x1F) * 527 + 23) >> 6;
g0 = (((c0 >> 5) & 0x3F) * 259 + 33) >> 6;
b0 = ((c0 & 0x1F) * 527 + 23) >> 6;
refColors[0] = 0xFF000000 | (b0 << 16) | (g0 << 8) | r0;
/* Unpack 565 ref colors */
r0 = (c0 >> 11) & 0x1F;
g0 = (c0 >> 5) & 0x3F;
b0 = c0 & 0x1F;
r1 = (((c1 >> 11) & 0x1F) * 527 + 23) >> 6;
g1 = (((c1 >> 5) & 0x3F) * 259 + 33) >> 6;
b1 = ((c1 & 0x1F) * 527 + 23) >> 6;
refColors[1] = 0xFF000000 | (b1 << 16) | (g1 << 8) | r1;
r1 = (c1 >> 11) & 0x1F;
g1 = (c1 >> 5) & 0x3F;
b1 = c1 & 0x1F;
/* Expand 565 ref colors to 888 */
r = (r0 * 527 + 23) >> 6;
g = (g0 * 259 + 33) >> 6;
b = (b0 * 527 + 23) >> 6;
refColors[0] = 0xFF000000 | (b << 16) | (g << 8) | r;
r = (r1 * 527 + 23) >> 6;
g = (g1 * 259 + 33) >> 6;
b = (b1 * 527 + 23) >> 6;
refColors[1] = 0xFF000000 | (b << 16) | (g << 8) | r;
if (c0 > c1 || onlyOpaqueMode) { /* Standard BC1 mode (also BC3 color block uses ONLY this mode) */
/* color_2 = 2/3*color_0 + 1/3*color_1
color_3 = 1/3*color_0 + 2/3*color_1 */
r = (2 * r0 + r1 + 1) / 3;
g = (2 * g0 + g1 + 1) / 3;
b = (2 * b0 + b1 + 1) / 3;
r = ((2 * r0 + r1) * 351 + 61) >> 7;
g = ((2 * g0 + g1) * 2763 + 1039) >> 11;
b = ((2 * b0 + b1) * 351 + 61) >> 7;
refColors[2] = 0xFF000000 | (b << 16) | (g << 8) | r;
r = (r0 + 2 * r1 + 1) / 3;
g = (g0 + 2 * g1 + 1) / 3;
b = (b0 + 2 * b1 + 1) / 3;
r = ((r0 + r1 * 2) * 351 + 61) >> 7;
g = ((g0 + g1 * 2) * 2763 + 1039) >> 11;
b = ((b0 + b1 * 2) * 351 + 61) >> 7;
refColors[3] = 0xFF000000 | (b << 16) | (g << 8) | r;
} else { /* Quite rare BC1A mode */
/* color_2 = 1/2*color_0 + 1/2*color_1;
color_3 = 0; */
r = (r0 + r1 + 1) >> 1;
g = (g0 + g1 + 1) >> 1;
b = (b0 + b1 + 1) >> 1;
r = ((r0 + r1) * 1053 + 125) >> 8;
g = ((g0 + g1) * 4145 + 1019) >> 11;
b = ((b0 + b1) * 1053 + 125) >> 8;
refColors[2] = 0xFF000000 | (b << 16) | (g << 8) | r;
refColors[3] = 0x00000000;
@@ -190,19 +219,19 @@ static void bcdec__smooth_alpha_block(const void* compressedBlock, void* decompr
if (alpha[0] > alpha[1]) {
/* 6 interpolated alpha values. */
alpha[2] = (6 * alpha[0] + alpha[1] + 1) / 7; /* 6/7*alpha_0 + 1/7*alpha_1 */
alpha[3] = (5 * alpha[0] + 2 * alpha[1] + 1) / 7; /* 5/7*alpha_0 + 2/7*alpha_1 */
alpha[4] = (4 * alpha[0] + 3 * alpha[1] + 1) / 7; /* 4/7*alpha_0 + 3/7*alpha_1 */
alpha[5] = (3 * alpha[0] + 4 * alpha[1] + 1) / 7; /* 3/7*alpha_0 + 4/7*alpha_1 */
alpha[6] = (2 * alpha[0] + 5 * alpha[1] + 1) / 7; /* 2/7*alpha_0 + 5/7*alpha_1 */
alpha[7] = ( alpha[0] + 6 * alpha[1] + 1) / 7; /* 1/7*alpha_0 + 6/7*alpha_1 */
alpha[2] = (6 * alpha[0] + alpha[1]) / 7; /* 6/7*alpha_0 + 1/7*alpha_1 */
alpha[3] = (5 * alpha[0] + 2 * alpha[1]) / 7; /* 5/7*alpha_0 + 2/7*alpha_1 */
alpha[4] = (4 * alpha[0] + 3 * alpha[1]) / 7; /* 4/7*alpha_0 + 3/7*alpha_1 */
alpha[5] = (3 * alpha[0] + 4 * alpha[1]) / 7; /* 3/7*alpha_0 + 4/7*alpha_1 */
alpha[6] = (2 * alpha[0] + 5 * alpha[1]) / 7; /* 2/7*alpha_0 + 5/7*alpha_1 */
alpha[7] = ( alpha[0] + 6 * alpha[1]) / 7; /* 1/7*alpha_0 + 6/7*alpha_1 */
}
else {
/* 4 interpolated alpha values. */
alpha[2] = (4 * alpha[0] + alpha[1] + 1) / 5; /* 4/5*alpha_0 + 1/5*alpha_1 */
alpha[3] = (3 * alpha[0] + 2 * alpha[1] + 1) / 5; /* 3/5*alpha_0 + 2/5*alpha_1 */
alpha[4] = (2 * alpha[0] + 3 * alpha[1] + 1) / 5; /* 2/5*alpha_0 + 3/5*alpha_1 */
alpha[5] = ( alpha[0] + 4 * alpha[1] + 1) / 5; /* 1/5*alpha_0 + 4/5*alpha_1 */
alpha[2] = (4 * alpha[0] + alpha[1]) / 5; /* 4/5*alpha_0 + 1/5*alpha_1 */
alpha[3] = (3 * alpha[0] + 2 * alpha[1]) / 5; /* 3/5*alpha_0 + 2/5*alpha_1 */
alpha[4] = (2 * alpha[0] + 3 * alpha[1]) / 5; /* 2/5*alpha_0 + 3/5*alpha_1 */
alpha[5] = ( alpha[0] + 4 * alpha[1]) / 5; /* 1/5*alpha_0 + 4/5*alpha_1 */
alpha[6] = 0x00;
alpha[7] = 0xFF;
}
@@ -218,6 +247,117 @@ static void bcdec__smooth_alpha_block(const void* compressedBlock, void* decompr
}
}
#ifdef BCDEC_BC4BC5_PRECISE
static void bcdec__bc4_block(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int pixelSize, int isSigned) {
signed char* sblock;
unsigned char* ublock;
int alpha[8];
int i, j;
unsigned long long block, indices;
static int aWeights4[4] = { 13107, 26215, 39321, 52429 };
static int aWeights6[6] = { 9363, 18724, 28086, 37450, 46812, 56173 };
block = *(unsigned long long*)compressedBlock;
if (isSigned) {
alpha[0] = (char)(block & 0xFF);
alpha[1] = (char)((block >> 8) & 0xFF);
if (alpha[0] < -127) alpha[0] = -127; /* -128 clamps to -127 */
if (alpha[1] < -127) alpha[1] = -127; /* -128 clamps to -127 */
} else {
alpha[0] = block & 0xFF;
alpha[1] = (block >> 8) & 0xFF;
}
if (alpha[0] > alpha[1]) {
/* 6 interpolated alpha values. */
alpha[2] = (aWeights6[5] * alpha[0] + aWeights6[0] * alpha[1] + 32768) >> 16; /* 6/7*alpha_0 + 1/7*alpha_1 */
alpha[3] = (aWeights6[4] * alpha[0] + aWeights6[1] * alpha[1] + 32768) >> 16; /* 5/7*alpha_0 + 2/7*alpha_1 */
alpha[4] = (aWeights6[3] * alpha[0] + aWeights6[2] * alpha[1] + 32768) >> 16; /* 4/7*alpha_0 + 3/7*alpha_1 */
alpha[5] = (aWeights6[2] * alpha[0] + aWeights6[3] * alpha[1] + 32768) >> 16; /* 3/7*alpha_0 + 4/7*alpha_1 */
alpha[6] = (aWeights6[1] * alpha[0] + aWeights6[4] * alpha[1] + 32768) >> 16; /* 2/7*alpha_0 + 5/7*alpha_1 */
alpha[7] = (aWeights6[0] * alpha[0] + aWeights6[5] * alpha[1] + 32768) >> 16; /* 1/7*alpha_0 + 6/7*alpha_1 */
} else {
/* 4 interpolated alpha values. */
alpha[2] = (aWeights4[3] * alpha[0] + aWeights4[0] * alpha[1] + 32768) >> 16; /* 4/5*alpha_0 + 1/5*alpha_1 */
alpha[3] = (aWeights4[2] * alpha[0] + aWeights4[1] * alpha[1] + 32768) >> 16; /* 3/5*alpha_0 + 2/5*alpha_1 */
alpha[4] = (aWeights4[1] * alpha[0] + aWeights4[2] * alpha[1] + 32768) >> 16; /* 2/5*alpha_0 + 3/5*alpha_1 */
alpha[5] = (aWeights4[0] * alpha[0] + aWeights4[3] * alpha[1] + 32768) >> 16; /* 1/5*alpha_0 + 4/5*alpha_1 */
alpha[6] = isSigned ? -127 : 0;
alpha[7] = isSigned ? 127 : 255;
}
indices = block >> 16;
if (isSigned) {
sblock = (char*)decompressedBlock;
for (i = 0; i < 4; ++i) {
for (j = 0; j < 4; ++j) {
sblock[j * pixelSize] = (char)alpha[indices & 0x07];
indices >>= 3;
}
sblock += destinationPitch;
}
} else {
ublock = (unsigned char*)decompressedBlock;
for (i = 0; i < 4; ++i) {
for (j = 0; j < 4; ++j) {
ublock[j * pixelSize] = (unsigned char)alpha[indices & 0x07];
indices >>= 3;
}
ublock += destinationPitch;
}
}
}
static void bcdec__bc4_block_float(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int pixelSize, int isSigned) {
float* decompressed;
float alpha[8];
int i, j;
unsigned long long block, indices;
block = *(unsigned long long*)compressedBlock;
decompressed = (float*)decompressedBlock;
if (isSigned) {
alpha[0] = (float)((char)(block & 0xFF)) / 127.0f;
alpha[1] = (float)((char)((block >> 8) & 0xFF)) / 127.0f;
if (alpha[0] < -1.0f) alpha[0] = -1.0f; /* -128 clamps to -127 */
if (alpha[1] < -1.0f) alpha[1] = -1.0f; /* -128 clamps to -127 */
} else {
alpha[0] = (float)(block & 0xFF) / 255.0f;
alpha[1] = (float)((block >> 8) & 0xFF) / 255.0f;
}
if (alpha[0] > alpha[1]) {
/* 6 interpolated alpha values. */
alpha[2] = (6.0f * alpha[0] + alpha[1]) / 7.0f; /* 6/7*alpha_0 + 1/7*alpha_1 */
alpha[3] = (5.0f * alpha[0] + 2.0f * alpha[1]) / 7.0f; /* 5/7*alpha_0 + 2/7*alpha_1 */
alpha[4] = (4.0f * alpha[0] + 3.0f * alpha[1]) / 7.0f; /* 4/7*alpha_0 + 3/7*alpha_1 */
alpha[5] = (3.0f * alpha[0] + 4.0f * alpha[1]) / 7.0f; /* 3/7*alpha_0 + 4/7*alpha_1 */
alpha[6] = (2.0f * alpha[0] + 5.0f * alpha[1]) / 7.0f; /* 2/7*alpha_0 + 5/7*alpha_1 */
alpha[7] = ( alpha[0] + 6.0f * alpha[1]) / 7.0f; /* 1/7*alpha_0 + 6/7*alpha_1 */
} else {
/* 4 interpolated alpha values. */
alpha[2] = (4.0f * alpha[0] + alpha[1]) / 5.0f; /* 4/5*alpha_0 + 1/5*alpha_1 */
alpha[3] = (3.0f * alpha[0] + 2.0f * alpha[1]) / 5.0f; /* 3/5*alpha_0 + 2/5*alpha_1 */
alpha[4] = (2.0f * alpha[0] + 3.0f * alpha[1]) / 5.0f; /* 2/5*alpha_0 + 3/5*alpha_1 */
alpha[5] = ( alpha[0] + 4.0f * alpha[1]) / 5.0f; /* 1/5*alpha_0 + 4/5*alpha_1 */
alpha[6] = isSigned ? -1.0f : 0.0f;
alpha[7] = 1.0f;
}
indices = block >> 16;
for (i = 0; i < 4; ++i) {
for (j = 0; j < 4; ++j) {
decompressed[j * pixelSize] = alpha[indices & 0x07];
indices >>= 3;
}
decompressed += destinationPitch;
}
}
#endif /* BCDEC_BC4BC5_PRECISE */
typedef struct bcdec__bitstream {
unsigned long long low;
unsigned long long high;
@@ -270,15 +410,37 @@ BCDECDEF void bcdec_bc3(const void* compressedBlock, void* decompressedBlock, in
bcdec__smooth_alpha_block(compressedBlock, ((char*)decompressedBlock) + 3, destinationPitch, 4);
}
#ifndef BCDEC_BC4BC5_PRECISE
BCDECDEF void bcdec_bc4(const void* compressedBlock, void* decompressedBlock, int destinationPitch) {
bcdec__smooth_alpha_block(compressedBlock, decompressedBlock, destinationPitch, 1);
#else
BCDECDEF void bcdec_bc4(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned) {
bcdec__bc4_block(compressedBlock, decompressedBlock, destinationPitch, 1, isSigned);
#endif
}
#ifndef BCDEC_BC4BC5_PRECISE
BCDECDEF void bcdec_bc5(const void* compressedBlock, void* decompressedBlock, int destinationPitch) {
bcdec__smooth_alpha_block(compressedBlock, decompressedBlock, destinationPitch, 2);
bcdec__smooth_alpha_block(((char*)compressedBlock) + 8, ((char*)decompressedBlock) + 1, destinationPitch, 2);
#else
BCDECDEF void bcdec_bc5(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned) {
bcdec__bc4_block(compressedBlock, decompressedBlock, destinationPitch, 2, isSigned);
bcdec__bc4_block(((char*)compressedBlock) + 8, ((char*)decompressedBlock) + 1, destinationPitch, 2, isSigned);
#endif
}
#ifdef BCDEC_BC4BC5_PRECISE
BCDECDEF void bcdec_bc4_float(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned) {
bcdec__bc4_block_float(compressedBlock, decompressedBlock, destinationPitch, 1, isSigned);
}
BCDECDEF void bcdec_bc5_float(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned) {
bcdec__bc4_block_float(compressedBlock, decompressedBlock, destinationPitch, 2, isSigned);
bcdec__bc4_block_float(((char*)compressedBlock) + 8, ((float*)decompressedBlock) + 1, destinationPitch, 2, isSigned);
}
#endif /* BCDEC_BC4BC5_PRECISE */
/* http://graphics.stanford.edu/~seander/bithacks.html#VariableSignExtend */
static int bcdec__extend_sign(int val, int bits) {
return (val << (32 - bits)) >> (32 - bits);
@@ -1269,8 +1431,6 @@ BCDECDEF void bcdec_bc7(const void* compressedBlock, void* decompressedBlock, in
#endif /* BCDEC_IMPLEMENTATION */
#endif /* BCDEC_HEADER_INCLUDED */
/* LICENSE:
This software is available under 2 licenses -- choose whichever you prefer.
+59 -20
View File
@@ -2,6 +2,9 @@
#include "ddsutils/ddsutils.h"
#include "cfile/cfile.h"
#include "osapi/osregistry.h"
#ifdef USE_OPENGL_ES
#include "graphics/opengl/es_compatibility.h"
#endif
#ifdef WITH_OPENGL
#include <glad/glad.h>
@@ -96,6 +99,11 @@ static uint conversion_resize(DDS_HEADER &dds_header)
// drop levels until we get to an appropriate size, but make sure we have
// at least 1 mipmap level remaining at the end (in case there's not a full chain)
while (((width > MAX_SIZE) || (height > MAX_SIZE)) && (offset < dds_header.dwMipMapCount-1)) {
// this shouldn't happen, but catch the obscure case (like 8192x4)
if ((width <= 4) || (height <= 4)) {
break;
}
width >>= 1;
height >>= 1;
depth >>= 1;
@@ -194,9 +202,10 @@ static int _dds_read_header(CFILE *ddsfile, DDS_HEADER &dds_header, DDS_HEADER_D
return DDS_ERROR_NONE;
}
static size_t compute_dds_size(const DDS_HEADER &dds_header)
static size_t compute_dds_size(const DDS_HEADER &dds_header, bool converting = false)
{
uint d_width, d_height, d_depth;
const uint block_sz = 4;
uint d_width = 0, d_height = 0, d_depth = 0;
size_t d_size = 0;
for (uint i = 0; i < dds_header.dwMipMapCount; i++) {
@@ -204,6 +213,17 @@ static size_t compute_dds_size(const DDS_HEADER &dds_header)
d_height = std::max(1U, dds_header.dwHeight >> i);
d_depth = std::max(1U, dds_header.dwDepth >> i);
// When converting we need to pad a bit to compensate for the decompression
// size on smaller mipmap levels. We need to ensure there is always enough
// room to decode an entire 4x4 block in rgba space
if (converting) {
auto sz = std::min(d_width, d_height);
if (sz < block_sz) {
d_size += ((block_sz * block_sz) - (sz * sz)) * d_depth * 4;
}
}
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);
@@ -247,6 +267,7 @@ int dds_read_header(const char *filename, CFILE *img_cfp, int *width, int *heigh
int retval = DDS_ERROR_NONE;
int ct = DDS_UNCOMPRESSED;
int is_cubemap = 0;
bool convert = false;
if (img_cfp == NULL) {
@@ -337,7 +358,9 @@ int dds_read_header(const char *filename, CFILE *img_cfp, int *width, int *heigh
}
// maybe do conversion if format not supported
if (conversion_needed(dds_header)) {
convert = conversion_needed(dds_header);
if (convert) {
// switch to uncompressed format and reset vars
dds_header.ddspf.dwFlags &= ~DDPF_FOURCC;
dds_header.ddspf.dwFlags |= DDPF_RGB;
@@ -359,7 +382,7 @@ int dds_read_header(const char *filename, CFILE *img_cfp, int *width, int *heigh
// stuff important info
if (size)
*size = compute_dds_size(dds_header);
*size = compute_dds_size(dds_header, convert);
if (bpp)
*bpp = get_bit_count(dds_header);
@@ -386,6 +409,9 @@ Done:
return retval;
}
static void (*decompress_dds)(const void *in, void *out, int pitch) = nullptr;
static uint32_t BLOCK_SIZE = 0;
//reads pixel info from a dds file
int dds_read_bitmap(const char *filename, ubyte *data, ubyte *bpp, int cf_type)
{
@@ -423,7 +449,7 @@ int dds_read_bitmap(const char *filename, ubyte *data, ubyte *bpp, int cf_type)
cfseek(cfp, (dds_header.ddspf.dwFourCC == FOURCC_DX10) ? DX10_OFFSET : DDS_OFFSET, CF_SEEK_SET);
size = compute_dds_size(dds_header);
size = compute_dds_size(dds_header); // don't add padding on this one!!
// read in the data
if ( !conversion_needed(dds_header) ) {
@@ -447,6 +473,28 @@ int dds_read_bitmap(const char *filename, ubyte *data, ubyte *bpp, int cf_type)
const int num_faces = (dds_header.dwCaps2 & DDSCAPS2_CUBEMAP) ? 6 : 1;
const bool has_depth = (dds_header.dwFlags & DDSD_DEPTH) == DDSD_DEPTH;
switch (dds_header.ddspf.dwFourCC) {
case FOURCC_DX10:
decompress_dds = bcdec_bc7;
BLOCK_SIZE = BCDEC_BC7_BLOCK_SIZE;
break;
case FOURCC_DXT5:
decompress_dds = bcdec_bc3;
BLOCK_SIZE = BCDEC_BC3_BLOCK_SIZE;
break;
case FOURCC_DXT1:
decompress_dds = bcdec_bc1;
BLOCK_SIZE = BCDEC_BC1_BLOCK_SIZE;
break;
case FOURCC_DXT3:
decompress_dds = bcdec_bc2;
BLOCK_SIZE = BCDEC_BC2_BLOCK_SIZE;
break;
default:
Error(LOCATION, "Invalid FourCC (%d) for DDS decompression!", dds_header.ddspf.dwFourCC);
break;
}
for (int f = 0; f < num_faces; ++f) {
// if we resized then skip over all of that data (altering values to match pre-resize)
for (uint x = 0; x < mipmap_offset; ++x) {
@@ -454,7 +502,7 @@ int dds_read_bitmap(const char *filename, ubyte *data, ubyte *bpp, int cf_type)
d_height = std::max(1U, dds_header.dwHeight << (mipmap_offset - x));
d_depth = has_depth ? std::max(1U, dds_header.dwDepth << (mipmap_offset - x)) : 1U;
src += (d_width * d_height * d_depth);
src += ((d_width + 3) / 4) * ((d_height + 3) / 4) * d_depth * BLOCK_SIZE;
}
for (uint m = mipmap_offset; m < dds_header.dwMipMapCount; ++m) {
@@ -464,23 +512,14 @@ int dds_read_bitmap(const char *filename, ubyte *data, ubyte *bpp, int cf_type)
d_depth = std::max(1U, dds_header.dwDepth >> (m - mipmap_offset));
for (uint d = 0; d < d_depth; ++d) {
auto depth_offset = d * d_width * d_height * 4;
for (uint i = 0; i < d_height; i += 4) {
for (uint j = 0; j < d_width; j += 4) {
dst = data + data_offset + ((i * d_width + j) * 4);
dst = data + data_offset + depth_offset + ((i * d_width + j) * 4);
if (dds_header.ddspf.dwFourCC == FOURCC_DX10) {
bcdec_bc7(src, dst, d_width * 4);
src += BCDEC_BC7_BLOCK_SIZE;
} else if (dds_header.ddspf.dwFourCC == FOURCC_DXT5) {
bcdec_bc3(src, dst, d_width * 4);
src += BCDEC_BC3_BLOCK_SIZE;
} else if (dds_header.ddspf.dwFourCC == FOURCC_DXT1) {
bcdec_bc1(src, dst, d_width * 4);
src += BCDEC_BC1_BLOCK_SIZE;
} else if (dds_header.ddspf.dwFourCC == FOURCC_DXT3) {
bcdec_bc2(src, dst, d_width * 4);
src += BCDEC_BC2_BLOCK_SIZE;
}
decompress_dds(src, dst, d_width * 4);
src += BLOCK_SIZE;
}
}
}
+172 -78
View File
@@ -37,7 +37,6 @@
#include "utils/Random.h"
#include "weapon/weapon.h"
constexpr int MIN_RADIUS_FOR_PERSISTENT_DEBRIS = 50; // ship radius at which debris from it becomes persistant
constexpr int DEBRIS_SOUND_DELAY = 2000; // time to start debris sound after created
int Num_hull_pieces; // number of hull pieces in existence
@@ -59,30 +58,55 @@ particle::ParticleEffectHandle Debris_hit_particle;
#define DEBRIS_INDEX(dp) (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);
/**
* Start the sequence of a piece of debris writhing in unholy agony!!!
*/
static void debris_start_death_roll(object *debris_obj, debris *debris_p, vec3d *hitpos = nullptr)
{
auto sip = &Ship_info[debris_p->ship_info_index];
if (debris_p->is_hull) {
// tell everyone else to blow up the piece of debris
if( MULTIPLAYER_MASTER )
send_debris_update_packet(debris_obj,DEBRIS_UPDATE_NUKE);
int fireball_type = fireball_ship_explosion_type(&Ship_info[debris_p->ship_info_index]);
if(fireball_type < 0) {
fireball_type = FIREBALL_EXPLOSION_LARGE1 + Random::next(FIREBALL_NUM_LARGE_EXPLOSIONS);
if (sip->debris_end_particles.isValid()) {
auto source = particle::ParticleManager::get()->createSource(sip->debris_end_particles);
// Use the position since the object is going to be invalid soon
auto host = std::make_unique<EffectHostVector>(debris_obj->pos, debris_obj->orient, debris_obj->phys_info.vel);
host->setRadius(debris_obj->radius);
source->setHost(std::move(host));
source->setNormal(debris_obj->orient.vec.uvec);
source->finishCreation();
} else {
int fireball_type = fireball_ship_explosion_type(sip);
if(fireball_type < 0) {
fireball_type = FIREBALL_EXPLOSION_LARGE1 + Random::next(FIREBALL_NUM_LARGE_EXPLOSIONS);
}
fireball_create( &debris_obj->pos, fireball_type, FIREBALL_LARGE_EXPLOSION, OBJ_INDEX(debris_obj), debris_obj->radius*1.75f);
}
fireball_create( &debris_obj->pos, fireball_type, FIREBALL_LARGE_EXPLOSION, OBJ_INDEX(debris_obj), debris_obj->radius*1.75f);
// only play debris destroy sound if hull piece and it has been around for at least 2 seconds
if ( Missiontime > debris_p->time_started + 2*F1_0 ) {
auto snd_id = Ship_info[debris_p->ship_info_index].debris_explosion_sound;
auto snd_id = sip->debris_explosion_sound;
if (snd_id.isValid()) {
snd_play_3d( gamesnd_get_game_sound(snd_id), &debris_obj->pos, &View_position, debris_obj->radius );
}
}
} else {
if (sip->shrapnel_end_particles.isValid()) {
auto source = particle::ParticleManager::get()->createSource(sip->shrapnel_end_particles);
// Use the position since the object is going to be invalid soon
auto host = std::make_unique<EffectHostVector>(debris_obj->pos, debris_obj->orient, debris_obj->phys_info.vel);
host->setRadius(debris_obj->radius);
source->setHost(std::move(host));
source->setNormal(debris_obj->orient.vec.uvec);
source->finishCreation();
}
}
if (scripting::hooks::OnDebrisDeath->isActive()) {
@@ -119,10 +143,13 @@ void debris_init()
Debris_hit_particle = particle::ParticleManager::get()->addEffect(particle::ParticleEffect(
"", //Name
::util::UniformFloatRange(10.f), //Particle num
particle::ParticleEffect::Duration::ONETIME, //Single Particle Emission
::util::UniformFloatRange(), //No duration
::util::UniformFloatRange (-1.f), //Single particle only
particle::ParticleEffect::ShapeDirection::ALIGNED, //Particle direction
::util::UniformFloatRange(1.f), //Velocity Inherit
false, //Velocity Inherit absolute?
make_unique<particle::LegacyAACuboidVolume>(0.3f, 1.f, true), //Velocity volume
std::make_unique<particle::LegacyAACuboidVolume>(0.3f, 1.f, true), //Velocity volume
::util::UniformFloatRange(0.f, 10.f), //Velocity volume multiplier
particle::ParticleEffect::VelocityScaling::NONE, //Velocity directional scaling
std::nullopt, //Orientation-based velocity
@@ -133,6 +160,12 @@ void debris_init()
true, //Affected by detail
1.f, //Culling range multiplier
true, //Disregard Animation Length. Must be true for everything using particle::Anim_bitmap_X
false, //Don't reverse animation
false, //parent local
false, //ignore velocity inherit if parented
false, //position velocity inherit absolute?
std::nullopt, //Local velocity offset
std::nullopt, //Local offset
::util::UniformFloatRange(0.25f, 0.75f), //Lifetime
::util::UniformFloatRange(0.2f, 0.4f), //Radius
particle::Anim_bitmap_id_fire)); //Bitmap
@@ -215,6 +248,7 @@ void debris_delete( object * obj )
if (db->model_instance_num >= 0) {
model_delete_instance(db->model_instance_num);
db->model_instance_num = -1;
}
if ( db->is_hull ) {
@@ -266,56 +300,48 @@ void debris_process_post(object * obj, float frame_time)
return; // If arc_frequency <= 0, this piece has no arcs on it
}
if ( !timestamp_elapsed(db->fire_timeout) && timestamp_elapsed(db->next_fireball)) {
if ( !timestamp_elapsed(db->arc_timeout) && timestamp_elapsed(db->arc_next_time)) {
db->next_fireball = _timestamp_rand(db->arc_frequency,db->arc_frequency*2 );
db->arc_next_time = _timestamp_rand(db->arc_frequency,db->arc_frequency*2 );
db->arc_frequency += 100;
if (db->is_hull) {
int n, n_arcs = Random::next(1, 3); // Create 1-3 sparks
int n_arcs = Random::next(1, 3); // Create 1-3 sparks
vec3d v1 = submodel_get_random_point(db->model_num, db->submodel_num);
vec3d v2 = submodel_get_random_point(db->model_num, db->submodel_num);
vec3d v3 = submodel_get_random_point(db->model_num, db->submodel_num);
vec3d v4 = submodel_get_random_point(db->model_num, db->submodel_num);
n = 0;
int lifetime = Random::next(100, 1000);
// Create the spark effects
for (int i=0; i<MAX_DEBRIS_ARCS; ++i) {
if ( !db->arc_timestamp[i].isValid() ) {
db->arc_timestamp[i] = _timestamp(lifetime); // live up to a second
for (int n = 0; n < n_arcs; n++) {
auto arc = debris_find_or_create_electrical_arc_slot(db, db->electrical_arcs.size() >= MAX_DEBRIS_ARCS);
if (arc) {
arc->timestamp = _timestamp(lifetime); // live up to a second
switch( n ) {
case 0:
db->arc_pts[i][0] = v1;
db->arc_pts[i][1] = v2;
arc->endpoint_1 = v1;
arc->endpoint_2 = v2;
break;
case 1:
db->arc_pts[i][0] = v2;
db->arc_pts[i][1] = v3;
arc->endpoint_1 = v2;
arc->endpoint_2 = v3;
break;
case 2:
db->arc_pts[i][0] = v2;
db->arc_pts[i][1] = v4;
arc->endpoint_1 = v2;
arc->endpoint_2 = v4;
break;
default:
Int3();
UNREACHABLE("Unhandled case %d for electrical arc creation in debris_process_post()!", n);
}
n++;
if ( n == n_arcs )
break; // Don't need to create anymore
}
}
// rotate v2 out of local coordinates into world.
// Use v2 since it is used in every bolt. See above switch().
vec3d snd_pos;
@@ -342,16 +368,20 @@ void debris_process_post(object * obj, float frame_time)
}
}
for (int i=0; i<MAX_DEBRIS_ARCS; ++i) {
if ( db->arc_timestamp[i].isValid() ) {
if ( timestamp_elapsed( db->arc_timestamp[i] ) ) {
for (auto &arc: db->electrical_arcs) {
if (arc.timestamp.isValid()) {
if (timestamp_elapsed(arc.timestamp)) {
// Kill off the spark
db->arc_timestamp[i] = TIMESTAMP::invalid();
arc.timestamp = TIMESTAMP::invalid();
} else {
// Maybe move a vertex.... 20% of the time maybe?
int mr = Random::next();
if ( mr < Random::MAX_VALUE/5 ) {
db->arc_pts[i][mr % 2] = submodel_get_random_point(db->model_num, db->submodel_num);
auto pt = submodel_get_random_point(db->model_num, db->submodel_num);
if (mr % 2 == 0)
arc.endpoint_1 = pt;
else
arc.endpoint_2 = pt;
}
}
}
@@ -420,6 +450,32 @@ object *debris_create(object *source_obj, int model_num, int submodel_num, const
{
debris_create_set_velocity(&Debris[obj->instance], shipp, exp_center, exp_force, source_subsys);
debris_create_fire_hook(obj, source_obj);
const auto& sip = Ship_info[Ships[source_obj->instance].ship_info_index];
particle::ParticleEffectHandle flame_effect;
if (source_subsys != nullptr) {
if (hull_flag) {
if (source_subsys->system_info->debris_flame_particles.isValid()) {
flame_effect = source_subsys->system_info->debris_flame_particles;
} else {
flame_effect = sip.default_subsys_debris_flame_particles;
}
} else {
if (source_subsys->system_info->shrapnel_flame_particles.isValid()) {
flame_effect = source_subsys->system_info->shrapnel_flame_particles;
} else {
flame_effect = sip.default_subsys_shrapnel_flame_particles;
}
}
} else {
flame_effect = hull_flag ? sip.debris_flame_particles : sip.shrapnel_flame_particles;
}
if (flame_effect.isValid()) {
auto source = particle::ParticleManager::get()->createSource(flame_effect);
source->setHost(std::make_unique<EffectHostObject>(obj, vmd_zero_vector));
source->setTriggerRadius(source_obj->radius);
source->setTriggerVelocity(vm_vec_mag_quick(&source_obj->phys_info.vel));
source->finishCreation();
}
}
return obj;
@@ -552,22 +608,10 @@ object *debris_create_only(int parent_objnum, int parent_ship_class, int alt_typ
}
// Create Debris piece n!
if ( hull_flag ) {
if (Random::next() < (Random::MAX_VALUE/6)) // Make some pieces blow up shortly after explosion.
db->lifeleft = 2.0f * (frand()) + 0.5f;
else {
db->flags.set(Debris_Flags::DoNotExpire);
db->lifeleft = -1.0f; // large hull pieces stay around forever
}
} else {
// small non-hull pieces should stick around longer the larger they are
// sqrtf should make sure its never too crazy long
db->lifeleft = (frand() * 2.0f + 0.1f) * sqrtf(radius);
}
//WMC - Oh noes, we may need to change lifeleft
if(hull_flag)
{
// set lifeleft based on tabled entries if they are not negative
// coded originally by WMC then clean-up added by wookieejedi
if(sip->debris_min_lifetime >= 0.0f && sip->debris_max_lifetime >= 0.0f)
{
db->lifeleft = (( sip->debris_max_lifetime - sip->debris_min_lifetime ) * frand()) + sip->debris_min_lifetime;
@@ -582,6 +626,22 @@ object *debris_create_only(int parent_objnum, int parent_ship_class, int alt_typ
if(db->lifeleft > sip->debris_max_lifetime)
db->lifeleft = sip->debris_max_lifetime;
}
// By default, make some pieces blow up shortly after explosion.
else if (Random::next() < (Random::MAX_VALUE / 6))
{
db->lifeleft = 2.0f * (frand()) + 0.5f;
}
else
{
db->flags.set(Debris_Flags::DoNotExpire);
db->lifeleft = -1.0f; // large hull pieces stay around forever
}
}
else
{
// small non-hull pieces should stick around longer the larger they are
// sqrtf should make sure its never too crazy long
db->lifeleft = (frand() * 2.0f + 0.1f) * sqrtf(radius);
}
// increase lifetime for vaporized debris
@@ -595,15 +655,13 @@ object *debris_create_only(int parent_objnum, int parent_ship_class, int alt_typ
db->ship_info_index = parent_ship_class;
db->team = team;
db->ambient_sound = (sip == nullptr) ? gamesnd_id(-1) : sip->debris_ambient_sound;
db->fire_timeout = TIMESTAMP::never(); // if not changed, timestamp_elapsed() will return false
db->arc_timeout = TIMESTAMP::never(); // if not changed, timestamp_elapsed() will return false
db->time_started = Missiontime;
db->species = (sip == nullptr) ? -1 : sip->species;
db->parent_alt_name = alt_type_index;
db->damage_mult = 1.0f;
for (int i=0; i<MAX_DEBRIS_ARCS; ++i) { // NOLINT
db->arc_timestamp[i] = TIMESTAMP::invalid();
}
db->electrical_arcs.clear();
if ( db->is_hull ) {
// Percent of debris pieces with arcs controlled via table (default 50%)
@@ -616,7 +674,7 @@ object *debris_create_only(int parent_objnum, int parent_ship_class, int alt_typ
db->arc_frequency = 0;
}
db->next_fireball = _timestamp_rand(500,2000); //start one 1/2 - 2 secs later
db->arc_next_time = _timestamp_rand(500,2000); //start one 1/2 - 2 secs later
flagset<Object::Object_Flags> default_flags;
default_flags.set(Object::Object_Flags::Renders);
@@ -650,19 +708,25 @@ object *debris_create_only(int parent_objnum, int parent_ship_class, int alt_typ
obj->hull_strength = hull_strength;
if (hull_flag) {
if(sip->debris_min_hitpoints >= 0.0f && sip->debris_max_hitpoints >= 0.0f)
{
obj->hull_strength = (( sip->debris_max_hitpoints - sip->debris_min_hitpoints ) * frand()) + sip->debris_min_hitpoints;
float min_hp = sip->debris_min_hitpoints;
float max_hp = sip->debris_max_hitpoints;
if (sip->debris_hitpoints_radius_multi >= 0.0f) {
min_hp *= sip->debris_hitpoints_radius_multi * radius;
max_hp *= sip->debris_hitpoints_radius_multi * radius;
}
else if(sip->debris_min_hitpoints >= 0.0f)
if (min_hp >= 0.0f && max_hp >= 0.0f)
{
if(obj->hull_strength < sip->debris_min_hitpoints)
obj->hull_strength = sip->debris_min_hitpoints;
obj->hull_strength = ((max_hp - min_hp) * frand()) + min_hp;
}
else if(sip->debris_max_hitpoints >= 0.0f)
else if(min_hp >= 0.0f)
{
if(obj->hull_strength > sip->debris_max_hitpoints)
obj->hull_strength = sip->debris_max_hitpoints;
if (obj->hull_strength < min_hp)
obj->hull_strength = min_hp;
}
else if(max_hp >= 0.0f)
{
if (obj->hull_strength > max_hp)
obj->hull_strength = max_hp;
}
db->damage_mult = sip->debris_damage_mult;
@@ -671,21 +735,23 @@ object *debris_create_only(int parent_objnum, int parent_ship_class, int alt_typ
// limit the amount of time that fireballs appear
// let fireball length be linked to radius of ship. Range is .33 radius => 3.33 radius seconds.
if (spark_timeout >= 0) {
db->fire_timeout = _timestamp(spark_timeout);
db->arc_timeout = _timestamp(spark_timeout);
} else if (parent_objnum >= 0) {
float t = 1000*Objects[parent_objnum].radius/3 + (fl2i(1000*3*Objects[parent_objnum].radius) == 0 ? 0 : Random::next(fl2i(1000*3*Objects[parent_objnum].radius)));
db->fire_timeout = _timestamp(fl2i(t)); // fireballs last from 5 - 30 seconds
db->arc_timeout = _timestamp(fl2i(t)); // fireballs last from 5 - 30 seconds
} else {
db->fire_timeout = TIMESTAMP::immediate();
db->arc_timeout = TIMESTAMP::immediate();
}
if (parent_objnum >= 0 && Objects[parent_objnum].radius >= MIN_RADIUS_FOR_PERSISTENT_DEBRIS) {
if (parent_objnum >= 0 && Objects[parent_objnum].radius >= Min_radius_for_persistent_debris) {
db->flags.set(Debris_Flags::DoNotExpire);
} else {
debris_add_to_hull_list(db);
}
}
db->max_hull = obj->hull_strength;
if (hull_flag) {
MONITOR_INC(NumHullDebris,1);
} else {
@@ -965,9 +1031,9 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
model_get_moving_submodel_list(submodel_vector, heavy_obj);
// turn off all moving submodels, collide against only 1 at a time.
// turn off collision detection for all moving submodels
mc.collision_checked.assign(pm->n_models, 0);
for (auto submodel : submodel_vector) {
pmi->submodel[submodel].collision_checked = true;
mc.collision_checked[submodel] = true;
}
// Only check single submodel now, since children of moving submodels are handled as moving as well
@@ -979,10 +1045,8 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
// check each submodel in turn
for (auto submodel: submodel_vector) {
auto smi = &pmi->submodel[submodel];
// turn on just one submodel for collision test
smi->collision_checked = false;
mc.collision_checked[submodel] = false;
// find the start and end positions of the sphere in submodel RF
model_instance_global_to_local_point(&p0, &light_obj->last_pos, pm, pmi, submodel, &heavy_obj->last_orient, &heavy_obj->last_pos, true);
@@ -1016,8 +1080,11 @@ int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, co
}
// Don't look at this submodel again
smi->collision_checked = true;
mc.collision_checked[submodel] = true;
}
// Clear collision_checked before base model pass so it auto-inits fresh
mc.collision_checked.clear();
}
// Now complete base model collision checks that do not take into account rotating submodels.
@@ -1162,7 +1229,7 @@ void calc_debris_physics_properties( physics_info *pi, vec3d *mins, vec3d *maxs,
*/
void debris_render(object * obj, model_draw_list *scene)
{
int i, num, swapped;
int num, swapped;
debris *db;
swapped = -1;
@@ -1197,9 +1264,9 @@ void debris_render(object * obj, model_draw_list *scene)
// Only render electrical arcs if within 500m of the eye (for a 10m piece)
if ( vm_vec_dist_quick( &obj->pos, &Eye_position ) < obj->radius*50.0f ) {
for (i=0; i<MAX_DEBRIS_ARCS; i++ ) {
if ( db->arc_timestamp[i].isValid() ) {
model_instance_add_arc( pm, pmi, db->submodel_num, &db->arc_pts[i][0], &db->arc_pts[i][1], MARC_TYPE_DAMAGED );
for (auto &arc: db->electrical_arcs) {
if ( arc.timestamp.isValid() ) {
model_instance_add_arc( pm, pmi, db->submodel_num, &arc.endpoint_1, &arc.endpoint_2, nullptr, MARC_TYPE_DAMAGED );
}
}
}
@@ -1240,13 +1307,40 @@ void create_generic_debris(object* ship_objp, const vec3d* pos, float min_num_de
if (ship_objp->type != OBJ_SHIP)
return;
// determine if using custom or non-custom generic debris
int model_num = use_ship_debris ? Ship_info[Ships[ship_objp->instance].ship_info_index].generic_debris_model_num : -1;
// if using non-custom generic debris, then bail early if that kind of debris is not allowed --wookeejedi
if ( (model_num < 0) && (Disable_all_noncustom_generic_debris) ) {
return;
}
float num_debris = frand_range(min_num_debris, max_num_debris);
num_debris *= (Detail.num_small_debris + 0.5f) / 4.5f;
vec3d create_pos = *pos;
for (int i = 0; i < num_debris; i++) {
int model_num = use_ship_debris ? Ship_info[Ships[ship_objp->instance].ship_info_index].generic_debris_model_num : -1;
debris_create(ship_objp, model_num, -1, &create_pos, pos, 0, speed_mult);
debris_create(ship_objp, model_num, -1, &create_pos, pos, false, speed_mult);
}
}
debris_electrical_arc *debris_find_or_create_electrical_arc_slot(debris *db, bool no_create)
{
size_t i = 0;
for (auto& ii : db->electrical_arcs)
{
if (!ii.timestamp.isValid())
break;
i++;
}
if (i == db->electrical_arcs.size())
{
if (no_create)
return nullptr;
db->electrical_arcs.emplace_back();
}
return &db->electrical_arcs[i];
}
+11 -5
View File
@@ -20,7 +20,7 @@ class object;
struct CFILE;
class model_draw_list;
#define MAX_DEBRIS_ARCS 8 // Must be less than MAX_ARC_EFFECTS in model.h
#define MAX_DEBRIS_ARCS 8
FLAG_LIST(Debris_Flags) {
Used,
@@ -30,6 +30,12 @@ FLAG_LIST(Debris_Flags) {
NUM_VALUES
};
struct debris_electrical_arc
{
vec3d endpoint_1;
vec3d endpoint_2;
TIMESTAMP timestamp; // When this times out, the spark goes away. Invalid is not used
};
typedef struct debris {
flagset<Debris_Flags> flags; // See DEBRIS_??? defines
@@ -43,15 +49,15 @@ typedef struct debris {
int model_num; // What model this uses
int model_instance_num; // What model instance this uses - needed for arcs
int submodel_num; // What submodel this uses
TIMESTAMP next_fireball; // When to start a fireball
TIMESTAMP arc_next_time; // When the next damage/emp arc will be created.
bool is_hull; // indicates whether this is a collideable, destructable piece of debris from the model, or just a generic debris fragment
float max_hull;
int species; // What species this is from. -1 if don't care.
TIMESTAMP fire_timeout; // timestamp that holds time for fireballs to stop appearing
TIMESTAMP arc_timeout; // timestamp that holds time for arcs to stop appearing
TIMESTAMP sound_delay; // timestamp to signal when sound should start
fix time_started; // time when debris was created
vec3d arc_pts[MAX_DEBRIS_ARCS][2]; // The endpoints of each arc
TIMESTAMP arc_timestamp[MAX_DEBRIS_ARCS]; // When this times out, the spark goes away. Invalid is not used
SCP_vector<debris_electrical_arc> electrical_arcs;
int arc_frequency; // Starts at 1000, gets bigger
int parent_alt_name;
+1 -1
View File
@@ -467,7 +467,7 @@ void dc_putc(char c)
*/
temp_str = line_str->substr(lastwhite);
line_str->resize(lastwhite);
dc_buffer.push_back(temp_str);
dc_buffer.push_back(std::move(temp_str));
line_str = &dc_buffer.back();
if ((dc_buffer.size() > DROWS) && (dc_scroll_y < SCROLL_Y_MAX)) {
+2 -2
View File
@@ -1083,7 +1083,7 @@ void dc_stuff_int(int *i)
value_l = dc_parse_long(token.c_str(), DCT_INT);
if ((value_l < INT_MAX) && (value_l > INT_MIN)) {
*i = value_l;
*i = static_cast<int>(value_l);
} else {
throw errParse(token.c_str(), DCT_INT);
@@ -1101,7 +1101,7 @@ void dc_stuff_uint(uint *i)
value_l = dc_parse_long(Cp, DCT_INT);
if (value_l < UINT_MAX) {
*i = value_l;
*i = static_cast<uint>(value_l);
} else {
throw errParse(token.c_str(), DCT_INT);
+127 -94
View File
@@ -181,66 +181,61 @@ void parse_decals_table(const char* filename) {
}
}
struct Decal {
int definition_handle = -1;
object_h object;
int orig_obj_type = OBJ_NONE;
int submodel = -1;
Decal::Decal() {
vm_vec_make(&scale, 1.f, 1.f, 1.f);
}
float creation_time = -1.0f; //!< The mission time at which this decal was created
float lifetime = -1.0f; //!< The time this decal is active. When negative it never expires
void Decal::markForDeletion() {
orig_obj_type = OBJ_NONE;
}
vec3d position = vmd_zero_vector;
vec3d scale;
matrix orientation = vmd_identity_matrix;
Decal() {
vm_vec_make(&scale, 1.f, 1.f, 1.f);
bool Decal::isValid() const {
if (!object.isValid()) {
return false;
}
if (object.objp()->flags[Object::Object_Flags::Should_be_dead]) {
return false;
}
bool isValid() const {
if (!object.isValid()) {
return false;
}
if (object.objp()->flags[Object::Object_Flags::Should_be_dead]) {
return false;
}
if (orig_obj_type != object.objp()->type) {
mprintf(("Decal object type for object %d has changed from %s to %s. Please let m!m know about this\n",
object.objnum, Object_type_names[orig_obj_type], Object_type_names[object.objp()->type]));
return false;
}
if (lifetime > 0.0f) {
if (f2fl(Missiontime) >= creation_time + lifetime) {
// Decal has expired
return false;
}
}
auto objp = object.objp();
if (objp->type == OBJ_SHIP) {
auto shipp = &Ships[objp->instance];
auto model_instance = model_get_instance(shipp->model_instance_num);
Assertion(submodel >= 0 && submodel < object_get_model(objp)->n_models,
"Invalid submodel number detected!");
auto smi = &model_instance->submodel[submodel];
if (smi->blown_off) {
return false;
}
} else {
Assertion(false, "Only ships are currently supported for decals!");
return false;
}
return true;
if (orig_obj_type == OBJ_NONE) {
//Decal should be cleared
return false;
}
};
SCP_vector<Decal> active_decals;
if (orig_obj_type != object.objp()->type) {
mprintf(("Decal object type for object %d has changed from %s to %s. Please let m!m know about this\n",
object.objnum, Object_type_names[orig_obj_type], Object_type_names[object.objp()->type]));
return false;
}
if (lifetime > 0.0f) {
if (f2fl(Missiontime) >= creation_time + lifetime) {
// Decal has expired
return false;
}
}
auto objp = object.objp();
if (objp->type == OBJ_SHIP) {
auto shipp = &Ships[objp->instance];
auto model_instance = model_get_instance(shipp->model_instance_num);
Assertion(submodel >= 0 && submodel < object_get_model(objp)->n_models,
"Invalid submodel number detected!");
auto smi = &model_instance->submodel[submodel];
if (smi->blown_off) {
return false;
}
} else {
Assertion(false, "Only ships are currently supported for decals!");
return false;
}
return true;
}
SCP_vector<Decal> active_decals, active_single_frame_decals;
bool required_string_if_new(const char* token, bool new_entry) {
if (!new_entry) {
@@ -370,7 +365,11 @@ void initializeMission() {
active_decals.clear();
}
matrix4 getDecalTransform(Decal& decal) {
// Discard any fragments where the angle to the direction to greater than 45°
const float DECAL_ANGLE_CUTOFF = fl_radians(45.f);
const float DECAL_ANGLE_FADE_START = fl_radians(30.f);
static matrix4 getDecalTransform(const Decal& decal, float alpha) {
Assertion(decal.object.objp()->type == OBJ_SHIP, "Only ships are currently supported for decals!");
auto objp = decal.object.objp();
@@ -405,11 +404,62 @@ matrix4 getDecalTransform(Decal& decal) {
matrix4 mat4;
vm_matrix4_set_transform(&mat4, &worldOrient, &worldPos);
// This is currently a constant but in the future this may be configurable by the decals table
mat4.a2d[0][3] = DECAL_ANGLE_CUTOFF;
mat4.a2d[1][3] = DECAL_ANGLE_FADE_START;
mat4.a2d[2][3] = alpha;
return mat4;
}
inline static void renderDecal(graphics::decal_draw_list& draw_list, const Decal& decal) {
auto mission_time = f2fl(Missiontime);
int diffuse_bm = -1;
int glow_bm = -1;
int normal_bm = -1;
auto decal_time = mission_time - decal.creation_time;
auto progress = decal_time / decal.lifetime;
float alpha = 1.0f;
if (progress > 0.8) {
// Fade the decal out for the last 20% of its lifetime
alpha = 1.0f - smoothstep(0.8f, 1.0f, progress);
}
if (std::holds_alternative<int>(decal.definition_handle)) {
int definition_handle = std::get<int>(decal.definition_handle);
Assertion(definition_handle >= 0 && definition_handle < (int) DecalDefinitions.size(),
"Invalid decal handle detected!");
auto &decalDef = DecalDefinitions[definition_handle];
if (decalDef.getDiffuseBitmap() >= 0) {
diffuse_bm = decalDef.getDiffuseBitmap()
+
bm_get_anim_frame(decalDef.getDiffuseBitmap(), decal_time, 0.0f, decalDef.isDiffuseLooping());
}
if (decalDef.getGlowBitmap() >= 0) {
glow_bm = decalDef.getGlowBitmap()
+ bm_get_anim_frame(decalDef.getGlowBitmap(), decal_time, 0.0f, decalDef.isGlowLooping());
}
if (decalDef.getNormalBitmap() >= 0) {
normal_bm = decalDef.getNormalBitmap()
+ bm_get_anim_frame(decalDef.getNormalBitmap(), decal_time, 0.0f, decalDef.isNormalLooping());
}
}
else {
std::tie(diffuse_bm, glow_bm, normal_bm) = std::get<std::tuple<int, int, int>>(decal.definition_handle);
}
draw_list.add_decal(diffuse_bm, glow_bm, normal_bm, decal_time, getDecalTransform(decal, alpha));
}
void renderAll() {
if (!Decal_system_active || !Decal_option_active) {
if (!Decal_system_active || !Decal_option_active || !gr_is_capable(gr_capability::CAPABILITY_INSTANCED_RENDERING)) {
return;
}
@@ -432,51 +482,21 @@ void renderAll() {
++iter;
}
if (active_decals.empty()) {
if (active_decals.empty() && active_single_frame_decals.empty()) {
return;
}
auto mission_time = f2fl(Missiontime);
graphics::decal_draw_list draw_list(active_decals.size());
for (auto& decal : active_decals) {
Assertion(decal.definition_handle >= 0 && decal.definition_handle < (int)DecalDefinitions.size(),
"Invalid decal handle detected!");
auto& decalDef = DecalDefinitions[decal.definition_handle];
int diffuse_bm = -1;
int glow_bm = -1;
int normal_bm = -1;
auto decal_time = mission_time - decal.creation_time;
auto progress = decal_time / decal.lifetime;
float alpha = 1.0f;
if (progress > 0.8) {
// Fade the decal out for the last 20% of its lifetime
alpha = 1.0f - smoothstep(0.8f, 1.0f, progress);
}
if (decalDef.getDiffuseBitmap() >= 0) {
diffuse_bm = decalDef.getDiffuseBitmap()
+ bm_get_anim_frame(decalDef.getDiffuseBitmap(), decal_time, 0.0f, decalDef.isDiffuseLooping());
}
if (decalDef.getGlowBitmap() >= 0) {
glow_bm = decalDef.getGlowBitmap()
+ bm_get_anim_frame(decalDef.getGlowBitmap(), decal_time, 0.0f, decalDef.isGlowLooping());
}
if (decalDef.getNormalBitmap() >= 0) {
normal_bm = decalDef.getNormalBitmap()
+ bm_get_anim_frame(decalDef.getNormalBitmap(), decal_time, 0.0f, decalDef.isNormalLooping());
}
draw_list.add_decal(diffuse_bm, glow_bm, normal_bm, decal_time, getDecalTransform(decal), alpha);
}
graphics::decal_draw_list draw_list;
for (auto& decal : active_decals)
renderDecal(draw_list, decal);
for (auto& decal : active_single_frame_decals)
renderDecal(draw_list, decal);
draw_list.render();
active_single_frame_decals.clear();
}
void addDecal(creation_info& info, const object* host, int submodel, const vec3d& local_pos, const matrix& local_orient) {
@@ -521,4 +541,17 @@ void addDecal(creation_info& info, const object* host, int submodel, const vec3d
active_decals.push_back(newDecal);
}
void addSingleFrameDecal(Decal&& info) {
active_single_frame_decals.push_back(info);
}
void invalidateForShip(const ship* shipp) {
int objnum = shipp->objnum;
for (Decal& decal : active_decals) {
if (decal.object.objnum == objnum) {
decal.markForDeletion();
}
}
}
}
+25
View File
@@ -50,6 +50,27 @@ class DecalDefinition {
bool isNormalLooping() const;
};
struct Decal {
//DecalDefinition idx vs immediate diffuse/glow/normal
std::variant<int, std::tuple<int, int, int>> definition_handle = -1;
object_h object;
int orig_obj_type = OBJ_NONE;
int submodel = -1;
float creation_time = -1.0f; //!< The mission time at which this decal was created
float lifetime = -1.0f; //!< The time this decal is active. When negative it never expires
vec3d position = vmd_zero_vector;
vec3d scale;
matrix orientation = vmd_identity_matrix;
Decal();
void markForDeletion();
bool isValid() const;
};
extern SCP_vector<DecalDefinition> DecalDefinitions;
extern bool Decal_system_active;
extern bool Decal_option_active;
@@ -139,4 +160,8 @@ void addDecal(creation_info& info,
const vec3d& local_pos,
const matrix& local_orient);
void addSingleFrameDecal(Decal&& info);
void invalidateForShip(const ship* shipp);
}
+8 -5
View File
@@ -65,7 +65,7 @@ $Max Player Attackers: 2, 3, 4, 5, 99
;; maximum number of active (i.e. 'thrown') asteroids that can be
;; heading toward a friendly ship at any given time
$Max Incoming Asteroids: 3, 4, 5, 7, 10
$Max Incoming Asteroids: 3, 4, 5, 7, 10
;; factor applied to damage suffered by the player
$Player Damage Factor: 0.25, 0.5, 0.65, 0.85, 1
@@ -181,9 +181,8 @@ $Player Autoaim FOV: 0, 0, 0, 0, 0
$Detail Distance Multiplier: 0.125, 0.25, 1.0, 4.0, 8.0
;; General AI-related flags. These were previously all lumped together
;; under the New AI mission flag.
;; under the New AI mission flag.
;; if set, big ships can attack a beam turret that's firing on them
;; from a ship that they don't currently have targeted.
$big ships can attack beam turrets on untargeted ships: NO
@@ -354,6 +353,10 @@ $no warp camera: NO
;; If set, this flag overrides the retail behavior whereby a ship
;; assigned to guard a ship in a wing will instead guard the entire wing
$ai guards specific ship in wing: NO
$ai guards specific ship in wing: NO
;; maximum number of allies allowed to be rearming at the same time
;; before the AI will hold off on requesting support
$max allies rearming threshold: 2
#End
+11 -23
View File
@@ -10,6 +10,12 @@ out vec4 fragOut0; // Diffuse buffer
out vec4 fragOut1; // Normal buffer
out vec4 fragOut2; // Emissive buffer
flat in mat4 invModelMatrix;
flat in vec3 decalDirection;
flat in float normal_angle_cutoff;
flat in float angle_fade_start;
flat in float alpha_scale;
uniform sampler2D gDepthBuffer;
uniform sampler2D gNormalBuffer;
@@ -23,24 +29,15 @@ layout (std140) uniform decalGlobalData {
mat4 invViewMatrix;
mat4 invProjMatrix;
vec3 ambientLight;
vec2 viewportSize;
};
layout (std140) uniform decalInfoData {
mat4 model_matrix;
mat4 inv_model_matrix;
vec3 decal_direction;
float normal_angle_cutoff;
int diffuse_index;
int glow_index;
int normal_index;
float angle_fade_start;
float alpha_scale;
int diffuse_blend_mode;
int glow_blend_mode;
};
@@ -77,7 +74,7 @@ vec3 getPixelNormal(vec3 frag_position, vec2 tex_coord, inout float alpha, out v
#endif
//Calculate angle between surface normal and decal direction
float angle = acos(dot(normal, decal_direction));
float angle = acos(dot(normal, decalDirection));
if (angle > normal_angle_cutoff) {
// The angle between surface normal and decal direction is too big
@@ -91,7 +88,7 @@ vec3 getPixelNormal(vec3 frag_position, vec2 tex_coord, inout float alpha, out v
}
vec2 getDecalTexCoord(vec3 view_pos, inout float alpha) {
vec4 object_pos = inv_model_matrix * invViewMatrix * vec4(view_pos, 1.0);
vec4 object_pos = invModelMatrix * invViewMatrix * vec4(view_pos, 1.0);
bvec3 invalidComponents = greaterThan(abs(object_pos.xyz), vec3(0.5));
bvec4 nanComponents = isnan(object_pos); // nan can happen some times if we have an infinite depth value
@@ -111,6 +108,7 @@ void main() {
vec3 frag_position = computeViewPosition(gl_FragCoord.xy);
float alpha = alpha_scale;
vec2 tex_coord = getDecalTexCoord(frag_position, alpha);
vec3 binormal;
@@ -134,16 +132,6 @@ void main() {
// Additive blending
diffuse_out = vec4(color.rgb * alpha, 1.0);
}
// The main model shader applies ambient lighting by drawing the ambient part of the texture into the emissive
// texture. We do the same here to make sure the decal material is applied correctly
if (glow_blend_mode == 0) {
// Normal alpha blending
emissive_out = vec4(color.rgb * ambientLight, color.a * alpha);
} else {
// Additive blending
emissive_out = vec4(alpha * color.rgb * ambientLight, 1.0);
}
}
if (glow_index >= 0) {
+21 -12
View File
@@ -1,5 +1,12 @@
in vec4 vertPosition;
in mat4 vertModelMatrix;
flat out mat4 invModelMatrix;
flat out vec3 decalDirection;
flat out float normal_angle_cutoff;
flat out float angle_fade_start;
flat out float alpha_scale;
layout (std140) uniform decalGlobalData {
mat4 viewMatrix;
@@ -7,27 +14,29 @@ layout (std140) uniform decalGlobalData {
mat4 invViewMatrix;
mat4 invProjMatrix;
vec3 ambientLight;
vec2 viewportSize;
};
layout (std140) uniform decalInfoData {
mat4 model_matrix;
mat4 inv_model_matrix;
vec3 decal_direction;
float normal_angle_cutoff;
int diffuse_index;
int glow_index;
int normal_index;
float angle_fade_start;
float alpha_scale;
int diffuse_blend_mode;
int glow_blend_mode;
};
void main() {
gl_Position = projMatrix * viewMatrix * model_matrix * vertPosition;
normal_angle_cutoff = vertModelMatrix[0][3];
angle_fade_start = vertModelMatrix[1][3];
alpha_scale = vertModelMatrix[2][3];
mat4 modelMatrix = vertModelMatrix;
modelMatrix[0][3] = 0.0;
modelMatrix[1][3] = 0.0;
modelMatrix[2][3] = 0.0;
invModelMatrix = inverse(modelMatrix);
decalDirection = mat3(viewMatrix) * modelMatrix[2].xyz;
gl_Position = projMatrix * viewMatrix * modelMatrix * vertPosition;
}
@@ -7,7 +7,7 @@ out vec4 fragOut5;
void main()
{
fragOut0 = vec4(0.0, 0.0, 0.0, 1.0); // color
fragOut1 = vec4(0.0, 0.0, -1000000.0, 1.0); // position
fragOut1 = vec4(0.0, 0.0, -1000000.0, 0.0); // position
fragOut2 = vec4(0.0, 0.0, 0.0, 1.0); // normal
fragOut3 = vec4(0.0, 0.0, 0.0, 0.0); // specular
fragOut4 = vec4(0.0, 0.0, 0.0, 1.0); // emissive
+93 -25
View File
@@ -1,7 +1,8 @@
//? #version 150
#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;
@@ -11,6 +12,11 @@ uniform sampler2D PositionBuffer;
uniform sampler2D SpecBuffer;
uniform sampler2DArray shadow_map;
#ifdef ENV_MAP
uniform samplerCube sEnvmap;
uniform samplerCube sIrrmap;
#endif
layout (std140) uniform globalDeferredData {
mat4 shadow_mv_matrix;
mat4 shadow_proj_matrix[4];
@@ -97,7 +103,7 @@ void GetLightInfo(vec3 position, in float alpha, in vec3 reflectDir, out vec3 li
discard;
}
attenuation = 1.0 - clamp(sqrt(dist / lightRadius), 0.0, 1.0);
}
}
else if (lightType == LT_TUBE) { // Tube light
vec3 beamVec = vec3(modelViewMatrix * vec4(0.0, 0.0, -scale.z, 0.0));
vec3 beamDir = normalize(beamVec);
@@ -138,7 +144,7 @@ void GetLightInfo(vec3 position, in float alpha, in vec3 reflectDir, out vec3 li
discard;
}
attenuation = 1.0 - clamp(sqrt(dist / lightRadius), 0.0, 1.0);
}
}
else if (lightType == LT_CONE) {
lightDirOut = lightPosition - position.xyz;
float coneDot = dot(normalize(-lightDirOut), coneDir);
@@ -165,46 +171,108 @@ void GetLightInfo(vec3 position, in float alpha, in vec3 reflectDir, out vec3 li
}
}
#ifdef ENV_MAP
void ComputeEnvLight(float alpha, float ao, vec3 light_dir, vec3 eyeDir, vec3 normal, vec4 baseColor, vec4 specColor, out vec3 envLight) {
// Bit of a hack here, pretend we're doing blinn-phong shading and use a (modified version of) the derivation from
// Plausible Blinn-Phong Reflection of Standard Cube MIP-Maps - McGuire et al.
// to determine the mip bias.
// We use the specular term listed at http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html
// 1/(pi*alpha^2) * NdotM^((2/alpha^2)-2)
// 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));
float alphaSqr = alpha * alpha;
float rough_bias = 0.5 * log2(2/alphaSqr - 1);
float mip_bias = REZ_BIAS - rough_bias;
// Sample light, using mip bias to blur it.
vec3 env_light_dir = vec3(modelViewMatrix * vec4(light_dir, 0.0));
vec4 specEnvColour = srgb_to_linear(textureLod(sEnvmap, env_light_dir, mip_bias));
vec3 halfVec = normal;
// Lots of hacks here to get things to look right. We aren't loading a BRDF split-sum integral texture
// or properly calculating IBL levels.
// Fresnel calculation as with standard lights.
vec3 fresnel = mix(specColor.rgb, FresnelSchlick(halfVec, eyeDir, specColor.rgb), specColor.a);
// Pseudo-IBL, so use k_IBL
float k = alpha * alpha/ 2.0f;
float NdotL = max(dot(light_dir, normal),0);
float g1vNL = GeometrySchlickGGX(NdotL, k);
vec3 specEnvLighting = specEnvColour.rgb * fresnel * g1vNL;
vec3 kD = vec3(1.0)-fresnel;
kD *= (vec3(1.0) - specColor.rgb);
vec3 diffEnvColor = srgb_to_linear(texture(sIrrmap, vec3(modelViewMatrix * vec4(normal, 0.0))).rgb);
vec3 diffEnvLighting = kD * baseColor.rgb * diffEnvColor * ao;
envLight = (specEnvLighting + diffEnvLighting) * baseColor.a;
}
#endif
void main()
{
vec2 screenPos = gl_FragCoord.xy * vec2(invScreenWidth, invScreenHeight);
vec3 position = texture(PositionBuffer, screenPos).xyz;
vec4 position_buffer = texture(PositionBuffer, screenPos);
vec3 position = vec3(position_buffer.xy, uncompress_depth_value(position_buffer.z));
if(abs(dot(position, position)) < nearPlane * nearPlane)
discard;
vec3 diffColor = texture(ColorBuffer, screenPos).rgb;
vec4 diffuse = texture(ColorBuffer, screenPos);
vec3 diffColor = diffuse.rgb;
vec4 normalData = texture(NormalBuffer, screenPos);
vec4 specColor = texture(SpecBuffer, screenPos);
// The vector in the normal buffer could be longer than the unit vector since decal rendering only adds to the normal buffer
vec3 normal = normalize(normalData.xyz);
float gloss = normalData.a;
float roughness = clamp(1.0f - gloss, 0.0f, 1.0f);
float alpha = roughness * roughness;
float fresnel = specColor.a;
vec3 eyeDir = normalize(-position);
vec3 lightDir;
float attenuation;
float area_normalisation;
vec3 reflectDir = reflect(-eyeDir, normal);
GetLightInfo(position, alpha, reflectDir, lightDir, attenuation, area_normalisation);
vec4 specColor = texture(SpecBuffer, screenPos);
if (enable_shadows) {
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 fragmentColor = vec4(1.0);
attenuation *= getShadowValue(shadow_map, -position.z, fragShadowPos.z, fragShadowUV, fardist, middist,
neardist, veryneardist);
if (lightType == LT_AMBIENT) {
float ao = position_buffer.w;
fragmentColor.rgb = diffuseLightColor * diffColor * ao;
#ifdef ENV_MAP
vec3 envLight;
ComputeEnvLight(alpha, ao, reflectDir, eyeDir, normal, diffuse, specColor, envLight);
fragmentColor.rgb += envLight;
#endif
}
else {
float fresnel = specColor.a;
vec3 lightDir;
float attenuation;
float area_normalisation;
GetLightInfo(position, alpha, reflectDir, lightDir, attenuation, area_normalisation);
if (enable_shadows) {
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);
attenuation *= getShadowValue(shadow_map, -position.z, fragShadowPos.z, fragShadowUV, fardist, middist,
neardist, veryneardist);
}
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;
}
vec3 halfVec = normalize(lightDir + eyeDir);
float NdotL = clamp(dot(normal, lightDir), 0.0, 1.0);
vec4 fragmentColor = vec4(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));
}
+1 -1
View File
@@ -29,7 +29,7 @@ layout (std140) uniform lightData {
void main()
{
if (lightType == LT_DIRECTIONAL) {
if (lightType == LT_DIRECTIONAL || lightType == LT_AMBIENT) {
gl_Position = vec4(vertPosition.xyz, 1.0);
} else {
gl_Position = projMatrix * modelViewMatrix * vec4(vertPosition.xyz * scale, 1.0);
+2 -1
View File
@@ -1,5 +1,6 @@
#include "gamma.sdr"
#include "z-compress.sdr"
in float fragRadius;
in vec4 fragPosition;
@@ -38,7 +39,7 @@ void main()
float sceneDepthLinear;
float fragDepthLinear;
if ( linear_depth == 1 ) {
sceneDepthLinear = -sceneDepth.z;
sceneDepthLinear = -uncompress_depth_value(sceneDepth.z);
fragDepthLinear = -fragPosition.z;
} else {
sceneDepthLinear = ( 2.0 * farZ * nearZ ) / ( farZ + nearZ - sceneDepth.x * (farZ-nearZ) );
+6
View File
@@ -15,6 +15,9 @@ layout (std140) uniform genericData {
float fog_density;
float zNear;
float zFar;
float clip_inf_dist;
float clip_dist;
};
void main()
@@ -28,6 +31,9 @@ void main()
// Now we compute the depth value in projection space using the clipping plane information
float view_depth = 2.0 * zNear * zFar / (zFar + zNear - depth_normalized * (zFar - zNear));
float skybox_compat_fake = step(1.0, depth_val);
view_depth = clamp(view_depth, 0.0, mix(clip_dist, clip_inf_dist, skybox_compat_fake));
if (isinf(view_depth)) {
fragOut0.rgb = color_in.rgb;
} else {
+1
View File
@@ -3,6 +3,7 @@ const int LT_DIRECTIONAL = 0; // A light like a sun
const int LT_POINT = 1; // A point light, like an explosion
const int LT_TUBE = 2; // A tube light, like a fluorescent light
const int LT_CONE = 3; // A cone light, like a flood light
const int LT_AMBIENT = 4; // Directionless ambient light
const float SPEC_FACTOR_NO_SPEC_MAP = 0.1;
const float GLOW_MAP_INTENSITY = 1.5;
+23 -79
View File
@@ -2,6 +2,7 @@
#include "shadows.sdr" //! #include "shadows.sdr"
#include "lighting.sdr" //! #include "lighting.sdr"
#include "gamma.sdr" //! #include "gamma.sdr"
#include "z-compress.sdr" //! #include "z-compress.sdr"
#conditional_include +"LARGE_SHADER" "main_large.sdr"
#conditional_include -"LARGE_SHADER" "main_small.sdr"
@@ -32,15 +33,14 @@ layout (std140) uniform modelData {
mat4 textureMatrix;
mat4 shadow_mv_matrix;
mat4 shadow_proj_matrix[4];
mat4 envMatrix;
vec4 color;
model_light lights[MAX_LIGHTS];
float outlineWidth;
float fogStart;
float fogScale;
float fogNear;
float fogDensity;
int buffer_matrix_offset;
vec4 clip_equation;
@@ -50,6 +50,7 @@ layout (std140) uniform modelData {
int n_lights;
float defaultGloss;
//EXCLUSIVELY used for non-deferred rendering
vec3 ambientFactor;
int desaturate;
@@ -85,22 +86,16 @@ layout (std140) uniform modelData {
float fardist;
int sGlowmapIndex;
int sSpecmapIndex;
int sNormalmapIndex;
int sAmbientmapIndex;
int sMiscmapIndex;
float alphaMult;
int flags;
};
in VertexOutput {
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_ENV
vec3 envReflect;
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_ENV
mat3 tangentMatrix;
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_FOG
@@ -126,10 +121,6 @@ uniform sampler2DArray sGlowmap;
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_SPEC
uniform sampler2DArray sSpecmap;
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_SPEC
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_ENV
uniform samplerCube sEnvmap;
uniform samplerCube sIrrmap;
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_ENV
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_NORMAL
uniform sampler2DArray sNormalmap;
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_NORMAL
@@ -187,7 +178,7 @@ void GetLightInfo(int i, out vec3 lightDir, out float attenuation)
vec3 CalculateLighting(vec3 normal, vec3 diffuseMaterial, vec3 specularMaterial, float gloss, float fresnel, float shadow, float aoFactor)
{
vec3 eyeDir = vec3(normalize(-vertIn.position).xyz);
vec3 lightAmbient = (emissionFactor + ambientFactor * ambientFactor) * aoFactor; // ambientFactor^2 due to legacy OpenGL compatibility behavior
vec3 lightAmbient = ambientFactor * aoFactor;
vec3 lightDiffuse = vec3(0.0, 0.0, 0.0);
vec3 lightSpecular = vec3(0.0, 0.0, 0.0);
#pragma optionNV unroll all
@@ -342,19 +333,7 @@ void main()
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_MISC
// Lights aren't applied when we are rendering to the G-buffers since that gets handled later
#prereplace IF_FLAG MODEL_SDR_FLAG_DEFERRED
#prereplace IF_FLAG MODEL_SDR_FLAG_LIGHT
// Ambient lighting still needs to be done since that counts as an "emissive" color
vec3 lightAmbient = (emissionFactor + ambientFactor * ambientFactor) * aoFactors.x; // ambientFactor^2 due to legacy OpenGL compatibility behavior
emissiveColor.rgb += baseColor.rgb * lightAmbient;
#prereplace ELSE_FLAG //MODEL_SDR_FLAG_LIGHT
#prereplace IF_FLAG MODEL_SDR_FLAG_SPEC
baseColor.rgb += pow(1.0 - clamp(dot(eyeDir, normal), 0.0, 1.0), 5.0 * clamp(glossData, 0.01, 1.0)) * specColor.rgb;
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_SPEC
// If there is no lighting then we copy the color data so far into the
emissiveColor.rgb += baseColor.rgb;
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_LIGHT
#prereplace ELSE_FLAG //MODEL_SDR_FLAG_DEFERRED
#prereplace IF_NOT_FLAG MODEL_SDR_FLAG_DEFERRED
#prereplace IF_FLAG MODEL_SDR_FLAG_LIGHT
float shadow = 1.0;
#prereplace IF_FLAG MODEL_SDR_FLAG_SHADOWS
@@ -368,54 +347,6 @@ void main()
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_LIGHT
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_DEFERRED
#prereplace IF_FLAG MODEL_SDR_FLAG_ENV
#prereplace IF_FLAG MODEL_SDR_FLAG_LIGHT
// Bit of a hack here, pretend we're doing blinn-phong shading and use a (modified version of) the derivation from
// Plausible Blinn-Phong Reflection of Standard Cube MIP-Maps - McGuire et al.
// to determine the mip bias.
// We use the specular term listed at http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html
// 1/(pi*alpha^2) * NdotM^((2/alpha^2)-2)
// 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));
float roughness = clamp(1.0f - glossData, 0.0f, 1.0f);
float alpha = roughness * roughness;
float alphaSqr = alpha * alpha;
float rough_bias = 0.5 * log2(2/alphaSqr - 1);
float mip_bias = REZ_BIAS - rough_bias;
// Sample light, using mip bias to blur it.
vec3 light_dir = reflect(-eyeDir, normal);
vec3 env_light_dir = vec3(envMatrix * vec4(light_dir, 0.0));
vec4 specEnvColour = srgb_to_linear(textureLod(sEnvmap, env_light_dir, mip_bias));
vec3 halfVec = normal;
// Lots of hacks here to get things to look right. We aren't loading a BRDF split-sum integral texture
// or properly calculating IBL levels.
// Fresnel calculation as with standard lights.
vec3 fresnel = mix(specColor.rgb, FresnelSchlick(halfVec, eyeDir, specColor.rgb), specColor.a);
// Pseudo-IBL, so use k_IBL
float k = alpha * alpha/ 2.0f;
float NdotL = max(dot(light_dir, normal),0);
float g1vNL = GeometrySchlickGGX(NdotL, k);
vec3 specEnvLighting = specEnvColour.rgb * fresnel * g1vNL;
vec3 kD = vec3(1.0)-fresnel;
kD *= (vec3(1.0) - specColor.rgb);
vec3 diffEnvColor = srgb_to_linear(texture(sIrrmap, vec3(envMatrix * vec4(normal, 0.0))).rgb);
vec3 diffEnvLighting = kD * baseColor.rgb * diffEnvColor * aoFactors.x;
emissiveColor.rgb += (specEnvLighting + diffEnvLighting) * baseColor.a;
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_LIGHT
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_ENV
#prereplace IF_FLAG MODEL_SDR_FLAG_GLOW
vec3 glowColor = texture(sGlowmap, vec3(texCoord, float(sGlowmapIndex))).rgb;
#prereplace IF_FLAG MODEL_SDR_FLAG_MISC
@@ -445,9 +376,10 @@ void main()
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_DIFFUSE
// This code will only be used by the forward renderer so we apply the fog effect to both the emissive and the base
// color.
baseColor.rgb = mix(emissiveColor.rgb + baseColor.rgb, finalFogColor, vertIn.fogDist);
float fogDensityFinal = clamp(1 - pow(fogDensity, vertIn.fogDist) , 0.0, 1.0);
baseColor.rgb = mix(emissiveColor.rgb + baseColor.rgb, finalFogColor, fogDensityFinal);
emissiveColor.rgb = vec3(0.0); // Zero the emissive color since it has already been added by the previous line
specColor.rgb *= vertIn.fogDist;
specColor.rgb *= fogDensityFinal;
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_FOG
#prereplace IF_FLAG MODEL_SDR_FLAG_DIFFUSE
@@ -483,10 +415,22 @@ void main()
baseColor.rgb += emissiveColor.rgb;
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_DEFERRED
#prereplace IF_FLAG MODEL_SDR_FLAG_DEFERRED
#prereplace IF_NOT_FLAG MODEL_SDR_FLAG_LIGHT
#prereplace IF_FLAG MODEL_SDR_FLAG_SPEC
baseColor.rgb += pow(1.0 - clamp(dot(eyeDir, normal), 0.0, 1.0), 5.0 * clamp(glossData, 0.01, 1.0)) * specColor.rgb;
glossData = 0;
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_SPEC
// If there is no lighting then we copy the color data so far into the emissive.
emissiveColor.rgb += baseColor.rgb;
aoFactors.x = 0;
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_LIGHT
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_DEFERRED
fragOut0 = baseColor;
#prereplace IF_FLAG MODEL_SDR_FLAG_DEFERRED
fragOut1 = vec4(vertIn.position.xyz, 1.0);
fragOut1 = vec4(vertIn.position.xy, compress_depth_value(vertIn.position.z), aoFactors.x);
fragOut2 = vec4(normal, glossData);
fragOut3 = vec4(specColor.rgb, fresnelFactor);
fragOut4 = emissiveColor;
+2 -22
View File
@@ -39,15 +39,14 @@ layout (std140) uniform modelData {
mat4 textureMatrix;
mat4 shadow_mv_matrix;
mat4 shadow_proj_matrix[4];
mat4 envMatrix;
vec4 color;
model_light lights[MAX_LIGHTS];
float outlineWidth;
float fogStart;
float fogScale;
float fogNear;
float fogDensity;
int buffer_matrix_offset;
vec4 clip_equation;
@@ -103,10 +102,6 @@ layout (std140) uniform modelData {
};
in VertexOutput {
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_ENV
vec3 envReflect;
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_ENV
mat3 tangentMatrix;
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_FOG
@@ -131,10 +126,6 @@ in VertexOutput {
} vertIn[];
out VertexOutput {
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_ENV
vec3 envReflect;
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_ENV
mat3 tangentMatrix;
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_FOG
@@ -172,19 +163,12 @@ out VertexOutput {
gl_Position = shadow_proj_matrix[instanceID] * gl_in[vert].gl_Position;
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_TRANSFORM
if(gl_Position.z < -1.0)
gl_Position.z = -1.0;
vertOut.position = gl_in[vert].gl_Position;
vertOut.normal = vertIn[vert].normal;
vertOut.texCoord = vertIn[vert].texCoord;
gl_Layer = instanceID;
#prereplace IF_FLAG MODEL_SDR_FLAG_ENV
vertOut.envReflect = vertIn[vert].envReflect;
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_ENV
#prereplace IF_FLAG MODEL_SDR_FLAG_NORMAL
vertOut.tangentMatrix = vertIn[vert].tangentMatrix;
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_NORMAL
@@ -239,10 +223,6 @@ out VertexOutput {
vertOut.normal = vertIn[vert].normal;
vertOut.texCoord = vertIn[vert].texCoord;
#prereplace IF_FLAG MODEL_SDR_FLAG_ENV
vertOut.envReflect = vertIn[vert].envReflect;
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_ENV
#prereplace IF_FLAG MODEL_SDR_FLAG_NORMAL
vertOut.tangentMatrix = vertIn[vert].tangentMatrix;
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_NORMAL
+3 -14
View File
@@ -34,15 +34,14 @@ layout (std140) uniform modelData {
mat4 textureMatrix;
mat4 shadow_mv_matrix;
mat4 shadow_proj_matrix[4];
mat4 envMatrix;
vec4 color;
model_light lights[MAX_LIGHTS];
float outlineWidth;
float fogStart;
float fogScale;
float fogNear;
float fogDensity;
int buffer_matrix_offset;
vec4 clip_equation;
@@ -102,10 +101,6 @@ uniform samplerBuffer transform_tex;
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_TRANSFORM
out VertexOutput {
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_ENV
vec3 envReflect;
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_ENV
mat3 tangentMatrix;
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_FOG
@@ -193,14 +188,8 @@ void main()
vec3 b = cross(normal, t) * vertTangent.w;
vertOut.tangentMatrix = mat3(t, b, normal);
#prereplace IF_FLAG MODEL_SDR_FLAG_ENV
// Environment mapping reflection vector.
vec3 envReflect = reflect(normalize(position.xyz), normal);
vertOut.envReflect = vec3(envMatrix * vec4(envReflect, 0.0));
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_ENV
#prereplace IF_FLAG MODEL_SDR_FLAG_FOG
vertOut.fogDist = clamp((gl_Position.z - fogStart) * 0.75 * fogScale, 0.0, 1.0);
vertOut.fogDist = gl_Position.z - fogNear;
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_FOG
#prereplace IF_FLAG MODEL_SDR_FLAG_TRANSFORM
@@ -14,22 +14,21 @@ SDR_FLAG(MODEL_SDR_FLAG_HDR , (1 << 2) , false)
SDR_FLAG(MODEL_SDR_FLAG_DIFFUSE , (1 << 3) , false)
SDR_FLAG(MODEL_SDR_FLAG_GLOW , (1 << 4) , false)
SDR_FLAG(MODEL_SDR_FLAG_SPEC , (1 << 5) , false)
SDR_FLAG(MODEL_SDR_FLAG_ENV , (1 << 6) , false)
SDR_FLAG(MODEL_SDR_FLAG_NORMAL , (1 << 7) , false)
SDR_FLAG(MODEL_SDR_FLAG_AMBIENT , (1 << 8) , false)
SDR_FLAG(MODEL_SDR_FLAG_MISC , (1 << 9) , false)
SDR_FLAG(MODEL_SDR_FLAG_TEAMCOLOR , (1 << 10), false)
SDR_FLAG(MODEL_SDR_FLAG_FOG , (1 << 11), false)
SDR_FLAG(MODEL_SDR_FLAG_TRANSFORM , (1 << 12), false)
SDR_FLAG(MODEL_SDR_FLAG_SHADOWS , (1 << 13), false)
SDR_FLAG(MODEL_SDR_FLAG_THRUSTER , (1 << 14), false)
SDR_FLAG(MODEL_SDR_FLAG_ALPHA_MULT , (1 << 15), false)
SDR_FLAG(MODEL_SDR_FLAG_NORMAL , (1 << 6) , false)
SDR_FLAG(MODEL_SDR_FLAG_AMBIENT , (1 << 7) , false)
SDR_FLAG(MODEL_SDR_FLAG_MISC , (1 << 8) , false)
SDR_FLAG(MODEL_SDR_FLAG_TEAMCOLOR , (1 << 9), false)
SDR_FLAG(MODEL_SDR_FLAG_FOG , (1 << 10), false)
SDR_FLAG(MODEL_SDR_FLAG_TRANSFORM , (1 << 11), false)
SDR_FLAG(MODEL_SDR_FLAG_SHADOWS , (1 << 12), false)
SDR_FLAG(MODEL_SDR_FLAG_THRUSTER , (1 << 13), false)
SDR_FLAG(MODEL_SDR_FLAG_ALPHA_MULT , (1 << 14), false)
#ifndef MODEL_SDR_FLAG_MODE_GLSL
//The following ones are used ONLY as compile-time flags, but they still need to be defined here to ensure no conflict occurs
//But since these are checked with ifdefs even for the large shader, they must never be available in GLSL mode
SDR_FLAG(MODEL_SDR_FLAG_SHADOW_MAP , (1 << 16), true)
SDR_FLAG(MODEL_SDR_FLAG_THICK_OUTLINES, (1 << 17), true)
SDR_FLAG(MODEL_SDR_FLAG_SHADOW_MAP , (1 << 15), true)
SDR_FLAG(MODEL_SDR_FLAG_THICK_OUTLINES, (1 << 16), true)
#endif
+11 -5
View File
@@ -1,3 +1,5 @@
#include "z-compress.sdr"
in vec4 fragTexCoord;
out vec4 fragOut0;
out vec4 fragOut1;
@@ -32,7 +34,7 @@ float getMedianDist(ivec2 texel) {
float dists[4];
for(int i = 0; i < 4; i++) {
dists[i] = texelFetch(texPos, texel, i).z;
dists[i] = uncompress_depth_value(texelFetch(texPos, texel, i).z);
}
SORT(0, 2)
@@ -51,7 +53,7 @@ float getMedianDist(ivec2 texel) {
float dists[8];
for(int i = 0; i < 8; i++) {
dists[i] = texelFetch(texPos, texel, i).z;
dists[i] = uncompress_depth_value(texelFetch(texPos, texel, i).z);
}
SORT(0, 4)
@@ -82,7 +84,7 @@ float getMedianDist(ivec2 texel) {
float dists[16];
for(int i = 0; i < 16; i++) {
dists[i] = texelFetch(texPos, texel, i).z;
dists[i] = uncompress_depth_value(texelFetch(texPos, texel, i).z);
}
SORT(0, 1)
@@ -145,7 +147,7 @@ float getMedianDist(ivec2 texel) {
float getMedianDist(ivec2 texel) {
float minDist = -1000000;
for(int i = 0; i < samples; i++) {
minDist = max(minDist, texelFetch(texPos, texel, i).z);
minDist = max(minDist, uncompress_depth_value(texelFetch(texPos, texel, i).z));
}
return minDist;
}
@@ -173,6 +175,7 @@ void main()
for(int i = 0; i < samples; i++) {
vec4 localPos = texelFetch(texPos, texel, i);
localPos.z = uncompress_depth_value(localPos.z);
//Calculate local weight from distance Voxel, but if the distance is 0 (i.e. no model at all), set weight to 1 to allow stuff like background emissive
//However, if the median distance is 0, only deal with current texel if it's local distance is 0 as well
float localWeight = max(step(-0.001, dist) * step(-0.001, localPos.z),
@@ -189,8 +192,11 @@ void main()
weight += localWeight;
}
pos /= weight;
pos.z = compress_depth_value(pos.z);
fragOut0 = color / weight;
fragOut1 = pos / weight;
fragOut1 = pos;
fragOut2 = vec4(normalize(normal.xyz), normal.a / weight);
fragOut3 = specular / weight;
fragOut4 = emissive / weight;
+8
View File
@@ -18,6 +18,14 @@ layout (std140) uniform genericData {
vec3 tint;
float dither;
// these are blank, valid slots for modders to create custom effects
// that can be defined in post_processing.tbl and coded below
vec3 custom_effect_vec3_a;
float custom_effect_float_a;
vec3 custom_effect_vec3_b;
float custom_effect_float_b;
};
void main()
+44 -37
View File
@@ -26,48 +26,45 @@ float sampleNoPCF(sampler2DArray shadow_map, float shadowDepth, int cascade, vec
return computeShadowFactor(shadowDepth, sampleShadowMap(shadow_map, shadowUV[cascade].xy, vec2(0.0, 0.0), cascade, 1.0/1024.0), 0.05);
}
float samplePoissonPCF(sampler2DArray shadow_map, float shadowDepth, int cascade, vec4 shadowUV[4])
float samplePoissonPCF(sampler2DArray shadow_map, float shadowDepth, int cascade, vec4 shadowUV[4], bool cockpit_shadow_bias)
{
if(cascade > 3 || cascade < 0) return 1.0;
vec2 poissonDisc[16];
poissonDisc[0] = vec2(-0.76275, -0.3432573);
poissonDisc[1] = vec2(-0.5226235, -0.8277544);
poissonDisc[2] = vec2(-0.3780261, 0.01528688);
poissonDisc[3] = vec2(-0.7742821, 0.4245702);
poissonDisc[4] = vec2(0.04196143, -0.02622231);
poissonDisc[5] = vec2(-0.2974772, -0.4722782);
poissonDisc[6] = vec2(-0.516093, 0.71495);
poissonDisc[7] = vec2(-0.3257416, 0.3910343);
poissonDisc[8] = vec2(0.2705966, 0.6670476);
poissonDisc[9] = vec2(0.4918377, 0.1853267);
poissonDisc[10] = vec2(0.4428544, -0.6251478);
poissonDisc[11] = vec2(-0.09204347, 0.9267113);
poissonDisc[12] = vec2(0.391505, -0.2558275);
poissonDisc[13] = vec2(0.05605913, -0.7570801);
poissonDisc[14] = vec2(0.81772, -0.02475523);
poissonDisc[15] = vec2(0.6890262, 0.5191521);
vec2 poissonDisc[16] = vec2[](
vec2(-0.76275, -0.3432573),
vec2(-0.5226235, -0.8277544),
vec2(-0.3780261, 0.01528688),
vec2(-0.7742821, 0.4245702),
vec2(0.04196143, -0.02622231),
vec2(-0.2974772, -0.4722782),
vec2(-0.516093, 0.71495),
vec2(-0.3257416, 0.3910343),
vec2(0.2705966, 0.6670476),
vec2(0.4918377, 0.1853267),
vec2(0.4428544, -0.6251478),
vec2(-0.09204347, 0.9267113),
vec2(0.391505, -0.2558275),
vec2(0.05605913, -0.7570801),
vec2(0.81772, -0.02475523),
vec2(0.6890262, 0.5191521)
);
float maxUVOffset[4];
maxUVOffset[0] = 1.0/300.0;
maxUVOffset[1] = 1.0/250.0;
maxUVOffset[2] = 1.0/200.0;
maxUVOffset[3] = 1.0/200.0;
vec2 sum = sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[0], cascade, maxUVOffset[cascade])*(1.0/16.0);
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[1], cascade, maxUVOffset[cascade])*(1.0/16.0);
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[2], cascade, maxUVOffset[cascade])*(1.0/16.0);
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[3], cascade, maxUVOffset[cascade])*(1.0/16.0);
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[4], cascade, maxUVOffset[cascade])*(1.0/16.0);
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[5], cascade, maxUVOffset[cascade])*(1.0/16.0);
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[6], cascade, maxUVOffset[cascade])*(1.0/16.0);
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[7], cascade, maxUVOffset[cascade])*(1.0/16.0);
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[8], cascade, maxUVOffset[cascade])*(1.0/16.0);
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[9], cascade, maxUVOffset[cascade])*(1.0/16.0);
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[10], cascade, maxUVOffset[cascade])*(1.0/16.0);
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[11], cascade, maxUVOffset[cascade])*(1.0/16.0);
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[12], cascade, maxUVOffset[cascade])*(1.0/16.0);
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[13], cascade, maxUVOffset[cascade])*(1.0/16.0);
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[14], cascade, maxUVOffset[cascade])*(1.0/16.0);
sum += sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[15], cascade, maxUVOffset[cascade])*(1.0/16.0);
return computeShadowFactor(shadowDepth, sum, 0.1);
// default external shadows
// bias is a bit larger to only pick up larger geometry
// favors light over shadow to avoid flickering/spotty shadows
// compute Chebyshev per-sample to avoid halos from moment-averaging at occluder edges
float visibility = 0.0f;
for (int i=0; i<16; i++) {
vec2 shadow_sample = sampleShadowMap(shadow_map, shadowUV[cascade].xy, poissonDisc[i], cascade, maxUVOffset[cascade]);
visibility += computeShadowFactor(shadowDepth, shadow_sample, cockpit_shadow_bias ? 0.01f : 0.1f);
}
return visibility * (1.0f/16.0f);
}
float getShadowValue(sampler2DArray shadow_map, float depth, float shadowDepth, vec4 shadowUV[4], float fardist,
@@ -86,10 +83,20 @@ float getShadowValue(sampler2DArray shadow_map, float depth, float shadowDepth,
cascade_start_dist[3] = middist;
cascade_start_dist[4] = fardist;
if(cascade > 3 || cascade < 0) return 1.0;
bool cockpit_shadow_bias;
if (fardist < 50.0f) {
// internal cockpit shadows
cockpit_shadow_bias = true;
} else {
// default external shadows
cockpit_shadow_bias = false;
}
float dist_threshold = (cascade_start_dist[cascade+1] - cascade_start_dist[cascade])*0.2;
if(cascade_start_dist[cascade+1] - dist_threshold > depth)
return samplePoissonPCF(shadow_map, shadowDepth, cascade, shadowUV);
return mix(samplePoissonPCF(shadow_map, shadowDepth, cascade, shadowUV), samplePoissonPCF(shadow_map, shadowDepth, cascade+1, shadowUV),
return samplePoissonPCF(shadow_map, shadowDepth, cascade, shadowUV, cockpit_shadow_bias);
return mix(samplePoissonPCF(shadow_map, shadowDepth, cascade, shadowUV, cockpit_shadow_bias), samplePoissonPCF(shadow_map, shadowDepth, cascade+1, shadowUV, cockpit_shadow_bias),
smoothstep(cascade_start_dist[cascade+1] - dist_threshold, cascade_start_dist[cascade+1], depth));
}
+4 -6
View File
@@ -100,8 +100,6 @@ void main()
vec3 sampleposition = position / nebSize + 0.5;
vec4 volume_sample = textureGrad(volume_tex, sampleposition, gradX, gradY);
float stepsize_current = min(max(stepsize, volume_sample.x * udfScale), mintMax - stept);
#ifdef DO_EDGE_SMOOTHING
//Average 3D texel with texels on corner, in an attempt to reduce jagged edges.
float stepcolor_alpha = volume_sample.a;
@@ -121,6 +119,8 @@ void main()
float stepcolor_alpha = volume_sample.a;
#endif
float stepsize_current = min(max(stepsize, step(stepcolor_alpha, 0.01) * volume_sample.x * udfScale), mintMax - stept);
float stepalpha = -(pow(alphalim, 1.0 / (opacitydistance / stepsize_current)) - 1.0f) * stepcolor_alpha;
//All the following computations are just required if we have a stepcoloralpha that is non-zero.
if(stepcolor_alpha > 0.01)
@@ -151,7 +151,7 @@ void main()
//Emissive
cumnebdist += stepcolor_alpha * stepsize_current;
vec3 emissive_lod = textureLod(emissive, fragTexCoord.xy, clamp(cumnebdist * emissiveSpreadFactor, 0, float(textureQueryLevels(emissive) - 1))).rgb;
vec3 stepcolor_emissive = emissive_lod.rgb * pow(alphalim, 1.0 / (opacitydistance / ((depth - stept) * emissiveFalloff + 0.01))) * emissiveIntensity;
vec3 stepcolor_emissive = clamp(emissive_lod.rgb * pow(alphalim, 1.0 / (opacitydistance / ((depth - stept) * emissiveFalloff + 0.01))) * emissiveIntensity, 0, 1);
//Step finish
vec3 stepcolor = clamp(stepcolor_diffuse + stepcolor_emissive, 0, 1);
@@ -165,7 +165,5 @@ void main()
break;
}
cumcolor += cumOMAlpha * color_in.rgb;
fragOut0 = vec4(cumcolor, 1);
fragOut0 = vec4(cumOMAlpha * color_in.rgb + ((1.0 - cumOMAlpha) * cumcolor), 1);
}
@@ -0,0 +1,89 @@
float compress_depth_value(float linear) {
float linear_mult;
float exponent_mult;
float offset;
linear = max(-linear, 0.0);
if(linear < 10.0) {
//Special handling for very close values. For smooth shadows at very close distances, equidistant steps get too inaccurate.
//Scaling this down linearly will push more accuracy to closer distances. Due to the scaling, this does not lose accuracy
//at any point over the worse of uncompressed / z-compress, but it does trade the gained accuracy from compression at 10m
//distance for accuracy lost due to compression below 0.5m (roughly)
return linear * 0.01;
}
else if (linear < 100.0) {
linear_mult = 0.05550473078;
exponent_mult = 0.08493173544;
offset = 0.0;
}
else if (linear < 1570.0) {
linear_mult = 11.53589613947;
exponent_mult = 0.00793869919;
offset = 0.0;
}
else if (linear < 3000.0) {
linear_mult = -0.10000000000;
exponent_mult = 0.00928910863;
offset = 3000.0;
}
else if (linear < 10000.0) {
linear_mult = -0.10000000000;
exponent_mult = 0.00094912231;
offset = 3000.0;
}
else if (linear < 100000.0) {
linear_mult = -6.98947320727;
exponent_mult = 0.00007382062;
offset = 3000.0;
}
else {
linear_mult = -1000.0;
exponent_mult = log2(1000.0 / 65000.0) / (100000.0 - GLOBAL_FAR_Z);
offset = 100000.0;
}
return linear_mult * pow(2.0, (linear - offset) * exponent_mult);
}
float uncompress_depth_value(float compressed) {
float linear_mult;
float exponent_mult;
float offset;
if(compressed < -1000.0) {
linear_mult = -1000.0;
exponent_mult = log2(1000.0 / 65000.0) / (100000.0 - GLOBAL_FAR_Z);
offset = 100000.0;
}
else if (compressed < -10.0) {
linear_mult = -6.98947320727;
exponent_mult = 0.00007382062;
offset = 3000.0;
}
else if (compressed < -0.1) {
linear_mult = -0.10000000000;
exponent_mult = 0.00094912231;
offset = 3000.0;
}
else if (compressed < 0.0) {
linear_mult = -0.10000000000;
exponent_mult = 0.00928910863;
offset = 3000.0;
}
else if (compressed < 0.1) {
return compressed * -100.0;
}
else if (compressed < 20.0) {
linear_mult = 0.05550473078;
exponent_mult = 0.08493173544;
offset = 0.0;
}
else {
linear_mult = 11.53589613947;
exponent_mult = 0.00793869919;
offset = 0.0;
}
return -(log2(compressed / linear_mult) / exponent_mult + offset);
}
@@ -84,3 +84,21 @@ setmetatable(Globals, mt)
Globals.nullVec = ba.createVector(0, 0, 0)
Globals.zeroVec = Globals.nullVec
Globals.identityOrient = ba.createOrientationFromVectors(ba.createVector(0, 0, 1), ba.createVector(0, 1, 0), ba.createVector(1, 0, 0))
Globals.FSOObjectsTableMetatable = {
__newindex = function(tbl, key, value)
for k, v in pairs(tbl) do
if k == key then
rawset(tbl, k, value)
return
end
end
rawset(tbl, key, value)
end,
__index = function(tbl, key)
for k, v in pairs(tbl) do
if k == key then
return v
end
end
end
}
+9 -7
View File
@@ -53,8 +53,9 @@ $AI:
+Ignored on cripple by: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "fighter" "bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
$Skip Death Roll Percent Chance: 0.0
$Name: Cargo
$Scannable: YES
$Name: Cargo
$Targetable as unscanned: YES
$Scannable by default: YES
$Max Debris Speed: 200
$FF Multiplier: 0.10
$EMP Multiplier: 10.0
@@ -110,9 +111,9 @@ $Fog:
+Start dist: 10.0
+Compl dist: 500.0
$AI:
+Valid goals: ( "fly to ship" "attack ship" "waypoints" "waypoints once" "depart" "attack subsys" "attack wing" "guard ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "attack any" "attack ship class" "ignore ship" "ignore ship (new)" "guard wing" "evade ship" "stay still" "play dead" "play dead (persistent)" "stay near ship" "keep safe dist" "form on wing")
+Valid goals: ( "fly to ship" "attack ship" "waypoints" "waypoints once" "depart" "attack subsys" "attack wing" "guard ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "attack any" "attack ship class" "attack ship type" "ignore ship" "ignore ship (new)" "guard wing" "evade ship" "stay still" "play dead" "play dead (persistent)" "stay near ship" "keep safe dist" "form on wing")
+Accept Player Orders: YES
+Player Orders: ( "attack ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "guard ship" "ignore ship" "ignore ship (new)" "form on wing" "cover me" "attack any" "attack ship class" "depart" "disable subsys" )
+Player Orders: ( "attack ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "guard ship" "ignore ship" "ignore ship (new)" "form on wing" "cover me" "attack any" "attack ship class" "attack ship type" "depart" "disable subsys" )
+Auto attacks: YES
+Actively Pursues: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "fighter" "bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
+Guards attack this: YES
@@ -138,9 +139,9 @@ $Fog:
+Start dist: 10.0
+Compl dist: 500.0
$AI:
+Valid goals: ( "fly to ship" "attack ship" "waypoints" "waypoints once" "depart" "attack subsys" "attack wing" "guard ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "attack any" "attack ship class" "ignore ship" "ignore ship (new)" "guard wing" "evade ship" "stay still" "play dead" "play dead (persistent)" "stay near ship" "keep safe dist" "form on wing" )
+Valid goals: ( "fly to ship" "attack ship" "waypoints" "waypoints once" "depart" "attack subsys" "attack wing" "guard ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "attack any" "attack ship class" "attack ship type" "ignore ship" "ignore ship (new)" "guard wing" "evade ship" "stay still" "play dead" "play dead (persistent)" "stay near ship" "keep safe dist" "form on wing" )
+Accept Player Orders: YES
+Player Orders: ( "attack ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "guard ship" "ignore ship" "ignore ship (new)" "form on wing" "cover me" "attack any" "attack ship class" "depart" "disable subsys" )
+Player Orders: ( "attack ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "guard ship" "ignore ship" "ignore ship (new)" "form on wing" "cover me" "attack any" "attack ship class" "attack ship type" "depart" "disable subsys" )
+Auto attacks: YES
+Actively Pursues: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "fighter" "bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" )
+Guards attack this: YES
@@ -156,6 +157,8 @@ $Praise Destruction: YES
$On Hotkey List: YES
$Target as Threat: YES
$Show Attack Direction: YES
$Targetable as unscanned: YES
$Scannable by default: YES
$Max Debris Speed: 150
$FF Multiplier: 1.0
$EMP Multiplier: 2.0
@@ -186,7 +189,6 @@ $Praise Destruction: YES
$On Hotkey List: YES
$Target as Threat: YES
$Show Attack Direction: YES
$Scannable: YES
$Max Debris Speed: 150
$FF Multiplier: 1.0
$EMP Multiplier: 1.75
+10 -1
View File
@@ -63,5 +63,14 @@ $AlwaysOn: false
$Default: 0.0
$Div: 50
$Add: 0
$Name: tint
$Uniform: tint
$Define: FLAG_TINT
$AlwaysOn: false
$Default: 0.0
$Div: 1
$Add: 0
$RGB: 0.2,0.2,0.2
#End
+68 -66
View File
@@ -107,7 +107,7 @@ void fireball_play_warphole_close_sound(fireball *fb)
snd_play_3d(gamesnd_get_game_sound(sound_index), &fireball_objp->pos, &Eye_position, fireball_objp->radius, NULL, 0, 1.0F, SND_PRIORITY_SINGLE_INSTANCE, NULL, fb->warp_sound_range_multiplier); // play warp sound effect
}
static void fireball_generate_unique_id(char *unique_id, int buffer_len, int fireball_index)
static void fireball_generate_unique_id(char *unique_id, size_t buffer_size, int fireball_index)
{
Assertion(SCP_vector_inbounds(Fireball_info, fireball_index), "fireball_index is out of bounds!");
@@ -115,37 +115,37 @@ static void fireball_generate_unique_id(char *unique_id, int buffer_len, int fir
{
// use sensible names for the fireball.tbl default entries
case FIREBALL_EXPLOSION_MEDIUM:
strncpy(unique_id, "Medium Explosion", buffer_len);
strncpy(unique_id, "Medium Explosion", buffer_size-1);
break;
case FIREBALL_WARP:
strncpy(unique_id, "Warp Effect", buffer_len);
strncpy(unique_id, "Warp Effect", buffer_size-1);
break;
case FIREBALL_KNOSSOS:
strncpy(unique_id, "Knossos Effect", buffer_len);
strncpy(unique_id, "Knossos Effect", buffer_size-1);
break;
case FIREBALL_ASTEROID:
strncpy(unique_id, "Asteroid Explosion", buffer_len);
strncpy(unique_id, "Asteroid Explosion", buffer_size-1);
break;
case FIREBALL_EXPLOSION_LARGE1:
strncpy(unique_id, "Large Explosion 1", buffer_len);
strncpy(unique_id, "Large Explosion 1", buffer_size-1);
break;
case FIREBALL_EXPLOSION_LARGE2:
strncpy(unique_id, "Large Explosion 2", buffer_len);
strncpy(unique_id, "Large Explosion 2", buffer_size-1);
break;
// base the id on the index
default:
snprintf(unique_id, buffer_len, "Custom Fireball %d", fireball_index - NUM_DEFAULT_FIREBALLS + 1);
snprintf(unique_id, buffer_size, "Custom Fireball %d", fireball_index - NUM_DEFAULT_FIREBALLS + 1);
break;
}
// null-terminate
unique_id[buffer_len - 1] = '\0';
unique_id[buffer_size-1] = '\0';
}
/**
@@ -437,48 +437,49 @@ static void parse_fireball_tbl(const char *table_filename)
default:
error_display(0, "Invalid warp model style. Must be classic or cinematic.");
}
// Set warp_model_style options if cinematic style is chosen
if (fi->warp_model_style == warp_style::CINEMATIC) {
if (optional_string("+Warp size ratio:")) {
stuff_float(&fi->warp_size_ratio);
} else {
fi->warp_size_ratio = 1.6f;
}
// The first two values need to be implied multiples of PI
// for convenience. These shouldn't need to be faster than a full
// rotation per second, which is already ridiculous.
if (optional_string("+Rotation anim:")) {
stuff_float_list(fi->rot_anim, 3);
CLAMP(fi->rot_anim[0], 0.0f, 2.0f);
CLAMP(fi->rot_anim[1], 0.0f, 2.0f);
fi->rot_anim[2] = MAX(0.0f, fi->rot_anim[2]);
} else {
// PI / 2.75f, PI / 10.0f, 2.0f
fi->rot_anim[0] = 0.365f;
fi->rot_anim[1] = 0.083f;
fi->rot_anim[2] = 2.0f;
}
// Variable frame rate for faster propagation of ripples
if (optional_string("+Frame anim:")) {
stuff_float_list(fi->frame_anim, 3);
// A frame rate that is 4 times the normal speed is ridiculous
CLAMP(fi->frame_anim[0], 0.0f, 4.0f);
CLAMP(fi->frame_anim[1], 1.0f, 4.0f);
fi->frame_anim[2] = MAX(0.0f, fi->frame_anim[2]);
} else {
fi->frame_anim[0] = 1.0f;
fi->frame_anim[1] = 1.0f;
fi->frame_anim[2] = 3.0f;
}
}
} else if (first_time) {
fi->warp_model_style = warp_style::CLASSIC;
}
// Set warp_model_style options if cinematic style is chosen
if (fi->warp_model_style == warp_style::CINEMATIC) {
if (optional_string("+Warp size ratio:")) {
stuff_float(&fi->warp_size_ratio);
} else {
fi->warp_size_ratio = 1.6f;
}
// The first two values need to be implied multiples of PI
// for convenience. These shouldn't need to be faster than a full
// rotation per second, which is already ridiculous.
if (optional_string("+Rotation anim:")) {
stuff_float_list(fi->rot_anim, 3);
CLAMP(fi->rot_anim[0], 0.0f, 2.0f);
CLAMP(fi->rot_anim[1], 0.0f, 2.0f);
fi->rot_anim[2] = MAX(0.0f, fi->rot_anim[2]);
} else {
// PI / 2.75f, PI / 10.0f, 2.0f
fi->rot_anim[0] = 0.365f;
fi->rot_anim[1] = 0.083f;
fi->rot_anim[2] = 2.0f;
}
// Variable frame rate for faster propagation of ripples
if (optional_string("+Frame anim:")) {
stuff_float_list(fi->frame_anim, 3);
// A frame rate that is 4 times the normal speed is ridiculous
CLAMP(fi->frame_anim[0], 0.0f, 4.0f);
CLAMP(fi->frame_anim[1], 1.0f, 4.0f);
fi->frame_anim[2] = MAX(0.0f, fi->frame_anim[2]);
} else {
fi->frame_anim[0] = 1.0f;
fi->frame_anim[1] = 1.0f;
fi->frame_anim[2] = 3.0f;
}
}
}
required_string("#End");
@@ -919,19 +920,7 @@ int fireball_create(vec3d *pos, int fireball_type, int render_type, int parent_o
return -1;
}
if (!Unused_fireball_indices.empty()) {
n = Unused_fireball_indices.back();
Unused_fireball_indices.pop_back();
}
else {
n = static_cast<int>(Fireballs.size());
Fireballs.emplace_back();
}
fireball* new_fireball = &Fireballs[n];
// get an lod to use
// get an lod to use
fb_lod = fireball_get_lod(pos, fd, size);
// change lod if low res is desired
@@ -946,8 +935,21 @@ int fireball_create(vec3d *pos, int fireball_type, int render_type, int parent_o
}
fl = &fd->lod[fb_lod];
new_fireball->lod = (char)fb_lod;
// don't create a fireball without usable graphics
if (fl->bitmap_id < 0 || fl->fps <= 0 || fl->num_frames <= 0) {
return -1;
}
if (!Unused_fireball_indices.empty()) {
n = Unused_fireball_indices.back();
Unused_fireball_indices.pop_back();
} else {
n = sz2i(Fireballs.size());
Fireballs.emplace_back();
}
auto new_fireball = &Fireballs[n];
new_fireball->lod = (char)fb_lod;
new_fireball->flags = extra_flags;
new_fireball->warp_open_sound_index = warp_open_sound;
new_fireball->warp_close_sound_index = warp_close_sound;
@@ -1150,12 +1152,12 @@ static float cutscene_wormhole(float t) {
float fireball_wormhole_intensity(fireball *fb)
{
float t = fb->time_elapsed;
float rad = cutscene_wormhole(t / fb->warp_open_duration);
float rad;
fireball_info* fi = &Fireball_info[fb->fireball_info_index];
if (fi->warp_model_style == warp_style::CINEMATIC) {
rad = cutscene_wormhole(t / fb->warp_open_duration);
rad *= cutscene_wormhole((fb->total_time - t) / fb->warp_close_duration);
rad /= cutscene_wormhole(fb->total_time / (2.0f * fb->warp_open_duration));
rad /= cutscene_wormhole(fb->total_time / (2.0f * fb->warp_close_duration));
@@ -1268,7 +1270,7 @@ void fireball_render(object* obj, model_draw_list *scene)
flare_rad,
fi->flicker_magnitude,
obj->radius,
fi->use_3d_warp || (fb->flags & FBF_WARP_3D) != 0,
!Is_standalone && (fi->use_3d_warp || (fb->flags & FBF_WARP_3D) != 0),
fi->warp_glow_bitmap,
fi->warp_ball_bitmap,
fi->warp_model_id,
@@ -1295,7 +1297,7 @@ int fireball_get_count()
void stuff_fireball_index_list(SCP_vector<int> &list, const char *name)
{
stuff_int_list(list, RAW_INTEGER_TYPE);
stuff_int_list(list, ParseLookupType::FIREBALL_INFO_TYPE);
list.erase(std::remove_if(list.begin(), list.end(), [&](int index) {
if (!SCP_vector_inbounds(Fireball_info, index)) {
+1 -1
View File
@@ -437,7 +437,7 @@ SCP_vector<gameplay_help_section> gameplay_help_init_text()
}
complete_help_text.push_back(thisHelp);
complete_help_text.push_back(std::move(thisHelp));
}
return complete_help_text;
+1
View File
@@ -53,6 +53,7 @@ static void parse_music_volume_func()
Default_music_volume = volume;
}
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
static auto MusicVolumeOption __UNUSED = options::OptionBuilder<float>("Audio.Music",
std::pair<const char*, int>{"Music", 1371},
std::pair<const char*, int>{"Volume used for playing music", 1760})
+2
View File
@@ -601,6 +601,7 @@ void gamesnd_unload_gameplay_sounds()
}
}
}
snd_unload_cleanup();
}
/**
@@ -636,6 +637,7 @@ void gamesnd_unload_interface_sounds()
}
}
}
snd_unload_cleanup();
}
void parse_gamesnd_old(game_snd* gs)
Loaded 100 of 1570 files, more files were not shown because too many files have changed in this diff. Show more