diff --git a/Phobos.vcxproj b/Phobos.vcxproj
index 781debeb8a..48cf99da95 100644
--- a/Phobos.vcxproj
+++ b/Phobos.vcxproj
@@ -128,6 +128,9 @@
+
+
+
@@ -370,6 +373,7 @@
+
diff --git a/src/Ext/LaserDraw/Body.cpp b/src/Ext/LaserDraw/Body.cpp
new file mode 100644
index 0000000000..b5a24bf236
--- /dev/null
+++ b/src/Ext/LaserDraw/Body.cpp
@@ -0,0 +1,160 @@
+#include
+
+#include
+#include
+
+std::unordered_map> LaserDrawExt::ShooterToLasers;
+std::unordered_map> LaserDrawExt::TargetToLasers;
+
+LaserDrawExt::ExtContainer LaserDrawExt::ExtMap;
+
+void LaserDrawExt::ExtData::Initialize(TechnoClass* pShooter, AbstractClass* pTarget, int weaponIdx, PositionFollow mode,
+ const CoordStruct& initialSource, const CoordStruct& localFLH, int burstIndex, bool stopOnFirerConvert)
+{
+ // reset any previous tracking state (an existing laser may be re-tracked)
+ this->Shooter = nullptr;
+ this->Target = nullptr;
+ this->WeaponIndex = 0;
+ this->FollowMode = PositionFollow::None;
+ this->SavedOffset = CoordStruct::Empty;
+ this->LocalFLH = CoordStruct::Empty;
+ this->FrozenBurstIndex = 0;
+ this->StopOnFirerConvert = false;
+ this->OriginalType = nullptr;
+
+ const auto pShooterBuilding = abstract_cast(pShooter);
+
+ if (pShooterBuilding && pShooterBuilding->Type->MaxNumberOccupants > 0)
+ mode &= ~PositionFollow::Firer;
+
+ if (pShooter && (mode & PositionFollow::Firer))
+ {
+ this->Shooter = pShooter;
+ this->LocalFLH = localFLH;
+ this->FrozenBurstIndex = burstIndex;
+ this->StopOnFirerConvert = stopOnFirerConvert;
+
+ if (stopOnFirerConvert)
+ this->OriginalType = pShooter->GetTechnoType();
+
+ const int savedBurstIndex = pShooter->CurrentBurstIndex;
+ pShooter->CurrentBurstIndex = burstIndex;
+ const CoordStruct worldFLH = pShooter->GetFLH(weaponIdx, CoordStruct::Empty);
+ pShooter->CurrentBurstIndex = savedBurstIndex;
+
+ this->SavedOffset = initialSource - worldFLH;
+ }
+
+ if (mode & PositionFollow::Target)
+ this->Target = abstract_cast(pTarget);
+
+ this->WeaponIndex = weaponIdx;
+ this->FollowMode = mode;
+}
+
+void LaserDrawExt::ExtData::Register()
+{
+ if (this->Shooter && (this->FollowMode & PositionFollow::Firer))
+ LaserDrawExt::ShooterToLasers[this->Shooter].push_back(this->OwnerObject());
+
+ if (this->Target && (this->FollowMode & PositionFollow::Target))
+ LaserDrawExt::TargetToLasers[this->Target].push_back(this->OwnerObject());
+}
+
+void LaserDrawExt::ExtData::Unregister()
+{
+ if (this->Shooter)
+ {
+ auto it = LaserDrawExt::ShooterToLasers.find(this->Shooter);
+ if (it != LaserDrawExt::ShooterToLasers.end())
+ {
+ auto& vec = it->second;
+ vec.erase(std::remove(vec.begin(), vec.end(), this->OwnerObject()), vec.end());
+ if (vec.empty())
+ LaserDrawExt::ShooterToLasers.erase(it);
+ }
+ }
+ if (this->Target)
+ {
+ auto it = LaserDrawExt::TargetToLasers.find(this->Target);
+ if (it != LaserDrawExt::TargetToLasers.end())
+ {
+ auto& vec = it->second;
+ vec.erase(std::remove(vec.begin(), vec.end(), this->OwnerObject()), vec.end());
+ if (vec.empty())
+ LaserDrawExt::TargetToLasers.erase(it);
+ }
+ }
+}
+
+// --- padding pointer ---
+
+static uint16_t GetPadHalf(LaserDrawClass* pLaser, uintptr_t offset)
+{
+ return *reinterpret_cast(reinterpret_cast(pLaser) + offset);
+}
+
+static void SetPadHalf(LaserDrawClass* pLaser, uintptr_t offset, uint16_t value)
+{
+ *reinterpret_cast(reinterpret_cast(pLaser) + offset) = value;
+}
+
+void LaserDrawExt::ClearPointer(LaserDrawClass* pLaser)
+{
+ if (!pLaser)
+ return;
+
+ SetPadHalf(pLaser, LaserDrawExt::PadHighOffset, 0);
+ SetPadHalf(pLaser, LaserDrawExt::PadLowOffset, 0);
+}
+
+LaserDrawExt::ExtData* LaserDrawExt::Find(LaserDrawClass* pLaser)
+{
+ if (!pLaser)
+ return nullptr;
+
+ if (reinterpret_cast(pLaser) < 0x10000)
+ return nullptr;
+
+ const uintptr_t address = (static_cast(GetPadHalf(pLaser, PadHighOffset)) << 16)
+ | GetPadHalf(pLaser, PadLowOffset);
+
+ return reinterpret_cast(address);
+}
+
+LaserDrawExt::ExtData* LaserDrawExt::Allocate(LaserDrawClass* pLaser)
+{
+ auto* pExt = ExtMap.Allocate(pLaser);
+ if (!pExt)
+ return nullptr;
+
+ const uintptr_t address = reinterpret_cast(pExt);
+ SetPadHalf(pLaser, PadHighOffset, static_cast(address >> 16));
+ SetPadHalf(pLaser, PadLowOffset, static_cast(address & 0xFFFF));
+
+ return pExt;
+}
+
+void LaserDrawExt::Release(LaserDrawClass* pLaser)
+{
+ ClearPointer(pLaser);
+ ExtMap.Remove(pLaser);
+}
+
+void LaserDrawExt::ResetPointer(LaserDrawClass* pLaser)
+{
+ ClearPointer(pLaser);
+}
+
+LaserDrawExt::ExtContainer::ExtContainer() : Container("LaserDraw") { }
+LaserDrawExt::ExtContainer::~ExtContainer() = default;
+
+bool LaserDrawExt::LoadGlobals(PhobosStreamReader& Stm)
+{
+ return Stm.Success();
+}
+
+bool LaserDrawExt::SaveGlobals(PhobosStreamWriter& Stm)
+{
+ return Stm.Success();
+}
diff --git a/src/Ext/LaserDraw/Body.h b/src/Ext/LaserDraw/Body.h
new file mode 100644
index 0000000000..80a19e402e
--- /dev/null
+++ b/src/Ext/LaserDraw/Body.h
@@ -0,0 +1,76 @@
+#pragma once
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+
+class LaserDrawExt
+{
+public:
+ using base_type = LaserDrawClass;
+
+ static constexpr DWORD Canary = 0x4C617365; // "Lase"
+
+ static std::unordered_map> ShooterToLasers;
+ static std::unordered_map> TargetToLasers;
+
+ class ExtData final : public Extension
+ {
+ public:
+ TechnoClass* Shooter = nullptr;
+ ObjectClass* Target = nullptr;
+ int WeaponIndex = 0;
+ PositionFollow FollowMode = PositionFollow::None;
+ CoordStruct SavedOffset = CoordStruct::Empty;
+ CoordStruct LocalFLH = CoordStruct::Empty;
+ int FrozenBurstIndex = 0;
+ bool StopOnFirerConvert = false;
+ const TechnoTypeClass* OriginalType = nullptr;
+
+ ExtData(LaserDrawClass* pOwner) : Extension(pOwner) { }
+
+ ~ExtData() override
+ {
+ this->Unregister();
+ LaserDrawExt::ClearPointer(this->OwnerObject());
+ }
+
+ void Initialize(TechnoClass* pShooter, AbstractClass* pTarget, int weaponIdx, PositionFollow mode,
+ const CoordStruct& initialSource, const CoordStruct& localFLH, int burstIndex, bool stopOnFirerConvert);
+
+ void Register();
+ void Unregister();
+ };
+
+ static constexpr uintptr_t PadHighOffset = 0x22; // align_22
+ static constexpr uintptr_t PadLowOffset = 0x4A; // align_4A
+
+ static ExtData* Find(LaserDrawClass* pLaser);
+ static ExtData* Allocate(LaserDrawClass* pLaser);
+ static void Release(LaserDrawClass* pLaser);
+ static void ResetPointer(LaserDrawClass* pLaser);
+ static void ClearPointer(LaserDrawClass* pLaser);
+
+ class ExtContainer final : public Container
+ {
+ public:
+ ExtContainer();
+ ~ExtContainer();
+ };
+
+ static ExtContainer ExtMap;
+
+ static bool LoadGlobals(PhobosStreamReader& Stm);
+ static bool SaveGlobals(PhobosStreamWriter& Stm);
+};
+
+using LaserDrawExtension = LaserDrawExt::ExtData;
diff --git a/src/Ext/LaserDraw/Hooks.cpp b/src/Ext/LaserDraw/Hooks.cpp
new file mode 100644
index 0000000000..f7f68bc1df
--- /dev/null
+++ b/src/Ext/LaserDraw/Hooks.cpp
@@ -0,0 +1,258 @@
+#include
+
+#include
+#include
+#include
+#include
+
+namespace LaserRT
+{
+ TechnoClass* Shooter = nullptr;
+ AbstractClass* Target = nullptr;
+ int WeaponIndex = 0;
+ bool IgnoreShooter = false;
+ CoordStruct SavedLocalFLH = CoordStruct::Empty;
+ int SavedBurstIndex = 0;
+
+ void SetLaserTrackingData(LaserDrawClass* pLaser, TechnoClass* pShooter, AbstractClass* pTarget, int weaponIdx, PositionFollow mode, bool ignoreShooter)
+ {
+ CoordStruct localFLH;
+ int burstIndex = 0;
+ bool stopOnFirerConvert = false;
+
+ if (pShooter)
+ {
+ bool flhFound = false;
+ localFLH = TechnoExt::GetBurstFLH(pShooter, weaponIdx, flhFound);
+
+ if (!flhFound)
+ localFLH = pShooter->GetWeapon(weaponIdx)->FLH;
+
+ burstIndex = pShooter->CurrentBurstIndex;
+
+ if (const auto pWeapon = pShooter->GetWeapon(weaponIdx)->WeaponType)
+ {
+ const auto pWeaponExt = WeaponTypeExt::Fetch(pWeapon);
+ stopOnFirerConvert = pWeaponExt->LaserPositionUpdate_StopOnFirerConvert.Get(RulesExt::Global()->LaserPositionUpdate_StopOnFirerConvert);
+ }
+ }
+
+ auto* pExt = LaserDrawExt::Find(pLaser);
+ if (!pExt)
+ pExt = LaserDrawExt::Allocate(pLaser);
+ if (!pExt)
+ return;
+
+ pExt->Unregister();
+ pExt->Initialize(ignoreShooter ? nullptr : pShooter, pTarget, weaponIdx, mode, pLaser->Source, localFLH, burstIndex, stopOnFirerConvert);
+ pExt->Register();
+ }
+}
+
+// container hooks
+
+// The CTOR must attach an extension even with empty tracking data - removing it
+// would break DiskLaser's charging ring (start point on ring A, end point on ring B).
+DEFINE_HOOK(0x54FE60, LaserDrawClass_CTOR_Update, 0x5)
+{
+ GET(LaserDrawClass*, pLaser, ECX);
+
+ LaserDrawExt::ResetPointer(pLaser);
+
+ if (Phobos::Optimizations::DisableLaserTracking)
+ return 0;
+
+ LaserDrawExt::Allocate(pLaser);
+
+ return 0;
+}
+
+static void RemoveLaserFromTracking(LaserDrawClass* pLaser)
+{
+ if (auto* pExt = LaserDrawExt::Find(pLaser))
+ {
+ pExt->Unregister();
+ LaserDrawExt::Release(pLaser);
+ }
+}
+
+DEFINE_HOOK_AGAIN(0x5501D7, LaserDrawClass_RemoveTracking, 0x5)
+DEFINE_HOOK(0x550016, LaserDrawClass_RemoveTracking, 0x6)
+{
+ GET(LaserDrawClass*, pLaser, ESI);
+ RemoveLaserFromTracking(pLaser);
+ return 0;
+}
+
+void WeaponTypeExt::OnObjectRemoved(ObjectClass* pObject)
+{
+ auto itShoot = LaserDrawExt::ShooterToLasers.find(pObject);
+ if (itShoot != LaserDrawExt::ShooterToLasers.end())
+ {
+ for (auto pLaser : itShoot->second)
+ {
+ if (auto* pExt = LaserDrawExt::Find(pLaser))
+ {
+ if (pExt->Shooter == pObject)
+ pExt->Shooter = nullptr;
+
+ if (!pExt->Shooter && !pExt->Target)
+ LaserDrawExt::Release(pLaser);
+ }
+ }
+ LaserDrawExt::ShooterToLasers.erase(itShoot);
+ }
+ LaserDrawExt::ShooterToLasers.erase(pObject);
+
+ auto itTarget = LaserDrawExt::TargetToLasers.find(pObject);
+ if (itTarget != LaserDrawExt::TargetToLasers.end())
+ {
+ for (auto pLaser : itTarget->second)
+ {
+ if (auto* pExt = LaserDrawExt::Find(pLaser))
+ {
+ if (pExt->Target == pObject)
+ pExt->Target = nullptr;
+
+ if (!pExt->Shooter && !pExt->Target)
+ LaserDrawExt::Release(pLaser);
+ }
+ }
+ LaserDrawExt::TargetToLasers.erase(itTarget);
+ }
+ LaserDrawExt::TargetToLasers.erase(pObject);
+}
+
+// hooks
+
+DEFINE_HOOK(0x6FD210, TechnoClass_LaserZap_SetTrackingContext, 0x7)
+{
+ if (Phobos::Optimizations::DisableLaserTracking)
+ return 0;
+
+ GET(TechnoClass*, pShooter, ECX);
+ GET_STACK(ObjectClass*, pTarget, 0x4);
+ GET_STACK(const int, weaponIdx, 0x8);
+
+ LaserRT::Shooter = LaserRT::IgnoreShooter ? nullptr : pShooter;
+ LaserRT::Target = pTarget;
+ LaserRT::WeaponIndex = weaponIdx;
+
+ LaserRT::SavedBurstIndex = pShooter->CurrentBurstIndex;
+ bool flhFound = false;
+ LaserRT::SavedLocalFLH = TechnoExt::GetBurstFLH(pShooter, weaponIdx, flhFound);
+
+ if (!flhFound)
+ {
+ LaserRT::SavedLocalFLH = pShooter->GetWeapon(weaponIdx)->FLH;
+
+ if (LaserRT::SavedBurstIndex % 2 != 0)
+ LaserRT::SavedLocalFLH.Y = -LaserRT::SavedLocalFLH.Y;
+ }
+ return 0;
+}
+
+DEFINE_HOOK(0x6FD446, TechnoClass_LaserZap_Tracking, 0x7)
+{
+ if (Phobos::Optimizations::DisableLaserTracking)
+ return 0;
+
+ GET(WeaponTypeClass*, pWeapon, ECX);
+ GET(LaserDrawClass*, pLaser, EAX);
+ const auto mode = WeaponTypeExt::Fetch(pWeapon)->LaserPositionUpdate.Get();
+
+ if (mode == PositionFollow::None)
+ return 0;
+
+ const auto pShooter = std::exchange(LaserRT::Shooter, nullptr);
+ const auto pTarget = std::exchange(LaserRT::Target, nullptr);
+ const int weaponIdx = std::exchange(LaserRT::WeaponIndex, 0);
+
+ // The current implementation no longer requires storing into a variable, but resetting operations still need to be handled.
+ std::exchange(LaserRT::SavedLocalFLH, CoordStruct::Empty);
+ std::exchange(LaserRT::SavedBurstIndex, 0);
+
+ LaserRT::SetLaserTrackingData(pLaser, pShooter, pTarget, weaponIdx, mode, false);
+ return 0;
+}
+
+static LaserDrawClass* __fastcall Shrapnel_CreateLaser_Wrapper(TechnoClass* pShooter, void*, ObjectClass* pTarget, int weaponIdx, WeaponTypeClass* pWeapon, const CoordStruct& sourceCoords)
+{
+ const auto mode = WeaponTypeExt::Fetch(pWeapon)->LaserPositionUpdate.Get();
+
+ if (mode == PositionFollow::None)
+ return pShooter->CreateLaser(pTarget, weaponIdx, pWeapon, sourceCoords);
+
+ LaserRT::IgnoreShooter = true;
+ const auto pLaser = pShooter->CreateLaser(pTarget, weaponIdx, pWeapon, sourceCoords);
+ LaserRT::IgnoreShooter = false;
+ return pLaser;
+}
+DEFINE_FUNCTION_JUMP(CALL, 0x46A8AC, Shrapnel_CreateLaser_Wrapper)
+DEFINE_FUNCTION_JUMP(CALL, 0x46AD81, Shrapnel_CreateLaser_Wrapper)
+
+// DiskLaser main beam activation
+DEFINE_HOOK(0x4A7696, DiskLaser_Update_ActivateMainBeam_Tracking, 0x6)
+{
+ if (Phobos::Optimizations::DisableLaserTracking)
+ return 0;
+
+ GET(LaserDrawClass*, pLaser, EAX);
+
+ if (!pLaser)
+ return 0;
+
+ GET(DiskLaserClass*, pDiskLaser, ESI);
+ const auto pWeapon = pDiskLaser->Weapon;
+
+ if (!pWeapon)
+ return 0;
+
+ const auto mode = WeaponTypeExt::Fetch(pWeapon)->LaserPositionUpdate.Get();
+
+ if (mode == PositionFollow::None)
+ return 0;
+
+ if (pLaser->Source == pLaser->Target)
+ return 0;
+
+ LaserRT::SetLaserTrackingData(pLaser, pDiskLaser->Owner, pDiskLaser->Target, 0, mode, false);
+ return 0;
+}
+
+// Per‑frame coordinate update
+DEFINE_HOOK(0x550173, LaserDrawClass_Update_Tracking, 0x6)
+{
+ if (LaserDrawExt::ExtMap.size() == 0)
+ return 0;
+
+ GET(LaserDrawClass*, pLaser, ESI);
+ auto* pExt = LaserDrawExt::Find(pLaser);
+
+ if (!pExt)
+ return 0;
+
+ if (const auto pShooter = pExt->Shooter)
+ {
+ if (pExt->StopOnFirerConvert && pExt->OriginalType)
+ {
+ if (pShooter->GetTechnoType() != pExt->OriginalType)
+ pExt->Shooter = nullptr;
+ }
+
+ if (pExt->Shooter)
+ {
+ const int savedBurstIndex = pShooter->CurrentBurstIndex;
+ pShooter->CurrentBurstIndex = pExt->FrozenBurstIndex;
+ const CoordStruct worldFLH = pShooter->GetFLH(pExt->WeaponIndex, CoordStruct::Empty);
+ pShooter->CurrentBurstIndex = savedBurstIndex;
+
+ pLaser->Source = worldFLH + pExt->SavedOffset;
+ }
+ }
+
+ if (const auto pTarget = pExt->Target)
+ pLaser->Target = pTarget->GetTargetCoords();
+
+ return 0;
+}
diff --git a/src/Misc/Hooks.LaserDraw.cpp b/src/Misc/Hooks.LaserDraw.cpp
index 9cdfbb89a0..51a717bc79 100644
--- a/src/Misc/Hooks.LaserDraw.cpp
+++ b/src/Misc/Hooks.LaserDraw.cpp
@@ -1,8 +1,6 @@
#include
-#include
#include
#include
-#include
namespace LaserDrawTemp
{
@@ -55,344 +53,3 @@ DEFINE_HOOK(0x6FD3FD, TechnoClass_LaserZap_ZAdjust, 0x5)
return 0;
}
-
-#pragma region LaserPositionUpdate
-
-namespace LaserRT
-{
- struct TrackingData
- {
- TechnoClass* Shooter { nullptr };
- ObjectClass* Target { nullptr };
- int WeaponIndex { 0 };
- PositionFollow FollowMode { PositionFollow::None };
- CoordStruct SavedOffset { CoordStruct::Empty };
- CoordStruct LocalFLH { CoordStruct::Empty };
- int FrozenBurstIndex { 0 };
- bool StopOnFirerConvert { false };
- const TechnoTypeClass* OriginalType { nullptr };
-
- void Initialize(TechnoClass* pShooter, AbstractClass* pTarget, int weaponIdx, PositionFollow mode, const CoordStruct& initialSource, const CoordStruct& localFLH, int burstIndex, bool stopOnFirerConvert)
- {
- const auto pShooterBuilding = abstract_cast(pShooter);
-
- if (pShooterBuilding && pShooterBuilding->Type->MaxNumberOccupants > 0)
- mode &= ~PositionFollow::Firer;
-
- if (pShooter && (mode & PositionFollow::Firer))
- {
- this->Shooter = pShooter;
- this->LocalFLH = localFLH;
- this->FrozenBurstIndex = burstIndex;
- this->StopOnFirerConvert = stopOnFirerConvert;
-
- if (stopOnFirerConvert)
- this->OriginalType = pShooter->GetTechnoType();
-
- const int savedBurstIndex = pShooter->CurrentBurstIndex;
- pShooter->CurrentBurstIndex = burstIndex;
- const CoordStruct worldFLH = pShooter->GetFLH(weaponIdx, localFLH);
- pShooter->CurrentBurstIndex = savedBurstIndex;
-
- this->SavedOffset = initialSource - worldFLH;
- }
-
- if (mode & PositionFollow::Target)
- this->Target = abstract_cast(pTarget);
-
- this->WeaponIndex = weaponIdx;
- this->FollowMode = mode;
- }
- };
-
- std::unordered_map TrackingMap;
-
- std::unordered_map> ShooterToLasers;
- std::unordered_map> TargetToLasers;
-
- static void RegisterTracking(LaserDrawClass* pLaser, const TrackingData& data)
- {
- if (data.Shooter && (data.FollowMode & PositionFollow::Firer))
- ShooterToLasers[data.Shooter].push_back(pLaser);
- if (data.Target && (data.FollowMode & PositionFollow::Target))
- TargetToLasers[data.Target].push_back(pLaser);
- }
-
- static void UnregisterTracking(LaserDrawClass* pLaser, const TrackingData& data)
- {
- if (data.Shooter)
- {
- auto it = ShooterToLasers.find(data.Shooter);
- if (it != ShooterToLasers.end())
- {
- auto& vec = it->second;
- vec.erase(std::remove(vec.begin(), vec.end(), pLaser), vec.end());
- if (vec.empty())
- ShooterToLasers.erase(it);
- }
- }
- if (data.Target)
- {
- auto it = TargetToLasers.find(data.Target);
- if (it != TargetToLasers.end())
- {
- auto& vec = it->second;
- vec.erase(std::remove(vec.begin(), vec.end(), pLaser), vec.end());
- if (vec.empty())
- TargetToLasers.erase(it);
- }
- }
- }
-
- void SetLaserTrackingData(LaserDrawClass* pLaser, TechnoClass* pShooter, AbstractClass* pTarget, int weaponIdx, PositionFollow mode, bool ignoreShooter)
- {
- CoordStruct localFLH;
- int burstIndex = 0;
- bool stopOnFirerConvert = false;
-
- if (pShooter)
- {
- bool flhFound = false;
- localFLH = TechnoExt::GetBurstFLH(pShooter, weaponIdx, flhFound);
-
- if (!flhFound)
- localFLH = pShooter->GetWeapon(weaponIdx)->FLH;
-
- burstIndex = pShooter->CurrentBurstIndex;
-
- if (const auto pWeapon = pShooter->GetWeapon(weaponIdx)->WeaponType)
- {
- const auto pWeaponExt = WeaponTypeExt::Fetch(pWeapon);
- stopOnFirerConvert = pWeaponExt->LaserPositionUpdate_StopOnFirerConvert.Get(RulesExt::Global()->LaserPositionUpdate_StopOnFirerConvert);
- }
- }
-
- TrackingData data;
- data.Initialize(ignoreShooter ? nullptr : pShooter, pTarget, weaponIdx, mode, pLaser->Source, localFLH, burstIndex, stopOnFirerConvert);
-
- auto it = TrackingMap.find(pLaser);
- if (it != TrackingMap.end())
- UnregisterTracking(pLaser, it->second);
-
- TrackingMap[pLaser] = data;
- RegisterTracking(pLaser, data);
- }
-
- TechnoClass* Shooter = nullptr;
- AbstractClass* Target = nullptr;
- int WeaponIndex = 0;
- bool IgnoreShooter = false;
- CoordStruct SavedLocalFLH = CoordStruct::Empty;
- int SavedBurstIndex = 0;
-}
-
-// container hooks
-
-// IsLaser this is no longer necessary, but the handling of DiskLaser is more complex, and keeping the CTOR is currently the most cost-effective solution.
-DEFINE_HOOK(0x54FE60, LaserDrawClass_CTOR_Update, 0x5)
-{
- if (!Phobos::Optimizations::DisableLaserTracking)
- {
- GET(LaserDrawClass*, pLaser, ECX);
- LaserRT::TrackingMap[pLaser] = LaserRT::TrackingData {};
- }
- return 0;
-}
-
-DEFINE_HOOK_AGAIN(0x5501D7, LaserDrawClass_DTOR_Tracking, 0x5)
-DEFINE_HOOK_AGAIN(0x5500EF, LaserDrawClass_DTOR_Tracking, 0x5)
-DEFINE_HOOK_AGAIN(0x550016, LaserDrawClass_DTOR_Tracking, 0x6)
-DEFINE_HOOK(0x54FFB0, LaserDrawClass_DTOR_Tracking, 0x7) // LaserDrawClass::DTOR
-{
- GET(LaserDrawClass*, pLaser, ECX);
-
- auto it = LaserRT::TrackingMap.find(pLaser);
- if (it != LaserRT::TrackingMap.end())
- {
- LaserRT::UnregisterTracking(pLaser, it->second);
- LaserRT::TrackingMap.erase(it);
- }
-
- return 0;
-}
-
-void WeaponTypeExt::OnObjectRemoved(ObjectClass* pObject)
-{
- auto itShoot = LaserRT::ShooterToLasers.find(pObject);
- if (itShoot != LaserRT::ShooterToLasers.end())
- {
- for (auto pLaser : itShoot->second)
- {
- auto dataIt = LaserRT::TrackingMap.find(pLaser);
- if (dataIt != LaserRT::TrackingMap.end())
- {
- auto& data = dataIt->second;
- if (data.Shooter == pObject)
- data.Shooter = nullptr;
- if (!data.Shooter && !data.Target)
- {
- LaserRT::TrackingMap.erase(dataIt);
- }
- }
- }
- LaserRT::ShooterToLasers.erase(itShoot);
- }
- LaserRT::ShooterToLasers.erase(pObject);
-
- auto itTarget = LaserRT::TargetToLasers.find(pObject);
- if (itTarget != LaserRT::TargetToLasers.end())
- {
- for (auto pLaser : itTarget->second)
- {
- auto dataIt = LaserRT::TrackingMap.find(pLaser);
- if (dataIt != LaserRT::TrackingMap.end())
- {
- auto& data = dataIt->second;
- if (data.Target == pObject)
- data.Target = nullptr;
- if (!data.Shooter && !data.Target)
- LaserRT::TrackingMap.erase(dataIt);
- }
- }
- LaserRT::TargetToLasers.erase(itTarget);
- }
- LaserRT::TargetToLasers.erase(pObject);
-}
-
-// hooks
-
-DEFINE_HOOK(0x6FD210, TechnoClass_LaserZap_SetTrackingContext, 0x7)
-{
- if (Phobos::Optimizations::DisableLaserTracking)
- return 0;
-
- GET(TechnoClass*, pShooter, ECX);
- GET_STACK(ObjectClass*, pTarget, 0x4);
- GET_STACK(const int, weaponIdx, 0x8);
-
- LaserRT::Shooter = LaserRT::IgnoreShooter ? nullptr : pShooter;
- LaserRT::Target = pTarget;
- LaserRT::WeaponIndex = weaponIdx;
-
- LaserRT::SavedBurstIndex = pShooter->CurrentBurstIndex;
- bool flhFound = false;
- LaserRT::SavedLocalFLH = TechnoExt::GetBurstFLH(pShooter, weaponIdx, flhFound);
-
- if (!flhFound)
- {
- LaserRT::SavedLocalFLH = pShooter->GetWeapon(weaponIdx)->FLH;
-
- if (LaserRT::SavedBurstIndex % 2 != 0)
- LaserRT::SavedLocalFLH.Y = -LaserRT::SavedLocalFLH.Y;
- }
- return 0;
-}
-
-DEFINE_HOOK(0x6FD446, TechnoClass_LaserZap_Tracking, 0x7)
-{
- if (Phobos::Optimizations::DisableLaserTracking)
- return 0;
-
- GET(WeaponTypeClass*, pWeapon, ECX);
- GET(LaserDrawClass*, pLaser, EAX);
- const auto mode = WeaponTypeExt::Fetch(pWeapon)->LaserPositionUpdate.Get();
-
- if (mode == PositionFollow::None)
- return 0;
-
- const auto pShooter = std::exchange(LaserRT::Shooter, nullptr);
- const auto pTarget = std::exchange(LaserRT::Target, nullptr);
- const int weaponIdx = std::exchange(LaserRT::WeaponIndex, 0);
-
- // The current implementation no longer requires storing into a variable, but resetting operations still need to be handled.
- std::exchange(LaserRT::SavedLocalFLH, CoordStruct::Empty);
- std::exchange(LaserRT::SavedBurstIndex, 0);
-
- LaserRT::SetLaserTrackingData(pLaser, pShooter, pTarget, weaponIdx, mode, false);
- return 0;
-}
-
-static LaserDrawClass* __fastcall Shrapnel_CreateLaser_Wrapper(TechnoClass* pShooter, void*, ObjectClass* pTarget, int weaponIdx, WeaponTypeClass* pWeapon, const CoordStruct& sourceCoords)
-{
- const auto mode = WeaponTypeExt::Fetch(pWeapon)->LaserPositionUpdate.Get();
-
- if (mode == PositionFollow::None)
- return pShooter->CreateLaser(pTarget, weaponIdx, pWeapon, sourceCoords);
-
- LaserRT::IgnoreShooter = true;
- const auto pLaser = pShooter->CreateLaser(pTarget, weaponIdx, pWeapon, sourceCoords);
- LaserRT::IgnoreShooter = false;
- return pLaser;
-}
-DEFINE_FUNCTION_JUMP(CALL, 0x46A8AC, Shrapnel_CreateLaser_Wrapper)
-DEFINE_FUNCTION_JUMP(CALL, 0x46AD81, Shrapnel_CreateLaser_Wrapper)
-
-// DiskLaser main beam activation
-DEFINE_HOOK(0x4A7696, DiskLaser_Update_ActivateMainBeam_Tracking, 0x6)
-{
- if (Phobos::Optimizations::DisableLaserTracking)
- return 0;
-
- GET(LaserDrawClass*, pLaser, EAX);
-
- if (!pLaser)
- return 0;
-
- GET(DiskLaserClass*, pDiskLaser, ESI);
- const auto pWeapon = pDiskLaser->Weapon;
-
- if (!pWeapon)
- return 0;
-
- const auto mode = WeaponTypeExt::Fetch(pWeapon)->LaserPositionUpdate.Get();
-
- if (mode == PositionFollow::None)
- return 0;
-
- if (pLaser->Source == pLaser->Target)
- return 0;
-
- LaserRT::SetLaserTrackingData(pLaser, pDiskLaser->Owner, pDiskLaser->Target, 0, mode, false);
- return 0;
-}
-
-// Per‑frame coordinate update
-DEFINE_HOOK(0x550173, LaserDrawClass_Update_Tracking, 0x6)
-{
- if (LaserRT::TrackingMap.empty())
- return 0;
-
- GET(LaserDrawClass*, pLaser, ESI);
- const auto it = LaserRT::TrackingMap.find(pLaser);
-
- if (it == LaserRT::TrackingMap.cend())
- return 0;
-
- auto& data = it->second;
-
- if (const auto pShooter = data.Shooter)
- {
- if (data.StopOnFirerConvert && data.OriginalType)
- {
- if (pShooter->GetTechnoType() != data.OriginalType)
- data.Shooter = nullptr;
- }
-
- if (data.Shooter)
- {
- const int savedBurstIndex = pShooter->CurrentBurstIndex;
- pShooter->CurrentBurstIndex = data.FrozenBurstIndex;
- const CoordStruct worldFLH = pShooter->GetFLH(data.WeaponIndex, data.LocalFLH);
- pShooter->CurrentBurstIndex = savedBurstIndex;
-
- pLaser->Source = worldFLH + data.SavedOffset;
- }
- }
-
- if (const auto pTarget = data.Target)
- pLaser->Target = pTarget->GetTargetCoords();
-
- return 0;
-}
-
-#pragma endregion
diff --git a/src/Phobos.Ext.cpp b/src/Phobos.Ext.cpp
index 58e30fc369..0e8df06eef 100644
--- a/src/Phobos.Ext.cpp
+++ b/src/Phobos.Ext.cpp
@@ -8,6 +8,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -316,6 +317,7 @@ using PhobosTypeRegistry = TypeRegistry <
WarheadTypeExt,
WeaponTypeExt,
ParticleTypeExt,
+ LaserDrawExt,
// New classes
ShieldTypeClass,
LaserTrailTypeClass,
diff --git a/src/Phobos.cpp b/src/Phobos.cpp
index 0586921434..0a750996b8 100644
--- a/src/Phobos.cpp
+++ b/src/Phobos.cpp
@@ -391,6 +391,20 @@ void Phobos::ApplyOptimizations()
Patch::Apply_RAW(0x4D62C0, { 0x8A, 0x88, 0x95, 0x06, 0x00, 0x00 });
}
+ // Disable LaserPositionUpdate tracking hooks when no weapon uses the feature
+ if (Phobos::Optimizations::DisableLaserTracking)
+ {
+ Patch::Apply_RAW(0x54FE60, { 0x56, 0x8B, 0xF1, 0x33, 0xC0 }); // LaserDrawClass_CTOR_Update
+ Patch::Apply_RAW(0x550016, { 0x8B, 0x15, 0x78, 0xC8, 0xAB, 0x00 }); // LaserDrawClass_RemoveTracking
+ Patch::Apply_RAW(0x5501D7, { 0xA1, 0x78, 0xC8, 0xAB, 0x00 }); // LaserDrawClass_RemoveTracking
+ Patch::Apply_RAW(0x6FD210, { 0x83, 0xEC, 0x38, 0x8B, 0x44, 0x24, 0x44 }); // TechnoClass_LaserZap_SetTrackingContext
+ Patch::Apply_RAW(0x6FD446, { 0x5F, 0x5E, 0x5D, 0x5B, 0x83, 0xC4, 0x38 }); // TechnoClass_LaserZap_Tracking
+ Patch::Apply_RAW(0x46A8AC, { 0xE8, 0x5F, 0x29, 0x29, 0x00 }); // Shrapnel_CreateLaser_Wrapper
+ Patch::Apply_RAW(0x46AD81, { 0xE8, 0x8A, 0x24, 0x29, 0x00 }); // Shrapnel_CreateLaser_Wrapper
+ Patch::Apply_RAW(0x4A7696, { 0x8B, 0x46, 0x2C, 0x8B, 0x56, 0x24 }); // DiskLaser_Update_ActivateMainBeam_Tracking
+ Patch::Apply_RAW(0x550173, { 0x8B, 0x56, 0x08, 0x8B, 0x46, 0x10 }); // LaserDrawClass_Update_Tracking
+ }
+
if (!SessionClass::IsMultiplayer())
{
// Disable TechnoClass_DeleteGap_CellCheck