reset stale AI order state and track previous_mode through dynamic chases

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]>
This commit is contained in:
Goober5000andClaude Fable 5.1 committed 2026-10-12 20:09:29 -04:00
1 parent a1250d7168
commit 2052dcaeb0
6 files changed
+78 -7

No files matched your search

+1
View File
@@ -535,6 +535,7 @@ extern void ai_ignore_wing(object *ignorer, int wingnum);
extern void ai_dock_with_object(object *docker, int docker_index, object *dockee, int dockee_index, int dock_type);
extern void ai_stay_still(object *still_objp, vec3d *view_pos);
extern void ai_do_default_behavior(object *obj);
extern void ai_reset_order_state(ai_info *aip);
extern void ai_start_waypoints(object *objp, int wl_index, int wp_flags, int start_index, bool force = false);
extern void ai_ship_hit(object *objp_ship, object *hit_objp, const vec3d *hit_normal);
extern void ai_ship_destroy(int shipnum);
+5
View File
@@ -180,6 +180,7 @@ namespace AI {
// 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
// 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,
Fix_AI_shield_management_bug,
@@ -202,6 +203,10 @@ namespace AI {
Fix_small_ai_recover_after_engines_repaired, // ensure small ship AI can switch back to useful AI modes if engines get repaired
Fix_model_path_refresh_randomization, // a subsystem path keeps its randomized point when the path is refreshed because the target moved
Fix_bay_speed_ramp, // the fighterbay speed ramp covers only the bay path itself, not points that route a departing ship around the carrier, and never exceeds max speed
Fix_stale_ai_order_state, // a) enemy_wing, guard_*, previous_mode, resume_goal_time, and the targeted subsystem (with its path) are reset when a new order takes over (enemy_wing is also reset under Fix_ai_target_recovery);
// b) a dynamic chase saves previous_mode/previous_submode; it and strafe entry leave them alone during an ongoing dynamic goal (evading a weapon still overwrites them, which is harmless since the evade resume re-processes orders);
// c) resuming guard duty discards any subsystem target and path left over from the interruption;
// d) a ship ordered to guard itself, or to guard a wing it leads (including one that becomes leader mid-mission), treats that goal as unachievable instead of holding it forever
NUM_VALUES
};
+3
View File
@@ -771,6 +771,8 @@ void parse_ai_profiles_tbl(const char *filename)
set_flag(profile, "$fix fighterbay speed ramp:", AI::Profile_Flags::Fix_bay_speed_ramp);
set_flag(profile, "$fix stale ai order state:", AI::Profile_Flags::Fix_stale_ai_order_state);
// end of options ----------------------------------------
// if we've been through once already and are at the same place, force a move
@@ -1007,5 +1009,6 @@ void ai_profile_t::reset()
flags.set(AI::Profile_Flags::Fix_ai_target_recovery);
flags.set(AI::Profile_Flags::Fix_model_path_refresh_randomization);
flags.set(AI::Profile_Flags::Fix_bay_speed_ramp);
flags.set(AI::Profile_Flags::Fix_stale_ai_order_state);
}
}
+15 -5
View File
@@ -66,6 +66,16 @@ static bool ai_big_strafe_maybe_retreat(const vec3d *target_pos);
extern void compute_desired_rvec(vec3d *rvec, const vec3d *goal_pos, const vec3d *cur_pos);
extern void big_ship_collide_recover_start(const object *objp, const object *big_objp, const vec3d *collision_normal);
// Record the mode being left as the one to return to, unless a dynamic goal is already in progress
// (in which case previous_mode still refers to the mode that goal interrupted).
static void ai_big_save_previous_mode(ai_info *aip)
{
if (The_mission.ai_profile->flags[AI::Profile_Flags::Fix_stale_ai_order_state] && aip->active_goal == AI_ACTIVE_GOAL_DYNAMIC)
return;
aip->previous_mode = aip->mode;
}
// Called by ai_big_pick_attack_point.
// Generates a random attack point.
@@ -499,7 +509,7 @@ bool ai_big_maybe_follow_subsys_path(bool do_dot_check)
// for now, only follow the path to the first point
if ( aip->path_cur < (aip->path_start+aip->path_length-1) ) {
if ( aip->goal_objnum != aip->target_objnum ) {
aip->previous_mode = aip->mode;
ai_big_save_previous_mode(aip);
aip->mode = AIM_NONE;
aip->submode = -1;
aip->submode_start_time = Missiontime;
@@ -727,7 +737,7 @@ void ai_big_chase_attack(ai_info *aip, ship_info *sip, vec3d *enemy_pos, float d
// including ai_profile flag and if enemy fighters are near
if (Pl_objp->phys_info.speed < The_mission.ai_profile->standard_strafe_when_below_speed &&
(The_mission.ai_profile->flags[AI::Profile_Flags::Standard_strafe_used_more] || ai_big_maybe_start_strafe(aip, sip))) {
aip->previous_mode = aip->mode;
ai_big_save_previous_mode(aip);
aip->mode = AIM_STRAFE;
aip->submode_parm0 = Missiontime; // use parm0 as time strafe mode entered (i.e. MODE start time)
ai_big_strafe_position();
@@ -736,7 +746,7 @@ void ai_big_chase_attack(ai_info *aip, ship_info *sip, vec3d *enemy_pos, float d
//Maybe enter glide strafe (check every 8 seconds, on a different schedule for each ship)
if ((sip->can_glide == true) && !(aip->ai_flags[AI::AI_Flags::Kamikaze]) && static_randf((Missiontime + static_rand(aip->shipnum)) >> 19) < aip->ai_glide_strafe_percent) {
aip->previous_mode = aip->mode;
ai_big_save_previous_mode(aip);
aip->mode = AIM_STRAFE;
aip->submode_parm0 = Missiontime; // use parm0 as time strafe mode entered (i.e. MODE start time)
aip->submode = AIS_STRAFE_GLIDE_ATTACK;
@@ -1008,7 +1018,7 @@ static void ai_big_maybe_fire_weapons(float dist_to_enemy, float dot_to_enemy)
// switch ai ship into chase mode
void ai_big_switch_to_chase_mode(ai_info *aip)
{
aip->previous_mode = aip->mode;
ai_big_save_previous_mode(aip);
aip->mode = AIM_CHASE;
aip->submode = SM_ATTACK;
aip->submode_start_time = Missiontime;
@@ -1987,7 +1997,7 @@ int ai_big_maybe_enter_strafe_mode(const object *pl_objp, int weapon_objnum)
// if we've got this far, the weapon must have come from the player's target, and it is a
// big/capital ship... so enter strafe mode
aip->previous_mode = aip->mode;
ai_big_save_previous_mode(aip);
aip->mode = AIM_STRAFE;
aip->submode_parm0 = Missiontime; // use parm0 as time strafe mode entered (i.e. MODE start time)
aip->submode = AIS_STRAFE_AVOID;
+38 -1
View File
@@ -5185,6 +5185,8 @@ int maybe_resume_previous_mode(object *objp, ai_info *aip)
aip->submode = AIS_GUARD_PATROL;
aip->submode_start_time = Missiontime;
aip->active_goal = AI_ACTIVE_GOAL_NONE;
if (The_mission.ai_profile->flags[AI::Profile_Flags::Fix_stale_ai_order_state])
set_targeted_subsys(aip, nullptr, -1); // also discards any subsystem path from attacking a big ship
return 1;
}
}
@@ -8350,6 +8352,7 @@ void ai_set_guard_wing(object *objp, int wingnum)
// prevents a ship from trying to guard a non-existent wing.
if (Wings[wingnum].current_count < 1) {
aip->guard_objnum = -1;
aip->guard_signature = -1;
aip->guard_wingnum = -1;
aip->mode = AIM_NONE;
} else {
@@ -16059,6 +16062,29 @@ void init_aip_from_class_and_profile(ai_info *aip, ai_class *aicp, ai_profile_t
aip->ai_profile_flags = profile->flags | (aicp->ai_profile_flags & aicp->ai_profile_flags_set);
}
// Clear the fields that remember which order set them up or which order a dynamic goal interrupted.
// Call when a new order takes over; the order's own setup repopulates whatever it needs.
void ai_reset_order_state(ai_info *aip)
{
if (The_mission.ai_profile->flags[AI::Profile_Flags::Fix_stale_ai_order_state]
|| The_mission.ai_profile->flags[AI::Profile_Flags::Fix_ai_target_recovery])
aip->enemy_wing = -1;
if (The_mission.ai_profile->flags[AI::Profile_Flags::Fix_stale_ai_order_state])
{
aip->guard_objnum = -1;
aip->guard_signature = -1;
aip->guard_wingnum = -1;
aip->previous_mode = AIM_NONE;
aip->previous_submode = 0;
aip->resume_goal_time = -1;
// the player's targeted subsystem is HUD state, not order state
if (aip != Player_ai || Player_use_ai)
set_targeted_subsys(aip, nullptr, -1); // also discards any subsystem path
}
}
void ai_do_default_behavior(object *obj)
{
Assertion(obj != nullptr, "Ai_do_default_behavior() was given a bad information. Specifically obj is a nullptr. This is a coder error, please report!");
@@ -16072,6 +16098,7 @@ void ai_do_default_behavior(object *obj)
aip->mode = AIM_NONE;
aip->submode_start_time = Missiontime;
aip->active_goal = AI_ACTIVE_GOAL_NONE;
ai_reset_order_state(aip);
// if we're not docked, we may modify the behavior a bit
if (!object_is_docked(obj))
@@ -16304,12 +16331,22 @@ void maybe_set_dynamic_chase(ai_info *aip, int hitter_objnum)
if (aip->target_objnum != hitter_objnum)
aip->aspect_locked_time = 0.0f;
set_target_objnum(aip, hitter_objnum);
if (The_mission.ai_profile->flags[AI::Profile_Flags::Fix_stale_ai_order_state]) {
// don't bash the mode we're returning to if this is a dynamic goal interrupting another one
if (aip->active_goal != AI_ACTIVE_GOAL_DYNAMIC) {
aip->previous_mode = aip->mode;
aip->previous_submode = aip->submode;
}
} else {
aip->previous_submode = aip->mode; // retail typo, almost certainly meant to be previous_mode
}
aip->resume_goal_time = Missiontime + i2f(20); // Only chase up to 20 seconds.
aip->active_goal = AI_ACTIVE_GOAL_DYNAMIC;
set_targeted_subsys(aip, NULL, -1); // Say not attacking any particular subsystem.
aip->previous_submode = aip->mode;
aip->mode = AIM_CHASE;
aip->submode = SM_ATTACK;
aip->submode_start_time = Missiontime;
+16 -1
View File
@@ -1803,6 +1803,20 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
auto target_ship_entry = aigp->target_name == nullptr ? nullptr : ship_registry_get(aigp->target_name);
// a ship can't guard itself, and the leader of a wing is the ship the rest of the wing guards
if (The_mission.ai_profile->flags[AI::Profile_Flags::Fix_stale_ai_order_state])
{
if (aigp->ai_mode == AI_GOAL_GUARD && target_ship_entry && target_ship_entry->has_shipp() && target_ship_entry->shipp() == shipp)
return ai_achievability::NOT_ACHIEVABLE;
if (aigp->ai_mode == AI_GOAL_GUARD_WING)
{
int wingnum = wing_name_lookup(aigp->target_name);
if (wingnum >= 0 && Wings[wingnum].ship_index[0] == objp->instance)
return ai_achievability::NOT_ACHIEVABLE;
}
}
// warp (depart) only achievable if there's somewhere to depart to
if (aigp->ai_mode == AI_GOAL_WARP)
{
@@ -2535,8 +2549,9 @@ void ai_process_mission_orders( int objnum, ai_info *aip )
if ( !(Player_use_ai) && (objp->flags[Object::Object_Flags::Player_ship]) && (current_goal->ai_mode != AI_GOAL_FORM_ON_WING) )
{
return;
}
}
ai_reset_order_state(aip);
std::unique_ptr<ai_goal> current_goal_backup;
auto current_goal_target_ship = current_goal->target_name ? ship_registry_get(current_goal->target_name) : nullptr;