Skip to content

Commit 23ad366

Browse files
committed
PreventNegativeDamage
1 parent 2db1169 commit 23ad366

File tree

7 files changed

+75
-48
lines changed

7 files changed

+75
-48
lines changed

docs/New-or-Enhanced-Logics.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ This page describes all the engine features that are either new and introduced b
7676
- `DisableWeapons` can be used to disable ability to fire any and all weapons.
7777
- On TechnoTypes with `OpenTopped=true`, `OpenTopped.CheckTransportDisableWeapons` can be set to true to make passengers not be able to fire out if transport's weapons are disabled by `DisableWeapons`.
7878
- `Unkillable` can be used to prevent the techno from being killed by taken damage (minimum health will be 1).
79+
- `PreventNegativeDamage` can be used to prevent the techno from taking negative damage. This includes both negative `Damage` and negative `Verses`.
7980
- It is possible to set groups for attach effect types by defining strings in `Groups`.
8081
- Groups can be used instead of types for removing effects and weapon filters.
8182

@@ -186,6 +187,7 @@ ReflectDamage.Override= ; integer
186187
ReflectDamage.UseInvokerAsOwner=false ; boolean
187188
DisableWeapons=false ; boolean
188189
Unkillable=false ; boolean
190+
PreventNegativeDamage=false ; boolean
189191
Groups= ; comma-separated list of strings (group IDs)
190192

191193
[SOMETECHNO] ; TechnoType

src/Ext/Techno/Body.Update.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1819,6 +1819,7 @@ void TechnoExt::ExtData::RecalculateStatMultipliers()
18191819
bool forceDecloak = false;
18201820
bool disableWeapons = false;
18211821
bool unkillable = false;
1822+
bool preventNegativeDamage = false;
18221823
bool hasRangeModifier = false;
18231824
bool hasTint = false;
18241825
bool reflectsDamage = false;
@@ -1842,6 +1843,7 @@ void TechnoExt::ExtData::RecalculateStatMultipliers()
18421843
forceDecloak |= type->ForceDecloak;
18431844
disableWeapons |= type->DisableWeapons;
18441845
unkillable |= type->Unkillable;
1846+
preventNegativeDamage |= type->PreventNegativeDamage;
18451847
hasRangeModifier |= (type->WeaponRange_ExtraRange != 0.0 || type->WeaponRange_Multiplier != 0.0);
18461848
hasTint |= type->HasTint();
18471849
reflectsDamage |= type->ReflectDamage;
@@ -1860,6 +1862,7 @@ void TechnoExt::ExtData::RecalculateStatMultipliers()
18601862
pAE.ForceDecloak = forceDecloak;
18611863
pAE.DisableWeapons = disableWeapons;
18621864
pAE.Unkillable = unkillable;
1865+
pAE.PreventNegativeDamage = preventNegativeDamage;
18631866
pAE.HasRangeModifier = hasRangeModifier;
18641867
pAE.HasTint = hasTint;
18651868
pAE.ReflectDamage = reflectsDamage;

src/Ext/Techno/Hooks.ReceiveDamage.cpp

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6)
2525

2626
const auto pSourceHouse = args->SourceHouse;
2727
const auto pTargetHouse = pThis->Owner;
28+
int& damage = *args->Damage;
2829

2930
// Calculate Damage Multiplier
30-
if (!args->IgnoreDefenses && *args->Damage)
31+
if (!args->IgnoreDefenses && damage)
3132
{
3233
double multiplier = 1.0;
3334

@@ -40,14 +41,14 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6)
4041

4142
if (multiplier != 1.0)
4243
{
43-
const auto sgnDamage = *args->Damage > 0 ? 1 : -1;
44-
const auto calculateDamage = static_cast<int>(*args->Damage * multiplier);
45-
*args->Damage = calculateDamage ? calculateDamage : sgnDamage;
44+
const auto sgnDamage = damage > 0 ? 1 : -1;
45+
const auto calculateDamage = static_cast<int>(damage * multiplier);
46+
damage = calculateDamage ? calculateDamage : sgnDamage;
4647
}
4748
}
4849

