Skip to content

Commit 0a9bbc3

Browse files
CrimRecyaCoronia
andauthored
[Customized] AI base construction modification (#1470)
- AI can now have some new behaviors. - `AIAutoDeployMCV` controls whether AI will still automatically deploy the mcv after owning a construction yard. - `AISetBaseCenter` controls whether AI will still set the newly deployed construction yard as the base center after owning a construction yard. - `AIBiasSpawnCell` controls whether AI will preferentially select the construction yard close to the birth point as the base center (useless in campaign). - `AIForbidConYard` controls whether AI cannot place buildings with `ConstructionYard=true`. AI will try to build one after a construction yard is destroyed but will not put it down. After that, it will continue to build other buildings. Building a construction yard will still take some time. You can try to reduce the build time of it. - `AINodeWallsOnly` controls whether AI can only automatically connect adjacent walls when there are wall base nodes around. - `AICleanWallNode` controls whether AI cannot place walls when there are no `ProtectWithWall` buildings around. If it cannot be placed, this base node will also be removed. In `rulesmd.ini`: ```ini [AI] AIAutoDeployMCV=true ; boolean AISetBaseCenter=true ; boolean AIBiasSpawnCell=false ; boolean AIForbidConYard=false ; boolean AINodeWallsOnly=false ; boolean AICleanWallNode=false ; boolean ``` --------- Co-authored-by: Coronia <[email protected]>
1 parent f1ec073 commit 0a9bbc3

File tree

7 files changed

+261
-0
lines changed

7 files changed

+261
-0
lines changed

CREDITS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,7 @@ This page lists all the individual contributions to the project by their author.
533533
- Fix an issue that if the garrison unload occupants when there is no open space around it would result in the disappearance of the occupants
534534
- Fix an issue where Ares' `Convert.Deploy` triggers repeatedly when the unit is turning or moving
535535
- Reverse engineer warhead
536+
- AI base construction modification
536537
- **Ollerus**:
537538
- Build limit group enhancement
538539
- Customizable rocker amplitude

Phobos.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@
180180
<ClCompile Include="src\Utilities\Debug.cpp" />
181181
<ClCompile Include="src\Ext\BuildingType\Body.cpp" />
182182
<ClCompile Include="src\Ext\BuildingType\Hooks.cpp" />
183+
<ClCompile Include="src\Ext\BuildingType\Hooks.Placing.cpp" />
183184
<ClCompile Include="src\Ext\WarheadType\Body.cpp" />
184185
<ClCompile Include="src\Ext\WarheadType\Hooks.cpp" />
185186
<ClCompile Include="src\Misc\FlyingStrings.cpp" />

docs/Fixed-or-Improved-Logics.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,27 @@ In `rulesmd.ini`:
703703
BuildingWaypoints=false ; boolean
704704
```
705705

706+
### AI base construction modification
707+
708+
- AI can now have some new behaviors.
709+
- `AIAutoDeployMCV` controls whether AI will still automatically deploy the mcv after owning a construction yard.
710+
- `AISetBaseCenter` controls whether AI will still set the newly deployed construction yard as the base center after owning a construction yard.
711+
- `AIBiasSpawnCell` controls whether AI will preferentially select the construction yard close to the birth point as the base center (useless in campaign).
712+
- `AIForbidConYard` controls whether AI cannot place buildings with `ConstructionYard=true`. AI will try to build one after a construction yard is destroyed but will not put it down. After that, it will continue to build other buildings. Building a construction yard will still take some time. You can try to reduce the build time of it.
713+
- `AINodeWallsOnly` controls whether AI can only automatically connect adjacent walls when there are wall base nodes around.
714+
- `AICleanWallNode` controls whether AI cannot place walls when there are no `ProtectWithWall` buildings around. If it cannot be placed, this base node will also be removed.
715+
716+
In `rulesmd.ini`:
717+
```ini
718+
[AI]
719+
AIAutoDeployMCV=true ; boolean
720+
AISetBaseCenter=true ; boolean
721+
AIBiasSpawnCell=false ; boolean
722+
AIForbidConYard=false ; boolean
723+
AINodeWallsOnly=false ; boolean
724+
AICleanWallNode=false ; boolean
725+
```
726+
706727
## Infantry
707728

708729
### Auto deploy for GI-like infantry

docs/Whats-New.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ New:
430430
- [Auto deploy for GI-like infantry](Fixed-or-Improved-Logics.md#auto-deploy-for-gi-like-infantry) (by TaranDahl)
431431
- When the vehicle loses its target, you can customize whether to align the turret direction with the vehicle body (by FlyStar)
432432
- Reverse engineer warhead (by CrimRecya)
433+
- AI base construction modification (by CrimRecya)
433434
434435
Vanilla fixes:
435436
- Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya)
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
#include "Body.h"
2+
3+
#include <HouseClass.h>
4+
5+
#include "Ext/Rules/Body.h"
6+
7+
// AIConstructionYard Hook #1 -> sub_740810 - Check number of construction yard before deploy.
8+
DEFINE_HOOK(0x740A11, UnitClass_Mission_Guard_AIAutoDeployMCV, 0x6)
9+
{
10+
enum { SkipGameCode = 0x740A50 };
11+
12+
GET(UnitClass*, pMCV, ESI);
13+
14+
return (!RulesExt::Global()->AIAutoDeployMCV && pMCV->Owner->NumConYards > 0) ? SkipGameCode : 0;
15+
}
16+
17+
// AIConstructionYard Hook #2 -> sub_7393C0 - Skip useless base center setting.
18+
DEFINE_HOOK(0x739889, UnitClass_TryToDeploy_AISetBaseCenter, 0x6)
19+
{
20+
enum { SkipGameCode = 0x73992B };
21+
22+
GET(UnitClass*, pMCV, EBP);
23+
24+
return (!RulesExt::Global()->AISetBaseCenter && pMCV->Owner->NumConYards > 1) ? SkipGameCode : 0;
25+
}
26+
27+
// AIConstructionYard Hook #3 -> sub_4FD500 - Update better base center.
28+
DEFINE_HOOK(0x4FD538, HouseClass_AIHouseUpdate_CheckAIBaseCenter, 0x7)
29+
{
30+
if (RulesExt::Global()->AIBiasSpawnCell && !SessionClass::IsCampaign())
31+
{
32+
GET(HouseClass*, pAI, EBX);
33+
34+
if (const auto count = pAI->ConYards.Count)
35+
{
36+
const auto wayPoint = pAI->GetSpawnPosition();
37+
38+
if (wayPoint != -1)
39+
{
40+
const auto center = ScenarioClass::Instance->GetWaypointCoords(wayPoint);
41+
auto newCenter = center;
42+
double distanceSquared = 131072.0;
43+
44+
for (int i = 0; i < count; ++i)
45+
{
46+
if (const auto pBuilding = pAI->ConYards.GetItem(i))
47+
{
48+
if (pBuilding->IsAlive && pBuilding->Health > 0 && !pBuilding->InLimbo)
49+
{
50+
const auto newDistanceSquared = pBuilding->GetMapCoords().DistanceFromSquared(center);
51+
52+
if (newDistanceSquared < distanceSquared)
53+
{
54+
distanceSquared = newDistanceSquared;
55+
newCenter = pBuilding->GetMapCoords();
56+
}
57+
}
58+
}
59+
}
60+
61+
if (newCenter != center)
62+
{
63+
pAI->BaseSpawnCell = newCenter;
64+
pAI->Base.Center = newCenter;
65+
}
66+
}
67+
}
68+
}
69+
70+
return 0;
71+
}
72+
73+
// AIConstructionYard Hook #4-1 -> sub_443C60 - Prohibit AI from building construction yard and clean up invalid walls nodes.
74+
DEFINE_HOOK(0x4451F8, BuildingClass_KickOutUnit_CleanUpAIBuildingSpace, 0x6)
75+
{
76+
enum { CanNotBuild = 0x4454E6, BuildFailed = 0x445696 };
77+
78+
GET(BaseNodeClass* const, pBaseNode, EBX);
79+
GET(BuildingClass* const, pBuilding, EDI);
80+
GET(const CellStruct, topLeftCell, EDX);
81+
82+
const auto pBuildingType = pBuilding->Type;
83+
84+
// Prohibit AI from building construction yard
85+
if (RulesExt::Global()->AIForbidConYard && pBuildingType->ConstructionYard)
86+
{
87+
if (pBaseNode)
88+
{
89+
pBaseNode->Placed = true;
90+
pBaseNode->Attempts = 0;
91+
}
92+
93+
return BuildFailed;
94+
}
95+
96+
// Clean up invalid walls nodes
97+
if (RulesExt::Global()->AICleanWallNode && pBuildingType->Wall)
98+
{
99+
auto notValidWallNode = [topLeftCell]()
100+
{
101+
const auto pCell = MapClass::Instance.GetCellAt(topLeftCell);
102+
103+
for (int i = 0; i < 8; ++i)
104+
{
105+
if (const auto pAdjBuilding = pCell->GetNeighbourCell(static_cast<FacingType>(i))->GetBuilding())
106+
{
107+
if (pAdjBuilding->Type->ProtectWithWall)
108+
return false;
109+
}
110+
}
111+
112+
return true;
113+
};
114+
115+
if (notValidWallNode())
116+
return CanNotBuild;
117+
}
118+
119+
return 0;
120+
}
121+
122+
// AIConstructionYard Hook #4-2 -> sub_42EB50 - Prohibit AI from building construction yard.
123+
DEFINE_HOOK(0x42EB8E, BaseClass_GetBaseNodeIndex_CheckValidBaseNode, 0x6)
124+
{
125+
enum { Valid = 0x42EBC3, Invalid = 0x42EBAE };
126+
127+
GET(BaseClass* const, pBase, ESI);
128+
GET(BaseNodeClass* const, pBaseNode, EAX);
129+
130+
if (RulesExt::Global()->AIForbidConYard && pBaseNode->Placed)
131+
{
132+
const auto index = pBaseNode->BuildingTypeIndex;
133+
134+
if (index >= 0 && index < BuildingTypeClass::Array.Count && BuildingTypeClass::Array.Items[index]->ConstructionYard)
135+
return Invalid;
136+
}
137+
138+
return reinterpret_cast<bool(__thiscall*)(HouseClass*, BaseNodeClass*)>(0x50CAD0)(pBase->Owner, pBaseNode) ? Valid : Invalid;
139+
}
140+
141+
// AIConstructionYard Hook #4-3 -> sub_7393C0 - Prohibit AI from building construction yard.
142+
DEFINE_HOOK(0x7397F4, UnitClass_TryToDeploy_SkipSetShouldRebuild, 0x7)
143+
{
144+
enum { SkipRebuildFlag = 0x7397FB };
145+
146+
GET(BuildingClass* const, pBuilding, EBX);
147+
148+
return (pBuilding->Type->ConstructionYard && RulesExt::Global()->AIForbidConYard) ? SkipRebuildFlag : 0;
149+
}
150+
151+
// AIConstructionYard Hook #4-4 -> sub_440580 - Prohibit AI from building construction yard.
152+
DEFINE_HOOK(0x440B7A, BuildingClass_Unlimbo_SkipSetShouldRebuild, 0x7)
153+
{
154+
enum { SkipRebuildFlag = 0x440B81 };
155+
156+
GET(BuildingClass* const, pBuilding, ESI);
157+
158+
return (pBuilding->Type->ConstructionYard && RulesExt::Global()->AIForbidConYard) ? SkipRebuildFlag : 0;
159+
}
160+
161+
// AIConstructionYard Hook #5-1 -> sub_588570 - Only expand walls on nodes.
162+
DEFINE_HOOK(0x5885D1, MapClass_BuildingToFirestormWall_SkipExtraWalls, 0x6)
163+
{
164+
enum { NextDirection = 0x588730 };
165+
166+
GET_STACK(const HouseClass* const, pHouse, STACK_OFFSET(0x38, 0x8));
167+
GET_STACK(const int, count, STACK_OFFSET(0x38, -0x24));
168+
169+
if (pHouse->IsControlledByHuman() || !RulesExt::Global()->AINodeWallsOnly || count)
170+
return 0;
171+
172+
GET(const CellStruct, cell, EBX);
173+
GET(const BuildingTypeClass* const, pType, EBP);
174+
175+
const auto index = pType->ArrayIndex;
176+
const auto& nodes = pHouse->Base.BaseNodes;
177+
178+
for (const auto& pNode : nodes)
179+
{
180+
if (pNode.MapCoords == cell && pNode.BuildingTypeIndex == index)
181+
return 0;
182+
}
183+
184+
return NextDirection;
185+
}
186+
187+
// AIConstructionYard Hook #5-2 -> sub_588750 - Only expand walls on nodes.
188+
DEFINE_HOOK(0x5887C1, MapClass_BuildingToWall_SkipExtraWalls, 0x6)
189+
{
190+
enum { NextDirection = 0x588935 };
191+
192+
GET_STACK(const HouseClass* const, pHouse, STACK_OFFSET(0x3C, 0x8));
193+
GET_STACK(const int, count, STACK_OFFSET(0x3C, -0x2C));
194+
195+
if (pHouse->IsControlledByHuman() || !RulesExt::Global()->AINodeWallsOnly || count)
196+
return 0;
197+
198+
GET(const CellStruct, cell, EDX);
199+
GET(const BuildingTypeClass* const, pType, EDI);
200+
201+
const auto index = pType->ArrayIndex;
202+
const auto& nodes = pHouse->Base.BaseNodes;
203+
204+
for (const auto& pNode : nodes)
205+
{
206+
if (pNode.MapCoords == cell && pNode.BuildingTypeIndex == index)
207+
return 0;
208+
}
209+
210+
return NextDirection;
211+
}

src/Ext/Rules/Body.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,13 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI)
251251

252252
this->UseFixedVoxelLighting.Read(exINI, GameStrings::AudioVisual, "UseFixedVoxelLighting");
253253

254+
this->AIAutoDeployMCV.Read(exINI, GameStrings::AI, "AIAutoDeployMCV");
255+
this->AISetBaseCenter.Read(exINI, GameStrings::AI, "AISetBaseCenter");
256+
this->AIBiasSpawnCell.Read(exINI, GameStrings::AI, "AIBiasSpawnCell");
257+
this->AIForbidConYard.Read(exINI, GameStrings::AI, "AIForbidConYard");
258+
this->AINodeWallsOnly.Read(exINI, GameStrings::AI, "AINodeWallsOnly");
259+
this->AICleanWallNode.Read(exINI, GameStrings::AI, "AICleanWallNode");
260+
254261
this->AttackMove_Aggressive.Read(exINI, GameStrings::General, "AttackMove.Aggressive");
255262
this->AttackMove_UpdateTarget.Read(exINI, GameStrings::General, "AttackMove.UpdateTarget");
256263

@@ -515,6 +522,12 @@ void RulesExt::ExtData::Serialize(T& Stm)
515522
.Process(this->CombatAlert_UseAttackVoice)
516523
.Process(this->CombatAlert_UseEVA)
517524
.Process(this->UseFixedVoxelLighting)
525+
.Process(this->AIAutoDeployMCV)
526+
.Process(this->AISetBaseCenter)
527+
.Process(this->AIBiasSpawnCell)
528+
.Process(this->AIForbidConYard)
529+
.Process(this->AINodeWallsOnly)
530+
.Process(this->AICleanWallNode)
518531
.Process(this->AttackMove_Aggressive)
519532
.Process(this->AttackMove_UpdateTarget)
520533
.Process(this->MindControl_ThreatDelay)

src/Ext/Rules/Body.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,13 @@ class RulesExt
197197
// Nullable<Vector3D<float>> VoxelShadowLightSource;
198198
Valueable<bool> UseFixedVoxelLighting;
199199

200+
Valueable<bool> AIAutoDeployMCV;
201+
Valueable<bool> AISetBaseCenter;
202+
Valueable<bool> AIBiasSpawnCell;
203+
Valueable<bool> AIForbidConYard;
204+
Valueable<bool> AINodeWallsOnly;
205+
Valueable<bool> AICleanWallNode;
206+
200207
Valueable<bool> AttackMove_Aggressive;
201208
Valueable<bool> AttackMove_UpdateTarget;
202209

@@ -403,6 +410,12 @@ class RulesExt
403410
, CombatAlert_UseAttackVoice { true }
404411
, CombatAlert_UseEVA { true }
405412
, UseFixedVoxelLighting { false }
413+
, AIAutoDeployMCV { true }
414+
, AISetBaseCenter { true }
415+
, AIBiasSpawnCell { false }
416+
, AIForbidConYard { false }
417+
, AINodeWallsOnly { false }
418+
, AICleanWallNode { false }
406419
, AttackMove_Aggressive { false }
407420
, AttackMove_UpdateTarget { false }
408421
, MindControl_ThreatDelay { 0 }

0 commit comments

Comments
 (0)