From d72c768a0034b261b5a31ad5109268b4503f4dda Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Fri, 16 Oct 2026 03:53:46 -0400 Subject: [PATCH] Make locked-homing weapons lose their target when it dies When a locked-homing (aspect/javelin) weapon's target was destroyed or departed, its homing_object kept pointing at the freed object slot, so "die on lost lock" never fired. If the slot was reused by an unrelated weapon, the missile would chase it, flying in circles until its lifetime expired. weapon_home() now checks the target signature once for locked-homing weapons. On a mismatch, find_homing_object_by_sig() re-acquires the original target or, if it is gone, clears homing_object, homing_subsys, target_num, and target_sig. A countermeasure that decoyed the weapon remains the only allowed mismatch, and the weapon still re-acquires its original target when the countermeasure expires. A countermeasure the weapon can't home on still breaks its lock, and now clears the same fields and sends a multiplayer homing update. This changes behavior for all locked-homing weapons, not just those with "die on lost lock". Once their target is destroyed or departs, missiles without the flag now fly straight instead of chasing whatever object reused the target's slot, and missiles with the flag now self-destruct, including when the target warps out. Since a missile whose target is gone could only score incidental hits either way, this doesn't affect balance, so it isn't gated behind an AI profile flag. Also, a locked-homing weapon fired without a lock that is decoyed by a countermeasure it can't home on now ignores the countermeasure and keeps flying straight, as a locked weapon already did. Previously it would turn and chase the countermeasure. Fixes #7849. Co-Authored-By: Claude Opus 5.5 --- code/weapon/weapons.cpp | 111 +++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 64 deletions(-) diff --git a/code/weapon/weapons.cpp b/code/weapon/weapons.cpp index f147f081c8a..7985f2eeef4 100644 --- a/code/weapon/weapons.cpp +++ b/code/weapon/weapons.cpp @@ -5628,6 +5628,23 @@ void find_homing_object_cmeasures(const SCP_vector &cmeasure_list) } } +/** + * Make weapon stop homing and forget its target. + */ +static void weapon_lose_target(object *weapon_objp) +{ + weapon *wp = &Weapons[weapon_objp->instance]; + bool had_homing_object = weapon_has_homing_object(wp); + + wp->homing_object = &obj_used_list; + wp->homing_subsys = nullptr; + wp->target_num = -1; + wp->target_sig = -1; + + if (MULTIPLAYER_MASTER && had_homing_object) + send_homing_weapon_info(weapon_objp->instance); +} + /** * Find object with signature "sig" and make weapon home on it. */ @@ -5638,20 +5655,16 @@ void find_homing_object_by_sig(object *weapon_objp, int sig) wp = &Weapons[weapon_objp->instance]; + int shipnum = ship_get_by_signature(sig); + if (shipnum < 0) { + weapon_lose_target(weapon_objp); + return; + } + // save the old object so that multiplayer masters know whether to send a homing update packet old_homing_objp = wp->homing_object; - for (auto sop: list_range(&Ship_obj_list)) { - auto objp = &Objects[sop->objnum]; - if (objp->flags[Object::Object_Flags::Should_be_dead]) - continue; - - if (objp->signature == sig) { - wp->homing_object = objp; - wp->target_sig = objp->signature; - break; - } - } + wp->homing_object = &Objects[Ships[shipnum].objnum]; // if the old homing object is different that the new one, send a packet to clients if ( MULTIPLAYER_MASTER && (old_homing_objp != wp->homing_object) ) { @@ -5659,30 +5672,15 @@ void find_homing_object_by_sig(object *weapon_objp, int sig) } } -bool aspect_should_lose_target(weapon* wp) +static bool aspect_can_home_on_cmeasure(const object* objp) { - Assert(wp != NULL); - - if (wp->homing_object->signature != wp->target_sig) { - if (wp->homing_object->type == OBJ_WEAPON) - { - weapon_info* target_info = &Weapon_info[Weapons[wp->homing_object->instance].weapon_info_index]; + if (objp->type != OBJ_WEAPON) + return false; - if (target_info->wi_flags[Weapon::Info_Flags::Cmeasure]) - { - // Check if we can home on this countermeasure - bool home_on_cmeasure = The_mission.ai_profile->flags[AI::Profile_Flags::Aspect_lock_countermeasure] - || target_info->wi_flags[Weapon::Info_Flags::Cmeasure_aspect_home_on]; + auto target_info = &Weapon_info[Weapons[objp->instance].weapon_info_index]; - if (!home_on_cmeasure) - { - return true; - } - } - } - } - - return false; + return target_info->wi_flags[Weapon::Info_Flags::Cmeasure] + && (The_mission.ai_profile->flags[AI::Profile_Flags::Aspect_lock_countermeasure] || target_info->wi_flags[Weapon::Info_Flags::Cmeasure_aspect_home_on]); } /** @@ -5781,14 +5779,15 @@ void weapon_home(object *obj, int num, float frame_time) // AL 4-8-98: If original target for aspect lock missile is lost, stop homing // WCS - or javelin - if (wip->is_locked_homing()) { - if ( wp->target_sig > 0 ) { - if (aspect_should_lose_target(wp)) - { - wp->homing_object = &obj_used_list; - return; - } - } + // If the target dies, its object slot is freed and may be reused by an unrelated object; + // a countermeasure that decoyed this weapon is the only other object it may home on + if (wip->is_locked_homing() && (hobjp->signature != wp->target_sig) && !aspect_can_home_on_cmeasure(hobjp)) { + // a countermeasure this weapon can't home on breaks its lock + if ((hobjp->type == OBJ_WEAPON) && Weapon_info[Weapons[hobjp->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Cmeasure]) + weapon_lose_target(obj); + else + find_homing_object_by_sig(obj, wp->target_sig); + return; } // AL 4-13-98: Stop homing on a subsystem if parent ship has changed @@ -5847,38 +5846,27 @@ void weapon_home(object *obj, int num, float frame_time) return; } - // TODO maybe add flag to allow WF_LOCKED_HOMING to lose target when target is dead - + // a locked-homing weapon that reaches here is homing on its target or a decoy switch (hobjp->type) { case OBJ_NONE: - if (wip->is_locked_homing()) { - find_homing_object_by_sig(obj, wp->target_sig); - } - else { - find_homing_object(obj, num); - } + find_homing_object(obj, num); return; break; case OBJ_SHIP: if (hobjp->signature != wp->target_sig) { - if (wip->is_locked_homing()) { - find_homing_object_by_sig(obj, wp->target_sig); - } - else { - find_homing_object(obj, num); - } + find_homing_object(obj, num); return; } break; case OBJ_WEAPON: { - weapon_info* hobj_infop = &Weapon_info[Weapons[hobjp->instance].weapon_info_index]; + if (wip->is_locked_homing()) + break; - bool home_on_cmeasure = The_mission.ai_profile->flags[AI::Profile_Flags::Aspect_lock_countermeasure] - || hobj_infop->wi_flags[Weapon::Info_Flags::Cmeasure_aspect_home_on]; + weapon_info* hobj_infop = &Weapon_info[Weapons[hobjp->instance].weapon_info_index]; // don't home on countermeasures or non-bombs, that's handled elsewhere - if (((hobj_infop->wi_flags[Weapon::Info_Flags::Cmeasure]) && !home_on_cmeasure)) + if (((hobj_infop->wi_flags[Weapon::Info_Flags::Cmeasure]) && !aspect_can_home_on_cmeasure(hobjp))) { break; } @@ -5887,12 +5875,7 @@ void weapon_home(object *obj, int num, float frame_time) break; } - if (wip->is_locked_homing()) { - find_homing_object_by_sig(obj, wp->target_sig); - } - else { - find_homing_object(obj, num); - } + find_homing_object(obj, num); break; } default: