Skip to content

Commit 1517b01

Browse files
initial
Co-authored-by: Netsu_Negi <71598172+NetsuNegi@users.noreply.github.com>
1 parent c54812d commit 1517b01

11 files changed

Lines changed: 321 additions & 2 deletions

File tree

docs/New-or-Enhanced-Logics.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2984,6 +2984,31 @@ UnlimboDetonate.KeepSelected=false ; boolean
29842984

29852985
## Weapons
29862986

2987+
## Allow Laser drawing position update
2988+
2989+
- Now you can define whether the endpoints of a laser drawing are updated during its duration.
2990+
- `None`: No update.
2991+
- `Firer`: The start point follows the firer's FLH; if the firer dies, the update stops.
2992+
- 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.
2993+
- `Target`: The end point follows the target; if the target object dies, the update stops.
2994+
- `All`: Equivalent to specifying both `Firer` and `Target`.
2995+
2996+
```{note}
2997+
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.
2998+
- If `Firer` is set, it will be treated as `None`.
2999+
- If `All` is set, it will be treated as `Target`.
3000+
```
3001+
3002+
In `rulesmd.ini`:
3003+
```ini
3004+
[SOMEWEAPON] ; WeaponType with IsLaser=yes or DiskLaser=yes
3005+
LaserPositionUpdate=none ; Position Follow Enumeration (none|firer|target|all)
3006+
```
3007+
3008+
```{warning}
3009+
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.
3010+
```
3011+
29873012
### AreaFire target customization
29883013

29893014
- 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.

docs/Whats-New.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,7 @@ New:
581581
- [Electric bolt Z-adjust](Fixed-or-Improved-Logics.md#electric-bolt-z-adjust) (by Noble_Fish)
582582
- Allow disabling the processing of the Z-depth of EBolt drawn by BuildingType being clamped to non-positive numbers (by Noble_Fish)
583583
- Add the `Bolt.ZAdjust` setting item to the LaserTrailType with `DrawType=ebolt` (by Noble_Fish)
584+
- Allow Laser drawing position update (by Noble_Fish)
584585
585586
Vanilla fixes:
586587
- Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya)

src/Ext/Bullet/Body.cpp

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
#include <Ext/EBolt/Body.h>
88
#include <New/Entity/LaserTrailClass.h>
99

10+
namespace LaserRT
11+
{
12+
void SetLaserTrackingData(LaserDrawClass* pLaser, TechnoClass* pShooter, AbstractClass* pTarget, int weaponIdx, PositionFollow mode, bool ignoreShooter);
13+
}
14+
1015
BulletExt::ExtContainer BulletExt::ExtMap;
1116

1217
void BulletExt::ExtData::InterceptBullet(TechnoClass* pSource, BulletClass* pInterceptor)
@@ -197,17 +202,22 @@ inline void BulletExt::SimulatedFiringReport(BulletClass* pBullet)
197202
inline void BulletExt::SimulatedFiringLaser(BulletClass* pBullet, HouseClass* pHouse)
198203
{
199204
// Can not use 0x6FD210 because the firer may die
205+
if (!pBullet || !pBullet->WeaponType)
206+
return;
207+
200208
const auto pWeapon = pBullet->WeaponType;
201209

202210
if (!pWeapon->IsLaser)
203211
return;
204212

205213
const auto pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon);
206214

