diff --git a/CREDITS.md b/CREDITS.md index 2adb1c7ce4..f3251dbc23 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -695,6 +695,7 @@ This page lists all the individual contributions to the project by their author. - Customize the step limit of the credits indicator - Disable the credits indicator smooth transition - Add `selling`, `undeploying` and `harvesting` conditions to `DiscardOn` + - Allow Laser drawing position update - **Ollerus**: - Build limit group enhancement - Customizable rocker amplitude diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index ec9ae2bdcc..099e2f32da 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -3081,6 +3081,36 @@ UnlimboDetonate.KeepSelected=true ; boolean ## Weapons +### Allow Laser drawing position update + +- Now you can define via `LaserPositionUpdate` whether the endpoints of a laser drawing are updated during its duration. + - `None`: No update. + - `Firer`: The start point follows the firer's FLH; if the firer dies, the update stops. + - Since the FLH of `DiskLaser` actually determines the center of the ring, in this scenario, during the update process, the direction of the line connecting the beam's starting point to the center is fixed relative to the ground and the distance is constant - that is, the starting point will still remain on the ring. + - `Target`: The end point follows the target; if the target object dies, the update stops. + - `All`: Equivalent to specifying both `Firer` and `Target`. +- `LaserPositionUpdate.StopOnFirerConvert` determines whether the laser source stops updating when the firer transforms. If set to false (default), the laser will continue to update using the transformed unit's corresponding parameters. + +```{note} +For a sub-weapon created by `ShrapnelWeapon` or `AirburstWeapon`, its start point is the position where the parent weapon detonates, not the firer's FLH. +- If `Firer` is set, it will be treated as `None`. +- If `All` is set, it will be treated as `Target`. +``` + +In `rulesmd.ini`: +```ini +[AudioVisual] +LaserPositionUpdate.StopOnFirerConvert=false ; boolean + +[SOMEWEAPON] ; WeaponType with IsLaser=yes or DiskLaser=yes +LaserPositionUpdate=none ; Position Follow Enumeration (none|firer|target|all) +LaserPositionUpdate.StopOnFirerConvert=false ; boolean, default to [AudioVisual] -> LaserPositionUpdate.StopOnFirerConvert +``` + +```{warning} +If the weapon sets this logic to a non-`None` value while also using other logics that change the drawing position, such as `FlakScatter` and `VisualScatter`, then after initially drawing the laser according to those other logics, the drawing position will be forced to change due to the update rules. +``` + ### AreaFire target customization - You can now specify how AreaFire weapon picks its target. By default it targets the base cell the firer is currently on, but this can now be changed to fire on the firer itself or at a random cell within the radius of the weapon's `Range` by setting `AreaFire.Target` to `self` or `random` respectively. diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 2c98a36155..2d122b0caa 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -606,6 +606,7 @@ HideShakeEffects=false ; boolean - [Customize the step limit of the credits indicator](User-Interface.md#customize-the-step-limit-of-the-credits-indicator) (by Noble_Fish) - [Disable the credits indicator smooth transition](User-Interface.md#disable-the-credits-indicator-smooth-transition) (by Noble_Fish) - Add `selling`, `undeploying` and `harvesting` conditions to `DiscardOn` (by Noble_Fish) +- [Allow Laser drawing position update](New-or-Enhanced-Logics.md#allow-laser-drawing-position-update) (by Noble_Fish) #### Vanilla fixes: - Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya) diff --git a/src/Ext/Bullet/Body.cpp b/src/Ext/Bullet/Body.cpp index ad16905d82..f6663a70d0 100644 --- a/src/Ext/Bullet/Body.cpp +++ b/src/Ext/Bullet/Body.cpp @@ -7,6 +7,11 @@ #include #include +namespace LaserRT +{ + void SetLaserTrackingData(LaserDrawClass* pLaser, TechnoClass* pShooter, AbstractClass* pTarget, int weaponIdx, PositionFollow mode, bool ignoreShooter); +} + BulletExt::ExtContainer BulletExt::ExtMap; void BulletExt::ExtData::InterceptBullet(TechnoClass* pSource, BulletClass* pInterceptor) @@ -204,10 +209,12 @@ inline void BulletExt::SimulatedFiringLaser(BulletClass* pBullet, HouseClass* pH const auto pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon); + LaserDrawClass* pLaser = nullptr; + if (pWeapon->IsHouseColor || pWeaponExt->Laser_IsSingleColor) { const auto black = ColorStruct { 0, 0, 0 }; - const auto pLaser = GameCreate(pBullet->SourceCoords, BulletExt::GetTargetCoordsForFiring(pBullet), + pLaser = GameCreate(pBullet->SourceCoords, BulletExt::GetTargetCoordsForFiring(pBullet), ((pWeapon->IsHouseColor && pHouse) ? pHouse->LaserColor : pWeapon->LaserInnerColor), black, black, pWeapon->LaserDuration); pLaser->IsHouseColor = true; @@ -216,13 +223,34 @@ inline void BulletExt::SimulatedFiringLaser(BulletClass* pBullet, HouseClass* pH } else { - const auto pLaser = GameCreate(pBullet->SourceCoords, BulletExt::GetTargetCoordsForFiring(pBullet), + pLaser = GameCreate(pBullet->SourceCoords, BulletExt::GetTargetCoordsForFiring(pBullet), pWeapon->LaserInnerColor, pWeapon->LaserOuterColor, pWeapon->LaserOuterSpread, pWeapon->LaserDuration); pLaser->IsHouseColor = false; pLaser->Thickness = 3; pLaser->IsSupported = false; } + + // LaserPositionUpdate + if (pLaser) + { + auto mode = pWeaponExt->LaserPositionUpdate; + const bool isSplit = BulletExt::ExtMap.Find(pBullet)->IsSplitFromAirburst; + + if (isSplit) + { + if (mode == PositionFollow::Firer) + mode = PositionFollow::None; + else if (mode == PositionFollow::All) + mode = PositionFollow::Target; + } + + if (mode != PositionFollow::None) + { + auto const pTarget = abstract_cast(pBullet->Target); + LaserRT::SetLaserTrackingData(pLaser, pBullet->Owner, pTarget, 0, mode, isSplit); + } + } } // Make sure pBullet and pBullet->WeaponType is not empty before call diff --git a/src/Ext/Bullet/Body.h b/src/Ext/Bullet/Body.h index 2fd695624d..5e53898abf 100644 --- a/src/Ext/Bullet/Body.h +++ b/src/Ext/Bullet/Body.h @@ -35,6 +35,7 @@ class BulletExt int ParabombFallRate; bool IsInstantDetonation; double FirepowerMult; + bool IsSplitFromAirburst = false; TrajectoryPointer Trajectory; diff --git a/src/Ext/Bullet/Hooks.DetonateLogics.cpp b/src/Ext/Bullet/Hooks.DetonateLogics.cpp index e4b1f505ba..f5bf92fd1d 100644 --- a/src/Ext/Bullet/Hooks.DetonateLogics.cpp +++ b/src/Ext/Bullet/Hooks.DetonateLogics.cpp @@ -901,6 +901,7 @@ DEFINE_HOOK(0x469EC0, BulletClass_Logics_AirburstWeapon, 0x6) } BulletExt::ExtMap.Find(pBullet)->FirepowerMult = pBulletExt->FirepowerMult; + BulletExt::ExtMap.Find(pBullet)->IsSplitFromAirburst = true; BulletExt::SimulatedFiringUnlimbo(pBullet, pOwner, pWeapon, coords, headToTarget, radialFire); BulletExt::SimulatedFiringEffects(pBullet, pOwner, nullptr, useFiringEffects, true); } diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index 9166c2ef1e..1509bfe38c 100644 --- a/src/Ext/Rules/Body.cpp +++ b/src/Ext/Rules/Body.cpp @@ -197,6 +197,7 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) this->AirstrikeLineColor.Read(exINI, GameStrings::AudioVisual, "AirstrikeLineColor"); this->AirstrikeLineZAdjust.Read(exINI, GameStrings::AudioVisual, "AirstrikeLineZAdjust"); + this->LaserPositionUpdate_StopOnFirerConvert.Read(exINI, GameStrings::AudioVisual, "LaserPositionUpdate.StopOnFirerConvert"); this->LaserZAdjust.Read(exINI, GameStrings::AudioVisual, "LaserZAdjust"); this->EBoltZAdjust.Read(exINI, GameStrings::AudioVisual, "EBoltZAdjust"); this->EBoltZAdjust_ClampInitialDepthForBuilding.Read(exINI, GameStrings::AudioVisual, "EBoltZAdjust.ClampInitialDepthForBuilding"); @@ -617,6 +618,7 @@ void RulesExt::ExtData::Serialize(T& Stm) .Process(this->ColorAddUse8BitRGB) .Process(this->AirstrikeLineColor) .Process(this->AirstrikeLineZAdjust) + .Process(this->LaserPositionUpdate_StopOnFirerConvert) .Process(this->LaserZAdjust) .Process(this->EBoltZAdjust) .Process(this->EBoltZAdjust_ClampInitialDepthForBuilding) diff --git a/src/Ext/Rules/Body.h b/src/Ext/Rules/Body.h index 285bcc07b4..34cf6271fc 100644 --- a/src/Ext/Rules/Body.h +++ b/src/Ext/Rules/Body.h @@ -144,6 +144,7 @@ class RulesExt Valueable AirstrikeLineColor; Valueable AirstrikeLineZAdjust; + Valueable LaserPositionUpdate_StopOnFirerConvert; Valueable LaserZAdjust; Valueable EBoltZAdjust; Valueable EBoltZAdjust_ClampInitialDepthForBuilding; @@ -486,6 +487,7 @@ class RulesExt , ColorAddUse8BitRGB { false } , AirstrikeLineColor { { 255, 0, 0 } } , AirstrikeLineZAdjust { 0 } + , LaserPositionUpdate_StopOnFirerConvert { false } , LaserZAdjust { 0 } , EBoltZAdjust { 0 } , EBoltZAdjust_ClampInitialDepthForBuilding { true } diff --git a/src/Ext/WeaponType/Body.cpp b/src/Ext/WeaponType/Body.cpp index 7998f9b30d..09be7be8ee 100644 --- a/src/Ext/WeaponType/Body.cpp +++ b/src/Ext/WeaponType/Body.cpp @@ -117,6 +117,8 @@ void WeaponTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->AreaFire_Target.Read(exINI, pSection, "AreaFire.Target"); this->FeedbackWeapon.Read(exINI, pSection, "FeedbackWeapon"); this->Laser_IsSingleColor.Read(exINI, pSection, "IsSingleColor"); + this->LaserPositionUpdate.Read(exINI, pSection, "LaserPositionUpdate"); + this->LaserPositionUpdate_StopOnFirerConvert.Read(exINI, pSection, "LaserPositionUpdate.StopOnFirerConvert"); this->LaserZAdjust.Read(exINI, pSection, "LaserZAdjust"); this->EBoltZAdjust.Read(exINI, pSection, "EBoltZAdjust"); this->EBoltZAdjust_ClampInitialDepthForBuilding.Read(exINI, pSection, "EBoltZAdjust.ClampInitialDepthForBuilding"); @@ -244,6 +246,8 @@ void WeaponTypeExt::ExtData::Serialize(T& Stm) .Process(this->AreaFire_Target) .Process(this->FeedbackWeapon) .Process(this->Laser_IsSingleColor) + .Process(this->LaserPositionUpdate) + .Process(this->LaserPositionUpdate_StopOnFirerConvert) .Process(this->LaserZAdjust) .Process(this->EBoltZAdjust) .Process(this->EBoltZAdjust_ClampInitialDepthForBuilding) diff --git a/src/Ext/WeaponType/Body.h b/src/Ext/WeaponType/Body.h index 5135863882..170ff387f8 100644 --- a/src/Ext/WeaponType/Body.h +++ b/src/Ext/WeaponType/Body.h @@ -45,6 +45,8 @@ class WeaponTypeExt Valueable AreaFire_Target; Valueable FeedbackWeapon; Valueable Laser_IsSingleColor; + Valueable LaserPositionUpdate; + Nullable LaserPositionUpdate_StopOnFirerConvert; Nullable LaserZAdjust; Nullable EBoltZAdjust; Nullable EBoltZAdjust_ClampInitialDepthForBuilding; @@ -136,6 +138,8 @@ class WeaponTypeExt , AreaFire_Target { AreaFireTarget::Base } , FeedbackWeapon {} , Laser_IsSingleColor { false } + , LaserPositionUpdate { PositionFollow::None } + , LaserPositionUpdate_StopOnFirerConvert {} , LaserZAdjust {} , EBoltZAdjust {} , EBoltZAdjust_ClampInitialDepthForBuilding {} @@ -235,4 +239,7 @@ class WeaponTypeExt static int GetRangeWithModifiers(WeaponTypeClass* pThis, TechnoClass* pFirer); static int GetRangeWithModifiers(WeaponTypeClass* pThis, TechnoClass* pFirer, int range); static int GetTechnoKeepRange(WeaponTypeClass* pThis, TechnoClass* pFirer, bool isMinimum); + + // Misc/Hooks.LaserDraw.cpp + static void LaserTrackingPointerExpired(void* ptr, bool removed); }; diff --git a/src/Misc/Hooks.LaserDraw.cpp b/src/Misc/Hooks.LaserDraw.cpp index 221502db44..9ae00e2ca7 100644 --- a/src/Misc/Hooks.LaserDraw.cpp +++ b/src/Misc/Hooks.LaserDraw.cpp @@ -1,6 +1,8 @@ #include +#include #include #include +#include namespace LaserDrawTemp { @@ -53,3 +55,268 @@ 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; + } + + void PointerExpired(void* ptr, bool removed) + { + if (!removed) + return; + + AnnounceInvalidPointer(this->Shooter, ptr); + AnnounceInvalidPointer(this->Target, ptr); + } + + static bool InvalidatePointerIgnorable(void* ptr) + { + const auto pAbstract = static_cast(ptr); + return !abstract_cast(pAbstract); + } + }; + + std::unordered_map TrackingMap; + + 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::ExtMap.Find(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); + TrackingMap[pLaser] = data; + } + + TechnoClass* Shooter = nullptr; + AbstractClass* Target = nullptr; + int WeaponIndex = 0; + bool IgnoreShooter = false; + CoordStruct SavedLocalFLH = CoordStruct::Empty; + int SavedBurstIndex = 0; +} + +// container hooks + +// IsLaser ctor/dtor/remove hooks +DEFINE_HOOK(0x54FE60, LaserDrawClass_CTOR_Update, 0x5) +{ + 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); + LaserRT::TrackingMap.erase(pLaser); + return 0; +} + +void WeaponTypeExt::LaserTrackingPointerExpired(void* ptr, bool removed) +{ + if (LaserRT::TrackingData::InvalidatePointerIgnorable(ptr)) + return; + + for (auto& [_, data] : LaserRT::TrackingMap) + data.PointerExpired(ptr, removed); +} + +// hooks + +DEFINE_HOOK(0x6FD210, TechnoClass_LaserZap_SetTrackingContext, 0x7) +{ + GET(TechnoClass*, pShooter, ECX); + GET_STACK(ObjectClass*, pTarget, 0x4); + GET_STACK(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) +{ + GET(WeaponTypeClass*, pWeapon, ECX); + GET(LaserDrawClass*, pLaser, EAX); + const auto it = LaserRT::TrackingMap.find(pLaser); + + if (it == LaserRT::TrackingMap.cend()) + return 0; + + const auto mode = WeaponTypeExt::ExtMap.Find(pWeapon)->LaserPositionUpdate.Get(); + + if (mode == PositionFollow::None) + { + LaserRT::TrackingMap.erase(it); + 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); + const CoordStruct localFLH = std::exchange(LaserRT::SavedLocalFLH, CoordStruct::Empty); + const int burstIndex = std::exchange(LaserRT::SavedBurstIndex, 0); + const bool stopOnFirerConvert = WeaponTypeExt::ExtMap.Find(pWeapon)->LaserPositionUpdate_StopOnFirerConvert.Get(RulesExt::Global()->LaserPositionUpdate_StopOnFirerConvert); + + it->second.Initialize(pShooter, pTarget, weaponIdx, mode, pLaser->Source, localFLH, burstIndex, stopOnFirerConvert); + return 0; +} + +static LaserDrawClass* __fastcall Shrapnel_CreateLaser_Wrapper(TechnoClass* pShooter, void*, ObjectClass* pTarget + , int weaponIdx, WeaponTypeClass* pWeapon, const CoordStruct& 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) +{ + GET(LaserDrawClass*, pLaser, EAX); + + if (!pLaser) + return 0; + + const auto it = LaserRT::TrackingMap.find(pLaser); + + if (it == LaserRT::TrackingMap.cend()) + return 0; + + GET(DiskLaserClass*, pDiskLaser, ESI); + const auto pWeapon = pDiskLaser->Weapon; + + if (!pWeapon) + return 0; + + const auto mode = WeaponTypeExt::ExtMap.Find(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) +{ + 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 1c0acf5406..d07812b7e0 100644 --- a/src/Phobos.Ext.cpp +++ b/src/Phobos.Ext.cpp @@ -254,6 +254,7 @@ DEFINE_HOOK(0x7258D0, AnnounceInvalidPointer, 0x6) GET(bool const, removed, EDX); PhobosTypeRegistry::InvalidatePointer(pInvalid, removed); + WeaponTypeExt::LaserTrackingPointerExpired(pInvalid, removed); return 0; } diff --git a/src/Utilities/Enum.h b/src/Utilities/Enum.h index 2e9d9645f4..d240e3152a 100644 --- a/src/Utilities/Enum.h +++ b/src/Utilities/Enum.h @@ -181,6 +181,16 @@ enum class SlaveChangeOwnerType Neutral = 4, }; +enum class PositionFollow : BYTE +{ + None = 0x0, + Firer = 0x1, + Target = 0x2, + All = Firer | Target +}; + +MAKE_ENUM_FLAGS(PositionFollow) + enum class AutoDeathBehavior { Kill = 0, // default death option diff --git a/src/Utilities/TemplateDef.h b/src/Utilities/TemplateDef.h index dcac3f3534..1c21f3347c 100644 --- a/src/Utilities/TemplateDef.h +++ b/src/Utilities/TemplateDef.h @@ -949,6 +949,33 @@ namespace detail return false; } + template <> + inline bool read(PositionFollow& value, INI_EX& parser, const char* pSection, const char* pKey) + { + if (parser.ReadString(pSection, pKey)) + { + static const std::pair Names[] = + { + {"none", PositionFollow::None}, + {"firer", PositionFollow::Firer}, + {"target", PositionFollow::Target}, + {"all", PositionFollow::All}, + }; + + for (auto const& [name, val] : Names) + { + if (_strcmpi(parser.value(), name) == 0) + { + value = val; + return true; + } + } + + Debug::INIParseFailed(pSection, pKey, parser.value(), "Expected a position follow mode (None, Firer, Target, All)"); + } + return false; + } + template <> inline bool read(SelfHealGainType& value, INI_EX& parser, const char* pSection, const char* pKey) {