Skip to content

Commit 5c73877

Browse files
authored
Fixes & improvements to electric bolt handling (#1587)
- Reverts the previous, partial or non-functional fixes for crash at `0x4C2C19`. - Replaces the code for handling tracking of electric bolts and their owner technos to support multiple electric bolt instances per techno (vanilla tracks this with single pointer in UnitClass only). This tracking code also correctly works for units in limbo which should prevent the aforementioned crash from occuring at all. - This tracking is used to determine whether or not the electric bolt updates its source coordinate based on FLH on every frame, allowing it to follow techno's rotation, movement etc. By default this is only enabled for vehicles just as before but can be overridden per weapon by explicitly setting value for `Bolt.FollowFLH`. It now correctly takes burst into accord thanks to changes mentioned in previous point. Fixes #1585
1 parent b6c545a commit 5c73877

File tree

11 files changed

+140
-22
lines changed

11 files changed

+140
-22
lines changed

CREDITS.md

+1
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ This page lists all the individual contributions to the project by their author.
256256
- Custom exit cell for infantry factory
257257
- Vehicles keeping target on move command
258258
- `IsSonic` wave drawing crash fix
259+
- Customizable electric bolt duration and electric bolt-related fixes
259260
- **Morton (MortonPL)**:
260261
- `XDrawOffset` for animations
261262
- Shield passthrough & absorption

docs/Fixed-or-Improved-Logics.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ This page describes all ingame logics that are fixed or improved in Phobos witho
194194
- Fixed issues caused by incorrect reference removal (f.ex. If the unit cloaks/enters transport, it cannot gain experience from previously launched spawners/C4/projectiles).
195195
- Fixed an issue that caused `IsSonic=true` wave drawing to crash the game if the wave traveled over a certain distance.
196196
- Buildings with foundation bigger than 1x1 can now recycle spawner correctly.
197+
- Electric bolts that are supposed to update their position based on units current firing coords (by default, those fired by vehicles) now do so correctly for more than one concurrent electric bolt.
197198

198199
## Fixes / interactions with other extensions
199200

@@ -1670,6 +1671,7 @@ FireOnce.ResetSequence=true ; boolean
16701671
- You can now specify individual bolts you want to disable for `IsElectricBolt=true` weapons. Note that this is only a visual change.
16711672
- By default `IsElectricBolt=true` effect draws a bolt with 8 arcs. This can now be customized per WeaponType with `Bolt.Arcs`. Value of 0 results in a straight line being drawn.
16721673
- `Bolt.Duration` can be specified to explicitly set the overall duration of the visual electric bolt effect. Only values in range of 1 to 31 are accepted, values outside this range are clamped into it.
1674+
- `Bolt.FollowFLH` can be used to override the default behaviour where the electric bolt source coordinates change to match the unit's firing coord on every frame (making it follow unit's movement, rotation etc). Defaults to true on vehicles, false for everything else.
16731675

16741676
In `rulesmd.ini`:
16751677
```ini
@@ -1679,10 +1681,11 @@ Bolt.Disable2=false ; boolean
16791681
Bolt.Disable3=false ; boolean
16801682
Bolt.Arcs=8 ; integer
16811683
Bolt.Duration=17 ; integer, game frames
1684+
Bolt.FollowFLH= ; boolean
16821685
```
16831686

16841687
```{note}
1685-
Due to technical constraints, these features do not work with electric bolts created from support weapon of [Ares' Prism Forwarding](https://ares-developers.github.io/Ares-docs/new/buildings/prismforwarding.html).
1688+
Due to technical constraints, these features do not work with electric bolts created from support weapon of [Ares' Prism Forwarding](https://ares-developers.github.io/Ares-docs/new/buildings/prismforwarding.html) or those from AirburstWeapon.
16861689
```
16871690

16881691
### Single-color lasers

docs/Whats-New.md

