diff --git a/code/ai/ai_flags.h b/code/ai/ai_flags.h index 292f59276be..d1ce7563376 100644 --- a/code/ai/ai_flags.h +++ b/code/ai/ai_flags.h @@ -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, diff --git a/code/ai/ai_profiles.cpp b/code/ai/ai_profiles.cpp index 15564a5e750..9a6d636d502 100644 --- a/code/ai/ai_profiles.cpp +++ b/code/ai/ai_profiles.cpp @@ -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 @@ -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); + } } diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index 70161e5d503..c1194c67967 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -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; @@ -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. @@ -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) { @@ -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; } @@ -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; } @@ -14720,6 +14745,7 @@ 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 @@ -14727,9 +14753,29 @@ int ai_avoid_shockwave(object *objp, ai_info *aip) // 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)