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;
-}