Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
252140b
Add veterancy threshold check for TechnoClass objects
Flactine Dec 17, 2025
8a983da
Add veterancy threshold checks for weapons and warheads
Flactine Dec 17, 2025
78305e1
Add debug logs and use float suffixes for veterancy values
Flactine Dec 17, 2025
dd1c9b8
Fix incorrect INI keys for veterancy percent loading
Flactine Dec 17, 2025
63dd822
Add veterancy threshold checks to targeting logic
Flactine Dec 17, 2025
8e7926a
Document veterancy-based targeting and warhead logic
Flactine Dec 17, 2025
0e57ed6
Update Whats-New.md
Flactine Dec 17, 2025
583a4da
Merge branch 'develop' into CanTarget-MaxLevel
Flactine Dec 17, 2025
28cf3b2
Use dynamic veterancy cap for target filtering
Flactine Dec 17, 2025
4a9d466
Cast VeteranCap to float in WarheadType and WeaponType
Flactine Dec 17, 2025
db04976
Switch veterancy fields to Nullable<float> in WarheadType and WeaponType
Flactine Dec 17, 2025
986d768
Refactor veterancy threshold handling for warheads and weapons
Flactine Dec 17, 2025
acd4885
Fix veterancy cap comparison logic in INI loading
Flactine Dec 17, 2025
1a4e160
Docs
Flactine Dec 18, 2025
ddd6d06
Refactor veterancy threshold types to double
Flactine Dec 18, 2025
084f720
Fix logic and type for veterancy and health checks
Flactine Dec 18, 2025
9eb972d
Clarify data types for Veterancy fields in docs
Flactine Dec 18, 2025
f814cad
Clarify veterancy parameter documentation
Flactine Dec 18, 2025
176f860
Fix alignment and spacing in logic documentation
Flactine Dec 18, 2025
aaa1f96
Update src/Ext/Techno/Body.cpp
Flactine Dec 18, 2025
0d47524
Update CREDITS.md
Flactine Dec 18, 2025
a050f51
Update Whats-New.md
Flactine Dec 18, 2025
3731407
Adjust veterancy threshold logic for warheads and weapons
Flactine Dec 18, 2025
c2b85ba
Merge branch 'develop' into CanTarget-MaxLevel
NetsuNegi Jan 24, 2026
c8cd156
Value to Enum
Flactine Jan 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -678,7 +678,9 @@ This page lists all the individual contributions to the project by their author.
- **solar-III (凤九歌)**
- Target scanning delay customization (documentation)
- Skip target scanning function calling for unarmed technos (documentation)
- **Flactine** - add target filtering options to attacheffect system
- **Flactine**
- Add target filtering options to attacheffect system
- Add veterancy-based target filtering for weapons and warheads
- **tyuah8**:
- Drive/Jumpjet/Ship/Teleport locomotor did not power on when it is un-piggybacked bugfix
- Destroyed unit leaves sensors bugfix
Expand Down
2 changes: 2 additions & 0 deletions docs/Fixed-or-Improved-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -2348,6 +2348,7 @@ Conventional.IgnoreUnits=false ; boolean
### Customizable Warhead trigger conditions

- `AffectsBelowPercent` and `AffectsAbovePercent` can be used to set the health percentage thresholds that target needs to be below/equal and/or above of for the Warhead to detonate. If target has zero health left this check is bypassed.
- `AffectsVeterancy` sets the veterancy levels that allow the Warhead to detonate on the target.
- If set to `false`, `AffectsNeutral` makes the warhead can't damage or affect target that belongs to neutral house.
- If set to `false`, `EffectsRequireVerses` makes the Phobos-introduced warhead effects trigger even if it can't damage the target because of it's current ArmorType (e.g. 0% in `Verses`).