215+
LaserDrawClass* pLaser = nullptr;
216+
207217
if (pWeapon->IsHouseColor || pWeaponExt->Laser_IsSingleColor)
208218
{
209219
const auto black = ColorStruct { 0, 0, 0 };
210-
const auto pLaser = GameCreate<LaserDrawClass>(pBullet->SourceCoords, ((pBullet->Type->Inviso && pBullet->Type->FlakScatter) ? pBullet->Location : pBullet->TargetCoords),
220+
pLaser = GameCreate<LaserDrawClass>(pBullet->SourceCoords, ((pBullet->Type->Inviso && pBullet->Type->FlakScatter) ? pBullet->Location : pBullet->TargetCoords),
211221
((pWeapon->IsHouseColor && pHouse) ? pHouse->LaserColor : pWeapon->LaserInnerColor), black, black, pWeapon->LaserDuration);
212222

213223
pLaser->IsHouseColor = true;
@@ -216,13 +226,35 @@ inline void BulletExt::SimulatedFiringLaser(BulletClass* pBullet, HouseClass* pH
216226
}
217227
else
218228
{
219-
const auto pLaser = GameCreate<LaserDrawClass>(pBullet->SourceCoords, ((pBullet->Type->Inviso && pBullet->Type->FlakScatter) ? pBullet->Location : pBullet->TargetCoords),
229+
pLaser = GameCreate<LaserDrawClass>(pBullet->SourceCoords, ((pBullet->Type->Inviso && pBullet->Type->FlakScatter) ? pBullet->Location : pBullet->TargetCoords),
220230
pWeapon->LaserInnerColor, pWeapon->LaserOuterColor, pWeapon->LaserOuterSpread, pWeapon->LaserDuration);
221231

222232
pLaser->IsHouseColor = false;
223233
pLaser->Thickness = 3;
224234
pLaser->IsSupported = false;
225235
}
236+
237+
// LaserPositionUpdate
238+
if (pLaser && pWeaponExt)
239+
{
240+
auto mode = pWeaponExt->LaserPositionUpdate;
241+
const auto pBulletExt = BulletExt::ExtMap.Find(pBullet);
242+
const bool isSplit = (pBulletExt && pBulletExt->IsSplitFromAirburst);
243+
244+
if (isSplit)
245+
{
246+
if (mode == PositionFollow::Firer)
247+
mode = PositionFollow::None;
248+
else if (mode == PositionFollow::All)
249+
mode = PositionFollow::Target;
250+
}
251+
252+
if (mode != PositionFollow::None)
253+
{
254+
auto const pTarget = abstract_cast<ObjectClass*>(pBullet->Target);
255+
LaserRT::SetLaserTrackingData(pLaser, pBullet->Owner, pTarget, 0, mode, isSplit);
256+
}
257+
}
226258
}
227259

228260
// Make sure pBullet and pBullet->WeaponType is not empty before call

src/Ext/Bullet/Body.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class BulletExt
2727
int DamageNumberOffset;
2828
int ParabombFallRate;
2929
bool IsInstantDetonation;
30+
bool IsSplitFromAirburst = false;
3031

3132
TrajectoryPointer Trajectory;
3233

src/Ext/Bullet/Hooks.DetonateLogics.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,7 @@ DEFINE_HOOK(0x469EC0, BulletClass_Logics_AirburstWeapon, 0x6)
833833
coords = MapClass::GetRandomCoordsNear(coords, distance, false);
834834
}
835835

836+
BulletExt::ExtMap.Find(pBullet)->IsSplitFromAirburst = true;
836837
BulletExt::SimulatedFiringUnlimbo(pBullet, pOwner, pWeapon, coords, true);
837838
BulletExt::SimulatedFiringEffects(pBullet, pOwner, nullptr, useFiringEffects, true);
838839
}

src/Ext/WeaponType/Body.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ void WeaponTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
117117
this->AreaFire_Target.Read(exINI, pSection, "AreaFire.Target");
118118
this->FeedbackWeapon.Read<true>(exINI, pSection, "FeedbackWeapon");
119119
this->Laser_IsSingleColor.Read(exINI, pSection, "IsSingleColor");
120+
this->LaserPositionUpdate.Read(exINI, pSection, "LaserPositionUpdate");
120121
this->LaserZAdjust.Read(exINI, pSection, "LaserZAdjust");
121122
this->EBoltZAdjust.Read(exINI, pSection, "EBoltZAdjust");
122123
this->EBoltZAdjust_ClampInitialDepthForBuilding.Read(exINI, pSection, "EBoltZAdjust.ClampInitialDepthForBuilding");
@@ -216,6 +217,7 @@ void WeaponTypeExt::ExtData::Serialize(T& Stm)
216217
.Process(this->AreaFire_Target)
217218
.Process(this->FeedbackWeapon)
218219
.Process(this->Laser_IsSingleColor)
220+
.Process(this->LaserPositionUpdate)
219221
.Process(this->LaserZAdjust)
220222
.Process(this->EBoltZAdjust)
221223
.Process(this->EBoltZAdjust_ClampInitialDepthForBuilding)

