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