Compare commits

...
67 Commits
Author SHA1 Message Date
SirKnightly 4b06a04908 Automated build commit 2026-09-15 10:00:09 +00:00
Fabian Woltermann d1767813c1 Secure forum posting by adding authentication header support (#7676)
* Secure forum posting by adding authentication header support

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

* also error out when the forum secret is not supplied
2026-09-15 05:35:16 -04:00
wookieejedi ca65cb630c fix jump in chase view (#7771)
Chase view deltas are not reset on mission init, so ensure they are reset to prevent the viewing jumps that can be seen at the start of mission in chase view. Also ensure perspective and slew lock are properly resotred.
2026-09-08 11:14:20 -04:00
BMagnu 8a8c5dca52 Also set custom gauges + dradis radar to slew (#7769) 2026-09-08 11:11:26 -04:00
Goober5000andClaude Opus 5 213d15daf0 fix copying multiple glow point banks into a virtual POF
Every copied bank was written to the same array slot, so all but the last
were lost, and the slots left behind kept their zero-initialized contents.
A zeroed bitmap handle is slot 0 rather than -1, so the paging code would
then page out an unrelated bitmap.

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

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

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

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

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-08 11:05:17 -04:00
Goober5000 7e0e0b704a Merge pull request #7757 from Goober5000/fix/flagset_64bit
Fix 32-bit truncation in the flagset u64 round-trip
2026-09-01 21:56:26 -04:00
Fabian Woltermann 91318985cf Fix a couple of oversights in prop collisions (#7753)
* Fix estimate_ship_speed_upper_limit to handle non-ship objects gracefully

* Add collision handling for OBJ_PROP objects

Implement OBJ_PROP-specific collision messaging and logic: update player death messages, fix incorrect argument usage in collision checks, and set killer attributes for PROP collisions.
2026-09-01 09:04:46 -04:00
wookieejedi 0e8ba7a32c Fix path that could lead to unnormalized vector (#7747)
* Fix path that could lead to unnormalized vector

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

More Detail:

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

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

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

Happy to discuss!

* fix typo

* no need for flag
2026-08-27 13:26:44 -04:00
Goober5000andClaude Fable 5 683ccdf4f7 fix AI ships getting permanently stuck after losing or refusing a target (#7736)
Generalize the unreleased "$fix target updating with strafe:" ai_profiles flag
into "$fix ai target recovery:", which now also covers two more ways a ship
holding a standing chase order could park in AIM_NONE forever:

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

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

Co-authored-by: Claude Fable 5 <[email protected]>
2026-08-26 19:31:51 -04:00
Goober5000 57cb2628c9 Merge pull request #7732 from Goober5000/fix/wing_hash_compatibility
wing name format compatibility
2026-08-26 10:35:54 -04:00
Goober5000 2f3e7e4b39 handle ship editor re-entrancy in FRED
Prevent the mission from being modified via the ship editor's window update when it is already being cleared or loaded.  This mirrors existing guards in `delete_object` and `delete_ship`.

No other FRED2 dialogs are vulnerable.  QtFRED uses a different ship editor mechanism which is not affected by this issue.
2026-08-26 10:33:24 -04:00
Goober5000 099593c8b3 patch a hole in SEXP syntax checking
An operator that isn't enclosed in parentheses could trigger an UNREACHABLE, which crashes in Debug and is undefined behavior in Release.  Catch it in a more appropriate way.
2026-08-26 10:31:51 -04:00
wookieejedi fa2b675e16 Fix CTD with ship-percent sexp edge case (#7738)
`sexp_percent_ships_arrive_depart_destroy_disarm_disable_scan` could have an edge case where the total value (ie the denominator) is 0 and thus crashes FSO. This could happen if `Cancel_future_waves_of_any_wing_launched_from_an_exited_ship` is used and the death or departure of a mothership triggers the total of a wing to change that is later checked in a sexp such a 'percent-destroyed` or other similar sexps.

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

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

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

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

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

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

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

	// Do over-the-top correction.
	// Headings are extracted with atan2, so each one lies in (-PI, PI] and their difference can be
	// nearly a full circle in either direction.  Without wrapping it back into (-PI, PI], a target just
	// past the left shoulder reads as being almost all the way around to the right instead.
	if (chase_slew_angles.h > PI)
		chase_slew_angles.h -= PI2;
	else if (chase_slew_angles.h < -PI)
		chase_slew_angles.h += PI2;
2026-08-25 15:12:36 -04:00
wookieejedi b9d8651af7 Update type Prop to ensure it's on object list
Seems like props were missing from the object type list, so adding them in this relatively small PR. Happy to edit or do something differently if folks want though!
2026-08-16 16:30:30 -04:00
Goober5000 13d7ef3f34 tiny tweak fix to the comm menu
Reset the selected item when we open a new menu.  This keeps the display state and internal state in sync if the player mixes arrow presses and number presses.  Follow-up to #7688.
2026-08-16 16:29:07 -04:00
Goober5000 d7e80847a6 Merge pull request #7688 from wookieejedi/fix-skip-hidden-menu-items
Fix COMMS_MENU_SELECT control usage with 'Hide  Rearm/Repair items' and cleanup selection visibility
2026-08-16 16:28:30 -04:00
Goober5000 b4b0a034a6 Merge pull request #7704 from wookieejedi/fix-ai-ramming-big-targets
More Fixes for AI Ramming into Stationary Targets
2026-08-16 16:26:16 -04:00
Goober5000 a5faa1a035 remove an unneeded SSM assertion and fix some parent checks
Contrary to the comment, local SSMs do not actually need a parent.

Also fix two other places where a parent was assumed but may not exist.
2026-08-16 16:23:13 -04:00
Fabian Woltermann 3e89550eb7 Fix base angle calculation for ventral turrets without custom matrices (#7706) 2026-08-16 16:21:37 -04:00
wookieejedi 661b34c02d clang 2026-08-16 16:11:35 -04:00
wookieejedi 63c3fdf2e4 Fix various multi bugs
PR addresses various small but longstanding mutli-related bugs. In draft status pending additional testing.
2026-08-16 16:10:52 -04:00
Kestrellius ae044cfe96 Vectorize MsgItems (#7687)
Convert MsgItems from an array to a vector, and remove the associated limit value and counter.

* vectorize msgitems

* appeasement

* leave me alone clang

* remove optional

* cleanup

* changes requested

* clang

* clang what is WRONG with you

* feedback

* remove flag in .h
2026-08-16 16:04:49 -04:00
wookieejedi 150775ccbb Fix two small issues (#7690)
* Fix two small issues

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

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

* comment formatting
2026-08-08 11:44:12 -04:00
wookieejedi 6d757a47f4 Fix scripting copy-paste bug and other minor optimizations (#7684)
* Fix scripting copy-paste bug

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

Also add some additional low-hanging fruit optimizations.

* radar distance and other cleanup

* clang
2026-08-08 11:43:09 -04:00
BMagnu 32af080dae Port of #7674 for the point release with SDL2 (no imgui fix needed) and no vulkan half (#7692) 2026-08-07 08:17:09 +02:00
Goober5000andClaude Fable 5 0a5ac7915e send-message-list arg-count forgiveness, plus better error checking
Due to a long-standing bug, the groups-of-4 argument count check for
send-message-list and send-message-chain never actually worked, so
missions through 25.0 could includes sexps with truncated argument lists
that recovered gracefully at runtime.  Now that the check works, don't
punish those missions: introduce SEXP_CHECK_BAD_ARG_COUNT_BENIGN, which
warns but does not prevent the mission from loading.

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

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

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-04 10:15:18 -04:00
Mike Nelson 4ce97ddbac Clear stale Prop_info model handles when a model is unloaded (#7669)
Repro: create a prop, File -> New Mission & Discard, then create the same prop
again. Asserts in model_get with "Index collision between model <other>.pof and
requested model N".

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

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

This is not OFP-specific; SCP master has props and the same missing pass.
2026-08-04 10:14:25 -04:00
Goober5000 440b49c58a fix keycontrol unsetting dual-fire
Quick follow-up to #7635.  Even though you can't set dual-fire when a non-dual-fire bank is active, you should be able to *un*set it.  Reorder the conditions so this is possible.
2026-08-04 10:13:20 -04:00
Goober5000andClaude Fable 5 349bfa0752 fix player subsystem targeting from sexps and scripting
Fix a regression from #7393: `last_targeted_subobject` must be updated when the player's target and target subsystem are changed, or the subsystem will be retargeted due to `hud_target_change_check`.

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

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-04 10:12:18 -04:00
Goober5000andClaude Opus 4.8 a6f5122104 fix crash when a ship with no primary banks is ordered to disable/disarm
Gunships and other custom ship classes may run through the primary weapon code despite having no primary banks.  Add a few guards to prevent this from causing crashes.

Fixes a crash reported in Causality: The Great War.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-04 10:09:36 -04:00
Goober5000andClaude Fable 5 9df54bab27 fix dual fire handling for banks that cannot use it
The Secondary_dual_fire flag is per-ship, but not every secondary bank
can dual fire.  Previously the HUD drew the dual fire link icon even
when the armed bank had only one firepoint, and firing such a bank
cleared the flag entirely, silently discarding the player's dual fire
preference for the other banks.

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

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

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-04 10:05:03 -04:00
Goober5000 28df181995 patch mission format compatibility for new support features (#7624)
Although #7249 added the new support features to `check_for_25_1_data()`, the new fields were still written to the mission file in all formats except retail.  Use a version check when writing the fields too.
2026-07-20 16:52:40 -04:00
Goober5000 6dbe720fe0 Off is 1286 2026-07-19 23:49:28 -04:00
Goober5000 7ad97097d5 when disbanding a wing, delete its reinforcement entry
Also rename `remove_wing` to `disband_wing` for clarity.
2026-07-19 23:49:05 -04:00
Goober5000 2d1f4e2411 fix an Assertion and a latent campaign option 2026-07-19 23:48:42 -04:00
Goober5000andClaude Fable 5 037c53d853 add briefing icon ID collision validation to qtFRED
FRED2 rejects an icon ID edit that would collide with another icon,
warning and reverting the field, but qtFRED's setIconId() applied the
value unconditionally. Two icons could be given the same ID in a stage
(making find_icon and forward propagation ambiguous), or a global rename
could merge two distinct icon chains by reusing a downstream ID.

setIconId() now mirrors FRED2:

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

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

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

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

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

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

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

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

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

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

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

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

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

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-19 23:42:54 -04:00
wookieejedi 4ea8885df1 Fix AI Bug with Strafing (#7619)
* Fix AI Bug with Strafing

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

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

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

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

* target 26.2
2026-07-19 23:36:31 -04:00
wookieejedi 22b6eff659 Fix fighter/bomber AI remaining stationary if engines repaired (#7609)
* draft both options

* cleanup

* authors

* whitespace
2026-07-19 23:35:02 -04:00
Goober5000andClaude Fable 5 8e308d4e34 Fix heap overrun in texture readback for the model transparency scan (#7616)
* Fix heap overrun in texture readback for the model transparency scan

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

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

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

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

* Harden the transparency-scan texture readback

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

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

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

* Fix inverted dimension check in opengl_export_render_target

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

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

---------

Co-authored-by: Claude Fable 5 <[email protected]>
2026-07-19 23:32:58 -04:00
Goober5000andClaude Fable 5 bb4b8cf792 fix Lua weapon bank setter writing primary fields for secondary banks
The SWH_SECONDARY case of the ship_bank setter was copy-pasted from the
primary case and still assigned primary_bank_* fields; it now assigns
the secondary_bank_* fields.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-19 23:29:47 -04:00
Goober5000andClaude Sonnet 4.6 64f7b603e5 fix prop selection using model geometry instead of bounding sphere in FRED
In both FRED2 and QtFRED, object_check_collision() only dispatched to
model_collide() for OBJ_SHIP and OBJ_START, leaving OBJ_PROP to fall
through to fvi_ray_sphere().  For flat, wide props the bounding sphere is
much larger than the visible geometry, making it impossible to
click-deselect without clicking far outside the model bounds.

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

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-07-19 23:27:22 -04:00
Goober5000andClaude Opus 4.8 f8f337a195 fix a bug with player ships being demoted to common ships
If a player ship is changed to a regular ship, be sure that
Player_start_shipnum does not still point to the old ship.

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

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

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-19 23:23:56 -04:00
wookieejedi 3d706aa2d3 Skip particle parsing to speed up FRED loading (#7602)
FRED takes a long time opening mods that have a lot of particle entries in the particles table such as FotG (can take over a minute or more sometimes), so given FRED never renders or pages in particles, just skip the parsing.
2026-07-19 23:16:26 -04:00
wookieejedi 75a5a7e086 Fix shockwave damage bug (#7601)
* Fix two shockwave bugs

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

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

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

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

Tested and work as expected.

* just the 1expire fix

* comment cleanup
2026-07-19 23:07:45 -04:00
Goober5000andClaude Fable 5 0342fc305e fix big ship waypoint completion (the "spinning Orion" bug)
Since FS1 retail, a large ship could get permanently stuck circling its
final waypoint.  Waypoint completion requires the ship center to pass
within MIN_DIST_TO_WAYPOINT_GOAL + sqrt(radius) of the point (~37m for
a 1000m-radius capital ship), and the check only runs when the ship
covers 0.1m in a single frame (a framerate-dependent minimum speed).
At any speed that passes that gate, a capital ship turning at
2PI/(3*rotation_time) has a turning circle of hundreds of meters, so
once it overshoots a misaligned final leg it can never re-aim into the
completion bubble: an endless overshoot-turnaround-miss loop.

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

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

With the flag unset, behavior is unchanged from retail.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-19 22:48:12 -04:00
Goober5000andClaude Opus 4.8 44b14d363e Keep event annotations attached to their node across tree mutations (#7538)
Event annotations cache the tree item they point to as a handle (HTREEITEM in
FRED, QTreeWidgetItem* in qtFRED). The handle was silently invalidated whenever
the tree control deleted-and-recreated an item, so annotations got decoupled.

Fix: notify when a node's handle changes. move_branch reports old->new, and
free_node2 / the root-delete path report old->null (deleted). The event tree
remaps the annotation's handle to follow a move, or clears it on delete (the path
is left intact, so a cleared annotation is dropped at save but survives a Cancel).

Co-authored-by: Claude Opus 4.8 <[email protected]>
2026-07-18 11:25:38 -04:00
BMagnu f671153a86 Ensure props survive mission restarts (#7571) 2026-07-12 17:40:37 -04:00
BMagnu 46b77b69fa Move inline static to global static (#7572) 2026-07-12 17:40:37 -04:00
Goober5000 ed581be4a3 fix translation/rotation bugs (#7544)
For standard translation offsets, the wrong axis was being used for translation.  Fix that.  Also fix a string offset when parsing a custom axis.

Follow-up to #4582 and #3056.
2026-07-12 17:40:37 -04:00
Goober5000 303478fb58 clean up ship status after mission (#7523)
Create a `ship_level_close()` function and call it from `game_level_close()`.  Among other tasks, this clears out any PRESENT or DEATH_ROLL statuses to prevent SEXPs or scripts from obtaining references to ships that have been deleted.

Follow-up to #7522.
2026-07-12 17:40:37 -04:00
the-eandClaude Sonnet 5 fe922d2ec9 Fix crash in ai_manage_bay_doors from stale destroyed parent ship
If the carrier a fighter launched from was destroyed earlier in the
mission, its Ships[] entry retains stale bay door state while its
object and model instance are already freed (objnum/model_instance_num
== -1). When the fighter later departs, ai_manage_bay_doors would still
try to animate the dead carrier's bay doors, calling
model_get_instance(-1) and asserting/crashing.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-07-12 17:40:36 -04:00
Asteroth 8544acc951 change version (#7570) 2026-07-12 17:40:36 -04:00
Goober5000 76e9522e5a fix FRED indexing of Props (#7546)
Get the Prop class from the list, in the same way that a Ship class is gotten, which accounts for the no_fred flag.  Fixes #7545.
2026-07-12 17:40:36 -04:00
Asteroth cfc2156a12 no collision avoidance for kamikaze 2026-07-12 17:40:36 -04:00
wookieejedi 4a8bcc45d7 Fix CockpitFOV and CockpitFOVToggle missing in default table (#7547)
* Update camera.cpp

* order cleanup
2026-07-12 17:40:36 -04:00
Goober5000andClaude Opus 4.8 570d6bcec9 Fix FRED resetting the grid position after saving a mission (#7552)
When FRED2 saves a mission, OnSaveDocument() reloads it via
load_mission(MPF_FAST_RELOAD).  That reload calls clear_mission() ->
fred_render_init() -> create_default_grid(), which rebuilds The_grid as
the default origin-centered XZ plane, discarding any adjustments made in
the Adjust Grid dialog (e.g. the X-Z Plane Y Level).  The grid is editor
display state and is not stored in the mission file, so the reload had
nothing to restore it from.

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

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

Fixes #7548

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-12 17:40:36 -04:00
Goober5000andClaude Fable 5 5bb7eabbc7 fix briefing lines when deleting an icon (#7556)
When an icon is deleted, remove any lines that reference it, and fix the
indexes of lines that reference the icons shifted down to fill the gap.
Previously stale lines silently reattached to the wrong icons.  This
matches the behavior of qtFRED's deleteCurrentIcon.

Co-authored-by: Claude Fable 5 <[email protected]>
2026-07-12 17:40:36 -04:00
Goober5000 9a952cc892 Merge pull request #7557 from Goober5000/fix/brief_stage_move
make brief_stage non-copyable and non-movable; provide swap() and reset()
2026-07-12 17:40:36 -04:00
Goober5000andClaude Fable 5 cff5b7327b two loadout fixes (#7558)
* fix stale color_to_draw causing draw_model_icon crash with model_id of -1

In draw_wing_block(), color_to_draw was initialized once outside the
slot loop while bitmap_to_draw was reset every iteration.  Once any
slot in the wing took the model-icon path, color_to_draw stayed
non-NULL for all remaining slots.  A later slot whose icon had no
model (model_index == -1) and no bitmap frame to draw (e.g. a carried
icon that has not moved yet, or an icon ani with missing frames) then
fell into the color branch and called draw_model_icon() with a model
index of -1, crashing in model_clear_instance().

Reset color_to_draw each iteration, and give draw_model_icon() the
same model_id guard that draw_model_rotating() already has.

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

* guard against NULL color_to_draw when an icon ani is missing frames

In draw_ship_icon_with_number() and wl_render_icon(), a bitmap-based
icon takes the bitmap branch initially, so color_to_draw is never
assigned.  If a hot/selected/disabled state then swaps in an icon ani
frame that does not exist (e.g. a single-frame or truncated icon ani),
the frame index is -1, the final dispatch falls into the color/model
branch, and gr_set_color_fast() dereferences a NULL color.

Fall back to the normal icon color in that case.

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

---------

Co-authored-by: Claude Fable 5 <[email protected]>
2026-07-12 17:40:36 -04:00
Goober5000andClaude Fable 5 631ef0ec82 call object_moved() in the FRED and qtFRED wing Align handlers (#7559)
The Align button repositions wing members by assigning objp->pos and objp->orient directly, skipping the post-move bookkeeping that the viewport drag and orient editor paths perform.  A wing member with a docked partner (e.g. an attached support ship) would be moved to its formation slot while the partner stayed behind with broken dock geometry.

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

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

Co-authored-by: Claude Fable 5 <[email protected]>
2026-07-12 17:40:36 -04:00
Goober5000 fbd0b6704c fix wing display names in a single wave wing (#7565)
As noticed by @Achille44, wing display names were handled properly for multi-wave wings, but were inadvertently skipped for single-wave wings.
2026-07-12 17:40:36 -04:00
John Fernandez 9fb3b31a10 Mark Builds as 26.0.1
Public builds will have RC appended, and anyone testing their own build can easily verify that it's definitely from the point release branch.
2026-07-12 17:40:36 -04:00
116 changed files with 1832 additions and 876 deletions

No files matched your search

@@ -33,6 +33,7 @@ env:
NEBULA_PASSWORD: ${{ secrets.NEBULA_PASSWORD }}
HLP_API: ${{ secrets.HLP_API }}
HLP_KEY: ${{ secrets.HLP_KEY }}
FORUM_SECRET: ${{ secrets.FORUM_SECRET }}
RELEASE_TAG: ${{ inputs.releaseTag }}
jobs:
@@ -69,6 +70,7 @@ jobs:
HLP_API: ${{ secrets.HLP_API }}
HLP_KEY: ${{ secrets.HLP_KEY }}
RELEASE_TAG: ${{ inputs.releaseTag }}
FORUM_SECRET: ${{ secrets.FORUM_SECRET }}
run: python ci/post/main.py release
- name: Post Builds (Manual trigger)
+8 -6
View File
@@ -33,12 +33,14 @@ class ForumAPI:
print("Post failed! No API or API_KEY given!")
return
resp = requests.post(self.config["hlp"]["api"], data={
"api_key": self.config["hlp"]["key"],
"board": str(board),
"subject": title,
"body": content
})
resp = requests.post(self.config["hlp"]["api"],
headers={"X-HLP-Bot": os.environ["FORUM_SECRET"]},
data={
"api_key": self.config["hlp"]["key"],
"board": str(board),
"subject": title,
"body": content
})
if resp.status_code != 200:
print("Post failed! Response: %s" %resp.text)
+5
View File
@@ -18,6 +18,7 @@
# Credentials into hard-light.net for forum post
# HLP_API:
# HLP_KEY:
# FORUM_SECRET:
# LINUX_RESULT: bool, if the linux builds were successfully uploaded
# WINDOWS_RESULT: bool, if the windows builds were successfully uploaded
@@ -124,6 +125,10 @@ def get_source_version(date_version: datetime, tag_name: str) -> semantic_versio
def main():
if "FORUM_SECRET" not in os.environ or not os.environ["FORUM_SECRET"]:
print(" ERROR: FORUM_SECRET environment variable not set")
sys.exit(1)
os.chdir(os.path.dirname(__file__)) # Change working directory to this file's directory
# Aggregate configuration data in a dictionary
+3 -3
View File
@@ -6,9 +6,9 @@ if (EXISTS "${PROJECT_SOURCE_DIR}/version_override.cmake")
include("${PROJECT_SOURCE_DIR}/version_override.cmake")
endif()
set_if_not_defined(FSO_VERSION_MAJOR 25)
set_if_not_defined(FSO_VERSION_MINOR 1)
set_if_not_defined(FSO_VERSION_BUILD 0)
set_if_not_defined(FSO_VERSION_MAJOR 26)
set_if_not_defined(FSO_VERSION_MINOR 0)
set_if_not_defined(FSO_VERSION_BUILD 1)
# A numeric revision e.g. the CI build number
set_if_not_defined(FSO_VERSION_REVISION 0)
+9
View File
@@ -176,6 +176,10 @@ namespace AI {
Dont_limit_change_in_speed_due_to_physics_whack,
Guards_ignore_protected_attackers,
Fix_standard_strafe,
Fix_ai_target_recovery, // a) strafing ships that lose their target re-process their orders, like chasing ships already do
// b) ships parked in AIM_NONE while holding a standing chase order (attack-any etc.) re-process their orders;
// c) target selection skips ships outside the attacker's actively-pursues list, and self-chosen targets
// outside that list are dropped rather than held forever
Standard_strafe_used_more,
Unify_usage_ai_shield_manage_delay,
Fix_AI_shield_management_bug,
@@ -191,6 +195,11 @@ namespace AI {
Purge_player_issued_form_on_wing_after_subsequent_order,
Cancel_future_waves_of_any_wing_launched_from_an_exited_ship,
Fix_ignore_if_dead_flag,
Kamikaze_no_collision_avoidance,
Fix_big_ship_waypoint_completion, // a) big ships complete a waypoint within their radius rather than sqrt(radius);
// b) completion no longer requires moving 0.1m in a single frame (framerate-dependent)
Fix_shockwave_expire_before_do_damage, // shockwaves whose lifetime is shorter than one frame apply their area damage at least once before expiring
Fix_small_ai_recover_after_engines_repaired, // ensure small ship AI can switch back to useful AI modes if engines get repaired
NUM_VALUES
};
+27
View File
@@ -677,6 +677,8 @@ void parse_ai_profiles_tbl(const char *filename)
set_flag(profile, "$fix standard strafe:", AI::Profile_Flags::Fix_standard_strafe);
set_flag(profile, "$fix ai target recovery:", AI::Profile_Flags::Fix_ai_target_recovery);
set_flag(profile, "$standard strafe used more:", AI::Profile_Flags::Standard_strafe_used_more);
if (optional_string("$standard strafe triggers under this speed:")) {
@@ -691,6 +693,14 @@ void parse_ai_profiles_tbl(const char *filename)
stuff_float(&profile->strafe_max_unhit_time);
}
if (optional_string("$strafe retreat collide time:")) {
stuff_float(&profile->strafe_retreat_collide_time);
}
if (optional_string("$strafe retreat collide distance:")) {
stuff_float(&profile->strafe_retreat_collide_distance);
}
if (optional_string("$guard uses big-orbit for target radius above:")) {
stuff_float(&profile->guard_big_orbit_above_target_radius);
}
@@ -749,6 +759,13 @@ void parse_ai_profiles_tbl(const char *filename)
set_flag(profile, "$fix 'ignore if dead' flag:", AI::Profile_Flags::Fix_ignore_if_dead_flag);
set_flag(profile, "$no collision avoidance for kamikaze fighters:", AI::Profile_Flags::Kamikaze_no_collision_avoidance);
set_flag(profile, "$fix big ship waypoint completion:", AI::Profile_Flags::Fix_big_ship_waypoint_completion);
set_flag(profile, "$fix shockwave expiring before dealing damage:", AI::Profile_Flags::Fix_shockwave_expire_before_do_damage);
set_flag(profile, "$fix fighter/bomber AI recovers after engines repaired:", AI::Profile_Flags::Fix_small_ai_recover_after_engines_repaired);
// end of options ----------------------------------------
@@ -849,6 +866,8 @@ void ai_profile_t::reset()
standard_strafe_when_below_speed = 3.0f;
strafe_retreat_box_dist = 300.0f;
strafe_max_unhit_time = 20.0f;
strafe_retreat_collide_time = 2.0f;
strafe_retreat_collide_distance = 100.0f;
guard_big_orbit_above_target_radius = 500.0f;
guard_big_orbit_max_speed_percent = 1.0f;
@@ -975,4 +994,12 @@ void ai_profile_t::reset()
if (mod_supports_version(26, 0, 0)) {
flags.set(AI::Profile_Flags::Fix_ignore_if_dead_flag);
}
if (mod_supports_version(26, 2, 0)) {
flags.set(AI::Profile_Flags::Kamikaze_no_collision_avoidance);
flags.set(AI::Profile_Flags::Fix_big_ship_waypoint_completion);
flags.set(AI::Profile_Flags::Fix_shockwave_expire_before_do_damage);
flags.set(AI::Profile_Flags::Fix_small_ai_recover_after_engines_repaired);
flags.set(AI::Profile_Flags::Fix_standard_strafe);
flags.set(AI::Profile_Flags::Fix_ai_target_recovery);
}
}
+3 -1
View File
@@ -143,6 +143,8 @@ public:
float standard_strafe_when_below_speed; // Speed at which standard strafing large ships is possibly triggered
float strafe_retreat_box_dist; // Distance beyond the bounding box to retreat to strafing point
float strafe_max_unhit_time; // Maximum amount of time to stay in strafe mode if not hit
float strafe_retreat_collide_time; // When anticipated collision time is less than this, begin retreat from strafe
float strafe_retreat_collide_distance; // When perpendicular distance to *surface* is less than this, begin retreat from strafe
// AI guard options --wookieejedi
float guard_big_orbit_above_target_radius; // Radius of guardee that triggers ai_big_guard()
@@ -161,7 +163,7 @@ extern int Num_ai_profiles;
extern int Default_ai_profile;
extern ai_profile_t Ai_profiles[MAX_AI_PROFILES];
#define AI_PROFILES_INDEX(ai_p) ((int)((ai_p) - Ai_profiles))
#define AI_PROFILES_INDEX(ai_p) (static_cast<int>((ai_p)-Ai_profiles))
void ai_profiles_init();
+67 -62
View File
@@ -40,9 +40,6 @@
// AI BIG MAGIC NUMBERS
// Select strafing options are now exposed to modders --wookieejedi
#define STRAFE_RETREAT_COLLIDE_TIME 2.0 // when anticipated collision time is less than this, begin retreat
#define STRAFE_RETREAT_COLLIDE_DIST 100 // when perpendicular distance to *surface* is less than this, begin retreat
#define EVADE_BOX_BASE_DISTANCE 300 // standard distance to end evade submode
#define EVADE_BOX_MIN_DISTANCE 200 // minimun distance to end evade submode, after long time
@@ -1198,70 +1195,78 @@ void ai_big_chase()
}
// If a collision is expected, pull out!
float dist_normal_to_enemy;
if (!(The_mission.ai_profile->flags[AI::Profile_Flags::Kamikaze_no_collision_avoidance] && aip->ai_flags[AI::AI_Flags::Kamikaze])) {
if (vm_vec_mag_squared(&aip->big_attack_surface_normal) > 0.9) {
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing])
dist_normal_to_enemy = fl_abs((dist_to_enemy / vm_vec_dot(&vec_to_enemy, &aip->big_attack_surface_normal)));
else // I'm fairly sure this retail code VVV meant to use / instead of *
dist_normal_to_enemy = fl_abs((dist_to_enemy * vm_vec_dot(&vec_to_enemy, &aip->big_attack_surface_normal)));
} else {
// don;t have normal so use a conservative value here
dist_normal_to_enemy = 0.3f * dist_to_enemy;
}
float dist_normal_to_enemy;
// float time_to_enemy = dist_normal_to_enemy / Pl_objp->phys_info.speed * fl_abs(vm_vec_dot(&Pl_objp->phys_info.vel, &aip->big_attack_surface_normal));
// if (Framecount % 30 == 1) {
// mprintf(("normal dist; %.1f, time: %.1f\n", dist_normal_to_enemy, time_to_enemy));
// }
// since we're not in strafe and we may get a bad normal, cap dist_normal_to_enemy as MIN(0.3*dist_to_enemy, self)
// this will allow us to get closer on a bad normal
dist_normal_to_enemy = MAX(0.3f*dist_to_enemy, dist_normal_to_enemy);
if (dist_to_enemy < ATTACK_COLLIDE_BASE_DIST) {
// within 50m or 1sec
float time_to_enemy;
if (vm_vec_mag_squared(&aip->big_attack_surface_normal) > 0.9) {
if (Pl_objp->phys_info.speed > 0.1) {
time_to_enemy = dist_normal_to_enemy / fl_abs(vm_vec_dot(&Pl_objp->phys_info.vel, &aip->big_attack_surface_normal));
} else {
// a big time
time_to_enemy = 100.0f;
}
} else {
if (Pl_objp->phys_info.speed > 0.1) {
time_to_enemy = dist_normal_to_enemy / Pl_objp->phys_info.speed;
} else {
// a big time
time_to_enemy = 100.0f;
}
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing])
dist_normal_to_enemy = fl_abs((dist_to_enemy / vm_vec_dot(&vec_to_enemy, &aip->big_attack_surface_normal)));
else // I'm fairly sure this retail code VVV meant to use / instead of *
dist_normal_to_enemy = fl_abs((dist_to_enemy * vm_vec_dot(&vec_to_enemy, &aip->big_attack_surface_normal)));
}
else {
// don;t have normal so use a conservative value here
dist_normal_to_enemy = 0.3f * dist_to_enemy;
}
float speed_dist = MAX(0.0f, (Pl_objp->phys_info.speed-50) * 2);
if ((dist_normal_to_enemy < ATTACK_COLLIDE_AVOID_DIST + speed_dist) || (time_to_enemy < ATTACK_COLLIDE_AVOID_TIME) ) {
// get away, simulate crsh recovery (don't use avoid)
// accelerate_ship(aip, -1.0f);
big_ship_collide_recover_start(Pl_objp, En_objp, nullptr);
// aip->submode = SM_AVOID;
// aip->submode_start_time = Missiontime;
} else if ((dist_normal_to_enemy < ATTACK_COLLIDE_SLOW_DIST) || (time_to_enemy < ATTACK_COLLIDE_SLOW_TIME) ) {
// slow down
accelerate_ship(aip, -1.0f);
}
}
// float time_to_enemy = dist_normal_to_enemy / Pl_objp->phys_info.speed * fl_abs(vm_vec_dot(&Pl_objp->phys_info.vel, &aip->big_attack_surface_normal));
// if (Framecount % 30 == 1) {
// mprintf(("normal dist; %.1f, time: %.1f\n", dist_normal_to_enemy, time_to_enemy));
// }
/*
if ((dot_to_enemy > 1.0f - 0.1f * En_objp->radius/(dist_to_enemy + 1.0f)) && (Pl_objp->phys_info.speed > dist_to_enemy/5.0f)) {
if (might_collide_with_ship(Pl_objp, En_objp, dot_to_enemy, dist_to_enemy, (aip->targeted_subsys == NULL)*2.0f + 1.5f)) {
if ((Missiontime - aip->last_hit_time > F1_0*4) && (dist_to_enemy < Pl_objp->radius*2 + En_objp->radius*2)) {
// since we're not in strafe and we may get a bad normal, cap dist_normal_to_enemy as MIN(0.3*dist_to_enemy, self)
// this will allow us to get closer on a bad normal
dist_normal_to_enemy = MAX(0.3f * dist_to_enemy, dist_normal_to_enemy);
if (dist_to_enemy < ATTACK_COLLIDE_BASE_DIST) {
// within 50m or 1sec
float time_to_enemy;
if (vm_vec_mag_squared(&aip->big_attack_surface_normal) > 0.9) {
if (Pl_objp->phys_info.speed > 0.1) {
time_to_enemy = dist_normal_to_enemy / fl_abs(vm_vec_dot(&Pl_objp->phys_info.vel, &aip->big_attack_surface_normal));
}
else {
// a big time
time_to_enemy = 100.0f;
}
}
else {
if (Pl_objp->phys_info.speed > 0.1) {
time_to_enemy = dist_normal_to_enemy / Pl_objp->phys_info.speed;
}
else {
// a big time
time_to_enemy = 100.0f;
}
}
float speed_dist = MAX(0.0f, (Pl_objp->phys_info.speed - 50) * 2);
if ((dist_normal_to_enemy < ATTACK_COLLIDE_AVOID_DIST + speed_dist) || (time_to_enemy < ATTACK_COLLIDE_AVOID_TIME)) {
// get away, simulate crsh recovery (don't use avoid)
// accelerate_ship(aip, -1.0f);
big_ship_collide_recover_start(Pl_objp, En_objp, nullptr);
// aip->submode = SM_AVOID;
// aip->submode_start_time = Missiontime;
}
else if ((dist_normal_to_enemy < ATTACK_COLLIDE_SLOW_DIST) || (time_to_enemy < ATTACK_COLLIDE_SLOW_TIME)) {
// slow down
accelerate_ship(aip, -1.0f);
} else {
aip->submode = SM_AVOID;
aip->submode_start_time = Missiontime;
}
}
} */
/*
if ((dot_to_enemy > 1.0f - 0.1f * En_objp->radius/(dist_to_enemy + 1.0f)) && (Pl_objp->phys_info.speed > dist_to_enemy/5.0f)) {
if (might_collide_with_ship(Pl_objp, En_objp, dot_to_enemy, dist_to_enemy, (aip->targeted_subsys == NULL)*2.0f + 1.5f)) {
if ((Missiontime - aip->last_hit_time > F1_0*4) && (dist_to_enemy < Pl_objp->radius*2 + En_objp->radius*2)) {
accelerate_ship(aip, -1.0f);
} else {
aip->submode = SM_AVOID;
aip->submode_start_time = Missiontime;
}
}
} */
}
}
switch (aip->submode) {
@@ -1444,8 +1449,8 @@ static bool ai_big_strafe_maybe_retreat(const vec3d *target_pos)
collide_time = false;
collide_distance = false;
} else {
collide_time = (dist_to_target / Pl_objp->phys_info.speed) < STRAFE_RETREAT_COLLIDE_TIME;
collide_distance = dist_to_target < (STRAFE_RETREAT_COLLIDE_DIST + speed_to_dist_penalty);
collide_time = (dist_to_target / Pl_objp->phys_info.speed) < (The_mission.ai_profile->strafe_retreat_collide_time);
collide_distance = dist_to_target < ((The_mission.ai_profile->strafe_retreat_collide_distance) + speed_to_dist_penalty);
}
} else {
float dist_normal_to_target;
@@ -1455,8 +1460,8 @@ static bool ai_big_strafe_maybe_retreat(const vec3d *target_pos)
} else {
dist_normal_to_target = 0.2f * dist_to_target;
}
collide_time = (dist_normal_to_target / Pl_objp->phys_info.speed) < STRAFE_RETREAT_COLLIDE_TIME;
collide_distance = dist_normal_to_target < (STRAFE_RETREAT_COLLIDE_DIST + speed_to_dist_penalty);
collide_time = (dist_normal_to_target / Pl_objp->phys_info.speed) < (The_mission.ai_profile->strafe_retreat_collide_time);
collide_distance = dist_normal_to_target < ((The_mission.ai_profile->strafe_retreat_collide_distance) + speed_to_dist_penalty);
}
//if ((dot_to_enemy > 1.0f - 0.1f * En_objp->radius/(dist_to_enemy + 1.0f)) && (Pl_objp->phys_info.speed > dist_to_enemy/5.0f))
+127 -42
View File
@@ -2235,8 +2235,16 @@ int is_ignore_object(ai_info *aip, int objnum, int just_the_original = 0)
return 0;
}
// Returns true if a ship of class type attacker_class_type is willing to chase a target of class type target_class_type.
// Mirrors the actively-pursues fallbacks in ai_chase(): if either class type is unknown, the chase is allowed.
static bool ai_class_type_actively_pursues(int attacker_class_type, int target_class_type)
{
if (attacker_class_type < 0 || target_class_type < 0)
return true;
const auto &pursues = Ship_types[attacker_class_type].ai_actively_pursues;
return std::any_of(pursues.begin(), pursues.end(), [target_class_type](int pursued_type) { return pursued_type == target_class_type; });
}
typedef struct eval_nearest_objnum {
int objnum;
@@ -2244,6 +2252,7 @@ typedef struct eval_nearest_objnum {
int enemy_team_mask;
int enemy_ship_info_index;
int enemy_class_type;
int attacker_class_type;
int enemy_wing;
float range;
int max_attackers;
@@ -2252,7 +2261,6 @@ typedef struct eval_nearest_objnum {
int check_danger_weapon_objnum;
} eval_nearest_objnum;
void evaluate_object_as_nearest_objnum(eval_nearest_objnum *eno)
{
ai_info *aip;
@@ -2303,6 +2311,13 @@ void evaluate_object_as_nearest_objnum(eval_nearest_objnum *eno)
float dist;
int num_attacking;
// Don't pick a target that this ship's type refuses to chase, or it will park in AIM_NONE holding a target it never attacks.
// Only for unconstrained searches: an explicit chase-ship-class/type order should still produce a target even if ai_chase() doesn't chase it.
if (The_mission.ai_profile->flags[AI::Profile_Flags::Fix_ai_target_recovery]
&& eno->enemy_ship_info_index < 0 && eno->enemy_class_type < 0
&& !ai_class_type_actively_pursues(eno->attacker_class_type, Ship_info[shipp->ship_info_index].class_type))
return;
// Allow targeting of stealth in nebula by his firing at me
// This is done for a specific ship, not generally.
if ( !eno->check_danger_weapon_objnum ) {
@@ -2382,6 +2397,7 @@ int get_nearest_objnum(int objnum, int enemy_team_mask, int enemy_wing, float ra
eno.enemy_team_mask = enemy_team_mask;
eno.enemy_ship_info_index = ship_info_index;
eno.enemy_class_type = class_type;
eno.attacker_class_type = Ship_info[Ships[Objects[objnum].instance].ship_info_index].class_type;
eno.enemy_wing = enemy_wing;
eno.max_attackers = max_attackers;
eno.objnum = objnum;
@@ -2543,28 +2559,36 @@ int find_enemy(int objnum, float range, int max_attackers, int ship_info_index,
if (objnum < 0)
return -1;
object* objp = &Objects[objnum];
enemy_team_mask = iff_get_attackee_mask(obj_team(&Objects[objnum]));
enemy_team_mask = iff_get_attackee_mask(obj_team(objp));
// if target_objnum != -1, use that as goal.
ai_info* aip = &Ai_info[Ships[Objects[objnum].instance].ai_index];
ship* shipp = &Ships[objp->instance];
ai_info* aip = &Ai_info[shipp->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;
object* target_objp = &Objects[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 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_objp->signature == aip->target_signature) {
ship* target_shipp = (target_objp->type == OBJ_SHIP) ? &Ships[target_objp->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;
if (class_type < 0 || class_type == Ship_info[target_shipp->ship_info_index].class_type) {
if (!(target_objp->flags[Object::Object_Flags::Protected])) {
// same as get_nearest_objnum: only skip an unpursued target for unconstrained searches
if (!The_mission.ai_profile->flags[AI::Profile_Flags::Fix_ai_target_recovery]
|| ship_info_index >= 0 || class_type >= 0
|| ai_class_type_actively_pursues(Ship_info[shipp->ship_info_index].class_type,
Ship_info[target_shipp->ship_info_index].class_type)) {
return target_objnum;
}
}
}
}
@@ -4936,14 +4960,24 @@ void ai_fly_to_target_position(const vec3d* target_pos, bool* pl_done_p=NULL, bo
}
float dist_to_cover_this_frame = (Pl_objp->phys_info.speed * flFrametime);
if ( (dist_to_goal < MIN_DIST_TO_WAYPOINT_GOAL) || dist_to_cover_this_frame > 0.1f ) {
// Retail's completion distance of sqrt(radius) is only ~32m for a capital ship with a 1000m radius --
// far smaller than such a ship's turning circle -- and retail also skipped the completion check
// entirely unless the ship covered 0.1m in a single frame, a framerate-dependent gate that excludes
// exactly the slow speeds at which a big ship could aim precisely. Together these could trap a
// capital ship in an endless overshoot-turnaround-miss loop at its final waypoint. With the flag
// set, big ships complete a waypoint anywhere within their own radius of it, and the check always runs.
bool fix_waypoint_completion = The_mission.ai_profile->flags[AI::Profile_Flags::Fix_big_ship_waypoint_completion];
float waypoint_tolerance = MIN_DIST_TO_WAYPOINT_GOAL + ((fix_waypoint_completion && sip->is_big_or_huge()) ? Pl_objp->radius : fl_sqrt(Pl_objp->radius));
if ( fix_waypoint_completion || (dist_to_goal < MIN_DIST_TO_WAYPOINT_GOAL) || dist_to_cover_this_frame > 0.1f ) {
vec3d nearest_point;
float r;
r = find_nearest_point_on_line(&nearest_point, &Pl_objp->last_pos, &Pl_objp->pos, target_pos);
if ( (dist_to_goal < (MIN_DIST_TO_WAYPOINT_GOAL + fl_sqrt(Pl_objp->radius) + dist_to_cover_this_frame))
|| (((r >= 0.0f) && (r <= 1.0f)) && (vm_vec_dist_quick(&nearest_point, target_pos) < (MIN_DIST_TO_WAYPOINT_GOAL + fl_sqrt(Pl_objp->radius)))))
if ( (dist_to_goal < (waypoint_tolerance + dist_to_cover_this_frame))
|| (((r >= 0.0f) && (r <= 1.0f)) && (vm_vec_dist_quick(&nearest_point, target_pos) < waypoint_tolerance)))
{
int treat_as_ship;
@@ -5672,6 +5706,14 @@ void ai_set_or_clear_preferred_primary_weapon(bool set_it, const object_ship_win
}
}
// find the first primary bank that actually contains a weapon, since banks might be empty for various reasons
static int ai_first_loaded_primary_bank(const ship_weapon *swp)
{
for (int i = 0; i < swp->num_primary_banks; i++)
if (swp->primary_bank_weapons[i] >= 0)
return i;
return -1;
}
//old version of this fuction, this will be useful for playing old missions and not having the new primary
//selection code throw off the balance of the mission.
@@ -5689,7 +5731,7 @@ static int ai_select_primary_weapon_OLD(const object *objp, Weapon::Info_Flags f
Assert( shipp->ship_info_index >= 0 && shipp->ship_info_index < ship_info_size());
if (flags == Weapon::Info_Flags::Puncture) {
if (swp->current_primary_bank >= 0) {
if (swp->current_primary_bank >= 0 && swp->primary_bank_weapons[swp->current_primary_bank] >= 0) {
int bank_index;
bank_index = swp->current_primary_bank;
@@ -5713,13 +5755,11 @@ static int ai_select_primary_weapon_OLD(const object *objp, Weapon::Info_Flags f
// AL 26-3-98: If we couldn't find a puncture weapon, pick first available weapon if one isn't active
if ( swp->current_primary_bank < 0 ) {
if ( swp->num_primary_banks > 0 ) {
swp->current_primary_bank = 0;
}
swp->current_primary_bank = ai_first_loaded_primary_bank(swp);
}
} else { // Don't need to be using a puncture weapon.
if (swp->current_primary_bank >= 0) {
if (swp->current_primary_bank >= 0 && swp->primary_bank_weapons[swp->current_primary_bank] >= 0) {
if (!(Weapon_info[swp->primary_bank_weapons[swp->current_primary_bank]].wi_flags[Weapon::Info_Flags::Puncture])){
return swp->current_primary_bank;
}
@@ -5736,8 +5776,7 @@ static int ai_select_primary_weapon_OLD(const object *objp, Weapon::Info_Flags f
// Wasn't able to find a non-puncture weapon. Stick with what we have.
}
Assert( swp->current_primary_bank != -1 ); // get Alan or Allender
// this can legitimately be -1 if no bank contains a weapon
return swp->current_primary_bank;
}
@@ -5770,6 +5809,11 @@ int ai_select_primary_weapon(object *objp, object *other_objp, Weapon::Info_Flag
return -1;
}
// if the ship has no primary banks, there is nothing to select
// (this can happen when e.g. a turreted gunship is ordered to disable another ship)
if (swp->num_primary_banks <= 0)
return -1;
bool other_is_ship = (other_objp->type == OBJ_SHIP);
if (other_is_ship)
@@ -5821,7 +5865,7 @@ int ai_select_primary_weapon(object *objp, object *other_objp, Weapon::Info_Flag
//made it so it only selects puncture weapons if the active goal is to disable something -Bobboau
if ((flags == Weapon::Info_Flags::Puncture) && ai_goal_is_disable_or_disarm(Ai_info[shipp->ai_index].goals[0].ai_mode))
{
if (swp->current_primary_bank >= 0)
if (swp->current_primary_bank >= 0 && swp->primary_bank_weapons[swp->current_primary_bank] >= 0)
{
int bank_index = swp->current_primary_bank;
@@ -5900,7 +5944,9 @@ int ai_select_primary_weapon(object *objp, object *other_objp, Weapon::Info_Flag
}
if (i_hullfactor_prev_bank == -1) // In the unlikely instance we don't find at least 1 candidate weapon
{
i_hullfactor_prev_bank = 0; // Just switch to the first one
i_hullfactor_prev_bank = ai_first_loaded_primary_bank(swp); // Just switch to the first one with a weapon in it
if (i_hullfactor_prev_bank == -1) // no bank contains a weapon
return -1;
}
swp->current_primary_bank = i_hullfactor_prev_bank; // Select the best weapon
nprintf(("AI", "%i: Ship %s selecting weapon %s (no shields) vs target %s\n", Framecount, shipp->ship_name, Weapon_info[swp->primary_bank_weapons[i_hullfactor_prev_bank]].name, (other_is_ship ? other_shipp->ship_name : "non-ship") ));
@@ -5910,7 +5956,7 @@ int ai_select_primary_weapon(object *objp, object *other_objp, Weapon::Info_Flag
//if the shields are above lets say 10% definitely use a pierceing weapon if there are any-Bobboau
if (enemy_remaining_shield >= 0.10f)
{
if (swp->current_primary_bank >= 0)
if (swp->current_primary_bank >= 0 && swp->primary_bank_weapons[swp->current_primary_bank] >= 0)
{
auto wip = &Weapon_info[swp->primary_bank_weapons[swp->current_primary_bank]];
@@ -5960,7 +6006,9 @@ int ai_select_primary_weapon(object *objp, object *other_objp, Weapon::Info_Flag
}
if (i_balancedfactor_prev_bank == -1) // In the unlikely instance we don't find at least 1 candidate weapon
{
i_balancedfactor_prev_bank = 0; // Just switch to the first one
i_balancedfactor_prev_bank = ai_first_loaded_primary_bank(swp); // Just switch to the first one with a weapon in it
if (i_balancedfactor_prev_bank == -1) // no bank contains a weapon
return -1;
}
swp->current_primary_bank = i_balancedfactor_prev_bank; // Select the best weapon
nprintf(("AI", "%i: Ship %s selecting weapon %s (<50%% shields)\n", Framecount, shipp->ship_name, Weapon_info[swp->primary_bank_weapons[i_balancedfactor_prev_bank]].name));
@@ -5990,7 +6038,9 @@ int ai_select_primary_weapon(object *objp, object *other_objp, Weapon::Info_Flag
}
if (i_shieldfactor_prev_bank == -1) // In the unlikely instance we don't find at least 1 candidate weapon
{
i_shieldfactor_prev_bank = 0; // Just switch to the first one
i_shieldfactor_prev_bank = ai_first_loaded_primary_bank(swp); // Just switch to the first one with a weapon in it
if (i_shieldfactor_prev_bank == -1) // no bank contains a weapon
return -1;
}
swp->current_primary_bank = i_shieldfactor_prev_bank; // Select the best weapon
nprintf(("AI", "%i: Ship %s selecting weapon %s (>50%% shields)\n", Framecount, shipp->ship_name, Weapon_info[swp->primary_bank_weapons[i_shieldfactor_prev_bank]].name));
@@ -6320,6 +6370,10 @@ int ai_fire_primary_weapon(object *objp)
aip->primary_select_timestamp = timestamp(5 * MILLISECONDS_PER_SECOND); // Maybe change primary weapon five seconds from now.
}
// if the ship has no primary weapon selected, whether because it has no primary banks or because no bank contains a weapon, then there is nothing to fire
if (swp->current_primary_bank < 0 || swp->primary_bank_weapons[swp->current_primary_bank] < 0)
return 0;
//We can only check LoS if we have a target defined
weapon_info* wip = &Weapon_info[swp->primary_bank_weapons[swp->current_primary_bank]];
if (aip->target_objnum != -1 && (The_mission.ai_profile->flags[AI::Profile_Flags::Require_exact_los] || wip->wi_flags[Weapon::Info_Flags::Require_exact_los])) {
@@ -7569,7 +7623,7 @@ static void get_behind_ship(ai_info *aip)
dot = vm_vec_dot(&vec_from_enemy, &En_objp->orient.vec.fvec);
if (ai_willing_to_afterburn_hard(aip) && dot > 0.9f && vm_vec_dist(&Pl_objp->pos, &En_objp->pos) > 1000.0f) {
if (ai_willing_to_afterburn_hard(aip) && dot > 0.9f && vm_vec_dist_squared(&Pl_objp->pos, &En_objp->pos) > 1000.0f * 1000.0f) {
ai_afterburn_hard(Pl_objp, aip);
} else if (dot > 0.25f) {
accelerate_ship(aip, 1.0f);
@@ -7738,8 +7792,8 @@ void mabs_pick_goal_point(object *objp, object *big_objp, vec3d *collision_point
}
}
Assert(i != -1);
if (i != -1) {
Assert(min_index != -1);
if (min_index != -1) {
*avoid_pos = goals[min_index].pos;
return;
}
@@ -7813,7 +7867,7 @@ bool better_collision_avoidance_triggered(bool flag_to_check, float avoidance_ag
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 (maybe_avoid_big_ship(pl_objp, ignore_objp, &Ai_info[shipp->ai_index], &collide_vec, avoidance_aggression, 0.1f));
}
return false;
}
@@ -8991,15 +9045,8 @@ void ai_chase()
ship_info *esip = &Ship_info[Ships[En_objp->instance].ship_info_index];
if (esip->class_type > -1)
{
ship_type_info *stp = &Ship_types[sip->class_type];
size_t ap_size = stp->ai_actively_pursues.size();
for(size_t i = 0; i < ap_size; i++)
{
if(stp->ai_actively_pursues[i] == esip->class_type) {
go_after_it = true;
break;
}
}
const auto &pursues = Ship_types[sip->class_type].ai_actively_pursues;
go_after_it = std::any_of(pursues.begin(), pursues.end(), [esip](int pursued_type) { return pursued_type == esip->class_type; });
}
else
{
@@ -9012,6 +9059,15 @@ void ai_chase()
//WMC - Guess we do need this
if (!go_after_it) {
// If we picked this target ourselves -- through auto-attack, dynamic chase, or a standing chase order --
// drop it, so that the retargeting logic in ai_frame() can find something we are willing to chase.
if (The_mission.ai_profile->flags[AI::Profile_Flags::Fix_ai_target_recovery]) {
if (aip->active_goal < 0 || aip->active_goal == AI_ACTIVE_GOAL_DYNAMIC
|| (aip->active_goal < MAX_AI_GOALS && ai_goal_is_standing_chase(aip->goals[aip->active_goal].ai_mode))) {
aip->target_objnum = -1;
aip->target_signature = -1;
}
}
aip->mode = AIM_NONE;
return;
}
@@ -9137,6 +9193,9 @@ void ai_chase()
set_predicted_enemy_pos(&predicted_enemy_pos, Pl_objp, &aip->last_aim_enemy_pos, &aip->last_aim_enemy_vel, aip);
}
}
} else {
// no primary weapon to lead with, so just aim at the target
predicted_enemy_pos = enemy_pos;
}
}
@@ -12350,10 +12409,12 @@ void ai_process_subobjects(int objnum)
// handle ending animations
if ( (pss->turret_animation_position == MA_POS_READY) && timestamp_elapsed(pss->turret_animation_done_time) ) {
ship_info* turret_sip = &Ship_info[shipp->ship_info_index];
polymodel_instance* turret_pmi = model_get_instance(shipp->model_instance_num);
//For legacy animations using subtype for turret number
bool started = (Ship_info[shipp->ship_info_index].animations.getAll(model_get_instance(shipp->model_instance_num), animation::ModelAnimationTriggerType::TurretFiring, pss->system_info->subobj_num, true)
bool started = (turret_sip->animations.getAll(turret_pmi, animation::ModelAnimationTriggerType::TurretFiring, pss->system_info->subobj_num, true)
//For modern animations using proper triggered-by-subsys name
+ Ship_info[shipp->ship_info_index].animations.get(model_get_instance(shipp->model_instance_num), animation::ModelAnimationTriggerType::TurretFiring, animation::anim_name_from_subsys(pss->system_info)))
+ turret_sip->animations.get(turret_pmi, animation::ModelAnimationTriggerType::TurretFiring, animation::anim_name_from_subsys(pss->system_info)))
.start(animation::ModelAnimationDirection::RWD);
if (started) {
@@ -12437,6 +12498,13 @@ void ai_process_subobjects(int objnum)
}
}
}
}
// Goober5000 and wookieejedi - conversely, if the engines are no longer blown (e.g. after repair), resume normal chase behavior.
// The rest of the AI assumes SM_ATTACK_FOREVER implies blown engines,
// so don't leave the submode set after the engines recover, or the ship will never evade and will ignore being hit.
else if (The_mission.ai_profile->flags[AI::Profile_Flags::Fix_small_ai_recover_after_engines_repaired] && ((aip->mode == AIM_CHASE) && (aip->submode == SM_ATTACK_FOREVER))) {
aip->submode = SM_ATTACK;
aip->submode_start_time = Missiontime;
}
}
@@ -13789,6 +13857,10 @@ void ai_manage_bay_doors(object *pl_objp, ai_info *aip, bool done)
ship *parent_ship = &Ships[shipp->bay_doors_parent_shipnum];
// the parent may have already been destroyed/departed, leaving stale bay door state behind
if (parent_ship->objnum < 0 || parent_ship->model_instance_num < 0)
return;
if (done)
parent_ship->bay_doors_wanting_open--;
@@ -15492,11 +15564,24 @@ void ai_frame(int objnum)
En_objp = NULL;
}
if (aip->mode == AIM_CHASE) {
if (En_objp == NULL) {
if (aip->mode == AIM_CHASE || ((The_mission.ai_profile->flags[AI::Profile_Flags::Fix_ai_target_recovery]) && aip->mode == AIM_STRAFE)) {
// If we're chasing or strafing against large ship but have lost our target, clear the active goal
// to ensure ai_process_mission_orders() re-evaluates our orders next frame.
// Without this fighter/bomber whose strafe target is destroyed drops to AIM_NONE (see ai_execute_behavior)
// with its active_goal still set, so a standing order like attack-any is never re-processed
// and the ship slows to zero and remains still until another order is issued.
if (En_objp == nullptr) {
aip->active_goal = -1;
}
}
// Similarly, a ship that has already dropped to AIM_NONE while holding a standing chase order can never
// recover on its own: ai_execute_behavior() does nothing in AIM_NONE, and ai_process_mission_orders()
// returns early while active_goal is set. Clear the active goal so the order is re-processed next frame.
else if (The_mission.ai_profile->flags[AI::Profile_Flags::Fix_ai_target_recovery] && aip->mode == AIM_NONE
&& aip->active_goal >= 0 && aip->active_goal < MAX_AI_GOALS
&& ai_goal_is_standing_chase(aip->goals[aip->active_goal].ai_mode)) {
aip->active_goal = -1;
}
// If there is a goal to resume and enough time has elapsed, resume the goal.
if ((aip->resume_goal_time > 0) && (aip->resume_goal_time < Missiontime)) {
View File
Whitespace-only changes.
+4
View File
@@ -106,6 +106,10 @@ inline bool ai_goal_is_specific_chase(ai_goal_mode ai_mode)
{
return ai_mode == AI_GOAL_CHASE || ai_mode == AI_GOAL_CHASE_WING || ai_mode == AI_GOAL_CHASE_SHIP_CLASS || ai_mode == AI_GOAL_CHASE_SHIP_TYPE;
}
inline bool ai_goal_is_standing_chase(ai_goal_mode ai_mode)
{
return ai_mode == AI_GOAL_CHASE_ANY;
}
enum class ai_achievability { ACHIEVABLE, NOT_ACHIEVABLE, NOT_KNOWN, SATISFIED };
+45 -91
View File
@@ -350,6 +350,18 @@ const weapon_info *get_turret_weapon_wip(const ship_weapon *swp, int weapon_num)
return &Weapon_info[wi_index];
}
// similar to the above, since "weapon_num" is a bank index over multiple banks
static std::pair<int, int> get_turret_weapon_bank(int weapon_num)
{
int pbank = -1;
int sbank = -1;
if (weapon_num >= MAX_SHIP_PRIMARY_BANKS)
sbank = weapon_num - MAX_SHIP_PRIMARY_BANKS;
else
pbank = weapon_num;
return { pbank, sbank };
}
int get_turret_weapon_next_fire_stamp(const ship_weapon *swp, int weapon_num)
{
Assert(weapon_num < MAX_SHIP_WEAPONS);
@@ -1095,7 +1107,8 @@ int find_turret_enemy(const ship_subsys *turret_subsys, int objnum, const vec3d
int target_objnum = aip->target_objnum;
if (Objects[target_objnum].signature == aip->target_signature) {
ship* target_shipp = &Ships[Objects[target_objnum].instance];
// The parent's target can be a weapon or an asteroid, so make sure it's a ship before indexing Ships[]
ship* target_shipp = (Objects[target_objnum].type == OBJ_SHIP) ? &Ships[Objects[target_objnum].instance] : nullptr;
if (target_shipp && iff_matches_mask(target_shipp->team, enemy_team_mask)) {
if (!(Objects[target_objnum].flags[Object::Object_Flags::Protected])) { // check this flag as well
// nprintf(("AI", "Frame %i: Object %i resuming goal of object %i\n", AI_FrameCount, objnum,
@@ -1728,6 +1741,7 @@ bool turret_fire_weapon(int weapon_num,
auto wip = get_turret_weapon_wip(&turret->weapons, weapon_num);
if (!wip)
return false;
auto [turret_pbank, turret_sbank] = get_turret_weapon_bank(weapon_num);
bool in_lab = (gameseq_get_state() == GS_STATE_LAB);
@@ -1754,7 +1768,7 @@ bool turret_fire_weapon(int weapon_num,
parent_aip = &Ai_info[Ships[Objects[parent_objnum].instance].ai_index];
parent_ship = &Ships[Objects[parent_objnum].instance];
int turret_weapon_class = weapon_info_get_index(wip);
int turret_weapon_class = WEAPON_INFO_INDEX(wip);
#ifndef NDEBUG
// moved here from check_ok_to_fire
@@ -1838,6 +1852,7 @@ bool turret_fire_weapon(int weapon_num,
else
fire_info.target_subsys = nullptr;
fire_info.turret = turret;
fire_info.bank = turret_pbank;
fire_info.burst_seed = old_burst_seed;
fire_info.fire_method = BFM_TURRET_FIRED;
fire_info.per_burst_rotation = swp->per_burst_rot;
@@ -1850,7 +1865,6 @@ bool turret_fire_weapon(int weapon_num,
fire_info.target_pos1 = turret->last_aim_enemy_pos;
}
// fire a beam weapon
weapon_objnum = beam_fire(&fire_info);
@@ -1876,113 +1890,53 @@ bool turret_fire_weapon(int weapon_num,
}
// don't fire swam, but set up swarm info instead
else if ((wip->wi_flags[Weapon::Info_Flags::Swarm]) || (wip->wi_flags[Weapon::Info_Flags::Corkscrew])) {
if (swp->current_secondary_bank < 0) {
swp->current_secondary_bank = 0;
// swarm and corkscrew are not restricted to missiles, so check whichever bank is actually firing
if (!in_lab && (turret->system_info->flags[Model::Subsystem_Flags::Turret_use_ammo])) {
bool has_ammo = (turret_pbank >= 0)
? swp->primary_bank_ammo[turret_pbank] > 0
: ship_secondary_has_ammo(swp, turret_sbank);
if (!has_ammo)
return false;
}
int bank_to_fire = swp->current_secondary_bank;
if (!in_lab && (turret->system_info->flags[Model::Subsystem_Flags::Turret_use_ammo]) && !ship_secondary_has_ammo(swp, bank_to_fire)) {
if (!(turret->system_info->flags[Model::Subsystem_Flags::Use_multiple_guns])) {
swp->current_secondary_bank++;
if (swp->current_secondary_bank >= swp->num_secondary_banks) {
swp->current_secondary_bank = 0;
}
return false;
} else {
return false;
}
} else {
turret_swarm_set_up_info(parent_objnum, turret, wip, turret->turret_next_fire_pos, in_lab);
turret->flags.set(Ship::Subsystem_Flags::Has_fired); //set fired flag for scripting -nike
return true;
}
turret_swarm_set_up_info(parent_objnum, turret, wip, turret->turret_next_fire_pos, turret_pbank, turret_sbank, in_lab);
turret->flags.set(Ship::Subsystem_Flags::Has_fired); //set fired flag for scripting -nike
return true;
}
// now do anything else
else {
float shots_mult = wip->weapon_launch_curves.get_output(weapon_info::WeaponLaunchCurveOutputs::SHOTS_MULT, launch_curve_data);
int shots = fl2i(i2fl(wip->shots) * shots_mult);
// note that wip is reassigned to the created weapon's class inside the loop, so any
// property of the bank's own weapon must be read before we get there
bool no_ammo_needed = (turret_sbank >= 0) && Weapon_info[swp->secondary_bank_weapons[turret_sbank]].wi_flags[Weapon::Info_Flags::SecondaryNoAmmo];
for (int i = 0; i < shots; i++) {
if (!in_lab && turret->system_info->flags[Model::Subsystem_Flags::Turret_use_ammo]) {
int bank_to_fire, num_slots = turret->system_info->turret_num_firing_points;
if (wip->subtype == WP_LASER) {
if (turret_pbank >= 0) {
int points;
if (swp->num_primary_banks <= 0) {
return false;
}
if (swp->current_primary_bank < 0){
swp->current_primary_bank = 0;
return false;
}
int num_primary_banks = swp->num_primary_banks;
Assert(num_primary_banks > 0);
if (num_primary_banks < 1){
return false;
}
bank_to_fire = swp->current_primary_bank;
if (turret->system_info->flags[Model::Subsystem_Flags::Turret_salvo]) {
points = num_slots;
points = turret->system_info->turret_num_firing_points;
} else {
points = 1;
}
if (swp->primary_bank_ammo[bank_to_fire] >= points) {
swp->primary_bank_ammo[bank_to_fire] -= points;
} else if ((swp->primary_bank_ammo[bank_to_fire] >= 0) && !(The_mission.ai_profile->flags[AI::Profile_Flags::Prevent_negative_turret_ammo])) {
if (swp->primary_bank_ammo[turret_pbank] >= points) {
swp->primary_bank_ammo[turret_pbank] -= points;
} else if ((swp->primary_bank_ammo[turret_pbank] >= 0) && !(The_mission.ai_profile->flags[AI::Profile_Flags::Prevent_negative_turret_ammo])) {
// default behavior allowed ammo to be negative
swp->primary_bank_ammo[bank_to_fire] -= points;
} else if (!(turret->system_info->flags[Model::Subsystem_Flags::Use_multiple_guns]) && (swp->primary_bank_ammo[bank_to_fire] < 0)) {
swp->current_primary_bank++;
if (swp->current_primary_bank >= swp->num_primary_banks) {
swp->current_primary_bank = 0;
}
return false;
swp->primary_bank_ammo[turret_pbank] -= points;
} else {
return false;
}
}
else if (wip->subtype == WP_MISSILE) {
if (swp->num_secondary_banks <= 0) {
return false;
}
if (swp->current_secondary_bank < 0){
swp->current_secondary_bank = 0;
return false;
}
bank_to_fire = swp->current_secondary_bank;
int start_slot, end_slot;
start_slot = swp->secondary_next_slot[bank_to_fire];
end_slot = start_slot;
for (int j = start_slot; j <= end_slot; j++) {
swp->secondary_next_slot[bank_to_fire]++;
if (Weapon_info[swp->secondary_bank_weapons[bank_to_fire]].wi_flags[Weapon::Info_Flags::SecondaryNoAmmo])
continue;
if (swp->secondary_next_slot[bank_to_fire] > (num_slots - 1)){
swp->secondary_next_slot[bank_to_fire] = 0;
}
if (swp->secondary_bank_ammo[bank_to_fire] > 0) {
swp->secondary_bank_ammo[bank_to_fire]--;
} else if ((swp->secondary_bank_ammo[bank_to_fire] == 0) && !(The_mission.ai_profile->flags[AI::Profile_Flags::Prevent_negative_turret_ammo])) {
else if (turret_sbank >= 0) {
if (!no_ammo_needed) {
if (swp->secondary_bank_ammo[turret_sbank] > 0) {
swp->secondary_bank_ammo[turret_sbank]--;
} else if ((swp->secondary_bank_ammo[turret_sbank] == 0) && !(The_mission.ai_profile->flags[AI::Profile_Flags::Prevent_negative_turret_ammo])) {
// default behavior allowed ammo to be negative
swp->secondary_bank_ammo[bank_to_fire]--;
} else if (!(turret->system_info->flags[Model::Subsystem_Flags::Use_multiple_guns]) && (swp->secondary_bank_ammo[bank_to_fire] < 0)) {
swp->current_secondary_bank++;
if (swp->current_secondary_bank >= swp->num_secondary_banks) {
swp->current_secondary_bank = 0;
}
return false;
swp->secondary_bank_ammo[turret_sbank]--;
} else {
return false;
}
@@ -1997,7 +1951,7 @@ bool turret_fire_weapon(int weapon_num,
// so we need to get the position info separately for each shot
ship_get_global_turret_gun_info(&Objects[parent_objnum], turret, &firing_pos_buf, false, nullptr, true, nullptr);
weapon_objnum = weapon_create(firing_pos, &firing_orient, turret_weapon_class, parent_objnum, -1, true, false, 0.0f, turret, launch_curve_data);
weapon_objnum = weapon_create(firing_pos, &firing_orient, turret_weapon_class, parent_objnum, -1, true, false, swp, turret_pbank, turret_sbank, launch_curve_data);
weapon_set_tracking_info(weapon_objnum, parent_objnum, turret->turret_enemy_objnum, 1, turret->targeted_subsys);
//nprintf(("AI", "Turret_time_enemy_in_range = %7.3f\n", ss->turret_time_enemy_in_range));
@@ -2140,7 +2094,7 @@ void turret_swarm_fire_from_turret(turret_swarm_info *tsi)
};
// create weapon and homing info
weapon_objnum = weapon_create(&turret_pos, &firing_orient, tsi->weapon_class, tsi->parent_objnum, -1, true, false, 0.0f, tsi->turret, launch_curve_data);
weapon_objnum = weapon_create(&turret_pos, &firing_orient, tsi->weapon_class, tsi->parent_objnum, -1, true, false, &tsi->turret->weapons, tsi->swp_pbank, tsi->swp_sbank, launch_curve_data);
weapon_set_tracking_info(weapon_objnum, tsi->parent_objnum, tsi->target_objnum, 1, tsi->target_subsys);
// do other cool stuff if weapon is created.
+26 -15
View File
@@ -83,7 +83,12 @@ static bool bm_inited = false;
static uint Bm_next_signature = 0x1234;
static int Bm_low_mem = 0;
SCP_map<int,ubyte*> bm_lookup_cache;
struct bm_lookup_cache_entry {
ubyte* data;
int width;
int height;
};
SCP_map<int, bm_lookup_cache_entry> bm_lookup_cache;
/**
* How much RAM bmpman can use for textures.
@@ -144,30 +149,31 @@ bitmap_slot* bm_get_slot(int handle, bool separate_ani_frames) {
// Declaration of private functions and templates(declared as static type func(type param);)
bitmap_lookup::bitmap_lookup(int bitmap_num):
Bitmap_data(NULL)
Bitmap_data(nullptr),
Width(0),
Height(0),
Num_channels(3)
{
if ( !bm_is_valid(bitmap_num) ) return;
Num_channels = 3;
if ( bm_has_alpha_channel(bitmap_num) ) {
Num_channels = 4;
}
bitmap_entry *be = bm_get_entry(bitmap_num);
Width = be->bm.w;
Height = be->bm.h;
auto cache_search = bm_lookup_cache.find(bitmap_num);
if (cache_search == bm_lookup_cache.end()) {
Bitmap_data = (ubyte*)vm_malloc(Width * Height * Num_channels * sizeof(ubyte));
// The texture in graphics memory may be smaller than the bitmap itself, e.g. when mipmap levels
// are culled at lower texture detail settings, so the readback reports the dimensions of the
// data it actually returns.
Bitmap_data = gr_get_bitmap_from_texture(bitmap_num, &Width, &Height);
gr_get_bitmap_from_texture((void*)Bitmap_data, bitmap_num);
bm_lookup_cache.insert({bitmap_num, Bitmap_data});
if (Bitmap_data != nullptr) {
bm_lookup_cache.insert({bitmap_num, {Bitmap_data, Width, Height}});
}
} else {
Bitmap_data = cache_search->second;
Bitmap_data = cache_search->second.data;
Width = cache_search->second.width;
Height = cache_search->second.height;
}
}
@@ -190,6 +196,11 @@ float bitmap_lookup::get_channel_alpha(float u, float v)
{
Assert( Bitmap_data != NULL );
// without an alpha channel there is nothing to look up, and indexing channel 3 would read out of bounds
if ( Num_channels < 4 ) {
return 1.0f;
}
int x = fl2i(map_texture_address(u) * (Width-1));
int y = fl2i(map_texture_address(v) * (Height-1));
@@ -198,7 +209,7 @@ float bitmap_lookup::get_channel_alpha(float u, float v)
void clear_bm_lookup_cache() {
for(auto &iter: bm_lookup_cache) {
free(iter.second);
vm_free(iter.second.data);
}
bm_lookup_cache.clear();
}
+39 -20
View File
@@ -56,6 +56,10 @@ APPLY_TO_FOV_T(-, sub)
// Used to set the default value for in-game options
static float fov_default = DEFAULT_FOV;
static float cockpit_fov_default = DEFAULT_FOV;
static bool cockpit_fov_toggle_default = false;
bool Use_cockpit_fov = false;
static SCP_string fov_display(float val)
{
@@ -73,6 +77,21 @@ static void parse_fov_func()
fov_default = value;
}
static void parse_cockpit_fov_func()
{
float value;
stuff_float(&value);
CLAMP(value, 0.436332f, 1.5708f);
cockpit_fov_default = value;
}
static void parse_cockpit_fov_toggle_func()
{
bool value;
stuff_boolean(&value);
cockpit_fov_toggle_default = value;
}
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
auto FovOption = options::OptionBuilder<float>("Graphics.FOV",
std::pair<const char*, int>{"Field Of View", 1703},
@@ -90,25 +109,6 @@ auto FovOption = options::OptionBuilder<float>("Graphics.FOV",
.parser(parse_fov_func)
.finish();
bool Use_cockpit_fov = false;
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
auto CockpitFOVToggleOption = options::OptionBuilder<bool>("Graphics.CockpitFOVToggle",
std::pair<const char*, int>{"Cockpit FOV Toggle", 1838},
std::pair<const char*, int>{"Whether or not to use a different FOV for cockpit rendering from normal rendering", 1839})
.category(std::make_pair("Graphics", 1825))
.default_val(false)
.change_listener([](bool val, bool) {
if (!val) {
COCKPIT_ZOOM_DEFAULT = VIEWER_ZOOM_DEFAULT;
}
return true; // This option will always persist so we never return false
})
.level(options::ExpertLevel::Advanced)
.bind_to(&Use_cockpit_fov)
.importance(61)
.finish();
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
auto CockpitFovOption = options::OptionBuilder<float>("Graphics.CockpitFOV",
std::pair<const char*, int>{"Cockpit Field Of View", 1840},
@@ -124,9 +124,28 @@ auto CockpitFovOption = options::OptionBuilder<float>("Graphics.CockpitFOV",
return true;
})
.display(fov_display)
.default_val(fov_default)
.default_func([]() { return cockpit_fov_default; })
.level(options::ExpertLevel::Advanced)
.importance(62)
.parser(parse_cockpit_fov_func)
.finish();
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
auto CockpitFOVToggleOption = options::OptionBuilder<bool>("Graphics.CockpitFOVToggle",
std::pair<const char*, int>{"Cockpit FOV Toggle", 1838},
std::pair<const char*, int>{"Whether or not to use a different FOV for cockpit rendering from normal rendering", 1839})
.category(std::make_pair("Graphics", 1825))
.default_func([]() {return cockpit_fov_toggle_default;})
.change_listener([](bool val, bool) {
if (!val) {
COCKPIT_ZOOM_DEFAULT = VIEWER_ZOOM_DEFAULT;
}
return true; // This option will always persist so we never return false
})
.level(options::ExpertLevel::Advanced)
.bind_to(&Use_cockpit_fov)
.importance(61)
.parser(parse_cockpit_fov_toggle_func)
.finish();
//*************************CLASS: camera*************************
+1 -1
View File
@@ -249,7 +249,7 @@ void photo_mode_compute_hud_layout()
if (param.type == photo_mode_param_type::INT_RANGE) {
snprintf(buf, sizeof(buf), "%s: %d", XSTR(param.label, param.xstr_id), param.max_val);
} else {
snprintf(buf, sizeof(buf), "%s: %s", XSTR(param.label, param.xstr_id), XSTR("Off", 1285));
snprintf(buf, sizeof(buf), "%s: %s", XSTR(param.label, param.xstr_id), XSTR("Off", 1286));
}
measure(buf);
}
+1 -1
View File
@@ -56,7 +56,7 @@ int Debris_num_submodels = 0;
particle::ParticleEffectHandle Debris_hit_particle;
#define DEBRIS_INDEX(dp) (int)(dp-Debris.data())
#define DEBRIS_INDEX(dp) (static_cast<int>((dp)-Debris.data()))
// Find the first available arc slot. If none is available, and no_create is false, add one.
debris_electrical_arc *debris_find_or_create_electrical_arc_slot(debris *db, bool no_create);
+10 -2
View File
@@ -194,9 +194,17 @@ class flagset {
bool any_set() const { return values.any(); }
bool none_set() const { return values.none(); }
void from_u64(std::uint64_t num) { values = (unsigned long) num; }
void from_u64(std::uint64_t num)
{
static_assert(SIZE <= 64, "from_u64 can only be used with flagsets of up to 64 flags");
values = std::bitset<SIZE>(num);
}
std::uint64_t to_u64() const { return (std::uint64_t) values.to_ulong(); }
std::uint64_t to_u64() const
{
static_assert(SIZE <= 64, "to_u64 can only be used with flagsets of up to 64 flags");
return static_cast<std::uint64_t>(values.to_ullong());
}
size_t hash() const { return std::hash<std::bitset<SIZE>>()(values); }
};
+21
View File
@@ -1432,6 +1432,27 @@ void gr_screen_resize(int width, int height)
gr_setup_viewport();
}
void gr_window_to_render_pos(float& x, float& y)
{
if (!Cmdline_window_res) {
// Rendering goes straight to the window, so the two spaces are the same
return;
}
x *= i2fl(gr_screen.max_w) / static_cast<float>(Cmdline_window_res->first);
y *= i2fl(gr_screen.max_h) / static_cast<float>(Cmdline_window_res->second);
}
void gr_render_to_window_pos(float& x, float& y)
{
if (!Cmdline_window_res) {
return;
}
x *= static_cast<float>(Cmdline_window_res->first) / i2fl(gr_screen.max_w);
y *= static_cast<float>(Cmdline_window_res->second) / i2fl(gr_screen.max_h);
}
int gr_get_resolution_class(int width, int height)
{
if ((width >= GR_1024_THRESHOLD_WIDTH) && (height >= GR_1024_THRESHOLD_HEIGHT)) {
+24 -1
View File
@@ -831,7 +831,10 @@ typedef struct screen {
std::function<void()> gf_clear_states;
std::function<void(int bitmap_handle, int bpp, const ubyte* data, int width, int height)> gf_update_texture;
std::function<void(void* data_out, int bitmap_num)> gf_get_bitmap_from_texture;
// Reads the texture uploaded for a bitmap back from graphics memory. Returns a vm_malloc'd buffer
// (freed by the caller with vm_free) and reports the dimensions of the returned data, which may be
// smaller than the bitmap if the texture was culled at upload; returns nullptr on failure.
std::function<ubyte*(int bitmap_num, int* width_out, int* height_out)> gf_get_bitmap_from_texture;
std::function<void(matrix4* shadow_view_matrix, const matrix* light_matrix, vec3d* eye_pos)> gf_shadow_map_start;
std::function<void()> gf_shadow_map_end;
@@ -1007,6 +1010,26 @@ extern bool gr_init(std::unique_ptr<os::GraphicsOperations>&& graphicsOps, int d
extern void gr_screen_resize(int width, int height);
extern int gr_get_resolution_class(int width, int height);
/**
* @brief Converts a position or delta from OS window pixels into internal render-target pixels
*
* @details When -window_res is in effect the frame is rendered into an offscreen buffer sized
* gr_screen.max_w/max_h and only stretched to the window when we flip, so everything the engine
* draws lives in render space while SDL reports input in window space. These are pure scale
* factors with no offset, so they are equally valid for absolute positions and for deltas.
*
* Note that this is unrelated to the gr_resize_screen_pos() family, which handles the retail
* 640/1024 menu coordinate system.
*/
void gr_window_to_render_pos(float& x, float& y);
/**
* @brief Converts a position or delta from internal render-target pixels into OS window pixels
*
* @see gr_window_to_render_pos
*/
void gr_render_to_window_pos(float& x, float& y);
// Call this when your app ends.
extern void gr_close();
+4 -2
View File
@@ -259,9 +259,11 @@ void gr_stub_update_texture(int /*bitmap_handle*/, int /*bpp*/, const ubyte *
{
}
void gr_stub_get_bitmap_from_texture(void* /*data_out*/, int /*bitmap_num*/)
ubyte* gr_stub_get_bitmap_from_texture(int /*bitmap_num*/, int* width_out, int* height_out)
{
*width_out = 0;
*height_out = 0;
return nullptr;
}
int gr_stub_bm_make_render_target(int /*n*/, int * /*width*/, int * /*height*/, int * /*bpp*/, int * /*mm_lvl*/, int /*flags*/)
+1 -1
View File
@@ -63,7 +63,7 @@ bool gr_opengl_openxr_test_capabilities() {
}
if (requirements.minApiVersionSupported > XR_MAKE_VERSION(GLVersion.major, GLVersion.minor, 0)) {
mprintf(("System doesn't meet OpenXR graphics requirements (min %" PRIu64 ", available %" PRIu64 ")!\n", requirements.minApiVersionSupported, static_cast<XrVersion>(XR_MAKE_VERSION(GLVersion.major, GLVersion.minor, 0))));
mprintf(("System doesn't meet OpenXR graphics requirements (min " UINT64_T_ARG ", available " UINT64_T_ARG ")!\n", requirements.minApiVersionSupported, static_cast<XrVersion>(XR_MAKE_VERSION(GLVersion.major, GLVersion.minor, 0))));
return false;
}
+42 -15
View File
@@ -1413,15 +1413,20 @@ int opengl_get_texture( GLenum target, GLenum pixel_format, GLenum data_format,
return m_offset;
}
void gr_opengl_get_bitmap_from_texture(void* data_out, int bitmap_num)
ubyte* gr_opengl_get_bitmap_from_texture(int bitmap_num, int* width_out, int* height_out)
{
*width_out = 0;
*height_out = 0;
float u,v;
uint32_t array_index = 0;
gr_opengl_tcache_set(bitmap_num, TCACHE_TYPE_NORMAL, &u, &v, &array_index);
if ( !gr_opengl_tcache_set(bitmap_num, TCACHE_TYPE_NORMAL, &u, &v, &array_index) ) {
return nullptr;
}
auto *ts = bm_get_gr_info<tcache_slot_opengl>(bitmap_num, true);
GLenum pixel_format = GL_RGB;
GLenum data_format = GL_UNSIGNED_BYTE;
int bytes_per_pixel = 3 * sizeof(ubyte);
@@ -1431,24 +1436,46 @@ void gr_opengl_get_bitmap_from_texture(void* data_out, int bitmap_num)
bytes_per_pixel = 4 * sizeof(ubyte);
}
// We can't read a specific layer of the texture so we need to read the entire texture and then memcpy the right part from that...
int num_frames = 0;
bm_get_info(bitmap_num, nullptr, nullptr, nullptr, &num_frames);
if (!bm_is_texture_array(bitmap_num)) {
num_frames = 1;
Assertion(ts->texture_target == GL_TEXTURE_2D_ARRAY, "Unexpected texture target encountered!");
// The texture in graphics memory may not match the dimensions or frame count that bmpman reports for
// this handle, e.g. when mipmap levels are culled at lower texture detail settings, so size the
// readback from what OpenGL will actually write, not from the bitmap or the texture cache slot.
GLint gl_width = 0, gl_height = 0, gl_layers = 0;
glGetTexLevelParameteriv(ts->texture_target, 0, GL_TEXTURE_WIDTH, &gl_width);
glGetTexLevelParameteriv(ts->texture_target, 0, GL_TEXTURE_HEIGHT, &gl_height);
glGetTexLevelParameteriv(ts->texture_target, 0, GL_TEXTURE_DEPTH, &gl_layers);
if ( (gl_width < 1) || (gl_height < 1) || (gl_layers < 1) || (array_index >= static_cast<uint32_t>(gl_layers)) ) {
mprintf(("Cannot read bitmap %d (%s) back from its texture; OpenGL reports level 0 as %dx%d with %d layer(s) but layer %u is needed.\n",
bitmap_num, bm_get_filename(bitmap_num), gl_width, gl_height, gl_layers, array_index));
return nullptr;
}
// The size of a single frame in the array
auto slice_size = ts->w * ts->h * bytes_per_pixel;
std::unique_ptr<std::uint8_t[]> buffer(new std::uint8_t[num_frames * slice_size]);
// At maximum texture detail no culling occurs on upload, so the texture must match the dimensions
// recorded in the texture cache slot; if this trips, we have another slot mismatch.
Assertion((Detail.hardware_textures < 4) || ((gl_width == ts->w) && (gl_height == ts->h)),
"Texture readback size mismatch for bitmap %d (%s): OpenGL reports level 0 as %dx%d but the texture cache slot expects %dx%d.",
bitmap_num, bm_get_filename(bitmap_num), gl_width, gl_height, ts->w, ts->h);
Assertion(ts->texture_target == GL_TEXTURE_2D_ARRAY, "Unexpected texture target encountered!");
// The size of a single layer in the array
size_t slice_size = static_cast<size_t>(gl_width) * gl_height * bytes_per_pixel;
// We can't read a specific layer of the texture so we need to read the entire texture and then memcpy the right part from that...
// Some drivers write tiny compressed textures back in whole 4x4 block granularity, so pad each layer
// of the staging buffer to full blocks to keep any such overwrite within bounds.
size_t padded_slice_size = static_cast<size_t>((gl_width + 3) & ~3) * ((gl_height + 3) & ~3) * bytes_per_pixel;
std::unique_ptr<std::uint8_t[]> buffer(new std::uint8_t[padded_slice_size * gl_layers]);
// Copy the entire texture level into the bitmap
glGetTexImage(ts->texture_target, 0, pixel_format, data_format, buffer.get());
auto buffer_offset = array_index * slice_size;
memcpy(data_out, buffer.get() + buffer_offset, slice_size);
auto data_out = reinterpret_cast<ubyte*>(vm_malloc(slice_size));
memcpy(data_out, buffer.get() + array_index * slice_size, slice_size);
*width_out = gl_width;
*height_out = gl_height;
return data_out;
}
void gr_opengl_get_texture_scale(int bitmap_handle, float *u_scale, float *v_scale)
@@ -1494,7 +1521,7 @@ size_t opengl_export_render_target( int slot, int width, int height, int alpha,
return 0;
}
if ( (ts->w != width) && (ts->h != height) ) {
if ( (ts->w != width) || (ts->h != height) ) {
mprintf(("OpenGL ERROR: Passed width and height do not match values for texture!\n"));
return 0;
}
+1 -1
View File
@@ -83,7 +83,7 @@ void opengl_preload_init();
void opengl_kill_render_target(bitmap_slot* slot);
int opengl_make_render_target(int handle, int *w, int *h, int *bpp, int *mm_lvl, int flags);
int opengl_set_render_target(int slot, int face = -1, int is_static = 0);
void gr_opengl_get_bitmap_from_texture(void* data_out, int bitmap_num);
ubyte* gr_opengl_get_bitmap_from_texture(int bitmap_num, int* width_out, int* height_out);
size_t opengl_export_render_target( int slot, int width, int height, int alpha, int num_mipmaps, ubyte *image_data );
void opengl_set_texture_target(GLenum target = GL_TEXTURE_2D);
void opengl_set_texture_face(GLenum face = GL_TEXTURE_2D);
+6 -1
View File
@@ -106,7 +106,12 @@ void stub_clear_states() {}
void stub_update_texture(int /*bitmap_handle*/, int /*bpp*/, const ubyte* /*data*/, int /*width*/, int /*height*/) {}
void stub_get_bitmap_from_texture(void* /*data_out*/, int /*bitmap_num*/) {}
ubyte* stub_get_bitmap_from_texture(int /*bitmap_num*/, int* width_out, int* height_out)
{
*width_out = 0;
*height_out = 0;
return nullptr;
}
int stub_bm_make_render_target(int /*n*/, int* /*width*/, int* /*height*/, int* /*bpp*/, int* /*mm_lvl*/, int /*flags*/)
{
+8
View File
@@ -1799,6 +1799,10 @@ void load_gauge_custom(gauge_settings* settings)
hud_gauge->initChase_view_only(settings->chase_view_only);
hud_gauge->initCockpit_view_choice(settings->cockpit_view_choice);
hud_gauge->initVisible_in_config(visible_in_config);
if (openxr_requested()) {
//In this case, we must always slew every hud gauge, no matter what.
hud_gauge->initSlew(true);
}
gauge_assign_common(settings, std::move(hud_gauge));
}
@@ -3366,6 +3370,10 @@ void load_gauge_radar_dradis(gauge_settings* settings)
hud_gauge->initSound(loop_snd, loop_snd_volume, arrival_beep_snd, departure_beep_snd, stealth_arrival_snd, stealth_departure_snd, arrival_beep_delay, departure_beep_delay);
hud_gauge->initChase_view_only(settings->chase_view_only);
hud_gauge->initCockpit_view_choice(settings->cockpit_view_choice);
if (openxr_requested()) {
//In this case, we must always slew every hud gauge, no matter what.
hud_gauge->initSlew(true);
}
gauge_assign_common(settings, std::move(hud_gauge));
}
+95 -143
View File
@@ -104,8 +104,8 @@ int Menu_pgdn_coords[GR_NUM_RESOLUTIONS][2] = {
// following defines/vars are used to build menus that are used in messaging mode
char Squad_msg_title[256] = "";
mmode_item MsgItems[MAX_MENU_ITEMS];
int Num_menu_items = -1; // number of items for a message menu
SCP_vector<mmode_item> MsgItems;
static bool Rebuild_MsgItems = true;
static int First_menu_item = -1; // index of first item in the menu. This tracks what element of comms options collection is displayed as first option, and displays the next 9 options. Changes only by +/- MAX_MENU_ITEMS (10)
static int Selected_menu_item = First_menu_item; //!< index of selected item in the menu. Possible index range: 0 - 9, assuming MAX_MENU_ITEMS == 10, and First_menu_item gets initialized
@@ -218,10 +218,11 @@ void hud_squadmsg_start()
Msg_key = -1;
Msg_key_set_from_scripting = false;
Num_menu_items = -1; // reset the menu items
Rebuild_MsgItems = true; // reset the menu items
MsgItems.clear();
First_menu_item = 0;
Selected_menu_item = First_menu_item; // make first menu item a selected object
Display_selector = false;
Display_selector = Always_show_selected_item_in_comms_gauge;
Squad_msg_mode = SM_MODE_TYPE_SELECT; // start off at the base state
Msg_mode_timestamp = _timestamp(DEFAULT_MSG_TIMEOUT); // initialize our timer to bogus value
@@ -376,11 +377,7 @@ int hud_squadmsg_count_ships(int add_to_menu)
count++;
if (add_to_menu)
{
Assert ( Num_menu_items < MAX_MENU_ITEMS );
MsgItems[Num_menu_items].text = shipp->get_display_name();
MsgItems[Num_menu_items].instance = SHIP_INDEX(shipp);
MsgItems[Num_menu_items].active = 1;
Num_menu_items++;
MsgItems.push_back({SHIP_INDEX(shipp), 1, shipp->get_display_name()});
}
}
@@ -431,11 +428,7 @@ int hud_squadmsg_count_wings( int add_to_menu )
if ( hud_squadmsg_wing_valid(&Wings[wingnum]) ) {
count++;
if ( add_to_menu ) {
Assert ( Num_menu_items < MAX_MENU_ITEMS );
MsgItems[Num_menu_items].text = Wings[wingnum].get_display_name();
MsgItems[Num_menu_items].instance = wingnum;
MsgItems[Num_menu_items].active = 1;
Num_menu_items++;
MsgItems.push_back({wingnum, 1, Wings[wingnum].get_display_name()});
}
}
}
@@ -452,11 +445,7 @@ int hud_squadmsg_count_wings( int add_to_menu )
if ( hud_squadmsg_wing_valid(&Wings[i]) ) {
count++;
if ( add_to_menu ) {
Assert ( Num_menu_items < MAX_MENU_ITEMS );
MsgItems[Num_menu_items].text = Wings[i].get_display_name();
MsgItems[Num_menu_items].instance = i;
MsgItems[Num_menu_items].active = 1;
Num_menu_items++;
MsgItems.push_back({i, 1, Wings[i].get_display_name()});
}
}
}
@@ -469,15 +458,17 @@ int hud_squadmsg_count_wings( int add_to_menu )
void hud_squadmsg_do_mode( int mode )
{
Squad_msg_mode = mode;
Num_menu_items = -1;
MsgItems.clear();
Rebuild_MsgItems = true;
First_menu_item = 0;
Selected_menu_item = 0; // keep offset consistent with the reset page across submenu transitions
}
void hud_squadmsg_page_down()
{
if ( (First_menu_item + MAX_MENU_DISPLAY) < Num_menu_items ) {
if ( (First_menu_item + MAX_MENU_DISPLAY) < sz2i(MsgItems.size()) ) {
First_menu_item += MAX_MENU_DISPLAY;
Assert ( First_menu_item < Num_menu_items );
Assert ( First_menu_item < sz2i(MsgItems.size()) );
}
}
@@ -490,77 +481,52 @@ void hud_squadmsg_page_up()
}
//Fuctions that allow selection of specific comms menu items with simple up/down/select buttons
void hud_squadmsg_selection_move_down() {
void hud_squadmsg_selection_move( bool up ) {
if (Rebuild_MsgItems || MsgItems.empty())
return;
//Check if comms menu is up
if (Player->flags & PLAYER_FLAGS_MSG_MODE)
{
//move down
++Selected_menu_item;
Display_selector = true;
//play scrolling sound and reset the comms window timeout timer, so the window doesn't disappear while we select our item
gamesnd_play_iface(InterfaceSounds::SCROLL);
Msg_mode_timestamp = _timestamp(DEFAULT_MSG_TIMEOUT);
//Move to next page if we went outside of current one
if (Selected_menu_item == MAX_MENU_DISPLAY
&& (First_menu_item + MAX_MENU_DISPLAY < Num_menu_items))
{
hud_squadmsg_page_down();
Selected_menu_item = 0;
//The selection moves +/-1 through the whole menu, wrapping around at either end.
//First_menu_item and Selected_menu_item are just the page and the offset within the page.
int num_items = sz2i(MsgItems.size());
int step = up ? -1 : 1;
int selected_index = First_menu_item + Selected_menu_item;
//skip over any hidden items (such as when using Hide_main_rearm_items_in_comms_gauge).
//bound the search by the item count so a fully hidden menu does not loop forever.
for (int i = 0; i < num_items; i++) {
selected_index = (selected_index + step + num_items) % num_items;
//stop once we land on a visible item
if (MsgItems[selected_index].active >= 0)
break;
}
//Select the first menu item if we went outside items range, so we can loop around
if (First_menu_item + Selected_menu_item >= Num_menu_items)
{
First_menu_item = 0;
Selected_menu_item = First_menu_item;
}
}
}
void hud_squadmsg_selection_move_up() {
//Check if comms menu is up
if (Player->flags & PLAYER_FLAGS_MSG_MODE)
{
//move up
--Selected_menu_item;
Display_selector = true;
//play scrolling sound and reset the comms window timeout timer, so the window doesn't disappear while we select our item
gamesnd_play_iface(InterfaceSounds::SCROLL);
Msg_mode_timestamp = _timestamp(DEFAULT_MSG_TIMEOUT);
//Move to previous page if it exists
if (Selected_menu_item < 0 && First_menu_item > 0)
{
hud_squadmsg_page_up();
Selected_menu_item = MAX_MENU_DISPLAY - 1; //if we're moving to previous page in the first place, we assume it was already populated to the max
}
//Select the last menu item if we went outside items range, so we can loop around
else if (Selected_menu_item < 0)
{
//Assuming MAX_MENU_DISPLAY = 10, set First_menu_item to the nearest lower multiple of 10
//So if we have 85 items in comms menu, looping back from 1st page to last would set First_menu_item to 80
//exactly like pageUp/pageDown does
First_menu_item = ((Num_menu_items - 1) / MAX_MENU_DISPLAY) * MAX_MENU_DISPLAY;
Selected_menu_item = Num_menu_items - 1 - First_menu_item;
}
Selected_menu_item = selected_index % MAX_MENU_DISPLAY;
First_menu_item = selected_index - Selected_menu_item;
}
}
//function that tricks hud_squadmsg_get_key() into thinking player selected a menu item with a num key press
//Yes, this is a pretty much a hack, but it's simple and works with every squadmsg type.
void hud_squadmsg_selection_select() {
if (Rebuild_MsgItems) {
return;
}
//Check if comms menu is up
if (Player->flags & PLAYER_FLAGS_MSG_MODE)
{
//Check if selected option is even active
if ((MsgItems[Selected_menu_item + First_menu_item].active > 0))
if ((sz2i(MsgItems.size()) > Selected_menu_item + First_menu_item) && (MsgItems[Selected_menu_item + First_menu_item].active > 0))
{
Msg_key_used = 1;
Msg_key = Selected_menu_item + 2; //+1 because menu items on actual menu start from 1, not 0
@@ -579,7 +545,7 @@ int hud_squadmsg_get_total_keys()
int num_keys_used;
num_keys_used = MAX_KEYS_NO_SCROLL;
if ( Num_menu_items > MAX_MENU_DISPLAY )
if ( sz2i(MsgItems.size()) > MAX_MENU_DISPLAY )
num_keys_used = MAX_KEYS_USED;
return num_keys_used;
@@ -669,15 +635,16 @@ int hud_squadmsg_get_key()
} else if ( k == KEY_ESC ) {
hud_squadmsg_toggle();
return -1;
} else if ( (i < Num_menu_items) && (Squad_msg_mode == SM_MODE_REINFORCEMENTS) ) // return any key if selecting reinforcement
} else if ( (i < sz2i(MsgItems.size())) && (Squad_msg_mode == SM_MODE_REINFORCEMENTS) ) { // return any key if selecting reinforcement
return i;
}
// play general fail sound if inactive item hit.
else if ((i + First_menu_item < Num_menu_items) && (MsgItems[i + First_menu_item].active == 0)) {
else if ((i + First_menu_item < sz2i(MsgItems.size())) && (MsgItems[i + First_menu_item].active == 0)) {
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
}
else if ((i + First_menu_item < Num_menu_items) && (MsgItems[i + First_menu_item].active > 0)) { // only return keys that are associated with menu items
else if ((i + First_menu_item < sz2i(MsgItems.size())) && (MsgItems[i + First_menu_item].active > 0)) { // only return keys that are associated with menu items
return i + First_menu_item;
}
@@ -1779,24 +1746,26 @@ void hud_squadmsg_type_select( )
num_order_types += (int)lua_cat_list.size();
MsgItems.clear();
Rebuild_MsgItems = false;
// Add the items
for (i = 0; i < num_order_types; i++)
{
if (i < NUM_COMM_ORDER_TYPES) {
MsgItems[i].text = Comm_order_types[i];
MsgItems.push_back({0, 1, Comm_order_types[i]}); // assume active
} else {
MsgItems[i].text = lua_cat_list[i - NUM_COMM_ORDER_TYPES];
MsgItems.push_back({0, 1, lua_cat_list[i - NUM_COMM_ORDER_TYPES]}); // assume active
}
MsgItems[i].active = 1; // assume active
}
Num_menu_items = num_order_types;
// check to see if the player is a traitor. If so, then he will not
// be able to do anything from this menu
if ((Player_ship != NULL) && (Player_ship->team == Iff_traitor)) {
for (i = 0; i < MAX_MENU_ITEMS; i++ )
MsgItems[i].active = 0;
for (auto &item : MsgItems)
item.active = 0;
goto do_main_menu;
}
@@ -1833,8 +1802,8 @@ void hud_squadmsg_type_select( )
//
// also, only allow support ship if this player is not allowed to messaage.
if ( (hud_communications_state(Player_ship) != COMM_OK) || ((Game_mode & GM_MULTIPLAYER) && !multi_can_message(Net_player)) ) {
for ( i = 0; i < MAX_MENU_ITEMS; i++ ){
MsgItems[i].active = 0;
for (auto &item : MsgItems){
item.active = 0;
}
MsgItems[TYPE_REPAIR_REARM_ITEM].active = Hide_main_rearm_items_in_comms_gauge ? -1 : 1;
@@ -1877,7 +1846,7 @@ do_main_menu:
strcpy_s(Squad_msg_title, XSTR( "Message What", 316));
k = hud_squadmsg_get_key();
if ( k != -1 ) { // when k != -1, we have a key that associates with menu item
Assert ( k < Num_menu_items );
Assert ( k < sz2i(MsgItems.size()) );
if ( k == TYPE_SHIP_ITEM ){
hud_squadmsg_do_mode( SM_MODE_SHIP_SELECT );
} else if ( k == TYPE_WING_ITEM ) {
@@ -1907,8 +1876,9 @@ void hud_squadmsg_ship_select()
{
int k;
if ( Num_menu_items == -1 ) {
Num_menu_items = 0;
if ( Rebuild_MsgItems ) {
MsgItems.clear();
Rebuild_MsgItems = false;
hud_squadmsg_count_ships( 1 );
}
@@ -1934,8 +1904,9 @@ void hud_squadmsg_wing_select()
{
int k;
if ( Num_menu_items == -1 ) {
Num_menu_items = 0;
if ( Rebuild_MsgItems ) {
MsgItems.clear();
Rebuild_MsgItems = false;
hud_squadmsg_count_wings( 1 );
}
@@ -2045,8 +2016,9 @@ void hud_squadmsg_call_reinforcement(reinforcements &reinforcement, int /*playe
// function to display a list of reinforcements available to the player
void hud_squadmsg_reinforcement_select()
{
if ( Num_menu_items == -1 ) {
Num_menu_items = 0;
if ( Rebuild_MsgItems ) {
MsgItems.clear();
Rebuild_MsgItems = false;
int i = -1;
for (const auto &reinforcement: Reinforcements) {
@@ -2092,16 +2064,7 @@ void hud_squadmsg_reinforcement_select()
r_name = p_objp->get_display_name(); // this will handle getting rid of the hash if necessary
}
Assert ( Num_menu_items < MAX_MENU_ITEMS );
MsgItems[Num_menu_items].text = r_name;
MsgItems[Num_menu_items].instance = i;
MsgItems[Num_menu_items].active = 0;
if ( reinforcement.flags & RF_IS_AVAILABLE ) {
MsgItems[Num_menu_items].active = 1;
}
Num_menu_items++;
MsgItems.push_back({i, (reinforcement.flags & RF_IS_AVAILABLE) ? 1 : 0, r_name}); // set the item to active if reinforcement is available
}
}
@@ -2158,25 +2121,23 @@ void hud_squadmsg_ship_command()
default_orders.insert(default_messages.cbegin(), default_messages.cend());
}
Num_menu_items = 0;
MsgItems.clear();
Rebuild_MsgItems = false;
for(size_t order_id : default_orders) {
Assert (Num_menu_items < MAX_MENU_ITEMS);
MsgItems[Num_menu_items].text = Player_orders[order_id].localized_name;
MsgItems[Num_menu_items].instance = (int)order_id;
MsgItems[Num_menu_items].active = 0;
MsgItems.push_back({(int)order_id, 0, Player_orders[order_id].localized_name});
// check the bit to see if the command is active
if (orders.find(order_id) != orders.end())
MsgItems[Num_menu_items].active = 1;
MsgItems.back().active = 1;
// if the order cannot be carried out by the ship, then item should be inactive
if ((Msg_instance != MESSAGE_ALL_FIGHTERS) && !hud_squadmsg_ship_order_valid(Msg_instance, (int)order_id))
MsgItems[Num_menu_items].active = 0;
MsgItems.back().active = 0;
// do some other checks to possibly gray out other items.
// if no target, remove any items which are associated with the players target
if (!hud_squadmsg_is_target_order_valid(order_id, nullptr))
MsgItems[Num_menu_items].active = 0;
MsgItems.back().active = 0;
// if messaging all fighters, see if we should gray out the order if no one will accept it,
// or modify the text if only some of the ships will accept it
@@ -2208,14 +2169,13 @@ void hud_squadmsg_ship_command()
if (!all_accept) {
// either modify the text if a partial accept, or grey it out if no one accepts
if (partial_accept) {
MsgItems[Num_menu_items].text += XSTR("(*)", 320);
MsgItems.back().text += XSTR("(*)", 320);
} else {
MsgItems[Num_menu_items].active = 0;
MsgItems.back().active = 0;
}
}
}
Num_menu_items++;
}
@@ -2225,7 +2185,7 @@ void hud_squadmsg_ship_command()
// when we get a valid goal, we must add the goal to the ai ship's goal list
if ( k != -1 ) {
Assert ( k < Num_menu_items );
Assert ( k < sz2i(MsgItems.size()) );
// when messaging all fighters or ignoring target, call the send_to_all_fighters routine
// Goober5000 - ignore no longer sends to all fighters
if (Msg_instance == MESSAGE_ALL_FIGHTERS)
@@ -2241,9 +2201,9 @@ void hud_squadmsg_msg_general()
{
int k;
Num_menu_items = 0;
MsgItems.clear();
Rebuild_MsgItems = false;
for (size_t order_id = 0; order_id < Player_orders.size(); order_id++) {
Assert(Num_menu_items < MAX_MENU_ITEMS);
if (Player_orders[order_id].lua_id <= 0) {
continue;
@@ -2263,17 +2223,12 @@ void hud_squadmsg_msg_general()
//Only add it if it is enabled for the mission
if (lua_porder->cur_enabled) {
MsgItems[Num_menu_items].text = Player_orders[order_id].localized_name;
MsgItems[Num_menu_items].instance = Player_orders[order_id].lua_id;
MsgItems[Num_menu_items].active = (int)lua_porder->cur_valid;
MsgItems.push_back({Player_orders[order_id].lua_id, (int)lua_porder->cur_valid, Player_orders[order_id].localized_name});
// do some other checks to possibly gray out other items.
// if no target, remove any items which are associated with the players target
if (!hud_squadmsg_is_target_order_valid(order_id, nullptr))
MsgItems[Num_menu_items].active = 0;
Num_menu_items++;
MsgItems.back().active = 0;
}
}
@@ -2283,7 +2238,7 @@ void hud_squadmsg_msg_general()
// when we get a valid goal, we must add the goal to the ai ship's goal list
if (k != -1) {
Assert(k < Num_menu_items);
Assert( k < sz2i(MsgItems.size()) );
ai_lua_start_general(MsgItems[k].instance, Player_ai->target_objnum);
@@ -2314,7 +2269,8 @@ void hud_squadmsg_wing_command()
default_orders.erase(CAPTURE_TARGET_ITEM); // we cannot capture any target with a wing.
Num_menu_items = 0;
MsgItems.clear();
Rebuild_MsgItems = false;
shipnum = wingp->ship_index[wingp->special_ship];
Assertion(shipnum >= 0, "Special ship (%d) for wing '%s' has a negative ship_index (%d). This should not happen; get a coder!\n", wingp->special_ship, wingp->name, shipnum);
orders = Ships[shipnum].orders_accepted; // get the orders that the wing leader will accept
@@ -2322,24 +2278,21 @@ void hud_squadmsg_wing_command()
for ( size_t order_id : default_orders ) {
// add the set of default orders to the comm menu. We will currently allow all messages
// to be available in the wing.
Assert ( Num_menu_items < MAX_MENU_ITEMS );
MsgItems[Num_menu_items].text = Player_orders[order_id].localized_name;
MsgItems[Num_menu_items].instance = (int)order_id;
MsgItems[Num_menu_items].active = 0;
MsgItems.push_back({(int)order_id, 0, Player_orders[order_id].localized_name});
// possibly grey out the menu item depending on whether or not the "wing" will accept this order
// the "wing" won't accept the order if the first ship in the wing doesn't accept it.
if ( orders.find(order_id) != orders.end() )
MsgItems[Num_menu_items].active = 1;
MsgItems.back().active = 1;
// do some other checks to possibly gray out other items.
// if no target, remove any items which are associated with the players target
if ( !hud_squadmsg_is_target_order_valid((int)order_id, 0, true) )
MsgItems[Num_menu_items].active = 0;
MsgItems.back().active = 0;
// if no ship in the wing can depart then gray out the departure order
if (order_id == DEPART_ITEM) {
if (MsgItems[Num_menu_items].active > 0) {
if (MsgItems.back().active > 0) {
int active = 0;
for (int i = 0; i < wingp->current_count; i++) {
if (hud_squadmsg_ship_order_valid(wingp->ship_index[i], (int)order_id)) {
@@ -2347,12 +2300,9 @@ void hud_squadmsg_wing_command()
break;
}
}
MsgItems[Num_menu_items].active = active;
MsgItems.back().active = active;
}
}
Num_menu_items++;
}
@@ -2904,15 +2854,17 @@ void HudGaugeSquadMessage::render(float /*frametime*/, bool config)
renderString(x + Header_offsets[0], y + fl2i(Header_offsets[1] * scale), title, scale, config);
}
int msg_items_size = sz2i(MsgItems.size());
int nitems;
if (!config) {
if (Num_menu_items < MAX_MENU_DISPLAY)
nitems = Num_menu_items;
if (msg_items_size < MAX_MENU_DISPLAY)
nitems = msg_items_size;
else {
if (First_menu_item == 0) // First_menu_item == 0 means first page of items
nitems = MAX_MENU_DISPLAY;
else if ((Num_menu_items - First_menu_item) <= MAX_MENU_DISPLAY) // check if remaining items fit on one page
nitems = Num_menu_items - First_menu_item;
else if (msg_items_size - First_menu_item <= MAX_MENU_DISPLAY) // check if remaining items fit on one page
nitems = msg_items_size - First_menu_item;
else {
nitems = MAX_MENU_DISPLAY;
}
@@ -2953,9 +2905,6 @@ void HudGaugeSquadMessage::render(float /*frametime*/, bool config)
XSTR("Abort Rearm", 298)
};
strcpy_s(text, temp_comm_order_types[i]);
if (Hide_main_rearm_items_in_comms_gauge && (i == TYPE_REPAIR_REARM_ITEM || i == TYPE_REPAIR_REARM_ABORT_ITEM)) {
MsgItems[First_menu_item + i].active = -1;
}
}
// blit the background
@@ -2976,7 +2925,10 @@ void HudGaugeSquadMessage::render(float /*frametime*/, bool config)
setGaugeColor(HUD_C_DIM, config);
}
if (MsgItems[First_menu_item + i].active >= 0) {
bool item_visible = config
? (!Hide_main_rearm_items_in_comms_gauge) || ((i != TYPE_REPAIR_REARM_ITEM) && (i != TYPE_REPAIR_REARM_ABORT_ITEM))
: (MsgItems[First_menu_item + i].active >= 0);
if (item_visible) {
// first print an icon to indicate selected item
item_num = (i + 1) % MAX_MENU_DISPLAY;
if (isSelectedItem && Display_selector) {
@@ -3001,7 +2953,7 @@ void HudGaugeSquadMessage::render(float /*frametime*/, bool config)
// maybe draw an extra line in to make room for [pgdn], or for the 'no active items'
// display
if (!config && (!messaging_allowed || none_valid || ((First_menu_item + nitems) < Num_menu_items) || (Msg_shortcut_command != -1))) {
if (!config && (!messaging_allowed || none_valid || ((First_menu_item + nitems) < msg_items_size) || (Msg_shortcut_command != -1))) {
// blit the background
setGaugeColor(HUD_C_NONE, config);
if ( Mbox_gauge[1].first_frame >= 0 ) {
@@ -3026,7 +2978,7 @@ void HudGaugeSquadMessage::render(float /*frametime*/, bool config)
renderPrintf(x + fl2i(Pgup_offsets[0] * scale), y + fl2i(Pgup_offsets[1] * scale), scale, config, "%s", XSTR( "[pgup]", 312) );
}
if ( (First_menu_item + nitems) < Num_menu_items ) {
if ( (First_menu_item + nitems) < msg_items_size ) {
renderPrintf(x + fl2i(Pgdn_offsets[0] * scale), y + fl2i(Pgdn_offsets[1] * scale), scale, config, "%s", XSTR( "[pgdn]", 313));
}
+2 -5
View File
@@ -62,7 +62,6 @@ struct reinforcements;
// used for Message box gauge
#define NUM_MBOX_FRAMES 3
#define MAX_MENU_ITEMS 50 // max number of items in the menu
#define MAX_MENU_DISPLAY 10 // max number that can be displayed
// following are defines and character strings that are used as part of messaging mode
@@ -83,8 +82,7 @@ typedef struct mmode_item {
} mmode_item;
extern char Squad_msg_title[256];
extern mmode_item MsgItems[MAX_MENU_ITEMS];
extern int Num_menu_items; // number of items for a message menu
extern SCP_vector<mmode_item> MsgItems;
typedef struct player_order {
private:
@@ -192,8 +190,7 @@ void hud_enemymsg_toggle(); // debug function to allow messaging of enemies
void hud_squadmsg_do_mode( int mode );
// functions for menu item selection with simple up/down/select buttons
void hud_squadmsg_selection_move_down();
void hud_squadmsg_selection_move_up();
void hud_squadmsg_selection_move( bool up );
void hud_squadmsg_selection_select();
// Added for checking message validity - Mjn
+7 -9
View File
@@ -6775,10 +6775,9 @@ void HudGaugeWeapons::render(float /*frametime*/, bool config)
renderPrintfWithGauge(x + fl2i(Weapon_sunlinked_offset_x * scale), name_y, EG_NULL, scale, config, "%c", Weapon_link_icon);
// indicate if this is linked
// don't draw the link indicator if the fire can't be fired link.
// the link flag is ignored rather than cleared so the player can cycle past a no-doublefire weapon without the setting being cleared
if (!config && Player_ship->flags[Ship::Ship_Flags::Secondary_dual_fire] && !wip->wi_flags[Weapon::Info_Flags::No_doublefire] &&
!The_mission.ai_profile->flags[AI::Profile_Flags::Disable_player_secondary_doublefire] ) {
// don't draw the link indicator if this bank can't actually fire linked.
// the link flag is ignored rather than cleared so the player can cycle past an incapable bank or weapon without the setting being cleared
if (!config && Player_ship->flags[Ship::Ship_Flags::Secondary_dual_fire] && ship_secondary_bank_can_dual_fire(Player_ship, i)) {
renderPrintfWithGauge(x + fl2i(Weapon_slinked_offset_x * scale), name_y, EG_NULL, scale, config, "%c", Weapon_link_icon);
}
@@ -7732,10 +7731,9 @@ void HudGaugeSecondaryWeapons::render(float /*frametime*/, bool config)
renderPrintfWithGauge(position[0] + _sunlinked_offset_x, position[1] + text_y_offset, EG_NULL, 1.0f, config, "%c", Weapon_link_icon);
// indicate if this is linked
// don't draw the link indicator if the fire can't be fired link.
// the link flag is ignored rather than cleared so the player can cycle past a no-doublefire weapon without the setting being cleared
if ( Player_ship->flags[Ship::Ship_Flags::Secondary_dual_fire] && !wip->wi_flags[Weapon::Info_Flags::No_doublefire] &&
!The_mission.ai_profile->flags[AI::Profile_Flags::Disable_player_secondary_doublefire] ) {
// don't draw the link indicator if this bank can't actually fire linked.
// the link flag is ignored rather than cleared so the player can cycle past an incapable bank or weapon without the setting being cleared
if ( Player_ship->flags[Ship::Ship_Flags::Secondary_dual_fire] && ship_secondary_bank_can_dual_fire(Player_ship, i) ) {
renderPrintfWithGauge(position[0] + _slinked_offset_x, position[1] + text_y_offset, EG_NULL, 1.0f, config, "%c", Weapon_link_icon);
}
@@ -7945,7 +7943,7 @@ void HudGaugeHardpoints::render(float /*frametime*/, bool config)
model_render_params weapon_render_info;
if ( swp->current_secondary_bank == i && ( swp->secondary_next_slot[i] == k || ( swp->secondary_next_slot[i]+1 == k && sp->flags[Ship::Ship_Flags::Secondary_dual_fire] ) ) ) {
if ( swp->current_secondary_bank == i && ( swp->secondary_next_slot[i] == k || ( swp->secondary_next_slot[i]+1 == k && sp->flags[Ship::Ship_Flags::Secondary_dual_fire] && ship_secondary_bank_can_dual_fire(sp, i) ) ) ) {
weapon_render_info.set_color(Color_bright_blue);
} else {
weapon_render_info.set_color(Color_bright_white);
+12 -9
View File
@@ -1931,19 +1931,22 @@ int button_function_critical(int n, net_player *p = NULL)
break;
}
polymodel *pm = model_get(Ship_info[Ships[objp->instance].ship_info_index].model_num);
int firepoints = pm->missile_banks[Ships[objp->instance].weapons.current_secondary_bank].num_slots;
if (Ships[objp->instance].flags[Ship::Ship_Flags::Secondary_dual_fire] || firepoints < 2) {
Ships[objp->instance].flags.remove(Ship::Ship_Flags::Secondary_dual_fire);
if (Ships[objp->instance].flags[Ship::Ship_Flags::Secondary_dual_fire]) {
Ships[objp->instance].flags.remove(Ship::Ship_Flags::Secondary_dual_fire);
if(at_self) {
HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR( "Secondary weapon set to normal fire mode", 34));
snd_play( gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::SECONDARY_CYCLE)) );
hud_gauge_popup_start(HUD_WEAPONS_GAUGE);
}
} else if (!ship_secondary_bank_can_dual_fire(&Ships[objp->instance], Ships[objp->instance].weapons.current_secondary_bank)) {
// leave the dual fire preference alone; it is ignored rather than cleared for banks that can't use it
if(at_self) {
HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR( "This secondary bank cannot fire dual missiles", 1932));
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
hud_gauge_popup_start(HUD_WEAPONS_GAUGE);
}
} else {
Ships[objp->instance].flags.set(Ship::Ship_Flags::Secondary_dual_fire);
Ships[objp->instance].flags.set(Ship::Ship_Flags::Secondary_dual_fire);
if(at_self) {
HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR( "Secondary weapon set to dual fire mode", 35));
snd_play( gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::SECONDARY_CYCLE)) );
@@ -2905,11 +2908,11 @@ int button_function(int n)
break;
case COMMS_MENU_MOVE_DOWN:
hud_squadmsg_selection_move_down();
hud_squadmsg_selection_move(false);
break;
case COMMS_MENU_MOVE_UP:
hud_squadmsg_selection_move_up();
hud_squadmsg_selection_move(true);
break;
case COMMS_MENU_SELECT:
+25 -13
View File
@@ -97,10 +97,6 @@ const std::shared_ptr<scripting::Hook<>> OnMouseWheelHook = scripting::Hook<>::F
{"MouseWheelX", "number", "Positive if moved right, negative if moved left."},
});
#define SCALE_MOUSE_TO_WINDOW(x, y, op) \
static_cast<decltype(x)>(Cmdline_window_res ? static_cast<float>(x) op (static_cast<float>(gr_screen.max_w) / static_cast<float>(Cmdline_window_res->first)) : x), \
static_cast<decltype(y)>(Cmdline_window_res ? static_cast<float>(y) op (static_cast<float>(gr_screen.max_h) / static_cast<float>(Cmdline_window_res->second)) : y)
namespace
{
bool mouse_key_event_handler(const SDL_Event& e)
@@ -139,7 +135,16 @@ namespace
return false;
}
mouse_event(SCALE_MOUSE_TO_WINDOW(e.motion.x, e.motion.y, *), SCALE_MOUSE_TO_WINDOW(e.motion.xrel, e.motion.yrel, *));
// SDL reports these in window pixels, the rest of the engine works in render pixels
float x = i2fl(e.motion.x);
float y = i2fl(e.motion.y);
gr_window_to_render_pos(x, y);
float dx = i2fl(e.motion.xrel);
float dy = i2fl(e.motion.yrel);
gr_window_to_render_pos(dx, dy);
mouse_event(fl2i(x), fl2i(y), fl2i(dx), fl2i(dy));
return true;
}
@@ -541,7 +546,11 @@ void mouse_get_wheel_delta(int* dx, int* dy)
void mouse_force_pos(int x, int y)
{
if (os_foreground()) { // only mess with windows's mouse if we are in control of it
SDL_WarpMouseInWindow(os::getSDLMainWindow(), SCALE_MOUSE_TO_WINDOW(x, y, /));
float fx = i2fl(x);
float fy = i2fl(y);
gr_render_to_window_pos(fx, fy);
SDL_WarpMouseInWindow(os::getSDLMainWindow(), fl2i(fx), fl2i(fy));
}
}
@@ -628,13 +637,16 @@ int mouse_get_pos_unscaled( int *xpos, int *ypos )
void mouse_get_real_pos(int *mx, int *my)
{
SDL_GetMouseState(mx, my);
if (Cmdline_window_res) {
if (mx)
*mx *= gr_screen.max_w / Cmdline_window_res->first;
if (my)
*my *= gr_screen.max_h / Cmdline_window_res->second;
}
int x = 0;
int y = 0;
SDL_GetMouseState(&x, &y);
float fx = i2fl(x);
float fy = i2fl(y);
gr_window_to_render_pos(fx, fy);
if (mx) *mx = fl2i(fx);
if (my) *my = fl2i(fy);
}
void mouse_set_pos(int xpos, int ypos)
+1 -1
View File
@@ -64,7 +64,7 @@ bool *Lcl_unexpected_tstring_check = nullptr;
// NOTE: with map storage of XSTR strings, the indexes no longer need to be contiguous,
// but internal strings should still increment XSTR_SIZE to avoid collisions.
// retail XSTR_SIZE = 1570
// #define XSTR_SIZE 1932 // This is the next available ID
// #define XSTR_SIZE 1933 // This is the next available ID
// struct to allow for strings.tbl-determined x offset
// offset is 0 for english, by default
+45 -4
View File
@@ -20,6 +20,8 @@
#include "hud/hud.h"
#include "utils/unicode.h"
#include <utility>
#define MAX_TEXT_STREAMS 2 // how many concurrent streams of text can be displayed
// ------------------------------------------------------------------------
@@ -163,15 +165,54 @@ public:
bool draw_grid;
color grid_color;
brief_stage( )
: camera_time( 0 ), flags( 0 ), formula( -1 ), num_icons(0), icons(nullptr), num_lines(0), lines(nullptr),
draw_grid(true)
{
brief_stage( )
{
reset();
}
// Restore this stage to its default-constructed state. Does not free
// the icons/lines buffers; the caller is responsible for those.
void reset()
{
text.clear();
voice[ 0 ] = 0;
camera_pos = vmd_zero_vector;
camera_orient = vmd_identity_matrix;
camera_time = 0;
flags = 0;
formula = -1;
num_icons = 0;
icons = nullptr;
num_lines = 0;
lines = nullptr;
draw_grid = true;
grid_color = Color_briefing_grid;
}
// icons and lines are raw owning pointers, so the memberwise copy/move
// operations would alias those buffers across two stages; use swap() to
// rearrange stages, or copy the buffer contents explicitly
brief_stage(const brief_stage&) = delete;
brief_stage& operator=(const brief_stage&) = delete;
brief_stage(brief_stage&&) = delete;
brief_stage& operator=(brief_stage&&) = delete;
friend void swap(brief_stage& a, brief_stage& b)
{
std::swap(a.text, b.text);
std::swap(a.voice, b.voice);
std::swap(a.camera_pos, b.camera_pos);
std::swap(a.camera_orient, b.camera_orient);
std::swap(a.camera_time, b.camera_time);
std::swap(a.flags, b.flags);
std::swap(a.formula, b.formula);
std::swap(a.num_icons, b.num_icons);
std::swap(a.icons, b.icons);
std::swap(a.num_lines, b.num_lines);
std::swap(a.lines, b.lines);
std::swap(a.draw_grid, b.draw_grid);
std::swap(a.grid_color, b.grid_color);
}
};
class debrief_stage
+44 -9
View File
@@ -4797,8 +4797,26 @@ int parse_wing_create_ships( wing *wingp, int num_to_create, bool force_create,
wingp->total_arrived_count++;
if (wingp->num_waves > 1)
{
char pre_bash_name[NAME_LENGTH];
strcpy_s(pre_bash_name, p_objp->name);
wing_bash_ship_name(p_objp, wingp, wingp->total_arrived_count + wingp->red_alert_skipped_ships);
// if the bash renamed this parse object (which can happen if the ship was saved using the legacy
// hash format), re-key its not-yet-present registry entry so that the old name doesn't shadow
// lookups of the new name. (For subsequent waves, the pre-bash name belongs to a previous wave's
// ship, whose entry must keep its key; the status and parse object checks exclude that case.)
if (stricmp(pre_bash_name, p_objp->name) != 0)
{
auto ship_it = Ship_registry_map.find(pre_bash_name);
if (ship_it != Ship_registry_map.end()
&& Ship_registry[ship_it->second].status == ShipStatus::NOT_YET_PRESENT
&& Ship_registry[ship_it->second].pobj_num == POBJ_INDEX(p_objp))
{
ship_registry_rename(ship_it->second, p_objp->name, true);
}
}
// subsequent waves of ships will not be in the ship registry, so add them
if (!ship_registry_exists(p_objp->name))
{
@@ -4810,6 +4828,12 @@ int parse_wing_create_ships( wing *wingp, int num_to_create, bool force_create,
Ship_registry_map[p_objp->name] = static_cast<int>(Ship_registry.size() - 1);
}
}
// make sure we still handle wing display names in a single-wave wing
else if (wingp->has_display_name())
{
wing_bash_ship_name(p_objp->display_name, wingp->get_display_name(), wingp->total_arrived_count + wingp->red_alert_skipped_ships);
p_objp->flags.set(Mission::Parse_Object_Flags::SF_Has_display_name);
}
// also, if multiplayer, set the parse object's net signature to be wing's net signature
// base + total_arrived_count (before adding 1)
@@ -8022,6 +8046,17 @@ int mission_parse_get_multi_mission_info( const char *filename )
return The_mission.num_players;
}
static p_object *mission_parse_get_arrival_ship_sub(const char *name)
{
for (auto p_objp : list_range(&Ship_arrival_list))
{
if (!stricmp(p_objp->name, name))
return p_objp; // still on the arrival list
}
return nullptr;
}
/**
* @brief Returns the parse object on the ship arrival list associated with the given name.
* @param[in] name The name of the object
@@ -8032,18 +8067,18 @@ int mission_parse_get_multi_mission_info( const char *filename )
*/
p_object *mission_parse_get_arrival_ship(const char *name)
{
p_object *p_objp;
if (name == nullptr)
return nullptr;
for (p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp))
{
if (!stricmp(p_objp->name, name))
{
return p_objp; // still on the arrival list
}
}
// try the normal lookup
auto p_objp = mission_parse_get_arrival_ship_sub(name);
if (p_objp)
return p_objp;
// also search for ship names hashed using the legacy format
SCP_string legacy_hashed;
if (wing_bash_legacy_hashed_ship_name(legacy_hashed, name))
return mission_parse_get_arrival_ship_sub(legacy_hashed.c_str());
return nullptr;
}
+1 -1
View File
@@ -530,7 +530,7 @@ public:
// Goober5000 - this is now dynamic
extern SCP_vector<p_object> Parse_objects;
#define POBJ_INDEX(pobjp) (int)(pobjp - &Parse_objects[0]) // yes, this arithmetic is valid :D
#define POBJ_INDEX(pobjp) (static_cast<int>((pobjp)-Parse_objects.data()))
extern p_object Support_ship_pobj, *Arriving_support_ship;
extern p_object Ship_arrival_list;
+1 -1
View File
@@ -268,7 +268,7 @@ int Fred_campaign_save::save_campaign_file(const char* pathname, const SCP_vecto
fout(")");
}
if (mission_fork) {
fout("Option: ", Campaign.missions[link.to].name);
fout("\n+Option: %s", Campaign.missions[link.to].name);
}
}
}
+21
View File
@@ -1,7 +1,9 @@
// methods and members common to any mission editor FSO may have
#include "common.h"
#include "globalincs/linklist.h"
#include "mission/missionparse.h"
#include "iff_defs/iff_defs.h"
#include "object/object.h"
#include "ship/ship.h"
// to keep track of data
@@ -145,3 +147,22 @@ void generate_weaponry_usage_list_wing(int wing_num, int* arr)
}
}
}
void ensure_valid_player_start_shipnum()
{
// nothing to do if the current player start is still valid
if (Player_start_shipnum >= 0 && Player_start_shipnum < MAX_SHIPS
&& Ships[Player_start_shipnum].objnum >= 0
&& Objects[Ships[Player_start_shipnum].objnum].type == OBJ_START) {
return;
}
// otherwise repoint to the first remaining player start, or -1 if there are none
Player_start_shipnum = -1;
for (auto *objp : list_range(&obj_used_list)) {
if (objp->type == OBJ_START) {
Player_start_shipnum = objp->instance;
break;
}
}
}
+5
View File
@@ -48,3 +48,8 @@ anchor_t target_to_anchor(int target);
void generate_weaponry_usage_list_team(int team, int* arr);
void generate_weaponry_usage_list_wing(int wing_num, int* arr);
// If Player_start_shipnum no longer refers to a valid player start ship, repoint it to the
// first remaining player start in the mission (or -1 if there are none). Call this after
// changing a ship to or from an OBJ_START via demotion, deletion, etc.
void ensure_valid_player_start_shipnum();
+45 -21
View File
@@ -2563,7 +2563,7 @@ int Fred_mission_save::save_mission_info()
fout("\n+Flags:");
}
fout(" %" PRIu64, The_mission.flags.to_u64());
fout(" " UINT64_T_ARG, The_mission.flags.to_u64());
// maybe write out Nebula values
if (The_mission.flags[Mission::Mission_Flags::Fullneb]) {
@@ -2858,22 +2858,29 @@ int Fred_mission_save::save_mission_info()
// this is compatible with non-SCP variants - Goober5000
fout(" %d", (The_mission.support_ships.max_support_ships == 0) ? 1 : 0);
// version-gated support options; see also check_for_25_1_data()
auto more_support_options_version = gameversion::version(25, 1);
if (save_config.save_format != MissionFormat::RETAIL) {
if (optional_string_fred("+Disallow Support Rearm:")) {
parse_comments(2);
} else {
fout("\n+Disallow Support Rearm:");
if (The_mission.required_fso_version >= more_support_options_version) {
if (optional_string_fred("+Disallow Support Rearm:")) {
parse_comments(2);
} else {
fout("\n+Disallow Support Rearm:");
}
fout(" %d", The_mission.support_ships.disallow_rearm ? 1 : 0);
}
fout(" %d", The_mission.support_ships.disallow_rearm ? 1 : 0);
if (The_mission.required_fso_version >= more_support_options_version) {
if (optional_string_fred("+Allow Support Rearm Weapon Precedence:")) {
parse_comments(2);
} else {
fout("\n+Allow Support Rearm Weapon Precedence:");
}
if (optional_string_fred("+Allow Support Rearm Weapon Precedence:")) {
parse_comments(2);
} else {
fout("\n+Allow Support Rearm Weapon Precedence:");
fout(" %d", The_mission.support_ships.allow_rearm_weapon_precedence ? 1 : 0);
}
fout(" %d", The_mission.support_ships.allow_rearm_weapon_precedence ? 1 : 0);
}
// here be WMCoolmon's hull and subsys repair stuff
@@ -2892,12 +2899,14 @@ int Fred_mission_save::save_mission_info()
}
fout(" %f", The_mission.support_ships.max_subsys_repair_val);
if (optional_string_fred("+Support Rearm Pool From Loadout:")) {
parse_comments(1);
} else {
fout("\n+Support Rearm Pool From Loadout:");
if (The_mission.required_fso_version >= more_support_options_version) {
if (optional_string_fred("+Support Rearm Pool From Loadout:")) {
parse_comments(1);
} else {
fout("\n+Support Rearm Pool From Loadout:");
}
fout(" %d", The_mission.support_ships.rearm_pool_from_loadout ? 1 : 0);
}
fout(" %d", The_mission.support_ships.rearm_pool_from_loadout ? 1 : 0);
}
if (Mission_all_attack) {
@@ -3602,7 +3611,7 @@ int Fred_mission_save::save_objects()
fout(" %s", save_config.fred_alt_names[i]);
}
Assertion(save_config.fred_callsigns != nullptr, "save_config.fred_alt_names is null!");
Assertion(save_config.fred_callsigns != nullptr, "save_config.fred_callsigns is null!");
// optional callsign
if (save_config.save_format != MissionFormat::RETAIL && strlen(save_config.fred_callsigns[i])) {
@@ -4390,8 +4399,7 @@ int Fred_mission_save::save_players()
for (i = 0; i < Num_teams; i++) {
required_string_fred("$Starting Shipname:");
parse_comments();
Assert(Player_start_shipnum >= 0);
fout(" %s", Ships[Player_start_shipnum].ship_name);
fout(" %s", (Player_start_shipnum >= 0) ? Ships[Player_start_shipnum].ship_name : "<none>");
if (save_config.save_format != MissionFormat::RETAIL) {
if (Team_data[i].do_not_validate) {
@@ -4509,7 +4517,23 @@ int Fred_mission_save::save_players()
fout(")");
if (save_config.save_format != MissionFormat::RETAIL && !The_mission.support_ships.rearm_pool_from_loadout) {
// version-gated support options; see also check_for_25_1_data()
auto more_support_options_version = gameversion::version(25, 1);
bool has_rearm_pool_entries = false;
if (save_config.save_format != MissionFormat::RETAIL && The_mission.required_fso_version >= more_support_options_version && !The_mission.support_ships.rearm_pool_from_loadout) {
for (j = 0; j < weapon_info_size(); j++) {
// mirror the skip conditions used when writing the pool below
if (!Weapon_info[j].wi_flags[Weapon::Info_Flags::Player_allowed] || Weapon_info[j].disallow_rearm) {
continue;
}
if (The_mission.support_ships.rearm_weapon_pool[i][j] != -1) {
has_rearm_pool_entries = true;
break;
}
}
}
if (has_rearm_pool_entries) {
if (optional_string_fred("+Support Rearm Pool:", "$Starting Shipname:")) {
parse_comments(2);
} else {
+2 -2
View File
@@ -821,7 +821,7 @@ void brief_compact_stages()
Briefing->stages[num].num_icons = 0;
for ( i = num+1; i < Briefing->num_stages; i++ ) {
Briefing->stages[i-1] = Briefing->stages[i];
swap(Briefing->stages[i-1], Briefing->stages[i]);
}
Briefing->num_stages--;
continue;
@@ -832,7 +832,7 @@ void brief_compact_stages()
// completely clear out the old entries (if any) so we don't access them by mistake - taylor
if ( before > Briefing->num_stages ) {
for ( i = Briefing->num_stages; i < before; i++ ) {
Briefing->stages[i] = brief_stage();
Briefing->stages[i].reset();
}
}
}
+5 -1
View File
@@ -1560,8 +1560,12 @@ int restore_wss_data(ubyte *data)
void draw_model_icon(int model_id, uint64_t flags, int x, int y, int w, int h, ship_info* sip, weapon_info* wip, float zoom_multiplier, int resize_mode)
{
// Can't draw a non-model
if (model_id < 0)
return;
lighting_profiles::set_non_mission_profile non_mission_lighting_profile;
matrix object_orient = IDENTITY_MATRIX;
angles rot_angles = vmd_zero_angles;
+4
View File
@@ -1697,6 +1697,9 @@ void draw_ship_icon_with_number(int screen_offset, int ship_class)
ss_icon->model_index = model_load(sip, true);
mprintf(("SL WARNING: Had to attempt to page in model for %s paged in manually! Result: %d\n", sip->name, ss_icon->model_index));
}
// the icon bitmap can be invalid without a color having been chosen, e.g. when the icon ani is missing frames
if (color_to_draw == nullptr)
color_to_draw = &Icon_colors[ICON_FRAME_NORMAL];
gr_set_color_fast(color_to_draw);
graphics::line_draw_list line_draw_list;
@@ -2184,6 +2187,7 @@ void draw_wing_block(int wb_num, int hot_slot, int selected_slot, int class_sele
GR_DEBUG_SCOPE("Single ship");
bitmap_to_draw = -1;
color_to_draw = nullptr;
ws = &wb->ss_slots[i];
slot_index = wb_num*MAX_WING_SLOTS + i;
+3
View File
@@ -3132,6 +3132,9 @@ void wl_render_icon(int index, int x, int y, int num, int draw_num_flag, int hot
}
else
{
// the icon bitmap can be invalid without a color having been chosen, e.g. when the icon ani is missing frames
if (color_to_draw == nullptr)
color_to_draw = &Icon_colors[ICON_FRAME_NORMAL];
gr_set_color_fast(color_to_draw);
graphics::line_draw_list line_draw_list;
+6
View File
@@ -170,6 +170,7 @@ bool Use_new_scanning_behavior;
bool Lua_API_returns_nil_instead_of_invalid_object;
bool Dont_show_callsigns_in_escort_list;
bool Hide_main_rearm_items_in_comms_gauge;
bool Always_show_selected_item_in_comms_gauge;
bool Fix_scripted_velocity;
color Overhead_line_colors[MAX_SHIP_SECONDARY_BANKS];
bool Preload_briefing_icon_models;
@@ -495,6 +496,10 @@ void parse_mod_table(const char *filename)
stuff_boolean(&Hide_main_rearm_items_in_comms_gauge);
}
if (optional_string("$Always show selected item in Comms Gauge:")) {
stuff_boolean(&Always_show_selected_item_in_comms_gauge);
}
optional_string("#SEXP SETTINGS");
if (optional_string("$Loop SEXPs Then Arguments:")) {
@@ -1882,6 +1887,7 @@ void mod_table_reset()
Use_new_scanning_behavior = false;
Lua_API_returns_nil_instead_of_invalid_object = false;
Dont_show_callsigns_in_escort_list = false;
Always_show_selected_item_in_comms_gauge = false;
Hide_main_rearm_items_in_comms_gauge = false;
Fix_scripted_velocity = false;
// These colors were taken from missionscreencommon.cpp line 591 which
+1
View File
@@ -192,6 +192,7 @@ extern bool Use_new_scanning_behavior;
extern bool Lua_API_returns_nil_instead_of_invalid_object;
extern bool Dont_show_callsigns_in_escort_list;
extern bool Hide_main_rearm_items_in_comms_gauge;
extern bool Always_show_selected_item_in_comms_gauge;
extern bool Fix_scripted_velocity;
extern color Overhead_line_colors[MAX_SHIP_SECONDARY_BANKS];
extern bool Preload_briefing_icon_models;
+17 -2
View File
@@ -35,6 +35,7 @@
#include "model/modelreplace.h"
#include "model/modelsinc.h"
#include "parse/parselo.h"
#include "prop/prop.h"
#include "render/3dinternal.h"
#include "ship/ship.h"
#include "starfield/starfield.h"
@@ -312,6 +313,16 @@ void model_unload(int modelnum, int force)
}
}
// and props, for the same reason: props_level_close() only clears the prop
// instances, so without this the class-level handle survives the model being
// freed and the next placement of that prop class reads a slot that has since
// been reused by an unrelated model.
for (auto& pip : Prop_info) {
if (pm->id == pip.model_num) {
pip.model_num = -1;
}
}
model_free(pm);
Polygon_models[num] = NULL;
@@ -1300,7 +1311,7 @@ void determine_submodel_movement(bool is_rotation, const char *filename, bsp_inf
if (in(p, props, axis_string))
{
if (get_user_vec3d_value(p + 20, movement_axis, true, sm->name, filename))
if (get_user_vec3d_value(p + strlen(axis_string), movement_axis, true, sm->name, filename))
{
if (!fl_near_zero(vm_vec_mag(movement_axis)))
vm_vec_normalize(movement_axis);
@@ -4075,7 +4086,7 @@ void submodel_canonicalize_translation(bsp_info *sm, submodel_instance *smi)
smi->canonical_prev_offset = smi->canonical_offset;
// get the vector
switch (sm->rotation_axis_id)
switch (sm->translation_axis_id)
{
case MOVEMENT_AXIS_X:
vm_vec_copy_scale(&smi->canonical_offset, &vmd_x_vector, smi->cur_offset);
@@ -4427,6 +4438,10 @@ bool model_rotate_gun(const object *objp, const polymodel *pm, const polymodel_i
vm_vec_sub(&dir, &planar_dst, &world_pos);
vm_vec_normalize(&dir);
desired_base_angle = vm_vec_delta_ang_norm(&rotated_vec, &dir, &world_axis);
// for ventral turrets without custom matrixes
if (vm_vec_dot(&base_sm->frame_of_reference.vec.uvec, &turret->turret_norm) < 0.0f) {
desired_base_angle = PI + desired_base_angle;
}
//------------
// Pretend the base is pointing directly at the target
+1
View File
@@ -482,6 +482,7 @@ void VirtualPOFOperationAddSubmodel::process(polymodel* pm, model_read_deferred_
for (const glow_point_bank* gpb : glowpointbanks) {
pm->glow_point_banks[insertFrom] = object_copy_including_array_member(*gpb, &glow_point_bank::num_points, &glow_point_bank::points);
change_submodel_numbers(pm->glow_point_banks[insertFrom], replaceSubobjNo);
insertFrom++;
}
}
}
+1 -1
View File
@@ -134,7 +134,7 @@ class player;
//
// netplayer management
#define NET_PLAYER_INDEX(np) (static_cast<int>(np-Net_players))
#define NET_PLAYER_INDEX(np) (static_cast<int>((np)-Net_players))
#define NET_PLAYER_NUM(np) (NET_PLAYER_INDEX(np))
#define MY_NET_PLAYER_NUM (NET_PLAYER_INDEX(Net_player))
+4 -2
View File
@@ -165,8 +165,10 @@ void interpolation_manager::interpolate_main(vec3d* pos, matrix* ori, physics_in
// correct the ship record for player ships when an up to date packet comes in.
void interpolation_manager::reinterpolate_previous(TIMESTAMP stamp, int prev_packet_index, int next_packet_index, vec3d& position, matrix& orientation, vec3d& velocity, vec3d& rotational_velocity)
{
// calc what the timing was previously.
float numerator = static_cast<float>(stamp.value()) - static_cast<float>(_packets[prev_packet_index].remote_missiontime);
// calc what the timing was previously. The caller hands us an absolute timestamp,
// but remote_missiontime is relative to mission start, so drop the start time before comparing.
auto local_time = static_cast<float>(stamp.value() - Multi_Timing_Info.get_mission_start_time());
float numerator = local_time - static_cast<float>(_packets[prev_packet_index].remote_missiontime);
float denominator = static_cast<float>(_packets[next_packet_index].remote_missiontime) - static_cast<float>(_packets[prev_packet_index].remote_missiontime);
denominator = (denominator > 0.05f) ? denominator : 0.05f;
+20 -9
View File
@@ -1223,6 +1223,17 @@ int multi_oo_pack_client_data(ubyte *data, ship* shipp)
return packet_size;
}
// vm_extract_angles_matrix_alternate returns angles in the range -PI..PI, but the subsystem list packer
// encodes them as an unsigned fraction of a full rotation, so wrap negatives around before sending.
static float multi_oo_normalized_angle(float angle)
{
if (angle < 0.0f) {
angle += PI2;
}
return angle / PI2;
}
// pack the appropriate info into the data
#define PACK_PERCENT(v) { std::uint8_t upercent; if(v < 0.0f){v = 0.0f;} upercent = (v * 255.0f) <= 255.0f ? (std::uint8_t)(v * 255.0f) : (std::uint8_t)255; memcpy(data + packet_size + header_bytes, &upercent, sizeof(std::uint8_t)); packet_size++; }
#define PACK_BYTE(v) { memcpy( data + packet_size + header_bytes, &v, 1 ); packet_size += 1; }
@@ -1411,32 +1422,32 @@ int multi_oo_pack_data(net_player *pl, object *objp, ushort oo_flags, ubyte *dat
// here we're checking to see if the subsystems rotated enough to send.
if (angs_1 != nullptr && angs_1->b != Oo_info.player_frame_info[pl->player_id].last_sent[objp->net_signature].subsystem_1b[i]) {
flags[i] |= OO_SUBSYS_ROTATION_1b;
subsys_data.push_back(angs_1->b / PI2);
subsys_data.push_back(multi_oo_normalized_angle(angs_1->b));
}
if (angs_1 != nullptr && angs_1->h != Oo_info.player_frame_info[pl->player_id].last_sent[objp->net_signature].subsystem_1h[i]) {
flags[i] |= OO_SUBSYS_ROTATION_1h;
subsys_data.push_back(angs_1->h / PI2);
subsys_data.push_back(multi_oo_normalized_angle(angs_1->h));
}
if (angs_1 != nullptr && angs_1->p != Oo_info.player_frame_info[pl->player_id].last_sent[objp->net_signature].subsystem_1p[i]) {
flags[i] |= OO_SUBSYS_ROTATION_1p;
subsys_data.push_back(angs_1->p / PI2);
subsys_data.push_back(multi_oo_normalized_angle(angs_1->p));
}
if (angs_2 != nullptr && angs_2->b != Oo_info.player_frame_info[pl->player_id].last_sent[objp->net_signature].subsystem_2b[i]) {
flags[i] |= OO_SUBSYS_ROTATION_2b;
subsys_data.push_back(angs_2->b / PI2);
subsys_data.push_back(multi_oo_normalized_angle(angs_2->b));
}
if (angs_2 != nullptr && angs_2->h != Oo_info.player_frame_info[pl->player_id].last_sent[objp->net_signature].subsystem_2h[i]) {
flags[i] |= OO_SUBSYS_ROTATION_2h;
subsys_data.push_back(angs_2->h / PI2);
subsys_data.push_back(multi_oo_normalized_angle(angs_2->h));
}
if (angs_2 != nullptr && angs_2->p != Oo_info.player_frame_info[pl->player_id].last_sent[objp->net_signature].subsystem_2p[i]) {
flags[i] |= OO_SUBSYS_ROTATION_2p;
subsys_data.push_back(angs_2->p / PI2);
subsys_data.push_back(multi_oo_normalized_angle(angs_2->p));
}
// clang says deleting null pointer has no effect
@@ -1600,8 +1611,8 @@ int multi_oo_unpack_client_data(net_player* pl, ubyte* data, bool keep_data)
int offset = 0;
// read flag info
ushort in_flags;
// read flag info -- this is packed as a single byte, so it must be read back as one
ubyte in_flags;
memcpy(&in_flags, data, sizeof(ubyte));
offset++;
@@ -1895,7 +1906,7 @@ int multi_oo_unpack_data(net_player* pl, ubyte* data, int seq_num, int time_delt
full_physics = true;
}
int r5 = multi_pack_unpack_desired_vel_and_desired_rotvel(0, full_physics, data + offset, &pobjp->phys_info, &local_desired_vel);
int r5 = multi_pack_unpack_desired_vel_and_desired_rotvel(0, full_physics, data + offset, &new_phys_info, &local_desired_vel);
offset += r5;
// change it back to global coordinates.
vm_vec_unrotate(&new_phys_info.desired_vel, &local_desired_vel, &new_orient);
+15 -6
View File
@@ -2646,7 +2646,7 @@ void process_ship_kill_packet( ubyte *data, header *hinfo )
}
// maybe set wash_killed
if (extra_death_info & EXTRA_DEATH_VAPORIZED) {
if (extra_death_info & EXTRA_DEATH_WASHED) {
Ships[sobjp->instance].wash_killed = 1;
}
@@ -3470,7 +3470,7 @@ void process_turret_fired_packet( ubyte *data, header *hinfo )
multi_set_network_signature( wnet_signature, MULTI_SIG_NON_PERMANENT );
}
weapon_objnum = weapon_create( &pos, &orient, wid, OBJ_INDEX(objp), -1, true, false, 0.0f, ssp, launch_curve_data);
weapon_objnum = weapon_create( &pos, &orient, wid, OBJ_INDEX(objp), -1, true, false, nullptr, -1, -1, launch_curve_data);
if (weapon_objnum != -1) {
if ( Weapon_info[wid].launch_snd.isValid() ) {
@@ -7630,7 +7630,8 @@ void process_homing_weapon_info( ubyte *data, header *hinfo )
}
if (flags & HWIF_BIG_UPDATE) {
wp->creation_time = Missiontime + missile_lifetime;
// the sender packed the missile's age, so walk creation_time back from now to recover it
wp->creation_time = Missiontime - missile_lifetime;
weapon_objp->pos = missile_pos;
weapon_objp->orient = orient_in;
wp->launch_speed = launch_speed;
@@ -8373,6 +8374,12 @@ void process_beam_fired_packet(ubyte *data, header *hinfo)
fire_info.target = multi_get_network_object(target_sig);
fire_info.burst_index = 0;
// the weapon class arrives over the network, so validate it before using it to index Weapon_info
if (!Weapon_info.in_bounds(fire_info.beam_info_index)) {
nprintf(("Network", "Received invalid weapon class %d for BEAM weapon!\n", fire_info.beam_info_index));
return;
}
if ( fire_info.target && (target_subsys_index >= 0) ) {
ship *targetp = &Ships[fire_info.target->instance];
fire_info.target_subsys = ship_get_indexed_subsys(targetp, target_subsys_index);
@@ -8385,12 +8392,14 @@ void process_beam_fired_packet(ubyte *data, header *hinfo)
Assertion(bank >= 0, "Fighter BEAM bank is invalid!");
Assertion(point >= 0, "Fighter BEAM point is invalid!");
if ( (bank < 0) || (point < 0) ) {
polymodel *pm = model_get( Ship_info[shipp->ship_info_index].model_num );
// the bank and point also arrive over the network, so validate them against the model
if ( (bank < 0) || (bank >= pm->n_guns) || (point < 0) || (point >= pm->gun_banks[bank].num_slots) ) {
nprintf(("Network", "Couldn't get firing point for fighter BEAM weapon!\n"));
return;
}
polymodel *pm = model_get( Ship_info[shipp->ship_info_index].model_num );
float field_of_fire = Weapon_info[fire_info.beam_info_index].field_of_fire;
fire_info.local_fire_postion = pm->gun_banks[bank].pnt[point];
@@ -8765,7 +8774,7 @@ void process_flak_fired_packet(ubyte *data, header *hinfo)
};
// create the weapon object
weapon_objnum = weapon_create( &pos, &orient, wid, OBJ_INDEX(objp), -1, true, false, 0.0f, ssp, launch_curve_data);
weapon_objnum = weapon_create( &pos, &orient, wid, OBJ_INDEX(objp), -1, true, false, nullptr, -1, -1, launch_curve_data);
if (weapon_objnum != -1) {
const weapon_info& wip = Weapon_info[wid];
if ( wip.launch_snd.isValid() ) {
+3 -3
View File
@@ -3625,9 +3625,9 @@ int multi_pack_unpack_desired_vel_and_desired_rotvel( int write, bool full_physi
a = bitbuffer_get_signed(&buf,5);
b = bitbuffer_get_signed(&buf,5);
c = bitbuffer_get_signed(&buf,5);
pi->rotvel.xyz.x = pi->max_rotvel.xyz.x * i2fl(a)/15.0f;
pi->rotvel.xyz.y = pi->max_rotvel.xyz.y * i2fl(b)/15.0f;
pi->rotvel.xyz.z = pi->max_rotvel.xyz.z * i2fl(c)/15.0f;
pi->desired_rotvel.xyz.x = pi->max_rotvel.xyz.x * i2fl(a)/15.0f;
pi->desired_rotvel.xyz.y = pi->max_rotvel.xyz.y * i2fl(b)/15.0f;
pi->desired_rotvel.xyz.z = pi->max_rotvel.xyz.z * i2fl(c)/15.0f;
}
+1 -1
View File
@@ -553,7 +553,7 @@ int collide_asteroid_prop(obj_pair* pair)
asteroid_hit_info.light = asteroid_objp;
}
hit = prop_check_collision(prop_objp, prop_objp, &hitpos, &asteroid_hit_info);
hit = prop_check_collision(prop_objp, asteroid_objp, &hitpos, &asteroid_hit_info);
if (hit)
{
bool ship_override = false, asteroid_override = false;
+9 -6
View File
@@ -947,22 +947,25 @@ int collide_prop_weapon(obj_pair* pair)
/**
* Upper limit estimate ship speed at end of time
*/
static float estimate_ship_speed_upper_limit( object *ship, float time )
static float estimate_ship_speed_upper_limit( object *objp, float time )
{
float exponent;
float delta_v;
float factor;
delta_v = Ship_info[Ships[ship->instance].ship_info_index].max_vel.xyz.z - ship->phys_info.speed;
if (objp->type != OBJ_SHIP)
return objp->phys_info.speed;
delta_v = Ship_info[Ships[objp->instance].ship_info_index].max_vel.xyz.z - objp->phys_info.speed;
if (ship->phys_info.forward_accel_time_const == 0) {
return ship->phys_info.speed;
if (objp->phys_info.forward_accel_time_const == 0) {
return objp->phys_info.speed;
}
exponent = time / ship->phys_info.forward_accel_time_const;
exponent = time / objp->phys_info.forward_accel_time_const;
factor = 1.0f - (float)exp( -exponent );
return ship->phys_info.speed + factor*delta_v;
return objp->phys_info.speed + factor*delta_v;
}
// maximum error allowed in detecting collisions between laser and big ship inside the radius
+1 -1
View File
@@ -1100,7 +1100,7 @@ void obj_collide_pair(object *A, object *B)
}
// for nonplayer ships, only create collision pair if close enough
if ( (B->parent >= 0) && !((Objects[B->parent].signature == B->parent_sig) && (Objects[B->parent].flags[Object::Object_Flags::Player_ship])) && (vm_vec_dist(&B->pos, &A->pos) < (4.0f*A->radius + 200.0f)) ) {
if ( (B->parent >= 0) && ((Objects[B->parent].signature != B->parent_sig) || !(Objects[B->parent].flags[Object::Object_Flags::Player_ship])) && (vm_vec_dist_squared(&B->pos, &A->pos) < (4.0f*A->radius + 200.0f) * (4.0f*A->radius + 200.0f)) ) {
collision_info->next_check_time = -1;
return;
}
+13 -10
View File
@@ -142,10 +142,13 @@ const char *Object_type_names[MAX_OBJECT_TYPES] = {
"Asteroid",
"Jump Node",
"Beam",
"Raw Pof"
"Raw Pof",
"Prop"
//XSTR:ON
};
static_assert(MAX_OBJECT_TYPES == OBJ_PROP + 1, "Object_type_names needs an entry for every object type!");
obj_flag_name Object_flag_names[] = {
{ Object::Object_Flags::Invulnerable, "invulnerable", },
{ Object::Object_Flags::Protected, "protect-ship", },
@@ -1412,7 +1415,9 @@ void obj_move_all_post(object *objp, float frametime)
//Check for changing team colors
ship* shipp = &Ships[objp->instance];
if (Ship_info[shipp->ship_info_index].uses_team_colors && stricmp(shipp->secondary_team_name.c_str(), "none") != 0) {
// team_change_time is nonzero only while a fade is in progress (see sexp_change_team_color),
// so use that as the initial short-circuit check before the string match
if (shipp->team_change_time != 0 && Ship_info[shipp->ship_info_index].uses_team_colors && stricmp(shipp->secondary_team_name.c_str(), "none") != 0) {
if (f2fl(Missiontime) * 1000 > f2fl(shipp->team_change_timestamp) * 1000 + shipp->team_change_time) {
shipp->team_name = shipp->secondary_team_name;
shipp->team_change_timestamp = 0;
@@ -1702,7 +1707,7 @@ void obj_move_all(float frametime)
obj_move_all_post(objp, frametime);
// Equipment script processing
if (objp->type == OBJ_SHIP) {
if (objp->type == OBJ_SHIP && scripting::hooks::OnWeaponEquipped->isActive()) {
ship* shipp = &Ships[objp->instance];
object* target;
@@ -1713,13 +1718,11 @@ void obj_move_all(float frametime)
if (objp == Player_obj && Player_ai->target_objnum != -1)
target = &Objects[Player_ai->target_objnum];
if (scripting::hooks::OnWeaponEquipped->isActive()) {
scripting::hooks::OnWeaponEquipped->run(scripting::hooks::WeaponEquippedConditions{ shipp, target },
scripting::hook_param_list(
scripting::hook_param("User", 'o', objp),
scripting::hook_param("Target", 'o', target)
));
}
scripting::hooks::OnWeaponEquipped->run(scripting::hooks::WeaponEquippedConditions{ shipp, target },
scripting::hook_param_list(
scripting::hook_param("User", 'o', objp),
scripting::hook_param("Target", 'o', target)
));
}
}
+2 -2
View File
@@ -54,7 +54,7 @@
#define OBJ_PROP 17 // A prop object like a landscape. Similar to ships but is entirely non-interactable with the exception of collisions
//Make sure to change Object_type_names in Object.c when adding another type!
#define MAX_OBJECT_TYPES 17
#define MAX_OBJECT_TYPES 18
#define UNUSED_OBJNUM (-MAX_OBJECTS*2) // Newer systems use this instead of -1 for invalid object.
@@ -250,7 +250,7 @@ extern object *Player_obj; // Which object is the player. Has to be valid.
// given it's pointer. This way, we can replace it with a macro
// to check that the pointer is valid for debugging.
// This code will break in 64 bit builds when we have more than 2^31 objects but that will probably never happen
#define OBJ_INDEX(objp) static_cast<int>(objp-Objects)
#define OBJ_INDEX(objp) (static_cast<int>((objp)-Objects))
/*
* FUNCTIONS
+73
View File
@@ -2988,6 +2988,79 @@ int stuff_long(long *l, bool optional)
return retval;
}
// Stuff an unsigned 64-bit value pointed at by Mp.
// Advances past integer characters.
int stuff_uint64(std::uint64_t *l, bool optional)
{
char *str_start = Mp;
// since strtoull ignores white space anyway, might as well make it explicit
ignore_white_space();
// this is a bit cumbersome
size_t span;
if (*Mp == '+')
{
span = strspn(Mp + 1, "0123456789");
// account for the sign symbol, but not if it's the only valid character
if (span > 0)
++span;
}
else
span = strspn(Mp, "0123456789");
// don't call strtoull unless we found digits, because it will happily parse
// (and wrap around) a negative number
std::uint64_t result = (span > 0) ? strtoull(Mp, nullptr, 10) : 0;
bool success = false, comma = false;
int retval = 0;
// no number found?
if (span == 0)
{
if (!optional)
error_display(1, "Expected unsigned integer, found [%.32s].\n", next_tokens());
}
else
{
*l = result;
success = true;
}
if (success)
Mp += span;
// if an unexpected character is part of the number, warn about it
if (success && unexpected_numeric_char(*Mp))
{
error_display(0, "Expected unsigned integer, found [%.32s].\n", next_tokens(true));
// Rather than back up to str_start, do what retail did and continue
// merrily parsing along at the next character. (Optional numbers
// will still back up to str_start - c.f. a few lines down.)
if (optional)
success = false;
}
if (check_first_non_grayspace_char(Mp, ',', &Mp))
comma = true;
if (optional && !success)
Mp = str_start;
if (success)
{
retval = 2;
diag_printf("Stuffed uint64: " UINT64_T_ARG "\n", *l);
}
else if (optional)
retval = comma ? 1 : 0;
else
skip_token();
return retval;
}
int stuff_float_optional(float *f)
{
return stuff_float(f, true);
+7 -9
View File
@@ -164,6 +164,7 @@ extern bool check_first_non_grayspace_char(const char *str, char ch_to_look_for,
extern int stuff_float(float *f, bool optional = false);
extern int stuff_int(int *i, bool optional = false);
extern int stuff_long(long *l, bool optional = false);
extern int stuff_uint64(std::uint64_t *l, bool optional = false);
extern void stuff_ubyte(ubyte *i);
extern int stuff_int_optional(int *i);
extern int stuff_float_optional(float *f);
@@ -246,17 +247,14 @@ void parse_string_flag_list_special(Flagset& dest, const special_flag_def_list_n
}
template<class T>
void stuff_flagset(T *dest) {
long l = 0;
stuff_long(&l);
void stuff_flagset(T *dest)
{
std::uint64_t val = 0;
stuff_uint64(&val);
if (l < 0) {
error_display(0, "Expected flagset value but got negative value %lu!\n", l);
l = 0;
}
dest->from_u64((std::uint64_t) l);
dest->from_u64(val);
diag_printf("Stuffed flagset: %" PRIu64 "\n", dest->to_u64());
diag_printf("Stuffed flagset: " UINT64_T_ARG "\n", dest->to_u64());
}
extern size_t stuff_int_list(int *ilp, size_t max_ints, ParseLookupType lookup_type = ParseLookupType::RAW_INTEGER_TYPE, bool warn_on_lookup_failure = true);
+72 -16
View File
@@ -1989,17 +1989,18 @@ int query_sexp_args_count(int node, bool only_valid_args = false)
return count;
}
enum class ArgCountCheck { CORRECT, INCORRECT_BENIGN, INCORRECT_FATAL };
/**
* Needed to fix bug with sexps like send-message list which have arguments that need to be supplied as a block
*
* @return whether the number of arguments for the supplied operation is correct
*/
static bool check_operator_argument_count(int count, int op_index)
static ArgCountCheck check_operator_argument_count(int count, int op_index)
{
Assertion(op_index >= 0 && op_index < sz2i(Operators.size()), "op_index is out of range!");
if (count < Operators[op_index].min || count > Operators[op_index].max)
return false;
return ArgCountCheck::INCORRECT_FATAL;
int op_const = Operators[op_index].value;
@@ -2011,9 +2012,9 @@ static bool check_operator_argument_count(int count, int op_index)
if (op_const == OP_SEND_MESSAGE_LIST || op_const == OP_SEND_MESSAGE_CHAIN)
if (count % 4 != 0)
return false;
return ArgCountCheck::INCORRECT_BENIGN; // historically, this check didn't work at all, and sexps gracefully recovered at runtime
return true;
return ArgCountCheck::CORRECT;
}
// helper functions for check_container_value_data_type()
@@ -2159,6 +2160,7 @@ int check_sexp_syntax(int node, int desired_return_type, int recursive, int *bad
int var_index = -1;
size_t st;
const sexp_container *p_container = nullptr; // for SEXPs that take container name as arg
int deferred_error = SEXP_CHECK_NO_ERROR, deferred_bad_node = -1; // for recoverable errors, so that the rest of the tree is still checked
Assertion(node >= 0 && node < Num_sexp_nodes, "Node %d must be a valid SEXP node!", node);
Assertion(Sexp_nodes[node].type != SEXP_NOT_USED, "Node %d must be in use!", node);
@@ -2207,8 +2209,15 @@ int check_sexp_syntax(int node, int desired_return_type, int recursive, int *bad
count = query_sexp_args_count(op_node);
if (!check_operator_argument_count(sz2i(count), op_index))
return SEXP_CHECK_BAD_ARG_COUNT; // incorrect number of arguments
auto arg_count_result = check_operator_argument_count(sz2i(count), op_index);
if (arg_count_result == ArgCountCheck::INCORRECT_FATAL)
return SEXP_CHECK_BAD_ARG_COUNT; // incorrect number of arguments
else if (arg_count_result == ArgCountCheck::INCORRECT_BENIGN)
{
// incorrect, but defer it and continue checking
deferred_error = SEXP_CHECK_BAD_ARG_COUNT_BENIGN;
deferred_bad_node = op_node;
}
node = Sexp_nodes[op_node].rest;
while (node != -1) {
@@ -2227,7 +2236,7 @@ int check_sexp_syntax(int node, int desired_return_type, int recursive, int *bad
// thing. (i.e. in the case of a cond statement, the conditional will fall into this if
// statement. MORE TO DO HERE!!!!
if (Sexp_nodes[i].subtype == SEXP_ATOM_LIST)
return 0;
break;
int op2_index = get_operator_index(i);
int op2_const = SCP_vector_inbounds(Operators, op2_index) ? Operators[op2_index].value : OP_NOT_AN_OP;
@@ -2242,10 +2251,19 @@ int check_sexp_syntax(int node, int desired_return_type, int recursive, int *bad
}
if ((z = check_sexp_syntax(i, (int)opr, recursive, bad_node)) != 0) {
return z;
if (!sexp_recoverable_error(z))
return z;
// defer recoverable errors so that the rest of the tree is still checked
if (deferred_error == SEXP_CHECK_NO_ERROR) {
deferred_error = z;
deferred_bad_node = bad_node ? *bad_node : -1;
}
}
}
} else if (node_subtype == SEXP_ATOM_OPERATOR) {
return SEXP_CHECK_DATA_EXPECTED; // operators should not be found here
} else if (node_subtype == SEXP_ATOM_NUMBER) {
node_return_type = OPR_POSITIVE;
auto ptr = CTEXT(node);
@@ -2376,9 +2394,8 @@ int check_sexp_syntax(int node, int desired_return_type, int recursive, int *bad
continue;
} else {
UNREACHABLE("SEXP subtype is %d when it should be SEXP_ATOM_LIST, SEXP_ATOM_NUMBER, SEXP_ATOM_STRING, "
"SEXP_ATOM_CONTAINER_NAME, or "
"SEXP_ATOM_CONTAINER_DATA!",
UNREACHABLE("SEXP subtype is %d when it should be SEXP_ATOM_LIST, SEXP_ATOM_OPERATOR, SEXP_ATOM_NUMBER, "
"SEXP_ATOM_STRING, SEXP_ATOM_CONTAINER_NAME, or SEXP_ATOM_CONTAINER_DATA!",
node_subtype);
}
@@ -3153,7 +3170,13 @@ int check_sexp_syntax(int node, int desired_return_type, int recursive, int *bad
// we should check the syntax of the actual goal!!!!
z = Sexp_nodes[node].first;
if ((z = check_sexp_syntax(z, OPR_AI_GOAL, recursive, bad_node)) != 0){
return z;
if (!sexp_recoverable_error(z))
return z;
// defer recoverable errors so that the rest of the tree is still checked
if (deferred_error == SEXP_CHECK_NO_ERROR) {
deferred_error = z;
deferred_bad_node = bad_node ? *bad_node : -1;
}
}
if (Fred_running) {
@@ -4209,6 +4232,14 @@ int check_sexp_syntax(int node, int desired_return_type, int recursive, int *bad
argnum++;
}
// now that the rest of the tree has been checked, report any recoverable error that was noted along the way
if (deferred_error != SEXP_CHECK_NO_ERROR)
{
if (bad_node)
*bad_node = deferred_bad_node;
return deferred_error;
}
return 0;
}
@@ -5813,7 +5844,15 @@ const ship_registry_entry *eval_ship(int node)
return eval_ship(arg_node);
}
auto ship_it = Ship_registry_map.find(CTEXT(node));
// look up the ship in the ship registry
auto ship_name = CTEXT(node);
auto ship_it = Ship_registry_map.find(ship_name);
if (ship_it == Ship_registry_map.end())
{
SCP_string legacy_hashed;
if (wing_bash_legacy_hashed_ship_name(legacy_hashed, ship_name))
ship_it = Ship_registry_map.find(legacy_hashed);
}
if (ship_it != Ship_registry_map.end())
{
// cache the value if it can't change later
@@ -10341,6 +10380,11 @@ int sexp_percent_ships_arrive_depart_destroy_disarm_disable_scan(int n, int what
}
}
// if there is nothing to check, the percentage is meaningless; this can happen if, for example, a wing
// arrives from a docking bay and its mothership is destroyed before the wing has a chance to arrive
if ( total <= 0 )
return SEXP_FALSE;
// now, look at the percentage
if ( ((count * 100) / total) >= percent )
return SEXP_KNOWN_TRUE;
@@ -12808,7 +12852,7 @@ int sexp_is_iff_or_species(int n, bool iff)
// ship is in the EXITED state but probably in the process of exploding
else if (oswpt.has_shipp())
{
UNREACHABLE("With the addition of the ShipStatus::DEATH_ROLL state, this shouldn't happen");
Assertion(false, "With the addition of the ShipStatus::DEATH_ROLL state, this shouldn't happen");
return SEXP_KNOWN_FALSE;
}
// ship has vanished
@@ -13966,8 +14010,9 @@ void sexp_set_player_target(int node)
new_subsys = ship_get_subsys(shipp, subsys_name);
}
}
set_target_objnum(Player_ai, objnum);
set_targeted_subsys(Player_ai, new_subsys, objnum);
set_target_objnum(Player_ai, objnum);
set_targeted_subsys(Player_ai, new_subsys, new_subsys ? objnum : -1);
shipp->last_targeted_subobject[Player_num] = new_subsys;
}
// Luytenky
@@ -22264,6 +22309,7 @@ void sexp_beam_fire(int node, bool at_coords)
// store the weapon info index
if (Weapon_info[fire_info.turret->weapons.primary_bank_weapons[idx]].wi_flags[Weapon::Info_Flags::Beam]) {
fire_info.beam_info_index = fire_info.turret->weapons.primary_bank_weapons[idx];
fire_info.bank = idx;
}
}
@@ -35347,11 +35393,13 @@ bool sexp_recoverable_error(int num)
// but the mission will run without crashing.
case SEXP_CHECK_AMBIGUOUS_EVENT_NAME:
case SEXP_CHECK_AMBIGUOUS_GOAL_NAME:
return true;
// Having an invalid gauge in FSO won't hurt,
// as all places which call hud_get_gauge() or hud_get_custom_gauge() check its return value for NULL.
case SEXP_CHECK_INVALID_CUSTOM_HUD_GAUGE:
case SEXP_CHECK_INVALID_ANY_HUD_GAUGE:
return true;
// Trying to set an invalid sound environment has no effect, and all sound enviroments are invalid if EFX is disabled.
// Invalid sound environment options are simiarly harmless.
@@ -35359,6 +35407,10 @@ bool sexp_recoverable_error(int num)
case SEXP_CHECK_INVALID_SOUND_ENVIRONMENT_OPTION:
return true;
// Certain argument counts historically weren't checked properly, but the runtime code could still recover
case SEXP_CHECK_BAD_ARG_COUNT_BENIGN:
return true;
// most errors will halt mission loading
default:
return false;
@@ -35374,6 +35426,9 @@ const char *sexp_error_message(int num)
case SEXP_CHECK_OP_EXPECTED:
return "Operator expected instead of data";
case SEXP_CHECK_DATA_EXPECTED:
return "Data expected instead of operator";
case SEXP_CHECK_UNKNOWN_OP:
return "Unrecognized operator";
@@ -35381,6 +35436,7 @@ const char *sexp_error_message(int num)
return "Argument type mismatch";
case SEXP_CHECK_BAD_ARG_COUNT:
case SEXP_CHECK_BAD_ARG_COUNT_BENIGN:
return "Argument count is illegal";
case SEXP_CHECK_UNKNOWN_TYPE:
+3 -1
View File
@@ -1203,9 +1203,11 @@ enum sexp_error_check
SEXP_CHECK_NONOP_ARGS, // non-operator has arguments
SEXP_CHECK_OP_EXPECTED, // operator expected, but found data instead
SEXP_CHECK_DATA_EXPECTED, // data expected, but found operator instead
SEXP_CHECK_UNKNOWN_OP, // unrecognized operator
SEXP_CHECK_TYPE_MISMATCH, // return type or data type mismatch
SEXP_CHECK_BAD_ARG_COUNT, // argument count in incorrect
SEXP_CHECK_BAD_ARG_COUNT, // argument count is incorrect
SEXP_CHECK_BAD_ARG_COUNT_BENIGN, // ditto, but don't prevent the mission from loading
SEXP_CHECK_UNKNOWN_TYPE, // unrecognized return type of data type
SEXP_CHECK_INVALID_NUM = 101, // number is not valid
-7
View File
@@ -154,13 +154,6 @@ void LuaAISEXP::parseTable() {
// Now get a list of all lua categories to add. Meow.
SCP_vector<SCP_string> lua_cat_list = ai_lua_get_general_order_categories();
// If we have too many categories then we have an oopsie!
if ((int)lua_cat_list.size() > (MAX_MENU_ITEMS - NUM_COMM_ORDER_TYPES)) {
Warning(LOCATION, "Too many defined Lua General Order Categories! Setting order to first available: %s", lua_cat_list[0].c_str());
order.category = lua_cat_list[0];
}
// General orders explicitely do not show up in FRED as sexps because they
// they are only meant for the comms board
_return_type = OPR_NONE;
+9
View File
@@ -243,6 +243,15 @@ namespace internal {
SCP_vector<int> handles;
for (auto const &name: bitmap_strings) {
// FRED never renders or pages in particle effects,
// so skip the (potentially expensive) animation loading here.
// Still record placeholder handle so the effect stays "defined" and
// can be resolved by name from the weapons/ships tables without spurious errors.
if (Fred_running) {
handles.push_back(0);
continue;
}
auto handle = bm_load_animation(name.c_str());
if (handle >= 0) {
handles.push_back(handle);
+22 -3
View File
@@ -417,6 +417,15 @@ void do_view_track_target()
chase_slew_angles.h = forward_angles.h - view_angles.h;
chase_slew_angles.p = -(forward_angles.p - view_angles.p);
// Do over-the-top correction.
// Headings are extracted with atan2, so each one lies in (-PI, PI] and their difference can be
// nearly a full circle in either direction. Without wrapping it back into (-PI, PI], a target just
// past the left shoulder reads as being almost all the way around to the right instead.
if (chase_slew_angles.h > PI)
chase_slew_angles.h -= PI2;
else if (chase_slew_angles.h < -PI)
chase_slew_angles.h += PI2;
// the gimbal limits of the player's virtual neck.
// These nested ifs prevent the player from looking up and
// down beyond 90 degree angles.
@@ -1420,7 +1429,6 @@ void player_restore_target_and_weapon_link_prefs()
{
ship_info *player_sip;
player_sip = &Ship_info[Player_ship->ship_info_index];
polymodel *pm = model_get(player_sip->model_num);
// Don't restores the save flags in training, as we must ensure certain things are off, such as speed matching.
if ( !(The_mission.game_type & MISSION_TYPE_TRAINING )) {
@@ -1433,7 +1441,9 @@ void player_restore_target_and_weapon_link_prefs()
}
}
if ( Player->flags & PLAYER_FLAGS_LINK_SECONDARY && (pm->n_missiles > 0 && pm->missile_banks[0].num_slots > 1) ) {
// restore the dual fire preference regardless of whether the current bank can use it;
// the flag is ignored rather than cleared for banks that can't
if ( Player->flags & PLAYER_FLAGS_LINK_SECONDARY ) {
Player_ship->flags.set(Ship::Ship_Flags::Secondary_dual_fire);
}
}
@@ -1449,6 +1459,8 @@ void player_level_init()
memset(&(Player->ci), 0, sizeof(control_info) ); // set the controls to 0
Viewer_slew_angles.p = 0.0f; Viewer_slew_angles.b = 0.0f; Viewer_slew_angles.h = 0.0f;
reset_angles(&chase_slew_angles);
reset_angles(&Viewer_slew_angles_delta);
Viewer_external_info.angles.p = 0.0f;
Viewer_external_info.angles.b = 0.0f;
Viewer_external_info.angles.h = 0.0f;
@@ -1467,7 +1479,10 @@ void player_level_init()
{
Viewer_mode = 0;
}
Perspective_locked = false;
Slew_locked = false;
Player_obj = NULL;
Player_ship = NULL;
Player_ai = NULL;
@@ -2079,6 +2094,10 @@ void player_generate_death_message(player *player_p)
sprintf(msg, XSTR( "%s was killed by a collision with an asteroid", 98), player_p->callsign);
break;
case OBJ_PROP:
sprintf(msg, XSTR( "%s was killed by a collision with an object", -1), player_p->callsign);
break;
case OBJ_BEAM:
if (strlen(player_p->killer_parent_name) <= 0)
{
+3 -6
View File
@@ -473,9 +473,8 @@ int prop_create(const matrix* orient, const vec3d* pos, int prop_type, const cha
Error(LOCATION, "Cannot create prop %s; pof file is not valid", pip->name.c_str());
return -1;
}
if (pip->model_num == -1) {
pip->model_num = model_load(pip->pof_file.c_str());
}
pip->model_num = model_load(pip->pof_file.c_str());
polymodel* pm = model_get(pip->model_num);
@@ -613,9 +612,7 @@ static void prop_model_change(int n, int prop_type)
//polymodel_instance* pmi = model_get_instance(sp->model_instance_num);
// get new model
if (sip->model_num == -1) {
sip->model_num = model_load(sip->pof_file.c_str());
}
sip->model_num = model_load(sip->pof_file.c_str());
polymodel* pm = model_get(sip->model_num);
Objects[sp->objnum].radius = model_get_radius(pm->id);
+10 -11
View File
@@ -290,12 +290,12 @@ void radar_plot_object( object *objp )
vm_vec_sub(&tempv, &world_pos, &Player_obj->pos);
vm_vec_rotate(&pos, &tempv, &eye_orient);
// Apply range filter
dist = vm_vec_dist(&world_pos, &Player_obj->pos);
// Apply range filter (squared-distance cull first so the sqrt only runs for blips that survive)
max_radar_dist = Radar_ranges[HUD_config.rp_dist];
if (dist > max_radar_dist) {
float dist_sq = vm_vec_mag_squared(&tempv);
if (dist_sq > max_radar_dist * max_radar_dist)
return;
}
dist = sqrtf(dist_sq);
// Mine range-based visibility: sensors_range is the outer detection envelope. The parser
// guarantees sensors_range >= targetable_range, so anything within targetable range is
@@ -572,7 +572,7 @@ RadarVisibility radar_is_visible( object *objp )
return NOT_VISIBLE;
vec3d pos, tempv;
float awacs_level, dist, max_radar_dist;
float awacs_level;
vec3d world_pos = objp->pos;
// get team-wide awacs level for the object if not ship
@@ -651,13 +651,12 @@ RadarVisibility radar_is_visible( object *objp )
vm_vec_sub(&tempv, &world_pos, &Player_obj->pos);
vm_vec_rotate(&pos, &tempv, &Player_obj->orient);
// Apply range filter
dist = vm_vec_dist(&world_pos, &Player_obj->pos);
max_radar_dist = Radar_ranges[HUD_config.rp_dist];
if (dist > max_radar_dist) {
// Apply range filter (squared-distance cull first so the sqrt only runs for blips that survive)
float max_radar_dist = Radar_ranges[HUD_config.rp_dist];
float dist_sq = vm_vec_mag_squared(&tempv);
if (dist_sq > max_radar_dist * max_radar_dist)
return NOT_VISIBLE;
}
if (objp->type == OBJ_SHIP)
{
// ships specifically hidden from sensors
+23 -2
View File
@@ -13,6 +13,7 @@
#include "scpui/rocket_ui.h"
#include "cfile/cfile.h"
#include "io/mouse.h"
#include "mod_table/mod_table.h"
#include "osapi/osapi.h"
#include "scpui/IncludeNodeHandler.h"
@@ -59,6 +60,26 @@ vec2d render_offset = {0.f, 0.f};
bool debugger_initialized = false;
/**
* @brief Retrieves the current cursor position in render-target pixels
*
* @details SDL reports event coordinates in window pixels, but a libRocket context is sized from
* gr_screen.max_w/max_h and renders through gr_set_2d_matrix, which is also render space. Rather
* than duplicating the conversion here we let mouse_get_real_pos do it.
*
* Note that this is the cursor position as of right now rather than the one carried by the event,
* so if several motion events are queued in a single os_poll libRocket sees the latest position
* for all of them. That is what it wants for hover state anyway.
*/
vec2d get_render_space_mouse_pos()
{
int x = 0;
int y = 0;
mouse_get_real_pos(&x, &y);
return {i2fl(x), i2fl(y)};
}
bool transform_to_rocket(vec2d& in)
{
in = {in.x - render_offset.x, in.y - render_offset.y};
@@ -145,7 +166,7 @@ bool mouse_motion_handler(const SDL_Event& evt)
return false;
}
vec2d evt_pos = {(float)evt.motion.x, (float)evt.motion.y};
vec2d evt_pos = get_render_space_mouse_pos();
if (!transform_to_rocket(evt_pos)) {
// Out of bounds
return false;
@@ -168,7 +189,7 @@ bool mouse_button_handler(const SDL_Event& evt)
return false;
}
vec2d evt_pos = {(float)evt.button.x, (float)evt.button.y};
vec2d evt_pos = get_render_space_mouse_pos();
if (!transform_to_rocket(evt_pos)) {
// Out of bounds
return false;
+2 -2
View File
@@ -343,7 +343,7 @@ ADE_INDEXER(l_Mission_Comm_Items,
// convert from lua index
idx--;
if ((idx < 0) || idx >= MAX_MENU_ITEMS)
if (idx < 0)
return ade_set_args(L, "o", l_Comm_Item.Set(-1));
return ade_set_args(L, "o", l_Comm_Item.Set(idx));
@@ -356,7 +356,7 @@ ADE_FUNC(__len,
"number",
"Number of comm orders in the mission. 0 if comm menu is closed")
{
return ade_set_args(L, "i", Num_menu_items);
return ade_set_args(L, "i", sz2i(MsgItems.size()));
}
//****SUBLIBRARY: Mission/Debris
+8 -5
View File
@@ -17,8 +17,11 @@ ADE_VIRTVAR(Name, l_Comm_Item, nullptr, "The name of the comm item", "string", "
if (ADE_SETTING_VAR) {
LuaError(L, "This property is read only.");
}
return ade_set_args(L, "s", MsgItems[current].text.c_str());
SCP_string message = "Invalid comm item!";
if (MsgItems.in_bounds(current)) {
message = MsgItems[current].text;
}
return ade_set_args(L, "s", message.c_str());
}
ADE_VIRTVAR(Active, l_Comm_Item, nullptr, "Whether or not the item is active", "boolean", "The active status")
@@ -32,7 +35,7 @@ ADE_VIRTVAR(Active, l_Comm_Item, nullptr, "Whether or not the item is active", "
LuaError(L, "This property is read only.");
}
if (MsgItems[current].active > 0) {
if (MsgItems.in_bounds(current) && MsgItems[current].active > 0) {
return ADE_RETURN_TRUE;
}
@@ -60,7 +63,7 @@ ADE_FUNC(selectItem, l_Comm_Item, nullptr, "Selects the item and either proceeds
if (!ade_get_args(L, "o", l_Comm_Item.Get(&current)))
return ADE_RETURN_FALSE;
if (current < 0 || current >= Num_menu_items) {
if (!MsgItems.in_bounds(current)) {
LuaError(L, "Lua tried to select squad message that is not valid!");
return ADE_RETURN_FALSE;
}
@@ -77,7 +80,7 @@ ADE_FUNC(isValid, l_Comm_Item, nullptr, "Detect if the handle is valid", "boolea
if (!ade_get_args(L, "o", l_Comm_Item.Get(&current)))
return ADE_RETURN_FALSE;
return ade_set_args(L, "b", (current >= 0) && (current < Num_menu_items));
return ade_set_args(L, "b", MsgItems.in_bounds(current));
}
} // namespace scripting::api
+1 -1
View File
@@ -445,7 +445,7 @@ ADE_VIRTVAR(TargetSubsystem, l_Order, "subsystem", "Target subsystem of the orde
set_targeted_subsys(aip, newh->ss, OBJ_INDEX(ss_objp));
}
if (aip == Player_ai) {
Ships[newh->ss->parent_objnum].last_targeted_subobject[Player_num] = newh->ss;
ss_shipp->last_targeted_subobject[Player_num] = newh->ss;
}
}
}
+2 -2
View File
@@ -848,7 +848,7 @@ ADE_VIRTVAR(SecondaryBanks, l_ParseSubsystem, nullptr, "The overridden secondary
luacpp::LuaTable tbl = luacpp::LuaTable::create(L);
auto po = poh->getSubsys();
for (int i = 0; i < MAX_SHIP_PRIMARY_BANKS; ++i) {
for (int i = 0; i < MAX_SHIP_SECONDARY_BANKS; ++i) {
if (po->secondary_banks[i] == -1) {
break;
}
@@ -886,7 +886,7 @@ ADE_VIRTVAR(SecondaryAmmo, l_ParseSubsystem, nullptr, "The overridden secondary
luacpp::LuaTable tbl = luacpp::LuaTable::create(L);
auto po = poh->getSubsys();
for (int i = 0; i < MAX_SHIP_PRIMARY_BANKS; ++i) {
for (int i = 0; i < MAX_SHIP_SECONDARY_BANKS; ++i) {
if (po->secondary_banks[i] == -1) {
break;
}
+21 -24
View File
@@ -784,20 +784,21 @@ ADE_VIRTVAR(Target, l_Ship, "object", "Target of ship. Value may also be a deriv
return ade_set_error(L, "o", l_Object.Set(object_h()));
if(ADE_SETTING_VAR && !(newh && aip->target_signature == newh->sig)) {
// we have a different target, or are clearng the target
// we have a different target, or are clearing the target
if(newh && newh->isValid()) {
aip->target_objnum = newh->objnum;
aip->target_signature = newh->sig;
aip->target_time = 0.0f;
set_targeted_subsys(aip, nullptr, -1);
aip->ok_to_target_timestamp = timestamp(0);
set_target_objnum(aip, newh->objnum);
if (aip == Player_ai) {
// prevent hud_target_change_check() from restoring the wrong targeted subsystem
if (newh->objp()->type == OBJ_SHIP)
Ships[newh->objp()->instance].last_targeted_subobject[Player_num] = nullptr;
if (aip == Player_ai)
hud_shield_hit_reset(newh->objp());
}
} else if (lua_isnil(L, 2)) {
aip->target_objnum = -1;
aip->target_signature = -1;
aip->target_time = 0.0f;
set_targeted_subsys(aip, nullptr, -1);
aip->ok_to_target_timestamp = timestamp(0);
set_target_objnum(aip, -1);
}
}
@@ -825,25 +826,21 @@ ADE_VIRTVAR(TargetSubsystem, l_Ship, "subsystem", "Target subsystem of ship.", "
{
if(newh && newh->isValid())
{
if (aip == Player_ai) {
if (aip->target_signature != newh->objh.sig)
hud_shield_hit_reset(newh->objh.objp());
// this must be checked before the signature is updated in set_target_objnum
if (aip == Player_ai && aip->target_signature != newh->objh.sig)
hud_shield_hit_reset(newh->objh.objp());
aip->ok_to_target_timestamp = timestamp(0);
set_target_objnum(aip, newh->objh.objnum);
set_targeted_subsys(aip, newh->ss, newh->objh.objnum);
if (aip == Player_ai)
Ships[Objects[newh->ss->parent_objnum].instance].last_targeted_subobject[Player_num] = newh->ss;
}
aip->target_objnum = newh->objh.objnum;
aip->target_signature = newh->objh.sig;
aip->target_time = 0.0f;
set_targeted_subsys(aip, newh->ss, aip->target_objnum);
}
else
{
aip->target_objnum = -1;
aip->target_signature = -1;
aip->target_time = 0.0f;
set_targeted_subsys(aip, NULL, -1);
aip->ok_to_target_timestamp = timestamp(0);
set_target_objnum(aip, -1);
}
}
+6 -5
View File
@@ -55,11 +55,12 @@ ADE_INDEXER(l_WeaponBankType, "number Index", "Array of weapon banks", "weaponba
idx--; //Lua->FS2
if(ADE_SETTING_VAR && newbank && newbank->isValid()) {
sb->sw->primary_bank_weapons[idx] = newbank->typeh.sw->primary_bank_weapons[idx];
sb->sw->next_primary_fire_stamp[idx] = timestamp(0);
sb->sw->primary_bank_ammo[idx] = newbank->typeh.sw->primary_bank_ammo[idx];
sb->sw->primary_bank_start_ammo[idx] = newbank->typeh.sw->primary_bank_start_ammo[idx];
sb->sw->primary_bank_capacity[idx] = newbank->typeh.sw->primary_bank_capacity[idx];
sb->sw->secondary_bank_weapons[idx] = newbank->typeh.sw->secondary_bank_weapons[idx];
sb->sw->next_secondary_fire_stamp[idx] = timestamp(0);
sb->sw->secondary_bank_ammo[idx] = newbank->typeh.sw->secondary_bank_ammo[idx];
sb->sw->secondary_bank_start_ammo[idx] = newbank->typeh.sw->secondary_bank_start_ammo[idx];
sb->sw->secondary_bank_capacity[idx] = newbank->typeh.sw->secondary_bank_capacity[idx];
sb->sw->secondary_bank_rearm_time[idx] = timestamp(0);
}
break;
case SWH_TERTIARY:
+156 -35
View File
@@ -238,6 +238,15 @@ int ship_registry_get_index(const char *name)
if (ship_it != Ship_registry_map.end())
return ship_it->second;
// also search for ship names hashed using the legacy format
SCP_string legacy_hashed;
if (wing_bash_legacy_hashed_ship_name(legacy_hashed, name))
{
ship_it = Ship_registry_map.find(legacy_hashed);
if (ship_it != Ship_registry_map.end())
return ship_it->second;
}
return -1;
}
@@ -247,6 +256,15 @@ int ship_registry_get_index(const SCP_string &name)
if (ship_it != Ship_registry_map.end())
return ship_it->second;
// also search for ship names hashed using the legacy format
SCP_string legacy_hashed;
if (wing_bash_legacy_hashed_ship_name(legacy_hashed, name.c_str()))
{
ship_it = Ship_registry_map.find(legacy_hashed);
if (ship_it != Ship_registry_map.end())
return ship_it->second;
}
return -1;
}
@@ -267,18 +285,18 @@ bool ship_registry_exists(int index)
const ship_registry_entry *ship_registry_get(const char *name)
{
auto ship_it = Ship_registry_map.find(name);
if (ship_it != Ship_registry_map.end())
return &Ship_registry[ship_it->second];
auto idx = ship_registry_get_index(name);
if (idx >= 0)
return &Ship_registry[idx];
return nullptr;
}
const ship_registry_entry *ship_registry_get(const SCP_string &name)
{
auto ship_it = Ship_registry_map.find(name);
if (ship_it != Ship_registry_map.end())
return &Ship_registry[ship_it->second];
auto idx = ship_registry_get_index(name);
if (idx >= 0)
return &Ship_registry[idx];
return nullptr;
}
@@ -297,6 +315,27 @@ const ship_registry_entry *ship_registry_get(anchor_t anchor)
return ship_registry_get(anchor.value());
}
void ship_registry_rename(int entry_index, const char *new_name, bool erase_old_key)
{
Assertion(Ship_registry.in_bounds(entry_index), "Invalid ship registry index %d passed to ship_registry_rename!", entry_index);
if (!Ship_registry.in_bounds(entry_index))
return;
auto entry = &Ship_registry[entry_index];
// the old key is sometimes useful for looking up the ship under its previous name;
// if not, remove it (provided it actually refers to this entry)
if (erase_old_key)
{
auto ship_it = Ship_registry_map.find(entry->name);
if (ship_it != Ship_registry_map.end() && ship_it->second == entry_index)
Ship_registry_map.erase(ship_it);
}
strcpy_s(entry->name, new_name);
Ship_registry_map[entry->name] = entry_index;
}
int Num_engine_wash_types;
int Num_ship_subobj_types;
@@ -4722,7 +4761,7 @@ static void parse_ship_values(ship_info* sip, const bool is_template, const bool
float help_hull_val;
stuff_float(&help_hull_val);
if (help_hull_val > 0.0f && help_hull_val <= 1.0f) {
sip->ask_help_shield_percent = help_hull_val;
sip->ask_help_hull_percent = help_hull_val;
} else {
error_display(0,"Ask Help Hull Percent for ship class %s is %f. This value is not within range of 0-1.0."
"Assuming default value of %f.", sip->name, help_hull_val, DEFAULT_ASK_HELP_HULL_PERCENT);
@@ -6914,6 +6953,25 @@ void ship_level_init()
Man_thruster_reset_timestamp = timestamp(0);
}
void ship_level_close()
{
// at this point ships have all gone through ship_delete;
// clean up any ships that are still present so we don't
// leave any stray references behind (e.g. for sexps)
for (auto &ship_entry : Ship_registry)
{
if (ship_entry.status == ShipStatus::PRESENT || ship_entry.status == ShipStatus::DEATH_ROLL)
{
ship_entry.cleanup_mode = (ship_entry.status == ShipStatus::DEATH_ROLL) ? SHIP_DESTROYED : SHIP_PRESENT_AT_MISSION_END;
ship_entry.status = ShipStatus::EXITED;
ship_entry.objnum = -1;
ship_entry.shipnum = -1;
}
}
ship_close_cockpit_displays(Player_ship);
}
/**
* Add a ship onto the exited ships list.
*
@@ -8745,6 +8803,7 @@ void ship_close_cockpit_displays(ship* shipp)
{
if (shipp && shipp->cockpit_model_instance >= 0) {
model_delete_instance(shipp->cockpit_model_instance);
shipp->cockpit_model_instance = -1;
}
for ( int i = 0; i < (int)Player_displays.size(); i++ ) {
@@ -13861,7 +13920,7 @@ int ship_fire_primary(object * obj, int force, bool rollback_shot)
// create the weapon -- the network signature for multiplayer is created inside
// of weapon_create
weapon_objnum = weapon_create( &firing_pos, &firing_orient, weapon_idx, OBJ_INDEX(obj), new_group_id,
false, false, swp->primary_bank_fof_cooldown[bank_to_fire], nullptr, launch_curve_data );
false, false, swp, bank_to_fire, -1, launch_curve_data );
if (weapon_objnum == -1) {
// Weapon most likely failed to fire
@@ -14229,6 +14288,38 @@ static bool ship_fire_secondary_detonate(object *obj, ship_weapon *swp)
extern void ai_maybe_announce_shockwave_weapon(object *firing_objp, int weapon_index);
// Determines whether this secondary bank is currently capable of dual fire. Note that the
// Secondary_dual_fire flag can legitimately be set even when this returns false: the flag is
// ignored rather than cleared so that the player's dual fire preference isn't lost when
// cycling through banks or weapons. All code that acts on the flag should check this too.
bool ship_secondary_bank_can_dual_fire(const ship *shipp, int bank)
{
if (bank < 0 || bank >= shipp->weapons.num_secondary_banks)
return false;
int weapon_class = shipp->weapons.secondary_bank_weapons[bank];
if (weapon_class < 0)
return false;
if (Weapon_info[weapon_class].wi_flags[Weapon::Info_Flags::No_doublefire])
return false;
// use the flag, not Player_obj, for multiplayer correctness
if (shipp->objnum >= 0 && Objects[shipp->objnum].flags[Object::Object_Flags::Player_ship])
{
if (The_mission.ai_profile->flags[AI::Profile_Flags::Disable_player_secondary_doublefire])
return false;
}
else
{
if (The_mission.ai_profile->flags[AI::Profile_Flags::Disable_ai_secondary_doublefire])
return false;
}
polymodel *pm = model_get(Ship_info[shipp->ship_info_index].model_num);
return pm->missile_banks[bank].num_slots > 1;
}
// Object *obj fires its secondary weapon, if it can.
// If its most recently fired weapon is a remotely detonatable weapon, detonate it.
// Returns number of weapons fired. Note, for swarmers, returns 1 if it is allowed
@@ -14591,25 +14682,14 @@ int ship_fire_secondary( object *obj, int allow_swarm, bool rollback_shot )
goto done_secondary;
}
// Handle the optional disabling of dual fire
// dual fire/doublefire can be disabled for individual weapons, for players, or for AIs
// if any of these apply to the current weapon, unset the dual fire flag on the ship
// then proceed as normal.
// This is only handled at firing time so dualfire isn't lost when cycling through weapons
if (shipp->flags[Ship_Flags::Secondary_dual_fire] &&
( wip->wi_flags[Weapon::Info_Flags::No_doublefire] ||
( The_mission.ai_profile->flags[AI::Profile_Flags::Disable_ai_secondary_doublefire] &&
shipp->objnum != OBJ_INDEX(Player_obj) ) ||
( The_mission.ai_profile->flags[AI::Profile_Flags::Disable_player_secondary_doublefire] &&
shipp->objnum == OBJ_INDEX(Player_obj) ))
) {
shipp->flags.remove(Ship_Flags::Secondary_dual_fire);
}
int start_slot, end_slot;
no_energy = shipp->weapon_energy < 2 * wip->energy_consumed; // whether there's enough energy for at least 1 shot was checked above
if ( shipp->flags[Ship_Flags::Secondary_dual_fire] && num_slots > 1) {
// Dual fire can be unavailable for this bank because it has only one firepoint, because
// the weapon disallows it, or because of an ai_profiles restriction. In any of these
// cases the flag is ignored rather than cleared so the dual fire preference isn't lost
// when cycling through banks or weapons.
if ( shipp->flags[Ship_Flags::Secondary_dual_fire] && ship_secondary_bank_can_dual_fire(shipp, bank) ) {
start_slot = swp->secondary_next_slot[bank];
// AL 11-19-97: Ensure enough ammo remains when firing linked secondary weapons
if ( check_ammo && ((swp->secondary_bank_ammo[bank] < 2 && !no_ammo_needed) || no_energy) ) {
@@ -14618,9 +14698,6 @@ int ship_fire_secondary( object *obj, int allow_swarm, bool rollback_shot )
end_slot = start_slot+1;
}
} else {
// de-set the flag just in case dual-fire was set but couldn't be used
// because there's less than two firepoints
shipp->flags.remove(Ship_Flags::Secondary_dual_fire);
start_slot = swp->secondary_next_slot[bank];
end_slot = start_slot;
}
@@ -14686,7 +14763,7 @@ int ship_fire_secondary( object *obj, int allow_swarm, bool rollback_shot )
// create the weapon -- for multiplayer, the net_signature is assigned inside
// of weapon_create
weapon_num = weapon_create( &firing_pos, &firing_orient, weapon_idx, OBJ_INDEX(obj), -1, tinfo.locked, false, 0.f, nullptr, launch_curve_data);
weapon_num = weapon_create( &firing_pos, &firing_orient, weapon_idx, OBJ_INDEX(obj), -1, tinfo.locked, false, swp, -1, bank, launch_curve_data);
if (weapon_num == -1) {
// Weapon most likely failed to fire
@@ -14749,7 +14826,7 @@ int ship_fire_secondary( object *obj, int allow_swarm, bool rollback_shot )
swp = &Player_ship->weapons;
if (bank >= 0) {
wip = &Weapon_info[swp->secondary_bank_weapons[bank]];
if (Player_ship->flags[Ship_Flags::Secondary_dual_fire]){
if (Player_ship->flags[Ship_Flags::Secondary_dual_fire] && ship_secondary_bank_can_dual_fire(Player_ship, bank)){
joy_ff_play_secondary_shoot((int) (wip->cargo_size * 2.0f));
} else {
joy_ff_play_secondary_shoot((int) wip->cargo_size);
@@ -15338,6 +15415,34 @@ void wing_bash_ship_name(ship *shipp, const wing *wingp, int ordinal, bool reset
}
}
bool wing_bash_legacy_hashed_ship_name(SCP_string &dest, const char *src)
{
// missions might have ships within wings that were saved using the legacy hash format, with the hash suffix at the end
auto hash = get_pointer_to_first_hash_symbol(src);
if (hash && *(hash + 1) != '\0')
{
// find the run of digits immediately preceding the hash
auto digits = hash;
while (digits > src && isdigit(static_cast<unsigned char>(*(digits - 1))))
digits--;
// the ordinal must be at least one digit, preceded by a space, preceded by the wing name
if (digits < hash && digits > (src + 1) && *(digits - 1) == ' ')
{
// move the ordinal from before the hash to the end of the name
dest.assign(src, digits - 1);
dest += hash;
dest += ' ';
dest.append(digits, hash);
// we changed it
return true;
}
}
return false;
}
/**
* Return the object index of the ship with name *name.
*/
@@ -15561,10 +15666,7 @@ int ship_info_lookup(const char *token)
return ship_info_lookup_sub(name);
}
/**
* Return the ship index of the ship with name *name.
*/
int ship_name_lookup(const char *name, int inc_players)
static int ship_name_lookup_sub(const char *name, int inc_players)
{
Assertion(name != nullptr, "NULL name passed to ship_name_lookup");
@@ -15582,7 +15684,26 @@ int ship_name_lookup(const char *name, int inc_players)
return -1;
}
int ship_type_name_lookup_sub(const char *name)
/**
* Return the ship index of the ship with name *name.
*/
int ship_name_lookup(const char *name, int inc_players)
{
// try the normal lookup
auto idx = ship_name_lookup_sub(name, inc_players);
if (idx >= 0)
return idx;
// also search for ship names hashed using the legacy format
SCP_string legacy_hashed;
if (wing_bash_legacy_hashed_ship_name(legacy_hashed, name))
return ship_name_lookup_sub(legacy_hashed.c_str(), inc_players);
// couldn't find it
return -1;
}
static int ship_type_name_lookup_sub(const char *name)
{
Assertion(name != nullptr, "NULL name passed to ship_type_name_lookup");
@@ -22466,7 +22587,7 @@ bool ship_stop_secondary_fire(object* objp)
}
bool ship_secondary_has_ammo(ship_weapon* swp, int bank_index) {
return swp->secondary_bank_ammo[bank_index] > 0 || Weapon_info[swp->secondary_bank_weapons[bank_index]].wi_flags[Weapon::Info_Flags::SecondaryNoAmmo];
return bank_index >= 0 && (swp->secondary_bank_ammo[bank_index] > 0 || Weapon_info[swp->secondary_bank_weapons[bank_index]].wi_flags[Weapon::Info_Flags::SecondaryNoAmmo]);
}
+16 -10
View File
@@ -973,6 +973,7 @@ extern const ship_registry_entry *ship_registry_get(const char *name);
extern const ship_registry_entry *ship_registry_get(const SCP_string &name);
extern const ship_registry_entry *ship_registry_get(int index);
extern const ship_registry_entry *ship_registry_get(anchor_t anchor);
extern void ship_registry_rename(int entry_index, const char *new_name, bool erase_old_key);
#define REGULAR_WEAPON (1<<0)
#define DOGFIGHT_WEAPON (1<<1)
@@ -1681,13 +1682,14 @@ extern SCP_vector<wing_formation> Wing_formations;
// Use the below macros when you want to find the index of an array element in the
// Wings[] or Ships[] arrays.
#define WING_INDEX(wingp) (static_cast<int>(wingp-Wings))
#define SHIP_INDEX(shipp) (static_cast<int>(shipp-Ships))
#define SHIP_REGISTRY_INDEX(ship_entry) (static_cast<int>(ship_entry-Ship_registry.data()))
#define WING_INDEX(wingp) (static_cast<int>((wingp)-Wings))
#define SHIP_INDEX(shipp) (static_cast<int>((shipp)-Ships))
#define SHIP_REGISTRY_INDEX(ship_entry) (static_cast<int>((ship_entry)-Ship_registry.data()))
extern void ship_init(); // called once at game start
extern void ship_level_init(); // called before the start of each level
extern void ship_level_close(); // called after the end of each level
//returns -1 if failed
extern int ship_create(matrix* orient, vec3d* pos, int ship_type, const char* ship_name = nullptr, bool standalone_ship = false);
@@ -1700,13 +1702,14 @@ extern void ship_delete( object * objp );
extern int ship_check_collision_fast( object * obj, object * other_obj, vec3d * hitpos );
extern int ship_get_num_ships();
#define SHIP_VANISHED (1<<0)
#define SHIP_DESTROYED (1<<1)
#define SHIP_DEPARTED_WARP (1<<2)
#define SHIP_DEPARTED_BAY (1<<3)
#define SHIP_DEPARTED ( SHIP_DEPARTED_BAY | SHIP_DEPARTED_WARP )
#define SHIP_DESTROYED_REDALERT (1<<4)
#define SHIP_DEPARTED_REDALERT (1<<5)
#define SHIP_VANISHED (1<<0)
#define SHIP_DESTROYED (1<<1)
#define SHIP_DEPARTED_WARP (1<<2)
#define SHIP_DEPARTED_BAY (1<<3)
#define SHIP_DEPARTED ( SHIP_DEPARTED_BAY | SHIP_DEPARTED_WARP )
#define SHIP_DESTROYED_REDALERT (1<<4)
#define SHIP_DEPARTED_REDALERT (1<<5)
#define SHIP_PRESENT_AT_MISSION_END (1<<6)
/**
* @brief Deletes and de-inits a ship.
@@ -1730,6 +1733,7 @@ extern bool in_autoaim_fov(ship *shipp, int bank_to_fire, object *obj);
extern int ship_stop_fire_primary(object * obj);
extern int ship_fire_primary(object * objp, int force = 0, bool rollback_shot = false);
extern int ship_fire_secondary(object * objp, int allow_swarm = 0, bool rollback_shot = false );
extern bool ship_secondary_bank_can_dual_fire(const ship *shipp, int bank);
bool ship_start_secondary_fire(object* objp);
bool ship_stop_secondary_fire(object* objp);
extern int ship_launch_countermeasure(object *objp, int rand_val = -1);
@@ -1780,6 +1784,8 @@ extern void wing_bash_ship_name(SCP_string &ship_name, const char *wing_name, in
extern void wing_bash_ship_name(char *ship_name, const char *wing_name, int ordinal);
extern void wing_bash_ship_name(p_object *p_objp, const wing *wingp, int ordinal, bool reset_display_name_if_normal = false);
extern void wing_bash_ship_name(ship *shipp, const wing *wingp, int ordinal, bool reset_display_name_if_normal = false);
extern bool wing_bash_legacy_hashed_ship_name(SCP_string &dest, const char *src);
extern int Player_ship_class;
// Do the special effect for energy dissipating into the shield for a hit.
+2
View File
@@ -1222,6 +1222,8 @@ static void shiphit_record_player_killer(const object *killer_objp, player *p)
break;
case OBJ_PROP:
p->killer_objtype = OBJ_PROP;
p->killer_weapon_index = -1;
strcpy_s(p->killer_parent_name, "");
p->killer_species = -1;
p->killer_parent_name[0] = '\0';
+4
View File
@@ -1,4 +1,5 @@
#include "Random.h"
#include "RandomRange.h"
#include <limits>
#include <random>
@@ -8,6 +9,9 @@
#include "globalincs/pstypes.h"
namespace util {
thread_local std::mt19937 seeder {std::random_device()()};
namespace {
template <typename RngType>
class RandomImpl {
+4 -4
View File
@@ -54,6 +54,10 @@ size_t parse_number_list(T (&list)[N])
}
} // namespace
//Sampling a random_device is REALLY expensive.
//Instead of sampling one for each seed, create a pseudorandom seeder which is initialized ONCE from a random_device.
extern thread_local std::mt19937 seeder;
/**
* @brief Generic class for generating numbers in a specific range
*
@@ -77,10 +81,6 @@ class RandomRange {
ValueType m_minValue;
ValueType m_maxValue;
//Sampling a random_device is REALLY expensive.
//Instead of sampling one for each seed, create a pseudorandom seeder which is initialized ONCE from a random_device.
inline static thread_local std::mt19937 seeder {std::random_device()()};
public:
template <typename T, typename... Ts, typename = typename std::enable_if<(sizeof... (Ts) >=1 || !std::is_convertible<T, ValueType>::value) && !std::is_same_v<std::decay_t<T>, RandomRange>, int>::type>
explicit RandomRange(T&& distributionFirstParameter, Ts&&... distributionParameters)
+20 -23
View File
@@ -364,33 +364,30 @@ int beam_fire(beam_fire_info *fire_info)
wip = &Weapon_info[fire_info->beam_info_index];
// copied from weapon_create()
if ((wip->num_substitution_patterns > 0) && (fire_info->shooter != nullptr)) {
// (a beam whose info was dictated by the server has already had its substitution resolved there)
if ((wip->num_substitution_patterns > 0) && (fire_info->shooter != nullptr) && (fire_info->beam_info_override == nullptr)) {
// using substitution
// get to the instance of the gun
Assertion(fire_info->shooter->type == OBJ_SHIP, "Expected type OBJ_SHIP, got %d", fire_info->shooter->type);
Assertion((fire_info->shooter->instance < MAX_SHIPS) && (fire_info->shooter->instance >= 0),
"Ship index is %d, which is out of range [%d,%d)", fire_info->shooter->instance, 0, MAX_SHIPS);
ship* parent_shipp = &(Ships[fire_info->shooter->instance]);
Assert(parent_shipp != nullptr);
size_t* position = get_pointer_to_weapon_fire_pattern_index(fire_info->beam_info_index, fire_info->shooter->instance, fire_info->turret);
Assertion(position != nullptr, "'%s' is trying to fire a weapon that is not selected", Ships[fire_info->shooter->instance].ship_name);
size_t curr_pos = *position;
if ((parent_shipp->flags[Ship::Ship_Flags::Primary_linked]) && curr_pos > 0) {
curr_pos--;
// the turret and fighter firing code both tell us which bank the beam is in
int pbank = fire_info->bank;
ship *parent_shipp = nullptr;
ship_weapon* swp = nullptr;
if (fire_info->fire_method == BFM_TURRET_FIRED || fire_info->fire_method == BFM_TURRET_FORCE_FIRED) {
// the fire method and the turret are set independently, so the turret can be null here
if (fire_info->turret != nullptr)
swp = &fire_info->turret->weapons;
} else if (fire_info->fire_method == BFM_FIGHTER_FIRED && fire_info->shooter->type == OBJ_SHIP) {
parent_shipp = &Ships[fire_info->shooter->instance];
swp = &parent_shipp->weapons;
}
++(*position);
*position = (*position) % wip->num_substitution_patterns;
if (wip->weapon_substitution_pattern[curr_pos] == -1) {
// weapon doesn't want any sub
return -1;
}
else if (wip->weapon_substitution_pattern[curr_pos] != fire_info->beam_info_index) {
fire_info->beam_info_index = wip->weapon_substitution_pattern[curr_pos];
// weapon wants to sub with weapon other than me
auto [use_substitution, substituted_weapon_info_index] = get_weapon_substitution_tuple(fire_info->beam_info_index, swp, pbank, -1, parent_shipp);
if (use_substitution) {
if (substituted_weapon_info_index < 0)
return -1;
fire_info->beam_info_index = substituted_weapon_info_index;
return beam_fire(fire_info);
}
}
+2 -2
View File
@@ -102,7 +102,7 @@ typedef struct beam_fire_info {
vec3d starting_pos; // starting positiong for floating beams -MageKing17
beam_info *beam_info_override; // (optional), pass this in to override all beam movement info (for multiplayer)
int num_shots; // (optional), only used for type D weapons
int bank; // for fighters, which bank of the primary weapons are they in
int bank; // which primary bank the beam is in (for fighters and turrets)
int point; // for fighters, which point on the bank it is from
int bfi_flags;
char team; // for floating beams, determines which team the beam is on
@@ -226,7 +226,7 @@ typedef struct beam {
extern std::array<beam, MAX_BEAMS> Beams; // all beams
extern int Beam_count;
#define BEAM_INDEX(beam) (int)((beam) - Beams.data())
#define BEAM_INDEX(beam) (static_cast<int>((beam)-Beams.data()))
// ------------------------------------------------------------------------------------------------
// BEAM WEAPON FUNCTIONS
+14 -2
View File
@@ -42,7 +42,7 @@ int Shockwave_inited = 0;
// -----------------------------------------------------------
// Function macros
// -----------------------------------------------------------
#define SW_INDEX(sw) (sw-Shockwaves)
#define SW_INDEX(sw) (static_cast<int>((sw)-Shockwaves))
// -----------------------------------------------------------
// Externals
@@ -308,7 +308,13 @@ void shockwave_move(object *shockwave_objp, float frametime)
CLAMP(sw->radius, 0.0f, sw->outer_radius);
}
if ( sw->time_elapsed > sw->total_time ) {
// It is possible the shockwave may have lifetime shorter than one frame (e.g. high speed, small radius),
// which by default results in shockwave applying no damage.
// Provide optional fix to ensure shockwave is not killed until after this frame's damage pass has run.
bool sw_expired = sw->time_elapsed > sw->total_time;
bool sw_expire_fix = The_mission.ai_profile->flags[AI::Profile_Flags::Fix_shockwave_expire_before_do_damage];
if ( sw_expired && !sw_expire_fix ) {
shockwave_objp->flags.set(Object::Object_Flags::Should_be_dead);
return;
}
@@ -443,6 +449,12 @@ void shockwave_move(object *shockwave_objp, float frametime)
}
} // end for
// See the sw_expired check above: shockwave has now applied its final damage pass this frame, so let it die.
// With the flag off we already returned before the loop.
if ( sw_expired && sw_expire_fix ) {
shockwave_objp->flags.set(Object::Object_Flags::Should_be_dead);
}
}
/**
+8 -2
View File
@@ -63,6 +63,8 @@ void swarm_level_init()
tswarmp->turret = NULL;
tswarmp->target_subsys = NULL;
tswarmp->time_to_fire = 0;
tswarmp->swp_pbank = -1;
tswarmp->swp_sbank = -1;
}
Turret_swarm_validity_next_check_time = timestamp(TURRET_SWARM_VALIDITY_CHECKTIME);
@@ -286,6 +288,8 @@ int turret_swarm_create()
tswarmp->turret = NULL;
tswarmp->target_subsys = NULL;
tswarmp->time_to_fire = 0;
tswarmp->swp_pbank = -1;
tswarmp->swp_sbank = -1;
tswarmp->flags |= SWARM_USED;
return i;
@@ -309,7 +313,7 @@ void turret_swarm_delete(int i)
}
// Set up turret swarm info struct
void turret_swarm_set_up_info(int parent_objnum, ship_subsys *turret, const weapon_info *wip, int weapon_num, bool no_tracking_object)
void turret_swarm_set_up_info(int parent_objnum, ship_subsys *turret, const weapon_info *wip, int weapon_num, int swp_pbank, int swp_sbank, bool no_tracking_object)
{
turret_swarm_info *tsi;
object *parent_obj;
@@ -387,7 +391,7 @@ void turret_swarm_set_up_info(int parent_objnum, ship_subsys *turret, const weap
int bank_fired = swp->current_secondary_bank;
// initialize tsi
tsi->weapon_class = weapon_info_get_index(wip);
tsi->weapon_class = WEAPON_INFO_INDEX(wip);
if (wip->wi_flags[Weapon::Info_Flags::Swarm]) {
tsi->num_to_launch = wip->swarm_count;
} else {
@@ -404,6 +408,8 @@ void turret_swarm_set_up_info(int parent_objnum, ship_subsys *turret, const weap
tsi->target_subsys = turret->targeted_subsys;
tsi->time_to_fire = 1; // first missile next frame
tsi->weapon_num = weapon_num;
tsi->swp_pbank = swp_pbank;
tsi->swp_sbank = swp_sbank;
}
void turret_swarm_fire_from_turret(turret_swarm_info *tsi);
+3 -1
View File
@@ -30,6 +30,8 @@ typedef struct turret_swarm_info {
ship_subsys* target_subsys;
int time_to_fire;
int weapon_num;
int swp_pbank;
int swp_sbank;
} turret_swarm_info;
struct swarm_info {
@@ -52,7 +54,7 @@ void swarm_maybe_fire_missile(int shipnum);
int turret_swarm_create();
void turret_swarm_delete(int i);
void turret_swarm_set_up_info(int parent_objnum, ship_subsys *turret, const weapon_info *wip, int weapon_num, bool no_tracking_object = false);
void turret_swarm_set_up_info(int parent_objnum, ship_subsys *turret, const weapon_info *wip, int weapon_num, int swp_pbank, int swp_sbank, bool no_tracking_object = false);
void turret_swarm_check_validity();
#endif /* __FREESPACE_SWARM_H__ */
+10 -6
View File
@@ -36,8 +36,11 @@
#include "utils/modular_curves.h"
#include <optional>
#include <tuple>
class object;
class ship;
class ship_weapon;
class ship_subsys;
#define WP_UNUSED -1
@@ -975,7 +978,8 @@ extern int Default_cmeasure_index;
extern SCP_vector<int> Player_weapon_precedence; // Vector of weapon types, precedence list for player weapon selection
#define WEAPON_INDEX(wp) (int)(wp-Weapons)
#define WEAPON_INDEX(wp) (static_cast<int>((wp)-Weapons))
#define WEAPON_INFO_INDEX(wip) (static_cast<int>((wip)-Weapon_info.data()))
typedef struct tracking_info {
ship_subsys *subsys;
@@ -986,7 +990,6 @@ typedef struct tracking_info {
} tracking_info;
int weapon_info_lookup(const char *name);
int weapon_info_get_index(const weapon_info *wip);
inline int weapon_info_size()
{
@@ -1028,8 +1031,9 @@ int weapon_create( const vec3d *pos,
int group_id=-1,
bool is_locked = false,
bool is_spawned = false,
float fof_cooldown = 0.0f,
ship_subsys *src_turret = nullptr,
ship_weapon *src_swp = nullptr,
int src_pbank = -1,
int src_sbank = -1,
const WeaponLaunchCurveData& launch_curve_data = WeaponLaunchCurveData {
0,
0.f,
@@ -1043,8 +1047,8 @@ inline void weapon_set_tracking_info(int weapon_objnum, int parent_objnum, track
}
// gets the substitution pattern pointer for a given weapon
// src_turret may be null
size_t* get_pointer_to_weapon_fire_pattern_index(int weapon_type, int ship_idx, ship_subsys* src_turret);
// returns [use_substitution, substituted_weapon_info_index]
std::tuple<bool, int> get_weapon_substitution_tuple(int weapon_info_index, ship_weapon *swp, int pbank, int sbank, ship *shipp_to_check);
bool weapon_armed(weapon *wp, bool hit_target);
void maybe_play_conditional_impacts(const std::array<std::optional<ConditionData>, NumHitTypes>& impact_data, const object* weapon_objp, const object* impacted_objp, bool armed_weapon, int submodel, const vec3d* hitpos, const vec3d* local_hitpos = nullptr, const vec3d* hit_normal = nullptr);
+79 -64
View File
@@ -389,17 +389,6 @@ int weapon_info_lookup(const char *name)
return -1;
}
/**
* Return the index of Weapon_info used by this pointer. Equivalent to the old WEAPON_INFO_INDEX macro:
* #define WEAPON_INFO_INDEX(wip) (int)(wip-Weapon_info)
*/
int weapon_info_get_index(const weapon_info *wip)
{
Assertion(wip != nullptr, "NULL wip passed to weapon_info_get_index");
const weapon_info *data = Weapon_info.data();
return static_cast<int>(std::distance(data, wip));
}
// Parse the weapon flags.
void parse_wi_flags(weapon_info *weaponp)
{
@@ -3893,7 +3882,7 @@ int parse_weapon(int subtype, bool replace, const char *filename)
wip->particle_spewers[spew_index] = particle::util::parseEffect(wip->name);
}
else { // we have a valid index, now parse the spewer already
auto& pspew_buffer = pspew_legacy_parse_data_buffer[weapon_info_get_index(wip)][spew_index];
auto& pspew_buffer = pspew_legacy_parse_data_buffer[WEAPON_INFO_INDEX(wip)][spew_index];
if (pspew_buffer.particle_spew_type == PSPEW_NONE) {
//This must be an uninitialized effect, store defaults.
pspew_buffer.particle_spew_count = 1;
@@ -3996,7 +3985,7 @@ int parse_weapon(int subtype, bool replace, const char *filename)
if (wip->particle_spewers.empty()) {
wip->particle_spewers.emplace_back(particle::ParticleEffectHandle::invalid());
}
auto& pspew_buffer = pspew_legacy_parse_data_buffer[weapon_info_get_index(wip)][0];
auto& pspew_buffer = pspew_legacy_parse_data_buffer[WEAPON_INFO_INDEX(wip)][0];
pspew_buffer.particle_spew_count = 1;
pspew_buffer.particle_spew_time = 25;
pspew_buffer.particle_spew_vel = 0.4f;
@@ -7077,31 +7066,69 @@ void weapon_set_tracking_info(int weapon_objnum, int parent_objnum, int target_o
}
}
size_t* get_pointer_to_weapon_fire_pattern_index(int weapon_type, int ship_idx, ship_subsys * src_turret)
static size_t *get_pointer_to_weapon_fire_pattern_index(ship_weapon *ship_weapon_p, int swp_pbank, int swp_sbank)
{
Assertion(ship_idx >= 0 && ship_idx < MAX_SHIPS, "Invalid ship index in get_pointer_to_weapon_fire_pattern_index()");
ship* shipp = &Ships[ship_idx];
ship_weapon* ship_weapon_p = &(shipp->weapons);
if(src_turret)
if (ship_weapon_p)
{
ship_weapon_p = &src_turret->weapons;
if (swp_pbank >= 0)
{
Assertion(swp_pbank < MAX_SHIP_PRIMARY_BANKS, "swp_pbank is %d, which is out of range [0,%d)", swp_pbank, MAX_SHIP_PRIMARY_BANKS);
return &(ship_weapon_p->primary_bank_substitution_pattern_index[swp_pbank]);
}
if (swp_sbank >= 0)
{
Assertion(swp_sbank < MAX_SHIP_SECONDARY_BANKS, "swp_sbank is %d, which is out of range [0,%d)", swp_sbank, MAX_SHIP_SECONDARY_BANKS);
return &(ship_weapon_p->secondary_bank_substitution_pattern_index[swp_sbank]);
}
}
Assert( ship_weapon_p != NULL );
return nullptr;
}
// search for the corresponding bank pattern index for the weapon_type that is being fired.
// Note: Because a weapon_type may not be unique to a weapon bank per ship this search may attribute
// the weapon to the wrong bank. Hopefully this isn't a problem.
for ( int pi = 0; pi < MAX_SHIP_PRIMARY_BANKS; pi++ ) {
if ( ship_weapon_p->primary_bank_weapons[pi] == weapon_type ) {
return &(ship_weapon_p->primary_bank_substitution_pattern_index[pi]);
// gets the substitution pattern pointer for a given weapon
// returns [use_substitution, substituted_weapon_info_index]
std::tuple<bool, int> get_weapon_substitution_tuple(int weapon_info_index, ship_weapon *swp, int pbank, int sbank, ship *shipp_to_check)
{
auto wip = &Weapon_info[weapon_info_index];
if (wip->num_substitution_patterns == 0)
return { false, -1 };
bool paired_shot = false;
if (shipp_to_check)
{
// only advance the pattern once per linked/paired shot rather than once per projectile
if (wip->subtype == WP_LASER)
{
paired_shot = shipp_to_check->flags[Ship::Ship_Flags::Primary_linked];
}
else if (wip->subtype == WP_MISSILE && shipp_to_check->flags[Ship::Ship_Flags::Secondary_dual_fire])
{
// for dual fire, also check whether the armed bank can actually use it, since the flag is ignored rather than cleared for banks that can't
paired_shot = ship_secondary_bank_can_dual_fire(shipp_to_check, sbank);
}
}
for ( int si = 0; si < MAX_SHIP_SECONDARY_BANKS; si++ ) {
if ( ship_weapon_p->secondary_bank_weapons[si] == weapon_type ) {
return &(ship_weapon_p->secondary_bank_substitution_pattern_index[si]);
auto position = get_pointer_to_weapon_fire_pattern_index(swp, pbank, sbank);
if (position)
{
size_t curr_pos = *position;
if (paired_shot && curr_pos > 0)
curr_pos--;
++(*position);
*position = (*position) % wip->num_substitution_patterns;
if (wip->weapon_substitution_pattern[curr_pos] == -1)
{
// weapon doesn't want any sub
return { true, -1 };
}
else if (wip->weapon_substitution_pattern[curr_pos] != weapon_info_index)
{
// weapon wants to sub with weapon other than me
return { true, wip->weapon_substitution_pattern[curr_pos] };
}
}
return NULL;
return { false, -1 };
}
/**
@@ -7110,7 +7137,7 @@ size_t* get_pointer_to_weapon_fire_pattern_index(int weapon_type, int ship_idx,
* @return Index of weapon in the Objects[] array, -1 if the weapon object was not created
*/
int Weapons_created = 0;
int weapon_create( const vec3d *pos, const matrix *porient, int weapon_type, int parent_objnum, int group_id, bool is_locked, bool is_spawned, float fof_cooldown, ship_subsys *src_turret, const WeaponLaunchCurveData& launch_curve_data )
int weapon_create( const vec3d *pos, const matrix *porient, int weapon_type, int parent_objnum, int group_id, bool is_locked, bool is_spawned, ship_weapon *src_swp, int src_pbank, int src_sbank, const WeaponLaunchCurveData& launch_curve_data )
{
int n, objnum;
object *objp, *parent_objp=NULL;
@@ -7135,32 +7162,17 @@ int weapon_create( const vec3d *pos, const matrix *porient, int weapon_type, int
if ( (wip->num_substitution_patterns > 0) && (parent_objp != NULL)) {
// using substitution
// linked/dual fire pairing only applies to ship-level fire, not to turrets or non-ship parents
ship *parent_shipp = nullptr;
if (parent_objp->type == OBJ_SHIP && src_swp == &Ships[parent_objp->instance].weapons)
parent_shipp = &Ships[parent_objp->instance];
auto [use_substitution, substituted_weapon_info_index] = get_weapon_substitution_tuple(weapon_type, src_swp, src_pbank, src_sbank, parent_shipp);
// get to the instance of the gun
Assertion( parent_objp->type == OBJ_SHIP, "Expected type OBJ_SHIP, got %d", parent_objp->type );
Assertion( (parent_objp->instance < MAX_SHIPS) && (parent_objp->instance >= 0),
"Ship index is %d, which is out of range [%d,%d)", parent_objp->instance, 0, MAX_SHIPS);
ship* parent_shipp = &(Ships[parent_objp->instance]);
Assert( parent_shipp != NULL );
if (use_substitution) {
if (substituted_weapon_info_index < 0)
return -1;
size_t *position = get_pointer_to_weapon_fire_pattern_index(weapon_type, parent_objp->instance, src_turret);
Assertion( position != NULL, "'%s' is trying to fire a weapon that is not selected", Ships[parent_objp->instance].ship_name );
size_t curr_pos = *position;
if (((Weapon_info[weapon_type].subtype == WP_LASER && parent_shipp->flags[Ship::Ship_Flags::Primary_linked]) ||
(Weapon_info[weapon_type].subtype == WP_MISSILE && parent_shipp->flags[Ship::Ship_Flags::Secondary_dual_fire])) &&
(curr_pos > 0)) {
curr_pos--;
}
++(*position);
*position = (*position) % wip->num_substitution_patterns;
if ( wip->weapon_substitution_pattern[curr_pos] == -1 ) {
// weapon doesn't want any sub
return -1;
} else if ( wip->weapon_substitution_pattern[curr_pos] != weapon_type ) {
// weapon wants to sub with weapon other than me
return weapon_create(pos, porient, wip->weapon_substitution_pattern[curr_pos], parent_objnum, group_id, is_locked, is_spawned, fof_cooldown, nullptr, launch_curve_data);
return weapon_create(pos, porient, substituted_weapon_info_index, parent_objnum, group_id, is_locked, is_spawned, src_swp, src_pbank, src_sbank, launch_curve_data);
}
}
@@ -7172,7 +7184,7 @@ int weapon_create( const vec3d *pos, const matrix *porient, int weapon_type, int
float test = rng.next();
if (test < failure_rate) {
if (wip->failure_sub != -1) {
return weapon_create(pos, porient, wip->failure_sub, parent_objnum, group_id, is_locked, is_spawned, fof_cooldown, nullptr, launch_curve_data);
return weapon_create(pos, porient, wip->failure_sub, parent_objnum, group_id, is_locked, is_spawned, src_swp, src_pbank, src_sbank, launch_curve_data);
} else {
return -1;
}
@@ -7225,9 +7237,15 @@ int weapon_create( const vec3d *pos, const matrix *porient, int weapon_type, int
orient = &morient;
float combined_fof = wip->field_of_fire * wip->weapon_launch_curves.get_output(weapon_info::WeaponLaunchCurveOutputs::FOF_MULT, launch_curve_data);
// If there is a fof_cooldown value, increase the spread linearly
if (fof_cooldown != 0.0f) {
combined_fof = wip->field_of_fire + (fof_cooldown * wip->max_fof_spread);
{
// If there is a fof_cooldown value, increase the spread linearly
float fof_cooldown = 0.0f;
if (src_swp && src_pbank >= 0)
fof_cooldown = src_swp->primary_bank_fof_cooldown[src_pbank];
// if (src_swp && src_sbank >= 0)
// fof_cooldown = src_swp->secondary_bank_fof_cooldown[src_sbank];
if (fof_cooldown != 0.0f)
combined_fof = wip->field_of_fire + (fof_cooldown * wip->max_fof_spread);
}
if(combined_fof > 0.0f){
@@ -7478,9 +7496,6 @@ int weapon_create( const vec3d *pos, const matrix *porient, int weapon_type, int
if (wip->wi_flags[Weapon::Info_Flags::Local_ssm])
{
Assert(parent_objp); //local ssms must have a parent
wp->lssm_warpout_time=timestamp(wip->lssm_warpout_delay);
wp->lssm_warpin_time=timestamp(wip->lssm_warpout_delay + wip->lssm_warpin_delay);
wp->lssm_stage=1;
@@ -7585,7 +7600,7 @@ int weapon_create( const vec3d *pos, const matrix *porient, int weapon_type, int
Num_weapons++;
if (Weapons_inherit_parent_collision_group) {
if (Weapons_inherit_parent_collision_group && parent_objnum >= 0) {
Objects[objnum].collision_group_id = Objects[parent_objnum].collision_group_id;
}
@@ -7625,7 +7640,7 @@ int weapon_create( const vec3d *pos, const matrix *porient, int weapon_type, int
}
if (scripting::hooks::OnWeaponCreated->isActive()) {
scripting::hooks::OnWeaponCreated->run(scripting::hooks::WeaponCreatedConditions{ wp, &Objects[parent_objnum] },
scripting::hooks::OnWeaponCreated->run(scripting::hooks::WeaponCreatedConditions{ wp, parent_objnum < 0 ? nullptr : &Objects[parent_objnum] },
scripting::hook_param_list(
scripting::hook_param("Weapon", 'o', &Objects[objnum])
));
+87 -27
View File
@@ -432,7 +432,18 @@ void briefing_editor_dlg::update_data(int update)
if (m_last_icon >= 0) {
valid = (m_id != ptr->icons[m_last_icon].id);
if (m_id >= 0) {
if (m_id < 0) {
// briefing icon ids must never be negative; revert to the previous id
if (valid) {
char msg[1024];
sprintf(msg, "Icon ID #%d is invalid. Briefing icon IDs cannot be negative.\n"
"Icon ID has been reset back to %d", m_id, ptr->icons[m_last_icon].id);
m_id = ptr->icons[m_last_icon].id;
MessageBox(msg);
}
} else {
if (valid && !m_change_local) {
for (i=m_last_stage+1; i<Briefing->num_stages; i++) {
if (find_icon(m_id, i) >= 0) {
@@ -473,7 +484,7 @@ void briefing_editor_dlg::update_data(int update)
string_copy(buf, m_icon_label, MAX_LABEL_LEN - 1);
lcl_fred_replace_stuff(buf, MAX_LABEL_LEN - 1);
if (stricmp(ptr->icons[m_last_icon].label, buf) && !m_change_local) {
if (strcmp(ptr->icons[m_last_icon].label, buf) && !m_change_local) {
set_modified();
reset_icon_loop(m_last_stage);
while (get_next_icon(m_id))
@@ -483,7 +494,7 @@ void briefing_editor_dlg::update_data(int update)
string_copy(buf, m_icon_closeup_label, MAX_LABEL_LEN - 1);
lcl_fred_replace_stuff(buf, MAX_LABEL_LEN - 1);
if (stricmp(ptr->icons[m_last_icon].closeup_label, buf) && !m_change_local) {
if (strcmp(ptr->icons[m_last_icon].closeup_label, buf) && !m_change_local) {
set_modified();
reset_icon_loop(m_last_stage);
while (get_next_icon(m_id))
@@ -491,28 +502,33 @@ void briefing_editor_dlg::update_data(int update)
}
strcpy_s(ptr->icons[m_last_icon].closeup_label, buf);
if (m_icon_scale > 0)
ptr->icons[m_last_icon].scale_factor = m_icon_scale / 100.0f;
if (m_icon_scale > 0) {
float new_scale = m_icon_scale / 100.0f;
if ((ptr->icons[m_last_icon].scale_factor != new_scale) && !m_change_local) {
set_modified();
reset_icon_loop(m_last_stage);
while (get_next_icon(m_id))
iconp->scale_factor = new_scale;
}
ptr->icons[m_last_icon].scale_factor = new_scale;
}
if ( m_hilight )
ptr->icons[m_last_icon].flags |= BI_HIGHLIGHT;
else
ptr->icons[m_last_icon].flags &= ~BI_HIGHLIGHT;
// build the new flags for this icon from the checkboxes, preserving any non-editable bits
const int editable_flags = BI_HIGHLIGHT | BI_MIRROR_ICON | BI_USE_WING_ICON | BI_USE_CARGO_ICON;
int new_flags = ptr->icons[m_last_icon].flags;
if (m_hilight) new_flags |= BI_HIGHLIGHT; else new_flags &= ~BI_HIGHLIGHT;
if (m_flipicon) new_flags |= BI_MIRROR_ICON; else new_flags &= ~BI_MIRROR_ICON;
if (m_use_wing) new_flags |= BI_USE_WING_ICON; else new_flags &= ~BI_USE_WING_ICON;
if (m_use_cargo) new_flags |= BI_USE_CARGO_ICON; else new_flags &= ~BI_USE_CARGO_ICON;
if (m_flipicon)
ptr->icons[m_last_icon].flags |= BI_MIRROR_ICON;
else
ptr->icons[m_last_icon].flags &= ~BI_MIRROR_ICON;
if (m_use_wing)
ptr->icons[m_last_icon].flags |= BI_USE_WING_ICON;
else
ptr->icons[m_last_icon].flags &= ~BI_USE_WING_ICON;
if (m_use_cargo)
ptr->icons[m_last_icon].flags |= BI_USE_CARGO_ICON;
else
ptr->icons[m_last_icon].flags &= ~BI_USE_CARGO_ICON;
if ((ptr->icons[m_last_icon].flags != new_flags) && !m_change_local) {
set_modified();
reset_icon_loop(m_last_stage);
// apply only the editable bits to later icons, preserving their other flags
while (get_next_icon(m_id))
iconp->flags = (iconp->flags & ~editable_flags) | (new_flags & editable_flags);
}
ptr->icons[m_last_icon].flags = new_flags;
if ((ptr->icons[m_last_icon].type != m_icon_image) && !m_change_local) {
set_modified();
@@ -942,6 +958,7 @@ void briefing_editor_dlg::copy_stage(int from, int to)
Briefing->stages[to].camera_time = 500;
Briefing->stages[to].num_icons = 0;
Briefing->stages[to].formula = Locked_sexp_true;
Briefing->stages[to].draw_grid = true;
Briefing->stages[to].grid_color = Color_briefing_grid;
return;
}
@@ -956,6 +973,7 @@ void briefing_editor_dlg::copy_stage(int from, int to)
Briefing->stages[to].num_icons = Briefing->stages[from].num_icons;
Briefing->stages[to].num_lines = Briefing->stages[from].num_lines;
Briefing->stages[to].formula = Briefing->stages[from].formula;
Briefing->stages[to].draw_grid = Briefing->stages[from].draw_grid;
// For now let's just always set this back to default. Eventually when we have a UI color picker in qtFRED, we can copy from stage to stage
Briefing->stages[to].grid_color = Color_briefing_grid;
@@ -1089,12 +1107,13 @@ void briefing_editor_dlg::OnMakeIcon()
strncpy(biconp->label, name, len);
biconp->label[len] = 0;
biconp->closeup_label[0] = 0;
// iconp->text[0] = 0;
biconp->type = 0;
biconp->team = team;
biconp->pos = pos;
biconp->flags = 0;
biconp->id = Cur_brief_id++;
biconp->id = get_unused_briefing_icon_id();
biconp->scale_factor = 1.0f;
biconp->modelnum = -1;
@@ -1198,7 +1217,7 @@ void briefing_editor_dlg::OnDeleteIcon()
void briefing_editor_dlg::delete_icon(int num)
{
int i, z;
int i, l, z;
if (num < 0)
num = m_cur_icon;
@@ -1217,6 +1236,24 @@ void briefing_editor_dlg::delete_icon(int num)
m_cur_icon = -1;
update_data(1);
obj_delete(icon_obj[num]);
// remove any lines that reference the icon being deleted
i = Briefing->stages[m_cur_stage].num_lines;
while (i--)
if ((Briefing->stages[m_cur_stage].lines[i].start_icon == num) || (Briefing->stages[m_cur_stage].lines[i].end_icon == num)) {
Briefing->stages[m_cur_stage].num_lines--;
for (l=i; l<Briefing->stages[m_cur_stage].num_lines; l++)
Briefing->stages[m_cur_stage].lines[l] = Briefing->stages[m_cur_stage].lines[l+1];
}
// fix the indexes of lines that reference the icons being shifted down
for (i=0; i<Briefing->stages[m_cur_stage].num_lines; i++) {
if (Briefing->stages[m_cur_stage].lines[i].start_icon > num)
Briefing->stages[m_cur_stage].lines[i].start_icon--;
if (Briefing->stages[m_cur_stage].lines[i].end_icon > num)
Briefing->stages[m_cur_stage].lines[i].end_icon--;
}
for (i=num+1; i<Briefing->stages[m_cur_stage].num_icons; i++) {
Briefing->stages[m_cur_stage].icons[i-1] = Briefing->stages[m_cur_stage].icons[i];
icon_obj[i-1] = icon_obj[i];
@@ -1282,10 +1319,13 @@ int briefing_editor_dlg::check_mouse_hit(int x, int y)
return -1;
}
void briefing_editor_dlg::OnPropagateIcons()
void briefing_editor_dlg::OnPropagateIcons()
{
object *ptr;
// commit any pending edits in the icon fields before propagating the icons forward
update_data(1);
ptr = GET_FIRST(&obj_used_list);
while (ptr != END_OF_LIST(&obj_used_list)) {
if ((ptr->type == OBJ_POINT) && (ptr->flags[Object::Object_Flags::Marked])) {
@@ -1325,6 +1365,26 @@ int briefing_editor_dlg::find_icon(int id, int stage)
return -1;
}
// is the given id already used by any icon in any stage of the current briefing?
bool briefing_editor_dlg::briefing_icon_id_used(int id)
{
for (int s=0; s<Briefing->num_stages; s++)
if (find_icon(id, s) >= 0)
return true;
return false;
}
// find an id that isn't already used by an icon in the current briefing, advancing
// Cur_brief_id past any values that are already taken (e.g. from manual id edits)
int briefing_editor_dlg::get_unused_briefing_icon_id()
{
while (briefing_icon_id_used(Cur_brief_id))
Cur_brief_id++;
return Cur_brief_id++;
}
void briefing_editor_dlg::reset_icon_loop(int stage)
{
stage_loop = stage + 1;
@@ -1337,7 +1397,7 @@ int briefing_editor_dlg::get_next_icon(int id)
icon_loop++;
if (icon_loop >= Briefing->stages[stage_loop].num_icons) {
stage_loop++;
if (stage_loop > Briefing->num_stages)
if (stage_loop >= Briefing->num_stages)
return 0;
icon_loop = -1;
+2
View File
@@ -34,6 +34,8 @@ public:
void OnOK();
void OnCancel();
int find_icon(int id, int stage);
bool briefing_icon_id_used(int id);
int get_unused_briefing_icon_id();
void propagate_icon(int num);
void reset_editor();
int check_mouse_hit(int x, int y);
+15
View File
@@ -1674,6 +1674,21 @@ int event_annotation_lookup(HTREEITEM handle)
return -1;
}
// The tree recreated (moved) or deleted the item for a node. Re-point any
// annotation on the old handle to the new one so it follows the node; a null
// new_handle means the node was deleted, which clears the handle. The path is
// left intact, so an annotation cleared this way is dropped at save (its path
// can no longer be rebuilt) but survives a Cancel (the path still resolves).
void event_sexp_tree::on_node_handle_changed(HTREEITEM old_handle, HTREEITEM new_handle)
{
if (!old_handle)
return;
int i = event_annotation_lookup(old_handle);
if (i >= 0)
Event_annotations[i].handle = new_handle;
}
void event_annotation_swap_image(event_sexp_tree *tree, HTREEITEM handle, int annotation_index)
{
event_annotation_swap_image(tree, handle, Event_annotations[annotation_index]);
+4
View File
@@ -31,6 +31,10 @@ protected:
virtual void PreSubclassWindow();
virtual void OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult);
// keep event annotations attached to their node when the tree recreates or
// deletes its handle (new_handle == nullptr means the node was deleted)
void on_node_handle_changed(HTREEITEM old_handle, HTREEITEM new_handle) override;
CStringA m_tooltiptextA;
CStringW m_tooltiptextW;
+20
View File
@@ -36,6 +36,7 @@
#include "localization/fhash.h"
#include "localization/localize.h"
#include "mission/missiongoals.h"
#include "mission/missiongrid.h"
#include "mission/missionparse.h"
#include "object/object.h"
#include "render/3d.h"
@@ -233,9 +234,20 @@ bool CFREDDoc::load_mission(const char *pathname, int flags) {
Parse_viewer_pos = view_pos;
Parse_viewer_orient = view_orient;
// preserve the editor grid across the reload; it's display state, not part of the mission,
// but clear_mission() recreates it via fred_render_init(). (See the view_pos/view_orient handling above and below.)
matrix grid_orient = The_grid->gmatrix;
vec3d grid_center = The_grid->center;
int grid_nrows = The_grid->nrows;
int grid_ncols = The_grid->ncols;
float grid_square_size = The_grid->square_size;
// activate the localizer hash table
fhash_flush();
// Guard against reentrant ship-editor write-back while the mission is loaded. The guard is released once the mission has stabilized.
Ship_editor_dialog.bypass_all++;
clear_mission(flags & MPF_FAST_RELOAD);
// message 1: required version
@@ -250,6 +262,7 @@ bool CFREDDoc::load_mission(const char *pathname, int flags) {
}
Fred_view_wnd->MessageBox(name);
Ship_editor_dialog.bypass_all--;
create_new_mission();
return false;
}
@@ -392,9 +405,16 @@ bool CFREDDoc::load_mission(const char *pathname, int flags) {
view_pos = Parse_viewer_pos;
view_orient = Parse_viewer_orient;
// restore the editor grid that was preserved above
create_grid(The_grid, &grid_orient.vec.fvec, &grid_orient.vec.rvec, &grid_center, grid_nrows, grid_ncols, grid_square_size);
set_modified(0);
stars_post_level_init();
// mission is fully built and consistent now; allow the editors to repopulate
Ship_editor_dialog.bypass_all--;
recreate_dialogs();
// This hook will allow for scripts to know when a mission has been loaded
+14 -6
View File
@@ -1424,21 +1424,29 @@ int object_check_collision(object *objp, vec3d *p0, vec3d *p1, vec3d *hitpos) {
return 0;
}
// PROP HERE?
if (objp->type == OBJ_PROP) {
if (!Props[objp->instance].has_value())
return 0;
}
if (objp->flags[Object::Object_Flags::Hidden, Object::Object_Flags::Locked_from_editing])
return 0;
if ((Show_ship_models || Show_outlines) && (objp->type == OBJ_SHIP)) {
mc.model_num = Ship_info[Ships[objp->instance].ship_info_index].model_num; // Fill in the model to check
mc.model_instance_num = -1;
} else if ((Show_ship_models || Show_outlines) && (objp->type == OBJ_START)) {
mc.model_num = Ship_info[Ships[objp->instance].ship_info_index].model_num; // Fill in the model to check
if ((Show_ship_models || Show_outlines) && (objp->type == OBJ_SHIP || objp->type == OBJ_START)) {
auto& shp = Ships[objp->instance];
mc.model_num = Ship_info[shp.ship_info_index].model_num; // Fill in the model to check
mc.model_instance_num = shp.model_instance_num;
} else if ((Show_ship_models || Show_outlines) && (objp->type == OBJ_PROP)) {
auto& prp = Props[objp->instance].value();
mc.model_num = Prop_info[prp.prop_info_index].model_num; // Fill in the model to check
mc.model_instance_num = prp.model_instance_num;
} else
return fvi_ray_sphere(hitpos, p0, p1, &objp->pos, (objp->radius > 0.1f) ? objp->radius : LOLLIPOP_SIZE);
mc.model_instance_num = -1;
mc.orient = &objp->orient; // The object's orient
mc.pos = &objp->pos; // The object's position
mc.p0 = p0; // Point 1 of ray to check
+9 -2
View File
@@ -32,6 +32,7 @@
#include "ai/aigoals.h"
#include "ship/ship.h" // for ship names
#include "prop/prop.h" // for prop names
#include "missioneditor/common.h"
#include "MissionGoalsDlg.h"
#include "MissionCutscenesDlg.h"
#include "wing.h"
@@ -2466,7 +2467,7 @@ int query_single_wing_marked()
void CFREDView::OnDisbandWing()
{
if (query_single_wing_marked()) {
remove_wing(cur_wing);
disband_wing(cur_wing);
FREDDoc_ptr->autosave("wing disband");
} else
@@ -2641,6 +2642,9 @@ int CFREDView::global_error_check()
ptr->type = OBJ_SHIP;
Player_starts--;
t--;
ensure_valid_player_start_shipnum();
if (error("Invalid ship type for a player. Ship has been reset to non-player ship.")){
return 1;
}
@@ -3130,7 +3134,9 @@ int CFREDView::global_error_check()
return -1;
}*/
Assert((Player_start_shipnum >= 0) && (Player_start_shipnum < MAX_SHIPS) && (Ships[Player_start_shipnum].objnum >= 0));
if ((Player_start_shipnum < 0) || (Player_start_shipnum >= MAX_SHIPS) || (Ships[Player_start_shipnum].objnum < 0)){
return internal_error("Mission has no valid player start ship");
}
i = global_error_check_player_wings(multi);
if (i){
return i;
@@ -3590,6 +3596,7 @@ int CFREDView::fred_check_sexp(int sexp, int type, const char *location, ...)
return 1;
}
z = 0;
if (Error_checker_checks_potential_issues || Error_checker_checks_potential_issues_once)
z = check_sexp_potential_issues(sexp, &faulty_node, issue_msg);
if (z)
+7 -13
View File
@@ -775,7 +775,7 @@ int create_object(vec3d *pos, int waypoint_instance, bool prop)
int obj, n;
if (prop) {
int prop_class = m_new_prop_type_combo_box.GetCurSel();
int prop_class = m_new_prop_type_combo_box.GetPropClass(m_new_prop_type_combo_box.GetCurSel());
if (prop_class < 0 || prop_class >= prop_info_size())
return -1;
@@ -858,11 +858,16 @@ void create_new_mission()
void reset_mission()
{
// Guard against reentrant ship-editor write-back while the mission is reset.
Ship_editor_dialog.bypass_all++;
clear_mission();
create_player(&vmd_zero_vector, &vmd_identity_matrix);
stars_post_level_init();
Ship_editor_dialog.bypass_all--;
}
void clear_mission(bool fast_reload)
@@ -1235,7 +1240,6 @@ int common_object_delete(int obj)
char msg[255];
const char *name;
int i, z, r, type;
object *objp;
SCP_list<CJumpNode>::iterator jnp;
type = Objects[obj].type;
@@ -1271,17 +1275,7 @@ int common_object_delete(int obj)
ai_do_objects_undocked_stuff(&Objects[obj], dock_get_first_docked_object(&Objects[obj]));
}
if (Player_start_shipnum == i) { // need a new single player start.
objp = GET_FIRST(&obj_used_list);
while (objp != END_OF_LIST(&obj_used_list)) {
if (objp->type == OBJ_START) {
Player_start_shipnum = objp->instance;
break;
}
objp = GET_NEXT(objp);
}
}
ensure_valid_player_start_shipnum(); // may need a new single player start
Player_starts--;
+12
View File
@@ -479,6 +479,11 @@ void sexp_tree::free_node2(int node)
Assert(tree_nodes[node].type != SEXPT_UNUSED);
Assert(total_nodes > 0);
*modified = 1;
// the node is being deleted; let subclasses drop anything attached to its handle
if (tree_nodes[node].handle)
on_node_handle_changed(tree_nodes[node].handle, nullptr);
tree_nodes[node].type = SEXPT_UNUSED;
total_nodes--;
if (tree_nodes[node].child != -1)
@@ -2640,6 +2645,9 @@ void sexp_tree::NodeDelete()
Assert(theNode >= 0);
free_node2(theNode);
// the event-root item is not a tree_nodes entry, so free_node2 above does
// not cover an annotation attached to the event root itself
on_node_handle_changed(item_handle, nullptr);
DeleteItem(item_handle);
*modified = 1;
return;
@@ -4668,6 +4676,10 @@ HTREEITEM sexp_tree::move_branch(HTREEITEM source, HTREEITEM parent, HTREEITEM a
h = insert(GetItemText(source), image1, image2, parent, after);
}
// the item was recreated with a new handle; let subclasses follow it
// (covers both tree_nodes entries and the event-root item, which is not one)
on_node_handle_changed(source, h);
SetItemData(h, GetItemData(source));
child = GetChildItem(source);
while (child) {
+5
View File
@@ -367,6 +367,11 @@ protected:
virtual void NodeReplacePaste();
virtual void NodeAddPaste();
// Notifies that the tree item for a node changed handle (moved/recreated), or
// was deleted (new_handle == nullptr). The event tree overrides this to keep
// event annotations attached to their node. Default: no-op.
virtual void on_node_handle_changed(HTREEITEM /*old_handle*/, HTREEITEM /*new_handle*/) {}
void update_item(HTREEITEM handle);
int load_branch(int index, int parent);
+17 -12
View File
@@ -1258,17 +1258,7 @@ int CShipEditorDlg::update_data(int redraw)
}
}
if (Player_start_shipnum < 0 || Objects[Ships[Player_start_shipnum].objnum].type != OBJ_START) { // need a new single player start.
ptr = GET_FIRST(&obj_used_list);
while (ptr != END_OF_LIST(&obj_used_list)) {
if (ptr->type == OBJ_START) {
Player_start_shipnum = ptr->instance;
break;
}
ptr = GET_NEXT(ptr);
}
}
ensure_valid_player_start_shipnum(); // may need a new single player start
if (modified)
set_modified();
@@ -1518,7 +1508,9 @@ int CShipEditorDlg::update_ship(int ship)
Objects[Ships[ship].objnum].type = OBJ_SHIP;
break;
}
}
ensure_valid_player_start_shipnum();
Update_ship = 1;
return 0;
@@ -2448,12 +2440,22 @@ void CShipEditorDlg::OnSetAsPlayerShip()
if (objp->flags[Object::Object_Flags::Marked]) // there should only be one selected ship
{
// set as player ship
if (objp->type != OBJ_START)
{
Player_starts++;
set_modified();
}
objp->type = OBJ_START;
objp->flags.set(Object::Object_Flags::Player_ship);
}
else
{
// set as regular ship
if (objp->type == OBJ_START)
{
Player_starts--;
set_modified();
}
objp->type = OBJ_SHIP;
objp->flags.remove(Object::Object_Flags::Player_ship);
}
@@ -2461,6 +2463,9 @@ void CShipEditorDlg::OnSetAsPlayerShip()
objp = GET_NEXT(objp);
}
// fix up Player_start_shipnum if it became invalid
ensure_valid_player_start_shipnum();
// finally set editor dialog
m_player_ship.SetCheck(1);
update_map_window();
+5 -2
View File
@@ -227,7 +227,8 @@ int create_wing() {
}
count = 0;
if (Objects[Ships[Player_start_shipnum].objnum].flags[Object::Object_Flags::Marked])
if ((Player_start_shipnum >= 0) && (Player_start_shipnum < MAX_SHIPS) && (Ships[Player_start_shipnum].objnum >= 0)
&& Objects[Ships[Player_start_shipnum].objnum].flags[Object::Object_Flags::Marked])
count = 1;
ptr = GET_FIRST(&obj_used_list);
@@ -442,7 +443,7 @@ void remove_ship_from_wing(int ship, int min) {
rename_ship(ship, buf);
}
void remove_wing(int wing_num)
void disband_wing(int wing_num)
{
int i, total;
object *ptr;
@@ -450,6 +451,8 @@ void remove_wing(int wing_num)
if (check_wing_dependencies(wing_num))
return;
delete_reinforcement(Wings[wing_num].name);
Ship_editor_dialog.bypass_errors = Wing_editor_dialog.bypass_errors = 1;
Ship_editor_dialog.update_data(0);
total = Wings[wing_num].wave_count;
+1 -1
View File
@@ -27,7 +27,7 @@ int create_wing();
*
* @param[in] wing_num Index of the wing
*/
void remove_wing(int wing_num);
void disband_wing(int wing_num);
/**
* @brief Takes a ship out of a wing, deleting the wing if that was the only ship in it.
+4 -1
View File
@@ -1078,7 +1078,7 @@ void wing_editor::OnDeleteWing()
void wing_editor::OnDisbandWing()
{
modified = 0; // no need to run update checks, since wing will be gone shortly anyway.
remove_wing(cur_wing);
disband_wing(cur_wing);
}
void wing_editor::OnGoals2()
@@ -1475,6 +1475,9 @@ void wing_editor::OnWingFormationAlign()
get_absolute_wing_pos(&objp->pos, leader_objp, cur_wing, i, false);
objp->orient = leader_objp->orient;
// drag any docked partners along (no-op for undocked ships)
object_moved(objp);
}
// roll back temporary formation
+4 -4
View File
@@ -984,7 +984,7 @@ void game_level_close()
particle::ParticleManager::get()->clearSources();
particle::close();
trail_level_close();
ship_close_cockpit_displays(Player_ship);
ship_level_close();
hud_level_close();
hud_escort_clear_all();
model_instance_free_all();
@@ -2306,15 +2306,15 @@ void game_show_framerate()
MEMORYSTATUSEX mem_stats;
mem_stats.dwLength = sizeof(mem_stats);
if (GlobalMemoryStatusEx(&mem_stats)) {
sprintf(mem_buffer, "Physical Free: %" PRIu64 " / %" PRIu64 " Meg", mem_stats.ullAvailPhys / 1024 / 1024, mem_stats.ullTotalPhys / 1024 / 1024);
sprintf(mem_buffer, "Physical Free: " UINT64_T_ARG " / " UINT64_T_ARG " Meg", mem_stats.ullAvailPhys / 1024 / 1024, mem_stats.ullTotalPhys / 1024 / 1024);
gr_string(sx, sy, mem_buffer.c_str(), GR_RESIZE_NONE);
sy += line_height;
sprintf(mem_buffer, "Pagefile Free: %" PRIu64 " / %" PRIu64 " Meg", mem_stats.ullAvailPageFile / 1024 / 1024, mem_stats.ullTotalPageFile / 1024 / 1024);
sprintf(mem_buffer, "Pagefile Free: " UINT64_T_ARG " / " UINT64_T_ARG " Meg", mem_stats.ullAvailPageFile / 1024 / 1024, mem_stats.ullTotalPageFile / 1024 / 1024);
gr_string(sx, sy, mem_buffer.c_str(), GR_RESIZE_NONE);
sy += line_height;
sprintf(mem_buffer, "Virtual Free: %" PRIu64 " / %" PRIu64 " Meg", mem_stats.ullAvailVirtual / 1024 / 1024, mem_stats.ullTotalVirtual / 1024 / 1024);
sprintf(mem_buffer, "Virtual Free: " UINT64_T_ARG " / " UINT64_T_ARG " Meg", mem_stats.ullAvailVirtual / 1024 / 1024, mem_stats.ullTotalVirtual / 1024 / 1024);
gr_string(sx, sy, mem_buffer.c_str(), GR_RESIZE_NONE);
}
}
+4 -13
View File
@@ -973,7 +973,6 @@ int Editor::common_object_delete(int obj) {
char msg[255];
const char *name;
int i, z, r, type;
object* objp;
SCP_list<CJumpNode>::iterator jnp;
type = Objects[obj].type;
@@ -1012,17 +1011,7 @@ int Editor::common_object_delete(int obj) {
ai_do_objects_undocked_stuff(&Objects[obj], dock_get_first_docked_object(&Objects[obj]));
}
if (Player_start_shipnum == i) { // need a new single player start.
objp = GET_FIRST(&obj_used_list);
while (objp != END_OF_LIST(&obj_used_list)) {
if (objp->type == OBJ_START) {
Player_start_shipnum = objp->instance;
break;
}
objp = GET_NEXT(objp);
}
}
ensure_valid_player_start_shipnum(); // may need a new single player start
Player_starts--;
@@ -1562,7 +1551,7 @@ int Editor::set_reinforcement(const char* name, int state) {
return 0;
}
void Editor::remove_wing(int wing_num) {
void Editor::disband_wing(int wing_num) {
int i, total;
object* ptr;
@@ -1571,6 +1560,8 @@ void Editor::remove_wing(int wing_num) {
return;
}
delete_reinforcement(Wings[wing_num].name);
total = Wings[wing_num].wave_count;
for (i = 0; i < total; i++) {
ptr = &Objects[wing_objects[wing_num][i]];
Loaded 100 of 116 files, more files were not shown because too many files have changed in this diff. Show more