diff --git a/CREDITS.md b/CREDITS.md index 0b5547141b..c21716f230 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -161,7 +161,7 @@ This page lists all the individual contributions to the project by their author. - Warhead shield penetration & breaking - Strafing aircraft weapon customization - Vehicle `DeployFire` fixes/improvements - - Stationary VehicleTypes + - Stationary units - Burst logic improvements - TechnoType auto-firing weapons - Secondary weapon fallback customization diff --git a/Phobos.vcxproj b/Phobos.vcxproj index d45a6ac91f..f5b24bd8e5 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -166,7 +166,7 @@ - + diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index 3c8bb66211..b27d3397f9 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -250,7 +250,6 @@ This page describes all ingame logics that are fixed or improved in Phobos witho - Fixed the bug that Locomotor warhead won't stop working when firer (except for vehicle) stop firing. - Fixed the bug that hover vehicle will sink if destroyed on bridge. - Fixed the fact that when the selected unit is in a rearmed state, it can unconditionally use attack mouse on the target. -- When `Speed=0` or the TechnoTypes cell cannot move due to `MovementRestrictedTo`, vehicles cannot attack targets beyond the weapon's range. `Area Guard` and `Hunt` missions will also become ineffective. - Fixed an issue that barrel anim data will be incorrectly overwritten by turret anim data if the techno's section exists in the map file. - Fixed pathfinding crashes (EIP 0x42A525, 0x42C507, 0x42C554) that happened on bigger maps due to too small pathfinding node buffer. - `IsSimpleDeployer` `BalloonHover=true` units with `DeployToLand=false` are no longer forced to land when hovering. @@ -1811,6 +1810,14 @@ In `rulesmd.ini`: RadarInvisibleToHouse= ; Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all), default to enemy if RadarInvisible=true, none otherwise ``` +### Stationary units + +- Infantry & vehicles with `Speed=0` or those that are prevented from moving on their cell by `MovementRestrictedTo` are now truly stationary will not attempt to move if issued movement commands. Should not be used on trainable/buildable units as they become stuck in factory. Following behaviours apply: + - Cannot retaliate if target is beyond their weapon range. + - Cannot acquire units outside their weapon range on `Area Guard` mission. + - `Hunt` mission is reassigned to `Guard`. + - Units rendered stationary by speed multipliers instead of intrinsic properties of TechnoType will respond to cursor actions e.g assigning destination but will not move while the multiplier is in effect. + ### Subterranean unit travel height and speed - It is now possible to control the height at which units with subterranean (Tunnel) `Locomotor` travel, globally or per TechnoType. @@ -2238,10 +2245,6 @@ SinkSpeed=5 ; integer, leptons per frame Sinkable.SquidGrab=true ; boolean ``` -### Stationary vehicles - -- Setting VehicleType `Speed` to 0 now makes game treat them as stationary, behaving in very similar manner to deployed vehicles with `IsSimpleDeployer` set to true. Should not be used on buildable vehicles, as they won't be able to exit factories. - ### Turret recoil - Now you can use `TurretRecoil` to control units' turret/barrel recoil effect when firing. diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 83f2fafb5d..7aedf97727 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -2270,7 +2270,7 @@ JumpjetTilt.SidewaysSpeedFactor=1.0 ; floating point value ### Turret Response - When the vehicle loses its target, you can customize whether to align the turret direction with the vehicle body. - - When `Speed=0` or TechnoTypes cells cannot move due to `MovementRestrictedTo`, the default value is no; in other cases, it is yes. + - If VehicleType has `Speed=0` or `MovementRestrictedTo` prevents it from moving on the cell, the default value is no; in other cases, it is yes. In `rulesmd.ini`: ```ini diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 096e7cd9c5..9f849280e3 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -542,6 +542,7 @@ New: - Allow jumpjet climbing ignore building height (by TaranDahl) - [Allow draw SuperWeapon timer as percentage](User-Interface.md#allow-draw-superweapon-timer-as-percentage) (by NetsuNegi) - Customize particle system of parasite logic (by NetsuNegi) +- [Improvements to stationary unit logic](Fixed-or-Improved-Logics.md#stationary-units) (by Starkku) Vanilla fixes: - Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya) diff --git a/src/Ext/Script/Mission.Attack.cpp b/src/Ext/Script/Mission.Attack.cpp index 417402ec53..b4d546958e 100644 --- a/src/Ext/Script/Mission.Attack.cpp +++ b/src/Ext/Script/Mission.Attack.cpp @@ -235,15 +235,11 @@ void ScriptExt::Mission_Attack(TeamClass* pTeam, int calcThreatMode, bool repeat continue; } - const auto whatAmI = pFoot->WhatAmI(); - - // If the vehicle cannot be moved, perhaps it is better this way. - if (whatAmI == AbstractType::Unit - && TechnoExt::CannotMove(static_cast(pFoot)) - && !pFoot->IsCloseEnough(pSelectedTarget, pFoot->SelectWeapon(pSelectedTarget))) - { + // If the unit cannot be moved, perhaps it is better this way. + if (TechnoExt::CannotMove(pFoot, true) && !pFoot->IsCloseEnough(pSelectedTarget, pFoot->SelectWeapon(pSelectedTarget))) continue; - } + + const auto whatAmI = pFoot->WhatAmI(); // Aircraft hack. I hate how this game auto-manages the aircraft missions. if (whatAmI == AbstractType::Aircraft diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index ed4ebd3ce3..796907b5b5 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -184,8 +184,9 @@ void TechnoExt::SyncInvulnerability(TechnoClass* pFrom, TechnoClass* pTo) double TechnoExt::GetCurrentSpeedMultiplier(FootClass* pThis) { - double houseMultiplier = 1.0; + auto const pExt = TechnoExt::ExtMap.Find(pThis); auto const whatAmI = pThis->WhatAmI(); + double houseMultiplier = 1.0; if (whatAmI == AbstractType::Aircraft) houseMultiplier = pThis->Owner->Type->SpeedAircraftMult; @@ -194,8 +195,6 @@ double TechnoExt::GetCurrentSpeedMultiplier(FootClass* pThis) else houseMultiplier = pThis->Owner->Type->SpeedUnitsMult; - auto const pExt = TechnoExt::ExtMap.Find(pThis); - return pThis->SpeedMultiplier * houseMultiplier * pExt->AE.SpeedMultiplier * (pThis->HasAbility(Ability::Faster) ? RulesClass::Instance->VeteranSpeed : 1.0); } @@ -747,28 +746,34 @@ bool TechnoExt::IsHealthInThreshold(TechnoClass* pObject, double min, double max return (hp > 0 ? hp > min : hp >= min) && hp <= max; } -bool TechnoExt::CannotMove(UnitClass* pThis) +bool TechnoExt::CannotMove(FootClass* pThis, bool checkSpeedMultiplier) { - const auto pType = pThis->Type; + const auto pType = pThis->GetTechnoType(); if (pType->Speed == 0) return true; - const auto movementRestrictedTo = pType->MovementRestrictedTo; - - if (movementRestrictedTo == LandType::None) - return false; + if (checkSpeedMultiplier && TechnoExt::ExtMap.Find(pThis)->IsZeroSpeed) + return true; auto landType = pThis->GetCell()->LandType; - if (landType == LandType::Tunnel) + if (landType == LandType::Tunnel && pType->Locomotor != LocomotionClass::CLSIDs::Jumpjet) return false; - if (pThis->OnBridge && (landType == LandType::Water || landType == LandType::Beach)) - landType = LandType::Road; + if (pThis->WhatAmI() == AbstractType::Unit) + { + const auto movementRestrictedTo = static_cast(pType)->MovementRestrictedTo; - if (movementRestrictedTo != landType) - return true; + if (movementRestrictedTo == LandType::None) + return false; + + if (!pThis->OnBridge && (landType == LandType::Water || landType == LandType::Beach)) + landType = LandType::Road; + + if (movementRestrictedTo != landType) + return true; + } return false; } @@ -1063,6 +1068,7 @@ void TechnoExt::ExtData::Serialize(T& Stm) .Process(this->SpecialTracked) .Process(this->FallingDownTracked) .Process(this->JumpjetStraightAscend) + .Process(this->IsZeroSpeed) ; } diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 7f178426b4..af0bec3972 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -100,6 +100,8 @@ class TechnoExt bool JumpjetStraightAscend; // Is set to true jumpjet units will ascend straight and do not adjust rotation or position during it. + bool IsZeroSpeed; // Temporary speed multipliers have made this techno stationary. + ExtData(TechnoClass* OwnerObject) : Extension(OwnerObject) , TypeExtData { nullptr } , Shield {} @@ -166,6 +168,7 @@ class TechnoExt , SpecialTracked { false } , FallingDownTracked { false } , JumpjetStraightAscend { false } + , IsZeroSpeed { false } { } void OnEarlyUpdate(); @@ -289,7 +292,7 @@ class TechnoExt static bool IsVeterancyInThreshold(TechnoClass* pObject, double min, double max); static UnitTypeClass* GetUnitTypeExtra(UnitClass* pUnit, TechnoTypeExt::ExtData* pData); static AircraftTypeClass* GetAircraftTypeExtra(AircraftClass* pAircraft); - static bool CannotMove(UnitClass* pThis); + static bool CannotMove(FootClass* pThis, bool checkSpeedMultiplier); static bool HasAmmoToDeploy(TechnoClass* pThis); static void HandleOnDeployAmmoChange(TechnoClass* pThis, int maxAmmoOverride = -1); static bool SimpleDeployerAllowedToDeploy(UnitClass* pThis, bool defaultValue, bool alwaysCheckLandTypes); diff --git a/src/Ext/Techno/Hooks.DisallowMoving.cpp b/src/Ext/Techno/Hooks.DisallowMoving.cpp new file mode 100644 index 0000000000..28ab019d27 --- /dev/null +++ b/src/Ext/Techno/Hooks.DisallowMoving.cpp @@ -0,0 +1,316 @@ +// Issue #5 Permanently stationary units +// Author: Starkku + +#include + +#include + +#pragma region Helpers + +static int inline HandleHunt(FootClass* pThis) +{ + if (TechnoExt::CannotMove(pThis, true)) + { + pThis->QueueMission(Mission::Guard, false); + pThis->NextMission(); + return pThis->Mission_Guard(); + } + + return 0; +} + +#pragma endregion + +#pragma region Infantry + +static bool __fastcall InfantryClass_CantMove_Wrapper(InfantryClass* pThis) +{ + return pThis->IsUnderEMP() || TechnoExt::CannotMove(pThis, true); +} + +DEFINE_FUNCTION_JUMP(CALL6, 0x75ACBD, InfantryClass_CantMove_Wrapper); + +DEFINE_HOOK(0x51AA84, InfantryClass_AssignDestination_DisallowMoving, 0x6) +{ + GET(InfantryClass*, pThis, EBP); + + if (TechnoExt::CannotMove(pThis, false)) + { + pThis->AbortMotion(); + return 0x51B1D7; + } + + return 0; +} + +DEFINE_HOOK(0x51D0DD, InfantryClass_Scatter_DisallowMoving, 0x6) +{ + GET(InfantryClass*, pThis, ESI); + + return TechnoExt::CannotMove(pThis, true) ? 0x51D6E6 : 0; +} + +DEFINE_HOOK(0x51EB94, InfantryClass_WhatAction_ObjectClass_DisallowMoving_1, 0x6) +{ + GET(InfantryClass*, pThis, EDI); + + return TechnoExt::CannotMove(pThis, false) ? 0x51EBC6 : 0; +} + +DEFINE_HOOK(0x51EBDF, InfantryClass_WhatAction_ObjectClass_DisallowMoving_2, 0x6) +{ + GET(InfantryClass*, pThis, EDI); + + return TechnoExt::CannotMove(pThis, false) ? 0x51EBE9 : 0; +} + +DEFINE_HOOK(0x51F8A8, InfantryClass_WhatAction_DisallowMoving, 0x6) +{ + GET(InfantryClass*, pThis, EDI); + GET(Action, action, EBX); + + if (TechnoExt::CannotMove(pThis, false)) + { + if (pThis->Owner->IsControlledByCurrentPlayer() && action == Action::Attack) + return 0x51F905; + else if (action == Action::Move) + return 0x51F94E; + } + + return 0; +} + +DEFINE_HOOK(0x51F543, InfantryClass_Mission_Hunt_DisallowMoving, 0x6) +{ + GET(UnitClass*, pThis, ESI); + + if (int delay = HandleHunt(pThis)) + { + R->EAX(delay); + return 0x51F5BE; + } + + return 0; +} + +DEFINE_HOOK(0x51CB8C, InfantryClass_GetFireError_DisallowMoving, 0x6) +{ + GET(InfantryClass*, pThis, EBX); + GET(const FireError, result, EAX); + + if (result == FireError::RANGE && TechnoExt::CannotMove(pThis, true)) + R->EAX(FireError::ILLEGAL); + + return 0; +} + +#pragma endregion + +#pragma region Unit + +DEFINE_HOOK(0x741AA7, UnitClass_AssignDestination_DisallowMoving, 0x6) +{ + GET(UnitClass*, pThis, EBP); + + return TechnoExt::CannotMove(pThis, false) ? 0x743173 : 0; +} + +DEFINE_HOOK(0x743B4B, UnitClass_Scatter_DisallowMoving, 0x6) +{ + GET(UnitClass*, pThis, EBP); + + return TechnoExt::CannotMove(pThis, true) ? 0x74408E : 0; +} + +DEFINE_HOOK(0x74038F, UnitClass_WhatAction_ObjectClass_DisallowMoving_1, 0x6) +{ + GET(UnitClass*, pThis, ESI); + + return TechnoExt::CannotMove(pThis, false) ? 0x7403A3 : 0; +} + +DEFINE_HOOK(0x7403B7, UnitClass_WhatAction_ObjectClass_DisallowMoving_2, 0x6) +{ + GET(UnitClass*, pThis, ESI); + + return TechnoExt::CannotMove(pThis, false) ? 0x7403C1 : 0; +} + +DEFINE_HOOK(0x740709, UnitClass_WhatAction_DisallowMoving_1, 0x6) +{ + GET(UnitClass*, pThis, ESI); + + return TechnoExt::CannotMove(pThis, false) ? 0x740727 : 0; +} + +DEFINE_HOOK(0x740744, UnitClass_WhatAction_DisallowMoving_2, 0x6) +{ + enum { AllowAttack = 0x74078E, ReturnNoMove = 0x740769, ReturnResult = 0x740801 }; + + GET(UnitClass*, pThis, ESI); + GET_STACK(const Action, result, 0x30); + + if (TechnoExt::CannotMove(pThis, false)) + { + if (result == Action::Move) + return ReturnNoMove; + + if (result != Action::Attack) + return ReturnResult; + + return AllowAttack; + } + + return 0; +} + +DEFINE_HOOK(0x736B60, UnitClass_RotationAI_DisallowMoving, 0x6) +{ + GET(UnitClass*, pThis, ESI); + + const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->Type); + + if (pTypeExt->TurretResponse.isset() ? !pTypeExt->TurretResponse.Get() : TechnoExt::CannotMove(pThis, false)) + return 0x736AFB; + + return 0; +} + +DEFINE_HOOK(0x73EFC4, UnitClass_Mission_Hunt_DisallowMoving, 0x6) +{ + GET(UnitClass*, pThis, ESI); + + if (int delay = HandleHunt(pThis)) + { + R->EAX(delay); + return 0x73F091; + } + + return 0; +} + +DEFINE_HOOK(0x74132B, UnitClass_GetFireError_DisallowMoving, 0x7) +{ + GET(UnitClass*, pThis, ESI); + GET(const FireError, result, EAX); + + if (result == FireError::RANGE && TechnoExt::CannotMove(pThis, true)) + R->EAX(FireError::ILLEGAL); + + return 0; +} + +#pragma endregion + +#pragma region Foot + +DEFINE_HOOK(0x4D4203, FootClass_Mission_Move_DisallowMoving, 0x6) +{ + GET(FootClass*, pThis, ESI); + + if (TechnoExt::CannotMove(pThis, true)) + { + pThis->ForceMission(Mission::Guard); + return 0x4D4248; + } + + return 0; +} + +DEFINE_HOOK(0x4D9563, FootClass_AssignDestination_DisallowMoving, 0x6) +{ + GET(FootClass*, pThis, EBP); + + // Prevent locomotor processing when assigned target if temporarily immobilized. + if (TechnoExt::ExtMap.Find(pThis)->IsZeroSpeed) + return 0x4D96C2; + + return 0; +} + +DEFINE_HOOK(0x4D7EB5, FootClass_ActiveClickWith_DisallowMoving, 0x5) +{ + GET(FootClass*, pThis, ESI); + + return TechnoExt::CannotMove(pThis, false) ? 0x4D7DC1 : 0; +} + +// 3 Sep, 2025 - Starkku: Separated from above, do not change to guard mission +// and only handle the target acquisition part of area guard for immobile units. +DEFINE_HOOK(0x4D6AAB, FootClass_Mission_AreaGuard_DisallowMoving, 0x6) +{ + GET(FootClass*, pThis, ESI); + + if (TechnoExt::CannotMove(pThis, true)) + { + if (pThis->CanPassiveAcquireTargets() && pThis->TargetingTimer.Completed()) + pThis->TargetAndEstimateDamage(pThis->Location, ThreatType::Range); + + int delay = 1; + + if (!pThis->Target) + { + pThis->UpdateIdleAction(); + auto const control = &MissionControlClass::Array[(int)Mission::Area_Guard]; + delay = static_cast(control->Rate * 900) + ScenarioClass::Instance->Random(1, 5); + } + + R->EBP(delay); + return 0x4D715A; + } + + return 0; +} + +DEFINE_HOOK(0x4D5716, FootClass_ApproachTarget_DisallowMoving, 0x7) +{ + GET(FootClass*, pThis, EBX); + + if (TechnoExt::CannotMove(pThis, true)) + return 0x4D571D; + + return 0; +} + +#pragma endregion + +#pragma region Techno + +DEFINE_HOOK(0x6F7CE2, TechnoClass_CanAutoTargetObject_DisallowMoving, 0x6) +{ + GET(TechnoClass* const, pThis, EDI); + GET(AbstractClass* const, pTarget, ESI); + GET(const int, weaponIndex, EBX); + + if (const auto pUnit = abstract_cast(pThis)) + { + if (TechnoExt::CannotMove(pUnit, true)) + { + R->EAX(pUnit->GetFireError(pTarget, weaponIndex, true)); + return 0x6F7CEE; + } + } + + return 0; +} + +DEFINE_HOOK(0x7088E3, TechnoClass_ShouldRetaliate_DisallowMoving, 0x6) +{ + GET(TechnoClass* const, pThis, EDI); + GET(AbstractClass* const, pTarget, EBP); + GET(const int, weaponIndex, EBX); + + if (const auto pUnit = abstract_cast(pThis)) + { + if (TechnoExt::CannotMove(pUnit, true)) + { + R->Stack(STACK_OFFSET(0x18, 0x4), weaponIndex); + R->EAX(pUnit->GetFireError(pTarget, weaponIndex, true)); + return 0x7088F3; + } + } + + return 0; +} + +#pragma endregion diff --git a/src/Ext/Techno/Hooks.cpp b/src/Ext/Techno/Hooks.cpp index 3ec93019e9..f2624b1284 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -31,7 +31,7 @@ DEFINE_HOOK(0x7363C9, UnitClass_AI_AnimationPaused, 0x6) enum { SkipGameCode = 0x7363DE }; GET(UnitClass*, pThis, ESI); - + if (TechnoExt::ExtMap.Find(pThis)->DelayedFireSequencePaused) return SkipGameCode; @@ -62,6 +62,27 @@ DEFINE_HOOK(0x4DA54E, FootClass_AI, 0x6) pExt->UpdateTiberiumEater(); pExt->AmmoAutoConvertActions(); + // Check speed multipliers for zero, update things like allowing + // locomotor to process destination again. + if (TechnoExt::GetCurrentSpeedMultiplier(pThis) > 0.0) + { + bool wasZeroSpeed = pExt->IsZeroSpeed; + pExt->IsZeroSpeed = false; + + if (wasZeroSpeed) + { + if (auto const pDest = pThis->Destination) + { + pThis->Destination = nullptr; // Force it to update. + pThis->SetDestination(pDest, false); + } + } + } + else + { + pExt->IsZeroSpeed = true; + } + return 0; } @@ -843,7 +864,7 @@ DEFINE_HOOK(0x655DDD, RadarClass_ProcessPoint_RadarInvisible, 0x6) if (pTypeExt->OwnerObject()->RadarInvisible && EnumFunctions::CanTargetHouse(pTypeExt->RadarInvisibleToHouse.Get(AffectedHouse::Enemies), pTechno->Owner, HouseClass::CurrentPlayer)) { - return Invisible; + return Invisible; } return GoOtherChecks; @@ -1210,7 +1231,7 @@ DEFINE_HOOK(0x4DF3A6, FootClass_UpdateAttackMove_Follow, 0x6) #pragma endregion -DEFINE_HOOK(0x708FC0, TechnoClass_ResponseMove_Pickup, 0x5) +DEFINE_HOOK(0x708FC0, TechnoClass_ResponseMove, 0x5) { enum { SkipResponse = 0x709015 }; @@ -1237,11 +1258,11 @@ DEFINE_HOOK(0x708FC0, TechnoClass_ResponseMove_Pickup, 0x5) } } } - else if (rtti == AbstractType::Unit) + else { - auto const pUnit = static_cast(pThis); + auto const pFoot = static_cast(pThis); - if (TechnoExt::CannotMove(pUnit)) + if (TechnoExt::CannotMove(pFoot, true)) return SkipResponse; } @@ -1288,7 +1309,7 @@ DEFINE_HOOK(0x71A8BD, TemporalClass_Update_WarpAwayAnim, 0x5) AnimExt::CreateRandomAnim(pExt->WarpAway, pTarget->Location, nullptr, pTarget->Owner); return 0x71A90E; } - + return 0; } diff --git a/src/Ext/Unit/Hooks.DisallowMoving.cpp b/src/Ext/Unit/Hooks.DisallowMoving.cpp deleted file mode 100644 index 42f629e90b..0000000000 --- a/src/Ext/Unit/Hooks.DisallowMoving.cpp +++ /dev/null @@ -1,216 +0,0 @@ -// Issue #5 Permanently stationary units -// Author: Starkku - -#include - -DEFINE_HOOK(0x740A93, UnitClass_Mission_Move_DisallowMoving, 0x6) -{ - GET(UnitClass*, pThis, ESI); - - return TechnoExt::CannotMove(pThis) ? 0x740AEF : 0; -} - -DEFINE_HOOK(0x741AA7, UnitClass_Assign_Destination_DisallowMoving, 0x6) -{ - GET(UnitClass*, pThis, EBP); - - return TechnoExt::CannotMove(pThis) ? 0x743173 : 0; -} - -DEFINE_HOOK(0x743B4B, UnitClass_Scatter_DisallowMoving, 0x6) -{ - GET(UnitClass*, pThis, EBP); - - return TechnoExt::CannotMove(pThis) ? 0x74408E : 0; -} - -DEFINE_HOOK(0x74038F, UnitClass_What_Action_ObjectClass_DisallowMoving_1, 0x6) -{ - GET(UnitClass*, pThis, ESI); - - return TechnoExt::CannotMove(pThis) ? 0x7403A3 : 0; -} - -DEFINE_HOOK(0x7403B7, UnitClass_What_Action_ObjectClass_DisallowMoving_2, 0x6) -{ - GET(UnitClass*, pThis, ESI); - - return TechnoExt::CannotMove(pThis) ? 0x7403C1 : 0; -} - -DEFINE_HOOK(0x740709, UnitClass_What_Action_DisallowMoving_1, 0x6) -{ - GET(UnitClass*, pThis, ESI); - - return TechnoExt::CannotMove(pThis) ? 0x740727 : 0; -} - -DEFINE_HOOK(0x740744, UnitClass_What_Action_DisallowMoving_2, 0x6) -{ - enum { AllowAttack = 0x74078E, ReturnNoMove = 0x740769, ReturnResult = 0x740801 }; - - GET(UnitClass*, pThis, ESI); - GET_STACK(const Action, result, 0x30); - - if (TechnoExt::CannotMove(pThis)) - { - if (result == Action::Move) - return ReturnNoMove; - - if (result != Action::Attack) - return ReturnResult; - - return AllowAttack; - } - - return 0; -} - -DEFINE_HOOK(0x736B60, UnitClass_Rotation_AI_DisallowMoving, 0x6) -{ - GET(UnitClass*, pThis, ESI); - - const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->Type); - return (pTypeExt->TurretResponse.isset() ? !pTypeExt->TurretResponse.Get() : TechnoExt::CannotMove(pThis)) ? 0x736AFB : 0; -} - -DEFINE_HOOK(0x73891D, UnitClass_Active_Click_With_DisallowMoving, 0x6) -{ - GET(UnitClass*, pThis, ESI); - - return TechnoExt::CannotMove(pThis) ? 0x738927 : 0; -} - -DEFINE_HOOK(0x73EFC4, UnitClass_Mission_Hunt_DisallowMoving, 0x6) -{ - GET(UnitClass*, pThis, ESI); - - if (TechnoExt::CannotMove(pThis)) - { - pThis->QueueMission(Mission::Guard, false); - pThis->NextMission(); - - R->EAX(pThis->Mission_Guard()); - return 0x73F091; - } - - return 0; -} - -// 3 Sep, 2025 - Starkku: Separated from above, do not change to guard mission -// and only handle the target acquisition part of area guard for immobile units. -DEFINE_HOOK(0x744103, UnitClass_Mission_AreaGuard_DisallowMoving, 0x6) -{ - GET(UnitClass*, pThis, ESI); - - if (TechnoExt::CannotMove(pThis)) - { - if (pThis->CanPassiveAcquireTargets() && pThis->TargetingTimer.Completed()) - pThis->TargetAndEstimateDamage(pThis->Location, ThreatType::Range); - - int delay = 1; - - if (!pThis->Target) - { - pThis->UpdateIdleAction(); - auto const control = &MissionControlClass::Array[(int)Mission::Area_Guard]; - delay = static_cast(control->Rate * 900) + ScenarioClass::Instance->Random(1, 5); - } - - R->EAX(delay); - return 0x744173; - } - - return 0; -} - -DEFINE_HOOK(0x74132B, UnitClass_GetFireError_DisallowMoving, 0x7) -{ - GET(UnitClass*, pThis, ESI); - GET(const FireError, result, EAX); - - if (result == FireError::RANGE && TechnoExt::CannotMove(pThis)) - R->EAX(FireError::ILLEGAL); - - return 0; -} - -namespace UnitApproachTargetTemp -{ - int WeaponIndex; -} - -DEFINE_HOOK(0x7414E0, UnitClass_ApproachTarget_DisallowMoving, 0xA) -{ - GET(UnitClass*, pThis, ECX); - - int weaponIndex = -1; - - if (TechnoExt::CannotMove(pThis)) - { - const auto pTarget = pThis->Target; - weaponIndex = pThis->SelectWeapon(pTarget); - - if (!pThis->IsCloseEnough(pTarget, weaponIndex)) - { - pThis->SetTarget(nullptr); - return 0x741690; - } - } - - UnitApproachTargetTemp::WeaponIndex = weaponIndex; - return 0; -} - -DEFINE_HOOK(0x7415A9, UnitClass_ApproachTarget_SetWeaponIndex, 0x6) -{ - if (UnitApproachTargetTemp::WeaponIndex != -1) - { - GET(UnitClass*, pThis, ESI); - - R->EDI(VTable::Get(pThis)); - R->EAX(UnitApproachTargetTemp::WeaponIndex); - UnitApproachTargetTemp::WeaponIndex = -1; - - return 0x7415BA; - } - - return 0; -} - -DEFINE_HOOK(0x6F7CE2, TechnoClass_CanAutoTargetObject_DisallowMoving, 0x6) -{ - GET(TechnoClass* const, pThis, EDI); - GET(AbstractClass* const, pTarget, ESI); - GET(const int, weaponIndex, EBX); - - if (const auto pUnit = abstract_cast(pThis)) - { - if (TechnoExt::CannotMove(pUnit)) - { - R->EAX(pUnit->GetFireError(pTarget, weaponIndex, true)); - return 0x6F7CEE; - } - } - - return 0; -} - -DEFINE_HOOK(0x7088E3, TechnoClass_ShouldRetaliate_DisallowMoving, 0x6) -{ - GET(TechnoClass* const, pThis, EDI); - GET(AbstractClass* const, pTarget, EBP); - GET(const int, weaponIndex, EBX); - - if (const auto pUnit = abstract_cast(pThis)) - { - if (TechnoExt::CannotMove(pUnit)) - { - R->Stack(STACK_OFFSET(0x18, 0x4), weaponIndex); - R->EAX(pUnit->GetFireError(pTarget, weaponIndex, true)); - return 0x7088F3; - } - } - - return 0; -}