Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 (航味麻酱)**:
Expand Down
28 changes: 27 additions & 1 deletion docs/New-or-Enhanced-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions src/Ext/Techno/Body.Update.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}
Expand All @@ -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;
Expand Down
106 changes: 99 additions & 7 deletions src/Ext/Techno/Hooks.ReceiveDamage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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));
Expand Down Expand Up @@ -128,11 +130,100 @@ 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<int>(static_cast<double>(damage) / pExt->AE.TransferDamageCount);
auto& random = ScenarioClass::Instance->Random;
int count = 0;
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<int>(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<int>(baseDamage * pType->TransferDamage_SelfMultiplier);
count++;

if (count >= pExt->AE.TransferDamageCount)
break;
}

damage = nDamageLeft;
}
}

if (const auto pShieldData = pExt->Shield.get())
{
if (pShieldData->IsActive())
Expand Down Expand Up @@ -373,15 +464,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<int>(*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<int>(*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;

Expand All @@ -395,6 +485,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<int>(*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;

Expand Down
7 changes: 7 additions & 0 deletions src/Ext/WarheadType/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
;
}
Expand Down
8 changes: 8 additions & 0 deletions src/Ext/WarheadType/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ class WarheadTypeExt
Valueable<bool> SuppressReflectDamage;
ValueableVector<AttachEffectTypeClass*> SuppressReflectDamage_Types;
std::vector<std::string> SuppressReflectDamage_Groups;
Valueable<bool> SuppressTransferDamage;
ValueableVector<AttachEffectTypeClass*> SuppressTransferDamage_Types;
std::vector<std::string> SuppressTransferDamage_Groups;

Valueable<bool> BuildingSell;
Valueable<bool> BuildingSell_IgnoreUnsellable;
Expand Down Expand Up @@ -239,6 +242,7 @@ class WarheadTypeExt
bool WasDetonatedOnAllMapObjects;
bool Splashed;
bool Reflected;
bool Transfered;
int RemainingAnimCreationInterval;
bool PossibleCellSpreadDetonate;
bool HealthCheck;
Expand Down Expand Up @@ -411,6 +415,9 @@ class WarheadTypeExt
, SuppressReflectDamage { false }
, SuppressReflectDamage_Types {}
, SuppressReflectDamage_Groups {}
, SuppressTransferDamage { false }
, SuppressTransferDamage_Types {}
, SuppressTransferDamage_Groups {}

, BuildingSell { false }
, BuildingSell_IgnoreUnsellable { false }
Expand Down Expand Up @@ -445,6 +452,7 @@ class WarheadTypeExt
, WasDetonatedOnAllMapObjects { false }
, Splashed { false }
, Reflected { false }
, Transfered { false }
, RemainingAnimCreationInterval { 0 }
, PossibleCellSpreadDetonate { false }
, HealthCheck { false }
Expand Down
3 changes: 3 additions & 0 deletions src/New/Entity/AttachEffectClass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<TechnoClass*, true>(abs);

if (int count = TechnoExt::ExtMap.Find(pTechno)->AttachedEffectInvokerCount)
Expand Down
2 changes: 2 additions & 0 deletions src/New/Entity/AttachEffectClass.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ struct AttachEffectTechnoProperties
bool HasRangeModifier;
bool HasTint;
bool ReflectDamage;
int TransferDamageCount;
bool HasOnFireDiscardables;
bool HasRestrictedArmorMultipliers;
bool HasCritModifiers;
Expand All @@ -133,6 +134,7 @@ struct AttachEffectTechnoProperties
, HasRangeModifier { false }
, HasTint { false }
, ReflectDamage { false }
, TransferDamageCount { 0 }
, HasOnFireDiscardables { false }
, HasRestrictedArmorMultipliers { false }
, HasCritModifiers { false }
Expand Down
Loading