Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d804b8a
Initial commit
FS-21 Aug 20, 2024
9b4bf42
Merge remote-tracking branch 'origin/develop' into feature/penetrates…
FS-21 Aug 30, 2024
9c49632
Fix typo
FS-21 Aug 30, 2024
433f0f7
Merge branch 'develop' into feature/penetrates-garrison
FS-21 Oct 27, 2024
02cd739
Merge branch 'develop' into feature/penetrates-garrison
FS-21 Oct 28, 2024
cfedc7a
Merge branch 'develop' into feature/penetrates-garrison
FS-21 Feb 18, 2025
3ffa046
Renaming tag
FS-21 May 11, 2025
416f941
Merge branch 'develop' into feature/penetrates-garrison
FS-21 May 19, 2025
19742ba
Applied some feedback
FS-21 May 19, 2025
a773290
Merge branch 'develop' into feature/penetrates-garrison
FS-21 May 23, 2025
053ae2a
Merge branch 'develop' into feature/penetrates-garrison
FS-21 May 29, 2025
41e953d
Merge branch 'develop' into feature/penetrates-garrison
FS-21 May 30, 2025
81e2820
Merge branch 'develop' into feature/penetrates-garrison
FS-21 Jun 8, 2025
7809074
Merge remote-tracking branch 'upstream/develop' into feature/penetrat…
Coronia Jul 8, 2025
c62028a
fix distance calculation
Coronia Jul 8, 2025
69d6b47
fix PenetratesGarrison.Allowed
Coronia Jul 9, 2025
fa86171
refresh image when cleared
Coronia Jul 9, 2025
362e4a1
pass through damage directly
Coronia Jul 9, 2025
75d9cd7
fix ini parsing
Coronia Jul 9, 2025
bdbe025
Merge branch 'develop' into feature/penetrates-garrison
FS-21 Aug 7, 2025
e649751
Merge branch 'develop' into feature/penetrates-garrison
FS-21 Aug 7, 2025
815968e
Merge branch 'develop' into feature/penetrates-garrison
FS-21 Sep 2, 2025
f4e990c
Applied feedback
FS-21 Aug 23, 2026
5277cc2
Merge branch 'develop' into feature/penetrates-garrison
FS-21 Aug 23, 2026
ad735e5
Fix TechnoType/Body.cpp conflict syntax
FS-21 Aug 23, 2026
05d8863
applied feedback
FS-21 Aug 23, 2026
7c71098
Merge remote-tracking branch 'origin/develop' into feature/penetrates…
FS-21 Aug 24, 2026
13edf25
Merge remote-tracking branch 'origin/develop' into feature/penetrates…
FS-21 Aug 28, 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
1 change: 1 addition & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ This page lists all the individual contributions to the project by their author.
- Warhead activation target health thresholds enhancements
- Event 606: AttachEffect is attaching to a Techno
- Linked superweapons
- Penetration damage on garrisonable structures
- Unit & infantry auto-conversion on ammo change
- Restore the ScriptType action#24 `Play speech` from Tiberian Sun
- Modify ammo on impact
Expand Down
24 changes: 24 additions & 0 deletions docs/New-or-Enhanced-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -3171,6 +3171,30 @@ PenetratesTransport.FatalRateMultiplier=1.0 ; double
PenetratesTransport.DamageMultiplier=1.0 ; double
```

### Penetration damage on garrisonable structures

![image](_static/images/garrison-penetration-01.gif)
*Penetration damage on garrisoned structures example in [C&C: Reloaded](https://www.moddb.com/mods/cncreloaded)*

- Warheads can now damage garrisoned infantry at impact.
- `PenetratesGarrison` Enables the logic.
- `PenetratesGarrison.RandomTarget` specifies if the damage will go at some random garrisoned soldier or if all infantry should be damaged at the same time.
- `PenetratesGarrison.DamageMultiplier` can be used to modify the damage applied against the garrisoned infantry. A random percentage value will be picked between the specified range.
- `PenetratesGarrison.CleanSound` can be used to specify a sound to play when the structure lost all the garrisoned soldiers with this logic.
- `PenetratesGarrison.Allowed` can be set on garrisonable buildings to protect the garrisoned infantry, or used on garrisoned infantry to make them not be affected by this logic.

In `rulesmd.ini`:
```ini
[SOMEWARHEAD] ; WarheadType
PenetratesGarrison=false ; boolean
PenetratesGarrison.RandomTarget=true ; boolean
PenetratesGarrison.DamageMultiplier=1.0,1.0 ; floating point value - single or comma-sep. range (percentages)
PenetratesGarrison.CleanSound= ; sound entry

