diff --git a/CREDITS.md b/CREDITS.md index 6e74c1718c..cdfdd7395b 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -287,6 +287,7 @@ This page lists all the individual contributions to the project by their author. - Deploy priority filtering - Customizable paradrop missions - Guard range customizations + - 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 a2060522c5..3672f03028 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -52,7 +52,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. @@ -838,6 +837,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 83f2fafb5d..37d5eac0a8 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -31,6 +31,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. Note that this does not currently work correctly together with `Animation.HideIfAttachedWith`, animations hidden by this may not cause drawing offset rules to be updated even if they should. - `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`. @@ -110,6 +112,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 95190b8f5c..53285844b1 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -543,6 +543,8 @@ New: - Allow jumpjet climbing ignore building height (by TaranDahl) - Allow draw SuperWeapon timer as percentage (by NetsuNegi) - Customize particle system of parasite logic (by NetsuNegi) +- [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 2afd1793ce..91e8d23598 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 8a89b62f51..41aa3eeb83 100644 --- a/src/Ext/Anim/Body.h +++ b/src/Ext/Anim/Body.h @@ -27,6 +27,7 @@ class AnimExt bool DelayedFireRemoveOnNoDelay; bool IsAttachedEffectAnim; bool IsShieldIdleAnim; + Point2D AEDrawOffset; ExtData(AnimClass* OwnerObject) : Extension(OwnerObject) , DeathUnitFacing { 0 } @@ -41,6 +42,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 189e6e2309..8790ddf5f6 100644 --- a/src/Ext/Anim/Hooks.cpp +++ b/src/Ext/Anim/Hooks.cpp @@ -261,14 +261,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 5e3bbba517..b5d04b7116 100644 --- a/src/Ext/AnimType/Body.cpp +++ b/src/Ext/AnimType/Body.cpp @@ -80,6 +80,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"); @@ -139,6 +143,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 1001f9ec5c..c76bf8c125 100644 --- a/src/Ext/AnimType/Body.h +++ b/src/Ext/AnimType/Body.h @@ -28,6 +28,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; @@ -67,6 +71,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 c5413beed4..91a9578a13 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -1893,7 +1893,10 @@ void TechnoExt::ExtData::UpdateAttachEffects() } if (altered) + { this->RecalculateStatMultipliers(); + this->UpdateAEAnimDrawingLogic(); + } if (markForRedraw) pThis->MarkForRedraw(); @@ -1965,7 +1968,10 @@ void TechnoExt::ExtData::UpdateSelfOwnedAttachEffects() const int count = AttachEffectClass::Attach(pThis, pThis->Owner, pThis, pThis, pTypeExt->AttachEffects); if (altered && !count) + { this->RecalculateStatMultipliers(); + this->UpdateAEAnimDrawingLogic(); + } } // Updates CumulativeAnimations AE's on techno. @@ -2010,6 +2016,15 @@ void TechnoExt::ExtData::UpdateCumulativeAttachEffects(AttachEffectTypeClass* pA } } +// Update AttachEffect animation drawing logic. +void TechnoExt::ExtData::UpdateAEAnimDrawingLogic() +{ + for (auto const& attachEffect : this->AttachedEffects) + { + attachEffect->UpdateConditionalAnimDrawingLogic(); + } +} + // 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 ed4ebd3ce3..4f62646e55 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -484,27 +484,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; @@ -512,9 +506,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) { @@ -550,14 +544,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 7f178426b4..fff80da408 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -188,6 +188,7 @@ class TechnoExt void UpdateKeepTargetOnMove(); void UpdateWarpInDelay(); void UpdateCumulativeAttachEffects(AttachEffectTypeClass* pAttachEffectType, AttachEffectClass* pRemoved = nullptr); + void UpdateAEAnimDrawingLogic(); void RecalculateStatMultipliers(); void UpdateTemporal(); void UpdateMindControlAnim(); @@ -197,7 +198,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 92d5235e85..c495911bd5 100644 --- a/src/New/Entity/AttachEffectClass.cpp +++ b/src/New/Entity/AttachEffectClass.cpp @@ -95,7 +95,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--; @@ -279,6 +283,16 @@ void AttachEffectClass::AI_Temporal() } void AttachEffectClass::AnimCheck() +{ + if (!this->Animation && this->CanShowAnim()) + this->CreateAnim(); +} + +// Updates animation drawing logic that depends on state of other AE's on owner. +// Called from TechnoExt::UpdateAEAnimDrawingLogic() on all AE's on owner if AE's are attached/detached/expire etc. +// or if their animation state changes. Do not call directly. +// Take care to to not invoke recursion - should not call any functions that call TechnoExt::UpdateAEAnimDrawingLogic() such as CreateAnim/KillAnim here. +void AttachEffectClass::UpdateConditionalAnimDrawingLogic() { if (this->Type->Animation_HideIfAttachedWith.size() > 0) { @@ -286,7 +300,13 @@ void AttachEffectClass::AnimCheck() if (pTechnoExt->HasAttachedEffects(this->Type->Animation_HideIfAttachedWith, false, false, nullptr, nullptr, nullptr, nullptr)) { - this->KillAnim(); + // Inlined because calling KillAnim() would cause recursive calls to this function. + if (this->Animation) + { + this->Animation->UnInit(); + this->Animation = nullptr; + } + this->IsAnimHidden = true; return; } @@ -294,8 +314,18 @@ void AttachEffectClass::AnimCheck() this->IsAnimHidden = false; - if (!this->Animation && this->CanShowAnim()) - 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() @@ -404,6 +434,7 @@ void AttachEffectClass::CreateAnim() pAnim->RemainingIterations = 0xFFu; this->Animation = pAnim; + TechnoExt::ExtMap.Find(this->Techno)->UpdateAEAnimDrawingLogic(); } } @@ -413,6 +444,7 @@ void AttachEffectClass::KillAnim() { this->Animation->UnInit(); this->Animation = nullptr; + TechnoExt::ExtMap.Find(this->Techno)->UpdateAEAnimDrawingLogic(); } } @@ -750,6 +782,8 @@ AttachEffectClass* AttachEffectClass::CreateAndAttach(AttachEffectTypeClass* pTy if (!currentTypeCount && cumulative && pType->CumulativeAnimations.size() > 0) pAE->HasCumulativeAnim = true; + TechnoExt::ExtMap.Find(pTarget)->UpdateAEAnimDrawingLogic(); + return pAE; } @@ -827,7 +861,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->UpdateAEAnimDrawingLogic(); + } if (markForRedraw) pTarget->MarkForRedraw(); @@ -930,6 +968,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); const auto pTargetType = pTarget->GetTechnoType(); @@ -994,8 +1033,15 @@ void AttachEffectClass::TransferAttachedEffects(TechnoClass* pSource, TechnoClas pAE->Duration = attachEffect->Duration; } + transferCount++; it = pSourceExt->AttachedEffects.erase(it); } + + if (transferCount) + { + pSourceExt->UpdateAEAnimDrawingLogic(); + pTargetExt->UpdateAEAnimDrawingLogic(); + } } #pragma endregion diff --git a/src/New/Entity/AttachEffectClass.h b/src/New/Entity/AttachEffectClass.h index c596d6d709..da188224bb 100644 --- a/src/New/Entity/AttachEffectClass.h +++ b/src/New/Entity/AttachEffectClass.h @@ -18,11 +18,17 @@ class AttachEffectClass void AI(); void AI_Temporal(); + void UpdateConditionalAnimDrawingLogic(); void KillAnim(); void CreateAnim(); void UpdateCumulativeAnim(); void TransferCumulativeAnim(AttachEffectClass* pSource); + bool HasAnim() const + { + return this->Animation != nullptr; + } + bool CanShowAnim() const { return (this->IsOnline || this->Type->Animation_OfflineAction != AttachedAnimFlag::Hides) diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index 02bc5abb9e..04430d7802 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -54,6 +54,14 @@ std::vector AttachEffectTypeClass::GetTypesFromGroups(co return std::vector(types.begin(), types.end()); } +bool AttachEffectTypeClass::HasAnim() const +{ + if (this->Cumulative) + return this->CumulativeAnimations.size() > 0 || this->Animation != nullptr; + else + return this->Animation != nullptr; +} + void AttachEffectTypeClass::HandleEvent(TechnoClass* pTarget) { if (const auto pTag = pTarget->AttachedTag) @@ -166,6 +174,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 @@ -232,6 +251,7 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->Unkillable) .Process(this->LaserTrail_Type) .Process(this->Groups) + .Process(this->Animation_DrawOffsets) ; } @@ -439,3 +459,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 b0759a97c0..89c88aa75d 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -36,6 +36,8 @@ enum class ExpireWeaponCondition : unsigned char MAKE_ENUM_FLAGS(ExpireWeaponCondition); +class AnimationDrawOffsetClass; + class AttachEffectTypeClass final : public Enumerable { static std::unordered_map> GroupsMap; @@ -102,6 +104,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 } @@ -164,6 +167,7 @@ class AttachEffectTypeClass final : public Enumerable , Unkillable { false } , LaserTrail_Type { -1 } , Groups {} + , Animation_DrawOffsets {} {}; bool HasTint() const @@ -173,6 +177,7 @@ class AttachEffectTypeClass final : public Enumerable bool HasGroup(const std::string& groupID) const; bool HasGroups(const std::vector& groupIDs, bool requireAll) const; + bool HasAnim() const; AnimTypeClass* GetCumulativeAnimation(int cumulativeCount) const { @@ -272,3 +277,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); +}; +