diff --git a/CREDITS.md b/CREDITS.md index 599ca1b29a..7bebac390a 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -277,6 +277,7 @@ This page lists all the individual contributions to the project by their author. - Restored parabombs - Delayed fire weapons - Changes / fixes to `Vertical` projectile logic and customizing projectile initial facing behavior + - Attached animation draw offset customizations - **Morton (MortonPL)**: - `XDrawOffset` for animations - Shield passthrough & absorption diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index 11847189fd..95ac915d05 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -51,7 +51,6 @@ This page describes all ingame logics that are fixed or improved in Phobos witho - Vehicle to building deployers now keep their target when deploying with `DeployToFire`. - Effects like lasers are no longer drawn from wrong firing offset on weapons that use Burst. -- Animations can now be offset on the X axis with `XDrawOffset`. - `IsSimpleDeployer` units now only play `DeploySound` and `UndeploySound` once, when done with (un)deploying instead of repeating it over duration of turning and/or `DeployingAnim`. - AITrigger can now recognize Building Upgrades as legal condition. - `EWGates` and `NSGates` now will link walls like `xxGateOne` and `xxGateTwo` do. @@ -438,6 +437,23 @@ In `artmd.ini`: Crater.DestroyTiberium= ; boolean, default to [General] -> AnimCraterDestroyTiberium ``` +### Draw offset customization + +- `XDrawOffset` can be used to adjust horizontal/X axis position of the animation. +- `YDrawOffset.ApplyBracketHeight` makes Y axis position follow it's owner object's selection bracket height (for buildings, this is based on `Height` and `Foundation`, for others it is influenced by `PixelSelectionBracketDelta`) if it is attached to one. + - By default this will only apply if the bracket position is negative e.g it is moved upwards from the object center. If `YDrawOffset.InvertBracketShift` is set to true, the opposite is true and negative shift is ignored. + - The bracket-based shift can be further adjusted with offset from `YDrawOffset.BracketAdjust`, overridden by `YDrawOffset.BracketAdjust.Buildings` for buildings only. + +In `artmd.ini`: +```ini +[SOMEANIM] ; AnimationType +XDrawOffset=0,0 ; X,Y, pixels relative to default +YDrawOffset.ApplyBracketHeight=false ; boolean +YDrawOffset.InvertBracketShift=false ; boolean +YDrawOffset.BracketAdjust=0,0 ; X,Y, pixels relative to default +YDrawOffset.BracketAdjust.Buildings= ; X,Y, pixels relative to default +``` + ### Fire animations spawned by Scorch & Flamer - Tiberian Sun allowed `Scorch=true` and `Flamer=true` animations to spawn fire animations from `[AudioVisual] -> SmallFire` & `LargeFire`. This behaviour has been reimplemented and is fully customizable. diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index b920d5b00e..724816cef8 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -28,6 +28,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. + - `Animation.DrawOffsetN` (where N is integer starting from 0) can be used to define draw offset rules for the animation. These are parsed starting from index 0 until offset with value 0,0 is encountered. + - `Animation.DrawOffsetN.RequiredTypes` contains list other AttachEffectTypes that need to be attached on the same techno as the current one for the draw offset rule to apply. - `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`. @@ -103,6 +105,8 @@ Animation.OfflineAction=Hides ; AttachedAnimFlag (None, Hid Animation.TemporalAction=None ; AttachedAnimFlag (None, Hides, Temporal, Paused or PausedTemporal) Animation.UseInvokerAsOwner=false ; boolean Animation.HideIfAttachedWith= ; List of AttachEffectTypes +Animation.DrawOffsetN=0,0 ; X,Y, pixels relative to default +Animation.DrawOffsetN.RequiredTypes= ; List of AttachEffectTypes CumulativeAnimations= ; List of AnimationTypes CumulativeAnimations.RestartOnChange=true ; boolean ExpireWeapon= ; WeaponType diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 432a86fe92..310d24e4ba 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -443,6 +443,8 @@ New: - [Ammo-based deploy customizations for vehicles expanded to non-IsSimpleDeployer deploy functions](New-or-Enhanced-Logics.md#automatic-deploy-and-blocking-deploying-based-on-ammo) (by Starkku) - Randomized anims for several behaviors (by Ollerus) - Shield respawn animation and weapon (by Ollerus) +- [Attached animation draw offset customizations](Fixed-or-Improved-Logics.md#draw-offset-customization) (by Starkku) +- [Draw offset rules for AttachEffect animations](New-or-Enhanced-Logics.md#attached-effects) by (Starkku) Vanilla fixes: - Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya) diff --git a/src/Ext/Anim/Body.cpp b/src/Ext/Anim/Body.cpp index f7c74fdb28..769bd74d82 100644 --- a/src/Ext/Anim/Body.cpp +++ b/src/Ext/Anim/Body.cpp @@ -411,6 +411,7 @@ void AnimExt::ExtData::Serialize(T& Stm) .Process(this->DelayedFireRemoveOnNoDelay) .Process(this->IsAttachedEffectAnim) .Process(this->IsShieldIdleAnim) + .Process(this->AEDrawOffset) ; } diff --git a/src/Ext/Anim/Body.h b/src/Ext/Anim/Body.h index 72f3726100..b9bbe4e2f3 100644 --- a/src/Ext/Anim/Body.h +++ b/src/Ext/Anim/Body.h @@ -31,6 +31,7 @@ class AnimExt bool DelayedFireRemoveOnNoDelay; bool IsAttachedEffectAnim; bool IsShieldIdleAnim; + Point2D AEDrawOffset; ExtData(AnimClass* OwnerObject) : Extension(OwnerObject) , DeathUnitFacing { 0 } @@ -45,6 +46,7 @@ class AnimExt , DelayedFireRemoveOnNoDelay { false } , IsAttachedEffectAnim { false } , IsShieldIdleAnim { false } + , AEDrawOffset { Point2D::Empty } { } void SetInvoker(TechnoClass* pInvoker); diff --git a/src/Ext/Anim/Hooks.cpp b/src/Ext/Anim/Hooks.cpp index 5c86a63d34..5eda173959 100644 --- a/src/Ext/Anim/Hooks.cpp +++ b/src/Ext/Anim/Hooks.cpp @@ -266,14 +266,57 @@ DEFINE_HOOK(0x424CF1, AnimClass_Start_DetachedReport, 0x6) } // 0x422CD8 is in an alternate code path only used by anims with ID RING1, unused normally but covering it just because -DEFINE_HOOK_AGAIN(0x422CD8, AnimClass_DrawIt_XDrawOffset, 0x6) -DEFINE_HOOK(0x423122, AnimClass_DrawIt_XDrawOffset, 0x6) +DEFINE_HOOK_AGAIN(0x422CD8, AnimClass_DrawIt_DrawOffset, 0x6) +DEFINE_HOOK(0x423122, AnimClass_DrawIt_DrawOffset, 0x6) { GET(AnimClass* const, pThis, ESI); GET_STACK(Point2D*, pLocation, STACK_OFFSET(0x110, 0x4)); - if (auto const pTypeExt = AnimTypeExt::ExtMap.TryFind(pThis->Type)) - pLocation->X += pTypeExt->XDrawOffset; + auto const pTypeExt = AnimTypeExt::ExtMap.TryFind(pThis->Type); + pLocation->X += pTypeExt->XDrawOffset; + + if (pTypeExt->YDrawOffset_ApplyBracketHeight && pThis->OwnerObject && pThis->OwnerObject->AbstractFlags & AbstractFlags::Techno) + { + // Le magic number. + constexpr int SHIELD_HEALTHBAR_OFFSET = -3; + + auto const pTechno = static_cast(pThis->OwnerObject); + bool inverse = pTypeExt->YDrawOffset_InvertBracketShift; + + if (auto const pBuilding = abstract_cast(pTechno)) + { + auto const pType = pBuilding->Type; + + if ((pType->Height >= 0 && !inverse) || (pType->Height < 0 && inverse)) + { + auto const pos = TechnoExt::GetBuildingSelectBracketPosition(pBuilding, BuildingSelectBracketPosition::Top); + pLocation->Y = pos.Y + pTypeExt->YDrawOffset_BracketAdjust_Buildings.Get(pTypeExt->YDrawOffset_BracketAdjust); + } + } + else + { + auto const pType = pTechno->GetTechnoType(); + + if ((pType->PixelSelectionBracketDelta <= 0 && !inverse) || (pType->PixelSelectionBracketDelta > 0 && inverse)) + { + auto const pos = TechnoExt::GetFootSelectBracketPosition(pTechno, Anchor(HorizontalPosition::Left, VerticalPosition::Top)); + pLocation->Y = pos.Y + pType->PixelSelectionBracketDelta + pTypeExt->YDrawOffset_BracketAdjust; + } + } + + if (auto const pShield = TechnoExt::ExtMap.Find(pTechno)->Shield.get()) + { + auto const pShieldType = pShield->GetType(); + + if (pShield->IsAvailable() && !pShield->IsBrokenAndNonRespawning() && (pShield->GetHealthRatio() > 0.0 || !pShieldType->Pips_HideIfNoStrength)) + { + if ((pShieldType->BracketDelta <= 0 && !inverse) || (pShieldType->BracketDelta > 0 && inverse)) + pLocation->Y += pShieldType->BracketDelta + SHIELD_HEALTHBAR_OFFSET; + } + } + } + + *pLocation += AnimExt::ExtMap.Find(pThis)->AEDrawOffset; return 0; } diff --git a/src/Ext/AnimType/Body.cpp b/src/Ext/AnimType/Body.cpp index 90c99f02fd..97455ae9bb 100644 --- a/src/Ext/AnimType/Body.cpp +++ b/src/Ext/AnimType/Body.cpp @@ -87,6 +87,10 @@ void AnimTypeExt::ExtData::LoadFromINIFile(CCINIClass* pINI) this->Palette.LoadFromINI(pINI, pID, "CustomPalette"); this->XDrawOffset.Read(exINI, pID, "XDrawOffset"); + this->YDrawOffset_ApplyBracketHeight.Read(exINI, pID, "YDrawOffset.ApplyBracketHeight"); + this->YDrawOffset_InvertBracketShift.Read(exINI, pID, "YDrawOffset.InvertBracketShift"); + this->YDrawOffset_BracketAdjust.Read(exINI, pID, "YDrawOffset.BracketAdjust"); + this->YDrawOffset_BracketAdjust_Buildings.Read(exINI, pID, "YDrawOffset.BracketAdjust.Buildings"); this->HideIfNoOre_Threshold.Read(exINI, pID, "HideIfNoOre.Threshold"); this->Layer_UseObjectLayer.Read(exINI, pID, "Layer.UseObjectLayer"); this->AttachedAnimPosition.Read(exINI, pID, "AttachedAnimPosition"); @@ -146,6 +150,10 @@ void AnimTypeExt::ExtData::Serialize(T& Stm) .Process(this->Palette) .Process(this->CreateUnitType) .Process(this->XDrawOffset) + .Process(this->YDrawOffset_ApplyBracketHeight) + .Process(this->YDrawOffset_InvertBracketShift) + .Process(this->YDrawOffset_BracketAdjust) + .Process(this->YDrawOffset_BracketAdjust_Buildings) .Process(this->HideIfNoOre_Threshold) .Process(this->Layer_UseObjectLayer) .Process(this->AttachedAnimPosition) diff --git a/src/Ext/AnimType/Body.h b/src/Ext/AnimType/Body.h index b628398c01..220f9cbd74 100644 --- a/src/Ext/AnimType/Body.h +++ b/src/Ext/AnimType/Body.h @@ -29,6 +29,10 @@ class AnimTypeExt CustomPalette Palette; std::unique_ptr CreateUnitType; Valueable XDrawOffset; + Valueable YDrawOffset_ApplyBracketHeight; + Valueable YDrawOffset_InvertBracketShift; + Valueable YDrawOffset_BracketAdjust; + Nullable YDrawOffset_BracketAdjust_Buildings; Valueable HideIfNoOre_Threshold; Nullable Layer_UseObjectLayer; Valueable AttachedAnimPosition; @@ -68,6 +72,8 @@ class AnimTypeExt , Palette { CustomPalette::PaletteMode::Temperate } , CreateUnitType { nullptr } , XDrawOffset { 0 } + , YDrawOffset_ApplyBracketHeight { false } + , YDrawOffset_InvertBracketShift { false } , HideIfNoOre_Threshold { 0 } , Layer_UseObjectLayer {} , AttachedAnimPosition { AttachedAnimPosition::Default } diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index da6be50161..718f29fd81 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -1840,7 +1840,10 @@ void TechnoExt::ExtData::UpdateAttachEffects() } if (altered) + { this->RecalculateStatMultipliers(); + this->UpdateAEAnimLogic(); + } if (markForRedraw) pThis->MarkForRedraw(); @@ -1912,7 +1915,10 @@ void TechnoExt::ExtData::UpdateSelfOwnedAttachEffects() const int count = AttachEffectClass::Attach(pThis, pThis->Owner, pThis, pThis, pTypeExt->AttachEffects); if (altered && !count) + { this->RecalculateStatMultipliers(); + this->UpdateAEAnimLogic(); + } if (markForRedraw) pThis->MarkForRedraw(); @@ -1960,6 +1966,15 @@ void TechnoExt::ExtData::UpdateCumulativeAttachEffects(AttachEffectTypeClass* pA } } +// Update AttachEffect animation logic. +void TechnoExt::ExtData::UpdateAEAnimLogic() +{ + for (auto const& attachEffect : this->AttachedEffects) + { + attachEffect->UpdateAnimLogic(); + } +} + // Recalculates AttachEffect stat multipliers and other bonuses. void TechnoExt::ExtData::RecalculateStatMultipliers() { diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 5d43b41ccf..61321a9c7e 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -465,27 +465,21 @@ bool TechnoExt::IsTypeImmune(TechnoClass* pThis, TechnoClass* pSource) return false; } -/// -/// Gets whether or not techno has listed AttachEffect types active on it -/// -/// Attacheffect types. -/// Whether or not to require all listed types to be present or if only one will satisfy the check. -/// Ignore AttachEffects that come from set invoker and source. -/// Invoker Techno used for same source check. -/// Source AbstractClass instance used for same source check. -/// True if techno has active AttachEffects that satisfy the source, false if not. -bool TechnoExt::ExtData::HasAttachedEffects(std::vector attachEffectTypes, bool requireAll, bool ignoreSameSource, - TechnoClass* pInvoker, AbstractClass* pSource, std::vector const* minCounts, std::vector const* maxCounts) const +// Gets whether or not techno has listed AttachEffect types active on it +bool TechnoExt::ExtData::HasAttachedEffects(std::vector const& attachEffectTypes, bool requireAll, bool ignoreSameSource, + TechnoClass* pInvoker, AbstractClass* pSource, std::vector const* minCounts, std::vector const* maxCounts, bool requireAnims) const { unsigned int foundCount = 0; unsigned int typeCounter = 1; const bool checkSource = ignoreSameSource && pInvoker && pSource; - for (auto const& type : attachEffectTypes) + for (auto const& pType : attachEffectTypes) { for (auto const& attachEffect : this->AttachedEffects) { - if (attachEffect->GetType() == type && attachEffect->IsActive()) + auto const pAttachedType = attachEffect->GetType(); + + if (pAttachedType == pType && attachEffect->IsActive() && (!requireAnims || !pAttachedType->HasAnim() || attachEffect->HasAnim())) { if (checkSource && attachEffect->IsFromSource(pInvoker, pSource)) continue; @@ -493,9 +487,9 @@ bool TechnoExt::ExtData::HasAttachedEffects(std::vector const unsigned int minSize = minCounts ? minCounts->size() : 0; const unsigned int maxSize = maxCounts ? maxCounts->size() : 0; - if (type->Cumulative && (minSize > 0 || maxSize > 0)) + if (pType->Cumulative && (minSize > 0 || maxSize > 0)) { - const int cumulativeCount = this->GetAttachedEffectCumulativeCount(type, ignoreSameSource, pInvoker, pSource); + const int cumulativeCount = this->GetAttachedEffectCumulativeCount(pType, ignoreSameSource, pInvoker, pSource); if (minSize > 0) { @@ -531,14 +525,7 @@ bool TechnoExt::ExtData::HasAttachedEffects(std::vector return false; } -/// -/// Gets how many counts of same cumulative AttachEffect type instance techno has active on it. -/// -/// AttachEffect type. -/// Ignore AttachEffects that come from set invoker and source. -/// Invoker Techno used for same source check. -/// Source AbstractClass instance used for same source check. -/// Number of active cumulative AttachEffect type instances on the techno. 0 if the AttachEffect type is not cumulative. +// Gets how many counts of same cumulative AttachEffect type instance techno has active on it. int TechnoExt::ExtData::GetAttachedEffectCumulativeCount(AttachEffectTypeClass* pAttachEffectType, bool ignoreSameSource, TechnoClass* pInvoker, AbstractClass* pSource) const { if (!pAttachEffectType->Cumulative) diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 3734979dd4..a7ec533376 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -174,6 +174,7 @@ class TechnoExt void UpdateKeepTargetOnMove(); void UpdateWarpInDelay(); void UpdateCumulativeAttachEffects(AttachEffectTypeClass* pAttachEffectType, AttachEffectClass* pRemoved = nullptr); + void UpdateAEAnimLogic(); void RecalculateStatMultipliers(); void UpdateTemporal(); void UpdateMindControlAnim(); @@ -183,7 +184,7 @@ class TechnoExt void InitializeLaserTrails(); void InitializeAttachEffects(); void UpdateSelfOwnedAttachEffects(); - bool HasAttachedEffects(std::vector attachEffectTypes, bool requireAll, bool ignoreSameSource, TechnoClass* pInvoker, AbstractClass* pSource, std::vector const* minCounts, std::vector const* maxCounts) const; + bool HasAttachedEffects(std::vector const& attachEffectTypes, bool requireAll, bool ignoreSameSource, TechnoClass* pInvoker, AbstractClass* pSource, std::vector const* minCounts, std::vector const* maxCounts, bool requireAnims = false) const; int GetAttachedEffectCumulativeCount(AttachEffectTypeClass* pAttachEffectType, bool ignoreSameSource = false, TechnoClass* pInvoker = nullptr, AbstractClass* pSource = nullptr) const; void InitializeDisplayInfo(); void ApplyMindControlRangeLimit(); diff --git a/src/New/Entity/AttachEffectClass.cpp b/src/New/Entity/AttachEffectClass.cpp index 37e60355c4..37cb78394e 100644 --- a/src/New/Entity/AttachEffectClass.cpp +++ b/src/New/Entity/AttachEffectClass.cpp @@ -100,7 +100,11 @@ AttachEffectClass::~AttachEffectClass() if (it != AttachEffectClass::Array.end()) AttachEffectClass::Array.erase(it); - this->KillAnim(); + if (this->Animation) + { + this->Animation->UnInit(); + this->Animation = nullptr; + } if (this->Invoker) TechnoExt::ExtMap.Find(this->Invoker)->AttachedEffectInvokerCount--; @@ -250,8 +254,6 @@ void AttachEffectClass::AI() if (!this->Animation && this->CanShowAnim()) this->CreateAnim(); - - this->AnimCheck(); } void AttachEffectClass::AI_Temporal() @@ -286,12 +288,10 @@ void AttachEffectClass::AI_Temporal() break; } } - - this->AnimCheck(); } } -void AttachEffectClass::AnimCheck() +void AttachEffectClass::UpdateAnimLogic() { if (this->Type->Animation_HideIfAttachedWith.size() > 0) { @@ -310,6 +310,19 @@ void AttachEffectClass::AnimCheck() this->CreateAnim(); } } + + if (this->Animation && this->Type->Animation_DrawOffsets.size() > 0) + { + auto const pAnimExt = AnimExt::ExtMap.Find(this->Animation); + auto const pTechnoExt = TechnoExt::ExtMap.Find(this->Techno); + pAnimExt->AEDrawOffset = Point2D::Empty; + + for (auto const& drawOffset : this->Type->Animation_DrawOffsets) + { + if (drawOffset.RequiredTypes.size() < 1 || pTechnoExt->HasAttachedEffects(drawOffset.RequiredTypes, false, false, nullptr, nullptr, nullptr, nullptr, true)) + pAnimExt->AEDrawOffset += drawOffset.Offset; + } + } } void AttachEffectClass::OnlineCheck() @@ -418,6 +431,7 @@ void AttachEffectClass::CreateAnim() pAnim->RemainingIterations = 0xFFu; this->Animation = pAnim; + TechnoExt::ExtMap.Find(this->Techno)->UpdateAEAnimLogic(); } } @@ -427,8 +441,13 @@ void AttachEffectClass::KillAnim() { this->Animation->UnInit(); this->Animation = nullptr; + TechnoExt::ExtMap.Find(this->Techno)->UpdateAEAnimLogic(); } } +bool AttachEffectClass::HasAnim() +{ + return this->Animation != nullptr; +} void AttachEffectClass::UpdateCumulativeAnim() { @@ -789,6 +808,8 @@ AttachEffectClass* AttachEffectClass::CreateAndAttach(AttachEffectTypeClass* pTy if (!currentTypeCount && pType->Cumulative && pType->CumulativeAnimations.size() > 0) pAE->HasCumulativeAnim = true; + TechnoExt::ExtMap.Find(pTarget)->UpdateAEAnimLogic(); + return pAE; } @@ -869,7 +890,11 @@ int AttachEffectClass::DetachTypes(TechnoClass* pTarget, AEAttachInfoTypeClass c } if (detachedCount > 0) - TechnoExt::ExtMap.Find(pTarget)->RecalculateStatMultipliers(); + { + auto const pExt = TechnoExt::ExtMap.Find(pTarget); + pExt->RecalculateStatMultipliers(); + pExt->UpdateAEAnimLogic(); + } if (markForRedraw) pTarget->MarkForRedraw(); @@ -972,6 +997,7 @@ int AttachEffectClass::RemoveAllOfType(AttachEffectTypeClass* pType, TechnoClass /// Target techno. void AttachEffectClass::TransferAttachedEffects(TechnoClass* pSource, TechnoClass* pTarget) { + int transferCount = 0; const auto pSourceExt = TechnoExt::ExtMap.Find(pSource); const auto pTargetExt = TechnoExt::ExtMap.Find(pTarget); std::vector>::iterator it; @@ -1022,8 +1048,15 @@ void AttachEffectClass::TransferAttachedEffects(TechnoClass* pSource, TechnoClas pAE->Duration = attachEffect->Duration; } + transferCount++; it = pSourceExt->AttachedEffects.erase(it); } + + if (transferCount) + { + pSourceExt->UpdateAEAnimLogic(); + pTargetExt->UpdateAEAnimLogic(); + } } #pragma endregion diff --git a/src/New/Entity/AttachEffectClass.h b/src/New/Entity/AttachEffectClass.h index e68c14314a..11169fe22e 100644 --- a/src/New/Entity/AttachEffectClass.h +++ b/src/New/Entity/AttachEffectClass.h @@ -18,8 +18,10 @@ class AttachEffectClass void AI(); void AI_Temporal(); + void UpdateAnimLogic(); void KillAnim(); void CreateAnim(); + bool HasAnim(); void UpdateCumulativeAnim(); void TransferCumulativeAnim(AttachEffectClass* pSource); bool CanShowAnim() const; @@ -48,7 +50,6 @@ class AttachEffectClass private: void OnlineCheck(); void CloakCheck(); - void AnimCheck(); static AttachEffectClass* CreateAndAttach(AttachEffectTypeClass* pType, TechnoClass* pTarget, std::vector>& targetAEs, HouseClass* pInvokerHouse, TechnoClass* pInvoker, AbstractClass* pSource, AEAttachParams const& attachInfo); diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index 47719b7268..862915085e 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -62,6 +62,14 @@ AnimTypeClass* AttachEffectTypeClass::GetCumulativeAnimation(int cumulativeCount return this->CumulativeAnimations.at(index); } +bool AttachEffectTypeClass::HasAnim() +{ + if (this->Cumulative) + return this->CumulativeAnimations.size() > 0 || this->Animation != nullptr; + else + return this->Animation != nullptr; +} + void AttachEffectTypeClass::HandleEvent(TechnoClass* pTarget) const { if (const auto pTag = pTarget->AttachedTag) @@ -171,6 +179,17 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) // Groups exINI.ParseStringList(this->Groups, pSection, "Groups"); AddToGroupsMap(); + + // Animation draw offsets. + for (int i = 0; i < INT32_MAX; i++) + { + AnimationDrawOffsetClass offset; + + if (offset.LoadFromINI(pINI, pSection, i)) + this->Animation_DrawOffsets.emplace_back(offset); + else + break; + } } template @@ -234,6 +253,7 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->Unkillable) .Process(this->LaserTrail_Type) .Process(this->Groups) + .Process(this->Animation_DrawOffsets) ; } @@ -438,3 +458,45 @@ bool AEAttachInfoTypeClass::Save(PhobosStreamWriter& stm) const } #pragma endregion(save/load) + +// AnimationDrawOffsetClass + +bool AnimationDrawOffsetClass::LoadFromINI(CCINIClass* pINI, const char* pSection, int index) +{ + INI_EX exINI(pINI); + char tempBuffer[48]; + + _snprintf_s(tempBuffer, sizeof(tempBuffer), "Animation.DrawOffset%d", index); + this->Offset.Read(exINI, pSection, tempBuffer); + + if (this->Offset.Get() == Point2D::Empty) + return false; + + _snprintf_s(tempBuffer, sizeof(tempBuffer), "Animation.DrawOffset%d.RequiredTypes", index); + this->RequiredTypes.Read(exINI, pSection, tempBuffer); + + return true; +} + +#pragma region(save/load) + +template +bool AnimationDrawOffsetClass::Serialize(T& stm) +{ + return stm + .Process(this->Offset) + .Process(this->RequiredTypes) + .Success(); +} + +bool AnimationDrawOffsetClass::Load(PhobosStreamReader& stm, bool registerForChange) +{ + return this->Serialize(stm); +} + +bool AnimationDrawOffsetClass::Save(PhobosStreamWriter& stm) const +{ + return const_cast(this)->Serialize(stm); +} + +#pragma endregion(save/load) diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index 373a144a10..a6e999a5b4 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -37,6 +37,8 @@ enum class ExpireWeaponCondition : unsigned char MAKE_ENUM_FLAGS(ExpireWeaponCondition); +class AnimationDrawOffsetClass; + class AttachEffectTypeClass final : public Enumerable { static std::unordered_map> GroupsMap; @@ -100,6 +102,7 @@ class AttachEffectTypeClass final : public Enumerable ValueableIdx LaserTrail_Type; std::vector Groups; + std::vector Animation_DrawOffsets; AttachEffectTypeClass(const char* const pTitle) : Enumerable(pTitle) , Duration { 0 } @@ -159,6 +162,7 @@ class AttachEffectTypeClass final : public Enumerable , Unkillable { false } , LaserTrail_Type { -1 } , Groups {} + , Animation_DrawOffsets {} {}; bool HasTint() const @@ -169,6 +173,7 @@ class AttachEffectTypeClass final : public Enumerable bool HasGroup(const std::string& groupID) const; bool HasGroups(const std::vector& groupIDs, bool requireAll) const; AnimTypeClass* GetCumulativeAnimation(int cumulativeCount) const; + bool HasAnim(); void HandleEvent(TechnoClass* pTarget) const; void LoadFromINI(CCINIClass* pINI); @@ -254,3 +259,25 @@ class AEAttachInfoTypeClass template bool Serialize(T& stm); }; + +// Container for AttachEffect animation draw offset info. +class AnimationDrawOffsetClass +{ +public: + Valueable Offset; + ValueableVector RequiredTypes; + + bool LoadFromINI(CCINIClass* pINI, const char* pSection, int index); + bool Load(PhobosStreamReader& stm, bool registerForChange); + bool Save(PhobosStreamWriter& stm) const; + + AnimationDrawOffsetClass() : + Offset { Point2D::Empty} + , RequiredTypes {} + { } + +private: + template + bool Serialize(T& stm); +}; +