From 45797d58ba4e9dce4c1d57facdb4528f6c1bcb75 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Sun, 2 Aug 2026 13:46:50 +0800 Subject: [PATCH 01/12] initial --- CREDITS.md | 1 + docs/New-or-Enhanced-Logics.md | 8 ++- docs/Whats-New.md | 1 + src/New/Entity/AttachEffectClass.cpp | 67 ++++++++++++++++++++++++++ src/New/Type/AttachEffectTypeClass.cpp | 28 +++++++++++ src/New/Type/AttachEffectTypeClass.h | 18 ++++++- 6 files changed, 121 insertions(+), 2 deletions(-) diff --git a/CREDITS.md b/CREDITS.md index 61295f2ce5..03946383dc 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -728,6 +728,7 @@ This page lists all the individual contributions to the project by their author. - Fix the bug where landing direction cannot be correctly converted when set to a value exceeding 256 - Separately define the global default values of TerrainTypes' `IsPassable` and `CanBeBuiltOn` based on `SpawnsTiberium` - Remove the restriction that prohibits InfantryTypes from using the InitialPayload logic + - Add `ammo`, `health`, `mission` and `landtype` conditions to `DiscardOn` - **Ollerus**: - Build limit group enhancement - Customizable rocker amplitude diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 1cef4bcfe4..fd217612e9 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -112,7 +112,13 @@ 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|selling|undeploying|harvesting|invokerdie) +DiscardOn=none ; List of discard condition enumeration (none|entry|move|stationary|drain|inrange|outofrange|selling|undeploying|harvesting|invokerdie|ammo|health|mission|landtype) +DiscardOn.Ammo.Min= ; integer +DiscardOn.Ammo.Max= ; integer +DiscardOn.Health.Min= ; integer +DiscardOn.Health.Max= ; integer +DiscardOn.Missions= ; List of MissionType +DiscardOn.LandTypes= ; List of LandTypes (none | clear | road | water | rock | wall | tiberium | beach | rough | ice | railroad | tunnel | weeds) DiscardOn.RangeOverride= ; floating point value, distance in cells DiscardOn.MoveBasedOnDestination= ; boolean, default to [General] -> DiscardOn.MoveBasedOnDestination DiscardOn.ConsiderHarvestingAsStationary= ; boolean, default to [General] -> DiscardOn.ConsiderHarvestingAsStationary diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 5562162d1a..f2bf9ce02e 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -646,6 +646,7 @@ HideShakeEffects=false ; boolean - Separately define the global default values of TerrainTypes' `IsPassable` and `CanBeBuiltOn` based on `SpawnsTiberium` (by Noble_Fish) - Customize reveal radius of `RevealToAll` (by NetsuNegi) - [Customize whether aircraft is a cargo plane](Fixed-or-Improved-Logics.md#customize-whether-aircraft-is-a-cargo-plane) (by TaranDahl) +- Add `ammo`, `health`, `mission` and `landtype` conditions to `DiscardOn` (by Noble_Fish) #### 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/New/Entity/AttachEffectClass.cpp b/src/New/Entity/AttachEffectClass.cpp index 88d01d870a..b7c98a6e94 100644 --- a/src/New/Entity/AttachEffectClass.cpp +++ b/src/New/Entity/AttachEffectClass.cpp @@ -635,6 +635,73 @@ bool AttachEffectClass::ShouldBeDiscardedNow() return true; } + if ((discardOn & DiscardCondition::Ammo) != DiscardCondition::None) + { + bool trigger = false; + if (pType->DiscardOn_Ammo_Min.isset() || pType->DiscardOn_Ammo_Max.isset()) + { + const int min = pType->DiscardOn_Ammo_Min.Get(-1); + const int max = pType->DiscardOn_Ammo_Max.Get(-1); + const int ammo = pTechno->Ammo; + + trigger = (min < 0 || ammo >= min) && (max < 0 || ammo <= max); + } + + if (trigger) + { + this->LastDiscardCheckValue = true; + return true; + } + } + + if ((discardOn & DiscardCondition::Health) != DiscardCondition::None) + { + if (auto const pTypeData = pTechno->GetTechnoType()) + { + const double hp = pTechno->GetHealthPercentage(); + + if (pType->DiscardOn_Health_Min.isset() || pType->DiscardOn_Health_Max.isset()) + { + const double min = pType->DiscardOn_Health_Min.Get(0.0); + const double max = pType->DiscardOn_Health_Max.Get(1.0); + + if ((hp > 0.0 ? hp > min : hp >= min) && hp <= max) + { + this->LastDiscardCheckValue = true; + return true; + } + } + } + } + + if ((discardOn & DiscardCondition::LandType) != DiscardCondition::None) + { + if (pType->DiscardOn_LandTypes != LandTypeFlags::None) + { + if (auto const pCell = pTechno->GetCell()) + { + LandTypeFlags landFlags = pType->DiscardOn_LandTypes; + if (IsLandTypeInFlags(landFlags, pCell->LandType)) + { + this->LastDiscardCheckValue = true; + return true; + } + } + } + } + + if ((discardOn & DiscardCondition::Mission) != DiscardCondition::None) + { + if (pType->DiscardOn_Missions.size() > 0) + { + if (pType->DiscardOn_Missions.Contains(pTechno->CurrentMission)) + { + this->LastDiscardCheckValue = true; + return true; + } + } + } + if (pTechno->Target) { const bool inRange = (discardOn & DiscardCondition::InRange) != DiscardCondition::None; diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index 6ed4ce8503..458f3a29c0 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -100,6 +100,12 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->Cumulative_MaxCount.Read(exINI, pSection, "Cumulative.MaxCount"); this->Powered.Read(exINI, pSection, "Powered"); this->DiscardOn.Read(exINI, pSection, "DiscardOn"); + this->DiscardOn_Ammo_Min.Read(exINI, pSection, "DiscardOn.Ammo.Min"); + this->DiscardOn_Ammo_Max.Read(exINI, pSection, "DiscardOn.Ammo.Max"); + this->DiscardOn_Health_Min.Read(exINI, pSection, "DiscardOn.Health.Min"); + this->DiscardOn_Health_Max.Read(exINI, pSection, "DiscardOn.Health.Max"); + this->DiscardOn_Missions.Read(exINI, pSection, "DiscardOn.Missions"); + this->DiscardOn_LandTypes.Read(exINI, pSection, "DiscardOn.LandTypes"); this->DiscardOn_RangeOverride.Read(exINI, pSection, "DiscardOn.RangeOverride"); this->DiscardOn_MoveBasedOnDestination.Read(exINI, pSection, "DiscardOn.MoveBasedOnDestination"); this->DiscardOn_ConsiderHarvestingAsStationary.Read(exINI, pSection, "DiscardOn.ConsiderHarvestingAsStationary"); @@ -218,6 +224,12 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->Cumulative_MaxCount) .Process(this->Powered) .Process(this->DiscardOn) + .Process(this->DiscardOn_Ammo_Min) + .Process(this->DiscardOn_Ammo_Max) + .Process(this->DiscardOn_Health_Min) + .Process(this->DiscardOn_Health_Max) + .Process(this->DiscardOn_Missions) + .Process(this->DiscardOn_LandTypes) .Process(this->DiscardOn_RangeOverride) .Process(this->DiscardOn_MoveBasedOnDestination) .Process(this->DiscardOn_ConsiderHarvestingAsStationary) @@ -353,6 +365,22 @@ namespace detail { parsed |= DiscardCondition::InvokerDie; } + else if (!_strcmpi(cur, "ammo")) + { + parsed |= DiscardCondition::Ammo; + } + else if (!_strcmpi(cur, "health")) + { + parsed |= DiscardCondition::Health; + } + else if (!_strcmpi(cur, "mission")) + { + parsed |= DiscardCondition::Mission; + } + else if (!_strcmpi(cur, "landtype")) + { + parsed |= DiscardCondition::LandType; + } else { Debug::INIParseFailed(pSection, pKey, cur, "Expected a discard condition type"); diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index 6de5e2d64c..6ed860b699 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -21,7 +21,11 @@ enum class DiscardCondition : unsigned short Selling = 0x80, Undeploying = 0x100, Harvesting = 0x200, - InvokerDie = 0x400 + InvokerDie = 0x400, + Ammo = 0x800, + Health = 0x1000, + Mission = 0x2000, + LandType = 0x4000 }; MAKE_ENUM_FLAGS(DiscardCondition); @@ -52,6 +56,12 @@ class AttachEffectTypeClass final : public Enumerable Valueable Cumulative_MaxCount; Valueable Powered; Valueable DiscardOn; + Nullable DiscardOn_Ammo_Min; + Nullable DiscardOn_Ammo_Max; + Nullable DiscardOn_Health_Min; + Nullable DiscardOn_Health_Max; + ValueableVector DiscardOn_Missions; + Valueable DiscardOn_LandTypes; Nullable DiscardOn_RangeOverride; Nullable DiscardOn_MoveBasedOnDestination; Nullable DiscardOn_ConsiderHarvestingAsStationary; @@ -122,6 +132,12 @@ class AttachEffectTypeClass final : public Enumerable , Cumulative_MaxCount { -1 } , Powered { false } , DiscardOn { DiscardCondition::None } + , DiscardOn_Ammo_Min {} + , DiscardOn_Ammo_Max {} + , DiscardOn_Health_Min {} + , DiscardOn_Health_Max {} + , DiscardOn_Missions {} + , DiscardOn_LandTypes { LandTypeFlags::None } , DiscardOn_RangeOverride {} , DiscardOn_MoveBasedOnDestination {} , DiscardOn_ConsiderHarvestingAsStationary {} From 07037dd55f402ba879ac839d1937df5fe435a672 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Sun, 2 Aug 2026 14:15:13 +0800 Subject: [PATCH 02/12] update doc --- docs/New-or-Enhanced-Logics.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index fd217612e9..c8cc8b0604 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -23,7 +23,11 @@ This page describes all the engine features that are either new and introduced b - `selling`: Discard when the building to which the effect is attached is sold. - `undeploying`: Discard when the building to which the effect is attached performs undeploy. - `harvesting`: Discard when the object the effect is attached is harvesting ore. This can only be used when `DiscardOn.ConsiderHarvestingAsStationary=false`. - - `invokerdie`: Discard when the invoker of the effect is destroyed. + - `invokerdie`: Discard when the invoker of the effect is destroyed. + - `ammo`: Discard when the ammo of the object the effect is attached to is within the interval `[DiscardOn.Ammo.Min, DiscardOn.Ammo.Max]`. + - `health`: Discard when the health ratio of the object the effect is attached to is within the interval `[DiscardOn.Health.Min, DiscardOn.Health.Max]`. + - `mission`: Discard when the current mission of the object the effect is attached to matches any one in the `DiscardOn.Missions` list. + - `landtype`: Discard when the land type of the cell where the object the effect is attached to is currently located matches any land type in the `DiscardOn.LandTypes` list. - `DiscardOn.MoveBasedOnDestination` defines whether to determine the movement state according to the presence or absence of a destination. It treats Jumpjet units hovering in the air as movement, and units that have no destination but are turning as stationary. - If used for an AE that has `DiscardOn=harvesting`, in order for it to judge correctly, this should be set to `true`. - `DiscardOn.ConsiderHarvestingAsStationary` defines whether to treat `harvesting` as `stationary`. When this flag is set to `false`, `DiscardOn=harvesting` can be used and it will not be considered `stationary` while `harvesting`. From 6f137b20313eb1a89d50211b8ac908ec31c64dfd Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Sun, 2 Aug 2026 14:35:07 +0800 Subject: [PATCH 03/12] update --- src/New/Entity/AttachEffectClass.cpp | 8 ++++---- src/New/Type/AttachEffectTypeClass.cpp | 8 ++++---- src/New/Type/AttachEffectTypeClass.h | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/New/Entity/AttachEffectClass.cpp b/src/New/Entity/AttachEffectClass.cpp index b7c98a6e94..b0bd58bfa1 100644 --- a/src/New/Entity/AttachEffectClass.cpp +++ b/src/New/Entity/AttachEffectClass.cpp @@ -635,7 +635,7 @@ bool AttachEffectClass::ShouldBeDiscardedNow() return true; } - if ((discardOn & DiscardCondition::Ammo) != DiscardCondition::None) + if ((discardOn & DiscardCondition::DiscardAmmo) != DiscardCondition::None) { bool trigger = false; if (pType->DiscardOn_Ammo_Min.isset() || pType->DiscardOn_Ammo_Max.isset()) @@ -654,7 +654,7 @@ bool AttachEffectClass::ShouldBeDiscardedNow() } } - if ((discardOn & DiscardCondition::Health) != DiscardCondition::None) + if ((discardOn & DiscardCondition::DiscardHealth) != DiscardCondition::None) { if (auto const pTypeData = pTechno->GetTechnoType()) { @@ -674,7 +674,7 @@ bool AttachEffectClass::ShouldBeDiscardedNow() } } - if ((discardOn & DiscardCondition::LandType) != DiscardCondition::None) + if ((discardOn & DiscardCondition::DiscardLandType) != DiscardCondition::None) { if (pType->DiscardOn_LandTypes != LandTypeFlags::None) { @@ -690,7 +690,7 @@ bool AttachEffectClass::ShouldBeDiscardedNow() } } - if ((discardOn & DiscardCondition::Mission) != DiscardCondition::None) + if ((discardOn & DiscardCondition::DiscardMission) != DiscardCondition::None) { if (pType->DiscardOn_Missions.size() > 0) { diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index 458f3a29c0..267328069d 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -367,19 +367,19 @@ namespace detail } else if (!_strcmpi(cur, "ammo")) { - parsed |= DiscardCondition::Ammo; + parsed |= DiscardCondition::DiscardAmmo; } else if (!_strcmpi(cur, "health")) { - parsed |= DiscardCondition::Health; + parsed |= DiscardCondition::DiscardHealth; } else if (!_strcmpi(cur, "mission")) { - parsed |= DiscardCondition::Mission; + parsed |= DiscardCondition::DiscardMission; } else if (!_strcmpi(cur, "landtype")) { - parsed |= DiscardCondition::LandType; + parsed |= DiscardCondition::DiscardLandType; } else { diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index 6ed860b699..aa140ceea5 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -22,10 +22,10 @@ enum class DiscardCondition : unsigned short Undeploying = 0x100, Harvesting = 0x200, InvokerDie = 0x400, - Ammo = 0x800, - Health = 0x1000, - Mission = 0x2000, - LandType = 0x4000 + DiscardAmmo = 0x800, + DiscardHealth = 0x1000, + DiscardMission = 0x2000, + DiscardLandType = 0x4000 }; MAKE_ENUM_FLAGS(DiscardCondition); From c2f37f75753a54bfa446933777bc46946e58af5f Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Sun, 2 Aug 2026 14:45:22 +0800 Subject: [PATCH 04/12] Revert "update" This reverts commit 6f137b20313eb1a89d50211b8ac908ec31c64dfd. --- src/New/Entity/AttachEffectClass.cpp | 8 ++++---- src/New/Type/AttachEffectTypeClass.cpp | 8 ++++---- src/New/Type/AttachEffectTypeClass.h | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/New/Entity/AttachEffectClass.cpp b/src/New/Entity/AttachEffectClass.cpp index b0bd58bfa1..b7c98a6e94 100644 --- a/src/New/Entity/AttachEffectClass.cpp +++ b/src/New/Entity/AttachEffectClass.cpp @@ -635,7 +635,7 @@ bool AttachEffectClass::ShouldBeDiscardedNow() return true; } - if ((discardOn & DiscardCondition::DiscardAmmo) != DiscardCondition::None) + if ((discardOn & DiscardCondition::Ammo) != DiscardCondition::None) { bool trigger = false; if (pType->DiscardOn_Ammo_Min.isset() || pType->DiscardOn_Ammo_Max.isset()) @@ -654,7 +654,7 @@ bool AttachEffectClass::ShouldBeDiscardedNow() } } - if ((discardOn & DiscardCondition::DiscardHealth) != DiscardCondition::None) + if ((discardOn & DiscardCondition::Health) != DiscardCondition::None) { if (auto const pTypeData = pTechno->GetTechnoType()) { @@ -674,7 +674,7 @@ bool AttachEffectClass::ShouldBeDiscardedNow() } } - if ((discardOn & DiscardCondition::DiscardLandType) != DiscardCondition::None) + if ((discardOn & DiscardCondition::LandType) != DiscardCondition::None) { if (pType->DiscardOn_LandTypes != LandTypeFlags::None) { @@ -690,7 +690,7 @@ bool AttachEffectClass::ShouldBeDiscardedNow() } } - if ((discardOn & DiscardCondition::DiscardMission) != DiscardCondition::None) + if ((discardOn & DiscardCondition::Mission) != DiscardCondition::None) { if (pType->DiscardOn_Missions.size() > 0) { diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index 267328069d..458f3a29c0 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -367,19 +367,19 @@ namespace detail } else if (!_strcmpi(cur, "ammo")) { - parsed |= DiscardCondition::DiscardAmmo; + parsed |= DiscardCondition::Ammo; } else if (!_strcmpi(cur, "health")) { - parsed |= DiscardCondition::DiscardHealth; + parsed |= DiscardCondition::Health; } else if (!_strcmpi(cur, "mission")) { - parsed |= DiscardCondition::DiscardMission; + parsed |= DiscardCondition::Mission; } else if (!_strcmpi(cur, "landtype")) { - parsed |= DiscardCondition::DiscardLandType; + parsed |= DiscardCondition::LandType; } else { diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index aa140ceea5..6ed860b699 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -22,10 +22,10 @@ enum class DiscardCondition : unsigned short Undeploying = 0x100, Harvesting = 0x200, InvokerDie = 0x400, - DiscardAmmo = 0x800, - DiscardHealth = 0x1000, - DiscardMission = 0x2000, - DiscardLandType = 0x4000 + Ammo = 0x800, + Health = 0x1000, + Mission = 0x2000, + LandType = 0x4000 }; MAKE_ENUM_FLAGS(DiscardCondition); From 3fec2cd34a44211445f9e603a5d04fdd99fcd7ab Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Sun, 2 Aug 2026 14:47:22 +0800 Subject: [PATCH 05/12] Temporarily remove mission check --- CREDITS.md | 2 +- docs/New-or-Enhanced-Logics.md | 4 +--- docs/Whats-New.md | 2 +- src/New/Entity/AttachEffectClass.cpp | 12 ------------ src/New/Type/AttachEffectTypeClass.cpp | 6 ------ src/New/Type/AttachEffectTypeClass.h | 5 +---- 6 files changed, 4 insertions(+), 27 deletions(-) diff --git a/CREDITS.md b/CREDITS.md index 03946383dc..115f69bc41 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -728,7 +728,7 @@ This page lists all the individual contributions to the project by their author. - Fix the bug where landing direction cannot be correctly converted when set to a value exceeding 256 - Separately define the global default values of TerrainTypes' `IsPassable` and `CanBeBuiltOn` based on `SpawnsTiberium` - Remove the restriction that prohibits InfantryTypes from using the InitialPayload logic - - Add `ammo`, `health`, `mission` and `landtype` conditions to `DiscardOn` + - Add `ammo`, `health` and `landtype` conditions to `DiscardOn` - **Ollerus**: - Build limit group enhancement - Customizable rocker amplitude diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index c8cc8b0604..ae6f9e5079 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -26,7 +26,6 @@ This page describes all the engine features that are either new and introduced b - `invokerdie`: Discard when the invoker of the effect is destroyed. - `ammo`: Discard when the ammo of the object the effect is attached to is within the interval `[DiscardOn.Ammo.Min, DiscardOn.Ammo.Max]`. - `health`: Discard when the health ratio of the object the effect is attached to is within the interval `[DiscardOn.Health.Min, DiscardOn.Health.Max]`. - - `mission`: Discard when the current mission of the object the effect is attached to matches any one in the `DiscardOn.Missions` list. - `landtype`: Discard when the land type of the cell where the object the effect is attached to is currently located matches any land type in the `DiscardOn.LandTypes` list. - `DiscardOn.MoveBasedOnDestination` defines whether to determine the movement state according to the presence or absence of a destination. It treats Jumpjet units hovering in the air as movement, and units that have no destination but are turning as stationary. - If used for an AE that has `DiscardOn=harvesting`, in order for it to judge correctly, this should be set to `true`. @@ -116,12 +115,11 @@ 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|selling|undeploying|harvesting|invokerdie|ammo|health|mission|landtype) +DiscardOn=none ; List of discard condition enumeration (none|entry|move|stationary|drain|inrange|outofrange|selling|undeploying|harvesting|invokerdie|ammo|health|landtype) DiscardOn.Ammo.Min= ; integer DiscardOn.Ammo.Max= ; integer DiscardOn.Health.Min= ; integer DiscardOn.Health.Max= ; integer -DiscardOn.Missions= ; List of MissionType DiscardOn.LandTypes= ; List of LandTypes (none | clear | road | water | rock | wall | tiberium | beach | rough | ice | railroad | tunnel | weeds) DiscardOn.RangeOverride= ; floating point value, distance in cells DiscardOn.MoveBasedOnDestination= ; boolean, default to [General] -> DiscardOn.MoveBasedOnDestination diff --git a/docs/Whats-New.md b/docs/Whats-New.md index f2bf9ce02e..d24a66272f 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -646,7 +646,7 @@ HideShakeEffects=false ; boolean - Separately define the global default values of TerrainTypes' `IsPassable` and `CanBeBuiltOn` based on `SpawnsTiberium` (by Noble_Fish) - Customize reveal radius of `RevealToAll` (by NetsuNegi) - [Customize whether aircraft is a cargo plane](Fixed-or-Improved-Logics.md#customize-whether-aircraft-is-a-cargo-plane) (by TaranDahl) -- Add `ammo`, `health`, `mission` and `landtype` conditions to `DiscardOn` (by Noble_Fish) +- Add `ammo`, `health` and `landtype` conditions to `DiscardOn` (by Noble_Fish) #### 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/New/Entity/AttachEffectClass.cpp b/src/New/Entity/AttachEffectClass.cpp index b7c98a6e94..144790fcef 100644 --- a/src/New/Entity/AttachEffectClass.cpp +++ b/src/New/Entity/AttachEffectClass.cpp @@ -689,18 +689,6 @@ bool AttachEffectClass::ShouldBeDiscardedNow() } } } - - if ((discardOn & DiscardCondition::Mission) != DiscardCondition::None) - { - if (pType->DiscardOn_Missions.size() > 0) - { - if (pType->DiscardOn_Missions.Contains(pTechno->CurrentMission)) - { - this->LastDiscardCheckValue = true; - return true; - } - } - } if (pTechno->Target) { diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index 458f3a29c0..d8e718cf78 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -104,7 +104,6 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->DiscardOn_Ammo_Max.Read(exINI, pSection, "DiscardOn.Ammo.Max"); this->DiscardOn_Health_Min.Read(exINI, pSection, "DiscardOn.Health.Min"); this->DiscardOn_Health_Max.Read(exINI, pSection, "DiscardOn.Health.Max"); - this->DiscardOn_Missions.Read(exINI, pSection, "DiscardOn.Missions"); this->DiscardOn_LandTypes.Read(exINI, pSection, "DiscardOn.LandTypes"); this->DiscardOn_RangeOverride.Read(exINI, pSection, "DiscardOn.RangeOverride"); this->DiscardOn_MoveBasedOnDestination.Read(exINI, pSection, "DiscardOn.MoveBasedOnDestination"); @@ -228,7 +227,6 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->DiscardOn_Ammo_Max) .Process(this->DiscardOn_Health_Min) .Process(this->DiscardOn_Health_Max) - .Process(this->DiscardOn_Missions) .Process(this->DiscardOn_LandTypes) .Process(this->DiscardOn_RangeOverride) .Process(this->DiscardOn_MoveBasedOnDestination) @@ -373,10 +371,6 @@ namespace detail { parsed |= DiscardCondition::Health; } - else if (!_strcmpi(cur, "mission")) - { - parsed |= DiscardCondition::Mission; - } else if (!_strcmpi(cur, "landtype")) { parsed |= DiscardCondition::LandType; diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index 6ed860b699..f839782554 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -24,8 +24,7 @@ enum class DiscardCondition : unsigned short InvokerDie = 0x400, Ammo = 0x800, Health = 0x1000, - Mission = 0x2000, - LandType = 0x4000 + LandType = 0x2000 }; MAKE_ENUM_FLAGS(DiscardCondition); @@ -60,7 +59,6 @@ class AttachEffectTypeClass final : public Enumerable Nullable DiscardOn_Ammo_Max; Nullable DiscardOn_Health_Min; Nullable DiscardOn_Health_Max; - ValueableVector DiscardOn_Missions; Valueable DiscardOn_LandTypes; Nullable DiscardOn_RangeOverride; Nullable DiscardOn_MoveBasedOnDestination; @@ -136,7 +134,6 @@ class AttachEffectTypeClass final : public Enumerable , DiscardOn_Ammo_Max {} , DiscardOn_Health_Min {} , DiscardOn_Health_Max {} - , DiscardOn_Missions {} , DiscardOn_LandTypes { LandTypeFlags::None } , DiscardOn_RangeOverride {} , DiscardOn_MoveBasedOnDestination {} From a01e44520163c80dad304bf61cfa2abb02510ed0 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Sun, 2 Aug 2026 20:48:31 +0800 Subject: [PATCH 06/12] Revert "Temporarily remove mission check" This reverts commit 3fec2cd34a44211445f9e603a5d04fdd99fcd7ab. --- CREDITS.md | 2 +- docs/New-or-Enhanced-Logics.md | 4 +++- docs/Whats-New.md | 2 +- src/New/Entity/AttachEffectClass.cpp | 12 ++++++++++++ src/New/Type/AttachEffectTypeClass.cpp | 6 ++++++ src/New/Type/AttachEffectTypeClass.h | 5 ++++- 6 files changed, 27 insertions(+), 4 deletions(-) diff --git a/CREDITS.md b/CREDITS.md index 115f69bc41..03946383dc 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -728,7 +728,7 @@ This page lists all the individual contributions to the project by their author. - Fix the bug where landing direction cannot be correctly converted when set to a value exceeding 256 - Separately define the global default values of TerrainTypes' `IsPassable` and `CanBeBuiltOn` based on `SpawnsTiberium` - Remove the restriction that prohibits InfantryTypes from using the InitialPayload logic - - Add `ammo`, `health` and `landtype` conditions to `DiscardOn` + - Add `ammo`, `health`, `mission` and `landtype` conditions to `DiscardOn` - **Ollerus**: - Build limit group enhancement - Customizable rocker amplitude diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index ae6f9e5079..c8cc8b0604 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -26,6 +26,7 @@ This page describes all the engine features that are either new and introduced b - `invokerdie`: Discard when the invoker of the effect is destroyed. - `ammo`: Discard when the ammo of the object the effect is attached to is within the interval `[DiscardOn.Ammo.Min, DiscardOn.Ammo.Max]`. - `health`: Discard when the health ratio of the object the effect is attached to is within the interval `[DiscardOn.Health.Min, DiscardOn.Health.Max]`. + - `mission`: Discard when the current mission of the object the effect is attached to matches any one in the `DiscardOn.Missions` list. - `landtype`: Discard when the land type of the cell where the object the effect is attached to is currently located matches any land type in the `DiscardOn.LandTypes` list. - `DiscardOn.MoveBasedOnDestination` defines whether to determine the movement state according to the presence or absence of a destination. It treats Jumpjet units hovering in the air as movement, and units that have no destination but are turning as stationary. - If used for an AE that has `DiscardOn=harvesting`, in order for it to judge correctly, this should be set to `true`. @@ -115,11 +116,12 @@ 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|selling|undeploying|harvesting|invokerdie|ammo|health|landtype) +DiscardOn=none ; List of discard condition enumeration (none|entry|move|stationary|drain|inrange|outofrange|selling|undeploying|harvesting|invokerdie|ammo|health|mission|landtype) DiscardOn.Ammo.Min= ; integer DiscardOn.Ammo.Max= ; integer DiscardOn.Health.Min= ; integer DiscardOn.Health.Max= ; integer +DiscardOn.Missions= ; List of MissionType DiscardOn.LandTypes= ; List of LandTypes (none | clear | road | water | rock | wall | tiberium | beach | rough | ice | railroad | tunnel | weeds) DiscardOn.RangeOverride= ; floating point value, distance in cells DiscardOn.MoveBasedOnDestination= ; boolean, default to [General] -> DiscardOn.MoveBasedOnDestination diff --git a/docs/Whats-New.md b/docs/Whats-New.md index d24a66272f..f2bf9ce02e 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -646,7 +646,7 @@ HideShakeEffects=false ; boolean - Separately define the global default values of TerrainTypes' `IsPassable` and `CanBeBuiltOn` based on `SpawnsTiberium` (by Noble_Fish) - Customize reveal radius of `RevealToAll` (by NetsuNegi) - [Customize whether aircraft is a cargo plane](Fixed-or-Improved-Logics.md#customize-whether-aircraft-is-a-cargo-plane) (by TaranDahl) -- Add `ammo`, `health` and `landtype` conditions to `DiscardOn` (by Noble_Fish) +- Add `ammo`, `health`, `mission` and `landtype` conditions to `DiscardOn` (by Noble_Fish) #### 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/New/Entity/AttachEffectClass.cpp b/src/New/Entity/AttachEffectClass.cpp index 144790fcef..b7c98a6e94 100644 --- a/src/New/Entity/AttachEffectClass.cpp +++ b/src/New/Entity/AttachEffectClass.cpp @@ -689,6 +689,18 @@ bool AttachEffectClass::ShouldBeDiscardedNow() } } } + + if ((discardOn & DiscardCondition::Mission) != DiscardCondition::None) + { + if (pType->DiscardOn_Missions.size() > 0) + { + if (pType->DiscardOn_Missions.Contains(pTechno->CurrentMission)) + { + this->LastDiscardCheckValue = true; + return true; + } + } + } if (pTechno->Target) { diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index d8e718cf78..458f3a29c0 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -104,6 +104,7 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->DiscardOn_Ammo_Max.Read(exINI, pSection, "DiscardOn.Ammo.Max"); this->DiscardOn_Health_Min.Read(exINI, pSection, "DiscardOn.Health.Min"); this->DiscardOn_Health_Max.Read(exINI, pSection, "DiscardOn.Health.Max"); + this->DiscardOn_Missions.Read(exINI, pSection, "DiscardOn.Missions"); this->DiscardOn_LandTypes.Read(exINI, pSection, "DiscardOn.LandTypes"); this->DiscardOn_RangeOverride.Read(exINI, pSection, "DiscardOn.RangeOverride"); this->DiscardOn_MoveBasedOnDestination.Read(exINI, pSection, "DiscardOn.MoveBasedOnDestination"); @@ -227,6 +228,7 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->DiscardOn_Ammo_Max) .Process(this->DiscardOn_Health_Min) .Process(this->DiscardOn_Health_Max) + .Process(this->DiscardOn_Missions) .Process(this->DiscardOn_LandTypes) .Process(this->DiscardOn_RangeOverride) .Process(this->DiscardOn_MoveBasedOnDestination) @@ -371,6 +373,10 @@ namespace detail { parsed |= DiscardCondition::Health; } + else if (!_strcmpi(cur, "mission")) + { + parsed |= DiscardCondition::Mission; + } else if (!_strcmpi(cur, "landtype")) { parsed |= DiscardCondition::LandType; diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index f839782554..6ed860b699 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -24,7 +24,8 @@ enum class DiscardCondition : unsigned short InvokerDie = 0x400, Ammo = 0x800, Health = 0x1000, - LandType = 0x2000 + Mission = 0x2000, + LandType = 0x4000 }; MAKE_ENUM_FLAGS(DiscardCondition); @@ -59,6 +60,7 @@ class AttachEffectTypeClass final : public Enumerable Nullable DiscardOn_Ammo_Max; Nullable DiscardOn_Health_Min; Nullable DiscardOn_Health_Max; + ValueableVector DiscardOn_Missions; Valueable DiscardOn_LandTypes; Nullable DiscardOn_RangeOverride; Nullable DiscardOn_MoveBasedOnDestination; @@ -134,6 +136,7 @@ class AttachEffectTypeClass final : public Enumerable , DiscardOn_Ammo_Max {} , DiscardOn_Health_Min {} , DiscardOn_Health_Max {} + , DiscardOn_Missions {} , DiscardOn_LandTypes { LandTypeFlags::None } , DiscardOn_RangeOverride {} , DiscardOn_MoveBasedOnDestination {} From 6fc348cd86de0ae88c881d6a86c69dba3f6c55e2 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Sun, 2 Aug 2026 21:01:26 +0800 Subject: [PATCH 07/12] `DiscardOn.AIMissions` --- docs/New-or-Enhanced-Logics.md | 5 +-- src/New/Entity/AttachEffectClass.cpp | 15 +++++---- src/New/Type/AttachEffectTypeClass.cpp | 2 ++ src/New/Type/AttachEffectTypeClass.h | 2 ++ src/Utilities/TemplateDef.h | 44 ++++++++++++++++++++++++++ 5 files changed, 60 insertions(+), 8 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index c8cc8b0604..4dfa91aa7f 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -26,7 +26,7 @@ This page describes all the engine features that are either new and introduced b - `invokerdie`: Discard when the invoker of the effect is destroyed. - `ammo`: Discard when the ammo of the object the effect is attached to is within the interval `[DiscardOn.Ammo.Min, DiscardOn.Ammo.Max]`. - `health`: Discard when the health ratio of the object the effect is attached to is within the interval `[DiscardOn.Health.Min, DiscardOn.Health.Max]`. - - `mission`: Discard when the current mission of the object the effect is attached to matches any one in the `DiscardOn.Missions` list. + - `mission`: Discard when the current mission of the object the effect is attached to matches any one in the `DiscardOn.Missions` list (or `DiscardOn.AIMissions` for AI-controlled objects, if set). - `landtype`: Discard when the land type of the cell where the object the effect is attached to is currently located matches any land type in the `DiscardOn.LandTypes` list. - `DiscardOn.MoveBasedOnDestination` defines whether to determine the movement state according to the presence or absence of a destination. It treats Jumpjet units hovering in the air as movement, and units that have no destination but are turning as stationary. - If used for an AE that has `DiscardOn=harvesting`, in order for it to judge correctly, this should be set to `true`. @@ -121,7 +121,8 @@ DiscardOn.Ammo.Min= ; integer DiscardOn.Ammo.Max= ; integer DiscardOn.Health.Min= ; integer DiscardOn.Health.Max= ; integer -DiscardOn.Missions= ; List of MissionType +DiscardOn.Missions= ; List of MissionTypes +DiscardOn.AIMissions= ; List of MissionTypes, default to [AttachEffectType] -> DiscardOn.Missions DiscardOn.LandTypes= ; List of LandTypes (none | clear | road | water | rock | wall | tiberium | beach | rough | ice | railroad | tunnel | weeds) DiscardOn.RangeOverride= ; floating point value, distance in cells DiscardOn.MoveBasedOnDestination= ; boolean, default to [General] -> DiscardOn.MoveBasedOnDestination diff --git a/src/New/Entity/AttachEffectClass.cpp b/src/New/Entity/AttachEffectClass.cpp index b7c98a6e94..ac7b4ccd4d 100644 --- a/src/New/Entity/AttachEffectClass.cpp +++ b/src/New/Entity/AttachEffectClass.cpp @@ -692,13 +692,16 @@ bool AttachEffectClass::ShouldBeDiscardedNow() if ((discardOn & DiscardCondition::Mission) != DiscardCondition::None) { - if (pType->DiscardOn_Missions.size() > 0) + auto const& missions = pTechno->Owner->IsControlledByHuman() + ? pType->DiscardOn_Missions + : (pType->DiscardOn_AIMissions.HasValue() + ? static_cast&>(pType->DiscardOn_AIMissions) + : pType->DiscardOn_Missions); + + if (missions.size() > 0 && missions.Contains(pTechno->CurrentMission)) { - if (pType->DiscardOn_Missions.Contains(pTechno->CurrentMission)) - { - this->LastDiscardCheckValue = true; - return true; - } + this->LastDiscardCheckValue = true; + return true; } } diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index 458f3a29c0..78ba4a1669 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -105,6 +105,7 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->DiscardOn_Health_Min.Read(exINI, pSection, "DiscardOn.Health.Min"); this->DiscardOn_Health_Max.Read(exINI, pSection, "DiscardOn.Health.Max"); this->DiscardOn_Missions.Read(exINI, pSection, "DiscardOn.Missions"); + this->DiscardOn_AIMissions.Read(exINI, pSection, "DiscardOn.AIMissions"); this->DiscardOn_LandTypes.Read(exINI, pSection, "DiscardOn.LandTypes"); this->DiscardOn_RangeOverride.Read(exINI, pSection, "DiscardOn.RangeOverride"); this->DiscardOn_MoveBasedOnDestination.Read(exINI, pSection, "DiscardOn.MoveBasedOnDestination"); @@ -229,6 +230,7 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->DiscardOn_Health_Min) .Process(this->DiscardOn_Health_Max) .Process(this->DiscardOn_Missions) + .Process(this->DiscardOn_AIMissions) .Process(this->DiscardOn_LandTypes) .Process(this->DiscardOn_RangeOverride) .Process(this->DiscardOn_MoveBasedOnDestination) diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index 6ed860b699..457a250936 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -61,6 +61,7 @@ class AttachEffectTypeClass final : public Enumerable Nullable DiscardOn_Health_Min; Nullable DiscardOn_Health_Max; ValueableVector DiscardOn_Missions; + NullableVector DiscardOn_AIMissions; Valueable DiscardOn_LandTypes; Nullable DiscardOn_RangeOverride; Nullable DiscardOn_MoveBasedOnDestination; @@ -137,6 +138,7 @@ class AttachEffectTypeClass final : public Enumerable , DiscardOn_Health_Min {} , DiscardOn_Health_Max {} , DiscardOn_Missions {} + , DiscardOn_AIMissions {} , DiscardOn_LandTypes { LandTypeFlags::None } , DiscardOn_RangeOverride {} , DiscardOn_MoveBasedOnDestination {} diff --git a/src/Utilities/TemplateDef.h b/src/Utilities/TemplateDef.h index a98518648d..34c61e781a 100644 --- a/src/Utilities/TemplateDef.h +++ b/src/Utilities/TemplateDef.h @@ -1867,6 +1867,25 @@ inline void ValueableVector::Read(INI_EX& parser, const char* } } +template <> +inline void ValueableVector::Read(INI_EX& parser, const char* pSection, const char* pKey) +{ + if (parser.ReadString(pSection, pKey)) + { + this->clear(); + char* str = parser.value(); + char* context = nullptr; + for (char* cur = strtok_s(str, Phobos::readDelims, &context); cur; cur = strtok_s(nullptr, Phobos::readDelims, &context)) + { + auto mission = MissionControlClass::FindIndex(cur); + if (mission != Mission::None) + this->push_back(mission); + else if (!INIClass::IsBlank(cur)) + Debug::INIParseFailed(pSection, pKey, cur, "Invalid Mission name"); + } + } +} + template bool ValueableVector::Load(PhobosStreamReader& Stm, bool RegisterForChange) { @@ -1969,6 +1988,31 @@ void __declspec(noinline) NullableVector::Read(INI_EX& parser, const char* pS } } +template <> +inline void NullableVector::Read(INI_EX& parser, const char* pSection, const char* pKey) +{ + if (parser.ReadString(pSection, pKey)) + { + this->clear(); + auto const non_default = _strcmpi(parser.value(), ""); + this->hasValue = non_default; + + if (non_default) + { + char* str = parser.value(); + char* context = nullptr; + for (char* cur = strtok_s(str, Phobos::readDelims, &context); cur; cur = strtok_s(nullptr, Phobos::readDelims, &context)) + { + auto mission = MissionControlClass::FindIndex(cur); + if (mission != Mission::None) + this->push_back(mission); + else if (!INIClass::IsBlank(cur)) + Debug::INIParseFailed(pSection, pKey, cur, "Invalid Mission name"); + } + } + } +} + template bool NullableVector::Load(PhobosStreamReader& Stm, bool RegisterForChange) { From 78a73950dc516ee449ab5aa46c1a0bc9947b229a Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Sun, 2 Aug 2026 21:46:54 +0800 Subject: [PATCH 08/12] `DiscardOn.Sequences` --- CREDITS.md | 2 +- docs/New-or-Enhanced-Logics.md | 4 +- docs/Whats-New.md | 2 +- src/New/Entity/AttachEffectClass.cpp | 12 +++++ src/New/Type/AttachEffectTypeClass.cpp | 6 +++ src/New/Type/AttachEffectTypeClass.h | 5 ++- src/Utilities/TemplateDef.h | 61 ++++++++++++++++++++++++++ 7 files changed, 88 insertions(+), 4 deletions(-) diff --git a/CREDITS.md b/CREDITS.md index 03946383dc..6c0b5aff8a 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -728,7 +728,7 @@ This page lists all the individual contributions to the project by their author. - Fix the bug where landing direction cannot be correctly converted when set to a value exceeding 256 - Separately define the global default values of TerrainTypes' `IsPassable` and `CanBeBuiltOn` based on `SpawnsTiberium` - Remove the restriction that prohibits InfantryTypes from using the InitialPayload logic - - Add `ammo`, `health`, `mission` and `landtype` conditions to `DiscardOn` + - Add `ammo`, `health`, `mission`, `landtype` and `sequence` conditions to `DiscardOn` - **Ollerus**: - Build limit group enhancement - Customizable rocker amplitude diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 4dfa91aa7f..8d6adbfa86 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -28,6 +28,7 @@ This page describes all the engine features that are either new and introduced b - `health`: Discard when the health ratio of the object the effect is attached to is within the interval `[DiscardOn.Health.Min, DiscardOn.Health.Max]`. - `mission`: Discard when the current mission of the object the effect is attached to matches any one in the `DiscardOn.Missions` list (or `DiscardOn.AIMissions` for AI-controlled objects, if set). - `landtype`: Discard when the land type of the cell where the object the effect is attached to is currently located matches any land type in the `DiscardOn.LandTypes` list. + - `sequence`: Discard when the infantry to which the effect is attached is playing a sequence that matches any one in the `DiscardOn.Sequences` list. - `DiscardOn.MoveBasedOnDestination` defines whether to determine the movement state according to the presence or absence of a destination. It treats Jumpjet units hovering in the air as movement, and units that have no destination but are turning as stationary. - If used for an AE that has `DiscardOn=harvesting`, in order for it to judge correctly, this should be set to `true`. - `DiscardOn.ConsiderHarvestingAsStationary` defines whether to treat `harvesting` as `stationary`. When this flag is set to `false`, `DiscardOn=harvesting` can be used and it will not be considered `stationary` while `harvesting`. @@ -116,7 +117,7 @@ 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|selling|undeploying|harvesting|invokerdie|ammo|health|mission|landtype) +DiscardOn=none ; List of discard condition enumeration (none|entry|move|stationary|drain|inrange|outofrange|selling|undeploying|harvesting|invokerdie|ammo|health|mission|landtype|sequence) DiscardOn.Ammo.Min= ; integer DiscardOn.Ammo.Max= ; integer DiscardOn.Health.Min= ; integer @@ -124,6 +125,7 @@ DiscardOn.Health.Max= ; integer DiscardOn.Missions= ; List of MissionTypes DiscardOn.AIMissions= ; List of MissionTypes, default to [AttachEffectType] -> DiscardOn.Missions DiscardOn.LandTypes= ; List of LandTypes (none | clear | road | water | rock | wall | tiberium | beach | rough | ice | railroad | tunnel | weeds) +DiscardOn.Sequences= ; List of Sequences (ready | guard | prone | walk | fireup | fireprone | secondaryfire | secondaryprone | down | crawl | up | idle1 | idle2 | die1 | die2 | die3 | die4 | die5 | deploy | deployed | deployedfire | deployedidle | undeploy | paradrop | cheer | panic | shovel | carry | fly | hover | firefly | tumble | airdeathstart | airdeathfalling | airdeathfinish | tread | swim | wetattack | wetidle1 | wetidle2 | wetdie1 | wetdie2) DiscardOn.RangeOverride= ; floating point value, distance in cells DiscardOn.MoveBasedOnDestination= ; boolean, default to [General] -> DiscardOn.MoveBasedOnDestination DiscardOn.ConsiderHarvestingAsStationary= ; boolean, default to [General] -> DiscardOn.ConsiderHarvestingAsStationary diff --git a/docs/Whats-New.md b/docs/Whats-New.md index f2bf9ce02e..cfdb36439c 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -646,7 +646,7 @@ HideShakeEffects=false ; boolean - Separately define the global default values of TerrainTypes' `IsPassable` and `CanBeBuiltOn` based on `SpawnsTiberium` (by Noble_Fish) - Customize reveal radius of `RevealToAll` (by NetsuNegi) - [Customize whether aircraft is a cargo plane](Fixed-or-Improved-Logics.md#customize-whether-aircraft-is-a-cargo-plane) (by TaranDahl) -- Add `ammo`, `health`, `mission` and `landtype` conditions to `DiscardOn` (by Noble_Fish) +- Add `ammo`, `health`, `mission`, `landtype` and `sequence` conditions to `DiscardOn` (by Noble_Fish) #### 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/New/Entity/AttachEffectClass.cpp b/src/New/Entity/AttachEffectClass.cpp index ac7b4ccd4d..1e6ec43c4c 100644 --- a/src/New/Entity/AttachEffectClass.cpp +++ b/src/New/Entity/AttachEffectClass.cpp @@ -705,6 +705,18 @@ bool AttachEffectClass::ShouldBeDiscardedNow() } } + if ((discardOn & DiscardCondition::Sequence) != DiscardCondition::None) + { + if (auto const pInf = abstract_cast(pTechno)) + { + if (pType->DiscardOn_Sequences.size() > 0 && pType->DiscardOn_Sequences.Contains(pInf->SequenceAnim)) + { + this->LastDiscardCheckValue = true; + return true; + } + } + } + if (pTechno->Target) { const bool inRange = (discardOn & DiscardCondition::InRange) != DiscardCondition::None; diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index 78ba4a1669..13e9a50cae 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -107,6 +107,7 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->DiscardOn_Missions.Read(exINI, pSection, "DiscardOn.Missions"); this->DiscardOn_AIMissions.Read(exINI, pSection, "DiscardOn.AIMissions"); this->DiscardOn_LandTypes.Read(exINI, pSection, "DiscardOn.LandTypes"); + this->DiscardOn_Sequences.Read(exINI, pSection, "DiscardOn.Sequences"); this->DiscardOn_RangeOverride.Read(exINI, pSection, "DiscardOn.RangeOverride"); this->DiscardOn_MoveBasedOnDestination.Read(exINI, pSection, "DiscardOn.MoveBasedOnDestination"); this->DiscardOn_ConsiderHarvestingAsStationary.Read(exINI, pSection, "DiscardOn.ConsiderHarvestingAsStationary"); @@ -232,6 +233,7 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->DiscardOn_Missions) .Process(this->DiscardOn_AIMissions) .Process(this->DiscardOn_LandTypes) + .Process(this->DiscardOn_Sequences) .Process(this->DiscardOn_RangeOverride) .Process(this->DiscardOn_MoveBasedOnDestination) .Process(this->DiscardOn_ConsiderHarvestingAsStationary) @@ -383,6 +385,10 @@ namespace detail { parsed |= DiscardCondition::LandType; } + else if (!_strcmpi(cur, "sequence")) + { + parsed |= DiscardCondition::Sequence; + } else { Debug::INIParseFailed(pSection, pKey, cur, "Expected a discard condition type"); diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index 457a250936..13ced1d6fd 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -25,7 +25,8 @@ enum class DiscardCondition : unsigned short Ammo = 0x800, Health = 0x1000, Mission = 0x2000, - LandType = 0x4000 + LandType = 0x4000, + Sequence = 0x8000 }; MAKE_ENUM_FLAGS(DiscardCondition); @@ -63,6 +64,7 @@ class AttachEffectTypeClass final : public Enumerable ValueableVector DiscardOn_Missions; NullableVector DiscardOn_AIMissions; Valueable DiscardOn_LandTypes; + ValueableVector DiscardOn_Sequences; Nullable DiscardOn_RangeOverride; Nullable DiscardOn_MoveBasedOnDestination; Nullable DiscardOn_ConsiderHarvestingAsStationary; @@ -140,6 +142,7 @@ class AttachEffectTypeClass final : public Enumerable , DiscardOn_Missions {} , DiscardOn_AIMissions {} , DiscardOn_LandTypes { LandTypeFlags::None } + , DiscardOn_Sequences {} , DiscardOn_RangeOverride {} , DiscardOn_MoveBasedOnDestination {} , DiscardOn_ConsiderHarvestingAsStationary {} diff --git a/src/Utilities/TemplateDef.h b/src/Utilities/TemplateDef.h index 34c61e781a..aae4ce2b37 100644 --- a/src/Utilities/TemplateDef.h +++ b/src/Utilities/TemplateDef.h @@ -560,6 +560,48 @@ namespace detail return false; } + inline bool parse_sequence(const char* str, Sequence& seq) + { + static const auto Sequences = { + "Ready", "Guard", "Prone", "Walk", "FireUp", "Down", "Crawl", "Up", + "FireProne", "Idle1", "Idle2", "Die1", "Die2", "Die3", "Die4", "Die5", + "Tread", "Swim", "WetIdle1", "WetIdle2", "WetDie1", "WetDie2", "WetAttack", + "Hover", "Fly", "Tumble", "FireFly", "Deploy", "Deployed", "DeployedFire", + "DeployedIdle", "Undeploy", "Cheer", "Paradrop", "AirDeathStart", + "AirDeathFalling", "AirDeathFinish", "Panic", "Shovel", "Carry", + "SecondaryFire", "SecondaryProne" + }; + auto it = Sequences.begin(); + for (auto i = 0u; i < Sequences.size(); ++i) + { + if (_strcmpi(str, *it++) == 0) + { + seq = static_cast(i); + return true; + } + } + return false; + } + + template <> + inline bool read(Sequence& value, INI_EX& parser, const char* pSection, const char* pKey) + { + if (parser.ReadString(pSection, pKey)) + { + Sequence seq; + if (detail::parse_sequence(parser.value(), seq)) + { + value = seq; + return true; + } + else if (!parser.empty()) + { + Debug::INIParseFailed(pSection, pKey, parser.value(), "Expected a Sequence animation name"); + } + } + return false; + } + template <> inline bool read(DirType& value, INI_EX& parser, const char* pSection, const char* pKey) { @@ -1886,6 +1928,25 @@ inline void ValueableVector::Read(INI_EX& parser, const char* pSection, } } +template <> +inline void ValueableVector::Read(INI_EX& parser, const char* pSection, const char* pKey) +{ + if (parser.ReadString(pSection, pKey)) + { + this->clear(); + char* str = parser.value(); + char* context = nullptr; + for (char* cur = strtok_s(str, Phobos::readDelims, &context); cur; cur = strtok_s(nullptr, Phobos::readDelims, &context)) + { + Sequence seq; + if (detail::parse_sequence(cur, seq)) + this->push_back(seq); + else if (!INIClass::IsBlank(cur)) + Debug::INIParseFailed(pSection, pKey, cur, "Invalid Sequence name"); + } + } +} + template bool ValueableVector::Load(PhobosStreamReader& Stm, bool RegisterForChange) { From 868d2f857f38d780a05f7b03e6d156f1e64f2552 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Sun, 2 Aug 2026 22:20:07 +0800 Subject: [PATCH 09/12] Optimization --- src/New/Entity/AttachEffectClass.cpp | 16 +++++----------- src/New/Type/AttachEffectTypeClass.h | 8 ++++---- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/New/Entity/AttachEffectClass.cpp b/src/New/Entity/AttachEffectClass.cpp index 1e6ec43c4c..c2945ea053 100644 --- a/src/New/Entity/AttachEffectClass.cpp +++ b/src/New/Entity/AttachEffectClass.cpp @@ -637,17 +637,11 @@ bool AttachEffectClass::ShouldBeDiscardedNow() if ((discardOn & DiscardCondition::Ammo) != DiscardCondition::None) { - bool trigger = false; - if (pType->DiscardOn_Ammo_Min.isset() || pType->DiscardOn_Ammo_Max.isset()) - { - const int min = pType->DiscardOn_Ammo_Min.Get(-1); - const int max = pType->DiscardOn_Ammo_Max.Get(-1); - const int ammo = pTechno->Ammo; - - trigger = (min < 0 || ammo >= min) && (max < 0 || ammo <= max); - } + const int min = pType->DiscardOn_Ammo_Min; + const int max = pType->DiscardOn_Ammo_Max; + const int ammo = pTechno->Ammo; - if (trigger) + if ((min < 0 || ammo >= min) && (max < 0 || ammo <= max)) { this->LastDiscardCheckValue = true; return true; @@ -665,7 +659,7 @@ bool AttachEffectClass::ShouldBeDiscardedNow() const double min = pType->DiscardOn_Health_Min.Get(0.0); const double max = pType->DiscardOn_Health_Max.Get(1.0); - if ((hp > 0.0 ? hp > min : hp >= min) && hp <= max) + if (hp >= min && hp <= max) { this->LastDiscardCheckValue = true; return true; diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index 13ced1d6fd..fb82cb4332 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -57,8 +57,8 @@ class AttachEffectTypeClass final : public Enumerable Valueable Cumulative_MaxCount; Valueable Powered; Valueable DiscardOn; - Nullable DiscardOn_Ammo_Min; - Nullable DiscardOn_Ammo_Max; + Valueable DiscardOn_Ammo_Min; + Valueable DiscardOn_Ammo_Max; Nullable DiscardOn_Health_Min; Nullable DiscardOn_Health_Max; ValueableVector DiscardOn_Missions; @@ -135,8 +135,8 @@ class AttachEffectTypeClass final : public Enumerable , Cumulative_MaxCount { -1 } , Powered { false } , DiscardOn { DiscardCondition::None } - , DiscardOn_Ammo_Min {} - , DiscardOn_Ammo_Max {} + , DiscardOn_Ammo_Min { -1 } + , DiscardOn_Ammo_Max { -1 } , DiscardOn_Health_Min {} , DiscardOn_Health_Max {} , DiscardOn_Missions {} From 984b26f07c2957e469854e9650450b62b3b12587 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Sun, 2 Aug 2026 23:51:20 +0800 Subject: [PATCH 10/12] =?UTF-8?q?HP.Min/Max=E2=86=92Above/Below;=20Ammo.Mi?= =?UTF-8?q?n/Max=20rename?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/New-or-Enhanced-Logics.md | 12 ++++++------ src/New/Entity/AttachEffectClass.cpp | 20 +++++++------------- src/New/Type/AttachEffectTypeClass.cpp | 24 ++++++++++++++++-------- src/New/Type/AttachEffectTypeClass.h | 16 ++++++++-------- 4 files changed, 37 insertions(+), 35 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 8d6adbfa86..e0d3eb3cba 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -24,8 +24,8 @@ This page describes all the engine features that are either new and introduced b - `undeploying`: Discard when the building to which the effect is attached performs undeploy. - `harvesting`: Discard when the object the effect is attached is harvesting ore. This can only be used when `DiscardOn.ConsiderHarvestingAsStationary=false`. - `invokerdie`: Discard when the invoker of the effect is destroyed. - - `ammo`: Discard when the ammo of the object the effect is attached to is within the interval `[DiscardOn.Ammo.Min, DiscardOn.Ammo.Max]`. - - `health`: Discard when the health ratio of the object the effect is attached to is within the interval `[DiscardOn.Health.Min, DiscardOn.Health.Max]`. + - `ammo`: Discard when the ammo of the object the effect is attached to is within the interval `[DiscardOn.Ammo.MinimumAmount, DiscardOn.Ammo.MaximumAmount]` (set to `-1` to ignore either bound). + - `health`: Discard when the health percentage of the object the effect is attached to is within the interval `(DiscardOn.Health.AbovePercent, DiscardOn.Health.BelowPercent]` (set to `-1` to ignore either bound). - `mission`: Discard when the current mission of the object the effect is attached to matches any one in the `DiscardOn.Missions` list (or `DiscardOn.AIMissions` for AI-controlled objects, if set). - `landtype`: Discard when the land type of the cell where the object the effect is attached to is currently located matches any land type in the `DiscardOn.LandTypes` list. - `sequence`: Discard when the infantry to which the effect is attached is playing a sequence that matches any one in the `DiscardOn.Sequences` list. @@ -118,10 +118,10 @@ Cumulative=false ; boolean Cumulative.MaxCount=-1 ; integer Powered=false ; boolean DiscardOn=none ; List of discard condition enumeration (none|entry|move|stationary|drain|inrange|outofrange|selling|undeploying|harvesting|invokerdie|ammo|health|mission|landtype|sequence) -DiscardOn.Ammo.Min= ; integer -DiscardOn.Ammo.Max= ; integer -DiscardOn.Health.Min= ; integer -DiscardOn.Health.Max= ; integer +DiscardOn.Ammo.MinimumAmount= ; integer +DiscardOn.Ammo.MaximumAmount= ; integer +DiscardOn.Health.BelowPercent= ; floating point value +DiscardOn.Health.AbovePercent= ; floating point value DiscardOn.Missions= ; List of MissionTypes DiscardOn.AIMissions= ; List of MissionTypes, default to [AttachEffectType] -> DiscardOn.Missions DiscardOn.LandTypes= ; List of LandTypes (none | clear | road | water | rock | wall | tiberium | beach | rough | ice | railroad | tunnel | weeds) diff --git a/src/New/Entity/AttachEffectClass.cpp b/src/New/Entity/AttachEffectClass.cpp index c2945ea053..4d8686fc6d 100644 --- a/src/New/Entity/AttachEffectClass.cpp +++ b/src/New/Entity/AttachEffectClass.cpp @@ -637,8 +637,8 @@ bool AttachEffectClass::ShouldBeDiscardedNow() if ((discardOn & DiscardCondition::Ammo) != DiscardCondition::None) { - const int min = pType->DiscardOn_Ammo_Min; - const int max = pType->DiscardOn_Ammo_Max; + const int min = pType->DiscardOn_Ammo_MinimumAmount; + const int max = pType->DiscardOn_Ammo_MaximumAmount; const int ammo = pTechno->Ammo; if ((min < 0 || ammo >= min) && (max < 0 || ammo <= max)) @@ -652,18 +652,12 @@ bool AttachEffectClass::ShouldBeDiscardedNow() { if (auto const pTypeData = pTechno->GetTechnoType()) { - const double hp = pTechno->GetHealthPercentage(); - - if (pType->DiscardOn_Health_Min.isset() || pType->DiscardOn_Health_Max.isset()) + const double min = pType->DiscardOn_Health_AbovePercent; + const double max = pType->DiscardOn_Health_BelowPercent; + if (TechnoExt::IsHealthInThreshold(pTechno, min, max)) { - const double min = pType->DiscardOn_Health_Min.Get(0.0); - const double max = pType->DiscardOn_Health_Max.Get(1.0); - - if (hp >= min && hp <= max) - { - this->LastDiscardCheckValue = true; - return true; - } + this->LastDiscardCheckValue = true; + return true; } } } diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index 13e9a50cae..ef97667569 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -100,10 +100,18 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->Cumulative_MaxCount.Read(exINI, pSection, "Cumulative.MaxCount"); this->Powered.Read(exINI, pSection, "Powered"); this->DiscardOn.Read(exINI, pSection, "DiscardOn"); - this->DiscardOn_Ammo_Min.Read(exINI, pSection, "DiscardOn.Ammo.Min"); - this->DiscardOn_Ammo_Max.Read(exINI, pSection, "DiscardOn.Ammo.Max"); - this->DiscardOn_Health_Min.Read(exINI, pSection, "DiscardOn.Health.Min"); - this->DiscardOn_Health_Max.Read(exINI, pSection, "DiscardOn.Health.Max"); + this->DiscardOn_Ammo_MinimumAmount.Read(exINI, pSection, "DiscardOn.Ammo.MinimumAmount"); + this->DiscardOn_Ammo_MaximumAmount.Read(exINI, pSection, "DiscardOn.Ammo.MaximumAmount"); + + if (this->DiscardOn_Ammo_MinimumAmount > this->DiscardOn_Ammo_MaximumAmount) + Debug::Log("[Developer warning][%s] DiscardOn.Ammo.MinimumAmount is greater than DiscardOn.Ammo.MaximumAmount, the ammo discard condition cannot be established.\n", pSection); + + this->DiscardOn_Health_BelowPercent.Read(exINI, pSection, "DiscardOn.Health.BelowPercent"); + this->DiscardOn_Health_AbovePercent.Read(exINI, pSection, "DiscardOn.Health.AbovePercent"); + + if (this->DiscardOn_Health_AbovePercent > this->DiscardOn_Health_BelowPercent) + Debug::Log("[Developer warning][%s] DiscardOn.Health.AbovePercent is greater than DiscardOn.Health.BelowPercent, the health discard condition cannot be established.\n", pSection); + this->DiscardOn_Missions.Read(exINI, pSection, "DiscardOn.Missions"); this->DiscardOn_AIMissions.Read(exINI, pSection, "DiscardOn.AIMissions"); this->DiscardOn_LandTypes.Read(exINI, pSection, "DiscardOn.LandTypes"); @@ -226,10 +234,10 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->Cumulative_MaxCount) .Process(this->Powered) .Process(this->DiscardOn) - .Process(this->DiscardOn_Ammo_Min) - .Process(this->DiscardOn_Ammo_Max) - .Process(this->DiscardOn_Health_Min) - .Process(this->DiscardOn_Health_Max) + .Process(this->DiscardOn_Ammo_MinimumAmount) + .Process(this->DiscardOn_Ammo_MaximumAmount) + .Process(this->DiscardOn_Health_BelowPercent) + .Process(this->DiscardOn_Health_AbovePercent) .Process(this->DiscardOn_Missions) .Process(this->DiscardOn_AIMissions) .Process(this->DiscardOn_LandTypes) diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index fb82cb4332..ae88089ae6 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -57,10 +57,10 @@ class AttachEffectTypeClass final : public Enumerable Valueable Cumulative_MaxCount; Valueable Powered; Valueable DiscardOn; - Valueable DiscardOn_Ammo_Min; - Valueable DiscardOn_Ammo_Max; - Nullable DiscardOn_Health_Min; - Nullable DiscardOn_Health_Max; + Valueable DiscardOn_Ammo_MinimumAmount; + Valueable DiscardOn_Ammo_MaximumAmount; + Nullable DiscardOn_Health_BelowPercent; + Nullable DiscardOn_Health_AbovePercent; ValueableVector DiscardOn_Missions; NullableVector DiscardOn_AIMissions; Valueable DiscardOn_LandTypes; @@ -135,10 +135,10 @@ class AttachEffectTypeClass final : public Enumerable , Cumulative_MaxCount { -1 } , Powered { false } , DiscardOn { DiscardCondition::None } - , DiscardOn_Ammo_Min { -1 } - , DiscardOn_Ammo_Max { -1 } - , DiscardOn_Health_Min {} - , DiscardOn_Health_Max {} + , DiscardOn_Ammo_MinimumAmount { -1 } + , DiscardOn_Ammo_MaximumAmount { -1 } + , DiscardOn_Health_BelowPercent { -1 } + , DiscardOn_Health_AbovePercent { -1 } , DiscardOn_Missions {} , DiscardOn_AIMissions {} , DiscardOn_LandTypes { LandTypeFlags::None } From 1b28d4477c0cfad4721837ede7653c06185afcf2 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Sun, 2 Aug 2026 23:56:07 +0800 Subject: [PATCH 11/12] update doc (default value -1) --- docs/New-or-Enhanced-Logics.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index e0d3eb3cba..ab70605322 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -118,10 +118,10 @@ Cumulative=false ; boolean Cumulative.MaxCount=-1 ; integer Powered=false ; boolean DiscardOn=none ; List of discard condition enumeration (none|entry|move|stationary|drain|inrange|outofrange|selling|undeploying|harvesting|invokerdie|ammo|health|mission|landtype|sequence) -DiscardOn.Ammo.MinimumAmount= ; integer -DiscardOn.Ammo.MaximumAmount= ; integer -DiscardOn.Health.BelowPercent= ; floating point value -DiscardOn.Health.AbovePercent= ; floating point value +DiscardOn.Ammo.MinimumAmount=-1 ; integer +DiscardOn.Ammo.MaximumAmount=-1 ; integer +DiscardOn.Health.BelowPercent=-1 ; floating point value +DiscardOn.Health.AbovePercent=-1 ; floating point value DiscardOn.Missions= ; List of MissionTypes DiscardOn.AIMissions= ; List of MissionTypes, default to [AttachEffectType] -> DiscardOn.Missions DiscardOn.LandTypes= ; List of LandTypes (none | clear | road | water | rock | wall | tiberium | beach | rough | ice | railroad | tunnel | weeds) From 620ca4274f7aea1b3fc22c96ade03c7c62b68947 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Mon, 3 Aug 2026 17:03:20 +0800 Subject: [PATCH 12/12] `DiscardOn.Sequences.Immediate` --- docs/New-or-Enhanced-Logics.md | 3 +++ src/Ext/Rules/Body.cpp | 2 ++ src/Ext/Rules/Body.h | 2 ++ src/New/Entity/AttachEffectClass.cpp | 25 ++++++++++++++++++++++--- src/New/Entity/AttachEffectClass.h | 1 + src/New/Type/AttachEffectTypeClass.cpp | 2 ++ src/New/Type/AttachEffectTypeClass.h | 2 ++ 7 files changed, 34 insertions(+), 3 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index b5eeabdcfd..5d0e31e520 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -29,6 +29,7 @@ This page describes all the engine features that are either new and introduced b - `mission`: Discard when the current mission of the object the effect is attached to matches any one in the `DiscardOn.Missions` list (or `DiscardOn.AIMissions` for AI-controlled objects, if set). - `landtype`: Discard when the land type of the cell where the object the effect is attached to is currently located matches any land type in the `DiscardOn.LandTypes` list. - `sequence`: Discard when the infantry to which the effect is attached is playing a sequence that matches any one in the `DiscardOn.Sequences` list. + - `DiscardOn.Sequences.Immediate` defines whether the `sequence` discard condition triggers immediately while the infantry is playing a matching sequence, or only when the infantry starts playing its next sequence after finishing that sequence. - `DiscardOn.MoveBasedOnDestination` defines whether to determine the movement state according to the presence or absence of a destination. It treats Jumpjet units hovering in the air as movement, and units that have no destination but are turning as stationary. - If used for an AE that has `DiscardOn=harvesting`, in order for it to judge correctly, this should be set to `true`. - `DiscardOn.ConsiderHarvestingAsStationary` defines whether to treat `harvesting` as `stationary`. When this flag is set to `false`, `DiscardOn=harvesting` can be used and it will not be considered `stationary` while `harvesting`. @@ -104,6 +105,7 @@ This page describes all the engine features that are either new and introduced b In `rulesmd.ini`: ```ini [General] +DiscardOn.Sequences.Immediate=true ; boolean DiscardOn.MoveBasedOnDestination=false ; boolean DiscardOn.ConsiderHarvestingAsStationary=true ; boolean OpenTopped.UseTransportRangeModifiers=false ; boolean @@ -128,6 +130,7 @@ DiscardOn.Missions= ; List of MissionTypes DiscardOn.AIMissions= ; List of MissionTypes, default to [AttachEffectType] -> DiscardOn.Missions DiscardOn.LandTypes= ; List of LandTypes (none | clear | road | water | rock | wall | tiberium | beach | rough | ice | railroad | tunnel | weeds) DiscardOn.Sequences= ; List of Sequences (ready | guard | prone | walk | fireup | fireprone | secondaryfire | secondaryprone | down | crawl | up | idle1 | idle2 | die1 | die2 | die3 | die4 | die5 | deploy | deployed | deployedfire | deployedidle | undeploy | paradrop | cheer | panic | shovel | carry | fly | hover | firefly | tumble | airdeathstart | airdeathfalling | airdeathfinish | tread | swim | wetattack | wetidle1 | wetidle2 | wetdie1 | wetdie2) +DiscardOn.Sequences.Immediate= ; boolean, default to [General] -> DiscardOn.Sequences.Immediate DiscardOn.RangeOverride= ; floating point value, distance in cells DiscardOn.MoveBasedOnDestination= ; boolean, default to [General] -> DiscardOn.MoveBasedOnDestination DiscardOn.ConsiderHarvestingAsStationary= ; boolean, default to [General] -> DiscardOn.ConsiderHarvestingAsStationary diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index 5cf6099f5f..716de5412e 100644 --- a/src/Ext/Rules/Body.cpp +++ b/src/Ext/Rules/Body.cpp @@ -546,6 +546,7 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) deploySound.Read(exINI, GameStrings::AudioVisual, "DeploySound"); pThis->DeploySound = deploySound; + this->DiscardOn_Sequences_Immediate.Read(exINI, GameStrings::General, "DiscardOn.Sequences.Immediate"); this->DiscardOn_MoveBasedOnDestination.Read(exINI, GameStrings::General, "DiscardOn.MoveBasedOnDestination"); this->DiscardOn_ConsiderHarvestingAsStationary.Read(exINI, GameStrings::General, "DiscardOn.ConsiderHarvestingAsStationary"); @@ -1031,6 +1032,7 @@ void RulesExt::ExtData::Serialize(T& Stm) .Process(this->Vertical_AircraftFix) .Process(this->Temporal_ApplyVersus) .Process(this->Temporal_ApplyMultiplier) + .Process(this->DiscardOn_Sequences_Immediate) .Process(this->DiscardOn_MoveBasedOnDestination) .Process(this->DiscardOn_ConsiderHarvestingAsStationary) .Process(this->RemoveMindControl_Silent) diff --git a/src/Ext/Rules/Body.h b/src/Ext/Rules/Body.h index 136cf87f17..21a4d8018b 100644 --- a/src/Ext/Rules/Body.h +++ b/src/Ext/Rules/Body.h @@ -486,6 +486,7 @@ class RulesExt Valueable Temporal_ApplyVersus; Valueable Temporal_ApplyMultiplier; + Valueable DiscardOn_Sequences_Immediate; Valueable DiscardOn_MoveBasedOnDestination; Valueable DiscardOn_ConsiderHarvestingAsStationary; Valueable RemoveMindControl_Silent; @@ -941,6 +942,7 @@ class RulesExt , Vertical_AircraftFix { true } , Temporal_ApplyVersus { false } , Temporal_ApplyMultiplier { false } + , DiscardOn_Sequences_Immediate { true } , DiscardOn_MoveBasedOnDestination { false } , DiscardOn_ConsiderHarvestingAsStationary { true } , RemoveMindControl_Silent { false } diff --git a/src/New/Entity/AttachEffectClass.cpp b/src/New/Entity/AttachEffectClass.cpp index 4d8686fc6d..de7e785691 100644 --- a/src/New/Entity/AttachEffectClass.cpp +++ b/src/New/Entity/AttachEffectClass.cpp @@ -42,6 +42,7 @@ AttachEffectClass::AttachEffectClass(AttachEffectTypeClass* pType, TechnoClass* , ShouldRecalculateStats { false } , LastDiscardCheckFrame { -1 } , LastDiscardCheckValue { false } + , LastSequenceCheck { Sequence::Nothing } { this->HasInitialized = false; @@ -697,12 +698,29 @@ bool AttachEffectClass::ShouldBeDiscardedNow() { if (auto const pInf = abstract_cast(pTechno)) { - if (pType->DiscardOn_Sequences.size() > 0 && pType->DiscardOn_Sequences.Contains(pInf->SequenceAnim)) + if (pType->DiscardOn_Sequences.size() > 0) { - this->LastDiscardCheckValue = true; - return true; + if (pType->DiscardOn_Sequences_Immediate.Get(RulesExt::Global()->DiscardOn_Sequences_Immediate)) + { + if (pType->DiscardOn_Sequences.Contains(pInf->SequenceAnim)) + { + this->LastDiscardCheckValue = true; + return true; + } + } + else + { + if (this->LastSequenceCheck != pInf->SequenceAnim && pType->DiscardOn_Sequences.Contains(this->LastSequenceCheck)) + { + this->LastDiscardCheckValue = true; + return true; + } + this->LastSequenceCheck = pInf->SequenceAnim; + } } } + else + this->LastSequenceCheck = Sequence::Nothing; } if (pTechno->Target) @@ -1254,6 +1272,7 @@ bool AttachEffectClass::Serialize(T& Stm) .Process(this->LastActiveStat) .Process(this->LaserTrail) .Process(this->ShouldRecalculateStats) + .Process(this->LastSequenceCheck) .Success(); } diff --git a/src/New/Entity/AttachEffectClass.h b/src/New/Entity/AttachEffectClass.h index 203100ea96..2923710f39 100644 --- a/src/New/Entity/AttachEffectClass.h +++ b/src/New/Entity/AttachEffectClass.h @@ -98,6 +98,7 @@ class AttachEffectClass bool LastDiscardCheckValue; bool LastActiveStat; LaserTrailClass* LaserTrail; + Sequence LastSequenceCheck; public: bool HasCumulativeAnim; diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index ef97667569..b39a2a95a7 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -116,6 +116,7 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->DiscardOn_AIMissions.Read(exINI, pSection, "DiscardOn.AIMissions"); this->DiscardOn_LandTypes.Read(exINI, pSection, "DiscardOn.LandTypes"); this->DiscardOn_Sequences.Read(exINI, pSection, "DiscardOn.Sequences"); + this->DiscardOn_Sequences_Immediate.Read(exINI, pSection, "DiscardOn.Sequences.Immediate"); this->DiscardOn_RangeOverride.Read(exINI, pSection, "DiscardOn.RangeOverride"); this->DiscardOn_MoveBasedOnDestination.Read(exINI, pSection, "DiscardOn.MoveBasedOnDestination"); this->DiscardOn_ConsiderHarvestingAsStationary.Read(exINI, pSection, "DiscardOn.ConsiderHarvestingAsStationary"); @@ -242,6 +243,7 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->DiscardOn_AIMissions) .Process(this->DiscardOn_LandTypes) .Process(this->DiscardOn_Sequences) + .Process(this->DiscardOn_Sequences_Immediate) .Process(this->DiscardOn_RangeOverride) .Process(this->DiscardOn_MoveBasedOnDestination) .Process(this->DiscardOn_ConsiderHarvestingAsStationary) diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index ae88089ae6..ef02b9d3a4 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -65,6 +65,7 @@ class AttachEffectTypeClass final : public Enumerable NullableVector DiscardOn_AIMissions; Valueable DiscardOn_LandTypes; ValueableVector DiscardOn_Sequences; + NullableDiscardOn_Sequences_Immediate; Nullable DiscardOn_RangeOverride; Nullable DiscardOn_MoveBasedOnDestination; Nullable DiscardOn_ConsiderHarvestingAsStationary; @@ -143,6 +144,7 @@ class AttachEffectTypeClass final : public Enumerable , DiscardOn_AIMissions {} , DiscardOn_LandTypes { LandTypeFlags::None } , DiscardOn_Sequences {} + , DiscardOn_Sequences_Immediate {} , DiscardOn_RangeOverride {} , DiscardOn_MoveBasedOnDestination {} , DiscardOn_ConsiderHarvestingAsStationary {}