Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix ai behavior when avoiding ship and weapon shockwaves #6378

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm loathe to add more collision checks unless absolutely necessary, especially since this appears to be called every frame for a thing which is in the average case unlikely to move much (and also this case doesn't need tons of accuracy, some approximation/lag should be fine), so at the very least the result should probably be cached.

Alternatively, have you considered just using the weapon's homing_pos?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a fair point, and I had the same reluctance, but I ended up adding it after seeing how many collision checks were used elsewhere in the code, and due to the difficulty of solving this problem without a collision check. I'm certainly open to suggestions though. And I could cache the result, at the cost of adding some additional fields to ai_info.

The weapon's homing_pos will work if the missile is homing on a subsystem, but will not work if the weapon is homing on the actual ship, since the position will be at the center of the ship.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a large ship, the homing_pos shouldn't be the center of the ship, see the usage of ai_big_pick_attack_point in weapon_home. They tend to spread themselves over the hull surface on the side facing the missile.

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
Copy link
Member

@Baezon Baezon Oct 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some additional weapon cases above which also return 0, but don't seem to reset shockwave_object, should those be updated as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The shockwave_object field should be reset when, and only when, an Avoid_shockwave_ flag is removed, not necessarily when the function returns 0.

There is of course the interesting // See if too far away to care about shockwave. case which returns 0 but does not remove the flag, as the flag is commented out -- even though the equivalent distance case for ship shockwaves removes the flag. The fact that the line is commented out makes me think Volition had a good reason for not clearing the flag. I'll give that another test. There's always the possibility of another separate AI profiles flag for that specific line.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I'm fine with whatever you go with, I figured I'd bring it up.

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
Loading