+2
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ New:
328328
- No turret unit turn to the target (by CrimRecya & TaranDahl)
329329
- Damage multiplier for different houses (by CrimRecya)
330330
- Customizable duration for electric bolts (by Starkku)
331+
- Customizable FLH tracking for electric bolts (by Starkku)
331332
- Extended gattling rate down logic (by CrimRecya)
332333
- Sell or undeploy building on impact (by CrimRecya)
333334
- No rearm and reload in EMP or temporal (by CrimRecya)
@@ -350,6 +351,7 @@ Vanilla fixes:
350351
- Prevent the units with locomotors that cause problems from entering the tank bunker (by TaranDahl)
351352
- Fixed an issue that harvesters with amphibious movement zone can not automatically return to refineries with `WaterBound` on water surface (by NetsuNegi)
352353
- Buildings with foundation bigger than 1x1 can now recycle spawned correctly (by TaranDahl)
354+
- Electric bolts that are supposed to update their position based on units current firing coords (by default, those fired by vehicles) now do so correctly for more than one concurrent electric bolt (by Starkku)
353355
354356
Fixes / interactions with other extensions:
355357
- Allowed `AuxBuilding` and Ares' `SW.Aux/NegBuildings` to count building upgrades (by Ollerus)

src/Ext/Bullet/Body.cpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,9 @@ inline void BulletExt::SimulatedFiringElectricBolt(BulletClass* pBullet)
268268
{
269269
pEBolt->AlternateColor = pWeapon->IsAlternateColor;
270270
//TODO Weapon's Bolt.Color1, Bolt.Color2, Bolt.Color3(Ares)
271-
WeaponTypeExt::BoltWeaponMap[pEBolt] = WeaponTypeExt::ExtMap.Find(pWeapon);
271+
auto& weaponStruct = WeaponTypeExt::BoltWeaponMap[pEBolt];
272+
weaponStruct.Weapon = WeaponTypeExt::ExtMap.Find(pWeapon);
273+
weaponStruct.BurstIndex = 0;
272274
pEBolt->Fire(pBullet->SourceCoords, pBullet->TargetCoords, 0);
273275
}
274276
}

src/Ext/Techno/Body.cpp

+15
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,18 @@ TechnoExt::ExtData::~ExtData()
4040
auto& vec = HouseExt::ExtMap.Find(pThis->Owner)->OwnedCountedHarvesters;
4141
vec.erase(std::remove(vec.begin(), vec.end(), pThis), vec.end());
4242
}
43+
44+
this->ClearElectricBolts();
45+
}
46+
47+
void TechnoExt::ExtData::ClearElectricBolts()
48+
{
49+
for (auto const pBolt : this->ElectricBolts)
50+
{
51+
pBolt->Owner = nullptr;
52+
}
53+
54+
this->ElectricBolts.clear();
4355
}
4456

4557
bool TechnoExt::IsActiveIgnoreEMP(TechnoClass* pThis)
@@ -552,6 +564,7 @@ void TechnoExt::ExtData::Serialize(T& Stm)
552564
.Process(this->AttachedEffects)
553565
.Process(this->AE)
554566
.Process(this->PreviousType)
567+
.Process(this->ElectricBolts)
555568
.Process(this->AnimRefCount)
556569
.Process(this->ReceiveDamage)
557570
.Process(this->PassengerDeletionTimer)
@@ -597,6 +610,8 @@ void TechnoExt::ExtData::SaveToStream(PhobosStreamWriter& Stm)
597610
{
598611
Extension<TechnoClass>::SaveToStream(Stm);
599612
this->Serialize(Stm);
613+
614+
this->ClearElectricBolts();
600615
}
601616

602617
bool TechnoExt::LoadGlobals(PhobosStreamReader& Stm)

src/Ext/Techno/Body.h

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class TechnoExt
2929
std::vector<std::unique_ptr<AttachEffectClass>> AttachedEffects;
3030
AttachEffectTechnoProperties AE;
3131
TechnoTypeClass* PreviousType; // Type change registered in TechnoClass::AI on current frame and used in FootClass::AI on same frame and reset after.
32+
std::vector<EBolt*> ElectricBolts;
3233
int AnimRefCount; // Used to keep track of how many times this techno is referenced in anims f.ex Invoker, ParentBuilding etc., for pointer invalidation.
3334
bool ReceiveDamage;
3435
bool LastKillWasTeamTarget;
@@ -75,6 +76,7 @@ class TechnoExt
7576
, AttachedEffects {}
7677
, AE {}
7778
, PreviousType { nullptr }
79+
, ElectricBolts {}
7880
, AnimRefCount { 0 }
7981
, ReceiveDamage { false }
8082
, LastKillWasTeamTarget { false }
@@ -153,6 +155,7 @@ class TechnoExt
153155
private:
154156
template <typename T>
155157
void Serialize(T& Stm);
158+
void ClearElectricBolts();
156159
};
157160

