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 @@ -741,6 +741,7 @@ This page lists all the individual contributions to the project by their author.
- Disable AlphaImage during Buildup
- Allow customizing the default value of `[Warhead] -> PreventScatter` via `[CombatDamage] -> Warhead.PreventScatter`
- Allow `(Pre)ProductionAnim` animations to use `Powered` & `PoweredLight/Effect/Special` keys
- `RoofProductionAnim`
- **Ollerus**:
- Build limit group enhancement
- Customizable rocker amplitude
Expand Down
21 changes: 21 additions & 0 deletions docs/New-or-Enhanced-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,27 @@ PowerPlantEnhancer.Factor=1.0 ; floating point value
PowerPlantEnhancer.MaxCount=-1 ; integer
```

### Roof production anim

- Now, you can use the `RoofProductionAnim*` series of flags to replace the `ProductionAnim*` series of flags when the produced infantry and vehicles are leaving the factory through the roof hatch.
- The criterion for exiting through the roof hatch is the same as `RoofDeployingAnim` in vanilla: the techno has `JumpJet=yes` or `BalloonHover=yes`.

In `artmd.ini`:
```ini
[SOMEBUILDING] ; BuildingType, with Factory=InfantryType or Factory=UnitType
RoofProductionAnim= ; AnimationType, falls back to ProductionAnim if unset
RoofProductionAnimDamaged= ; AnimationType, falls back to RoofProductionAnim if unset
RoofProductionAnimGarrisoned= ; AnimationType, falls back to RoofProductionAnim if unset
RoofProductionAnimX= ; integer, defaults to ProductionAnimX
RoofProductionAnimY= ; integer, defaults to ProductionAnimY
RoofProductionAnimYSort= ; integer, defaults to ProductionAnimYSort
RoofProductionAnimZAdjust= ; integer, defaults to ProductionAnimZAdjust
RoofProductionAnimPowered= ; boolean, defaults to ProductionAnimPowered
RoofProductionAnimPoweredLight= ; boolean, defaults to ProductionAnimPoweredLight
RoofProductionAnimPoweredEffect= ; boolean, defaults to ProductionAnimPoweredEffect
RoofProductionAnimPoweredSpecial= ; boolean, defaults to ProductionAnimPoweredSpecial
```

### Spy effects

- Additional espionage bonuses can be toggled with `SpyEffect.Custom`.
Expand Down
1 change: 1 addition & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@ HideShakeEffects=false ; boolean
:open:

#### New:
- [`RoofProductionAnim`](New-or-Enhanced-Logics.md#roof-production-anim) (by Noble_Fish)

#### Vanilla fixes:

Expand Down
3 changes: 3 additions & 0 deletions docs/locale/zh_CN/LC_MESSAGES/CREDITS.po
Original file line number Diff line number Diff line change
Expand Up @@ -2535,6 +2535,9 @@ msgstr ""
"允许 `(Pre)ProductionAnim` 动画使用 `Powered` 与 `PoweredLight/Effect/Special` "
"类的标签"

msgid "`RoofProductionAnim`"
msgstr "`RoofProductionAnim`"

msgid "**Ollerus**:"
msgstr "**Ollerus**:"

Expand Down
16 changes: 16 additions & 0 deletions docs/locale/zh_CN/LC_MESSAGES/New-or-Enhanced-Logics.po
Original file line number Diff line number Diff line change
Expand Up @@ -1871,6 +1871,22 @@ msgid ""
"means no limitation."
msgstr "`PowerPlantEnhancer.MaxCount` 决定了此类建筑中参与电厂增幅的数量上限。负值代表无上限。"

msgid "Roof production anim"
msgstr "天窗生产动画"

msgid ""
"Now, you can use the `RoofProductionAnim*` series of flags to replace the `ProductionAnim*` series of flags when the produced infantry and vehicles are leaving the factory through the roof hatch."
msgstr ""
"现在,你可以用 `RoofProductionAnim*` 系列语句在所生产的步兵和载具从天窗离开工厂时替代 `ProductionAnim*` 系列语句使用。"

msgid ""
"The criterion for exiting through the roof hatch is the same as "
"`RoofDeployingAnim` in vanilla: the unit has `Jumpjet=yes` or "
"`BalloonHover=yes`."
msgstr ""
"从天窗离开的判定标准与原版中 `RoofDeployingAnim` 相同:单位拥有 `Jumpjet=yes` 或 "
"`BalloonHover=yes`。"

msgid "Spy effects"
msgstr "间谍效果"

Expand Down
7 changes: 7 additions & 0 deletions docs/locale/zh_CN/LC_MESSAGES/Whats-New.po
Original file line number Diff line number Diff line change
Expand Up @@ -2495,6 +2495,13 @@ msgstr ""
"允许 `(Pre)ProductionAnim` 动画使用 `Powered` 与 `PoweredLight/Effect/Special` "
"类的标签(by Noble_Fish)"

msgid ""
"[`RoofProductionAnim`](New-or-Enhanced-Logics.md#roof-production-anim) "
"(by Noble_Fish)"
msgstr ""
"[`RoofProductionAnim`](New-or-Enhanced-Logics.md#roof-production-anim)(by"
" Noble_Fish)"

msgid "Vanilla fixes:"
msgstr "原版问题修复:"

Expand Down
2 changes: 2 additions & 0 deletions src/Ext/Building/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class BuildingExt final : public TechnoExt, public Detach::Listener<BuildingClas
int TurretAnimFiringFrame;
int TurretAnimRateTick;
int ConstructionStartFacing;
bool IsPlayingRoofProductionAnim;

BuildingExt(BuildingClass* OwnerObject) : TechnoExt(OwnerObject)
, DeployedTechno { false }
Expand All @@ -44,6 +45,7 @@ class BuildingExt final : public TechnoExt, public Detach::Listener<BuildingClas
, TurretAnimIdleFrame { 0 }
, TurretAnimFiringFrame { -1 }
, TurretAnimRateTick { 0 }
, IsPlayingRoofProductionAnim { false }
{ }

// typed owner accessor (shadows the TechnoClass one from the base)
Expand Down
135 changes: 129 additions & 6 deletions src/Ext/Building/Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1024,12 +1024,88 @@ static __inline bool AllowBuildingProductionAnim(BuildingTypeClass* pType)
if (pType->ConstructionYard)
return true;

if (pType->Factory == AbstractType::BuildingType && GeneralUtils::IsValidString(pType->BuildingAnim[(int)BuildingAnimSlot::Production].Anim))
if (pType->Factory == AbstractType::BuildingType && GeneralUtils::IsValidString(pType->GetBuildingAnim(BuildingAnimSlot::Production).Anim))
return true;

return false;
}

static bool IsRoofExitTechno(TechnoTypeClass* pType)
{
return pType->JumpJet || pType->BalloonHover;
}

static bool IsRoofExitBuildingUnit(BuildingClass* pBuilding)
{
auto pUnit = pBuilding->GetNthLink();
return pUnit && IsRoofExitTechno(pUnit->GetTechnoType());
}

static AnimTypeClass* PickRoofProductionAnim(BuildingTypeExt* pTypeExt, bool isDamaged, bool garrisoned)
{
if (garrisoned && pTypeExt->RoofProductionAnimGarrisoned.Get() != nullptr)
return pTypeExt->RoofProductionAnimGarrisoned.Get();

if (isDamaged && pTypeExt->RoofProductionAnimDamaged.Get() != nullptr)
return pTypeExt->RoofProductionAnimDamaged.Get();

return pTypeExt->RoofProductionAnim.Get();
}

static void PlayRoofProductionAnim(BuildingClass* pBuilding, BuildingTypeExt* pTypeExt, bool isDamaged, bool garrisoned)
{
const char* animName = nullptr;

if (auto pAnimType = PickRoofProductionAnim(pTypeExt, isDamaged, garrisoned))
{
animName = pAnimType->get_ID();
}
else
{
// Fall back to the plain ProductionAnim only, so the roof line stays independent
// of the ProductionAnimDamaged/Garrisoned branches.
animName = pBuilding->Type->GetBuildingAnim(BuildingAnimSlot::Production).Anim;
}

if (!GeneralUtils::IsValidString(animName))
return;

// The roof anim needs its own placement and power values without affecting
// the plain line, so apply them only for the duration of this PlayAnim
// call.
auto& prodAnim = pBuilding->Type->GetBuildingAnim(BuildingAnimSlot::Production);

const Point2D savedPosition = prodAnim.Position;
const int savedZAdjust = prodAnim.ZAdjust;
const int savedYSort = prodAnim.YSort;
const bool savedPowered = prodAnim.Powered;
const bool savedPoweredLight = prodAnim.PoweredLight;
const bool savedPoweredEffect = prodAnim.PoweredEffect;
const bool savedPoweredSpecial = prodAnim.PoweredSpecial;

prodAnim.Position.X = pTypeExt->RoofProductionAnimX.Get(savedPosition.X);
prodAnim.Position.Y = pTypeExt->RoofProductionAnimY.Get(savedPosition.Y);
prodAnim.ZAdjust = pTypeExt->RoofProductionAnimZAdjust.Get(savedZAdjust);
prodAnim.YSort = pTypeExt->RoofProductionAnimYSort.Get(savedYSort);
prodAnim.Powered = pTypeExt->RoofProductionAnimPowered.Get(savedPowered);
prodAnim.PoweredLight = pTypeExt->RoofProductionAnimPoweredLight.Get(savedPoweredLight);
prodAnim.PoweredEffect = pTypeExt->RoofProductionAnimPoweredEffect.Get(savedPoweredEffect);
prodAnim.PoweredSpecial = pTypeExt->RoofProductionAnimPoweredSpecial.Get(savedPoweredSpecial);

auto pExt = BuildingExt::Fetch(pBuilding);
pExt->IsPlayingRoofProductionAnim = true;
pBuilding->PlayAnim(animName, BuildingAnimSlot::Production, isDamaged, garrisoned, 0);
pExt->IsPlayingRoofProductionAnim = false;

prodAnim.Position = savedPosition;
prodAnim.ZAdjust = savedZAdjust;
prodAnim.YSort = savedYSort;
prodAnim.Powered = savedPowered;
prodAnim.PoweredLight = savedPoweredLight;
prodAnim.PoweredEffect = savedPoweredEffect;
prodAnim.PoweredSpecial = savedPoweredSpecial;
}

DEFINE_HOOK(0x43CC73, BuildingClass_ReceiveMessage_ProductionAnim, 0x6)
{
enum { SkipGameCode = 0x43CC79 };
Expand All @@ -1052,23 +1128,70 @@ DEFINE_HOOK(0x44B7AE, BuildingClass_Mission_Repair_ProductionAnim, 0x6)
return SkipGameCode;
}

// isRoofExit tells whether the produced unit leaves through the roof hatch;
// each caller decides it from its own reliable source.
static bool TryPlayRoofProductionAnim(BuildingClass* pBuilding, bool isDamaged, bool isRoofExit)
{
if (!isRoofExit)
return false;

auto pTypeExt = BuildingTypeExt::Fetch(pBuilding->Type);
const bool garrisoned = pBuilding->GetOccupantCount() > 0;

const auto& prodAnim = pBuilding->Type->GetBuildingAnim(BuildingAnimSlot::Production);
if (BuildingTypeExt::IsPoweredAnimBlocked(pBuilding,
pTypeExt->RoofProductionAnimPowered.Get(prodAnim.Powered),
pTypeExt->RoofProductionAnimPoweredLight.Get(prodAnim.PoweredLight),
pTypeExt->RoofProductionAnimPoweredEffect.Get(prodAnim.PoweredEffect),
pTypeExt->RoofProductionAnimPoweredSpecial.Get(prodAnim.PoweredSpecial)))
return true;

PlayRoofProductionAnim(pBuilding, pTypeExt, isDamaged, garrisoned);
return true;
}

DEFINE_HOOK(0x44DDF0, BuildingClass_Unload_RoofProductionAnim, 0x6)
{
enum { SkipGameCode = 0x44E267 };

GET(BuildingClass*, pBuilding, EBP);

return TryPlayRoofProductionAnim(pBuilding, false, IsRoofExitBuildingUnit(pBuilding)) ? SkipGameCode : 0;
}

DEFINE_HOOK(0x44DDDE, BuildingClass_Unload_RoofProductionAnim_Damaged, 0x6)
{
enum { SkipGameCode = 0x44E267 };

GET(BuildingClass*, pBuilding, EBP);

return TryPlayRoofProductionAnim(pBuilding, true, IsRoofExitBuildingUnit(pBuilding)) ? SkipGameCode : 0;
}

DEFINE_HOOK(0x444D11, BuildingClass_ExitObject_ProductionAnimForInfantryFactory, 0x6)
{
GET(BuildingClass*, pThis, ESI);
// The roof check must use the unit that is actually leaving the factory.
GET(TechnoClass*, pUnit, EDI);

auto const pType = pThis->Type;

if (pType->Factory == AbstractType::InfantryType)
{
bool isDamaged = false;
auto anim = pType->BuildingAnim[(int)BuildingAnimSlot::Production].Anim;
const bool isDamaged = pThis->GetHealthPercentage() <= RulesClass::Instance->ConditionYellow;

if (pThis->GetHealthPercentage() <= RulesClass::Instance->ConditionYellow)
if (pUnit && IsRoofExitTechno(pUnit->GetTechnoType()))
{
isDamaged = true;
anim = pType->BuildingAnim[(int)BuildingAnimSlot::Production].Damaged;
pThis->DestroyNthAnim(BuildingAnimSlot::Idle);
TryPlayRoofProductionAnim(pThis, isDamaged, true);
return 0;
}

auto anim = pType->GetBuildingAnim(BuildingAnimSlot::Production).Anim;

if (isDamaged)
anim = pType->GetBuildingAnim(BuildingAnimSlot::Production).Damaged;

if (GeneralUtils::IsValidString(anim))
{
pThis->DestroyNthAnim(BuildingAnimSlot::Idle);
Expand Down
23 changes: 23 additions & 0 deletions src/Ext/BuildingType/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,18 @@ void BuildingTypeExt::LoadFromINIFile(CCINIClass* const pINI)
prodAnim.PoweredEffect = pArtINI->ReadBool(pArtSection, "ProductionAnimPoweredEffect", prodAnim.PoweredEffect);
prodAnim.PoweredSpecial = pArtINI->ReadBool(pArtSection, "ProductionAnimPoweredSpecial", prodAnim.PoweredSpecial);

this->RoofProductionAnim.Read(exArtINI, pArtSection, "RoofProductionAnim");
this->RoofProductionAnimDamaged.Read(exArtINI, pArtSection, "RoofProductionAnimDamaged");
this->RoofProductionAnimGarrisoned.Read(exArtINI, pArtSection, "RoofProductionAnimGarrisoned");
this->RoofProductionAnimX.Read(exArtINI, pArtSection, "RoofProductionAnimX");
this->RoofProductionAnimY.Read(exArtINI, pArtSection, "RoofProductionAnimY");
this->RoofProductionAnimZAdjust.Read(exArtINI, pArtSection, "RoofProductionAnimZAdjust");
this->RoofProductionAnimYSort.Read(exArtINI, pArtSection, "RoofProductionAnimYSort");
this->RoofProductionAnimPowered.Read(exArtINI, pArtSection, "RoofProductionAnimPowered");
this->RoofProductionAnimPoweredLight.Read(exArtINI, pArtSection, "RoofProductionAnimPoweredLight");
this->RoofProductionAnimPoweredEffect.Read(exArtINI, pArtSection, "RoofProductionAnimPoweredEffect");
this->RoofProductionAnimPoweredSpecial.Read(exArtINI, pArtSection, "RoofProductionAnimPoweredSpecial");

// Ares tag
this->SpyEffect_Custom.Read(exINI, pSection, "SpyEffect.Custom");
if (SuperWeaponTypeClass::Array.Count > 0)
Expand Down Expand Up @@ -469,6 +481,17 @@ void BuildingTypeExt::Serialize(T& Stm)
.Process(this->SetTabBySelecting)
.Process(this->RevealToAll_Radius)
.Process(this->DeployFireDelay)
.Process(this->RoofProductionAnim)
.Process(this->RoofProductionAnimDamaged)
.Process(this->RoofProductionAnimGarrisoned)
.Process(this->RoofProductionAnimX)
.Process(this->RoofProductionAnimY)
.Process(this->RoofProductionAnimZAdjust)
.Process(this->RoofProductionAnimYSort)
.Process(this->RoofProductionAnimPowered)
.Process(this->RoofProductionAnimPoweredLight)
.Process(this->RoofProductionAnimPoweredEffect)
.Process(this->RoofProductionAnimPoweredSpecial)

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

Nullable<int> DeployFireDelay;

Valueable<AnimTypeClass*> RoofProductionAnim;
Valueable<AnimTypeClass*> RoofProductionAnimDamaged;
Valueable<AnimTypeClass*> RoofProductionAnimGarrisoned;
Nullable<int> RoofProductionAnimX;
Nullable<int> RoofProductionAnimY;
Nullable<int> RoofProductionAnimZAdjust;
Nullable<int> RoofProductionAnimYSort;
Nullable<bool> RoofProductionAnimPowered;
Nullable<bool> RoofProductionAnimPoweredLight;
Nullable<bool> RoofProductionAnimPoweredEffect;
Nullable<bool> RoofProductionAnimPoweredSpecial;

// Ares 0.2
Valueable<bool> CloningFacility;

Expand Down Expand Up @@ -226,6 +238,18 @@ class BuildingTypeExt final : public TechnoTypeExt
, RevealToAll_Radius {}
, DeployFireDelay {}

, RoofProductionAnim { nullptr }
, RoofProductionAnimDamaged { nullptr }
, RoofProductionAnimGarrisoned { nullptr }
, RoofProductionAnimX {}
, RoofProductionAnimY {}
, RoofProductionAnimZAdjust {}
, RoofProductionAnimYSort {}
, RoofProductionAnimPowered { }
, RoofProductionAnimPoweredLight { }
, RoofProductionAnimPoweredEffect { }
, RoofProductionAnimPoweredSpecial { }

// Ares 0.2
, CloningFacility { false }

Expand Down
5 changes: 4 additions & 1 deletion src/Ext/BuildingType/Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ DEFINE_HOOK(0x44E826, BuildingClass_GetPowerOutput_Enhancer, 0x6)

const auto pOwner = pThis->Owner;
auto [power, extraPower] = BuildingTypeExt::GetEnhancedPower(pThis->Type, R->EDI<int>(), pOwner, pThis);

if (pThis->UpgradeLevel)
{
for (const auto pUpgrade : pThis->Upgrades)
Expand Down Expand Up @@ -533,6 +533,9 @@ static __forceinline bool AllowPoweredAnim(BuildingClass* pBuilding, BuildingAni
}
else if (anim == BuildingAnimSlot::Production || anim == BuildingAnimSlot::PreProduction)
{
if (anim == BuildingAnimSlot::Production && BuildingExt::Fetch(pBuilding)->IsPlayingRoofProductionAnim)
return true;

auto const animData = pType->GetBuildingAnim(anim);

if (BuildingTypeExt::IsPoweredAnimBlocked(pBuilding, animData.Powered, animData.PoweredLight, animData.PoweredEffect, animData.PoweredSpecial))
Expand Down
Loading