Skip to content

Commit

Permalink
fix ai behavior when avoiding ship and weapon shockwaves
Browse files Browse the repository at this point in the history
The FreeSpace AI includes behavior to run away from shockwaves to try to avoid explosion damage.  This is implemented for both ships and missiles, although the missile implementation has been buggy ever since retail.  There are several issues:

1. The AI attempts to evade a weapon immediately upon launch.  Many missiles have a period of free flight before they start homing on their target.  During the free flight period, the homing position is not set.  Consequently, the AI will not know the position to avoid and will make assumptions.
2. The AI assumes the shockwave-producing weapon will detonate at the center of the ship it is targeting, although missiles detonate on the surface.  A capital ship is likely to be significantly larger than the weapon's blast radius, leading the AI to believe the detonation will be much farther away than it actually is.
3. When avoiding ship shockwaves, the `shockwave_object` field is not cleared when avoiding is complete.  This can result in stale references (though not crashes, due to object type checks), causing AI to not avoid future explosions in certain cases.
4. When avoiding ship shockwaves, the code does not check the correct ship for the amount of damage caused by the shockwave.

All of these issues are now fixed.  Since this is a change in AI behavior, these fixes are tied to a new AI profiles flag.
  • Loading branch information
Goober5000 committed Oct 21, 2024
1 parent 91a2291 commit 869596d
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 3 deletions.
4 changes: 4 additions & 0 deletions code/ai/ai_flags.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ namespace AI {
Fix_heat_seeker_stealth_bug,
Fix_linked_primary_bug,
Fix_ramming_stationary_targets_bug,
Fix_avoid_shockwave_bugs, // a) waiting until a homing weapon actually homes before evading;
// b) picking the correct expected impact position for capships;
// c) not clearing shockwave_object for ships;
// d) checking the explosion damage of the correct ship
Force_beam_turret_fov,
Free_afterburner_use,
Glide_decay_requires_thrust,
Expand Down
5 changes: 5 additions & 0 deletions code/ai/ai_profiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,8 @@ void parse_ai_profiles_tbl(const char *filename)
stuff_float(&profile->rot_fac_multiplier_ply_collisions);
}

set_flag(profile, "$fix avoid-shockwave bugs:", AI::Profile_Flags::Fix_avoid_shockwave_bugs);

// end of options ----------------------------------------

