apply $fix ai target recovery's pursuit filter only to targets picked for pursuit

The actively-pursues filter added to "$fix ai target recovery:" in #7736 was applied to every target search.  But a ship's target is not only used for pursuit: it also drives the ship's turrets, other ships' guard reactions, and attacker counts.  In retail SM1-04, the friendly Knossos (which does not pursue fighters) was made to target the Behemoth instead of a Mara, which caused the Mara guards to react to the protected Knossos endlessly instead of rushing the player.

The filter now applies only where the target is chosen for maneuvering:
* find_enemy() and get_nearest_objnum() take a required for_pursuit parameter; automatic searches pass ai_targets_for_pursuit(), which is true only in AIM_CHASE or AIM_STRAFE
* guard reactions, retaliation (dynamic chase), and the retargets in ai_big_chase_attack() and ai_big_maybe_enter_strafe_mode() skip ships the attacker won't pursue, via the new ai_declines_pursuit()
* ai_attack_wing() prefers living wing members the ship will pursue; if there are none, it holds a member as an explicit order to attack an unpursued ship does
* get_nearest_objnum() holds an unpursued member of the enemy wing rather than leaving the wing

Since refused targets are no longer picked for pursuit, ai_chase() no longer needs to drop them.  Retail behavior is unchanged when the flag is disabled.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Goober5000andClaude Opus 5.5 committed 2026-10-12 21:26:53 -04:00
1 parent 6e469a2180
commit 1af0eb82bf
4 files changed
+81 -41

No files matched your search

