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