4950
// Raise Combat Alert
50-
if (pRules->CombatAlert && *args->Damage > 1)
51+
if (pRules->CombatAlert && damage > 1)
5152
{
5253
auto raiseCombatAlert = [&]()
5354
{
@@ -104,7 +105,7 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6)
104105
// Shield Receive Damage
105106
if (!args->IgnoreDefenses)
106107
{
107-
int nDamageLeft = *args->Damage;
108+
int nDamageLeft = damage;
108109

109110
if (const auto pShieldData = pExt->Shield.get())
110111
{
@@ -114,9 +115,9 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6)
114115

115116
if (nDamageLeft >= 0)
116117
{
117-
*args->Damage = nDamageLeft;
118+
damage = nDamageLeft;
118119

119-
if (auto pTag = pThis->AttachedTag)
120+
if (const auto pTag = pThis->AttachedTag)
120121
pTag->RaiseEvent((TriggerEvent)PhobosTriggerEvent::ShieldBroken, pThis, CellStruct::Empty);
121122
}
122123

@@ -131,11 +132,17 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6)
131132
&& MapClass::GetTotalDamage(nDamageLeft, args->WH, pThis->GetTechnoType()->Armor, args->DistanceToEpicenter) >= pThis->Health)
132133
{
133134
// Update remaining damage and check if the target will die and should be avoided
134-
*args->Damage = 0;
135+
damage = 0;
135136
pThis->Health = 1;
136137
pThis->EstimatedHealth = 1;
137138
ReceiveDamageTemp::SkipLowDamageCheck = true;
138139
}
140+
141+
if (pExt->AE.PreventNegativeDamage && nDamageLeft != 0 && pWHExt->CanTargetHouse(pSourceHouse, pThis)
142+
&& MapClass::GetTotalDamage(nDamageLeft, args->WH, pThis->GetTechnoType()->Armor, args->DistanceToEpicenter) < 0)
143+
{
144+
damage = 0;
145+
}
139146
}
140147

141148
return 0;

src/New/Entity/AttachEffectClass.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ struct AttachEffectTechnoProperties
9797
bool ForceDecloak;
9898
bool DisableWeapons;
9999
bool Unkillable;
100+
bool PreventNegativeDamage;
100101
bool HasRangeModifier;
101102
bool HasTint;
102103
bool ReflectDamage;
@@ -115,6 +116,7 @@ struct AttachEffectTechnoProperties
115116
, ForceDecloak { false }
116117
, DisableWeapons { false }
117118
, Unkillable { false }
119+
, PreventNegativeDamage { false }
118120
, HasRangeModifier { false }
119121
, HasTint { false }
120122
, ReflectDamage { false }

src/New/Entity/ShieldClass.cpp

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -160,13 +160,17 @@ bool ShieldClass::ShieldIsBrokenTEvent(ObjectClass* pAttached)
160160

