Skip to content

Commit dd84bce

Browse files
committed
Improved hit calculation according to SOC/CS/COP formulas (#382, #392)
Engine now correctly uses hit_fraction_actor/hit_fraction_npc/hit_fraction from COP/CS/SOC. I used a hacky solution to determine which formula to use, though: Since hit_fraction_actor exists both in CS and COP, but fire_wound_protection was removed in COP, its presence or absence is used to distinguish between CS and COP.
1 parent a4d85ba commit dd84bce

File tree

7 files changed

+249
-80
lines changed

7 files changed

+249
-80
lines changed

src/xrGame/ActorHelmet.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,8 @@ float CHelmet::HitThroughArmor(float hit_power, s16 element, float ap, bool& add
229229
if (!IsGameTypeSingle())
230230
{
231231
float hit_fraction = (ap - BoneArmor) / ap;
232-
if (hit_fraction < m_boneProtection->m_fHitFracActor)
233-
hit_fraction = m_boneProtection->m_fHitFracActor;
232+
if (hit_fraction < m_boneProtection->m_fHitFrac)
233+
hit_fraction = m_boneProtection->m_fHitFrac;
234234

235235
NewHitPower *= hit_fraction;
236236
NewHitPower *= m_boneProtection->getBoneProtection(element);
@@ -241,7 +241,7 @@ float CHelmet::HitThroughArmor(float hit_power, s16 element, float ap, bool& add
241241
else
242242
{
243243
//пуля НЕ пробила бронь
244-
NewHitPower *= m_boneProtection->m_fHitFracActor;
244+
NewHitPower *= m_boneProtection->m_fHitFrac;
245245
add_wound = false; //раны нет
246246
}
247247
}

src/xrGame/BoneProtections.cpp

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#include "BoneProtections.h"
33
#include "Include/xrRender/Kinematics.h"
44
#include "xrCore/Animation/Bone.hpp"
5-
#include "Level.h"
5+
#include "game_type.h"
66

77
float SBoneProtections::getBoneProtection(s16 bone_id) const
88
{
@@ -35,20 +35,34 @@ void SBoneProtections::reload(const shared_str& bone_sect, IKinematics* kinemati
3535
{
3636
VERIFY(kinematics);
3737

38-
float defaultHitFraction = 0.1f;
39-
if (ShadowOfChernobylMode || ClearSkyMode)
38+
if (m_hitFracType != HitFractionActorCS && m_hitFracType != HitFractionActorCOP)
4039
{
41-
defaultHitFraction = pSettings->read_if_exists<float>(bone_sect, "hit_fraction", defaultHitFraction);
40+
if (pSettings->line_exist(bone_sect, HIT_FRACTION_NPC))
41+
{
42+
m_hitFracType = HitFractionNPC;
43+
m_fHitFrac = pSettings->r_float(bone_sect, HIT_FRACTION_NPC);
44+
}
45+
else if (pSettings->line_exist(bone_sect, HIT_FRACTION))
46+
{
47+
m_hitFracType = HitFraction;
48+
m_fHitFrac = pSettings->r_float(bone_sect, HIT_FRACTION);
49+
}
50+
else
51+
{
52+
m_fHitFrac = 0.1f;
53+
m_hitFracType = HitFractionNPC;
54+
}
4255
}
43-
m_fHitFracNpc = pSettings->read_if_exists<float>(bone_sect, "hit_fraction_npc", defaultHitFraction);
4456

4557
m_bones_koeff.clear();
4658
m_bones_koeff[BI_NONE] = BoneProtection{};
4759

4860
CInifile::Sect& protections = pSettings->r_section(bone_sect);
4961
for (const auto& [name, value] : protections.Data)
5062
{
51-
if (xr_strcmp(name.c_str(), "hit_fraction") == 0)
63+
if (xr_strcmp(name.c_str(), HIT_FRACTION) == 0)
64+
continue;
65+
if (xr_strcmp(name.c_str(), HIT_FRACTION_NPC) == 0)
5266
continue;
5367

5468
string256 buffer;
@@ -85,17 +99,17 @@ void SBoneProtections::add(const shared_str& bone_sect, IKinematics* kinematics)
8599

86100
VERIFY(kinematics);
87101

88-
float defaultHitFraction = 0.0f;
89-
if (ShadowOfChernobylMode || ClearSkyMode)
90-
{
91-
defaultHitFraction = READ_IF_EXISTS(pSettings, r_float, bone_sect, "hit_fraction", defaultHitFraction);
92-
}
93-
m_fHitFracNpc += READ_IF_EXISTS(pSettings, r_float, bone_sect.c_str(), "hit_fraction_npc", defaultHitFraction);
102+
if (m_hitFracType == HitFractionNPC)
103+
m_fHitFrac += pSettings->read_if_exists<float>(bone_sect, HIT_FRACTION_NPC, 0.0f);
104+
else if (m_hitFracType == HitFraction)
105+
m_fHitFrac += pSettings->read_if_exists<float>(bone_sect, HIT_FRACTION, 0.0f);
94106

95107
CInifile::Sect& protections = pSettings->r_section(bone_sect);
96108
for (const auto& [name, value] : protections.Data)
97109
{
98-
if (xr_strcmp(name.c_str(), "hit_fraction") == 0)
110+
if (xr_strcmp(name.c_str(), HIT_FRACTION) == 0)
111+
continue;
112+
if (xr_strcmp(name.c_str(), HIT_FRACTION_NPC) == 0)
99113
continue;
100114

101115
string256 buffer;

src/xrGame/BoneProtections.h

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,24 @@ class IKinematics;
44

55
struct SBoneProtections
66
{
7+
static constexpr cpcstr HIT_FRACTION = "hit_fraction";
8+
static constexpr cpcstr HIT_FRACTION_NPC = "hit_fraction_npc";
9+
10+
enum HitFractionType : u8
11+
{
12+
// Exists in SOC and CS
13+
HitFraction,
14+
15+
// Introduced in CS, externally assigned
16+
HitFractionActorCS,
17+
18+
// Introduced in COP
19+
HitFractionNPC,
20+
21+
// Hit formula changed in COP, externally assigned
22+
HitFractionActorCOP,
23+
};
24+
725
struct BoneProtection
826
{
927
float koeff{ 1.0f };
@@ -13,8 +31,8 @@ struct SBoneProtections
1331

1432
mutable xr_map<s16, BoneProtection> m_bones_koeff;
1533

16-
float m_fHitFracNpc{ 0.1f };
17-
float m_fHitFracActor{ 0.1f };
34+
float m_fHitFrac{ 0.1f };
35+
HitFractionType m_hitFracType{ HitFractionNPC };
1836

1937
[[nodiscard]] float getBoneProtection(s16 bone_id) const;
2038
[[nodiscard]] float getBoneArmor(s16 bone_id) const;

src/xrGame/CustomOutfit.cpp

Lines changed: 158 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,24 @@ void CCustomOutfit::Load(LPCSTR section)
6666
m_HitTypeProtection[ALife::eHitTypeTelepatic] = pSettings->r_float(section, "telepatic_protection");
6767
m_HitTypeProtection[ALife::eHitTypeChemicalBurn] = pSettings->r_float(section, "chemical_burn_protection");
6868
m_HitTypeProtection[ALife::eHitTypeExplosion] = pSettings->r_float(section, "explosion_protection");
69-
m_HitTypeProtection[ALife::eHitTypeFireWound] = 0.f; // pSettings->r_float(section,"fire_wound_protection");
69+
m_HitTypeProtection[ALife::eHitTypeFireWound] = pSettings->read_if_exists<float>(section, "fire_wound_protection", 0.0f);
7070
m_HitTypeProtection[ALife::eHitTypePhysicStrike] = pSettings->read_if_exists<float>(
7171
section, "physic_strike_protection", m_HitTypeProtection[ALife::eHitTypeStrike]);
7272
m_HitTypeProtection[ALife::eHitTypeLightBurn] = m_HitTypeProtection[ALife::eHitTypeBurn];
73-
m_boneProtection->m_fHitFracActor = pSettings->read_if_exists<float>(section, "hit_fraction_actor", 0.1f);
73+
74+
if (pSettings->line_exist(section, "hit_fraction_actor"))
75+
{
76+
m_boneProtection->m_fHitFrac = pSettings->r_float(section, "hit_fraction_actor");
77+
78+
// Since hit_fraction_actor exists both in CS and COP, but fire_wound_protection was removed in COP,
79+
// We can use this hacky solution to determine which damage formula to use.
80+
// It not robust for mods, because they can have fire_wound_protection in configs, despite that
81+
// original COP engine doesn't read it.
82+
if (pSettings->line_exist(section, "fire_wound_protection"))
83+
m_boneProtection->m_hitFracType = SBoneProtections::HitFractionActorCS;
84+
else
85+
m_boneProtection->m_hitFracType = SBoneProtections::HitFractionActorCOP;
86+
}
7487

7588
if (pSettings->line_exist(section, "nightvision_sect"))
7689
m_NightVisionSect = pSettings->r_string(section, "nightvision_sect");
@@ -124,14 +137,23 @@ void CCustomOutfit::Hit(float hit_power, ALife::EHitType hit_type)
124137

125138
float CCustomOutfit::GetDefHitTypeProtection(ALife::EHitType hit_type) const
126139
{
127-
return m_HitTypeProtection[hit_type] * GetCondition();
140+
const float base = m_HitTypeProtection[hit_type] * GetCondition();
141+
142+
if (m_boneProtection->m_hitFracType == SBoneProtections::HitFraction)
143+
return 1.0f - base; // SOC
144+
145+
return base; // CS/COP
128146
}
129147

130148
float CCustomOutfit::GetHitTypeProtection(ALife::EHitType hit_type, s16 element) const
131149
{
132-
float fBase = m_HitTypeProtection[hit_type] * GetCondition();
133-
float bone = m_boneProtection->getBoneProtection(element);
134-
return fBase * bone;
150+
const float base = m_HitTypeProtection[hit_type] * GetCondition();
151+
const float bone = m_boneProtection->getBoneProtection(element);
152+
153+
if (m_boneProtection->m_hitFracType == SBoneProtections::HitFraction)
154+
return 1.0f - base * bone; // SOC
155+
156+
return base * bone; // CS/COP
135157
}
136158

137159
float CCustomOutfit::GetBoneArmor(s16 element) const
@@ -142,51 +164,131 @@ float CCustomOutfit::GetBoneArmor(s16 element) const
142164
float CCustomOutfit::HitThroughArmor(float hit_power, s16 element, float ap, bool& add_wound, ALife::EHitType hit_type)
143165
{
144166
float NewHitPower = hit_power;
145-
if (hit_type == ALife::eHitTypeFireWound)
146-
{
147-
float ba = GetBoneArmor(element);
148-
if (ba < 0.0f)
149-
return NewHitPower;
150167

151-
float BoneArmor = ba * GetCondition();
152-
if (/*!fis_zero(ba, EPS) && */ (ap > BoneArmor))
168+
switch (m_boneProtection->m_hitFracType)
169+
{
170+
default:
171+
case SBoneProtections::HitFractionActorCOP:
172+
{
173+
if (hit_type == ALife::eHitTypeFireWound)
153174
{
154-
//пуля пробила бронь
155-
if (!IsGameTypeSingle())
175+
const float ba = GetBoneArmor(element);
176+
if (ba < 0.0f)
177+
return NewHitPower;
178+
179+
float BoneArmor = ba * GetCondition();
180+
if (/*!fis_zero(ba, EPS) &&*/ ap > BoneArmor)
156181
{
157-
float hit_fraction = (ap - BoneArmor) / ap;
158-
if (hit_fraction < m_boneProtection->m_fHitFracActor)
159-
hit_fraction = m_boneProtection->m_fHitFracActor;
182+
//пуля пробила бронь
183+
if (!IsGameTypeSingle())
184+
{
185+
float hit_fraction = (ap - BoneArmor) / ap;
186+
if (hit_fraction < m_boneProtection->m_fHitFrac)
187+
hit_fraction = m_boneProtection->m_fHitFrac;
188+
189+
NewHitPower *= hit_fraction;
190+
NewHitPower *= m_boneProtection->getBoneProtection(element);
191+
}
160192

161-
NewHitPower *= hit_fraction;
162-
NewHitPower *= m_boneProtection->getBoneProtection(element);
193+
VERIFY(NewHitPower >= 0.0f);
163194
}
195+
else
196+
{
197+
//пуля НЕ пробила бронь
198+
NewHitPower *= m_boneProtection->m_fHitFrac;
199+
add_wound = false; //раны нет
200+
}
201+
}
202+
else
203+
{
204+
float one = 0.1f;
205+
if (hit_type == ALife::eHitTypeStrike ||
206+
hit_type == ALife::eHitTypeWound ||
207+
hit_type == ALife::eHitTypeWound_2 ||
208+
hit_type == ALife::eHitTypeExplosion)
209+
{
210+
one = 1.0f;
211+
}
212+
const float protect = GetDefHitTypeProtection(hit_type);
213+
NewHitPower -= protect * one;
164214

165-
VERIFY(NewHitPower >= 0.0f);
215+
if (NewHitPower < 0.f)
216+
NewHitPower = 0.f;
217+
}
218+
219+
//увеличить изношенность костюма
220+
Hit(hit_power, hit_type);
221+
break;
222+
}
223+
case SBoneProtections::HitFractionActorCS:
224+
{
225+
if (hit_type == ALife::eHitTypeFireWound)
226+
{
227+
const float BoneArmor = m_boneProtection->getBoneArmor(element) * GetCondition();
228+
229+
if (ap > EPS && ap > BoneArmor)
230+
{
231+
//пуля пробила бронь
232+
const float d_ap = ap - BoneArmor;
233+
NewHitPower *= (d_ap / ap);
234+
235+
if (NewHitPower < m_boneProtection->m_fHitFrac)
236+
NewHitPower = m_boneProtection->m_fHitFrac;
237+
238+
if (!IsGameTypeSingle())
239+
{
240+
NewHitPower *= m_boneProtection->getBoneProtection(element);
241+
}
242+
243+
if (NewHitPower < 0.0f)
244+
NewHitPower = 0.0f;
245+
}
246+
else
247+
{
248+
//пуля НЕ пробила бронь
249+
NewHitPower *= m_boneProtection->m_fHitFrac;
250+
add_wound = false; //раны нет
251+
}
166252
}
167253
else
168254
{
169-
//пуля НЕ пробила бронь
170-
NewHitPower *= m_boneProtection->m_fHitFracActor;
171-
add_wound = false; //раны нет
255+
float one = 0.1f;
256+
if (hit_type == ALife::eHitTypeWound ||
257+
hit_type == ALife::eHitTypeWound_2 ||
258+
hit_type == ALife::eHitTypeExplosion)
259+
{
260+
one = 1.0f;
261+
}
262+
263+
const float protect = GetHitTypeProtection(hit_type, element);
264+
NewHitPower -= protect * one;
265+
if (NewHitPower < 0.0f)
266+
NewHitPower = 0.0f;
172267
}
268+
269+
//увеличить изношенность костюма
270+
Hit(NewHitPower, hit_type);
271+
break;
173272
}
174-
else
273+
case SBoneProtections::HitFraction:
175274
{
176-
float one = 0.1f;
177-
if (hit_type == ALife::eHitTypeStrike || hit_type == ALife::eHitTypeWound ||
178-
hit_type == ALife::eHitTypeWound_2 || hit_type == ALife::eHitTypeExplosion)
275+
if (hit_type == ALife::eHitTypeFireWound)
179276
{
180-
one = 1.0f;
277+
const float BoneArmor = m_boneProtection->getBoneArmor(element) * GetCondition() * (1 - ap);
278+
NewHitPower -= BoneArmor;
279+
if (NewHitPower < hit_power * m_boneProtection->m_fHitFrac)
280+
NewHitPower = hit_power * m_boneProtection->m_fHitFrac;
281+
}
282+
else
283+
{
284+
NewHitPower *= GetHitTypeProtection(hit_type, element);
181285
}
182-
float protect = GetDefHitTypeProtection(hit_type);
183-
NewHitPower -= protect * one;
184286

185-
if (NewHitPower < 0.f)
186-
NewHitPower = 0.f;
287+
//увеличить изношенность костюма
288+
Hit(hit_power, hit_type);
289+
break;
187290
}
188-
//увеличить изношенность костюма
189-
Hit(hit_power, hit_type);
291+
} // switch (m_boneProtection->m_hitFracType)
190292

191293
return NewHitPower;
192294
}
@@ -285,6 +387,21 @@ u32 CCustomOutfit::ef_equipment_type() const
285387
return m_ef_equipment_type;
286388
}
287389

390+
float CCustomOutfit::GetPowerLoss() const
391+
{
392+
// Hit fraction and power loss are unrelated,
393+
// but it's the only way we can distinguish between SOC/CS and COP.
394+
// Sorry.
395+
if (m_boneProtection->m_hitFracType != SBoneProtections::HitFractionActorCOP)
396+
{
397+
if (m_fPowerLoss < 1 && GetCondition() <= 0)
398+
{
399+
return 1.0f;
400+
}
401+
}
402+
return m_fPowerLoss;
403+
};
404+
288405
bool CCustomOutfit::install_upgrade_impl(LPCSTR section, bool test)
289406
{
290407
bool result = inherited::install_upgrade_impl(section, test);
@@ -326,10 +443,13 @@ bool CCustomOutfit::install_upgrade_impl(LPCSTR section, bool test)
326443
result2 = process_if_exists_set(section, "bones_koeff_protection_add", &CInifile::r_string, str, test);
327444
if (result2 && !test)
328445
AddBonesProtection(str);
329-
330446
result |= result2;
331-
result |=
332-
process_if_exists(section, "hit_fraction_actor", &CInifile::r_float, m_boneProtection->m_fHitFracActor, test);
447+
448+
if (m_boneProtection->m_hitFracType == SBoneProtections::HitFractionActorCS ||
449+
m_boneProtection->m_hitFracType == SBoneProtections::HitFractionActorCOP)
450+
{
451+
result |= process_if_exists(section, "hit_fraction_actor", &CInifile::r_float, m_boneProtection->m_fHitFrac, test);
452+
}
333453

334454
result |= process_if_exists(section, "additional_inventory_weight", &CInifile::r_float, m_additional_weight, test);
335455
result |=

src/xrGame/CustomOutfit.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ class CCustomOutfit : public CInventoryItemObject
2525
[[nodiscard]] float GetDefHitTypeProtection(ALife::EHitType hit_type) const;
2626
[[nodiscard]] float GetBoneArmor(s16 element) const;
2727

28+
//коэффициент на который домножается потеря силы
29+
//если на персонаже надет костюм
30+
[[nodiscard]] float GetPowerLoss() const;
31+
2832
float HitThroughArmor(float hit_power, s16 element, float ap, bool& add_wound, ALife::EHitType hit_type);
2933

3034
virtual void OnMoveToSlot(const SInvItemPlace& prev);

0 commit comments

Comments
 (0)