From 1517b0183da170b5bda5f19f32703514d5fef19c Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Sat, 16 May 2026 00:02:50 +0800 Subject: [PATCH 01/10] initial Co-authored-by: Netsu_Negi <71598172+NetsuNegi@users.noreply.github.com> --- docs/New-or-Enhanced-Logics.md | 25 +++ docs/Whats-New.md | 1 + src/Ext/Bullet/Body.cpp | 36 +++- src/Ext/Bullet/Body.h | 1 + src/Ext/Bullet/Hooks.DetonateLogics.cpp | 1 + src/Ext/WeaponType/Body.cpp | 2 + src/Ext/WeaponType/Body.h | 5 + src/Misc/Hooks.LaserDraw.cpp | 214 ++++++++++++++++++++++++ src/Phobos.Ext.cpp | 1 + src/Utilities/Enum.h | 10 ++ src/Utilities/TemplateDef.h | 27 +++ 11 files changed, 321 insertions(+), 2 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index ab4bf22e77..480984b0d0 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -2984,6 +2984,31 @@ UnlimboDetonate.KeepSelected=false ; boolean ## Weapons +## Allow Laser drawing position update + +- Now you can define 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`. + +```{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 +[SOMEWEAPON] ; WeaponType with IsLaser=yes or DiskLaser=yes +LaserPositionUpdate=none ; Position Follow Enumeration (none|firer|target|all) +``` + +```{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 e820a418eb..48f87fc74b 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -581,6 +581,7 @@ New: - [Electric bolt Z-adjust](Fixed-or-Improved-Logics.md#electric-bolt-z-adjust) (by Noble_Fish) - Allow disabling the processing of the Z-depth of EBolt drawn by BuildingType being clamped to non-positive numbers (by Noble_Fish) - Add the `Bolt.ZAdjust` setting item to the LaserTrailType with `DrawType=ebolt` (by Noble_Fish) +- 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 764849f238..5295b2a828 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) @@ -197,6 +202,9 @@ inline void BulletExt::SimulatedFiringReport(BulletClass* pBullet) inline void BulletExt::SimulatedFiringLaser(BulletClass* pBullet, HouseClass* pHouse) { // Can not use 0x6FD210 because the firer may die + if (!pBullet || !pBullet->WeaponType) + return; + const auto pWeapon = pBullet->WeaponType; if (!pWeapon->IsLaser) @@ -204,10 +212,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, ((pBullet->Type->Inviso && pBullet->Type->FlakScatter) ? pBullet->Location : pBullet->TargetCoords), + pLaser = GameCreate(pBullet->SourceCoords, ((pBullet->Type->Inviso && pBullet->Type->FlakScatter) ? pBullet->Location : pBullet->TargetCoords), ((pWeapon->IsHouseColor && pHouse) ? pHouse->LaserColor : pWeapon->LaserInnerColor), black, black, pWeapon->LaserDuration); pLaser->IsHouseColor = true; @@ -216,13 +226,35 @@ inline void BulletExt::SimulatedFiringLaser(BulletClass* pBullet, HouseClass* pH } else { - const auto pLaser = GameCreate(pBullet->SourceCoords, ((pBullet->Type->Inviso && pBullet->Type->FlakScatter) ? pBullet->Location : pBullet->TargetCoords), + pLaser = GameCreate(pBullet->SourceCoords, ((pBullet->Type->Inviso && pBullet->Type->FlakScatter) ? pBullet->Location : pBullet->TargetCoords), pWeapon->LaserInnerColor, pWeapon->LaserOuterColor, pWeapon->LaserOuterSpread, pWeapon->LaserDuration); pLaser->IsHouseColor = false; pLaser->Thickness = 3; pLaser->IsSupported = false; } + + // LaserPositionUpdate + if (pLaser && pWeaponExt) + { + auto mode = pWeaponExt->LaserPositionUpdate; + const auto pBulletExt = BulletExt::ExtMap.Find(pBullet); + const bool isSplit = (pBulletExt && pBulletExt->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 6d2c1b428b..8d01a9726c 100644 --- a/src/Ext/Bullet/Body.h +++ b/src/Ext/Bullet/Body.h @@ -27,6 +27,7 @@ class BulletExt int DamageNumberOffset; int ParabombFallRate; bool IsInstantDetonation; + bool IsSplitFromAirburst = false; TrajectoryPointer Trajectory; diff --git a/src/Ext/Bullet/Hooks.DetonateLogics.cpp b/src/Ext/Bullet/Hooks.DetonateLogics.cpp index 6e4d6b5c6a..9c63e6d077 100644 --- a/src/Ext/Bullet/Hooks.DetonateLogics.cpp +++ b/src/Ext/Bullet/Hooks.DetonateLogics.cpp @@ -833,6 +833,7 @@ DEFINE_HOOK(0x469EC0, BulletClass_Logics_AirburstWeapon, 0x6) coords = MapClass::GetRandomCoordsNear(coords, distance, false); } + BulletExt::ExtMap.Find(pBullet)->IsSplitFromAirburst = true; BulletExt::SimulatedFiringUnlimbo(pBullet, pOwner, pWeapon, coords, true); BulletExt::SimulatedFiringEffects(pBullet, pOwner, nullptr, useFiringEffects, true); } diff --git a/src/Ext/WeaponType/Body.cpp b/src/Ext/WeaponType/Body.cpp index f98678cfdc..0587224501 100644 --- a/src/Ext/WeaponType/Body.cpp +++ b/src/Ext/WeaponType/Body.cpp @@ -117,6 +117,7 @@ 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->LaserZAdjust.Read(exINI, pSection, "LaserZAdjust"); this->EBoltZAdjust.Read(exINI, pSection, "EBoltZAdjust"); this->EBoltZAdjust_ClampInitialDepthForBuilding.Read(exINI, pSection, "EBoltZAdjust.ClampInitialDepthForBuilding"); @@ -216,6 +217,7 @@ void WeaponTypeExt::ExtData::Serialize(T& Stm) .Process(this->AreaFire_Target) .Process(this->FeedbackWeapon) .Process(this->Laser_IsSingleColor) + .Process(this->LaserPositionUpdate) .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 5bb8734a4b..68b4ae3701 100644 --- a/src/Ext/WeaponType/Body.h +++ b/src/Ext/WeaponType/Body.h @@ -45,6 +45,7 @@ class WeaponTypeExt Valueable AreaFire_Target; Valueable FeedbackWeapon; Valueable Laser_IsSingleColor; + Valueable LaserPositionUpdate; Nullable LaserZAdjust; Nullable EBoltZAdjust; Nullable EBoltZAdjust_ClampInitialDepthForBuilding; @@ -134,6 +135,7 @@ class WeaponTypeExt , AreaFire_Target { AreaFireTarget::Base } , FeedbackWeapon {} , Laser_IsSingleColor { false } + , LaserPositionUpdate { PositionFollow::None } , LaserZAdjust {} , EBoltZAdjust {} , EBoltZAdjust_ClampInitialDepthForBuilding {} @@ -231,4 +233,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..b66b2dcfe5 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,215 @@ DEFINE_HOOK(0x6FD3FD, TechnoClass_LaserZap_ZAdjust, 0x5) return 0; } + +#pragma region LaserPositionUpdate + +namespace LaserRT +{ + static CoordStruct GetRelativeFLH(TechnoClass* pShooter, int weaponIndex) + { + bool flhFound = false; + CoordStruct result = TechnoExt::GetBurstFLH(pShooter, weaponIndex, flhFound); + + if (!flhFound) + { + result = pShooter->GetWeapon(weaponIndex)->FLH; + + if (pShooter->CurrentBurstIndex % 2 != 0) + result.Y = -result.Y; + } + + return result; + } + + struct TrackingData + { + TechnoClass* Shooter { nullptr }; + ObjectClass* Target { nullptr }; + int WeaponIndex { 0 }; + PositionFollow FollowMode { PositionFollow::None }; + bool IsDiskLaser = false; + CoordStruct SavedRelativeFLH { CoordStruct::Empty }; + + void Initialize(TechnoClass* pShooter, AbstractClass* pTarget, int weaponIdx, PositionFollow mode) + { + if (pShooter && mode & PositionFollow::Firer) + { + this->Shooter = pShooter; + this->SavedRelativeFLH = LaserRT::GetRelativeFLH(pShooter, weaponIdx); + } + + 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); + } + }; + + std::unordered_map TrackingMap; + + void SetLaserTrackingData(LaserDrawClass* pLaser, TechnoClass* pShooter, AbstractClass* pTarget, int weaponIdx, PositionFollow mode, bool ignoreShooter) + { + TrackingData data; + data.Initialize(ignoreShooter ? nullptr : pShooter, pTarget, weaponIdx, mode); + TrackingMap[pLaser] = data; + } + + TechnoClass* Shooter = nullptr; + AbstractClass* Target = nullptr; + int WeaponIndex = 0; + bool IgnoreShooter = false; +} + +// 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) +{ + 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; + return 0; +} + +DEFINE_HOOK(0x6FD446, TechnoClass_LaserZap_Tracking, 0x7) +{ + GET(WeaponTypeClass*, pWeapon, ECX); + const auto pShooter = std::exchange(LaserRT::Shooter, nullptr); + const auto pTarget = std::exchange(LaserRT::Target, nullptr); + const int weaponIdx = std::exchange(LaserRT::WeaponIndex, 0); + + 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) + return 0; + + it->second.Initialize(pShooter, pTarget, weaponIdx, mode); + 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; + + it->second.Initialize(pDiskLaser->Owner, pDiskLaser->Target, 0, mode); + + if (it->second.Shooter && (mode & PositionFollow::Firer)) + { + CoordStruct flh; + it->second.Shooter->GetFLH(&flh, 0, CoordStruct { 0, 0, 0 }); + it->second.SavedRelativeFLH = pLaser->Source - flh; + it->second.IsDiskLaser = true; + } + + 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; + + const auto& data = it->second; + + if (const auto pShooter = data.Shooter) + { + if (data.IsDiskLaser) + { + CoordStruct flh; + pShooter->GetFLH(&flh, data.WeaponIndex, CoordStruct { 0, 0, 0 }); + pLaser->Source = flh + data.SavedRelativeFLH; + } + else + { + pLaser->Source = TechnoExt::GetFLHAbsoluteCoords(pShooter, data.SavedRelativeFLH, true); + } + } + + 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 2109fdf943..97356e2989 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 ec0fd1e1a7..53017df144 100644 --- a/src/Utilities/TemplateDef.h +++ b/src/Utilities/TemplateDef.h @@ -904,6 +904,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) { From a16fdf673625b77ff604f9849d742a58620240e4 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Wed, 27 May 2026 15:15:18 +0800 Subject: [PATCH 02/10] fix `AlternateFLH` --- src/Misc/Hooks.LaserDraw.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Misc/Hooks.LaserDraw.cpp b/src/Misc/Hooks.LaserDraw.cpp index b66b2dcfe5..ccce405957 100644 --- a/src/Misc/Hooks.LaserDraw.cpp +++ b/src/Misc/Hooks.LaserDraw.cpp @@ -256,7 +256,9 @@ DEFINE_HOOK(0x550173, LaserDrawClass_Update_Tracking, 0x6) } else { - pLaser->Source = TechnoExt::GetFLHAbsoluteCoords(pShooter, data.SavedRelativeFLH, true); + CoordStruct flh; + pShooter->GetFLH(&flh, data.WeaponIndex, CoordStruct { 0, 0, 0 }); + pLaser->Source = flh; } } From 9b934712ea17eeab0b65ccbc00494cffd67854d0 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Wed, 27 May 2026 15:28:05 +0800 Subject: [PATCH 03/10] note the occupied building --- docs/New-or-Enhanced-Logics.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 480984b0d0..9ba785cc55 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -2993,6 +2993,10 @@ UnlimboDetonate.KeepSelected=false ; boolean - `Target`: The end point follows the target; if the target object dies, the update stops. - `All`: Equivalent to specifying both `Firer` and `Target`. +```{note} +In vanilla, an occupied building uses the weapons of its occupants in turn. Thus, if an infantry's occupy weapon uses `Firer` , the laser's starting point will update to the position specified by the most recently used `MuzzleFlashX` after each shot. +``` + ```{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`. From 15ec06d02873c0f5c5d0cfc53891b3ef02c7aa6f Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Wed, 27 May 2026 23:09:56 +0800 Subject: [PATCH 04/10] FrozenBurstIndex --- src/Misc/Hooks.LaserDraw.cpp | 100 ++++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 42 deletions(-) diff --git a/src/Misc/Hooks.LaserDraw.cpp b/src/Misc/Hooks.LaserDraw.cpp index ccce405957..3f32ba5b7f 100644 --- a/src/Misc/Hooks.LaserDraw.cpp +++ b/src/Misc/Hooks.LaserDraw.cpp @@ -60,37 +60,31 @@ DEFINE_HOOK(0x6FD3FD, TechnoClass_LaserZap_ZAdjust, 0x5) namespace LaserRT { - static CoordStruct GetRelativeFLH(TechnoClass* pShooter, int weaponIndex) - { - bool flhFound = false; - CoordStruct result = TechnoExt::GetBurstFLH(pShooter, weaponIndex, flhFound); - - if (!flhFound) - { - result = pShooter->GetWeapon(weaponIndex)->FLH; - - if (pShooter->CurrentBurstIndex % 2 != 0) - result.Y = -result.Y; - } - - return result; - } - struct TrackingData { TechnoClass* Shooter { nullptr }; ObjectClass* Target { nullptr }; int WeaponIndex { 0 }; PositionFollow FollowMode { PositionFollow::None }; - bool IsDiskLaser = false; - CoordStruct SavedRelativeFLH { CoordStruct::Empty }; + CoordStruct SavedOffset { CoordStruct::Empty }; + CoordStruct LocalFLH { CoordStruct::Empty }; + int FrozenBurstIndex { 0 }; - void Initialize(TechnoClass* pShooter, AbstractClass* pTarget, int weaponIdx, PositionFollow mode) + void Initialize(TechnoClass* pShooter, AbstractClass* pTarget, int weaponIdx, PositionFollow mode, const CoordStruct& initialSource, const CoordStruct& localFLH, int burstIndex) { - if (pShooter && mode & PositionFollow::Firer) + if (pShooter && (mode & PositionFollow::Firer)) { this->Shooter = pShooter; - this->SavedRelativeFLH = LaserRT::GetRelativeFLH(pShooter, weaponIdx); + this->LocalFLH = localFLH; + this->FrozenBurstIndex = burstIndex; + + const int savedBurstIndex = pShooter->CurrentBurstIndex; + pShooter->CurrentBurstIndex = burstIndex; + CoordStruct worldFLH; + pShooter->GetFLH(&worldFLH, weaponIdx, localFLH); + pShooter->CurrentBurstIndex = savedBurstIndex; + + this->SavedOffset = initialSource - worldFLH; } if (mode & PositionFollow::Target) @@ -114,8 +108,19 @@ namespace LaserRT void SetLaserTrackingData(LaserDrawClass* pLaser, TechnoClass* pShooter, AbstractClass* pTarget, int weaponIdx, PositionFollow mode, bool ignoreShooter) { + CoordStruct localFLH; + int burstIndex = 0; + if (pShooter) + { + bool flhFound = false; + localFLH = TechnoExt::GetBurstFLH(pShooter, weaponIdx, flhFound); + if (!flhFound) + localFLH = pShooter->GetWeapon(weaponIdx)->FLH; + burstIndex = pShooter->CurrentBurstIndex; + } + TrackingData data; - data.Initialize(ignoreShooter ? nullptr : pShooter, pTarget, weaponIdx, mode); + data.Initialize(ignoreShooter ? nullptr : pShooter, pTarget, weaponIdx, mode, pLaser->Source, localFLH, burstIndex); TrackingMap[pLaser] = data; } @@ -123,6 +128,8 @@ namespace LaserRT AbstractClass* Target = nullptr; int WeaponIndex = 0; bool IgnoreShooter = false; + CoordStruct SavedLocalFLH = CoordStruct::Empty; + int SavedBurstIndex = 0; } // container hooks @@ -162,6 +169,16 @@ DEFINE_HOOK(0x6FD210, TechnoClass_LaserZap_SetTrackingContext, 0x7) 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; } @@ -171,6 +188,8 @@ DEFINE_HOOK(0x6FD446, TechnoClass_LaserZap_Tracking, 0x7) 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); GET(LaserDrawClass*, pLaser, EAX); const auto it = LaserRT::TrackingMap.find(pLaser); @@ -183,7 +202,7 @@ DEFINE_HOOK(0x6FD446, TechnoClass_LaserZap_Tracking, 0x7) if (mode == PositionFollow::None) return 0; - it->second.Initialize(pShooter, pTarget, weaponIdx, mode); + it->second.Initialize(pShooter, pTarget, weaponIdx, mode, pLaser->Source, localFLH, burstIndex); return 0; } @@ -222,16 +241,18 @@ DEFINE_HOOK(0x4A7696, DiskLaser_Update_ActivateMainBeam_Tracking, 0x6) if (mode == PositionFollow::None) return 0; - it->second.Initialize(pDiskLaser->Owner, pDiskLaser->Target, 0, mode); - - if (it->second.Shooter && (mode & PositionFollow::Firer)) + auto pShooter = pDiskLaser->Owner; + const int burstIndex = pShooter->CurrentBurstIndex; + bool flhFound = false; + CoordStruct localFLH = TechnoExt::GetBurstFLH(pShooter, 0, flhFound); + if (!flhFound) { - CoordStruct flh; - it->second.Shooter->GetFLH(&flh, 0, CoordStruct { 0, 0, 0 }); - it->second.SavedRelativeFLH = pLaser->Source - flh; - it->second.IsDiskLaser = true; + localFLH = pShooter->GetWeapon(0)->FLH; + if (burstIndex % 2 != 0) + localFLH.Y = -localFLH.Y; } + it->second.Initialize(pShooter, pDiskLaser->Target, 0, mode, pLaser->Source, localFLH, burstIndex); return 0; } @@ -248,18 +269,13 @@ DEFINE_HOOK(0x550173, LaserDrawClass_Update_Tracking, 0x6) if (const auto pShooter = data.Shooter) { - if (data.IsDiskLaser) - { - CoordStruct flh; - pShooter->GetFLH(&flh, data.WeaponIndex, CoordStruct { 0, 0, 0 }); - pLaser->Source = flh + data.SavedRelativeFLH; - } - else - { - CoordStruct flh; - pShooter->GetFLH(&flh, data.WeaponIndex, CoordStruct { 0, 0, 0 }); - pLaser->Source = flh; - } + const int savedBurstIndex = pShooter->CurrentBurstIndex; + pShooter->CurrentBurstIndex = data.FrozenBurstIndex; + CoordStruct worldFLH; + pShooter->GetFLH(&worldFLH, data.WeaponIndex, data.LocalFLH); + pShooter->CurrentBurstIndex = savedBurstIndex; + + pLaser->Source = worldFLH + data.SavedOffset; } if (const auto pTarget = data.Target) From 5db54d98cae97137fbd156d56a3b6200844299a4 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Sat, 30 May 2026 19:18:27 +0800 Subject: [PATCH 05/10] occupied building fallback --- src/Misc/Hooks.LaserDraw.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Misc/Hooks.LaserDraw.cpp b/src/Misc/Hooks.LaserDraw.cpp index 3f32ba5b7f..d3fae8a2ec 100644 --- a/src/Misc/Hooks.LaserDraw.cpp +++ b/src/Misc/Hooks.LaserDraw.cpp @@ -72,6 +72,20 @@ namespace LaserRT void Initialize(TechnoClass* pShooter, AbstractClass* pTarget, int weaponIdx, PositionFollow mode, const CoordStruct& initialSource, const CoordStruct& localFLH, int burstIndex) { + if (pShooter) + { + if (const auto pBuilding = abstract_cast(pShooter)) + { + if (pBuilding->Type->MaxNumberOccupants > 0) + { + if (mode == PositionFollow::Firer) + mode = PositionFollow::None; + else if (mode == PositionFollow::All) + mode = PositionFollow::Target; + } + } + } + if (pShooter && (mode & PositionFollow::Firer)) { this->Shooter = pShooter; From 62fdeea5a357a0c8b1ece4662d41a654c1c990b3 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Sat, 30 May 2026 19:26:35 +0800 Subject: [PATCH 06/10] Revert "note the occupied building" This reverts commit 9b934712ea17eeab0b65ccbc00494cffd67854d0. --- docs/New-or-Enhanced-Logics.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index f863347411..42baa48978 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -2995,10 +2995,6 @@ UnlimboDetonate.KeepSelected=false ; boolean - `Target`: The end point follows the target; if the target object dies, the update stops. - `All`: Equivalent to specifying both `Firer` and `Target`. -```{note} -In vanilla, an occupied building uses the weapons of its occupants in turn. Thus, if an infantry's occupy weapon uses `Firer` , the laser's starting point will update to the position specified by the most recently used `MuzzleFlashX` after each shot. -``` - ```{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`. From 95029f7e7257434856aa3410902a3d84080a9f17 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Sat, 30 May 2026 19:35:42 +0800 Subject: [PATCH 07/10] Simplify the `DiskLaser_Update_ActivateMainBeam_Tracking` function --- src/Misc/Hooks.LaserDraw.cpp | 33 ++++++--------------------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/src/Misc/Hooks.LaserDraw.cpp b/src/Misc/Hooks.LaserDraw.cpp index d3fae8a2ec..79664ae8d3 100644 --- a/src/Misc/Hooks.LaserDraw.cpp +++ b/src/Misc/Hooks.LaserDraw.cpp @@ -235,38 +235,17 @@ DEFINE_FUNCTION_JUMP(CALL, 0x46AD81, Shrapnel_CreateLaser_Wrapper) DEFINE_HOOK(0x4A7696, DiskLaser_Update_ActivateMainBeam_Tracking, 0x6) { GET(LaserDrawClass*, pLaser, EAX); - - if (!pLaser) - return 0; - + if (!pLaser) return 0; const auto it = LaserRT::TrackingMap.find(pLaser); - - if (it == LaserRT::TrackingMap.cend()) - return 0; - + if (it == LaserRT::TrackingMap.cend()) return 0; GET(DiskLaserClass*, pDiskLaser, ESI); const auto pWeapon = pDiskLaser->Weapon; - - if (!pWeapon) - return 0; - + 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; - if (mode == PositionFollow::None) - return 0; - - auto pShooter = pDiskLaser->Owner; - const int burstIndex = pShooter->CurrentBurstIndex; - bool flhFound = false; - CoordStruct localFLH = TechnoExt::GetBurstFLH(pShooter, 0, flhFound); - if (!flhFound) - { - localFLH = pShooter->GetWeapon(0)->FLH; - if (burstIndex % 2 != 0) - localFLH.Y = -localFLH.Y; - } - - it->second.Initialize(pShooter, pDiskLaser->Target, 0, mode, pLaser->Source, localFLH, burstIndex); + LaserRT::SetLaserTrackingData(pLaser, pDiskLaser->Owner, pDiskLaser->Target, 0, mode, false); return 0; } From 0d7f5c54c8c2c8c8712c2fbc80c82bce697cd487 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Sun, 31 May 2026 15:50:51 +0800 Subject: [PATCH 08/10] add `LaserPositionUpdate.StopOnFirerConvert` --- docs/New-or-Enhanced-Logics.md | 13 +++++++--- src/Ext/Rules/Body.cpp | 2 ++ src/Ext/Rules/Body.h | 2 ++ src/Ext/WeaponType/Body.cpp | 2 ++ src/Ext/WeaponType/Body.h | 2 ++ src/Misc/Hooks.LaserDraw.cpp | 46 ++++++++++++++++++++++++++-------- 6 files changed, 53 insertions(+), 14 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 42baa48978..daeb545259 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -840,7 +840,7 @@ Interceptor.WeaponOverride= ; WeaponType Interceptor.WeaponReplaceProjectile=false ; boolean Interceptor.WeaponCumulativeDamage=false ; boolean Interceptor.KeepIntact=false ; boolean - + [SOMEPROJECTILE] ; Projectile Interceptable=false ; boolean Interceptable.DeleteOnIntercept=false ; boolean @@ -2988,12 +2988,13 @@ UnlimboDetonate.KeepSelected=false ; boolean ## Allow Laser drawing position update -- Now you can define whether the endpoints of a laser drawing are updated during its duration. +- 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. @@ -3003,8 +3004,12 @@ For a sub-weapon created by `ShrapnelWeapon` or `AirburstWeapon`, its start poin In `rulesmd.ini`: ```ini -[SOMEWEAPON] ; WeaponType with IsLaser=yes or DiskLaser=yes -LaserPositionUpdate=none ; Position Follow Enumeration (none|firer|target|all) +[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} diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index 0fdda59211..58b1e73646 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"); @@ -575,6 +576,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 a5bac34b0b..452bac2285 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; @@ -456,6 +457,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 0587224501..379cc8f588 100644 --- a/src/Ext/WeaponType/Body.cpp +++ b/src/Ext/WeaponType/Body.cpp @@ -118,6 +118,7 @@ void WeaponTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) 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"); @@ -218,6 +219,7 @@ void WeaponTypeExt::ExtData::Serialize(T& Stm) .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 68b4ae3701..85b239c08d 100644 --- a/src/Ext/WeaponType/Body.h +++ b/src/Ext/WeaponType/Body.h @@ -46,6 +46,7 @@ class WeaponTypeExt Valueable FeedbackWeapon; Valueable Laser_IsSingleColor; Valueable LaserPositionUpdate; + Nullable LaserPositionUpdate_StopOnFirerConvert; Nullable LaserZAdjust; Nullable EBoltZAdjust; Nullable EBoltZAdjust_ClampInitialDepthForBuilding; @@ -136,6 +137,7 @@ class WeaponTypeExt , FeedbackWeapon {} , Laser_IsSingleColor { false } , LaserPositionUpdate { PositionFollow::None } + , LaserPositionUpdate_StopOnFirerConvert {} , LaserZAdjust {} , EBoltZAdjust {} , EBoltZAdjust_ClampInitialDepthForBuilding {} diff --git a/src/Misc/Hooks.LaserDraw.cpp b/src/Misc/Hooks.LaserDraw.cpp index 79664ae8d3..83ade683cc 100644 --- a/src/Misc/Hooks.LaserDraw.cpp +++ b/src/Misc/Hooks.LaserDraw.cpp @@ -69,8 +69,10 @@ namespace LaserRT 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) + void Initialize(TechnoClass* pShooter, AbstractClass* pTarget, int weaponIdx, PositionFollow mode, const CoordStruct& initialSource, const CoordStruct& localFLH, int burstIndex, bool stopOnFirerConvert) { if (pShooter) { @@ -91,6 +93,9 @@ namespace LaserRT 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; @@ -124,6 +129,7 @@ namespace LaserRT { CoordStruct localFLH; int burstIndex = 0; + bool stopOnFirerConvert = false; if (pShooter) { bool flhFound = false; @@ -131,10 +137,17 @@ namespace LaserRT if (!flhFound) localFLH = pShooter->GetWeapon(weaponIdx)->FLH; burstIndex = pShooter->CurrentBurstIndex; + + auto pWeapon = pShooter->GetWeapon(weaponIdx)->WeaponType; + if (pWeapon) + { + auto pExt = WeaponTypeExt::ExtMap.Find(pWeapon); + stopOnFirerConvert = pExt->LaserPositionUpdate_StopOnFirerConvert.Get(RulesExt::Global()->LaserPositionUpdate_StopOnFirerConvert); + } } TrackingData data; - data.Initialize(ignoreShooter ? nullptr : pShooter, pTarget, weaponIdx, mode, pLaser->Source, localFLH, burstIndex); + data.Initialize(ignoreShooter ? nullptr : pShooter, pTarget, weaponIdx, mode, pLaser->Source, localFLH, burstIndex, stopOnFirerConvert); TrackingMap[pLaser] = data; } @@ -216,7 +229,9 @@ DEFINE_HOOK(0x6FD446, TechnoClass_LaserZap_Tracking, 0x7) if (mode == PositionFollow::None) return 0; - it->second.Initialize(pShooter, pTarget, weaponIdx, mode, pLaser->Source, localFLH, burstIndex); + 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; } @@ -258,17 +273,28 @@ DEFINE_HOOK(0x550173, LaserDrawClass_Update_Tracking, 0x6) if (it == LaserRT::TrackingMap.cend()) return 0; - const auto& data = it->second; + auto& data = it->second; if (const auto pShooter = data.Shooter) { - const int savedBurstIndex = pShooter->CurrentBurstIndex; - pShooter->CurrentBurstIndex = data.FrozenBurstIndex; - CoordStruct worldFLH; - pShooter->GetFLH(&worldFLH, data.WeaponIndex, data.LocalFLH); - pShooter->CurrentBurstIndex = savedBurstIndex; + if (data.StopOnFirerConvert && data.OriginalType) + { + if (pShooter->GetTechnoType() != data.OriginalType) + { + data.Shooter = nullptr; + } + } - pLaser->Source = worldFLH + data.SavedOffset; + if (data.Shooter) + { + const int savedBurstIndex = pShooter->CurrentBurstIndex; + pShooter->CurrentBurstIndex = data.FrozenBurstIndex; + CoordStruct worldFLH; + pShooter->GetFLH(&worldFLH, data.WeaponIndex, data.LocalFLH); + pShooter->CurrentBurstIndex = savedBurstIndex; + + pLaser->Source = worldFLH + data.SavedOffset; + } } if (const auto pTarget = data.Target) From b86496d44d2e01fbf12aad30e4da555211e0332a Mon Sep 17 00:00:00 2001 From: NetsuNegi39 Date: Mon, 1 Jun 2026 06:27:31 +0800 Subject: [PATCH 09/10] Optimize code style --- src/Misc/Hooks.LaserDraw.cpp | 61 +++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/src/Misc/Hooks.LaserDraw.cpp b/src/Misc/Hooks.LaserDraw.cpp index 83ade683cc..beec9f244e 100644 --- a/src/Misc/Hooks.LaserDraw.cpp +++ b/src/Misc/Hooks.LaserDraw.cpp @@ -74,19 +74,10 @@ namespace LaserRT void Initialize(TechnoClass* pShooter, AbstractClass* pTarget, int weaponIdx, PositionFollow mode, const CoordStruct& initialSource, const CoordStruct& localFLH, int burstIndex, bool stopOnFirerConvert) { - if (pShooter) - { - if (const auto pBuilding = abstract_cast(pShooter)) - { - if (pBuilding->Type->MaxNumberOccupants > 0) - { - if (mode == PositionFollow::Firer) - mode = PositionFollow::None; - else if (mode == PositionFollow::All) - mode = PositionFollow::Target; - } - } - } + const auto pShooterBuilding = abstract_cast(pShooter); + + if (pShooterBuilding && pShooterBuilding->Type->MaxNumberOccupants > 0) + mode &= ~PositionFollow::Firer; if (pShooter && (mode & PositionFollow::Firer)) { @@ -94,13 +85,13 @@ namespace LaserRT this->LocalFLH = localFLH; this->FrozenBurstIndex = burstIndex; this->StopOnFirerConvert = stopOnFirerConvert; + if (stopOnFirerConvert) this->OriginalType = pShooter->GetTechnoType(); const int savedBurstIndex = pShooter->CurrentBurstIndex; pShooter->CurrentBurstIndex = burstIndex; - CoordStruct worldFLH; - pShooter->GetFLH(&worldFLH, weaponIdx, localFLH); + const CoordStruct worldFLH = pShooter->GetFLH(weaponIdx, localFLH); pShooter->CurrentBurstIndex = savedBurstIndex; this->SavedOffset = initialSource - worldFLH; @@ -130,19 +121,21 @@ namespace LaserRT 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; - auto pWeapon = pShooter->GetWeapon(weaponIdx)->WeaponType; - if (pWeapon) + if (const auto pWeapon = pShooter->GetWeapon(weaponIdx)->WeaponType) { - auto pExt = WeaponTypeExt::ExtMap.Find(pWeapon); - stopOnFirerConvert = pExt->LaserPositionUpdate_StopOnFirerConvert.Get(RulesExt::Global()->LaserPositionUpdate_StopOnFirerConvert); + const auto pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon); + stopOnFirerConvert = pWeaponExt->LaserPositionUpdate_StopOnFirerConvert.Get(RulesExt::Global()->LaserPositionUpdate_StopOnFirerConvert); } } @@ -200,9 +193,11 @@ DEFINE_HOOK(0x6FD210, TechnoClass_LaserZap_SetTrackingContext, 0x7) 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; } @@ -250,15 +245,28 @@ DEFINE_FUNCTION_JUMP(CALL, 0x46AD81, Shrapnel_CreateLaser_Wrapper) DEFINE_HOOK(0x4A7696, DiskLaser_Update_ActivateMainBeam_Tracking, 0x6) { GET(LaserDrawClass*, pLaser, EAX); - if (!pLaser) return 0; + + if (!pLaser) + return 0; + const auto it = LaserRT::TrackingMap.find(pLaser); - if (it == LaserRT::TrackingMap.cend()) return 0; + + if (it == LaserRT::TrackingMap.cend()) + return 0; + GET(DiskLaserClass*, pDiskLaser, ESI); const auto pWeapon = pDiskLaser->Weapon; - if (!pWeapon) return 0; + + 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; + + 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; @@ -280,17 +288,14 @@ DEFINE_HOOK(0x550173, LaserDrawClass_Update_Tracking, 0x6) 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; - CoordStruct worldFLH; - pShooter->GetFLH(&worldFLH, data.WeaponIndex, data.LocalFLH); + const CoordStruct worldFLH = pShooter->GetFLH(data.WeaponIndex, data.LocalFLH); pShooter->CurrentBurstIndex = savedBurstIndex; pLaser->Source = worldFLH + data.SavedOffset; From 04ccc0bad62b16bb9cab434f0e8b321537858cff Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Wed, 3 Jun 2026 02:39:38 +0800 Subject: [PATCH 10/10] update docs - title level & add link --- docs/New-or-Enhanced-Logics.md | 2 +- docs/Whats-New.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index f82567f908..610f3e108d 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -2991,7 +2991,7 @@ UnlimboDetonate.KeepSelected=false ; boolean ## Weapons -## Allow Laser drawing position update +### 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. diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 78bb8a86d2..2b93d57028 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -579,7 +579,7 @@ HideShakeEffects=false ; boolean - [Allow customizing guard mission retry delay for buildings with weapons](Fixed-or-Improved-Logics.md#armed-building-guard-retry-delay) (by Starkku) - [Allow `Temporal` warhead to apply ratio and bonus](Fixed-or-Improved-Logics.md#allow-temporal-warhead-to-apply-ratio-and-bonus) (by NetsuNegi) - Allow enabling a looser movement state check for the `DiscardOn=move` condition of AE to support more usage scenarios (by Noble_Fish) -- Allow Laser drawing position update (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)