From 9295d1a796b0e5547b6c4add9d5bb6d79b615c0c Mon Sep 17 00:00:00 2001 From: wookieejedi Date: Wed, 12 Aug 2026 12:57:29 -0400 Subject: [PATCH 1/4] More Fixes for AI Ramming into Stationary Targets TLDR: This is a long description, but it boils down to exposing 2 hardcoded AI values and updating 1 value in the optional 'better collision avoidance' behavior allows mods to substantially fix fighters ramming into capital ships they are attacking. AI accidentally ramming into big ships while trying to evade fire from a hostile turret or a dogfight makes some sense given a chaotic combat situation. What does not seem good though is AI not being able to attack a large stationary target that is not shooting back and has no fighters around. This is especially evident in mods that use the new strafing AI fields, where AI will fly to a distant point then turn around to do an attack run, then once close enough pick another strafing run to conduct. In these situations, AI will commonly not pull up from the attack run in time, especially for mods that use different speeds or AI damp values compared to retail. Fortunately, there exposing two values to the AI profiles allows modders to tune these values, the strafe retreat time and strafe retreat distance. By default these values are 2 seconds and 100 meters, which is commonly far too short for mods with higher speeds or larger damp values. Exposing these values allows modders to make the AI far more effective and virtually eliminate ramming into stationary ships they are attacking. For example, on current 26.0 with FotG testing we register 2-4 rams of a stationary target per minute. Using the new exposed values in this branch eliminates the ramming (after 10 minutes of firing no ramming occurs across multiple runs of this and other test missions). Furthermore, this PR helps `better_collision_avoidance_triggered` function (enabled by using `better collision avoidance` in the `ai_profiles.tbl` fix an edge case with predicting collisions. On current master the `delta_time` value used in this function was `0`, which was only ever used eventually downstream in the `will_collide_with_big_ship` function call. This `will_collide_with_big_ship` function had an early return on line 7620: ``` int will_collide_with_big_ship(object *objp, vec3d *goal_point, object *big_objp, vec3d *collision_point, float delta_time) { float radius; vec3d end_pos; radius = big_objp->radius + delta_time * objp->phys_info.speed; if (vm_vec_dist_quick(&big_objp->pos, &objp->pos) > radius) { return 0; } ``` This early return and the passing of `0` to the `delta_time` from `better_collision_avoidance_triggered` meant that the early out radius distance was only the big object's radius, and did not project any forward time. In other words, better collision avoidance only actually checked or triggered when the small ship was within the radius distance of the large ship. Thus, with ships that had geometry reach right to the edge of the object radius, it could result in the small ship not having enough time to react to avoid a collision. Notably, all other calls to `will_collide_with_big_ship` use a `delta_time` of 5-10. As such, this PR also fixes that edge case by ensuring the better collision avoidance also incorporates some lead travel time by passing a non-zero value for delta time. Given `better_collision_avoidance_triggered` aggression factor is 3.5 by default, it fit well in testing to pass this value to `delta_time`. That aggression factor is already tuneable for mods, so also using it here provides extra adaptability. Overall these values and updates were tested and substantially remove collision accidents, especially with ships with high speeds and/or higher damps. The PR boils down to exposing two values to the AI profiles table and updating values in the optional 'better collision avoidance' flag, so it will not affect retail. Happy to discuss as well, thanks! --- code/ai/ai_profiles.cpp | 10 ++++++++++ code/ai/ai_profiles.h | 2 ++ code/ai/aibig.cpp | 11 ++++------- code/ai/aicode.cpp | 2 +- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/code/ai/ai_profiles.cpp b/code/ai/ai_profiles.cpp index d65ba45ecfc..9210d056774 100644 --- a/code/ai/ai_profiles.cpp +++ b/code/ai/ai_profiles.cpp @@ -693,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); } @@ -858,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; diff --git a/code/ai/ai_profiles.h b/code/ai/ai_profiles.h index 9f03bce4782..ecd6b22fe69 100644 --- a/code/ai/ai_profiles.h +++ b/code/ai/ai_profiles.h @@ -143,6 +143,8 @@ class ai_profile_t { 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() diff --git a/code/ai/aibig.cpp b/code/ai/aibig.cpp index c532a3074ae..344e6d28cc5 100644 --- a/code/ai/aibig.cpp +++ b/code/ai/aibig.cpp @@ -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 @@ -1452,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; @@ -1463,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)) diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index 9136144dae1..241de8e047b 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -7801,7 +7801,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; } From 891a63420c79bfcbb53c8a3e4778bb6050a0fc16 Mon Sep 17 00:00:00 2001 From: wookieejedi Date: Wed, 12 Aug 2026 15:43:19 -0400 Subject: [PATCH 2/4] fix old copy paste bug and power usage --- code/ai/aicode.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index 241de8e047b..246f47dea11 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -7699,8 +7699,9 @@ void mabs_pick_goal_point(object *objp, object *big_objp, vec3d *collision_point int i; for (i=0; i<4; i++) { vec3d p = big_objp->pos; - float ku = big_objp->radius*s + objp->radius * (OBJ_INDEX(objp) % 4)/4; // This objp->radius stuff to prevent ships from glomming together at one point - float kr = big_objp->radius*s + objp->radius * ((OBJ_INDEX(objp) % 4) ^ 2)/4; + float ix = (OBJ_INDEX(objp) % 4); + float ku = big_objp->radius*s + objp->radius * ix/4; // This objp->radius stuff to prevent ships from glomming together at one point + float kr = big_objp->radius*s + objp->radius * (ix*ix)/4; if (i&1) ku = -ku; if (i&2) @@ -7726,8 +7727,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; } From e983dfbe15b1eb2bfefcfb4dba5cc2bc346bf9a2 Mon Sep 17 00:00:00 2001 From: wookieejedi Date: Wed, 12 Aug 2026 16:18:55 -0400 Subject: [PATCH 3/4] clang --- code/ai/aicode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index 246f47dea11..05f09b38959 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -7699,7 +7699,7 @@ void mabs_pick_goal_point(object *objp, object *big_objp, vec3d *collision_point int i; for (i=0; i<4; i++) { vec3d p = big_objp->pos; - float ix = (OBJ_INDEX(objp) % 4); + float ix = i2fl(OBJ_INDEX(objp) % 4); float ku = big_objp->radius*s + objp->radius * ix/4; // This objp->radius stuff to prevent ships from glomming together at one point float kr = big_objp->radius*s + objp->radius * (ix*ix)/4; if (i&1) From d5c371c7a2ccbe1154dbeaf2837f594ffc90612f Mon Sep 17 00:00:00 2001 From: wookieejedi Date: Wed, 12 Aug 2026 16:20:17 -0400 Subject: [PATCH 4/4] clang 2 --- code/ai/aicode.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index 05f09b38959..f6ccbe0b7a1 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -7699,9 +7699,8 @@ void mabs_pick_goal_point(object *objp, object *big_objp, vec3d *collision_point int i; for (i=0; i<4; i++) { vec3d p = big_objp->pos; - float ix = i2fl(OBJ_INDEX(objp) % 4); - float ku = big_objp->radius*s + objp->radius * ix/4; // This objp->radius stuff to prevent ships from glomming together at one point - float kr = big_objp->radius*s + objp->radius * (ix*ix)/4; + float ku = big_objp->radius*s + objp->radius * (OBJ_INDEX(objp) % 4)/4; // This objp->radius stuff to prevent ships from glomming together at one point + float kr = big_objp->radius*s + objp->radius * ((OBJ_INDEX(objp) % 4) ^ 2)/4; if (i&1) ku = -ku; if (i&2)