src/Ext/WeaponType/Body.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class WeaponTypeExt
4545
Valueable<AreaFireTarget> AreaFire_Target;
4646
Valueable<WeaponTypeClass*> FeedbackWeapon;
4747
Valueable<bool> Laser_IsSingleColor;
48+
Valueable<PositionFollow> LaserPositionUpdate;
4849
Nullable<int> LaserZAdjust;
4950
Nullable<int> EBoltZAdjust;
5051
Nullable<bool> EBoltZAdjust_ClampInitialDepthForBuilding;
@@ -134,6 +135,7 @@ class WeaponTypeExt
134135
, AreaFire_Target { AreaFireTarget::Base }
135136
, FeedbackWeapon {}
136137
, Laser_IsSingleColor { false }
138+
, LaserPositionUpdate { PositionFollow::None }
137139
, LaserZAdjust {}
138140
, EBoltZAdjust {}
139141
, EBoltZAdjust_ClampInitialDepthForBuilding {}
@@ -231,4 +233,7 @@ class WeaponTypeExt
231233
static int GetRangeWithModifiers(WeaponTypeClass* pThis, TechnoClass* pFirer);
232234
static int GetRangeWithModifiers(WeaponTypeClass* pThis, TechnoClass* pFirer, int range);
233235
static int GetTechnoKeepRange(WeaponTypeClass* pThis, TechnoClass* pFirer, bool isMinimum);
236+
237+
// Misc/Hooks.LaserDraw.cpp
238+
static void LaserTrackingPointerExpired(void* ptr, bool removed);
234239
};

src/Misc/Hooks.LaserDraw.cpp

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#include <Ext\WeaponType\Body.h>
2+
#include <Ext/Techno/Body.h>
23
#include <Helpers/Macro.h>
34
#include <Utilities/GeneralUtils.h>
5+
#include <unordered_map>
46

