diff --git a/CREDITS.md b/CREDITS.md index 838c670fad..4b9b810b8a 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -48,6 +48,7 @@ This page lists all the individual contributions to the project by their author. - Ability to disable shadow for debris & meteor animations - Voxel light source position customization - `UseFixedVoxelLighting` + - Warhead activation target health thresholds - **Uranusian (Thrifinesma)**: - Mind Control enhancement - Custom warhead splash list @@ -144,6 +145,7 @@ This page lists all the individual contributions to the project by their author. - Map Events 604 & 605 for checking if a specific Techno enters in a cell - Warhead that can not kill - `Pips.HideIfNoStrength` and `SelfHealing.EnabledBy` additions for shields + - Warhead activation target health thresholds enhancements - **Starkku**: - Misc. minor bugfixes & improvements - AI script actions: diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index 5b9c9df410..c513de22dc 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -1843,6 +1843,20 @@ Rocker.AmplitudeMultiplier=1.0 ; floating point value, multiplier Rocker.AmplitudeOverride= ; integer ``` +### Customizable Warhead trigger conditions + +- It is now possible to make warheads only trigger when target's HP is above and/or below certain percentage. + - Both conditions need to evaluate to true in order for the warhead to trigger. +- 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`). + +In `rulesmd.ini`: +```ini +[SOMEWARHEAD] ; WarheadType +AffectsAbovePercent=0.0 ; floating point value, percents or absolute +AffectsBelowPercent=1.0 ; floating point value, percents or absolute +EffectsRequireVerses=false ; boolean +``` + ### Customizable Warhead animation behaviour - It is possible to make game play random animation from `AnimList` by setting `AnimList.PickRandom` to true. The result is similar to what `EMEffect=true` produces, however it comes with no side-effects (`EMEffect=true` prevents `Inviso=true` projectiles from snapping on targets, making them miss moving targets). diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 83d462bad1..f744f247b2 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -1867,7 +1867,7 @@ FireUp.ResetInRetarget=true ; boolean All new Warhead effects - Can be used with `CellSpread` and Ares' GenericWarhead superweapon where applicable. - Cannot be used with `MindControl.Permanent=yes` of Ares. -- Respect `Verses` where applicable unless `EffectsRequireVerses` is set to false. If target has an active shield, its armor type is used instead unless warhead can penetrate the shield. +- Respect `Verses` where applicable unless `EffectsRequireVerses` is set to `false`. If target has an active shield, its armor type is used instead unless warhead can penetrate the shield. ``` ### Break Mind Control on impact diff --git a/docs/Whats-New.md b/docs/Whats-New.md index f59fcc7537..b5a2a29b4a 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -594,6 +594,7 @@ New: - Draw visual effects for airburst weapons (by CrimRecya) - Unit `Speed` setting now accepts floating point values (by Starkku) - `Strafing` is now disabled by default when using `Trajectory` (by CrimRecya) +- Warhead activation target health thresholds (by FS-21, Kerbiter) Vanilla fixes: - Allow AI to repair structures built from base nodes/trigger action 125/SW delivery in single player missions (by Trsdy) diff --git a/src/Ext/Bullet/Hooks.DetonateLogics.cpp b/src/Ext/Bullet/Hooks.DetonateLogics.cpp index 39e779da80..3bbf6ce281 100644 --- a/src/Ext/Bullet/Hooks.DetonateLogics.cpp +++ b/src/Ext/Bullet/Hooks.DetonateLogics.cpp @@ -10,17 +10,27 @@ #include #include -DEFINE_HOOK(0x4690D4, BulletClass_Logics_ScreenShake, 0x6) +DEFINE_HOOK(0x4690D4, BulletClass_Logics_NewChecks, 0x6) { - enum { SkipShaking = 0x469130 }; + enum { SkipShaking = 0x469130, GoToExtras = 0x469AA4 }; + GET(BulletClass*, pBullet, ESI); GET(WarheadTypeClass*, pWarhead, EAX); GET_BASE(CoordStruct*, pCoords, 0x8); - auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWarhead); + auto const pExt = WarheadTypeExt::ExtMap.Find(pWarhead); + + if (auto pTarget = abstract_cast(pBullet->Target)) + { + // Check if the WH should affect the techno target or skip it + if (!pExt->IsHealthInThreshold(pTarget) && pBullet->Owner != pBullet->Target) + return GoToExtras; + } + + // Check for ScreenShake auto&& [_, visible] = TacticalClass::Instance->CoordsToClient(*pCoords); - if (pWHExt->ShakeIsLocal && !visible) + if (pExt->ShakeIsLocal && !visible) return SkipShaking; return 0; @@ -310,6 +320,13 @@ DEFINE_HOOK(0x469AA4, BulletClass_Logics_Extras, 0x5) auto const pWH = pWeaponExt->ExtraWarheads[i]; int damage = defaultDamage; size_t size = pWeaponExt->ExtraWarheads_DamageOverrides.size(); + auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWH); + + if (auto const pTarget = abstract_cast(pThis->Target)) + { + if (!pWHExt->IsHealthInThreshold(pTarget)) + continue; + } if (size > i) damage = pWeaponExt->ExtraWarheads_DamageOverrides[i]; diff --git a/src/Ext/RadSite/Body.cpp b/src/Ext/RadSite/Body.cpp index 324d5eb252..a97b15ba59 100644 --- a/src/Ext/RadSite/Body.cpp +++ b/src/Ext/RadSite/Body.cpp @@ -16,6 +16,10 @@ void RadSiteExt::ExtData::Initialize() bool RadSiteExt::ExtData::ApplyRadiationDamage(TechnoClass* pTarget, int& damage) { const auto pWarhead = this->Type->GetWarhead(); + const auto pWHExt = WarheadTypeExt::ExtMap.Find(pWarhead); + + if (!pWHExt->IsHealthInThreshold(pTarget)) + return false; if (!this->Type->GetWarheadDetonate()) { @@ -25,14 +29,9 @@ bool RadSiteExt::ExtData::ApplyRadiationDamage(TechnoClass* pTarget, int& damage else { if (this->Type->GetWarheadDetonateFull()) - { WarheadTypeExt::DetonateAt(pWarhead, pTarget, this->RadInvoker, damage, this->RadHouse); - } else - { - const auto pWHExt = WarheadTypeExt::ExtMap.Find(pWarhead); pWHExt->DamageAreaWithTarget(pTarget->GetCoords(), damage, this->RadInvoker, pWarhead, true, this->RadHouse, pTarget); - } if (!pTarget->IsAlive) return false; diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index e9ab4c68af..e4e0de1fbc 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -35,12 +35,25 @@ bool WarheadTypeExt::ExtData::CanAffectTarget(TechnoClass* pTarget) const if (!pTarget) return false; + if (!IsHealthInThreshold(pTarget)) + return false; + if (!this->EffectsRequireVerses) return true; return GeneralUtils::GetWarheadVersusArmor(this->OwnerObject(), pTarget) != 0.0; } +bool WarheadTypeExt::ExtData::IsHealthInThreshold(TechnoClass* pTarget) const +{ + // Check if the WH should affect the techno target or skip it + double hp = pTarget->GetHealthPercentage(); + bool hpBelowPercent = hp <= this->AffectsBelowPercent; + bool hpAbovePercent = hp > this->AffectsAbovePercent; + + return hpBelowPercent && hpAbovePercent; +} + // Checks if Warhead can affect target that might or might be currently invulnerable. bool WarheadTypeExt::ExtData::CanAffectInvulnerable(TechnoClass* pTarget) const { @@ -274,6 +287,12 @@ void WarheadTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->AirstrikeTargets.Read(exINI, pSection, "AirstrikeTargets"); + this->AffectsAbovePercent.Read(exINI, pSection, "AffectsAbovePercent"); + this->AffectsBelowPercent.Read(exINI, pSection, "AffectsBelowPercent"); + + if (this->AffectsAbovePercent > this->AffectsBelowPercent) + Debug::Log("[Developer warning][%s] AffectsAbovePercent is bigger than AffectsBelowPercent, the warhead will never activate!\n", pSection); + // Convert.From & Convert.To TypeConvertGroup::Parse(this->Convert_Pairs, exINI, pSection, AffectedHouse::All); @@ -493,6 +512,9 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .Process(this->SuppressReflectDamage_Types) .Process(this->SuppressReflectDamage_Groups) + .Process(this->AffectsAbovePercent) + .Process(this->AffectsBelowPercent) + .Process(this->InflictLocomotor) .Process(this->RemoveInflictedLocomotor) diff --git a/src/Ext/WarheadType/Body.h b/src/Ext/WarheadType/Body.h index b6265fb55a..273699c3bf 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -177,6 +177,9 @@ class WarheadTypeExt Valueable AirstrikeTargets; + Valueable AffectsAbovePercent; + Valueable AffectsBelowPercent; + // Ares tags // http://ares-developers.github.io/Ares-docs/new/warheads/general.html Valueable AffectsEnemies; @@ -352,6 +355,9 @@ class WarheadTypeExt , AirstrikeTargets { AffectedTarget::Building } + , AffectsAbovePercent { 0.0 } + , AffectsBelowPercent { 1.0 } + , AffectsEnemies { true } , AffectsOwner {} , EffectsRequireVerses { true } @@ -386,6 +392,7 @@ class WarheadTypeExt bool CanAffectTarget(TechnoClass* pTarget) const; bool CanAffectInvulnerable(TechnoClass* pTarget) const; bool EligibleForFullMapDetonation(TechnoClass* pTechno, HouseClass* pOwner) const; + bool IsHealthInThreshold(TechnoClass* pTarget) const; virtual ~ExtData() = default; virtual void LoadFromINIFile(CCINIClass* pINI) override;