[SOMETECHNO] ; TechnoType
PenetratesGarrison.Allowed=true ; boolean
```

### Remove disguise on impact

- Warheads can now remove disguise from disguised spies or mirage tanks. This will work even if the disguised was acquired by default through `PermaDisguise`.
Expand Down
1 change: 1 addition & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ HideShakeEffects=false ; boolean
#### New:
- [Customized transport plane for teams](AI-Scripting-and-Mapping.md#customized-transport-plane-for-teams) (by FlyStar)
- [Modify ammo on impact](New-or-Enhanced-Logics.md#modify-ammo-on-impact) (by FS-21)
- [Penetration damage on garrisonable structures](New-or-Enhanced-Logics.md#penetration-damage-on-garrisonable-structures) (by FS-21)
- [Customize whether mind-controlled Insignificant technos can be auto-targeted](Fixed-or-Improved-Logics.md#customize-whether-mind-controlled-Insignificant-technos-can-be-auto-targeted) (by Noble_Fish)
- [AutoDeath based on player power status and player credits](New-or-Enhanced-Logics.md#kill-object-automatically) (by Flactine)
- [Roof production anim](New-or-Enhanced-Logics.md#roof-production-anim) (by Noble_Fish)
Expand Down
Binary file added docs/_static/images/garrison-penetration-01.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/Ext/TechnoType/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1145,6 +1145,8 @@ void TechnoTypeExt::LoadFromINIFile(CCINIClass* const pINI)
this->AttackMove_Follow_IncludeAir.Read(exINI, pSection, "AttackMove.Follow.IncludeAir");
this->AttackMove_Follow_IfMindControlIsFull.Read(exINI, pSection, "AttackMove.Follow.IfMindControlIsFull");

this->PenetratesGarrison_Allowed.Read(exINI, pSection, "PenetratesGarrison.Allowed");

this->Ammo_AutoConvertMinimumAmount.Read(exINI, pSection, "Ammo.AutoConvertMinimumAmount");
this->Ammo_AutoConvertMaximumAmount.Read(exINI, pSection, "Ammo.AutoConvertMaximumAmount");
this->Ammo_AutoConvertType.Read(exINI, pSection, "Ammo.AutoConvertType");
Expand Down Expand Up @@ -1761,6 +1763,8 @@ void TechnoTypeExt::Serialize(T& Stm)
.Process(this->FallingDownDamage_Water)
.Process(this->FallingDownDamage_AllowEMP)

.Process(this->PenetratesGarrison_Allowed)

.Process(this->Ammo_AutoConvertMinimumAmount)
.Process(this->Ammo_AutoConvertMaximumAmount)
.Process(this->Ammo_AutoConvertType)
Expand Down
2 changes: 2 additions & 0 deletions src/Ext/TechnoType/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class TechnoTypeExt : public ObjectTypeExt
Nullable<bool> Harvester_Counted;
Nullable<bool> Promote_IncludeSpawns;
Valueable<bool> ImmuneToCrit;
Valueable<bool> PenetratesGarrison_Allowed;
Nullable<bool> MultiMindControl_ReleaseVictim;
Valueable<int> CameoPriority;
PhobosPCXFile AltCameoPCX;
Expand Down Expand Up @@ -496,6 +497,7 @@ class TechnoTypeExt : public ObjectTypeExt
, Harvester_Counted {}
, Promote_IncludeSpawns {}
, ImmuneToCrit { false }
, PenetratesGarrison_Allowed { true }
, MultiMindControl_ReleaseVictim {}
, CameoPriority { 0 }
, AltCameoPCX {}
Expand Down
11 changes: 11 additions & 0 deletions src/Ext/WarheadType/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,11 @@ void WarheadTypeExt::LoadFromINIFile(CCINIClass* const pINI)
if (this->AffectsAbovePercent > this->AffectsBelowPercent)
Debug::Log("[Developer warning][%s] AffectsAbovePercent is bigger than AffectsBelowPercent, the warhead will never activate!\n", pSection);

this->PenetratesGarrison.Read(exINI, pSection, "PenetratesGarrison");
this->PenetratesGarrison_RandomTarget.Read(exINI, pSection, "PenetratesGarrison.RandomTarget");
this->PenetratesGarrison_DamageMultiplier.Read(exINI, pSection, "PenetratesGarrison.DamageMultiplier");
this->PenetratesGarrison_CleanSound.Read(exINI, pSection, "PenetratesGarrison.CleanSound");

this->ReverseEngineer.Read(exINI, pSection, "ReverseEngineer");

this->UnlimboDetonate.Read(exINI, pSection, "UnlimboDetonate");
Expand Down Expand Up @@ -512,6 +517,7 @@ void WarheadTypeExt::LoadFromINIFile(CCINIClass* const pINI)
|| this->AttachEffects.RemoveGroups.size() > 0
|| this->BuildingSell
|| this->BuildingUndeploy
|| this->PenetratesGarrison
|| this->ReverseEngineer
|| this->ReturnWarhead
|| this->PenetratesTransport_Level > 0
Expand Down Expand Up @@ -776,6 +782,11 @@ void WarheadTypeExt::Serialize(T& Stm)

.Process(this->AirstrikeTargets)

.Process(this->PenetratesGarrison)
.Process(this->PenetratesGarrison_RandomTarget)
.Process(this->PenetratesGarrison_DamageMultiplier)
.Process(this->PenetratesGarrison_CleanSound)

.Process(this->CanKill)

.Process(this->ReverseEngineer)
Expand Down
11 changes: 11 additions & 0 deletions src/Ext/WarheadType/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,11 @@ class WarheadTypeExt final : public AbstractTypeExt
Valueable<bool> PenetratesTransport_DamageAll;
ValueableIdx<VocClass> PenetratesTransport_CleanSound;

Valueable<bool> PenetratesGarrison;
Valueable<bool> PenetratesGarrison_RandomTarget;
Valueable<PartialVector2D<double>> PenetratesGarrison_DamageMultiplier;
NullableIdx<VocClass> PenetratesGarrison_CleanSound;

Valueable<bool> Taunt;

Nullable<StackingMode> Psychedelic_StackingMode;
Expand Down Expand Up @@ -512,6 +517,11 @@ class WarheadTypeExt final : public AbstractTypeExt
, PenetratesTransport_DamageAll { false }
, PenetratesTransport_CleanSound { -1 }

, PenetratesGarrison { false }
, PenetratesGarrison_RandomTarget { true }
, PenetratesGarrison_DamageMultiplier { { 1.0, 1.0 } }
, PenetratesGarrison_CleanSound { }

, AffectsEnemies { true }
, AffectsOwner {}
, EffectsRequireVerses { true }
Expand Down Expand Up @@ -613,6 +623,7 @@ class WarheadTypeExt final : public AbstractTypeExt
void ApplyReverseEngineer(HouseClass* pHouse, TechnoClass* pTarget);
void ApplyReturnWarhead(HouseClass* pHouse, TechnoClass* pTarget, TechnoClass* Owner);
void ApplyPenetratesTransport(TechnoClass* pTarget, TechnoClass* pInvoker, HouseClass* pInvokerHouse, const CoordStruct& coords, int damage, int distance);
void ApplyPenetratesGarrison(HouseClass* pInvokerHouse, TechnoClass* pTarget, TechnoClass* pInvoker, int damage, const CoordStruct& coords);
double GetCritChance(TechnoClass* pFirer) const;
void ApplyAmmoModifier(TechnoClass* pTarget);

Expand Down
56 changes: 55 additions & 1 deletion src/Ext/WarheadType/Detonate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@ void WarheadTypeExt::DetonateOnOneUnit(HouseClass* pHouse, TechnoClass* pTarget,
if (this->PenetratesTransport_Level > 0 && damage)
this->ApplyPenetratesTransport(pTarget, pOwner, pHouse, coords, damage, distance);

if (this->PenetratesGarrison && damage)
this->ApplyPenetratesGarrison(pHouse, pTarget, pOwner, damage, coords);

if (this->Taunt && pOwner)
pTarget->Override_Mission(Mission::Attack, pOwner, nullptr);

Expand Down Expand Up @@ -923,11 +926,62 @@ void WarheadTypeExt::ApplyPenetratesTransport(TechnoClass* pTarget, TechnoClass*
}
}

void WarheadTypeExt::ExtData::ApplyAmmoModifier(TechnoClass* pTarget)
void WarheadTypeExt::ApplyAmmoModifier(TechnoClass* pTarget)
{
const int maxAmmo = pTarget->GetTechnoType()->Ammo;
int newCurrentAmmo = this->Ammo + pTarget->Ammo;

newCurrentAmmo = newCurrentAmmo < 0 ? 0 : newCurrentAmmo;
pTarget->Ammo = newCurrentAmmo > maxAmmo ? maxAmmo : newCurrentAmmo;
}

void WarheadTypeExt::ApplyPenetratesGarrison(HouseClass* pInvokerHouse, TechnoClass* pTarget, TechnoClass* pInvoker, int damage, const CoordStruct& coords)
{
auto const pBuilding = abstract_cast<BuildingClass*, true>(pTarget);

if (!pBuilding || !pBuilding->Occupants.Count)
return;

auto const pTargetTypeExt = TechnoTypeExt::Fetch(pBuilding->Type);

if (!pTargetTypeExt->PenetratesGarrison_Allowed)
return;

auto const pWH = this->OwnerObject();
auto const& location = pTarget->GetCenterCoords();
const int occupantIndex = this->PenetratesGarrison_RandomTarget ? ScenarioClass::Instance->Random.RandomRanged(0, pBuilding->Occupants.Count - 1) : -1;
const int distance = static_cast<int>(location.DistanceFrom(coords));

auto doDamage = [=](int index)
{
auto const pPassenger = pBuilding->Occupants.GetItem(index);
auto const pType = pPassenger->Type;
auto const pTypeExt = TechnoTypeExt::Fetch(pType);

if (!pTypeExt->PenetratesGarrison_Allowed)
return;

pPassenger->SetLocation(location);

int applyDamage = static_cast<int>(damage * GeneralUtils::GetRangedRandomOrSingleValue(this->PenetratesGarrison_DamageMultiplier));
pPassenger->ReceiveDamage(&applyDamage, distance, pWH, pInvoker, false, true, pInvokerHouse);
};

if (occupantIndex < 0)
{
for (int i = pBuilding->Occupants.Count - 1; i >= 0; i--)
doDamage(i);
}
else
{
doDamage(occupantIndex);
}

if (!pBuilding->Occupants.Count)
{
pBuilding->Mark(MarkType::Change);

if (this->PenetratesGarrison_CleanSound.isset())
VocClass::PlayAt(this->PenetratesGarrison_CleanSound.Get(), location);
}
}
Loading