Skip to content

Commit 920a086

Browse files
committed
Building turret animations
1 parent bef2e13 commit 920a086

8 files changed

Lines changed: 154 additions & 0 deletions

File tree

CREDITS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ This page lists all the individual contributions to the project by their author.
291291
- Wall overlay unit sell exploit fix
292292
- Fix vehicles disguised as trees incorrectly displaying veterancy insignia when they shouldn't
293293
- GapGen + SpySat desync fix
294+
- Building turret idle/firing/low power animations
294295
- **Morton (MortonPL)**:
295296
- `XDrawOffset` for animations
296297
- Shield passthrough & absorption

docs/Fixed-or-Improved-Logics.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -999,6 +999,21 @@ In `rulesmd.ini`:
999999
ConsideredVehicle= ; boolean
10001000
```
10011001

1002+
### Building turret animations
1003+
1004+
- By default building `TurretAnim(Damaged)` with `TurretAnimIsVoxel=false` only displays one frame per each of the 32 facings. This can now be increased an there are additional animations available for low power state and firing weapons.
1005+
- The frames in the .shp file should be in the order: `IdleFrames`, `LowPowerIdleFrames`, `FiringFrames`, `LowPowerFiringFrames`, animations with frame count set to 0 will be skipped / ignored.
1006+
- Note that `FiringFrames` starts playing when attacking and weapon can fire, it will not stop firing of weapon until it has finished playing nor will anything prevent it from looping multiple times if weapon firing is blocked by [delayed firing](New-or-Enhanced-Logics.md#delayed-firing) for longer than there are frames for. Matching delayed firing duration with firing frame count can be used to make pre-firing animation.
1007+
1008+
In `rulesmd.ini`:
1009+
```ini
1010+
[SOMEBUILDING] ; BuildingType
1011+
TurretAnim.IdleFrames=1 ; integer
1012+
TurretAnim.LowPowerIdleFrames=0 ; integer
1013+
TurretAnim.FiringFrames=0 ; integer
1014+
TurretAnim.LowPowerFiringFrames=0 ; integer
1015+
```
1016+
10021017
### Custom exit cell for infantry factory
10031018

10041019
- By default `Factory=InfantryType` buildings use exit cell for the created infantry based on hardcoded settings if any of `GDIBarracks`, `NODBarracks` or `YuriBarracks` are set to true. It is now possible to define arbitrary exit cell for such building via `BarracksExitCell`. Below is a reference of the cell offsets for the hardcoded values.

docs/Whats-New.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,7 @@ New:
564564
- Allow each side to customize the color when the proportion of working miners is higher than `HarvesterCounter.ConditionYellow` (by Noble_Fish)
565565
- [Allow disable an over-optimization in targeting](Fixed-or-Improved-Logics.md#allow-disable-an-over-optimization-in-targeting) (by TaranDahl)
566566
- [Extra threat](New-or-Enhanced-Logics.md#extra-threat) (by TaranDahl)
567+
- [Building turret idle/firing/low power animations](Fixed-or-Improved-Logics.md#building-turret-animations) (by Starkku)
567568
568569
Vanilla fixes:
569570
- Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya)

src/Ext/Building/Body.cpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,74 @@ void BuildingExt::KickOutClone(std::pair<TechnoTypeClass*, HouseClass*>& info, v
455455
pClone->UnInit();
456456
}
457457

458+
int BuildingExt::GetTurretFrame(BuildingClass* pThis)
459+
{
460+
auto const pExt = BuildingExt::ExtMap.Find(pThis);
461+
auto const pTypeExt = pExt->TypeExtData;
462+
int facing = pThis->PrimaryFacing.Current().GetValue<5>();
463+
int shapeFacing = ObjectClass::BodyShape[facing];
464+
465+
bool isLowPower = !pThis->StuffEnabled || !pThis->IsPowerOnline();
466+
bool isFiring = pExt->TurretAnimFiringFrame != -1;
467+
468+
int idleBlockSize = 32 * pTypeExt->TurretAnim_IdleFrames;
469+
int lowPowerIdleBlockSize = 32 * pTypeExt->TurretAnim_LowPowerIdleFrames;
470+
int firingBlockSize = 32 * pTypeExt->TurretAnim_FiringFrames;
471+
int offsetIdle = 0;
472+
int offsetLowPowerIdle = offsetIdle + idleBlockSize;
473+
int offsetFiring = offsetLowPowerIdle + lowPowerIdleBlockSize;
474+
int offsetLowPowerFiring = offsetFiring + firingBlockSize;
475+
476+
int framesPerFacing = pTypeExt->TurretAnim_IdleFrames;
477+
int baseOffset = offsetIdle;
478+
479+
if (isLowPower)
480+
{
481+
if (isFiring)
482+
{
483+
framesPerFacing = pTypeExt->TurretAnim_LowPowerFiringFrames;
484+
baseOffset = offsetLowPowerFiring;
485+
}
486+
else if (pTypeExt->TurretAnim_LowPowerIdleFrames > 0)
487+
{
488+
framesPerFacing = pTypeExt->TurretAnim_LowPowerIdleFrames;
489+
baseOffset = offsetLowPowerIdle;
490+
}
491+
}
492+
else
493+
{
494+
if (isFiring)
495+
{
496+
framesPerFacing = pTypeExt->TurretAnim_FiringFrames;
497+
baseOffset = offsetFiring;
498+
}
499+
}
500+
501+
int animFrame = 0;
502+
503+
if (framesPerFacing > 1)
504+
{
505+
if (isFiring)
506+
{
507+
animFrame = pExt->TurretAnimFiringFrame;
508+
pExt->TurretAnimFiringFrame++;
509+
510+
if (pExt->TurretAnimFiringFrame >= framesPerFacing)
511+
{
512+
pExt->TurretAnimFiringFrame = -1;
513+
pExt->TurretAnimIdleFrame = 0; // Reset idle anim frame.
514+
}
515+
}
516+
else
517+
{
518+
animFrame = pExt->TurretAnimIdleFrame;
519+
++pExt->TurretAnimIdleFrame %= framesPerFacing;
520+
}
521+
}
522+
523+
return baseOffset + (shapeFacing * framesPerFacing) + animFrame;
524+
}
525+
458526
// =============================
459527
// load / save
460528

@@ -474,6 +542,8 @@ void BuildingExt::ExtData::Serialize(T& Stm)
474542
.Process(this->CurrentLaserWeaponIndex)
475543
.Process(this->PoweredUpToLevel)
476544
.Process(this->CurrentEMPulseSW)
545+
.Process(this->TurretAnimIdleFrame)
546+
.Process(this->TurretAnimFiringFrame)
477547
//.Process(this->IsFiringNow) It is set and reset within a same function.
478548
;
479549
}

src/Ext/Building/Body.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ class BuildingExt
2727
int PoweredUpToLevel; // Distinct from UpgradeLevel, and set to highest PowersUpToLevel out of applied upgrades regardless of how many are currently applied to this building.
2828
SuperClass* CurrentEMPulseSW;
2929
bool IsFiringNow;
30+
int TurretAnimIdleFrame;
31+
int TurretAnimFiringFrame;
3032

3133
ExtData(BuildingClass* OwnerObject) : Extension<BuildingClass>(OwnerObject)
3234
, TypeExtData { nullptr }
@@ -42,6 +44,8 @@ class BuildingExt
4244
, PoweredUpToLevel { 0 }
4345
, CurrentEMPulseSW {}
4446
, IsFiringNow { false }
47+
, TurretAnimIdleFrame { 0 }
48+
, TurretAnimFiringFrame { -1 }
4549
{ }
4650

4751
void DisplayIncomeString();
@@ -102,4 +106,5 @@ class BuildingExt
102106
static const std::vector<CellStruct> GetFoundationCells(BuildingClass* pThis, CellStruct baseCoords, bool includeOccupyHeight = false);
103107
static WeaponStruct* GetLaserWeapon(BuildingClass* pThis);
104108
static void __fastcall KickOutClone(std::pair<TechnoTypeClass*, HouseClass*>& info, void*, BuildingClass* pFactory);
109+
static int GetTurretFrame(BuildingClass* pThis);
105110
};

src/Ext/Building/Hooks.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,3 +1087,45 @@ DEFINE_HOOK(0x45670D, BuildingClass_GetRadialIndicatorRange_Extras, 0x7)
10871087
R->EAX(pThis->TechnoClass::GetTurretWeapon());
10881088
return ApplyTurretWeapon;
10891089
}
1090+
1091+
#pragma region TurretAnim
1092+
1093+
DEFINE_HOOK(0x451242, BuildingClass_AnimationAI_TurretAnim, 0xA)
1094+
{
1095+
enum { SkipGameCode = 0x451296 };
1096+
1097+
GET(BuildingClass*, pThis, ESI);
1098+
1099+
if (auto const pAnim = pThis->Anims[(int)BuildingAnimSlot::Turret])
1100+
{
1101+
pAnim->Animation.Value = BuildingExt::GetTurretFrame(pThis);
1102+
pAnim->Animation.Step = 0;
1103+
}
1104+
1105+
return SkipGameCode;
1106+
}
1107+
1108+
DEFINE_HOOK(0x44B6C7, BuildingClass_Mission_Attack_TurretAnim, 0x6)
1109+
{
1110+
enum { SkipFiring = 0x44B6FE };
1111+
1112+
GET(BuildingClass*, pThis, ESI);
1113+
1114+
if (pThis->HasTurret())
1115+
{
1116+
if (auto const pAnim = pThis->Anims[(int)BuildingAnimSlot::Turret])
1117+
{
1118+
auto const pExt = BuildingExt::ExtMap.Find(pThis);
1119+
auto const pTypeExt = pExt->TypeExtData;
1120+
bool isLowPower = !pThis->StuffEnabled || !pThis->IsPowerOnline();
1121+
bool firingFrames = isLowPower ? pTypeExt->TurretAnim_LowPowerFiringFrames : pTypeExt->TurretAnim_FiringFrames;
1122+
1123+
if (firingFrames > 0 && pExt->TurretAnimFiringFrame == -1)
1124+
pExt->TurretAnimFiringFrame = 0;
1125+
}
1126+
}
1127+
1128+
return 0;
1129+
}
1130+
1131+
#pragma endregion

src/Ext/BuildingType/Body.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ int BuildingTypeExt::GetUpgradesAmount(BuildingTypeClass* pBuilding, HouseClass*
139139
return isUpgrade ? result : -1;
140140
}
141141

142+
142143
void BuildingTypeExt::ExtData::Initialize()
143144
{ }
144145

@@ -223,6 +224,12 @@ void BuildingTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
223224
this->UndeploysInto_Sellable.Read(exINI, pSection, "UndeploysInto.Sellable");
224225
this->BuildingRadioLink_SyncOwner.Read(exINI, pSection, "BuildingRadioLink.SyncOwner");
225226

227+
// Existing TurretAnim characteristics are read from rules so following the pattern here.
228+
this->TurretAnim_IdleFrames.Read(exINI, pSection, "TurretAnim.IdleFrames");
229+
this->TurretAnim_LowPowerIdleFrames.Read(exINI, pSection, "TurretAnim.LowPowerIdleFrames");
230+
this->TurretAnim_FiringFrames.Read(exINI, pSection, "TurretAnim.FiringFrames");
231+
this->TurretAnim_LowPowerFiringFrames.Read(exINI, pSection, "TurretAnim.LowPowerFiringFrames");
232+
226233
if (pThis->NumberOfDocks > 0)
227234
{
228235
std::optional<DirType> empty;
@@ -378,6 +385,10 @@ void BuildingTypeExt::ExtData::Serialize(T& Stm)
378385
.Process(this->HasPowerUpAnim)
379386
.Process(this->UndeploysInto_Sellable)
380387
.Process(this->BuildingRadioLink_SyncOwner)
388+
.Process(this->TurretAnim_IdleFrames)
389+
.Process(this->TurretAnim_LowPowerIdleFrames)
390+
.Process(this->TurretAnim_FiringFrames)
391+
.Process(this->TurretAnim_LowPowerFiringFrames)
381392

382393
// Ares 0.2
383394
.Process(this->CloningFacility)

src/Ext/BuildingType/Body.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ class BuildingTypeExt
104104

105105
Nullable<bool> BuildingRadioLink_SyncOwner;
106106

107+
Valueable<int> TurretAnim_IdleFrames;
108+
Valueable<int> TurretAnim_LowPowerIdleFrames;
109+
Valueable<int> TurretAnim_FiringFrames;
110+
Valueable<int> TurretAnim_LowPowerFiringFrames;
111+
107112
// Ares 0.2
108113
Valueable<bool> CloningFacility;
109114

@@ -183,6 +188,10 @@ class BuildingTypeExt
183188
, HasPowerUpAnim {}
184189
, UndeploysInto_Sellable { false }
185190
, BuildingRadioLink_SyncOwner {}
191+
, TurretAnim_IdleFrames { 1 }
192+
, TurretAnim_LowPowerIdleFrames { 0 }
193+
, TurretAnim_FiringFrames { 0 }
194+
, TurretAnim_LowPowerFiringFrames { 0 }
186195

187196
// Ares 0.2
188197
, CloningFacility { false }

0 commit comments

Comments
 (0)