From 32772e46c4e129f61000b0363784837cb89a4216 Mon Sep 17 00:00:00 2001 From: Coronia <2217891145@qq.com> Date: Thu, 4 Dec 2025 15:30:01 +0800 Subject: [PATCH 1/6] simple stack --- docs/New-or-Enhanced-Logics.md | 6 +- src/Ext/Techno/Body.Update.cpp | 83 ++++++++++++++---- src/Ext/Techno/Body.cpp | 7 +- src/Ext/WarheadType/Detonate.cpp | 4 +- src/Ext/WeaponType/Body.cpp | 4 +- src/New/Entity/AttachEffectClass.cpp | 113 ++++++++++++++++++------- src/New/Entity/AttachEffectClass.h | 1 + src/New/Type/AttachEffectTypeClass.cpp | 4 +- src/New/Type/AttachEffectTypeClass.h | 2 + 9 files changed, 167 insertions(+), 57 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 38342ff07d..97d8c7acf1 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -11,6 +11,7 @@ This page describes all the engine features that are either new and introduced b - If `Duration.ApplyFirepowerMult` set to true, the duration will multiply the invoker's firepower multipliers if it's not negative. Can't reduce duration to below 0 by a negative firepower multiplier. - If `Duration.ApplyArmorMultOnTarget` set to true, the duration will divide the target's armor multipliers if it's not negative. This'll also include `ArmorMultiplier` from its own and ignore `ArmorMultiplier.Allow/DisallowWarheads`. Can't reduce duration to below 0 by a negative armor multiplier. - `Cumulative`, if set to true, allows the same type of effect to be applied on same object multiple times, up to `Cumulative.MaxCount` number or with no limit if `Cumulative.MaxCount` is a negative number. If the target already has `Cumulative.MaxCount` number of the same effect applied on it, trying to attach another will refresh duration of the attached instance with shortest remaining duration. + - If `Cumulative.SimpleStack` also set to true, latter applied effects will be counted as a part of the first one, instead of its own entity. This is better for performance but will make the effect always refresh during a reapplication. - `Powered` controls whether or not the effect is rendered inactive if the object it is attached to is deactivated (`PoweredUnit` or affected by EMP) or on low power. What happens to animation is controlled by `Animation.OfflineAction`. - `DiscardOn` accepts a list of values corresponding to conditions where the attached effect should be discarded. Defaults to `none`, meaning it is never discarded. - `entry`: Discard on exiting the map when entering transports or buildings etc. @@ -70,8 +71,8 @@ This page describes all the engine features that are either new and introduced b - AttachEffectTypes can be attached to objects via Warheads using `AttachEffect.AttachTypes`. - `AttachEffect.DurationOverrides` can be used to override the default durations. Duration matching the position in `AttachTypes` is used for that type, or the last listed duration if not available. - - `AttachEffect.CumulativeRefreshAll` if set to true makes it so that trying to attach `Cumulative=true` effect to a target that already has `Cumulative.MaxCount` amount of effects will refresh duration of all attached effects of the same type instead of only the one with shortest remaining duration. If `AttachEffect.CumulativeRefreshAll.OnAttach` is also set to true, this refresh applies even if the target does not have maximum allowed amount of effects of same type. - - `AttachEffect.CumulativeRefreshSameSourceOnly` controls whether or not trying to apply `Cumulative=true` effect on target requires any existing effects of same type to come from same Warhead by same firer for them to be eligible for duration refresh. + - `AttachEffect.CumulativeRefreshAll` if set to true makes it so that trying to attach `Cumulative=true` effect to a target that already has `Cumulative.MaxCount` amount of effects will refresh duration of all attached effects of the same type instead of only the one with shortest remaining duration. If `AttachEffect.CumulativeRefreshAll.OnAttach` is also set to true, this refresh applies even if the target does not have maximum allowed amount of effects of same type. These toggles dont' work on effects with `Cumulative.SimpleStack=true`, which always refresh during a reapplication. + - `AttachEffect.CumulativeRefreshSameSourceOnly` controls whether or not trying to apply `Cumulative=true` effect on target requires any existing effects of same type to come from same Warhead by same firer for them to be eligible for duration refresh. Doesn't work on effects with `Cumulative.SimpleStack=true`. - Attached Effects can be removed from objects by Warheads using `AttachEffect.RemoveTypes` or `AttachEffect.RemoveGroups`. - `AttachEffect.CumulativeRemoveMinCounts` sets minimum number of active instaces per `RemoveTypes`/`RemoveGroups` required for `Cumulative=true` types to be removed. - `AttachEffect.CumulativeRemoveMaxCounts` sets maximum number of active instaces per `RemoveTypes`/`RemoveGroups` for `Cumulative=true` that are removed at once by this Warhead. @@ -94,6 +95,7 @@ Duration=0 ; integer - game frames or ne Duration.ApplyFirepowerMult=false ; boolean Duration.ApplyArmorMultOnTarget=false ; boolean Cumulative=false ; boolean +Cumulative.SimpleStack=false ; boolean Cumulative.MaxCount=-1 ; integer Powered=false ; boolean DiscardOn=none ; List of discard condition enumeration (none|entry|move|stationary|drain|inrange|outofrange) diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index bb54d15d5a..5fc28a64d5 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -1805,6 +1805,19 @@ void TechnoExt::ExtData::UpdateAttachEffects() std::vector>::iterator it; std::vector> expireWeapons; + auto handleExpireWeapon = [&](WeaponTypeClass* pWeapon, TechnoClass* pTarget, TechnoClass* pInvoker, bool invokerOwner) + { + if (invokerOwner) + { + if (pInvoker) + expireWeapons.emplace_back(pWeapon, pInvoker); + } + else + { + expireWeapons.emplace_back(pWeapon, pTarget); + } + }; + for (it = this->AttachedEffects.begin(); it != this->AttachedEffects.end(); ) { auto const attachEffect = it->get(); @@ -1834,19 +1847,31 @@ void TechnoExt::ExtData::UpdateAttachEffects() if (pType->Cumulative && pType->CumulativeAnimations.size() > 0) this->UpdateCumulativeAttachEffects(attachEffect->GetType(), attachEffect); - if (pType->ExpireWeapon && ((hasExpired && (pType->ExpireWeapon_TriggerOn & ExpireWeaponCondition::Expire) != ExpireWeaponCondition::None) + auto const pWeapon = pType->ExpireWeapon; + + if (pWeapon && ((hasExpired && (pType->ExpireWeapon_TriggerOn & ExpireWeaponCondition::Expire) != ExpireWeaponCondition::None) || (shouldDiscard && (pType->ExpireWeapon_TriggerOn & ExpireWeaponCondition::Discard) != ExpireWeaponCondition::None))) { - if (!pType->Cumulative || !pType->ExpireWeapon_CumulativeOnlyOnce || this->GetAttachedEffectCumulativeCount(pType) < 1) + const bool simpleStack = pType->Cumulative && pType->Cumulative_SimpleStack; + const bool invokerOwner = pType->ExpireWeapon_UseInvokerAsOwner; + auto const pInvoker = attachEffect->GetInvoker(); + + if (!simpleStack || !pType->ExpireWeapon_CumulativeOnlyOnce || this->GetAttachedEffectCumulativeCount(pType) < 1) { - if (pType->ExpireWeapon_UseInvokerAsOwner) + handleExpireWeapon(pWeapon, pThis, pInvoker, invokerOwner); + } + else if (simpleStack) + { + if (pType->ExpireWeapon_CumulativeOnlyOnce) { - if (auto const pInvoker = attachEffect->GetInvoker()) - expireWeapons.push_back(std::make_pair(pType->ExpireWeapon, pInvoker)); + handleExpireWeapon(pWeapon, pThis, pInvoker, invokerOwner); } else { - expireWeapons.push_back(std::make_pair(pType->ExpireWeapon, pThis)); + for (int i = 0; i < attachEffect->SimpleStackCount; i++) + { + handleExpireWeapon(pWeapon, pThis, pInvoker, invokerOwner); + } } } } @@ -1891,6 +1916,19 @@ void TechnoExt::ExtData::UpdateSelfOwnedAttachEffects() std::vector> expireWeapons; bool altered = false; + auto handleExpireWeapon = [&](WeaponTypeClass* pWeapon, TechnoClass* pTarget, TechnoClass* pInvoker, bool invokerOwner) + { + if (invokerOwner) + { + if (pInvoker) + expireWeapons.emplace_back(pWeapon, pInvoker); + } + else + { + expireWeapons.emplace_back(pWeapon, pTarget); + } + }; + // Delete ones on old type and not on current. for (it = this->AttachedEffects.begin(); it != this->AttachedEffects.end(); ) { @@ -1902,18 +1940,30 @@ void TechnoExt::ExtData::UpdateSelfOwnedAttachEffects() if (remove) { - if (pType->ExpireWeapon && (pType->ExpireWeapon_TriggerOn & ExpireWeaponCondition::Expire) != ExpireWeaponCondition::None) + auto const pWeapon = pType->ExpireWeapon; + + if (pWeapon && (pType->ExpireWeapon_TriggerOn & ExpireWeaponCondition::Expire) != ExpireWeaponCondition::None) { - if (!pType->Cumulative || !pType->ExpireWeapon_CumulativeOnlyOnce || this->GetAttachedEffectCumulativeCount(pType) < 1) + const bool simpleStack = pType->Cumulative && pType->Cumulative_SimpleStack; + const bool invokerOwner = pType->ExpireWeapon_UseInvokerAsOwner; + auto const pInvoker = attachEffect->GetInvoker(); + + if (!simpleStack || !pType->ExpireWeapon_CumulativeOnlyOnce || this->GetAttachedEffectCumulativeCount(pType) < 1) { - if (pType->ExpireWeapon_UseInvokerAsOwner) + handleExpireWeapon(pWeapon, pThis, pInvoker, invokerOwner); + } + else if (simpleStack) + { + if (pType->ExpireWeapon_CumulativeOnlyOnce) { - if (auto const pInvoker = attachEffect->GetInvoker()) - expireWeapons.push_back(std::make_pair(pType->ExpireWeapon, pInvoker)); + handleExpireWeapon(pWeapon, pThis, pInvoker, invokerOwner); } else { - expireWeapons.push_back(std::make_pair(pType->ExpireWeapon, pThis)); + for (int i = 0; i < attachEffect->SimpleStackCount; i++) + { + handleExpireWeapon(pWeapon, pThis, pInvoker, invokerOwner); + } } } } @@ -2012,15 +2062,16 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() continue; auto const type = attachEffect->GetType(); - firepower *= type->FirepowerMultiplier; - speed *= type->SpeedMultiplier; + const int simpleStackCount = type->Cumulative_SimpleStack; + firepower *= std::pow(type->FirepowerMultiplier, simpleStackCount); + speed *= std::pow(type->SpeedMultiplier, simpleStackCount); if (type->ArmorMultiplier != 1.0 && (type->ArmorMultiplier_AllowWarheads.size() > 0 || type->ArmorMultiplier_DisallowWarheads.size() > 0)) hasRestrictedArmorMultipliers = true; else - armor *= type->ArmorMultiplier; + armor *= std::pow(type->ArmorMultiplier, simpleStackCount); - ROF *= type->ROFMultiplier; + ROF *= std::pow(type->ROFMultiplier, simpleStackCount); cloak |= type->Cloakable; forceDecloak |= type->ForceDecloak; disableWeapons |= type->DisableWeapons; diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 121ece2f9a..3749e46118 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -498,6 +498,8 @@ bool TechnoExt::ExtData::HasAttachedEffects(std::vector for (auto const& type : attachEffectTypes) { + const bool cumulative = type->Cumulative; + for (auto const& attachEffect : this->AttachedEffects) { if (attachEffect->GetType() == type && attachEffect->IsActive()) @@ -508,15 +510,16 @@ 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 (cumulative && (minSize > 0 || maxSize > 0)) { - const int cumulativeCount = this->GetAttachedEffectCumulativeCount(type, ignoreSameSource, pInvoker, pSource); + const int cumulativeCount = type->Cumulative_SimpleStack? attachEffect->SimpleStackCount : this->GetAttachedEffectCumulativeCount(type, ignoreSameSource, pInvoker, pSource); if (minSize > 0) { if (cumulativeCount < minCounts->at(typeCounter - 1 >= minSize ? minSize - 1 : typeCounter - 1)) continue; } + if (maxSize > 0) { if (cumulativeCount > maxCounts->at(typeCounter - 1 >= maxSize ? maxSize - 1 : typeCounter - 1)) diff --git a/src/Ext/WarheadType/Detonate.cpp b/src/Ext/WarheadType/Detonate.cpp index 2cfbc5f6a5..93eea96d26 100644 --- a/src/Ext/WarheadType/Detonate.cpp +++ b/src/Ext/WarheadType/Detonate.cpp @@ -671,8 +671,8 @@ double WarheadTypeExt::ExtData::GetCritChance(TechnoClass* pFirer) const if (disallowWarheads.size() > 0 && disallowWarheads.Contains(pObject)) continue; - critChance = critChance * Math::max(pType->Crit_Multiplier, 0); - extraChance += pType->Crit_ExtraChance; + critChance = critChance * Math::max(std::pow(pType->Crit_Multiplier, attachEffect->SimpleStackCount), 0); + extraChance += pType->Crit_ExtraChance * attachEffect->SimpleStackCount; } return critChance + extraChance; diff --git a/src/Ext/WeaponType/Body.cpp b/src/Ext/WeaponType/Body.cpp index e3cd9c1d5c..7933b8ff05 100644 --- a/src/Ext/WeaponType/Body.cpp +++ b/src/Ext/WeaponType/Body.cpp @@ -346,8 +346,8 @@ int WeaponTypeExt::GetRangeWithModifiers(WeaponTypeClass* pThis, TechnoClass* pF if (type->WeaponRange_DisallowWeapons.size() > 0 && type->WeaponRange_DisallowWeapons.Contains(pThis)) continue; - range = static_cast(range * Math::max(type->WeaponRange_Multiplier, 0.0)); - extraRange += type->WeaponRange_ExtraRange; + range = static_cast(range * Math::max(std::pow(type->WeaponRange_Multiplier, attachEffect->SimpleStackCount), 0.0)); + extraRange += type->WeaponRange_ExtraRange * attachEffect->SimpleStackCount; } range += static_cast(extraRange * Unsorted::LeptonsPerCell); diff --git a/src/New/Entity/AttachEffectClass.cpp b/src/New/Entity/AttachEffectClass.cpp index bcb452cb20..5d81529873 100644 --- a/src/New/Entity/AttachEffectClass.cpp +++ b/src/New/Entity/AttachEffectClass.cpp @@ -23,6 +23,7 @@ AttachEffectClass::AttachEffectClass() , NeedsRecalculateStat { false } , LastDiscardCheckFrame { -1 } , LastDiscardCheckValue { false } + , SimpleStackCount { 1 } { this->HasInitialized = false; AttachEffectClass::Array.emplace_back(this); @@ -48,6 +49,7 @@ AttachEffectClass::AttachEffectClass(AttachEffectTypeClass* pType, TechnoClass* , NeedsRecalculateStat { false } , LastDiscardCheckFrame { -1 } , LastDiscardCheckValue { false } + , SimpleStackCount { 1 } { this->HasInitialized = false; @@ -391,7 +393,7 @@ void AttachEffectClass::CreateAnim() if (!this->HasCumulativeAnim) return; - const int count = TechnoExt::ExtMap.Find(pTechno)->GetAttachedEffectCumulativeCount(pType); + const int count = pType->Cumulative_SimpleStack ? this->SimpleStackCount : TechnoExt::ExtMap.Find(pTechno)->GetAttachedEffectCumulativeCount(pType); pAnimType = pType->GetCumulativeAnimation(count); } else @@ -442,7 +444,7 @@ void AttachEffectClass::UpdateCumulativeAnim() return; const auto pType = this->Type; - const int count = TechnoExt::ExtMap.Find(this->Techno)->GetAttachedEffectCumulativeCount(pType); + const int count = pType->Cumulative_SimpleStack ? this->SimpleStackCount : TechnoExt::ExtMap.Find(this->Techno)->GetAttachedEffectCumulativeCount(pType); if (count < 1) { @@ -694,7 +696,8 @@ AttachEffectClass* AttachEffectClass::CreateAndAttach(AttachEffectTypeClass* pTy return nullptr; int currentTypeCount = 0; - const bool cumulative = pType->Cumulative && checkCumulative; + const bool simpleStack = pType->Cumulative && pType->Cumulative_SimpleStack; + const bool cumulative = pType->Cumulative && !pType->Cumulative_SimpleStack && checkCumulative; AttachEffectClass* match = nullptr; std::vector cumulativeMatches; cumulativeMatches.reserve(targetAEs.size()); @@ -709,8 +712,17 @@ AttachEffectClass* AttachEffectClass::CreateAndAttach(AttachEffectTypeClass* pTy if (!cumulative) { - match = attachEffect; - break; + if (simpleStack) + { + attachEffect->SimpleStackCount++; + + if (pType->CumulativeAnimations.size() > 0) + attachEffect->HasCumulativeAnim = true; + } + + attachEffect->RefreshDuration(attachParams.DurationOverride); + AttachEffectTypeClass::HandleEvent(pTarget); + return nullptr; } else if (!attachParams.CumulativeRefreshSameSourceOnly || (attachEffect->Source == pSource && attachEffect->Invoker == pInvoker)) { @@ -750,23 +762,13 @@ AttachEffectClass* AttachEffectClass::CreateAndAttach(AttachEffectTypeClass* pTy } } - if (!cumulative && match) - { - match->RefreshDuration(attachParams.DurationOverride); - AttachEffectTypeClass::HandleEvent(pTarget); - } - else - { - targetAEs.emplace_back(std::make_unique(pType, pTarget, pInvokerHouse, pInvoker, pSource, attachParams.DurationOverride, attachParams.Delay, attachParams.InitialDelay, attachParams.RecreationDelay)); - auto const pAE = targetAEs.back().get(); + targetAEs.emplace_back(std::make_unique(pType, pTarget, pInvokerHouse, pInvoker, pSource, attachParams.DurationOverride, attachParams.Delay, attachParams.InitialDelay, attachParams.RecreationDelay)); + auto const pAE = targetAEs.back().get(); - if (!currentTypeCount && cumulative && pType->CumulativeAnimations.size() > 0) - pAE->HasCumulativeAnim = true; + if (!currentTypeCount && cumulative && pType->CumulativeAnimations.size() > 0) + pAE->HasCumulativeAnim = true; - return pAE; - } - - return nullptr; + return pAE; } /// @@ -865,19 +867,41 @@ int AttachEffectClass::RemoveAllOfType(AttachEffectTypeClass* pType, TechnoClass return 0; auto const pTargetExt = TechnoExt::ExtMap.Find(pTarget); - int detachedCount = 0; + const bool cumulative = pType->Cumulative; + const bool simpleStack = cumulative && pType->Cumulative_SimpleStack; + const bool complexStack = cumulative && !pType->Cumulative_SimpleStack; int stackCount = -1; - if (pType->Cumulative) + if (complexStack) + { stackCount = pTargetExt->GetAttachedEffectCumulativeCount(pType); - if (minCount > 0 && stackCount > -1 && pType->Cumulative && minCount > stackCount) - return 0; + if (minCount > 0 && stackCount > -1 && minCount > stackCount) + return 0; + } + int detachedCount = 0; auto const targetAEs = &pTargetExt->AttachedEffects; + auto const pWeapon = pType->ExpireWeapon; + const bool triggerOnDiscard = (pType->ExpireWeapon_TriggerOn & ExpireWeaponCondition::Remove) != ExpireWeaponCondition::None; + const bool onlyOnce = pType->ExpireWeapon_CumulativeOnlyOnce; + const bool invokerOwner = pType->ExpireWeapon_UseInvokerAsOwner; std::vector>::iterator it; std::vector> expireWeapons; + auto handleExpireWeapon = [&](TechnoClass* pInvoker) + { + if (invokerOwner) + { + if (pInvoker) + expireWeapons.emplace_back(pWeapon, pInvoker); + } + else + { + expireWeapons.emplace_back(pWeapon, pTarget); + } + }; + for (it = targetAEs->begin(); it != targetAEs->end(); ) { if (maxCount > 0 && detachedCount >= maxCount) @@ -889,24 +913,48 @@ int AttachEffectClass::RemoveAllOfType(AttachEffectTypeClass* pType, TechnoClass { detachedCount++; - if (pType->ExpireWeapon && (pType->ExpireWeapon_TriggerOn & ExpireWeaponCondition::Remove) != ExpireWeaponCondition::None) + if (simpleStack) { + detachedCount = attachEffect->SimpleStackCount; + + if (minCount > 0 && minCount > detachedCount) + return 0; + + if (maxCount > 0 && detachedCount > maxCount) + { + detachedCount = maxCount; + attachEffect->SimpleStackCount -= maxCount; + stackCount = attachEffect->SimpleStackCount; + } + } + + if (pWeapon && triggerOnDiscard) + { + auto const pInvoker = attachEffect->Invoker; + // can't be GetAttachedEffectCumulativeCount(pType) < 2, or inactive AE might make it stack more than once - if (!pType->Cumulative || !pType->ExpireWeapon_CumulativeOnlyOnce || stackCount == 1) + if (!simpleStack || !onlyOnce || stackCount == 1) + { + handleExpireWeapon(pInvoker); + } + else if (simpleStack) // handle ExpireWeapon for Cumulative.SimpleStack { - if (pType->ExpireWeapon_UseInvokerAsOwner) + if (onlyOnce) { - if (auto const pInvoker = attachEffect->Invoker) - expireWeapons.push_back(std::make_pair(pType->ExpireWeapon, pInvoker)); + if (stackCount <= 0) + handleExpireWeapon(pInvoker); } else { - expireWeapons.push_back(std::make_pair(pType->ExpireWeapon, pTarget)); + for (int i = 0; i < detachedCount; i++) + { + handleExpireWeapon(pInvoker); + } } } } - if (pType->Cumulative && pType->CumulativeAnimations.size() > 0) + if (complexStack && pType->CumulativeAnimations.size() > 0) pTargetExt->UpdateCumulativeAttachEffects(pType, attachEffect); if (attachEffect->ResetIfRecreatable()) @@ -917,7 +965,7 @@ int AttachEffectClass::RemoveAllOfType(AttachEffectTypeClass* pType, TechnoClass it = targetAEs->erase(it); - if (!pType->Cumulative) + if (!cumulative) break; stackCount--; @@ -1049,6 +1097,7 @@ bool AttachEffectClass::Serialize(T& Stm) .Process(this->LastActiveStat) .Process(this->LaserTrail) .Process(this->NeedsRecalculateStat) + .Process(this->SimpleStackCount) .Success(); } diff --git a/src/New/Entity/AttachEffectClass.h b/src/New/Entity/AttachEffectClass.h index c596d6d709..021d2c5e6d 100644 --- a/src/New/Entity/AttachEffectClass.h +++ b/src/New/Entity/AttachEffectClass.h @@ -101,6 +101,7 @@ class AttachEffectClass bool HasCumulativeAnim; bool ShouldBeDiscarded; bool NeedsRecalculateStat; + int SimpleStackCount; }; // Container for TechnoClass-specific AttachEffect fields. diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index c4a4d0c208..55b8229071 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -57,7 +57,7 @@ AnimTypeClass* AttachEffectTypeClass::GetCumulativeAnimation(int cumulativeCount if (cumulativeCount < 0 || this->CumulativeAnimations.size() < 1) return nullptr; - int index = static_cast(cumulativeCount) >= this->CumulativeAnimations.size() ? this->CumulativeAnimations.size() - 1 : cumulativeCount - 1; + const int index = static_cast(cumulativeCount) >= this->CumulativeAnimations.size() ? this->CumulativeAnimations.size() - 1 : cumulativeCount - 1; return this->CumulativeAnimations.at(index); } @@ -105,6 +105,7 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->Duration_ApplyFirepowerMult.Read(exINI, pSection, "Duration.ApplyFirepowerMult"); this->Duration_ApplyArmorMultOnTarget.Read(exINI, pSection, "Duration.ApplyArmorMultOnTarget"); this->Cumulative.Read(exINI, pSection, "Cumulative"); + this->Cumulative_SimpleStack.Read(exINI, pSection, "SimpleStack"); this->Cumulative_MaxCount.Read(exINI, pSection, "Cumulative.MaxCount"); this->Powered.Read(exINI, pSection, "Powered"); this->DiscardOn.Read(exINI, pSection, "DiscardOn"); @@ -184,6 +185,7 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->Duration_ApplyFirepowerMult) .Process(this->Duration_ApplyArmorMultOnTarget) .Process(this->Cumulative) + .Process(this->Cumulative_SimpleStack) .Process(this->Cumulative_MaxCount) .Process(this->Powered) .Process(this->DiscardOn) diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index 95a7fa216d..ad92a64fd0 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -46,6 +46,7 @@ class AttachEffectTypeClass final : public Enumerable Valueable Duration_ApplyFirepowerMult; Valueable Duration_ApplyArmorMultOnTarget; Valueable Cumulative; + Valueable Cumulative_SimpleStack; Valueable Cumulative_MaxCount; Valueable Powered; Valueable DiscardOn; @@ -109,6 +110,7 @@ class AttachEffectTypeClass final : public Enumerable , Duration_ApplyFirepowerMult { false } , Duration_ApplyArmorMultOnTarget { false } , Cumulative { false } + , Cumulative_SimpleStack { false } , Cumulative_MaxCount { -1 } , Powered { false } , DiscardOn { DiscardCondition::None } From ade1ce9c8f50cae38d2298bcbd2364d0787d365f Mon Sep 17 00:00:00 2001 From: Coronia <2217891145@qq.com> Date: Thu, 4 Dec 2025 16:51:40 +0800 Subject: [PATCH 2/6] optimize HasAttachedEffects --- src/Ext/Techno/Body.cpp | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 3749e46118..e639ededba 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -495,10 +495,15 @@ bool TechnoExt::ExtData::HasAttachedEffects(std::vector unsigned int foundCount = 0; unsigned int typeCounter = 1; const bool checkSource = ignoreSameSource && pInvoker && pSource; + const unsigned int minSize = minCounts ? minCounts->size() : 0; + const unsigned int maxSize = maxCounts ? maxCounts->size() : 0; + const bool hasMinMax = minSize > 0 || maxSize > 0; for (auto const& type : attachEffectTypes) { const bool cumulative = type->Cumulative; + const bool simpleStack = type->Cumulative_SimpleStack; + int cumulativeCount = -1; for (auto const& attachEffect : this->AttachedEffects) { @@ -507,23 +512,36 @@ bool TechnoExt::ExtData::HasAttachedEffects(std::vector if (checkSource && attachEffect->IsFromSource(pInvoker, pSource)) continue; - const unsigned int minSize = minCounts ? minCounts->size() : 0; - const unsigned int maxSize = maxCounts ? maxCounts->size() : 0; - - if (cumulative && (minSize > 0 || maxSize > 0)) + if (cumulative && hasMinMax) { - const int cumulativeCount = type->Cumulative_SimpleStack? attachEffect->SimpleStackCount : this->GetAttachedEffectCumulativeCount(type, ignoreSameSource, pInvoker, pSource); + if (cumulativeCount == -1) + { + if (simpleStack) + cumulativeCount = attachEffect->SimpleStackCount; + else + cumulativeCount = this->GetAttachedEffectCumulativeCount(type, ignoreSameSource, pInvoker, pSource); + } if (minSize > 0) { - if (cumulativeCount < minCounts->at(typeCounter - 1 >= minSize ? minSize - 1 : typeCounter - 1)) + if (cumulativeCount < minSize > 0 ? minCounts->at(typeCounter - 1 >= minSize ? minSize - 1 : typeCounter - 1) : 0) + { + if (simpleStack) + break; + continue; + } } if (maxSize > 0) { - if (cumulativeCount > maxCounts->at(typeCounter - 1 >= maxSize ? maxSize - 1 : typeCounter - 1)) + if (cumulativeCount > maxSize > 0 ? maxCounts->at(typeCounter - 1 >= maxSize ? maxSize - 1 : typeCounter - 1) : 0) + { + if (simpleStack) + break; + continue; + } } } From e6475fa0b3731c4e05d673815401ff43d931605a Mon Sep 17 00:00:00 2001 From: Coronia <2217891145@qq.com> Date: Thu, 4 Dec 2025 17:10:22 +0800 Subject: [PATCH 3/6] fix --- docs/New-or-Enhanced-Logics.md | 2 +- src/Ext/Techno/Body.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 97d8c7acf1..7abaabe6bc 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -71,7 +71,7 @@ This page describes all the engine features that are either new and introduced b - AttachEffectTypes can be attached to objects via Warheads using `AttachEffect.AttachTypes`. - `AttachEffect.DurationOverrides` can be used to override the default durations. Duration matching the position in `AttachTypes` is used for that type, or the last listed duration if not available. - - `AttachEffect.CumulativeRefreshAll` if set to true makes it so that trying to attach `Cumulative=true` effect to a target that already has `Cumulative.MaxCount` amount of effects will refresh duration of all attached effects of the same type instead of only the one with shortest remaining duration. If `AttachEffect.CumulativeRefreshAll.OnAttach` is also set to true, this refresh applies even if the target does not have maximum allowed amount of effects of same type. These toggles dont' work on effects with `Cumulative.SimpleStack=true`, which always refresh during a reapplication. + - `AttachEffect.CumulativeRefreshAll` if set to true makes it so that trying to attach `Cumulative=true` effect to a target that already has `Cumulative.MaxCount` amount of effects will refresh duration of all attached effects of the same type instead of only the one with shortest remaining duration. If `AttachEffect.CumulativeRefreshAll.OnAttach` is also set to true, this refresh applies even if the target does not have maximum allowed amount of effects of same type. These toggles don't work on effects with `Cumulative.SimpleStack=true`, which always refresh during a reapplication. - `AttachEffect.CumulativeRefreshSameSourceOnly` controls whether or not trying to apply `Cumulative=true` effect on target requires any existing effects of same type to come from same Warhead by same firer for them to be eligible for duration refresh. Doesn't work on effects with `Cumulative.SimpleStack=true`. - Attached Effects can be removed from objects by Warheads using `AttachEffect.RemoveTypes` or `AttachEffect.RemoveGroups`. - `AttachEffect.CumulativeRemoveMinCounts` sets minimum number of active instaces per `RemoveTypes`/`RemoveGroups` required for `Cumulative=true` types to be removed. diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index e639ededba..6234452f92 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -524,7 +524,7 @@ bool TechnoExt::ExtData::HasAttachedEffects(std::vector if (minSize > 0) { - if (cumulativeCount < minSize > 0 ? minCounts->at(typeCounter - 1 >= minSize ? minSize - 1 : typeCounter - 1) : 0) + if (cumulativeCount < minCounts->at(typeCounter - 1 >= minSize ? minSize - 1 : typeCounter - 1)) { if (simpleStack) break; @@ -535,7 +535,7 @@ bool TechnoExt::ExtData::HasAttachedEffects(std::vector if (maxSize > 0) { - if (cumulativeCount > maxSize > 0 ? maxCounts->at(typeCounter - 1 >= maxSize ? maxSize - 1 : typeCounter - 1) : 0) + if (cumulativeCount > maxCounts->at(typeCounter - 1 >= maxSize ? maxSize - 1 : typeCounter - 1)) { if (simpleStack) break; From 465f2242961a03c9d2b57f60089ad41efc4e8076 Mon Sep 17 00:00:00 2001 From: Coronia <2217891145@qq.com> Date: Thu, 4 Dec 2025 23:58:39 +0800 Subject: [PATCH 4/6] fix Cumulative.MaxCount --- src/New/Entity/AttachEffectClass.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/New/Entity/AttachEffectClass.cpp b/src/New/Entity/AttachEffectClass.cpp index 5d81529873..03a0ca1bcd 100644 --- a/src/New/Entity/AttachEffectClass.cpp +++ b/src/New/Entity/AttachEffectClass.cpp @@ -712,7 +712,7 @@ AttachEffectClass* AttachEffectClass::CreateAndAttach(AttachEffectTypeClass* pTy if (!cumulative) { - if (simpleStack) + if (simpleStack && (pType->Cumulative_MaxCount < 0 || attachEffect->SimpleStackCount < pType->Cumulative_MaxCount)) { attachEffect->SimpleStackCount++; @@ -734,7 +734,7 @@ AttachEffectClass* AttachEffectClass::CreateAndAttach(AttachEffectTypeClass* pTy } } - if (cumulativeMatches.size() > 0) + if (cumulative) { if (pType->Cumulative_MaxCount >= 0 && currentTypeCount >= pType->Cumulative_MaxCount) { @@ -745,7 +745,7 @@ AttachEffectClass* AttachEffectClass::CreateAndAttach(AttachEffectTypeClass* pTy ae->RefreshDuration(attachParams.DurationOverride); } } - else + else if (match) { match->RefreshDuration(attachParams.DurationOverride); } From 3f4149c85cbbf5e8b4bbeda5d91c3bef9c4aab0f Mon Sep 17 00:00:00 2001 From: Coronia <2217891145@qq.com> Date: Fri, 5 Dec 2025 10:49:50 +0800 Subject: [PATCH 5/6] adjust cumulative anims --- src/Ext/Techno/Body.Update.cpp | 2 +- src/New/Entity/AttachEffectClass.cpp | 7 ++++++- src/New/Type/AttachEffectTypeClass.cpp | 10 ---------- src/New/Type/AttachEffectTypeClass.h | 11 ++++++++++- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index 5fc28a64d5..5caedb00e5 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -1844,7 +1844,7 @@ void TechnoExt::ExtData::UpdateAttachEffects() if (pType->HasTint()) markForRedraw = true; - if (pType->Cumulative && pType->CumulativeAnimations.size() > 0) + if (pType->Cumulative && !pType->Cumulative_SimpleStack && pType->CumulativeAnimations.size() > 0) this->UpdateCumulativeAttachEffects(attachEffect->GetType(), attachEffect); auto const pWeapon = pType->ExpireWeapon; diff --git a/src/New/Entity/AttachEffectClass.cpp b/src/New/Entity/AttachEffectClass.cpp index 03a0ca1bcd..73682572f6 100644 --- a/src/New/Entity/AttachEffectClass.cpp +++ b/src/New/Entity/AttachEffectClass.cpp @@ -641,7 +641,12 @@ int AttachEffectClass::Attach(TechnoClass* pTarget, HouseClass* pInvokerHouse, T markForRedraw = true; if (pType->Cumulative && pType->CumulativeAnimations.size() > 0) - pTargetExt->UpdateCumulativeAttachEffects(pType); + { + if (!pType->Cumulative_SimpleStack) + pTargetExt->UpdateCumulativeAttachEffects(pType); + else + pAE->UpdateCumulativeAnim(); + } } } } diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index 55b8229071..5e6a75ab28 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -52,16 +52,6 @@ std::vector AttachEffectTypeClass::GetTypesFromGroups(co return std::vector(types.begin(), types.end()); } -AnimTypeClass* AttachEffectTypeClass::GetCumulativeAnimation(int cumulativeCount) const -{ - if (cumulativeCount < 0 || this->CumulativeAnimations.size() < 1) - return nullptr; - - const int index = static_cast(cumulativeCount) >= this->CumulativeAnimations.size() ? this->CumulativeAnimations.size() - 1 : cumulativeCount - 1; - - return this->CumulativeAnimations.at(index); -} - void AttachEffectTypeClass::HandleEvent(TechnoClass* pTarget) { if (const auto pTag = pTarget->AttachedTag) diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index ad92a64fd0..ab8848b531 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -176,7 +176,16 @@ 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; + + AnimTypeClass* GetCumulativeAnimation(int cumulativeCount) const + { + if (cumulativeCount < 0 || this->CumulativeAnimations.size() < 1) + return nullptr; + + const int index = static_cast(cumulativeCount) >= this->CumulativeAnimations.size() ? this->CumulativeAnimations.size() - 1 : cumulativeCount - 1; + + return this->CumulativeAnimations.at(index); + } void LoadFromINI(CCINIClass* pINI); void LoadFromStream(PhobosStreamReader& Stm); From 5c1819922f18a97111650afc49f4adc5a566d7df Mon Sep 17 00:00:00 2001 From: Coronia <2217891145@qq.com> Date: Fri, 5 Dec 2025 12:10:34 +0800 Subject: [PATCH 6/6] revert some tweaks --- src/Ext/Techno/Body.cpp | 50 +++++++++++++++------------- src/New/Entity/AttachEffectClass.cpp | 8 ++--- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 6234452f92..aa90704ad2 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -495,14 +495,10 @@ bool TechnoExt::ExtData::HasAttachedEffects(std::vector unsigned int foundCount = 0; unsigned int typeCounter = 1; const bool checkSource = ignoreSameSource && pInvoker && pSource; - const unsigned int minSize = minCounts ? minCounts->size() : 0; - const unsigned int maxSize = maxCounts ? maxCounts->size() : 0; - const bool hasMinMax = minSize > 0 || maxSize > 0; for (auto const& type : attachEffectTypes) { const bool cumulative = type->Cumulative; - const bool simpleStack = type->Cumulative_SimpleStack; int cumulativeCount = -1; for (auto const& attachEffect : this->AttachedEffects) @@ -512,35 +508,41 @@ bool TechnoExt::ExtData::HasAttachedEffects(std::vector if (checkSource && attachEffect->IsFromSource(pInvoker, pSource)) continue; - if (cumulative && hasMinMax) + if (cumulative) { - if (cumulativeCount == -1) - { - if (simpleStack) - cumulativeCount = attachEffect->SimpleStackCount; - else - cumulativeCount = this->GetAttachedEffectCumulativeCount(type, ignoreSameSource, pInvoker, pSource); - } + const unsigned int minSize = minCounts ? minCounts->size() : 0; + const unsigned int maxSize = maxCounts ? maxCounts->size() : 0; - if (minSize > 0) + if (minSize > 0 || maxSize > 0) { - if (cumulativeCount < minCounts->at(typeCounter - 1 >= minSize ? minSize - 1 : typeCounter - 1)) + if (cumulativeCount == -1) { - if (simpleStack) - break; + if (type->Cumulative_SimpleStack) + cumulativeCount = attachEffect->SimpleStackCount; + else + cumulativeCount = this->GetAttachedEffectCumulativeCount(type, ignoreSameSource, pInvoker, pSource); + } - continue; + if (minSize > 0) + { + if (cumulativeCount < minCounts->at(typeCounter - 1 >= minSize ? minSize - 1 : typeCounter - 1)) + { + if (type->Cumulative_SimpleStack) + break; + + continue; + } } - } - if (maxSize > 0) - { - if (cumulativeCount > maxCounts->at(typeCounter - 1 >= maxSize ? maxSize - 1 : typeCounter - 1)) + if (maxSize > 0) { - if (simpleStack) - break; + if (cumulativeCount > maxCounts->at(typeCounter - 1 >= maxSize ? maxSize - 1 : typeCounter - 1)) + { + if (type->Cumulative_SimpleStack) + break; - continue; + continue; + } } } } diff --git a/src/New/Entity/AttachEffectClass.cpp b/src/New/Entity/AttachEffectClass.cpp index 73682572f6..dbc31a8f98 100644 --- a/src/New/Entity/AttachEffectClass.cpp +++ b/src/New/Entity/AttachEffectClass.cpp @@ -888,8 +888,6 @@ int AttachEffectClass::RemoveAllOfType(AttachEffectTypeClass* pType, TechnoClass int detachedCount = 0; auto const targetAEs = &pTargetExt->AttachedEffects; auto const pWeapon = pType->ExpireWeapon; - const bool triggerOnDiscard = (pType->ExpireWeapon_TriggerOn & ExpireWeaponCondition::Remove) != ExpireWeaponCondition::None; - const bool onlyOnce = pType->ExpireWeapon_CumulativeOnlyOnce; const bool invokerOwner = pType->ExpireWeapon_UseInvokerAsOwner; std::vector>::iterator it; std::vector> expireWeapons; @@ -933,18 +931,18 @@ int AttachEffectClass::RemoveAllOfType(AttachEffectTypeClass* pType, TechnoClass } } - if (pWeapon && triggerOnDiscard) + if (pWeapon && (pType->ExpireWeapon_TriggerOn & ExpireWeaponCondition::Remove) != ExpireWeaponCondition::None) { auto const pInvoker = attachEffect->Invoker; // can't be GetAttachedEffectCumulativeCount(pType) < 2, or inactive AE might make it stack more than once - if (!simpleStack || !onlyOnce || stackCount == 1) + if (!simpleStack || !pType->ExpireWeapon_CumulativeOnlyOnce || stackCount == 1) { handleExpireWeapon(pInvoker); } else if (simpleStack) // handle ExpireWeapon for Cumulative.SimpleStack { - if (onlyOnce) + if (pType->ExpireWeapon_CumulativeOnlyOnce) { if (stackCount <= 0) handleExpireWeapon(pInvoker);