158161
class ExtContainer final : public Container<TechnoExt>

src/Ext/WeaponType/Body.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ void WeaponTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
8181
this->Bolt_Disable3.Read(exINI, pSection, "Bolt.Disable3");
8282
this->Bolt_Arcs.Read(exINI, pSection, "Bolt.Arcs");
8383
this->Bolt_Duration.Read(exINI, pSection, "Bolt.Duration");
84+
this->Bolt_FollowFLH.Read(exINI, pSection, "Bolt.FollowFLH");
8485

8586
this->RadType.Read<true>(exINI, pSection, "RadType");
8687

@@ -139,6 +140,7 @@ void WeaponTypeExt::ExtData::Serialize(T& Stm)
139140
.Process(this->Bolt_Disable3)
140141
.Process(this->Bolt_Arcs)
141142
.Process(this->Bolt_Duration)
143+
.Process(this->Bolt_FollowFLH)
142144
.Process(this->Strafing)
143145
.Process(this->Strafing_Shots)
144146
.Process(this->Strafing_SimulateBurst)

src/Ext/WeaponType/Body.h

+10-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class WeaponTypeExt
3030
Valueable<bool> Bolt_Disable3;
3131
Valueable<int> Bolt_Arcs;
3232
Valueable<int> Bolt_Duration;
33+
Nullable<bool> Bolt_FollowFLH;
3334
Nullable<bool> Strafing;
3435
Nullable<int> Strafing_Shots;
3536
Valueable<bool> Strafing_SimulateBurst;
@@ -82,6 +83,7 @@ class WeaponTypeExt
8283
, Bolt_Disable3 { false }
8384
, Bolt_Arcs { 8 }
8485
, Bolt_Duration { 17 }
86+
, Bolt_FollowFLH {}
8587
, Strafing { }
8688
, Strafing_Shots {}
8789
, Strafing_SimulateBurst { false }
@@ -152,13 +154,19 @@ class WeaponTypeExt
152154
~ExtContainer();
153155
};
154156

157+
struct EBoltWeaponStruct
158+
{
159+
WeaponTypeExt::ExtData* Weapon;
160+
int BurstIndex;
161+
};
162+
155163
static ExtContainer ExtMap;
156164

157165
static bool LoadGlobals(PhobosStreamReader& Stm);
158166
static bool SaveGlobals(PhobosStreamWriter& Stm);
159167

160168
static double OldRadius;
161-
static PhobosMap<EBolt*, const WeaponTypeExt::ExtData*> BoltWeaponMap;
169+
static PhobosMap<EBolt*, EBoltWeaponStruct> BoltWeaponMap;
162170
static const WeaponTypeExt::ExtData* BoltWeaponType;
163171

164172
static void DetonateAt(WeaponTypeClass* pThis, AbstractClass* pTarget, TechnoClass* pOwner, HouseClass* pFiringHouse = nullptr);
@@ -168,4 +176,5 @@ class WeaponTypeExt
168176
static int GetRangeWithModifiers(WeaponTypeClass* pThis, TechnoClass* pFirer);
169177
static int GetRangeWithModifiers(WeaponTypeClass* pThis, TechnoClass* pFirer, int range);
170178
static int GetTechnoKeepRange(WeaponTypeClass* pThis, TechnoClass* pFirer, bool isMinimum);
179+
171180
};

src/Ext/WeaponType/Hook.EBolt.cpp

+100-5
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
#include "Body.h"
22
#include <EBolt.h>
3+
#include <Ext/Techno/Body.h>
34
#include <Utilities/Macro.h>
45
#include <Helpers/Macro.h>
56

6-
PhobosMap<EBolt*, const WeaponTypeExt::ExtData*> WeaponTypeExt::BoltWeaponMap;
7+
PhobosMap<EBolt*, WeaponTypeExt::EBoltWeaponStruct> WeaponTypeExt::BoltWeaponMap;
78
const WeaponTypeExt::ExtData* WeaponTypeExt::BoltWeaponType = nullptr;
89

