1 #include "activity_actor.h"
2 
3 #include <algorithm>
4 #include <array>
5 #include <cmath>
6 #include <cstddef>
7 #include <cstdint>
8 #include <functional>
9 #include <list>
10 #include <map>
11 #include <new>
12 #include <string>
13 #include <type_traits>
14 #include <utility>
15 #include <vector>
16 
17 #include "action.h"
18 #include "activity_actor_definitions.h"
19 #include "activity_handlers.h" // put_into_vehicle_or_drop and drop_on_map
20 #include "advanced_inv.h"
21 #include "avatar.h"
22 #include "avatar_action.h"
23 #include "bodypart.h"
24 #include "calendar.h"
25 #include "character.h"
26 #include "coordinates.h"
27 #include "craft_command.h"
28 #include "debug.h"
29 #include "enums.h"
30 #include "event.h"
31 #include "event_bus.h"
32 #include "flag.h"
33 #include "game.h"
34 #include "game_constants.h"
35 #include "gates.h"
36 #include "gun_mode.h"
37 #include "handle_liquid.h"
38 #include "iexamine.h"
39 #include "item.h"
40 #include "item_contents.h"
41 #include "item_group.h"
42 #include "item_location.h"
43 #include "itype.h"
44 #include "json.h"
45 #include "line.h"
46 #include "map.h"
47 #include "map_iterator.h"
48 #include "mapdata.h"
49 #include "memory_fast.h"
50 #include "messages.h"
51 #include "monster.h"
52 #include "morale_types.h"
53 #include "mtype.h"
54 #include "npc.h"
55 #include "optional.h"
56 #include "options.h"
57 #include "output.h"
58 #include "pickup.h"
59 #include "pimpl.h"
60 #include "player.h"
61 #include "player_activity.h"
62 #include "point.h"
63 #include "ranged.h"
64 #include "recipe.h"
65 #include "requirements.h"
66 #include "ret_val.h"
67 #include "rng.h"
68 #include "sounds.h"
69 #include "string_formatter.h"
70 #include "timed_event.h"
71 #include "translations.h"
72 #include "ui.h"
73 #include "uistate.h"
74 #include "units.h"
75 #include "value_ptr.h"
76 #include "vehicle.h"
77 #include "vpart_position.h"
78 
79 static const efftype_id effect_pet( "pet" );
80 static const efftype_id effect_sleep( "sleep" );
81 
82 static const efftype_id effect_tied( "tied" );
83 
84 static const itype_id itype_bone_human( "bone_human" );
85 static const itype_id itype_disassembly( "disassembly" );
86 static const itype_id itype_electrohack( "electrohack" );
87 static const itype_id itype_pseudo_bio_picklock( "pseudo_bio_picklock" );
88 
89 static const skill_id skill_computer( "computer" );
90 static const skill_id skill_mechanics( "mechanics" );
91 static const skill_id skill_traps( "traps" );
92 
93 static const proficiency_id proficiency_prof_lockpicking( "prof_lockpicking" );
94 static const proficiency_id proficiency_prof_lockpicking_expert( "prof_lockpicking_expert" );
95 
96 static const mtype_id mon_zombie( "mon_zombie" );
97 static const mtype_id mon_zombie_fat( "mon_zombie_fat" );
98 static const mtype_id mon_zombie_rot( "mon_zombie_rot" );
99 static const mtype_id mon_skeleton( "mon_skeleton" );
100 static const mtype_id mon_zombie_crawler( "mon_zombie_crawler" );
101 
102 static const quality_id qual_LOCKPICK( "LOCKPICK" );
103 
get_progress_message(const player_activity & act) const104 std::string activity_actor::get_progress_message( const player_activity &act ) const
105 {
106     if( act.moves_total > 0 ) {
107         const int pct = ( ( act.moves_total - act.moves_left ) * 100 ) / act.moves_total;
108         return string_format( "%d%%", pct );
109     } else {
110         return std::string();
111     }
112 }
113 
aim_activity_actor()114 aim_activity_actor::aim_activity_actor()
115 {
116     initial_view_offset = get_avatar().view_offset;
117 }
118 
use_wielded()119 aim_activity_actor aim_activity_actor::use_wielded()
120 {
121     return aim_activity_actor();
122 }
123 
use_bionic(const item & fake_gun,const units::energy & cost_per_shot)124 aim_activity_actor aim_activity_actor::use_bionic( const item &fake_gun,
125         const units::energy &cost_per_shot )
126 {
127     aim_activity_actor act = aim_activity_actor();
128     act.bp_cost_per_shot = cost_per_shot;
129     act.fake_weapon = fake_gun;
130     return act;
131 }
132 
use_mutation(const item & fake_gun)133 aim_activity_actor aim_activity_actor::use_mutation( const item &fake_gun )
134 {
135     aim_activity_actor act = aim_activity_actor();
136     act.fake_weapon = fake_gun;
137     return act;
138 }
139 
start(player_activity & act,Character &)140 void aim_activity_actor::start( player_activity &act, Character &/*who*/ )
141 {
142     // Time spent on aiming is determined on the go by the player
143     act.moves_total = 1;
144     act.moves_left = 1;
145 }
146 
do_turn(player_activity & act,Character & who)147 void aim_activity_actor::do_turn( player_activity &act, Character &who )
148 {
149     if( !who.is_avatar() ) {
150         debugmsg( "ACT_AIM not implemented for NPCs" );
151         aborted = true;
152         act.moves_left = 0;
153         return;
154     }
155     avatar &you = get_avatar();
156 
157     item *weapon = get_weapon();
158     if( !weapon || !avatar_action::can_fire_weapon( you, get_map(), *weapon ) ) {
159         aborted = true;
160         act.moves_left = 0;
161         return;
162     }
163 
164     gun_mode gun = weapon->gun_current_mode();
165     if( first_turn && gun->has_flag( flag_RELOAD_AND_SHOOT ) && !gun->ammo_remaining() ) {
166         if( !load_RAS_weapon() ) {
167             aborted = true;
168             act.moves_left = 0;
169             return;
170         }
171     }
172 
173     g->temp_exit_fullscreen();
174     target_handler::trajectory trajectory = target_handler::mode_fire( you, *this );
175     g->reenter_fullscreen();
176 
177     if( aborted ) {
178         act.moves_left = 0;
179     } else {
180         if( !trajectory.empty() ) {
181             fin_trajectory = trajectory;
182             act.moves_left = 0;
183         }
184         // If aborting on the first turn, keep 'first_turn' as 'true'.
185         // This allows refunding moves spent on unloading RELOAD_AND_SHOOT weapons
186         // to simulate avatar not loading them in the first place
187         first_turn = false;
188 
189         // Allow interrupting activity only during 'aim and fire'.
190         // Prevents '.' key for 'aim for 10 turns' from conflicting with '.' key for 'interrupt activity'
191         // in case of high input lag (curses, sdl sometimes...), but allows to interrupt aiming
192         // if a bug happens / stars align to cause an endless aiming loop.
193         act.interruptable_with_kb = action != "AIM";
194     }
195 }
196 
finish(player_activity & act,Character & who)197 void aim_activity_actor::finish( player_activity &act, Character &who )
198 {
199     act.set_to_null();
200     restore_view();
201     item *weapon = get_weapon();
202     if( !weapon ) {
203         return;
204     }
205     if( aborted ) {
206         unload_RAS_weapon();
207         if( reload_requested ) {
208             // Reload the gun / select different arrows
209             // May assign ACT_RELOAD
210             g->reload_wielded( true );
211         }
212         return;
213     }
214 
215     // Fire!
216     gun_mode gun = weapon->gun_current_mode();
217     int shots_fired = static_cast<player *>( &who )->fire_gun( fin_trajectory.back(), gun.qty, *gun );
218 
219     // TODO: bionic power cost of firing should be derived from a value of the relevant weapon.
220     if( shots_fired && ( bp_cost_per_shot > 0_J ) ) {
221         who.mod_power_level( -bp_cost_per_shot * shots_fired );
222     }
223 }
224 
canceled(player_activity &,Character &)225 void aim_activity_actor::canceled( player_activity &/*act*/, Character &/*who*/ )
226 {
227     restore_view();
228     unload_RAS_weapon();
229 }
230 
serialize(JsonOut & jsout) const231 void aim_activity_actor::serialize( JsonOut &jsout ) const
232 {
233     jsout.start_object();
234 
235     jsout.member( "fake_weapon", fake_weapon );
236     jsout.member( "bp_cost_per_shot", bp_cost_per_shot );
237     jsout.member( "first_turn", first_turn );
238     jsout.member( "action", action );
239     jsout.member( "aif_duration", aif_duration );
240     jsout.member( "aiming_at_critter", aiming_at_critter );
241     jsout.member( "snap_to_target", snap_to_target );
242     jsout.member( "shifting_view", shifting_view );
243     jsout.member( "initial_view_offset", initial_view_offset );
244 
245     jsout.end_object();
246 }
247 
deserialize(JsonIn & jsin)248 std::unique_ptr<activity_actor> aim_activity_actor::deserialize( JsonIn &jsin )
249 {
250     aim_activity_actor actor = aim_activity_actor();
251 
252     JsonObject data = jsin.get_object();
253 
254     data.read( "fake_weapon", actor.fake_weapon );
255     data.read( "bp_cost_per_shot", actor.bp_cost_per_shot );
256     data.read( "first_turn", actor.first_turn );
257     data.read( "action", actor.action );
258     data.read( "aif_duration", actor.aif_duration );
259     data.read( "aiming_at_critter", actor.aiming_at_critter );
260     data.read( "snap_to_target", actor.snap_to_target );
261     data.read( "shifting_view", actor.shifting_view );
262     data.read( "initial_view_offset", actor.initial_view_offset );
263 
264     return actor.clone();
265 }
266 
get_weapon()267 item *aim_activity_actor::get_weapon()
268 {
269     if( fake_weapon.has_value() ) {
270         // TODO: check if the player lost relevant bionic/mutation
271         return &fake_weapon.value();
272     } else {
273         // Check for lost gun (e.g. yanked by zombie technician)
274         // TODO: check that this is the same gun that was used to start aiming
275         item *weapon = &get_player_character().weapon;
276         return weapon->is_null() ? nullptr : weapon;
277     }
278 }
279 
restore_view()280 void aim_activity_actor::restore_view()
281 {
282     avatar &player_character = get_avatar();
283     bool changed_z = player_character.view_offset.z != initial_view_offset.z;
284     player_character.view_offset = initial_view_offset;
285     if( changed_z ) {
286         get_map().invalidate_map_cache( player_character.view_offset.z );
287         g->invalidate_main_ui_adaptor();
288     }
289 }
290 
load_RAS_weapon()291 bool aim_activity_actor::load_RAS_weapon()
292 {
293     // TODO: use activity for fetching ammo and loading weapon
294     player &you = get_avatar();
295     item *weapon = get_weapon();
296     gun_mode gun = weapon->gun_current_mode();
297     const auto ammo_location_is_valid = [&]() -> bool {
298         if( !you.ammo_location )
299         {
300             return false;
301         }
302         if( !gun->can_reload_with( you.ammo_location->typeId() ) )
303         {
304             return false;
305         }
306         if( square_dist( you.pos(), you.ammo_location.position() ) > 1 )
307         {
308             return false;
309         }
310         return true;
311     };
312     item::reload_option opt = ammo_location_is_valid() ? item::reload_option( &you, weapon,
313                               weapon, you.ammo_location ) : you.select_ammo( *gun );
314     if( !opt ) {
315         // Menu canceled
316         return false;
317     }
318     int reload_time = 0;
319     reload_time += opt.moves();
320     if( !gun->reload( you, std::move( opt.ammo ), 1 ) ) {
321         // Reload not allowed
322         return false;
323     }
324 
325     // Burn 0.2% max base stamina x the strength required to fire.
326     you.mod_stamina( gun->get_min_str() * static_cast<int>( 0.002f *
327                      get_option<int>( "PLAYER_MAX_STAMINA" ) ) );
328     // At low stamina levels, firing starts getting slow.
329     int sta_percent = ( 100 * you.get_stamina() ) / you.get_stamina_max();
330     reload_time += ( sta_percent < 25 ) ? ( ( 25 - sta_percent ) * 2 ) : 0;
331 
332     you.moves -= reload_time;
333     return true;
334 }
335 
unload_RAS_weapon()336 void aim_activity_actor::unload_RAS_weapon()
337 {
338     // Unload reload-and-shoot weapons to avoid leaving bows pre-loaded with arrows
339     avatar &you = get_avatar();
340     item *weapon = get_weapon();
341     if( !weapon ) {
342         return;
343     }
344 
345     gun_mode gun = weapon->gun_current_mode();
346     if( gun->has_flag( flag_RELOAD_AND_SHOOT ) ) {
347         int moves_before_unload = you.moves;
348 
349         // Note: this code works only for avatar
350         item_location loc = item_location( you, gun.target );
351         you.unload( loc, true );
352 
353         // Give back time for unloading as essentially nothing has been done.
354         if( first_turn ) {
355             you.moves = moves_before_unload;
356         }
357     }
358 }
359 
start(player_activity & act,Character & who)360 void autodrive_activity_actor::start( player_activity &act, Character &who )
361 {
362     const bool in_vehicle = who.in_vehicle && who.controlling_vehicle;
363     const map &here = get_map();
364     const optional_vpart_position vp = here.veh_at( who.pos() );
365     if( !( vp && in_vehicle ) ) {
366         who.cancel_activity();
367         return;
368     }
369 
370     player_vehicle = &vp->vehicle();
371     act.moves_left = calendar::INDEFINITELY_LONG;
372     who.moves = 0;
373 }
374 
do_turn(player_activity & act,Character & who)375 void autodrive_activity_actor::do_turn( player_activity &act, Character &who )
376 {
377     if( who.in_vehicle && who.controlling_vehicle && player_vehicle && player_vehicle->is_autodriving &&
378         !who.omt_path.empty() && !player_vehicle->omt_path.empty() ) {
379         player_vehicle->do_autodrive();
380         if( who.global_omt_location() == who.omt_path.back() ) {
381             who.omt_path.pop_back();
382         }
383         who.moves = 0;
384     } else {
385         who.cancel_activity();
386         return;
387     }
388     if( player_vehicle->omt_path.empty() ) {
389         act.moves_left = 0;
390     }
391 }
392 
canceled(player_activity & act,Character & who)393 void autodrive_activity_actor::canceled( player_activity &act, Character &who )
394 {
395     who.add_msg_if_player( m_info, _( "Auto-drive canceled." ) );
396     if( player_vehicle && !player_vehicle->omt_path.empty() ) {
397         player_vehicle->omt_path.clear();
398     }
399     if( !who.omt_path.empty() ) {
400         who.omt_path.clear();
401     }
402     if( player_vehicle ) {
403         player_vehicle->is_autodriving = false;
404     }
405     act.set_to_null();
406 }
407 
finish(player_activity & act,Character & who)408 void autodrive_activity_actor::finish( player_activity &act, Character &who )
409 {
410     who.add_msg_if_player( m_info, _( "You have reached your destination." ) );
411     player_vehicle->is_autodriving = false;
412     act.set_to_null();
413 }
414 
serialize(JsonOut & jsout) const415 void autodrive_activity_actor::serialize( JsonOut &jsout ) const
416 {
417     // Activity is not being saved but still provide some valid json if called.
418     jsout.write_null();
419 }
420 
deserialize(JsonIn &)421 std::unique_ptr<activity_actor> autodrive_activity_actor::deserialize( JsonIn & )
422 {
423     return autodrive_activity_actor().clone();
424 }
425 
start(player_activity & act,Character &)426 void dig_activity_actor::start( player_activity &act, Character & )
427 {
428     act.moves_total = moves_total;
429     act.moves_left = moves_total;
430 }
431 
do_turn(player_activity &,Character & who)432 void dig_activity_actor::do_turn( player_activity &, Character &who )
433 {
434     sfx::play_activity_sound( "tool", "shovel", sfx::get_heard_volume( location ) );
435     if( calendar::once_every( 1_minutes ) ) {
436         //~ Sound of a shovel digging a pit at work!
437         sounds::sound( location, 10, sounds::sound_t::activity, _( "hsh!" ) );
438     }
439     get_map().maybe_trigger_trap( location, who, true );
440 }
441 
finish(player_activity & act,Character & who)442 void dig_activity_actor::finish( player_activity &act, Character &who )
443 {
444     map &here = get_map();
445     const bool grave = here.ter( location ) == t_grave;
446 
447     if( grave ) {
448         if( one_in( 10 ) ) {
449             static const std::array<mtype_id, 5> monids = { {
450                     mon_zombie, mon_zombie_fat,
451                     mon_zombie_rot, mon_skeleton,
452                     mon_zombie_crawler
453                 }
454             };
455 
456             g->place_critter_at( random_entry( monids ), byproducts_location );
457             here.furn_set( location, f_coffin_o );
458             who.add_msg_if_player( m_warning, _( "Something crawls out of the coffin!" ) );
459         } else {
460             here.spawn_item( location, itype_bone_human, rng( 5, 15 ) );
461             here.furn_set( location, f_coffin_c );
462         }
463         std::vector<item *> dropped =
464             here.place_items( item_group_id( "allclothes" ), 50, location, location, false,
465                               calendar::turn );
466         here.place_items( item_group_id( "grave" ), 25, location, location, false,
467                           calendar::turn );
468         here.place_items( item_group_id( "jewelry_front" ), 20, location, location, false,
469                           calendar::turn );
470         for( item * const &it : dropped ) {
471             if( it->is_armor() ) {
472                 it->set_flag( flag_FILTHY );
473                 it->set_damage( rng( 1, it->max_damage() - 1 ) );
474             }
475         }
476         get_event_bus().send<event_type::exhumes_grave>( who.getID() );
477     }
478 
479     here.ter_set( location, ter_id( result_terrain ) );
480 
481     for( int i = 0; i < byproducts_count; i++ ) {
482         here.spawn_items( byproducts_location, item_group::items_from( byproducts_item_group,
483                           calendar::turn ) );
484     }
485 
486     const int helpersize = get_player_character().get_num_crafting_helpers( 3 );
487     who.mod_stored_nutr( 5 - helpersize );
488     who.mod_thirst( 5 - helpersize );
489     who.mod_fatigue( 10 - ( helpersize * 2 ) );
490     if( grave ) {
491         who.add_msg_if_player( m_good, _( "You finish exhuming a grave." ) );
492     } else {
493         who.add_msg_if_player( m_good, _( "You finish digging the %s." ),
494                                here.ter( location ).obj().name() );
495     }
496 
497     act.set_to_null();
498 }
499 
serialize(JsonOut & jsout) const500 void dig_activity_actor::serialize( JsonOut &jsout ) const
501 {
502     jsout.start_object();
503 
504     jsout.member( "moves", moves_total );
505     jsout.member( "location", location );
506     jsout.member( "result_terrain", result_terrain );
507     jsout.member( "byproducts_location", byproducts_location );
508     jsout.member( "byproducts_count", byproducts_count );
509     jsout.member( "byproducts_item_group", byproducts_item_group );
510 
511     jsout.end_object();
512 }
513 
deserialize(JsonIn & jsin)514 std::unique_ptr<activity_actor> dig_activity_actor::deserialize( JsonIn &jsin )
515 {
516     dig_activity_actor actor( 0, tripoint_zero,
517                               {}, tripoint_zero, 0, {} );
518 
519     JsonObject data = jsin.get_object();
520 
521     data.read( "moves", actor.moves_total );
522     data.read( "location", actor.location );
523     data.read( "result_terrain", actor.result_terrain );
524     data.read( "byproducts_location", actor.byproducts_location );
525     data.read( "byproducts_count", actor.byproducts_count );
526     data.read( "byproducts_item_group", actor.byproducts_item_group );
527 
528     return actor.clone();
529 }
530 
start(player_activity & act,Character &)531 void dig_channel_activity_actor::start( player_activity &act, Character & )
532 {
533     act.moves_total = moves_total;
534     act.moves_left = moves_total;
535 }
536 
do_turn(player_activity &,Character & who)537 void dig_channel_activity_actor::do_turn( player_activity &, Character &who )
538 {
539     sfx::play_activity_sound( "tool", "shovel", sfx::get_heard_volume( location ) );
540     if( calendar::once_every( 1_minutes ) ) {
541         //~ Sound of a shovel digging a pit at work!
542         sounds::sound( location, 10, sounds::sound_t::activity, _( "hsh!" ) );
543     }
544     get_map().maybe_trigger_trap( location, who, true );
545 }
546 
finish(player_activity & act,Character & who)547 void dig_channel_activity_actor::finish( player_activity &act, Character &who )
548 {
549     map &here = get_map();
550     here.ter_set( location, ter_id( result_terrain ) );
551 
552     for( int i = 0; i < byproducts_count; i++ ) {
553         here.spawn_items( byproducts_location, item_group::items_from( byproducts_item_group,
554                           calendar::turn ) );
555     }
556 
557     const int helpersize = get_player_character().get_num_crafting_helpers( 3 );
558     who.mod_stored_nutr( 5 - helpersize );
559     who.mod_thirst( 5 - helpersize );
560     who.mod_fatigue( 10 - ( helpersize * 2 ) );
561     who.add_msg_if_player( m_good, _( "You finish digging up %s." ),
562                            here.ter( location ).obj().name() );
563 
564     act.set_to_null();
565 }
566 
serialize(JsonOut & jsout) const567 void dig_channel_activity_actor::serialize( JsonOut &jsout ) const
568 {
569     jsout.start_object();
570 
571     jsout.member( "moves", moves_total );
572     jsout.member( "location", location );
573     jsout.member( "result_terrain", result_terrain );
574     jsout.member( "byproducts_location", byproducts_location );
575     jsout.member( "byproducts_count", byproducts_count );
576     jsout.member( "byproducts_item_group", byproducts_item_group );
577 
578     jsout.end_object();
579 }
580 
deserialize(JsonIn & jsin)581 std::unique_ptr<activity_actor> dig_channel_activity_actor::deserialize( JsonIn &jsin )
582 {
583     dig_channel_activity_actor actor( 0, tripoint_zero,
584                                       {}, tripoint_zero, 0, {} );
585 
586     JsonObject data = jsin.get_object();
587 
588     data.read( "moves", actor.moves_total );
589     data.read( "location", actor.location );
590     data.read( "result_terrain", actor.result_terrain );
591     data.read( "byproducts_location", actor.byproducts_location );
592     data.read( "byproducts_count", actor.byproducts_count );
593     data.read( "byproducts_item_group", actor.byproducts_item_group );
594 
595     return actor.clone();
596 }
597 
start(player_activity & act,Character &)598 void gunmod_remove_activity_actor::start( player_activity &act, Character & )
599 {
600     act.moves_total = moves_total;
601     act.moves_left = moves_total;
602 }
603 
finish(player_activity & act,Character & who)604 void gunmod_remove_activity_actor::finish( player_activity &act, Character &who )
605 {
606     item *it_gun = gun.get_item();
607     if( !it_gun ) {
608         debugmsg( "ACT_GUNMOD_REMOVE lost target gun" );
609         act.set_to_null();
610         return;
611     }
612     item *it_mod = nullptr;
613     std::vector<item *> mods = it_gun->gunmods();
614     if( gunmod_idx >= 0 && mods.size() > static_cast<size_t>( gunmod_idx ) ) {
615         it_mod = mods[gunmod_idx];
616     } else {
617         debugmsg( "ACT_GUNMOD_REMOVE lost target gunmod" );
618         act.set_to_null();
619         return;
620     }
621     act.set_to_null();
622     gunmod_remove( who, *it_gun, *it_mod );
623 }
624 
gunmod_unload(Character & who,item & gunmod)625 bool gunmod_remove_activity_actor::gunmod_unload( Character &who, item &gunmod )
626 {
627     if( gunmod.has_flag( flag_BRASS_CATCHER ) ) {
628         // Exclude brass catchers so that removing them wouldn't spill the casings
629         return true;
630     }
631     // TODO: unloading gunmods happens instantaneously in some cases, but should take time
632     item_location loc = item_location( who, &gunmod );
633     return !( gunmod.ammo_remaining() && !who.as_player()->unload( loc, true ) );
634 }
635 
gunmod_remove(Character & who,item & gun,item & mod)636 void gunmod_remove_activity_actor::gunmod_remove( Character &who, item &gun, item &mod )
637 {
638     if( !gunmod_unload( who, mod ) ) {
639         return;
640     }
641 
642     gun.gun_set_mode( gun_mode_id( "DEFAULT" ) );
643     const itype *modtype = mod.type;
644 
645     who.i_add_or_drop( mod );
646     gun.remove_item( mod );
647 
648     // If the removed gunmod added mod locations, check to see if any mods are in invalid locations
649     if( !modtype->gunmod->add_mod.empty() ) {
650         std::map<gunmod_location, int> mod_locations = gun.get_mod_locations();
651         for( const auto &slot : mod_locations ) {
652             int free_slots = gun.get_free_mod_locations( slot.first );
653 
654             for( item *the_mod : gun.gunmods() ) {
655                 if( the_mod->type->gunmod->location == slot.first && free_slots < 0 ) {
656                     gunmod_remove( who, gun, *the_mod );
657                     free_slots++;
658                 } else if( mod_locations.find( the_mod->type->gunmod->location ) ==
659                            mod_locations.end() ) {
660                     gunmod_remove( who, gun, *the_mod );
661                 }
662             }
663         }
664     }
665 
666     //~ %1$s - gunmod, %2$s - gun.
667     who.add_msg_if_player( _( "You remove your %1$s from your %2$s." ), modtype->nname( 1 ),
668                            gun.tname() );
669 }
670 
serialize(JsonOut & jsout) const671 void gunmod_remove_activity_actor::serialize( JsonOut &jsout ) const
672 {
673     jsout.start_object();
674 
675     jsout.member( "moves_total", moves_total );
676     jsout.member( "gun", gun );
677     jsout.member( "gunmod", gunmod_idx );
678 
679     jsout.end_object();
680 }
681 
deserialize(JsonIn & jsin)682 std::unique_ptr<activity_actor> gunmod_remove_activity_actor::deserialize( JsonIn &jsin )
683 {
684     gunmod_remove_activity_actor actor( 0, item_location(), -1 );
685 
686     JsonObject data = jsin.get_object();
687 
688     data.read( "moves_total", actor.moves_total );
689     data.read( "gun", actor.gun );
690     data.read( "gunmod", actor.gunmod_idx );
691 
692     return actor.clone();
693 }
694 
start(player_activity & act,Character &)695 void hacking_activity_actor::start( player_activity &act, Character & )
696 {
697     act.moves_total = to_moves<int>( 5_minutes );
698     act.moves_left = to_moves<int>( 5_minutes );
699 }
700 
701 enum class hack_result : int {
702     UNABLE,
703     FAIL,
704     NOTHING,
705     SUCCESS
706 };
707 
708 enum class hack_type : int {
709     SAFE,
710     DOOR,
711     GAS,
712     NONE
713 };
714 
hack_level(const Character & who)715 static int hack_level( const Character &who )
716 {
717     ///\EFFECT_COMPUTER increases success chance of hacking card readers
718     // odds go up with int>8, down with int<8
719     // 4 int stat is worth 1 computer skill here
720     ///\EFFECT_INT increases success chance of hacking card readers
721     return who.get_skill_level( skill_computer ) + who.int_cur / 2 - 8;
722 }
723 
hack_attempt(Character & who,const bool using_bionic)724 static hack_result hack_attempt( Character &who, const bool using_bionic )
725 {
726     // TODO: Remove this once player -> Character migration is complete
727     {
728         player *p = dynamic_cast<player *>( &who );
729         p->practice( skill_computer, 20 );
730     }
731 
732     // only skilled supergenius never cause short circuits, but the odds are low for people
733     // with moderate skills
734     const int hack_stddev = 5;
735     int success = std::ceil( normal_roll( hack_level( who ), hack_stddev ) );
736     if( success < 0 ) {
737         who.add_msg_if_player( _( "You cause a short circuit!" ) );
738         if( using_bionic ) {
739             who.mod_power_level( -25_kJ );
740         } else {
741             who.use_charges( itype_electrohack, 25 );
742         }
743 
744         if( success <= -5 ) {
745             if( !using_bionic ) {
746                 who.add_msg_if_player( m_bad, _( "Your electrohack is ruined!" ) );
747                 who.use_amount( itype_electrohack, 1 );
748             } else {
749                 who.add_msg_if_player( m_bad, _( "Your power is drained!" ) );
750                 who.mod_power_level( units::from_kilojoule( -rng( 25,
751                                      units::to_kilojoule( who.get_power_level() ) ) ) );
752             }
753         }
754         return hack_result::FAIL;
755     } else if( success < 6 ) {
756         return hack_result::NOTHING;
757     } else {
758         return hack_result::SUCCESS;
759     }
760 }
761 
get_hack_type(const tripoint & examp)762 static hack_type get_hack_type( const tripoint &examp )
763 {
764     hack_type type = hack_type::NONE;
765     map &here = get_map();
766     const furn_t &xfurn_t = here.furn( examp ).obj();
767     const ter_t &xter_t = here.ter( examp ).obj();
768     if( xter_t.has_examine( iexamine::pay_gas ) || xfurn_t.has_examine( iexamine::pay_gas ) ) {
769         type = hack_type::GAS;
770     } else if( xter_t.has_examine( iexamine::cardreader ) ||
771                xfurn_t.has_examine( iexamine::cardreader ) ) {
772         type = hack_type::DOOR;
773     } else if( xter_t.has_examine( iexamine::gunsafe_el ) ||
774                xfurn_t.has_examine( iexamine::gunsafe_el ) ) {
775         type = hack_type::SAFE;
776     }
777     return type;
778 }
779 
hacking_activity_actor(use_bionic)780 hacking_activity_actor::hacking_activity_actor( use_bionic )
781     : using_bionic( true )
782 {
783 }
784 
finish(player_activity & act,Character & who)785 void hacking_activity_actor::finish( player_activity &act, Character &who )
786 {
787     tripoint examp = act.placement;
788     hack_type type = get_hack_type( examp );
789     switch( hack_attempt( who, using_bionic ) ) {
790         case hack_result::UNABLE:
791             who.add_msg_if_player( _( "You cannot hack this." ) );
792             break;
793         case hack_result::FAIL:
794             // currently all things that can be hacked have equivalent alarm failure states.
795             // this may not always be the case with new hackable things.
796             get_event_bus().send<event_type::triggers_alarm>( who.getID() );
797             sounds::sound( who.pos(), 60, sounds::sound_t::music, _( "an alarm sound!" ), true, "environment",
798                            "alarm" );
799             if( examp.z > 0 && !get_timed_events().queued( timed_event_type::WANTED ) ) {
800                 get_timed_events().add( timed_event_type::WANTED, calendar::turn + 30_minutes, 0,
801                                         who.global_sm_location() );
802             }
803             break;
804         case hack_result::NOTHING:
805             who.add_msg_if_player( _( "You fail the hack, but no alarms are triggered." ) );
806             break;
807         case hack_result::SUCCESS:
808             map &here = get_map();
809             if( type == hack_type::GAS ) {
810                 int tankUnits;
811                 std::string fuelType;
812                 const cata::optional<tripoint> pTank_ = iexamine::getNearFilledGasTank( examp, tankUnits,
813                                                         fuelType );
814                 if( !pTank_ ) {
815                     break;
816                 }
817                 const tripoint pTank = *pTank_;
818                 const cata::optional<tripoint> pGasPump = iexamine::getGasPumpByNumber( examp,
819                         uistate.ags_pay_gas_selected_pump );
820                 if( pGasPump && iexamine::toPumpFuel( pTank, *pGasPump, tankUnits ) ) {
821                     who.add_msg_if_player( _( "You hack the terminal and route all available fuel to your pump!" ) );
822                     sounds::sound( examp, 6, sounds::sound_t::activity,
823                                    _( "Glug Glug Glug Glug Glug Glug Glug Glug Glug" ), true, "tool", "gaspump" );
824                 } else {
825                     who.add_msg_if_player( _( "Nothing happens." ) );
826                 }
827             } else if( type == hack_type::SAFE ) {
828                 who.add_msg_if_player( m_good, _( "The door on the safe swings open." ) );
829                 here.furn_set( examp, furn_str_id( "f_safe_o" ) );
830             } else if( type == hack_type::DOOR ) {
831                 who.add_msg_if_player( _( "You activate the panel!" ) );
832                 who.add_msg_if_player( m_good, _( "The nearby doors unlock." ) );
833                 here.ter_set( examp, t_card_reader_broken );
834                 for( const tripoint &tmp : here.points_in_radius( ( examp ), 3 ) ) {
835                     if( here.ter( tmp ) == t_door_metal_locked ) {
836                         here.ter_set( tmp, t_door_metal_c );
837                     }
838                 }
839             }
840             break;
841     }
842     act.set_to_null();
843 }
844 
serialize(JsonOut & jsout) const845 void hacking_activity_actor::serialize( JsonOut &jsout ) const
846 {
847     jsout.start_object();
848     jsout.member( "using_bionic", using_bionic );
849     jsout.end_object();
850 }
851 
deserialize(JsonIn & jsin)852 std::unique_ptr<activity_actor> hacking_activity_actor::deserialize( JsonIn &jsin )
853 {
854     hacking_activity_actor actor;
855     if( jsin.test_null() ) {
856         // Old saves might contain a null instead of an object.
857         // Since we do not know whether a bionic or an item was chosen we assume
858         // it was an item.
859         actor.using_bionic = false;
860     } else {
861         JsonObject jsobj = jsin.get_object();
862         jsobj.read( "using_bionic", actor.using_bionic );
863     }
864     return actor.clone();
865 }
866 
start(player_activity & act,Character &)867 void hotwire_car_activity_actor::start( player_activity &act, Character & )
868 {
869     act.moves_total = moves_total;
870     act.moves_left = moves_total;
871 }
872 
do_turn(player_activity & act,Character &)873 void hotwire_car_activity_actor::do_turn( player_activity &act, Character & )
874 {
875     map &here = get_map();
876     if( calendar::once_every( 1_minutes ) ) {
877         bool lost = !here.veh_at( here.getlocal( target ) ).has_value();
878         if( lost ) {
879             act.set_to_null();
880             debugmsg( "Lost ACT_HOTWIRE_CAR target vehicle" );
881         }
882     }
883 }
884 
finish(player_activity & act,Character & who)885 void hotwire_car_activity_actor::finish( player_activity &act, Character &who )
886 {
887     act.set_to_null();
888 
889     map &here = get_map();
890     const optional_vpart_position vp = here.veh_at( here.getlocal( target ) );
891     if( !vp ) {
892         debugmsg( "Lost ACT_HOTWIRE_CAR target vehicle" );
893         return;
894     }
895     vehicle &veh = vp->vehicle();
896 
897     int skill = who.get_skill_level( skill_mechanics );
898     if( skill > rng( 1, 6 ) ) {
899         // Success
900         who.add_msg_if_player( _( "You found the wire that starts the engine." ) );
901         veh.is_locked = false;
902     } else if( skill > rng( 0, 4 ) ) {
903         // Soft fail
904         who.add_msg_if_player( _( "You found a wire that looks like the right one." ) );
905         veh.is_alarm_on = veh.has_security_working();
906         veh.is_locked = false;
907     } else if( !veh.is_alarm_on ) {
908         // Hard fail
909         who.add_msg_if_player( _( "The red wire always starts the engine, doesn't it?" ) );
910         veh.is_alarm_on = veh.has_security_working();
911     } else {
912         // Already failed
913         who.add_msg_if_player(
914             _( "By process of elimination, you found the wire that starts the engine." ) );
915         veh.is_locked = false;
916     }
917 }
918 
serialize(JsonOut & jsout) const919 void hotwire_car_activity_actor::serialize( JsonOut &jsout ) const
920 {
921     jsout.start_object();
922 
923     jsout.member( "target", target );
924     jsout.member( "moves_total", moves_total );
925 
926     jsout.end_object();
927 }
928 
deserialize(JsonIn & jsin)929 std::unique_ptr<activity_actor> hotwire_car_activity_actor::deserialize( JsonIn &jsin )
930 {
931     hotwire_car_activity_actor actor( 0, tripoint_zero );
932 
933     JsonObject data = jsin.get_object();
934 
935     data.read( "target", actor.target );
936     data.read( "moves_total", actor.moves_total );
937 
938     return actor.clone();
939 }
940 
do_turn(player_activity & act,Character & who)941 void move_items_activity_actor::do_turn( player_activity &act, Character &who )
942 {
943     const tripoint dest = relative_destination + who.pos();
944 
945     while( who.moves > 0 && !target_items.empty() ) {
946         item_location target = std::move( target_items.back() );
947         const int quantity = quantities.back();
948         target_items.pop_back();
949         quantities.pop_back();
950 
951         if( !target ) {
952             debugmsg( "Lost target item of ACT_MOVE_ITEMS" );
953             continue;
954         }
955 
956         // Check that we can pick it up.
957         if( !target->made_of_from_type( phase_id::LIQUID ) ) {
958             item &leftovers = *target;
959             // Make a copy to be put in the destination location
960             item newit = leftovers;
961 
962             if( newit.is_owned_by( who, true ) ) {
963                 newit.set_owner( who );
964             } else {
965                 continue;
966             }
967 
968             // Handle charges, quantity == 0 means move all
969             if( quantity != 0 && newit.count_by_charges() ) {
970                 newit.charges = std::min( newit.charges, quantity );
971                 leftovers.charges -= quantity;
972             } else {
973                 leftovers.charges = 0;
974             }
975 
976             // This is for hauling across zlevels, remove when going up and down stairs
977             // is no longer teleportation
978             const tripoint src = target.position();
979             const int distance = src.z == dest.z ? std::max( rl_dist( src, dest ), 1 ) : 1;
980             // Yuck, I'm sticking weariness scaling based on activity level here
981             const float weary_mult = who.exertion_adjusted_move_multiplier( exertion_level() );
982             who.mod_moves( -Pickup::cost_to_move_item( who, newit ) * distance * weary_mult );
983             if( to_vehicle ) {
984                 put_into_vehicle_or_drop( who, item_drop_reason::deliberate, { newit }, dest );
985             } else {
986                 drop_on_map( who, item_drop_reason::deliberate, { newit }, dest );
987             }
988             // If we picked up a whole stack, remove the leftover item
989             if( leftovers.charges <= 0 ) {
990                 target.remove_item();
991             }
992         }
993     }
994 
995     if( target_items.empty() ) {
996         // Nuke the current activity, leaving the backlog alone.
997         act.set_to_null();
998         if( who.is_hauling() && !get_map().has_haulable_items( who.pos() ) ) {
999             who.stop_hauling();
1000         }
1001     }
1002 }
1003 
serialize(JsonOut & jsout) const1004 void move_items_activity_actor::serialize( JsonOut &jsout ) const
1005 {
1006     jsout.start_object();
1007 
1008     jsout.member( "target_items", target_items );
1009     jsout.member( "quantities", quantities );
1010     jsout.member( "to_vehicle", to_vehicle );
1011     jsout.member( "relative_destination", relative_destination );
1012 
1013     jsout.end_object();
1014 }
1015 
deserialize(JsonIn & jsin)1016 std::unique_ptr<activity_actor> move_items_activity_actor::deserialize( JsonIn &jsin )
1017 {
1018     move_items_activity_actor actor( {}, {}, false, tripoint_zero );
1019 
1020     JsonObject data = jsin.get_object();
1021 
1022     data.read( "target_items", actor.target_items );
1023     data.read( "quantities", actor.quantities );
1024     data.read( "to_vehicle", actor.to_vehicle );
1025     data.read( "relative_destination", actor.relative_destination );
1026 
1027     return actor.clone();
1028 }
1029 
cancel_pickup(Character & who)1030 static void cancel_pickup( Character &who )
1031 {
1032     who.cancel_activity();
1033     if( who.is_hauling() && !get_map().has_haulable_items( who.pos() ) ) {
1034         who.stop_hauling();
1035     }
1036 }
1037 
do_turn(player_activity &,Character & who)1038 void pickup_activity_actor::do_turn( player_activity &, Character &who )
1039 {
1040     // If we don't have target items bail out
1041     if( target_items.empty() ) {
1042         cancel_pickup( who );
1043         return;
1044     }
1045 
1046     // If the player moves while picking up (i.e.: in a moving vehicle) cancel
1047     // the activity, only populate starting_pos when grabbing from the ground
1048     if( starting_pos && *starting_pos != who.pos() ) {
1049         who.add_msg_if_player( _( "Moving canceled auto-pickup." ) );
1050         cancel_pickup( who );
1051         return;
1052     }
1053 
1054     // Auto_resume implies autopickup.
1055     const bool autopickup = who.activity.auto_resume;
1056 
1057     // False indicates that the player canceled pickup when met with some prompt
1058     const bool keep_going = Pickup::do_pickup( target_items, quantities, autopickup );
1059 
1060     // If there are items left we ran out of moves, so continue the activity
1061     // Otherwise, we are done.
1062     if( !keep_going || target_items.empty() ) {
1063         cancel_pickup( who );
1064 
1065         if( who.get_value( "THIEF_MODE_KEEP" ) != "YES" ) {
1066             who.set_value( "THIEF_MODE", "THIEF_ASK" );
1067         }
1068 
1069         if( !keep_going ) {
1070             // The user canceled the activity, so we're done
1071             // AIM might have more pickup activities pending, also cancel them.
1072             // TODO: Move this to advanced inventory instead of hacking it in here
1073             cancel_aim_processing();
1074         }
1075     }
1076 }
1077 
serialize(JsonOut & jsout) const1078 void pickup_activity_actor::serialize( JsonOut &jsout ) const
1079 {
1080     jsout.start_object();
1081 
1082     jsout.member( "target_items", target_items );
1083     jsout.member( "quantities", quantities );
1084     jsout.member( "starting_pos", starting_pos );
1085 
1086     jsout.end_object();
1087 }
1088 
deserialize(JsonIn & jsin)1089 std::unique_ptr<activity_actor> pickup_activity_actor::deserialize( JsonIn &jsin )
1090 {
1091     pickup_activity_actor actor( {}, {}, cata::nullopt );
1092 
1093     JsonObject data = jsin.get_object();
1094 
1095     data.read( "target_items", actor.target_items );
1096     data.read( "quantities", actor.quantities );
1097     data.read( "starting_pos", actor.starting_pos );
1098 
1099     return actor.clone();
1100 }
1101 
use_item(int moves_total,const item_location & lockpick,const tripoint & target)1102 lockpick_activity_actor lockpick_activity_actor::use_item(
1103     int moves_total,
1104     const item_location &lockpick,
1105     const tripoint &target
1106 )
1107 {
1108     return lockpick_activity_actor {
1109         moves_total,
1110         lockpick,
1111         cata::nullopt,
1112         target
1113     };
1114 }
1115 
use_bionic(const tripoint & target)1116 lockpick_activity_actor lockpick_activity_actor::use_bionic(
1117     const tripoint &target
1118 )
1119 {
1120     return lockpick_activity_actor {
1121         to_moves<int>( 4_seconds ),
1122         cata::nullopt,
1123         item( itype_pseudo_bio_picklock ),
1124         target
1125     };
1126 }
1127 
start(player_activity & act,Character &)1128 void lockpick_activity_actor::start( player_activity &act, Character & )
1129 {
1130     act.moves_left = moves_total;
1131     act.moves_total = moves_total;
1132 }
1133 
finish(player_activity & act,Character & who)1134 void lockpick_activity_actor::finish( player_activity &act, Character &who )
1135 {
1136     act.set_to_null();
1137 
1138     item *it;
1139     if( lockpick.has_value() ) {
1140         it = ( *lockpick ).get_item();
1141     } else {
1142         it = &*fake_lockpick;
1143     }
1144 
1145     if( !it ) {
1146         debugmsg( "Lost ACT_LOCKPICK item" );
1147         return;
1148     }
1149 
1150     map &here = get_map();
1151     const tripoint target = here.getlocal( this->target );
1152     const ter_id ter_type = here.ter( target );
1153     const furn_id furn_type = here.furn( target );
1154     ter_id new_ter_type;
1155     furn_id new_furn_type;
1156     std::string open_message;
1157     if( ter_type == t_chaingate_l ) {
1158         new_ter_type = t_chaingate_c;
1159         open_message = _( "With a satisfying click, the lock on the gate opens." );
1160     } else if( ter_type == t_door_locked || ter_type == t_door_locked_alarm ||
1161                ter_type == t_door_locked_interior ) {
1162         new_ter_type = t_door_c;
1163         open_message = _( "With a satisfying click, the lock on the door opens." );
1164     } else if( ter_type == t_door_locked_peep ) {
1165         new_ter_type = t_door_c_peep;
1166         open_message = _( "With a satisfying click, the lock on the door opens." );
1167     } else if( ter_type == t_door_metal_pickable ) {
1168         new_ter_type = t_door_metal_c;
1169         open_message = _( "With a satisfying click, the lock on the door opens." );
1170     } else if( ter_type == t_door_bar_locked ) {
1171         new_ter_type = t_door_bar_o;
1172         //Bar doors auto-open (and lock if closed again) so show a different message)
1173         open_message = _( "The door swings open…" );
1174     } else if( furn_type == f_gunsafe_ml ) {
1175         new_furn_type = f_safe_o;
1176         open_message = _( "With a satisfying click, the lock on the door opens." );
1177     }
1178 
1179     bool perfect = it->has_flag( flag_PERFECT_LOCKPICK );
1180     bool destroy = false;
1181 
1182     // Your devices skill is the primary skill that applies to your roll. Your mechanics skill has a little input.
1183     const float weighted_skill_average = ( 3.0f * who.get_skill_level(
1184             skill_traps ) + who.get_skill_level( skill_mechanics ) ) / 4.0f;
1185 
1186     // Your dexterity determines most of your stat contribution, but your intelligence and perception combined are about half as much.
1187     const float weighted_stat_average = ( 6.0f * who.dex_cur + 2.0f * who.per_cur +
1188                                           who.int_cur ) / 9.0f;
1189 
1190     // Get a bonus from your lockpick quality if the quality is higher than 3, or a penalty if it is lower. For a bobby pin this puts you at -2, for a locksmith kit, +2.
1191     const float tool_effect = ( it->get_quality( qual_LOCKPICK ) - 3 ) - ( it->damage() / 2000.0 );
1192 
1193     // Without at least a basic lockpick proficiency, your skill level is effectively 6 levels lower.
1194     int proficiency_effect = -3;
1195     int duration_proficiency_factor = 10;
1196     if( who.has_proficiency( proficiency_prof_lockpicking ) ) {
1197         // If you have the basic lockpick prof, negate the above penalty
1198         proficiency_effect = 0;
1199         duration_proficiency_factor = 5;
1200     }
1201     if( who.has_proficiency( proficiency_prof_lockpicking_expert ) ) {
1202         // If you have the locksmith proficiency, your skill level is effectively 4 levels higher.
1203         proficiency_effect = 3;
1204         duration_proficiency_factor = 1;
1205     }
1206 
1207     // We get our average roll by adding the above factors together. For a person with no skill, average stats, no proficiencies, and an improvised lockpick, mean_roll will be 2.
1208     const float mean_roll = weighted_skill_average + ( weighted_stat_average / 4 ) + proficiency_effect
1209                             + tool_effect;
1210 
1211     // A standard deviation of 2 means that about 2/3 of rolls will come within 2 points of your mean_roll. Lockpicking
1212     int pick_roll = std::round( normal_roll( mean_roll, 2 ) );
1213 
1214     // Lock_roll should be replaced with a flat value defined by the door, soon.
1215     // In the meantime, let's roll 3d5-3, giving us a range of 0-12.
1216     int lock_roll = rng( 0, 4 ) + rng( 0, 4 ) + rng( 0, 4 );
1217 
1218     add_msg( m_debug, _( "Rolled %i. Mean_roll %g. Difficulty %i." ), pick_roll, mean_roll, lock_roll );
1219 
1220     // Your base skill XP gain is derived from the lock difficulty (which is currently random but shouldn't be).
1221     int xp_gain = 3 * lock_roll;
1222     if( perfect || ( pick_roll >= lock_roll ) ) {
1223         if( !perfect ) {
1224             // Increase your XP if you successfully pick the lock, unless you were using a Perfect Lockpick.
1225             xp_gain = xp_gain * 2;
1226         }
1227         here.has_furn( target ) ?
1228         here.furn_set( target, new_furn_type ) :
1229         static_cast<void>( here.ter_set( target, new_ter_type ) );
1230         who.add_msg_if_player( m_good, open_message );
1231     } else if( furn_type == f_gunsafe_ml && lock_roll > ( 3 * pick_roll ) ) {
1232         who.add_msg_if_player( m_bad, _( "Your clumsy attempt jams the lock!" ) );
1233         here.furn_set( target, furn_str_id( "f_gunsafe_mj" ) );
1234     } else if( lock_roll > ( 1.5 * pick_roll ) ) {
1235         if( it->inc_damage() ) {
1236             who.add_msg_if_player( m_bad,
1237                                    _( "The lock stumps your efforts to pick it, and you destroy your tool." ) );
1238             destroy = true;
1239         } else {
1240             who.add_msg_if_player( m_bad,
1241                                    _( "The lock stumps your efforts to pick it, and you damage your tool." ) );
1242         }
1243     } else {
1244         who.add_msg_if_player( m_bad, _( "The lock stumps your efforts to pick it." ) );
1245     }
1246 
1247     if( avatar *you = dynamic_cast<avatar *>( &who ) ) {
1248         // Gives another boost to XP, reduced by your skill level.
1249         // Higher skill levels require more difficult locks to gain a meaningful amount of xp.
1250         // Again, we're using randomized lock_roll until a defined lock difficulty is implemented.
1251         if( lock_roll > you->get_skill_level( skill_traps ) ) {
1252             xp_gain += lock_roll + ( xp_gain / ( you->get_skill_level( skill_traps ) + 1 ) );
1253         } else {
1254             xp_gain += xp_gain / ( you->get_skill_level( skill_traps ) + 1 );
1255         }
1256         you->practice( skill_traps, xp_gain );
1257     }
1258 
1259     if( !perfect && ter_type == t_door_locked_alarm && ( lock_roll + dice( 1, 30 ) ) > pick_roll ) {
1260         sounds::sound( who.pos(), 40, sounds::sound_t::alarm, _( "an alarm sound!" ), true, "environment",
1261                        "alarm" );
1262         if( !get_timed_events().queued( timed_event_type::WANTED ) ) {
1263             get_timed_events().add( timed_event_type::WANTED, calendar::turn + 30_minutes, 0,
1264                                     who.global_sm_location() );
1265         }
1266     }
1267 
1268     if( destroy && lockpick.has_value() ) {
1269         ( *lockpick ).remove_item();
1270     }
1271 
1272     who.practice_proficiency( proficiency_prof_lockpicking,
1273                               time_duration::from_moves( act.moves_total ) / duration_proficiency_factor );
1274     who.practice_proficiency( proficiency_prof_lockpicking_expert,
1275                               time_duration::from_moves( act.moves_total ) / duration_proficiency_factor );
1276 }
1277 
select_location(avatar & you)1278 cata::optional<tripoint> lockpick_activity_actor::select_location( avatar &you )
1279 {
1280     if( you.is_mounted() ) {
1281         you.add_msg_if_player( m_info, _( "You cannot do that while mounted." ) );
1282         return cata::nullopt;
1283     }
1284 
1285     const std::function<bool( const tripoint & )> is_pickable = [&you]( const tripoint & p ) {
1286         if( p == you.pos() ) {
1287             return false;
1288         }
1289         return get_map().has_flag( "PICKABLE", p );
1290     };
1291 
1292     const cata::optional<tripoint> target = choose_adjacent_highlight(
1293             _( "Use your lockpick where?" ), _( "There is nothing to lockpick nearby." ), is_pickable, false );
1294     if( !target ) {
1295         return cata::nullopt;
1296     }
1297 
1298     if( is_pickable( *target ) ) {
1299         return target;
1300     }
1301 
1302     const ter_id terr_type = get_map().ter( *target );
1303     if( *target == you.pos() ) {
1304         you.add_msg_if_player( m_info, _( "You pick your nose and your sinuses swing open." ) );
1305     } else if( g->critter_at<npc>( *target ) ) {
1306         you.add_msg_if_player( m_info,
1307                                _( "You can pick your friends, and you can\npick your nose, but you can't pick\nyour friend's nose." ) );
1308     } else if( terr_type == t_door_c ) {
1309         you.add_msg_if_player( m_info, _( "That door isn't locked." ) );
1310     } else {
1311         you.add_msg_if_player( m_info, _( "That cannot be picked." ) );
1312     }
1313     return cata::nullopt;
1314 }
1315 
serialize(JsonOut & jsout) const1316 void lockpick_activity_actor::serialize( JsonOut &jsout ) const
1317 {
1318     jsout.start_object();
1319 
1320     jsout.member( "moves_total", moves_total );
1321     jsout.member( "lockpick", lockpick );
1322     jsout.member( "fake_lockpick", fake_lockpick );
1323     jsout.member( "target", target );
1324 
1325     jsout.end_object();
1326 }
1327 
deserialize(JsonIn & jsin)1328 std::unique_ptr<activity_actor> lockpick_activity_actor::deserialize( JsonIn &jsin )
1329 {
1330     lockpick_activity_actor actor( 0, cata::nullopt, cata::nullopt, tripoint_zero );
1331 
1332     JsonObject data = jsin.get_object();
1333 
1334     data.read( "moves_total", actor.moves_total );
1335     data.read( "lockpick", actor.lockpick );
1336     data.read( "fake_lockpick", actor.fake_lockpick );
1337     data.read( "target", actor.target );
1338 
1339     return actor.clone();
1340 }
1341 
do_turn(player_activity & act,Character & who)1342 void migration_cancel_activity_actor::do_turn( player_activity &act, Character &who )
1343 {
1344     // Stop the activity
1345     act.set_to_null();
1346 
1347     // Ensure that neither avatars nor npcs end up in an invalid state
1348     if( who.is_npc() ) {
1349         npc &npc_who = dynamic_cast<npc &>( who );
1350         npc_who.revert_after_activity();
1351     } else {
1352         avatar &avatar_who = dynamic_cast<avatar &>( who );
1353         avatar_who.clear_destination();
1354         avatar_who.backlog.clear();
1355     }
1356 }
1357 
serialize(JsonOut & jsout) const1358 void migration_cancel_activity_actor::serialize( JsonOut &jsout ) const
1359 {
1360     // This will probably never be called, but write null to avoid invalid json in
1361     // the case that it is
1362     jsout.write_null();
1363 }
1364 
deserialize(JsonIn &)1365 std::unique_ptr<activity_actor> migration_cancel_activity_actor::deserialize( JsonIn & )
1366 {
1367     return migration_cancel_activity_actor().clone();
1368 }
1369 
start(player_activity & act,Character &)1370 void open_gate_activity_actor::start( player_activity &act, Character & )
1371 {
1372     act.moves_total = moves_total;
1373     act.moves_left = moves_total;
1374 }
1375 
finish(player_activity & act,Character &)1376 void open_gate_activity_actor::finish( player_activity &act, Character & )
1377 {
1378     gates::open_gate( placement );
1379     act.set_to_null();
1380 }
1381 
serialize(JsonOut & jsout) const1382 void open_gate_activity_actor::serialize( JsonOut &jsout ) const
1383 {
1384     jsout.start_object();
1385 
1386     jsout.member( "moves", moves_total );
1387     jsout.member( "placement", placement );
1388 
1389     jsout.end_object();
1390 }
1391 
deserialize(JsonIn & jsin)1392 std::unique_ptr<activity_actor> open_gate_activity_actor::deserialize( JsonIn &jsin )
1393 {
1394     open_gate_activity_actor actor( 0, tripoint_zero );
1395 
1396     JsonObject data = jsin.get_object();
1397 
1398     data.read( "moves", actor.moves_total );
1399     data.read( "placement", actor.placement );
1400 
1401     return actor.clone();
1402 }
1403 
start(player_activity & act,Character & guy)1404 void consume_activity_actor::start( player_activity &act, Character &guy )
1405 {
1406     int moves;
1407     Character &player_character = get_player_character();
1408     if( consume_location ) {
1409         const ret_val<edible_rating> ret = player_character.will_eat( *consume_location, true );
1410         if( !ret.success() ) {
1411             canceled = true;
1412             consume_menu_selections = std::vector<int>();
1413             consume_menu_selected_items.clear();
1414             consume_menu_filter.clear();
1415             return;
1416         }
1417         moves = to_moves<int>( guy.get_consume_time( *consume_location ) );
1418     } else if( !consume_item.is_null() ) {
1419         const ret_val<edible_rating> ret = player_character.will_eat( consume_item, true );
1420         if( !ret.success() ) {
1421             canceled = true;
1422             consume_menu_selections = std::vector<int>();
1423             consume_menu_selected_items.clear();
1424             consume_menu_filter.clear();
1425             return;
1426         }
1427         moves = to_moves<int>( guy.get_consume_time( consume_item ) );
1428     } else {
1429         debugmsg( "Item/location to be consumed should not be null." );
1430         canceled = true;
1431         return;
1432     }
1433 
1434     act.moves_total = moves;
1435     act.moves_left = moves;
1436 }
1437 
finish(player_activity & act,Character &)1438 void consume_activity_actor::finish( player_activity &act, Character & )
1439 {
1440     // Prevent interruptions from this point onwards, so that e.g. pain from
1441     // injecting serum doesn't pop up messages about cancelling consuming (it's
1442     // too late; we've already consumed).
1443     act.interruptable = false;
1444 
1445     // Consuming an item may cause various effects, including cancelling our activity.
1446     // Back up these values since this activity actor might be destroyed.
1447     std::vector<int> temp_selections = consume_menu_selections;
1448     const std::vector<item_location> temp_selected_items = consume_menu_selected_items;
1449     const std::string temp_filter = consume_menu_filter;
1450     item_location consume_loc = consume_location;
1451     activity_id new_act = type;
1452 
1453     avatar &player_character = get_avatar();
1454     if( !canceled ) {
1455         if( consume_loc ) {
1456             player_character.consume( consume_loc, /*force=*/true );
1457         } else if( !consume_item.is_null() ) {
1458             player_character.consume( consume_item, /*force=*/true );
1459         } else {
1460             debugmsg( "Item location/name to be consumed should not be null." );
1461         }
1462         if( player_character.get_value( "THIEF_MODE_KEEP" ) != "YES" ) {
1463             player_character.set_value( "THIEF_MODE", "THIEF_ASK" );
1464         }
1465     }
1466 
1467     if( act.id() == activity_id( "ACT_CONSUME" ) ) {
1468         act.set_to_null();
1469     }
1470 
1471     if( !temp_selections.empty() || !temp_selected_items.empty() || !temp_filter.empty() ) {
1472         if( act.is_null() ) {
1473             player_character.assign_activity( new_act );
1474             player_character.activity.values = temp_selections;
1475             player_character.activity.targets = temp_selected_items;
1476             player_character.activity.str_values = { temp_filter, "true" };
1477         } else {
1478             player_activity eat_menu( new_act );
1479             eat_menu.values = temp_selections;
1480             eat_menu.targets = temp_selected_items;
1481             eat_menu.str_values = { temp_filter, "true" };
1482             player_character.backlog.push_back( eat_menu );
1483         }
1484     }
1485 }
1486 
serialize(JsonOut & jsout) const1487 void consume_activity_actor::serialize( JsonOut &jsout ) const
1488 {
1489     jsout.start_object();
1490 
1491     jsout.member( "consume_location", consume_location );
1492     jsout.member( "consume_item", consume_item );
1493     jsout.member( "consume_menu_selections", consume_menu_selections );
1494     jsout.member( "consume_menu_selected_items", consume_menu_selected_items );
1495     jsout.member( "consume_menu_filter", consume_menu_filter );
1496     jsout.member( "canceled", canceled );
1497 
1498     jsout.end_object();
1499 }
1500 
deserialize(JsonIn & jsin)1501 std::unique_ptr<activity_actor> consume_activity_actor::deserialize( JsonIn &jsin )
1502 {
1503     item_location null;
1504     consume_activity_actor actor( null );
1505 
1506     JsonObject data = jsin.get_object();
1507 
1508     data.read( "consume_location", actor.consume_location );
1509     data.read( "consume_item", actor.consume_item );
1510     data.read( "consume_menu_selections", actor.consume_menu_selections );
1511     data.read( "consume_menu_selected_items", actor.consume_menu_selected_items );
1512     data.read( "consume_menu_filter", actor.consume_menu_filter );
1513     data.read( "canceled", actor.canceled );
1514 
1515     return actor.clone();
1516 }
1517 
start(player_activity & act,Character &)1518 void try_sleep_activity_actor::start( player_activity &act, Character &/*who*/ )
1519 {
1520     act.moves_total = to_moves<int>( duration );
1521     act.moves_left = act.moves_total;
1522 }
1523 
do_turn(player_activity & act,Character & who)1524 void try_sleep_activity_actor::do_turn( player_activity &act, Character &who )
1525 {
1526     if( who.has_effect( effect_sleep ) ) {
1527         return;
1528     }
1529     if( dynamic_cast<player *>( &who )->can_sleep() ) {
1530         who.fall_asleep(); // calls act.set_to_null()
1531         if( !who.has_effect( effect_sleep ) ) {
1532             // Character can potentially have immunity for 'effect_sleep'
1533             who.add_msg_if_player(
1534                 _( "You feel you should've fallen asleep by now, but somehow you're still awake." ) );
1535         }
1536         return;
1537     }
1538     if( one_in( 1000 ) ) {
1539         who.add_msg_if_player( _( "You toss and turn…" ) );
1540     }
1541     if( calendar::once_every( 30_minutes ) ) {
1542         query_keep_trying( act, who );
1543     }
1544 }
1545 
finish(player_activity & act,Character & who)1546 void try_sleep_activity_actor::finish( player_activity &act, Character &who )
1547 {
1548     act.set_to_null();
1549     if( !who.has_effect( effect_sleep ) ) {
1550         who.add_msg_if_player( _( "You try to sleep, but can't." ) );
1551     }
1552 }
1553 
query_keep_trying(player_activity & act,Character & who)1554 void try_sleep_activity_actor::query_keep_trying( player_activity &act, Character &who )
1555 {
1556     if( disable_query || !who.is_avatar() ) {
1557         return;
1558     }
1559 
1560     uilist sleep_query;
1561     sleep_query.text = _( "You have trouble sleeping, keep trying?" );
1562     sleep_query.addentry( 1, true, 'S', _( "Stop trying to fall asleep and get up." ) );
1563     sleep_query.addentry( 2, true, 'c', _( "Continue trying to fall asleep." ) );
1564     sleep_query.addentry( 3, true, 'C',
1565                           _( "Continue trying to fall asleep and don't ask again." ) );
1566     sleep_query.query();
1567     switch( sleep_query.ret ) {
1568         case UILIST_CANCEL:
1569         case 1:
1570             act.set_to_null();
1571             break;
1572         case 3:
1573             disable_query = true;
1574             break;
1575         case 2:
1576         default:
1577             break;
1578     }
1579 }
1580 
serialize(JsonOut & jsout) const1581 void try_sleep_activity_actor::serialize( JsonOut &jsout ) const
1582 {
1583     jsout.start_object();
1584 
1585     jsout.member( "disable_query", disable_query );
1586     jsout.member( "duration", duration );
1587 
1588     jsout.end_object();
1589 }
1590 
deserialize(JsonIn & jsin)1591 std::unique_ptr<activity_actor> try_sleep_activity_actor::deserialize( JsonIn &jsin )
1592 {
1593     try_sleep_activity_actor actor = try_sleep_activity_actor( 0_seconds );
1594 
1595     JsonObject data = jsin.get_object();
1596 
1597     data.read( "disable_query", actor.disable_query );
1598     data.read( "duration", actor.duration );
1599 
1600     return actor.clone();
1601 }
1602 
start(player_activity & act,Character &)1603 void unload_activity_actor::start( player_activity &act, Character & )
1604 {
1605     act.moves_left = moves_total;
1606     act.moves_total = moves_total;
1607 }
1608 
finish(player_activity & act,Character & who)1609 void unload_activity_actor::finish( player_activity &act, Character &who )
1610 {
1611     act.set_to_null();
1612     unload( who, target );
1613 }
1614 
unload(Character & who,item_location & target)1615 void unload_activity_actor::unload( Character &who, item_location &target )
1616 {
1617     int qty = 0;
1618     item &it = *target.get_item();
1619     bool actually_unloaded = false;
1620 
1621     if( it.is_container() ) {
1622         contents_change_handler handler;
1623         bool changed = false;
1624         for( item *contained : it.contents.all_items_top() ) {
1625             int old_charges = contained->charges;
1626             const bool consumed = who.add_or_drop_with_msg( *contained, true, &it );
1627             if( consumed || contained->charges != old_charges ) {
1628                 changed = true;
1629                 handler.unseal_pocket_containing( item_location( target, contained ) );
1630             }
1631             if( consumed ) {
1632                 it.remove_item( *contained );
1633             }
1634         }
1635 
1636         if( changed ) {
1637             it.on_contents_changed();
1638             who.invalidate_weight_carried_cache();
1639             handler.handle_by( who );
1640         }
1641         return;
1642     }
1643 
1644     std::vector<item *> remove_contained;
1645     for( item *contained : it.contents.all_items_top() ) {
1646         if( contained->ammo_type() == ammotype( "plutonium" ) ) {
1647             contained->charges /= PLUTONIUM_CHARGES;
1648         }
1649         if( who.as_player()->add_or_drop_with_msg( *contained, true, &it ) ) {
1650             qty += contained->charges;
1651             remove_contained.push_back( contained );
1652             actually_unloaded = true;
1653         }
1654     }
1655     // remove the ammo leads in the belt
1656     for( item *remove : remove_contained ) {
1657         it.remove_item( *remove );
1658         actually_unloaded = true;
1659     }
1660 
1661     // remove the belt linkage
1662     if( it.is_ammo_belt() ) {
1663         if( it.type->magazine->linkage ) {
1664             item link( *it.type->magazine->linkage, calendar::turn, qty );
1665             if( who.as_player()->add_or_drop_with_msg( link, true ) ) {
1666                 actually_unloaded = true;
1667             }
1668         }
1669         if( actually_unloaded ) {
1670             who.add_msg_if_player( _( "You disassemble your %s." ), it.tname() );
1671         }
1672     } else if( actually_unloaded ) {
1673         who.add_msg_if_player( _( "You unload your %s." ), it.tname() );
1674     }
1675 
1676     if( it.has_flag( flag_MAG_DESTROY ) && it.ammo_remaining() == 0 ) {
1677         target.remove_item();
1678     }
1679 }
1680 
serialize(JsonOut & jsout) const1681 void unload_activity_actor::serialize( JsonOut &jsout ) const
1682 {
1683     jsout.start_object();
1684 
1685     jsout.member( "moves_total", moves_total );
1686     jsout.member( "target", target );
1687 
1688     jsout.end_object();
1689 }
1690 
deserialize(JsonIn & jsin)1691 std::unique_ptr<activity_actor> unload_activity_actor::deserialize( JsonIn &jsin )
1692 {
1693     unload_activity_actor actor = unload_activity_actor( 0, item_location::nowhere );
1694 
1695     JsonObject data = jsin.get_object();
1696 
1697     data.read( "moves_total", actor.moves_total );
1698     data.read( "target", actor.target );
1699 
1700     return actor.clone();
1701 }
1702 
craft_activity_actor(item_location & it,const bool is_long)1703 craft_activity_actor::craft_activity_actor( item_location &it, const bool is_long ) :
1704     craft_item( it ), is_long( is_long )
1705 {
1706     cached_crafting_speed = 0;
1707     cached_assistants = 0;
1708     cached_base_total_moves = 1;
1709     cached_cur_total_moves = 1;
1710 }
1711 
check_if_craft_okay(item_location & craft_item,Character & crafter)1712 bool craft_activity_actor::check_if_craft_okay( item_location &craft_item, Character &crafter )
1713 {
1714     item *craft = craft_item.get_item();
1715 
1716     // item_location::get_item() will return nullptr if the item is lost
1717     if( !craft ) {
1718         crafter.add_msg_player_or_npc(
1719             _( "You no longer have the in progress craft in your possession.  "
1720                "You stop crafting.  "
1721                "Reactivate the in progress craft to continue crafting." ),
1722             _( "<npcname> no longer has the in progress craft in their possession.  "
1723                "<npcname> stops crafting." ) );
1724         return false;
1725     }
1726 
1727     if( !craft->is_craft() ) {
1728         debugmsg( "ACT_CRAFT target '%s' is not a craft.  Aborting ACT_CRAFT.", craft->tname() );
1729         return false;
1730     }
1731 
1732     if( !cached_continuation_requirements ) {
1733         cached_continuation_requirements = craft->get_continue_reqs();
1734     }
1735     return crafter.can_continue_craft( *craft, *cached_continuation_requirements );
1736 }
1737 
start(player_activity & act,Character & crafter)1738 void craft_activity_actor::start( player_activity &act, Character &crafter )
1739 {
1740     if( !check_if_craft_okay( craft_item, crafter ) ) {
1741         act.set_to_null();
1742     }
1743     act.moves_left = calendar::INDEFINITELY_LONG;
1744     activity_override = craft_item.get_item()->get_making().exertion_level();
1745     cached_crafting_speed = 0;
1746 }
1747 
do_turn(player_activity & act,Character & crafter)1748 void craft_activity_actor::do_turn( player_activity &act, Character &crafter )
1749 {
1750     if( !check_if_craft_okay( craft_item, crafter ) ) {
1751         act.set_to_null();
1752         return;
1753     }
1754 
1755     // We already checked if this is nullptr above
1756     item &craft = *craft_item.get_item();
1757 
1758     const cata::optional<tripoint> location = craft_item.where() == item_location::type::character
1759             ? cata::optional<tripoint>() : cata::optional<tripoint>( craft_item.position() );
1760     const recipe &rec = craft.get_making();
1761     const float crafting_speed = crafter.crafting_speed_multiplier( craft, location );
1762     const int assistants = crafter.available_assistant_count( craft.get_making() );
1763 
1764     if( crafting_speed <= 0.0f ) {
1765         crafter.cancel_activity();
1766         return;
1767     }
1768 
1769     if( cached_crafting_speed != crafting_speed || cached_assistants != assistants ) {
1770         cached_crafting_speed = crafting_speed;
1771         cached_assistants = assistants;
1772 
1773         // Base moves for batch size with no speed modifier or assistants
1774         // Must ensure >= 1 so we don't divide by 0;
1775         cached_base_total_moves = std::max( static_cast<int64_t>( 1 ),
1776                                             rec.batch_time( crafter, craft.charges, 1.0f, 0 ) );
1777         // Current expected total moves, includes crafting speed modifiers and assistants
1778         cached_cur_total_moves = std::max( static_cast<int64_t>( 1 ),
1779                                            rec.batch_time( crafter, craft.charges, crafting_speed,
1780                                                    assistants ) );
1781     }
1782     const double base_total_moves = cached_base_total_moves;
1783     const double cur_total_moves = cached_cur_total_moves;
1784 
1785     // item_counter represents the percent progress relative to the base batch time
1786     // stored precise to 5 decimal places ( e.g. 67.32 percent would be stored as 6'732'000 )
1787     const int old_counter = craft.item_counter;
1788 
1789     // Delta progress in moves adjusted for current crafting speed /
1790     //crafter.exertion_adjusted_move_multiplier( exertion_level() )
1791     int spent_moves = crafter.get_moves() * crafter.exertion_adjusted_move_multiplier(
1792                           exertion_level() );
1793     const double delta_progress = spent_moves * base_total_moves / cur_total_moves;
1794     // Current progress in moves
1795     const double current_progress = craft.item_counter * base_total_moves / 10'000'000.0 +
1796                                     delta_progress;
1797     // Current progress as a percent of base_total_moves to 2 decimal places
1798     craft.item_counter = std::round( current_progress / base_total_moves * 10'000'000.0 );
1799     crafter.set_moves( 0 );
1800 
1801     // This is to ensure we don't over count skill steps
1802     craft.item_counter = std::min( craft.item_counter, 10'000'000 );
1803 
1804     // This nominal craft time is also how many practice ticks to perform
1805     // spread out evenly across the actual duration.
1806     const double total_practice_ticks = rec.time_to_craft_moves( crafter,
1807                                         recipe_time_flag::ignore_proficiencies ) / 100.0;
1808 
1809     const int ticks_per_practice = 10'000'000.0 / total_practice_ticks;
1810     int num_practice_ticks = craft.item_counter / ticks_per_practice -
1811                              old_counter / ticks_per_practice;
1812     if( num_practice_ticks > 0 ) {
1813         crafter.craft_skill_gain( craft, num_practice_ticks );
1814     }
1815     // Proficiencies and tools are gained/consumed after every 5% progress
1816     int five_percent_steps = craft.item_counter / 500'000 - old_counter / 500'000;
1817     if( five_percent_steps > 0 ) {
1818         // Divide by 100 for seconds, 20 for 5%
1819         const time_duration pct_time = time_duration::from_seconds( base_total_moves / 2000 );
1820         crafter.craft_proficiency_gain( craft, pct_time * five_percent_steps );
1821         // Invalidate the crafting time cache because proficiencies may have changed
1822         cached_crafting_speed = 0;
1823     }
1824 
1825     // Unlike skill, tools are consumed once at the start and should not be consumed at the end
1826     if( craft.item_counter >= 10'000'000 ) {
1827         --five_percent_steps;
1828     }
1829 
1830     if( five_percent_steps > 0 ) {
1831         if( !crafter.craft_consume_tools( craft, five_percent_steps, false ) ) {
1832             // So we don't skip over any tool comsuption
1833             craft.item_counter -= craft.item_counter % 500'000 + 1;
1834             crafter.cancel_activity();
1835             return;
1836         }
1837     }
1838 
1839     // if item_counter has reached 100% or more
1840     if( craft.item_counter >= 10'000'000 ) {
1841         item craft_copy = craft;
1842         craft_item.remove_item();
1843         // We need to cache this before we cancel the activity else we risk Use After Free
1844         bool will_continue = is_long;
1845         crafter.cancel_activity();
1846         crafter.complete_craft( craft_copy, location );
1847         if( will_continue ) {
1848             if( crafter.making_would_work( crafter.lastrecipe, craft_copy.charges ) ) {
1849                 crafter.last_craft->execute( location );
1850             }
1851         }
1852     } else if( craft.item_counter >= craft.get_next_failure_point() ) {
1853         bool destroy = craft.handle_craft_failure( crafter );
1854         // If the craft needs to be destroyed, do it and stop crafting.
1855         if( destroy ) {
1856             crafter.add_msg_player_or_npc( _( "There is nothing left of the %s to craft from." ),
1857                                            _( "There is nothing left of the %s <npcname> was crafting." ), craft.tname() );
1858             craft_item.remove_item();
1859             crafter.cancel_activity();
1860         }
1861     }
1862 }
1863 
finish(player_activity & act,Character &)1864 void craft_activity_actor::finish( player_activity &act, Character & )
1865 {
1866     act.set_to_null();
1867 }
1868 
get_progress_message(const player_activity &) const1869 std::string craft_activity_actor::get_progress_message( const player_activity & ) const
1870 {
1871     if( !craft_item ) {
1872         //We have somehow lost the craft item.  This will be handled in do_turn in the check_if_craft_is_ok call.
1873         return "";
1874     }
1875     return craft_item.get_item()->tname();
1876 }
1877 
exertion_level() const1878 float craft_activity_actor::exertion_level() const
1879 {
1880     return activity_override;
1881 }
1882 
serialize(JsonOut & jsout) const1883 void craft_activity_actor::serialize( JsonOut &jsout ) const
1884 {
1885     jsout.start_object();
1886 
1887     jsout.member( "craft_loc", craft_item );
1888     jsout.member( "long", is_long );
1889 
1890     jsout.end_object();
1891 }
1892 
deserialize(JsonIn & jsin)1893 std::unique_ptr<activity_actor> craft_activity_actor::deserialize( JsonIn &jsin )
1894 {
1895     item_location tmp_item_loc;
1896     bool tmp_long;
1897 
1898     JsonObject data = jsin.get_object();
1899 
1900     data.read( "craft_loc", tmp_item_loc );
1901     data.read( "long", tmp_long );
1902 
1903     craft_activity_actor actor = craft_activity_actor( tmp_item_loc, tmp_long );
1904 
1905     return actor.clone();
1906 }
1907 
start(player_activity & act,Character & who)1908 void workout_activity_actor::start( player_activity &act, Character &who )
1909 {
1910     if( who.get_fatigue() > fatigue_levels::DEAD_TIRED ) {
1911         who.add_msg_if_player( _( "You are too tired to exercise." ) );
1912         act_id = activity_id::NULL_ID();
1913         act.set_to_null();
1914         return;
1915     }
1916     if( who.get_thirst() > 240 ) {
1917         who.add_msg_if_player( _( "You are too dehydrated to exercise." ) );
1918         act_id = activity_id::NULL_ID();
1919         act.set_to_null();
1920         return;
1921     }
1922     if( who.is_armed() ) {
1923         who.add_msg_if_player( _( "Empty your hands first." ) );
1924         act_id = activity_id::NULL_ID();
1925         act.set_to_null();
1926         return;
1927     }
1928     map &here = get_map();
1929     // free training requires all limbs intact, but specialized workout machines
1930     // train upper or lower parts of body only and may permit workout with
1931     // broken limbs as long as they are not involved by the machine
1932     bool hand_equipment = here.has_flag_furn( "WORKOUT_ARMS", location );
1933     bool leg_equipment = here.has_flag_furn( "WORKOUT_LEGS", location );
1934 
1935     if( hand_equipment && ( ( who.is_limb_broken( body_part_arm_l ) ) ||
1936                             who.is_limb_broken( body_part_arm_r ) ) ) {
1937         who.add_msg_if_player( _( "You cannot train here with a broken arm." ) );
1938         act_id = activity_id::NULL_ID();
1939         act.set_to_null();
1940         return;
1941     }
1942     if( leg_equipment && ( ( who.is_limb_broken( body_part_leg_l ) ) ||
1943                            who.is_limb_broken( body_part_leg_r ) ) ) {
1944         who.add_msg_if_player( _( "You cannot train here with a broken leg." ) );
1945         act_id = activity_id::NULL_ID();
1946         act.set_to_null();
1947         return;
1948     }
1949     if( !hand_equipment && !leg_equipment &&
1950         ( who.is_limb_broken( body_part_arm_l ) ||
1951           who.is_limb_broken( body_part_arm_r ) ||
1952           who.is_limb_broken( body_part_leg_l ) ||
1953           who.is_limb_broken( body_part_leg_r ) ) ) {
1954         who.add_msg_if_player( _( "You cannot train freely with a broken limb." ) );
1955         act_id = activity_id::NULL_ID();
1956         act.set_to_null();
1957         return;
1958     }
1959     uilist workout_query;
1960     workout_query.desc_enabled = true;
1961     workout_query.text =
1962         _( "Physical effort determines workout efficiency, but also rate of exhaustion." );
1963     workout_query.title = _( "Choose training intensity:" );
1964     workout_query.addentry_desc( 1, true, 'l', pgettext( "training intensity", "Light" ),
1965                                  _( "Light exercise comparable in intensity to walking, but more focused and methodical." ) );
1966     workout_query.addentry_desc( 2, true, 'm', pgettext( "training intensity", "Moderate" ),
1967                                  _( "Moderate exercise without excessive exertion, but with enough effort to break a sweat." ) );
1968     workout_query.addentry_desc( 3, true, 'a', pgettext( "training intensity", "Active" ),
1969                                  _( "Active exercise with full involvement.  Strenuous, but in a controlled manner." ) );
1970     workout_query.addentry_desc( 4, true, 'h', pgettext( "training intensity", "High" ),
1971                                  _( "High intensity exercise with maximum effort and full power.  Exhausting in the long run." ) );
1972     workout_query.query();
1973     switch( workout_query.ret ) {
1974         case UILIST_CANCEL:
1975             act.set_to_null();
1976             act_id = activity_id::NULL_ID();
1977             return;
1978         case 4:
1979             act_id = activity_id( "ACT_WORKOUT_HARD" );
1980             intensity_modifier = 4;
1981             break;
1982         case 3:
1983             act_id = activity_id( "ACT_WORKOUT_ACTIVE" );
1984             intensity_modifier = 3;
1985             break;
1986         case 2:
1987             act_id = activity_id( "ACT_WORKOUT_MODERATE" );
1988             intensity_modifier = 2;
1989             break;
1990         case 1:
1991         default:
1992             act_id = activity_id( "ACT_WORKOUT_LIGHT" );
1993             intensity_modifier = 1;
1994             break;
1995     }
1996     int length;
1997     query_int( length, _( "Train for how long (minutes): " ) );
1998     if( length > 0 ) {
1999         duration = length * 1_minutes;
2000     } else {
2001         act_id = activity_id::NULL_ID();
2002         act.set_to_null();
2003         return;
2004     }
2005     act.moves_total = to_moves<int>( duration );
2006     act.moves_left = act.moves_total;
2007     if( who.male ) {
2008         sfx::play_activity_sound( "plmove", "fatigue_m_med", sfx::get_heard_volume( location ) );
2009     } else {
2010         sfx::play_activity_sound( "plmove", "fatigue_f_med", sfx::get_heard_volume( location ) );
2011     }
2012     who.add_msg_if_player( _( "You start your workout session." ) );
2013 }
2014 
do_turn(player_activity & act,Character & who)2015 void workout_activity_actor::do_turn( player_activity &act, Character &who )
2016 {
2017     if( who.get_fatigue() > fatigue_levels::DEAD_TIRED ) {
2018         who.add_msg_if_player( _( "You are exhausted so you finish your workout early." ) );
2019         act.set_to_null();
2020         return;
2021     }
2022     if( who.get_thirst() > 240 ) {
2023         who.add_msg_if_player( _( "You are dehydrated so you finish your workout early." ) );
2024         act.set_to_null();
2025         return;
2026     }
2027     if( !rest_mode && who.get_stamina() > who.get_stamina_max() / 3 ) {
2028         who.mod_stamina( -25 - intensity_modifier );
2029         if( one_in( 180 / intensity_modifier ) ) {
2030             who.mod_fatigue( 1 );
2031             who.mod_thirst( 1 );
2032         }
2033         if( calendar::once_every( 16_minutes / intensity_modifier ) ) {
2034             //~ heavy breathing when exercising
2035             std::string huff = _( "yourself huffing and puffing!" );
2036             sounds::sound( location + tripoint_east, 2 * intensity_modifier, sounds::sound_t::speech, huff,
2037                            true );
2038         }
2039         // morale bonus kicks in gradually after 5 minutes of exercise
2040         if( calendar::once_every( 2_minutes ) &&
2041             ( ( elapsed + act.moves_total - act.moves_left ) / 100 * 1_turns ) > 5_minutes ) {
2042             who.add_morale( MORALE_FEELING_GOOD, intensity_modifier, 20, 6_hours, 30_minutes );
2043         }
2044         if( calendar::once_every( 2_minutes ) ) {
2045             who.add_msg_if_player( m_debug, who.activity_level_str() );
2046             who.add_msg_if_player( m_debug, act.id().c_str() );
2047         }
2048     } else if( !rest_mode ) {
2049         rest_mode = true;
2050         who.add_msg_if_player( _( "You catch your breath for few moments." ) );
2051     } else if( who.get_stamina() >= who.get_stamina_max() ) {
2052         rest_mode = false;
2053         who.add_msg_if_player( _( "You get back to your training." ) );
2054     }
2055 }
2056 
finish(player_activity & act,Character & who)2057 void workout_activity_actor::finish( player_activity &act, Character &who )
2058 {
2059     if( !query_keep_training( act, who ) ) {
2060         act.set_to_null();
2061         who.add_msg_if_player( _( "You finish your workout session." ) );
2062     }
2063 }
2064 
canceled(player_activity &,Character &)2065 void workout_activity_actor::canceled( player_activity &/*act*/, Character &/*who*/ )
2066 {
2067     stop_time = calendar::turn;
2068 }
2069 
query_keep_training(player_activity & act,Character & who)2070 bool workout_activity_actor::query_keep_training( player_activity &act, Character &who )
2071 {
2072     if( disable_query || !who.is_avatar() ) {
2073         elapsed += act.moves_total - act.moves_left;
2074         act.moves_total = to_moves<int>( 60_minutes );
2075         act.moves_left = act.moves_total;
2076         return true;
2077     }
2078     int length;
2079     uilist workout_query;
2080     workout_query.text = _( "You have finished your training cycle, keep training?" );
2081     workout_query.addentry( 1, true, 'S', _( "Stop training." ) );
2082     workout_query.addentry( 2, true, 'c', _( "Continue training." ) );
2083     workout_query.addentry( 3, true, 'C', _( "Continue training and don't ask again." ) );
2084     workout_query.query();
2085     switch( workout_query.ret ) {
2086         case UILIST_CANCEL:
2087         case 1:
2088             act_id = activity_id::NULL_ID();
2089             act.set_to_null();
2090             return false;
2091         case 3:
2092             disable_query = true;
2093             elapsed += act.moves_total - act.moves_left;
2094             act.moves_total = to_moves<int>( 60_minutes );
2095             act.moves_left = act.moves_total;
2096             return true;
2097         case 2:
2098         default:
2099             query_int( length, _( "Train for how long (minutes): " ) );
2100             elapsed += act.moves_total - act.moves_left;
2101             act.moves_total = to_moves<int>( length * 1_minutes );
2102             act.moves_left = act.moves_total;
2103             return true;
2104     }
2105 }
2106 
serialize(JsonOut & jsout) const2107 void workout_activity_actor::serialize( JsonOut &jsout ) const
2108 {
2109     jsout.start_object();
2110 
2111     jsout.member( "disable_query", disable_query );
2112     jsout.member( "act_id", act_id );
2113     jsout.member( "duration", duration );
2114     jsout.member( "location", location );
2115     jsout.member( "stop_time", stop_time );
2116     jsout.member( "elapsed", elapsed );
2117     jsout.member( "intensity_modifier", intensity_modifier );
2118     jsout.member( "rest_mode", rest_mode );
2119 
2120     jsout.end_object();
2121 }
2122 
deserialize(JsonIn & jsin)2123 std::unique_ptr<activity_actor> workout_activity_actor::deserialize( JsonIn &jsin )
2124 {
2125     workout_activity_actor actor = workout_activity_actor( tripoint_zero );
2126 
2127     JsonObject data = jsin.get_object();
2128 
2129     data.read( "disable_query", actor.disable_query );
2130     data.read( "act_id", actor.act_id );
2131     data.read( "duration", actor.duration );
2132     data.read( "location", actor.location );
2133     data.read( "stop_time", actor.stop_time );
2134     data.read( "elapsed", actor.elapsed );
2135     data.read( "intensity_modifier", actor.intensity_modifier );
2136     data.read( "rest_mode", actor.rest_mode );
2137 
2138     return actor.clone();
2139 }
2140 
2141 // TODO: Display costs in the multidrop menu
debug_drop_list(const std::vector<drop_or_stash_item_info> & items)2142 static void debug_drop_list( const std::vector<drop_or_stash_item_info> &items )
2143 {
2144     if( !debug_mode ) {
2145         return;
2146     }
2147 
2148     std::string res( "Items ordered to drop:\n" );
2149     for( const drop_or_stash_item_info &it : items ) {
2150         item_location loc = it.loc();
2151         if( !loc ) {
2152             // some items could have been destroyed by e.g. monster attack
2153             continue;
2154         }
2155         res += string_format( "Drop %d %s\n", it.count(), loc->display_name( it.count() ) );
2156     }
2157     popup( res, PF_GET_KEY );
2158 }
2159 
2160 // Return a list of items to be dropped by the given item-dropping activity in the current turn.
obtain_activity_items(std::vector<drop_or_stash_item_info> & items,contents_change_handler & handler,Character & who)2161 static std::list<item> obtain_activity_items(
2162     std::vector<drop_or_stash_item_info> &items,
2163     contents_change_handler &handler,
2164     Character &who )
2165 {
2166     std::list<item> res;
2167 
2168     debug_drop_list( items );
2169 
2170     auto it = items.begin();
2171     for( ; it != items.end(); ++it ) {
2172         item_location loc = it->loc();
2173         if( !loc ) {
2174             // some items could have been destroyed by e.g. monster attack
2175             continue;
2176         }
2177 
2178         const int consumed_moves = loc.obtain_cost( who, it->count() );
2179         if( !who.is_npc() && who.moves <= 0 && consumed_moves > 0 ) {
2180             break;
2181         }
2182 
2183         who.mod_moves( -consumed_moves );
2184 
2185         // If item is inside another (container/pocket), unseal it, and update encumbrance
2186         if( loc.has_parent() ) {
2187             // Save parent items to be handled when finishing or canceling activity,
2188             // in case there are still other items to drop from the same container.
2189             // This assumes that an item and any of its parents/ancestors are not
2190             // dropped at the same time.
2191             handler.unseal_pocket_containing( loc );
2192         } else {
2193             // when parent's encumbrance cannot be marked as dirty,
2194             // mark character's encumbrance as dirty instead (correctness over performance)
2195             who.set_check_encumbrance( true );
2196         }
2197 
2198         // Take off the item or remove it from the player's inventory
2199         if( who.is_worn( *loc ) ) {
2200             who.as_player()->takeoff( *loc, &res );
2201         } else if( loc->count_by_charges() ) {
2202             res.push_back( who.as_player()->reduce_charges( &*loc, it->count() ) );
2203         } else {
2204             res.push_back( who.i_rem( &*loc ) );
2205         }
2206     }
2207 
2208     // Remove handled items from activity
2209     items.erase( items.begin(), it );
2210     return res;
2211 }
2212 
serialize(JsonOut & jsout) const2213 void drop_or_stash_item_info::serialize( JsonOut &jsout ) const
2214 {
2215     jsout.start_object();
2216     jsout.member( "loc", _loc );
2217     jsout.member( "count", _count );
2218     jsout.end_object();
2219 }
2220 
deserialize(JsonIn & jsin)2221 void drop_or_stash_item_info::deserialize( JsonIn &jsin )
2222 {
2223     JsonObject jsobj = jsin.get_object();
2224     jsobj.read( "loc", _loc );
2225     jsobj.read( "count", _count );
2226 }
2227 
do_turn(player_activity &,Character & who)2228 void drop_activity_actor::do_turn( player_activity &, Character &who )
2229 {
2230     const tripoint pos = placement + who.pos();
2231     who.invalidate_weight_carried_cache();
2232     put_into_vehicle_or_drop( who, item_drop_reason::deliberate,
2233                               obtain_activity_items( items, handler, who ),
2234                               pos, force_ground );
2235     // Cancel activity if items is empty. Otherwise, we modified in place and we will continue
2236     // to resolve the drop next turn. This is different from the pickup logic which creates
2237     // a brand new activity every turn and cancels the old activity
2238     if( items.empty() ) {
2239         who.cancel_activity();
2240     }
2241 }
2242 
canceled(player_activity &,Character & who)2243 void drop_activity_actor::canceled( player_activity &, Character &who )
2244 {
2245     handler.handle_by( who );
2246 }
2247 
serialize(JsonOut & jsout) const2248 void drop_activity_actor::serialize( JsonOut &jsout ) const
2249 {
2250     jsout.start_object();
2251     jsout.member( "items", items );
2252     jsout.member( "unhandled_containers", handler );
2253     jsout.member( "placement", placement );
2254     jsout.member( "force_ground", force_ground );
2255     jsout.end_object();
2256 }
2257 
deserialize(JsonIn & jsin)2258 std::unique_ptr<activity_actor> drop_activity_actor::deserialize( JsonIn &jsin )
2259 {
2260     drop_activity_actor actor;
2261 
2262     JsonObject jsobj = jsin.get_object();
2263     jsobj.read( "items", actor.items );
2264     jsobj.read( "unhandled_containers", actor.handler );
2265     jsobj.read( "placement", actor.placement );
2266     jsobj.read( "force_ground", actor.force_ground );
2267 
2268     return actor.clone();
2269 }
2270 
stash_on_pet(const std::list<item> & items,monster & pet,Character & who)2271 static void stash_on_pet( const std::list<item> &items, monster &pet, Character &who )
2272 {
2273     units::volume remaining_volume = pet.storage_item->get_total_capacity() - pet.get_carried_volume();
2274     units::mass remaining_weight = pet.weight_capacity() - pet.get_carried_weight();
2275     map &here = get_map();
2276 
2277     for( const item &it : items ) {
2278         if( it.volume() > remaining_volume ) {
2279             add_msg( m_bad, _( "%1$s did not fit and fell to the %2$s." ), it.display_name(),
2280                      here.name( pet.pos() ) );
2281             here.add_item_or_charges( pet.pos(), it );
2282         } else if( it.weight() > remaining_weight ) {
2283             add_msg( m_bad, _( "%1$s is too heavy and fell to the %2$s." ), it.display_name(),
2284                      here.name( pet.pos() ) );
2285             here.add_item_or_charges( pet.pos(), it );
2286         } else {
2287             pet.add_item( it );
2288             remaining_volume -= it.volume();
2289             remaining_weight -= it.weight();
2290         }
2291         // TODO: if NPCs can have pets or move items onto pets
2292         item( it ).handle_pickup_ownership( who );
2293     }
2294 }
2295 
do_turn(player_activity &,Character & who)2296 void stash_activity_actor::do_turn( player_activity &, Character &who )
2297 {
2298     const tripoint pos = placement + who.pos();
2299 
2300     monster *pet = g->critter_at<monster>( pos );
2301     if( pet != nullptr && pet->has_effect( effect_pet ) ) {
2302         stash_on_pet( obtain_activity_items( items, handler, who ),
2303                       *pet, who );
2304         if( items.empty() ) {
2305             who.cancel_activity();
2306         }
2307     } else {
2308         who.add_msg_if_player( _( "The pet has moved somewhere else." ) );
2309         who.cancel_activity();
2310     }
2311 }
2312 
canceled(player_activity &,Character & who)2313 void stash_activity_actor::canceled( player_activity &, Character &who )
2314 {
2315     handler.handle_by( who );
2316 }
2317 
serialize(JsonOut & jsout) const2318 void stash_activity_actor::serialize( JsonOut &jsout ) const
2319 {
2320     jsout.start_object();
2321     jsout.member( "items", items );
2322     jsout.member( "unhandled_containers", handler );
2323     jsout.member( "placement", placement );
2324     jsout.end_object();
2325 }
2326 
deserialize(JsonIn & jsin)2327 std::unique_ptr<activity_actor> stash_activity_actor::deserialize( JsonIn &jsin )
2328 {
2329     stash_activity_actor actor;
2330 
2331     JsonObject jsobj = jsin.get_object();
2332     jsobj.read( "items", actor.items );
2333     jsobj.read( "unhandled_containers", actor.handler );
2334     jsobj.read( "placement", actor.placement );
2335 
2336     return actor.clone();
2337 }
2338 
start(player_activity & act,Character &)2339 void move_furniture_activity_actor::start( player_activity &act, Character & )
2340 {
2341     int moves = g->grabbed_furn_move_time( dp );
2342     act.moves_left = moves;
2343     act.moves_total = moves;
2344 }
2345 
finish(player_activity & act,Character & who)2346 void move_furniture_activity_actor::finish( player_activity &act, Character &who )
2347 {
2348     if( !g->grabbed_furn_move( dp ) ) {
2349         g->walk_move( who.pos() + dp, via_ramp, true );
2350     }
2351     act.set_to_null();
2352 }
2353 
canceled(player_activity &,Character &)2354 void move_furniture_activity_actor::canceled( player_activity &, Character & )
2355 {
2356     add_msg( m_warning, _( "You let go of the grabbed object." ) );
2357     get_avatar().grab( object_type::NONE );
2358 }
2359 
serialize(JsonOut & jsout) const2360 void move_furniture_activity_actor::serialize( JsonOut &jsout ) const
2361 {
2362     jsout.start_object();
2363     jsout.member( "dp", dp );
2364     jsout.member( "via_ramp", via_ramp );
2365     jsout.end_object();
2366 }
2367 
deserialize(JsonIn & jsin)2368 std::unique_ptr<activity_actor> move_furniture_activity_actor::deserialize( JsonIn &jsin )
2369 {
2370     move_furniture_activity_actor actor = move_furniture_activity_actor( tripoint_zero, false );
2371 
2372     JsonObject data = jsin.get_object();
2373 
2374     data.read( "dp", actor.dp );
2375     data.read( "via_ramp", actor.via_ramp );
2376 
2377     return actor.clone();
2378 }
2379 
item_move_cost(Character & who,item_location & item)2380 static int item_move_cost( Character &who, item_location &item )
2381 {
2382     // Cost to take an item from a container or map
2383     const int obtain_cost = item.obtain_cost( who );
2384     // Cost to move an item to a container, vehicle or the ground
2385     const int move_cost = Pickup::cost_to_move_item( who, *item );
2386     return obtain_cost + move_cost;
2387 }
2388 
start(player_activity & act,Character & who)2389 void insert_item_activity_actor::start( player_activity &act, Character &who )
2390 {
2391     if( items.empty() ) {
2392         debugmsg( "ACT_INSERT_ITEM was passed an empty list" );
2393         act.set_to_null();
2394     }
2395 
2396     all_pockets_rigid = holster->contents.all_pockets_rigid();
2397 
2398     const int total_moves = item_move_cost( who, items.front().first );
2399     act.moves_left = total_moves;
2400     act.moves_total = total_moves;
2401 }
2402 
finish(player_activity & act,Character & who)2403 void insert_item_activity_actor::finish( player_activity &act, Character &who )
2404 {
2405     bool success = false;
2406     drop_location &holstered_item = items.front();
2407     if( holstered_item.first ) {
2408         item &it = *holstered_item.first;
2409         if( !it.count_by_charges() ) {
2410             if( holster->can_contain( it ) && ( all_pockets_rigid ||
2411                                                 holster.parents_can_contain_recursive( &it ) ) ) {
2412 
2413                 success = holster->put_in( it, item_pocket::pocket_type::CONTAINER,
2414                                            /*unseal_pockets=*/true ).success();
2415                 if( success ) {
2416                     //~ %1$s: item to put in the container, %2$s: container to put item in
2417                     who.add_msg_if_player( string_format( _( "You put your %1$s into the %2$s." ),
2418                                                           holstered_item.first->display_name(), holster->type->nname( 1 ) ) );
2419                     handler.add_unsealed( holster );
2420                     handler.unseal_pocket_containing( holstered_item.first );
2421                     holstered_item.first.remove_item();
2422                 }
2423 
2424             }
2425         } else {
2426             int charges = all_pockets_rigid ? holstered_item.second : std::min( holstered_item.second,
2427                           holster.max_charges_by_parent_recursive( it ) );
2428 
2429             if( charges > 0 && holster->can_contain_partial( it ) ) {
2430                 int result = holster->fill_with( it, charges,
2431                                                  /*unseal_pockets=*/true,
2432                                                  /*allow_sealed=*/true );
2433                 success = result > 0;
2434 
2435                 if( success ) {
2436                     item copy( it );
2437                     copy.charges = result;
2438                     //~ %1$s: item to put in the container, %2$s: container to put item in
2439                     who.add_msg_if_player( string_format( _( "You put your %1$s into the %2$s." ),
2440                                                           copy.display_name(), holster->type->nname( 1 ) ) );
2441                     handler.add_unsealed( holster );
2442                     handler.unseal_pocket_containing( holstered_item.first );
2443                     it.charges -= result;
2444                     if( it.charges == 0 ) {
2445                         holstered_item.first.remove_item();
2446                     }
2447                 }
2448             }
2449         }
2450 
2451         if( !success ) {
2452             who.add_msg_if_player(
2453                 string_format(
2454                     _( "Could not put %1$s into %2$s, aborting." ),
2455                     it.tname(), holster->tname() ) );
2456         }
2457     } else {
2458         // item was lost, so just go to next item
2459         success = true;
2460     }
2461 
2462     items.pop_front();
2463     if( items.empty() || !success ) {
2464         handler.handle_by( who );
2465         act.set_to_null();
2466         return;
2467     }
2468 
2469     // Restart the activity
2470     const int total_moves = item_move_cost( who, items.front().first );
2471     act.moves_left = total_moves;
2472     act.moves_total = total_moves;
2473 }
2474 
canceled(player_activity &,Character & who)2475 void insert_item_activity_actor::canceled( player_activity &/*act*/, Character &who )
2476 {
2477     handler.handle_by( who );
2478 }
2479 
serialize(JsonOut & jsout) const2480 void insert_item_activity_actor::serialize( JsonOut &jsout ) const
2481 {
2482     jsout.start_object();
2483 
2484     jsout.member( "holster", holster );
2485     jsout.member( "items", items );
2486     jsout.member( "handler", handler );
2487     jsout.member( "all_pockets_rigid", all_pockets_rigid );
2488 
2489     jsout.end_object();
2490 }
2491 
deserialize(JsonIn & jsin)2492 std::unique_ptr<activity_actor> insert_item_activity_actor::deserialize( JsonIn &jsin )
2493 {
2494     insert_item_activity_actor actor;
2495 
2496     JsonObject data = jsin.get_object();
2497 
2498     data.read( "holster", actor.holster );
2499     data.read( "items", actor.items );
2500     data.read( "handler", actor.handler );
2501     data.read( "all_pockets_rigid", actor.all_pockets_rigid );
2502 
2503     return actor.clone();
2504 }
2505 
can_reload() const2506 bool reload_activity_actor::can_reload() const
2507 {
2508     if( reload_targets.size() != 2 || quantity <= 0 ) {
2509         debugmsg( "invalid arguments to ACT_RELOAD" );
2510         return false;
2511     }
2512 
2513     if( !reload_targets[0] ) {
2514         debugmsg( "reload target is null, failed to reload" );
2515         return false;
2516     }
2517 
2518     if( !reload_targets[1] ) {
2519         debugmsg( "ammo target is null, failed to reload" );
2520         return false;
2521     }
2522 
2523     return true;
2524 }
2525 
start(player_activity & act,Character &)2526 void reload_activity_actor::start( player_activity &act, Character &/*who*/ )
2527 {
2528     act.moves_total = moves_total;
2529     act.moves_left = moves_total;
2530 }
2531 
make_reload_sound(Character & who,item & reloadable)2532 void reload_activity_actor::make_reload_sound( Character &who, item &reloadable )
2533 {
2534     if( reloadable.type->gun->reload_noise_volume > 0 ) {
2535         sfx::play_variant_sound( "reload", reloadable.typeId().str(),
2536                                  sfx::get_heard_volume( who.pos() ) );
2537         sounds::ambient_sound( who.pos(), reloadable.type->gun->reload_noise_volume,
2538                                sounds::sound_t::activity, reloadable.type->gun->reload_noise.translated() );
2539     }
2540 }
2541 
finish(player_activity & act,Character & who)2542 void reload_activity_actor::finish( player_activity &act, Character &who )
2543 {
2544     act.set_to_null();
2545     if( !can_reload() ) {
2546         return;
2547     }
2548 
2549     item &reloadable = *reload_targets[ 0 ];
2550     item &ammo = *reload_targets[ 1 ];
2551     const std::string reloadable_name = reloadable.tname();
2552     // cache check results because reloading deletes the ammo item
2553     const std::string ammo_name = ammo.tname();
2554     const bool ammo_is_filthy = ammo.is_filthy();
2555     const bool ammo_uses_speedloader = ammo.has_flag( flag_SPEEDLOADER );
2556 
2557     if( !reloadable.reload( who, std::move( reload_targets[ 1 ] ), quantity ) ) {
2558         add_msg( m_info, _( "Can't reload the %s." ), reloadable_name );
2559         return;
2560     }
2561 
2562     if( ammo_is_filthy ) {
2563         reloadable.set_flag( flag_FILTHY );
2564     }
2565 
2566     if( reloadable.get_var( "dirt", 0 ) > 7800 ) {
2567         add_msg( m_neutral, _( "You manage to loosen some debris and make your %s somewhat operational." ),
2568                  reloadable_name );
2569         reloadable.set_var( "dirt", ( reloadable.get_var( "dirt", 0 ) - rng( 790, 2750 ) ) );
2570     }
2571 
2572     if( reloadable.is_gun() ) {
2573         who.recoil = MAX_RECOIL;
2574         if( reloadable.has_flag( flag_RELOAD_ONE ) && !ammo_uses_speedloader ) {
2575             add_msg( m_neutral, _( "You insert %dx %s into the %s." ), quantity, ammo_name, reloadable_name );
2576         }
2577         make_reload_sound( who, reloadable );
2578     } else if( reloadable.is_watertight_container() ) {
2579         add_msg( m_neutral, _( "You refill the %s." ), reloadable_name );
2580     } else {
2581         add_msg( m_neutral, _( "You reload the %1$s with %2$s." ), reloadable_name, ammo_name );
2582     }
2583     item_location loc = reload_targets[0];
2584     // Reload may have caused the item to increase in size more than the pocket/location can contain.
2585     // We want to avoid this because items will be deleted on a save/load.
2586     if( loc.volume_capacity() < units::volume() ||
2587         loc.weight_capacity() < units::mass() ) {
2588         // In player inventory and player is wielding nothing.
2589         if( !who.is_armed() && loc.held_by( who ) ) {
2590             add_msg( m_neutral, _( "The %s no longer fits in your inventory so you wield it instead." ),
2591                      reloadable_name );
2592             who.wield( reloadable );
2593         } else {
2594             // In player inventory and player is wielding something.
2595             if( loc.held_by( who ) ) {
2596                 add_msg( m_neutral, _( "The %s no longer fits in your inventory so you drop it instead." ),
2597                          reloadable_name );
2598                 // Default handling message.
2599             } else {
2600                 add_msg( m_neutral, _( "The %s no longer fits its location so you drop it instead." ),
2601                          reloadable_name );
2602             }
2603             get_map().add_item_or_charges( loc.position(), reloadable );
2604             loc.remove_item();
2605         }
2606     }
2607 }
2608 
canceled(player_activity & act,Character &)2609 void reload_activity_actor::canceled( player_activity &act, Character &/*who*/ )
2610 {
2611     act.moves_total = 0;
2612     act.moves_left = 0;
2613 }
2614 
serialize(JsonOut & jsout) const2615 void reload_activity_actor::serialize( JsonOut &jsout ) const
2616 {
2617     jsout.start_object();
2618 
2619     jsout.member( "moves_total", moves_total );
2620     jsout.member( "qty", quantity );
2621     jsout.member( "reload_targets", reload_targets );
2622 
2623     jsout.end_object();
2624 }
2625 
deserialize(JsonIn & jsin)2626 std::unique_ptr<activity_actor> reload_activity_actor::deserialize( JsonIn &jsin )
2627 {
2628     reload_activity_actor actor;
2629 
2630     JsonObject data = jsin.get_object();
2631 
2632     data.read( "moves_total", actor.moves_total );
2633     data.read( "qty", actor.quantity );
2634     data.read( "reload_targets", actor.reload_targets );
2635     return actor.clone();
2636 }
2637 
start(player_activity & act,Character &)2638 void milk_activity_actor::start( player_activity &act, Character &/*who*/ )
2639 {
2640     act.moves_total = total_moves;
2641     act.moves_left = total_moves;
2642 }
2643 
finish(player_activity & act,Character & who)2644 void milk_activity_actor::finish( player_activity &act, Character &who )
2645 {
2646     if( monster_coords.empty() ) {
2647         debugmsg( "milking activity with no position of monster stored" );
2648         return;
2649     }
2650     map &here = get_map();
2651     const tripoint source_pos = here.getlocal( monster_coords.at( 0 ) );
2652     monster *source_mon = g->critter_at<monster>( source_pos );
2653     if( source_mon == nullptr ) {
2654         debugmsg( "could not find source creature for liquid transfer" );
2655         return;
2656     }
2657     auto milked_item = source_mon->ammo.find( source_mon->type->starting_ammo.begin()->first );
2658     if( milked_item == source_mon->ammo.end() ) {
2659         debugmsg( "animal has no milkable ammo type" );
2660         return;
2661     }
2662     if( milked_item->second <= 0 ) {
2663         debugmsg( "started milking but udders are now empty before milking finishes" );
2664         return;
2665     }
2666     item milk( milked_item->first, calendar::turn, milked_item->second );
2667     milk.set_item_temperature( 311.75 );
2668     if( liquid_handler::handle_liquid( milk, nullptr, 1, nullptr, nullptr, -1, source_mon ) ) {
2669         milked_item->second = 0;
2670         if( milk.charges > 0 ) {
2671             milked_item->second = milk.charges;
2672         } else {
2673             who.add_msg_if_player( _( "The %s's udders run dry." ), source_mon->get_name() );
2674         }
2675     }
2676     // if the monster was not manually tied up, but needed to be fixed in place temporarily then
2677     // remove that now.
2678     if( !string_values.empty() && string_values[0] == "temp_tie" ) {
2679         source_mon->remove_effect( effect_tied );
2680     }
2681 
2682     act.set_to_null();
2683 }
2684 
serialize(JsonOut & jsout) const2685 void milk_activity_actor::serialize( JsonOut &jsout ) const
2686 {
2687     jsout.start_object();
2688 
2689     jsout.member( "total_moves", total_moves );
2690     jsout.member( "monster_coords", monster_coords );
2691     jsout.member( "string_values", string_values );
2692 
2693     jsout.end_object();
2694 }
2695 
deserialize(JsonIn & jsin)2696 std::unique_ptr<activity_actor> milk_activity_actor::deserialize( JsonIn &jsin )
2697 {
2698     milk_activity_actor actor;
2699     JsonObject data = jsin.get_object();
2700 
2701     data.read( "moves_total", actor.total_moves );
2702     data.read( "monster_coords", actor.monster_coords );
2703     data.read( "string_values", actor.string_values );
2704 
2705     return actor.clone();
2706 }
2707 
check_if_disassemble_okay(item_location target,Character & who)2708 static bool check_if_disassemble_okay( item_location target, Character &who )
2709 {
2710     item *disassembly = target.get_item();
2711 
2712     // item_location::get_item() will return nullptr if the item is lost
2713     if( !disassembly ) {
2714         who.add_msg_player_or_npc(
2715             _( "You no longer have the in progress disassembly in your possession.  "
2716                "You stop disassembling.  "
2717                "Reactivate the in progress disassembly to continue disassembling." ),
2718             _( "<npcname> no longer has the in progress disassembly in their possession.  "
2719                "<npcname> stops disassembling." ) );
2720         return false;
2721     }
2722     return true;
2723 }
2724 
start(player_activity & act,Character & who)2725 void disassemble_activity_actor::start( player_activity &act, Character &who )
2726 {
2727     if( act.targets.empty() ) {
2728         act.set_to_null();
2729         return;
2730     }
2731 
2732     if( act.targets.back()->typeId() != itype_disassembly ) {
2733         target = who.create_in_progress_disassembly( act.targets.back() );
2734     } else {
2735         target = act.targets.back();
2736     }
2737     act.targets.pop_back();
2738 
2739     if( !check_if_disassemble_okay( target, who ) ) {
2740         act.set_to_null();
2741         return;
2742     }
2743 
2744     // We already checked if this is nullptr above
2745     item *craft = target.get_item();
2746     double counter = craft->item_counter;
2747 
2748     act.moves_left = moves_total - ( counter / 10'000'000.0 ) * moves_total;
2749     act.moves_total = moves_total;
2750 }
2751 
do_turn(player_activity & act,Character & who)2752 void disassemble_activity_actor::do_turn( player_activity &act, Character &who )
2753 {
2754     if( !check_if_disassemble_okay( target, who ) ) {
2755         act.set_to_null();
2756         return;
2757     }
2758 
2759     // We already checked if this is nullptr above
2760     item *craft = target.get_item();
2761 
2762     double moves_left = act.moves_left;
2763     double moves_total = act.moves_total;
2764     // Current progress as a percent of base_total_moves to 2 decimal places
2765     craft->item_counter = std::round( ( moves_total - moves_left ) / moves_total * 10'000'000.0 );
2766 }
2767 
finish(player_activity & act,Character & who)2768 void disassemble_activity_actor::finish( player_activity &act, Character &who )
2769 {
2770     if( !check_if_disassemble_okay( target, who ) ) {
2771         act.set_to_null();
2772         return;
2773     }
2774     who.complete_disassemble( target );
2775 }
2776 
serialize(JsonOut & jsout) const2777 void disassemble_activity_actor::serialize( JsonOut &jsout ) const
2778 {
2779     jsout.start_object();
2780 
2781     jsout.member( "moves_total", moves_total );
2782 
2783     jsout.end_object();
2784 }
2785 
deserialize(JsonIn & jsin)2786 std::unique_ptr<activity_actor> disassemble_activity_actor::deserialize( JsonIn &jsin )
2787 {
2788     disassemble_activity_actor actor = disassemble_activity_actor( 0 );
2789 
2790     JsonObject data = jsin.get_object();
2791 
2792     data.read( "moves_total", actor.moves_total );
2793 
2794     return actor.clone();
2795 }
2796 
2797 namespace activity_actors
2798 {
2799 
2800 // Please keep this alphabetically sorted
2801 const std::unordered_map<activity_id, std::unique_ptr<activity_actor>( * )( JsonIn & )>
2802 deserialize_functions = {
2803     { activity_id( "ACT_AIM" ), &aim_activity_actor::deserialize },
2804     { activity_id( "ACT_AUTODRIVE" ), &autodrive_activity_actor::deserialize },
2805     { activity_id( "ACT_CONSUME" ), &consume_activity_actor::deserialize },
2806     { activity_id( "ACT_CRAFT" ), &craft_activity_actor::deserialize },
2807     { activity_id( "ACT_DIG" ), &dig_activity_actor::deserialize },
2808     { activity_id( "ACT_DIG_CHANNEL" ), &dig_channel_activity_actor::deserialize },
2809     { activity_id( "ACT_DISASSEMBLE" ), &disassemble_activity_actor::deserialize },
2810     { activity_id( "ACT_DROP" ), &drop_activity_actor::deserialize },
2811     { activity_id( "ACT_GUNMOD_REMOVE" ), &gunmod_remove_activity_actor::deserialize },
2812     { activity_id( "ACT_HACKING" ), &hacking_activity_actor::deserialize },
2813     { activity_id( "ACT_HOTWIRE_CAR" ), &hotwire_car_activity_actor::deserialize },
2814     { activity_id( "ACT_INSERT_ITEM" ), &insert_item_activity_actor::deserialize },
2815     { activity_id( "ACT_LOCKPICK" ), &lockpick_activity_actor::deserialize },
2816     { activity_id( "ACT_MIGRATION_CANCEL" ), &migration_cancel_activity_actor::deserialize },
2817     { activity_id( "ACT_MILK" ), &milk_activity_actor::deserialize },
2818     { activity_id( "ACT_MOVE_ITEMS" ), &move_items_activity_actor::deserialize },
2819     { activity_id( "ACT_OPEN_GATE" ), &open_gate_activity_actor::deserialize },
2820     { activity_id( "ACT_PICKUP" ), &pickup_activity_actor::deserialize },
2821     { activity_id( "ACT_RELOAD" ), &reload_activity_actor::deserialize },
2822     { activity_id( "ACT_STASH" ), &stash_activity_actor::deserialize },
2823     { activity_id( "ACT_TRY_SLEEP" ), &try_sleep_activity_actor::deserialize },
2824     { activity_id( "ACT_UNLOAD" ), &unload_activity_actor::deserialize },
2825     { activity_id( "ACT_WORKOUT_HARD" ), &workout_activity_actor::deserialize },
2826     { activity_id( "ACT_WORKOUT_ACTIVE" ), &workout_activity_actor::deserialize },
2827     { activity_id( "ACT_WORKOUT_MODERATE" ), &workout_activity_actor::deserialize },
2828     { activity_id( "ACT_WORKOUT_LIGHT" ), &workout_activity_actor::deserialize },
2829     { activity_id( "ACT_FURNITURE_MOVE" ), &move_furniture_activity_actor::deserialize },
2830 };
2831 } // namespace activity_actors
2832 
serialize(const cata::clone_ptr<activity_actor> & actor,JsonOut & jsout)2833 void serialize( const cata::clone_ptr<activity_actor> &actor, JsonOut &jsout )
2834 {
2835     if( !actor ) {
2836         jsout.write_null();
2837     } else {
2838         jsout.start_object();
2839 
2840         jsout.member( "actor_type", actor->get_type() );
2841         jsout.member( "actor_data", *actor );
2842 
2843         jsout.end_object();
2844     }
2845 }
2846 
deserialize(cata::clone_ptr<activity_actor> & actor,JsonIn & jsin)2847 void deserialize( cata::clone_ptr<activity_actor> &actor, JsonIn &jsin )
2848 {
2849     if( jsin.test_null() ) {
2850         actor = nullptr;
2851     } else {
2852         JsonObject data = jsin.get_object();
2853         if( data.has_member( "actor_data" ) ) {
2854             activity_id actor_type;
2855             data.read( "actor_type", actor_type );
2856             auto deserializer = activity_actors::deserialize_functions.find( actor_type );
2857             if( deserializer != activity_actors::deserialize_functions.end() ) {
2858                 actor = deserializer->second( *data.get_raw( "actor_data" ) );
2859             } else {
2860                 debugmsg( "Failed to find activity actor deserializer for type \"%s\"", actor_type.c_str() );
2861                 actor = nullptr;
2862             }
2863         } else {
2864             debugmsg( "Failed to load activity actor" );
2865             actor = nullptr;
2866         }
2867     }
2868 }
2869