From efe8f209f89cae6065b172b318de4db6d2ea0071 Mon Sep 17 00:00:00 2001 From: Coronia <2217891145@qq.com> Date: Wed, 23 Apr 2025 10:47:55 +0800 Subject: [PATCH 01/10] AE modification --- docs/New-or-Enhanced-Logics.md | 8 ++++++ src/New/Entity/AttachEffectClass.cpp | 38 +++++++++++++++++++++++--- src/New/Type/AttachEffectTypeClass.cpp | 10 +++++++ src/New/Type/AttachEffectTypeClass.h | 10 +++++++ 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 23710b33cb..891b26f53c 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -8,6 +8,7 @@ This page describes all the engine features that are either new and introduced b - Similar (but not identical) to [Ares' AttachEffect](https://ares-developers.github.io/Ares-docs/new/attacheffect.html), but with some differences and new features. The largest difference is that here attached effects are explicitly defined types. - `Duration` determines how long the effect lasts for. It can be overriden by `DurationOverrides` on TechnoTypes and Warheads. + - `Duration.ApplyVersus.Warhead` can multiply the duration by the set warhead's versus against the target's armor type if it's not negative. Can't reduce duration to below 0 by a negative versus. - 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. @@ -20,6 +21,8 @@ This page describes all the engine features that are either new and introduced b - `inrange`: Discard if within weapon range from current target. Distance can be overridden via `DiscardOn.RangeOverride`. - `outofrange`: Discard if outside weapon range from current target. Distance can be overridden via `DiscardOn.RangeOverride`. - `firing`: Discard when firing a weapon. This counts special weapons that are not actually fired such as ones with `Spawner=true` or `DrainWeapon=true`. + - If `DiscardOn.AbovePercent` or `DiscardOn.BelowPercent` is set, the effect is discarded when the object's health percentage is above/below that value. + - If `AffectAbovePercent` or `AffectBelowPercent` is set, the effect can be applied only when the object's health percentage is above/below that value. - If `PenetratesIronCurtain` is not set to true, the effect is not applied on currently invulnerable objects. - `PenetratesForceShield` can be used to set this separately for Force Shielded objects, defaults to value of `PenetratesIronCurtain`. - `Animation` defines animation to play in an indefinite loop for as long as the effect is active on the object it is attached to. @@ -84,12 +87,17 @@ In `rulesmd.ini`: [SOMEATTACHEFFECT] ; AttachEffectType Duration=0 ; integer - game frames or negative value for indefinite duration +Duration.ApplyVersus.Warhead= ; WarheadType Duration.ApplyFirepowerMult=false ; boolean Duration.ApplyArmorMultOnTarget=false ; boolean Cumulative=false ; boolean Cumulative.MaxCount=-1 ; integer Powered=false ; boolean DiscardOn=none ; List of discard condition enumeration (none|entry|move|stationary|drain|inrange|outofrange) +DiscardOn.AbovePercent= ; floating point value, percents or absolute (0.0-1.0) +DiscardOn.BelowPercent= ; floating point value, percents or absolute (0.0-1.0) +AffectAbovePercent= ; floating point value, percents or absolute (0.0-1.0) +AffectBelowPercent= ; floating point value, percents or absolute (0.0-1.0) DiscardOn.RangeOverride= ; floating point value, distance in cells PenetratesIronCurtain=false ; boolean PenetratesForceShield= ; boolean diff --git a/src/New/Entity/AttachEffectClass.cpp b/src/New/Entity/AttachEffectClass.cpp index 3638afb567..ecf18e692f 100644 --- a/src/New/Entity/AttachEffectClass.cpp +++ b/src/New/Entity/AttachEffectClass.cpp @@ -46,11 +46,20 @@ AttachEffectClass::AttachEffectClass(AttachEffectTypeClass* pType, TechnoClass* this->Duration = this->DurationOverride != 0 ? this->DurationOverride : this->Type->Duration; - if (this->Type->Duration_ApplyFirepowerMult && this->Duration > 0 && pInvoker) - this->Duration = Math::max(static_cast(this->Duration * pInvoker->FirepowerMultiplier * TechnoExt::ExtMap.Find(pInvoker)->AE.FirepowerMultiplier), 0); + if (this->Duration > 0) + { + if (this->Type->Duration_ApplyVersus_Warhead) + { + auto const pArmor = pTechno->Shield && pTechno->Shield->IsActive() ? pTechno->Shield->GetArmor() : pTechno->GetTechnoType()->Armor; + this->Duration = Math::max(static_cast(this->Duration * GeneralUtils::GetWarheadVersusArmor(this->Type->Duration_ApplyVersus_Warhead, pArmor), 0)); + } + + if (this->Type->Duration_ApplyFirepowerMult && pInvoker) + this->Duration = Math::max(static_cast(this->Duration * pInvoker->FirepowerMultiplier * TechnoExt::ExtMap.Find(pInvoker)->AE.FirepowerMultiplier), 0); - if (this->Type->Duration_ApplyArmorMultOnTarget && this->Duration > 0) // count its own ArmorMultiplier as well - this->Duration = Math::max(static_cast(this->Duration / pTechno->ArmorMultiplier / TechnoExt::ExtMap.Find(pTechno)->AE.ArmorMultiplier / this->Type->ArmorMultiplier), 0); + if (this->Type->Duration_ApplyArmorMultOnTarget) // count its own ArmorMultiplier as well + this->Duration = Math::max(static_cast(this->Duration / pTechno->ArmorMultiplier / TechnoExt::ExtMap.Find(pTechno)->AE.ArmorMultiplier / this->Type->ArmorMultiplier), 0); + } AttachEffectClass::Array.emplace_back(this); } @@ -388,6 +397,12 @@ void AttachEffectClass::RefreshDuration(int durationOverride) else this->Duration = this->DurationOverride ? this->DurationOverride : this->Type->Duration; + if (this->Type->Duration_ApplyVersus_Warhead) + { + auto const pArmor = this->Techno->Shield && this->Techno->Shield->IsActive() ? this->Techno->Shield->GetArmor() : this->Techno->GetTechnoType()->Armor; + this->Duration = Math::max(static_cast(this->Duration * GeneralUtils::GetWarheadVersusArmor(this->Type->Duration_ApplyVersus_Warhead, pArmor), 0)); + } + if (this->Type->Duration_ApplyFirepowerMult && this->Duration > 0 && this->Invoker) this->Duration = Math::max(static_cast(this->Duration * this->Invoker->FirepowerMultiplier * TechnoExt::ExtMap.Find(this->Invoker)->AE.FirepowerMultiplier), 0); @@ -427,6 +442,15 @@ bool AttachEffectClass::ShouldBeDiscardedNow() const auto const pTechno = this->Techno; + if (this->Type->DiscardOn_AbovePercent > 0.0 && pTechno->GetHealthPercentage() >= this->Type->DiscardOn_AbovePercent) + return false; + + if (this->Type->DiscardOn_BelowPercent > 0.0 && pTechno->GetHealthPercentage() <= this->Type->DiscardOn_BelowPercent) + return false; + + if (this->Type->DiscardOn == DiscardCondition::None) + return false; + if (auto const pFoot = abstract_cast(pTechno)) { bool isMoving = pFoot->Locomotor->Is_Really_Moving_Now(); @@ -567,6 +591,12 @@ AttachEffectClass* AttachEffectClass::CreateAndAttach(AttachEffectTypeClass* pTy if (!pType || !pTarget) return nullptr; + if (pType->AffectAbovePercent > 0.0 && pTarget->GetHealthPercentage() < pType->AffectAbovePercent) + return nullptr; + + if (pType->AffectBelowPercent > 0.0 && pTarget->GetHealthPercentage() > pType->AffectBelowPercent) + return nullptr; + if (pTarget->IsIronCurtained()) { bool penetrates = pTarget->ForceShielded ? pType->PenetratesForceShield.Get(pType->PenetratesIronCurtain) : pType->PenetratesIronCurtain; diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index 8b66eac42f..5bfdd1592e 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -96,6 +96,7 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) INI_EX exINI(pINI); this->Duration.Read(exINI, pSection, "Duration"); + this->Duration_ApplyVersus_Warhead.Read(exINI, pSection, "Duration.ApplyVersusWarhead"); this->Duration_ApplyFirepowerMult.Read(exINI, pSection, "Duration.ApplyFirepowerMult"); this->Duration_ApplyArmorMultOnTarget.Read(exINI, pSection, "Duration.ApplyArmorMultOnTarget"); this->Cumulative.Read(exINI, pSection, "Cumulative"); @@ -103,6 +104,10 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->Powered.Read(exINI, pSection, "Powered"); this->DiscardOn.Read(exINI, pSection, "DiscardOn"); this->DiscardOn_RangeOverride.Read(exINI, pSection, "DiscardOn.RangeOverride"); + this->DiscardOn_AbovePercent.Read(exINI, pSection, "DiscardOn.AbovePercent"); + this->DiscardOn_BelowPercent.Read(exINI, pSection, "DiscardOn.BelowPercent"); + this->AffectAbovePercent.Read(exINI, pSection, "AffectAbovePercent"); + this->AffectBelowPercent.Read(exINI, pSection, "AffectBelowPercent"); this->PenetratesIronCurtain.Read(exINI, pSection, "PenetratesIronCurtain"); this->PenetratesForceShield.Read(exINI, pSection, "PenetratesForceShield"); @@ -168,6 +173,7 @@ void AttachEffectTypeClass::Serialize(T& Stm) { Stm .Process(this->Duration) + .Process(this->Duration_ApplyVersus_Warhead) .Process(this->Duration_ApplyFirepowerMult) .Process(this->Duration_ApplyArmorMultOnTarget) .Process(this->Cumulative) @@ -175,6 +181,10 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->Powered) .Process(this->DiscardOn) .Process(this->DiscardOn_RangeOverride) + .Process(this->DiscardOn_AbovePercent) + .Process(this->DiscardOn_BelowPercent) + .Process(this->AffectAbovePercent) + .Process(this->AffectBelowPercent) .Process(this->PenetratesIronCurtain) .Process(this->PenetratesForceShield) .Process(this->Animation) diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index 9a3f9d1359..72e48470a6 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -41,6 +41,7 @@ class AttachEffectTypeClass final : public Enumerable public: Valueable Duration; + Valueable Duration_ApplyVersus_Warhead; Valueable Duration_ApplyFirepowerMult; Valueable Duration_ApplyArmorMultOnTarget; Valueable Cumulative; @@ -48,6 +49,10 @@ class AttachEffectTypeClass final : public Enumerable Valueable Powered; Valueable DiscardOn; Nullable DiscardOn_RangeOverride; + Valueable DiscardOn_AbovePercent; + Valueable DiscardOn_BelowPercent; + Valueable AffectAbovePercent; + Valueable AffectBelowPercent; Valueable PenetratesIronCurtain; Nullable PenetratesForceShield; Valueable Animation; @@ -97,6 +102,7 @@ class AttachEffectTypeClass final : public Enumerable AttachEffectTypeClass(const char* const pTitle) : Enumerable(pTitle) , Duration { 0 } + , Duration_ApplyVersus_Warhead {} , Duration_ApplyFirepowerMult { false } , Duration_ApplyArmorMultOnTarget { false } , Cumulative { false } @@ -104,6 +110,10 @@ class AttachEffectTypeClass final : public Enumerable , Powered { false } , DiscardOn { DiscardCondition::None } , DiscardOn_RangeOverride {} + , DiscardOn_AbovePercent { 0.0 } + , DiscardOn_BelowPercent { 0.0 } + , AffectAbovePercent { 0.0 } + , AffectBelowPercent { 0.0 } , PenetratesIronCurtain { false } , PenetratesForceShield {} , Animation {} From 14f124f07f69220f9a9ffde4896902d4bf794974 Mon Sep 17 00:00:00 2001 From: Coronia <2217891145@qq.com> Date: Wed, 23 Apr 2025 11:23:18 +0800 Subject: [PATCH 02/10] ExtraWarheads AE --- CREDITS.md | 2 + docs/New-or-Enhanced-Logics.md | 11 +++++- docs/Whats-New.md | 2 + src/Ext/Bullet/Body.cpp | 41 +++++++++++++++++++++ src/Ext/Bullet/Body.h | 1 + src/Ext/Bullet/Hooks.DetonateLogics.cpp | 49 ++++++++----------------- src/Ext/Techno/Body.Update.cpp | 6 +++ src/Ext/WarheadType/Detonate.cpp | 4 ++ src/New/Entity/AttachEffectClass.cpp | 35 ++++++++++++------ src/New/Entity/AttachEffectClass.h | 2 + src/New/Type/AttachEffectTypeClass.cpp | 9 +++++ src/New/Type/AttachEffectTypeClass.h | 8 ++++ 12 files changed, 124 insertions(+), 46 deletions(-) diff --git a/CREDITS.md b/CREDITS.md index 0cc4546f48..d1bf42f954 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -468,6 +468,8 @@ This page lists all the individual contributions to the project by their author. - Fire weapon when Warhead kills something - Promotion animation deglobalization - Forcing specific weapon by range + - Attached effect attach/discard by health + - Attached effect with `ExtraWarheads` - **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 891b26f53c..6f0c2291d7 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -8,7 +8,7 @@ This page describes all the engine features that are either new and introduced b - Similar (but not identical) to [Ares' AttachEffect](https://ares-developers.github.io/Ares-docs/new/attacheffect.html), but with some differences and new features. The largest difference is that here attached effects are explicitly defined types. - `Duration` determines how long the effect lasts for. It can be overriden by `DurationOverrides` on TechnoTypes and Warheads. - - `Duration.ApplyVersus.Warhead` can multiply the duration by the set warhead's versus against the target's armor type if it's not negative. Can't reduce duration to below 0 by a negative versus. + - If `Duration.ApplyVersus.Warhead` is set, it can multiply the duration by the set warhead's versus against the target's armor type if it's not negative. Can't reduce duration to below 0 by a negative versus. - 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. @@ -36,6 +36,11 @@ This page describes all the engine features that are either new and introduced b - Attached effect can fire off a weapon when expired / removed / object dies by setting `ExpireWeapon`. - `ExpireWeapon.TriggerOn` determines the exact conditions upon which the weapon is fired, defaults to `expire` which means only if the effect naturally expires. - `ExpireWeapon.CumulativeOnlyOnce`, if set to true, makes it so that `Cumulative=true` attached effects only detonate the weapon once period, instead of once per active instance. On `remove` and `expire` condition this means it will only detonate after last instance has expired or been removed. + - Attached effect can allow the TechnoType's weapon to detonate multiple Warheads on impact by listing `ExtraWarheads`. The warheads are detonated at same location as the main one, after it in listed order. This only works in cases where a projectile has been fired by a weapon and still remembers it when it is detonated (due to currently existing technical limitations, this excludes `AirburstWeapon`). + - `ExtraWarheads.DamageOverrides` can be used to override the weapon's `Damage` for the extra Warhead detonations. Value from position matching the position from `ExtraWarheads` is used if found, or last listed value if not found. If list is empty, WeaponType `Damage` is used. + - `ExtraWarheads.DetonationChances` can be used to customize the chance of each extra Warhead detonation occuring. Value from position matching the position from `ExtraWarheads` is used if found, or last listed value if not found. If list is empty, every extra Warhead detonation is guaranteed to occur. + - `ExtraWarheads.FullDetonation` can be used to customize whether or not each individual Warhead is detonated fully (as part of a dummy weapon) or simply deals area damage and applies Phobos' Warhead effects. Value from position matching the position from `ExtraWarheads` is used if found, or last listed value if not found. If list is empty, defaults to true. + - Note that the listed Warheads must be listed in `[Warheads]` for them to work. - `Tint.Color` & `Tint.Intensity` can be used to set a color tint effect and additive lighting increase/decrease on the object the effect is attached to, respectively. - `Tint.VisibleToHouses` can be used to control which houses can see the tint effect. - `FirepowerMultiplier`, `ArmorMultiplier`, `SpeedMultiplier` and `ROFMultiplier` can be used to modify the object's firepower, armor strength, movement speed and weapon reload rate, respectively. @@ -112,6 +117,10 @@ CumulativeAnimations.RestartOnChange=true ; boolean ExpireWeapon= ; WeaponType ExpireWeapon.TriggerOn=expire ; List of expire weapon trigger condition enumeration (none|expire|remove|death|discard|all) ExpireWeapon.CumulativeOnlyOnce=false ; boolean +ExtraWarheads= ; List of WarheadTypes +ExtraWarheads.DamageOverrides= ; List of integers +ExtraWarheads.DetonationChances= ; List of floating-point values (percentage or absolute) +ExtraWarheads.FullDetonation= ; List of booleans Tint.Color= ; integer - R,G,B Tint.Intensity= ; floating point value Tint.VisibleToHouses=all ; List of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) diff --git a/docs/Whats-New.md b/docs/Whats-New.md index c4c8116fac..593136f930 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -370,6 +370,8 @@ New: - Customize airstrike targets (by NetsuNegi) - Aggressive attack move mission (by CrimRecya) - Amphibious access vehicle (by CrimRecya) +- Attached effect attach/discard by health (by Ollerus) +- Attached effect with `ExtraWarheads` (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/Bullet/Body.cpp b/src/Ext/Bullet/Body.cpp index 66dac0462f..76aff93f56 100644 --- a/src/Ext/Bullet/Body.cpp +++ b/src/Ext/Bullet/Body.cpp @@ -154,6 +154,47 @@ void BulletExt::ExtData::InitializeLaserTrails() } } +void BulletExt::ExtData::ApplyExtraWarheads(std::vector exWH, std::vector exWHDamageOverrides, std::vector exWHChances, std::vector exWHFull, CoordStruct* coords, HouseClass* pOwner) +{ + auto const pThis = this->OwnerObject(); + int damage = pThis->WeaponType ? pThis->WeaponType->Damage : 0; + + for (size_t i = 0; i < exWH.size(); i++) + { + auto const pWH = exWH[i]; + size_t size = exWHDamageOverrides.size(); + + if (size > i) + damage = exWHDamageOverrides[i]; + else if (size > 0) + damage = exWHDamageOverrides[size - 1]; + + bool detonate = true; + size = exWHChances.size(); + + if (size > i) + detonate = exWHChances[i] >= ScenarioClass::Instance->Random.RandomDouble(); + else if (size > 0) + detonate = exWHChances[size - 1] >= ScenarioClass::Instance->Random.RandomDouble(); + + bool isFull = true; + size = exWHFull.size(); + + if (size > i) + isFull = exWHFull[i]; + else if (size > 0) + isFull = exWHFull[size - 1]; + + if (!detonate) + continue; + + if (isFull) + WarheadTypeExt::DetonateAt(pWH, *coords, pThis->Owner, damage, pOwner, pThis->Target); + else + WarheadTypeExt::ExtMap.Find(pWH)->DamageAreaWithTarget(*coords, damage, pThis->Owner, pWH, true, pOwner, abstract_cast(pThis->Target)); + } +} + static inline int SetBuildingFireAnimZAdjust(BuildingClass* pBuilding, int animY) { if (pBuilding->GetOccupantCount() > 0) diff --git a/src/Ext/Bullet/Body.h b/src/Ext/Bullet/Body.h index 2bc3d6c33d..036709ed17 100644 --- a/src/Ext/Bullet/Body.h +++ b/src/Ext/Bullet/Body.h @@ -54,6 +54,7 @@ class BulletExt void InterceptBullet(TechnoClass* pSource, WeaponTypeClass* pWeapon); void ApplyRadiationToCell(CellStruct Cell, int Spread, int RadLevel); void InitializeLaserTrails(); + void ApplyExtraWarheads(std::vector exWH, std::vector exWHDamageOverrides, std::vector exWHChances, std::vector exWHFull, CoordStruct* coords, HouseClass* pOwner); private: template diff --git a/src/Ext/Bullet/Hooks.DetonateLogics.cpp b/src/Ext/Bullet/Hooks.DetonateLogics.cpp index 679e6d42c3..3d8de0cdc1 100644 --- a/src/Ext/Bullet/Hooks.DetonateLogics.cpp +++ b/src/Ext/Bullet/Hooks.DetonateLogics.cpp @@ -297,48 +297,29 @@ DEFINE_HOOK(0x469AA4, BulletClass_Logics_Extras, 0x5) GET(BulletClass*, pThis, ESI); GET_BASE(CoordStruct*, coords, 0x8); - auto const pOwner = pThis->Owner ? pThis->Owner->Owner : BulletExt::ExtMap.Find(pThis)->FirerHouse; + auto const pBulletExt = BulletExt::ExtMap.Find(pThis); + auto const pOwner = pThis->Owner ? pThis->Owner->Owner : pBulletExt->FirerHouse; // Extra warheads if (pThis->WeaponType) { auto const pWeaponExt = WeaponTypeExt::ExtMap.Find(pThis->WeaponType); - int defaultDamage = pThis->WeaponType->Damage; - - for (size_t i = 0; i < pWeaponExt->ExtraWarheads.size(); i++) - { - auto const pWH = pWeaponExt->ExtraWarheads[i]; - int damage = defaultDamage; - size_t size = pWeaponExt->ExtraWarheads_DamageOverrides.size(); - - if (size > i) - damage = pWeaponExt->ExtraWarheads_DamageOverrides[i]; - else if (size > 0) - damage = pWeaponExt->ExtraWarheads_DamageOverrides[size - 1]; - - bool detonate = true; - size = pWeaponExt->ExtraWarheads_DetonationChances.size(); - - if (size > i) - detonate = pWeaponExt->ExtraWarheads_DetonationChances[i] >= ScenarioClass::Instance->Random.RandomDouble(); - else if (size > 0) - detonate = pWeaponExt->ExtraWarheads_DetonationChances[size - 1] >= ScenarioClass::Instance->Random.RandomDouble(); - - bool isFull = true; - size = pWeaponExt->ExtraWarheads_FullDetonation.size(); + pBulletExt->ApplyExtraWarheads(pWeaponExt->ExtraWarheads, pWeaponExt->ExtraWarheads_DamageOverrides, pWeaponExt->ExtraWarheads_DetonationChances, pWeaponExt->ExtraWarheads_FullDetonation, coords, pOwner); + } - if (size > i) - isFull = pWeaponExt->ExtraWarheads_FullDetonation[i]; - else if (size > 0) - isFull = pWeaponExt->ExtraWarheads_FullDetonation[size - 1]; + if (pThis->Owner) + { + auto const pExt = TechnoExt::ExtMap.Find(pThis->Owner); - if (!detonate) - continue; + if (pExt->AE.HasExtraWarheads) + { + for (auto const& pAE : pExt->AttachedEffects) + { + auto const pType = pAE->GetType(); - if (isFull) - WarheadTypeExt::DetonateAt(pWH, *coords, pThis->Owner, damage, pOwner, pThis->Target); - else - WarheadTypeExt::ExtMap.Find(pWH)->DamageAreaWithTarget(*coords, damage, pThis->Owner, pWH, true, pOwner, abstract_cast(pThis->Target)); + if (pType->ExtraWarheads.size() > 0) + pBulletExt->ApplyExtraWarheads(pType->ExtraWarheads, pType->ExtraWarheads_DamageOverrides, pType->ExtraWarheads_DetonationChances, pType->ExtraWarheads_FullDetonation, coords, pOwner); + } } } diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index b462a4f654..07f6d6ab87 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -1169,6 +1169,8 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() bool reflectsDamage = false; bool hasOnFireDiscardables = false; bool hasRestrictedArmorMultipliers = false; + bool hasCritModifiers = false; + bool hasExtraWarheads = false; for (const auto& attachEffect : this->AttachedEffects) { @@ -1189,6 +1191,8 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() reflectsDamage |= type->ReflectDamage; hasOnFireDiscardables |= (type->DiscardOn & DiscardCondition::Firing) != DiscardCondition::None; hasRestrictedArmorMultipliers |= (type->ArmorMultiplier != 1.0 && (type->ArmorMultiplier_AllowWarheads.size() > 0 || type->ArmorMultiplier_DisallowWarheads.size() > 0)); + hasCritModifiers |= (type->Crit_Multiplier != 1.0 || type->Crit_ExtraChance != 0.0); + hasExtraWarheads |= type->ExtraWarheads.size() > 0; } this->AE.FirepowerMultiplier = firepower; @@ -1204,6 +1208,8 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() this->AE.ReflectDamage = reflectsDamage; this->AE.HasOnFireDiscardables = hasOnFireDiscardables; this->AE.HasRestrictedArmorMultipliers = hasRestrictedArmorMultipliers; + this->AE.HasCritModifiers = hasCritModifiers; + this->AE.HasExtraWarheads = hasExtraWarheads; if (forceDecloak && pThis->CloakState == CloakState::Cloaked) pThis->Uncloak(true); diff --git a/src/Ext/WarheadType/Detonate.cpp b/src/Ext/WarheadType/Detonate.cpp index a739119676..7dc353c209 100644 --- a/src/Ext/WarheadType/Detonate.cpp +++ b/src/Ext/WarheadType/Detonate.cpp @@ -634,6 +634,10 @@ double WarheadTypeExt::ExtData::GetCritChance(TechnoClass* pFirer) const return critChance; auto const pExt = TechnoExt::ExtMap.Find(pFirer); + + if (!pExt->AE.HasCritModifiers) + return critChance; + double extraChance = 0.0; for (auto& attachEffect : pExt->AttachedEffects) diff --git a/src/New/Entity/AttachEffectClass.cpp b/src/New/Entity/AttachEffectClass.cpp index ecf18e692f..570deaed8c 100644 --- a/src/New/Entity/AttachEffectClass.cpp +++ b/src/New/Entity/AttachEffectClass.cpp @@ -46,21 +46,26 @@ AttachEffectClass::AttachEffectClass(AttachEffectTypeClass* pType, TechnoClass* this->Duration = this->DurationOverride != 0 ? this->DurationOverride : this->Type->Duration; - if (this->Duration > 0) + if (this->Type->Duration_ApplyVersus_Warhead && this->Duration > 0) { - if (this->Type->Duration_ApplyVersus_Warhead) + auto const pTechnoExt = TechnoExt::ExtMap.Find(pTechno); + auto pArmor = pTechno->GetTechnoType()->Armor; + + if (auto const pShieldData = pTechnoExt->Shield.get()) { - auto const pArmor = pTechno->Shield && pTechno->Shield->IsActive() ? pTechno->Shield->GetArmor() : pTechno->GetTechnoType()->Armor; - this->Duration = Math::max(static_cast(this->Duration * GeneralUtils::GetWarheadVersusArmor(this->Type->Duration_ApplyVersus_Warhead, pArmor), 0)); + if (pShieldData->IsActive()) + pArmor = pShieldData->GetArmorType(); } - if (this->Type->Duration_ApplyFirepowerMult && pInvoker) - this->Duration = Math::max(static_cast(this->Duration * pInvoker->FirepowerMultiplier * TechnoExt::ExtMap.Find(pInvoker)->AE.FirepowerMultiplier), 0); - - if (this->Type->Duration_ApplyArmorMultOnTarget) // count its own ArmorMultiplier as well - this->Duration = Math::max(static_cast(this->Duration / pTechno->ArmorMultiplier / TechnoExt::ExtMap.Find(pTechno)->AE.ArmorMultiplier / this->Type->ArmorMultiplier), 0); + this->Duration = Math::max(static_cast(this->Duration * GeneralUtils::GetWarheadVersusArmor(this->Type->Duration_ApplyVersus_Warhead, pArmor)), 0); } + if (this->Type->Duration_ApplyFirepowerMult && this->Duration > 0 && pInvoker) + this->Duration = Math::max(static_cast(this->Duration * pInvoker->FirepowerMultiplier * TechnoExt::ExtMap.Find(pInvoker)->AE.FirepowerMultiplier), 0); + + if (this->Type->Duration_ApplyArmorMultOnTarget && this->Duration > 0) // count its own ArmorMultiplier as well + this->Duration = Math::max(static_cast(this->Duration / pTechno->ArmorMultiplier / TechnoExt::ExtMap.Find(pTechno)->AE.ArmorMultiplier / this->Type->ArmorMultiplier), 0); + AttachEffectClass::Array.emplace_back(this); } @@ -399,8 +404,16 @@ void AttachEffectClass::RefreshDuration(int durationOverride) if (this->Type->Duration_ApplyVersus_Warhead) { - auto const pArmor = this->Techno->Shield && this->Techno->Shield->IsActive() ? this->Techno->Shield->GetArmor() : this->Techno->GetTechnoType()->Armor; - this->Duration = Math::max(static_cast(this->Duration * GeneralUtils::GetWarheadVersusArmor(this->Type->Duration_ApplyVersus_Warhead, pArmor), 0)); + auto const pTechnoExt = TechnoExt::ExtMap.Find(this->Techno); + auto pArmor = this->Techno->GetTechnoType()->Armor; + + if (auto const pShieldData = pTechnoExt->Shield.get()) + { + if (pShieldData->IsActive()) + pArmor = pShieldData->GetArmorType(); + } + + this->Duration = Math::max(static_cast(this->Duration * GeneralUtils::GetWarheadVersusArmor(this->Type->Duration_ApplyVersus_Warhead, pArmor)), 0); } if (this->Type->Duration_ApplyFirepowerMult && this->Duration > 0 && this->Invoker) diff --git a/src/New/Entity/AttachEffectClass.h b/src/New/Entity/AttachEffectClass.h index 56797c9bf6..3059cfb2d9 100644 --- a/src/New/Entity/AttachEffectClass.h +++ b/src/New/Entity/AttachEffectClass.h @@ -96,6 +96,8 @@ struct AttachEffectTechnoProperties bool ReflectDamage; bool HasOnFireDiscardables; bool HasRestrictedArmorMultipliers; + bool HasCritModifiers; + bool HasExtraWarheads; AttachEffectTechnoProperties() : FirepowerMultiplier { 1.0 } diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index 5bfdd1592e..a9d1a463d3 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -124,6 +124,11 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->ExpireWeapon_TriggerOn.Read(exINI, pSection, "ExpireWeapon.TriggerOn"); this->ExpireWeapon_CumulativeOnlyOnce.Read(exINI, pSection, "ExpireWeapon.CumulativeOnlyOnce"); + this->ExtraWarheads.Read(exINI, pSection, "ExtraWarheads"); + this->ExtraWarheads_DamageOverrides.Read(exINI, pSection, "ExtraWarheads.DamageOverrides"); + this->ExtraWarheads_DetonationChances.Read(exINI, pSection, "ExtraWarheads.DetonationChances"); + this->ExtraWarheads_FullDetonation.Read(exINI, pSection, "ExtraWarheads.FullDetonation"); + this->Tint_Color.Read(exINI, pSection, "Tint.Color"); this->Tint_Intensity.Read(exINI, pSection, "Tint.Intensity"); this->Tint_VisibleToHouses.Read(exINI, pSection, "Tint.VisibleToHouses"); @@ -198,6 +203,10 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->ExpireWeapon) .Process(this->ExpireWeapon_TriggerOn) .Process(this->ExpireWeapon_CumulativeOnlyOnce) + .Process(this->ExtraWarheads) + .Process(this->ExtraWarheads_DamageOverrides) + .Process(this->ExtraWarheads_DetonationChances) + .Process(this->ExtraWarheads_FullDetonation) .Process(this->Tint_Color) .Process(this->Tint_Intensity) .Process(this->Tint_VisibleToHouses) diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index 72e48470a6..796f0c991e 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -66,6 +66,10 @@ class AttachEffectTypeClass final : public Enumerable Valueable ExpireWeapon; Valueable ExpireWeapon_TriggerOn; Valueable ExpireWeapon_CumulativeOnlyOnce; + ValueableVector ExtraWarheads; + ValueableVector ExtraWarheads_DamageOverrides; + ValueableVector ExtraWarheads_DetonationChances; + ValueableVector ExtraWarheads_FullDetonation; Nullable Tint_Color; Valueable Tint_Intensity; Valueable Tint_VisibleToHouses; @@ -127,6 +131,10 @@ class AttachEffectTypeClass final : public Enumerable , ExpireWeapon {} , ExpireWeapon_TriggerOn { ExpireWeaponCondition::Expire } , ExpireWeapon_CumulativeOnlyOnce { false } + , ExtraWarheads {} + , ExtraWarheads_DamageOverrides {} + , ExtraWarheads_DetonationChances {} + , ExtraWarheads_FullDetonation {} , Tint_Color {} , Tint_Intensity { 0.0 } , Tint_VisibleToHouses { AffectedHouse::All } From 8552728c136fb3b6d0c4c963091a4f400f24bdd1 Mon Sep 17 00:00:00 2001 From: Coronia <2217891145@qq.com> Date: Wed, 23 Apr 2025 14:12:21 +0800 Subject: [PATCH 03/10] AE FeedbackWeapon --- docs/New-or-Enhanced-Logics.md | 3 +++ src/Ext/Techno/Body.Update.cpp | 3 +++ src/Ext/Techno/Hooks.Firing.cpp | 16 ++++++++++++++++ src/New/Entity/AttachEffectClass.cpp | 5 +++-- src/New/Entity/AttachEffectClass.h | 4 ++++ src/New/Type/AttachEffectTypeClass.cpp | 3 +++ src/New/Type/AttachEffectTypeClass.h | 2 ++ 7 files changed, 34 insertions(+), 2 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 6f0c2291d7..cb019144c9 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -41,6 +41,8 @@ This page describes all the engine features that are either new and introduced b - `ExtraWarheads.DetonationChances` can be used to customize the chance of each extra Warhead detonation occuring. Value from position matching the position from `ExtraWarheads` is used if found, or last listed value if not found. If list is empty, every extra Warhead detonation is guaranteed to occur. - `ExtraWarheads.FullDetonation` can be used to customize whether or not each individual Warhead is detonated fully (as part of a dummy weapon) or simply deals area damage and applies Phobos' Warhead effects. Value from position matching the position from `ExtraWarheads` is used if found, or last listed value if not found. If list is empty, defaults to true. - Note that the listed Warheads must be listed in `[Warheads]` for them to work. + - `FeedbackWeapon` can specify an auxiliary weapon to be fired on the firer itself when a weapon is fired. + - `FireInTransport` setting of the feedback weapon is respected to determine if it can be fired when the original weapon is fired from inside `OpenTopped=true` transport. If feedback weapon is fired, it is fired on the transport. `OpenToppedDamageMultiplier` is not applied on feedback weapons. - `Tint.Color` & `Tint.Intensity` can be used to set a color tint effect and additive lighting increase/decrease on the object the effect is attached to, respectively. - `Tint.VisibleToHouses` can be used to control which houses can see the tint effect. - `FirepowerMultiplier`, `ArmorMultiplier`, `SpeedMultiplier` and `ROFMultiplier` can be used to modify the object's firepower, armor strength, movement speed and weapon reload rate, respectively. @@ -121,6 +123,7 @@ ExtraWarheads= ; List of WarheadTypes ExtraWarheads.DamageOverrides= ; List of integers ExtraWarheads.DetonationChances= ; List of floating-point values (percentage or absolute) ExtraWarheads.FullDetonation= ; List of booleans +FeedbackWeapon= ; WeaponType Tint.Color= ; integer - R,G,B Tint.Intensity= ; floating point value Tint.VisibleToHouses=all ; List of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index 07f6d6ab87..b1746334a4 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -1171,6 +1171,7 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() bool hasRestrictedArmorMultipliers = false; bool hasCritModifiers = false; bool hasExtraWarheads = false; + bool hasFeedbackWeapon = false; for (const auto& attachEffect : this->AttachedEffects) { @@ -1193,6 +1194,7 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() hasRestrictedArmorMultipliers |= (type->ArmorMultiplier != 1.0 && (type->ArmorMultiplier_AllowWarheads.size() > 0 || type->ArmorMultiplier_DisallowWarheads.size() > 0)); hasCritModifiers |= (type->Crit_Multiplier != 1.0 || type->Crit_ExtraChance != 0.0); hasExtraWarheads |= type->ExtraWarheads.size() > 0; + hasFeedbackWeapon |= type->FeedbackWeapon != nullptr; } this->AE.FirepowerMultiplier = firepower; @@ -1210,6 +1212,7 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() this->AE.HasRestrictedArmorMultipliers = hasRestrictedArmorMultipliers; this->AE.HasCritModifiers = hasCritModifiers; this->AE.HasExtraWarheads = hasExtraWarheads; + this->AE.HasFeedbackWeapon = hasFeedbackWeapon; if (forceDecloak && pThis->CloakState == CloakState::Cloaked) pThis->Uncloak(true); diff --git a/src/Ext/Techno/Hooks.Firing.cpp b/src/Ext/Techno/Hooks.Firing.cpp index af7b5dce16..9cfdd5a43c 100644 --- a/src/Ext/Techno/Hooks.Firing.cpp +++ b/src/Ext/Techno/Hooks.Firing.cpp @@ -586,6 +586,22 @@ DEFINE_HOOK(0x6FF43F, TechnoClass_FireAt_FeedbackWeapon, 0x6) } } + auto const pExt = TechnoExt::ExtMap.Find(pThis); + + if (pExt->AE.HasFeedbackWeapon) + { + for (auto const& pAE : pExt->AttachedEffects) + { + if (auto const pWeaponFeedback = pAE->GetType()->FeedbackWeapon) + { + if (pThis->InOpenToppedTransport && !pWeaponFeedback->FireInTransport) + return 0; + + WeaponTypeExt::DetonateAt(pWeaponFeedback, pThis, pThis); + } + } + } + return 0; } diff --git a/src/New/Entity/AttachEffectClass.cpp b/src/New/Entity/AttachEffectClass.cpp index 570deaed8c..595824fd1d 100644 --- a/src/New/Entity/AttachEffectClass.cpp +++ b/src/New/Entity/AttachEffectClass.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -57,7 +58,7 @@ AttachEffectClass::AttachEffectClass(AttachEffectTypeClass* pType, TechnoClass* pArmor = pShieldData->GetArmorType(); } - this->Duration = Math::max(static_cast(this->Duration * GeneralUtils::GetWarheadVersusArmor(this->Type->Duration_ApplyVersus_Warhead, pArmor)), 0); + this->Duration = Math::max(MapClass::GetTotalDamage(this->Duration, this->Type->Duration_ApplyVersus_Warhead, pArmor, 0), 0); } if (this->Type->Duration_ApplyFirepowerMult && this->Duration > 0 && pInvoker) @@ -413,7 +414,7 @@ void AttachEffectClass::RefreshDuration(int durationOverride) pArmor = pShieldData->GetArmorType(); } - this->Duration = Math::max(static_cast(this->Duration * GeneralUtils::GetWarheadVersusArmor(this->Type->Duration_ApplyVersus_Warhead, pArmor)), 0); + this->Duration = Math::max(MapClass::GetTotalDamage(this->Duration, this->Type->Duration_ApplyVersus_Warhead, pArmor, 0), 0); } if (this->Type->Duration_ApplyFirepowerMult && this->Duration > 0 && this->Invoker) diff --git a/src/New/Entity/AttachEffectClass.h b/src/New/Entity/AttachEffectClass.h index 3059cfb2d9..02a1bfae87 100644 --- a/src/New/Entity/AttachEffectClass.h +++ b/src/New/Entity/AttachEffectClass.h @@ -98,6 +98,7 @@ struct AttachEffectTechnoProperties bool HasRestrictedArmorMultipliers; bool HasCritModifiers; bool HasExtraWarheads; + bool HasFeedbackWeapon; AttachEffectTechnoProperties() : FirepowerMultiplier { 1.0 } @@ -113,5 +114,8 @@ struct AttachEffectTechnoProperties , ReflectDamage { false } , HasOnFireDiscardables { false } , HasRestrictedArmorMultipliers { false } + , HasCritModifiers { false } + , HasExtraWarheads { false } + , HasFeedbackWeapon { false } { } }; diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index a9d1a463d3..ecf8216ef4 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -129,6 +129,8 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->ExtraWarheads_DetonationChances.Read(exINI, pSection, "ExtraWarheads.DetonationChances"); this->ExtraWarheads_FullDetonation.Read(exINI, pSection, "ExtraWarheads.FullDetonation"); + this->FeedbackWeapon.Read(exINI, pSection, "FeedbackWeapon"); + this->Tint_Color.Read(exINI, pSection, "Tint.Color"); this->Tint_Intensity.Read(exINI, pSection, "Tint.Intensity"); this->Tint_VisibleToHouses.Read(exINI, pSection, "Tint.VisibleToHouses"); @@ -207,6 +209,7 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->ExtraWarheads_DamageOverrides) .Process(this->ExtraWarheads_DetonationChances) .Process(this->ExtraWarheads_FullDetonation) + .Process(this->FeedbackWeapon) .Process(this->Tint_Color) .Process(this->Tint_Intensity) .Process(this->Tint_VisibleToHouses) diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index 796f0c991e..8905186162 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -70,6 +70,7 @@ class AttachEffectTypeClass final : public Enumerable ValueableVector ExtraWarheads_DamageOverrides; ValueableVector ExtraWarheads_DetonationChances; ValueableVector ExtraWarheads_FullDetonation; + Valueable FeedbackWeapon; Nullable Tint_Color; Valueable Tint_Intensity; Valueable Tint_VisibleToHouses; @@ -135,6 +136,7 @@ class AttachEffectTypeClass final : public Enumerable , ExtraWarheads_DamageOverrides {} , ExtraWarheads_DetonationChances {} , ExtraWarheads_FullDetonation {} + , FeedbackWeapon {} , Tint_Color {} , Tint_Intensity { 0.0 } , Tint_VisibleToHouses { AffectedHouse::All } From 7355c24a2baba3c28a0f53c27ba5f3d246e89bba Mon Sep 17 00:00:00 2001 From: Coronia <2217891145@qq.com> Date: Wed, 23 Apr 2025 14:43:54 +0800 Subject: [PATCH 04/10] RealLaunch --- docs/New-or-Enhanced-Logics.md | 7 ++- src/Ext/Techno/WeaponHelpers.cpp | 59 ++++++++++++++++++++++++-- src/Ext/TechnoType/Body.cpp | 2 + src/Ext/TechnoType/Body.h | 2 + src/Ext/WarheadType/Body.cpp | 2 + src/Ext/WarheadType/Body.h | 2 + src/New/Type/AttachEffectTypeClass.cpp | 2 + src/New/Type/AttachEffectTypeClass.h | 2 + 8 files changed, 74 insertions(+), 4 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index cb019144c9..e5cee3ffbc 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -57,6 +57,7 @@ This page describes all the engine features that are either new and introduced b - `Crit.AllowWarheads` can be used to list only Warheads that can benefit from this critical hit chance multiplier and `Crit.DisallowWarheads` weapons that are not allowed to, respectively. - `RevengeWeapon` can be used to temporarily grant the specified weapon as a [revenge weapon](#revenge-weapon) for the attached object. - `RevengeWeapon.AffectsHouses` customizes which houses can trigger the revenge weapon. + - If `RevengeWeapon.RealLaunch` set to true, the weapon will be fired through a real projectile from the TechnoType to the killer. - `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. - 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. - `DisableWeapons` can be used to disable ability to fire any and all weapons. @@ -146,6 +147,7 @@ Crit.AllowWarheads= ; List of WarheadTypes Crit.DisallowWarheads= ; List of WarheadTypes RevengeWeapon= ; WeaponType RevengeWeapon.AffectsHouses=all ; List of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) +RevengeWeapon.RealLaunch=false ; boolean ReflectDamage=false ; boolean ReflectDamage.Warhead= ; WarheadType ReflectDamage.Warhead.Detonate=false ; WarheadType @@ -1681,6 +1683,7 @@ RecountBurst= ; boolean - Similar to `DeathWeapon` in that it is fired after a TechnoType is killed, but with the difference that it will be fired on whoever dealt the damage that killed the TechnoType. If TechnoType died of sources other than direct damage dealt by another TechnoType, `RevengeWeapon` will not be fired. - `RevengeWeapon.AffectsHouses` can be used to filter which houses the damage that killed the TechnoType is allowed to come from to fire the weapon. + - If `RevengeWeapon.RealLaunch` set to true, the weapon will be fired through a real projectile from the TechnoType to the killer. - It is possible to grant revenge weapons through [attached effects](#attached-effects) as well. - If a Warhead has `SuppressRevengeWeapons` set to true, it will not trigger revenge weapons. `SuppressRevengeWeapons.Types` can be used to list WeaponTypes affected by this, if none are listed all WeaponTypes are affected. @@ -1689,6 +1692,7 @@ In `rulesmd.ini`: [SOMETECHNO] ; TechnoType RevengeWeapon= ; WeaponType RevengeWeapon.AffectsHouses=all ; List of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) +RevengeWeapon.RealLaunch=false ; boolean [SOMEWARHEAD] ; WarheadType SuppressRevengeWeapons=false ; boolean @@ -2022,7 +2026,7 @@ While this feature can provide better performance than a large `CellSpread` valu ### Fire weapon when Warhead kills something - `KillWeapon` will be fired at the target TechnoType's location once it's killed by this Warhead. -- `KillWeapon.OnFirer` will be fired at the attacker's location once the target TechnoType is killed by this Warhead. If the source of this Warhead is not another TechnoType, `KillWeapon.OnFirer` will not be fired. +- `KillWeapon.OnFirer` will be fired at the attacker's location once the target TechnoType is killed by this Warhead. If the source of this Warhead is not another TechnoType, `KillWeapon.OnFirer` will not be fired. If `KillWeapon.OnFirer.RealLaunch` set to true, the weapon will be fired through a real projectile from the TechnoType to the killer. - `KillWeapon.AffectsHouses` / `KillWeapon.OnFirer.AffectsHouses` and `KillWeapon.Affects` / `KillWeapon.OnFirer.Affects` can be used to filter which houses targets can belong to and which types of targets are be considered valid for `KillWeapon` and `KillWeapon.OnFirer` respectively. - If the source of this Warhead is not another TechnoType, `KillWeapon` will be fired regardless of the target's house or type. - If a TechnoType has `SuppressKillWeapons` set to true, it will not trigger `KillWeapon` or `KillWeapon.OnFirer` upon being killed. `SuppressKillWeapons.Types` can be used to list WeaponTypes affected by this, if none are listed all WeaponTypes are affected. @@ -2036,6 +2040,7 @@ KillWeapon.AffectsHouses=all ; List of Affected House Enumeration (none KillWeapon.OnFirer.AffectsHouses=all ; List of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) KillWeapon.Affects=all ; List of Affected Target Enumeration (none|aircraft|buildings|infantry|units|all) KillWeapon.OnFirer.Affects=all ; List of Affected Target Enumeration (none|aircraft|buildings|infantry|units|all) +KillWeapon.OnFirer.RealLaunch=false ; boolean [SOMETECHNO] ; TechnoType SuppressKillWeapons=false ; boolean diff --git a/src/Ext/Techno/WeaponHelpers.cpp b/src/Ext/Techno/WeaponHelpers.cpp index 85dd3411d7..ccd3bf9ace 100644 --- a/src/Ext/Techno/WeaponHelpers.cpp +++ b/src/Ext/Techno/WeaponHelpers.cpp @@ -1,7 +1,9 @@ #include "Body.h" #include +#include +#include #include #include #include @@ -219,7 +221,24 @@ void TechnoExt::ApplyKillWeapon(TechnoClass* pThis, TechnoClass* pSource, Warhea && EnumFunctions::IsTechnoEligible(pThis, pWHExt->KillWeapon_OnFirer_Affects)) { if (!pTypeExt->SuppressKillWeapons || (hasFilters && !pTypeExt->SuppressKillWeapons_Types.Contains(pWHExt->KillWeapon_OnFirer))) - WeaponTypeExt::DetonateAt(pWHExt->KillWeapon_OnFirer, pSource, pSource); + { + if (pWHExt->KillWeapon_OnFirer_RealLaunch) + { + auto const pWeapon = pWHExt->KillWeapon_OnFirer; + + if (BulletClass* pBullet = pWeapon->Projectile->CreateBullet(pSource, pSource, + pWeapon->Damage, pWeapon->Warhead, pWeapon->Speed, pWeapon->Bright)) + { + pBullet->WeaponType = pWeapon; + pBullet->MoveTo(pSource->Location, BulletVelocity::Empty); + BulletExt::ExtMap.Find(pBullet)->FirerHouse = pSource->Owner; + } + } + else + { + WeaponTypeExt::DetonateAt(pWHExt->KillWeapon_OnFirer, pSource, pSource); + } + } } } @@ -233,7 +252,24 @@ void TechnoExt::ApplyRevengeWeapon(TechnoClass* pThis, TechnoClass* pSource, War if (pTypeExt->RevengeWeapon && EnumFunctions::CanTargetHouse(pTypeExt->RevengeWeapon_AffectsHouses, pThis->Owner, pSource->Owner)) { if (!pWHExt->SuppressRevengeWeapons || (hasFilters && !pWHExt->SuppressRevengeWeapons_Types.Contains(pTypeExt->RevengeWeapon))) - WeaponTypeExt::DetonateAt(pTypeExt->RevengeWeapon, pSource, pThis); + { + if (pTypeExt->RevengeWeapon_RealLaunch) + { + auto const pWeapon = pTypeExt->RevengeWeapon; + + if (BulletClass* pBullet = pWeapon->Projectile->CreateBullet(pSource, pThis, + pWeapon->Damage, pWeapon->Warhead, pWeapon->Speed, pWeapon->Bright)) + { + pBullet->WeaponType = pWeapon; + pBullet->MoveTo(pSource->Location, BulletVelocity::Empty); + BulletExt::ExtMap.Find(pBullet)->FirerHouse = pThis->Owner; + } + } + else + { + WeaponTypeExt::DetonateAt(pTypeExt->RevengeWeapon, pSource, pThis); + } + } } for (auto& attachEffect : pExt->AttachedEffects) @@ -250,7 +286,24 @@ void TechnoExt::ApplyRevengeWeapon(TechnoClass* pThis, TechnoClass* pSource, War continue; if (EnumFunctions::CanTargetHouse(pType->RevengeWeapon_AffectsHouses, pThis->Owner, pSource->Owner)) - WeaponTypeExt::DetonateAt(pType->RevengeWeapon, pSource, pThis); + { + if (pType->RevengeWeapon_RealLaunch) + { + auto const pWeapon = pType->RevengeWeapon; + + if (BulletClass* pBullet = pWeapon->Projectile->CreateBullet(pSource, pThis, + pWeapon->Damage, pWeapon->Warhead, pWeapon->Speed, pWeapon->Bright)) + { + pBullet->WeaponType = pWeapon; + pBullet->MoveTo(pSource->Location, BulletVelocity::Empty); + BulletExt::ExtMap.Find(pBullet)->FirerHouse = pThis->Owner; + } + } + else + { + WeaponTypeExt::DetonateAt(pType->RevengeWeapon, pSource, pThis); + } + } } } diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index 0da13ea0c3..b67a991894 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -469,6 +469,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->RevengeWeapon.Read(exINI, pSection, "RevengeWeapon"); this->RevengeWeapon_AffectsHouses.Read(exINI, pSection, "RevengeWeapon.AffectsHouses"); + this->RevengeWeapon_RealLaunch.Read(exINI, pSection, "RevengeWeapon.RealLaunch"); this->RecountBurst.Read(exINI, pSection, "RecountBurst"); @@ -934,6 +935,7 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->RevengeWeapon) .Process(this->RevengeWeapon_AffectsHouses) + .Process(this->RevengeWeapon_RealLaunch) .Process(this->AttachEffects) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index 7b3b6d35a1..3352e21ef4 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -242,6 +242,7 @@ class TechnoTypeExt Valueable RevengeWeapon; Valueable RevengeWeapon_AffectsHouses; + Valueable RevengeWeapon_RealLaunch; AEAttachInfoTypeClass AttachEffects; @@ -559,6 +560,7 @@ class TechnoTypeExt , RevengeWeapon {} , RevengeWeapon_AffectsHouses { AffectedHouse::All } + , RevengeWeapon_RealLaunch { false } , AttachEffects {} diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index 0426a2a665..24aadb5676 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -290,6 +290,7 @@ void WarheadTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->KillWeapon_OnFirer_AffectsHouses.Read(exINI, pSection, "KillWeapon.OnFirer.AffectsHouses"); this->KillWeapon_Affects.Read(exINI, pSection, "KillWeapon.Affects"); this->KillWeapon_OnFirer_Affects.Read(exINI, pSection, "KillWeapon.OnFirer.Affects"); + this->KillWeapon_OnFirer_RealLaunch.Read(exINI, pSection, "KillWeapon.OnFirer.RealLaunch"); this->ElectricAssaultLevel.Read(exINI, pSection, "ElectricAssaultLevel"); @@ -543,6 +544,7 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .Process(this->KillWeapon_OnFirer_AffectsHouses) .Process(this->KillWeapon_Affects) .Process(this->KillWeapon_OnFirer_Affects) + .Process(this->KillWeapon_OnFirer_RealLaunch) .Process(this->ElectricAssaultLevel) diff --git a/src/Ext/WarheadType/Body.h b/src/Ext/WarheadType/Body.h index b1a7ed3742..b7a4b61dac 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -172,6 +172,7 @@ class WarheadTypeExt Valueable KillWeapon_OnFirer_AffectsHouses; Valueable KillWeapon_Affects; Valueable KillWeapon_OnFirer_Affects; + Valueable KillWeapon_OnFirer_RealLaunch; Valueable ElectricAssaultLevel; @@ -376,6 +377,7 @@ class WarheadTypeExt , KillWeapon_OnFirer_AffectsHouses { AffectedHouse::All } , KillWeapon_Affects { AffectedTarget::All } , KillWeapon_OnFirer_Affects { AffectedTarget::All } + , KillWeapon_OnFirer_RealLaunch { false } { } void ApplyConvert(HouseClass* pHouse, TechnoClass* pTarget); diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index ecf8216ef4..0aa99c9413 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -158,6 +158,7 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->RevengeWeapon.Read(exINI, pSection, "RevengeWeapon"); this->RevengeWeapon_AffectsHouses.Read(exINI, pSection, "RevengeWeapon.AffectsHouses"); + this->RevengeWeapon_RealLaunch.Read(exINI, pSection, "RevengeWeapon.RealLaunch"); this->ReflectDamage.Read(exINI, pSection, "ReflectDamage"); this->ReflectDamage_Warhead.Read(exINI, pSection, "ReflectDamage.Warhead"); @@ -232,6 +233,7 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->Crit_DisallowWarheads) .Process(this->RevengeWeapon) .Process(this->RevengeWeapon_AffectsHouses) + .Process(this->RevengeWeapon_RealLaunch) .Process(this->ReflectDamage) .Process(this->ReflectDamage_Warhead) .Process(this->ReflectDamage_Warhead_Detonate) diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index 8905186162..b4175a6f2e 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -93,6 +93,7 @@ class AttachEffectTypeClass final : public Enumerable ValueableVector Crit_DisallowWarheads; Valueable RevengeWeapon; Valueable RevengeWeapon_AffectsHouses; + Valueable RevengeWeapon_RealLaunch; Valueable ReflectDamage; Nullable ReflectDamage_Warhead; Valueable ReflectDamage_Warhead_Detonate; @@ -159,6 +160,7 @@ class AttachEffectTypeClass final : public Enumerable , Crit_DisallowWarheads {} , RevengeWeapon {} , RevengeWeapon_AffectsHouses { AffectedHouse::All } + , RevengeWeapon_RealLaunch { false } , ReflectDamage { false } , ReflectDamage_Warhead {} , ReflectDamage_Warhead_Detonate { false } From cf714e53c043cf584fead9c5b8f2d0ad3773b13e Mon Sep 17 00:00:00 2001 From: Coronia <2217891145@qq.com> Date: Sun, 27 Apr 2025 21:43:52 +0800 Subject: [PATCH 05/10] AuxWeapon --- docs/New-or-Enhanced-Logics.md | 57 +++++++++--- src/Ext/Bullet/Hooks.DetonateLogics.cpp | 41 ++------- src/Ext/BulletType/Body.cpp | 2 + src/Ext/BulletType/Body.h | 2 + src/Ext/Techno/Body.Update.cpp | 6 +- src/Ext/Techno/Body.h | 2 + src/Ext/Techno/Hooks.Firing.cpp | 27 ++++-- src/Ext/Techno/WeaponHelpers.cpp | 117 +++++++++++++++++++++++- src/Ext/WeaponType/Body.cpp | 16 ++++ src/Ext/WeaponType/Body.h | 16 ++++ src/New/Entity/AttachEffectClass.h | 4 +- src/New/Type/AttachEffectTypeClass.cpp | 19 ++++ src/New/Type/AttachEffectTypeClass.h | 18 ++++ 13 files changed, 265 insertions(+), 62 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index e5cee3ffbc..efa35de607 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -41,7 +41,15 @@ This page describes all the engine features that are either new and introduced b - `ExtraWarheads.DetonationChances` can be used to customize the chance of each extra Warhead detonation occuring. Value from position matching the position from `ExtraWarheads` is used if found, or last listed value if not found. If list is empty, every extra Warhead detonation is guaranteed to occur. - `ExtraWarheads.FullDetonation` can be used to customize whether or not each individual Warhead is detonated fully (as part of a dummy weapon) or simply deals area damage and applies Phobos' Warhead effects. Value from position matching the position from `ExtraWarheads` is used if found, or last listed value if not found. If list is empty, defaults to true. - Note that the listed Warheads must be listed in `[Warheads]` for them to work. - - `FeedbackWeapon` can specify an auxiliary weapon to be fired on the firer itself when a weapon is fired. + - You can now specify an auxiliary weapon to be fired when a weapon is fired. + - `FireInTransport` setting of the auxiliary weapons are respected to determine if it can be fired when the original weapon is fired from inside `OpenTopped=true` transport. If auxiliary weapons are fired, it is fired on the transport. `OpenToppedDamageMultiplier` is not applied on auxiliary weapons. + - `AuxWeapon` is fired at the original target, or another nearby target if `AuxWeapon.Retarget` set to true. + - `AuxWeapon.Offset` defines the relative position to the firer that the auxiliary weapon will be fired from. `AuxWeapon.FireOnTurret` defines if the FLH is relative to the turret rather than the body. + - If `AuxWeapon.AllowZeroDamage` set to true, the auxiliary weapon will be fired even if its damage on the set target is 0. + - `AuxWeapon.ApplyFirepowerMult` determines whether or not the auxiliary weapon's damage should multiply the firer's firepower multipliers. + - `AuxWeapon.Retarget.AroundFirer` determines whether the original target or the firer will be the center of the retargeting. `AuxWeapon.Retarget.Range` determines the radius of the retargeting, default to the auxiliary weapon's `Range` if the center is the firer, and 0 if the center is the original target. + - `AuxWeapon.Retarget.Accuracy` defines the probability that the auxiliary weapon is fired to the original target. + - `FeedbackWeapon` is fired at the firer. - `FireInTransport` setting of the feedback weapon is respected to determine if it can be fired when the original weapon is fired from inside `OpenTopped=true` transport. If feedback weapon is fired, it is fired on the transport. `OpenToppedDamageMultiplier` is not applied on feedback weapons. - `Tint.Color` & `Tint.Intensity` can be used to set a color tint effect and additive lighting increase/decrease on the object the effect is attached to, respectively. - `Tint.VisibleToHouses` can be used to control which houses can see the tint effect. @@ -124,6 +132,15 @@ ExtraWarheads= ; List of WarheadTypes ExtraWarheads.DamageOverrides= ; List of integers ExtraWarheads.DetonationChances= ; List of floating-point values (percentage or absolute) ExtraWarheads.FullDetonation= ; List of booleans +AuxWeapon= ; WeaponType +AuxWeapon.Offset=0,0,0 ; integer - Forward,Lateral,Height +AuxWeapon.FireOnTurret=false ; boolean +AuxWeapon.AllowZeroDamage=true ; boolean +AuxWeapon.ApplyFirepowerMult=false ; boolean +AuxWeapon.Retarget=false ; boolean +AuxWeapon.Retarget.Range= ; floating point value +AuxWeapon.Retarget.Accuracy= ; floating point value, percents or absolute (0.0-1.0) +AuxWeapon.Retarget.AroundFirer=false ; boolean FeedbackWeapon= ; WeaponType Tint.Color= ; integer - R,G,B Tint.Intensity= ; floating point value @@ -966,11 +983,13 @@ SubjectToGround=false ; boolean ### Return weapon - It is now possible to make another weapon & projectile go off from a detonated projectile (in somewhat similar manner to `AirburstWeapon` or `ShrapnelWeapon`) straight back to the firer by setting `ReturnWeapon`. If the firer perishes before the initial projectile detonates, `ReturnWeapon` is not fired off. + - `ReturnWeapon.ApplyFirepowerMult` determines whether or not the auxiliary weapon's damage should multiply the firer's firepower multipliers. In `rulesmd.ini`: ```ini -[SOMEPROJECTILE] ; Projectile -ReturnWeapon= ; WeaponType +[SOMEPROJECTILE] ; Projectile +ReturnWeapon= ; WeaponType +ReturnWeapon.ApplyFirepowerMult=false ; boolean ``` ```{note} @@ -2241,18 +2260,34 @@ ExtraWarheads.DetonationChances= ; List of floating-point values (percentage or ExtraWarheads.FullDetonation= ; List of booleans ``` -### Feedback weapon +### Auxiliary weapon ![image](_static/images/feedbackweapon.gif) *`FeedbackWeapon` used to apply healing aura upon firing a weapon in [Project Phantom](https://www.moddb.com/mods/project-phantom)* -- You can now specify an auxiliary weapon to be fired on the firer itself when a weapon is fired. - - `FireInTransport` setting of the feedback weapon is respected to determine if it can be fired when the original weapon is fired from inside `OpenTopped=true` transport. If feedback weapon is fired, it is fired on the transport. `OpenToppedDamageMultiplier` is not applied on feedback weapons. - -In `rulesmd.ini`: -```ini -[SOMEWEAPON] ; WeaponType -FeedbackWeapon= ; WeaponType +- You can now specify an auxiliary weapon to be fired when a weapon is fired. + - `FireInTransport` setting of the auxiliary weapons are respected to determine if it can be fired when the original weapon is fired from inside `OpenTopped=true` transport. If auxiliary weapons are fired, it is fired on the transport. `OpenToppedDamageMultiplier` is not applied on auxiliary weapons. +- `AuxWeapon` is fired at the original target, or another nearby target if `AuxWeapon.Retarget` set to true. + - `AuxWeapon.Offset` defines the relative position to the firer that the auxiliary weapon will be fired from. `AuxWeapon.FireOnTurret` defines if the FLH is relative to the turret rather than the body. + - If `AuxWeapon.AllowZeroDamage` set to true, the auxiliary weapon will be fired even if its damage on the set target is 0. + - `AuxWeapon.ApplyFirepowerMult` determines whether or not the auxiliary weapon's damage should multiply the firer's firepower multipliers. + - `AuxWeapon.Retarget.AroundFirer` determines whether the original target or the firer will be the center of the retargeting. `AuxWeapon.Retarget.Range` determines the radius of the retargeting, default to the auxiliary weapon's `Range` if the center is the firer, and 0 if the center is the original target. + - `AuxWeapon.Retarget.Accuracy` defines the probability that the auxiliary weapon is fired to the original target. +- `FeedbackWeapon` is fired at the firer. + +In `rulesmd.ini`: +```ini +[SOMEWEAPON] ; WeaponType +AuxWeapon= ; WeaponType +AuxWeapon.Offset=0,0,0 ; integer - Forward,Lateral,Height +AuxWeapon.FireOnTurret=false ; boolean +AuxWeapon.AllowZeroDamage=true ; boolean +AuxWeapon.ApplyFirepowerMult=false ; boolean +AuxWeapon.Retarget=false ; boolean +AuxWeapon.Retarget.Range= ; floating point value +AuxWeapon.Retarget.Accuracy= ; floating point value, percents or absolute (0.0-1.0) +AuxWeapon.Retarget.AroundFirer=false ; boolean +FeedbackWeapon= ; WeaponType ``` ### Keep Range After Firing diff --git a/src/Ext/Bullet/Hooks.DetonateLogics.cpp b/src/Ext/Bullet/Hooks.DetonateLogics.cpp index 3d8de0cdc1..39a85f615c 100644 --- a/src/Ext/Bullet/Hooks.DetonateLogics.cpp +++ b/src/Ext/Bullet/Hooks.DetonateLogics.cpp @@ -330,8 +330,13 @@ DEFINE_HOOK(0x469AA4, BulletClass_Logics_Extras, 0x5) if (auto const pWeapon = pTypeExt->ReturnWeapon) { + auto damage = pWeapon->Damage; + + if (pTypeExt->ReturnWeapon_ApplyFirepowerMult) + damage = static_cast(damage * pThis->Owner->FirepowerMultiplier * TechnoExt::ExtMap.Find(pThis->Owner)->AE.FirepowerMultiplier); + if (BulletClass* pBullet = pWeapon->Projectile->CreateBullet(pThis->Owner, pThis->Owner, - pWeapon->Damage, pWeapon->Warhead, pWeapon->Speed, pWeapon->Bright)) + damage, pWeapon->Warhead, pWeapon->Speed, pWeapon->Bright)) { pBullet->WeaponType = pWeapon; pBullet->MoveTo(pThis->Location, BulletVelocity::Empty); @@ -347,38 +352,6 @@ DEFINE_HOOK(0x469AA4, BulletClass_Logics_Extras, 0x5) #pragma region Airburst -static bool IsAllowedSplitsTarget(TechnoClass* pSource, HouseClass* pOwner, WeaponTypeClass* pWeapon, TechnoClass* pTarget, bool useWeaponTargeting) -{ - auto const pWH = pWeapon->Warhead; - - if (useWeaponTargeting) - { - auto const pType = pTarget->GetTechnoType(); - - if (!pType->LegalTarget || GeneralUtils::GetWarheadVersusArmor(pWH, pType->Armor) == 0.0) - return false; - - auto const pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon); - - if (!EnumFunctions::CanTargetHouse(pWeaponExt->CanTargetHouses, pOwner, pTarget->Owner) - || !EnumFunctions::IsCellEligible(pTarget->GetCell(), pWeaponExt->CanTarget, true, true) - || !EnumFunctions::IsTechnoEligible(pTarget, pWeaponExt->CanTarget)) - { - return false; - } - - if (!pWeaponExt->HasRequiredAttachedEffects(pTarget, pSource)) - return false; - } - else - { - if (!WarheadTypeExt::ExtMap.Find(pWH)->CanTargetHouse(pOwner, pTarget)) - return false; - } - - return true; -} - // Disable Ares' Airburst implementation. DEFINE_PATCH(0x469EBA, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90); @@ -494,7 +467,7 @@ DEFINE_HOOK(0x469EC0, BulletClass_Logics_AirburstWeapon, 0x6) if (coordsTarget.DistanceFrom(coords) < pTypeExt->Splits_TargetingDistance.Get() && (pType->AA || !pTechno->IsInAir()) - && IsAllowedSplitsTarget(pSource, pOwner, pWeapon, pTechno, pTypeExt->Splits_UseWeaponTargeting)) + && TechnoExt::IsAllowedSplitsTarget(pSource, pOwner, pWeapon, pTechno, pTypeExt->Splits_UseWeaponTargeting)) { targets.AddItem(pTechno); } diff --git a/src/Ext/BulletType/Body.cpp b/src/Ext/BulletType/Body.cpp index e599189390..2f706ed11d 100644 --- a/src/Ext/BulletType/Body.cpp +++ b/src/Ext/BulletType/Body.cpp @@ -52,6 +52,7 @@ void BulletTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->AAOnly.Read(exINI, pSection, "AAOnly"); this->Arcing_AllowElevationInaccuracy.Read(exINI, pSection, "Arcing.AllowElevationInaccuracy"); this->ReturnWeapon.Read(exINI, pSection, "ReturnWeapon"); + this->ReturnWeapon_ApplyFirepowerMult.Read(exINI, pSection, "ReturnWeapon.ApplyFirepowerMult"); this->SubjectToGround.Read(exINI, pSection, "SubjectToGround"); this->Splits.Read(exINI, pSection, "Splits"); @@ -144,6 +145,7 @@ void BulletTypeExt::ExtData::Serialize(T& Stm) .Process(this->AAOnly) .Process(this->Arcing_AllowElevationInaccuracy) .Process(this->ReturnWeapon) + .Process(this->ReturnWeapon_ApplyFirepowerMult) .Process(this->SubjectToGround) .Process(this->Splits) .Process(this->AirburstSpread) diff --git a/src/Ext/BulletType/Body.h b/src/Ext/BulletType/Body.h index 743dfd7e34..58ac6cf9ee 100644 --- a/src/Ext/BulletType/Body.h +++ b/src/Ext/BulletType/Body.h @@ -44,6 +44,7 @@ class BulletTypeExt Valueable AAOnly; Valueable Arcing_AllowElevationInaccuracy; Valueable ReturnWeapon; + Valueable ReturnWeapon_ApplyFirepowerMult; Valueable SubjectToGround; @@ -92,6 +93,7 @@ class BulletTypeExt , AAOnly { false } , Arcing_AllowElevationInaccuracy { true } , ReturnWeapon {} + , ReturnWeapon_ApplyFirepowerMult { false } , SubjectToGround { false } , Splits { false } , AirburstSpread { 1.5 } diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index b1746334a4..9730bee74d 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -1171,7 +1171,7 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() bool hasRestrictedArmorMultipliers = false; bool hasCritModifiers = false; bool hasExtraWarheads = false; - bool hasFeedbackWeapon = false; + bool hasFeedbackOrAuxWeapon = false; for (const auto& attachEffect : this->AttachedEffects) { @@ -1194,7 +1194,7 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() hasRestrictedArmorMultipliers |= (type->ArmorMultiplier != 1.0 && (type->ArmorMultiplier_AllowWarheads.size() > 0 || type->ArmorMultiplier_DisallowWarheads.size() > 0)); hasCritModifiers |= (type->Crit_Multiplier != 1.0 || type->Crit_ExtraChance != 0.0); hasExtraWarheads |= type->ExtraWarheads.size() > 0; - hasFeedbackWeapon |= type->FeedbackWeapon != nullptr; + hasFeedbackOrAuxWeapon |= type->FeedbackWeapon != nullptr || type->AuxWeapon != nullptr; } this->AE.FirepowerMultiplier = firepower; @@ -1212,7 +1212,7 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() this->AE.HasRestrictedArmorMultipliers = hasRestrictedArmorMultipliers; this->AE.HasCritModifiers = hasCritModifiers; this->AE.HasExtraWarheads = hasExtraWarheads; - this->AE.HasFeedbackWeapon = hasFeedbackWeapon; + this->AE.HasFeedbackOrAuxWeapon = hasFeedbackOrAuxWeapon; if (forceDecloak && pThis->CloakState == CloakState::Cloaked) pThis->Uncloak(true); diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index c8ad3f5c4c..7d42ee6adf 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -146,6 +146,7 @@ class TechnoExt int GetAttachedEffectCumulativeCount(AttachEffectTypeClass* pAttachEffectType, bool ignoreSameSource = false, TechnoClass* pInvoker = nullptr, AbstractClass* pSource = nullptr) const; void ApplyMindControlRangeLimit(); int ApplyForceWeaponInRange(TechnoClass* pTarget); + void ApplyAuxWeapon(WeaponTypeClass* pAuxWeapon, AbstractClass* pTarget, CoordStruct offset, double accuracy, bool onTurret, bool retarget, bool aroundFirer, bool zeroDamage, bool firepowerMult); UnitTypeClass* GetUnitTypeExtra() const; @@ -222,4 +223,5 @@ class TechnoExt static int GetWeaponIndexAgainstWall(TechnoClass* pThis, OverlayTypeClass* pWallOverlayType); static void ApplyKillWeapon(TechnoClass* pThis, TechnoClass* pSource, WarheadTypeClass* pWH); static void ApplyRevengeWeapon(TechnoClass* pThis, TechnoClass* pSource, WarheadTypeClass* pWH); + static bool IsAllowedSplitsTarget(TechnoClass* pSource, HouseClass* pOwner, WeaponTypeClass* pWeapon, TechnoClass* pTarget, bool useWeaponTargeting = true, bool allowZeroDamage = false); }; diff --git a/src/Ext/Techno/Hooks.Firing.cpp b/src/Ext/Techno/Hooks.Firing.cpp index 9cfdd5a43c..ac59339d6f 100644 --- a/src/Ext/Techno/Hooks.Firing.cpp +++ b/src/Ext/Techno/Hooks.Firing.cpp @@ -574,31 +574,40 @@ DEFINE_HOOK(0x6FF43F, TechnoClass_FireAt_FeedbackWeapon, 0x6) { GET(TechnoClass*, pThis, ESI); GET(WeaponTypeClass*, pWeapon, EBX); + GET_BASE(AbstractClass*, pTarget, 0x8); + + auto const pExt = TechnoExt::ExtMap.Find(pThis); if (auto const pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon)) { if (auto const pWeaponFeedback = pWeaponExt->FeedbackWeapon) { - if (pThis->InOpenToppedTransport && !pWeaponFeedback->FireInTransport) - return 0; - - WeaponTypeExt::DetonateAt(pWeaponFeedback, pThis, pThis); + if (!pThis->InOpenToppedTransport || pWeaponFeedback->FireInTransport) + WeaponTypeExt::DetonateAt(pWeaponFeedback, pThis, pThis); } - } - auto const pExt = TechnoExt::ExtMap.Find(pThis); + if (auto const pAuxWeapon = pWeaponExt->AuxWeapon) + pExt->ApplyAuxWeapon(pThis, pAuxWeapon, pTarget, pWeaponExt->AuxWeapon_Offset, pWeaponExt->AuxWeapon_Retarget_Accuracy, pWeaponExt->AuxWeapon_FireOnTurret, + pWeaponExt->AuxWeapon_Retarget, pWeaponExt->AuxWeapon_Retarget_AroundFirer, pWeaponExt->AuxWeapon_AllowZeroDamage, pWeaponExt->AuxWeapon_ApplyFirepowerMult); + } - if (pExt->AE.HasFeedbackWeapon) + if (pExt->AE.HasFeedbackOrAuxWeapon) { for (auto const& pAE : pExt->AttachedEffects) { - if (auto const pWeaponFeedback = pAE->GetType()->FeedbackWeapon) + auto const pAEType = pAE->GetType(); + + if (auto const pWeaponFeedback = pAEType->FeedbackWeapon) { if (pThis->InOpenToppedTransport && !pWeaponFeedback->FireInTransport) - return 0; + continue; WeaponTypeExt::DetonateAt(pWeaponFeedback, pThis, pThis); } + + if (auto const pAuxWeapon = pAEType->AuxWeapon) + pExt->ApplyAuxWeapon(pAuxWeapon, pTarget, pAEType->AuxWeapon_Offset, pAEType->AuxWeapon_Retarget_Accuracy, pAEType->AuxWeapon_FireOnTurret, + pAEType->AuxWeapon_Retarget, pAEType->AuxWeapon_Retarget_AroundFirer, pAEType->AuxWeapon_AllowZeroDamage, pAEType->AuxWeapon_ApplyFirepowerMult); } } diff --git a/src/Ext/Techno/WeaponHelpers.cpp b/src/Ext/Techno/WeaponHelpers.cpp index ccd3bf9ace..979905a6b9 100644 --- a/src/Ext/Techno/WeaponHelpers.cpp +++ b/src/Ext/Techno/WeaponHelpers.cpp @@ -225,9 +225,10 @@ void TechnoExt::ApplyKillWeapon(TechnoClass* pThis, TechnoClass* pSource, Warhea if (pWHExt->KillWeapon_OnFirer_RealLaunch) { auto const pWeapon = pWHExt->KillWeapon_OnFirer; + auto const damage = static_cast(pWeapon->Damage * pSource->FirepowerMultiplier * TechnoExt::ExtMap.Find(pSource)->AE.FirepowerMultiplier); if (BulletClass* pBullet = pWeapon->Projectile->CreateBullet(pSource, pSource, - pWeapon->Damage, pWeapon->Warhead, pWeapon->Speed, pWeapon->Bright)) + damage, pWeapon->Warhead, pWeapon->Speed, pWeapon->Bright)) { pBullet->WeaponType = pWeapon; pBullet->MoveTo(pSource->Location, BulletVelocity::Empty); @@ -256,9 +257,10 @@ void TechnoExt::ApplyRevengeWeapon(TechnoClass* pThis, TechnoClass* pSource, War if (pTypeExt->RevengeWeapon_RealLaunch) { auto const pWeapon = pTypeExt->RevengeWeapon; + auto const damage = static_cast(pWeapon->Damage * pThis->FirepowerMultiplier * pExt->AE.FirepowerMultiplier); if (BulletClass* pBullet = pWeapon->Projectile->CreateBullet(pSource, pThis, - pWeapon->Damage, pWeapon->Warhead, pWeapon->Speed, pWeapon->Bright)) + damage, pWeapon->Warhead, pWeapon->Speed, pWeapon->Bright)) { pBullet->WeaponType = pWeapon; pBullet->MoveTo(pSource->Location, BulletVelocity::Empty); @@ -290,9 +292,10 @@ void TechnoExt::ApplyRevengeWeapon(TechnoClass* pThis, TechnoClass* pSource, War if (pType->RevengeWeapon_RealLaunch) { auto const pWeapon = pType->RevengeWeapon; + auto const damage = static_cast(pWeapon->Damage * pThis->FirepowerMultiplier * pExt->AE.FirepowerMultiplier); if (BulletClass* pBullet = pWeapon->Projectile->CreateBullet(pSource, pThis, - pWeapon->Damage, pWeapon->Warhead, pWeapon->Speed, pWeapon->Bright)) + damage, pWeapon->Warhead, pWeapon->Speed, pWeapon->Bright)) { pBullet->WeaponType = pWeapon; pBullet->MoveTo(pSource->Location, BulletVelocity::Empty); @@ -365,3 +368,111 @@ int TechnoExt::ExtData::ApplyForceWeaponInRange(TechnoClass* pTarget) return forceWeaponIndex; } + +bool TechnoExt::IsAllowedSplitsTarget(TechnoClass* pSource, HouseClass* pOwner, WeaponTypeClass* pWeapon, TechnoClass* pTarget, bool useWeaponTargeting, bool allowZeroDamage) +{ + auto const pWH = pWeapon->Warhead; + + if (useWeaponTargeting) + { + auto const pType = pTarget->GetTechnoType(); + + if (!pType->LegalTarget || (!allowZeroDamage && GeneralUtils::GetWarheadVersusArmor(pWH, pType->Armor) == 0.0)) + return false; + + auto const pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon); + + if (!EnumFunctions::CanTargetHouse(pWeaponExt->CanTargetHouses, pOwner, pTarget->Owner) + || !EnumFunctions::IsCellEligible(pTarget->GetCell(), pWeaponExt->CanTarget, true, true) + || !EnumFunctions::IsTechnoEligible(pTarget, pWeaponExt->CanTarget)) + { + return false; + } + + if (!pWeaponExt->HasRequiredAttachedEffects(pTarget, pSource)) + return false; + } + else + { + if (!WarheadTypeExt::ExtMap.Find(pWH)->CanTargetHouse(pOwner, pTarget)) + return false; + } + + return true; +} + +void TechnoExt::ExtData::ApplyAuxWeapon(WeaponTypeClass* pAuxWeapon, AbstractClass* pTarget, CoordStruct offset, double accuracy, bool onTurret, bool retarget, bool aroundFirer, bool zeroDamage, bool firepowerMult) +{ + auto const pThis = this->OwnerObject(); + if (pThis->InOpenToppedTransport && !pAuxWeapon->FireInTransport) + return; + + TechnoClass* pTargetTechno = nullptr; + CellClass* pTargetCell = nullptr; + + if (retarget && accuracy < ScenarioClass::Instance->Random.RandomDouble()) + { + auto const coord = aroundFirer ? pThis->Location : pTarget->GetCoords(); + auto cellSpread = static_cast(pAuxWeapon->Retarget_Range.Get(aroundFirer ? pAuxWeapon->Range : 0)); + + std::vector targets; + + for (auto const pTechno : Helpers::Alex::getCellSpreadItems(coord, cellSpread, true)) + { + if (pTechno->IsInPlayfield && pTechno->IsOnMap && pTechno->IsAlive && pTechno->Health > 0 && !pTechno->InLimbo && pTechno != pThis) + { + if ((pAuxWeapon->Projectile->AA || !pTechno->IsInAir()) && TechnoExt::IsAllowedSplitsTarget(pThis, pThis->Owner, pAuxWeapon, pTechno, true, zeroDamage)) + targets.push_back(pTechno); + } + } + + if (!targets.empty()) + { + pTargetTechno = targets[ScenarioClass::Instance->Random.RandomRanged(0, targets.size() - 1)]; + } + else + { + auto const cellTarget = CellClass::Coord2Cell(coord); + int x = ScenarioClass::Instance->Random.RandomRanged(-cellSpread, cellSpread); + int y = ScenarioClass::Instance->Random.RandomRanged(-cellSpread, cellSpread); + CellStruct cell = { static_cast(cellTarget.X + x), static_cast(cellTarget.Y + y) }; + pTargetCell = MapClass::Instance.GetCellAt(cell); + } + } + else + { + pTargetTechno = abstract_cast(pTarget); + + if (pTargetTechno && ((!pAuxWeapon->Projectile->AA && pTargetTechno->IsInAir()) || !TechnoExt::IsAllowedSplitsTarget(pThis, pThis->Owner, pAuxWeapon, pTargetTechno, true, zeroDamage))) + return; + + if (!pTargetTechno) + { + if (auto const pCell = abstract_cast(pTarget)) + pTargetCell = pCell; + else if (auto const pObject = abstract_cast(pTarget)) + pTargetCell = pObject->GetCell(); + + if (!EnumFunctions::IsCellEligible(pTargetCell, WeaponTypeExt::ExtMap.Find(pAuxWeapon)->CanTarget, true, true)) + return; + } + } + + if (!pTargetTechno && !pTargetCell) + return; + + auto location = TechnoExt::GetFLHAbsoluteCoords(pThis, offset, onTurret); + auto damage = pAuxWeapon->Damage; + + if (firepowerMult) + damage = static_cast(damage * pThis->FirepowerMultiplier * this->AE.FirepowerMultiplier); + + if (BulletClass* pBullet = pAuxWeapon->Projectile->CreateBullet(pTargetTechno ? pTargetTechno : pTargetCell, pThis->Owner, + damage, pAuxWeapon->Warhead, pAuxWeapon->Speed, pAuxWeapon->Bright)) + { + pBullet->WeaponType = pAuxWeapon; + pBullet->MoveTo(location, BulletVelocity::Empty); + BulletExt::ExtMap.Find(pBullet)->FirerHouse = pThis->Owner; + AnimExt::CreateRandomAnim(location, pAuxWeapon->Anim, pThis, pThis->Owner, true); + } +} diff --git a/src/Ext/WeaponType/Body.cpp b/src/Ext/WeaponType/Body.cpp index ce628924a0..dc62653cb6 100644 --- a/src/Ext/WeaponType/Body.cpp +++ b/src/Ext/WeaponType/Body.cpp @@ -96,6 +96,14 @@ void WeaponTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Burst_FireWithinSequence.Read(exINI, pSection, "Burst.FireWithinSequence"); this->AreaFire_Target.Read(exINI, pSection, "AreaFire.Target"); this->FeedbackWeapon.Read(exINI, pSection, "FeedbackWeapon"); + this->AuxWeapon.Read(exINI, pSection, "AuxWeapon"); + this->AuxWeapon_Offset.Read(exINI, pSection, "AuxWeapon.Offset"); + this->AuxWeapon_FireOnTurret.Read(exINI, pSection, "AuxWeapon.FireOnTurret"); + this->AuxWeapon_AllowZeroDamage.Read(exINI, pSection, "AuxWeapon.AllowZeroDamage"); + this->AuxWeapon_Retarget.Read(exINI, pSection, "AuxWeapon.Retarget"); + this->AuxWeapon_Retarget_AroundFirer.Read(exINI, pSection, "AuxWeapon.Retarget.AroundFirer"); + this->AuxWeapon_Retarget_Range.Read(exINI, pSection, "AuxWeapon.Retarget.Range"); + this->AuxWeapon_Retarget_Accuracy.Read(exINI, pSection, "AuxWeapon.Retarget.Accuracy"); this->Laser_IsSingleColor.Read(exINI, pSection, "IsSingleColor"); this->VisualScatter.Read(exINI, pSection, "VisualScatter"); this->ROF_RandomDelay.Read(exINI, pSection, "ROF.RandomDelay"); @@ -154,6 +162,14 @@ void WeaponTypeExt::ExtData::Serialize(T& Stm) .Process(this->Burst_FireWithinSequence) .Process(this->AreaFire_Target) .Process(this->FeedbackWeapon) + .Process(this->AuxWeapon) + .Process(this->AuxWeapon_Offset) + .Process(this->AuxWeapon_FireOnTurret) + .Process(this->AuxWeapon_AllowZeroDamage) + .Process(this->AuxWeapon_Retarget) + .Process(this->AuxWeapon_Retarget_AroundFirer) + .Process(this->AuxWeapon_Retarget_Range) + .Process(this->AuxWeapon_Retarget_Accuracy) .Process(this->Laser_IsSingleColor) .Process(this->VisualScatter) .Process(this->ROF_RandomDelay) diff --git a/src/Ext/WeaponType/Body.h b/src/Ext/WeaponType/Body.h index 323910a811..d5edcbdab7 100644 --- a/src/Ext/WeaponType/Body.h +++ b/src/Ext/WeaponType/Body.h @@ -42,6 +42,14 @@ class WeaponTypeExt Valueable Burst_FireWithinSequence; Valueable AreaFire_Target; Valueable FeedbackWeapon; + Valueable AuxWeapon; + Valueable AuxWeapon_Offset; + Valueable AuxWeapon_FireOnTurret; + Valueable AuxWeapon_AllowZeroDamage; + Valueable AuxWeapon_Retarget; + Valueable AuxWeapon_Retarget_AroundFirer; + Valueable AuxWeapon_Retarget_Range; + Valueable AuxWeapon_Retarget_Accuracy; Valueable Laser_IsSingleColor; Valueable VisualScatter; Nullable> ROF_RandomDelay; @@ -96,6 +104,14 @@ class WeaponTypeExt , Burst_FireWithinSequence { false } , AreaFire_Target { AreaFireTarget::Base } , FeedbackWeapon {} + , AuxWeapon {} + , AuxWeapon_Offset { (0, 0, 0) } + , AuxWeapon_FireOnTurret { false } + , AuxWeapon_AllowZeroDamage { false } + , AuxWeapon_Retarget { false } + , AuxWeapon_Retarget_AroundFirer { false } + , AuxWeapon_Retarget_Range { 0 } + , AuxWeapon_Retarget_Accuracy { 0 } , Laser_IsSingleColor { false } , VisualScatter { false } , ROF_RandomDelay {} diff --git a/src/New/Entity/AttachEffectClass.h b/src/New/Entity/AttachEffectClass.h index 02a1bfae87..6b523a5ceb 100644 --- a/src/New/Entity/AttachEffectClass.h +++ b/src/New/Entity/AttachEffectClass.h @@ -98,7 +98,7 @@ struct AttachEffectTechnoProperties bool HasRestrictedArmorMultipliers; bool HasCritModifiers; bool HasExtraWarheads; - bool HasFeedbackWeapon; + bool HasFeedbackOrAuxWeapon; AttachEffectTechnoProperties() : FirepowerMultiplier { 1.0 } @@ -116,6 +116,6 @@ struct AttachEffectTechnoProperties , HasRestrictedArmorMultipliers { false } , HasCritModifiers { false } , HasExtraWarheads { false } - , HasFeedbackWeapon { false } + , HasFeedbackOrAuxWeapon { false } { } }; diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index 0aa99c9413..c3ad532aac 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -131,6 +131,16 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->FeedbackWeapon.Read(exINI, pSection, "FeedbackWeapon"); + this->AuxWeapon.Read(exINI, pSection, "AuxWeapon"); + this->AuxWeapon_Offset.Read(exINI, pSection, "AuxWeapon.Offset"); + this->AuxWeapon_FireOnTurret.Read(exINI, pSection, "AuxWeapon.FireOnTurret"); + this->AuxWeapon_AllowZeroDamage.Read(exINI, pSection, "AuxWeapon.AllowZeroDamage"); + this->AuxWeapon_ApplyFirepowerMult.Read(exINI, pSection, "AuxWeapon.ApplyFirepowerMult"); + this->AuxWeapon_Retarget.Read(exINI, pSection, "AuxWeapon.Retarget"); + this->AuxWeapon_Retarget_AroundFirer.Read(exINI, pSection, "AuxWeapon.Retarget.AroundFirer"); + this->AuxWeapon_Retarget_Range.Read(exINI, pSection, "AuxWeapon.Retarget.Range"); + this->AuxWeapon_Retarget_Accuracy.Read(exINI, pSection, "AuxWeapon.Retarget.Accuracy"); + this->Tint_Color.Read(exINI, pSection, "Tint.Color"); this->Tint_Intensity.Read(exINI, pSection, "Tint.Intensity"); this->Tint_VisibleToHouses.Read(exINI, pSection, "Tint.VisibleToHouses"); @@ -211,6 +221,15 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->ExtraWarheads_DetonationChances) .Process(this->ExtraWarheads_FullDetonation) .Process(this->FeedbackWeapon) + .Process(this->AuxWeapon) + .Process(this->AuxWeapon_Offset) + .Process(this->AuxWeapon_FireOnTurret) + .Process(this->AuxWeapon_AllowZeroDamage) + .Process(this->AuxWeapon_ApplyFirepowerMult) + .Process(this->AuxWeapon_Retarget) + .Process(this->AuxWeapon_Retarget_AroundFirer) + .Process(this->AuxWeapon_Retarget_Range) + .Process(this->AuxWeapon_Retarget_Accuracy) .Process(this->Tint_Color) .Process(this->Tint_Intensity) .Process(this->Tint_VisibleToHouses) diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index b4175a6f2e..90acb3735b 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -71,6 +71,15 @@ class AttachEffectTypeClass final : public Enumerable ValueableVector ExtraWarheads_DetonationChances; ValueableVector ExtraWarheads_FullDetonation; Valueable FeedbackWeapon; + Valueable AuxWeapon; + Valueable AuxWeapon_Offset; + Valueable AuxWeapon_FireOnTurret; + Valueable AuxWeapon_AllowZeroDamage; + Valueable AuxWeapon_ApplyFirepowerMult; + Valueable AuxWeapon_Retarget; + Valueable AuxWeapon_Retarget_AroundFirer; + Nullable AuxWeapon_Retarget_Range; + Valueable AuxWeapon_Retarget_Accuracy; Nullable Tint_Color; Valueable Tint_Intensity; Valueable Tint_VisibleToHouses; @@ -138,6 +147,15 @@ class AttachEffectTypeClass final : public Enumerable , ExtraWarheads_DetonationChances {} , ExtraWarheads_FullDetonation {} , FeedbackWeapon {} + , AuxWeapon {} + , AuxWeapon_Offset { (0, 0, 0) } + , AuxWeapon_FireOnTurret { false } + , AuxWeapon_AllowZeroDamage { true } + , AuxWeapon_ApplyFirepowerMult { false } + , AuxWeapon_Retarget { false } + , AuxWeapon_Retarget_AroundFirer { false } + , AuxWeapon_Retarget_Range {} + , AuxWeapon_Retarget_Accuracy { 0.0 } , Tint_Color {} , Tint_Intensity { 0.0 } , Tint_VisibleToHouses { AffectedHouse::All } From 05243c2b4439dc023ce3ffbd9b3ad0aa110ca89a Mon Sep 17 00:00:00 2001 From: Coronia <2217891145@qq.com> Date: Wed, 18 Jun 2025 00:08:02 +0800 Subject: [PATCH 06/10] PreventNegativeDamage --- docs/New-or-Enhanced-Logics.md | 2 + src/Ext/Bullet/Hooks.DetonateLogics.cpp | 11 +++- src/Ext/Techno/Body.Update.cpp | 3 + src/Ext/Techno/Hooks.ReceiveDamage.cpp | 25 ++++--- src/Ext/Techno/WeaponHelpers.cpp | 16 +++-- src/New/Entity/AttachEffectClass.cpp | 29 +++++---- src/New/Entity/AttachEffectClass.h | 2 + src/New/Entity/ShieldClass.cpp | 87 ++++++++++++++----------- src/New/Type/AttachEffectTypeClass.cpp | 2 + src/New/Type/AttachEffectTypeClass.h | 2 + 10 files changed, 108 insertions(+), 71 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index b9204fb525..008a459916 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -76,6 +76,7 @@ This page describes all the engine features that are either new and introduced b - `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). + - `PreventNegativeDamage` can be used to prevent the techno from taking negative damage. This includes both negative `Damage` and negative `Verses`. - It is possible to set groups for attach effect types by defining strings in `Groups`. - Groups can be used instead of types for removing effects and weapon filters. @@ -186,6 +187,7 @@ ReflectDamage.Override= ; integer ReflectDamage.UseInvokerAsOwner=false ; boolean DisableWeapons=false ; boolean Unkillable=false ; boolean +PreventNegativeDamage=false ; boolean Groups= ; comma-separated list of strings (group IDs) [SOMETECHNO] ; TechnoType diff --git a/src/Ext/Bullet/Hooks.DetonateLogics.cpp b/src/Ext/Bullet/Hooks.DetonateLogics.cpp index b78f247229..6d6e3750ae 100644 --- a/src/Ext/Bullet/Hooks.DetonateLogics.cpp +++ b/src/Ext/Bullet/Hooks.DetonateLogics.cpp @@ -316,9 +316,14 @@ DEFINE_HOOK(0x469AA4, BulletClass_Logics_Extras, 0x5) if (pThis->WeaponType) { auto const pWeaponExt = WeaponTypeExt::ExtMap.Find(pThis->WeaponType); - std::vector vec; - pBulletExt->ApplyExtraWarheads(pWeaponExt->ExtraWarheads, pWeaponExt->ExtraWarheads_DamageOverrides, - pWeaponExt->ExtraWarheads_DetonationChances, pWeaponExt->ExtraWarheads_FullDetonation, vec, *coords, pOwner); + + if (pWeaponExt->ExtraWarheads.size() > 0) + { + std::vector vec; + + pBulletExt->ApplyExtraWarheads(pWeaponExt->ExtraWarheads, pWeaponExt->ExtraWarheads_DamageOverrides, + pWeaponExt->ExtraWarheads_DetonationChances, pWeaponExt->ExtraWarheads_FullDetonation, vec, *coords, pOwner); + } } if (pThis->Owner) diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index 0630556109..bfcee30dab 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -1819,6 +1819,7 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() bool forceDecloak = false; bool disableWeapons = false; bool unkillable = false; + bool preventNegativeDamage = false; bool hasRangeModifier = false; bool hasTint = false; bool reflectsDamage = false; @@ -1842,6 +1843,7 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() forceDecloak |= type->ForceDecloak; disableWeapons |= type->DisableWeapons; unkillable |= type->Unkillable; + preventNegativeDamage |= type->PreventNegativeDamage; hasRangeModifier |= (type->WeaponRange_ExtraRange != 0.0 || type->WeaponRange_Multiplier != 0.0); hasTint |= type->HasTint(); reflectsDamage |= type->ReflectDamage; @@ -1860,6 +1862,7 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() pAE.ForceDecloak = forceDecloak; pAE.DisableWeapons = disableWeapons; pAE.Unkillable = unkillable; + pAE.PreventNegativeDamage = preventNegativeDamage; pAE.HasRangeModifier = hasRangeModifier; pAE.HasTint = hasTint; pAE.ReflectDamage = reflectsDamage; diff --git a/src/Ext/Techno/Hooks.ReceiveDamage.cpp b/src/Ext/Techno/Hooks.ReceiveDamage.cpp index 02e222c8bd..7129ec13a2 100644 --- a/src/Ext/Techno/Hooks.ReceiveDamage.cpp +++ b/src/Ext/Techno/Hooks.ReceiveDamage.cpp @@ -25,9 +25,10 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) const auto pSourceHouse = args->SourceHouse; const auto pTargetHouse = pThis->Owner; + int& damage = *args->Damage; // Calculate Damage Multiplier - if (!args->IgnoreDefenses && *args->Damage) + if (!args->IgnoreDefenses && damage) { double multiplier = 1.0; @@ -40,14 +41,14 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) if (multiplier != 1.0) { - const auto sgnDamage = *args->Damage > 0 ? 1 : -1; - const auto calculateDamage = static_cast(*args->Damage * multiplier); - *args->Damage = calculateDamage ? calculateDamage : sgnDamage; + const auto sgnDamage = damage > 0 ? 1 : -1; + const auto calculateDamage = static_cast(damage * multiplier); + damage = calculateDamage ? calculateDamage : sgnDamage; } } // Raise Combat Alert - if (pRules->CombatAlert && *args->Damage > 1) + if (pRules->CombatAlert && damage > 1) { auto raiseCombatAlert = [&]() { @@ -104,7 +105,7 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) // Shield Receive Damage if (!args->IgnoreDefenses) { - int nDamageLeft = *args->Damage; + int nDamageLeft = damage; if (const auto pShieldData = pExt->Shield.get()) { @@ -114,9 +115,9 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) if (nDamageLeft >= 0) { - *args->Damage = nDamageLeft; + damage = nDamageLeft; - if (auto pTag = pThis->AttachedTag) + if (const auto pTag = pThis->AttachedTag) pTag->RaiseEvent((TriggerEvent)PhobosTriggerEvent::ShieldBroken, pThis, CellStruct::Empty); } @@ -131,11 +132,17 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) && MapClass::GetTotalDamage(nDamageLeft, args->WH, pThis->GetTechnoType()->Armor, args->DistanceToEpicenter) >= pThis->Health) { // Update remaining damage and check if the target will die and should be avoided - *args->Damage = 0; + damage = 0; pThis->Health = 1; pThis->EstimatedHealth = 1; ReceiveDamageTemp::SkipLowDamageCheck = true; } + + if (pExt->AE.PreventNegativeDamage && nDamageLeft != 0 && pWHExt->CanTargetHouse(pSourceHouse, pThis) + && MapClass::GetTotalDamage(nDamageLeft, args->WH, pThis->GetTechnoType()->Armor, args->DistanceToEpicenter) < 0) + { + damage = 0; + } } return 0; diff --git a/src/Ext/Techno/WeaponHelpers.cpp b/src/Ext/Techno/WeaponHelpers.cpp index 5c018ef7d3..dcc442cdea 100644 --- a/src/Ext/Techno/WeaponHelpers.cpp +++ b/src/Ext/Techno/WeaponHelpers.cpp @@ -408,16 +408,18 @@ bool TechnoExt::IsAllowedSplitsTarget(TechnoClass* pSource, HouseClass* pOwner, void TechnoExt::ExtData::ApplyAuxWeapon(WeaponTypeClass* pAuxWeapon, AbstractClass* pTarget, const CoordStruct& offset, int range, const double& accuracy, bool onTurret, bool retarget, bool aroundFirer, bool zeroDamage, bool firepowerMult, TechnoClass* pInvoker) { auto const pThis = this->OwnerObject(); + if (pThis->InOpenToppedTransport && !pAuxWeapon->FireInTransport) return; TechnoClass* pTargetTechno = nullptr; CellClass* pTargetCell = nullptr; + auto& random = ScenarioClass::Instance->Random; - if (retarget && accuracy < ScenarioClass::Instance->Random.RandomDouble()) + if (retarget && accuracy < random.RandomDouble()) { auto const coord = aroundFirer ? pThis->Location : pTarget->GetCoords(); - auto cellSpread = range > 0 ? range : (aroundFirer ? pAuxWeapon->Range : 0); + auto const cellSpread = range > 0 ? range : (aroundFirer ? pAuxWeapon->Range : 0); std::vector targets; @@ -432,14 +434,14 @@ void TechnoExt::ExtData::ApplyAuxWeapon(WeaponTypeClass* pAuxWeapon, AbstractCla if (!targets.empty()) { - pTargetTechno = targets[ScenarioClass::Instance->Random.RandomRanged(0, targets.size() - 1)]; + pTargetTechno = targets[random.RandomRanged(0, targets.size() - 1)]; } else { auto const cellTarget = CellClass::Coord2Cell(coord); - int x = ScenarioClass::Instance->Random.RandomRanged(-cellSpread, cellSpread); - int y = ScenarioClass::Instance->Random.RandomRanged(-cellSpread, cellSpread); - CellStruct cell = { static_cast(cellTarget.X + x), static_cast(cellTarget.Y + y) }; + const int x = random.RandomRanged(-cellSpread, cellSpread); + const int y = random.RandomRanged(-cellSpread, cellSpread); + const CellStruct cell = { static_cast(cellTarget.X + x), static_cast(cellTarget.Y + y) }; pTargetCell = MapClass::Instance.GetCellAt(cell); } } @@ -465,7 +467,7 @@ void TechnoExt::ExtData::ApplyAuxWeapon(WeaponTypeClass* pAuxWeapon, AbstractCla if (!pTargetTechno && !pTargetCell) return; - auto location = TechnoExt::GetFLHAbsoluteCoords(pThis, offset, onTurret); + auto const location = TechnoExt::GetFLHAbsoluteCoords(pThis, offset, onTurret); auto damage = pAuxWeapon->Damage; if (firepowerMult) diff --git a/src/New/Entity/AttachEffectClass.cpp b/src/New/Entity/AttachEffectClass.cpp index 84763b0022..2963566e5d 100644 --- a/src/New/Entity/AttachEffectClass.cpp +++ b/src/New/Entity/AttachEffectClass.cpp @@ -454,18 +454,18 @@ void AttachEffectClass::RefreshDuration(int durationOverride) else duration = this->DurationOverride ? this->DurationOverride : this->Type->Duration; - if (this->Type->Duration_ApplyVersus_Warhead) + if (this->Type->Duration_ApplyVersus_Warhead && duration > 0) { auto const pTechnoExt = TechnoExt::ExtMap.Find(this->Techno); - auto pArmor = this->Techno->GetTechnoType()->Armor; + auto armor = this->Techno->GetTechnoType()->Armor; if (auto const pShieldData = pTechnoExt->Shield.get()) { if (pShieldData->IsActive()) - pArmor = pShieldData->GetArmorType(); + armor = pShieldData->GetArmorType(); } - duration = Math::max(MapClass::GetTotalDamage(duration, this->Type->Duration_ApplyVersus_Warhead, pArmor, 0), 0); + duration = Math::max(MapClass::GetTotalDamage(duration, this->Type->Duration_ApplyVersus_Warhead, armor, 0), 0); } if (this->Type->Duration_ApplyFirepowerMult && duration > 0 && this->Invoker) @@ -524,13 +524,16 @@ bool AttachEffectClass::ShouldBeDiscardedNow() auto const pTechno = this->Techno; if (this->Type->DiscardOn_AbovePercent > 0.0 && pTechno->GetHealthPercentage() >= this->Type->DiscardOn_AbovePercent) - return false; + { + this->LastDiscardCheckValue = false; + return true; + } if (this->Type->DiscardOn_BelowPercent > 0.0 && pTechno->GetHealthPercentage() <= this->Type->DiscardOn_BelowPercent) - return false; - - if (discardOn == DiscardCondition::None) - return false; + { + this->LastDiscardCheckValue = false; + return true; + } if (auto const pFoot = abstract_cast(pTechno)) { @@ -557,8 +560,8 @@ bool AttachEffectClass::ShouldBeDiscardedNow() if (pTechno->Target) { - bool inRange = (discardOn & DiscardCondition::InRange) != DiscardCondition::None; - bool outOfRange = (discardOn & DiscardCondition::OutOfRange) != DiscardCondition::None; + const bool inRange = (discardOn & DiscardCondition::InRange) != DiscardCondition::None; + const bool outOfRange = (discardOn & DiscardCondition::OutOfRange) != DiscardCondition::None; if (inRange || outOfRange) { @@ -570,14 +573,14 @@ bool AttachEffectClass::ShouldBeDiscardedNow() } else { - int weaponIndex = pTechno->SelectWeapon(pTechno->Target); + const int weaponIndex = pTechno->SelectWeapon(pTechno->Target); auto const pWeapon = pTechno->GetWeapon(weaponIndex)->WeaponType; if (pWeapon) distance = WeaponTypeExt::GetRangeWithModifiers(pWeapon, pTechno); } - int distanceFromTgt = pTechno->DistanceFrom(pTechno->Target); + const int distanceFromTgt = pTechno->DistanceFrom(pTechno->Target); if ((inRange && distanceFromTgt <= distance) || (outOfRange && distanceFromTgt >= distance)) { diff --git a/src/New/Entity/AttachEffectClass.h b/src/New/Entity/AttachEffectClass.h index 3fcc471c5d..216409b17b 100644 --- a/src/New/Entity/AttachEffectClass.h +++ b/src/New/Entity/AttachEffectClass.h @@ -97,6 +97,7 @@ struct AttachEffectTechnoProperties bool ForceDecloak; bool DisableWeapons; bool Unkillable; + bool PreventNegativeDamage; bool HasRangeModifier; bool HasTint; bool ReflectDamage; @@ -115,6 +116,7 @@ struct AttachEffectTechnoProperties , ForceDecloak { false } , DisableWeapons { false } , Unkillable { false } + , PreventNegativeDamage { false } , HasRangeModifier { false } , HasTint { false } , ReflectDamage { false } diff --git a/src/New/Entity/ShieldClass.cpp b/src/New/Entity/ShieldClass.cpp index a57ab4c4dd..642808b5f0 100644 --- a/src/New/Entity/ShieldClass.cpp +++ b/src/New/Entity/ShieldClass.cpp @@ -160,13 +160,17 @@ bool ShieldClass::ShieldIsBrokenTEvent(ObjectClass* pAttached) int ShieldClass::ReceiveDamage(args_ReceiveDamage* args) { - if (!this->HP || this->Temporal || *args->Damage == 0) - return *args->Damage; + int& damage = *args->Damage; + + if (!this->HP || this->Temporal || damage == 0) + return damage; + + auto const pTechno = this->Techno; // Handle a special case where parasite damages shield but not the unit and unit itself cannot be targeted by repair weapons. - if (*args->Damage < 0) + if (damage < 0) { - if (auto const pFoot = abstract_cast(this->Techno)) + if (auto const pFoot = abstract_cast(pTechno)) { if (auto const pParasite = pFoot->ParasiteEatingMe) { @@ -177,59 +181,64 @@ int ShieldClass::ReceiveDamage(args_ReceiveDamage* args) } } - auto const pWHExt = WarheadTypeExt::ExtMap.Find(args->WH); - bool IC = pWHExt->CanAffectInvulnerable(this->Techno); + auto const pWH = args->WH; + auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWH); + const bool IC = pWHExt->CanAffectInvulnerable(pTechno); - if (!IC || CanBePenetrated(args->WH) || this->Techno->GetTechnoType()->Immune || TechnoExt::IsTypeImmune(this->Techno, args->Attacker)) - return *args->Damage; + if (!IC || CanBePenetrated(pWH) || pTechno->GetTechnoType()->Immune || TechnoExt::IsTypeImmune(pTechno, args->Attacker)) + return damage; int nDamage = 0; int shieldDamage = 0; int healthDamage = 0; + auto const pType = this->Type; - if (pWHExt->CanTargetHouse(args->SourceHouse, this->Techno) && !args->WH->Temporal) + if (pWHExt->CanTargetHouse(args->SourceHouse, pTechno) && !pWH->Temporal) { - if (*args->Damage > 0) - nDamage = MapClass::GetTotalDamage(*args->Damage, args->WH, this->GetArmorType(), args->DistanceToEpicenter); + if (damage > 0) + nDamage = MapClass::GetTotalDamage(damage, pWH, this->GetArmorType(), args->DistanceToEpicenter); else - nDamage = -MapClass::GetTotalDamage(-*args->Damage, args->WH, this->GetArmorType(), args->DistanceToEpicenter); + nDamage = -MapClass::GetTotalDamage(damage, pWH, this->GetArmorType(), args->DistanceToEpicenter); - bool affectsShield = pWHExt->Shield_AffectTypes.size() <= 0 || pWHExt->Shield_AffectTypes.Contains(this->Type); - double absorbPercent = affectsShield ? pWHExt->Shield_AbsorbPercent.Get(this->Type->AbsorbPercent) : this->Type->AbsorbPercent; - double passPercent = affectsShield ? pWHExt->Shield_PassPercent.Get(this->Type->PassPercent) : this->Type->PassPercent; + const bool affectsShield = pWHExt->Shield_AffectTypes.size() <= 0 || pWHExt->Shield_AffectTypes.Contains(pType); + const double absorbPercent = affectsShield ? pWHExt->Shield_AbsorbPercent.Get(pType->AbsorbPercent) : pType->AbsorbPercent; + const double passPercent = affectsShield ? pWHExt->Shield_PassPercent.Get(pType->PassPercent) : pType->PassPercent; shieldDamage = (int)((double)nDamage * absorbPercent); // passthrough damage shouldn't be affected by shield armor - healthDamage = (int)((double)*args->Damage * passPercent); + healthDamage = (int)((double)damage * passPercent); } - int originalShieldDamage = shieldDamage; - int min = pWHExt->Shield_ReceivedDamage_Minimum.Get(this->Type->ReceivedDamage_Minimum); - int max = pWHExt->Shield_ReceivedDamage_Maximum.Get(this->Type->ReceivedDamage_Maximum); - int minDmg = static_cast(min * pWHExt->Shield_ReceivedDamage_MinMultiplier); - int maxDmg = static_cast(max * pWHExt->Shield_ReceivedDamage_MaxMultiplier); + const int originalShieldDamage = shieldDamage; + const int min = pWHExt->Shield_ReceivedDamage_Minimum.Get(pType->ReceivedDamage_Minimum); + const int max = pWHExt->Shield_ReceivedDamage_Maximum.Get(pType->ReceivedDamage_Maximum); + const int minDmg = static_cast(min * pWHExt->Shield_ReceivedDamage_MinMultiplier); + const int maxDmg = static_cast(max * pWHExt->Shield_ReceivedDamage_MaxMultiplier); shieldDamage = Math::clamp(shieldDamage, minDmg, maxDmg); + if (shieldDamage < 0 && TechnoExt::ExtMap.Find(pTechno)->AE.PreventNegativeDamage) + shieldDamage = 0; + if (Phobos::DisplayDamageNumbers && shieldDamage != 0) - GeneralUtils::DisplayDamageNumberString(shieldDamage, DamageDisplayType::Shield, this->Techno->GetRenderCoords(), TechnoExt::ExtMap.Find(this->Techno)->DamageNumberOffset); + GeneralUtils::DisplayDamageNumberString(shieldDamage, DamageDisplayType::Shield, pTechno->GetRenderCoords(), TechnoExt::ExtMap.Find(pTechno)->DamageNumberOffset); if (shieldDamage > 0) { - bool whModifiersApplied = this->Timers.SelfHealing_WHModifier.InProgress(); - bool restart = whModifiersApplied ? this->SelfHealing_RestartInCombat_Warhead : this->Type->SelfHealing_RestartInCombat; + const bool whModifiersApplied = this->Timers.SelfHealing_WHModifier.InProgress(); + const bool restart = whModifiersApplied ? this->SelfHealing_RestartInCombat_Warhead : pType->SelfHealing_RestartInCombat; if (restart) { - int delay = whModifiersApplied ? this->SelfHealing_RestartInCombatDelay_Warhead : this->Type->SelfHealing_RestartInCombatDelay; + const int delay = whModifiersApplied ? this->SelfHealing_RestartInCombatDelay_Warhead : pType->SelfHealing_RestartInCombatDelay; if (delay > 0) { - this->Timers.SelfHealing_CombatRestart.Start(this->Type->SelfHealing_RestartInCombatDelay); + this->Timers.SelfHealing_CombatRestart.Start(pType->SelfHealing_RestartInCombatDelay); this->Timers.SelfHealing.Stop(); } else { - const int rate = whModifiersApplied ? this->SelfHealing_Rate_Warhead : this->Type->SelfHealing_Rate; + const int rate = whModifiersApplied ? this->SelfHealing_Rate_Warhead : pType->SelfHealing_Rate; this->Timers.SelfHealing.Start(rate); // when attacked, restart the timer } } @@ -238,41 +247,41 @@ int ShieldClass::ReceiveDamage(args_ReceiveDamage* args) this->ResponseAttack(); if (pWHExt->DecloakDamagedTargets) - this->Techno->Uncloak(false); + pTechno->Uncloak(false); - int residueDamage = shieldDamage - this->HP; + const int residueDamage = shieldDamage - this->HP; if (residueDamage >= 0) { - int actualResidueDamage = Math::max(0, int((double)(originalShieldDamage - this->HP) / - GeneralUtils::GetWarheadVersusArmor(args->WH, this->GetArmorType()))); //only absord percentage damage + const int actualResidueDamage = Math::max(0, int((double)(originalShieldDamage - this->HP) / + GeneralUtils::GetWarheadVersusArmor(pWH, this->GetArmorType()))); //only absord percentage damage this->BreakShield(pWHExt->Shield_BreakAnim, pWHExt->Shield_BreakWeapon.Get(nullptr)); - return this->Type->AbsorbOverDamage ? healthDamage : actualResidueDamage + healthDamage; + return pType->AbsorbOverDamage ? healthDamage : actualResidueDamage + healthDamage; } else { - if (this->Type->HitFlash && pWHExt->Shield_HitFlash) + if (pType->HitFlash && pWHExt->Shield_HitFlash) { - int size = this->Type->HitFlash_FixedSize.Get((shieldDamage * 2)); + const int size = pType->HitFlash_FixedSize.Get((shieldDamage * 2)); SpotlightFlags flags = SpotlightFlags::NoColor; - if (this->Type->HitFlash_Black) + if (pType->HitFlash_Black) { flags = SpotlightFlags::NoColor; } else { - if (!this->Type->HitFlash_Red) + if (!pType->HitFlash_Red) flags = SpotlightFlags::NoRed; - if (!this->Type->HitFlash_Green) + if (!pType->HitFlash_Green) flags |= SpotlightFlags::NoGreen; - if (!this->Type->HitFlash_Blue) + if (!pType->HitFlash_Blue) flags |= SpotlightFlags::NoBlue; } - MapClass::FlashbangWarheadAt(size, args->WH, this->Techno->Location, true, flags); + MapClass::FlashbangWarheadAt(size, pWH, pTechno->Location, true, flags); } if (!pWHExt->Shield_SkipHitAnim) diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index f99b5b68d9..046183ea2e 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -186,6 +186,7 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->DisableWeapons.Read(exINI, pSection, "DisableWeapons"); this->Unkillable.Read(exINI, pSection, "Unkillable"); + this->PreventNegativeDamage.Read(exINI, pSection, "PreventNegativeDamage"); // Groups exINI.ParseStringList(this->Groups, pSection, "Groups"); @@ -274,6 +275,7 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->ReflectDamage_UseInvokerAsOwner) .Process(this->DisableWeapons) .Process(this->Unkillable) + .Process(this->PreventNegativeDamage) .Process(this->Groups) ; } diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index 83edc89efd..cfb77baeb0 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -118,6 +118,7 @@ class AttachEffectTypeClass final : public Enumerable Valueable ReflectDamage_UseInvokerAsOwner; Valueable DisableWeapons; Valueable Unkillable; + Valueable PreventNegativeDamage; std::vector Groups; @@ -200,6 +201,7 @@ class AttachEffectTypeClass final : public Enumerable , ReflectDamage_UseInvokerAsOwner { false } , DisableWeapons { false } , Unkillable { false } + , PreventNegativeDamage { false } , Groups {} {}; From a7db33e8e53ceec1492d9ebfabd0f105929e3fce Mon Sep 17 00:00:00 2001 From: Coronia <2217891145@qq.com> Date: Mon, 23 Jun 2025 21:05:21 +0800 Subject: [PATCH 07/10] fix --- docs/New-or-Enhanced-Logics.md | 4 +++- src/Ext/Bullet/Body.cpp | 18 +++++++--------- src/Ext/Bullet/Body.h | 2 +- src/Ext/Techno/Body.h | 2 +- src/Ext/Techno/WeaponHelpers.cpp | 9 +++++--- src/New/Entity/AttachEffectClass.cpp | 29 ++++++++++++++++---------- src/New/Type/AttachEffectTypeClass.cpp | 4 +++- src/New/Type/AttachEffectTypeClass.h | 2 ++ 8 files changed, 42 insertions(+), 28 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 008a459916..3646cbe726 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -21,6 +21,7 @@ This page describes all the engine features that are either new and introduced b - `inrange`: Discard if within weapon range from current target. Distance can be overridden via `DiscardOn.RangeOverride`. - `outofrange`: Discard if outside weapon range from current target. Distance can be overridden via `DiscardOn.RangeOverride`. - `firing`: Discard when firing a weapon. This counts special weapons that are not actually fired such as ones with `Spawner=true` or `DrainWeapon=true`. + - If `DiscardOn.CumulativeCount` is greater than 0, the effect is discarded when it has `Cumulative=yes` and been attached to the object more than this amount of times. - If `DiscardOn.AbovePercent` or `DiscardOn.BelowPercent` is set, the effect is discarded when the object's health percentage is above/below that value. - If `AffectAbovePercent` or `AffectBelowPercent` is set, the effect can be applied only when the object's health percentage is above/below that value. - If `PenetratesIronCurtain` is not set to true, the effect is not applied on currently invulnerable objects. @@ -117,11 +118,12 @@ Cumulative=false ; boolean Cumulative.MaxCount=-1 ; integer Powered=false ; boolean DiscardOn=none ; List of discard condition enumeration (none|entry|move|stationary|drain|inrange|outofrange) +DiscardOn.RangeOverride= ; floating point value, distance in cells +DiscardOn.CumulativeCount=-1 ; integer DiscardOn.AbovePercent= ; floating point value, percents or absolute (0.0-1.0) DiscardOn.BelowPercent= ; floating point value, percents or absolute (0.0-1.0) AffectAbovePercent= ; floating point value, percents or absolute (0.0-1.0) AffectBelowPercent= ; floating point value, percents or absolute (0.0-1.0) -DiscardOn.RangeOverride= ; floating point value, distance in cells PenetratesIronCurtain=false ; boolean PenetratesForceShield= ; boolean Animation= ; AnimationType diff --git a/src/Ext/Bullet/Body.cpp b/src/Ext/Bullet/Body.cpp index eb80dec46c..ba95ed0794 100644 --- a/src/Ext/Bullet/Body.cpp +++ b/src/Ext/Bullet/Body.cpp @@ -151,9 +151,10 @@ void BulletExt::ExtData::InitializeLaserTrails() } } -void BulletExt::ExtData::ApplyExtraWarheads(const std::vector& exWH, const std::vector& exWHDamageOverrides, const std::vector& exWHChances, const std::vector& exWHFull, const std::vector& exWHOwner, const CoordStruct& coords, HouseClass* pOwner, TechnoClass* pInvoker) +void BulletExt::ExtData::ApplyExtraWarheads(const std::vector& exWH, const std::vector& exWHOverrides, const std::vector& exWHChances, const std::vector& exWHFull, const std::vector& exWHOwner, const CoordStruct& coords, HouseClass* pOwner, TechnoClass* pInvoker) { auto const pThis = this->OwnerObject(); + auto const pTarget = abstract_cast(pThis->Target); const int defaultDamage = pThis->WeaponType ? pThis->WeaponType->Damage : 0; for (size_t i = 0; i < exWH.size(); i++) @@ -161,19 +162,16 @@ void BulletExt::ExtData::ApplyExtraWarheads(const std::vector auto const pWH = exWH[i]; auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWH); - if (auto const pTarget = abstract_cast(pThis->Target)) - { - if (!pWHExt->IsHealthInThreshold(pTarget)) - continue; - } + if (pTarget && !pWHExt->IsHealthInThreshold(pTarget)) + continue; int damage = defaultDamage; - size_t size = exWHDamageOverrides.size(); + size_t size = exWHOverrides.size(); if (size > i) - damage = exWHDamageOverrides[i]; + damage = exWHOverrides[i]; else if (size > 0) - damage = exWHDamageOverrides[size - 1]; + damage = exWHOverrides[size - 1]; bool detonate = true; size = exWHChances.size(); @@ -222,7 +220,7 @@ void BulletExt::ExtData::ApplyExtraWarheads(const std::vector if (isFull) WarheadTypeExt::DetonateAt(pWH, coords, pFirer, damage, pHouse, pThis->Target); else - WarheadTypeExt::ExtMap.Find(pWH)->DamageAreaWithTarget(coords, damage, pFirer, pWH, true, pHouse, abstract_cast(pThis->Target)); + pWHExt->DamageAreaWithTarget(coords, damage, pFirer, pWH, true, pHouse, pTarget); } } diff --git a/src/Ext/Bullet/Body.h b/src/Ext/Bullet/Body.h index 91e2c26d85..b886db96dd 100644 --- a/src/Ext/Bullet/Body.h +++ b/src/Ext/Bullet/Body.h @@ -54,7 +54,7 @@ class BulletExt void InterceptBullet(TechnoClass* pSource, WeaponTypeClass* pWeapon); void ApplyRadiationToCell(CellStruct cell, int spread, int radLevel); void InitializeLaserTrails(); - void ApplyExtraWarheads(const std::vector& exWH, const std::vector& exWHDamageOverrides, const std::vector& exWHChances, const std::vector& exWHFull, const std::vector& exWHOwner, const CoordStruct& coords, HouseClass* pOwner, TechnoClass* pInvoker = nullptr); + void ApplyExtraWarheads(const std::vector& exWH, const std::vector& exWHOverrides, const std::vector& exWHChances, const std::vector& exWHFull, const std::vector& exWHOwner, const CoordStruct& coords, HouseClass* pOwner, TechnoClass* pInvoker = nullptr); private: template diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 6df4740d25..6f94c95c87 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -262,5 +262,5 @@ class TechnoExt static void ApplyKillWeapon(TechnoClass* pThis, TechnoClass* pSource, WarheadTypeClass* pWH); static void ApplyRevengeWeapon(TechnoClass* pThis, TechnoClass* pSource, WarheadTypeClass* pWH); static bool IsAllowedSplitsTarget(TechnoClass* pSource, HouseClass* pOwner, WeaponTypeClass* pWeapon, TechnoClass* pTarget, bool useWeaponTargeting = true, bool allowZeroDamage = false); - static void RealLaunch(WeaponTypeClass* pWeapon, TechnoClass* pSource, TechnoClass* pTarget, bool applyFirepowerMult = true); + static void RealLaunch(WeaponTypeClass* pWeapon, TechnoClass* pSource, TechnoClass* pTarget, bool applyFirepowerMult = true, TechnoClass* pFirer = nullptr); }; diff --git a/src/Ext/Techno/WeaponHelpers.cpp b/src/Ext/Techno/WeaponHelpers.cpp index dcc442cdea..cae2f921d9 100644 --- a/src/Ext/Techno/WeaponHelpers.cpp +++ b/src/Ext/Techno/WeaponHelpers.cpp @@ -254,7 +254,7 @@ void TechnoExt::ApplyKillWeapon(TechnoClass* pThis, TechnoClass* pSource, Warhea if (!pTypeExt->SuppressKillWeapons || (hasFilters && !pTypeExt->SuppressKillWeapons_Types.Contains(pWHExt->KillWeapon_OnFirer))) { if (pWHExt->KillWeapon_OnFirer_RealLaunch) - RealLaunch(pWHExt->KillWeapon_OnFirer, pSource, pSource); + RealLaunch(pWHExt->KillWeapon_OnFirer, pSource, pSource, true, pThis); else WeaponTypeExt::DetonateAt(pWHExt->KillWeapon_OnFirer, pSource, pSource); } @@ -488,7 +488,7 @@ void TechnoExt::ExtData::ApplyAuxWeapon(WeaponTypeClass* pAuxWeapon, AbstractCla } } -void TechnoExt::RealLaunch(WeaponTypeClass* pWeapon, TechnoClass* pSource, TechnoClass* pTarget, bool applyFirepowerMult) +void TechnoExt::RealLaunch(WeaponTypeClass* pWeapon, TechnoClass* pSource, TechnoClass* pTarget, bool applyFirepowerMult, TechnoClass* pFirer) { int damage = pWeapon->Damage; @@ -498,7 +498,10 @@ void TechnoExt::RealLaunch(WeaponTypeClass* pWeapon, TechnoClass* pSource, Techn if (BulletClass* pBullet = pWeapon->Projectile->CreateBullet(pTarget, pSource, damage, pWeapon->Warhead, pWeapon->Speed, pWeapon->Bright)) { - BulletExt::SimulatedFiringUnlimbo(pBullet, pSource->Owner, pWeapon, pSource->Location, true); + if (!pFirer) + pFirer = pSource; + + BulletExt::SimulatedFiringUnlimbo(pBullet, pSource->Owner, pWeapon, pFirer->Location, true); BulletExt::SimulatedFiringEffects(pBullet, pSource->Owner, nullptr, false, true); } } diff --git a/src/New/Entity/AttachEffectClass.cpp b/src/New/Entity/AttachEffectClass.cpp index 2963566e5d..778815c70c 100644 --- a/src/New/Entity/AttachEffectClass.cpp +++ b/src/New/Entity/AttachEffectClass.cpp @@ -513,26 +513,33 @@ bool AttachEffectClass::ShouldBeDiscardedNow() return true; } - auto const discardOn = this->Type->DiscardOn; + auto const pType = this->Type; + auto const pTechno = this->Techno; - if (discardOn == DiscardCondition::None) + if (pType->DiscardOn_AbovePercent > 0.0 && pTechno->GetHealthPercentage() >= pType->DiscardOn_AbovePercent) { - this->LastDiscardCheckValue = false; - return false; + this->LastDiscardCheckValue = true; + return true; } - auto const pTechno = this->Techno; + if (pType->DiscardOn_BelowPercent > 0.0 && pTechno->GetHealthPercentage() <= pType->DiscardOn_BelowPercent) + { + this->LastDiscardCheckValue = true; + return true; + } - if (this->Type->DiscardOn_AbovePercent > 0.0 && pTechno->GetHealthPercentage() >= this->Type->DiscardOn_AbovePercent) + if (pType->DiscardOn_CumulativeCount > 0 && pType->DiscardOn_CumulativeCount <= pTechno->GetAttachedEffectCumulativeCount(pType)) { - this->LastDiscardCheckValue = false; + this->LastDiscardCheckValue = true; return true; } - if (this->Type->DiscardOn_BelowPercent > 0.0 && pTechno->GetHealthPercentage() <= this->Type->DiscardOn_BelowPercent) + auto const discardOn = pType->DiscardOn; + + if (discardOn == DiscardCondition::None) { this->LastDiscardCheckValue = false; - return true; + return false; } if (auto const pFoot = abstract_cast(pTechno)) @@ -567,9 +574,9 @@ bool AttachEffectClass::ShouldBeDiscardedNow() { int distance = -1; - if (this->Type->DiscardOn_RangeOverride.isset()) + if (pType->DiscardOn_RangeOverride.isset()) { - distance = this->Type->DiscardOn_RangeOverride.Get(); + distance = pType->DiscardOn_RangeOverride.Get(); } else { diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index 046183ea2e..41dae5109f 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -96,7 +96,7 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) INI_EX exINI(pINI); this->Duration.Read(exINI, pSection, "Duration"); - this->Duration_ApplyVersus_Warhead.Read(exINI, pSection, "Duration.ApplyVersusWarhead"); + this->Duration_ApplyVersus_Warhead.Read(exINI, pSection, "Duration.ApplyVersus.Warhead"); this->Duration_ApplyFirepowerMult.Read(exINI, pSection, "Duration.ApplyFirepowerMult"); this->Duration_ApplyArmorMultOnTarget.Read(exINI, pSection, "Duration.ApplyArmorMultOnTarget"); this->Cumulative.Read(exINI, pSection, "Cumulative"); @@ -106,6 +106,7 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->DiscardOn_RangeOverride.Read(exINI, pSection, "DiscardOn.RangeOverride"); this->DiscardOn_AbovePercent.Read(exINI, pSection, "DiscardOn.AbovePercent"); this->DiscardOn_BelowPercent.Read(exINI, pSection, "DiscardOn.BelowPercent"); + this->DiscardOn_CumulativeCount.Read(exINI, pSection, "DiscardOn.CumulativeCount"); this->AffectAbovePercent.Read(exINI, pSection, "AffectAbovePercent"); this->AffectBelowPercent.Read(exINI, pSection, "AffectBelowPercent"); this->PenetratesIronCurtain.Read(exINI, pSection, "PenetratesIronCurtain"); @@ -208,6 +209,7 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->DiscardOn_RangeOverride) .Process(this->DiscardOn_AbovePercent) .Process(this->DiscardOn_BelowPercent) + .Process(this->DiscardOn_CumulativeCount) .Process(this->AffectAbovePercent) .Process(this->AffectBelowPercent) .Process(this->PenetratesIronCurtain) diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index cfb77baeb0..e584fe63c7 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -51,6 +51,7 @@ class AttachEffectTypeClass final : public Enumerable Nullable DiscardOn_RangeOverride; Valueable DiscardOn_AbovePercent; Valueable DiscardOn_BelowPercent; + Valueable DiscardOn_CumulativeCount; Valueable AffectAbovePercent; Valueable AffectBelowPercent; Valueable PenetratesIronCurtain; @@ -134,6 +135,7 @@ class AttachEffectTypeClass final : public Enumerable , DiscardOn_RangeOverride {} , DiscardOn_AbovePercent { 0.0 } , DiscardOn_BelowPercent { 0.0 } + , DiscardOn_CumulativeCount { -1 } , AffectAbovePercent { 0.0 } , AffectBelowPercent { 0.0 } , PenetratesIronCurtain { false } From 69146c76b45a76cc765abd36cf63aef1b120bd01 Mon Sep 17 00:00:00 2001 From: Coronia <2217891145@qq.com> Date: Thu, 26 Jun 2025 18:29:22 +0800 Subject: [PATCH 08/10] KillWeapon --- docs/New-or-Enhanced-Logics.md | 4 ++ src/Ext/Bullet/Body.cpp | 2 +- src/Ext/Techno/WeaponHelpers.cpp | 69 +++++++++++++++++++------- src/New/Type/AttachEffectTypeClass.cpp | 15 ++++++ src/New/Type/AttachEffectTypeClass.h | 14 ++++++ 5 files changed, 86 insertions(+), 18 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 0cd5e8d0c3..0d2fd10dd3 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -67,6 +67,10 @@ This page describes all the engine features that are either new and introduced b - On TechnoTypes with `OpenTopped=true`, `OpenTopped.UseTransportRangeModifiers` can be set to true to make passengers firing out use the transport's active range bonuses instead. - `Crit.Multiplier` and `Crit.ExtraChance` can be used to multiply the [critical hit](#chance-based-extra-damage-or-warhead-detonation--critical-hits) chance or grant a fixed bonus to it for the object the effect is attached to, respectively. - `Crit.AllowWarheads` can be used to list only Warheads that can benefit from this critical hit chance multiplier and `Crit.DisallowWarheads` weapons that are not allowed to, respectively. + - `KillWeapon` will be fired at the target TechnoType's location once it's killed by the attached object. + - `KillWeapon.OnFirer` will be fired at the attacker's location once the target TechnoType is killed the attached object. If `KillWeapon.OnFirer.RealLaunch` set to true, the weapon will be fired through a real projectile from the TechnoType to the attached object. + - `KillWeapon.AffectsHouses` / `KillWeapon.OnFirer.AffectsHouses` and `KillWeapon.Affects` / `KillWeapon.OnFirer.Affects` can be used to filter which houses targets can belong to and which types of targets are be considered valid for `KillWeapon` and `KillWeapon.OnFirer` respectively. + - If a TechnoType has `SuppressKillWeapons` set to true, it will not trigger `KillWeapon` or `KillWeapon.OnFirer` upon being killed. `SuppressKillWeapons.Types` can be used to list WeaponTypes affected by this, if none are listed all WeaponTypes are affected. - `RevengeWeapon` can be used to temporarily grant the specified weapon as a [revenge weapon](#revenge-weapon) for the attached object. - `RevengeWeapon.AffectsHouses` customizes which houses can trigger the revenge weapon. - If `RevengeWeapon.RealLaunch` set to true, the weapon will be fired through a real projectile from the TechnoType to the killer. diff --git a/src/Ext/Bullet/Body.cpp b/src/Ext/Bullet/Body.cpp index a0c3609b2d..9b7063de0b 100644 --- a/src/Ext/Bullet/Body.cpp +++ b/src/Ext/Bullet/Body.cpp @@ -153,13 +153,13 @@ void BulletExt::ExtData::InitializeLaserTrails() void BulletExt::ExtData::ApplyExtraWarheads(const std::vector& exWH, const std::vector& exWHOverrides, const std::vector& exWHChances, const std::vector& exWHFull, const std::vector& exWHOwner, const CoordStruct& coords, HouseClass* pOwner, TechnoClass* pInvoker) { auto const pThis = this->OwnerObject(); - auto const pTarget = abstract_cast(pThis->Target); const int defaultDamage = pThis->WeaponType ? pThis->WeaponType->Damage : 0; for (size_t i = 0; i < exWH.size(); i++) { auto const pWH = exWH[i]; auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWH); + auto const pTarget = abstract_cast(pThis->Target); // must be check in every loop if (pTarget && !pWHExt->IsHealthInThreshold(pTarget)) continue; diff --git a/src/Ext/Techno/WeaponHelpers.cpp b/src/Ext/Techno/WeaponHelpers.cpp index 23ebabcac4..f72f45d2e9 100644 --- a/src/Ext/Techno/WeaponHelpers.cpp +++ b/src/Ext/Techno/WeaponHelpers.cpp @@ -239,25 +239,60 @@ void TechnoExt::ApplyKillWeapon(TechnoClass* pThis, TechnoClass* pSource, Warhea auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWH); const bool hasFilters = pTypeExt->SuppressKillWeapons_Types.size() > 0; - // KillWeapon can be triggered without the source - if (pWHExt->KillWeapon && (!pSource || (EnumFunctions::CanTargetHouse(pWHExt->KillWeapon_AffectsHouses, pSource->Owner, pThis->Owner) - && EnumFunctions::IsTechnoEligible(pThis, pWHExt->KillWeapon_Affects)))) - { - if (!pTypeExt->SuppressKillWeapons || (hasFilters && !pTypeExt->SuppressKillWeapons_Types.Contains(pWHExt->KillWeapon))) - WeaponTypeExt::DetonateAt(pWHExt->KillWeapon, pThis, pSource); - } - - // KillWeapon.OnFirer must have a source - if (pWHExt->KillWeapon_OnFirer && pSource && EnumFunctions::CanTargetHouse(pWHExt->KillWeapon_OnFirer_AffectsHouses, pSource->Owner, pThis->Owner) - && EnumFunctions::IsTechnoEligible(pThis, pWHExt->KillWeapon_OnFirer_Affects)) - { - if (!pTypeExt->SuppressKillWeapons || (hasFilters && !pTypeExt->SuppressKillWeapons_Types.Contains(pWHExt->KillWeapon_OnFirer))) + auto tryKillWeapon = [&](auto pWeapon, AffectedHouse affectsHouses, AffectedTarget affects, bool realLaunch, bool onFirer) { - if (pWHExt->KillWeapon_OnFirer_RealLaunch) - RealLaunch(pWHExt->KillWeapon_OnFirer, pSource, pSource, true, pThis); + if (!pWeapon) + return; + + if (onFirer) + { + if (!pSource) + return; + + if (!(EnumFunctions::CanTargetHouse(affectsHouses, pSource->Owner, pThis->Owner) + && EnumFunctions::IsTechnoEligible(pThis, affects))) + { + return; + } + } else - WeaponTypeExt::DetonateAt(pWHExt->KillWeapon_OnFirer, pSource, pSource); - } + { + if (pSource && !(EnumFunctions::CanTargetHouse(affectsHouses, pSource->Owner, pThis->Owner) + && EnumFunctions::IsTechnoEligible(pThis, affects))) + { + return; + } + } + + if (pTypeExt->SuppressKillWeapons && (!hasFilters || pTypeExt->SuppressKillWeapons_Types.Contains(pWeapon))) + return; + + if (onFirer) + { + if (realLaunch) + RealLaunch(pWeapon, pSource, pSource, true, pThis); + else + WeaponTypeExt::DetonateAt(pWeapon, pSource, pSource); + } + else + { + WeaponTypeExt::DetonateAt(pWeapon, pThis, pSource); + } + }; + + tryKillWeapon(pWHExt->KillWeapon, pWHExt->KillWeapon_AffectsHouses, pWHExt->KillWeapon_Affects, false, false); + tryKillWeapon(pWHExt->KillWeapon_OnFirer, pWHExt->KillWeapon_OnFirer_AffectsHouses, pWHExt->KillWeapon_OnFirer_Affects, pWHExt->KillWeapon_OnFirer_RealLaunch, true); + + auto const pExt = TechnoExt::ExtMap.Find(pSource); + + for (auto const& attachEffect : pExt->AttachedEffects) + { + if (!attachEffect->IsActive()) + continue; + + auto const pType = attachEffect->GetType(); + tryKillWeapon(pType->KillWeapon, pType->KillWeapon_AffectsHouses, pType->KillWeapon_Affects, false, false); + tryKillWeapon(pType->KillWeapon_OnFirer, pType->KillWeapon_OnFirer_AffectsHouses, pType->KillWeapon_OnFirer_Affects, pType->KillWeapon_OnFirer_RealLaunch, true); } } diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index cac35994c9..8ba7ece80f 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -171,6 +171,14 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->Crit_AllowWarheads.Read(exINI, pSection, "Crit.AllowWarheads"); this->Crit_DisallowWarheads.Read(exINI, pSection, "Crit.DisallowWarheads"); + this->KillWeapon.Read(exINI, pSection, "KillWeapon"); + this->KillWeapon_OnFirer.Read(exINI, pSection, "KillWeapon.OnFirer"); + this->KillWeapon_AffectsHouses.Read(exINI, pSection, "KillWeapon.AffectsHouses"); + this->KillWeapon_OnFirer_AffectsHouses.Read(exINI, pSection, "KillWeapon.OnFirer.AffectsHouses"); + this->KillWeapon_Affects.Read(exINI, pSection, "KillWeapon.Affects"); + this->KillWeapon_OnFirer_Affects.Read(exINI, pSection, "KillWeapon.OnFirer.Affects"); + this->KillWeapon_OnFirer_RealLaunch.Read(exINI, pSection, "KillWeapon.OnFirer.RealLaunch"); + this->RevengeWeapon.Read(exINI, pSection, "RevengeWeapon"); this->RevengeWeapon_AffectsHouses.Read(exINI, pSection, "RevengeWeapon.AffectsHouses"); this->RevengeWeapon_RealLaunch.Read(exINI, pSection, "RevengeWeapon.RealLaunch"); @@ -264,6 +272,13 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->Crit_ExtraChance) .Process(this->Crit_AllowWarheads) .Process(this->Crit_DisallowWarheads) + .Process(this->KillWeapon) + .Process(this->KillWeapon_OnFirer) + .Process(this->KillWeapon_AffectsHouses) + .Process(this->KillWeapon_OnFirer_AffectsHouses) + .Process(this->KillWeapon_Affects) + .Process(this->KillWeapon_OnFirer_Affects) + .Process(this->KillWeapon_OnFirer_RealLaunch) .Process(this->RevengeWeapon) .Process(this->RevengeWeapon_AffectsHouses) .Process(this->RevengeWeapon_RealLaunch) diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index f38062993d..cef1bd1b73 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -106,6 +106,13 @@ class AttachEffectTypeClass final : public Enumerable Valueable Crit_ExtraChance; ValueableVector Crit_AllowWarheads; ValueableVector Crit_DisallowWarheads; + Valueable KillWeapon; + Valueable KillWeapon_OnFirer; + Valueable KillWeapon_AffectsHouses; + Valueable KillWeapon_OnFirer_AffectsHouses; + Valueable KillWeapon_Affects; + Valueable KillWeapon_OnFirer_Affects; + Valueable KillWeapon_OnFirer_RealLaunch; Valueable RevengeWeapon; Valueable RevengeWeapon_AffectsHouses; Valueable RevengeWeapon_RealLaunch; @@ -191,6 +198,13 @@ class AttachEffectTypeClass final : public Enumerable , Crit_ExtraChance { 0.0 } , Crit_AllowWarheads {} , Crit_DisallowWarheads {} + , KillWeapon {} + , KillWeapon_OnFirer {} + , KillWeapon_AffectsHouses { AffectedHouse::All } + , KillWeapon_OnFirer_AffectsHouses { AffectedHouse::All } + , KillWeapon_Affects { AffectedTarget::All } + , KillWeapon_OnFirer_Affects { AffectedTarget::All } + , KillWeapon_OnFirer_RealLaunch { false } , RevengeWeapon {} , RevengeWeapon_AffectsHouses { AffectedHouse::All } , RevengeWeapon_RealLaunch { false } From 1830fd02833d4aa2645542e64adf6db43623503c Mon Sep 17 00:00:00 2001 From: Coronia <2217891145@qq.com> Date: Fri, 27 Jun 2025 11:39:36 +0800 Subject: [PATCH 09/10] NegativeDamage.Multiplier --- docs/New-or-Enhanced-Logics.md | 4 ++-- src/Ext/Techno/Body.Update.cpp | 8 ++++---- src/Ext/Techno/Hooks.ReceiveDamage.cpp | 4 ++-- src/New/Entity/AttachEffectClass.h | 4 ++-- src/New/Entity/ShieldClass.cpp | 4 ++-- src/New/Type/AttachEffectTypeClass.cpp | 4 ++-- src/New/Type/AttachEffectTypeClass.h | 4 ++-- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index ce8719b631..feb80bb165 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -81,7 +81,7 @@ This page describes all the engine features that are either new and introduced b - `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). - - `PreventNegativeDamage` can be used to prevent the techno from taking negative damage. This includes both negative `Damage` and negative `Verses`. + - `NegativeDamage.Multiplier` will be multiplied on the negative damage taken by the attached object. This includes both negative `Damage` and negative `Verses`. - It is possible to set groups for attach effect types by defining strings in `Groups`. - Groups can be used instead of types for removing effects and weapon filters. @@ -200,7 +200,7 @@ ReflectDamage.Override= ; integer ReflectDamage.UseInvokerAsOwner=false ; boolean DisableWeapons=false ; boolean Unkillable=false ; boolean -PreventNegativeDamage=false ; boolean +NegativeDamage.Multiplier=1.0 ; floating point value LaserTrail.Type= ; lasertrail type Groups= ; comma-separated list of strings (group IDs) diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index 0be37603ed..6a7c6df608 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -43,7 +43,7 @@ void TechnoExt::ExtData::OnEarlyUpdate() this->ApplyMindControlRangeLimit(); this->UpdateRecountBurst(); this->UpdateRearmInEMPState(); - + if (this->AttackMoveFollowerTempCount) { this->AttackMoveFollowerTempCount--; @@ -1833,11 +1833,11 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() double armor = 1.0; double speed = 1.0; double ROF = 1.0; + double negativeDamage = 1.0; bool cloak = false; bool forceDecloak = false; bool disableWeapons = false; bool unkillable = false; - bool preventNegativeDamage = false; bool hasRangeModifier = false; bool hasTint = false; bool reflectsDamage = false; @@ -1857,11 +1857,11 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() speed *= type->SpeedMultiplier; armor *= type->ArmorMultiplier; ROF *= type->ROFMultiplier; + negativeDamage *= type->NegativeDamage_Multiplier; cloak |= type->Cloakable; forceDecloak |= type->ForceDecloak; disableWeapons |= type->DisableWeapons; unkillable |= type->Unkillable; - preventNegativeDamage |= type->PreventNegativeDamage; hasRangeModifier |= (type->WeaponRange_ExtraRange != 0.0 || type->WeaponRange_Multiplier != 0.0); hasTint |= type->HasTint(); reflectsDamage |= type->ReflectDamage; @@ -1876,11 +1876,11 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() pAE.ArmorMultiplier = armor; pAE.SpeedMultiplier = speed; pAE.ROFMultiplier = ROF; + pAE.NegativeDamageMultiplier = negativeDamage; pAE.Cloakable = cloak; pAE.ForceDecloak = forceDecloak; pAE.DisableWeapons = disableWeapons; pAE.Unkillable = unkillable; - pAE.PreventNegativeDamage = preventNegativeDamage; pAE.HasRangeModifier = hasRangeModifier; pAE.HasTint = hasTint; pAE.ReflectDamage = reflectsDamage; diff --git a/src/Ext/Techno/Hooks.ReceiveDamage.cpp b/src/Ext/Techno/Hooks.ReceiveDamage.cpp index af996d4df8..d095a99b2b 100644 --- a/src/Ext/Techno/Hooks.ReceiveDamage.cpp +++ b/src/Ext/Techno/Hooks.ReceiveDamage.cpp @@ -138,10 +138,10 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) ReceiveDamageTemp::SkipLowDamageCheck = true; } - if (pExt->AE.PreventNegativeDamage && nDamageLeft != 0 && pWHExt->CanTargetHouse(pSourceHouse, pThis) + if (pExt->AE.NegativeDamageMultiplier != 1.0 && nDamageLeft != 0 && pWHExt->CanTargetHouse(pSourceHouse, pThis) && MapClass::GetTotalDamage(nDamageLeft, args->WH, pThis->GetTechnoType()->Armor, args->DistanceToEpicenter) < 0) { - damage = 0; + damage = static_cast(damage * pExt->AE.NegativeDamageMultiplier); } } diff --git a/src/New/Entity/AttachEffectClass.h b/src/New/Entity/AttachEffectClass.h index 7b3152db9d..c09f1802bc 100644 --- a/src/New/Entity/AttachEffectClass.h +++ b/src/New/Entity/AttachEffectClass.h @@ -96,11 +96,11 @@ struct AttachEffectTechnoProperties double ArmorMultiplier; double SpeedMultiplier; double ROFMultiplier; + double NegativeDamageMultiplier; bool Cloakable; bool ForceDecloak; bool DisableWeapons; bool Unkillable; - bool PreventNegativeDamage; bool HasRangeModifier; bool HasTint; bool ReflectDamage; @@ -115,11 +115,11 @@ struct AttachEffectTechnoProperties , ArmorMultiplier { 1.0 } , SpeedMultiplier { 1.0 } , ROFMultiplier { 1.0 } + , NegativeDamageMultiplier { 1.0 } , Cloakable { false } , ForceDecloak { false } , DisableWeapons { false } , Unkillable { false } - , PreventNegativeDamage { false } , HasRangeModifier { false } , HasTint { false } , ReflectDamage { false } diff --git a/src/New/Entity/ShieldClass.cpp b/src/New/Entity/ShieldClass.cpp index db172f3185..e5c34af61f 100644 --- a/src/New/Entity/ShieldClass.cpp +++ b/src/New/Entity/ShieldClass.cpp @@ -222,8 +222,8 @@ int ShieldClass::ReceiveDamage(args_ReceiveDamage* args) const int maxDmg = static_cast(max * pWHExt->Shield_ReceivedDamage_MaxMultiplier); shieldDamage = Math::clamp(shieldDamage, minDmg, maxDmg); - if (shieldDamage < 0 && TechnoExt::ExtMap.Find(pTechno)->AE.PreventNegativeDamage) - shieldDamage = 0; + if (shieldDamage < 0) + shieldDamage = static_cast(shieldDamage * TechnoExt::ExtMap.Find(pTechno)->AE.NegativeDamageMultiplier); if (Phobos::DisplayDamageNumbers && shieldDamage != 0) GeneralUtils::DisplayDamageNumberString(shieldDamage, DamageDisplayType::Shield, pTechno->GetRenderCoords(), TechnoExt::ExtMap.Find(pTechno)->DamageNumberOffset); diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index 8ba7ece80f..e0bfd6cd4c 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -195,7 +195,7 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->DisableWeapons.Read(exINI, pSection, "DisableWeapons"); this->Unkillable.Read(exINI, pSection, "Unkillable"); - this->PreventNegativeDamage.Read(exINI, pSection, "PreventNegativeDamage"); + this->NegativeDamage_Multiplier.Read(exINI, pSection, "NegativeDamage.Multiplier"); this->LaserTrail_Type.Read(exINI, pSection, "LaserTrail.Type"); // Groups @@ -293,7 +293,7 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->ReflectDamage_UseInvokerAsOwner) .Process(this->DisableWeapons) .Process(this->Unkillable) - .Process(this->PreventNegativeDamage) + .Process(this->NegativeDamage_Multiplier) .Process(this->LaserTrail_Type) .Process(this->Groups) ; diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index cef1bd1b73..662ced9b25 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -127,7 +127,7 @@ class AttachEffectTypeClass final : public Enumerable Valueable ReflectDamage_UseInvokerAsOwner; Valueable DisableWeapons; Valueable Unkillable; - Valueable PreventNegativeDamage; + Valueable NegativeDamage_Multiplier; ValueableIdx LaserTrail_Type; std::vector Groups; @@ -219,7 +219,7 @@ class AttachEffectTypeClass final : public Enumerable , ReflectDamage_UseInvokerAsOwner { false } , DisableWeapons { false } , Unkillable { false } - , PreventNegativeDamage { false } + , NegativeDamage_Multiplier { 1.0 } , LaserTrail_Type { -1 } , Groups {} {}; From 84066ee2260bb4a1897618bc89df30328808cfc4 Mon Sep 17 00:00:00 2001 From: Coronia <2217891145@qq.com> Date: Sun, 29 Jun 2025 15:03:57 +0800 Subject: [PATCH 10/10] tweak --- src/Ext/Bullet/Body.cpp | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/Ext/Bullet/Body.cpp b/src/Ext/Bullet/Body.cpp index 9b7063de0b..79d6272f1d 100644 --- a/src/Ext/Bullet/Body.cpp +++ b/src/Ext/Bullet/Body.cpp @@ -190,21 +190,10 @@ void BulletExt::ExtData::ApplyExtraWarheads(const std::vector { size = exWHOwner.size(); - if (size > i) + if ((size > i && exWHOwner[i]) || (size > 0 && exWHOwner[size - 1])) { - if (exWHOwner[i]) - { - pFirer = pInvoker; - pHouse = pInvoker->Owner; - } - } - else if (size > 0) - { - if (exWHOwner[size - 1]) - { - pFirer = pInvoker; - pHouse = pInvoker->Owner; - } + pFirer = pInvoker; + pHouse = pInvoker->Owner; } }