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