Expand All @@ -2356,6 +2357,7 @@ In `rulesmd.ini`:
[SOMEWARHEAD] ; WarheadType
AffectsBelowPercent=1.0 ; floating point value, percents or absolute
AffectsAbovePercent=0.0 ; floating point value, percents or absolute
AffectsVeterancy=all ; List of Affected Veterancy Enumeration (none|rookie|veteran|elite|all)
AffectsNeutral=true ; boolean
EffectsRequireVerses=false ; boolean
```
Expand Down
11 changes: 6 additions & 5 deletions docs/New-or-Enhanced-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -2992,11 +2992,12 @@ This function is only used as an additional scattering visual display, which is

In `rulesmd.ini`:
```ini
[SOMEWEAPON] ; WeaponType
CanTarget=all ; List of Affected Target Enumeration (none|land|water|empty|infantry|units|buildings|all)
CanTargetHouses=all ; List of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all)
CanTarget.MaxHealth=1.0 ; floating point value, percents or absolute
CanTarget.MinHealth=0.0 ; floating point value, percents or absolute
[SOMEWEAPON] ; WeaponType
CanTarget=all ; List of Affected Target Enumeration (none|land|water|empty|infantry|units|buildings|all)
CanTargetHouses=all ; List of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all)
CanTarget.MaxHealth=1.0 ; floating point value, percents or absolute
CanTarget.MinHealth=0.0 ; floating point value, percents or absolute
CanTargetVeterancy=all ; List of Affected Veterancy Enumeration (none|rookie|veteran|elite|all)
```

```{note}
Expand Down
2 changes: 2 additions & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,8 @@ New:
- [Attack non-threatening structures extensions](New-or-Enhanced-Logics.md#attack-non-threatening-structures-techno) (by FlyStar)
- [Customize size for mind controlled unit](New-or-Enhanced-Logics.md#mind-control-enhancement) (by NetsuNegi)
- [Deploy priority filtering](New-or-Enhanced-Logics.md#low-priority-for-deploy) (by Starkku)
- [Weapon target filtering by target veterancy](New-or-Enhanced-Logics.md#weapon-targeting-filter) (by Flactine)
- [Warhead effect filtering by target veterancy](Fixed-or-Improved-Logics.md#customizable-warhead-trigger-conditions) (by Flactine)

Vanilla fixes:
- Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya)
Expand Down
7 changes: 4 additions & 3 deletions src/Ext/Bullet/Hooks.DetonateLogics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ DEFINE_HOOK(0x4690D4, BulletClass_Logics_NewChecks, 0x6)
if (auto const pTarget = abstract_cast<TechnoClass*>(pBullet->Target))
{
// Check if the WH should affect the techno target or skip it
if (!pExt->IsHealthInThreshold(pTarget) || (!pExt->AffectsNeutral && pTarget->Owner->IsNeutral()))
if (!pExt->IsHealthInThreshold(pTarget) || !pExt->IsVeterancyInThreshold(pTarget) || (!pExt->AffectsNeutral && pTarget->Owner->IsNeutral()))
return GoToExtras;
}

Expand Down Expand Up @@ -382,7 +382,7 @@ DEFINE_HOOK(0x469AA4, BulletClass_Logics_Extras, 0x5)
auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWH);
auto const pTarget = abstract_cast<TechnoClass*>(pThis->Target);

if (pTarget && !pWHExt->IsHealthInThreshold(pTarget))
if (pTarget && !pWHExt->IsHealthInThreshold(pTarget) && !pWHExt->IsVeterancyInThreshold(pTarget))
continue;

int damage = defaultDamage;
Expand Down Expand Up @@ -556,7 +556,8 @@ static bool IsAllowedSplitsTarget(TechnoClass* pSource, HouseClass* pOwner, Weap
if (!EnumFunctions::CanTargetHouse(pWeaponExt->CanTargetHouses, pOwner, pTarget->Owner)
|| !EnumFunctions::IsCellEligible(pTarget->GetCell(), pWeaponExt->CanTarget, true, true)
|| !EnumFunctions::IsTechnoEligible(pTarget, pWeaponExt->CanTarget)
|| !pWeaponExt->IsHealthInThreshold(pTarget))
|| !pWeaponExt->IsHealthInThreshold(pTarget)
|| !pWeaponExt->IsVeterancyInThreshold(pTarget))
{
return false;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Ext/Bullet/Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ DEFINE_HOOK(0x46A4FB, BulletClass_Shrapnel_Targeting, 0x6)
if (!pWeaponExt->SkipWeaponPicking)
{
if (!EnumFunctions::CanTargetHouse(pWeaponExt->CanTargetHouses, pOwner, pTechno->Owner) || !EnumFunctions::IsTechnoEligible(pTechno, pWeaponExt->CanTarget)
|| !pWeaponExt->IsHealthInThreshold(pTechno) || !pWeaponExt->HasRequiredAttachedEffects(pTechno, pSource))
|| !pWeaponExt->IsHealthInThreshold(pTechno) || !pWeaponExt->IsVeterancyInThreshold(pTechno) || !pWeaponExt->HasRequiredAttachedEffects(pTechno, pSource))
{
return SkipObject;
}
Expand Down
1 change: 1 addition & 0 deletions src/Ext/Techno/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ class TechnoExt
static void CreateDelayedFireAnim(TechnoClass* pThis, AnimTypeClass* pAnimType, int weaponIndex, bool attach, bool center, bool removeOnNoDelay, bool onTurret, CoordStruct firingCoords);
static bool HandleDelayedFireWithPauseSequence(TechnoClass* pThis, WeaponTypeClass* pWeapon, int weaponIndex, int frame, int firingFrame);
static bool IsHealthInThreshold(TechnoClass* pObject, double min, double max);
static bool IsVeterancyInThreshold(TechnoClass* pObject, double min, double max);
static UnitTypeClass* GetUnitTypeExtra(UnitClass* pUnit, TechnoTypeExt::ExtData* pData);
static AircraftTypeClass* GetAircraftTypeExtra(AircraftClass* pAircraft);
static bool CannotMove(UnitClass* pThis);
Expand Down
1 change: 1 addition & 0 deletions src/Ext/Techno/Hooks.Firing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ DEFINE_HOOK(0x6FC339, TechnoClass_CanFire, 0x6)
if (!EnumFunctions::IsTechnoEligible(pTargetTechno, pWeaponExt->CanTarget)
|| !EnumFunctions::CanTargetHouse(pWeaponExt->CanTargetHouses, pThis->Owner, pTargetTechno->Owner)
|| !pWeaponExt->IsHealthInThreshold(pTargetTechno)
|| !pWeaponExt->IsVeterancyInThreshold(pTargetTechno)
|| !pWeaponExt->HasRequiredAttachedEffects(pTargetTechno, pThis))
{
return CannotFire;
Expand Down
2 changes: 1 addition & 1 deletion src/Ext/Techno/Hooks.ReceiveDamage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6)

// AffectsAbove/BelowPercent & AffectsNeutral can ignore IgnoreDefenses like AffectsAllies/Enmies/Owner
// They should be checked here to cover all cases that directly use ReceiveDamage to deal damage
if (!pWHExt->IsHealthInThreshold(pThis) || (!pWHExt->AffectsNeutral && pThis->Owner->IsNeutral()))
if (!pWHExt->IsHealthInThreshold(pThis) || !pWHExt->IsVeterancyInThreshold(pThis) || (!pWHExt->AffectsNeutral && pThis->Owner->IsNeutral()))
{
damage = 0;
return 0;
Expand Down
2 changes: 2 additions & 0 deletions src/Ext/Techno/WeaponHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ int TechnoExt::PickWeaponIndex(TechnoClass* pThis, TechnoClass* pTargetTechno, A
if (!EnumFunctions::IsTechnoEligible(pTargetTechno, pSecondExt->CanTarget)
|| !EnumFunctions::CanTargetHouse(pSecondExt->CanTargetHouses, pThis->Owner, pTargetTechno->Owner)
|| !pSecondExt->IsHealthInThreshold(pTargetTechno)
|| !pSecondExt->IsVeterancyInThreshold(pTargetTechno)
|| !pSecondExt->HasRequiredAttachedEffects(pTargetTechno, pThis))
{
return weaponIndexOne;
Expand Down Expand Up @@ -70,6 +71,7 @@ int TechnoExt::PickWeaponIndex(TechnoClass* pThis, TechnoClass* pTargetTechno, A
if (!EnumFunctions::IsTechnoEligible(pTargetTechno, pFirstExt->CanTarget)
|| !EnumFunctions::CanTargetHouse(pFirstExt->CanTargetHouses, pThis->Owner, pTargetTechno->Owner)
|| !pFirstExt->IsHealthInThreshold(pTargetTechno)
|| !pFirstExt->IsVeterancyInThreshold(pTargetTechno)
|| !firstAllowedAE)
{
return weaponIndexTwo;
Expand Down
15 changes: 15 additions & 0 deletions src/Ext/WarheadType/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ bool WarheadTypeExt::ExtData::CanAffectTarget(TechnoClass* pTarget) const
if (!IsHealthInThreshold(pTarget))
return false;

if (!IsVeterancyInThreshold(pTarget))
return false;

if (!this->EffectsRequireVerses)
return true;

Expand All @@ -51,6 +54,14 @@ bool WarheadTypeExt::ExtData::IsHealthInThreshold(TechnoClass* pTarget) const
return TechnoExt::IsHealthInThreshold(pTarget, this->AffectsAbovePercent, this->AffectsBelowPercent);
}

bool WarheadTypeExt::ExtData::IsVeterancyInThreshold(TechnoClass* pTarget) const
{
if (!this->VeterancyCheck)
return true;

return EnumFunctions::CanTargetVeterancy(this->AffectsVeterancy, pTarget);
}

// Checks if Warhead can affect target that might or might be currently invulnerable.
bool WarheadTypeExt::ExtData::CanAffectInvulnerable(TechnoClass* pTarget) const
{
Expand Down Expand Up @@ -283,11 +294,13 @@ void WarheadTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)

this->AffectsBelowPercent.Read(exINI, pSection, "AffectsBelowPercent");
this->AffectsAbovePercent.Read(exINI, pSection, "AffectsAbovePercent");
this->AffectsVeterancy.Read(exINI, pSection, "AffectsVeterancy");
this->AffectsNeutral.Read(exINI, pSection, "AffectsNeutral");
this->AffectsGround.Read(exINI, pSection, "AffectsGround");
this->AffectsAir.Read(exINI, pSection, "AffectsAir");
this->CellSpread_Cylinder.Read(exINI, pSection, "CellSpread.Cylinder");
this->HealthCheck = this->AffectsAbovePercent > 0.0 || this->AffectsBelowPercent < 1.0;
this->VeterancyCheck = this->AffectsVeterancy != AffectedVeterancy::All;

if (this->AffectsAbovePercent > this->AffectsBelowPercent)
Debug::Log("[Developer warning][%s] AffectsAbovePercent is bigger than AffectsBelowPercent, the warhead will never activate!\n", pSection);
Expand Down Expand Up @@ -537,11 +550,13 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm)

.Process(this->AffectsBelowPercent)
.Process(this->AffectsAbovePercent)
.Process(this->AffectsVeterancy)
.Process(this->AffectsNeutral)
.Process(this->AffectsGround)
.Process(this->AffectsAir)
.Process(this->CellSpread_Cylinder)
.Process(this->HealthCheck)
.Process(this->VeterancyCheck)

.Process(this->InflictLocomotor)
.Process(this->RemoveInflictedLocomotor)
Expand Down
6 changes: 6 additions & 0 deletions src/Ext/WarheadType/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ class WarheadTypeExt

Valueable<double> AffectsBelowPercent;
Valueable<double> AffectsAbovePercent;
Valueable<AffectedVeterancy> AffectsVeterancy;

Valueable<bool> AffectsNeutral;
Valueable<bool> AffectsGround;
Valueable<bool> AffectsAir;
Expand Down Expand Up @@ -231,6 +233,7 @@ class WarheadTypeExt
int RemainingAnimCreationInterval;
bool PossibleCellSpreadDetonate;
bool HealthCheck;
bool VeterancyCheck;
TechnoClass* DamageAreaTarget;

private:
Expand Down Expand Up @@ -405,6 +408,7 @@ class WarheadTypeExt

, AffectsBelowPercent { 1.0 }
, AffectsAbovePercent { 0.0 }
, AffectsVeterancy { AffectedVeterancy::All }
, AffectsNeutral { true }
, AffectsGround { true }
, AffectsAir { true }
Expand All @@ -426,6 +430,7 @@ class WarheadTypeExt
, RemainingAnimCreationInterval { 0 }
, PossibleCellSpreadDetonate { false }
, HealthCheck { false }
, VeterancyCheck { false }
, DamageAreaTarget {}

, CanKill { true }
Expand Down Expand Up @@ -462,6 +467,7 @@ class WarheadTypeExt
bool CanAffectInvulnerable(TechnoClass* pTarget) const;
bool EligibleForFullMapDetonation(TechnoClass* pTechno, TechnoTypeClass* pType, HouseClass* pOwner) const;
bool IsHealthInThreshold(TechnoClass* pTarget) const;
bool IsVeterancyInThreshold(TechnoClass* pTarget) const;

virtual ~ExtData() = default;
virtual void LoadFromINIFile(CCINIClass* pINI) override;
Expand Down
8 changes: 8 additions & 0 deletions src/Ext/WeaponType/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ bool WeaponTypeExt::ExtData::IsHealthInThreshold(TechnoClass* pTarget) const
return TechnoExt::IsHealthInThreshold(pTarget, this->CanTarget_MinHealth, this->CanTarget_MaxHealth);
}

bool WeaponTypeExt::ExtData::IsVeterancyInThreshold(TechnoClass* pTarget) const
{
return EnumFunctions::CanTargetVeterancy(this->CanTargetVeterancy, pTarget);
}

void WeaponTypeExt::ExtData::Initialize()
{
this->RadType = RadTypeClass::FindOrAllocate(GameStrings::Radiation);
Expand Down Expand Up @@ -103,6 +108,7 @@ void WeaponTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
this->CanTargetHouses.Read(exINI, pSection, "CanTargetHouses");
this->CanTarget_MaxHealth.Read(exINI, pSection, "CanTarget.MaxHealth");
this->CanTarget_MinHealth.Read(exINI, pSection, "CanTarget.MinHealth");
this->CanTargetVeterancy.Read(exINI, pSection, "CanTargetVeterancy");
this->Burst_Delays.Read(exINI, pSection, "Burst.Delays");
this->Burst_FireWithinSequence.Read(exINI, pSection, "Burst.FireWithinSequence");
this->Burst_NoDelay.Read(exINI, pSection, "Burst.NoDelay");
Expand Down Expand Up @@ -163,6 +169,7 @@ void WeaponTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
// handle SkipWeaponPicking
if (this->CanTarget != AffectedTarget::All || this->CanTargetHouses != AffectedHouse::All
|| this->CanTarget_MaxHealth < 1.0 || this->CanTarget_MinHealth > 0.0
|| this->CanTargetVeterancy != AffectedVeterancy::All
|| this->AttachEffect_RequiredTypes.size() || this->AttachEffect_RequiredGroups.size()
|| this->AttachEffect_DisallowedTypes.size() || this->AttachEffect_DisallowedGroups.size())
{
Expand Down Expand Up @@ -192,6 +199,7 @@ void WeaponTypeExt::ExtData::Serialize(T& Stm)
.Process(this->CanTargetHouses)
.Process(this->CanTarget_MaxHealth)
.Process(this->CanTarget_MinHealth)
.Process(this->CanTargetVeterancy)
.Process(this->RadType)
.Process(this->Burst_Delays)
.Process(this->Burst_FireWithinSequence)
Expand Down
3 changes: 3 additions & 0 deletions src/Ext/WeaponType/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class WeaponTypeExt
Valueable<AffectedHouse> CanTargetHouses;
Valueable<double> CanTarget_MaxHealth;
Valueable<double> CanTarget_MinHealth;
Valueable<AffectedVeterancy> CanTargetVeterancy;
ValueableVector<int> Burst_Delays;
Valueable<bool> Burst_FireWithinSequence;
Valueable<bool> Burst_NoDelay;
Expand Down Expand Up @@ -115,6 +116,7 @@ class WeaponTypeExt
, CanTargetHouses { AffectedHouse::All }
, CanTarget_MaxHealth { 1.0 }
, CanTarget_MinHealth { 0.0 }
, CanTargetVeterancy { AffectedVeterancy::All }
, Burst_Delays {}
, Burst_FireWithinSequence { false }
, Burst_NoDelay { false }
Expand Down Expand Up @@ -177,6 +179,7 @@ class WeaponTypeExt
int GetBurstDelay(int burstIndex) const;
bool HasRequiredAttachedEffects(TechnoClass* pTechno, TechnoClass* pFirer) const;
bool IsHealthInThreshold(TechnoClass* pTarget) const;
bool IsVeterancyInThreshold(TechnoClass* pTarget) const;

virtual ~ExtData() = default;

Expand Down
12 changes: 12 additions & 0 deletions src/Utilities/Enum.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,18 @@ enum class AffectedTarget : unsigned char

MAKE_ENUM_FLAGS(AffectedTarget);

enum class AffectedVeterancy : unsigned char
{
None = 0x0,
Rookie = 0x1,
Veteran = 0x2,
Elite = 0x4,

All = Rookie | Veteran | Elite
};

MAKE_ENUM_FLAGS(AffectedVeterancy);

enum class AffectedHouse : unsigned char
{
None = 0x0,
Expand Down
17 changes: 17 additions & 0 deletions src/Utilities/EnumFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,23 @@ bool EnumFunctions::CanTargetHouse(AffectedHouse flags, HouseClass* ownerHouse,
return (flags & AffectedHouse::Enemies) != AffectedHouse::None;
}

bool EnumFunctions::CanTargetVeterancy(AffectedVeterancy flags, TechnoClass* pTechno)
{
if (flags == AffectedVeterancy::All)
return true;

switch (pTechno->Veterancy.GetRemainingLevel())
{
case Rank::Elite:
return (flags & AffectedVeterancy::Elite) != AffectedVeterancy::None;
case Rank::Veteran:
return (flags & AffectedVeterancy::Veteran) != AffectedVeterancy::None;
default:
return (flags & AffectedVeterancy::Rookie) != AffectedVeterancy::None;
}
}


bool EnumFunctions::IsCellEligible(CellClass* const pCell, AffectedTarget allowed, bool explicitEmptyCells, bool considerBridgesLand)
{
if (allowed == AffectedTarget::All)
Expand Down
1 change: 1 addition & 0 deletions src/Utilities/EnumFunctions.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class EnumFunctions
{
public:
static bool CanTargetHouse(AffectedHouse flags, HouseClass* ownerHouse, HouseClass* targetHouse);
static bool CanTargetVeterancy(AffectedVeterancy flags, TechnoClass* pTechno);
static bool IsCellEligible(CellClass* const pCell, AffectedTarget allowed, bool explicitEmptyCells = false, bool considerBridgesLand = false);
static bool IsTechnoEligible(TechnoClass* const pTechno, AffectedTarget allowed, bool considerAircraftSeparately = false);
static bool AreCellAndObjectsEligible(CellClass* const pCell, AffectedTarget allowed, AffectedHouse allowedHouses, HouseClass* owner, bool explicitEmptyCells = false, bool considerAircraftSeparately = false, bool allowBridges = false);
Expand Down
46 changes: 46 additions & 0 deletions src/Utilities/TemplateDef.h
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,52 @@ namespace detail
return false;
}

template <>
inline bool read<AffectedVeterancy>(AffectedVeterancy& value, INI_EX& parser, const char* pSection, const char* pKey)
{
if (parser.ReadString(pSection, pKey))
{
static const std::pair<const char*, AffectedVeterancy> Names[] =
{
{"rookie", AffectedVeterancy::Rookie},
{"veteran", AffectedVeterancy::Veteran},
{"elite", AffectedVeterancy::Elite},
{"all", AffectedVeterancy::All},
{"none", AffectedVeterancy::None},
};


auto parsed = AffectedVeterancy::None;
for (auto&& part : std::string_view { parser.value() } | std::views::split(','))
{
std::string_view&& cur { part.begin(),part.end() };
*const_cast<char*>(cur.data() + cur.find_last_not_of(" \t\r") + 1) = 0;
auto pCur = cur.data() + cur.find_first_not_of(" \t\r");
bool matched = false;
for (auto const& [name, val] : Names)
{
if (_strcmpi(pCur, name) == 0)
{
parsed |= val;
matched = true;
break;
}
}
if (!matched)
{
Debug::INIParseFailed(pSection, pKey, pCur, "Expected an affected veterancy");
return false;
}
}

value = parsed;
return true;
}

return false;
}


template <>
inline bool read<AttachedAnimFlag>(AttachedAnimFlag& value, INI_EX& parser, const char* pSection, const char* pKey)
{
Expand Down