57
namespace LaserDrawTemp
68
{
@@ -53,3 +55,215 @@ DEFINE_HOOK(0x6FD3FD, TechnoClass_LaserZap_ZAdjust, 0x5)
5355

5456
return 0;
5557
}
58+
59+
#pragma region LaserPositionUpdate
60+
61+
namespace LaserRT
62+
{
63+
static CoordStruct GetRelativeFLH(TechnoClass* pShooter, int weaponIndex)
64+
{
65+
bool flhFound = false;
66+
CoordStruct result = TechnoExt::GetBurstFLH(pShooter, weaponIndex, flhFound);
67+
68+
if (!flhFound)
69+
{
70+
result = pShooter->GetWeapon(weaponIndex)->FLH;
71+
72+
if (pShooter->CurrentBurstIndex % 2 != 0)
73+
result.Y = -result.Y;
74+
}
75+
76+
return result;
77+
}
78+
79+
struct TrackingData
80+
{
81+
TechnoClass* Shooter { nullptr };
82+
ObjectClass* Target { nullptr };
83+
int WeaponIndex { 0 };
84+
PositionFollow FollowMode { PositionFollow::None };
85+
bool IsDiskLaser = false;
86+
CoordStruct SavedRelativeFLH { CoordStruct::Empty };
87+
88+
void Initialize(TechnoClass* pShooter, AbstractClass* pTarget, int weaponIdx, PositionFollow mode)
89+
{
90+
if (pShooter && mode & PositionFollow::Firer)
91+
{
92+
this->Shooter = pShooter;
93+
this->SavedRelativeFLH = LaserRT::GetRelativeFLH(pShooter, weaponIdx);
94+
}
95+
96+
if (mode & PositionFollow::Target)
97+
this->Target = abstract_cast<ObjectClass*>(pTarget);
98+
99+
this->WeaponIndex = weaponIdx;
100+
this->FollowMode = mode;
101+
}
102+
103+
void PointerExpired(void* ptr, bool removed)
104+
{
105+
if (!removed)
106+
return;
107+
108+
AnnounceInvalidPointer(this->Shooter, ptr);
109+
AnnounceInvalidPointer(this->Target, ptr);
110+
}
111+
};
112+
113+
std::unordered_map<LaserDrawClass*, TrackingData> TrackingMap;
114+
115+
void SetLaserTrackingData(LaserDrawClass* pLaser, TechnoClass* pShooter, AbstractClass* pTarget, int weaponIdx, PositionFollow mode, bool ignoreShooter)
116+
{
117+
TrackingData data;
118+
data.Initialize(ignoreShooter ? nullptr : pShooter, pTarget, weaponIdx, mode);
119+
TrackingMap[pLaser] = data;
120+
}
121+
122+
TechnoClass* Shooter = nullptr;
123+
AbstractClass* Target = nullptr;
124+
int WeaponIndex = 0;
125+
bool IgnoreShooter = false;
126+
}
127+
128+
// container hooks
129+
130+
// IsLaser ctor/dtor/remove hooks
131+
DEFINE_HOOK(0x54FE60, LaserDrawClass_CTOR_Update, 0x5)
132+
{
133+
GET(LaserDrawClass*, pLaser, ECX);
134+
LaserRT::TrackingMap[pLaser] = LaserRT::TrackingData {};
135+
return 0;
136+
}
137+
138+
DEFINE_HOOK_AGAIN(0x5501D7, LaserDrawClass_DTOR_Tracking, 0x5)
139+
DEFINE_HOOK_AGAIN(0x5500EF, LaserDrawClass_DTOR_Tracking, 0x5)
140+
DEFINE_HOOK_AGAIN(0x550016, LaserDrawClass_DTOR_Tracking, 0x6)
141+
DEFINE_HOOK(0x54FFB0, LaserDrawClass_DTOR_Tracking, 0x7) // LaserDrawClass::DTOR
142+
{
143+
GET(LaserDrawClass*, pLaser, ECX);
144+
LaserRT::TrackingMap.erase(pLaser);
145+
return 0;
146+
}
147+
148+
void WeaponTypeExt::LaserTrackingPointerExpired(void* ptr, bool removed)
149+
{
150+
for (auto& [_, data] : LaserRT::TrackingMap)
151+
data.PointerExpired(ptr, removed);
152+
}
153+
154+
// hooks
155+
156+
DEFINE_HOOK(0x6FD210, TechnoClass_LaserZap_SetTrackingContext, 0x7)
157+
{
158+
GET(TechnoClass*, pShooter, ECX);
159+
GET_STACK(ObjectClass*, pTarget, 0x4);
160+
GET_STACK(int, weaponIdx, 0x8);
161+
162+
LaserRT::Shooter = LaserRT::IgnoreShooter ? nullptr : pShooter;
163+
LaserRT::Target = pTarget;
164+
LaserRT::WeaponIndex = weaponIdx;
165+
return 0;
166+
}
167+
168+
DEFINE_HOOK(0x6FD446, TechnoClass_LaserZap_Tracking, 0x7)
169+
{
170+
GET(WeaponTypeClass*, pWeapon, ECX);
171+
const auto pShooter = std::exchange(LaserRT::Shooter, nullptr);
172+
const auto pTarget = std::exchange(LaserRT::Target, nullptr);
173+
const int weaponIdx = std::exchange(LaserRT::WeaponIndex, 0);
174+
175+
GET(LaserDrawClass*, pLaser, EAX);
176+
const auto it = LaserRT::TrackingMap.find(pLaser);
177+
178+
if (it == LaserRT::TrackingMap.cend())
179+
return 0;
180+
181+
const auto mode = WeaponTypeExt::ExtMap.Find(pWeapon)->LaserPositionUpdate.Get();
182+
183+
if (mode == PositionFollow::None)
184+
return 0;
185+
186+
it->second.Initialize(pShooter, pTarget, weaponIdx, mode);
187+
return 0;
188+
}
189+
190+
static LaserDrawClass* __fastcall Shrapnel_CreateLaser_Wrapper(TechnoClass* pShooter, void*, ObjectClass* pTarget
191+
, int weaponIdx, WeaponTypeClass* pWeapon, const CoordStruct& sourceCoords)
192+
{
193+
LaserRT::IgnoreShooter = true;
194+
const auto pLaser = pShooter->CreateLaser(pTarget, weaponIdx, pWeapon, sourceCoords);
195+
LaserRT::IgnoreShooter = false;
196+
return pLaser;
197+
}
198+
DEFINE_FUNCTION_JUMP(CALL, 0x46A8AC, Shrapnel_CreateLaser_Wrapper)
199+
DEFINE_FUNCTION_JUMP(CALL, 0x46AD81, Shrapnel_CreateLaser_Wrapper)
200+
201+
// DiskLaser main beam activation
202+
DEFINE_HOOK(0x4A7696, DiskLaser_Update_ActivateMainBeam_Tracking, 0x6)
203+
{
204+
GET(LaserDrawClass*, pLaser, EAX);
205+
206+
if (!pLaser)
207+
return 0;
208+
209+
const auto it = LaserRT::TrackingMap.find(pLaser);
210+
211+
if (it == LaserRT::TrackingMap.cend())
212+
return 0;
213+
214+
GET(DiskLaserClass*, pDiskLaser, ESI);
215+
const auto pWeapon = pDiskLaser->Weapon;
216+
217+
if (!pWeapon)
218+
return 0;
219+
220+
const auto mode = WeaponTypeExt::ExtMap.Find(pWeapon)->LaserPositionUpdate.Get();
221+
222+
if (mode == PositionFollow::None)
223+
return 0;
224+
225+
it->second.Initialize(pDiskLaser->Owner, pDiskLaser->Target, 0, mode);
226+
227+
if (it->second.Shooter && (mode & PositionFollow::Firer))
228+
{
229+
CoordStruct flh;
230+
it->second.Shooter->GetFLH(&flh, 0, CoordStruct { 0, 0, 0 });
231+
it->second.SavedRelativeFLH = pLaser->Source - flh;
232+
it->second.IsDiskLaser = true;
233+
}
234+
235+
return 0;
236+
}
237+
238+
// Per‑frame coordinate update
239+
DEFINE_HOOK(0x550173, LaserDrawClass_Update_Tracking, 0x6)
240+
{
241+
GET(LaserDrawClass*, pLaser, ESI);
242+
const auto it = LaserRT::TrackingMap.find(pLaser);
243+
244+
if (it == LaserRT::TrackingMap.cend())
245+
return 0;
246+
247+
const auto& data = it->second;
248+
249+
if (const auto pShooter = data.Shooter)
250+
{
251+
if (data.IsDiskLaser)
252+
{
253+
CoordStruct flh;
254+
pShooter->GetFLH(&flh, data.WeaponIndex, CoordStruct { 0, 0, 0 });
255+
pLaser->Source = flh + data.SavedRelativeFLH;
256+
}
257+
else
258+
{
259+
pLaser->Source = TechnoExt::GetFLHAbsoluteCoords(pShooter, data.SavedRelativeFLH, true);
260+
}
261+
}
262+
263+
if (const auto pTarget = data.Target)
264+
pLaser->Target = pTarget->GetTargetCoords();
265+
266+
return 0;
267+
}
268+
269+
#pragma endregion

0 commit comments

Comments
 (0)