Skip to content
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

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

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ This page lists all the individual contributions to the project by their author.
- `TurretOffset` support for SHP vehicles
- Customizable rocker amplitude
- Customizable wake anim
- Initial effort on optimization for crates' random distribution
- Initial effort on optimization for crates' random distribution
- **Fryone**
- Customizable ElectricBolt Arcs
- Sound entry on unit's creation
Expand All @@ -384,6 +384,7 @@ This page lists all the individual contributions to the project by their author.
- Enhanced Straight trajectory
- Enable Building Production Queue
- Fix for sidebar not updating queued unit numbers when on hold
- 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 @@ -1711,6 +1711,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 @@ -317,6 +317,7 @@ New:
- Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya)
- Custom exit cell for infantry factory (by Starkku)
- Option for vehicles to keep target when issued move command (by Starkku)
- 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 @@ -191,6 +191,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 @@ -382,6 +384,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 @@ -152,6 +152,8 @@ class RulesExt
Valueable<AnimTypeClass*> Promote_VeteranAnimation;
Valueable<AnimTypeClass*> Promote_EliteAnimation;

Valueable<bool> MergeBuildingDamage;

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

Expand Down Expand Up @@ -279,6 +281,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 @@ -484,6 +486,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 @@ -292,6 +294,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.
PhobosMap<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]);

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

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

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