From 2052dcaeb066d7d9d76abebd5bae48e1be701789 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Fri, 9 Oct 2026 00:31:27 -0400 Subject: [PATCH] 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 --- code/ai/ai.h | 1 + code/ai/ai_flags.h | 5 +++++ code/ai/ai_profiles.cpp | 3 +++ code/ai/aibig.cpp | 20 +++++++++++++++----- code/ai/aicode.cpp | 39 ++++++++++++++++++++++++++++++++++++++- code/ai/aigoals.cpp | 17 ++++++++++++++++- 6 files changed, 78 insertions(+), 7 deletions(-) diff --git a/code/ai/ai.h b/code/ai/ai.h index 5e43d79ccc..c1744c1c67 100644 --- a/code/ai/ai.h +++ b/code/ai/ai.h @@ -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); diff --git a/code/ai/ai_flags.h b/code/ai/ai_flags.h index 42a54b4f95..d2d6810fb5 100644 --- a/code/ai/ai_flags.h +++ b/code/ai/ai_flags.h @@ -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 }; diff --git a/code/ai/ai_profiles.cpp b/code/ai/ai_profiles.cpp index 07c5dbe048..28c3bbefbf 100644 --- a/code/ai/ai_profiles.cpp +++ b/code/ai/ai_profiles.cpp @@ -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); } } diff --git a/code/ai/aibig.cpp b/code/ai/aibig.cpp index 8b1cae8462..bc7ee714c9 100644 --- a/code/ai/aibig.cpp +++ b/code/ai/aibig.cpp @@ -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; diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index ef949fe97d..34a2b6100d 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -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; diff --git a/code/ai/aigoals.cpp b/code/ai/aigoals.cpp index 4b8439950c..56e5c10de2 100644 --- a/code/ai/aigoals.cpp +++ b/code/ai/aigoals.cpp @@ -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 current_goal_backup; auto current_goal_target_ship = current_goal->target_name ? ship_registry_get(current_goal->target_name) : nullptr;