910
DEFINE_HOOK(0x6FD494, TechnoClass_FireEBolt_SetExtMap_AfterAres, 0x7)
1011
{
11-
GET_STACK(WeaponTypeClass*, pWeapon, STACK_OFFSET(0x30, 0x8));
12+
GET(TechnoClass*, pThis, EDI);
1213
GET(EBolt*, pBolt, EAX);
14+
GET_STACK(WeaponTypeClass*, pWeapon, STACK_OFFSET(0x30, 0x8));
1315

1416
if (pWeapon)
1517
{
1618
auto const pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon);
17-
WeaponTypeExt::BoltWeaponMap[pBolt] = pWeaponExt;
19+
auto& weaponStruct = WeaponTypeExt::BoltWeaponMap[pBolt];
20+
weaponStruct.Weapon = pWeaponExt;
21+
weaponStruct.BurstIndex = pThis->CurrentBurstIndex;
1822
pBolt->Lifetime = 1 << (Math::clamp(pWeaponExt->Bolt_Duration, 1, 31) - 1);
1923
}
2024

@@ -35,7 +39,7 @@ DEFINE_HOOK(0x4C20BC, EBolt_DrawArcs, 0xB)
3539
enum { DoLoop = 0x4C20C7, Break = 0x4C2400 };
3640

3741
GET_STACK(EBolt*, pBolt, 0x40);
38-
WeaponTypeExt::BoltWeaponType = WeaponTypeExt::BoltWeaponMap.get_or_default(pBolt);
42+
WeaponTypeExt::BoltWeaponType = WeaponTypeExt::BoltWeaponMap.get_or_default(pBolt).Weapon;
3943

4044
GET_STACK(int, plotIndex, STACK_OFFSET(0x408, -0x3E0));
4145