// if we've been through once already and are at the same place, force a move
Expand Down Expand Up @@ -856,4 +858,7 @@ void ai_profile_t::reset()
flags.set(AI::Profile_Flags::Force_beam_turret_fov);
flags.set(AI::Profile_Flags::Guards_ignore_protected_attackers);
}
if (mod_supports_version(25, 0, 0)) {
flags.set(AI::Profile_Flags::Fix_avoid_shockwave_bugs);
}
}
52 changes: 49 additions & 3 deletions code/ai/aicode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14556,7 +14556,11 @@ static int ai_find_shockwave_ship(object *objp)
shipp = &Ships[A->instance];
// Only look at objects in the process of dying.
if (shipp->flags[Ship::Ship_Flags::Dying]) {
float damage = ship_get_exp_damage(objp);
float damage;
if (The_mission.ai_profile->flags[AI::Profile_Flags::Fix_avoid_shockwave_bugs])
damage = ship_get_exp_damage(A); // A is the object that is actually exploding!
else
damage = ship_get_exp_damage(objp);

if (damage >= EVADE_SHOCKWAVE_DAMAGE_THRESHOLD) { // Only evade quite large blasts
float dist;
Expand All @@ -14576,6 +14580,8 @@ static int ai_find_shockwave_ship(object *objp)

int aas_1(object *objp, ai_info *aip, vec3d *safe_pos)
{
bool fix_bugs = The_mission.ai_profile->flags[AI::Profile_Flags::Fix_avoid_shockwave_bugs];

// MAKE SURE safe_pos DOES NOT TAKE US TOWARD THE A SHIP WE'RE ATTACKING.
if (aip->ai_flags[AI::AI_Flags::Avoid_shockwave_weapon]) {
// If we don't currently know of a weapon to avoid, try to find one.
Expand Down Expand Up @@ -14636,7 +14642,24 @@ int aas_1(object *objp, ai_info *aip, vec3d *safe_pos)
}
}

if (!pos_set) {
if (pos_set) {
// if the object is not a small ship, a surface impact is likely to be some distance away from the ship center, so refine the position
if (fix_bugs && target_ship_obj && !Ship_info[Ships[target_ship_obj->instance].ship_info_index].is_small_ship()) {
mc_info mc;
mc.model_instance_num = Ships[target_ship_obj->instance].model_instance_num;
mc.model_num = Ship_info[Ships[target_ship_obj->instance].ship_info_index].model_num;
mc.orient = &target_ship_obj->orient;
mc.pos = &target_ship_obj->pos;
mc.p0 = &weapon_objp->pos; // Point 1 of ray to check
mc.p1 = &expected_pos; // Point 2 of ray to check
mc.flags = MC_CHECK_MODEL;

model_collide(&mc);
if (mc.num_hits > 0) {
expected_pos = mc.hit_point_world;
}
}
} else {
float time_scale;

if (wip->lifetime - weaponp->lifeleft > 5.0f) {
Expand Down Expand Up @@ -14682,12 +14705,13 @@ int aas_1(object *objp, ai_info *aip, vec3d *safe_pos)
Assert(aip->shockwave_object > -1);
object *ship_objp = &Objects[aip->shockwave_object];
if (ship_objp == objp) {
aip->shockwave_object = -1;
aip->shockwave_object = -1; // this one was already present in retail
return 0;
}

if (ship_objp->type != OBJ_SHIP) {
aip->ai_flags.remove(AI::AI_Flags::Avoid_shockwave_ship);
if (fix_bugs) aip->shockwave_object = -1;
return 0;
}

Expand All @@ -14701,6 +14725,7 @@ int aas_1(object *objp, ai_info *aip, vec3d *safe_pos)

if (vm_vec_dist_quick(&objp->pos, &ship_objp->pos) > outer_rad*1.5f) {
aip->ai_flags.remove(AI::AI_Flags::Avoid_shockwave_ship);
if (fix_bugs) aip->shockwave_object = -1;
return 0;
}

Expand All @@ -14720,16 +14745,37 @@ int aas_1(object *objp, ai_info *aip, vec3d *safe_pos)
int ai_avoid_shockwave(object *objp, ai_info *aip)
{
vec3d safe_pos;
bool fix_bugs = The_mission.ai_profile->flags[AI::Profile_Flags::Fix_avoid_shockwave_bugs];

// BIG|HUGE do not respond to shockwaves
// Goober5000 - let's treat shockwave response the same way whether from weapon or ship
if (!Ship_info[Ships[objp->instance].ship_info_index].avoids_shockwaves()) {
// don't come here again
aip->ai_flags.remove(AI::AI_Flags::Avoid_shockwave_ship);
aip->ai_flags.remove(AI::AI_Flags::Avoid_shockwave_weapon);
if (fix_bugs) aip->shockwave_object = -1;
return 0;
}

// Don't try to evade a homing weapon until it starts homing
if (fix_bugs && aip->ai_flags[AI::AI_Flags::Avoid_shockwave_weapon] && !(aip->ai_flags[AI::AI_Flags::Avoid_shockwave_started])) {
// since the "don't all react right away" timer should only start when the weapon begins homing, we need the actual weapon now...
// so choose the weapon ahead of time using the same technique aas_1() would normally use later
if (aip->shockwave_object == -1) {
aip->shockwave_object = ai_find_shockwave_weapon(objp);
if (aip->shockwave_object == -1) {
aip->ai_flags.remove(AI::AI_Flags::Avoid_shockwave_weapon);
return 0;
}
}

// if this is a homing weapon that isn't homing, don't react yet
auto wp = &Weapons[Objects[aip->shockwave_object].instance];
if (Weapon_info[wp->weapon_info_index].is_homing() && IS_VEC_NULL(&wp->homing_pos)) {
return 0;
}
}

// Don't all react right away.
if (!(aip->ai_flags[AI::AI_Flags::Avoid_shockwave_started])) {
float evadeChance = (aip->ai_shockwave_evade_chance == FLT_MIN)
Expand Down

0 comments on commit 869596d

Please sign in to comment.