diff --git a/CREDITS.md b/CREDITS.md index 44c87160fb..f019124aa6 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -781,3 +781,4 @@ This page lists all the individual contributions to the project by their author. - Wall overlay unit sell exploit fix - Multiplayer gamespeed fix for RealTimeTimers - Revert Ares patch to allow OpenTopped transport customization + - Fix for units with Fly, Jumpjet or Rocket locomotors crashing off-map not being cleaned up diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index 6c2070cb3b..523ea3ae9d 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -273,6 +273,7 @@ This page describes all ingame logics that are fixed or improved in Phobos witho - Fixed the bug that vehicle fall on infantry will make all cell content has been removed. - Fixed buildings that have their owner changed during buildup skipping buildup and sometimes not correctly clearing the state. - Fixed preplaced aircraft outside visible map being incorrectly flagged as crashing under certain conditions. +- Fixed units with Fly, Jumpjet or Rocket locomotors destroyed while crashing off-map never being fully cleaned up, permanently blocking production slots and counting towards unit limits. - Fixed `MovementZone=Subterannean` harvesters being unable to find docks if in area enclosed by water, cliffs etc. - Fixed an issue where some effects pointing to a unit were not properly cleared when the unit changed its owner. - Allow Reveal Crate to take effect when picking up by another player controlled house in campaign. diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 39685cd218..e8a7058dfc 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -668,6 +668,7 @@ Phobos fixes: - Fixed an issue where hover vehicles could not be destroyed after malfunctioning on water surfaces (by FlyStar) - Fixed an issue where shadow matrix scaling was incorrectly applied to `TurretOffset` causing turret shadow misplacement (by Noble_Fish) - Fixed an issue that customizable warhead animation scatter cannot override 32 leptons scatter of `Inviso=yes` projectile (by NetsuNegi) +- Fixed units with Fly, Jumpjet or Rocket locomotors destroyed while crashing off-map never being fully cleaned up, permanently blocking production slots and counting towards unit limits (by RAZER) Fixes / interactions with other extensions: diff --git a/src/Misc/Hooks.BugFixes.cpp b/src/Misc/Hooks.BugFixes.cpp index 63873b28c9..e3a1e4b614 100644 --- a/src/Misc/Hooks.BugFixes.cpp +++ b/src/Misc/Hooks.BugFixes.cpp @@ -2874,6 +2874,63 @@ DEFINE_HOOK(0x4DB874, FootClass_SetLocation_Extra, 0xA) return SkipGameCode; } +// Fix crash descent for aircraft/units off-map. +// In all three locomotors below, MapClass::In_Radar blocks position/coordinate updates +// when outside the map. This means crashing units off-map never descend to ground level +// (or reach their destination), so the cleanup code (fire death weapon, UnInit) never runs. +// The fix: at each locomotor's height/health check, treat off-map as ground-touch. +// FlyLocomotionClass::Process - height check after crash descent calculation. +// If off-map and crashing, skip the height > 0 check and go straight to ground-touch cleanup. +// The IsCrashing guard is needed because healthy non-moving airborne aircraft also reach this +// code path (Is_Moving()==false && Height>0) and must not be sent to cleanup. +DEFINE_HOOK(0x4CD797, FlyLocomotionClass_CrashDescent_OffMap, 0x5) +{ + enum { GroundTouchCleanup = 0x4CD7AA }; + + GET(FlyLocomotionClass*, pThis, ESI); + + const auto pLinkedTo = pThis->LinkedTo; + + if (pLinkedTo->IsCrashing && !MapClass::Instance.IsWithinUsableArea(pLinkedTo->GetCoords())) + return GroundTouchCleanup; + + return 0; +} + +// JumpjetLocomotionClass::Process - height check before IsCrashing gate. +// If off-map, skip height check and go to the IsCrashing check directly. +DEFINE_HOOK(0x54CC16, JumpjetLocomotionClass_CrashDescent_OffMap, 0x8) +{ + enum { IsCrashingCheck = 0x54CC36 }; + + GET(JumpjetLocomotionClass*, pThis, EDI); + + if (!MapClass::Instance.IsWithinUsableArea(pThis->LinkedTo->GetCoords())) + { + // Replicate the stack init from the stolen bytes (mov byte ptr [esp+11h], 0) + // so the "fell on something" flag is properly zeroed for the crash path. + REF_STACK(BYTE, fellOnSomething, STACK_OFFSET(0x34, -0x23)); + fellOnSomething = 0; + return IsCrashingCheck; + } + + return 0; +} + +// RocketLocomotionClass::Process - health check after position update. +// If off-map, bypass the Health > 0 skip and force detonation/cleanup. +DEFINE_HOOK(0x662FD5, RocketLocomotionClass_Process_OffMap, 0x6) +{ + enum { ForceCleanup = 0x662FDF }; + + GET(RocketLocomotionClass*, pThis, EDI); + + if (!MapClass::Instance.IsWithinUsableArea(pThis->LinkedTo->GetCoords())) + return ForceCleanup; + + return 0; +} + DEFINE_HOOK(0x4DEC7F, FootClass_Crash_FallingDownFix, 0x7) { GET(FootClass*, pThis, ESI);