Skip to content

Commit fd9f115

Browse files
authored
Improvements to tank bunker logic (#2266)
- Introduced alternate code paths for odd-sized foundations in relevant places to allow odd-sized tank bunkers f.ex 3x3 ones. - The logic to make vehicles enter center of building on even-sized foundation hinges on function (`Force_Track()`) not implemented on all locomotors and does not behave in visually consistent manner on odd-sized foundations. Conversely the simpler approach with discrete center cells present cannot be used with even-sized foundations. - Non-Drive locomotors should hypothetically work better with the discrete center cell approach but since there's no guarantee of parity I have elected to not lift the hardcoded locomotor restrictions. - Added customization for the logic update delay that can be used to make tank bunkers more responsive on unit attempting to enter. - Documentation with prior changes to tank bunker logic was consolidated under single header since there's not a large amount of changes. ------------------------------------- ### Tank Bunker improvements - `Bunker=true` and associated logic should now work more reliably on odd-sized foundations. It still expects size on both X and Y axis to be equal e.g `2x2` or `3x3` and will not behave correctly otherwise. - It is now possible to change the tank bunker logic update delay by setting `BunkerStateUpdateDelay`. Defaults to `[General]` -> `BunkerStateUpdateDelay`. Lowering the value from the default value of 15 will make units trying to bunker up spend less time waiting. In `rulesmd.ini`: ```ini [General] BunkerStateUpdateDelay=15 ; integer, game frames [SOMEBUILDING] ; BuildingType BunkerStateUpdateDelay= ; integer, game frames ```
1 parent 1eb7032 commit fd9f115

9 files changed

Lines changed: 167 additions & 18 deletions

File tree

CREDITS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ This page lists all the individual contributions to the project by their author.
299299
- Building turret idle/firing/low power animations
300300
- Animation theater/tile palette toggle
301301
- Animatable template
302+
- Tank Bunker improvements
302303
- **Morton (MortonPL)**:
303304
- `XDrawOffset` for animations
304305
- Shield passthrough & absorption

YRpp

docs/Fixed-or-Improved-Logics.md

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,6 +1257,28 @@ IsAnimDelayedBurst=true ; boolean
12571257
The prism towers' fire is hardcoded to be delayed. Their fire will ignore this flag, just as they ignore `IsAnimDelayedFire`.
12581258
```
12591259

1260+
### Tank Bunker improvements
1261+
1262+
- `Bunker=true` and associated logic should now work more reliably on odd-sized foundations. It still expects size on both X and Y axis to be equal e.g `2x2` or `3x3` and will not behave correctly otherwise.
1263+
- It is now possible to change the tank bunker logic update delay by setting `BunkerStateUpdateDelay`. Defaults to `[General]` -> `BunkerStateUpdateDelay`. Lowering the value from the default value of 15 will make units trying to bunker up spend less time waiting.
1264+
- Setting `BunkerableAnyway=true` on a vehicle will bypass several otherwise hardcoded checks that would otherwise prevent it from being bunkerable - namely that it needs to have a turret, weapon and `SpeedType` other than `Hover`. It will still require `Bunkerable=true` and the vehicle to not be parasitized to allow bunkering.
1265+
1266+
In `rulesmd.ini`:
1267+
```ini
1268+
[General]
1269+
BunkerStateUpdateDelay=15 ; integer, game frames
1270+
1271+
[SOMEBUILDING] ; BuildingType
1272+
BunkerStateUpdateDelay= ; integer, game frames
1273+
1274+
[SOMEVEHICLE] ; VehicleType
1275+
BunkerableAnyway=false ; boolean
1276+
```
1277+
1278+
```{warning}
1279+
Skipping the bunkerable checks doesn't mean that vehicles and tank bunkers will interact correctly - actual bunkerability is mainly determined by `Locomotor`. Details about locomotors' bunkerability can be found on [ModEnc](https://modenc.renegadeprojects.com/Bunkerable).
1280+
```
1281+
12601282
### Unit repair customization
12611283

12621284
- It is now possible to customize the repairing of units by `UnitRepair=true`, `UnitReload=true` and `Hospital=true` buildings.
@@ -2260,22 +2282,6 @@ Harvester.CanGuardArea=false ; boolean
22602282
Harvester.CanGuardArea.RequireTarget=false ; boolean
22612283
```
22622284

2263-
### Bunker entering check dehardcode
2264-
2265-
- In vanilla, vehicles entering tank bunkers are subject to a series of hardcoding restrictions, including having to have turrets, having to have weapons, and not having Hover speed types. Now you can skip these restrictions.
2266-
- This needs to be used with `Bunkerable=yes`.
2267-
- This flag only skips the static check, that is, the check on the unit type. The dynamic check (cannot be parasitized) remains unchanged.
2268-
2269-
In `rulesmd.ini`:
2270-
```ini
2271-
[SOMEVEHICLE] ; VehicleType
2272-
BunkerableAnyway=false ; boolean
2273-
```
2274-
2275-
```{warning}
2276-
Skipping checks with this feature doesn't mean that vehicles and tank bunkers will interact correctly. Following the simple checks performed by the provider of this feature, bunkerability is mainly determined by Locomotor. The details about locomotors' bunkerability can be found on [ModEnc](https://modenc.renegadeprojects.com/Bunkerable).
2277-
```
2278-
22792285
### Custom Unit Crate Reroll Chance
22802286

22812287
- It is possible to influence weighting of units given from crates (`CrateGoodie=true`) via `CrateGoodie.RerollChance`, which determines the chance that if this type of unit is rolled, it will reroll again for another type of unit.

docs/Whats-New.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ HideShakeEffects=false ; boolean
397397
- [Raise alert when technos are taking damage](New-or-Enhanced-Logics.md#raise-alert-when-technos-are-taking-damage) (by TaranDahl)
398398
- [Enhanced Bombard trajectory](New-or-Enhanced-Logics.md#bombard-trajectory) (by CrimRecya & Ollerus, based on knowledge of NaotoYuuki)
399399
- [Toggle waypoint for building](Fixed-or-Improved-Logics.md#waypoints-for-buildings) (by TaranDahl)
400-
- [Bunkerable checks dehardcode](Fixed-or-Improved-Logics.md#bunker-entering-check-dehardcode) (by TaranDahl)
400+
- [Bunkerable checks dehardcode](Fixed-or-Improved-Logics.md#tank-bunker-improvements) (by TaranDahl)
401401
- [No turret unit turn to the target](Fixed-or-Improved-Logics.md#unit-without-turret-always-turn-to-target) (by CrimRecya & TaranDahl)
402402
- [Damage multipliers](New-or-Enhanced-Logics.md#damage-multipliers) (by CrimRecya & Ollerus)
403403
- Customizable duration for electric bolts (by Starkku)
@@ -596,6 +596,7 @@ HideShakeEffects=false ; boolean
596596
- [Allow infantry to perform type conversion when deploying and undeploying](New-or-Enhanced-Logics.md#allow-infantry-to-perform-type-conversion-when-deploying-and-undeploying) (by Noble_Fish)
597597
- Add `ClampToScreen` tag for `BannerType` (defaults to `true`) to control whether banner position is clamped to the visible area (by Chang_zhi)
598598
- [Customizable Berzerk mission](New-or-Enhanced-Logics.md#enhanced-berzerk-behavior) (by TaranDahl)
599+
- [Tank Bunker foundation and state update delay improvements](Fixed-or-Improved-Logics.md#tank-bunker-improvements) (by Starkku)
599600
600601
#### Vanilla fixes:
601602
- 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: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1170,3 +1170,133 @@ DEFINE_HOOK(0x44B6C7, BuildingClass_Mission_Attack_TurretAnim, 0x6)
11701170

11711171
return 0;
11721172
}
1173+
1174+
#pragma endregion
1175+
1176+
#pragma region TankBunker
1177+
1178+
// Jun 23, 2026 - Starkku: Vanilla tank bunker code assumes
1179+
// even-sized foundation. The approach used for even-sized
1180+
// foundations and those that have discrete center cell are
1181+
// mutually exclusive due to pathfinding constraints.
1182+
1183+
// Handle docking offset calculations.
1184+
DEFINE_HOOK(0x447BE3, BuildingClass_DockingCoord_TankBunker, 0x6)
1185+
{
1186+
enum { SkipGameCode = 0x447CE1 };
1187+
1188+
GET(BuildingClass*, pThis, ESI);
1189+
1190+
// Discrete center cell: Docking coord is building center instead of cell closest to approach.
1191+
if (pThis->Type->GetFoundationWidth() % 2)
1192+
{
1193+
const auto coords = pThis->GetCenterCoords();
1194+
1195+
// Cleaner hook return at function return is causing problems
1196+
// due to stack offsets, which is why we're doing it like this.
1197+
R->ECX(coords.X);
1198+
R->EDX(coords.Y);
1199+
R->EAX(&coords);
1200+
1201+
return SkipGameCode;
1202+
}
1203+
1204+
return 0;
1205+
}
1206+
1207+
// Remove now unnecessary (and in fact interfering) rotation code in BuildingClass::UpdateTankBunker().
1208+
DEFINE_HOOK(0x459069, BuildingClass_UpdateTankBunker_CheckOccupants, 0x7)
1209+
{
1210+
enum { SkipGameCode = 0x4590EF };
1211+
1212+
GET(BuildingClass*, pThis, ESI);
1213+
1214+
// Discrete center cell: No need to change facing at this stage.
1215+
if (pThis->Type->GetFoundationWidth() % 2)
1216+
return SkipGameCode;
1217+
1218+
return 0;
1219+
}
1220+
1221+
// Handle force moving unit to center of bunker.
1222+
DEFINE_HOOK(0x459101, BuildingClass_UpdateTankBunker_RotateToTrack, 0x6)
1223+
{
1224+
enum { ReturnFromFunction = 0x4591CE };
1225+
1226+
GET(BuildingClass*, pThis, ESI);
1227+
GET(UnitClass*, pUnit, EBP);
1228+
1229+
// Discrete center cell: Skip straight to rotating in bunker once finished moving instead of forcing track to center.
1230+
if (pThis->Type->GetFoundationWidth() % 2)
1231+
{
1232+
if (!pUnit->Locomotor->Is_Moving())
1233+
{
1234+
pUnit->PrimaryFacing.SetDesired(DirStruct(DirType::South));
1235+
pThis->TankBunkerState = TankBunkerState::RotateInBunker;
1236+
}
1237+
1238+
return ReturnFromFunction;
1239+
}
1240+
1241+
return 0;
1242+
}
1243+
1244+
inline void EjectBunkeredUnit(BuildingClass* pThis, UnitClass* pUnit)
1245+
{
1246+
auto const pType = pUnit->Type;
1247+
auto const cell = pThis->GetMapCoords() + CellStruct(-1, 1);
1248+
auto const pNearbyCell = MapClass::Instance.NearByLocation(cell, pType->SpeedType, -1, pType->MovementZone, false, 1, 1, false, false, false, true, cell, false, false);
1249+
pUnit->SetDestination(MapClass::Instance.GetCellAt(pNearbyCell), false);
1250+
pUnit->QueueMission(Mission::Move, false);
1251+
}
1252+
1253+
// Bunker was destroyed, sold or warped away.
1254+
DEFINE_HOOK(0x4593C7, BuildingClass_DestroyTankBunker, 0x6)
1255+
{
1256+
enum { SkipGameCode = 0x459450 };
1257+
1258+
GET(BuildingClass*, pThis, EDI);
1259+
GET(UnitClass*, pUnit, ESI);
1260+
1261+
// Discrete center cell: Send unit out to a nearby cell without forcing drive track.
1262+
if (pThis->Type->GetFoundationWidth() % 2)
1263+
{
1264+
EjectBunkeredUnit(pThis, pUnit);
1265+
return SkipGameCode;
1266+
}
1267+
1268+
return 0;
1269+
}
1270+
1271+
// Manual unload of the bunker.
1272+
DEFINE_HOOK(0x4596EC, BuildingClass_UnloadTankBunker, 0x6)
1273+
{
1274+
enum { SkipGameCode = 0x45980D };
1275+
1276+
GET(BuildingClass*, pThis, EDI);
1277+
GET(UnitClass*, pUnit, ESI);
1278+
1279+
// Discrete center cell: Send unit out to a nearby cell without forcing drive track.
1280+
if (pThis->Type->GetFoundationWidth() % 2)
1281+
{
1282+
EjectBunkeredUnit(pThis, pUnit);
1283+
return SkipGameCode;
1284+
}
1285+
1286+
return 0;
1287+
}
1288+
1289+
// Add customization for tank bunker logic update delay.
1290+
DEFINE_HOOK(0x44C976, BuildingClass_Mission_Repair_TankBunker, 0x5)
1291+
{
1292+
GET(BuildingClass*, pThis, EBP);
1293+
1294+
auto const pType = pThis->Type;
1295+
1296+
if (pType->Bunker && (pThis->TankBunkerState > TankBunkerState::Idle && pThis->TankBunkerState < TankBunkerState::Bunkered))
1297+
R->EAX(BuildingTypeExt::ExtMap.Find(pType)->BunkerStateUpdateDelay.Get(RulesExt::Global()->BunkerStateUpdateDelay));
1298+
1299+
return 0;
1300+
}
1301+
1302+
#pragma endregion

src/Ext/BuildingType/Body.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ void BuildingTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
228228
this->BuildingBunkerROFMult.Read(exINI, pSection, "BunkerROFMultMultiplier");
229229
this->BunkerWallsUpSound.Read(exINI, pSection, "BunkerWallsUpSound");
230230
this->BunkerWallsDownSound.Read(exINI, pSection, "BunkerWallsDownSound");
231+
this->BunkerStateUpdateDelay.Read(exINI, pSection, "BunkerStateUpdateDelay");
231232
this->BuildingRepairedSound.Read(exINI, pSection, "BuildingRepairedSound");
232233
this->Refinery_UseStorage.Read(exINI, pSection, "Refinery.UseStorage");
233234
this->UndeploysInto_Sellable.Read(exINI, pSection, "UndeploysInto.Sellable");
@@ -404,6 +405,7 @@ void BuildingTypeExt::ExtData::Serialize(T& Stm)
404405
.Process(this->BuildingBunkerROFMult)
405406
.Process(this->BunkerWallsUpSound)
406407
.Process(this->BunkerWallsDownSound)
408+
.Process(this->BunkerStateUpdateDelay)
407409
.Process(this->BuildingRepairedSound)
408410
.Process(this->Refinery_UseNormalActiveAnim)
409411
.Process(this->HasPowerUpAnim)

src/Ext/BuildingType/Body.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ class BuildingTypeExt
9494
Nullable<float> BuildingBunkerROFMult;
9595
NullableIdx<VocClass> BunkerWallsUpSound;
9696
NullableIdx<VocClass> BunkerWallsDownSound;
97+
Nullable<int> BunkerStateUpdateDelay;
9798

9899
NullableIdx<VocClass> BuildingRepairedSound;
99100

@@ -189,6 +190,7 @@ class BuildingTypeExt
189190
, BuildingBunkerROFMult {}
190191
, BunkerWallsUpSound {}
191192
, BunkerWallsDownSound {}
193+
, BunkerStateUpdateDelay {}
192194
, BuildingRepairedSound {}
193195
, Refinery_UseNormalActiveAnim { false }
194196
, HasPowerUpAnim {}

src/Ext/Rules/Body.cpp

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

435435
this->TeamDelays_DynamicType.Read(exINI, GameStrings::General, "TeamDelays.DynamicType");
436436

437+
this->BunkerStateUpdateDelay.Read(exINI, GameStrings::General, "BunkerStateUpdateDelay");
438+
437439
char tempBuffer[40];
438440
for (size_t i = 0; i < 8; i++)
439441
{
@@ -787,6 +789,7 @@ void RulesExt::ExtData::Serialize(T& Stm)
787789
.Process(this->TeamDelays_DynamicType)
788790
.Process(this->TeamDelays_Count)
789791
.Process(this->BerzerkMission)
792+
.Process(this->BunkerStateUpdateDelay)
790793
;
791794
}
792795

src/Ext/Rules/Body.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,8 @@ class RulesExt
374374

375375
Valueable<Mission> BerzerkMission;
376376

377+
Valueable<int> BunkerStateUpdateDelay;
378+
377379
ExtData(RulesClass* OwnerObject) : Extension<RulesClass>(OwnerObject)
378380
, Storage_TiberiumIndex { -1 }
379381
, HarvesterDumpAmount { 0.0f }
@@ -685,6 +687,8 @@ class RulesExt
685687
, TeamDelays_DynamicType { DynamicTeamDelayType::StartingPoint }
686688
, TeamDelays_Count {}
687689
, BerzerkMission { Mission::Hunt }
690+
691+
, BunkerStateUpdateDelay { 15 }
688692
{ }
689693

690694
virtual ~ExtData() = default;

0 commit comments

Comments
 (0)