Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ This page lists all the individual contributions to the project by their author.
- Wall overlay unit sell exploit fix
- Fix vehicles disguised as trees incorrectly displaying veterancy insignia when they shouldn't
- GapGen + SpySat desync fix
- Building turret idle/firing/low power animations
- **Morton (MortonPL)**:
- `XDrawOffset` for animations
- Shield passthrough & absorption
Expand Down
18 changes: 18 additions & 0 deletions docs/Fixed-or-Improved-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,24 @@ In `rulesmd.ini`:
ConsideredVehicle= ; boolean
```

### Building turret animations

- By default building `TurretAnim(Damaged)` with `TurretAnimIsVoxel=false` only displays one frame per each of the 32 facings. This can now be increased and there are additional animations available for low power state and firing weapons.
- 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.
- 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.
- `TurretAnim.IdleRate` and `TurretAnim.FiringRate` can be used to customize animation frame playback rate for idle and firing frames respectively.

In `rulesmd.ini`:
```ini
[SOMEBUILDING] ; BuildingType
TurretAnim.IdleFrames=1 ; integer
TurretAnim.LowPowerIdleFrames=0 ; integer
TurretAnim.FiringFrames=0 ; integer
TurretAnim.LowPowerFiringFrames=0 ; integer
TurretAnim.IdleRate=1 ; integer, game frames
TurretAnim.FiringRate=1 ; integer, game frames
```

### Custom exit cell for infantry factory

- 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.
Expand Down
1 change: 1 addition & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,7 @@ New:
- [Technos with Walk locomotor spawn wake like ship](Fixed-or-Improved-Logics.md#customizable-wake-anim) (by TaranDahl)
- [Hotkey for deselect object from current selection](User-Interface.md#deselect-object) (by FrozenFog)
- [Updateable firing anim](Fixed-or-Improved-Logics.md#updateable-firing-anim) (by TaranDahl)
- [Building turret idle/firing/low power animations](Fixed-or-Improved-Logics.md#building-turret-animations) (by Starkku)
Vanilla fixes:
- Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya)
Expand Down
92 changes: 91 additions & 1 deletion src/Ext/Building/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,93 @@ void BuildingExt::KickOutClone(std::pair<TechnoTypeClass*, HouseClass*>& info, v
pClone->UnInit();
}

int BuildingExt::GetTurretFrame(BuildingClass* pThis)
{
auto const pExt = BuildingExt::ExtMap.Find(pThis);
auto const pTypeExt = pExt->TypeExtData;
int facing = pThis->PrimaryFacing.Current().GetValue<5>();
int shapeFacing = ObjectClass::BodyShape[facing];

bool isLowPower = !pThis->StuffEnabled || !pThis->IsPowerOnline();
bool isFiring = pExt->TurretAnimFiringFrame != -1;

int idleBlockSize = 32 * pTypeExt->TurretAnim_IdleFrames;
int lowPowerIdleBlockSize = 32 * pTypeExt->TurretAnim_LowPowerIdleFrames;
int firingBlockSize = 32 * pTypeExt->TurretAnim_FiringFrames;
int offsetIdle = 0;
int offsetLowPowerIdle = offsetIdle + idleBlockSize;
int offsetFiring = offsetLowPowerIdle + lowPowerIdleBlockSize;
int offsetLowPowerFiring = offsetFiring + firingBlockSize;

int framesPerFacing = pTypeExt->TurretAnim_IdleFrames;
int baseOffset = offsetIdle;
bool hasFiringFrames = false;

if (isLowPower)
{
if (isFiring && pTypeExt->TurretAnim_LowPowerFiringFrames > 0)
{
framesPerFacing = pTypeExt->TurretAnim_LowPowerFiringFrames;
baseOffset = offsetLowPowerFiring;
hasFiringFrames = true;
}
else if (pTypeExt->TurretAnim_LowPowerIdleFrames > 0)
{
framesPerFacing = pTypeExt->TurretAnim_LowPowerIdleFrames;
baseOffset = offsetLowPowerIdle;
}
}
else
{
if (isFiring && pTypeExt->TurretAnim_FiringFrames > 0)
{
framesPerFacing = pTypeExt->TurretAnim_FiringFrames;
baseOffset = offsetFiring;
hasFiringFrames = true;
}
}

int animFrame = 0;

if (isFiring && hasFiringFrames)
{
animFrame = pExt->TurretAnimFiringFrame;
pExt->TurretAnimRateTick++;

if (pExt->TurretAnimRateTick >= pTypeExt->TurretAnim_FiringRate)
{
pExt->TurretAnimRateTick = 0;
pExt->TurretAnimFiringFrame++;
}

if (pExt->TurretAnimFiringFrame >= framesPerFacing)
{
pExt->TurretAnimFiringFrame = -1;
pExt->TurretAnimIdleFrame = 0; // Reset idle anim frame.
pExt->TurretAnimRateTick = 0;
}
}
else if (framesPerFacing > 1)
{
animFrame = pExt->TurretAnimIdleFrame;
pExt->TurretAnimRateTick++;

if (pExt->TurretAnimRateTick >= pTypeExt->TurretAnim_IdleRate)
{
pExt->TurretAnimRateTick = 0;
pExt->TurretAnimIdleFrame++;
}

if (pExt->TurretAnimIdleFrame >= framesPerFacing)
{
pExt->TurretAnimIdleFrame = 0;
pExt->TurretAnimRateTick = 0;
}
}

return baseOffset + (shapeFacing * framesPerFacing) + animFrame;
}

// =============================
// load / save

Expand All @@ -474,6 +561,9 @@ void BuildingExt::ExtData::Serialize(T& Stm)
.Process(this->CurrentLaserWeaponIndex)
.Process(this->PoweredUpToLevel)
.Process(this->CurrentEMPulseSW)
.Process(this->TurretAnimIdleFrame)
.Process(this->TurretAnimFiringFrame)
.Process(this->TurretAnimRateTick)
//.Process(this->IsFiringNow) It is set and reset within a same function.
;
}
Expand Down Expand Up @@ -505,7 +595,7 @@ bool BuildingExt::SaveGlobals(PhobosStreamWriter& Stm)
// =============================
// container

BuildingExt::ExtContainer::ExtContainer() : Container("BuildingClass") { }
BuildingExt::ExtContainer::ExtContainer() : Container("BuildingClass") {}

BuildingExt::ExtContainer::~ExtContainer() = default;

Expand Down
7 changes: 7 additions & 0 deletions src/Ext/Building/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ class BuildingExt
int PoweredUpToLevel; // Distinct from UpgradeLevel, and set to highest PowersUpToLevel out of applied upgrades regardless of how many are currently applied to this building.
SuperClass* CurrentEMPulseSW;
bool IsFiringNow;
int TurretAnimIdleFrame;
int TurretAnimFiringFrame;
int TurretAnimRateTick;

ExtData(BuildingClass* OwnerObject) : Extension<BuildingClass>(OwnerObject)
, TypeExtData { nullptr }
Expand All @@ -42,6 +45,9 @@ class BuildingExt
, PoweredUpToLevel { 0 }
, CurrentEMPulseSW {}
, IsFiringNow { false }
, TurretAnimIdleFrame { 0 }
, TurretAnimFiringFrame { -1 }
, TurretAnimRateTick { 0 }
{ }

void DisplayIncomeString();
Expand Down Expand Up @@ -102,4 +108,5 @@ class BuildingExt
static const std::vector<CellStruct> GetFoundationCells(BuildingClass* pThis, CellStruct baseCoords, bool includeOccupyHeight = false);
static WeaponStruct* GetLaserWeapon(BuildingClass* pThis);
static void __fastcall KickOutClone(std::pair<TechnoTypeClass*, HouseClass*>& info, void*, BuildingClass* pFactory);
static int GetTurretFrame(BuildingClass* pThis);
};
45 changes: 45 additions & 0 deletions src/Ext/Building/Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1087,3 +1087,48 @@
R->EAX(pThis->TechnoClass::GetTurretWeapon());
return ApplyTurretWeapon;
}

#pragma region TurretAnim

DEFINE_HOOK(0x451242, BuildingClass_AnimationAI_TurretAnim, 0xA)
{
enum { SkipGameCode = 0x451296 };

GET(BuildingClass*, pThis, ESI);

if (auto const pAnim = pThis->Anims[(int)BuildingAnimSlot::Turret])
{
pAnim->Animation.Value = BuildingExt::GetTurretFrame(pThis);
pAnim->Animation.Step = 0;
}

return SkipGameCode;
}

DEFINE_HOOK(0x44B6C7, BuildingClass_Mission_Attack_TurretAnim, 0x6)
{
enum { SkipFiring = 0x44B6FE };

GET(BuildingClass*, pThis, ESI);

if (pThis->HasTurret())
{
if (auto const pAnim = pThis->Anims[(int)BuildingAnimSlot::Turret])
{
auto const pExt = BuildingExt::ExtMap.Find(pThis);
auto const pTypeExt = pExt->TypeExtData;
bool isLowPower = !pThis->StuffEnabled || !pThis->IsPowerOnline();
bool firingFrames = isLowPower ? pTypeExt->TurretAnim_LowPowerFiringFrames : pTypeExt->TurretAnim_FiringFrames;

if (firingFrames > 0 && pExt->TurretAnimFiringFrame == -1)

Check warning on line 1123 in src/Ext/Building/Hooks.cpp

View workflow job for this annotation

GitHub Actions / build

'>': unsafe use of type 'bool' in operation [D:\a\Phobos\Phobos\Phobos.vcxproj]
{
pExt->TurretAnimFiringFrame = 0;
pExt->TurretAnimRateTick = 0;
}
}
}

return 0;
}

#pragma endregion
15 changes: 15 additions & 0 deletions src/Ext/BuildingType/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ int BuildingTypeExt::GetUpgradesAmount(BuildingTypeClass* pBuilding, HouseClass*
return isUpgrade ? result : -1;
}


void BuildingTypeExt::ExtData::Initialize()
{ }

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

// Existing TurretAnim characteristics are read from rules so following the pattern here.
this->TurretAnim_IdleFrames.Read(exINI, pSection, "TurretAnim.IdleFrames");
this->TurretAnim_LowPowerIdleFrames.Read(exINI, pSection, "TurretAnim.LowPowerIdleFrames");
this->TurretAnim_FiringFrames.Read(exINI, pSection, "TurretAnim.FiringFrames");
this->TurretAnim_LowPowerFiringFrames.Read(exINI, pSection, "TurretAnim.LowPowerFiringFrames");
this->TurretAnim_IdleRate.Read(exINI, pSection, "TurretAnim.IdleRate");
this->TurretAnim_FiringRate.Read(exINI, pSection, "TurretAnim.FiringRate");

if (pThis->NumberOfDocks > 0)
{
std::optional<DirType> empty;
Expand Down Expand Up @@ -378,6 +387,12 @@ void BuildingTypeExt::ExtData::Serialize(T& Stm)
.Process(this->HasPowerUpAnim)
.Process(this->UndeploysInto_Sellable)
.Process(this->BuildingRadioLink_SyncOwner)
.Process(this->TurretAnim_IdleFrames)
.Process(this->TurretAnim_LowPowerIdleFrames)
.Process(this->TurretAnim_FiringFrames)
.Process(this->TurretAnim_LowPowerFiringFrames)
.Process(this->TurretAnim_IdleRate)
.Process(this->TurretAnim_FiringFrames)

// Ares 0.2
.Process(this->CloningFacility)
Expand Down
13 changes: 13 additions & 0 deletions src/Ext/BuildingType/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ class BuildingTypeExt

Nullable<bool> BuildingRadioLink_SyncOwner;

Valueable<int> TurretAnim_IdleFrames;
Valueable<int> TurretAnim_LowPowerIdleFrames;
Valueable<int> TurretAnim_FiringFrames;
Valueable<int> TurretAnim_LowPowerFiringFrames;
Valueable<int> TurretAnim_IdleRate;
Valueable<int> TurretAnim_FiringRate;

// Ares 0.2
Valueable<bool> CloningFacility;

Expand Down Expand Up @@ -183,6 +190,12 @@ class BuildingTypeExt
, HasPowerUpAnim {}
, UndeploysInto_Sellable { false }
, BuildingRadioLink_SyncOwner {}
, TurretAnim_IdleFrames { 1 }
, TurretAnim_LowPowerIdleFrames { 0 }
, TurretAnim_FiringFrames { 0 }
, TurretAnim_LowPowerFiringFrames { 0 }
, TurretAnim_IdleRate { 1 }
, TurretAnim_FiringRate { 1 }

// Ares 0.2
, CloningFacility { false }
Expand Down
Loading