From 067d5835feb8f979de537080a6ecfcbdda19c1ca Mon Sep 17 00:00:00 2001 From: Coronia <2217891145@qq.com> Date: Wed, 4 Feb 2026 22:30:21 +0800 Subject: [PATCH 1/3] transfer damage AE --- CREDITS.md | 1 + docs/New-or-Enhanced-Logics.md | 28 +++++++- docs/Whats-New.md | 1 + src/Ext/Techno/Body.Update.cpp | 3 + src/Ext/Techno/Hooks.ReceiveDamage.cpp | 99 ++++++++++++++++++++++++-- src/Ext/WarheadType/Body.cpp | 7 ++ src/Ext/WarheadType/Body.h | 8 +++ src/New/Entity/AttachEffectClass.h | 2 + src/New/Type/AttachEffectTypeClass.cpp | 32 +++++++++ src/New/Type/AttachEffectTypeClass.h | 28 ++++++++ 10 files changed, 201 insertions(+), 8 deletions(-) diff --git a/CREDITS.md b/CREDITS.md index e9773e684a..71ef5e12c1 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -619,6 +619,7 @@ This page lists all the individual contributions to the project by their author. - AutoDeath upon ownership change - Maximum amount for power plant enhancer - Return warhead + - Attached effect for transfering damage to invoker - **NaotoYuuki** - Vertical & meteor trajectory projectile prototypes - **handama** - AI script action to `16005 Jump Back To Previous Script` - **TaranDahl (航味麻酱)**: diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 0e2c666d66..d9eec630dd 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -52,9 +52,18 @@ This page describes all the engine features that are either new and introduced b - `RevengeWeapon` can be used to temporarily grant the specified weapon as a [revenge weapon](#revenge-weapon) for the attached object. - `RevengeWeapon.AffectsHouse` customizes which houses can trigger the revenge weapon. - `RevengeWeapon.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 weapon's owner & invoker instead of the object the effect is attached to. - - `ReflectDamage` can be set to true to have any positive damage dealt to the object the effect is attached to be reflected back to the attacker. `ReflectDamage.Warhead` determines which Warhead is used to deal the damage, defaults to `[CombatDamage] -> C4Warhead`. If `ReflectDamage.Warhead.Detonate` is set to true, the Warhead is fully detonated instead of used to simply deal damage. `ReflectDamage.Chance` determines the chance of reflection. `ReflectDamage.Multiplier` is a multiplier to the damage received and then reflected back, while `ReflectDamage.Override` directly overrides the damage. Already reflected damage cannot be further reflected back. + - `ReflectDamage` can be set to true to have any positive damage dealt to the object the effect is attached to be reflected back to the attacker. + - `ReflectDamage.Warhead` determines which Warhead is used to deal the damage, defaults to `[CombatDamage] -> C4Warhead`. If `ReflectDamage.Warhead.Detonate` is set to true, the Warhead is fully detonated instead of used to simply deal damage. If `ReflectDamage.UseOriginalWarhead` is set to true, it'll reuse the Warhead that dealt damage to the object. + - `ReflectDamage.Chance` determines the chance of reflection, while `ReflectDamage.AffectsHouse` customizes which houses can trigger the reflect damage. + - `ReflectDamage.Multiplier` is a multiplier to the damage received and then reflected back, while `ReflectDamage.Override` directly overrides the damage. Already reflected damage cannot be further reflected back. - Warheads can prevent reflect damage from occuring by setting `SuppressReflectDamage` to true. `SuppressReflectDamage.Types` can control which AttachEffectTypes' reflect damage is suppressed, if none are listed then all of them are suppressed. `SuppressReflectDamage.Groups` does the same thing but for all AttachEffectTypes in the listed groups. - `ReflectDamage.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 reflected damage's owner & invoker instead of the object the effect is attached to. + - `TransferDamage` can be set to true to have any damage dealt to the object the effect is attached to be transfered to the invoker of the effect if it's alive. + - `TransferDamage.SelfMultiplier` and `TransferDamage.InvokerMultiplier` are multuipliers of damage that'll be applied to the attached object and the invoker if a transfer happens. If an object has multiple effects with `TransferDamage`, the damage will be equally divided and calculated for each effect independently. `TransferDamage.Minimum` and `TransferDamage.Maximum` further restrict the bound of damage transfering to the invoker. + - `TransferDamage.Warhead` determines which Warhead is used to deal the damage, defaults to `[CombatDamage] -> C4Warhead`. If `TransferDamage.Warhead.Detonate` is set to true, the Warhead is fully detonated instead of used to simply deal damage. If `TransferDamage.UseOriginalWarhead` is set to true, it'll reuse the Warhead that dealt damage to the object. + - `TransferDamage.Chance` determines the chance of transfer, while `TransferDamage.AffectsHouse` customizes which houses can trigger the transfer damage. `TransferDamage.Invoker.AbovePercent` and `TransferDamage.Invoker.BelowPercent` determine if the transfer can happen when the invoker's health is above or below the given values. + - Warheads can prevent transfer damage from occuring by setting `SuppressTransferDamage` to true. `SuppressTransferDamage.Types` can control which AttachEffectTypes' transfer damage is suppressed, if none are listed then all of them are suppressed. `SuppressTransferDamage.Groups` does the same thing but for all AttachEffectTypes in the listed groups. + - `SuppressTransferDamage.SelfOwned` can be used to set the house and TechnoType that the object the effect is attached to as the transfered damage's owner & invoker. Otherwise, it'll be set to the original damage dealer that trigger this transfer. - `DisableWeapons` can be used to disable ability to fire any and all weapons. - On TechnoTypes with `OpenTopped=true`, `OpenTopped.CheckTransportDisableWeapons` can be set to true to make passengers not be able to fire out if transport's weapons are disabled by `DisableWeapons`. - `Unkillable` can be used to prevent the techno from being killed by taken damage (minimum health will be 1). @@ -142,11 +151,25 @@ RevengeWeapon.UseInvokerAsOwner=false ; boolean ReflectDamage=false ; boolean ReflectDamage.Warhead= ; WarheadType ReflectDamage.Warhead.Detonate=false ; WarheadType +ReflectDamage.UseOriginalWarhead=false ; boolean ReflectDamage.Multiplier=1.0 ; floating point value, percents or absolute ReflectDamage.AffectsHouse=all ; List of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) ReflectDamage.Chance=1.0 ; floating point value ReflectDamage.Override= ; integer ReflectDamage.UseInvokerAsOwner=false ; boolean +TransferDamage=false ; boolean +TransferDamage.SelfMultiplier=0.5 ; floating point value +TransferDamage.InvokerMultiplier=0.5 ; floating point value +TransferDamage.Minimum=-2147483648 ; integer +TransferDamage.Maximum=2147483647 ; integer +TransferDamage.Warhead= ; WarheadType +TransferDamage.Warhead.Detonate=false ; WarheadType +TransferDamage.UseOriginalWarhead=false ; boolean +TransferDamage.AffectsHouse=all ; List of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) +TransferDamage.Chance=1.0 ; floating point value +TransferDamage.Invoker.AbovePercent=0.0 ; floating point value +TransferDamage.Invoker.BelowPercent=1.0 ; floating point value +TransferDamage.SelfOwned=false ; boolean DisableWeapons=false ; boolean Unkillable=false ; boolean LaserTrail.Type= ; LaserTrailType @@ -187,6 +210,9 @@ AttachEffect.DurationOverrides= ; integer - duration override SuppressReflectDamage=false ; boolean SuppressReflectDamage.Types= ; List of AttachEffectTypes SuppressReflectDamage.Groups= ; comma-separated list of strings (group IDs) +SuppressTransferDamage=false ; boolean +SuppressTransferDamage.Types= ; List of AttachEffectTypes +SuppressTransferDamage.Groups= ; comma-separated list of strings (group IDs) ``` ### Custom Radiation Types diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 4afcf3f597..95b3f931a2 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -535,6 +535,7 @@ New: - Option to scale `PowerSurplus` setting if enabled to current power drain with `PowerSurplus.ScaleToDrainAmount` (by Starkku) - Global default value for `DefaultToGuardArea` (by TaranDahl) - [Weapon range finding in cylinder](New-or-Enhanced-Logics.md#range-finding-in-cylinder) (by TaranDahl) +- Attached effect for transfering damage to invoker (by Ollerus) 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/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index 95da6d343d..bcd5058d8d 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -2030,6 +2030,7 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() bool hasRangeModifier = false; bool hasTint = false; bool reflectsDamage = false; + int transferDamageCount = 0; bool hasOnFireDiscardables = false; bool hasRestrictedArmorMultipliers = false; bool hasCritModifiers = false; @@ -2056,6 +2057,7 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() hasRangeModifier |= (type->WeaponRange_ExtraRange != 0.0 || type->WeaponRange_Multiplier != 0.0); hasTint |= type->HasTint(); reflectsDamage |= type->ReflectDamage; + transferDamageCount += type->TransferDamage ? 1 : 0; hasOnFireDiscardables |= (type->DiscardOn & DiscardCondition::Firing) != DiscardCondition::None; hasCritModifiers |= (type->Crit_Multiplier != 1.0 || type->Crit_ExtraChance != 0.0); } @@ -2071,6 +2073,7 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() pAE.HasRangeModifier = hasRangeModifier; pAE.HasTint = hasTint; pAE.ReflectDamage = reflectsDamage; + pAE.TransferDamageCount = transferDamageCount; pAE.HasOnFireDiscardables = hasOnFireDiscardables; pAE.HasRestrictedArmorMultipliers = hasRestrictedArmorMultipliers; pAE.HasCritModifiers = hasCritModifiers; diff --git a/src/Ext/Techno/Hooks.ReceiveDamage.cpp b/src/Ext/Techno/Hooks.ReceiveDamage.cpp index cae211a938..cf05746ddf 100644 --- a/src/Ext/Techno/Hooks.ReceiveDamage.cpp +++ b/src/Ext/Techno/Hooks.ReceiveDamage.cpp @@ -16,7 +16,8 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) GET(TechnoClass*, pThis, ECX); LEA_STACK(args_ReceiveDamage*, args, 0x4); - const auto pWHExt = WarheadTypeExt::ExtMap.Find(args->WH); + const auto pWarhead = args->WH; + const auto pWHExt = WarheadTypeExt::ExtMap.Find(pWarhead); int& damage = *args->Damage; // AffectsAbove/BelowPercent & AffectsNeutral can ignore IgnoreDefenses like AffectsAllies/Enmies/Owner @@ -28,19 +29,20 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) } const auto pExt = TechnoExt::ExtMap.Find(pThis); + const auto pSource = args->Attacker; const auto pSourceHouse = args->SourceHouse; const auto pTargetHouse = pThis->Owner; // Apply warhead effects if (damage && !pWHExt->ApplyPerTargetEffectsOnDetonate.Get(RulesExt::Global()->ApplyPerTargetEffectsOnDetonate)) - pWHExt->DetonateOnOneUnit(args->SourceHouse, pThis, args->Attacker); + pWHExt->DetonateOnOneUnit(args->SourceHouse, pThis, pSource); // Calculate Damage Multiplier if (!args->IgnoreDefenses && damage) { double multiplier = 1.0; - if (args->Attacker && args->Attacker->Berzerk) + if (pSource && pSource->Berzerk) { if (!pSourceHouse || !pTargetHouse || !pSourceHouse->IsAlliedWith(pTargetHouse)) multiplier = pWHExt->DamageEnemiesMultiplier_Berzerk.Get(RulesExt::Global()->DamageEnemiesMultiplier_Berzerk.Get(RulesExt::Global()->DamageEnemiesMultiplier)); @@ -128,11 +130,93 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) raiseCombatAlert(); } - // Shield Receive Damage + // Transfer Damage and Shield Receive Damage if (!args->IgnoreDefenses) { int nDamageLeft = damage; + if (pExt->AE.TransferDamageCount > 0 && !pWHExt->Transfered) + { + const auto& suppressType = pWHExt->SuppressTransferDamage_Types; + const auto& suppressGroup = pWHExt->SuppressTransferDamage_Groups; + const bool suppress = pWHExt->SuppressTransferDamage; + const bool suppressByType = suppressType.size() > 0; + const bool suppressByGroup = suppressGroup.size() > 0; + + if (!suppress || suppressByType || suppressByGroup) + { + const int baseDamage = static_cast(static_cast(damage) / pExt->AE.TransferDamageCount); + auto& random = ScenarioClass::Instance->Random; + nDamageLeft = 0; + + for (const auto& attachEffect : pExt->AttachedEffects) + { + const auto pType = attachEffect->GetType(); + + if (!pType->TransferDamage) + continue; + + if (pType->TransferDamage_Chance < random.RandomDouble()) + { + nDamageLeft += baseDamage; + continue; + } + + const auto pInvoker = attachEffect->GetInvoker(); + + if (!pInvoker || TechnoExt::IsHealthInThreshold(pInvoker, pType->TransferDamage_Invoker_BelowPercent, pType->TransferDamage_Invoker_AbovePercent)) + { + nDamageLeft += baseDamage; + continue; + } + + if (suppress) + { + if (suppressByType && suppressType.Contains(pType)) + { + nDamageLeft += baseDamage; + continue; + } + + if (suppressByGroup && pType->HasGroups(suppressGroup, false)) + { + nDamageLeft += baseDamage; + continue; + } + } + + if (!EnumFunctions::CanTargetHouse(pType->TransferDamage_AffectsHouse, pSourceHouse, pTargetHouse)) + { + nDamageLeft += baseDamage; + continue; + } + + int invokerDamage = std::clamp(static_cast(baseDamage * pType->TransferDamage_InvokerMultiplier), pType->TransferDamage_Minimum.Get(), pType->TransferDamage_Maximum.Get()); + auto const pWH = pType->TransferDamage_UseOriginalWarhead ? pWarhead : pType->TransferDamage_Warhead.Get(RulesClass::Instance->C4Warhead); + auto const pWHExtRef = WarheadTypeExt::ExtMap.Find(pWH); + pWHExtRef->Transfered = true; + + if (pType->TransferDamage_SelfOwned) + { + if (pType->TransferDamage_Warhead_Detonate) + WarheadTypeExt::DetonateAt(pWH, pInvoker, pThis, invokerDamage, pTargetHouse); + else + pInvoker->ReceiveDamage(&invokerDamage, 0, pWH, pThis, false, false, pTargetHouse); + } + else + { + if (pType->TransferDamage_Warhead_Detonate) + WarheadTypeExt::DetonateAt(pWH, pInvoker, pSource, invokerDamage, pSourceHouse); + else + pInvoker->ReceiveDamage(&invokerDamage, 0, pWH, pSource, false, false, pSourceHouse); + } + + pWHExtRef->Transfered = false; + nDamageLeft += static_cast(baseDamage * pType->TransferDamage_SelfMultiplier); + } + } + } + if (const auto pShieldData = pExt->Shield.get()) { if (pShieldData->IsActive()) @@ -373,15 +457,14 @@ DEFINE_HOOK(0x701E18, TechnoClass_ReceiveDamage_ReflectDamage, 0x7) continue; } - auto const pWH = pType->ReflectDamage_Warhead.Get(RulesClass::Instance->C4Warhead); - int damage = pType->ReflectDamage_Override.Get(static_cast(*pDamage * pType->ReflectDamage_Multiplier)); - if (pType->ReflectDamage_UseInvokerAsOwner) { auto const pInvoker = attachEffect->GetInvoker(); if (pInvoker && EnumFunctions::CanTargetHouse(pType->ReflectDamage_AffectsHouse, pInvoker->Owner, pSourceHouse)) { + int damage = pType->ReflectDamage_Override.Get(static_cast(*pDamage * pType->ReflectDamage_Multiplier)); + auto const pWH = pType->ReflectDamage_UseOriginalWarhead ? pWarhead : pType->ReflectDamage_Warhead.Get(RulesClass::Instance->C4Warhead); auto const pWHExtRef = WarheadTypeExt::ExtMap.Find(pWH); pWHExtRef->Reflected = true; @@ -395,6 +478,8 @@ DEFINE_HOOK(0x701E18, TechnoClass_ReceiveDamage_ReflectDamage, 0x7) } else if (EnumFunctions::CanTargetHouse(pType->ReflectDamage_AffectsHouse, pThis->Owner, pSourceHouse)) { + int damage = pType->ReflectDamage_Override.Get(static_cast(*pDamage * pType->ReflectDamage_Multiplier)); + auto const pWH = pType->ReflectDamage_UseOriginalWarhead ? pWarhead : pType->ReflectDamage_Warhead.Get(RulesClass::Instance->C4Warhead); auto const pWHExtRef = WarheadTypeExt::ExtMap.Find(pWH); pWHExtRef->Reflected = true; diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index 15f335d1bc..b697e82ab3 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -280,6 +280,9 @@ void WarheadTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->SuppressReflectDamage.Read(exINI, pSection, "SuppressReflectDamage"); this->SuppressReflectDamage_Types.Read(exINI, pSection, "SuppressReflectDamage.Types"); exINI.ParseStringList(this->SuppressReflectDamage_Groups, pSection, "SuppressReflectDamage.Groups"); + this->SuppressTransferDamage.Read(exINI, pSection, "SuppressTransferDamage"); + this->SuppressTransferDamage_Types.Read(exINI, pSection, "SuppressTransferDamage.Types"); + exINI.ParseStringList(this->SuppressTransferDamage_Groups, pSection, "SuppressTransferDamage.Groups"); this->BuildingSell.Read(exINI, pSection, "BuildingSell"); this->BuildingSell_IgnoreUnsellable.Read(exINI, pSection, "BuildingSell.IgnoreUnsellable"); @@ -565,6 +568,9 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .Process(this->SuppressReflectDamage) .Process(this->SuppressReflectDamage_Types) .Process(this->SuppressReflectDamage_Groups) + .Process(this->SuppressTransferDamage) + .Process(this->SuppressTransferDamage_Types) + .Process(this->SuppressTransferDamage_Groups) .Process(this->AffectsBelowPercent) .Process(this->AffectsAbovePercent) @@ -646,6 +652,7 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .Process(this->RemainingAnimCreationInterval) .Process(this->PossibleCellSpreadDetonate) .Process(this->Reflected) + .Process(this->Transfered) .Process(this->DamageAreaTarget) ; } diff --git a/src/Ext/WarheadType/Body.h b/src/Ext/WarheadType/Body.h index 51742427d8..c359b0e2ef 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -178,6 +178,9 @@ class WarheadTypeExt Valueable SuppressReflectDamage; ValueableVector SuppressReflectDamage_Types; std::vector SuppressReflectDamage_Groups; + Valueable SuppressTransferDamage; + ValueableVector SuppressTransferDamage_Types; + std::vector SuppressTransferDamage_Groups; Valueable BuildingSell; Valueable BuildingSell_IgnoreUnsellable; @@ -239,6 +242,7 @@ class WarheadTypeExt bool WasDetonatedOnAllMapObjects; bool Splashed; bool Reflected; + bool Transfered; int RemainingAnimCreationInterval; bool PossibleCellSpreadDetonate; bool HealthCheck; @@ -411,6 +415,9 @@ class WarheadTypeExt , SuppressReflectDamage { false } , SuppressReflectDamage_Types {} , SuppressReflectDamage_Groups {} + , SuppressTransferDamage { false } + , SuppressTransferDamage_Types {} + , SuppressTransferDamage_Groups {} , BuildingSell { false } , BuildingSell_IgnoreUnsellable { false } @@ -445,6 +452,7 @@ class WarheadTypeExt , WasDetonatedOnAllMapObjects { false } , Splashed { false } , Reflected { false } + , Transfered { false } , RemainingAnimCreationInterval { 0 } , PossibleCellSpreadDetonate { false } , HealthCheck { false } diff --git a/src/New/Entity/AttachEffectClass.h b/src/New/Entity/AttachEffectClass.h index c596d6d709..f0ccc931df 100644 --- a/src/New/Entity/AttachEffectClass.h +++ b/src/New/Entity/AttachEffectClass.h @@ -117,6 +117,7 @@ struct AttachEffectTechnoProperties bool HasRangeModifier; bool HasTint; bool ReflectDamage; + int TransferDamageCount; bool HasOnFireDiscardables; bool HasRestrictedArmorMultipliers; bool HasCritModifiers; @@ -133,6 +134,7 @@ struct AttachEffectTechnoProperties , HasRangeModifier { false } , HasTint { false } , ReflectDamage { false } + , TransferDamageCount { 0 } , HasOnFireDiscardables { false } , HasRestrictedArmorMultipliers { false } , HasCritModifiers { false } diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index 02bc5abb9e..c0011c84a7 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -153,12 +153,30 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->ReflectDamage.Read(exINI, pSection, "ReflectDamage"); this->ReflectDamage_Warhead.Read(exINI, pSection, "ReflectDamage.Warhead"); this->ReflectDamage_Warhead_Detonate.Read(exINI, pSection, "ReflectDamage.Warhead.Detonate"); + this->ReflectDamage_UseOriginalWarhead.Read(exINI, pSection, "ReflectDamage.UseOriginalWarhead"); this->ReflectDamage_Multiplier.Read(exINI, pSection, "ReflectDamage.Multiplier"); this->ReflectDamage_AffectsHouse.Read(exINI, pSection, "ReflectDamage.AffectsHouse"); this->ReflectDamage_Chance.Read(exINI, pSection, "ReflectDamage.Chance"); this->ReflectDamage_Override.Read(exINI, pSection, "ReflectDamage.Override"); this->ReflectDamage_UseInvokerAsOwner.Read(exINI, pSection, "ReflectDamage.UseInvokerAsOwner"); + this->TransferDamage.Read(exINI, pSection, "TransferDamage"); + this->TransferDamage_SelfMultiplier.Read(exINI, pSection, "TransferDamage.SelfMultiplier"); + this->TransferDamage_InvokerMultiplier.Read(exINI, pSection, "TransferDamage.InvokerMultiplier"); + this->TransferDamage_Invoker_BelowPercent.Read(exINI, pSection, "TransferDamage.Invoker.BelowPercent"); + this->TransferDamage_Invoker_AbovePercent.Read(exINI, pSection, "TransferDamage.Invoker.AbovePercent"); + this->TransferDamage_Warhead.Read(exINI, pSection, "TransferDamage.Warhead"); + this->TransferDamage_Warhead_Detonate.Read(exINI, pSection, "TransferDamage.Warhead.Detonate"); + this->TransferDamage_UseOriginalWarhead.Read(exINI, pSection, "TransferDamage.UseOriginalWarhead"); + this->TransferDamage_AffectsHouse.Read(exINI, pSection, "TransferDamage.AffectsHouse"); + this->TransferDamage_Chance.Read(exINI, pSection, "TransferDamage.Chance"); + this->TransferDamage_SelfOwned.Read(exINI, pSection, "TransferDamage.SelfOwned"); + this->TransferDamage_Minimum.Read(exINI, pSection, "TransferDamage.Minimum"); + this->TransferDamage_Maximum.Read(exINI, pSection, "TransferDamage.Maximum"); + + if (this->TransferDamage_Invoker_AbovePercent > this->TransferDamage_Invoker_BelowPercent) + Debug::Log("[Developer warning][%s] TransferDamage.Invoker.AbovePercent is bigger than TransferDamage.Invoker.BelowPercent, transfer damage will never activate!\n", pSection); + this->DisableWeapons.Read(exINI, pSection, "DisableWeapons"); this->Unkillable.Read(exINI, pSection, "Unkillable"); this->LaserTrail_Type.Read(exINI, pSection, "LaserTrail.Type"); @@ -223,11 +241,25 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->ReflectDamage) .Process(this->ReflectDamage_Warhead) .Process(this->ReflectDamage_Warhead_Detonate) + .Process(this->ReflectDamage_UseOriginalWarhead) .Process(this->ReflectDamage_Multiplier) .Process(this->ReflectDamage_AffectsHouse) .Process(this->ReflectDamage_Chance) .Process(this->ReflectDamage_Override) .Process(this->ReflectDamage_UseInvokerAsOwner) + .Process(this->TransferDamage) + .Process(this->TransferDamage_SelfMultiplier) + .Process(this->TransferDamage_InvokerMultiplier) + .Process(this->TransferDamage_Invoker_BelowPercent) + .Process(this->TransferDamage_Invoker_AbovePercent) + .Process(this->TransferDamage_Warhead) + .Process(this->TransferDamage_Warhead_Detonate) + .Process(this->TransferDamage_UseOriginalWarhead) + .Process(this->TransferDamage_AffectsHouse) + .Process(this->TransferDamage_Chance) + .Process(this->TransferDamage_SelfOwned) + .Process(this->TransferDamage_Minimum) + .Process(this->TransferDamage_Maximum) .Process(this->DisableWeapons) .Process(this->Unkillable) .Process(this->LaserTrail_Type) diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index b0759a97c0..5df3624512 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -92,11 +92,25 @@ class AttachEffectTypeClass final : public Enumerable Valueable ReflectDamage; Nullable ReflectDamage_Warhead; Valueable ReflectDamage_Warhead_Detonate; + Valueable ReflectDamage_UseOriginalWarhead; Valueable ReflectDamage_Multiplier; Valueable ReflectDamage_AffectsHouse; Valueable ReflectDamage_Chance; Nullable ReflectDamage_Override; Valueable ReflectDamage_UseInvokerAsOwner; + Valueable TransferDamage; + Valueable TransferDamage_SelfMultiplier; + Valueable TransferDamage_InvokerMultiplier; + Valueable TransferDamage_Invoker_BelowPercent; + Valueable TransferDamage_Invoker_AbovePercent; + Nullable TransferDamage_Warhead; + Valueable TransferDamage_Warhead_Detonate; + Valueable TransferDamage_UseOriginalWarhead; + Valueable TransferDamage_AffectsHouse; + Valueable TransferDamage_Chance; + Valueable TransferDamage_SelfOwned; + Valueable TransferDamage_Minimum; + Valueable TransferDamage_Maximum; Valueable DisableWeapons; Valueable Unkillable; ValueableIdx LaserTrail_Type; @@ -155,11 +169,25 @@ class AttachEffectTypeClass final : public Enumerable , RevengeWeapon_UseInvokerAsOwner { false } , ReflectDamage_Warhead {} , ReflectDamage_Warhead_Detonate { false } + , ReflectDamage_UseOriginalWarhead { false } , ReflectDamage_Multiplier { 1.0 } , ReflectDamage_AffectsHouse { AffectedHouse::All } , ReflectDamage_Chance { 1.0 } , ReflectDamage_Override {} , ReflectDamage_UseInvokerAsOwner { false } + , TransferDamage { false } + , TransferDamage_SelfMultiplier { 0.5 } + , TransferDamage_InvokerMultiplier { 0.5 } + , TransferDamage_Invoker_BelowPercent { 1.0 } + , TransferDamage_Invoker_AbovePercent { 0.0 } + , TransferDamage_Warhead {} + , TransferDamage_Warhead_Detonate { true } + , TransferDamage_UseOriginalWarhead { false } + , TransferDamage_AffectsHouse { AffectedHouse::All } + , TransferDamage_Chance { 1.0 } + , TransferDamage_SelfOwned { false } + , TransferDamage_Minimum { INT32_MIN } + , TransferDamage_Maximum { INT32_MAX } , DisableWeapons { false } , Unkillable { false } , LaserTrail_Type { -1 } From acf3c28de67f3375625e08fd7497762cd910ef11 Mon Sep 17 00:00:00 2001 From: Coronia <2217891145@qq.com> Date: Thu, 5 Feb 2026 00:32:18 +0800 Subject: [PATCH 2/3] fix --- src/Ext/Techno/Hooks.ReceiveDamage.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Ext/Techno/Hooks.ReceiveDamage.cpp b/src/Ext/Techno/Hooks.ReceiveDamage.cpp index cf05746ddf..fde96906af 100644 --- a/src/Ext/Techno/Hooks.ReceiveDamage.cpp +++ b/src/Ext/Techno/Hooks.ReceiveDamage.cpp @@ -147,6 +147,7 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) { const int baseDamage = static_cast(static_cast(damage) / pExt->AE.TransferDamageCount); auto& random = ScenarioClass::Instance->Random; + int count = 0; nDamageLeft = 0; for (const auto& attachEffect : pExt->AttachedEffects) @@ -213,7 +214,13 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) pWHExtRef->Transfered = false; nDamageLeft += static_cast(baseDamage * pType->TransferDamage_SelfMultiplier); + count++; + + if (count >= pExt->AE.TransferDamageCount) + break; } + + damage = nDamageLeft; } } From 09091c7ddf4fda6a1322c76bd01156240c1cf51a Mon Sep 17 00:00:00 2001 From: Coronia <2217891145@qq.com> Date: Thu, 5 Feb 2026 10:12:37 +0800 Subject: [PATCH 3/3] fix removed --- src/New/Entity/AttachEffectClass.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/New/Entity/AttachEffectClass.cpp b/src/New/Entity/AttachEffectClass.cpp index b315154109..8169514035 100644 --- a/src/New/Entity/AttachEffectClass.cpp +++ b/src/New/Entity/AttachEffectClass.cpp @@ -124,6 +124,9 @@ void AttachEffectClass::PointerGotInvalid(void* ptr, bool removed) } else if ((abs->AbstractFlags & AbstractFlags::Techno) != AbstractFlags::None) { + if (!removed) + return; + auto const pTechno = abstract_cast(abs); if (int count = TechnoExt::ExtMap.Find(pTechno)->AttachedEffectInvokerCount)