From 0b823ed6e9c349db36fd3fd975f09e819f1b1a68 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Fri, 31 Jul 2026 10:49:14 +0800 Subject: [PATCH 01/12] initial --- src/Misc/Hooks.LaserDraw.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Misc/Hooks.LaserDraw.cpp b/src/Misc/Hooks.LaserDraw.cpp index 4950453ebc..86fcf27a54 100644 --- a/src/Misc/Hooks.LaserDraw.cpp +++ b/src/Misc/Hooks.LaserDraw.cpp @@ -305,8 +305,7 @@ DEFINE_HOOK(0x6FD446, TechnoClass_LaserZap_Tracking, 0x7) return 0; } -static LaserDrawClass* __fastcall Shrapnel_CreateLaser_Wrapper(TechnoClass* pShooter, void*, ObjectClass* pTarget - , int weaponIdx, WeaponTypeClass* pWeapon, const CoordStruct& sourceCoords) +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(); From 83c121c2c52ba3e815dbbca49bc6dfd3d59a1e21 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Fri, 31 Jul 2026 12:04:52 +0800 Subject: [PATCH 02/12] Effective Mode Counter --- src/Ext/WeaponType/Body.cpp | 7 +++++++ src/Ext/WeaponType/Body.h | 1 + src/Misc/Hooks.LaserDraw.cpp | 7 +++++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Ext/WeaponType/Body.cpp b/src/Ext/WeaponType/Body.cpp index 2fe46d5a67..ca0b87395b 100644 --- a/src/Ext/WeaponType/Body.cpp +++ b/src/Ext/WeaponType/Body.cpp @@ -3,6 +3,7 @@ #include WeaponTypeExt::ExtContainer WeaponTypeExt::ExtMap; +int WeaponTypeExt::LaserTrackingWeaponCount = 0; bool WeaponTypeExt::HasRequiredAttachedEffects(TechnoClass* pTarget, TechnoClass* pFirer) const { @@ -118,6 +119,8 @@ void WeaponTypeExt::LoadFromINIFile(CCINIClass* const pINI) this->FeedbackWeapon.Read(exINI, pSection, "FeedbackWeapon"); this->Laser_IsSingleColor.Read(exINI, pSection, "IsSingleColor"); this->LaserPositionUpdate.Read(exINI, pSection, "LaserPositionUpdate"); + if (this->LaserPositionUpdate != PositionFollow::None) + ++WeaponTypeExt::LaserTrackingWeaponCount; this->LaserPositionUpdate_StopOnFirerConvert.Read(exINI, pSection, "LaserPositionUpdate.StopOnFirerConvert"); this->LaserZAdjust.Read(exINI, pSection, "LaserZAdjust"); this->EBoltZAdjust.Read(exINI, pSection, "EBoltZAdjust"); @@ -506,6 +509,10 @@ DEFINE_HOOK(0x77311D, WeaponTypeClass_SDDTOR, 0x6) { GET(WeaponTypeClass*, pItem, ESI); + auto pExt = WeaponTypeExt::ExtMap.Find(pItem); + if (pExt && pExt->LaserPositionUpdate != PositionFollow::None) + --WeaponTypeExt::LaserTrackingWeaponCount; + WeaponTypeExt::ExtMap.Remove(pItem); return 0; diff --git a/src/Ext/WeaponType/Body.h b/src/Ext/WeaponType/Body.h index 2b7832743f..5dddb1ebcb 100644 --- a/src/Ext/WeaponType/Body.h +++ b/src/Ext/WeaponType/Body.h @@ -259,5 +259,6 @@ class WeaponTypeExt final : public AbstractTypeExt // Misc/Hooks.LaserDraw.cpp static void OnObjectRemoved(ObjectClass* pObject); + static int LaserTrackingWeaponCount; }; diff --git a/src/Misc/Hooks.LaserDraw.cpp b/src/Misc/Hooks.LaserDraw.cpp index 86fcf27a54..adb0ccc318 100644 --- a/src/Misc/Hooks.LaserDraw.cpp +++ b/src/Misc/Hooks.LaserDraw.cpp @@ -193,8 +193,11 @@ namespace LaserRT // 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) { - GET(LaserDrawClass*, pLaser, ECX); - LaserRT::TrackingMap[pLaser] = LaserRT::TrackingData {}; + if (WeaponTypeExt::LaserTrackingWeaponCount > 0) + { + GET(LaserDrawClass*, pLaser, ECX); + LaserRT::TrackingMap[pLaser] = LaserRT::TrackingData {}; + } return 0; } From 16d14b9f7e9acb93042e821e9405a1ea3f9722a2 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Fri, 31 Jul 2026 19:30:05 +0800 Subject: [PATCH 03/12] TryFind --- src/Ext/WeaponType/Body.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ext/WeaponType/Body.cpp b/src/Ext/WeaponType/Body.cpp index ca0b87395b..c4ee1729f3 100644 --- a/src/Ext/WeaponType/Body.cpp +++ b/src/Ext/WeaponType/Body.cpp @@ -509,7 +509,7 @@ DEFINE_HOOK(0x77311D, WeaponTypeClass_SDDTOR, 0x6) { GET(WeaponTypeClass*, pItem, ESI); - auto pExt = WeaponTypeExt::ExtMap.Find(pItem); + auto pExt = WeaponTypeExt::ExtMap.TryFind(pItem); if (pExt && pExt->LaserPositionUpdate != PositionFollow::None) --WeaponTypeExt::LaserTrackingWeaponCount; From 2cad8d68f573a73fd2cf157ab42e504f484ade3f Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Fri, 31 Jul 2026 19:36:27 +0800 Subject: [PATCH 04/12] TryFetch --- src/Ext/WeaponType/Body.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ext/WeaponType/Body.cpp b/src/Ext/WeaponType/Body.cpp index c4ee1729f3..ddf178b2e0 100644 --- a/src/Ext/WeaponType/Body.cpp +++ b/src/Ext/WeaponType/Body.cpp @@ -509,7 +509,7 @@ DEFINE_HOOK(0x77311D, WeaponTypeClass_SDDTOR, 0x6) { GET(WeaponTypeClass*, pItem, ESI); - auto pExt = WeaponTypeExt::ExtMap.TryFind(pItem); + auto pExt = WeaponTypeExt::TryFetch(pItem); if (pExt && pExt->LaserPositionUpdate != PositionFollow::None) --WeaponTypeExt::LaserTrackingWeaponCount; From 5e4f4a961ec8e0f029976ab41962c3bd6dbb433a Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Fri, 31 Jul 2026 20:03:04 +0800 Subject: [PATCH 05/12] update --- src/Ext/WeaponType/Body.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ext/WeaponType/Body.cpp b/src/Ext/WeaponType/Body.cpp index ddf178b2e0..e42b66c416 100644 --- a/src/Ext/WeaponType/Body.cpp +++ b/src/Ext/WeaponType/Body.cpp @@ -509,8 +509,8 @@ DEFINE_HOOK(0x77311D, WeaponTypeClass_SDDTOR, 0x6) { GET(WeaponTypeClass*, pItem, ESI); - auto pExt = WeaponTypeExt::TryFetch(pItem); - if (pExt && pExt->LaserPositionUpdate != PositionFollow::None) + auto pExt = WeaponTypeExt::Fetch(pItem); + if (pExt->LaserPositionUpdate != PositionFollow::None) --WeaponTypeExt::LaserTrackingWeaponCount; WeaponTypeExt::ExtMap.Remove(pItem); From a7c915a500452974db8f280e5c5bde65d6f53448 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Sat, 1 Aug 2026 18:03:51 +0800 Subject: [PATCH 06/12] bool --- src/Ext/WeaponType/Body.cpp | 8 ++------ src/Ext/WeaponType/Body.h | 2 +- src/Misc/Hooks.LaserDraw.cpp | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Ext/WeaponType/Body.cpp b/src/Ext/WeaponType/Body.cpp index e42b66c416..d263191d5c 100644 --- a/src/Ext/WeaponType/Body.cpp +++ b/src/Ext/WeaponType/Body.cpp @@ -3,7 +3,7 @@ #include WeaponTypeExt::ExtContainer WeaponTypeExt::ExtMap; -int WeaponTypeExt::LaserTrackingWeaponCount = 0; +bool WeaponTypeExt::LaserTrackingWeaponSeen = false; bool WeaponTypeExt::HasRequiredAttachedEffects(TechnoClass* pTarget, TechnoClass* pFirer) const { @@ -120,7 +120,7 @@ void WeaponTypeExt::LoadFromINIFile(CCINIClass* const pINI) this->Laser_IsSingleColor.Read(exINI, pSection, "IsSingleColor"); this->LaserPositionUpdate.Read(exINI, pSection, "LaserPositionUpdate"); if (this->LaserPositionUpdate != PositionFollow::None) - ++WeaponTypeExt::LaserTrackingWeaponCount; + WeaponTypeExt::LaserTrackingWeaponSeen = true; this->LaserPositionUpdate_StopOnFirerConvert.Read(exINI, pSection, "LaserPositionUpdate.StopOnFirerConvert"); this->LaserZAdjust.Read(exINI, pSection, "LaserZAdjust"); this->EBoltZAdjust.Read(exINI, pSection, "EBoltZAdjust"); @@ -509,10 +509,6 @@ DEFINE_HOOK(0x77311D, WeaponTypeClass_SDDTOR, 0x6) { GET(WeaponTypeClass*, pItem, ESI); - auto pExt = WeaponTypeExt::Fetch(pItem); - if (pExt->LaserPositionUpdate != PositionFollow::None) - --WeaponTypeExt::LaserTrackingWeaponCount; - WeaponTypeExt::ExtMap.Remove(pItem); return 0; diff --git a/src/Ext/WeaponType/Body.h b/src/Ext/WeaponType/Body.h index 5dddb1ebcb..e41da88542 100644 --- a/src/Ext/WeaponType/Body.h +++ b/src/Ext/WeaponType/Body.h @@ -259,6 +259,6 @@ class WeaponTypeExt final : public AbstractTypeExt // Misc/Hooks.LaserDraw.cpp static void OnObjectRemoved(ObjectClass* pObject); - static int LaserTrackingWeaponCount; + static bool LaserTrackingWeaponSeen; }; diff --git a/src/Misc/Hooks.LaserDraw.cpp b/src/Misc/Hooks.LaserDraw.cpp index adb0ccc318..678623c421 100644 --- a/src/Misc/Hooks.LaserDraw.cpp +++ b/src/Misc/Hooks.LaserDraw.cpp @@ -193,7 +193,7 @@ namespace LaserRT // 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 (WeaponTypeExt::LaserTrackingWeaponCount > 0) + if (WeaponTypeExt::LaserTrackingWeaponSeen) { GET(LaserDrawClass*, pLaser, ECX); LaserRT::TrackingMap[pLaser] = LaserRT::TrackingData {}; From 6696c2719e68a1365c24c412716317faf8043542 Mon Sep 17 00:00:00 2001 From: Coronia <2217891145@qq.com> Date: Sun, 2 Aug 2026 14:56:54 +0800 Subject: [PATCH 07/12] replace unordered_multimap --- src/Misc/Hooks.LaserDraw.cpp | 80 ++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 41 deletions(-) diff --git a/src/Misc/Hooks.LaserDraw.cpp b/src/Misc/Hooks.LaserDraw.cpp index 678623c421..c985317cf8 100644 --- a/src/Misc/Hooks.LaserDraw.cpp +++ b/src/Misc/Hooks.LaserDraw.cpp @@ -107,41 +107,39 @@ namespace LaserRT std::unordered_map TrackingMap; - std::unordered_multimap ShooterToLasers; - std::unordered_multimap TargetToLasers; + std::unordered_map> ShooterToLasers; + std::unordered_map> TargetToLasers; static void RegisterTracking(LaserDrawClass* pLaser, const TrackingData& data) { if (data.Shooter && (data.FollowMode & PositionFollow::Firer)) - ShooterToLasers.emplace(data.Shooter, pLaser); + ShooterToLasers[data.Shooter].push_back(pLaser); if (data.Target && (data.FollowMode & PositionFollow::Target)) - TargetToLasers.emplace(data.Target, pLaser); + TargetToLasers[data.Target].push_back(pLaser); } static void UnregisterTracking(LaserDrawClass* pLaser, const TrackingData& data) { if (data.Shooter) { - auto [begin, end] = ShooterToLasers.equal_range(data.Shooter); - for (auto it = begin; it != end; ++it) + auto it = ShooterToLasers.find(data.Shooter); + if (it != ShooterToLasers.end()) { - if (it->second == pLaser) - { + auto& vec = it->second; + vec.erase(std::remove(vec.begin(), vec.end(), pLaser), vec.end()); + if (vec.empty()) ShooterToLasers.erase(it); - break; - } } } if (data.Target) { - auto [begin, end] = TargetToLasers.equal_range(data.Target); - for (auto it = begin; it != end; ++it) + auto it = TargetToLasers.find(data.Target); + if (it != TargetToLasers.end()) { - if (it->second == pLaser) - { + auto& vec = it->second; + vec.erase(std::remove(vec.begin(), vec.end(), pLaser), vec.end()); + if (vec.empty()) TargetToLasers.erase(it); - break; - } } } } @@ -220,43 +218,43 @@ DEFINE_HOOK(0x54FFB0, LaserDrawClass_DTOR_Tracking, 0x7) // LaserDrawClass::DTOR void WeaponTypeExt::OnObjectRemoved(ObjectClass* pObject) { - auto shooterRange = LaserRT::ShooterToLasers.equal_range(pObject); - for (auto it = shooterRange.first; it != shooterRange.second; ++it) + auto itShoot = LaserRT::ShooterToLasers.find(pObject); + if (itShoot != LaserRT::ShooterToLasers.end()) { - LaserDrawClass* pLaser = it->second; - auto dataIt = LaserRT::TrackingMap.find(pLaser); - if (dataIt != LaserRT::TrackingMap.end()) + for (auto pLaser : itShoot->second) { - auto& data = dataIt->second; - if (data.Shooter == pObject) - data.Shooter = nullptr; - - if (!data.Shooter && !data.Target) + auto dataIt = LaserRT::TrackingMap.find(pLaser); + if (dataIt != LaserRT::TrackingMap.end()) { - LaserRT::UnregisterTracking(pLaser, data); - LaserRT::TrackingMap.erase(dataIt); + 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 targetRange = LaserRT::TargetToLasers.equal_range(pObject); - for (auto it = targetRange.first; it != targetRange.second; ++it) + auto itTarget = LaserRT::TargetToLasers.find(pObject); + if (itTarget != LaserRT::TargetToLasers.end()) { - LaserDrawClass* pLaser = it->second; - auto dataIt = LaserRT::TrackingMap.find(pLaser); - if (dataIt != LaserRT::TrackingMap.end()) + for (auto pLaser : itTarget->second) { - auto& data = dataIt->second; - if (data.Target == pObject) - data.Target = nullptr; - - if (!data.Shooter && !data.Target) + auto dataIt = LaserRT::TrackingMap.find(pLaser); + if (dataIt != LaserRT::TrackingMap.end()) { - LaserRT::UnregisterTracking(pLaser, data); - LaserRT::TrackingMap.erase(dataIt); + 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); } @@ -267,7 +265,7 @@ DEFINE_HOOK(0x6FD210, TechnoClass_LaserZap_SetTrackingContext, 0x7) { GET(TechnoClass*, pShooter, ECX); GET_STACK(ObjectClass*, pTarget, 0x4); - GET_STACK(int, weaponIdx, 0x8); + GET_STACK(const int, weaponIdx, 0x8); LaserRT::Shooter = LaserRT::IgnoreShooter ? nullptr : pShooter; LaserRT::Target = pTarget; From 60878d67e8a9719c0a1b45e98d16d9529e88abb6 Mon Sep 17 00:00:00 2001 From: Coronia <2217891145@qq.com> Date: Mon, 3 Aug 2026 20:49:08 +0800 Subject: [PATCH 08/12] move toggle to Optimizations --- src/Ext/WeaponType/Body.cpp | 3 +-- src/Ext/WeaponType/Body.h | 1 - src/Misc/Hooks.LaserDraw.cpp | 11 ++++++++++- src/Phobos.cpp | 1 + src/Phobos.h | 1 + 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Ext/WeaponType/Body.cpp b/src/Ext/WeaponType/Body.cpp index d263191d5c..3d0e4dfc39 100644 --- a/src/Ext/WeaponType/Body.cpp +++ b/src/Ext/WeaponType/Body.cpp @@ -3,7 +3,6 @@ #include WeaponTypeExt::ExtContainer WeaponTypeExt::ExtMap; -bool WeaponTypeExt::LaserTrackingWeaponSeen = false; bool WeaponTypeExt::HasRequiredAttachedEffects(TechnoClass* pTarget, TechnoClass* pFirer) const { @@ -120,7 +119,7 @@ void WeaponTypeExt::LoadFromINIFile(CCINIClass* const pINI) this->Laser_IsSingleColor.Read(exINI, pSection, "IsSingleColor"); this->LaserPositionUpdate.Read(exINI, pSection, "LaserPositionUpdate"); if (this->LaserPositionUpdate != PositionFollow::None) - WeaponTypeExt::LaserTrackingWeaponSeen = true; + Phobos::Optimizations::DisableLaserTracking = false; this->LaserPositionUpdate_StopOnFirerConvert.Read(exINI, pSection, "LaserPositionUpdate.StopOnFirerConvert"); this->LaserZAdjust.Read(exINI, pSection, "LaserZAdjust"); this->EBoltZAdjust.Read(exINI, pSection, "EBoltZAdjust"); diff --git a/src/Ext/WeaponType/Body.h b/src/Ext/WeaponType/Body.h index e41da88542..2b7832743f 100644 --- a/src/Ext/WeaponType/Body.h +++ b/src/Ext/WeaponType/Body.h @@ -259,6 +259,5 @@ class WeaponTypeExt final : public AbstractTypeExt // Misc/Hooks.LaserDraw.cpp static void OnObjectRemoved(ObjectClass* pObject); - static bool LaserTrackingWeaponSeen; }; diff --git a/src/Misc/Hooks.LaserDraw.cpp b/src/Misc/Hooks.LaserDraw.cpp index c985317cf8..9cdfbb89a0 100644 --- a/src/Misc/Hooks.LaserDraw.cpp +++ b/src/Misc/Hooks.LaserDraw.cpp @@ -191,7 +191,7 @@ namespace LaserRT // 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 (WeaponTypeExt::LaserTrackingWeaponSeen) + if (!Phobos::Optimizations::DisableLaserTracking) { GET(LaserDrawClass*, pLaser, ECX); LaserRT::TrackingMap[pLaser] = LaserRT::TrackingData {}; @@ -263,6 +263,9 @@ void WeaponTypeExt::OnObjectRemoved(ObjectClass* pObject) 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); @@ -287,6 +290,9 @@ DEFINE_HOOK(0x6FD210, TechnoClass_LaserZap_SetTrackingContext, 0x7) 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(); @@ -324,6 +330,9 @@ 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) diff --git a/src/Phobos.cpp b/src/Phobos.cpp index eae8f834c8..0586921434 100644 --- a/src/Phobos.cpp +++ b/src/Phobos.cpp @@ -29,6 +29,7 @@ bool Phobos::Optimizations::Applied = false; bool Phobos::Optimizations::DisableBalloonHoverPathingFix = false; bool Phobos::Optimizations::DisableRadDamageOnBuildings = true; bool Phobos::Optimizations::DisableSyncLogging = false; +bool Phobos::Optimizations::DisableLaserTracking = true; #ifdef STR_GIT_COMMIT const wchar_t* Phobos::VersionDescription = L"Phobos nightly build (" STR_GIT_COMMIT L" @ " STR_GIT_BRANCH L"). DO NOT SHIP IN MODS!"; diff --git a/src/Phobos.h b/src/Phobos.h index 8947fb02ed..e6bef45c31 100644 --- a/src/Phobos.h +++ b/src/Phobos.h @@ -137,5 +137,6 @@ class Phobos static bool DisableBalloonHoverPathingFix; static bool DisableRadDamageOnBuildings; static bool DisableSyncLogging; + static bool DisableLaserTracking; }; }; From 12aff117c3b52fefae7878c438b36e62a0f6da32 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Mon, 10 Aug 2026 05:52:16 +0800 Subject: [PATCH 09/12] Move the LaserPositionUpdate tracking hooks into Ext/LaserDraw/Hooks.cpp Pure relocation - the implementation is copied verbatim from src/Misc/Hooks.LaserDraw.cpp, which keeps only the laser drawing and Z-adjust hooks. --- Phobos.vcxproj | 2 + src/Ext/LaserDraw/Hooks.cpp | 344 +++++++++++++++++++++++++++++++++++ src/Misc/Hooks.LaserDraw.cpp | 343 ---------------------------------- 3 files changed, 346 insertions(+), 343 deletions(-) create mode 100644 src/Ext/LaserDraw/Hooks.cpp diff --git a/Phobos.vcxproj b/Phobos.vcxproj index 781debeb8a..d0af2aae1a 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -128,6 +128,8 @@ + + diff --git a/src/Ext/LaserDraw/Hooks.cpp b/src/Ext/LaserDraw/Hooks.cpp new file mode 100644 index 0000000000..90a5a8b8ba --- /dev/null +++ b/src/Ext/LaserDraw/Hooks.cpp @@ -0,0 +1,344 @@ +#include +#include +#include +#include +#include +#include + +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; +} + 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 From 37009928121388d1a8d9caea481b2425e08e9bea Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Mon, 10 Aug 2026 05:52:49 +0800 Subject: [PATCH 10/12] [Improvement] Replace per-frame LaserPositionUpdate hash lookups with a LaserDrawExt O(1) extension LaserPositionUpdate looked up tracking data in a std::unordered_map on every frame for every LaserDrawClass. The tracking data now lives in a LaserDrawExt extension whose pointer is stored split across the two unused 16-bit padding words of LaserDrawClass (align_22 @ 0x22, align_4A @ 0x4A), giving O(1) access on the hot path. The extension follows the project Ext conventions (Body.h/cpp, ExtContainer/ExtMap, TypeRegistry registration). --- Phobos.vcxproj | 2 + src/Ext/LaserDraw/Body.cpp | 154 ++++++++++++++++++++++++++ src/Ext/LaserDraw/Body.h | 81 ++++++++++++++ src/Ext/LaserDraw/Hooks.cpp | 209 +++++++++++------------------------- src/Phobos.Ext.cpp | 2 + 5 files changed, 299 insertions(+), 149 deletions(-) create mode 100644 src/Ext/LaserDraw/Body.cpp create mode 100644 src/Ext/LaserDraw/Body.h diff --git a/Phobos.vcxproj b/Phobos.vcxproj index d0af2aae1a..48cf99da95 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -129,6 +129,7 @@ + @@ -372,6 +373,7 @@ + diff --git a/src/Ext/LaserDraw/Body.cpp b/src/Ext/LaserDraw/Body.cpp new file mode 100644 index 0000000000..e3bef9f6aa --- /dev/null +++ b/src/Ext/LaserDraw/Body.cpp @@ -0,0 +1,154 @@ +#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, localFLH); + 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; +} + +static void ClearPointer(LaserDrawClass* pLaser) +{ + SetPadHalf(pLaser, LaserDrawExt::PadHighOffset, 0); + SetPadHalf(pLaser, LaserDrawExt::PadLowOffset, 0); +} + +LaserDrawExt::ExtData* LaserDrawExt::Find(LaserDrawClass* pLaser) +{ + if (!pLaser) + 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..81360850f9 --- /dev/null +++ b/src/Ext/LaserDraw/Body.h @@ -0,0 +1,81 @@ +#pragma once +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +// O(1) per-laser tracking data for LaserPositionUpdate. +// +// The extension pointer is stored split across the two unused 16-bit padding +// words of LaserDrawClass (align_22 @ 0x22 = high half, align_4A @ 0x4A = low +// half), giving O(1) access on the per-frame hot path instead of the previous +// std::unordered_map lookup. LaserDrawClass is not AbstractClass-derived, so it +// has no unified 0x18 extension slot - the padding split is the only spare space. +class LaserDrawExt +{ +public: + using base_type = LaserDrawClass; + + static constexpr DWORD Canary = 0x4C617365; // "Lase" + + // reverse maps: an ObjectClass being removed -> the lasers tracking it, for + // pointer invalidation on object removal (not per-frame hot) + 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) { } + + virtual ~ExtData() = default; + + void Initialize(TechnoClass* pShooter, AbstractClass* pTarget, int weaponIdx, PositionFollow mode, + const CoordStruct& initialSource, const CoordStruct& localFLH, int burstIndex, bool stopOnFirerConvert); + + void Register(); + void Unregister(); + }; + + // --- O(1) padding-pointer access (the per-frame hot path) --- + 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); + + 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 index 90a5a8b8ba..a76815c58b 100644 --- a/src/Ext/LaserDraw/Hooks.cpp +++ b/src/Ext/LaserDraw/Hooks.cpp @@ -1,95 +1,18 @@ -#include +#include + #include +#include #include #include -#include -#include 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); - } - } - } + 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) { @@ -114,35 +37,31 @@ namespace LaserRT } } - TrackingData data; - data.Initialize(ignoreShooter ? nullptr : pShooter, pTarget, weaponIdx, mode, pLaser->Source, localFLH, burstIndex, stopOnFirerConvert); + auto* pExt = LaserDrawExt::Find(pLaser); + if (!pExt) + pExt = LaserDrawExt::Allocate(pLaser); + if (!pExt) + return; - auto it = TrackingMap.find(pLaser); - if (it != TrackingMap.end()) - UnregisterTracking(pLaser, it->second); - - TrackingMap[pLaser] = data; - RegisterTracking(pLaser, data); + pExt->Unregister(); + pExt->Initialize(ignoreShooter ? nullptr : pShooter, pTarget, weaponIdx, mode, pLaser->Source, localFLH, burstIndex, stopOnFirerConvert); + pExt->Register(); } - - 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. +// 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) - { - GET(LaserDrawClass*, pLaser, ECX); - LaserRT::TrackingMap[pLaser] = LaserRT::TrackingData {}; - } + LaserDrawExt::Allocate(pLaser); + return 0; } @@ -153,11 +72,10 @@ DEFINE_HOOK(0x54FFB0, LaserDrawClass_DTOR_Tracking, 0x7) // LaserDrawClass::DTOR { GET(LaserDrawClass*, pLaser, ECX); - auto it = LaserRT::TrackingMap.find(pLaser); - if (it != LaserRT::TrackingMap.end()) + if (auto* pExt = LaserDrawExt::Find(pLaser)) { - LaserRT::UnregisterTracking(pLaser, it->second); - LaserRT::TrackingMap.erase(it); + pExt->Unregister(); + LaserDrawExt::Release(pLaser); } return 0; @@ -165,45 +83,41 @@ DEFINE_HOOK(0x54FFB0, LaserDrawClass_DTOR_Tracking, 0x7) // LaserDrawClass::DTOR void WeaponTypeExt::OnObjectRemoved(ObjectClass* pObject) { - auto itShoot = LaserRT::ShooterToLasers.find(pObject); - if (itShoot != LaserRT::ShooterToLasers.end()) + auto itShoot = LaserDrawExt::ShooterToLasers.find(pObject); + if (itShoot != LaserDrawExt::ShooterToLasers.end()) { for (auto pLaser : itShoot->second) { - auto dataIt = LaserRT::TrackingMap.find(pLaser); - if (dataIt != LaserRT::TrackingMap.end()) + if (auto* pExt = LaserDrawExt::Find(pLaser)) { - auto& data = dataIt->second; - if (data.Shooter == pObject) - data.Shooter = nullptr; - if (!data.Shooter && !data.Target) - { - LaserRT::TrackingMap.erase(dataIt); - } + if (pExt->Shooter == pObject) + pExt->Shooter = nullptr; + + if (!pExt->Shooter && !pExt->Target) + LaserDrawExt::Release(pLaser); } } - LaserRT::ShooterToLasers.erase(itShoot); + LaserDrawExt::ShooterToLasers.erase(itShoot); } - LaserRT::ShooterToLasers.erase(pObject); + LaserDrawExt::ShooterToLasers.erase(pObject); - auto itTarget = LaserRT::TargetToLasers.find(pObject); - if (itTarget != LaserRT::TargetToLasers.end()) + auto itTarget = LaserDrawExt::TargetToLasers.find(pObject); + if (itTarget != LaserDrawExt::TargetToLasers.end()) { for (auto pLaser : itTarget->second) { - auto dataIt = LaserRT::TrackingMap.find(pLaser); - if (dataIt != LaserRT::TrackingMap.end()) + if (auto* pExt = LaserDrawExt::Find(pLaser)) { - auto& data = dataIt->second; - if (data.Target == pObject) - data.Target = nullptr; - if (!data.Shooter && !data.Target) - LaserRT::TrackingMap.erase(dataIt); + if (pExt->Target == pObject) + pExt->Target = nullptr; + + if (!pExt->Shooter && !pExt->Target) + LaserDrawExt::Release(pLaser); } } - LaserRT::TargetToLasers.erase(itTarget); + LaserDrawExt::TargetToLasers.erase(itTarget); } - LaserRT::TargetToLasers.erase(pObject); + LaserDrawExt::TargetToLasers.erase(pObject); } // hooks @@ -306,39 +220,36 @@ DEFINE_HOOK(0x4A7696, DiskLaser_Update_ActivateMainBeam_Tracking, 0x6) // Per‑frame coordinate update DEFINE_HOOK(0x550173, LaserDrawClass_Update_Tracking, 0x6) { - if (LaserRT::TrackingMap.empty()) + if (LaserDrawExt::ExtMap.size() == 0) return 0; GET(LaserDrawClass*, pLaser, ESI); - const auto it = LaserRT::TrackingMap.find(pLaser); + auto* pExt = LaserDrawExt::Find(pLaser); - if (it == LaserRT::TrackingMap.cend()) + if (!pExt) return 0; - auto& data = it->second; - - if (const auto pShooter = data.Shooter) + if (const auto pShooter = pExt->Shooter) { - if (data.StopOnFirerConvert && data.OriginalType) + if (pExt->StopOnFirerConvert && pExt->OriginalType) { - if (pShooter->GetTechnoType() != data.OriginalType) - data.Shooter = nullptr; + if (pShooter->GetTechnoType() != pExt->OriginalType) + pExt->Shooter = nullptr; } - if (data.Shooter) + if (pExt->Shooter) { const int savedBurstIndex = pShooter->CurrentBurstIndex; - pShooter->CurrentBurstIndex = data.FrozenBurstIndex; - const CoordStruct worldFLH = pShooter->GetFLH(data.WeaponIndex, data.LocalFLH); + pShooter->CurrentBurstIndex = pExt->FrozenBurstIndex; + const CoordStruct worldFLH = pShooter->GetFLH(pExt->WeaponIndex, pExt->LocalFLH); pShooter->CurrentBurstIndex = savedBurstIndex; - pLaser->Source = worldFLH + data.SavedOffset; + pLaser->Source = worldFLH + pExt->SavedOffset; } } - if (const auto pTarget = data.Target) + if (const auto pTarget = pExt->Target) pLaser->Target = pTarget->GetTargetCoords(); return 0; } - 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, From 0c1cd842f66458d08222be307a16c23501375de7 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Mon, 10 Aug 2026 06:09:08 +0800 Subject: [PATCH 11/12] update --- src/Ext/LaserDraw/Body.cpp | 10 ++++++++-- src/Ext/LaserDraw/Body.h | 17 ++++++---------- src/Ext/LaserDraw/Hooks.cpp | 39 ++++++++++++++++++++++++++++--------- 3 files changed, 44 insertions(+), 22 deletions(-) diff --git a/src/Ext/LaserDraw/Body.cpp b/src/Ext/LaserDraw/Body.cpp index e3bef9f6aa..b5a24bf236 100644 --- a/src/Ext/LaserDraw/Body.cpp +++ b/src/Ext/LaserDraw/Body.cpp @@ -39,7 +39,7 @@ void LaserDrawExt::ExtData::Initialize(TechnoClass* pShooter, AbstractClass* pTa const int savedBurstIndex = pShooter->CurrentBurstIndex; pShooter->CurrentBurstIndex = burstIndex; - const CoordStruct worldFLH = pShooter->GetFLH(weaponIdx, localFLH); + const CoordStruct worldFLH = pShooter->GetFLH(weaponIdx, CoordStruct::Empty); pShooter->CurrentBurstIndex = savedBurstIndex; this->SavedOffset = initialSource - worldFLH; @@ -99,8 +99,11 @@ static void SetPadHalf(LaserDrawClass* pLaser, uintptr_t offset, uint16_t value) *reinterpret_cast(reinterpret_cast(pLaser) + offset) = value; } -static void ClearPointer(LaserDrawClass* pLaser) +void LaserDrawExt::ClearPointer(LaserDrawClass* pLaser) { + if (!pLaser) + return; + SetPadHalf(pLaser, LaserDrawExt::PadHighOffset, 0); SetPadHalf(pLaser, LaserDrawExt::PadLowOffset, 0); } @@ -110,6 +113,9 @@ 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); diff --git a/src/Ext/LaserDraw/Body.h b/src/Ext/LaserDraw/Body.h index 81360850f9..80a19e402e 100644 --- a/src/Ext/LaserDraw/Body.h +++ b/src/Ext/LaserDraw/Body.h @@ -13,13 +13,6 @@ #include #include -// O(1) per-laser tracking data for LaserPositionUpdate. -// -// The extension pointer is stored split across the two unused 16-bit padding -// words of LaserDrawClass (align_22 @ 0x22 = high half, align_4A @ 0x4A = low -// half), giving O(1) access on the per-frame hot path instead of the previous -// std::unordered_map lookup. LaserDrawClass is not AbstractClass-derived, so it -// has no unified 0x18 extension slot - the padding split is the only spare space. class LaserDrawExt { public: @@ -27,8 +20,6 @@ class LaserDrawExt static constexpr DWORD Canary = 0x4C617365; // "Lase" - // reverse maps: an ObjectClass being removed -> the lasers tracking it, for - // pointer invalidation on object removal (not per-frame hot) static std::unordered_map> ShooterToLasers; static std::unordered_map> TargetToLasers; @@ -47,7 +38,11 @@ class LaserDrawExt ExtData(LaserDrawClass* pOwner) : Extension(pOwner) { } - virtual ~ExtData() = default; + ~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); @@ -56,7 +51,6 @@ class LaserDrawExt void Unregister(); }; - // --- O(1) padding-pointer access (the per-frame hot path) --- static constexpr uintptr_t PadHighOffset = 0x22; // align_22 static constexpr uintptr_t PadLowOffset = 0x4A; // align_4A @@ -64,6 +58,7 @@ class LaserDrawExt 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 { diff --git a/src/Ext/LaserDraw/Hooks.cpp b/src/Ext/LaserDraw/Hooks.cpp index a76815c58b..e6b27b7b40 100644 --- a/src/Ext/LaserDraw/Hooks.cpp +++ b/src/Ext/LaserDraw/Hooks.cpp @@ -4,6 +4,20 @@ #include #include #include +#include + +static void NukeTrackingHooks() +{ + 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 +} namespace LaserRT { @@ -59,25 +73,32 @@ DEFINE_HOOK(0x54FE60, LaserDrawClass_CTOR_Update, 0x5) LaserDrawExt::ResetPointer(pLaser); - if (!Phobos::Optimizations::DisableLaserTracking) - LaserDrawExt::Allocate(pLaser); + if (Phobos::Optimizations::DisableLaserTracking) + { + // no weapon uses LaserPositionUpdate: drop every tracking hook for good + NukeTrackingHooks(); + return 0; + } + + LaserDrawExt::Allocate(pLaser); 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 +static void RemoveLaserFromTracking(LaserDrawClass* pLaser) { - GET(LaserDrawClass*, pLaser, ECX); - 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; } @@ -241,7 +262,7 @@ DEFINE_HOOK(0x550173, LaserDrawClass_Update_Tracking, 0x6) { const int savedBurstIndex = pShooter->CurrentBurstIndex; pShooter->CurrentBurstIndex = pExt->FrozenBurstIndex; - const CoordStruct worldFLH = pShooter->GetFLH(pExt->WeaponIndex, pExt->LocalFLH); + const CoordStruct worldFLH = pShooter->GetFLH(pExt->WeaponIndex, CoordStruct::Empty); pShooter->CurrentBurstIndex = savedBurstIndex; pLaser->Source = worldFLH + pExt->SavedOffset; From 52b08fa6726359a7889d97132dfb46631f6811e6 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Wed, 12 Aug 2026 03:44:02 +0800 Subject: [PATCH 12/12] Move Nuke to Phobos.cpp --- src/Ext/LaserDraw/Hooks.cpp | 18 ------------------ src/Phobos.cpp | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/Ext/LaserDraw/Hooks.cpp b/src/Ext/LaserDraw/Hooks.cpp index e6b27b7b40..f7f68bc1df 100644 --- a/src/Ext/LaserDraw/Hooks.cpp +++ b/src/Ext/LaserDraw/Hooks.cpp @@ -4,20 +4,6 @@ #include #include #include -#include - -static void NukeTrackingHooks() -{ - 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 -} namespace LaserRT { @@ -74,11 +60,7 @@ DEFINE_HOOK(0x54FE60, LaserDrawClass_CTOR_Update, 0x5) LaserDrawExt::ResetPointer(pLaser); if (Phobos::Optimizations::DisableLaserTracking) - { - // no weapon uses LaserPositionUpdate: drop every tracking hook for good - NukeTrackingHooks(); return 0; - } LaserDrawExt::Allocate(pLaser); 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