diff --git a/YRpp b/YRpp index d107962d13..54d40c2efd 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit d107962d130d898132c182540a2c218f23548b2f +Subproject commit 54d40c2efd00a47590e2a8bffa494d60ade64508 diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index f42cf5bb76..6cd47d7638 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -25,7 +25,8 @@ This page describes all the engine features that are either new and introduced b - `Animation.TemporalAction` determines what happens to the animation when the attached object is under effect of `Temporal=true` Warhead. - `Animation.UseInvokerAsOwner` can be used to set the house and TechnoType that created the effect (e.g firer of the weapon that applied it) as the animation's owner & invoker instead of the object the effect is attached to. - `Animation.HideIfAttachedWith` contains list of other AttachEffectTypes that if attached to same techno as the current one, will hide this effect's animation. - - `CumulativeAnimations` can be used to declare a list of animations used for `Cumulative=true` types instead of `Animation`. An animation is picked from the list in order matching the number of active instances of the type on the object, with last listed animation used if number is higher than the number of listed animations. This animation is only displayed once, on the first active instance of the effect found attached and is updated and restarted if the number of active instances changed. + - `CumulativeAnimations` can be used to declare a list of animations used for `Cumulative=true` types instead of `Animation`. An animation is picked from the list in order matching the number of active instances of the type on the object, with last listed animation used if number is higher than the number of listed animations. This animation is only displayed once and is transferred from the effect to another of same type (specifically one with longest remaining duration), if such exists, upon expiration or removal. Note that because `Cumulative.MaxCount` limits the number of effects of same type that can be applied this can cause animations to 'flicker' here as effects expire before new ones can be applied in some circumstances. + - `CumulativeAnimations.RestartOnChange` determines if the animation playback is restarted when the type of animation changes, if not then playback resumes at frame at same position relative to the animation's length. - Attached effect can fire off a weapon when expired / removed / object dies by setting `ExpireWeapon`. - `ExpireWeapon.TriggerOn` determines the exact conditions upon which the weapon is fired, defaults to `expire` which means only if the effect naturally expires. - `ExpireWeapon.CumulativeOnlyOnce`, if set to true, makes it so that `Cumulative=true` attached effects only detonate the weapon once period, instead of once per active instance. On `remove` and `expire` condition this means it will only detonate after last instance has expired or been removed. @@ -90,6 +91,7 @@ Animation.TemporalAction=None ; AttachedAnimFlag (None, Hides, Animation.UseInvokerAsOwner=false ; boolean Animation.HideIfAttachedWith= ; List of AttachEffectTypes CumulativeAnimations= ; list of animations +CumulativeAnimations.RestartOnChange=true ; boolean ExpireWeapon= ; WeaponType ExpireWeapon.TriggerOn=expire ; List of expire weapon trigger condition enumeration (none|expire|remove|death|all) ExpireWeapon.CumulativeOnlyOnce=false ; boolean diff --git a/src/Ext/Anim/Body.cpp b/src/Ext/Anim/Body.cpp index 9fb253a058..d068d1259d 100644 --- a/src/Ext/Anim/Body.cpp +++ b/src/Ext/Anim/Body.cpp @@ -1,5 +1,7 @@ #include "Body.h" +#include + #include #include #include @@ -130,6 +132,54 @@ void AnimExt::VeinAttackAI(AnimClass* pAnim) } } +// Changes type of anim in similar fashion to Next. +void AnimExt::ChangeAnimType(AnimClass* pAnim, AnimTypeClass* pNewType, bool resetLoops, bool restart) +{ + double percentThrough = pAnim->Animation.Value / static_cast(pAnim->Type->End); + + if (pNewType->End == -1) + { + pNewType->End = pNewType->GetImage()->Frames; + + if (pNewType->Shadow) + pNewType->End /= 2; + } + + if (pNewType->LoopEnd == -1) + { + pNewType->LoopEnd = pNewType->End; + } + + pAnim->Type = pNewType; + + if (resetLoops) + pAnim->RemainingIterations = static_cast(pNewType->LoopCount); + + pAnim->Accum = 0; + pAnim->UnableToContinue = false; + pAnim->Reverse = pNewType->Reverse; + + int rate = pNewType->Rate; + + if (pNewType->RandomRate.Min || pNewType->RandomRate.Max) + rate = ScenarioClass::Instance->Random.RandomRanged(pNewType->RandomRate.Min, pNewType->RandomRate.Max); + + if (pNewType->Normalized) + rate = GameOptionsClass::Instance->GetAnimSpeed(rate); + + pAnim->Animation.Start(rate, pNewType->Reverse ? -1 : 1); + + if (restart) + { + pAnim->Animation.Value = pNewType->Reverse ? pNewType->End : pNewType->Start; + pAnim->Start(); + } + else + { + pAnim->Animation.Value = static_cast(pNewType->End * percentThrough); + } +} + void AnimExt::HandleDebrisImpact(AnimTypeClass* pExpireAnim, AnimTypeClass* pWakeAnim, Iterator splashAnims, HouseClass* pOwner, WarheadTypeClass* pWarhead, int nDamage, CellClass* pCell, CoordStruct nLocation, bool heightFlag, bool isMeteor, bool warheadDetonate, bool explodeOnWater, bool splashAnimsPickRandom) { diff --git a/src/Ext/Anim/Body.h b/src/Ext/Anim/Body.h index 975779040e..2ff4f3e434 100644 --- a/src/Ext/Anim/Body.h +++ b/src/Ext/Anim/Body.h @@ -72,9 +72,8 @@ class AnimExt static bool SetAnimOwnerHouseKind(AnimClass* pAnim, HouseClass* pInvoker, HouseClass* pVictim, bool defaultToVictimOwner = true, bool defaultToInvokerOwner = false); static HouseClass* GetOwnerHouse(AnimClass* pAnim, HouseClass* pDefaultOwner = nullptr); - static void VeinAttackAI(AnimClass* pAnim); - + static void ChangeAnimType(AnimClass* pAnim, AnimTypeClass* pNewType, bool resetLoops, bool restart); static void HandleDebrisImpact(AnimTypeClass* pExpireAnim, AnimTypeClass* pWakeAnim, Iterator splashAnims, HouseClass* pOwner, WarheadTypeClass* pWarhead, int nDamage, CellClass* pCell, CoordStruct nLocation, bool heightFlag, bool isMeteor, bool warheadDetonate, bool explodeOnWater, bool splashAnimsPickRandom); diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index 12a4e75be7..06bbe2bd41 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -836,7 +836,8 @@ void TechnoExt::ExtData::UpdateAttachEffects() if (pType->HasTint()) markForRedraw = true; - this->UpdateCumulativeAttachEffects(attachEffect->GetType()); + if (pType->Cumulative && pType->CumulativeAnimations.size() > 0) + this->UpdateCumulativeAttachEffects(attachEffect->GetType(), attachEffect); if (pType->ExpireWeapon && (pType->ExpireWeapon_TriggerOn & ExpireWeaponCondition::Expire) != ExpireWeaponCondition::None) { @@ -949,31 +950,45 @@ void TechnoExt::ExtData::UpdateSelfOwnedAttachEffects() pThis->MarkForRedraw(); } -// Updates state of AttachEffects of same cumulative type on techno, (which one is first active instance existing, if any), kills animations if needed. -void TechnoExt::ExtData::UpdateCumulativeAttachEffects(AttachEffectTypeClass* pAttachEffectType) +// Updates CumulativeAnimations AE's on techno. +void TechnoExt::ExtData::UpdateCumulativeAttachEffects(AttachEffectTypeClass* pAttachEffectType, AttachEffectClass* pRemoved) { - if (!pAttachEffectType || !pAttachEffectType->Cumulative) - return; - - bool foundFirst = false; + AttachEffectClass* pAELargestDuration = nullptr; + AttachEffectClass* pAEWithAnim = nullptr; + int duration = 0; for (auto const& attachEffect : this->AttachedEffects) { - if (attachEffect->GetType() != pAttachEffectType || !attachEffect->IsActive()) + if (attachEffect->GetType() != pAttachEffectType) continue; - if (!foundFirst) + if (attachEffect->HasCumulativeAnim) { - foundFirst = true; - attachEffect->IsFirstCumulativeInstance = true; + pAEWithAnim = attachEffect.get(); } - else + else if (attachEffect->CanShowAnim()) { - attachEffect->IsFirstCumulativeInstance = false; + int currentDuration = attachEffect->GetRemainingDuration(); + + if (currentDuration < 0 || currentDuration > duration) + { + pAELargestDuration = attachEffect.get(); + duration = currentDuration; + } } + } + + if (pAEWithAnim) + { + pAEWithAnim->UpdateCumulativeAnim(); - if (pAttachEffectType->CumulativeAnimations.size() > 0) - attachEffect->KillAnim(); + if (pRemoved == pAEWithAnim) + { + pAEWithAnim->HasCumulativeAnim = false; + + if (pAELargestDuration) + pAELargestDuration->TransferCumulativeAnim(pAEWithAnim); + } } } diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 0a90b4f688..9905d64bcc 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -100,7 +100,7 @@ class TechnoExt void UpdateTypeData(TechnoTypeClass* currentType); void UpdateLaserTrails(); void UpdateAttachEffects(); - void UpdateCumulativeAttachEffects(AttachEffectTypeClass* pAttachEffectType); + void UpdateCumulativeAttachEffects(AttachEffectTypeClass* pAttachEffectType, AttachEffectClass* pRemoved = nullptr); void RecalculateStatMultipliers(); void UpdateTemporal(); void UpdateMindControlAnim(); diff --git a/src/New/Entity/AttachEffectClass.cpp b/src/New/Entity/AttachEffectClass.cpp index 343a1d61be..f74c2bfec0 100644 --- a/src/New/Entity/AttachEffectClass.cpp +++ b/src/New/Entity/AttachEffectClass.cpp @@ -16,7 +16,7 @@ AttachEffectClass::AttachEffectClass() , Duration { 0 } , CurrentDelay { 0 } , NeedsDurationRefresh { false } - , IsFirstCumulativeInstance { false } + , HasCumulativeAnim { false } { this->HasInitialized = false; AttachEffectClass::Array.emplace_back(this); @@ -35,7 +35,7 @@ AttachEffectClass::AttachEffectClass(AttachEffectTypeClass* pType, TechnoClass* , IsOnline { true } , IsCloaked { false } , NeedsDurationRefresh { false } - , IsFirstCumulativeInstance { false } + , HasCumulativeAnim { false } { this->HasInitialized = false; @@ -154,7 +154,7 @@ void AttachEffectClass::AI() this->CloakCheck(); this->OnlineCheck(); - if (!this->Animation && !this->IsUnderTemporal && this->IsOnline && !this->IsCloaked && !this->IsInTunnel && !this->IsAnimHidden) + if (!this->Animation && this->CanShowAnim()) this->CreateAnim(); this->AnimCheck(); @@ -168,7 +168,7 @@ void AttachEffectClass::AI_Temporal() this->CloakCheck(); - if (!this->Animation && this->Type->Animation_TemporalAction != AttachedAnimFlag::Hides && this->IsOnline && !this->IsCloaked && !this->IsInTunnel && !this->IsAnimHidden) + if (!this->Animation && this->CanShowAnim()) this->CreateAnim(); if (this->Animation) @@ -212,7 +212,7 @@ void AttachEffectClass::AnimCheck() { this->IsAnimHidden = false; - if (!this->Animation && (!this->IsUnderTemporal || this->Type->Animation_TemporalAction != AttachedAnimFlag::Hides)) + if (!this->Animation && this->CanShowAnim()) this->CreateAnim(); } } @@ -290,7 +290,7 @@ void AttachEffectClass::CreateAnim() if (this->Type->Cumulative && this->Type->CumulativeAnimations.size() > 0) { - if (!this->IsFirstCumulativeInstance) + if (!this->HasCumulativeAnim) return; int count = TechnoExt::ExtMap.Find(this->Techno)->GetAttachedEffectCumulativeCount(this->Type); @@ -328,6 +328,44 @@ void AttachEffectClass::KillAnim() } } +void AttachEffectClass::UpdateCumulativeAnim() +{ + if (!this->HasCumulativeAnim || !this->Animation) + return; + + int count = TechnoExt::ExtMap.Find(this->Techno)->GetAttachedEffectCumulativeCount(this->Type); + + if (count < 1) + { + this->KillAnim(); + return; + } + + auto const pAnimType = this->Type->GetCumulativeAnimation(count); + + if (this->Animation->Type != pAnimType) + AnimExt::ChangeAnimType(this->Animation, pAnimType, false, this->Type->CumulativeAnimations_RestartOnChange); +} + +void AttachEffectClass::TransferCumulativeAnim(AttachEffectClass* pSource) +{ + if (!pSource || !pSource->Animation) + return; + + this->KillAnim(); + this->Animation = pSource->Animation; + this->HasCumulativeAnim = true; + pSource->Animation = nullptr; + pSource->HasCumulativeAnim = false; +} + +bool AttachEffectClass::CanShowAnim() const +{ + return (!this->IsUnderTemporal || this->Type->Animation_TemporalAction != AttachedAnimFlag::Hides) + && (this->IsOnline || this->Type->Animation_OfflineAction != AttachedAnimFlag::Hides) + && !this->IsCloaked && !this->IsInTunnel && !this->IsAnimHidden; +} + void AttachEffectClass::SetAnimationTunnelState(bool visible) { if (!this->IsInTunnel && !visible) @@ -346,7 +384,9 @@ void AttachEffectClass::RefreshDuration(int durationOverride) if (this->Type->Animation_ResetOnReapply) { this->KillAnim(); - this->CreateAnim(); + + if (this->CanShowAnim()) + this->CreateAnim(); } } @@ -362,11 +402,6 @@ bool AttachEffectClass::ResetIfRecreatable() return true; } -bool AttachEffectClass::IsSelfOwned() const -{ - return this->Source == this->Techno; -} - bool AttachEffectClass::HasExpired() const { return this->IsSelfOwned() && this->Delay >= 0 ? false : !this->Duration; @@ -435,11 +470,6 @@ bool AttachEffectClass::IsFromSource(TechnoClass* pInvoker, AbstractClass* pSour return pInvoker == this->Invoker && pSource == this->Source; } -AttachEffectTypeClass* AttachEffectClass::GetType() const -{ - return this->Type; -} - #pragma region StaticFunctions_AttachDetachTransfer /// @@ -532,7 +562,7 @@ int AttachEffectClass::Attach(std::vector const& types, if (pType->HasTint()) markForRedraw = true; - if (pType->Cumulative) + if (pType->Cumulative && pType->CumulativeAnimations.size() > 0) pTargetExt->UpdateCumulativeAttachEffects(pType); } } @@ -612,7 +642,12 @@ AttachEffectClass* AttachEffectClass::CreateAndAttach(AttachEffectTypeClass* pTy else { targetAEs.push_back(std::make_unique(pType, pTarget, pInvokerHouse, pInvoker, pSource, durationOverride, delay, initialDelay, recreationDelay)); - return targetAEs.back().get(); + auto const pAE = targetAEs.back().get(); + + if (!currentTypeCount && pType->Cumulative && pType->CumulativeAnimations.size() > 0) + pAE->HasCumulativeAnim = true; + + return pAE; } return nullptr; @@ -673,9 +708,6 @@ int AttachEffectClass::Detach(std::vector const& types, if (count && pType->HasTint()) markForRedraw = true; - if (count && pType->Cumulative) - pTargetExt->UpdateCumulativeAttachEffects(pType); - detachedCount += count; index++; } @@ -760,6 +792,10 @@ int AttachEffectClass::RemoveAllOfType(AttachEffectTypeClass* pType, TechnoClass expireWeapons.push_back(pType->ExpireWeapon); } + + if (pType->Cumulative && pType->CumulativeAnimations.size() > 0) + pTargetExt->UpdateCumulativeAttachEffects(pType, attachEffect); + if (attachEffect->ResetIfRecreatable()) { ++it; @@ -872,6 +908,7 @@ bool AttachEffectClass::Serialize(T& Stm) .Process(this->IsCloaked) .Process(this->HasInitialized) .Process(this->NeedsDurationRefresh) + .Process(this->HasCumulativeAnim) .Success(); } diff --git a/src/New/Entity/AttachEffectClass.h b/src/New/Entity/AttachEffectClass.h index 11633f7933..e4f55fec42 100644 --- a/src/New/Entity/AttachEffectClass.h +++ b/src/New/Entity/AttachEffectClass.h @@ -17,11 +17,16 @@ class AttachEffectClass void AI(); void AI_Temporal(); void KillAnim(); + void CreateAnim(); + void UpdateCumulativeAnim(); + void TransferCumulativeAnim(AttachEffectClass* pSource); + bool CanShowAnim() const; void SetAnimationTunnelState(bool visible); - AttachEffectTypeClass* GetType() const; + AttachEffectTypeClass* GetType() const { return this->Type; } + int GetRemainingDuration() const { return this->Duration; } void RefreshDuration(int durationOverride = 0); bool ResetIfRecreatable(); - bool IsSelfOwned() const; + bool IsSelfOwned() const { return this->Source == this->Techno; } bool HasExpired() const; bool AllowedToBeActive() const; bool IsActive() const; @@ -62,7 +67,6 @@ class AttachEffectClass void OnlineCheck(); void CloakCheck(); void AnimCheck(); - void CreateAnim(); static AttachEffectClass* CreateAndAttach(AttachEffectTypeClass* pType, TechnoClass* pTarget, std::vector>& targetAEs, HouseClass* pInvokerHouse, TechnoClass* pInvoker, AbstractClass* pSource, int durationOverride = 0, int delay = 0, int initialDelay = 0, int recreationDelay = -1); @@ -93,7 +97,7 @@ class AttachEffectClass bool NeedsDurationRefresh; public: - bool IsFirstCumulativeInstance; + bool HasCumulativeAnim; }; // Container for TechnoClass-specific AttachEffect fields. diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index a5bc36b051..4617ddacfa 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -106,6 +106,7 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->Animation.Read(exINI, pSection, "Animation"); this->CumulativeAnimations.Read(exINI, pSection, "CumulativeAnimations"); + this->CumulativeAnimations_RestartOnChange.Read(exINI, pSection, "CumulativeAnimations.RestartOnChange"); this->Animation_ResetOnReapply.Read(exINI, pSection, "Animation.ResetOnReapply"); this->Animation_OfflineAction.Read(exINI, pSection, "Animation.OfflineAction"); this->Animation_TemporalAction.Read(exINI, pSection, "Animation.TemporalAction"); @@ -169,6 +170,7 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->PenetratesForceShield) .Process(this->Animation) .Process(this->CumulativeAnimations) + .Process(this->CumulativeAnimations_RestartOnChange) .Process(this->Animation_ResetOnReapply) .Process(this->Animation_OfflineAction) .Process(this->Animation_TemporalAction) diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index 3be8b8cd2f..c2ad5e6627 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -22,6 +22,7 @@ class AttachEffectTypeClass final : public Enumerable Nullable PenetratesForceShield; Valueable Animation; ValueableVector CumulativeAnimations; + Valueable CumulativeAnimations_RestartOnChange; Valueable Animation_ResetOnReapply; Valueable Animation_OfflineAction; Valueable Animation_TemporalAction; @@ -70,6 +71,7 @@ class AttachEffectTypeClass final : public Enumerable , PenetratesForceShield {} , Animation {} , CumulativeAnimations {} + , CumulativeAnimations_RestartOnChange { true } , Animation_ResetOnReapply { false } , Animation_OfflineAction { AttachedAnimFlag::Hides } , Animation_TemporalAction { AttachedAnimFlag::None }