@@ -47,7 +51,7 @@ DEFINE_HOOK(0x4C20BC, EBolt_DrawArcs, 0xB)
4751
DEFINE_HOOK(0x4C24E4, Ebolt_DrawFist_Disable, 0x8)
4852
{
4953
GET_STACK(EBolt*, pBolt, 0x40);
50-
WeaponTypeExt::BoltWeaponType = WeaponTypeExt::BoltWeaponMap.get_or_default(pBolt);
54+
WeaponTypeExt::BoltWeaponType = WeaponTypeExt::BoltWeaponMap.get_or_default(pBolt).Weapon;
5155

5256
return (WeaponTypeExt::BoltWeaponType && WeaponTypeExt::BoltWeaponType->Bolt_Disable1) ? 0x4C2515 : 0;
5357
}
@@ -61,3 +65,94 @@ DEFINE_HOOK(0x4C26EE, Ebolt_DrawThird_Disable, 0x8)
6165
{
6266
return (WeaponTypeExt::BoltWeaponType && WeaponTypeExt::BoltWeaponType->Bolt_Disable3) ? 0x4C2710 : 0;
6367
}
68+
69+
#pragma region EBoltTrackingFixes
70+
71+
class EBoltFake final : public EBolt
72+
{
73+
public:
74+
void _SetOwner(TechnoClass* pTechno, int weaponIndex);
75+
void _RemoveFromOwner();
76+
};
77+
78+
void EBoltFake::_SetOwner(TechnoClass* pTechno, int weaponIndex)
79+
{
80+
if (pTechno && pTechno->IsAlive)
81+
{
82+
auto const pWeapon = pTechno->GetWeapon(weaponIndex)->WeaponType;
83+
auto const pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon);
84+
85+
if (!pWeaponExt->Bolt_FollowFLH.Get(pTechno->WhatAmI() == AbstractType::Unit))
86+
return;
87+
88+
this->Owner = pTechno;
89+
this->WeaponSlot = weaponIndex;
90+
91+
auto const pExt = TechnoExt::ExtMap.Find(pTechno);
92+
pExt->ElectricBolts.push_back(this);
93+
}
94+
}
95+
96+
void EBoltFake::_RemoveFromOwner()
97+
{
98+
auto const pExt = TechnoExt::ExtMap.Find(this->Owner);
99+
auto& vec = pExt->ElectricBolts;
100+
vec.erase(std::remove(vec.begin(), vec.end(), this), vec.end());
101+
this->Owner = nullptr;
102+
}
103+
104+
DEFINE_FUNCTION_JUMP(LJMP, 0x4C2BD0, EBoltFake::_SetOwner); // Failsafe in case called in another module
105+
106+
DEFINE_HOOK(0x6FD5D6, TechnoClass_InitEBolt, 0x6)
107+
{
108+
enum { SkipGameCode = 0x6FD60B };
109+
110+
GET(TechnoClass*, pThis, ESI);
111+
GET(EBolt*, pBolt, EAX);
112+
GET(int, weaponIndex, EBX);
113+
114+
if (pBolt)
115+
((EBoltFake*)pBolt)->_SetOwner(pThis, weaponIndex);
116+
117+
return SkipGameCode;
118+
}
119+
120+
DEFINE_HOOK(0x4C285D, EBolt_DrawAll_BurstIndex, 0x5)
121+
{
122+
enum { SkipGameCode = 0x4C2882 };
123+
124+
GET(TechnoClass*, pTechno, ECX);
125+
GET_STACK(EBolt*, pThis, STACK_OFFSET(0x34, -0x24));
126+
127+
int burstIndex = pTechno->CurrentBurstIndex;
128+
pTechno->CurrentBurstIndex = WeaponTypeExt::BoltWeaponMap[pThis].BurstIndex;
129+
auto const fireCoords = pTechno->GetFLH(pThis->WeaponSlot, CoordStruct::Empty);
130+
pTechno->CurrentBurstIndex = burstIndex;
131+
R->EAX(&fireCoords);
132+
133+
return SkipGameCode;
134+
}
135+
136+
DEFINE_HOOK(0x4C299F, EBolt_DrawAll_EndOfLife, 0x6)
137+
{
138+
enum { SkipGameCode = 0x4C29B9 };
139+
140+
GET(EBolt*, pThis, EAX);
141+
142+
if (pThis->Owner)
143+
((EBoltFake*)pThis)->_RemoveFromOwner();
144+
145+
return SkipGameCode;
146+
}
147+
148+
DEFINE_HOOK(0x4C2A02, EBolt_DestroyVector, 0x6)
149+
{
150+
enum { SkipGameCode = 0x4C2A08 };
151+
152+
GET(EBolt*, pThis, EAX);
153+
154+
((EBoltFake*)pThis)->_RemoveFromOwner();
155+
156+
return SkipGameCode;
157+
}
158+
#pragma endregion

src/Misc/Hooks.BugFixes.cpp

-7
Original file line numberDiff line numberDiff line change
@@ -1563,10 +1563,3 @@ DEFINE_HOOK(0x75EE49, WaveClass_DrawSonic_CrashFix, 0x7)
15631563

15641564
return 0;
15651565
}
1566-
1567-
// EIP 004C2C19 crash has 2 causes: the Owner of an EBolt being invalid, and the ElectricBolt of a Unit being invalid
1568-
// Vanilla doesn't have InvalidatePointer for EBolt, so it's made into this way to clear the pointer on EBolt
1569-
// now we'll clear Owner for EBolt in AnnounceInvalidPointer so there won't be nullptr when EBolt trying to access an Owner
1570-
// in this case, we can also dismiss ElectricBolt on Unit, to prevent the crash that caused by its invalidation
1571-
DEFINE_JUMP(LJMP, 0x6FD5F2, 0x6FD5FC)
1572-
DEFINE_JUMP(LJMP, 0x6FD600, 0x6FD606)

src/Phobos.Ext.cpp

-7
Original file line numberDiff line numberDiff line change
@@ -240,13 +240,6 @@ DEFINE_HOOK(0x7258D0, AnnounceInvalidPointer, 0x6)
240240

241241
PhobosTypeRegistry::InvalidatePointer(pInvalid, removed);
242242

243-
// Fix EBolt Owner not being invalidated
244-
for (auto const pBolt : EBolt::Array)
245-
{
246-
if (pBolt->Owner == pInvalid)
247-
pBolt->Owner = nullptr;
248-
}
249-
250243
return 0;
251244
}
252245

0 commit comments

Comments
 (0)