+4 -2
View File
@@ -598,7 +598,9 @@ extern int ai_maybe_fire_afterburner(object *objp, ai_info *aip);
extern void set_predicted_enemy_pos(vec3d *predicted_enemy_pos, object *pobjp, vec3d *enemy_pos, vec3d *enemy_vel, ai_info *aip);
extern int is_instructor(object *objp);
extern int find_enemy(int objnum, float range, int max_attackers, int ship_info_index = -1, int class_type = -1);
extern int find_enemy(int objnum, float range, int max_attackers, bool for_pursuit, int ship_info_index = -1, int class_type = -1);
extern bool ai_targets_for_pursuit(const ai_info *aip);
extern bool ai_declines_pursuit(const object *attacker_objp, const object *target_objp);
float ai_get_weapon_speed(const ship_weapon *swp);
void set_predicted_enemy_pos_turret(vec3d *predicted_enemy_pos, const vec3d *gun_pos, const object *pobjp, const vec3d *enemy_pos, const vec3d *enemy_vel, float weapon_speed, float time_enemy_in_range);
@@ -619,7 +621,7 @@ extern float dock_orient_and_approach(object *docker_objp, int docker_index, obj
void ai_set_mode_warp_out(object *objp, ai_info *aip);
// prototyped by Goober5000
int get_nearest_objnum(int objnum, int enemy_team_mask, int enemy_wing, float range, int max_attackers, int ship_info_index, int class_type = -1);
int get_nearest_objnum(int objnum, int enemy_team_mask, int enemy_wing, float range, int max_attackers, bool for_pursuit, int ship_info_index, int class_type = -1);
// moved to header file by Goober5000
void ai_announce_ship_dying(object *dying_objp);
+3 -2
View File
@@ -178,8 +178,9 @@ namespace AI {
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
// c) target selection for pursuit (chasing, guard reactions, retaliation) skips ships outside the attacker's
// actively-pursues list; targets held while not chasing are unaffected, and explicitly ordered targets are
// still assigned but not chased or strafed;
// d) enemy_wing is reset when a new order takes over (shared with Fix_stale_ai_order_state)
Standard_strafe_used_more,
Unify_usage_ai_shield_manage_delay,
+7 -7
View File
@@ -716,7 +716,8 @@ void ai_big_chase_attack(ai_info *aip, ship_info *sip, vec3d *enemy_pos, float d
if (vm_vec_dot(&in_vec, &objp->orient.vec.fvec) > 0.0f) {
dist = vm_vec_normalize(&in_vec);
if ((dist < 200.0f) && (vm_vec_dot(&in_vec, &objp->orient.vec.fvec) > 0.95f)) {
if ((Objects[objp->parent].signature == objp->parent_sig) && (vm_vec_dist_quick(&objp->pos, &Objects[objp->parent].pos) < 300.0f)) {
if ((Objects[objp->parent].signature == objp->parent_sig) && (vm_vec_dist_quick(&objp->pos, &Objects[objp->parent].pos) < 300.0f)
&& !ai_declines_pursuit(Pl_objp, &Objects[objp->parent])) {
set_target_objnum(aip, objp->parent);
aip->submode = SM_ATTACK;
aip->submode_start_time = Missiontime;
@@ -1118,7 +1119,7 @@ void ai_big_chase()
} else if (En_objp->flags[Object::Object_Flags::Protected]) { // If protected and we're not attacking a subsystem, stop attacking!
update_aspect_lock_information(aip, &vec_to_enemy, dist_to_enemy - En_objp->radius, En_objp->radius);
aip->target_objnum = -1;
if (find_enemy(OBJ_INDEX(Pl_objp), MAX_ENEMY_DISTANCE, The_mission.ai_profile->max_attackers[Game_skill_level]) == -1) {
if (find_enemy(OBJ_INDEX(Pl_objp), MAX_ENEMY_DISTANCE, The_mission.ai_profile->max_attackers[Game_skill_level], ai_targets_for_pursuit(aip)) == -1) {
ai_do_default_behavior(Pl_objp);
return;
}
@@ -1978,19 +1979,18 @@ int ai_big_maybe_enter_strafe_mode(const object *pl_objp, int weapon_objnum)
return 0;
}
if (ai_declines_pursuit(pl_objp, parent_objp)) {
return 0;
}
// Maybe the ship which fired the weapon isn't the current target
if ( OBJ_INDEX(parent_objp) != aip->target_objnum ) {
//JAS IMPOSSIBLE if (1) { // consider_target_only ) {
//JAS IMPOSSIBLE return 0;
//JAS IMPOSSIBLE } else {
// switch targets
sip = &Ship_info[Ships[parent_objp->instance].ship_info_index];
if ( !(sip->is_big_or_huge()) || (sip->flags[Ship::Info_Flags::Transport]) ) {
return 0;
}
set_target_objnum(aip, OBJ_INDEX(parent_objp));
//JAS IMPOSSIBLE }
}
ai_big_strafe_maybe_attack_turret(pl_objp, weapon_objp);
+67 -30
View File
@@ -2215,16 +2215,35 @@ static bool ai_class_type_actively_pursues(int attacker_class_type, int target_c
return std::any_of(pursues.begin(), pursues.end(), [target_class_type](int pursued_type) { return pursued_type == target_class_type; });
}
// Returns true if a target this ship picks for itself will be used for pursuit, and therefore applicable to the "actively_pursues" check.
// In other modes a ship's target is held for its turrets, and is also read by other ships (guard reactions, attacker counts).
bool ai_targets_for_pursuit(const ai_info *aip)
{
return aip->mode == AIM_CHASE || aip->mode == AIM_STRAFE;
}
// Returns true if attacker_objp should not pick target_objp as a target to pursue.
bool ai_declines_pursuit(const object *attacker_objp, const object *target_objp)
{
Assertion(attacker_objp->type == OBJ_SHIP, "ai_declines_pursuit() was called with an attacker of object type %d; it must be a ship!", attacker_objp->type);
if (!The_mission.ai_profile->flags[AI::Profile_Flags::Fix_ai_target_recovery] || target_objp->type != OBJ_SHIP)
return false;
return !ai_class_type_actively_pursues(Ship_info[Ships[attacker_objp->instance].ship_info_index].class_type,
Ship_info[Ships[target_objp->instance].ship_info_index].class_type);
}
typedef struct eval_nearest_objnum {
int objnum;
object *trial_objp;
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;
bool for_pursuit;
int nearest_objnum;
float nearest_dist;
int check_danger_weapon_objnum;
@@ -2280,11 +2299,10 @@ 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.
// If pursuing, don't pick a target that this ship's type refuses to pursue, 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))
if (eno->for_pursuit && eno->enemy_ship_info_index < 0 && eno->enemy_class_type < 0
&& ai_declines_pursuit(&Objects[eno->objnum], eno->trial_objp))
return;
// Allow targeting of stealth in nebula by his firing at me
@@ -2352,10 +2370,11 @@ void evaluate_object_as_nearest_objnum(eval_nearest_objnum *eno)
* @param enemy_wing Enemy wing chosen
* @param range Ship must be within range "range".
* @param max_attackers Don't attack a ship that already has at least max_attackers attacking it.
* @param for_pursuit True if the ship will chase the enemy found; false if the target is only held (e.g. for turrets)
* @param ship_info_index If >=0, the enemy object must be of the specified ship class
* @param class_type If >=0, the enemy object must be of the specified ship type
*/
int get_nearest_objnum(int objnum, int enemy_team_mask, int enemy_wing, float range, int max_attackers, int ship_info_index, int class_type)
int get_nearest_objnum(int objnum, int enemy_team_mask, int enemy_wing, float range, int max_attackers, bool for_pursuit, int ship_info_index, int class_type)
{
object *danger_weapon_objp;
ai_info *aip;
@@ -2366,9 +2385,9 @@ 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.for_pursuit = for_pursuit;
eno.objnum = objnum;
eno.range = range;
eno.nearest_dist = range;
@@ -2409,10 +2428,17 @@ int get_nearest_objnum(int objnum, int enemy_team_mask, int enemy_wing, float ra
}
}
// If no wing member can be pursued, hold one anyway rather than leave the wing, as an explicit order to attack an unpursued ship does
if ((eno.nearest_objnum == -1) && (enemy_wing != -1) && for_pursuit && The_mission.ai_profile->flags[AI::Profile_Flags::Fix_ai_target_recovery]) {
int held_objnum = get_nearest_objnum(objnum, enemy_team_mask, enemy_wing, range, max_attackers, false, ship_info_index, class_type);
if (held_objnum >= 0 && Objects[held_objnum].type == OBJ_SHIP && Ships[Objects[held_objnum].instance].wingnum == enemy_wing)
return held_objnum;
}
// If only looking for target in certain wing and couldn't find anything in
// that wing, look for any object.
if ((eno.nearest_objnum == -1) && (enemy_wing != -1)) {
return get_nearest_objnum(objnum, enemy_team_mask, -1, range, max_attackers, ship_info_index, class_type);
return get_nearest_objnum(objnum, enemy_team_mask, -1, range, max_attackers, for_pursuit, ship_info_index, class_type);
}
return eno.nearest_objnum;
@@ -2519,10 +2545,11 @@ int get_enemy_timestamp()
* @param objnum Object number
* @param range Range within which to look
* @param max_attackers Don't attack a ship that already has at least max_attackers attacking it.
* @param for_pursuit True if the ship will chase the enemy found; false if the target is only held (e.g. for turrets)
* @param ship_info_index If specified, restrict the search to enemies with this ship class
* @param class_type If specified, restrict the search to enemies with this ship type
*/
int find_enemy(int objnum, float range, int max_attackers, int ship_info_index, int class_type)
int find_enemy(int objnum, float range, int max_attackers, bool for_pursuit, int ship_info_index, int class_type)
{
int enemy_team_mask;
@@ -2552,10 +2579,7 @@ int find_enemy(int objnum, float range, int max_attackers, int ship_info_index,
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)) {
if (!for_pursuit || ship_info_index >= 0 || class_type >= 0 || !ai_declines_pursuit(objp, target_objp)) {
return target_objnum;
}
}
@@ -2568,7 +2592,7 @@ int find_enemy(int objnum, float range, int max_attackers, int ship_info_index,
}
}
return get_nearest_objnum(objnum, enemy_team_mask, aip->enemy_wing, range, max_attackers, ship_info_index, class_type);
return get_nearest_objnum(objnum, enemy_team_mask, aip->enemy_wing, range, max_attackers, for_pursuit, ship_info_index, class_type);
} else {
aip->target_objnum = -1;
aip->target_signature = -1;
@@ -2642,7 +2666,7 @@ void ai_attack_object(object* attacker, object* attacked, int ship_info_index, i
if (attacked == nullptr) {
aip->choose_enemy_timestamp = timestamp(0);
// nebula safe
set_target_objnum(aip, find_enemy(OBJ_INDEX(attacker), 99999.9f, 4, ship_info_index, class_type));
set_target_objnum(aip, find_enemy(OBJ_INDEX(attacker), 99999.9f, 4, true, ship_info_index, class_type));
} else {
// check if we can see attacked in nebula
if (aip->target_objnum != OBJ_INDEX(attacked)) {
@@ -2703,10 +2727,24 @@ void ai_attack_wing(object *attacker, int wingnum)
if (count > 0) {
int index;
index = (int) (frand() * count);
// prefer wing members we will pursue; if there are none, hold any member, as an explicit order to attack an unpursued ship does
SCP_vector<int> pursued_indexes;
if (The_mission.ai_profile->flags[AI::Profile_Flags::Fix_ai_target_recovery]) {
for (int i = 0; i < count; i++) {
auto member_shipp = &Ships[Wings[wingnum].ship_index[i]];
if (!member_shipp->flags[Ship::Ship_Flags::Dying] && !ai_declines_pursuit(attacker, &Objects[member_shipp->objnum]))
pursued_indexes.push_back(i);
}
}
if (index >= count)
index = 0;
if (!pursued_indexes.empty()) {
index = pursued_indexes[Random::next(sz2i(pursued_indexes.size()))];
} else {
index = (int) (frand() * count);
if (index >= count)
index = 0;
}
set_target_objnum(aip, Ships[Wings[wingnum].ship_index[index]].objnum);
@@ -9051,15 +9089,6 @@ 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;
}
@@ -10458,6 +10487,10 @@ void guard_object_was_hit(object *guard_objp, object *hitter_objp)
if (Ships[guard_objp->instance].team == Ships[hitter_objp->instance].team)
return;
// Keep guarding rather than chase a ship we won't pursue
if (ai_declines_pursuit(guard_objp, hitter_objp))
return;
// limit the number of ships attacking hitter_objnum (for now, only if hitter_objnum is player)
if ( ai_maybe_limit_attackers(hitter_objnum) == 1 ) {
return;
@@ -15499,7 +15532,7 @@ void ai_frame(int objnum)
} else if (aip->resume_goal_time == -1) {
// AL 12-9-97: Don't allow cargo and navbuoys to set their aip->target_objnum
if ( Ship_info[shipp->ship_info_index].class_type > -1 && (Ship_types[Ship_info[shipp->ship_info_index].class_type].flags[Ship::Type_Info_Flags::AI_auto_attacks]) ) {
target_objnum = find_enemy(objnum, MAX_ENEMY_DISTANCE, The_mission.ai_profile->max_attackers[Game_skill_level]); // Attack up to 2.5K units away.
target_objnum = find_enemy(objnum, MAX_ENEMY_DISTANCE, The_mission.ai_profile->max_attackers[Game_skill_level], ai_targets_for_pursuit(aip)); // Attack up to 2.5K units away.
if (target_objnum != -1) {
if (aip->target_objnum != target_objnum)
aip->aspect_locked_time = 0.0f;
@@ -15583,7 +15616,7 @@ void ai_frame(int objnum)
if ((aip->resume_goal_time > 0) && (aip->resume_goal_time < Missiontime)) {
aip->active_goal = AI_ACTIVE_GOAL_NONE;
aip->resume_goal_time = -1;
target_objnum = find_enemy(objnum, 2000.0f, The_mission.ai_profile->max_attackers[Game_skill_level]);
target_objnum = find_enemy(objnum, 2000.0f, The_mission.ai_profile->max_attackers[Game_skill_level], ai_targets_for_pursuit(aip));
if (target_objnum != -1) {
if (aip->target_objnum != target_objnum) {
aip->aspect_locked_time = 0.0f;
@@ -16106,7 +16139,7 @@ void ai_do_default_behavior(object *obj)
// fighters automatically chase things
if (!is_instructor(obj) && (sip->is_fighter_bomber()))
{
int enemy_objnum = find_enemy(OBJ_INDEX(obj), 1000.0f, The_mission.ai_profile->max_attackers[Game_skill_level]);
int enemy_objnum = find_enemy(OBJ_INDEX(obj), 1000.0f, The_mission.ai_profile->max_attackers[Game_skill_level], true);
set_target_objnum(aip, enemy_objnum);
aip->mode = AIM_CHASE;
aip->submode = SM_ATTACK;
@@ -16328,6 +16361,10 @@ void maybe_set_dynamic_chase(ai_info *aip, int hitter_objnum)
return;
}
if (ai_declines_pursuit(&Objects[Ships[aip->shipnum].objnum], &Objects[hitter_objnum])) {
return;
}
if (aip->target_objnum != hitter_objnum)
aip->aspect_locked_time = 0.0f;
set_target_objnum(aip, hitter_objnum);