Skip to content

Commit 094b10a

Browse files
authored
Infantry support for IsGattling (#1689)
![加特林](https://github.com/user-attachments/assets/27492c16-cc6b-4db2-b5d9-be9363a72b95) Infantry support `IsGattling=yes`. --------- 让步兵支持`IsGattling=yes`。
1 parent 2592d1d commit 094b10a

File tree

12 files changed

+252
-100
lines changed

12 files changed

+252
-100
lines changed

CREDITS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@ This page lists all the individual contributions to the project by their author.
369369
- Second weapon with `ElectricAssault=yes` will not unconditionally attack your building with `Overpowerable=yes`
370370
- Fix the issue where some units crashed after the deployment transformation
371371
- Turretless vehicles with `Voxel=no` support use `FireUp` like infantry
372+
- Infantry support `IsGattling=yes`
372373
- **NetsuNegi**:
373374
- Forbidding parallel AI queues by type
374375
- Jumpjet crash speed fix when crashing onto building

Phobos.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
</ProjectConfiguration>
1919
</ItemGroup>
2020
<ItemGroup>
21+
<ClCompile Include="src\Ext\Infantry\Hooks.Firing.cpp" />
2122
<ClCompile Include="src\Ext\Unit\Hooks.Firing.cpp" />
2223
<ClCompile Include="src\New\Type\Affiliated\CreateUnitTypeClass.cpp" />
2324
<ClCompile Include="src\Blowfish\blowfish.cpp" />

docs/Fixed-or-Improved-Logics.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ This page describes all ingame logics that are fixed or improved in Phobos witho
229229
- Fixed an issue where airstrike flare line drawn to target at lower elevation would clip.
230230
- Fixed the bug that uncontrolled scatter when elite techno attacked by aircraft or some unit try crush it.
231231
- Second weapon with `ElectricAssault=yes` will not unconditionally attack your building with `Overpowerable=yes`.
232+
- Infantry support `IsGattling=yes`.
232233

233234
## Fixes / interactions with other extensions
234235

docs/Whats-New.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,7 @@ New:
389389
- [Customize whether `Crater=yes` animation would destroy tiberium](Fixed-or-Improved-Logics.md#customize-whether-crater-yes-animation-would-destroy-tiberium) (by TaranDahl)
390390
- Weapon target filtering by health percentage (by NetsuNegi)
391391
- [Turretless vehicles with `Voxel=no` support use `FireUp` like infantry](New-or-Enhanced-Logics.md#turretless-shape-vehicle-fireup) (by FlyStar)
392+
- Infantry support `IsGattling=yes` (by FlyStar)
392393
393394
Vanilla fixes:
394395
- Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya)

src/Ext/Building/Hooks.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ DEFINE_HOOK(0x43FE69, BuildingClass_AI, 0xA)
2121
auto const pExt = BuildingExt::ExtMap.Find(pThis);
2222
pExt->DisplayIncomeString();
2323
pExt->ApplyPoweredKillSpawns();
24-
pExt->TechnoExtData->UpdateGattlingRateDownReset();
2524

2625
return 0;
2726
}

src/Ext/Infantry/Hooks.Firing.cpp

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
#include <Helpers/Macro.h>
2+
#include <InfantryClass.h>
3+
4+
#include <Ext/Techno/Body.h>
5+
#include <Ext/WeaponType/Body.h>
6+
7+
namespace FiringAITemp
8+
{
9+
bool canFire;
10+
int weaponIndex;
11+
bool isSecondary;
12+
WeaponTypeClass* WeaponType;
13+
FireError fireError;
14+
}
15+
16+
DEFINE_HOOK(0x520AD2, InfantryClass_FiringAI_NoTarget, 0x7)
17+
{
18+
GET(InfantryClass*, pThis, EBP);
19+
20+
if (pThis->Type->IsGattling)
21+
pThis->GattlingRateDown(1);
22+
23+
FiringAITemp::canFire = false;
24+
return 0;
25+
}
26+
27+
DEFINE_HOOK(0x5206D2, InfantryClass_FiringAI_SetContext, 0x6)
28+
{
29+
GET(InfantryClass*, pThis, EBP);
30+
GET(int, WeaponIndex, EDI);
31+
enum { SkipGameCode = 0x5209A6 };
32+
33+
auto const pWeapon = pThis->GetWeapon(WeaponIndex)->WeaponType;
34+
35+
if (!pWeapon)
36+
{
37+
if (pThis->Type->IsGattling)
38+
pThis->GattlingRateDown(1);
39+
40+
R->AL(false);
41+
FiringAITemp::canFire = false;
42+
return SkipGameCode;
43+
}
44+
45+
const auto pTarget = pThis->Target;
46+
FiringAITemp::weaponIndex = WeaponIndex;
47+
FiringAITemp::isSecondary = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType())->IsSecondary(WeaponIndex);
48+
FiringAITemp::WeaponType = pWeapon;
49+
FiringAITemp::fireError = pThis->GetFireError(pTarget, WeaponIndex, true);
50+
FiringAITemp::canFire = true;
51+
52+
return 0;
53+
}
54+
55+
// avoid repeatedly calling GetFireError().
56+
DEFINE_HOOK_AGAIN(0x5209D2, InfantryClass_FiringAI_SetFireError, 0x6)
57+
DEFINE_HOOK(0x5206E4, InfantryClass_FiringAI_SetFireError, 0x6)
58+
{
59+
R->EAX(FiringAITemp::fireError);
60+
return R->Origin() == 0x5206E4 ? 0x5206F9 : 0x5209E4;
61+
}
62+
63+
// determine if it is the second.
64+
DEFINE_HOOK_AGAIN(0x520968, InfantryClass_UpdateFiring_IsSecondary, 0x6)
65+
DEFINE_HOOK(0x520888, InfantryClass_UpdateFiring_IsSecondary, 0x8)
66+
{
67+
GET(InfantryClass*, pThis, EBP);
68+
const bool isSecondary = FiringAITemp::isSecondary;
69+
70+
if (R->Origin() == 0x520888)
71+
{
72+
R->AL(pThis->Crawling);
73+
return isSecondary ? 0x520890 : 0x5208DC;
74+
}
75+
else if (isSecondary)
76+
{
77+
return pThis->Crawling ? 0x520970 : 0x52098A;
78+
}
79+
else
80+
{
81+
return 0x5209A0;
82+
}
83+
}
84+
85+
DEFINE_HOOK(0x5209AF, InfantryClass_FiringAI_BurstDelays, 0x6)
86+
{
87+
GET(InfantryClass*, pThis, EBP);
88+
GET(int, firingFrame, EDX);
89+
enum { Continue = 0x5209CD, ReturnFromFunction = 0x520AD9 };
90+
91+
int cumulativeDelay = 0;
92+
int projectedDelay = 0;
93+
const int weaponIndex = FiringAITemp::weaponIndex;
94+
const auto pWeaponExt = WeaponTypeExt::ExtMap.Find(FiringAITemp::WeaponType);
95+
const bool allowBurst = pWeaponExt->Burst_FireWithinSequence;
96+
97+
// Calculate cumulative burst delay as well cumulative delay after next shot (projected delay).
98+
if (allowBurst)
99+
{
100+
for (int i = 0; i <= pThis->CurrentBurstIndex; i++)
101+
{
102+
const int burstDelay = pWeaponExt->GetBurstDelay(i);
103+
int delay = 0;
104+
105+
if (burstDelay > -1)
106+
delay = burstDelay;
107+
else
108+
delay = ScenarioClass::Instance->Random.RandomRanged(3, 5);
109+
110+
// Other than initial delay, treat 0 frame delays as 1 frame delay due to per-frame processing.
111+
if (i != 0)
112+
delay = Math::max(delay, 1);
113+
114+
cumulativeDelay += delay;
115+
116+
if (i == pThis->CurrentBurstIndex)
117+
projectedDelay = cumulativeDelay + delay;
118+
}
119+
}
120+
121+
if (pThis->Animation.Value == firingFrame + cumulativeDelay)
122+
{
123+
if (allowBurst)
124+
{
125+
const int frameCount = pThis->Type->Sequence->GetSequence(pThis->SequenceAnim).CountFrames;
126+
127+
// If projected frame for firing next shot goes beyond the sequence frame count, cease firing after this shot and start rearm timer.
128+
if (firingFrame + projectedDelay > frameCount)
129+
{
130+
auto const pExt = TechnoExt::ExtMap.Find(pThis);
131+
pExt->ForceFullRearmDelay = true;
132+
}
133+
}
134+
135+
R->EAX(weaponIndex); // Reuse the weapon index to save some time.
136+
return Continue;
137+
}
138+
139+
return ReturnFromFunction;
140+
}
141+
142+
DEFINE_HOOK(0x520AD9, InfantryClass_FiringAI_IsGattling, 0x5)
143+
{
144+
GET(InfantryClass*, pThis, EBP);
145+
146+
if (FiringAITemp::canFire)
147+
{
148+
if (pThis->Type->IsGattling)
149+
{
150+
const FireError fireError = FiringAITemp::fireError;
151+
152+
switch (fireError)
153+
{
154+
case FireError::OK:
155+
case FireError::REARM:
156+
case FireError::FACING:
157+
case FireError::ROTATING:
158+
pThis->GattlingRateUp(1);
159+
break;
160+
default:
161+
pThis->GattlingRateDown(1);
162+
break;
163+
}
164+
}
165+
166+
FiringAITemp::canFire = false;
167+
}
168+
169+
return 0;
170+
}

src/Ext/Techno/Body.Update.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1234,11 +1234,17 @@ void TechnoExt::ExtData::UpdateGattlingRateDownReset()
12341234

12351235
if (pTypeExt->RateDown_Reset && (!pThis->Target || this->LastTargetID != pThis->Target->UniqueID))
12361236
{
1237+
int oldStage = pThis->CurrentGattlingStage;
12371238
this->LastTargetID = pThis->Target ? pThis->Target->UniqueID : 0xFFFFFFFF;
12381239
pThis->GattlingValue = 0;
12391240
pThis->CurrentGattlingStage = 0;
12401241
this->AccumulatedGattlingValue = 0;
12411242
this->ShouldUpdateGattlingValue = false;
1243+
1244+
if (oldStage != 0)
1245+
{
1246+
pThis->GattlingRateDown(0);
1247+
}
12421248
}
12431249
}
12441250
}

