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