Skip to content

[Highly Customized] Allow merging AOE damage to buildings into one #1504

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from
1 change: 1 addition & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ This page lists all the individual contributions to the project by their author.
- Fix for sidebar not updating queued unit numbers when on hold
- New Parabola trajectory
- Enhanced Bombard trajectory
- Allow merging AOE damage to buildings into one
- **Ollerus**
- Build limit group enhancement
- Customizable rocker amplitude
Expand Down
18 changes: 18 additions & 0 deletions docs/New-or-Enhanced-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -1867,6 +1867,24 @@ In `rulesmd.ini`:
NotHuman.DeathSequence= ; integer (1 to 5)
```

### Allow merging AOE damage to buildings into one

- Warheads are now able to damage building only once by merging the AOE damage when setting `MergeBuildingDamage` to true, which default to `[CombatDamage]->MergeBuildingDamage`.

In `rulesmd.ini`:
```ini
[CombatDamage]
MergeBuildingDamage=false ; boolean

[SOMEWARHEAD] ; Warhead
MergeBuildingDamage= ; boolean
```

```{note}
- This is different from `CellSpread.MaxAffect`.
- Due to the rounding of damage, there may be a slight increase in damage.
```

## Weapons

### AreaFire target customization
Expand Down
1 change: 1 addition & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ New:
- Enhanced Bombard trajectory (by CrimRecya & Ollerus, based on knowledge of NaotoYuuki)
- Toggle waypoint for building (by TaranDahl)
- Bunkerable checks dehardcode (by TaranDahl)
- Allow merging AOE damage to buildings into one (by CrimRecya)

Vanilla fixes:
- Aircraft will now behave as expected according to it's `MovementZone` and `SpeedType` when moving onto different surfaces. In particular, this fixes erratic behavior when vanilla aircraft is ordered to move onto water surface and instead the movement order changes to a shore nearby (by CrimRecya)
Expand Down
3 changes: 3 additions & 0 deletions src/Ext/Rules/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI)
this->Vehicles_DefaultDigitalDisplayTypes.Read(exINI, GameStrings::AudioVisual, "Vehicles.DefaultDigitalDisplayTypes");
this->Aircraft_DefaultDigitalDisplayTypes.Read(exINI, GameStrings::AudioVisual, "Aircraft.DefaultDigitalDisplayTypes");

this->MergeBuildingDamage.Read(exINI, GameStrings::CombatDamage, "MergeBuildingDamage");

this->AircraftLevelLightMultiplier.Read(exINI, GameStrings::AudioVisual, "AircraftLevelLightMultiplier");
this->JumpjetLevelLightMultiplier.Read(exINI, GameStrings::AudioVisual, "JumpjetLevelLightMultiplier");

Expand Down Expand Up @@ -399,6 +401,7 @@ void RulesExt::ExtData::Serialize(T& Stm)
.Process(this->ShowDesignatorRange)
.Process(this->DropPodTrailer)
.Process(this->PodImage)
.Process(this->MergeBuildingDamage)
.Process(this->AircraftLevelLightMultiplier)
.Process(this->JumpjetLevelLightMultiplier)
.Process(this->VoxelLightSource)
Expand Down
3 changes: 3 additions & 0 deletions src/Ext/Rules/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ class RulesExt
Valueable<AnimTypeClass*> Promote_VeteranAnimation;
Valueable<AnimTypeClass*> Promote_EliteAnimation;

Valueable<bool> MergeBuildingDamage;

Valueable<double> AircraftLevelLightMultiplier;
Valueable<double> JumpjetLevelLightMultiplier;

Expand Down Expand Up @@ -296,6 +298,7 @@ class RulesExt
, ShowDesignatorRange { true }
, DropPodTrailer { }
, PodImage { }
, MergeBuildingDamage { false }
, AircraftLevelLightMultiplier { 1.0 }
, JumpjetLevelLightMultiplier { 0.0 }
, VoxelLightSource { }
Expand Down
4 changes: 4 additions & 0 deletions src/Ext/WarheadType/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,8 @@ void WarheadTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)

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

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

this->CombatLightDetailLevel.Read(exINI, pSection, "CombatLightDetailLevel");
this->CombatLightChance.Read(exINI, pSection, "CombatLightChance");
this->CLIsBlack.Read(exINI, pSection, "CLIsBlack");
Expand Down Expand Up @@ -487,6 +489,8 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm)

.Process(this->Nonprovocative)

.Process(this->MergeBuildingDamage)

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

Valueable<bool> Nonprovocative;

Nullable<bool> MergeBuildingDamage;

Nullable<int> CombatLightDetailLevel;
Valueable<double> CombatLightChance;
Valueable<bool> CLIsBlack;
Expand Down Expand Up @@ -295,6 +297,8 @@ class WarheadTypeExt

, Nonprovocative { false }

, MergeBuildingDamage {}

, CombatLightDetailLevel {}
, CombatLightChance { 1.0 }
, CLIsBlack { false }
Expand Down
73 changes: 73 additions & 0 deletions src/Ext/WarheadType/Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,79 @@ DEFINE_HOOK(0x489B49, MapClass_DamageArea_Rocker, 0xA)
return 0x489B53;
}

#pragma region MergeBuildingDamage

DEFINE_HOOK(0x4899DA, DamageArea_DamageBuilding_CauseMergeBuildingDamage, 0x7)
{
GET_BASE(WarheadTypeClass* const, pWH, 0x0C);

if (!WarheadTypeExt::ExtMap.Find(pWH)->MergeBuildingDamage.Get(RulesExt::Global()->MergeBuildingDamage))
return 0;

struct DamageGroup
{
ObjectClass* Target;
int Distance;
};

GET_STACK(const DynamicVectorClass<DamageGroup*>, groups, STACK_OFFSET(0xE0, -0xA8));
GET_STACK(const bool, invincibleWithoutPenetrateAndCloseTo, STACK_OFFSET(0xE0, -0xC9));
GET_STACK(const int, baseDamage, STACK_OFFSET(0xE0, -0xBC));
GET_BASE(TechnoClass* const, pAttacker, 0x08);
GET_BASE(HouseClass* const, pAttackHouse, 0x14);

// Because during the process of causing damage, fragments may be generated that need to continue causing damage, resulting in nested calls
// to this function. Therefore, a single global variable cannot be used to store this data.
std::unordered_map<BuildingClass*, double> MapBuildings;
{
const auto cellSpread = Game::F2I(pWH->CellSpread * Unsorted::LeptonsPerCell);
const auto percentDifference = 1.0 - pWH->PercentAtMax; // Vanilla will first multiply the damage and round it up, but we don't need to.

for (const auto& group : groups)
{
if (const auto pBuilding = abstract_cast<BuildingClass*>(group->Target))
{
if (group->Distance > cellSpread)
continue;

const auto multiplier = (cellSpread && percentDifference) ? 1.0 - (percentDifference * group->Distance / cellSpread) : 1.0;
MapBuildings[pBuilding] += multiplier > 0 ? multiplier : 0;
}
}
}

for (const auto& group : groups) // Causing damage to the building alone and avoiding repeated injuries later.
{
if (const auto pBuilding = abstract_cast<BuildingClass*>(group->Target))
{
if (pBuilding->IsAlive && !pBuilding->Type->InvisibleInGame && (!invincibleWithoutPenetrateAndCloseTo || pBuilding->IsIronCurtained())
&& pBuilding->Health > 0 && pBuilding->IsOnMap && !pBuilding->InLimbo && MapBuildings.contains(pBuilding))
{
auto receiveDamage = Game::F2I(baseDamage * MapBuildings[pBuilding]);
MapBuildings.erase(pBuilding);

if (!receiveDamage && baseDamage)
receiveDamage = Math::sgn(baseDamage);

pBuilding->ReceiveDamage(&receiveDamage, 0, pWH, pAttacker, false, false, pAttackHouse);
}
}
}

return 0;
}

DEFINE_HOOK(0x489A1B, DamageArea_DamageBuilding_SkipVanillaBuildingDamage, 0x6)
{
enum { SkipGameCode = 0x489AC1 };

GET_BASE(WarheadTypeClass* const, pWH, 0x0C);

return WarheadTypeExt::ExtMap.Find(pWH)->MergeBuildingDamage.Get(RulesExt::Global()->MergeBuildingDamage) ? SkipGameCode : 0;
}

#pragma endregion

#pragma region Nonprovocative

// Do not retaliate against being hit by these Warheads.
Expand Down