src/Ext/Techno/Hooks.Firing.cpp

Lines changed: 2 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -229,12 +229,13 @@ DEFINE_HOOK(0x6F3432, TechnoClass_WhatWeaponShouldIUse_Gattling, 0xA)
229229

230230
DEFINE_HOOK(0x5218F3, InfantryClass_WhatWeaponShouldIUse_DeployFireWeapon, 0x6)
231231
{
232+
GET(InfantryClass*, pThis, ESI);
232233
GET(TechnoTypeClass*, pType, ECX);
233234

234235
if (pType->DeployFireWeapon == -1)
235236
return 0x52194E;
236237

237-
return 0;
238+
return pType->IsGattling && !pThis->IsDeployed() ? 0x52194E : 0;
238239
}
239240

240241
#pragma endregion
@@ -836,78 +837,6 @@ DEFINE_HOOK(0x6FB086, TechnoClass_Reload_ReloadAmount, 0x8)
836837
return 0;
837838
}
838839

839-
namespace FiringAITemp
840-
{
841-
int weaponIndex;
842-
}
843-
844-
DEFINE_HOOK(0x5206D2, InfantryClass_FiringAI_SetContext, 0x6)
845-
{
846-
GET(int, weaponIndex, EDI);
847-
848-
FiringAITemp::weaponIndex = weaponIndex;
849-
850-
return 0;
851-
}
852-
853-
DEFINE_HOOK(0x5209AF, InfantryClass_FiringAI_BurstDelays, 0x6)
854-
{
855-
enum { Continue = 0x5209CD, ReturnFromFunction = 0x520AD9 };
856-
857-
GET(InfantryClass*, pThis, EBP);
858-
GET(int, firingFrame, EDX);
859-
860-
int cumulativeDelay = 0;
861-
int projectedDelay = 0;
862-
const int weaponIndex = FiringAITemp::weaponIndex;
863-
auto const pWeapon = pThis->GetWeapon(weaponIndex)->WeaponType;
864-
auto const pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon);
865-
866-
// Calculate cumulative burst delay as well cumulative delay after next shot (projected delay).
867-
if (pWeaponExt && pWeaponExt->Burst_FireWithinSequence)
868-
{
869-
for (int i = 0; i <= pThis->CurrentBurstIndex; i++)
870-
{
871-
const int burstDelay = pWeaponExt->GetBurstDelay(i);
872-
int delay = 0;
873-
874-
if (burstDelay > -1)
875-
delay = burstDelay;
876-
else
877-
delay = ScenarioClass::Instance->Random.RandomRanged(3, 5);
878-
879-
// Other than initial delay, treat 0 frame delays as 1 frame delay due to per-frame processing.
880-
if (i != 0)
881-
delay = Math::max(delay, 1);
882-
883-
cumulativeDelay += delay;
884-
885-
if (i == pThis->CurrentBurstIndex)
886-
projectedDelay = cumulativeDelay + delay;
887-
}
888-
}
889-
890-
if (pThis->Animation.Value == firingFrame + cumulativeDelay)
891-
{
892-
if (pWeaponExt && pWeaponExt->Burst_FireWithinSequence)
893-
{
894-
const int frameCount = pThis->Type->Sequence->GetSequence(pThis->SequenceAnim).CountFrames;
895-
896-
// If projected frame for firing next shot goes beyond the sequence frame count, cease firing after this shot and start rearm timer.
897-
if (firingFrame + projectedDelay > frameCount)
898-
{
899-
auto const pExt = TechnoExt::ExtMap.Find(pThis);
900-
pExt->ForceFullRearmDelay = true;
901-
}
902-
}
903-
904-
R->EAX(weaponIndex); // Reuse the weapon index to save some time.
905-
return Continue;
906-
}
907-
908-
return ReturnFromFunction;
909-
}
910-
911840
// Author: Otamaa
912841
DEFINE_HOOK(0x5223B3, InfantryClass_Approach_Target_DeployFireWeapon, 0x6)
913842
{

0 commit comments

Comments
 (0)