diff --git a/CREDITS.md b/CREDITS.md
index 41f07e59b0..116a7536dd 100644
--- a/CREDITS.md
+++ b/CREDITS.md
@@ -528,6 +528,7 @@ This page lists all the individual contributions to the project by their author.
- Burst without delay
- Fix an issue that if the garrison unload occupants when there is no open space around it would result in the disappearance of the occupants
- Fix an issue where Ares' `Convert.Deploy` triggers repeatedly when the unit is turning or moving
+ - New AdvancedDrive locomotor
- **Ollerus**:
- Build limit group enhancement
- Customizable rocker amplitude
diff --git a/Phobos.vcxproj b/Phobos.vcxproj
index 47cd65d4a7..cf00adec3a 100644
--- a/Phobos.vcxproj
+++ b/Phobos.vcxproj
@@ -87,6 +87,7 @@
+
@@ -248,6 +249,7 @@
+
diff --git a/docs/Miscellanous.md b/docs/Miscellanous.md
index 903e40d2bb..2aa2f90a76 100644
--- a/docs/Miscellanous.md
+++ b/docs/Miscellanous.md
@@ -46,19 +46,20 @@ SaveVariablesOnScenarioEnd=false ; boolean
- It's now possible to write locomotor aliases instead of their CLSIDs in the `Locomotor` tag value. Use the table below to find the needed alias for a locomotor.
-| *Alias* | *CLSID* |
-|--------:|:----------------------------------------:|
-|Drive | `{4A582741-9839-11d1-B709-00A024DDAFD1}` |
-|Hover | `{4A582742-9839-11d1-B709-00A024DDAFD1}` |
-|Tunnel | `{4A582743-9839-11d1-B709-00A024DDAFD1}` |
-|Walk | `{4A582744-9839-11d1-B709-00A024DDAFD1}` |
-|DropPod | `{4A582745-9839-11d1-B709-00A024DDAFD1}` |
-|Fly | `{4A582746-9839-11d1-B709-00A024DDAFD1}` |
-|Teleport | `{4A582747-9839-11d1-B709-00A024DDAFD1}` |
-|Mech | `{55D141B8-DB94-11d1-AC98-006008055BB5}` |
-|Ship | `{2BEA74E1-7CCA-11d3-BE14-00104B62A16C}` |
-|Jumpjet | `{92612C46-F71F-11d1-AC9F-006008055BB5}` |
-|Rocket | `{B7B49766-E576-11d3-9BD9-00104B972FE8}` |
+| *Alias* | *CLSID* |
+|------------:|:----------------------------------------:|
+|Drive | `{4A582741-9839-11d1-B709-00A024DDAFD1}` |
+|Hover | `{4A582742-9839-11d1-B709-00A024DDAFD1}` |
+|Tunnel | `{4A582743-9839-11d1-B709-00A024DDAFD1}` |
+|Walk | `{4A582744-9839-11d1-B709-00A024DDAFD1}` |
+|DropPod | `{4A582745-9839-11d1-B709-00A024DDAFD1}` |
+|Fly | `{4A582746-9839-11d1-B709-00A024DDAFD1}` |
+|Teleport | `{4A582747-9839-11d1-B709-00A024DDAFD1}` |
+|Mech | `{55D141B8-DB94-11d1-AC98-006008055BB5}` |
+|Ship | `{2BEA74E1-7CCA-11d3-BE14-00104B62A16C}` |
+|Jumpjet | `{92612C46-F71F-11d1-AC9F-006008055BB5}` |
+|Rocket | `{B7B49766-E576-11d3-9BD9-00104B972FE8}` |
+|AdvancedDrive| `{4A582751-9839-11d1-B709-00A024DDAFD1}` |
```{note}
`Chrono` is not a standard Alias, but since the default behavior of using `Teleport` will be triggered when the value of `Locomotor` is incorrect, the result of the operation will appear as if `Chrono` has taken effect.
diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md
index 0ed79088c9..955dacf25b 100644
--- a/docs/New-or-Enhanced-Logics.md
+++ b/docs/New-or-Enhanced-Logics.md
@@ -733,6 +733,31 @@ In `artmd.ini`:
OnlyUseLandSequences=false ; boolean
```
+## Locomotor
+
+### AdvancedDrive Locomotor
+
+- `AdvancedDrive` locomotor (`{4A582751-9839-11d1-B709-00A024DDAFD1}`) is an improved `Drive` locomotor (`{4A582741-9839-11d1-B709-00A024DDAFD1}`) that can serve as a complete upper level replacement for it.
+- It fixes the issue where `Drive` cannot correctly crush objects during rapid turns.
+- It has smoother uphill and downhill dynamic visual effects.
+- It has the function of driving the unit backwards.
+ - `AdvancedDrive.ReverseSpeed` controls the speed ratio when reversing.
+ - `AdvancedDrive.FaceTargetRange` controls how close the unit is to its target, allowing reversing.
+ - `AdvancedDrive.MinimumDistance` controls how close the unit is to its destination, allowing reversing.
+ - `AdvancedDrive.ConfrontEnemies` controls whether to maitain the frontal movement towards the enemy within `AdvancedDrive.FaceTargetRange` and no longer automatically selects by the current orientation.
+ - `AdvancedDrive.RetreatDuration` controls how long since the unit was last injured, allowing reversing.
+
+In `rulesmd.ini`:
+```ini
+[SOMEVEHICLE] ; VehicleType
+Locomotor=AdvancedDrive ; Locomotor
+AdvancedDrive.ReverseSpeed=0.85 ; floating point value
+AdvancedDrive.FaceTargetRange=16.0 ; floating point value
+AdvancedDrive.MinimumDistance=2.5 ; floating point value
+AdvancedDrive.ConfrontEnemies=true ; boolean
+AdvancedDrive.RetreatDuration=150 ; integer, game frames
+```
+
## Projectiles
### Parabombs
diff --git a/docs/Whats-New.md b/docs/Whats-New.md
index 494f81dde2..c8672122eb 100644
--- a/docs/Whats-New.md
+++ b/docs/Whats-New.md
@@ -428,6 +428,7 @@ New:
- [Units can customize the attack voice that plays when using more weapons](New-or-Enhanced-Logics.md#multi-voiceattack) (by FlyStar)
- Customize squid grapple animation (by NetsuNegi)
- [Auto deploy for GI-like infantry](Fixed-or-Improved-Logics.md#auto-deploy-for-gi-like-infantry) (by TaranDahl)
+- New AdvancedDrive locomotor (by CrimRecya)
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/Techno/Body.cpp b/src/Ext/Techno/Body.cpp
index 7521101d2f..ef8875a537 100644
--- a/src/Ext/Techno/Body.cpp
+++ b/src/Ext/Techno/Body.cpp
@@ -769,6 +769,7 @@ void TechnoExt::ExtData::Serialize(T& Stm)
.Process(this->FiringObstacleCell)
.Process(this->IsDetachingForCloak)
.Process(this->BeControlledThreatFrame)
+ .Process(this->LastHurtFrame)
.Process(this->LastTargetID)
.Process(this->AccumulatedGattlingValue)
.Process(this->ShouldUpdateGattlingValue)
diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h
index 6493623e47..59ed90ccef 100644
--- a/src/Ext/Techno/Body.h
+++ b/src/Ext/Techno/Body.h
@@ -56,6 +56,7 @@ class TechnoExt
CellClass* FiringObstacleCell; // Set on firing if there is an obstacle cell between target and techno, used for updating WaveClass target etc.
bool IsDetachingForCloak; // Used for checking animation detaching, set to true before calling Detach_All() on techno when this anim is attached to and to false after when cloaking only.
int BeControlledThreatFrame;
+ int LastHurtFrame;
DWORD LastTargetID;
int AccumulatedGattlingValue;
bool ShouldUpdateGattlingValue;
@@ -122,6 +123,7 @@ class TechnoExt
, FiringObstacleCell {}
, IsDetachingForCloak { false }
, BeControlledThreatFrame { 0 }
+ , LastHurtFrame { 0 }
, LastTargetID { 0xFFFFFFFF }
, AccumulatedGattlingValue { 0 }
, ShouldUpdateGattlingValue { false }
diff --git a/src/Ext/Techno/Hooks.ReceiveDamage.cpp b/src/Ext/Techno/Hooks.ReceiveDamage.cpp
index 66a06fe8a6..3a123d1673 100644
--- a/src/Ext/Techno/Hooks.ReceiveDamage.cpp
+++ b/src/Ext/Techno/Hooks.ReceiveDamage.cpp
@@ -30,7 +30,10 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6)
return 0;
}
+ const auto pRules = RulesExt::Global();
const auto pExt = TechnoExt::ExtMap.Find(pThis);
+ const auto pTypeExt = pExt->TypeExtData;
+ const auto pType = pTypeExt->OwnerObject();
const auto pSourceHouse = args->SourceHouse;
const auto pTargetHouse = pThis->Owner;
@@ -61,7 +64,7 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6)
}
// Raise Combat Alert
- if (RulesExt::Global()->CombatAlert && damage > 1)
+ if (*args->Damage && (MapClass::GetTotalDamage(*args->Damage, args->WH, pType->Armor, args->DistanceToEpicenter) > 0))
{
auto raiseCombatAlert = [&]()
{
@@ -72,11 +75,7 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6)
if (pHouseExt->CombatAlertTimer.HasTimeLeft() || pWHExt->CombatAlert_Suppress.Get(!pWHExt->Malicious || pWHExt->Nonprovocative))
return;
-
- const auto pTypeExt = pExt->TypeExtData;
- const auto pType = pTypeExt->OwnerObject();
-
- if (!pTypeExt->CombatAlert.Get(RulesExt::Global()->CombatAlert_Default.Get(!pType->Insignificant && !pType->Spawned)) || !pThis->IsInPlayfield)
+ else if (!pTypeExt->CombatAlert.Get(pRules->CombatAlert_Default.Get(!pType->Insignificant && !pType->Spawned)) || !pThis->IsInPlayfield)
return;
const auto pBuilding = abstract_cast(pThis);
@@ -112,7 +111,12 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6)
if (index != -1)
VoxClass::PlayIndex(index);
};
- raiseCombatAlert();
+
+ if (pRules->CombatAlert)
+ raiseCombatAlert();
+
+ if (pWHExt->CanTargetHouse(pSourceHouse, pThis))
+ pExt->LastHurtFrame = Unsorted::CurrentFrame;
}
// Shield Receive Damage
@@ -142,7 +146,7 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6)
if ((!pWHExt->CanKill || pExt->AE.Unkillable)
&& pThis->Health > 0 && nDamageLeft != 0
&& pWHExt->CanTargetHouse(pSourceHouse, pThis)
- && MapClass::GetTotalDamage(nDamageLeft, args->WH, pThis->GetTechnoType()->Armor, args->DistanceToEpicenter) >= pThis->Health)
+ && MapClass::GetTotalDamage(nDamageLeft, args->WH, pType->Armor, args->DistanceToEpicenter) >= pThis->Health)
{
// Update remaining damage and check if the target will die and should be avoided
damage = 0;
diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp
index f32b4669f9..119d12d47e 100644
--- a/src/Ext/TechnoType/Body.cpp
+++ b/src/Ext/TechnoType/Body.cpp
@@ -844,6 +844,12 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
this->RecountBurst.Read(exINI, pSection, "RecountBurst");
+ this->AdvancedDrive_ReverseSpeed.Read(exINI, pSection, "AdvancedDrive.ReverseSpeed");
+ this->AdvancedDrive_FaceTargetRange.Read(exINI, pSection, "AdvancedDrive.FaceTargetRange");
+ this->AdvancedDrive_MinimumDistance.Read(exINI, pSection, "AdvancedDrive.MinimumDistance");
+ this->AdvancedDrive_ConfrontEnemies.Read(exINI, pSection, "AdvancedDrive.ConfrontEnemies");
+ this->AdvancedDrive_RetreatDuration.Read(exINI, pSection, "AdvancedDrive.RetreatDuration");
+
this->BuildLimitGroup_Types.Read(exINI, pSection, "BuildLimitGroup.Types");
this->BuildLimitGroup_Nums.Read(exINI, pSection, "BuildLimitGroup.Nums");
this->BuildLimitGroup_Factor.Read(exINI, pSection, "BuildLimitGroup.Factor");
@@ -1466,6 +1472,12 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm)
.Process(this->RecountBurst)
+ .Process(this->AdvancedDrive_ReverseSpeed)
+ .Process(this->AdvancedDrive_FaceTargetRange)
+ .Process(this->AdvancedDrive_MinimumDistance)
+ .Process(this->AdvancedDrive_ConfrontEnemies)
+ .Process(this->AdvancedDrive_RetreatDuration)
+
.Process(this->BuildLimitGroup_Types)
.Process(this->BuildLimitGroup_Nums)
.Process(this->BuildLimitGroup_Factor)
diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h
index 75d46d3169..d3480c85db 100644
--- a/src/Ext/TechnoType/Body.h
+++ b/src/Ext/TechnoType/Body.h
@@ -273,6 +273,12 @@ class TechnoTypeExt
Nullable RecountBurst;
+ Valueable AdvancedDrive_ReverseSpeed;
+ Valueable AdvancedDrive_FaceTargetRange;
+ Valueable AdvancedDrive_MinimumDistance;
+ Valueable AdvancedDrive_ConfrontEnemies;
+ Valueable AdvancedDrive_RetreatDuration;
+
ValueableVector BuildLimitGroup_Types;
ValueableVector BuildLimitGroup_Nums;
Valueable BuildLimitGroup_Factor;
@@ -660,6 +666,12 @@ class TechnoTypeExt
, RecountBurst {}
+ , AdvancedDrive_ReverseSpeed { 0.85 }
+ , AdvancedDrive_FaceTargetRange { Leptons(4096) }
+ , AdvancedDrive_MinimumDistance { Leptons(640) }
+ , AdvancedDrive_ConfrontEnemies { true }
+ , AdvancedDrive_RetreatDuration { 150 }
+
, BuildLimitGroup_Types {}
, BuildLimitGroup_Nums {}
, BuildLimitGroup_Factor { 1 }
diff --git a/src/Ext/Unit/Hooks.Crushing.cpp b/src/Ext/Unit/Hooks.Crushing.cpp
index acd03863d4..f3d0842af4 100644
--- a/src/Ext/Unit/Hooks.Crushing.cpp
+++ b/src/Ext/Unit/Hooks.Crushing.cpp
@@ -19,7 +19,10 @@ DEFINE_HOOK(0x73B05B, UnitClass_PerCellProcess_TiltWhenCrushes, 0x6)
if (!pTypeExt->TiltsWhenCrushes_Overlays.Get(pType->TiltsWhenCrushes))
return SkipGameCode;
- pThis->RockingForwardsPerFrame += static_cast(pTypeExt->CrushOverlayExtraForwardTilt);
+ if (AdvancedDriveLocomotionClass::IsReversing(pThis))
+ pThis->RockingForwardsPerFrame -= static_cast(pTypeExt->CrushOverlayExtraForwardTilt);
+ else
+ pThis->RockingForwardsPerFrame += static_cast(pTypeExt->CrushOverlayExtraForwardTilt);
return SkipGameCode;
}
@@ -36,7 +39,10 @@ DEFINE_HOOK(0x741941, UnitClass_OverrunSquare_TiltWhenCrushes, 0x6)
if (!pTypeExt->TiltsWhenCrushes_Vehicles.Get(pType->TiltsWhenCrushes))
return SkipGameCode;
- pThis->RockingForwardsPerFrame = static_cast(pTypeExt->CrushForwardTiltPerFrame.Get(-0.050000001));
+ if (AdvancedDriveLocomotionClass::IsReversing(pThis))
+ pThis->RockingForwardsPerFrame = static_cast(-pTypeExt->CrushForwardTiltPerFrame.Get(-0.05));
+ else
+ pThis->RockingForwardsPerFrame = static_cast(pTypeExt->CrushForwardTiltPerFrame.Get(-0.05));
return SkipGameCode;
}
@@ -68,7 +74,7 @@ DEFINE_HOOK(0x4B19F7, DriveLocomotionClass_WhileMoving_CrushTilt, 0xD)
auto const pLinkedTo = pThis->LinkedTo;
auto const pTypeExt = TechnoExt::ExtMap.Find(pLinkedTo)->TypeExtData;
- pLinkedTo->RockingForwardsPerFrame = static_cast(pTypeExt->CrushForwardTiltPerFrame.Get(-0.050000001));
+ pLinkedTo->RockingForwardsPerFrame = static_cast(pTypeExt->CrushForwardTiltPerFrame.Get(-0.05));
return R->Origin() == 0x4B19F7 ? SkipGameCode1 : SkipGameCode2;
}
diff --git a/src/Locomotion/AdvancedDriveLocomotionClass.cpp b/src/Locomotion/AdvancedDriveLocomotionClass.cpp
new file mode 100644
index 0000000000..4ea57c2735
--- /dev/null
+++ b/src/Locomotion/AdvancedDriveLocomotionClass.cpp
@@ -0,0 +1,1701 @@
+#include "AdvancedDriveLocomotionClass.h"
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+// Virtual
+
+bool AdvancedDriveLocomotionClass::Process()
+{
+ const auto pLinked = this->LinkedTo;
+ const auto slopeIndex = pLinked->GetCell()->SlopeIndex;
+
+ if (slopeIndex != this->CurrentRamp)
+ {
+ this->PreviousRamp = this->CurrentRamp;
+ this->CurrentRamp = slopeIndex;
+ // Dynamic slope change
+ const auto speed = pLinked->GetTechnoType()->Speed;
+ this->SlopeTimer.Start((speed > 0) ? (90 / speed) : 0);
+ }
+
+ // Record target cell for reversing
+ this->UpdateSituation();
+
+ if (!this->InMotion())
+ return false;
+
+ if (this->Is_Moving_Now() && !(Unsorted::CurrentFrame % 10))
+ {
+ if (!pLinked->OnBridge && pLinked->GetCell()->LandType == LandType::Water)
+ {
+ const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pLinked->GetTechnoType());
+ // Customized wake
+ if (const auto pAnimType = pTypeExt->Wake.Get(RulesClass::Instance->Wake))
+ GameCreate(pAnimType, pLinked->Location);
+ }
+ }
+
+ if (this->TargetCoord == CoordStruct::Empty && this->HeadToCoord == CoordStruct::Empty
+ && pLinked->PathDirections[0] == -1 && pLinked->SpeedPercentage > 0.0)
+ {
+ pLinked->SetSpeedPercentage(0.0);
+ }
+
+ return this->Is_Moving();
+}
+
+void AdvancedDriveLocomotionClass::Move_To(CoordStruct to)
+{
+ const auto pLinked = this->LinkedTo;
+
+ if (!pLinked->IsUnderEMP() && !pLinked->IsParalyzed()
+ && !pLinked->IsBeingWarpedOut() && !pLinked->IsWarpingIn())
+ {
+ this->TargetCoord = to;
+
+ if (to != CoordStruct::Empty && MapClass::Instance.GetCellAt(to)->ContainsBridge())
+ this->TargetCoord.Z += CellClass::BridgeHeight;
+ }
+}
+
+void AdvancedDriveLocomotionClass::Stop_Moving()
+{
+ const auto pLinked = this->LinkedTo;
+
+ if (this->HeadToCoord != CoordStruct::Empty && pLinked->GetTechnoType()->IsTrain)
+ {
+ const auto pUnit = static_cast(pLinked);
+
+ if (!pUnit->IsFollowerCar)
+ {
+ if (auto pFollowerCar = pUnit->FollowerCar)
+ {
+ do
+ {
+ pFollowerCar->Locomotor->Stop_Moving();
+ pFollowerCar = pFollowerCar->FollowerCar;
+ }
+ while (pFollowerCar && pFollowerCar != pFollowerCar->FollowerCar);
+ }
+ }
+ }
+
+ // I think no body want to see slowly~ slowly~ moving, so I change this one
+ if (pLinked->GetTechnoType()->Accelerates)
+ {
+ if (this->MovementSpeed >= 0.5 && pLinked->Location.DistanceFromSquared(this->HeadToCoord) < 16384)
+ this->MovementSpeed = 0.5;
+ }
+ // Slow down according to normal conditions
+ this->TargetCoord = this->HeadToCoord;
+}
+
+void AdvancedDriveLocomotionClass::Do_Turn(DirStruct dir)
+{
+ this->LinkedTo->PrimaryFacing.SetDesired(dir);
+}
+
+void AdvancedDriveLocomotionClass::Force_Track(int track, CoordStruct coord)
+{
+ this->TrackNumber = track;
+ this->TrackIndex = 0;
+
+ if (coord != CoordStruct::Empty)
+ {
+ this->HeadToCoord = coord;
+ this->IsDriving = true;
+
+ const auto pLinked = this->LinkedTo;
+ const auto pCell = MapClass::Instance.GetCellAt(coord);
+
+ if (!pCell->CollectCrate(pLinked) || pLinked->InLimbo)
+ {
+ if (pLinked->IsAlive)
+ this->StopDriving();
+ }
+ else
+ {
+ this->MarkOccupation(coord, MarkType::Down);
+ this->TargetCoord = coord;
+ this->MovementSpeed = 1.0;
+ }
+ }
+}
+
+void AdvancedDriveLocomotionClass::Mark_All_Occupation_Bits(MarkType mark)
+{
+ if (this->HeadToCoord != CoordStruct::Empty)
+ this->MarkOccupation(this->HeadToCoord, mark);
+}
+
+bool AdvancedDriveLocomotionClass::Is_Moving_Here(CoordStruct to)
+{
+ const auto headToCoord = this->Head_To_Coord();
+
+ if (headToCoord == CoordStruct::Empty)
+ return false;
+
+ if (!this->IsOnShortTrack)
+ {
+ const auto trackNum = this->TrackNumber;
+
+ if (trackNum != -1)
+ {
+ if (const auto trackStructIndex = DriveLocomotionClass::TurnTrack[trackNum].NormalTrackStructIndex)
+ {
+ const auto trackIdx = DriveLocomotionClass::RawTrack[trackStructIndex].CellIndex;
+
+ if (trackIdx > -1 && this->TrackIndex < trackIdx)
+ {
+ const auto trackPt = DriveLocomotionClass::RawTrack[trackStructIndex].TrackPoint;
+ const auto& trackPtr = trackPt[trackIdx];
+ auto face = trackPtr.Face; // copy
+ const auto location = this->GetTrackOffset(trackPtr.Point, face, this->LinkedTo->Location.Z);
+
+ if (CellClass::Coord2Cell(location) == CellClass::Coord2Cell(to)
+ && std::abs(location.Z - to.Z) <= Unsorted::CellHeight)
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return (CellClass::Coord2Cell(headToCoord) == CellClass::Coord2Cell(to)
+ && std::abs(headToCoord.Z - to.Z) <= Unsorted::CellHeight);
+}
+
+bool AdvancedDriveLocomotionClass::Will_Jump_Tracks()
+{
+ const auto pathDir = this->LinkedTo->PathDirections[0];
+
+ if (pathDir < 0 || pathDir >= 8)
+ return false;
+
+ const auto& data = DriveLocomotionClass::TurnTrack[this->TrackNumber];
+ const auto dir = DirStruct(data.Face << 8).GetValue<3>();
+
+ if (static_cast(dir) == pathDir || !this->TrackIndex)
+ return false;
+
+ const auto trackStructIndex = this->IsOnShortTrack ? data.ShortTrackStructIndex : data.NormalTrackStructIndex;
+
+ if (DriveLocomotionClass::RawTrack[trackStructIndex].JumpIndex != this->TrackIndex)
+ return false;
+
+ const auto dirIndex = DriveLocomotionClass::TurnTrack[8 * dir + pathDir].NormalTrackStructIndex;
+
+ return dirIndex && DriveLocomotionClass::RawTrack[dirIndex].EntryIndex;
+}
+
+// Non-virtual
+
+bool AdvancedDriveLocomotionClass::MovingProcess(bool fix)
+{
+ const auto pLinked = this->LinkedTo;
+ const auto pType = pLinked->GetTechnoType();
+
+ if ((!this->IsDriving || this->TrackNumber == -1) && pLinked->PathDirections[0] != 8
+ || this->IsRotating && !pType->Turret)
+ {
+ this->SpeedAccum = 0;
+ return false;
+ }
+
+ if (!pType->Accelerates)
+ {
+ pLinked->SetSpeedPercentage(this->MovementSpeed);
+ }
+ else if (this->TrackNumber < 64
+ && (pLinked->WhatAmI() != AbstractType::Unit || !static_cast(pType)->Passive))
+ {
+ do
+ {
+ auto coords = this->TargetCoord;
+ coords.Z = MapClass::Instance.GetCellFloorHeight(coords);
+
+ if (MapClass::Instance.GetCellAt(coords)->ContainsBridge())
+ coords.Z += CellClass::BridgeHeight;
+
+ const auto defaultSpeed = pLinked->GetDefaultSpeed();
+ auto speed = pLinked->SpeedPercentage;
+
+ if (Game::F2I((pLinked->Location - coords).Magnitude()) < pType->SlowdownDistance)
+ {
+ speed -= defaultSpeed * pType->DecelerationFactor;
+
+ if (speed < 0.3)
+ speed = 0.3;
+
+ if (pLinked->IsCrushingSomething)
+ {
+ // Customized crush slow down speed
+ const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pType);
+
+ if (this->MovementSpeed > pTypeExt->CrushSlowdownMultiplier)
+ this->MovementSpeed = pTypeExt->CrushSlowdownMultiplier;
+
+ speed = this->MovementSpeed;
+ }
+ }
+ else if (pLinked->IsSinking)
+ {
+ speed -= defaultSpeed * 0.0015;
+
+ if (speed < 0.1)
+ speed = 0.1;
+ }
+ else if (pLinked->IsCrushingSomething)
+ {
+ // Customized crush slow down speed
+ const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pType);
+
+ if (this->MovementSpeed > pTypeExt->CrushSlowdownMultiplier)
+ this->MovementSpeed = pTypeExt->CrushSlowdownMultiplier;
+
+ speed = this->MovementSpeed;
+ }
+ else if (pLinked->SpeedPercentage < this->MovementSpeed)
+ {
+ speed = pType->AccelerationFactor + pLinked->SpeedPercentage;
+
+ if (this->MovementSpeed < speed)
+ speed = this->MovementSpeed;
+ }
+ else if (pLinked->SpeedPercentage > this->MovementSpeed)
+ {
+ speed = pLinked->SpeedPercentage - defaultSpeed * pType->DecelerationFactor;
+
+ if (this->MovementSpeed > speed)
+ speed = this->MovementSpeed;
+ }
+ else
+ {
+ break;
+ }
+
+ pLinked->SetSpeedPercentage(speed);
+ }
+ while (false);
+
+ if (pLinked->WhatAmI() == AbstractType::Unit)
+ {
+ if (auto pFollowerCar = static_cast(pLinked)->FollowerCar)
+ {
+ do
+ {
+ pFollowerCar->SetSpeedPercentage(pLinked->SpeedPercentage);
+ pFollowerCar = pFollowerCar->FollowerCar;
+ }
+ while (pFollowerCar && pFollowerCar != pFollowerCar->FollowerCar);
+ }
+ }
+ }
+
+ if (pLinked->PathDirections[0] == 8 && this->TrackNumber == -1)
+ {
+ pLinked->Mark(MarkType::Up);
+ this->StopDriving();
+
+ const int tubeIndex = pLinked->GetCell()->TubeIndex;
+
+ if (tubeIndex >= 0 && tubeIndex < TubeClass::Array.Count)
+ {
+ const auto pTube = TubeClass::Array.Items[tubeIndex];
+ this->HeadToCoord = CellClass::Cell2Coord(pTube->ExitCell);
+
+ memmove(&pLinked->PathDirections[0], &pLinked->PathDirections[1], 0x5Cu);
+ pLinked->PathDirections[23] = -1;
+ pLinked->TubeIndex = static_cast(tubeIndex);
+ pLinked->TubeFaceIndex = 0;
+
+ const auto nextCell = pTube->EnterCell + CellSpread::GetNeighbourOffset(pTube->Faces[0] & 7);
+ const auto pNextCell = MapClass::Instance.GetCellAt(nextCell);
+ pLinked->CurrentTunnelCoords = pNextCell->GetCellCoords();
+
+ const auto currentHeight = MapClass::Instance.GetCellFloorHeight(pLinked->Location);
+ const auto exitHeight = MapClass::Instance.GetCellFloorHeight(this->HeadToCoord);
+ pLinked->CurrentTunnelCoords.Z = currentHeight + (exitHeight - currentHeight) / pTube->FaceCount;
+
+ this->IsDriving = true;
+ this->TrackNumber = -1;
+ return false;
+ }
+
+ pLinked->PathDirections[0] = -1;
+ this->TrackNumber = -1;
+ this->HeadToCoord = CoordStruct::Empty;
+ return false;
+ }
+
+ auto speedAccum = this->SpeedAccum;
+
+ if (!fix)
+ speedAccum += pLinked->GetCurrentSpeed();
+
+ if (const auto result = this->UpdateSpeedAccum(speedAccum))
+ return static_cast(result - 1);
+
+ this->SpeedAccum = speedAccum;
+
+ if (this->SpeedAccum <= 0)
+ return false;
+
+ if (this->TrackNumber <= -1)
+ return false;
+
+ const auto pTrackData = &DriveLocomotionClass::TurnTrack[this->TrackNumber];
+ const int trackStructIndex = this->IsOnShortTrack ? pTrackData->ShortTrackStructIndex : pTrackData->NormalTrackStructIndex;
+ const auto pTrackPoint = &DriveLocomotionClass::RawTrack[trackStructIndex].TrackPoint[this->TrackIndex];
+
+ if (pTrackPoint->Point == Point2D::Empty && this->TrackIndex)
+ return false;
+
+ int face = pTrackPoint->Face;
+ auto location = this->GetTrackOffset(pTrackPoint->Point, face);
+ const auto record = location;
+ const auto pTrackCell = MapClass::Instance.GetCellAt(location);
+ location -= pLinked->Location;
+
+ // Fix UpdatePosition bug
+ if (this->IsShifting)
+ {
+ const auto curDir = pLinked->PrimaryFacing.Current();
+ const auto nextDir = DirStruct((this->TrackNumber & 7) << 13);
+
+ if (std::abs(static_cast(static_cast(curDir.Raw) - static_cast(nextDir.Raw))) <= 4096)
+ {
+ this->IsShifting = false;
+ const auto oldPlan = pLinked->PlanningPathIdx;
+ pLinked->PlanningPathIdx = -1;
+ pLinked->UpdatePosition(PCPType::Rotation);
+ pLinked->PlanningPathIdx = oldPlan;
+ }
+ }
+
+ const auto ratio = static_cast(this->SpeedAccum * 0.1428571428571428);
+ auto newPos = pLinked->Location + AdvancedDriveLocomotionClass::CoordLerp(CoordStruct::Empty, location, ratio);
+
+ const auto pOldCell = MapClass::Instance.GetCellAt(pLinked->Location);
+ auto pNewCell = MapClass::Instance.GetCellAt(newPos);
+
+ if (pNewCell != pTrackCell && pNewCell != pOldCell && this->SpeedAccum > 3)
+ {
+ newPos.X = record.X;
+ newPos.Y = record.Y;
+ pNewCell = pTrackCell;
+ }
+
+ newPos.Z = pLinked->Location.Z;
+
+ if (CellClass::Coord2Cell(newPos) == CellClass::Coord2Cell(pLinked->Location))
+ {
+ const bool wasOnMap = pLinked->IsOnMap;
+ pLinked->IsOnMap = false;
+ pLinked->SetLocation(newPos);
+ pLinked->IsOnMap = wasOnMap;
+ }
+ else
+ {
+ pLinked->Mark(MarkType::Up);
+ pLinked->SetLocation(newPos);
+ this->UpdateOnBridge(pNewCell, pOldCell);
+ pLinked->Mark(MarkType::Down);
+ }
+
+ return false;
+}
+
+bool AdvancedDriveLocomotionClass::PassableCheck(bool* pStop, bool force, bool check)
+{
+ const auto pLinked = this->LinkedTo;
+ int pathDir = pLinked->PathDirections[0];
+
+ if (!this->Is_Moving() && pathDir == -1)
+ {
+ this->IsTurretLockedDown = false;
+ this->StopDriving();
+
+ if (pLinked->GetCurrentMission() == Mission::Move)
+ *pStop = pLinked->EnterIdleMode(false, true);
+
+ return false;
+ }
+
+ if (this->TargetCoord == CoordStruct::Empty || pLinked->IsBeingWarpedOut() || pLinked->IsWarpingIn())
+ return false;
+
+ if (const auto pSpawnManager = pLinked->SpawnManager)
+ {
+ if (pSpawnManager->CountLaunchingSpawns())
+ return true;
+ }
+
+ if (pLinked->IsUnderEMP() || pLinked->IsParalyzed())
+ return true;
+
+ const auto pType = pLinked->GetTechnoType();
+
+ do
+ {
+ if (pathDir != -1)
+ {
+ if (const auto pDest = pLinked->Destination)
+ {
+ const auto absType = pDest->WhatAmI();
+
+ if (absType == AbstractType::Infantry || absType == AbstractType::Unit)
+ {
+ const int distance = Game::F2I((pLinked->Location - this->TargetCoord).Magnitude()) / 256;
+
+ if (distance < 24)
+ {
+ pLinked->PathDirections[distance] = -1;
+ pathDir = pLinked->PathDirections[0];
+ }
+ }
+ }
+
+ if (pathDir != -1)
+ break;
+ }
+
+ auto& timer = pLinked->PathDelayTimer;
+
+ if (timer.GetTimeLeft() > 0)
+ return false;
+
+ timer.Start(Game::F2I(RulesClass::Instance->PathDelay * 900.0));
+
+ if (!pLinked->UpdatePathfinding(CellClass::Coord2Cell(this->TargetCoord), false, 0))
+ {
+ if (!this->LinkedTo)
+ {
+ *pStop = true;
+ return false;
+ }
+
+ if (!pLinked->IsInSameZoneAsCoords(this->TargetCoord))
+ {
+ pLinked->SetDestination(nullptr, true);
+ return false;
+ }
+
+ if (this->TargetCoord == CoordStruct::Empty)
+ return false;
+
+ const auto mission = pLinked->GetCurrentMission();
+
+ if (mission != Mission::Enter
+ && (pLinked->Location - this->TargetCoord).Magnitude() < RulesClass::Instance->CloseEnough
+ && (mission == Mission::Move || mission == Mission::Area_Guard))
+ {
+ this->StopDriving();
+
+ if (this->StopMotion())
+ return true;
+
+ if (!pLinked->IsAlive)
+ return false;
+ }
+ else
+ {
+ auto primaryFace = pLinked->PrimaryFacing.Current();
+ const auto primaryDir = (primaryFace.GetValue<3>() + (this->IsForward ? 0 : 4)) & 7;
+ const auto faceCell = pLinked->GetMapCoords() + CellSpread::GetNeighbourOffset(primaryDir);
+
+ if (MapClass::Instance.IsWithinUsableArea(faceCell, true))
+ {
+ const auto pCell = MapClass::Instance.GetCellAt(faceCell);
+ const auto move = pLinked->IsCellOccupied(pCell, static_cast(primaryDir),
+ pLinked->GetCellLevel(), nullptr, true);
+
+ if (move == Move::ClosedGate)
+ {
+ MapClass::Instance.MakeTraversable(pLinked, faceCell);
+ }
+ else if (move == Move::Temp)
+ {
+ if (const auto pCellTechno = pCell->FindTechnoNearestTo(Point2D::Empty,
+ (pLinked->Location.Z > (MapClass::Instance.GetCellFloorHeight(
+ CellClass::Cell2Coord(faceCell)) + 2 * Unsorted::CellHeight)), nullptr))
+ {
+ if (pLinked->Owner->IsAlliedWith(pCellTechno) && !pType->IsTrain)
+ {
+ if ((pLinked->Location - this->TargetCoord).Magnitude() < RulesClass::Instance->CloseEnough
+ && !pLinked->HasAnyLink()
+ && std::abs(this->TargetCoord.Z - pLinked->Location.Z) < (2 * Unsorted::CellHeight)
+ && MapClass::Instance.GetCellAt(pLinked->Location)->LandType != LandType::Tunnel)
+ {
+ this->StopDriving();
+ return this->StopMotion();
+ }
+
+ const bool onBridge = pCell->ContainsBridge()
+ && (std::abs(pLinked->Location.Z / Unsorted::CellHeight - pCell->Level) > 2);
+ pCell->ScatterContent(CoordStruct::Empty, true, true, onBridge);
+ }
+ }
+ }
+ }
+
+ const auto pathWaitTimes = pLinked->PathWaitTimes;
+
+ if (pathWaitTimes <= 0)
+ {
+ this->StopDriving();
+
+ if (this->StopMotion())
+ return true;
+
+ if (!pLinked->IsAlive)
+ return false;
+
+ if (pLinked->ShouldScanForTarget)
+ VocClass::PlayGlobal(RulesClass::Instance->ScoldSound, 0x2000, 1.0);
+
+ pLinked->ShouldScanForTarget = false;
+ }
+ else
+ {
+ pLinked->PathWaitTimes = pathWaitTimes - 1;
+ }
+ }
+
+ if (!this->Is_Moving())
+ {
+ if (const auto pTarget = pLinked->Target)
+ {
+ if (!pLinked->IsCloseEnoughToAttack(pTarget))
+ {
+ pLinked->IsScanLimited = true;
+
+ if (const auto pTeam = pLinked->Team)
+ pTeam->ScanLimit();
+
+ pLinked->SetTarget(nullptr);
+ }
+ }
+ }
+
+ this->StopDriving();
+ this->TrackNumber = -1;
+ this->IsTurretLockedDown = false;
+ return false;
+ }
+
+ const auto nowDir = pLinked->PathDirections[0];
+
+ if (nowDir == 8)
+ return false;
+
+ const auto pathCell = pLinked->GetMapCoords() + CellSpread::GetNeighbourOffset(nowDir & 7);
+
+ if (MapClass::Instance.IsWithinUsableArea(pathCell, true))
+ {
+ const auto pCell = MapClass::Instance.GetCellAt(pathCell);
+ const auto move = pLinked->IsCellOccupied(pCell, static_cast(nowDir),
+ pLinked->GetCellLevel(), nullptr, true);
+
+ if (move == Move::ClosedGate)
+ {
+ MapClass::Instance.MakeTraversable(pLinked, pathCell);
+ }
+ else if (move == Move::Temp)
+ {
+ if (const auto pCellTechno = pCell->FindTechnoNearestTo(Point2D::Empty,
+ (pLinked->Location.Z > (MapClass::Instance.GetCellFloorHeight(
+ CellClass::Cell2Coord(pathCell)) + 2 * Unsorted::CellHeight)), nullptr))
+ {
+ if (pLinked->Owner->IsAlliedWith(pCellTechno) && !pType->IsTrain)
+ {
+ if ((pLinked->Location - this->TargetCoord).Magnitude() < RulesClass::Instance->CloseEnough
+ && !pLinked->HasAnyLink()
+ && std::abs(this->TargetCoord.Z - pLinked->Location.Z) < (2 * Unsorted::CellHeight)
+ && MapClass::Instance.GetCellAt(pLinked->Location)->LandType != LandType::Tunnel)
+ {
+ this->StopDriving();
+ return this->StopMotion();
+ }
+
+ const bool onBridge = pCell->ContainsBridge()
+ && (std::abs(pLinked->Location.Z / Unsorted::CellHeight - pCell->Level) > 2);
+ pCell->ScatterContent(CoordStruct::Empty, true, true, onBridge);
+ }
+ }
+ }
+ }
+
+ pLinked->PathWaitTimes = 10;
+ pathDir = pLinked->PathDirections[0];
+ }
+ while (false);
+
+ if (pathDir == 8)
+ return false;
+
+ auto nextPos = pLinked->Location;
+ nextPos.X += Unsorted::AdjacentCoord[pathDir & 7].X;
+ nextPos.Y += Unsorted::AdjacentCoord[pathDir & 7].Y;
+
+ const int cellLevel = MapClass::Instance.GetCellAt(pLinked->Location)->Level + (pLinked->OnBridge ? 4 : 0);
+ auto pNextCell = MapClass::Instance.GetCellAt(nextPos);
+
+ if (pLinked->OnBridge != pNextCell->ContainsBridge())
+ pLinked->unknown_bool_68B = true; // Seems like useless
+
+ if (!pLinked->vt_entry_29C()) // Unknown unload state check
+ return true;
+
+ auto nextCell = CellClass::Coord2Cell(nextPos);
+
+ if (!MapClass::Instance.MakeTraversable(pLinked, nextCell))
+ return true;
+
+ const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pType);
+
+ // Reverse movement
+ const int desiredRaw = pathDir << 13;
+
+ do
+ {
+ if (pLinked->WhatAmI() != AbstractType::Unit)
+ break;
+
+ if (static_cast(pType)->Harvester || static_cast(pType)->Weeder)
+ {
+ auto IsReturnToRefinery = [pLinked]()
+ {
+ if (pLinked->CurrentMission != Mission::Enter || pLinked->MissionStatus)
+ return false;
+
+ if (pLinked->DistanceFrom(pLinked->Destination) > 363 || pLinked->GetCell()->GetBuilding())
+ return false;
+
+ const auto pLink = pLinked->GetNthLink();
+
+ if (!pLink || pLink->WhatAmI() != AbstractType::Building)
+ return false;
+
+ return static_cast(pLink)->Type->Refinery;
+ };
+
+ if (IsReturnToRefinery())
+ {
+ this->IsForward = false;
+ break;
+ }
+ else if (pLinked->CurrentMission == Mission::Harvest)
+ {
+ this->IsForward = true;
+ break;
+ }
+ }
+
+ if (this->ForwardTo != CoordStruct::Empty)
+ {
+ const auto tgtDir = pTypeExt->AdvancedDrive_ConfrontEnemies
+ ? DirStruct(Math::atan2(pLinked->Location.Y - this->ForwardTo.Y, this->ForwardTo.X - pLinked->Location.X))
+ : pLinked->PrimaryFacing.Current();
+ const auto deltaTgtDir = std::abs(static_cast(static_cast(desiredRaw)
+ - static_cast(tgtDir.Raw)));
+ const auto deltaOppDir = std::abs(static_cast(static_cast(desiredRaw + 32768)
+ - static_cast(tgtDir.Raw)));
+ this->IsForward = deltaTgtDir <= deltaOppDir;
+ }
+ else if ((Unsorted::CurrentFrame - TechnoExt::ExtMap.Find(pLinked)->LastHurtFrame) <= pTypeExt->AdvancedDrive_RetreatDuration
+ || pLinked->Destination && pLinked->DistanceFrom(pLinked->Destination) <= pTypeExt->AdvancedDrive_MinimumDistance.Get())
+ {
+ const auto curDir = pLinked->PrimaryFacing.Current();
+ const auto deltaCurDir = std::abs(static_cast(static_cast(desiredRaw)
+ - static_cast(curDir.Raw)));
+ const auto deltaOppDir = std::abs(static_cast(static_cast(desiredRaw + 32768)
+ - static_cast(curDir.Raw)));
+ this->IsForward = deltaCurDir <= deltaOppDir;
+ }
+ else if (pLinked->ArchiveTarget && pLinked->CurrentMission == Mission::Area_Guard
+ && pLinked->Owner->IsControlledByHuman() && !pType->DefaultToGuardArea)
+ {
+ const auto defDir = pLinked->GetTargetDirection(pLinked->ArchiveTarget);
+ const auto deltaDefDir = std::abs(static_cast(static_cast(desiredRaw)
+ - static_cast(defDir.Raw)));
+ const auto deltaOppDir = std::abs(static_cast(static_cast(desiredRaw + 32768)
+ - static_cast(defDir.Raw)));
+ this->IsForward = deltaDefDir > deltaOppDir;
+ }
+ else
+ {
+ this->IsForward = true;
+ }
+ }
+ while (false);
+
+ const auto desDir = DirStruct(this->IsForward ? desiredRaw : (desiredRaw + 32768));
+
+ if (pLinked->PrimaryFacing.Current() != desDir)
+ {
+ this->Do_Turn(desDir);
+ return true;
+ }
+
+ pLinked->Mark(MarkType::Up);
+ auto moveResult = pLinked->IsCellOccupied(pNextCell, static_cast(pathDir), cellLevel, nullptr, true);
+ pLinked->Mark(MarkType::Down);
+
+ if (moveResult < Move::No && pType->IsTrain
+ || (moveResult == Move::Destroyable || moveResult == Move::FriendlyDestroyable)
+ && pType->Crusher && !pNextCell->OverlayTypeIndex)
+ {
+ moveResult = Move::OK;
+ }
+
+ bool crushableOverlay = false;
+
+ do
+ {
+ if (pNextCell->OverlayTypeIndex != -1)
+ {
+ if (moveResult == Move::OK)
+ {
+ const auto pOverlay = OverlayTypeClass::Array.Items[pNextCell->OverlayTypeIndex];
+
+ if (pOverlay->Crushable || pOverlay->Wall && pType->MovementZone == MovementZone::CrusherAll)
+ crushableOverlay = true;
+
+ break;
+ }
+ }
+ else if (moveResult == Move::OK)
+ {
+ break;
+ }
+
+ if (moveResult == Move::ClosedGate)
+ {
+ MapClass::Instance.MakeTraversable(pLinked, nextCell);
+ }
+ else if (moveResult == Move::Temp)
+ {
+ if (!pType->IsTrain)
+ {
+ if (force)
+ {
+ pLinked->PathDirections[0] = -1;
+ pLinked->PathDelayTimer.Start(0);
+ return this->PassableCheck(pStop, false, false);
+ }
+
+ if ((pLinked->Location - this->TargetCoord).Magnitude() < RulesClass::Instance->CloseEnough
+ && std::abs(this->TargetCoord.Z - pLinked->Location.Z) < (2 * Unsorted::CellHeight)
+ && MapClass::Instance.GetCellAt(pLinked->Location)->LandType != LandType::Tunnel)
+ {
+ this->StopDriving();
+
+ if (this->StopMotion())
+ return true;
+ }
+ else
+ {
+ const bool onBridge = pNextCell->ContainsBridge()
+ && (std::abs(pLinked->Location.Z / Unsorted::CellHeight - pNextCell->Level) > 2);
+ pNextCell->ScatterContent(CoordStruct::Empty, true, true, onBridge);
+ }
+ }
+ }
+ else if (moveResult == Move::Cloak)
+ {
+ pNextCell->RevealCellObjects();
+
+ if (force)
+ {
+ pLinked->PathDirections[0] = -1;
+ return this->PassableCheck(pStop, false, false);
+ }
+
+ this->StopDriving();
+ return this->StopMotion();
+ }
+
+ this->StopDriving();
+
+ do
+ {
+ if (moveResult == Move::MovingBlock)
+ {
+ if (!pLinked->IsWaitingBlockagePath)
+ {
+ pLinked->IsWaitingBlockagePath = true;
+ pLinked->BlockagePathTimer.Start(RulesClass::Instance->BlockagePathDelay);
+ }
+
+ if (!pLinked->PathDelayTimer.GetTimeLeft())
+ {
+ const int findMode = static_cast(pLinked->IsWaitingBlockagePath
+ && !pLinked->BlockagePathTimer.HasTimeLeft()) + 1;
+ const bool pathFound = pLinked->UpdatePathfinding(CellClass::Coord2Cell(this->TargetCoord), false, findMode);
+
+ if (!this->LinkedTo)
+ {
+ *pStop = true;
+ return false;
+ }
+
+ if (pathFound || pLinked->IsInSameZoneAsCoords(this->TargetCoord))
+ {
+ pLinked->PathDelayTimer.Start(Game::F2I(RulesClass::Instance->PathDelay * 900.0));
+ return true;
+ }
+
+ pLinked->SetDestination(nullptr, true);
+ return false;
+ }
+
+ if (pLinked->ShouldScanForTarget)
+ VocClass::PlayGlobal(RulesClass::Instance->ScoldSound, 0x2000u, 1.0);
+
+ break;
+ }
+
+ if (moveResult != Move::Destroyable && moveResult != Move::FriendlyDestroyable)
+ {
+ if (pLinked->ShouldScanForTarget)
+ VocClass::PlayGlobal(RulesClass::Instance->ScoldSound, 0x2000u, 1.0);
+
+ break;
+ }
+
+ if (force)
+ {
+ pLinked->PathDirections[0] = -1;
+ pLinked->PathDelayTimer.Start(0);
+ return this->PassableCheck(pStop, false, false);
+ }
+
+ if (const auto pObject = pNextCell->GetSomeObject(CoordStruct::Empty, false))
+ {
+ if (!pLinked->Owner->IsAlliedWith(pObject))
+ pLinked->Override_Mission(Mission::Attack, pObject, nullptr);
+ }
+ else if (pNextCell->OverlayTypeIndex != -1 && OverlayTypeClass::Array.Items[pNextCell->OverlayTypeIndex]->Wall)
+ {
+ pLinked->Override_Mission(Mission::Attack, pNextCell, nullptr);
+ }
+ }
+ while (false);
+
+ if (moveResult != Move::No)
+ {
+ pLinked->ShouldScanForTarget = false;
+ this->TrackNumber = -1;
+ return true;
+ }
+
+ if (force)
+ {
+ pLinked->PathDirections[0] = -1;
+ pLinked->PathDelayTimer.Start(0);
+ return this->PassableCheck(pStop, false, false);
+ }
+
+ this->StopDriving();
+ return this->StopMotion();
+ }
+ while (false);
+
+ const bool different = std::abs(cellLevel - pNextCell->Level) >= 2;
+ const auto landType = different ? LandType::Road : pNextCell->LandType;
+ const auto landLevel = different ? cellLevel : pNextCell->Level;
+
+ double speedFactor = GroundType::Array[static_cast(landType)].Cost[static_cast(pType->SpeedType)];
+
+ if (speedFactor > 1.0)
+ speedFactor = 1.0;
+
+ if (pLinked->WhatAmI() == AbstractType::Unit)
+ {
+ int currentHeight = MapClass::Instance.GetCellFloorHeight(pLinked->Location);
+ int nextHeight = MapClass::Instance.GetCellFloorHeight(pNextCell->GetCellCoords());
+
+ if (nextHeight > currentHeight)
+ {
+ if (pType->SpeedType == SpeedType::Track)
+ speedFactor *= RulesClass::Instance->TrackedUphill;
+ else
+ speedFactor *= RulesClass::Instance->WheeledUphill;
+ }
+ else if (nextHeight < currentHeight)
+ {
+ if (pType->SpeedType == SpeedType::Track)
+ speedFactor *= RulesClass::Instance->TrackedDownhill;
+ else
+ speedFactor *= RulesClass::Instance->WheeledDownhill;
+ }
+ }
+
+ if (speedFactor == 0.0)
+ speedFactor = 0.5;
+
+ // Customized backward speed
+ if (!this->IsForward)
+ speedFactor *= pTypeExt->AdvancedDrive_ReverseSpeed;
+
+ // Customized damaged speed
+ const auto ratio = pLinked->GetHealthPercentage();
+
+ if (ratio <= RulesClass::Instance->ConditionYellow)
+ speedFactor *= pTypeExt->DamagedSpeed.Get(RulesExt::Global()->DamagedSpeed);
+
+ if (this->TrackNumber >= 64)
+ pLinked->SetSpeedPercentage(speedFactor);
+ else
+ this->MovementSpeed = speedFactor;
+
+ pLinked->TryCrushCell(nextCell, true);
+ auto nextDir = pLinked->PathDirections[1];
+
+ do
+ {
+ if (nextDir == -1)
+ {
+ if (Game::F2I((pLinked->Location - this->TargetCoord).Magnitude()) <= 512)
+ {
+ nextDir = pathDir;
+ break;
+ }
+ else
+ {
+ const bool pathFound = pLinked->UpdatePathfinding(CellClass::Coord2Cell(this->TargetCoord), pType->IsTrain, 0);
+
+ if (!pathFound)
+ {
+ if (!this->LinkedTo)
+ {
+ *pStop = true;
+ return false;
+ }
+
+ if (!pLinked->IsInSameZoneAsCoords(this->TargetCoord))
+ pLinked->SetDestination(nullptr, true);
+ }
+
+ nextDir = pLinked->PathDirections[1];
+ }
+ }
+
+ if (nextDir == 8 || nextDir == -1 || check)
+ nextDir = pathDir;
+ }
+ while (false);
+
+ do
+ {
+ if (nextDir != -1)
+ {
+ const auto pAdjCell = pNextCell->GetNeighbourCell(static_cast(nextDir));
+
+ if (pAdjCell->OverlayTypeIndex != -1)
+ {
+ const auto pOverlay = OverlayTypeClass::Array.Items[pAdjCell->OverlayTypeIndex];
+
+ if (pOverlay->Crushable
+ || ((pOverlay->Wall || pAdjCell->GetUnit(false))
+ && pType->MovementZone == MovementZone::CrusherAll))
+ {
+ this->IsRocking = true;
+ nextDir = pathDir;
+ break;
+ }
+ }
+ }
+
+ if (crushableOverlay)
+ {
+ this->IsRocking = true;
+ nextDir = pathDir;
+ break;
+ }
+
+ this->IsRocking = false;
+
+ // Reset is crushing flag
+ pLinked->IsCrushingSomething = false;
+ }
+ while (false);
+
+ this->IsOnShortTrack = false;
+ this->TrackNumber = nextDir + 8 * pathDir;
+
+ if (!DriveLocomotionClass::TurnTrack[this->TrackNumber].NormalTrackStructIndex)
+ this->TrackNumber = 9 * pathDir;
+
+ if (DriveLocomotionClass::TurnTrack[this->TrackNumber].Flag & 8)
+ {
+ this->IsShifting = true;
+ auto nextMoveResult = Move::No;
+
+ if (pNextCell->CollectCrate(pLinked) || pLinked->InLimbo)
+ {
+ if (!pLinked->IsAlive)
+ return false;
+
+ nextPos.X += Unsorted::AdjacentCoord[nextDir & 7].X;
+ nextPos.Y += Unsorted::AdjacentCoord[nextDir & 7].Y;
+ nextCell = CellClass::Coord2Cell(nextPos);
+ pNextCell = MapClass::Instance.GetCellAt(nextCell);
+ nextMoveResult = pLinked->IsCellOccupied(pNextCell, static_cast(nextDir), landLevel, nullptr, true);
+
+ if (nextMoveResult < Move::No && pType->IsTrain
+ || (nextMoveResult == Move::FriendlyDestroyable || nextMoveResult == Move::Destroyable)
+ && pType->Crusher && !pNextCell->OverlayTypeIndex)
+ {
+ nextMoveResult = Move::OK;
+ }
+ }
+ else if (!pLinked->IsAlive)
+ {
+ return false;
+ }
+
+ if (nextMoveResult != Move::OK)
+ {
+ if (nextMoveResult == Move::ClosedGate)
+ {
+ MapClass::Instance.MakeTraversable(pLinked, nextCell);
+ }
+ else if (nextMoveResult == Move::MovingBlock)
+ {
+ return this->PassableCheck(pStop, force, true);
+ }
+ else if (nextMoveResult == Move::Temp)
+ {
+ if (!pType->IsTrain)
+ {
+ if (force)
+ {
+ pLinked->PathDirections[0] = -1;
+ pLinked->PathDelayTimer.Start(0);
+ return this->PassableCheck(pStop, false, false);
+ }
+
+ if ((pLinked->Location - this->TargetCoord).Magnitude() < RulesClass::Instance->CloseEnough
+ && std::abs(this->TargetCoord.Z - pLinked->Location.Z) < (2 * Unsorted::CellHeight)
+ && MapClass::Instance.GetCellAt(pLinked->Location)->LandType != LandType::Tunnel)
+ {
+ this->StopDriving();
+
+ if (this->StopMotion())
+ return true;
+ }
+ else
+ {
+ const bool onBridge = pNextCell->ContainsBridge()
+ && (std::abs(pLinked->Location.Z / Unsorted::CellHeight - pNextCell->Level) > 2);
+ pNextCell->ScatterContent(CoordStruct::Empty, true, true, onBridge);
+ }
+ }
+ }
+ else if (nextMoveResult == Move::Cloak)
+ {
+ pNextCell->RevealCellObjects();
+
+ if (force)
+ {
+ pLinked->PathDirections[0] = -1;
+ return this->PassableCheck(pStop, false, false);
+ }
+
+ this->StopDriving();
+ return this->StopMotion();
+ }
+ else if (nextMoveResult == Move::No)
+ {
+ if (force)
+ {
+ pLinked->PathDirections[0] = -1;
+ pLinked->PathDelayTimer.Start(0);
+ return this->PassableCheck(pStop, false, false);
+ }
+
+ this->StopDriving();
+ return this->StopMotion();
+ }
+
+ pLinked->PathDirections[0] = -1;
+ this->TrackNumber = -1;
+ nextPos = CoordStruct::Empty;
+
+ if (nextMoveResult == Move::Destroyable || nextMoveResult == Move::FriendlyDestroyable)
+ return this->PassableCheck(pStop, force, true);
+ }
+ else
+ {
+ memmove(&pLinked->PathDirections[0], &pLinked->PathDirections[2], 0x58u);
+ pLinked->PathDirections[22] = -1;
+ pLinked->unknown_bool_68B = true; // Seems like useless
+ }
+ }
+ else
+ {
+ memmove(&pLinked->PathDirections[0], &pLinked->PathDirections[1], 0x5Cu);
+ }
+
+ pLinked->PathDirections[23] = -1;
+ pLinked->CurrentMapCoords = nextCell;
+ pLinked->ShouldScanForTarget = false;
+ this->TrackIndex = 0;
+ this->StopDriving();
+
+ if (nextPos != CoordStruct::Empty)
+ {
+ this->IsDriving = true;
+ this->HeadToCoord = nextPos;
+
+ if (pNextCell->CollectCrate(pLinked) && !pLinked->InLimbo)
+ {
+ this->MarkOccupation(nextPos, MarkType::Down);
+ return false;
+ }
+ else if (pLinked->IsAlive)
+ {
+ this->StopDriving();
+ }
+ }
+
+ this->TrackNumber = -1;
+ pLinked->PathDirections[0] = -1;
+ pLinked->SetSpeedPercentage(0.0);
+ return false;
+}
+
+void AdvancedDriveLocomotionClass::MarkOccupation(const CoordStruct& to, MarkType mark)
+{
+ if (to == CoordStruct::Empty)
+ return;
+
+ if (!this->IsOnShortTrack)
+ {
+ const auto trackNum = this->TrackNumber;
+
+ if (trackNum != -1)
+ {
+ if (const auto trackStructIndex = DriveLocomotionClass::TurnTrack[trackNum].NormalTrackStructIndex)
+ {
+ const auto& track = DriveLocomotionClass::RawTrack[trackStructIndex];
+ const auto trackIdx = track.CellIndex;
+
+ if (trackIdx > -1 && this->TrackIndex < trackIdx)
+ {
+ if (mark == MarkType::Up)
+ {
+ const auto& trackPt = track.TrackPoint[trackIdx];
+ auto face = trackPt.Face; // copy
+ const auto pLinked = this->LinkedTo;
+ pLinked->UnmarkAllOccupationBits(this->GetTrackOffset(trackPt.Point, face, pLinked->Location.Z));
+ }
+ else if (mark == MarkType::Down || mark == MarkType::ChangeRedraw)
+ {
+ const auto& trackPt = track.TrackPoint[trackIdx];
+ auto face = trackPt.Face; // copy
+ const auto pLinked = this->LinkedTo;
+ pLinked->MarkAllOccupationBits(this->GetTrackOffset(trackPt.Point, face, pLinked->Location.Z));
+ }
+ }
+ }
+ }
+ }
+
+ if (mark == MarkType::Up)
+ this->LinkedTo->UnmarkAllOccupationBits(to);
+ else if (mark == MarkType::Down || mark == MarkType::ChangeRedraw)
+ this->LinkedTo->MarkAllOccupationBits(to);
+}
+
+CoordStruct AdvancedDriveLocomotionClass::GetTrackOffset(const Point2D& base, int& face, int z)
+{
+ const auto dataFlag = DriveLocomotionClass::TurnTrack[this->TrackNumber].Flag;
+ auto pt = base;
+
+ if (dataFlag & 1)
+ {
+ pt.X = base.Y;
+ pt.Y = base.X;
+ face = static_cast(0xC0 - face);
+ }
+
+ if (dataFlag & 2)
+ {
+ pt.X = -pt.X;
+ face = static_cast(-static_cast(face));
+ }
+
+ if (dataFlag & 4)
+ {
+ pt.Y = -pt.Y;
+ face = static_cast(0x80 - face);
+ }
+
+ return CoordStruct { this->HeadToCoord.X + pt.X, this->HeadToCoord.Y + pt.Y, z };
+}
+
+CoordStruct AdvancedDriveLocomotionClass::CoordLerp(const CoordStruct& crd1, const CoordStruct& crd2, float alpha)
+{
+ const float i_alpha = 1.0f - alpha;
+ return CoordStruct
+ {
+ Game::F2I(crd2.X * alpha + crd1.X * i_alpha),
+ Game::F2I(crd2.Y * alpha + crd1.Y * i_alpha),
+ Game::F2I(crd2.Z * alpha + crd1.Z * i_alpha)
+ };
+}
+
+// Auxiliary
+
+inline void AdvancedDriveLocomotionClass::UpdateSituation()
+{
+ const auto pLinked = this->LinkedTo;
+ std::optional pTypeExt;
+
+ if (const auto pTarget = pLinked->MegaMissionIsAttackMove() ? nullptr : pLinked->Target)
+ {
+ pTypeExt = TechnoTypeExt::ExtMap.Find(pLinked->GetTechnoType());
+
+ if (pLinked->DistanceFrom(pTarget) <= pTypeExt.value()->AdvancedDrive_FaceTargetRange.Get())
+ {
+ this->ForwardTo = pTarget->GetCoords();
+ this->TargetFrame = Unsorted::CurrentFrame;
+ this->TargetDistance = 0;
+ return;
+ }
+ }
+
+ if (this->ForwardTo != CoordStruct::Empty)
+ {
+ if (!pTypeExt.has_value())
+ pTypeExt = TechnoTypeExt::ExtMap.Find(pLinked->GetTechnoType());
+
+ const auto currentDistance = static_cast(pLinked->Location.DistanceFrom(this->ForwardTo));
+
+ if (currentDistance > pTypeExt.value()->AdvancedDrive_FaceTargetRange.Get()
+ || (Unsorted::CurrentFrame - this->TargetFrame) > pTypeExt.value()->AdvancedDrive_RetreatDuration
+ || currentDistance < this->TargetDistance)
+ {
+ this->ForwardTo = CoordStruct::Empty;
+ this->TargetFrame = 0;
+ }
+
+ this->TargetDistance = currentDistance;
+ }
+}
+
+inline bool AdvancedDriveLocomotionClass::InMotion()
+{
+ const auto pLinked = this->LinkedTo;
+
+ if (this->TrackNumber != -1 && this->IsDriving)
+ {
+ if (this->MovingProcess(false) || !pLinked->IsAlive)
+ return false;
+
+ if (this->TrackNumber != -1 || !this->Is_Moving() && pLinked->PathDirections[0] == -1)
+ return true;
+
+ if (pLinked->WhatAmI() == AbstractType::Unit)
+ {
+ if (static_cast(pLinked)->Unloading)
+ return true;
+
+ if (const auto pDestination = abstract_cast(pLinked->Destination))
+ {
+ const auto coord = pDestination->GetDestination(pLinked);
+
+ if (coord != this->TargetCoord)
+ this->Move_To(coord);
+ }
+ }
+
+ return this->TakeMovingAction(true);
+ }
+
+ const auto pDest = pLinked->Destination;
+
+ if ((!pDest || pDest->WhatAmI() != AbstractType::Cell
+ || pLinked->GetMapCoords() != static_cast(pDest)->MapCoords)
+ && (pLinked->CurrentMission != Mission::Guard || this->IsDriving
+ || this->TargetCoord == CoordStruct::Empty || this->TargetCoord != pLinked->Location))
+ {
+ if (pLinked->PrimaryFacing.IsRotating())
+ {
+ this->IsRotating = true;
+ return true;
+ }
+ else if (this->IsRotating)
+ {
+ this->IsRotating = false;
+ pLinked->UpdatePosition(PCPType::Rotation);
+
+ if (this->LinkCannotMove())
+ return false;
+ }
+
+ const auto mission = pLinked->GetCurrentMission();
+
+ if (mission == Mission::Guard)
+ {
+ if (!this->Is_Moving())
+ return true;
+ }
+ else if (mission == Mission::Unload)
+ {
+ // Unload stuck fix
+ if (pLinked->GetTechnoType()->Passengers <= 0 || !pLinked->Passengers.GetFirstPassenger())
+ return true;
+ }
+
+ if (!this->Is_Moving() && pLinked->PathDirections[0] == -1)
+ {
+ if (pLinked->IsSinking)
+ {
+ this->StopDriving();
+ this->MovementSpeed = 0.0;
+ }
+ else if (const auto pDestination = pLinked->Destination)
+ {
+ this->Move_To(pDestination->GetDestination(pLinked));
+ }
+
+ return true;
+ }
+
+ if (pLinked->IsInPlayfield && mission != Mission::Enter && this->Is_Moving()
+ && !pLinked->IsInSameZoneAsCoords(this->TargetCoord))
+ {
+ this->StopDriving();
+
+ if (this->StopMotion())
+ return false;
+ }
+
+ return this->TakeMovingAction(false);
+ }
+
+ this->StopMotion();
+ return false;
+}
+
+inline int AdvancedDriveLocomotionClass::UpdateSpeedAccum(int& speedAccum)
+{
+ if (speedAccum <= 7)
+ return 0;
+
+ const auto pLinked = this->LinkedTo;
+ auto pTrackData = &DriveLocomotionClass::TurnTrack[this->TrackNumber];
+ int trackStructIndex = this->IsOnShortTrack ? pTrackData->ShortTrackStructIndex : pTrackData->NormalTrackStructIndex;
+ auto pTrackPoints = DriveLocomotionClass::RawTrack[trackStructIndex].TrackPoint;
+ const auto pathDir = pLinked->PathDirections[0];
+
+ if (pathDir < -1 || pathDir > 8)
+ {
+ pLinked->PathDirections[0] = -1;
+ return 1;
+ }
+
+ bool dirChanged = pathDir != 8 && pathDir != -1
+ && static_cast(DirStruct(pTrackData->Face << 8).GetValue<3>()) != pathDir;
+
+ while (true)
+ {
+ int trackIndex = this->TrackIndex;
+ const auto& trackPoint = pTrackPoints[trackIndex];
+ speedAccum -= 7;
+
+ if (trackPoint.Point == Point2D::Empty && trackIndex)
+ break;
+
+ if (pLinked->IsStandingStill())
+ {
+ pLinked->UnmarkAllOccupationBits(pLinked->Location);
+ pLinked->FrozenStill = false;
+ pLinked->IsWaitingBlockagePath = false;
+ }
+
+ CellStruct previousCell;
+
+ if (trackIndex)
+ {
+ const auto& prevTrackPoint = pTrackPoints[trackIndex - 1];
+ auto prevFace = prevTrackPoint.Face;
+ previousCell = CellClass::Coord2Cell(this->GetTrackOffset(prevTrackPoint.Point, prevFace));
+ }
+ else
+ {
+ previousCell = pLinked->GetMapCoords();
+ }
+
+ auto face = trackPoint.Face;
+ const auto newPos = this->GetTrackOffset(trackPoint.Point, face, pLinked->Location.Z);
+
+ if (CellClass::Coord2Cell(newPos) == CellClass::Coord2Cell(pLinked->Location))
+ {
+ const bool wasOnMap = pLinked->IsOnMap;
+ pLinked->IsOnMap = false;
+ pLinked->SetLocation(newPos);
+ pLinked->IsOnMap = wasOnMap;
+ }
+ else
+ {
+ pLinked->Mark(MarkType::Up);
+ pLinked->SetLocation(newPos);
+
+ const auto pNewCell = MapClass::Instance.GetCellAt(newPos);
+ this->UpdateOnBridge(pNewCell, MapClass::Instance.GetCellAt(previousCell));
+
+ if (pLinked->GetTechnoType()->IsTrain && !static_cast(pLinked)->IsFollowerCar)
+ {
+ auto pObject = (pLinked->OnBridge || (pLinked->Location.Z >= (CellClass::BridgeHeight
+ + MapClass::Instance.GetCellFloorHeight(pLinked->Location))))
+ ? pNewCell->AltObject : pNewCell->FirstObject;
+
+ while (pObject)
+ {
+ const auto pNext = pObject->NextObject;
+
+ if (!pObject->IsCrushable(pLinked))
+ {
+ auto damage = 10000;
+ pObject->ReceiveDamage(&damage, 0, RulesClass::Instance->C4Warhead, nullptr, true, true, nullptr);
+ damage = 20;
+ pLinked->ReceiveDamage(&damage, 0, RulesClass::Instance->C4Warhead, nullptr, true, false, nullptr);
+ }
+
+ pObject = pNext;
+ }
+ }
+
+ if (!pLinked->IsAlive)
+ return 1;
+
+ pLinked->Mark(MarkType::Down);
+
+ if (this->IsRocking)
+ {
+ const auto pType = pLinked->GetTechnoType();
+
+ if ((pType->MovementZone == MovementZone::CrusherAll && pNewCell->GetUnit(false))
+ || (pNewCell->OverlayTypeIndex != -1
+ && (pType->Crusher || pLinked->HasAbility(Ability::Crusher))
+ && OverlayTypeClass::Array.Items[pNewCell->OverlayTypeIndex]->Wall))
+ {
+ pLinked->IsCrushingSomething = true;
+
+ if (pType->TiltsWhenCrushes)
+ {
+ // Customized crush tilt speed
+ const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pType);
+ pLinked->RockingForwardsPerFrame = this->IsForward
+ ? static_cast(pTypeExt->CrushForwardTiltPerFrame.Get(-0.05))
+ : static_cast(-pTypeExt->CrushForwardTiltPerFrame.Get(-0.05));
+ }
+ }
+ }
+ }
+
+ if (!pLinked->IsAlive)
+ return 1;
+
+ const bool wasOnMap = pLinked->IsOnMap;
+ pLinked->IsOnMap = false;
+ pLinked->SetHeight(0);
+ pLinked->IsOnMap = wasOnMap;
+ pLinked->PrimaryFacing.SetCurrent(DirStruct((face << 8) + (this->IsForward ? 0 : 32768)));
+ trackIndex = this->TrackIndex;
+
+ if (trackIndex && DriveLocomotionClass::RawTrack[trackStructIndex].CellIndex == trackIndex)
+ pLinked->UnmarkAllOccupationBits(pLinked->Location);
+
+ if (pathDir != 8 && pathDir != -1 && dirChanged
+ && DriveLocomotionClass::RawTrack[trackStructIndex].JumpIndex == trackIndex
+ && trackIndex)
+ {
+ const int newTrack = pathDir + 8 * DirStruct(pTrackData->Face << 8).GetValue<3>();
+ const auto pNewTrackData = &DriveLocomotionClass::TurnTrack[newTrack];
+ const auto normalIndex = pNewTrackData->NormalTrackStructIndex;
+
+ if (normalIndex && DriveLocomotionClass::RawTrack[normalIndex].EntryIndex)
+ {
+ auto coords = this->HeadToCoord;
+ coords.X += Unsorted::AdjacentCoord[pathDir].X;
+ coords.Y += Unsorted::AdjacentCoord[pathDir].Y;
+ const auto pCell = MapClass::Instance.GetCellAt(coords);
+
+ switch (pLinked->IsCellOccupied(pCell, static_cast(pathDir),
+ pLinked->GetCellLevel(), nullptr, true))
+ {
+ case Move::OK:
+ case Move::MovingBlock:
+ {
+ if (pLinked->WhatAmI() == AbstractType::Unit && !static_cast(pLinked)->Type->Passive)
+ break;
+
+ const auto speedPercent = pLinked->SpeedPercentage;
+ this->IsOnShortTrack = false;
+ this->TrackNumber = newTrack;
+ pTrackData = pNewTrackData;
+ dirChanged = false;
+ trackStructIndex = pNewTrackData->NormalTrackStructIndex;
+ this->TrackIndex = DriveLocomotionClass::RawTrack[trackStructIndex].EntryIndex - 1;
+ pTrackPoints = DriveLocomotionClass::RawTrack[trackStructIndex].TrackPoint;
+
+ this->StopDriving();
+ this->IsDriving = true;
+ pLinked->UpdatePosition(PCPType::End);
+ this->IsDriving = false;
+
+ if (this->LinkCannotMove())
+ return 1;
+
+ this->StopDriving();
+
+ if (coords != CoordStruct::Empty)
+ {
+ this->IsDriving = true;
+ this->HeadToCoord = coords;
+
+ if (!pCell->CollectCrate(pLinked) || pLinked->InLimbo)
+ {
+ if (pLinked->IsAlive)
+ this->StopDriving();
+ }
+ else
+ {
+ this->MarkOccupation(coords, MarkType::Down);
+ pLinked->SetSpeedPercentage(speedPercent);
+ memmove(&pLinked->PathDirections[0], &pLinked->PathDirections[1], 0x5Cu);
+ pLinked->PathDirections[23] = -1;
+ }
+ }
+
+ break;
+ }
+
+ case Move::Cloak:
+ {
+ pCell->RevealCellObjects();
+ break;
+ }
+
+ case Move::ClosedGate:
+ {
+ MapClass::Instance.MakeTraversable(pLinked, CellClass::Coord2Cell(coords));
+ break;
+ }
+
+ case Move::Temp:
+ {
+ const bool onBridge = pCell->ContainsBridge()
+ && (std::abs(pLinked->Location.Z / Unsorted::CellHeight - pCell->Level) > 2);
+ MapClass::Instance.GetCellAt(this->HeadToCoord)->ScatterContent(CoordStruct::Empty,
+ true, true, onBridge);
+
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ ++this->TrackIndex;
+
+ if (speedAccum <= 7)
+ return 0;
+ }
+
+ const auto delta = this->HeadToCoord - pLinked->Location;
+ const auto distance = std::abs(delta.X) + std::abs(delta.Y);
+ speedAccum += Game::F2I((1.0 - distance / 11.0) * 7.0);
+
+ pLinked->FrozenStill = true;
+ pLinked->IsWaitingBlockagePath = false;
+
+ if (CellClass::Coord2Cell(this->HeadToCoord) == CellClass::Coord2Cell(pLinked->Location))
+ {
+ const bool wasOnMap = pLinked->IsOnMap;
+ pLinked->IsOnMap = false;
+ pLinked->SetLocation(this->HeadToCoord);
+ pLinked->SetHeight(0);
+ pLinked->IsOnMap = wasOnMap;
+ }
+ else
+ {
+ pLinked->Mark(MarkType::Up);
+ pLinked->SetLocation(this->HeadToCoord);
+ pLinked->SetHeight(0);
+ pLinked->Mark(MarkType::Down);
+ }
+
+ this->StopDriving();
+ this->TrackNumber = -1;
+ this->TrackIndex = 0;
+ bool reachedDestination = false;
+
+ if (const auto pDestination = pLinked->Destination)
+ {
+ if (pLinked->GetMapCoords() == CellClass::Coord2Cell(pDestination->GetDestination(pLinked))
+ && std::abs(pLinked->GetDestination(pLinked).Z - this->TargetCoord.Z) < 2 * Unsorted::CellHeight)
+ {
+ reachedDestination = true;
+ this->TargetCoord = CoordStruct::Empty;
+ this->StopDriving();
+ this->IsDriving = false;
+ }
+ }
+
+ pLinked->UpdatePosition(PCPType::End);
+
+ if (!this->LinkedTo || this->LinkCannotMove())
+ return 2;
+
+ if (reachedDestination)
+ {
+ pLinked->AbortMotion();
+ pLinked->PathDirections[0] = -1;
+
+ if (pLinked->GetCurrentMission() == Mission::Move && pLinked->EnterIdleMode(false, true))
+ return 2;
+ }
+
+ if (pLinked->TryEnterIdle())
+ return 2;
+
+ return pLinked->IsAlive ? 0 : 1;
+}
+
+// Hooks
+
+DEFINE_HOOK(0x4DA9FB, FootClass_Update_WalkedFrames, 0x6)
+{
+ enum { SkipGameCode = 0x4DAA01 };
+
+ GET(FootClass* const, pThis, ESI);
+
+ if (AdvancedDriveLocomotionClass::IsReversing(pThis))
+ {
+ --pThis->WalkedFramesSoFar;
+ return SkipGameCode;
+ }
+
+ return 0; // ++pThis->WalkedFramesSoFar;
+}
diff --git a/src/Locomotion/AdvancedDriveLocomotionClass.h b/src/Locomotion/AdvancedDriveLocomotionClass.h
new file mode 100644
index 0000000000..013f560f09
--- /dev/null
+++ b/src/Locomotion/AdvancedDriveLocomotionClass.h
@@ -0,0 +1,391 @@
+#pragma once
+
+#include
+#include
+
+#include
+
+#include
+#include
+
+class __declspec(uuid("4A582751-9839-11d1-B709-00A024DDAFD1"))
+ AdvancedDriveLocomotionClass : public LocomotionClass, public IPiggyback
+{
+public:
+ // IUnknown
+ virtual HRESULT __stdcall QueryInterface(REFIID iid, LPVOID* ppvObject) override
+ {
+ HRESULT hr = this->LocomotionClass::QueryInterface(iid, ppvObject);
+
+ if (hr != E_NOINTERFACE)
+ return hr;
+
+ if (iid == __uuidof(IPiggyback))
+ {
+ *ppvObject = static_cast(this);
+ }
+
+ if (*ppvObject)
+ {
+ this->AddRef();
+
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+ }
+ virtual ULONG __stdcall AddRef() override { return this->LocomotionClass::AddRef(); }
+ virtual ULONG __stdcall Release() override { return this->LocomotionClass::Release(); }
+
+ // IPersist
+ virtual HRESULT __stdcall GetClassID(CLSID* pClassID) override
+ {
+ if (pClassID == nullptr)
+ return E_POINTER;
+
+ *pClassID = __uuidof(this);
+
+ return S_OK;
+ }
+
+ // IPersistStream
+// virtual HRESULT __stdcall IsDirty() override { return !this->Dirty; }
+ virtual HRESULT __stdcall Load(IStream* pStm) override
+ {
+ HRESULT hr = this->LocomotionClass::Load(pStm);
+
+ if (FAILED(hr))
+ return hr;
+
+ if (this)
+ {
+ this->Piggybacker.Detach();
+ new (this) AdvancedDriveLocomotionClass(noinit_t());
+ }
+
+ bool piggybackerPresent = false;
+ hr = pStm->Read(&piggybackerPresent, sizeof(piggybackerPresent), nullptr);
+
+ if (!piggybackerPresent)
+ return hr;
+
+ hr = OleLoadFromStream(pStm, __uuidof(ILocomotion), reinterpret_cast(&this->Piggybacker));
+
+ return hr;
+ }
+ virtual HRESULT __stdcall Save(IStream* pStm, BOOL fClearDirty) override
+ {
+ HRESULT hr = this->LocomotionClass::Save(pStm, fClearDirty);
+
+ if (FAILED(hr))
+ return hr;
+
+ bool piggybackerPresent = this->Piggybacker != nullptr;
+ hr = pStm->Write(&piggybackerPresent, sizeof(piggybackerPresent), nullptr);
+
+ if (!piggybackerPresent)
+ return hr;
+
+ IPersistStreamPtr piggyPersist(this->Piggybacker);
+ hr = OleSaveToStream(piggyPersist, pStm);
+
+ return hr;
+ }
+/* virtual HRESULT __stdcall GetSizeMax(ULARGE_INTEGER* pcbSize) override
+ {
+ if (pcbSize == nullptr)
+ return E_POINTER;
+
+ return this->LocomotionClass::GetSizeMax(pcbSize);
+ }*/
+ virtual int Size() override { return sizeof(*this); }
+
+ // ILocomotion
+/* virtual HRESULT __stdcall Link_To_Object(void* pointer) override
+ {
+ HRESULT hr = this->LocomotionClass::Link_To_Object(pointer);
+
+ if (SUCCEEDED(hr))
+ Debug::Log("AdvancedDriveLocomotionClass - Sucessfully linked to \"%s\"\n", Owner->get_ID());
+
+ return hr;
+ }*/
+ virtual bool __stdcall Is_Moving() override
+ {
+ if (this->TargetCoord != CoordStruct::Empty)
+ return true;
+
+ return this->HeadToCoord != CoordStruct::Empty
+ && (this->HeadToCoord.X != this->LinkedTo->Location.X
+ || this->HeadToCoord.Y != this->LinkedTo->Location.Y);
+ }
+ virtual CoordStruct __stdcall Destination() override { return this->TargetCoord; } // CoordStruct*
+ virtual CoordStruct __stdcall Head_To_Coord() override // CoordStruct*
+ {
+ if (this->HeadToCoord == CoordStruct::Empty)
+ return this->LinkedTo->Location;
+
+ return this->HeadToCoord;
+ }
+// virtual Move __stdcall Can_Enter_Cell(CellStruct cell) override { return Move::OK; }
+// virtual bool __stdcall Is_To_Have_Shadow() override { return true; }
+ virtual Matrix3D __stdcall Draw_Matrix(VoxelIndexKey* key) override { JMP_STD(0x4AFF60); } // TODO but lazy
+ virtual Matrix3D __stdcall Shadow_Matrix(VoxelIndexKey* key) override { JMP_STD(0x4B0410); } // TODO but lazy
+// virtual Point2D __stdcall Draw_Point() override { return this->LocomotionClass::Draw_Point(); } // Point2D*
+// virtual Point2D __stdcall Shadow_Point() override { return this->LocomotionClass::Shadow_Point(); } // Point2D*
+// virtual VisualType __stdcall Visual_Character(bool raw) override { return VisualType::Normal; }
+ virtual int __stdcall Z_Adjust() override { return 0; }
+ virtual ZGradient __stdcall Z_Gradient() override { return ZGradient::Deg90; }
+ virtual bool __stdcall Process() override;
+ virtual void __stdcall Move_To(CoordStruct to) override;
+ virtual void __stdcall Stop_Moving() override;
+ virtual void __stdcall Do_Turn(DirStruct dir) override;
+ virtual void __stdcall Unlimbo() override { this->Force_New_Slope(this->LinkedTo->GetCell()->SlopeIndex); }
+// virtual void __stdcall Tilt_Pitch_AI() override {}
+/* virtual bool __stdcall Power_On() override
+ {
+ this->Powered = true;
+ return this->Is_Powered();
+ }*/
+/* virtual bool __stdcall Power_Off() override
+ {
+ this->Powered = false;
+ return this->Is_Powered();
+ }*/
+// virtual bool __stdcall Is_Powered() override { return this->Powered; }
+// virtual bool __stdcall Is_Ion_Sensitive() override { return false; }
+// virtual bool __stdcall Push(DirStruct dir) override { return false; }
+// virtual bool __stdcall Shove(DirStruct dir) override { return false; }
+ virtual void __stdcall Force_Track(int track, CoordStruct coord) override;
+ virtual Layer __stdcall In_Which_Layer() override { return Layer::Ground; }
+// virtual void __stdcall Force_Immediate_Destination(CoordStruct coord) override {}
+ virtual void __stdcall Force_New_Slope(int ramp) override
+ {
+ this->PreviousRamp = ramp;
+ this->CurrentRamp = ramp;
+ this->SlopeTimer.Start(0);
+ }
+ virtual bool __stdcall Is_Moving_Now() override
+ {
+ if (this->LinkedTo->PrimaryFacing.IsRotating())
+ return true;
+
+ return (this->TargetCoord != CoordStruct::Empty
+ || this->HeadToCoord.X != this->LinkedTo->Location.X
+ || this->HeadToCoord.Y != this->LinkedTo->Location.Y)
+ && this->HeadToCoord != CoordStruct::Empty
+ && this->LinkedTo->GetCurrentSpeed() > 0;
+ }
+// virtual int __stdcall Apparent_Speed() override { return this->LinkedTo->GetCurrentSpeed(); }
+// virtual int __stdcall Drawing_Code() override { return 0; }
+// virtual FireError __stdcall Can_Fire() override { return FireError::OK; }
+// virtual int __stdcall Get_Status() override { return 0; }
+// virtual void __stdcall Acquire_Hunter_Seeker_Target() override {}
+// virtual bool __stdcall Is_Surfacing() override { return false; }
+ virtual void __stdcall Mark_All_Occupation_Bits(MarkType mark) override;
+ virtual bool __stdcall Is_Moving_Here(CoordStruct to) override;
+ virtual bool __stdcall Will_Jump_Tracks() override;
+// virtual bool __stdcall Is_Really_Moving_Now() override { return this->Is_Moving_Now(); }
+// virtual void __stdcall Stop_Movement_Animation() override {}
+// virtual void __stdcall Limbo() override {}
+ virtual void __stdcall Lock() override { this->UnLocked = false; }
+ virtual void __stdcall Unlock() override { this->UnLocked = true; }
+ virtual int __stdcall Get_Track_Number() override { return this->TrackNumber; }
+ virtual int __stdcall Get_Track_Index() override { return this->TrackIndex; }
+ virtual int __stdcall Get_Speed_Accum() override { return this->SpeedAccum; }
+
+ //IPiggy
+ virtual HRESULT __stdcall Begin_Piggyback(ILocomotion* pointer) override
+ {
+ if (!pointer)
+ return E_POINTER;
+
+ if (this->Piggybacker)
+ return E_FAIL;
+
+ this->Piggybacker = pointer;
+ pointer->AddRef();
+
+ return S_OK;
+ }
+ virtual HRESULT __stdcall End_Piggyback(ILocomotion** pointer) override
+ {
+ if (!pointer)
+ return E_POINTER;
+
+ if (!this->Piggybacker)
+ return S_FALSE;
+
+ *pointer = this->Piggybacker.Detach();
+
+ const auto pLinkedTo = this->LinkedTo;
+
+ if (!pLinkedTo->Deactivated && !pLinkedTo->IsUnderEMP())
+ this->Power_On();
+ else
+ this->Power_Off();
+
+ return S_OK;
+ }
+ virtual bool __stdcall Is_Ok_To_End() override
+ {
+ return !this->Is_Moving() && this->Piggybacker && this->UnLocked && !this->LinkedTo->IsAttackedByLocomotor;
+ }
+ virtual HRESULT __stdcall Piggyback_CLSID(GUID* classid) override
+ {
+ if (classid == nullptr)
+ return E_POINTER;
+
+ if (this->Piggybacker)
+ {
+ IPersistStreamPtr piggyAsPersist(this->Piggybacker);
+ return piggyAsPersist->GetClassID(classid);
+ }
+
+ if (reinterpret_cast(this) == nullptr)
+ return E_FAIL;
+
+ IPersistStreamPtr thisAsPersist(this);
+
+ if (thisAsPersist == nullptr)
+ return E_FAIL;
+
+ return thisAsPersist->GetClassID(classid);
+ }
+ virtual bool __stdcall Is_Piggybacking() override
+ {
+ return this->Piggybacker != nullptr;
+ }
+
+ // Constructors
+ inline AdvancedDriveLocomotionClass(noinit_t) : LocomotionClass { noinit_t() } { }
+ inline AdvancedDriveLocomotionClass() : LocomotionClass { }
+ , CurrentRamp { 0 }
+ , PreviousRamp { 0 }
+ , SlopeTimer {}
+ , TargetCoord { CoordStruct::Empty }
+ , HeadToCoord { CoordStruct::Empty }
+ , SpeedAccum { 0 }
+ , MovementSpeed { 0.0 }
+ , TrackNumber { -1 }
+ , TrackIndex { -1 }
+ , IsOnShortTrack { false }
+ , IsTurretLockedDown { false }
+ , IsRotating { false }
+ , IsDriving { false }
+ , IsRocking { false }
+ , UnLocked { true }
+ , IsForward { true }
+ , IsShifting { false }
+ , Piggybacker { nullptr }
+ , ForwardTo { CoordStruct::Empty }
+ , TargetFrame { 0 }
+ , TargetDistance { 0 }
+ { }
+
+ // Destructor
+ inline virtual ~AdvancedDriveLocomotionClass() override = default;
+
+ // Properties
+ int CurrentRamp;
+ int PreviousRamp;
+ RateTimer SlopeTimer;
+ CoordStruct TargetCoord;
+ CoordStruct HeadToCoord;
+ int SpeedAccum;
+ double MovementSpeed;
+ int TrackNumber;
+ int TrackIndex;
+ bool IsOnShortTrack;
+ bool IsTurretLockedDown;
+ bool IsRotating;
+ bool IsDriving;
+ bool IsRocking;
+ bool UnLocked;
+ bool IsForward;
+ bool IsShifting;
+ ILocomotionPtr Piggybacker;
+ CoordStruct ForwardTo;
+ int TargetFrame;
+ int TargetDistance;
+
+private:
+ // Vanilla auxiliary function
+ bool MovingProcess(bool fix); // 0x4B0F20
+ bool PassableCheck(bool* pStop, bool force, bool check); // 0x4B2630
+ void MarkOccupation(const CoordStruct& to, MarkType mark); // 0x4B0AD0
+ CoordStruct GetTrackOffset(const Point2D& base, int& face, int z = 0); // 0x4B4780
+
+ static CoordStruct CoordLerp(const CoordStruct& crd1, const CoordStruct& crd2, float alpha); // 0x75F540
+
+ // Added inline auxiliary function
+ inline void UpdateSituation();
+ template
+ inline void StopDriving()
+ {
+ if constexpr (check)
+ if (this->HeadToCoord == CoordStruct::Empty)
+ return;
+
+ this->HeadToCoord = CoordStruct::Empty;
+ this->IsDriving = false;
+ }
+ inline bool StopMotion()
+ {
+ const auto pLinked = this->LinkedTo;
+
+ if (!pLinked->unknown_abstract_array_588.Count)
+ {
+ pLinked->SetDestination(nullptr, true);
+ return false;
+ }
+
+ pLinked->AbortMotion();
+ return pLinked->EnterIdleMode(false, true);
+ }
+ inline bool InMotion(); // Main loco motion process
+ inline bool LinkCannotMove()
+ {
+ const auto pLinked = this->LinkedTo;
+
+ return !pLinked->IsAlive || pLinked->InLimbo || pLinked->IsFallingDown;
+ }
+ inline bool TakeMovingAction(bool fix)
+ {
+ bool stop = false;
+ this->PassableCheck(&stop, true, false);
+
+ if (stop || !this->LinkedTo->IsAlive)
+ return false;
+
+ this->MovingProcess(true);
+ const auto pLinked = this->LinkedTo;
+
+ return pLinked && pLinked->IsAlive;
+ }
+ inline void UpdateOnBridge(CellClass* pNewCell, CellClass* pOldCell)
+ {
+ const auto pLinked = this->LinkedTo;
+
+ if (pNewCell->Level == (pOldCell->Level - 4))
+ {
+ if (pNewCell->ContainsBridge())
+ pLinked->OnBridge = true;
+ else if (pOldCell->ContainsBridge())
+ pLinked->OnBridge = false;
+ }
+ else if (!pNewCell->ContainsBridge())
+ {
+ if (pOldCell->ContainsBridge())
+ pLinked->OnBridge = false;
+ }
+ }
+ inline int UpdateSpeedAccum(int& speedAccum); // Avoid using goto
+public:
+ static bool IsReversing(FootClass* pFoot)
+ {
+ const auto pLoco = locomotion_cast(pFoot->Locomotor);
+
+ return pLoco && !pLoco->IsForward;
+ }
+};
diff --git a/src/Phobos.COM.cpp b/src/Phobos.COM.cpp
index b31c953dfd..1c46e1f558 100644
--- a/src/Phobos.COM.cpp
+++ b/src/Phobos.COM.cpp
@@ -3,18 +3,19 @@
#include
#include
+#include
-
-#ifdef CUSTOM_LOCO_EXAMPLE_ENABLED // Register the loco
DEFINE_HOOK(0x6BD68D, WinMain_PhobosRegistrations, 0x6)
{
Debug::Log("Starting COM registration...\n");
// Add new classes to be COM-registered below
+#ifdef CUSTOM_LOCO_EXAMPLE_ENABLED // Register the loco
RegisterFactoryForClass();
+#endif
+ RegisterFactoryForClass();
Debug::Log("COM registration done!\n");
return 0;
}
-#endif
diff --git a/src/Phobos.COM.h b/src/Phobos.COM.h
index ef98e297fe..6838b4a3cf 100644
--- a/src/Phobos.COM.h
+++ b/src/Phobos.COM.h
@@ -13,7 +13,7 @@ void RegisterFactoryForClass(IClassFactory* pFactory)
else
Debug::Log("Class factory for %s registered.\n", typeid(T).name());
- Game::COMClasses->AddItem((ULONG)dwRegister);
+ Game::COMClasses.AddItem((ULONG)dwRegister);
}
// Registers an automatically created factory for a class.
diff --git a/src/Utilities/TemplateDef.h b/src/Utilities/TemplateDef.h
index a533f4e65f..0ef1baff23 100644
--- a/src/Utilities/TemplateDef.h
+++ b/src/Utilities/TemplateDef.h
@@ -54,6 +54,7 @@
#include
#include
#include
+#include
namespace detail
{
@@ -1115,6 +1116,7 @@ if(_strcmpi(parser.value(), #name) == 0){ value = __uuidof(name ## LocomotionCla
#ifdef CUSTOM_LOCO_EXAMPLE_ENABLED // Add semantic parsing for loco
PARSE_IF_IS_PHOBOS_LOCO(Test);
#endif
+ PARSE_IF_IS_PHOBOS_LOCO(AdvancedDrive);
#undef PARSE_IF_IS_PHOBOS_LOCO