The actively-pursues filter added to "$fix ai target recovery:" in #7736 was applied to every target search. But a ship's target is not only used for pursuit: it also drives the ship's turrets, other ships' guard reactions, and attacker counts. In retail SM1-04, the friendly Knossos (which does not pursue fighters) was made to target the Behemoth instead of a Mara, which caused the Mara guards to react to the protected Knossos endlessly instead of rushing the player.
The filter now applies only where the target is chosen for maneuvering:
* find_enemy() and get_nearest_objnum() take a required for_pursuit parameter; automatic searches pass ai_targets_for_pursuit(), which is true only in AIM_CHASE or AIM_STRAFE
* guard reactions, retaliation (dynamic chase), and the retargets in ai_big_chase_attack() and ai_big_maybe_enter_strafe_mode() skip ships the attacker won't pursue, via the new ai_declines_pursuit()
* ai_attack_wing() prefers living wing members the ship will pursue; if there are none, it holds a member as an explicit order to attack an unpursued ship does
* get_nearest_objnum() holds an unpursued member of the enemy wing rather than leaving the wing
Since refused targets are no longer picked for pursuit, ai_chase() no longer needs to drop them. Retail behavior is unchanged when the flag is disabled.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Several ai_info fields remember which order set them up (enemy_wing,
guard_objnum/signature/wingnum) or which mode a dynamic goal interrupted
(previous_mode, resume_goal_time), but nothing cleared them when a new
order took over. A ship that once guarded a wing would be yanked back
into AIM_GUARD whenever a wingmate died; a stale previous_mode of
AIM_GUARD could make a former guard abandon a counterattack; a stale
enemy_wing biased every find_enemy() toward a long-ago target; and a
resume_goal_time left over from a dynamic chase suppressed target
acquisition for up to 20 seconds into the next order.
Add ai_reset_order_state(), called when ai_process_mission_orders()
takes up a goal and when ai_do_default_behavior() runs. Each order's
own setup repopulates the fields it needs. enemy_wing is reset under
either $fix stale ai order state or $fix ai target recovery, since it
affects target selection; the rest only under the new flag.
maybe_set_dynamic_chase() wrote aip->mode into previous_submode, a
retail typo that left previous_mode untracked for dynamic chases. Under
the flag, save both fields properly, but not when a dynamic goal is
already in progress, so a nested interruption does not clobber the mode
to return to. Apply the same guard to the strafe/chase transitions in
aibig.cpp.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
* Safeguard divide by 0 for extra collision avoidance
Extra collision avoidance could have divided by zero, so just safeguard that possibility.
* one more div 0 failsafe
TLDR: This is a long description, but it boils down to exposing 2 hardcoded AI values and updating 1 value in the optional 'better collision avoidance' behavior allows mods to substantially fix fighters ramming into capital ships they are attacking.
AI accidentally ramming into big ships while trying to evade fire from a hostile turret or a dogfight makes some sense given a chaotic combat situation. What does not seem good though is AI not being able to attack a large stationary target that is not shooting back and has no fighters around. This is especially evident in mods that use the new strafing AI fields, where AI will fly to a distant point then turn around to do an attack run, then once close enough pick another strafing run to conduct. In these situations, AI will commonly not pull up from the attack run in time, especially for mods that use different speeds or AI damp values compared to retail.
Fortunately, there exposing two values to the AI profiles allows modders to tune these values, the strafe retreat time and strafe retreat distance. By default these values are 2 seconds and 100 meters, which is commonly far too short for mods with higher speeds or larger damp values. Exposing these values allows modders to make the AI far more effective and virtually eliminate ramming into stationary ships they are attacking. For example, on current 26.0 with FotG testing we register 2-4 rams of a stationary target per minute. Using the new exposed values in this branch eliminates the ramming (after 10 minutes of firing no ramming occurs across multiple runs of this and other test missions).
Furthermore, this PR helps `better_collision_avoidance_triggered` function (enabled by using `better collision avoidance` in the `ai_profiles.tbl` fix an edge case with predicting collisions. On current master the `delta_time` value used in this function was `0`, which was only ever used eventually downstream in the `will_collide_with_big_ship` function call. This `will_collide_with_big_ship` function had an early return on line 7620:
```
int will_collide_with_big_ship(object *objp, vec3d *goal_point, object *big_objp, vec3d *collision_point, float delta_time)
{
float radius;
vec3d end_pos;
radius = big_objp->radius + delta_time * objp->phys_info.speed;
if (vm_vec_dist_quick(&big_objp->pos, &objp->pos) > radius) {
return 0;
}
```
This early return and the passing of `0` to the `delta_time` from `better_collision_avoidance_triggered` meant that the early out radius distance was only the big object's radius, and did not project any forward time. In other words, better collision avoidance only actually checked or triggered when the small ship was within the radius distance of the large ship. Thus, with ships that had geometry reach right to the edge of the object radius, it could result in the small ship not having enough time to react to avoid a collision. Notably, all other calls to `will_collide_with_big_ship` use a `delta_time` of 5-10. As such, this PR also fixes that edge case by ensuring the better collision avoidance also incorporates some lead travel time by passing a non-zero value for delta time. Given `better_collision_avoidance_triggered` aggression factor is 3.5 by default, it fit well in testing to pass this value to `delta_time`. That aggression factor is already tuneable for mods, so also using it here provides extra adaptability.
Overall these values and updates were tested and substantially remove collision accidents, especially with ships with high speeds and/or higher damps. The PR boils down to exposing two values to the AI profiles table and updating values in the optional 'better collision avoidance' flag, so it will not affect retail. Happy to discuss as well, thanks!
* avoid undefined behavior from UNREACHABLE on reachable code paths
UNREACHABLE expands to __assume(false) / __builtin_unreachable() in
release builds, which is undefined behavior if the code is ever actually
reached. Many UNREACHABLE sites only guard against unexpected-but-
possible runtime states (values from tables, mission/save files, network
packets, scripting, or user input), so reaching them should degrade
gracefully rather than invoke UB.
Replace UNREACHABLE with Assertion(false, ...) at all such sites. It
still pops an error in debug builds, but in release it is a harmless
no-op that simply falls through to the following code. Sites that are
genuinely unreachable by local invariants (e.g. a switch over a bounded
random number) are left as UNREACHABLE.
A few converted sites had no safe fall-through -- an uninitialized read,
null dereference, or null call would have followed -- so they also get a
minimal recovery: initialize the value, guard the dereference, return
early, or skip the offending item.
Where the converted site was a simple guard of the form
if (x) { Assertion(false, ...); return; }
hoist the check onto the invariant instead:
Assertion(!x, ...);
if (x) { return; }
This states the expectation directly and shows the offending value in
the debug popup, while keeping the same graceful runtime degradation.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* addendum
After consultation with Lafiel, use UNREACHABLE in situations that are effectively impossible even if not provably impossible. UNREACHABLE = "this shouldn't ever happen"; Assertion = "if this ever happens, it's a coding error". Also fix a few other sites and change a couple to Warnings.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
* AI Cleanup and Flag Fixes
This PR does 3 things.
1) Cleanup `ship_subsystem_in_sight` so it properly returns a bool, which is the return type of the function. Also update `ai_big_maybe_follow_subsys_path` to be a bool instead of int.
2) Adds `+subsystem attack pathing extra distance:` for use within `"$improved subsystem attack pathing:"`. FotG found, especially with the new strafing values we use, could really use the ability to set at what distance `ai_new_maybe_reposition_attack_subsys()` determines if it wants to pick a new point or not. By default the value was 2000.
3) In FotG we started using the `standard_strafe` mode more now thanks to #6511, but we have now discovered some inherit issues with it, namely that it will prematurely trigger `ai_big_strafe_maybe_retreat` because the distance it uses for determining if a collision is going to occur is `dist_normal_to_target`, which commonly resulted in the AI flying to a point to start a strafe, then thinking it would collide with the target and then retreating from strafe mode, only to realize it was now far enough to re-engage strafe mode, which commonly resulted in AI just flying around not accomplishing much. This PR adds a flag which instead uses the `distance_to_target` and a check to see if the ship is even facing the target.
Overall all are tested and work as expected. Happy to change or tune however requested, especially regarding point 3.
* clang cleanup
* cleanup copy paste name
* remove un-needed exposed float
Currently, the `$better combat collision avoidance for fightercraft:` flag only checks for possible big ship collisions when in normal chase mode (`AIM_CHASE`), but strafing a big ship is a separate AI mode (`AIM_STRAFE`). Per discussion with Asteroth, strafe attacking should also check for better collision avoidance, as the modder likely would expect that to fall under this AI flag. Also took the chance to make the collision avoidance check into a function to improve maintenance and usability.
Most places in the code that use `vm_vector_2_matrix` do so on already-normalized vectors. Changing these to `vm_vector_2_matrix_norm` avoids redundant normalization.
This function call checked the acceleration of the targeted object, rather than the active object as it should have. Fix it to check the active object. For mods that use `$Random Sidethrust Percent:` (such as Inferno), this fixes a bug that could cause an invalid memory access in situations where the targeted object is -1.
* Allow Modders to Tune Strafing
Many mods change ship velocities and damp values, but this commonly leads to poor behaviors attacking large ships within strafe mode (which is the main method for attacking big ships in a sustained way). As an example, FotG has damp and velocity values that are higher than base FS, thus the defined strafe values result in behavior where fighters never can complete a sustained firing pass against a large ship unless they are sitting and parking (which does not fit our goals to match the movie).
The values for dictating how strafe behavior occurs are easily editable by changing some key values, which this PR exposes to modders. Furthermore, mods may give large ships very long range weapons, so being able to set how far away a ship should do a strafe run begins is very valuable.
In testing it was very easy to get desired behavior of having AI do long sustained strafing runs. I estimate other mods will likely be able to leverage these values to tune strafing behavior.
I did numerous tests and evaluations finding the best balance of values to expose. I would be happy to discuss these changes in detail too, as I know this exposes a number of values.
* clang
* incorporate feedback
* Adds particle effect offset.
* bugfixing
* Switches to a cleaner approach.
* Futureproofing for particle parenting.
* actually let's just bundle parenting into this one
* working on turrets
* grinding toward turret correctness
* still grinding toward correctness
* Gets turrets working correctly.
* Fixes merge artifact
* appeases clang-tidy
* Updates some comments.
* Updates some comments.
* Tiny bit of cleanup.
* Deduplicates comments.
`ai_get_weapon` is not guaranteed to return a valid `wip`, so check for that here. This should now cover all uses of `ai_get_weapon` in the codebase.
Fixes an Event Horizon crash reported in Discord. Follow-up to #5842, commit 8dbe0e7.
Add "tactical" versions of disable and disarm goals which only perform the specific task without implying anything else. The target ship is not protected and attack goals are not cleared from other ships.
There is also a new AI profiles flag to make this the default hud squad message command instead of the classic disable/disarm.
This initializes `mc_info` to the correct values when it is constructed, removing the need for `mc_info_init`.
This PR also fixes a group of incorrectly inverted assignments in beam.cpp.
Follow-up to #5040. This extends the checks to most iterations over `obj_used_list` in FSO. There are a few exceptions such as `waypoint_add` and `waypoint_remove`, plus a few others. And none of the object iterations in FRED have the guard because FRED does not use the same object life cycle as FSO.
When iterating through `Ship_obj_list`, check the `Should_be_dead` flag before using the object. This fixes#4990 and adds defense-in-depth checks to guard against similar errors elsewhere.
I considered moving the `Ship_obj_list` removal from `obj_delete` to `ship_cleanup`, but decided it would be prudent to avoid fundamental changes to lifecycle management.
Adds gravity! Applies on a per-mission basis, and weapons, ships, debris, and asteroids now have a configurable "gravity const" which multiplies the effect of gravity on them, by default everything except debris is 0 (meaning no effect even when the mission has gravity). AI and the lead indicator will also lead for ballistic trajectories as well, given a gravity-affected weapon. No FRED support or gravity direction control yet, to come in later PRs.