1 #include "damage.h"
2
3 #include <algorithm>
4 #include <cstdlib>
5 #include <map>
6 #include <numeric>
7 #include <string>
8 #include <utility>
9
10 #include "bodypart.h"
11 #include "cata_utility.h"
12 #include "debug.h"
13 #include "generic_factory.h"
14 #include "item.h"
15 #include "json.h"
16 #include "monster.h"
17 #include "mtype.h"
18 #include "translations.h"
19 #include "units.h"
20
21 namespace io
22 {
23
24 template<>
enum_to_string(damage_type data)25 std::string enum_to_string<damage_type>( damage_type data )
26 {
27 switch( data ) {
28 // *INDENT-OFF*
29 case damage_type::NONE: return "none";
30 case damage_type::PURE: return "pure";
31 case damage_type::BIOLOGICAL: return "biological";
32 case damage_type::BASH: return "bash";
33 case damage_type::CUT: return "cut";
34 case damage_type::ACID: return "acid";
35 case damage_type::STAB: return "stab";
36 case damage_type::HEAT: return "heat";
37 case damage_type::COLD: return "cold";
38 case damage_type::ELECTRIC: return "electric";
39 case damage_type::BULLET: return "bullet";
40 // *INDENT-ON*
41 case damage_type::NUM:
42 break;
43 }
44 debugmsg( "Invalid damage_type" );
45 abort();
46 }
47
48 } // namespace io
49
operator ==(const damage_unit & other) const50 bool damage_unit::operator==( const damage_unit &other ) const
51 {
52 return type == other.type &&
53 amount == other.amount &&
54 res_pen == other.res_pen &&
55 res_mult == other.res_mult &&
56 damage_multiplier == other.damage_multiplier &&
57 unconditional_res_mult == other.unconditional_res_mult &&
58 unconditional_damage_mult == other.unconditional_res_mult;
59 }
60
61 damage_instance::damage_instance() = default;
physical(float bash,float cut,float stab,float arpen)62 damage_instance damage_instance::physical( float bash, float cut, float stab, float arpen )
63 {
64 damage_instance d;
65 d.add_damage( damage_type::BASH, bash, arpen );
66 d.add_damage( damage_type::CUT, cut, arpen );
67 d.add_damage( damage_type::STAB, stab, arpen );
68 return d;
69 }
damage_instance(damage_type dt,float amt,float arpen,float arpen_mult,float dmg_mult,float unc_arpen_mult,float unc_dmg_mult)70 damage_instance::damage_instance( damage_type dt, float amt, float arpen, float arpen_mult,
71 float dmg_mult, float unc_arpen_mult, float unc_dmg_mult )
72 {
73 add_damage( dt, amt, arpen, arpen_mult, dmg_mult, unc_arpen_mult, unc_dmg_mult );
74 }
75
add_damage(damage_type dt,float amt,float arpen,float arpen_mult,float dmg_mult,float unc_arpen_mult,float unc_dmg_mult)76 void damage_instance::add_damage( damage_type dt, float amt, float arpen, float arpen_mult,
77 float dmg_mult, float unc_arpen_mult, float unc_dmg_mult )
78 {
79 damage_unit du( dt, amt, arpen, arpen_mult, dmg_mult, unc_arpen_mult, unc_dmg_mult );
80 add( du );
81 }
82
mult_damage(double multiplier,bool pre_armor)83 void damage_instance::mult_damage( double multiplier, bool pre_armor )
84 {
85 if( multiplier <= 0.0 ) {
86 clear();
87 }
88
89 if( pre_armor ) {
90 for( damage_unit &elem : damage_units ) {
91 elem.amount *= multiplier;
92 }
93 } else {
94 for( damage_unit &elem : damage_units ) {
95 elem.damage_multiplier *= multiplier;
96 }
97 }
98 }
type_damage(damage_type dt) const99 float damage_instance::type_damage( damage_type dt ) const
100 {
101 float ret = 0.0f;
102 for( const damage_unit &elem : damage_units ) {
103 if( elem.type == dt ) {
104 ret += elem.amount * elem.damage_multiplier * elem.unconditional_damage_mult;
105 }
106 }
107 return ret;
108 }
109 //This returns the damage from this damage_instance. The damage done to the target will be reduced by their armor.
total_damage() const110 float damage_instance::total_damage() const
111 {
112 float ret = 0.0f;
113 for( const damage_unit &elem : damage_units ) {
114 ret += elem.amount * elem.damage_multiplier * elem.unconditional_damage_mult;
115 }
116 return ret;
117 }
clear()118 void damage_instance::clear()
119 {
120 damage_units.clear();
121 }
122
empty() const123 bool damage_instance::empty() const
124 {
125 return damage_units.empty();
126 }
127
add(const damage_instance & added_di)128 void damage_instance::add( const damage_instance &added_di )
129 {
130 for( const damage_unit &added_du : added_di.damage_units ) {
131 add( added_du );
132 }
133 }
134
add(const damage_unit & added_du)135 void damage_instance::add( const damage_unit &added_du )
136 {
137 auto iter = std::find_if( damage_units.begin(), damage_units.end(),
138 [&added_du]( const damage_unit & du ) {
139 return du.type == added_du.type;
140 } );
141 if( iter == damage_units.end() ) {
142 damage_units.emplace_back( added_du );
143 } else {
144 damage_unit &du = *iter;
145 float mult = added_du.damage_multiplier / du.damage_multiplier;
146 du.amount += added_du.amount * mult;
147 du.res_pen += added_du.res_pen * mult;
148 // Linearly interpolate armor multiplier based on damage proportion contributed
149 float t = added_du.damage_multiplier / ( added_du.damage_multiplier + du.damage_multiplier );
150 du.res_mult = lerp( du.res_mult, added_du.damage_multiplier, t );
151
152 du.unconditional_res_mult *= added_du.unconditional_res_mult;
153 du.unconditional_damage_mult *= added_du.unconditional_damage_mult;
154 }
155 }
156
begin()157 std::vector<damage_unit>::iterator damage_instance::begin()
158 {
159 return damage_units.begin();
160 }
161
begin() const162 std::vector<damage_unit>::const_iterator damage_instance::begin() const
163 {
164 return damage_units.begin();
165 }
166
end()167 std::vector<damage_unit>::iterator damage_instance::end()
168 {
169 return damage_units.end();
170 }
171
end() const172 std::vector<damage_unit>::const_iterator damage_instance::end() const
173 {
174 return damage_units.end();
175 }
176
operator ==(const damage_instance & other) const177 bool damage_instance::operator==( const damage_instance &other ) const
178 {
179 return damage_units == other.damage_units;
180 }
181
deserialize(JsonIn & jsin)182 void damage_instance::deserialize( JsonIn &jsin )
183 {
184 // TODO: Clean up
185 if( jsin.test_object() ) {
186 JsonObject jo = jsin.get_object();
187 damage_units = load_damage_instance( jo ).damage_units;
188 } else if( jsin.test_array() ) {
189 damage_units = load_damage_instance( jsin.get_array() ).damage_units;
190 } else {
191 jsin.error( "Expected object or array for damage_instance" );
192 }
193 }
194
dealt_damage_instance()195 dealt_damage_instance::dealt_damage_instance()
196 {
197 dealt_dams.fill( 0 );
198 bp_hit = bodypart_id( "torso" );
199 }
200
set_damage(damage_type dt,int amount)201 void dealt_damage_instance::set_damage( damage_type dt, int amount )
202 {
203 if( static_cast<int>( dt ) < 0 || dt >= damage_type::NUM ) {
204 debugmsg( "Tried to set invalid damage type %d. damage_type::NUM is %d", dt, damage_type::NUM );
205 return;
206 }
207
208 dealt_dams[static_cast<int>( dt )] = amount;
209 }
type_damage(damage_type dt) const210 int dealt_damage_instance::type_damage( damage_type dt ) const
211 {
212 if( static_cast<size_t>( dt ) < dealt_dams.size() ) {
213 return dealt_dams[static_cast<int>( dt )];
214 }
215
216 return 0;
217 }
total_damage() const218 int dealt_damage_instance::total_damage() const
219 {
220 return std::accumulate( dealt_dams.begin(), dealt_dams.end(), 0 );
221 }
222
resistances()223 resistances::resistances()
224 {
225 resist_vals.fill( 0 );
226 }
227
resistances(const item & armor,bool to_self)228 resistances::resistances( const item &armor, bool to_self )
229 {
230 // Armors protect, but all items can resist
231 if( to_self || armor.is_armor() ) {
232 for( int i = 0; i < static_cast<int>( damage_type::NUM ); i++ ) {
233 damage_type dt = static_cast<damage_type>( i );
234 set_resist( dt, armor.damage_resist( dt, to_self ) );
235 }
236 }
237 }
resistances(monster & monster)238 resistances::resistances( monster &monster ) : resistances()
239 {
240 set_resist( damage_type::BASH, monster.type->armor_bash );
241 set_resist( damage_type::CUT, monster.type->armor_cut );
242 set_resist( damage_type::STAB, monster.type->armor_stab );
243 set_resist( damage_type::BULLET, monster.type->armor_bullet );
244 set_resist( damage_type::ACID, monster.type->armor_acid );
245 set_resist( damage_type::HEAT, monster.type->armor_fire );
246 }
set_resist(damage_type dt,float amount)247 void resistances::set_resist( damage_type dt, float amount )
248 {
249 resist_vals[static_cast<int>( dt )] = amount;
250 }
type_resist(damage_type dt) const251 float resistances::type_resist( damage_type dt ) const
252 {
253 return resist_vals[static_cast<int>( dt )];
254 }
get_effective_resist(const damage_unit & du) const255 float resistances::get_effective_resist( const damage_unit &du ) const
256 {
257 return std::max( type_resist( du.type ) - du.res_pen,
258 0.0f ) * du.res_mult * du.unconditional_res_mult;
259 }
260
operator +=(const resistances & other)261 resistances &resistances::operator+=( const resistances &other )
262 {
263 for( size_t i = 0; i < static_cast<int>( damage_type::NUM ); i++ ) {
264 resist_vals[ i ] += other.resist_vals[ i ];
265 }
266
267 return *this;
268 }
269
270 static const std::map<std::string, damage_type> dt_map = {
271 { translate_marker_context( "damage type", "pure" ), damage_type::PURE },
272 { translate_marker_context( "damage type", "biological" ), damage_type::BIOLOGICAL },
273 { translate_marker_context( "damage type", "bash" ), damage_type::BASH },
274 { translate_marker_context( "damage type", "cut" ), damage_type::CUT },
275 { translate_marker_context( "damage type", "acid" ), damage_type::ACID },
276 { translate_marker_context( "damage type", "stab" ), damage_type::STAB },
277 { translate_marker_context( "damage_type", "bullet" ), damage_type::BULLET },
278 { translate_marker_context( "damage type", "heat" ), damage_type::HEAT },
279 { translate_marker_context( "damage type", "cold" ), damage_type::COLD },
280 { translate_marker_context( "damage type", "electric" ), damage_type::ELECTRIC }
281 };
282
get_dt_map()283 const std::map<std::string, damage_type> &get_dt_map()
284 {
285 return dt_map;
286 }
287
dt_by_name(const std::string & name)288 damage_type dt_by_name( const std::string &name )
289 {
290 const auto &iter = dt_map.find( name );
291 if( iter == dt_map.end() ) {
292 return damage_type::NONE;
293 }
294
295 return iter->second;
296 }
297
name_by_dt(const damage_type & dt)298 std::string name_by_dt( const damage_type &dt )
299 {
300 auto iter = dt_map.cbegin();
301 while( iter != dt_map.cend() ) {
302 if( iter->second == dt ) {
303 return pgettext( "damage type", iter->first.c_str() );
304 }
305 iter++;
306 }
307 static const std::string err_msg( "dt_not_found" );
308 return err_msg;
309 }
310
skill_by_dt(damage_type dt)311 const skill_id &skill_by_dt( damage_type dt )
312 {
313 static skill_id skill_bashing( "bashing" );
314 static skill_id skill_cutting( "cutting" );
315 static skill_id skill_stabbing( "stabbing" );
316
317 switch( dt ) {
318 case damage_type::BASH:
319 return skill_bashing;
320
321 case damage_type::CUT:
322 return skill_cutting;
323
324 case damage_type::STAB:
325 return skill_stabbing;
326
327 default:
328 return skill_id::NULL_ID();
329 }
330 }
331
load_damage_unit(const JsonObject & curr)332 static damage_unit load_damage_unit( const JsonObject &curr )
333 {
334 damage_type dt = dt_by_name( curr.get_string( "damage_type" ) );
335 if( dt == damage_type::NONE ) {
336 curr.throw_error( "Invalid damage type" );
337 }
338
339 float amount = curr.get_float( "amount", 0 );
340 float arpen = curr.get_float( "armor_penetration", 0 );
341 float armor_mul = curr.get_float( "armor_multiplier", 1.0f );
342 float damage_mul = curr.get_float( "damage_multiplier", 1.0f );
343
344 float unc_armor_mul = curr.get_float( "constant_armor_multiplier", 1.0f );
345 float unc_damage_mul = curr.get_float( "constant_damage_multiplier", 1.0f );
346
347 return damage_unit( dt, amount, arpen, armor_mul, damage_mul, unc_armor_mul, unc_damage_mul );
348 }
349
load_damage_unit_inherit(const JsonObject & curr,const damage_instance & parent)350 static damage_unit load_damage_unit_inherit( const JsonObject &curr, const damage_instance &parent )
351 {
352 damage_unit ret = load_damage_unit( curr );
353
354 const std::vector<damage_unit> &parent_damage = parent.damage_units;
355 auto du_iter = std::find_if( parent_damage.begin(),
356 parent_damage.end(), [&ret]( const damage_unit & dmg ) {
357 return dmg.type == ret.type;
358 } );
359
360 if( du_iter == parent_damage.end() ) {
361 return ret;
362 }
363
364 const damage_unit &parent_du = *du_iter;
365
366 if( !curr.has_float( "amount" ) ) {
367 ret.amount = parent_du.amount;
368 }
369 if( !curr.has_float( "armor_penetration" ) ) {
370 ret.res_pen = parent_du.res_pen;
371 }
372 if( !curr.has_float( "armor_multiplier" ) ) {
373 ret.res_mult = parent_du.res_mult;
374 }
375 if( !curr.has_float( "damage_multiplier" ) ) {
376 ret.damage_multiplier = parent_du.damage_multiplier;
377 }
378 if( !curr.has_float( "constant_armor_multiplier" ) ) {
379 ret.unconditional_res_mult = parent_du.unconditional_res_mult;
380 }
381 if( !curr.has_float( "constant_damage_multiplier" ) ) {
382 ret.unconditional_damage_mult = parent_du.unconditional_damage_mult;
383 }
384
385 return ret;
386 }
387
blank_damage_instance()388 static damage_instance blank_damage_instance()
389 {
390 damage_instance ret;
391
392 for( int i = 0; i < static_cast<int>( damage_type::NUM ); ++i ) {
393 ret.add_damage( static_cast<damage_type>( i ), 0.0f );
394 }
395
396 return ret;
397 }
398
load_damage_instance(const JsonObject & jo)399 damage_instance load_damage_instance( const JsonObject &jo )
400 {
401 return load_damage_instance_inherit( jo, blank_damage_instance() );
402 }
403
load_damage_instance(const JsonArray & jarr)404 damage_instance load_damage_instance( const JsonArray &jarr )
405 {
406 return load_damage_instance_inherit( jarr, blank_damage_instance() );
407 }
408
load_damage_instance_inherit(const JsonObject & jo,const damage_instance & parent)409 damage_instance load_damage_instance_inherit( const JsonObject &jo, const damage_instance &parent )
410 {
411 damage_instance di;
412 if( jo.has_array( "values" ) ) {
413 for( const JsonObject curr : jo.get_array( "values" ) ) {
414 di.damage_units.push_back( load_damage_unit_inherit( curr, parent ) );
415 }
416 } else if( jo.has_string( "damage_type" ) ) {
417 di.damage_units.push_back( load_damage_unit_inherit( jo, parent ) );
418 }
419
420 return di;
421 }
422
load_damage_instance_inherit(const JsonArray & jarr,const damage_instance & parent)423 damage_instance load_damage_instance_inherit( const JsonArray &jarr, const damage_instance &parent )
424 {
425 damage_instance di;
426 for( const JsonObject curr : jarr ) {
427 di.damage_units.push_back( load_damage_unit_inherit( curr, parent ) );
428 }
429
430 return di;
431 }
432
load_damage_array(const JsonObject & jo)433 std::array<float, static_cast<int>( damage_type::NUM )> load_damage_array( const JsonObject &jo )
434 {
435 std::array<float, static_cast<int>( damage_type::NUM )> ret;
436 float init_val = jo.get_float( "all", 0.0f );
437
438 float phys = jo.get_float( "physical", init_val );
439 ret[ static_cast<int>( damage_type::BASH ) ] = jo.get_float( "bash", phys );
440 ret[ static_cast<int>( damage_type::CUT )] = jo.get_float( "cut", phys );
441 ret[ static_cast<int>( damage_type::STAB ) ] = jo.get_float( "stab", phys );
442 ret[ static_cast<int>( damage_type::BULLET ) ] = jo.get_float( "bullet", phys );
443
444 float non_phys = jo.get_float( "non_physical", init_val );
445 ret[ static_cast<int>( damage_type::BIOLOGICAL ) ] = jo.get_float( "biological", non_phys );
446 ret[ static_cast<int>( damage_type::ACID ) ] = jo.get_float( "acid", non_phys );
447 ret[ static_cast<int>( damage_type::HEAT ) ] = jo.get_float( "heat", non_phys );
448 ret[ static_cast<int>( damage_type::COLD ) ] = jo.get_float( "cold", non_phys );
449 ret[ static_cast<int>( damage_type::ELECTRIC ) ] = jo.get_float( "electric", non_phys );
450
451 // damage_type::PURE should never be resisted
452 ret[ static_cast<int>( damage_type::PURE ) ] = 0.0f;
453 return ret;
454 }
455
load_resistances_instance(const JsonObject & jo)456 resistances load_resistances_instance( const JsonObject &jo )
457 {
458 resistances ret;
459 ret.resist_vals = load_damage_array( jo );
460 return ret;
461 }
462
load(const JsonObject & obj)463 void damage_over_time_data::load( const JsonObject &obj )
464 {
465 std::string tmp_string;
466 mandatory( obj, was_loaded, "damage_type", tmp_string );
467 type = dt_by_name( tmp_string );
468 mandatory( obj, was_loaded, "amount", amount );
469 mandatory( obj, was_loaded, "bodyparts", bps );
470
471 if( obj.has_string( "duration" ) ) {
472 duration = read_from_json_string<time_duration>( *obj.get_raw( "duration" ), time_duration::units );
473 } else {
474 duration = time_duration::from_turns( obj.get_int( "duration", 0 ) );
475 }
476 }
477
serialize(JsonOut & jsout) const478 void damage_over_time_data::serialize( JsonOut &jsout ) const
479 {
480 jsout.start_object();
481 jsout.member( "damage_type", name_by_dt( type ) );
482 jsout.member( "duration", duration );
483 jsout.member( "amount", amount );
484 jsout.member( "bodyparts", bps );
485 jsout.end_object();
486 }
487
deserialize(JsonIn & jsin)488 void damage_over_time_data::deserialize( JsonIn &jsin )
489 {
490 const JsonObject &jo = jsin.get_object();
491 std::string tmp_string = jo.get_string( "damage_type" );
492 // Remove after 0.F, migrating DT_TRUE to DT_PURE
493 if( tmp_string == "true" ) {
494 tmp_string = "pure";
495 }
496 type = dt_by_name( tmp_string );
497 jo.read( "amount", amount );
498 jo.read( "duration", duration );
499 jo.read( "bodyparts", bps );
500 }
501