1 #include "mattack_actors.h"
2 
3 #include <algorithm>
4 #include <limits>
5 #include <list>
6 #include <memory>
7 #include <string>
8 
9 #include "avatar.h"
10 #include "bodypart.h"
11 #include "calendar.h"
12 #include "character.h"
13 #include "creature.h"
14 #include "enums.h"
15 #include "game.h"
16 #include "generic_factory.h"
17 #include "gun_mode.h"
18 #include "item.h"
19 #include "item_pocket.h"
20 #include "json.h"
21 #include "line.h"
22 #include "map.h"
23 #include "map_iterator.h"
24 #include "messages.h"
25 #include "monster.h"
26 #include "npc.h"
27 #include "player.h"
28 #include "point.h"
29 #include "ret_val.h"
30 #include "rng.h"
31 #include "sounds.h"
32 #include "translations.h"
33 #include "viewer.h"
34 
35 static const efftype_id effect_badpoison( "badpoison" );
36 static const efftype_id effect_bite( "bite" );
37 static const efftype_id effect_grabbed( "grabbed" );
38 static const efftype_id effect_infected( "infected" );
39 static const efftype_id effect_laserlocked( "laserlocked" );
40 static const efftype_id effect_poison( "poison" );
41 static const efftype_id effect_stunned( "stunned" );
42 static const efftype_id effect_sensor_stun( "sensor_stun" );
43 static const efftype_id effect_targeted( "targeted" );
44 static const efftype_id effect_was_laserlocked( "was_laserlocked" );
45 
46 static const trait_id trait_TOXICFLESH( "TOXICFLESH" );
47 
load_internal(const JsonObject & obj,const std::string &)48 void leap_actor::load_internal( const JsonObject &obj, const std::string & )
49 {
50     // Mandatory:
51     max_range = obj.get_float( "max_range" );
52     // Optional:
53     min_range = obj.get_float( "min_range", 1.0f );
54     allow_no_target = obj.get_bool( "allow_no_target", false );
55     move_cost = obj.get_int( "move_cost", 150 );
56     min_consider_range = obj.get_float( "min_consider_range", 0.0f );
57     max_consider_range = obj.get_float( "max_consider_range", 200.0f );
58 }
59 
clone() const60 std::unique_ptr<mattack_actor> leap_actor::clone() const
61 {
62     return std::make_unique<leap_actor>( *this );
63 }
64 
call(monster & z) const65 bool leap_actor::call( monster &z ) const
66 {
67     if( !z.can_act() || !z.move_effects( false ) ) {
68         return false;
69     }
70 
71     std::vector<tripoint> options;
72     tripoint target = z.move_target();
73     float best_float = trigdist ? trig_dist( z.pos(), target ) : square_dist( z.pos(), target );
74     if( best_float < min_consider_range || best_float > max_consider_range ) {
75         return false;
76     }
77 
78     // We wanted the float for range check
79     // int here will make the jumps more random
80     int best = std::numeric_limits<int>::max();
81     if( !allow_no_target && z.attack_target() == nullptr ) {
82         return false;
83     }
84     map &here = get_map();
85     std::multimap<int, tripoint> candidates;
86     for( const tripoint &candidate : here.points_in_radius( z.pos(), max_range ) ) {
87         if( candidate == z.pos() ) {
88             continue;
89         }
90         float leap_dist = trigdist ? trig_dist( z.pos(), candidate ) :
91                           square_dist( z.pos(), candidate );
92         if( leap_dist > max_range || leap_dist < min_range ) {
93             continue;
94         }
95         int candidate_dist = rl_dist( candidate, target );
96         if( candidate_dist >= best_float ) {
97             continue;
98         }
99         candidates.emplace( candidate_dist, candidate );
100     }
101     for( const auto &candidate : candidates ) {
102         const int &cur_dist = candidate.first;
103         const tripoint &dest = candidate.second;
104         if( cur_dist > best ) {
105             break;
106         }
107         if( !z.sees( dest ) ) {
108             continue;
109         }
110         if( !g->is_empty( dest ) ) {
111             continue;
112         }
113         bool blocked_path = false;
114         // check if monster has a clear path to the proposed point
115         std::vector<tripoint> line = here.find_clear_path( z.pos(), dest );
116         for( auto &i : line ) {
117             if( here.impassable( i ) ) {
118                 blocked_path = true;
119                 break;
120             }
121         }
122         // don't leap into water if you could drown (#38038)
123         if( z.is_aquatic_danger( dest ) ) {
124             blocked_path = true;
125         }
126         if( blocked_path ) {
127             continue;
128         }
129 
130         options.push_back( dest );
131         best = cur_dist;
132     }
133 
134     if( options.empty() ) {
135         return false;    // Nowhere to leap!
136     }
137 
138     z.moves -= move_cost;
139     viewer &player_view = get_player_view();
140     const tripoint chosen = random_entry( options );
141     bool seen = player_view.sees( z ); // We can see them jump...
142     z.setpos( chosen );
143     seen |= player_view.sees( z ); // ... or we can see them land
144     if( seen ) {
145         add_msg( _( "The %s leaps!" ), z.name() );
146     }
147 
148     return true;
149 }
150 
clone() const151 std::unique_ptr<mattack_actor> mon_spellcasting_actor::clone() const
152 {
153     return std::make_unique<mon_spellcasting_actor>( *this );
154 }
155 
load_internal(const JsonObject & obj,const std::string &)156 void mon_spellcasting_actor::load_internal( const JsonObject &obj, const std::string & )
157 {
158     mandatory( obj, was_loaded, "spell_data", spell_data );
159     translation monster_message;
160     optional( obj, was_loaded, "monster_message", monster_message,
161               //~ "<Monster Display name> cast <Spell Name> on <Target name>!"
162               to_translation( "%1$s casts %2$s at %3$s!" ) );
163     spell_data.trigger_message = monster_message;
164 }
165 
call(monster & mon) const166 bool mon_spellcasting_actor::call( monster &mon ) const
167 {
168     if( !mon.can_act() ) {
169         return false;
170     }
171 
172     if( !mon.attack_target() ) {
173         // this is an attack. there is no reason to attack if there isn't a real target.
174         return false;
175     }
176 
177     const tripoint target = spell_data.self ? mon.pos() : mon.attack_target()->pos();
178     spell spell_instance = spell_data.get_spell();
179     spell_instance.set_message( spell_data.trigger_message );
180 
181     // Bail out if the target is out of range.
182     if( !spell_data.self && rl_dist( mon.pos(), target ) > spell_instance.range() ) {
183         return false;
184     }
185 
186     std::string target_name;
187     if( const Creature *target_monster = g->critter_at( target ) ) {
188         target_name = target_monster->disp_name();
189     }
190 
191     add_msg_if_player_sees( target, spell_instance.message(), mon.disp_name(),
192                             spell_instance.name(), target_name );
193 
194     avatar fake_player;
195     mon.moves -= spell_instance.casting_time( fake_player, true );
196     spell_instance.cast_all_effects( mon, target );
197 
198     return true;
199 }
200 
melee_actor()201 melee_actor::melee_actor()
202 {
203     damage_max_instance = damage_instance::physical( 9, 0, 0, 0 );
204     min_mul = 0.5f;
205     max_mul = 1.0f;
206     move_cost = 100;
207 }
208 
load_internal(const JsonObject & obj,const std::string &)209 void melee_actor::load_internal( const JsonObject &obj, const std::string & )
210 {
211     // Optional:
212     if( obj.has_array( "damage_max_instance" ) ) {
213         damage_max_instance = load_damage_instance( obj.get_array( "damage_max_instance" ) );
214     } else if( obj.has_object( "damage_max_instance" ) ) {
215         damage_max_instance = load_damage_instance( obj );
216     }
217 
218     min_mul = obj.get_float( "min_mul", 0.0f );
219     max_mul = obj.get_float( "max_mul", 1.0f );
220     move_cost = obj.get_int( "move_cost", 100 );
221     accuracy = obj.get_int( "accuracy", INT_MIN );
222 
223     optional( obj, was_loaded, "miss_msg_u", miss_msg_u,
224               to_translation( "The %s lunges at you, but you dodge!" ) );
225     optional( obj, was_loaded, "no_dmg_msg_u", no_dmg_msg_u,
226               to_translation( "The %1$s bites your %2$s, but fails to penetrate armor!" ) );
227     optional( obj, was_loaded, "hit_dmg_u", hit_dmg_u,
228               to_translation( "The %1$s bites your %2$s!" ) );
229     optional( obj, was_loaded, "miss_msg_npc", miss_msg_npc,
230               to_translation( "The %s lunges at <npcname>, but they dodge!" ) );
231     optional( obj, was_loaded, "no_dmg_msg_npc", no_dmg_msg_npc,
232               to_translation( "The %1$s bites <npcname>'s %2$s, but fails to penetrate armor!" ) );
233     optional( obj, was_loaded, "hit_dmg_npc", hit_dmg_npc,
234               to_translation( "The %1$s bites <npcname>'s %2$s!" ) );
235 
236     if( obj.has_array( "body_parts" ) ) {
237         for( JsonArray sub : obj.get_array( "body_parts" ) ) {
238             const bodypart_str_id bp( sub.get_string( 0 ) );
239             const float prob = sub.get_float( 1 );
240             body_parts.add_or_replace( bp, prob );
241         }
242     }
243 
244     if( obj.has_array( "effects" ) ) {
245         for( JsonObject eff : obj.get_array( "effects" ) ) {
246             effects.push_back( load_mon_effect_data( eff ) );
247         }
248     }
249 }
250 
find_target(monster & z) const251 Creature *melee_actor::find_target( monster &z ) const
252 {
253     if( !z.can_act() ) {
254         return nullptr;
255     }
256 
257     Creature *target = z.attack_target();
258     if( target == nullptr || !z.is_adjacent( target, false ) ) {
259         return nullptr;
260     }
261 
262     return target;
263 }
264 
call(monster & z) const265 bool melee_actor::call( monster &z ) const
266 {
267     Creature *target = find_target( z );
268     if( target == nullptr ) {
269         return false;
270     }
271 
272     z.mod_moves( -move_cost );
273 
274     add_msg_debug( "%s attempting to melee_attack %s", z.name(),
275                    target->disp_name() );
276 
277     const int acc = accuracy >= 0 ? accuracy : z.type->melee_skill;
278     int hitspread = target->deal_melee_attack( &z, dice( acc, 10 ) );
279 
280     if( hitspread < 0 ) {
281         game_message_type msg_type = target->is_avatar() ? m_warning : m_info;
282         sfx::play_variant_sound( "mon_bite", "bite_miss", sfx::get_heard_volume( z.pos() ),
283                                  sfx::get_heard_angle( z.pos() ) );
284         target->add_msg_player_or_npc( msg_type, miss_msg_u, miss_msg_npc, z.name() );
285         return true;
286     }
287 
288     damage_instance damage = damage_max_instance;
289 
290     double multiplier = rng_float( min_mul, max_mul );
291     damage.mult_damage( multiplier );
292 
293     bodypart_str_id bp_hit = body_parts.empty() ?
294                              target->select_body_part( &z, hitspread ).id() :
295                              *body_parts.pick();
296 
297     target->on_hit( &z, bp_hit.id() );
298     dealt_damage_instance dealt_damage = target->deal_damage( &z, bp_hit.id(), damage );
299     dealt_damage.bp_hit = bp_hit.id();
300 
301     int damage_total = dealt_damage.total_damage();
302     add_msg_debug( "%s's melee_attack did %d damage", z.name(), damage_total );
303     if( damage_total > 0 ) {
304         on_damage( z, *target, dealt_damage );
305     } else {
306         sfx::play_variant_sound( "mon_bite", "bite_miss", sfx::get_heard_volume( z.pos() ),
307                                  sfx::get_heard_angle( z.pos() ) );
308         target->add_msg_player_or_npc( m_neutral, no_dmg_msg_u, no_dmg_msg_npc, z.name(),
309                                        body_part_name_accusative( bp_hit.id() ) );
310     }
311 
312     return true;
313 }
314 
on_damage(monster & z,Creature & target,dealt_damage_instance & dealt) const315 void melee_actor::on_damage( monster &z, Creature &target, dealt_damage_instance &dealt ) const
316 {
317     if( target.is_player() ) {
318         sfx::play_variant_sound( "mon_bite", "bite_hit", sfx::get_heard_volume( z.pos() ),
319                                  sfx::get_heard_angle( z.pos() ) );
320         sfx::do_player_death_hurt( dynamic_cast<player &>( target ), false );
321     }
322     game_message_type msg_type = target.attitude_to( get_player_character() ) ==
323                                  Creature::Attitude::FRIENDLY ?
324                                  m_bad : m_neutral;
325     const bodypart_id &bp = dealt.bp_hit ;
326     target.add_msg_player_or_npc( msg_type, hit_dmg_u, hit_dmg_npc, z.name(),
327                                   body_part_name_accusative( bp ) );
328 
329     for( const mon_effect_data &eff : effects ) {
330         if( x_in_y( eff.chance, 100 ) ) {
331             const bodypart_id affected_bp = eff.affect_hit_bp ? bp : eff.bp.id();
332             target.add_effect( eff.id, time_duration::from_turns( eff.duration ), affected_bp, eff.permanent );
333         }
334     }
335 }
336 
clone() const337 std::unique_ptr<mattack_actor> melee_actor::clone() const
338 {
339     return std::make_unique<melee_actor>( *this );
340 }
341 
342 bite_actor::bite_actor() = default;
343 
load_internal(const JsonObject & obj,const std::string & src)344 void bite_actor::load_internal( const JsonObject &obj, const std::string &src )
345 {
346     melee_actor::load_internal( obj, src );
347     no_infection_chance = obj.get_int( "no_infection_chance", 14 );
348 }
349 
on_damage(monster & z,Creature & target,dealt_damage_instance & dealt) const350 void bite_actor::on_damage( monster &z, Creature &target, dealt_damage_instance &dealt ) const
351 {
352     melee_actor::on_damage( z, target, dealt );
353     if( target.has_effect( effect_grabbed ) && one_in( no_infection_chance - dealt.total_damage() ) ) {
354         const bodypart_id &hit = dealt.bp_hit;
355         if( target.has_effect( effect_bite, hit.id() ) ) {
356             target.add_effect( effect_bite, 40_minutes, hit, true );
357         } else if( target.has_effect( effect_infected, hit.id() ) ) {
358             target.add_effect( effect_infected, 25_minutes, hit, true );
359         } else {
360             target.add_effect( effect_bite, 1_turns, hit, true );
361         }
362     }
363     if( target.has_trait( trait_TOXICFLESH ) ) {
364         z.add_effect( effect_poison, 5_minutes );
365         z.add_effect( effect_badpoison, 5_minutes );
366     }
367 }
368 
clone() const369 std::unique_ptr<mattack_actor> bite_actor::clone() const
370 {
371     return std::make_unique<bite_actor>( *this );
372 }
373 
gun_actor()374 gun_actor::gun_actor() : description( to_translation( "The %1$s fires its %2$s!" ) ),
375     targeting_sound( to_translation( "beep-beep-beep!" ) )
376 {
377 }
378 
load_internal(const JsonObject & obj,const std::string &)379 void gun_actor::load_internal( const JsonObject &obj, const std::string & )
380 {
381     obj.read( "gun_type", gun_type, true );
382 
383     obj.read( "ammo_type", ammo_type );
384 
385     if( obj.has_array( "fake_skills" ) ) {
386         for( JsonArray cur : obj.get_array( "fake_skills" ) ) {
387             fake_skills[skill_id( cur.get_string( 0 ) )] = cur.get_int( 1 );
388         }
389     }
390 
391     obj.read( "fake_str", fake_str );
392     obj.read( "fake_dex", fake_dex );
393     obj.read( "fake_int", fake_int );
394     obj.read( "fake_per", fake_per );
395 
396     for( JsonArray mode : obj.get_array( "ranges" ) ) {
397         if( mode.size() < 2 || mode.get_int( 0 ) > mode.get_int( 1 ) ) {
398             obj.throw_error( "incomplete or invalid range specified", "ranges" );
399         }
400         ranges.emplace( std::make_pair<int, int>( mode.get_int( 0 ), mode.get_int( 1 ) ),
401                         gun_mode_id( mode.size() > 2 ? mode.get_string( 2 ) : "" ) );
402     }
403 
404     obj.read( "max_ammo", max_ammo );
405 
406     obj.read( "move_cost", move_cost );
407 
408     obj.read( "description", description );
409     obj.read( "failure_msg", failure_msg );
410     if( !obj.read( "no_ammo_sound", no_ammo_sound ) ) {
411         no_ammo_sound = to_translation( "Click." );
412     }
413 
414     obj.read( "targeting_cost", targeting_cost );
415 
416     obj.read( "require_targeting_player", require_targeting_player );
417     obj.read( "require_targeting_npc", require_targeting_npc );
418     obj.read( "require_targeting_monster", require_targeting_monster );
419 
420     obj.read( "targeting_timeout", targeting_timeout );
421     obj.read( "targeting_timeout_extend", targeting_timeout_extend );
422 
423     if( !obj.read( "targeting_sound", targeting_sound ) ) {
424         targeting_sound = to_translation( "Beep." );
425     }
426 
427     obj.read( "targeting_volume", targeting_volume );
428 
429     laser_lock = obj.get_bool( "laser_lock", false );
430 
431     obj.read( "require_sunlight", require_sunlight );
432 }
433 
clone() const434 std::unique_ptr<mattack_actor> gun_actor::clone() const
435 {
436     return std::make_unique<gun_actor>( *this );
437 }
438 
call(monster & z) const439 bool gun_actor::call( monster &z ) const
440 {
441     Creature *target;
442 
443     if( z.friendly ) {
444         int max_range = 0;
445         for( const auto &e : ranges ) {
446             max_range = std::max( std::max( max_range, e.first.first ), e.first.second );
447         }
448 
449         int hostiles; // hostiles which cannot be engaged without risking friendly fire
450         target = z.auto_find_hostile_target( max_range, hostiles );
451         if( !target ) {
452             if( hostiles > 0 ) {
453                 add_msg_if_player_sees( z, m_warning,
454                                         ngettext( "Pointed in your direction, the %s emits an IFF warning beep.",
455                                                   "Pointed in your direction, the %s emits %d annoyed sounding beeps.",
456                                                   hostiles ),
457                                         z.name(), hostiles );
458             }
459             return false;
460         }
461 
462     } else {
463         target = z.attack_target();
464         if( !target || !z.sees( *target ) ) {
465             return false;
466         }
467     }
468 
469     int dist = rl_dist( z.pos(), target->pos() );
470     for( const auto &e : ranges ) {
471         if( dist >= e.first.first && dist <= e.first.second ) {
472             shoot( z, *target, e.second );
473             return true;
474         }
475     }
476     return false;
477 }
478 
shoot(monster & z,Creature & target,const gun_mode_id & mode) const479 void gun_actor::shoot( monster &z, Creature &target, const gun_mode_id &mode ) const
480 {
481     if( require_sunlight && !g->is_in_sunlight( z.pos() ) ) {
482         if( one_in( 3 ) ) {
483             add_msg_if_player_sees( z, failure_msg.translated(), z.name() );
484         }
485         return;
486     }
487 
488     const bool require_targeting = ( require_targeting_player && target.is_player() ) ||
489                                    ( require_targeting_npc && target.is_npc() ) ||
490                                    ( require_targeting_monster && target.is_monster() );
491     const bool not_targeted = require_targeting && !z.has_effect( effect_targeted );
492     const bool not_laser_locked = require_targeting && laser_lock &&
493                                   !target.has_effect( effect_was_laserlocked );
494 
495     if( not_targeted || not_laser_locked ) {
496         if( targeting_volume > 0 && !targeting_sound.empty() ) {
497             sounds::sound( z.pos(), targeting_volume, sounds::sound_t::alarm,
498                            targeting_sound );
499         }
500         if( not_targeted ) {
501             z.add_effect( effect_targeted, time_duration::from_turns( targeting_timeout ) );
502         }
503         if( not_laser_locked ) {
504             target.add_effect( effect_laserlocked, time_duration::from_turns( targeting_timeout ) );
505             target.add_effect( effect_was_laserlocked, time_duration::from_turns( targeting_timeout ) );
506             target.add_msg_if_player( m_warning,
507                                       _( "You're not sure why you've got a laser dot on you…" ) );
508         }
509 
510         z.moves -= targeting_cost;
511         return;
512     }
513 
514     z.moves -= move_cost;
515 
516     item gun( gun_type );
517     gun.gun_set_mode( mode );
518 
519     itype_id ammo = ammo_type;
520     if( ammo.is_null() ) {
521         if( gun.magazine_integral() ) {
522             ammo = gun.ammo_default();
523         } else {
524             ammo = item( gun.magazine_default() ).ammo_default();
525         }
526     }
527 
528     if( !ammo.is_null() ) {
529         if( gun.magazine_integral() ) {
530             gun.ammo_set( ammo, z.ammo[ammo] );
531         } else {
532             item mag( gun.magazine_default() );
533             mag.ammo_set( ammo, z.ammo[ammo] );
534             gun.put_in( mag, item_pocket::pocket_type::MAGAZINE_WELL );
535         }
536     }
537 
538     if( z.has_effect( effect_stunned ) || z.has_effect( effect_sensor_stun ) ) {
539         return;
540     }
541 
542     if( !gun.ammo_sufficient() ) {
543         if( !no_ammo_sound.empty() ) {
544             sounds::sound( z.pos(), 10, sounds::sound_t::combat, no_ammo_sound );
545         }
546         return;
547     }
548 
549     standard_npc tmp( _( "The " ) + z.name(), z.pos(), {}, 8,
550                       fake_str, fake_dex, fake_int, fake_per );
551     tmp.worn.push_back( item( "backpack" ) );
552     tmp.set_fake( true );
553     tmp.set_attitude( z.friendly ? NPCATT_FOLLOW : NPCATT_KILL );
554     tmp.recoil = 0; // no need to aim
555 
556     for( const auto &pr : fake_skills ) {
557         tmp.set_skill_level( pr.first, pr.second );
558     }
559 
560     tmp.weapon = gun;
561     tmp.i_add( item( "UPS_off", calendar::turn, 1000 ) );
562 
563     add_msg_if_player_sees( z, m_warning, description.translated(), z.name(), tmp.weapon.tname() );
564 
565     z.ammo[ammo] -= tmp.fire_gun( target.pos(), gun.gun_current_mode().qty );
566 
567     if( require_targeting ) {
568         z.add_effect( effect_targeted, time_duration::from_turns( targeting_timeout_extend ) );
569     }
570 
571     if( laser_lock ) {
572         // To prevent spamming laser locks when the player can tank that stuff somehow
573         target.add_effect( effect_was_laserlocked, 5_turns );
574     }
575 }
576