161161
int ShieldClass::ReceiveDamage(args_ReceiveDamage* args)
162162
{
163-
if (!this->HP || this->Temporal || *args->Damage == 0)
164-
return *args->Damage;
163+
int& damage = *args->Damage;
164+
165+
if (!this->HP || this->Temporal || damage == 0)
166+
return damage;
167+
168+
auto const pTechno = this->Techno;
165169

166170
// Handle a special case where parasite damages shield but not the unit and unit itself cannot be targeted by repair weapons.
167-
if (*args->Damage < 0)
171+
if (damage < 0)
168172
{
169-
if (auto const pFoot = abstract_cast<FootClass*, true>(this->Techno))
173+
if (auto const pFoot = abstract_cast<FootClass*, true>(pTechno))
170174
{
171175
if (auto const pParasite = pFoot->ParasiteEatingMe)
172176
{
@@ -177,59 +181,64 @@ int ShieldClass::ReceiveDamage(args_ReceiveDamage* args)
177181
}
178182
}
179183

180-
auto const pWHExt = WarheadTypeExt::ExtMap.Find(args->WH);
181-
bool IC = pWHExt->CanAffectInvulnerable(this->Techno);
184+
auto const pWH = args->WH;
185+
auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWH);
186+
const bool IC = pWHExt->CanAffectInvulnerable(pTechno);
182187

183-
if (!IC || CanBePenetrated(args->WH) || this->Techno->GetTechnoType()->Immune || TechnoExt::IsTypeImmune(this->Techno, args->Attacker))
184-
return *args->Damage;
188+
if (!IC || CanBePenetrated(pWH) || pTechno->GetTechnoType()->Immune || TechnoExt::IsTypeImmune(pTechno, args->Attacker))
189+
return damage;
185190

186191
int nDamage = 0;
187192
int shieldDamage = 0;
188193
int healthDamage = 0;
194+
auto const pType = this->Type;
189195

190-
if (pWHExt->CanTargetHouse(args->SourceHouse, this->Techno) && !args->WH->Temporal)
196+
if (pWHExt->CanTargetHouse(args->SourceHouse, pTechno) && !pWH->Temporal)
191197
{
192-
if (*args->Damage > 0)
193-
nDamage = MapClass::GetTotalDamage(*args->Damage, args->WH, this->GetArmorType(), args->DistanceToEpicenter);
198+
if (damage > 0)
199+
nDamage = MapClass::GetTotalDamage(damage, pWH, this->GetArmorType(), args->DistanceToEpicenter);
194200
else
195-
nDamage = -MapClass::GetTotalDamage(-*args->Damage, args->WH, this->GetArmorType(), args->DistanceToEpicenter);
201+
nDamage = -MapClass::GetTotalDamage(damage, pWH, this->GetArmorType(), args->DistanceToEpicenter);
196202

197-
bool affectsShield = pWHExt->Shield_AffectTypes.size() <= 0 || pWHExt->Shield_AffectTypes.Contains(this->Type);
198-
double absorbPercent = affectsShield ? pWHExt->Shield_AbsorbPercent.Get(this->Type->AbsorbPercent) : this->Type->AbsorbPercent;
199-
double passPercent = affectsShield ? pWHExt->Shield_PassPercent.Get(this->Type->PassPercent) : this->Type->PassPercent;
203+
const bool affectsShield = pWHExt->Shield_AffectTypes.size() <= 0 || pWHExt->Shield_AffectTypes.Contains(pType);
204+
const double absorbPercent = affectsShield ? pWHExt->Shield_AbsorbPercent.Get(pType->AbsorbPercent) : pType->AbsorbPercent;
205+
const double passPercent = affectsShield ? pWHExt->Shield_PassPercent.Get(pType->PassPercent) : pType->PassPercent;
200206

201207
shieldDamage = (int)((double)nDamage * absorbPercent);
202208
// passthrough damage shouldn't be affected by shield armor
203-
healthDamage = (int)((double)*args->Damage * passPercent);
209+
healthDamage = (int)((double)damage * passPercent);
204210
}
205211

206-
int originalShieldDamage = shieldDamage;
207-
int min = pWHExt->Shield_ReceivedDamage_Minimum.Get(this->Type->ReceivedDamage_Minimum);
208-
int max = pWHExt->Shield_ReceivedDamage_Maximum.Get(this->Type->ReceivedDamage_Maximum);
209-
int minDmg = static_cast<int>(min * pWHExt->Shield_ReceivedDamage_MinMultiplier);
210-
int maxDmg = static_cast<int>(max * pWHExt->Shield_ReceivedDamage_MaxMultiplier);
212+
const int originalShieldDamage = shieldDamage;
213+
const int min = pWHExt->Shield_ReceivedDamage_Minimum.Get(pType->ReceivedDamage_Minimum);
214+
const int max = pWHExt->Shield_ReceivedDamage_Maximum.Get(pType->ReceivedDamage_Maximum);
215+
const int minDmg = static_cast<int>(min * pWHExt->Shield_ReceivedDamage_MinMultiplier);
216+
const int maxDmg = static_cast<int>(max * pWHExt->Shield_ReceivedDamage_MaxMultiplier);
211217
shieldDamage = Math::clamp(shieldDamage, minDmg, maxDmg);
212218

219+
if (shieldDamage < 0 && TechnoExt::ExtMap.Find(pTechno)->AE.PreventNegativeDamage)
220+
shieldDamage = 0;
221+
213222
if (Phobos::DisplayDamageNumbers && shieldDamage != 0)
214-
GeneralUtils::DisplayDamageNumberString(shieldDamage, DamageDisplayType::Shield, this->Techno->GetRenderCoords(), TechnoExt::ExtMap.Find(this->Techno)->DamageNumberOffset);
223+
GeneralUtils::DisplayDamageNumberString(shieldDamage, DamageDisplayType::Shield, pTechno->GetRenderCoords(), TechnoExt::ExtMap.Find(pTechno)->DamageNumberOffset);
215224

216225
if (shieldDamage > 0)
217226
{
218-
bool whModifiersApplied = this->Timers.SelfHealing_WHModifier.InProgress();
219-
bool restart = whModifiersApplied ? this->SelfHealing_RestartInCombat_Warhead : this->Type->SelfHealing_RestartInCombat;
227+
const bool whModifiersApplied = this->Timers.SelfHealing_WHModifier.InProgress();
228+
const bool restart = whModifiersApplied ? this->SelfHealing_RestartInCombat_Warhead : pType->SelfHealing_RestartInCombat;
220229

221230
if (restart)
222231
{
223-
int delay = whModifiersApplied ? this->SelfHealing_RestartInCombatDelay_Warhead : this->Type->SelfHealing_RestartInCombatDelay;
232+
const int delay = whModifiersApplied ? this->SelfHealing_RestartInCombatDelay_Warhead : pType->SelfHealing_RestartInCombatDelay;
224233

225234
if (delay > 0)
226235
{
227-
this->Timers.SelfHealing_CombatRestart.Start(this->Type->SelfHealing_RestartInCombatDelay);
236+
this->Timers.SelfHealing_CombatRestart.Start(pType->SelfHealing_RestartInCombatDelay);
228237
this->Timers.SelfHealing.Stop();
229238
}
230239
else
231240
{
232-
const int rate = whModifiersApplied ? this->SelfHealing_Rate_Warhead : this->Type->SelfHealing_Rate;
241+
const int rate = whModifiersApplied ? this->SelfHealing_Rate_Warhead : pType->SelfHealing_Rate;
233242
this->Timers.SelfHealing.Start(rate); // when attacked, restart the timer
234243
}
235244
}
@@ -238,41 +247,41 @@ int ShieldClass::ReceiveDamage(args_ReceiveDamage* args)
238247
this->ResponseAttack();
239248

240249
if (pWHExt->DecloakDamagedTargets)
241-
this->Techno->Uncloak(false);
250+
pTechno->Uncloak(false);
242251

243-
int residueDamage = shieldDamage - this->HP;
252+
const int residueDamage = shieldDamage - this->HP;
244253

245254
if (residueDamage >= 0)
246255
{
247-
int actualResidueDamage = Math::max(0, int((double)(originalShieldDamage - this->HP) /
248-
GeneralUtils::GetWarheadVersusArmor(args->WH, this->GetArmorType()))); //only absord percentage damage
256+
const int actualResidueDamage = Math::max(0, int((double)(originalShieldDamage - this->HP) /
257+
GeneralUtils::GetWarheadVersusArmor(pWH, this->GetArmorType()))); //only absord percentage damage
249258

250259
this->BreakShield(pWHExt->Shield_BreakAnim, pWHExt->Shield_BreakWeapon.Get(nullptr));
251260

252-
return this->Type->AbsorbOverDamage ? healthDamage : actualResidueDamage + healthDamage;
261+
return pType->AbsorbOverDamage ? healthDamage : actualResidueDamage + healthDamage;
253262
}
254263
else
255264
{
256-
if (this->Type->HitFlash && pWHExt->Shield_HitFlash)
265+
if (pType->HitFlash && pWHExt->Shield_HitFlash)
257266
{
258-
int size = this->Type->HitFlash_FixedSize.Get((shieldDamage * 2));
267+
const int size = pType->HitFlash_FixedSize.Get((shieldDamage * 2));
259268
SpotlightFlags flags = SpotlightFlags::NoColor;
260269

261-
if (this->Type->HitFlash_Black)
270+
if (pType->HitFlash_Black)
262271
{
263272
flags = SpotlightFlags::NoColor;
264273
}
265274
else
266275
{
267-
if (!this->Type->HitFlash_Red)
276+
if (!pType->HitFlash_Red)
268277
flags = SpotlightFlags::NoRed;
269-
if (!this->Type->HitFlash_Green)
278+
if (!pType->HitFlash_Green)
270279
flags |= SpotlightFlags::NoGreen;
271-
if (!this->Type->HitFlash_Blue)
280+
if (!pType->HitFlash_Blue)
272281
flags |= SpotlightFlags::NoBlue;
273282
}
274283

275-
MapClass::FlashbangWarheadAt(size, args->WH, this->Techno->Location, true, flags);
284+
MapClass::FlashbangWarheadAt(size, pWH, pTechno->Location, true, flags);
276285
}
277286

278287
if (!pWHExt->Shield_SkipHitAnim)

src/New/Type/AttachEffectTypeClass.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI)
186186

187187
this->DisableWeapons.Read(exINI, pSection, "DisableWeapons");
188188
this->Unkillable.Read(exINI, pSection, "Unkillable");
189+
this->PreventNegativeDamage.Read(exINI, pSection, "PreventNegativeDamage");
189190

190191
// Groups
191192
exINI.ParseStringList(this->Groups, pSection, "Groups");
@@ -274,6 +275,7 @@ void AttachEffectTypeClass::Serialize(T& Stm)
274275
.Process(this->ReflectDamage_UseInvokerAsOwner)
275276
.Process(this->DisableWeapons)
276277
.Process(this->Unkillable)
278+
.Process(this->PreventNegativeDamage)
277279
.Process(this->Groups)
278280
;
279281
}

src/New/Type/AttachEffectTypeClass.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ class AttachEffectTypeClass final : public Enumerable<AttachEffectTypeClass>
118118
Valueable<bool> ReflectDamage_UseInvokerAsOwner;
119119
Valueable<bool> DisableWeapons;
120120
Valueable<bool> Unkillable;
121+
Valueable<bool> PreventNegativeDamage;
121122

122123
std::vector<std::string> Groups;
123124

@@ -200,6 +201,7 @@ class AttachEffectTypeClass final : public Enumerable<AttachEffectTypeClass>
200201
, ReflectDamage_UseInvokerAsOwner { false }
201202
, DisableWeapons { false }
202203
, Unkillable { false }
204+
, PreventNegativeDamage { false }
203205
, Groups {}
204206
{};
205207

0 commit comments

Comments
 (0)