1 #include "activity_handlers.h"
2 
3 #include <algorithm>
4 #include <climits>
5 #include <cmath>
6 #include <cstdlib>
7 #include <iterator>
8 #include <memory>
9 #include <ostream>
10 #include <queue>
11 #include <set>
12 #include <stdexcept>
13 #include <string>
14 #include <type_traits>
15 #include <utility>
16 
17 #include "action.h"
18 #include "activity_type.h"
19 #include "advanced_inv.h"
20 #include "avatar.h"
21 #include "avatar_action.h"
22 #include "bionics.h"
23 #include "bodypart.h"
24 #include "butchery_requirements.h"
25 #include "calendar.h"
26 #include "cata_utility.h"
27 #include "character.h"
28 #include "character_martial_arts.h"
29 #include "clzones.h"
30 #include "colony.h"
31 #include "color.h"
32 #include "construction.h"
33 #include "coordinates.h"
34 #include "creature.h"
35 #include "damage.h"
36 #include "debug.h"
37 #include "effect_source.h"
38 #include "enums.h"
39 #include "event.h"
40 #include "event_bus.h"
41 #include "fault.h"
42 #include "field_type.h"
43 #include "flag.h"
44 #include "game.h"
45 #include "game_constants.h"
46 #include "game_inventory.h"
47 #include "handle_liquid.h"
48 #include "harvest.h"
49 #include "iexamine.h"
50 #include "inventory.h"
51 #include "item.h"
52 #include "item_contents.h"
53 #include "item_factory.h"
54 #include "item_location.h"
55 #include "item_pocket.h"
56 #include "item_stack.h"
57 #include "itype.h"
58 #include "iuse.h"
59 #include "iuse_actor.h"
60 #include "line.h"
61 #include "magic.h"
62 #include "map.h"
63 #include "map_iterator.h"
64 #include "map_selector.h"
65 #include "mapdata.h"
66 #include "martialarts.h"
67 #include "memory_fast.h"
68 #include "messages.h"
69 #include "mongroup.h"
70 #include "monster.h"
71 #include "morale_types.h"
72 #include "mtype.h"
73 #include "npc.h"
74 #include "omdata.h"
75 #include "optional.h"
76 #include "output.h"
77 #include "overmapbuffer.h"
78 #include "pimpl.h"
79 #include "player.h"
80 #include "player_activity.h"
81 #include "point.h"
82 #include "proficiency.h"
83 #include "ranged.h"
84 #include "requirements.h"
85 #include "ret_val.h"
86 #include "rng.h"
87 #include "skill.h"
88 #include "sounds.h"
89 #include "string_formatter.h"
90 #include "text_snippets.h"
91 #include "translations.h"
92 #include "type_id.h"
93 #include "ui.h"
94 #include "units.h"
95 #include "value_ptr.h"
96 #include "veh_interact.h"
97 #include "vehicle.h"
98 #include "vpart_position.h"
99 #include "weather.h"
100 
101 enum class creature_size : int;
102 
103 static const efftype_id effect_sheared( "sheared" );
104 
105 #define dbg(x) DebugLog((x),D_GAME) << __FILE__ << ":" << __LINE__ << ": "
106 
107 static const activity_id ACT_ADV_INVENTORY( "ACT_ADV_INVENTORY" );
108 static const activity_id ACT_ARMOR_LAYERS( "ACT_ARMOR_LAYERS" );
109 static const activity_id ACT_ATM( "ACT_ATM" );
110 static const activity_id ACT_BUILD( "ACT_BUILD" );
111 static const activity_id ACT_BLEED( "ACT_BLEED" );
112 static const activity_id ACT_BUTCHER( "ACT_BUTCHER" );
113 static const activity_id ACT_BUTCHER_FULL( "ACT_BUTCHER_FULL" );
114 static const activity_id ACT_CHOP_LOGS( "ACT_CHOP_LOGS" );
115 static const activity_id ACT_CHOP_PLANKS( "ACT_CHOP_PLANKS" );
116 static const activity_id ACT_CHOP_TREE( "ACT_CHOP_TREE" );
117 static const activity_id ACT_CHURN( "ACT_CHURN" );
118 static const activity_id ACT_CLEAR_RUBBLE( "ACT_CLEAR_RUBBLE" );
119 static const activity_id ACT_CONSUME_DRINK_MENU( "ACT_CONSUME_DRINK_MENU" );
120 static const activity_id ACT_CONSUME_FOOD_MENU( "ACT_CONSUME_FOOD_MENU" );
121 static const activity_id ACT_CONSUME_MEDS_MENU( "ACT_CONSUME_MEDS_MENU" );
122 static const activity_id ACT_CRACKING( "ACT_CRACKING" );
123 static const activity_id ACT_DISMEMBER( "ACT_DISMEMBER" );
124 static const activity_id ACT_DISSECT( "ACT_DISSECT" );
125 static const activity_id ACT_EAT_MENU( "ACT_EAT_MENU" );
126 static const activity_id ACT_FERTILIZE_PLOT( "ACT_FERTILIZE_PLOT" );
127 static const activity_id ACT_FETCH_REQUIRED( "ACT_FETCH_REQUIRED" );
128 static const activity_id ACT_FIELD_DRESS( "ACT_FIELD_DRESS" );
129 static const activity_id ACT_FILL_LIQUID( "ACT_FILL_LIQUID" );
130 static const activity_id ACT_FILL_PIT( "ACT_FILL_PIT" );
131 static const activity_id ACT_FIND_MOUNT( "ACT_FIND_MOUNT" );
132 static const activity_id ACT_FIRSTAID( "ACT_FIRSTAID" );
133 static const activity_id ACT_FISH( "ACT_FISH" );
134 static const activity_id ACT_FORAGE( "ACT_FORAGE" );
135 static const activity_id ACT_GAME( "ACT_GAME" );
136 static const activity_id ACT_GENERIC_GAME( "ACT_GENERIC_GAME" );
137 static const activity_id ACT_GUNMOD_ADD( "ACT_GUNMOD_ADD" );
138 static const activity_id ACT_HACKSAW( "ACT_HACKSAW" );
139 static const activity_id ACT_HAIRCUT( "ACT_HAIRCUT" );
140 static const activity_id ACT_HAND_CRANK( "ACT_HAND_CRANK" );
141 static const activity_id ACT_HEATING( "ACT_HEATING" );
142 static const activity_id ACT_JACKHAMMER( "ACT_JACKHAMMER" );
143 static const activity_id ACT_LONGSALVAGE( "ACT_LONGSALVAGE" );
144 static const activity_id ACT_MEDITATE( "ACT_MEDITATE" );
145 static const activity_id ACT_MEND_ITEM( "ACT_MEND_ITEM" );
146 static const activity_id ACT_MIND_SPLICER( "ACT_MIND_SPLICER" );
147 static const activity_id ACT_MOVE_LOOT( "ACT_MOVE_LOOT" );
148 static const activity_id ACT_MULTIPLE_BUTCHER( "ACT_MULTIPLE_BUTCHER" );
149 static const activity_id ACT_MULTIPLE_CHOP_PLANKS( "ACT_MULTIPLE_CHOP_PLANKS" );
150 static const activity_id ACT_MULTIPLE_CHOP_TREES( "ACT_MULTIPLE_CHOP_TREES" );
151 static const activity_id ACT_MULTIPLE_CONSTRUCTION( "ACT_MULTIPLE_CONSTRUCTION" );
152 static const activity_id ACT_MULTIPLE_MINE( "ACT_MULTIPLE_MINE" );
153 static const activity_id ACT_MULTIPLE_FARM( "ACT_MULTIPLE_FARM" );
154 static const activity_id ACT_MULTIPLE_FISH( "ACT_MULTIPLE_FISH" );
155 static const activity_id ACT_OPERATION( "ACT_OPERATION" );
156 static const activity_id ACT_OXYTORCH( "ACT_OXYTORCH" );
157 static const activity_id ACT_PICKAXE( "ACT_PICKAXE" );
158 static const activity_id ACT_PLANT_SEED( "ACT_PLANT_SEED" );
159 static const activity_id ACT_PLAY_WITH_PET( "ACT_PLAY_WITH_PET" );
160 static const activity_id ACT_PRY_NAILS( "ACT_PRY_NAILS" );
161 static const activity_id ACT_PULP( "ACT_PULP" );
162 static const activity_id ACT_QUARTER( "ACT_QUARTER" );
163 static const activity_id ACT_READ( "ACT_READ" );
164 static const activity_id ACT_REPAIR_ITEM( "ACT_REPAIR_ITEM" );
165 static const activity_id ACT_ROBOT_CONTROL( "ACT_ROBOT_CONTROL" );
166 static const activity_id ACT_SHAVE( "ACT_SHAVE" );
167 static const activity_id ACT_SKIN( "ACT_SKIN" );
168 static const activity_id ACT_SOCIALIZE( "ACT_SOCIALIZE" );
169 static const activity_id ACT_SPELLCASTING( "ACT_SPELLCASTING" );
170 static const activity_id ACT_START_ENGINES( "ACT_START_ENGINES" );
171 static const activity_id ACT_START_FIRE( "ACT_START_FIRE" );
172 static const activity_id ACT_STUDY_SPELL( "ACT_STUDY_SPELL" );
173 static const activity_id ACT_TIDY_UP( "ACT_TIDY_UP" );
174 static const activity_id ACT_TOOLMOD_ADD( "ACT_TOOLMOD_ADD" );
175 static const activity_id ACT_TRAIN( "ACT_TRAIN" );
176 static const activity_id ACT_TRAVELLING( "ACT_TRAVELLING" );
177 static const activity_id ACT_TREE_COMMUNION( "ACT_TREE_COMMUNION" );
178 static const activity_id ACT_VEHICLE( "ACT_VEHICLE" );
179 static const activity_id ACT_VEHICLE_DECONSTRUCTION( "ACT_VEHICLE_DECONSTRUCTION" );
180 static const activity_id ACT_VEHICLE_REPAIR( "ACT_VEHICLE_REPAIR" );
181 static const activity_id ACT_VIBE( "ACT_VIBE" );
182 static const activity_id ACT_WAIT( "ACT_WAIT" );
183 static const activity_id ACT_WAIT_NPC( "ACT_WAIT_NPC" );
184 static const activity_id ACT_WAIT_STAMINA( "ACT_WAIT_STAMINA" );
185 static const activity_id ACT_WAIT_WEATHER( "ACT_WAIT_WEATHER" );
186 static const activity_id ACT_WASH( "ACT_WASH" );
187 static const activity_id ACT_WEAR( "ACT_WEAR" );
188 
189 static const efftype_id effect_blind( "blind" );
190 static const efftype_id effect_controlled( "controlled" );
191 static const efftype_id effect_narcosis( "narcosis" );
192 static const efftype_id effect_pet( "pet" );
193 static const efftype_id effect_sleep( "sleep" );
194 static const efftype_id effect_tied( "tied" );
195 static const efftype_id effect_under_operation( "under_operation" );
196 
197 static const itype_id itype_2x4( "2x4" );
198 static const itype_id itype_animal( "animal" );
199 static const itype_id itype_battery( "battery" );
200 static const itype_id itype_burnt_out_bionic( "burnt_out_bionic" );
201 static const itype_id itype_log( "log" );
202 static const itype_id itype_mind_scan_robofac( "mind_scan_robofac" );
203 static const itype_id itype_muscle( "muscle" );
204 static const itype_id itype_nail( "nail" );
205 static const itype_id itype_pipe( "pipe" );
206 static const itype_id itype_rebar( "rebar" );
207 static const itype_id itype_scrap( "scrap" );
208 static const itype_id itype_sheet_metal( "sheet_metal" );
209 static const itype_id itype_spike( "spike" );
210 static const itype_id itype_splinter( "splinter" );
211 static const itype_id itype_stick_long( "stick_long" );
212 static const itype_id itype_steel_chunk( "steel_chunk" );
213 static const itype_id itype_steel_plate( "steel_plate" );
214 static const itype_id itype_UPS( "UPS" );
215 static const itype_id itype_wire( "wire" );
216 static const itype_id itype_wool_staple( "wool_staple" );
217 
218 static const zone_type_id zone_type_FARM_PLOT( "FARM_PLOT" );
219 
220 static const skill_id skill_computer( "computer" );
221 static const skill_id skill_electronics( "electronics" );
222 static const skill_id skill_fabrication( "fabrication" );
223 static const skill_id skill_firstaid( "firstaid" );
224 static const skill_id skill_survival( "survival" );
225 
226 static const proficiency_id proficiency_prof_safecracking( "prof_safecracking" );
227 
228 static const quality_id qual_BUTCHER( "BUTCHER" );
229 static const quality_id qual_CUT_FINE( "CUT_FINE" );
230 
231 static const species_id species_HUMAN( "HUMAN" );
232 static const species_id species_ZOMBIE( "ZOMBIE" );
233 
234 static const json_character_flag json_flag_CANNIBAL( "CANNIBAL" );
235 static const json_character_flag json_flag_PSYCHOPATH( "PSYCHOPATH" );
236 static const json_character_flag json_flag_SAPIOVORE( "SAPIOVORE" );
237 static const json_character_flag json_flag_SUPER_HEARING( "SUPER_HEARING" );
238 
239 static const bionic_id bio_painkiller( "bio_painkiller" );
240 
241 static const trait_id trait_DEBUG_HS( "DEBUG_HS" );
242 static const trait_id trait_NOPAIN( "NOPAIN" );
243 static const trait_id trait_SPIRITUAL( "SPIRITUAL" );
244 static const trait_id trait_STOCKY_TROGLO( "STOCKY_TROGLO" );
245 
246 // not to confuse with item flags (json_flag)
247 static const std::string flag_AUTODOC( "AUTODOC" );
248 static const std::string flag_AUTODOC_COUCH( "AUTODOC_COUCH" );
249 static const std::string flag_PLANTABLE( "PLANTABLE" );
250 static const std::string flag_SUPPORTS_ROOF( "SUPPORTS_ROOF" );
251 
252 using namespace activity_handlers;
253 
254 const std::map< activity_id, std::function<void( player_activity *, player * )> >
255 activity_handlers::do_turn_functions = {
256     { ACT_FILL_LIQUID, fill_liquid_do_turn },
257     { ACT_PICKAXE, pickaxe_do_turn },
258     { ACT_PULP, pulp_do_turn },
259     { ACT_GAME, game_do_turn },
260     { ACT_GENERIC_GAME, generic_game_do_turn },
261     { ACT_START_FIRE, start_fire_do_turn },
262     { ACT_VIBE, vibe_do_turn },
263     { ACT_HAND_CRANK, hand_crank_do_turn },
264     { ACT_OXYTORCH, oxytorch_do_turn },
265     { ACT_WEAR, wear_do_turn },
266     { ACT_MULTIPLE_FISH, multiple_fish_do_turn },
267     { ACT_MULTIPLE_CONSTRUCTION, multiple_construction_do_turn },
268     { ACT_MULTIPLE_MINE, multiple_mine_do_turn },
269     { ACT_MULTIPLE_BUTCHER, multiple_butcher_do_turn },
270     { ACT_MULTIPLE_FARM, multiple_farm_do_turn },
271     { ACT_FETCH_REQUIRED, fetch_do_turn },
272     { ACT_BUILD, build_do_turn },
273     { ACT_EAT_MENU, eat_menu_do_turn },
274     { ACT_VEHICLE_DECONSTRUCTION, vehicle_deconstruction_do_turn },
275     { ACT_VEHICLE_REPAIR, vehicle_repair_do_turn },
276     { ACT_MULTIPLE_CHOP_TREES, chop_trees_do_turn },
277     { ACT_CONSUME_FOOD_MENU, consume_food_menu_do_turn },
278     { ACT_CONSUME_DRINK_MENU, consume_drink_menu_do_turn },
279     { ACT_CONSUME_MEDS_MENU, consume_meds_menu_do_turn },
280     { ACT_MOVE_LOOT, move_loot_do_turn },
281     { ACT_ADV_INVENTORY, adv_inventory_do_turn },
282     { ACT_ARMOR_LAYERS, armor_layers_do_turn },
283     { ACT_ATM, atm_do_turn },
284     { ACT_CRACKING, cracking_do_turn },
285     { ACT_FISH, fish_do_turn },
286     { ACT_REPAIR_ITEM, repair_item_do_turn },
287     { ACT_BLEED, butcher_do_turn },
288     { ACT_BUTCHER, butcher_do_turn },
289     { ACT_BUTCHER_FULL, butcher_do_turn },
290     { ACT_TRAVELLING, travel_do_turn },
291     { ACT_FIELD_DRESS, butcher_do_turn },
292     { ACT_SKIN, butcher_do_turn },
293     { ACT_QUARTER, butcher_do_turn },
294     { ACT_DISMEMBER, butcher_do_turn },
295     { ACT_DISSECT, butcher_do_turn },
296     { ACT_HACKSAW, hacksaw_do_turn },
297     { ACT_PRY_NAILS, pry_nails_do_turn },
298     { ACT_CHOP_TREE, chop_tree_do_turn },
299     { ACT_CHOP_LOGS, chop_tree_do_turn },
300     { ACT_TIDY_UP, tidy_up_do_turn },
301     { ACT_CHOP_PLANKS, chop_tree_do_turn },
302     { ACT_TIDY_UP, tidy_up_do_turn },
303     { ACT_JACKHAMMER, jackhammer_do_turn },
304     { ACT_FIND_MOUNT, find_mount_do_turn },
305     { ACT_FILL_PIT, fill_pit_do_turn },
306     { ACT_MULTIPLE_CHOP_PLANKS, multiple_chop_planks_do_turn },
307     { ACT_FERTILIZE_PLOT, fertilize_plot_do_turn },
308     { ACT_OPERATION, operation_do_turn },
309     { ACT_ROBOT_CONTROL, robot_control_do_turn },
310     { ACT_TREE_COMMUNION, tree_communion_do_turn },
311     { ACT_STUDY_SPELL, study_spell_do_turn},
312     { ACT_READ, read_do_turn},
313     { ACT_WAIT_STAMINA, wait_stamina_do_turn }
314 };
315 
316 const std::map< activity_id, std::function<void( player_activity *, player * )> >
317 activity_handlers::finish_functions = {
318     { ACT_BLEED, butcher_finish },
319     { ACT_BUTCHER, butcher_finish },
320     { ACT_BUTCHER_FULL, butcher_finish },
321     { ACT_FIELD_DRESS, butcher_finish },
322     { ACT_SKIN, butcher_finish },
323     { ACT_QUARTER, butcher_finish },
324     { ACT_DISMEMBER, butcher_finish },
325     { ACT_DISSECT, butcher_finish },
326     { ACT_FIRSTAID, firstaid_finish },
327     { ACT_FISH, fish_finish },
328     { ACT_FORAGE, forage_finish },
329     { ACT_LONGSALVAGE, longsalvage_finish },
330     { ACT_PICKAXE, pickaxe_finish },
331     { ACT_START_FIRE, start_fire_finish },
332     { ACT_TRAIN, train_finish },
333     { ACT_CHURN, churn_finish },
334     { ACT_PLANT_SEED, plant_seed_finish },
335     { ACT_VEHICLE, vehicle_finish },
336     { ACT_START_ENGINES, start_engines_finish },
337     { ACT_OXYTORCH, oxytorch_finish },
338     { ACT_PULP, pulp_finish },
339     { ACT_CRACKING, cracking_finish },
340     { ACT_REPAIR_ITEM, repair_item_finish },
341     { ACT_HEATING, heat_item_finish },
342     { ACT_MEND_ITEM, mend_item_finish },
343     { ACT_GUNMOD_ADD, gunmod_add_finish },
344     { ACT_TOOLMOD_ADD, toolmod_add_finish },
345     { ACT_CLEAR_RUBBLE, clear_rubble_finish },
346     { ACT_MEDITATE, meditate_finish },
347     { ACT_READ, read_finish },
348     { ACT_WAIT, wait_finish },
349     { ACT_WAIT_WEATHER, wait_weather_finish },
350     { ACT_WAIT_NPC, wait_npc_finish },
351     { ACT_WAIT_STAMINA, wait_stamina_finish },
352     { ACT_SOCIALIZE, socialize_finish },
353     { ACT_OPERATION, operation_finish },
354     { ACT_VIBE, vibe_finish },
355     { ACT_ATM, atm_finish },
356     { ACT_EAT_MENU, eat_menu_finish },
357     { ACT_CONSUME_FOOD_MENU, eat_menu_finish },
358     { ACT_CONSUME_DRINK_MENU, eat_menu_finish },
359     { ACT_CONSUME_MEDS_MENU, eat_menu_finish },
360     { ACT_WASH, washing_finish },
361     { ACT_HACKSAW, hacksaw_finish },
362     { ACT_PRY_NAILS, pry_nails_finish },
363     { ACT_CHOP_TREE, chop_tree_finish },
364     { activity_id( "ACT_SHEAR" ), shear_finish },
365     { ACT_CHOP_LOGS, chop_logs_finish },
366     { ACT_CHOP_PLANKS, chop_planks_finish },
367     { ACT_JACKHAMMER, jackhammer_finish },
368     { ACT_FILL_PIT, fill_pit_finish },
369     { ACT_PLAY_WITH_PET, play_with_pet_finish },
370     { ACT_SHAVE, shaving_finish },
371     { ACT_HAIRCUT, haircut_finish },
372     { ACT_ROBOT_CONTROL, robot_control_finish },
373     { ACT_MIND_SPLICER, mind_splicer_finish },
374     { ACT_SPELLCASTING, spellcasting_finish },
375     { ACT_STUDY_SPELL, study_spell_finish }
376 };
377 
378 namespace io
379 {
380 // *INDENT-OFF*
381 template<>
enum_to_string(butcher_type data)382 std::string enum_to_string<butcher_type>( butcher_type data )
383 {
384     switch( data ) {
385     case butcher_type::BLEED: return "BLEED";
386     case butcher_type::DISMEMBER: return "DISMEMBER";
387     case butcher_type::DISSECT: return "DISSECT";
388     case butcher_type::FIELD_DRESS: return "FIELD_DRESS";
389     case butcher_type::FULL: return "FULL";
390     case butcher_type::QUARTER: return "QUARTER";
391     case butcher_type::QUICK: return "QUICK";
392     case butcher_type::SKIN: return "SKIN";
393     case butcher_type::NUM_TYPES: break;
394     }
395     debugmsg( "Invalid valid_target" );
396     abort();
397 }
398 // *INDENT-ON*
399 
400 } // namespace io
401 
resume_for_multi_activities(player & p)402 bool activity_handlers::resume_for_multi_activities( player &p )
403 {
404     if( !p.backlog.empty() ) {
405         player_activity &back_act = p.backlog.front();
406         if( back_act.is_multi_type() ) {
407             p.assign_activity( p.backlog.front().id() );
408             p.backlog.clear();
409             return true;
410         }
411     }
412     return false;
413 }
414 
check_butcher_cbm(const int roll)415 static bool check_butcher_cbm( const int roll )
416 {
417     // Failure rates for dissection rolls
418     // 90% at roll 0, 72% at roll 1, 60% at roll 2, 51% @ 3, 45% @ 4, 40% @ 5, ... , 25% @ 10
419     // Roll is roughly a rng(0, -3 + 1st_aid + fine_cut_quality + 1/2 electronics + small_dex_bonus)
420     // Roll is reduced by corpse damage level, but to no less then 0
421     add_msg_debug( _( "Roll = %i" ), roll );
422     add_msg_debug( _( "Failure chance = %f%%" ), ( 9.0f / ( 10.0f + roll * 2.5f ) ) * 100.0f );
423     const bool failed = x_in_y( 9, ( 10 + roll * 2.5 ) );
424     return !failed;
425 }
426 
butcher_cbm_item(const itype_id & what,const tripoint & pos,const time_point & age,const int roll,const std::vector<flag_id> & flags,const std::vector<fault_id> & faults)427 static void butcher_cbm_item( const itype_id &what, const tripoint &pos,
428                               const time_point &age, const int roll, const std::vector<flag_id> &flags,
429                               const std::vector<fault_id> &faults )
430 {
431     if( roll < 0 ) {
432         return;
433     }
434     map &here = get_map();
435     if( item::find_type( what )->bionic ) {
436         item cbm( check_butcher_cbm( roll ) ? what : itype_burnt_out_bionic, age );
437         for( const flag_id &flg : flags ) {
438             cbm.set_flag( flg );
439         }
440         for( const fault_id &flt : faults ) {
441             cbm.faults.emplace( flt );
442         }
443         add_msg( m_good, _( "You discover a %s!" ), cbm.tname() );
444         here.add_item( pos, cbm );
445     } else if( check_butcher_cbm( roll ) ) {
446         item something( what, age );
447         for( const flag_id &flg : flags ) {
448             something.set_flag( flg );
449         }
450         for( const fault_id &flt : faults ) {
451             something.faults.emplace( flt );
452         }
453         add_msg( m_good, _( "You discover a %s!" ), something.tname() );
454         here.add_item( pos, something );
455     } else {
456         add_msg( m_bad, _( "You discover only damaged organs." ) );
457     }
458 }
459 
butcher_cbm_group(const item_group_id & group,const tripoint & pos,const time_point & age,const int roll,const std::vector<flag_id> & flags,const std::vector<fault_id> & faults)460 static void butcher_cbm_group(
461     const item_group_id &group, const tripoint &pos, const time_point &age, const int roll,
462     const std::vector<flag_id> &flags, const std::vector<fault_id> &faults )
463 {
464     if( roll < 0 ) {
465         return;
466     }
467 
468     map &here = get_map();
469     //To see if it spawns a random additional CBM
470     if( check_butcher_cbm( roll ) ) {
471         //The CBM works
472         const std::vector<item *> spawned = here.put_items_from_loc( group, pos, age );
473         for( item *it : spawned ) {
474             for( const flag_id &flg : flags ) {
475                 it->set_flag( flg );
476             }
477             for( const fault_id &flt : faults ) {
478                 it->faults.emplace( flt );
479             }
480             add_msg( m_good, _( "You discover a %s!" ), it->tname() );
481         }
482     } else {
483         //There is a burnt out CBM
484         item cbm( itype_burnt_out_bionic, age );
485         for( const flag_id &flg : flags ) {
486             cbm.set_flag( flg );
487         }
488         for( const fault_id &flt : faults ) {
489             cbm.faults.emplace( flt );
490         }
491         add_msg( m_good, _( "You discover a %s!" ), cbm.tname() );
492         here.add_item( pos, cbm );
493     }
494 }
495 
set_up_butchery(player_activity & act,player & u,butcher_type action)496 static void set_up_butchery( player_activity &act, player &u, butcher_type action )
497 {
498     const int factor = u.max_quality( action == butcher_type::DISSECT ? qual_CUT_FINE : qual_BUTCHER );
499 
500     const item &corpse_item = *act.targets.back();
501     const mtype &corpse = *corpse_item.get_mtype();
502 
503     if( action != butcher_type::DISSECT ) {
504         if( factor == INT_MIN ) {
505             u.add_msg_if_player( m_info,
506                                  _( "None of your cutting tools are suitable for butchering." ) );
507             act.set_to_null();
508             return;
509         } else if( factor < 0 && one_in( 3 ) ) {
510             u.add_msg_if_player( m_bad,
511                                  _( "You don't trust the quality of your tools, but carry on anyway." ) );
512         }
513     }
514 
515     if( action == butcher_type::DISSECT ) {
516         switch( factor ) {
517             case INT_MIN:
518                 u.add_msg_if_player( m_info, _( "None of your tools are sharp and precise enough to do that." ) );
519                 act.set_to_null();
520                 return;
521             case 1:
522                 u.add_msg_if_player( m_info, _( "You could use a better tool, but this will do." ) );
523                 break;
524             case 2:
525                 u.add_msg_if_player( m_info, _( "This tool is great, but you still would like a scalpel." ) );
526                 break;
527             case 3:
528                 u.add_msg_if_player( m_info, _( "You dissect the corpse with a trusty scalpel." ) );
529                 break;
530             case 5:
531                 u.add_msg_if_player( m_info,
532                                      _( "You dissect the corpse with a sophisticated system of surgical grade scalpels." ) );
533                 break;
534         }
535     }
536 
537     const std::pair<float, requirement_id> butchery_requirements =
538         corpse.harvest->get_butchery_requirements().get_fastest_requirements( u.crafting_inventory(),
539                 corpse.size, action );
540 
541     // Requirements for the various types
542     const requirement_id butchery_requirement = butchery_requirements.second;
543 
544     if( !butchery_requirement->can_make_with_inventory(
545             u.crafting_inventory( u.pos(), PICKUP_RANGE ), is_crafting_component ) ) {
546         std::string popup_output = _( "You can't butcher this; you are missing some tools.\n" );
547 
548         for( const std::string &str : butchery_requirement->get_folded_components_list(
549                  45, c_light_gray, u.crafting_inventory( u.pos(), PICKUP_RANGE ), is_crafting_component ) ) {
550             popup_output += str + '\n';
551         }
552         for( const std::string &str : butchery_requirement->get_folded_tools_list(
553                  45, c_light_gray, u.crafting_inventory( u.pos(), PICKUP_RANGE ) ) ) {
554             popup_output += str + '\n';
555         }
556 
557         act.set_to_null();
558         popup( popup_output );
559         return;
560     }
561 
562     if( action == butcher_type::BLEED && ( corpse_item.has_flag( flag_BLED ) ||
563                                            corpse_item.has_flag( flag_QUARTERED ) || corpse_item.has_flag( flag_FIELD_DRESS_FAILED ) ||
564                                            corpse_item.has_flag( flag_FIELD_DRESS ) ) ) {
565         u.add_msg_if_player( m_info, _( "This corpse hase already been bled." ) );
566         act.targets.pop_back();
567         return;
568     }
569 
570     if( action == butcher_type::DISSECT && ( corpse_item.has_flag( flag_QUARTERED ) ||
571             corpse_item.has_flag( flag_FIELD_DRESS_FAILED ) ) ) {
572         u.add_msg_if_player( m_info,
573                              _( "It would be futile to search for implants inside this badly damaged corpse." ) );
574         act.targets.pop_back();
575         return;
576     }
577 
578     if( action == butcher_type::FIELD_DRESS && ( corpse_item.has_flag( flag_FIELD_DRESS ) ||
579             corpse_item.has_flag( flag_FIELD_DRESS_FAILED ) ) ) {
580         u.add_msg_if_player( m_info, _( "This corpse is already field dressed." ) );
581         act.targets.pop_back();
582         return;
583     }
584 
585     if( action == butcher_type::SKIN && corpse_item.has_flag( flag_SKINNED ) ) {
586         u.add_msg_if_player( m_info, _( "This corpse is already skinned." ) );
587         act.targets.pop_back();
588         return;
589     }
590 
591     if( action == butcher_type::QUARTER ) {
592         if( corpse.size == creature_size::tiny ) {
593             u.add_msg_if_player( m_bad, _( "This corpse is too small to quarter without damaging." ),
594                                  corpse.nname() );
595             act.targets.pop_back();
596             return;
597         }
598         if( corpse_item.has_flag( flag_QUARTERED ) ) {
599             u.add_msg_if_player( m_bad, _( "This is already quartered." ), corpse.nname() );
600             act.targets.pop_back();
601             return;
602         }
603         if( !( corpse_item.has_flag( flag_FIELD_DRESS ) ||
604                corpse_item.has_flag( flag_FIELD_DRESS_FAILED ) ) &&
605             corpse_item.get_mtype()->harvest->has_entry_type( "offal" ) ) {
606             u.add_msg_if_player( m_bad, _( "You need to perform field dressing before quartering." ),
607                                  corpse.nname() );
608             act.targets.pop_back();
609             return;
610         }
611     }
612 
613     // applies to all butchery actions
614     const bool is_human = corpse.id == mtype_id::NULL_ID() || ( corpse.in_species( species_HUMAN ) &&
615                           !corpse.in_species( species_ZOMBIE ) );
616     if( is_human && !( u.has_trait_flag( json_flag_CANNIBAL ) ||
617                        u.has_trait_flag( json_flag_PSYCHOPATH ) ||
618                        u.has_trait_flag( json_flag_SAPIOVORE ) ) ) {
619 
620         if( u.is_player() ) {
621             if( query_yn( _( "Would you dare desecrate the mortal remains of a fellow human being?" ) ) ) {
622                 switch( rng( 1, 3 ) ) {
623                     case 1:
624                         u.add_msg_if_player( m_bad, _( "You clench your teeth at the prospect of this gruesome job." ) );
625                         break;
626                     case 2:
627                         u.add_msg_if_player( m_bad, _( "This will haunt you in your dreams." ) );
628                         break;
629                     case 3:
630                         u.add_msg_if_player( m_bad,
631                                              _( "You try to look away, but this gruesome image will stay on your mind for some time." ) );
632                         break;
633                 }
634                 get_player_character().add_morale( MORALE_BUTCHER, -50, 0, 2_days, 3_hours );
635             } else {
636                 u.add_msg_if_player( m_good, _( "It needs a coffin, not a knife." ) );
637                 act.targets.pop_back();
638                 return;
639             }
640         } else {
641             u.add_morale( MORALE_BUTCHER, -50, 0, 2_days, 3_hours );
642         }
643     }
644 
645     act.moves_left = butcher_time_to_cut( u, corpse_item, action ) * butchery_requirements.first;
646 
647     // We have a valid target, so preform the full finish function
648     // instead of just selecting the next valid target
649     act.index = false;
650 }
651 
butcher_time_to_cut(const player & u,const item & corpse_item,const butcher_type action)652 int butcher_time_to_cut( const player &u, const item &corpse_item, const butcher_type action )
653 {
654     const mtype &corpse = *corpse_item.get_mtype();
655     const int factor = u.max_quality( action == butcher_type::DISSECT ? qual_CUT_FINE : qual_BUTCHER );
656 
657     int time_to_cut;
658     switch( corpse.size ) {
659         // Time (roughly) in turns to cut up the corpse
660         case creature_size::tiny:
661             time_to_cut = 150;
662             break;
663         case creature_size::small:
664             time_to_cut = 300;
665             break;
666         case creature_size::medium:
667             time_to_cut = 450;
668             break;
669         case creature_size::large:
670             time_to_cut = 600;
671             break;
672         case creature_size::huge:
673             time_to_cut = 1800;
674             break;
675         default:
676             debugmsg( "ERROR: Invalid creature_size on %s", corpse.nname() );
677             time_to_cut = 450; // default to medium
678             break;
679     }
680 
681     // At factor 0, base 100 time_to_cut remains 100. At factor 50, it's 50 , at factor 75 it's 25
682     time_to_cut *= std::max( 25, 100 - factor );
683 
684     switch( action ) {
685         case butcher_type::QUICK:
686         case butcher_type::BLEED:
687             break;
688         case butcher_type::FULL:
689             if( !corpse_item.has_flag( flag_FIELD_DRESS ) || corpse_item.has_flag( flag_FIELD_DRESS_FAILED ) ) {
690                 time_to_cut *= 6;
691             } else {
692                 time_to_cut *= 4;
693             }
694             break;
695         case butcher_type::FIELD_DRESS:
696         case butcher_type::SKIN:
697             time_to_cut *= 2;
698             break;
699         case butcher_type::QUARTER:
700             time_to_cut /= 4;
701             if( time_to_cut < 1200 ) {
702                 time_to_cut = 1200;
703             }
704             break;
705         case butcher_type::DISMEMBER:
706             time_to_cut /= 10;
707             if( time_to_cut < 600 ) {
708                 time_to_cut = 600;
709             }
710             break;
711         case butcher_type::DISSECT:
712             time_to_cut *= 6;
713             break;
714         case butcher_type::NUM_TYPES:
715             debugmsg( "ERROR: Invalid butcher_type" );
716             break;
717     }
718 
719     if( corpse_item.has_flag( flag_QUARTERED ) ) {
720         time_to_cut /= 4;
721     }
722     time_to_cut *= ( 1.0f - ( get_player_character().get_num_crafting_helpers( 3 ) / 10.0f ) );
723     return time_to_cut;
724 }
725 
726 // this function modifies the input weight by its damage level, depending on the bodypart
corpse_damage_effect(int weight,const std::string & entry_type,int damage_level)727 static int corpse_damage_effect( int weight, const std::string &entry_type, int damage_level )
728 {
729     const float slight_damage = 0.9f;
730     const float damage = 0.75f;
731     const float high_damage = 0.5f;
732     const int destroyed = 0;
733 
734     switch( damage_level ) {
735         case 2:
736             // "damaged"
737             if( entry_type == "offal" ) {
738                 return std::round( weight * damage );
739             }
740             if( entry_type == "skin" ) {
741                 return std::round( weight * damage );
742             }
743             if( entry_type == "flesh" ) {
744                 return std::round( weight * slight_damage );
745             }
746             break;
747         case 3:
748             // "mangled"
749             if( entry_type == "offal" ) {
750                 return destroyed;
751             }
752             if( entry_type == "skin" ) {
753                 return std::round( weight * high_damage );
754             }
755             if( entry_type == "bone" ) {
756                 return std::round( weight * slight_damage );
757             }
758             if( entry_type == "flesh" ) {
759                 return std::round( weight * damage );
760             }
761             break;
762         case 4:
763             // "pulped"
764             if( entry_type == "offal" ) {
765                 return destroyed;
766             }
767             if( entry_type == "skin" ) {
768                 return destroyed;
769             }
770             if( entry_type == "bone" ) {
771                 return std::round( weight * damage );
772             }
773             if( entry_type == "flesh" ) {
774                 return std::round( weight * high_damage );
775             }
776             break;
777         default:
778             // "bruised" modifier is almost impossible to avoid; also includes no modifier (zero damage)
779             break;
780     }
781     return weight;
782 }
783 
butchery_drops_harvest(item * corpse_item,const mtype & mt,player & p,const std::function<int ()> & roll_butchery,butcher_type action,const std::function<double ()> & roll_drops)784 static void butchery_drops_harvest( item *corpse_item, const mtype &mt, player &p,
785                                     const std::function<int()> &roll_butchery, butcher_type action,
786                                     const std::function<double()> &roll_drops )
787 {
788     p.add_msg_if_player( m_neutral, mt.harvest->message() );
789     int monster_weight = to_gram( mt.weight );
790     monster_weight += std::round( monster_weight * rng_float( -0.1, 0.1 ) );
791     if( corpse_item->has_flag( flag_QUARTERED ) ) {
792         monster_weight *= 0.95;
793     }
794     if( corpse_item->has_flag( flag_GIBBED ) ) {
795         monster_weight = std::round( 0.85 * monster_weight );
796         if( action != butcher_type::FIELD_DRESS ) {
797             p.add_msg_if_player( m_bad,
798                                  _( "You salvage what you can from the corpse, but it is badly damaged." ) );
799         }
800     }
801     if( corpse_item->has_flag( flag_SKINNED ) ) {
802         monster_weight = std::round( 0.85 * monster_weight );
803     }
804     int monster_weight_remaining = monster_weight;
805     int practice = 4 + roll_butchery();
806 
807     if( mt.harvest.is_null() ) {
808         debugmsg( "ERROR: %s has no harvest entry.", mt.id.c_str() );
809         return;
810     }
811 
812     map &here = get_map();
813     for( const harvest_entry &entry : *mt.harvest ) {
814         const int butchery = roll_butchery();
815         const float min_num = entry.base_num.first + butchery * entry.scale_num.first;
816         const float max_num = entry.base_num.second + butchery * entry.scale_num.second;
817         int roll = 0;
818         // mass_ratio will override the use of base_num, scale_num, and max
819         if( entry.mass_ratio != 0.00f ) {
820             roll = static_cast<int>( std::round( entry.mass_ratio * monster_weight ) );
821             roll = corpse_damage_effect( roll, entry.type, corpse_item->damage_level() );
822         } else if( entry.type != "bionic" && entry.type != "bionic_group" ) {
823             roll = std::min<int>( entry.max, std::round( rng_float( min_num, max_num ) ) );
824             // will not give less than min_num defined in the JSON
825             roll = std::max<int>( corpse_damage_effect( roll, entry.type, corpse_item->damage_level() ),
826                                   entry.base_num.first );
827         }
828         itype_id drop_id = itype_id::NULL_ID();
829         const itype *drop = nullptr;
830         if( entry.type != "bionic_group" ) {
831             drop_id = itype_id( entry.drop );
832             drop = item::find_type( drop_id );
833         }
834 
835         // BIONIC handling - no code for DISSECT to let the bionic drop fall through
836         if( entry.type == "bionic" || entry.type == "bionic_group" ) {
837             if( action == butcher_type::FIELD_DRESS ) {
838                 if( drop != nullptr && !drop->bionic ) {
839                     if( one_in( 3 ) ) {
840                         p.add_msg_if_player( m_bad,
841                                              _( "You notice some strange organs, perhaps harvestable via careful dissection." ) );
842                     }
843                     continue;
844                 }
845                 p.add_msg_if_player( m_bad,
846                                      _( "You suspect there might be bionics implanted in this corpse, that careful dissection might reveal." ) );
847                 continue;
848             }
849             if( action == butcher_type::QUICK ||
850                 action == butcher_type::FULL ||
851                 action == butcher_type::DISMEMBER ) {
852                 if( drop != nullptr && !drop->bionic ) {
853                     if( one_in( 3 ) ) {
854                         p.add_msg_if_player( m_bad,
855                                              _( "Your butchering tool destroys a strange organ.  Perhaps a more surgical approach would allow harvesting it." ) );
856                     }
857                     continue;
858                 }
859                 switch( rng( 1, 3 ) ) {
860                     case 1:
861                         p.add_msg_if_player( m_bad,
862                                              _( "Your butchering tool encounters something implanted in this corpse, but your rough cuts destroy it." ) );
863                         break;
864                     case 2:
865                         p.add_msg_if_player( m_bad,
866                                              _( "You find traces of implants in the body, but you care only for the flesh." ) );
867                         break;
868                     case 3:
869                         p.add_msg_if_player( m_bad,
870                                              _( "You found some bionics in the body, but harvesting them would require more surgical approach." ) );
871                         break;
872                 }
873                 continue;
874             }
875         }
876         if( action == butcher_type::DISSECT ) {
877             int roll = roll_butchery() - corpse_item->damage_level();
878             roll = roll < 0 ? 0 : roll;
879             roll = std::min( entry.max, roll );
880             add_msg_debug( _( "Roll penalty for corpse damage = %s" ), 0 - corpse_item->damage_level() );
881             if( entry.type == "bionic" ) {
882                 butcher_cbm_item( drop_id, p.pos(), calendar::turn, roll, entry.flags, entry.faults );
883             } else if( entry.type == "bionic_group" ) {
884                 butcher_cbm_group( item_group_id( entry.drop ), p.pos(), calendar::turn, roll,
885                                    entry.flags, entry.faults );
886             }
887             continue;
888         }
889 
890         // Check if monster was gibbed, and handle accordingly
891         if( corpse_item->has_flag( flag_GIBBED ) && ( entry.type == "flesh" || entry.type == "bone" ) ) {
892             roll /= 2;
893         }
894 
895         if( corpse_item->has_flag( flag_SKINNED ) && entry.type == "skin" ) {
896             roll = 0;
897         }
898 
899         // QUICK BUTCHERY
900         if( action == butcher_type::QUICK ) {
901             if( entry.type == "flesh" ) {
902                 roll = roll / 4;
903             } else if( entry.type == "bone" ) {
904                 roll /= 2;
905             } else if( corpse_item->get_mtype()->size >= creature_size::medium && ( entry.type == "skin" ) ) {
906                 roll /= 2;
907             } else if( entry.type == "offal" ) {
908                 roll /= 5;
909             } else {
910                 continue;
911             }
912         }
913         // RIP AND TEAR
914         if( action == butcher_type::DISMEMBER ) {
915             if( entry.type == "flesh" ) {
916                 roll /= 6;
917             } else {
918                 continue;
919             }
920         }
921         // field dressing ignores skin, flesh, and blood
922         if( action == butcher_type::FIELD_DRESS ) {
923             if( entry.type == "bone" ) {
924                 roll = rng( 0, roll / 2 );
925             }
926             if( entry.type == "flesh" ) {
927                 continue;
928             }
929             if( entry.type == "skin" ) {
930                 continue;
931             }
932         }
933 
934         // you only get the blood from bleeding
935         if( action == butcher_type::BLEED ) {
936             if( entry.type != "blood" ) {
937                 continue;
938             }
939         }
940 
941         // you only get the skin from skinning
942         if( action == butcher_type::SKIN ) {
943             if( entry.type != "skin" ) {
944                 continue;
945             }
946             if( corpse_item->has_flag( flag_FIELD_DRESS_FAILED ) ) {
947                 roll = rng( 0, roll );
948             }
949         }
950 
951         // field dressing removed innards and bones from meatless limbs
952         if( ( action == butcher_type::FULL || action == butcher_type::QUICK ) &&
953             corpse_item->has_flag( flag_FIELD_DRESS ) ) {
954             if( entry.type == "offal" ) {
955                 continue;
956             }
957             if( entry.type == "bone" ) {
958                 roll = ( roll / 2 ) + rng( roll / 2, roll );
959             }
960         }
961         // unskillfull field dressing may damage the skin, meat, and other parts
962         if( ( action == butcher_type::FULL || action == butcher_type::QUICK ) &&
963             corpse_item->has_flag( flag_FIELD_DRESS_FAILED ) ) {
964             if( entry.type == "offal" ) {
965                 continue;
966             }
967             if( entry.type == "bone" ) {
968                 roll = ( roll / 2 ) + rng( roll / 2, roll );
969             }
970             if( entry.type == "flesh" || entry.type == "skin" ) {
971                 roll = rng( 0, roll );
972             }
973         }
974         // quartering ruins skin
975         if( corpse_item->has_flag( flag_QUARTERED ) ) {
976             if( entry.type == "skin" ) {
977                 //not continue to show fail effect
978                 roll = 0;
979             } else {
980                 roll /= 4;
981             }
982         }
983 
984         if( entry.type != "bionic_group" ) {
985             // divide total dropped weight by drop's weight to get amount
986             if( entry.mass_ratio != 0.00f ) {
987                 // apply skill before converting to items, but only if mass_ratio is defined
988                 roll *= roll_drops();
989                 monster_weight_remaining -= roll;
990                 roll = std::ceil( static_cast<double>( roll ) /
991                                   to_gram( drop->weight ) );
992             } else {
993                 monster_weight_remaining -= roll * to_gram( drop->weight );
994             }
995 
996             if( roll <= 0 ) {
997                 p.add_msg_if_player( m_bad, _( "You fail to harvest: %s" ), drop->nname( 1 ) );
998                 continue;
999             }
1000             if( drop->phase == phase_id::LIQUID ) {
1001                 item obj( drop, calendar::turn, roll );
1002                 if( obj.has_temperature() ) {
1003                     obj.set_item_temperature( 0.00001 * corpse_item->temperature );
1004                     if( obj.goes_bad() ) {
1005                         obj.set_rot( corpse_item->get_rot() );
1006                     }
1007                 }
1008                 for( const flag_id &flg : entry.flags ) {
1009                     obj.set_flag( flg );
1010                 }
1011                 for( const fault_id &flt : entry.faults ) {
1012                     obj.faults.emplace( flt );
1013                 }
1014 
1015                 // TODO: smarter NPC liquid handling
1016                 // If we're not bleeding the animal we don't care about the blood being wasted
1017                 if( p.is_npc() || action != butcher_type::BLEED ) {
1018                     drop_on_map( p, item_drop_reason::deliberate, { obj }, p.pos() );
1019                 } else {
1020                     liquid_handler::handle_all_liquid( obj, 1 );
1021                 }
1022             } else if( drop->count_by_charges() ) {
1023                 item obj( drop, calendar::turn, roll );
1024                 if( obj.has_temperature() ) {
1025                     obj.set_item_temperature( 0.00001 * corpse_item->temperature );
1026                     if( obj.goes_bad() ) {
1027                         obj.set_rot( corpse_item->get_rot() );
1028                     }
1029                 }
1030                 for( const flag_id &flg : entry.flags ) {
1031                     obj.set_flag( flg );
1032                 }
1033                 for( const fault_id &flt : entry.faults ) {
1034                     obj.faults.emplace( flt );
1035                 }
1036                 if( !p.backlog.empty() && p.backlog.front().id() == ACT_MULTIPLE_BUTCHER ) {
1037                     obj.set_var( "activity_var", p.name );
1038                 }
1039                 here.add_item_or_charges( p.pos(), obj );
1040             } else {
1041                 item obj( drop, calendar::turn );
1042                 obj.set_mtype( &mt );
1043                 if( obj.has_temperature() ) {
1044                     obj.set_item_temperature( 0.00001 * corpse_item->temperature );
1045                     if( obj.goes_bad() ) {
1046                         obj.set_rot( corpse_item->get_rot() );
1047                     }
1048                 }
1049                 for( const flag_id &flg : entry.flags ) {
1050                     obj.set_flag( flg );
1051                 }
1052                 for( const fault_id &flt : entry.faults ) {
1053                     obj.faults.emplace( flt );
1054                 }
1055                 if( !p.backlog.empty() && p.backlog.front().id() == ACT_MULTIPLE_BUTCHER ) {
1056                     obj.set_var( "activity_var", p.name );
1057                 }
1058                 for( int i = 0; i != roll; ++i ) {
1059                     here.add_item_or_charges( p.pos(), obj );
1060                 }
1061             }
1062             p.add_msg_if_player( m_good, _( "You harvest: %s" ), drop->nname( roll ) );
1063         }
1064         practice++;
1065     }
1066     // 20% of the original corpse weight is not an item, but liquid gore
1067     monster_weight_remaining -= monster_weight / 5;
1068     // add the remaining unusable weight as rotting garbage
1069     if( monster_weight_remaining > 0 && action != butcher_type::BLEED ) {
1070         if( action == butcher_type::FIELD_DRESS ) {
1071             // 25% of the corpse weight is what's removed during field dressing
1072             monster_weight_remaining -= monster_weight * 3 / 4;
1073         } else if( action == butcher_type::SKIN ) {
1074             monster_weight_remaining -= monster_weight * 0.85;
1075         } else {
1076             // a carcass is 75% of the weight of the unmodified creature's weight
1077             if( ( corpse_item->has_flag( flag_FIELD_DRESS ) ||
1078                   corpse_item->has_flag( flag_FIELD_DRESS_FAILED ) ) &&
1079                 !corpse_item->has_flag( flag_QUARTERED ) ) {
1080                 monster_weight_remaining -= monster_weight / 4;
1081             } else if( corpse_item->has_flag( flag_QUARTERED ) ) {
1082                 monster_weight_remaining -= ( monster_weight - ( monster_weight * 3 / 4 / 4 ) );
1083             }
1084             if( corpse_item->has_flag( flag_SKINNED ) ) {
1085                 monster_weight_remaining -= monster_weight * 0.15;
1086             }
1087         }
1088         const itype_id &leftover_id = mt.harvest->leftovers;
1089         const int item_charges = monster_weight_remaining / to_gram( (
1090                                      item::find_type( leftover_id ) )->weight );
1091         if( item_charges > 0 ) {
1092             item ruined_parts( leftover_id, calendar::turn, item_charges );
1093             ruined_parts.set_mtype( &mt );
1094             ruined_parts.set_item_temperature( 0.00001 * corpse_item->temperature );
1095             ruined_parts.set_rot( corpse_item->get_rot() );
1096             if( !p.backlog.empty() && p.backlog.front().id() == ACT_MULTIPLE_BUTCHER ) {
1097                 ruined_parts.set_var( "activity_var", p.name );
1098             }
1099             here.add_item_or_charges( p.pos(), ruined_parts );
1100         }
1101     }
1102 
1103     if( action == butcher_type::DISSECT ) {
1104         p.practice( skill_firstaid, std::max( 0, practice ), std::max( mt.size - creature_size::medium,
1105                     0 ) + 4 );
1106     } else {
1107         p.practice( skill_survival, std::max( 0, practice ), std::max( mt.size - creature_size::medium,
1108                     0 ) + 4 );
1109     }
1110 }
1111 
butchery_quarter(item * corpse_item,const player & p)1112 static void butchery_quarter( item *corpse_item, const player &p )
1113 {
1114     corpse_item->set_flag( flag_QUARTERED );
1115     p.add_msg_if_player( m_good,
1116                          _( "You roughly slice the corpse of %s into four parts and set them aside." ),
1117                          corpse_item->get_mtype()->nname() );
1118     map &here = get_map();
1119     // 4 quarters (one exists, add 3, flag does the rest)
1120     for( int i = 1; i <= 3; i++ ) {
1121         here.add_item_or_charges( p.pos(), *corpse_item, true );
1122     }
1123 }
1124 
butcher_finish(player_activity * act,player * p)1125 void activity_handlers::butcher_finish( player_activity *act, player *p )
1126 {
1127     // No targets means we are done
1128     if( act->targets.empty() ) {
1129         act->set_to_null();
1130         resume_for_multi_activities( *p );
1131         return;
1132     }
1133 
1134     item_location target = act->targets.back();
1135 
1136     // Corpses can disappear (rezzing!), so check for that
1137     if( !target || !target->is_corpse() ) {
1138         p->add_msg_if_player( m_info, _( "There's no corpse to butcher!" ) );
1139         act->set_to_null();
1140         return;
1141     }
1142 
1143     butcher_type action = butcher_type::QUICK;
1144     if( act->id() == ACT_BUTCHER ) {
1145         action = butcher_type::QUICK;
1146     } else if( act->id() == ACT_BUTCHER_FULL ) {
1147         action = butcher_type::FULL;
1148     } else if( act->id() == ACT_FIELD_DRESS ) {
1149         action = butcher_type::FIELD_DRESS;
1150     } else if( act->id() == ACT_QUARTER ) {
1151         action = butcher_type::QUARTER;
1152     } else if( act->id() == ACT_DISSECT ) {
1153         action = butcher_type::DISSECT;
1154     } else if( act->id() == ACT_BLEED ) {
1155         action = butcher_type::BLEED;
1156     } else if( act->id() == ACT_SKIN ) {
1157         action = butcher_type::SKIN;
1158     } else if( act->id() == ACT_DISMEMBER ) {
1159         action = butcher_type::DISMEMBER;
1160     }
1161 
1162     // index is a bool that determines if we are ready to start the next target
1163     if( act->index ) {
1164         set_up_butchery( *act, *p, action );
1165         return;
1166     }
1167 
1168     item &corpse_item = *target;
1169     const mtype *corpse = corpse_item.get_mtype();
1170     const field_type_id type_blood = corpse->bloodType();
1171     const field_type_id type_gib = corpse->gibType();
1172 
1173     if( action == butcher_type::QUARTER ) {
1174         butchery_quarter( &corpse_item, *p );
1175         act->index = true;
1176         return;
1177     }
1178 
1179     int skill_level = p->get_skill_level( skill_survival );
1180     int factor = p->max_quality( action == butcher_type::DISSECT ? qual_CUT_FINE :
1181                                  qual_BUTCHER );
1182 
1183     // DISSECT has special case factor calculation and results.
1184     if( action == butcher_type::DISSECT ) {
1185         skill_level = p->get_skill_level( skill_firstaid );
1186         skill_level += p->max_quality( qual_CUT_FINE );
1187         skill_level += p->get_skill_level( skill_electronics ) / 2;
1188         add_msg_debug( _( "Skill: %s" ), skill_level );
1189     }
1190 
1191     const auto roll_butchery = [&]() {
1192         double skill_shift = 0.0;
1193         ///\EFFECT_SURVIVAL randomly increases butcher rolls
1194         skill_shift += rng_float( 0, skill_level - 3 );
1195         ///\EFFECT_DEX >8 randomly increases butcher rolls, slightly, <8 decreases
1196         skill_shift += rng_float( 0, p->dex_cur - 8 ) / 4.0;
1197 
1198         if( factor < 0 ) {
1199             skill_shift -= rng_float( 0, -factor / 5.0 );
1200         }
1201 
1202         return static_cast<int>( std::round( skill_shift ) );
1203     };
1204 
1205     map &here = get_map();
1206     if( action == butcher_type::DISMEMBER ) {
1207         here.add_splatter( type_gib, p->pos(), rng( corpse->size + 2, ( corpse->size + 1 ) * 2 ) );
1208     }
1209 
1210     //all BUTCHERY types - FATAL FAILURE
1211     if( action != butcher_type::DISSECT && roll_butchery() <= ( -15 ) && one_in( 2 ) ) {
1212         switch( rng( 1, 3 ) ) {
1213             case 1:
1214                 p->add_msg_if_player( m_warning,
1215                                       _( "You hack up the corpse so unskillfully, that there is nothing left to salvage from this bloody mess." ) );
1216                 break;
1217             case 2:
1218                 p->add_msg_if_player( m_warning,
1219                                       _( "You wanted to cut the corpse, but instead you hacked the meat, spilled the guts all over it, and made a bloody mess." ) );
1220                 break;
1221             case 3:
1222                 p->add_msg_if_player( m_warning,
1223                                       _( "You made so many mistakes during the process that you doubt even vultures will be interested in what's left of it." ) );
1224                 break;
1225         }
1226 
1227         // Remove the target from the map
1228         target.remove_item();
1229         act->targets.pop_back();
1230 
1231         here.add_splatter( type_gib, p->pos(), rng( corpse->size + 2, ( corpse->size + 1 ) * 2 ) );
1232         here.add_splatter( type_blood, p->pos(), rng( corpse->size + 2, ( corpse->size + 1 ) * 2 ) );
1233         for( int i = 1; i <= corpse->size; i++ ) {
1234             here.add_splatter_trail( type_gib, p->pos(), random_entry( here.points_in_radius( p->pos(),
1235                                      corpse->size + 1 ) ) );
1236             here.add_splatter_trail( type_blood, p->pos(), random_entry( here.points_in_radius( p->pos(),
1237                                      corpse->size + 1 ) ) );
1238         }
1239 
1240         // Ready to move on to the next item, if there is one
1241         act->index = true;
1242         return;
1243     }
1244     // function just for drop yields
1245     const auto roll_drops = [&]() {
1246         factor = std::max( factor, -50 );
1247         return 0.5 * skill_level / 10 + 0.3 * ( factor + 50 ) / 100 + 0.2 * p->dex_cur / 20;
1248     };
1249     // all action types - yields
1250     butchery_drops_harvest( &corpse_item, *corpse, *p, roll_butchery, action, roll_drops );
1251     // after this point, if there was a liquid handling from the harvest,
1252     // and the liquid handling was interrupted, then the activity was canceled,
1253     // therefore operations on this activity's targets and values may be invalidated.
1254     // reveal hidden items / hidden content
1255     if( action != butcher_type::FIELD_DRESS && action != butcher_type::SKIN &&
1256         action != butcher_type::BLEED ) {
1257         for( item *content : corpse_item.contents.all_items_top() ) {
1258             if( ( roll_butchery() + 10 ) * 5 > rng( 0, 100 ) ) {
1259                 //~ %1$s - item name, %2$s - monster name
1260                 p->add_msg_if_player( m_good, _( "You discover a %1$s in the %2$s!" ), content->tname(),
1261                                       corpse->nname() );
1262                 here.add_item_or_charges( p->pos(), *content );
1263             } else if( content->is_bionic() ) {
1264                 here.spawn_item( p->pos(), itype_burnt_out_bionic, 1, 0, calendar::turn );
1265             }
1266         }
1267     }
1268 
1269     //end messages and effects
1270     switch( action ) {
1271         case butcher_type::QUARTER:
1272             break;
1273         case butcher_type::QUICK:
1274             p->add_msg_if_player( m_good,
1275                                   _( "You apply few quick cuts to the %s and leave what's left of it for scavengers." ),
1276                                   corpse_item.tname() );
1277 
1278             // Remove the target from the map
1279             target.remove_item();
1280             if( !act->targets.empty() ) {
1281                 act->targets.pop_back();
1282             }
1283             break;
1284         case butcher_type::FULL:
1285             p->add_msg_if_player( m_good, _( "You finish butchering the %s." ), corpse_item.tname() );
1286 
1287             // Remove the target from the map
1288             target.remove_item();
1289             if( !act->targets.empty() ) {
1290                 act->targets.pop_back();
1291             }
1292             break;
1293         case butcher_type::FIELD_DRESS:
1294             // partial failure
1295             if( roll_butchery() < 0 ) {
1296                 switch( rng( 1, 3 ) ) {
1297                     case 1:
1298                         p->add_msg_if_player( m_warning,
1299                                               _( "You unskillfully hack up the corpse and chop off some excess body parts.  You're left wondering how you did so poorly." ) );
1300                         break;
1301                     case 2:
1302                         p->add_msg_if_player( m_warning,
1303                                               _( "Your unskilled hands slip and damage the corpse.  You still hope it's not a total waste though." ) );
1304                         break;
1305                     case 3:
1306                         p->add_msg_if_player( m_warning,
1307                                               _( "You did something wrong and hacked the corpse badly.  Maybe it's still recoverable." ) );
1308                         break;
1309                 }
1310                 corpse_item.set_flag( flag_FIELD_DRESS_FAILED );
1311 
1312                 here.add_splatter( type_gib, p->pos(), rng( corpse->size + 2, ( corpse->size + 1 ) * 2 ) );
1313                 here.add_splatter( type_blood, p->pos(), rng( corpse->size + 2, ( corpse->size + 1 ) * 2 ) );
1314                 for( int i = 1; i <= corpse->size; i++ ) {
1315                     here.add_splatter_trail( type_gib, p->pos(), random_entry( here.points_in_radius( p->pos(),
1316                                              corpse->size + 1 ) ) );
1317                     here.add_splatter_trail( type_blood, p->pos(), random_entry( here.points_in_radius( p->pos(),
1318                                              corpse->size + 1 ) ) );
1319                 }
1320 
1321             } else {
1322                 // success
1323                 switch( rng( 1, 3 ) ) {
1324                     case 1:
1325                         p->add_msg_if_player( m_good, _( "You field dress the %s." ), corpse->nname() );
1326                         break;
1327                     case 2:
1328                         p->add_msg_if_player( m_good,
1329                                               _( "You slice the corpse's belly and remove intestines and organs, until you're confident that it will not rot from inside." ) );
1330                         break;
1331                     case 3:
1332                         p->add_msg_if_player( m_good,
1333                                               _( "You remove guts and excess parts, preparing the corpse for later use." ) );
1334                         break;
1335                 }
1336                 corpse_item.set_flag( flag_FIELD_DRESS );
1337 
1338                 here.add_splatter( type_gib, p->pos(), rng( corpse->size + 2, ( corpse->size + 1 ) * 2 ) );
1339                 here.add_splatter( type_blood, p->pos(), rng( corpse->size + 2, ( corpse->size + 1 ) * 2 ) );
1340                 for( int i = 1; i <= corpse->size; i++ ) {
1341                     here.add_splatter_trail( type_gib, p->pos(), random_entry( here.points_in_radius( p->pos(),
1342                                              corpse->size + 1 ) ) );
1343                     here.add_splatter_trail( type_blood, p->pos(), random_entry( here.points_in_radius( p->pos(),
1344                                              corpse->size + 1 ) ) );
1345                 }
1346 
1347             }
1348             if( !act->targets.empty() ) {
1349                 act->targets.pop_back();
1350             }
1351             break;
1352         case butcher_type::SKIN:
1353             switch( rng( 1, 4 ) ) {
1354                 case 1:
1355                     p->add_msg_if_player( m_good, _( "You skin the %s." ), corpse->nname() );
1356                     break;
1357                 case 2:
1358                     p->add_msg_if_player( m_good, _( "You carefully remove the hide from the %s" ),
1359                                           corpse->nname() );
1360                     break;
1361                 case 3:
1362                     p->add_msg_if_player( m_good,
1363                                           _( "The %s is challenging to skin, but you get a good hide from it." ),
1364                                           corpse->nname() );
1365                     break;
1366                 case 4:
1367                     p->add_msg_if_player( m_good, _( "With a few deft slices you take the skin from the %s" ),
1368                                           corpse->nname() );
1369                     break;
1370             }
1371             corpse_item.set_flag( flag_SKINNED );
1372             if( !act->targets.empty() ) {
1373                 act->targets.pop_back();
1374             }
1375             break;
1376         case butcher_type::BLEED:
1377             p->add_msg_if_player( m_good, _( "You bleed the %s." ), corpse->nname() );
1378             corpse_item.set_flag( flag_BLED );
1379             if( !act->targets.empty() ) {
1380                 act->targets.pop_back();
1381             }
1382             break;
1383         case butcher_type::DISMEMBER:
1384             switch( rng( 1, 3 ) ) {
1385                 case 1:
1386                     p->add_msg_if_player( m_good, _( "You hack the %s apart." ), corpse_item.tname() );
1387                     break;
1388                 case 2:
1389                     p->add_msg_if_player( m_good, _( "You lop the limbs off the %s." ), corpse_item.tname() );
1390                     break;
1391                 case 3:
1392                     p->add_msg_if_player( m_good, _( "You cleave the %s into pieces." ), corpse_item.tname() );
1393             }
1394 
1395             // Remove the target from the map
1396             target.remove_item();
1397             if( !act->targets.empty() ) {
1398                 act->targets.pop_back();
1399             }
1400             break;
1401         case butcher_type::DISSECT:
1402             p->add_msg_if_player( m_good, _( "You finish dissecting the %s." ), corpse_item.tname() );
1403 
1404             // Remove the target from the map
1405             target.remove_item();
1406             if( !act->targets.empty() ) {
1407                 act->targets.pop_back();
1408             }
1409             break;
1410         case butcher_type::NUM_TYPES:
1411             debugmsg( "ERROR: Invalid butcher_type" );
1412             break;
1413     }
1414 
1415     // Ready to move on to the next item, if there is one (for example if multibutchering)
1416     act->index = true;
1417     // if its mutli-tile butchering,then restart the backlog.
1418     resume_for_multi_activities( *p );
1419 }
1420 
shear_finish(player_activity * act,player * p)1421 void activity_handlers::shear_finish( player_activity *act, player *p )
1422 {
1423     if( act->coords.empty() ) {
1424         debugmsg( "shearing activity with no position of monster stored" );
1425         return;
1426     }
1427     item_location &loc = act->targets[ 0 ];
1428     item *shears = loc.get_item();
1429     if( shears == nullptr ) {
1430         debugmsg( "shearing item location lost" );
1431         return;
1432     }
1433     map &here = get_map();
1434     const tripoint source_pos = here.getlocal( act->coords.at( 0 ) );
1435     monster *source_mon = g->critter_at<monster>( source_pos );
1436     if( source_mon == nullptr ) {
1437         debugmsg( "could not find source creature for shearing" );
1438         return;
1439     }
1440     // 22 wool staples corresponds to an average wool-producing sheep yield of 10 lbs or so
1441     for( int i = 0; i != 22; ++i ) {
1442         item wool_staple( itype_wool_staple, calendar::turn );
1443         here.add_item_or_charges( p->pos(), wool_staple );
1444     }
1445     source_mon->add_effect( effect_sheared, calendar::season_length() );
1446     if( !act->str_values.empty() && act->str_values[0] == "temp_tie" ) {
1447         source_mon->remove_effect( effect_tied );
1448     }
1449     act->set_to_null();
1450     if( shears->type->can_have_charges() ) {
1451         p->consume_charges( *shears, shears->type->charges_to_use() );
1452     }
1453 }
1454 
fill_liquid_do_turn(player_activity * act,player * p)1455 void activity_handlers::fill_liquid_do_turn( player_activity *act, player *p )
1456 {
1457     player_activity &act_ref = *act;
1458     try {
1459         // 1. Gather the source item.
1460         vehicle *source_veh = nullptr;
1461         const tripoint source_pos = act_ref.coords.at( 0 );
1462         map &here = get_map();
1463         map_stack source_stack = here.i_at( source_pos );
1464         map_stack::iterator on_ground;
1465         monster *source_mon = nullptr;
1466         item liquid;
1467         const liquid_source_type source_type = static_cast<liquid_source_type>( act_ref.values.at( 0 ) );
1468         int part_num = -1;
1469         int veh_charges = 0;
1470         switch( source_type ) {
1471             case liquid_source_type::VEHICLE:
1472                 source_veh = veh_pointer_or_null( here.veh_at( source_pos ) );
1473                 if( source_veh == nullptr ) {
1474                     throw std::runtime_error( "could not find source vehicle for liquid transfer" );
1475                 }
1476                 deserialize( liquid, act_ref.str_values.at( 0 ) );
1477                 part_num = static_cast<int>( act_ref.values.at( 1 ) );
1478                 veh_charges = liquid.charges;
1479                 break;
1480             case liquid_source_type::INFINITE_MAP:
1481                 deserialize( liquid, act_ref.str_values.at( 0 ) );
1482                 liquid.charges = item::INFINITE_CHARGES;
1483                 break;
1484             case liquid_source_type::MAP_ITEM:
1485                 if( static_cast<size_t>( act_ref.values.at( 1 ) ) >= source_stack.size() ) {
1486                     throw std::runtime_error( "could not find source item on ground for liquid transfer" );
1487                 }
1488                 on_ground = source_stack.begin();
1489                 std::advance( on_ground, act_ref.values.at( 1 ) );
1490                 liquid = *on_ground;
1491                 break;
1492             case liquid_source_type::MONSTER:
1493                 Creature *c = g->critter_at( source_pos );
1494                 source_mon = dynamic_cast<monster *>( c );
1495                 if( source_mon == nullptr ) {
1496                     debugmsg( "could not find source creature for liquid transfer" );
1497                     act_ref.set_to_null();
1498                 }
1499                 deserialize( liquid, act_ref.str_values.at( 0 ) );
1500                 liquid.charges = 1;
1501                 break;
1502         }
1503 
1504         static const units::volume volume_per_second = units::from_liter( 4.0F / 6.0F );
1505         const int charges_per_second = std::max( 1, liquid.charges_per_volume( volume_per_second ) );
1506         liquid.charges = std::min( charges_per_second, liquid.charges );
1507         const int original_charges = liquid.charges;
1508         if( liquid.has_temperature() && liquid.specific_energy < 0 ) {
1509             liquid.set_item_temperature( temp_to_kelvin( std::max( get_weather().get_temperature( p->pos() ),
1510                                          temperatures::cold ) ) );
1511         }
1512 
1513         // 2. Transfer charges.
1514         switch( static_cast<liquid_target_type>( act_ref.values.at( 2 ) ) ) {
1515             case liquid_target_type::VEHICLE: {
1516                 const optional_vpart_position vp = here.veh_at( act_ref.coords.at( 1 ) );
1517                 if( act_ref.values.size() > 4 && vp ) {
1518                     const vpart_reference vpr( vp->vehicle(), act_ref.values.at( 4 ) );
1519                     p->pour_into( vpr, liquid );
1520                 } else {
1521                     throw std::runtime_error( "could not find target vehicle for liquid transfer" );
1522                 }
1523                 break;
1524             }
1525             case liquid_target_type::CONTAINER:
1526                 p->pour_into( *act_ref.targets.at( 0 ), liquid );
1527                 break;
1528             case liquid_target_type::MAP:
1529                 if( iexamine::has_keg( act_ref.coords.at( 1 ) ) ) {
1530                     iexamine::pour_into_keg( act_ref.coords.at( 1 ), liquid );
1531                 } else {
1532                     here.add_item_or_charges( act_ref.coords.at( 1 ), liquid );
1533                     p->add_msg_if_player( _( "You pour %1$s onto the ground." ), liquid.tname() );
1534                     liquid.charges = 0;
1535                 }
1536                 break;
1537             case liquid_target_type::MONSTER:
1538                 liquid.charges = 0;
1539                 break;
1540         }
1541 
1542         const int removed_charges = original_charges - liquid.charges;
1543         if( removed_charges == 0 ) {
1544             // Nothing has been transferred, target must be full.
1545             act_ref.set_to_null();
1546             return;
1547         }
1548 
1549         // 3. Remove charges from source.
1550         switch( source_type ) {
1551             case liquid_source_type::VEHICLE:
1552                 if( part_num != -1 ) {
1553                     source_veh->drain( part_num, removed_charges );
1554                     liquid.charges = veh_charges - removed_charges;
1555                     // If there's no liquid left in this tank we're done, otherwise
1556                     // we need to update our liquid serialization to reflect how
1557                     // many charges are actually left for the next time we come
1558                     // around this loop.
1559                     if( !liquid.charges ) {
1560                         act_ref.set_to_null();
1561                     } else {
1562                         if( act_ref.str_values.empty() ) {
1563                             act_ref.str_values.push_back( std::string() );
1564                         }
1565                         act_ref.str_values.at( 0 ) = serialize( liquid );
1566                     }
1567                 } else {
1568                     source_veh->drain( liquid.typeId(), removed_charges );
1569                 }
1570                 if( source_veh->fuel_left( liquid.typeId() ) <= 0 ) {
1571                     act_ref.set_to_null();
1572                 }
1573                 break;
1574             case liquid_source_type::MAP_ITEM:
1575                 on_ground->charges -= removed_charges;
1576                 if( on_ground->charges <= 0 ) {
1577                     source_stack.erase( on_ground );
1578                     if( here.ter( source_pos )->has_examine( iexamine::gaspump ) ) {
1579                         add_msg( _( "With a clang and a shudder, the %s pump goes silent." ),
1580                                  liquid.type_name( 1 ) );
1581                     } else if( here.furn( source_pos )->has_examine( iexamine::fvat_full ) ) {
1582                         add_msg( _( "You squeeze the last drops of %s from the vat." ),
1583                                  liquid.type_name( 1 ) );
1584                         map_stack items_here = here.i_at( source_pos );
1585                         if( items_here.empty() ) {
1586                             here.furn_set( source_pos, f_fvat_empty );
1587                         }
1588                     }
1589                     act_ref.set_to_null();
1590                 }
1591                 break;
1592             case liquid_source_type::INFINITE_MAP:
1593                 // nothing, the liquid source is infinite
1594                 break;
1595             case liquid_source_type::MONSTER:
1596                 // liquid source charges handled in monexamine::milk_source
1597                 if( liquid.charges == 0 ) {
1598                     act_ref.set_to_null();
1599                 }
1600                 break;
1601         }
1602 
1603         if( removed_charges < original_charges ) {
1604             // Transferred less than the available charges -> target must be full
1605             act_ref.set_to_null();
1606         }
1607 
1608     } catch( const std::runtime_error &err ) {
1609         debugmsg( "error in activity data: \"%s\"", err.what() );
1610         act_ref.set_to_null();
1611         return;
1612     }
1613 }
1614 
firstaid_finish(player_activity * act,player * p)1615 void activity_handlers::firstaid_finish( player_activity *act, player *p )
1616 {
1617     static const std::string iuse_name_string( "heal" );
1618 
1619     item &it = *act->targets.front();
1620     item *used_tool = it.get_usable_item( iuse_name_string );
1621     if( used_tool == nullptr ) {
1622         debugmsg( "Lost tool used for healing" );
1623         act->set_to_null();
1624         return;
1625     }
1626 
1627     const use_function *use_fun = used_tool->get_use( iuse_name_string );
1628     const heal_actor *actor = dynamic_cast<const heal_actor *>( use_fun->get_actor_ptr() );
1629     if( actor == nullptr ) {
1630         debugmsg( "iuse_actor type descriptor and actual type mismatch" );
1631         act->set_to_null();
1632         return;
1633     }
1634 
1635     // TODO: Store the patient somehow, retrieve here
1636     player &patient = *p;
1637     const bodypart_id healed = bodypart_id( act->str_values[0] );
1638     const int charges_consumed = actor->finish_using( *p, patient, *used_tool, healed );
1639     p->consume_charges( it, charges_consumed );
1640 
1641     // Erase activity and values.
1642     act->set_to_null();
1643     act->values.clear();
1644 }
1645 
forage_finish(player_activity * act,player * p)1646 void activity_handlers::forage_finish( player_activity *act, player *p )
1647 {
1648     // Don't forage if we aren't next to the bush - otherwise we get weird bugs
1649     bool next_to_bush = false;
1650     map &here = get_map();
1651     for( const tripoint &pnt : here.points_in_radius( p->pos(), 1 ) ) {
1652         if( here.getabs( pnt ) == act->placement ) {
1653             next_to_bush = true;
1654             break;
1655         }
1656     }
1657 
1658     if( !next_to_bush ) {
1659         act->set_to_null();
1660         return;
1661     }
1662 
1663     const int veggy_chance = rng( 1, 100 );
1664     bool found_something = false;
1665 
1666     item_group_id group_id;
1667     ter_str_id next_ter;
1668 
1669     switch( season_of_year( calendar::turn ) ) {
1670         case SPRING:
1671             group_id = item_group_id( "forage_spring" );
1672             next_ter = ter_str_id( "t_underbrush_harvested_spring" );
1673             break;
1674         case SUMMER:
1675             group_id = item_group_id( "forage_summer" );
1676             next_ter = ter_str_id( "t_underbrush_harvested_summer" );
1677             break;
1678         case AUTUMN:
1679             group_id = item_group_id( "forage_autumn" );
1680             next_ter = ter_str_id( "t_underbrush_harvested_autumn" );
1681             break;
1682         case WINTER:
1683             group_id = item_group_id( "forage_winter" );
1684             next_ter = ter_str_id( "t_underbrush_harvested_winter" );
1685             break;
1686         default:
1687             debugmsg( "Invalid season" );
1688     }
1689 
1690     const tripoint bush_pos = here.getlocal( act->placement );
1691     here.ter_set( bush_pos, next_ter );
1692 
1693     // Survival gives a bigger boost, and Perception is leveled a bit.
1694     // Both survival and perception affect time to forage
1695 
1696     ///\EFFECT_PER slightly increases forage success chance
1697     ///\EFFECT_SURVIVAL increases forage success chance
1698     if( veggy_chance < p->get_skill_level( skill_survival ) * 3 + p->per_cur - 2 ) {
1699         const std::vector<item *> dropped =
1700             here.put_items_from_loc( group_id, p->pos(), calendar::turn );
1701         for( item *it : dropped ) {
1702             add_msg( m_good, _( "You found: %s!" ), it->tname() );
1703             found_something = true;
1704             if( it->has_flag( flag_FORAGE_POISON ) && one_in( 10 ) ) {
1705                 it->set_flag( flag_HIDDEN_POISON );
1706                 it->poison = rng( 2, 7 );
1707             }
1708             if( it->has_flag( flag_FORAGE_HALLU ) && !it->has_flag( flag_HIDDEN_POISON ) && one_in( 10 ) ) {
1709                 it->set_flag( flag_HIDDEN_HALLU );
1710             }
1711         }
1712     }
1713     // 10% to drop a item/items from this group.
1714     if( one_in( 10 ) ) {
1715         const std::vector<item *> dropped =
1716             here.put_items_from_loc( item_group_id( "trash_forest" ), p->pos(), calendar::turn );
1717         for( item * const &it : dropped ) {
1718             add_msg( m_good, _( "You found: %s!" ), it->tname() );
1719             found_something = true;
1720         }
1721     }
1722 
1723     if( !found_something ) {
1724         add_msg( _( "You didn't find anything." ) );
1725     }
1726 
1727     iexamine::practice_survival_while_foraging( p );
1728 
1729     act->set_to_null();
1730 
1731     here.maybe_trigger_trap( bush_pos, *p, true );
1732 }
1733 
generic_game_turn_handler(player_activity * act,player * p,int morale_bonus,int morale_max_bonus)1734 void activity_handlers::generic_game_turn_handler( player_activity *act, player *p,
1735         int morale_bonus, int morale_max_bonus )
1736 {
1737     // Consume battery charges for every minute spent playing
1738     if( calendar::once_every( 1_minutes ) ) {
1739         if( !act->targets.empty() ) {
1740             item &game_item = *act->targets.front();
1741             const int ammo_required = game_item.ammo_required();
1742             bool fail = false;
1743             if( game_item.has_flag( flag_USE_UPS ) ) {
1744                 fail = !p->use_charges_if_avail( itype_UPS, ammo_required );
1745             } else {
1746                 fail = game_item.ammo_consume( ammo_required, p->pos() ) == 0;
1747             }
1748             if( fail ) {
1749                 act->moves_left = 0;
1750                 add_msg( m_info, _( "The %s runs out of batteries." ), game_item.tname() );
1751                 return;
1752             }
1753         }
1754         //1 points/min, almost 2 hours to fill
1755         p->add_morale( MORALE_GAME, morale_bonus, morale_max_bonus );
1756     }
1757 }
1758 
generic_game_do_turn(player_activity * act,player * p)1759 void activity_handlers::generic_game_do_turn( player_activity *act, player *p )
1760 {
1761     generic_game_turn_handler( act, p, 4, 60 );
1762 }
1763 
game_do_turn(player_activity * act,player * p)1764 void activity_handlers::game_do_turn( player_activity *act, player *p )
1765 {
1766     generic_game_turn_handler( act, p, 1, 100 );
1767 }
1768 
longsalvage_finish(player_activity * act,player * p)1769 void activity_handlers::longsalvage_finish( player_activity *act, player *p )
1770 {
1771     static const std::string salvage_string = "salvage";
1772     item &main_tool = p->i_at( act->index );
1773     map &here = get_map();
1774     map_stack items = here.i_at( p->pos() );
1775     item *salvage_tool = main_tool.get_usable_item( salvage_string );
1776     if( salvage_tool == nullptr ) {
1777         debugmsg( "Lost tool used for long salvage" );
1778         act->set_to_null();
1779         return;
1780     }
1781 
1782     const use_function *use_fun = salvage_tool->get_use( salvage_string );
1783     const salvage_actor *actor = dynamic_cast<const salvage_actor *>( use_fun->get_actor_ptr() );
1784     if( actor == nullptr ) {
1785         debugmsg( "iuse_actor type descriptor and actual type mismatch" );
1786         act->set_to_null();
1787         return;
1788     }
1789 
1790     for( item &it : items ) {
1791         if( actor->valid_to_cut_up( it ) ) {
1792             item_location item_loc( map_cursor( p->pos() ), &it );
1793             actor->cut_up( *p, *salvage_tool, item_loc );
1794             return;
1795         }
1796     }
1797 
1798     add_msg( _( "You finish salvaging." ) );
1799     act->set_to_null();
1800 }
1801 
pickaxe_do_turn(player_activity * act,player *)1802 void activity_handlers::pickaxe_do_turn( player_activity *act, player * )
1803 {
1804     const tripoint &pos = get_map().getlocal( act->placement );
1805     sfx::play_activity_sound( "tool", "pickaxe", sfx::get_heard_volume( pos ) );
1806     // each turn is too much
1807     if( calendar::once_every( 1_minutes ) ) {
1808         //~ Sound of a Pickaxe at work!
1809         sounds::sound( pos, 30, sounds::sound_t::destructive_activity, _( "CHNK!  CHNK!  CHNK!" ) );
1810     }
1811 }
1812 
pickaxe_finish(player_activity * act,player * p)1813 void activity_handlers::pickaxe_finish( player_activity *act, player *p )
1814 {
1815     map &here = get_map();
1816     const tripoint pos( here.getlocal( act->placement ) );
1817     // Invalidate the activity early to prevent a query from mod_pain()
1818     act->set_to_null();
1819     if( p->is_avatar() ) {
1820         const int helpersize = get_player_character().get_num_crafting_helpers( 3 );
1821         if( here.is_bashable( pos ) && here.has_flag( flag_SUPPORTS_ROOF, pos ) &&
1822             here.ter( pos ) != t_tree ) {
1823             // Tunneling through solid rock is sweaty, backbreaking work
1824             // Betcha wish you'd opted for the J-Hammer
1825             if( p->has_trait( trait_STOCKY_TROGLO ) ) {
1826                 p->mod_pain( std::max( 0, ( 1 * static_cast<int>( rng( 0, 3 ) ) ) - helpersize ) );
1827             } else {
1828                 p->mod_pain( std::max( 0, ( 2 * static_cast<int>( rng( 1, 3 ) ) ) - helpersize ) );
1829             }
1830         }
1831     }
1832     p->add_msg_player_or_npc( m_good,
1833                               _( "You finish digging." ),
1834                               _( "<npcname> finishes digging." ) );
1835     here.destroy( pos, true );
1836     if( !act->targets.empty() ) {
1837         item &it = *act->targets.front();
1838         p->consume_charges( it, it.ammo_required() );
1839     } else {
1840         debugmsg( "pickaxe activity targets empty" );
1841     }
1842     if( resume_for_multi_activities( *p ) ) {
1843         for( item &elem : here.i_at( pos ) ) {
1844             elem.set_var( "activity_var", p->name );
1845         }
1846     }
1847 }
1848 
pulp_do_turn(player_activity * act,player * p)1849 void activity_handlers::pulp_do_turn( player_activity *act, player *p )
1850 {
1851     map &here = get_map();
1852     const tripoint &pos = here.getlocal( act->placement );
1853 
1854     // Stabbing weapons are a lot less effective at pulping
1855     const int cut_power = std::max( p->weapon.damage_melee( damage_type::CUT ),
1856                                     p->weapon.damage_melee( damage_type::STAB ) / 2 );
1857 
1858     ///\EFFECT_STR increases pulping power, with diminishing returns
1859     float pulp_power = std::sqrt( ( p->str_cur + p->weapon.damage_melee( damage_type::BASH ) ) *
1860                                   ( cut_power + 1.0f ) );
1861     float pulp_effort = p->str_cur + p->weapon.damage_melee( damage_type::BASH );
1862     // Multiplier to get the chance right + some bonus for survival skill
1863     pulp_power *= 40 + p->get_skill_level( skill_survival ) * 5;
1864 
1865     const int mess_radius = p->weapon.has_flag( flag_MESSY ) ? 2 : 1;
1866 
1867     int moves = 0;
1868     // use this to collect how many corpse are pulped
1869     int &num_corpses = act->index;
1870     map_stack corpse_pile = here.i_at( pos );
1871     for( item &corpse : corpse_pile ) {
1872         const mtype *corpse_mtype = corpse.get_mtype();
1873         if( !corpse.is_corpse() || !corpse.can_revive() ||
1874             ( std::find( act->str_values.begin(), act->str_values.end(), "auto_pulp_no_acid" ) !=
1875               act->str_values.end() && corpse_mtype->bloodType().obj().has_acid ) ) {
1876             // Don't smash non-rezing corpses //don't smash acid zombies when auto pulping
1877             continue;
1878         }
1879 
1880         while( corpse.damage() < corpse.max_damage() ) {
1881             // Increase damage as we keep smashing ensuring we eventually smash the target.
1882             if( x_in_y( pulp_power, corpse.volume() / units::legacy_volume_factor ) ) {
1883                 corpse.inc_damage( damage_type::BASH );
1884                 if( corpse.damage() == corpse.max_damage() ) {
1885                     num_corpses++;
1886                 }
1887             }
1888 
1889             if( x_in_y( pulp_power, corpse.volume() / units::legacy_volume_factor ) ) {
1890                 // Splatter some blood around
1891                 // Splatter a bit more randomly, so that it looks cooler
1892                 const int radius = mess_radius + x_in_y( pulp_power, 500 ) + x_in_y( pulp_power, 1000 );
1893                 const tripoint dest( pos + point( rng( -radius, radius ), rng( -radius, radius ) ) );
1894                 const field_type_id type_blood = ( mess_radius > 1 && x_in_y( pulp_power, 10000 ) ) ?
1895                                                  corpse.get_mtype()->gibType() :
1896                                                  corpse.get_mtype()->bloodType();
1897                 here.add_splatter_trail( type_blood, pos, dest );
1898             }
1899 
1900             p->mod_stamina( -pulp_effort );
1901 
1902             if( one_in( 4 ) ) {
1903                 // Smashing may not be butchery, but it involves some zombie anatomy
1904                 p->practice( skill_survival, 2, 2 );
1905             }
1906 
1907             float stamina_ratio = static_cast<float>( p->get_stamina() ) / p->get_stamina_max();
1908             moves += 100 / std::max( 0.25f,
1909                                      stamina_ratio ) * p->exertion_adjusted_move_multiplier( act->exertion_level() );
1910             if( stamina_ratio < 0.33 || p->is_npc() ) {
1911                 p->moves = std::min( 0, p->moves - moves );
1912                 return;
1913             }
1914             if( moves >= p->moves ) {
1915                 // Enough for this turn;
1916                 p->moves -= moves;
1917                 return;
1918             }
1919         }
1920         corpse.set_flag( flag_PULPED );
1921     }
1922     // If we reach this, all corpses have been pulped, finish the activity
1923     act->moves_left = 0;
1924     if( num_corpses == 0 ) {
1925         p->add_msg_if_player( m_bad, _( "The corpse moved before you could finish smashing it!" ) );
1926         return;
1927     }
1928     // TODO: Factor in how long it took to do the smashing.
1929     p->add_msg_player_or_npc( ngettext( "The corpse is thoroughly pulped.",
1930                                         "The corpses are thoroughly pulped.", num_corpses ),
1931                               ngettext( "<npcname> finished pulping the corpse.",
1932                                         "<npcname> finished pulping the corpses.", num_corpses ) );
1933 }
1934 
pulp_finish(player_activity * act,player * p)1935 void activity_handlers::pulp_finish( player_activity *act, player *p )
1936 {
1937     if( p->is_npc() ) {
1938         npc *guy = dynamic_cast<npc *>( p );
1939         guy->revert_after_activity();
1940     } else {
1941         act->set_to_null();
1942     }
1943 }
1944 
start_fire_finish(player_activity * act,player * p)1945 void activity_handlers::start_fire_finish( player_activity *act, player *p )
1946 {
1947     static const std::string iuse_name_string( "firestarter" );
1948 
1949     item &it = *act->targets.front();
1950     item *used_tool = it.get_usable_item( iuse_name_string );
1951     if( used_tool == nullptr ) {
1952         debugmsg( "Lost tool used for starting fire" );
1953         act->set_to_null();
1954         return;
1955     }
1956 
1957     const use_function *use_fun = used_tool->get_use( iuse_name_string );
1958     const firestarter_actor *actor = dynamic_cast<const firestarter_actor *>
1959                                      ( use_fun->get_actor_ptr() );
1960     if( actor == nullptr ) {
1961         debugmsg( "iuse_actor type descriptor and actual type mismatch" );
1962         act->set_to_null();
1963         return;
1964     }
1965 
1966     if( it.type->can_have_charges() ) {
1967         p->consume_charges( it, it.type->charges_to_use() );
1968     }
1969     p->practice( skill_survival, act->index, 5 );
1970 
1971     firestarter_actor::resolve_firestarter_use( *p, act->placement );
1972     act->set_to_null();
1973 }
1974 
start_fire_do_turn(player_activity * act,player * p)1975 void activity_handlers::start_fire_do_turn( player_activity *act, player *p )
1976 {
1977     map &here = get_map();
1978     if( !here.is_flammable( act->placement ) ) {
1979         try_fuel_fire( *act, *p, true );
1980         if( !here.is_flammable( act->placement ) ) {
1981             p->add_msg_if_player( m_info, _( "There's nothing to light there." ) );
1982             p->cancel_activity();
1983             return;
1984         }
1985     }
1986 
1987     // Sometimes when an item is dropped it causes the whole item* to get set to null.
1988     // This null pointer gets cast to a reference at some point, causing UB and
1989     // segfaults. It looks like something is supposed to catch this, maybe
1990     // get_safe_reference in item.cpp, but it's not working so we check for a
1991     // null pointer here.
1992     //
1993     if( act->targets.front().get_item() == nullptr ) {
1994         add_msg( m_bad, _( "You have lost the item you were using to start the fire." ) );
1995         p->cancel_activity();
1996         return;
1997     }
1998 
1999     item &firestarter = *act->targets.front();
2000     if( firestarter.has_flag( flag_REQUIRES_TINDER ) ) {
2001         if( !here.tinder_at( act->placement ) ) {
2002             p->add_msg_if_player( m_info, _( "This item requires tinder to light." ) );
2003             p->cancel_activity();
2004             return;
2005         }
2006     }
2007 
2008     const use_function *usef = firestarter.type->get_use( "firestarter" );
2009     if( usef == nullptr || usef->get_actor_ptr() == nullptr ) {
2010         add_msg( m_bad, _( "You have lost the item you were using to start the fire." ) );
2011         p->cancel_activity();
2012         return;
2013     }
2014 
2015     p->mod_moves( -p->moves );
2016     const firestarter_actor *actor = dynamic_cast<const firestarter_actor *>( usef->get_actor_ptr() );
2017     const float light = actor->light_mod( p->pos() );
2018     act->moves_left -= light * 100;
2019     if( light < 0.1 ) {
2020         add_msg( m_bad, _( "There is not enough sunlight to start a fire now.  You stop trying." ) );
2021         p->cancel_activity();
2022     }
2023 }
2024 
magic_train(player_activity * act,player * p)2025 static bool magic_train( player_activity *act, player *p )
2026 {
2027     if( !p ) {
2028         return false;
2029     }
2030     const spell_id &sp_id = spell_id( act->name );
2031     if( sp_id.is_valid() ) {
2032         const bool knows = get_player_character().magic->knows_spell( sp_id );
2033         if( knows ) {
2034             spell &studying = p->magic->get_spell( sp_id );
2035             const int expert_multiplier = act->values.empty() ? 0 : act->values[0];
2036             const int xp = roll_remainder( studying.exp_modifier( *p ) * expert_multiplier );
2037             studying.gain_exp( xp );
2038             p->add_msg_if_player( m_good, _( "You learn a little about the spell: %s" ),
2039                                   sp_id->name );
2040         } else {
2041             p->magic->learn_spell( act->name, *p );
2042             // you can decline to learn this spell , as it may lock you out of other magic.
2043             if( p->magic->knows_spell( sp_id ) ) {
2044                 add_msg( m_good, _( "You learn %s." ), sp_id->name.translated() );
2045             } else {
2046                 act->set_to_null();
2047             }
2048         }
2049         return true;
2050     }
2051     return false;
2052 }
2053 
train_finish(player_activity * act,player * p)2054 void activity_handlers::train_finish( player_activity *act, player *p )
2055 {
2056     const skill_id sk( act->name );
2057     if( sk.is_valid() ) {
2058         const Skill &skill = sk.obj();
2059         std::string skill_name = skill.name();
2060         int old_skill_level = p->get_skill_level( sk );
2061         p->practice( sk, 100, old_skill_level + 2 );
2062         int new_skill_level = p->get_skill_level( sk );
2063         if( old_skill_level != new_skill_level ) {
2064             add_msg( m_good, _( "You finish training %s to level %d." ),
2065                      skill_name, new_skill_level );
2066             get_event_bus().send<event_type::gains_skill_level>( p->getID(), sk, new_skill_level );
2067         } else {
2068             add_msg( m_good, _( "You get some training in %s." ), skill_name );
2069         }
2070         act->set_to_null();
2071         return;
2072     }
2073 
2074     const proficiency_id &proficiency = proficiency_id( act->name );
2075     if( proficiency.is_valid() ) {
2076         p->practice_proficiency( proficiency, 15_minutes );
2077         add_msg( m_good, _( "You get some training in %s." ), proficiency->name() );
2078         act->set_to_null();
2079         return;
2080     }
2081 
2082     const matype_id &ma_id = matype_id( act->name );
2083     if( ma_id.is_valid() ) {
2084         const martialart &mastyle = ma_id.obj();
2085         // Trained martial arts,
2086         get_event_bus().send<event_type::learns_martial_art>( p->getID(), ma_id );
2087         p->martial_arts_data->learn_style( mastyle.id, p->is_avatar() );
2088     } else if( !magic_train( act, p ) ) {
2089         debugmsg( "train_finish without a valid skill or style or spell name" );
2090     }
2091 
2092     act->set_to_null();
2093 }
2094 
vehicle_finish(player_activity * act,player * p)2095 void activity_handlers::vehicle_finish( player_activity *act, player *p )
2096 {
2097     map &here = get_map();
2098     //Grab this now, in case the vehicle gets shifted
2099     const optional_vpart_position vp = here.veh_at( here.getlocal( tripoint( act->values[0],
2100                                        act->values[1],
2101                                        p->posz() ) ) );
2102     veh_interact::complete_vehicle( *p );
2103     // complete_vehicle set activity type to NULL if the vehicle
2104     // was completely dismantled, otherwise the vehicle still exist and
2105     // is to be examined again.
2106     if( act->is_null() ) {
2107         if( npc *guy = dynamic_cast<npc *>( p ) ) {
2108             guy->revert_after_activity();
2109             guy->set_moves( 0 );
2110         }
2111         return;
2112     }
2113     act->set_to_null();
2114     if( !p->is_npc() ) {
2115         if( act->values.size() < 7 ) {
2116             dbg( D_ERROR ) << "game:process_activity: invalid ACT_VEHICLE values: "
2117                            << act->values.size();
2118             debugmsg( "process_activity invalid ACT_VEHICLE values:%d",
2119                       act->values.size() );
2120         } else {
2121             if( vp ) {
2122                 here.invalidate_map_cache( here.get_abs_sub().z );
2123                 // TODO: Z (and also where the activity is queued)
2124                 // Or not, because the vehicle coordinates are dropped anyway
2125                 if( !resume_for_multi_activities( *p ) ) {
2126                     g->exam_vehicle( vp->vehicle(), point( act->values[ 2 ], act->values[ 3 ] ) );
2127                 }
2128                 return;
2129             } else {
2130                 dbg( D_ERROR ) << "game:process_activity: ACT_VEHICLE: vehicle not found";
2131                 debugmsg( "process_activity ACT_VEHICLE: vehicle not found" );
2132             }
2133         }
2134     }
2135 }
2136 
hand_crank_do_turn(player_activity * act,player * p)2137 void activity_handlers::hand_crank_do_turn( player_activity *act, player *p )
2138 {
2139     // Hand-crank chargers seem to range from 2 watt (very common easily verified)
2140     // to 10 watt (suspicious claims from some manufacturers) sustained output.
2141     // It takes 2.4 minutes to produce 1kj at just slightly under 7 watts (25 kj per hour)
2142     // time-based instead of speed based because it's a sustained activity
2143     item &hand_crank_item = *act->targets.front();
2144 
2145     int time_to_crank = to_seconds<int>( 144_seconds );
2146     // Modify for weariness
2147     time_to_crank /= p->exertion_adjusted_move_multiplier( act->exertion_level() );
2148     if( calendar::once_every( time_duration::from_seconds( time_to_crank ) ) ) {
2149         if( hand_crank_item.ammo_capacity( ammotype( "battery" ) ) > hand_crank_item.ammo_remaining() ) {
2150             hand_crank_item.ammo_set( itype_id( "battery" ), hand_crank_item.ammo_remaining() + 1 );
2151         } else {
2152             act->moves_left = 0;
2153             add_msg( m_info, _( "You've charged the battery completely." ) );
2154         }
2155     }
2156     if( p->get_fatigue() >= fatigue_levels::DEAD_TIRED ) {
2157         act->moves_left = 0;
2158         add_msg( m_info, _( "You're too exhausted to keep cranking." ) );
2159     }
2160 
2161 }
2162 
vibe_do_turn(player_activity * act,player * p)2163 void activity_handlers::vibe_do_turn( player_activity *act, player *p )
2164 {
2165     //Using a vibrator takes time (10 minutes), not speed
2166     //Linear increase in morale during action with a small boost at end
2167     //Deduct 1 battery charge for every minute in use, or vibrator is much less effective
2168     item &vibrator_item = *act->targets.front();
2169 
2170     if( p->encumb( bodypart_id( "mouth" ) ) >= 30 ) {
2171         act->moves_left = 0;
2172         add_msg( m_bad, _( "You have trouble breathing, and stop." ) );
2173     }
2174 
2175     if( calendar::once_every( 1_minutes ) ) {
2176         if( vibrator_item.ammo_remaining() > 0 ) {
2177             vibrator_item.ammo_consume( 1, p->pos() );
2178             p->add_morale( MORALE_FEELING_GOOD, 3, 40 );
2179             if( vibrator_item.ammo_remaining() == 0 ) {
2180                 add_msg( m_info, _( "The %s runs out of batteries." ), vibrator_item.tname() );
2181             }
2182         } else {
2183             //twenty minutes to fill
2184             p->add_morale( MORALE_FEELING_GOOD, 1, 40 );
2185         }
2186     }
2187     // Dead Tired: different kind of relaxation needed
2188     if( p->get_fatigue() >= fatigue_levels::DEAD_TIRED ) {
2189         act->moves_left = 0;
2190         add_msg( m_info, _( "You're too tired to continue." ) );
2191     }
2192 
2193     // Vibrator requires that you be able to move around, stretch, etc, so doesn't play
2194     // well with roots.  Sorry.  :-(
2195 }
2196 
start_engines_finish(player_activity * act,player * p)2197 void activity_handlers::start_engines_finish( player_activity *act, player *p )
2198 {
2199     act->set_to_null();
2200     // Find the vehicle by looking for a remote vehicle first, then by player relative coordinates
2201     vehicle *veh = g->remoteveh();
2202     map &here = get_map();
2203     if( !veh ) {
2204         const tripoint pos = act->placement + get_player_character().pos();
2205         veh = veh_pointer_or_null( here.veh_at( pos ) );
2206         if( !veh ) {
2207             return;
2208         }
2209     }
2210 
2211     int attempted = 0;
2212     int non_muscle_attempted = 0;
2213     int started = 0;
2214     int non_muscle_started = 0;
2215     int non_combustion_started = 0;
2216     const bool take_control = act->values[0];
2217 
2218     for( size_t e = 0; e < veh->engines.size(); ++e ) {
2219         if( veh->is_engine_on( e ) ) {
2220             attempted++;
2221             if( !veh->is_engine_type( e, itype_muscle ) &&
2222                 !veh->is_engine_type( e, itype_animal ) ) {
2223                 non_muscle_attempted++;
2224             }
2225             if( veh->start_engine( e ) ) {
2226                 started++;
2227                 if( !veh->is_engine_type( e, itype_muscle ) &&
2228                     !veh->is_engine_type( e, itype_animal ) ) {
2229                     non_muscle_started++;
2230                 } else {
2231                     non_combustion_started++;
2232                 }
2233             }
2234         }
2235     }
2236 
2237     //Did any engines start?
2238     veh->engine_on = started;
2239     //init working engine noise
2240     sfx::do_vehicle_engine_sfx();
2241 
2242     if( attempted == 0 ) {
2243         add_msg( m_info, _( "The %s doesn't have an engine!" ), veh->name );
2244     } else if( non_muscle_attempted > 0 ) {
2245         //Some non-muscle engines tried to start
2246         if( non_muscle_attempted == non_muscle_started ) {
2247             //All of the non-muscle engines started
2248             add_msg( ngettext( "The %s's engine starts up.",
2249                                "The %s's engines start up.", non_muscle_started ), veh->name );
2250         } else if( non_muscle_started > 0 ) {
2251             //Only some of the non-muscle engines started
2252             add_msg( ngettext( "One of the %s's engines start up.",
2253                                "Some of the %s's engines start up.", non_muscle_started ), veh->name );
2254         } else if( non_combustion_started > 0 ) {
2255             //Non-combustions "engines" started
2256             add_msg( _( "The %s is ready for movement." ), veh->name );
2257         } else {
2258             //All of the non-muscle engines failed
2259             add_msg( m_bad, ngettext( "The %s's engine fails to start.",
2260                                       "The %s's engines fail to start.", non_muscle_attempted ), veh->name );
2261         }
2262     }
2263 
2264     if( take_control && !veh->engine_on && !veh->velocity ) {
2265         p->controlling_vehicle = false;
2266         add_msg( _( "You let go of the controls." ) );
2267     }
2268 }
2269 
oxytorch_do_turn(player_activity * act,player * p)2270 void activity_handlers::oxytorch_do_turn( player_activity *act, player *p )
2271 {
2272     if( act->values[0] <= 0 ) {
2273         return;
2274     }
2275 
2276     item &it = *act->targets.front();
2277     // act->values[0] is the number of charges yet to be consumed
2278     const int charges_used = std::min( act->values[0], it.ammo_required() );
2279 
2280     it.ammo_consume( charges_used, p->pos() );
2281     act->values[0] -= static_cast<int>( charges_used );
2282 
2283     sfx::play_activity_sound( "tool", "oxytorch", sfx::get_heard_volume( act->placement ) );
2284     if( calendar::once_every( 2_turns ) ) {
2285         sounds::sound( act->placement, 10, sounds::sound_t::destructive_activity, _( "hissssssssss!" ) );
2286     }
2287 }
2288 
oxytorch_finish(player_activity * act,player * p)2289 void activity_handlers::oxytorch_finish( player_activity *act, player *p )
2290 {
2291     act->set_to_null();
2292     map &here = get_map();
2293     const tripoint &pos = act->placement;
2294     const ter_id ter = here.ter( pos );
2295     const furn_id furn = here.furn( pos );
2296     // fast players might still have some charges left to be consumed
2297     act->targets.front()->ammo_consume( act->values[0], p->pos() );
2298 
2299     if( furn == f_rack ) {
2300         here.furn_set( pos, f_null );
2301         here.spawn_item( p->pos(), itype_steel_chunk, rng( 2, 6 ) );
2302     } else if( ter == t_chainfence || ter == t_chaingate_c || ter == t_chaingate_l ) {
2303         here.ter_set( pos, t_dirt );
2304         here.spawn_item( pos, itype_pipe, rng( 1, 4 ) );
2305         here.spawn_item( pos, itype_wire, rng( 4, 16 ) );
2306     } else if( ter == t_chainfence_posts ) {
2307         here.ter_set( pos, t_dirt );
2308         here.spawn_item( pos, itype_pipe, rng( 1, 4 ) );
2309     } else if( ter == t_door_metal_locked || ter == t_door_metal_c || ter == t_door_bar_c ||
2310                ter == t_door_bar_locked || ter == t_door_metal_pickable ) {
2311         here.ter_set( pos, t_mdoor_frame );
2312         here.spawn_item( pos, itype_steel_plate, rng( 0, 1 ) );
2313         here.spawn_item( pos, itype_steel_chunk, rng( 3, 8 ) );
2314     } else if( ter == t_window_enhanced || ter == t_window_enhanced_noglass ) {
2315         here.ter_set( pos, t_window_empty );
2316         here.spawn_item( pos, itype_steel_plate, rng( 0, 1 ) );
2317         here.spawn_item( pos, itype_sheet_metal, rng( 1, 3 ) );
2318     } else if( ter == t_reb_cage ) {
2319         here.ter_set( pos, t_pit );
2320         here.spawn_item( pos, itype_spike, rng( 1, 19 ) );
2321         here.spawn_item( pos, itype_scrap, rng( 1, 8 ) );
2322     } else if( ter == t_bars ) {
2323         if( here.ter( pos + point_east ) == t_sewage || here.ter( pos + point_south ) ==
2324             t_sewage ||
2325             here.ter( pos + point_west ) == t_sewage || here.ter( pos + point_north ) ==
2326             t_sewage ) {
2327             here.ter_set( pos, t_sewage );
2328             here.spawn_item( p->pos(), itype_pipe, rng( 1, 2 ) );
2329         } else {
2330             here.ter_set( pos, t_floor );
2331             here.spawn_item( p->pos(), itype_pipe, rng( 1, 2 ) );
2332         }
2333     } else if( ter == t_window_bars_alarm ) {
2334         here.ter_set( pos, t_window_alarm );
2335         here.spawn_item( p->pos(), itype_rebar, rng( 1, 2 ) );
2336     } else if( ter == t_window_bars ) {
2337         here.ter_set( pos, t_window_empty );
2338         here.spawn_item( p->pos(), itype_rebar, rng( 1, 2 ) );
2339     } else if( ter == t_window_bars_curtains || ter == t_window_bars_domestic ) {
2340         here.ter_set( pos, t_window_domestic );
2341         here.spawn_item( p->pos(), itype_rebar, rng( 1, 2 ) );
2342     } else if( furn == f_safe_l || furn == f_gunsafe_ml || furn == f_gunsafe_mj ||
2343                furn == f_gun_safe_el ) {
2344         here.furn_set( pos, f_safe_o );
2345         // 50% of starting a fire.
2346         if( here.flammable_items_at( pos ) && rng( 1, 100 ) < 50 ) {
2347             here.add_field( pos, fd_fire, 1, 10_minutes );
2348         }
2349     } else if( ter == t_metal_grate_window || ter == t_metal_grate_window_with_curtain ||
2350                ter == t_metal_grate_window_with_curtain_open ) {
2351         here.ter_set( pos, t_window_reinforced );
2352         here.spawn_item( p->pos(), itype_pipe, rng( 1, 12 ) );
2353         here.spawn_item( p->pos(), itype_sheet_metal, 4 );
2354     } else if( ter == t_metal_grate_window_noglass ||
2355                ter == t_metal_grate_window_with_curtain_noglass ||
2356                ter == t_metal_grate_window_with_curtain_open_noglass ) {
2357         here.ter_set( pos, t_window_reinforced_noglass );
2358         here.spawn_item( p->pos(), itype_pipe, rng( 1, 12 ) );
2359         here.spawn_item( p->pos(), itype_sheet_metal, 4 );
2360     }
2361 }
2362 
cracking_finish(player_activity * act,player * guy)2363 void activity_handlers::cracking_finish( player_activity *act, player *guy )
2364 {
2365     guy->add_msg_if_player( m_good, _( "With a satisfying click, the lock on the safe opens!" ) );
2366     if( !guy->has_proficiency( proficiency_prof_safecracking ) ) {
2367         guy->practice_proficiency( proficiency_prof_safecracking, 60_minutes );
2368     }
2369     get_map().furn_set( act->placement, f_safe_c );
2370     act->set_to_null();
2371 }
2372 
2373 enum class repeat_type : int {
2374     // INIT should be zero. In some scenarios (vehicle welder), activity value default to zero.
2375     INIT = 0,    // Haven't found repeat value yet.
2376     ONCE,        // Repeat just once
2377     FOREVER,     // Repeat for as long as possible
2378     FULL,        // Repeat until damage==0
2379     EVENT,       // Repeat until something interesting happens
2380     CANCEL,      // Stop repeating
2381 };
2382 
2383 using I = std::underlying_type_t<repeat_type>;
operator >=(const I & lhs,const repeat_type & rhs)2384 static constexpr bool operator>=( const I &lhs, const repeat_type &rhs )
2385 {
2386     return lhs >= static_cast<I>( rhs );
2387 }
2388 
operator <=(const I & lhs,const repeat_type & rhs)2389 static constexpr bool operator<=( const I &lhs, const repeat_type &rhs )
2390 {
2391     return lhs <= static_cast<I>( rhs );
2392 }
2393 
operator -(const repeat_type & lhs,const repeat_type & rhs)2394 static constexpr I operator-( const repeat_type &lhs, const repeat_type &rhs )
2395 {
2396     return static_cast<I>( lhs ) - static_cast<I>( rhs );
2397 }
2398 
repeat_menu(const std::string & title,repeat_type last_selection)2399 static repeat_type repeat_menu( const std::string &title, repeat_type last_selection )
2400 {
2401     uilist rmenu;
2402     rmenu.text = title;
2403 
2404     rmenu.addentry( static_cast<int>( repeat_type::ONCE ), true, '1', _( "Repeat once" ) );
2405     rmenu.addentry( static_cast<int>( repeat_type::FOREVER ), true, '2',
2406                     _( "Repeat until reinforced" ) );
2407     rmenu.addentry( static_cast<int>( repeat_type::FULL ), true, '3',
2408                     _( "Repeat until fully repaired, but don't reinforce" ) );
2409     rmenu.addentry( static_cast<int>( repeat_type::EVENT ), true, '4',
2410                     _( "Repeat until success/failure/level up" ) );
2411     rmenu.addentry( static_cast<int>( repeat_type::INIT ), true, '5', _( "Back to item selection" ) );
2412 
2413     rmenu.selected = last_selection - repeat_type::ONCE;
2414     rmenu.query();
2415 
2416     if( rmenu.ret >= repeat_type::INIT && rmenu.ret <= repeat_type::EVENT ) {
2417         return static_cast<repeat_type>( rmenu.ret );
2418     }
2419 
2420     return repeat_type::CANCEL;
2421 }
2422 
2423 // HACK: This is a part of a hack to provide pseudo items for long repair activity
2424 // Note: similar hack could be used to implement all sorts of vehicle pseudo-items
2425 //  and possibly CBM pseudo-items too.
2426 struct weldrig_hack {
2427     cata::optional<vpart_reference> part;
2428     item pseudo;
2429 
weldrig_hackweldrig_hack2430     weldrig_hack() : part( cata::nullopt ) { }
2431 
initweldrig_hack2432     bool init( const player_activity &act ) {
2433         if( act.coords.empty() || act.str_values.size() < 2 ) {
2434             return false;
2435         }
2436 
2437         const optional_vpart_position vp = get_map().veh_at( act.coords[0] );
2438         if( !vp ) {
2439             return false;
2440         }
2441 
2442         itype_id tool_id( act.get_str_value( 1, "" ) );
2443         pseudo = item( tool_id, calendar::turn );
2444         part = vp->part_with_tool( tool_id );
2445         return part.has_value();
2446     }
2447 
get_itemweldrig_hack2448     item &get_item() {
2449         if( !part ) {
2450             // null item should be handled just fine
2451             return null_item_reference();
2452         }
2453 
2454         item pseudo_magazine( pseudo.magazine_default() );
2455         pseudo.put_in( pseudo_magazine, item_pocket::pocket_type::MAGAZINE_WELL );
2456         pseudo.ammo_set( itype_battery, part->vehicle().drain( itype_battery,
2457                          pseudo.ammo_capacity( ammotype( "battery" ) ) ) );
2458         return pseudo;
2459     }
2460 
clean_upweldrig_hack2461     void clean_up() {
2462         if( !part ) {
2463             return;
2464         }
2465 
2466         part->vehicle().charge_battery( pseudo.ammo_remaining() ); // return unused charges
2467     }
2468 
~weldrig_hackweldrig_hack2469     ~weldrig_hack() {
2470         clean_up();
2471     }
2472 };
2473 
repair_item_finish(player_activity * act,player * p)2474 void activity_handlers::repair_item_finish( player_activity *act, player *p )
2475 {
2476     const std::string iuse_name_string = act->get_str_value( 0, "repair_item" );
2477     repeat_type repeat = static_cast<repeat_type>( act->get_value( 0,
2478                          static_cast<int>( repeat_type::INIT ) ) );
2479     weldrig_hack w_hack;
2480     item_location *ploc = nullptr;
2481 
2482     if( !act->targets.empty() ) {
2483         ploc = &act->targets[0];
2484     }
2485 
2486     item &main_tool = !w_hack.init( *act ) ?
2487                       ploc ?
2488                       **ploc : p->i_at( act->index ) : w_hack.get_item();
2489 
2490     item *used_tool = main_tool.get_usable_item( iuse_name_string );
2491     if( used_tool == nullptr ) {
2492         debugmsg( "Lost tool used for long repair" );
2493         act->set_to_null();
2494         return;
2495     }
2496 
2497     const use_function *use_fun = used_tool->get_use( iuse_name_string );
2498     // TODO: De-uglify this block. Something like get_use<iuse_actor_type>() maybe?
2499     const repair_item_actor *actor = dynamic_cast<const repair_item_actor *>
2500                                      ( use_fun->get_actor_ptr() );
2501     if( actor == nullptr ) {
2502         debugmsg( "iuse_actor type descriptor and actual type mismatch" );
2503         act->set_to_null();
2504         return;
2505     }
2506 
2507     // Valid Repeat choice and target, attempt repair.
2508     if( repeat != repeat_type::INIT && act->targets.size() >= 2 ) {
2509         item_location &fix_location = act->targets[1];
2510 
2511         // Remember our level: we want to stop retrying on level up
2512         const int old_level = p->get_skill_level( actor->used_skill );
2513         const repair_item_actor::attempt_hint attempt = actor->repair( *p, *used_tool, fix_location );
2514         if( attempt != repair_item_actor::AS_CANT ) {
2515             if( ploc && ploc->where() == item_location::type::map ) {
2516                 used_tool->ammo_consume( used_tool->ammo_required(), ploc->position() );
2517             } else {
2518                 p->consume_charges( *used_tool, used_tool->ammo_required() );
2519             }
2520         }
2521 
2522         // TODO: Allow setting this in the actor
2523         // TODO: Don't use charges_to_use: welder has 50 charges per use, soldering iron has 1
2524         if( !used_tool->units_sufficient( *p ) ) {
2525             p->add_msg_if_player( _( "Your %s ran out of charges." ), used_tool->tname() );
2526             act->set_to_null();
2527             return;
2528         }
2529 
2530         // Print message explaining why we stopped
2531         // But only if we didn't destroy the item (because then it's obvious)
2532         const bool destroyed = attempt == repair_item_actor::AS_DESTROYED;
2533         const bool cannot_continue_repair = attempt == repair_item_actor::AS_CANT ||
2534                                             destroyed || !actor->can_repair_target( *p, *fix_location, !destroyed );
2535         if( cannot_continue_repair ) {
2536             // Cannot continue to repair target, select another target.
2537             // **Warning**: as soon as the item is popped back, it is destroyed and can't be used anymore!
2538             act->targets.pop_back();
2539         }
2540 
2541         const bool event_happened = attempt == repair_item_actor::AS_FAILURE ||
2542                                     attempt == repair_item_actor::AS_SUCCESS ||
2543                                     old_level != p->get_skill_level( actor->used_skill );
2544 
2545         const bool need_input =
2546             ( repeat == repeat_type::ONCE ) ||
2547             ( repeat == repeat_type::EVENT && event_happened ) ||
2548             ( repeat == repeat_type::FULL && ( cannot_continue_repair || fix_location->damage() <= 0 ) );
2549         if( need_input ) {
2550             repeat = repeat_type::INIT;
2551         }
2552     }
2553     // Check tool is valid before we query target and Repeat choice.
2554     if( !actor->can_use_tool( *p, *used_tool, true ) ) {
2555         act->set_to_null();
2556         return;
2557     }
2558 
2559     // target selection and validation.
2560     while( act->targets.size() < 2 ) {
2561         item_location item_loc = game_menus::inv::repair( *p, actor, &main_tool );
2562 
2563         if( item_loc == item_location::nowhere ) {
2564             p->add_msg_if_player( m_info, _( "Never mind." ) );
2565             act->set_to_null();
2566             return;
2567         }
2568         if( actor->can_repair_target( *p, *item_loc, true ) ) {
2569             act->targets.emplace_back( item_loc );
2570             repeat = repeat_type::INIT;
2571         }
2572     }
2573 
2574     const item &fix = *act->targets[1];
2575     if( repeat == repeat_type::INIT ) {
2576         const int level = p->get_skill_level( actor->used_skill );
2577         repair_item_actor::repair_type action_type = actor->default_action( fix, level );
2578         if( action_type == repair_item_actor::RT_NOTHING ) {
2579             p->add_msg_if_player( _( "You won't learn anything more by doing that." ) );
2580         }
2581 
2582         const std::pair<float, float> chance = actor->repair_chance( *p, fix, action_type );
2583         if( chance.first <= 0.0f ) {
2584             action_type = repair_item_actor::RT_PRACTICE;
2585         }
2586 
2587         std::string title = string_format( _( "%s %s\n" ),
2588                                            repair_item_actor::action_description( action_type ),
2589                                            fix.tname() );
2590         ammotype current_ammo;
2591         std::string ammo_name;
2592         if( used_tool->has_flag( flag_USES_BIONIC_POWER ) ) {
2593             ammo_name = _( "bionic power" );
2594 
2595         } else {
2596             if( used_tool->ammo_current().is_null() ) {
2597                 current_ammo = item_controller->find_template( used_tool->ammo_default() )->ammo->type;
2598             } else {
2599                 current_ammo = item_controller->find_template( used_tool->ammo_current() )->ammo->type;
2600             }
2601 
2602             ammo_name = item::nname( used_tool->ammo_current() );
2603         }
2604 
2605         int ammo_remaining = used_tool->ammo_remaining();
2606         if( used_tool->has_flag( flag_USE_UPS ) ) {
2607             ammo_remaining = p->charges_of( itype_UPS );
2608         }
2609 
2610         std::set<itype_id> valid_entries = actor->get_valid_repair_materials( fix );
2611         const inventory &crafting_inv = p->crafting_inventory();
2612         std::function<bool( const item & )> filter;
2613         if( fix.is_filthy() ) {
2614             filter = []( const item & component ) {
2615                 return component.allow_crafting_component();
2616             };
2617         } else {
2618             filter = is_crafting_component;
2619         }
2620         std::vector<std::string> material_list;
2621         for( const auto &component_id : valid_entries ) {
2622             if( item::count_by_charges( component_id ) ) {
2623                 if( crafting_inv.has_charges( component_id, 1 ) ) {
2624                     material_list.push_back( string_format( _( "%s (%d)" ), item::nname( component_id ),
2625                                                             crafting_inv.charges_of( component_id ) ) );
2626                 }
2627             } else if( crafting_inv.has_amount( component_id, 1, false, filter ) ) {
2628                 material_list.push_back( string_format( _( "%s (%d)" ), item::nname( component_id ),
2629                                                         crafting_inv.amount_of( component_id, false ) ) );
2630             }
2631         }
2632 
2633         title += string_format( _( "Charges: <color_light_blue>%s/%s</color> %s (%s per use)\n" ),
2634                                 ammo_remaining, used_tool->ammo_capacity( current_ammo ),
2635                                 ammo_name,
2636                                 used_tool->ammo_required() );
2637         title += string_format( _( "Materials available: %s\n" ), join( material_list, ", " ) );
2638         title += string_format( _( "Skill used: <color_light_blue>%s (%s)</color>\n" ),
2639                                 actor->used_skill.obj().name(), level );
2640         title += string_format( _( "Success chance: <color_light_blue>%.1f</color>%%\n" ),
2641                                 100.0f * chance.first );
2642         title += string_format( _( "Damage chance: <color_light_blue>%.1f</color>%%" ),
2643                                 100.0f * chance.second );
2644 
2645         if( act->values.empty() ) {
2646             act->values.resize( 1 );
2647         }
2648         do {
2649             repeat = repeat_menu( title, repeat );
2650 
2651             if( repeat == repeat_type::CANCEL ) {
2652                 act->set_to_null();
2653                 return;
2654             }
2655             act->values[0] = static_cast<int>( repeat );
2656             // BACK selected, redo target selection next.
2657             if( repeat == repeat_type::INIT ) {
2658                 p->activity.targets.pop_back();
2659                 return;
2660             }
2661             if( repeat == repeat_type::FULL && fix.damage() <= 0 ) {
2662                 p->add_msg_if_player( m_info, _( "Your %s is already fully repaired." ), fix.tname() );
2663                 repeat = repeat_type::INIT;
2664             }
2665         } while( repeat == repeat_type::INIT );
2666     }
2667     // Otherwise keep retrying
2668     act->moves_left = actor->move_cost;
2669 }
2670 
heat_item_finish(player_activity * act,player * p)2671 void activity_handlers::heat_item_finish( player_activity *act, player *p )
2672 {
2673     act->set_to_null();
2674     if( act->targets.size() != 1 ) {
2675         debugmsg( "invalid arguments to ACT_HEATING" );
2676         return;
2677     }
2678     item_location &loc = act->targets[ 0 ];
2679     item *const heat = loc.get_item();
2680     if( heat == nullptr ) {
2681         return;
2682     }
2683     item *const food = heat->get_food();
2684     if( food == nullptr ) {
2685         debugmsg( "item %s is not food", heat->typeId().str() );
2686         return;
2687     }
2688     item &target = *food;
2689     if( target.has_own_flag( flag_FROZEN ) ) {
2690         target.apply_freezerburn();
2691         if( target.has_flag( flag_EATEN_COLD ) ) {
2692             target.cold_up();
2693             p->add_msg_if_player( m_info,
2694                                   _( "You defrost the food, but don't heat it up, since you enjoy it cold." ) );
2695         } else {
2696             target.heat_up();
2697             p->add_msg_if_player( m_info, _( "You defrost and heat up the food." ) );
2698         }
2699     } else {
2700         target.heat_up();
2701         p->add_msg_if_player( m_info, _( "You heat up the food." ) );
2702     }
2703 }
2704 
mend_item_finish(player_activity * act,player * p)2705 void activity_handlers::mend_item_finish( player_activity *act, player *p )
2706 {
2707     act->set_to_null();
2708     if( act->targets.size() != 1 ) {
2709         debugmsg( "invalid arguments to ACT_MEND_ITEM" );
2710         return;
2711     }
2712 
2713     item_location &target = act->targets[ 0 ];
2714 
2715     const auto f = target->faults.find( fault_id( act->name ) );
2716     if( f == target->faults.end() ) {
2717         debugmsg( "item %s does not have fault %s", target->tname(), act->name );
2718         return;
2719     }
2720 
2721     if( act->str_values.empty() ) {
2722         debugmsg( "missing mending_method id for ACT_MEND_ITEM." );
2723         return;
2724     }
2725 
2726     const mending_method *method = fault_id( act->name )->find_mending_method( act->str_values[0] );
2727     if( !method ) {
2728         debugmsg( "invalid mending_method id for ACT_MEND_ITEM." );
2729         return;
2730     }
2731 
2732     const inventory inv = p->crafting_inventory();
2733     const requirement_data &reqs = method->requirements.obj();
2734     if( !reqs.can_make_with_inventory( inv, is_crafting_component ) ) {
2735         add_msg( m_info, _( "You are currently unable to mend the %s." ), target->tname() );
2736     }
2737     for( const auto &e : reqs.get_components() ) {
2738         p->consume_items( e );
2739     }
2740     for( const auto &e : reqs.get_tools() ) {
2741         p->consume_tools( e );
2742     }
2743     p->invalidate_crafting_inventory();
2744 
2745     target->faults.erase( *f );
2746     if( method->turns_into ) {
2747         target->faults.emplace( *method->turns_into );
2748     }
2749     // also_mends removes not just the fault picked to be mended, but this as well.
2750     if( method->also_mends ) {
2751         target->faults.erase( *method->also_mends );
2752     }
2753     if( act->name == "fault_gun_blackpowder" || act->name == "fault_gun_dirt" ) {
2754         target->set_var( "dirt", 0 );
2755     }
2756 
2757     //get skill list from mending method, iterate through and give xp
2758     for( const std::pair<const skill_id, int> &e : method->skills ) {
2759         p->practice( e.first, 10, static_cast<int>( e.second * 1.25 ) );
2760     }
2761 
2762     add_msg( m_good, method->success_msg.translated(), target->tname() );
2763 }
2764 
gunmod_add_finish(player_activity * act,player * p)2765 void activity_handlers::gunmod_add_finish( player_activity *act, player *p )
2766 {
2767     act->set_to_null();
2768     // first unpack all of our arguments
2769     if( act->values.size() != 4 ) {
2770         debugmsg( "Insufficient arguments to ACT_GUNMOD_ADD" );
2771         return;
2772     }
2773 
2774     item &gun = *act->targets.at( 0 );
2775     item &mod = *act->targets.at( 1 );
2776 
2777     // chance of success (%)
2778     const int roll = act->values[1];
2779     // chance of damage (%)
2780     const int risk = act->values[2];
2781 
2782     // any tool charges used during installation
2783     const itype_id tool( act->name );
2784     const int qty = act->values[3];
2785 
2786     if( !gun.is_gunmod_compatible( mod ).success() ) {
2787         debugmsg( "Invalid arguments in ACT_GUNMOD_ADD" );
2788         return;
2789     }
2790 
2791     if( !tool.is_empty() && qty > 0 ) {
2792         p->use_charges( tool, qty );
2793     }
2794 
2795     if( rng( 0, 100 ) <= roll ) {
2796         add_msg( m_good, _( "You successfully attached the %1$s to your %2$s." ), mod.tname(),
2797                  gun.tname() );
2798         gun.put_in( p->i_rem( &mod ), item_pocket::pocket_type::MOD );
2799 
2800     } else if( rng( 0, 100 ) <= risk ) {
2801         if( gun.inc_damage() ) {
2802             // Remove irremovable mods prior to destroying the gun
2803             for( item *mod : gun.gunmods() ) {
2804                 if( mod->is_irremovable() ) {
2805                     p->remove_item( *mod );
2806                 }
2807             }
2808             add_msg( m_bad, _( "You failed at installing the %s and destroyed your %s!" ), mod.tname(),
2809                      gun.tname() );
2810             p->i_rem( &gun );
2811         } else {
2812             add_msg( m_bad, _( "You failed at installing the %s and damaged your %s!" ), mod.tname(),
2813                      gun.tname() );
2814         }
2815 
2816     } else {
2817         add_msg( m_info, _( "You failed at installing the %s." ), mod.tname() );
2818     }
2819 }
2820 
toolmod_add_finish(player_activity * act,player * p)2821 void activity_handlers::toolmod_add_finish( player_activity *act, player *p )
2822 {
2823     act->set_to_null();
2824     if( act->targets.size() != 2 || !act->targets[0] || !act->targets[1] ) {
2825         debugmsg( "Incompatible arguments to ACT_TOOLMOD_ADD" );
2826         return;
2827     }
2828     item &tool = *act->targets[0];
2829     item &mod = *act->targets[1];
2830     p->add_msg_if_player( m_good, _( "You successfully attached the %1$s to your %2$s." ),
2831                           mod.tname(), tool.tname() );
2832     mod.set_flag( flag_IRREMOVABLE );
2833     tool.put_in( mod, item_pocket::pocket_type::MOD );
2834     act->targets[1].remove_item();
2835 }
2836 
clear_rubble_finish(player_activity * act,player * p)2837 void activity_handlers::clear_rubble_finish( player_activity *act, player *p )
2838 {
2839     const tripoint &pos = act->placement;
2840     map &here = get_map();
2841     p->add_msg_if_player( m_info, _( "You clear up the %s." ),
2842                           here.furnname( pos ) );
2843     here.furn_set( pos, f_null );
2844 
2845     act->set_to_null();
2846 
2847     here.maybe_trigger_trap( pos, *p, true );
2848 }
2849 
meditate_finish(player_activity * act,player * p)2850 void activity_handlers::meditate_finish( player_activity *act, player *p )
2851 {
2852     p->add_msg_if_player( m_good, _( "You pause to engage in spiritual contemplation." ) );
2853     p->add_morale( MORALE_FEELING_GOOD, 5, 10 );
2854     act->set_to_null();
2855 }
2856 
wear_do_turn(player_activity * act,player * p)2857 void activity_handlers::wear_do_turn( player_activity *act, player *p )
2858 {
2859     activity_on_turn_wear( *act, *p );
2860 }
2861 
2862 // This activity opens the menu (it's not meant to queue consumption of items)
eat_menu_do_turn(player_activity *,player *)2863 void activity_handlers::eat_menu_do_turn( player_activity *, player * )
2864 {
2865     avatar &player_character = get_avatar();
2866     avatar_action::eat( player_character, game_menus::inv::consume( player_character ) );
2867 }
2868 
consume_food_menu_do_turn(player_activity *,player *)2869 void activity_handlers::consume_food_menu_do_turn( player_activity *, player * )
2870 {
2871     avatar &player_character = get_avatar();
2872     avatar_action::eat( player_character, game_menus::inv::consume_food( player_character ) );
2873 }
2874 
consume_drink_menu_do_turn(player_activity *,player *)2875 void activity_handlers::consume_drink_menu_do_turn( player_activity *, player * )
2876 {
2877     avatar &player_character = get_avatar();
2878     avatar_action::eat( player_character, game_menus::inv::consume_drink( player_character ) );
2879 }
2880 
consume_meds_menu_do_turn(player_activity *,player *)2881 void activity_handlers::consume_meds_menu_do_turn( player_activity *, player * )
2882 {
2883     avatar &player_character = get_avatar();
2884     avatar_action::eat( player_character, game_menus::inv::consume_meds( player_character ) );
2885 }
2886 
move_loot_do_turn(player_activity * act,player * p)2887 void activity_handlers::move_loot_do_turn( player_activity *act, player *p )
2888 {
2889     activity_on_turn_move_loot( *act, *p );
2890 }
2891 
adv_inventory_do_turn(player_activity *,player * p)2892 void activity_handlers::adv_inventory_do_turn( player_activity *, player *p )
2893 {
2894     p->cancel_activity();
2895     create_advanced_inv();
2896 }
2897 
travel_do_turn(player_activity * act,player * p)2898 void activity_handlers::travel_do_turn( player_activity *act, player *p )
2899 {
2900     if( !p->omt_path.empty() ) {
2901         p->omt_path.pop_back();
2902         if( p->omt_path.empty() ) {
2903             p->add_msg_if_player( m_info, _( "You have reached your destination." ) );
2904             act->set_to_null();
2905             return;
2906         }
2907         map &here = get_map();
2908         // TODO: fix point types
2909         tripoint sm_tri = here.getlocal(
2910                               project_to<coords::ms>( p->omt_path.back() ).raw() );
2911         tripoint centre_sub = sm_tri + point( SEEX, SEEY );
2912         if( !here.passable( centre_sub ) ) {
2913             tripoint_range<tripoint> candidates = here.points_in_radius( centre_sub, 2 );
2914             for( const tripoint &elem : candidates ) {
2915                 if( here.passable( elem ) ) {
2916                     centre_sub = elem;
2917                     break;
2918                 }
2919             }
2920         }
2921         const std::vector<tripoint> route_to = here.route( p->pos(), centre_sub,
2922                                                p->get_pathfinding_settings(),
2923                                                p->get_path_avoid() );
2924         if( !route_to.empty() ) {
2925             const activity_id act_travel = ACT_TRAVELLING;
2926             p->set_destination( route_to, player_activity( act_travel ) );
2927         } else {
2928             p->add_msg_if_player( _( "You cannot reach that destination" ) );
2929         }
2930     } else {
2931         p->add_msg_if_player( m_info, _( "You have reached your destination." ) );
2932     }
2933     act->set_to_null();
2934 }
2935 
armor_layers_do_turn(player_activity *,player * p)2936 void activity_handlers::armor_layers_do_turn( player_activity *, player *p )
2937 {
2938     p->cancel_activity();
2939     p->sort_armor();
2940 }
2941 
atm_do_turn(player_activity *,player * p)2942 void activity_handlers::atm_do_turn( player_activity *, player *p )
2943 {
2944     iexamine::atm( *p, p->pos() );
2945 }
2946 
2947 // fish-with-rod fish catching function.
rod_fish(player * p,const std::vector<monster * > & fishables)2948 static void rod_fish( player *p, const std::vector<monster *> &fishables )
2949 {
2950     map &here = get_map();
2951     //if the vector is empty (no fish around) the player is still given a small chance to get a (let us say it was hidden) fish
2952     if( fishables.empty() ) {
2953         const std::vector<mtype_id> fish_group = MonsterGroupManager::GetMonstersFromGroup(
2954                     mongroup_id( "GROUP_FISH" ) );
2955         const mtype_id fish_mon = random_entry_ref( fish_group );
2956         here.add_item_or_charges( p->pos(), item::make_corpse( fish_mon, calendar::turn + rng( 0_turns,
2957                                   3_hours ) ) );
2958         p->add_msg_if_player( m_good, _( "You caught a %s." ), fish_mon.obj().nname() );
2959     } else {
2960         monster *chosen_fish = random_entry( fishables );
2961         chosen_fish->fish_population -= 1;
2962         if( chosen_fish->fish_population <= 0 ) {
2963             g->catch_a_monster( chosen_fish, p->pos(), p, 50_hours );
2964         } else {
2965             here.add_item_or_charges( p->pos(), item::make_corpse( chosen_fish->type->id,
2966                                       calendar::turn + rng( 0_turns,
2967                                               3_hours ) ) );
2968             p->add_msg_if_player( m_good, _( "You caught a %s." ), chosen_fish->type->nname() );
2969         }
2970     }
2971     for( item &elem : here.i_at( p->pos() ) ) {
2972         if( elem.is_corpse() && !elem.has_var( "activity_var" ) ) {
2973             elem.set_var( "activity_var", p->name );
2974         }
2975     }
2976 }
2977 
fish_do_turn(player_activity * act,player * p)2978 void activity_handlers::fish_do_turn( player_activity *act, player *p )
2979 {
2980     item &it = *act->targets.front();
2981     int fish_chance = 1;
2982     int survival_skill = p->get_skill_level( skill_survival );
2983     if( it.has_flag( flag_FISH_POOR ) ) {
2984         survival_skill += dice( 1, 6 );
2985     } else if( it.has_flag( flag_FISH_GOOD ) ) {
2986         // Much better chances with a good fishing implement.
2987         survival_skill += dice( 4, 9 );
2988         survival_skill *= 2;
2989     }
2990     std::vector<monster *> fishables = g->get_fishable_monsters( act->coord_set );
2991     // Fish are always there, even if it doesn't seem like they are visible!
2992     if( fishables.empty() ) {
2993         fish_chance += survival_skill / 2;
2994     } else {
2995         // if they are visible however, it implies a larger population
2996         for( monster *elem : fishables ) {
2997             fish_chance += elem->fish_population;
2998         }
2999         fish_chance += survival_skill;
3000     }
3001     // no matter the population of fish, your skill and tool limits the ease of catching.
3002     fish_chance = std::min( survival_skill * 10, fish_chance );
3003     if( x_in_y( fish_chance, 600000 ) ) {
3004         p->add_msg_if_player( m_good, _( "You feel a tug on your line!" ) );
3005         rod_fish( p, fishables );
3006     }
3007     if( calendar::once_every( 60_minutes ) ) {
3008         p->practice( skill_survival, rng( 1, 3 ) );
3009     }
3010 
3011 }
3012 
fish_finish(player_activity * act,player * p)3013 void activity_handlers::fish_finish( player_activity *act, player *p )
3014 {
3015     act->set_to_null();
3016     p->add_msg_if_player( m_info, _( "You finish fishing" ) );
3017     if( !p->backlog.empty() && p->backlog.front().id() == ACT_MULTIPLE_FISH ) {
3018         p->backlog.clear();
3019         p->assign_activity( ACT_TIDY_UP );
3020     }
3021 }
3022 
cracking_do_turn(player_activity * act,player * p)3023 void activity_handlers::cracking_do_turn( player_activity *act, player *p )
3024 {
3025     auto cracking_tool = p->crafting_inventory().items_with( []( const item & it ) -> bool {
3026         item temporary_item( it.type );
3027         return temporary_item.has_flag( flag_SAFECRACK );
3028     } );
3029     if( cracking_tool.empty() && !p->has_flag( json_flag_SUPER_HEARING ) ) {
3030         // We lost our cracking tool somehow, bail out.
3031         act->set_to_null();
3032         return;
3033     }
3034 }
3035 
repair_item_do_turn(player_activity * act,player * p)3036 void activity_handlers::repair_item_do_turn( player_activity *act, player *p )
3037 {
3038     // Moves are decremented based on a combination of speed and good vision (not in the dark, farsighted, etc)
3039     const float exertion_mult = p->exertion_adjusted_move_multiplier( act->exertion_level() );
3040     const int effective_moves = p->moves / ( p->fine_detail_vision_mod() * exertion_mult );
3041     if( effective_moves <= act->moves_left ) {
3042         act->moves_left -= effective_moves;
3043         p->moves = 0;
3044     } else {
3045         p->moves -= act->moves_left * p->fine_detail_vision_mod();
3046         act->moves_left = 0;
3047     }
3048 }
3049 
butcher_do_turn(player_activity *,player * p)3050 void activity_handlers::butcher_do_turn( player_activity * /*act*/, player *p )
3051 {
3052     p->mod_stamina( -20 );
3053 }
3054 
read_do_turn(player_activity * act,player * p)3055 void activity_handlers::read_do_turn( player_activity *act, player *p )
3056 {
3057     if( p->is_player() ) {
3058         // next check doesn't work for NPCs because it is counted for player's submap and z-level only
3059         if( p->fine_detail_vision_mod() > 4 ) {
3060             //It got too dark during the process of reading, bail out.
3061             act->set_to_null();
3062             p->add_msg_if_player( m_bad, _( "It's too dark to read!" ) );
3063             return;
3064         }
3065 
3066         if( !act->str_values.empty() && act->str_values[0] == "martial_art" && one_in( 3 ) ) {
3067             if( act->values.empty() ) {
3068                 act->values.push_back( p->get_stamina() );
3069             }
3070             p->set_stamina( act->values[0] - 1 );
3071             act->values[0] = p->get_stamina();
3072         }
3073     } else {
3074         p->moves = 0;
3075     }
3076 }
3077 
read_finish(player_activity * act,player * p)3078 void activity_handlers::read_finish( player_activity *act, player *p )
3079 {
3080     if( !act || !act->targets.front() ) {
3081         debugmsg( "Lost target of ACT_READ" );
3082         return;
3083     }
3084     if( p->is_npc() ) {
3085         npc *guy = dynamic_cast<npc *>( p );
3086         guy->finish_read( * act->targets.front().get_item() );
3087     } else {
3088         if( avatar *u = p->as_avatar() ) {
3089             u->do_read( *act->targets.front().get_item() );
3090         } else {
3091             act->set_to_null();
3092         }
3093         if( !act ) {
3094             p->add_msg_if_player( m_info, _( "You finish reading." ) );
3095         }
3096     }
3097 }
3098 
wait_finish(player_activity * act,player * p)3099 void activity_handlers::wait_finish( player_activity *act, player *p )
3100 {
3101     p->add_msg_if_player( _( "You finish waiting." ) );
3102     act->set_to_null();
3103 }
3104 
wait_weather_finish(player_activity * act,player * p)3105 void activity_handlers::wait_weather_finish( player_activity *act, player *p )
3106 {
3107     p->add_msg_if_player( _( "You finish waiting." ) );
3108     act->set_to_null();
3109 }
3110 
find_mount_do_turn(player_activity * act,player * p)3111 void activity_handlers::find_mount_do_turn( player_activity *act, player *p )
3112 {
3113     //npc only activity
3114     if( p->is_player() ) {
3115         act->set_to_null();
3116         return;
3117     }
3118     npc &guy = dynamic_cast<npc &>( *p );
3119     auto strong_monster = guy.chosen_mount.lock();
3120     monster *mon = strong_monster.get();
3121     if( !mon ) {
3122         act->set_to_null();
3123         guy.revert_after_activity();
3124         return;
3125     }
3126     if( rl_dist( guy.pos(), mon->pos() ) <= 1 ) {
3127         if( mon->has_effect( effect_controlled ) ) {
3128             mon->remove_effect( effect_controlled );
3129         }
3130         if( p->can_mount( *mon ) ) {
3131             act->set_to_null();
3132             guy.revert_after_activity();
3133             guy.chosen_mount = weak_ptr_fast<monster>();
3134             p->mount_creature( *mon );
3135         } else {
3136             act->set_to_null();
3137             guy.revert_after_activity();
3138             return;
3139         }
3140     } else {
3141         const std::vector<tripoint> route = route_adjacent( *p, guy.chosen_mount.lock()->pos() );
3142         if( route.empty() ) {
3143             act->set_to_null();
3144             guy.revert_after_activity();
3145             mon->remove_effect( effect_controlled );
3146             return;
3147         } else {
3148             p->activity = player_activity();
3149             mon->add_effect( effect_controlled, 40_turns );
3150             p->set_destination( route, player_activity( ACT_FIND_MOUNT ) );
3151         }
3152     }
3153 }
3154 
wait_npc_finish(player_activity * act,player * p)3155 void activity_handlers::wait_npc_finish( player_activity *act, player *p )
3156 {
3157     p->add_msg_if_player( _( "%s finishes with you…" ), act->str_values[0] );
3158     act->set_to_null();
3159 }
3160 
wait_stamina_do_turn(player_activity * act,player * p)3161 void activity_handlers::wait_stamina_do_turn( player_activity *act, player *p )
3162 {
3163     int stamina_threshold = p->get_stamina_max();
3164     if( !act->values.empty() ) {
3165         stamina_threshold = act->values[0];
3166         // remember initial stamina, only for waiting-with-threshold
3167         if( act->values.size() == 1 ) {
3168             act->values.push_back( p->get_stamina() );
3169         }
3170     }
3171     if( p->get_stamina() >= stamina_threshold ) {
3172         wait_stamina_finish( act, p );
3173     }
3174 }
3175 
wait_stamina_finish(player_activity * act,player * p)3176 void activity_handlers::wait_stamina_finish( player_activity *act, player *p )
3177 {
3178     if( !act->values.empty() ) {
3179         const int stamina_threshold = act->values[0];
3180         const int stamina_initial = ( act->values.size() > 1 ) ? act->values[1] : p->get_stamina();
3181         if( p->get_stamina() < stamina_threshold && p->get_stamina() <= stamina_initial ) {
3182             debugmsg( "Failed to wait until stamina threshold %d reached, only at %d. You may not be regaining stamina.",
3183                       act->values.front(), p->get_stamina() );
3184         }
3185     } else if( p->get_stamina() < p->get_stamina_max() ) {
3186         p->add_msg_if_player( _( "You are bored of waiting, so you stop." ) );
3187     } else {
3188         p->add_msg_if_player( _( "You finish waiting and feel refreshed." ) );
3189     }
3190     act->set_to_null();
3191 }
3192 
socialize_finish(player_activity * act,player * p)3193 void activity_handlers::socialize_finish( player_activity *act, player *p )
3194 {
3195     p->add_msg_if_player( _( "%s finishes chatting with you." ), act->str_values[0] );
3196     act->set_to_null();
3197 }
3198 
operation_do_turn(player_activity * act,player * p)3199 void activity_handlers::operation_do_turn( player_activity *act, player *p )
3200 {
3201     /**
3202     - values[0]: Difficulty
3203     - values[1]: success
3204     - values[2]: max_power_level
3205     - values[3]: pl_skill
3206     - str_values[0]: install/uninstall
3207     - str_values[1]: bionic_id
3208     - str_values[2]: installer_name
3209     - str_values[3]: bool autodoc
3210     */
3211     enum operation_values_ids {
3212         operation_type = 0,
3213         cbm_id = 1,
3214         installer_name = 2,
3215         is_autodoc = 3
3216     };
3217     const bionic_id bid( act->str_values[cbm_id] );
3218     const bool autodoc = act->str_values[is_autodoc] == "true";
3219     Character &player_character = get_player_character();
3220     const bool u_see = player_character.sees( p->pos() ) &&
3221                        ( !player_character.has_effect( effect_narcosis ) ||
3222                          player_character.has_bionic( bio_painkiller ) || player_character.has_trait( trait_NOPAIN ) );
3223 
3224     const int difficulty = act->values.front();
3225 
3226     const std::vector<bodypart_id> bps = get_occupied_bodyparts( bid );
3227 
3228     const time_duration half_op_duration = difficulty * 10_minutes;
3229     const time_duration message_freq = difficulty * 2_minutes;
3230     time_duration time_left = time_duration::from_moves( act->moves_left );
3231 
3232     map &here = get_map();
3233     if( autodoc && here.inbounds( p->pos() ) ) {
3234         const std::list<tripoint> autodocs = here.find_furnitures_with_flag_in_radius( p->pos(), 1,
3235                                              flag_AUTODOC );
3236 
3237         if( !here.has_flag_furn( flag_AUTODOC_COUCH, p->pos() ) || autodocs.empty() ) {
3238             p->remove_effect( effect_under_operation );
3239             act->set_to_null();
3240 
3241             if( u_see ) {
3242                 add_msg( m_bad, _( "The autodoc suffers a catastrophic failure." ) );
3243 
3244                 p->add_msg_player_or_npc( m_bad,
3245                                           _( "The Autodoc's failure damages you greatly." ),
3246                                           _( "The Autodoc's failure damages <npcname> greatly." ) );
3247             }
3248             if( !bps.empty() ) {
3249                 for( const bodypart_id &bp : bps ) {
3250                     p->make_bleed( effect_source::empty(), bp, 1_turns, difficulty, true );
3251                     p->apply_damage( nullptr, bp, 20 * difficulty );
3252 
3253                     if( u_see ) {
3254                         p->add_msg_player_or_npc( m_bad, _( "Your %s is ripped open." ),
3255                                                   _( "<npcname>'s %s is ripped open." ), body_part_name_accusative( bp ) );
3256                     }
3257 
3258                     if( bp == bodypart_id( "eyes" ) ) {
3259                         p->add_effect( effect_blind, 1_hours );
3260                     }
3261                 }
3262             } else {
3263                 p->make_bleed( effect_source::empty(), bodypart_id( "bp_null" ), 1_turns, difficulty, true );
3264                 p->apply_damage( nullptr, bodypart_id( "torso" ), 20 * difficulty );
3265             }
3266         }
3267     }
3268 
3269     if( time_left > half_op_duration ) {
3270         if( !bps.empty() ) {
3271             for( const bodypart_id &bp : bps ) {
3272                 if( calendar::once_every( message_freq ) && u_see && autodoc ) {
3273                     p->add_msg_player_or_npc( m_info,
3274                                               _( "The Autodoc is meticulously cutting your %s open." ),
3275                                               _( "The Autodoc is meticulously cutting <npcname>'s %s open." ),
3276                                               body_part_name_accusative( bp ) );
3277                 }
3278             }
3279         } else {
3280             if( calendar::once_every( message_freq ) && u_see ) {
3281                 p->add_msg_player_or_npc( m_info,
3282                                           _( "The Autodoc is meticulously cutting you open." ),
3283                                           _( "The Autodoc is meticulously cutting <npcname> open." ) );
3284             }
3285         }
3286     } else if( time_left == half_op_duration ) {
3287         if( act->str_values[operation_type] == "uninstall" ) {
3288             if( u_see && autodoc ) {
3289                 add_msg( m_info, _( "The Autodoc attempts to carefully extract the bionic." ) );
3290             }
3291 
3292             if( p->has_bionic( bid ) ) {
3293                 p->perform_uninstall( bid, act->values[0], act->values[1],
3294                                       units::from_millijoule( act->values[2] ), act->values[3] );
3295             } else {
3296                 debugmsg( _( "Tried to uninstall %s, but you don't have this bionic installed." ), bid.c_str() );
3297                 p->remove_effect( effect_under_operation );
3298                 act->set_to_null();
3299             }
3300         } else {
3301             if( u_see && autodoc ) {
3302                 add_msg( m_info, _( "The Autodoc attempts to carefully insert the bionic." ) );
3303             }
3304 
3305             if( bid.is_valid() ) {
3306                 const bionic_id upbid = bid->upgraded_bionic;
3307                 p->perform_install( bid, upbid, act->values[0], act->values[1], act->values[3],
3308                                     act->str_values[installer_name], bid->canceled_mutations, p->pos() );
3309             } else {
3310                 debugmsg( _( "%s is no a valid bionic_id" ), bid.c_str() );
3311                 p->remove_effect( effect_under_operation );
3312                 act->set_to_null();
3313             }
3314         }
3315     } else if( act->values[1] > 0 ) {
3316         if( !bps.empty() ) {
3317             for( const bodypart_id &bp : bps ) {
3318                 if( calendar::once_every( message_freq ) && u_see && autodoc ) {
3319                     p->add_msg_player_or_npc( m_info,
3320                                               _( "The Autodoc is stitching your %s back up." ),
3321                                               _( "The Autodoc is stitching <npcname>'s %s back up." ),
3322                                               body_part_name_accusative( bp ) );
3323                 }
3324             }
3325         } else {
3326             if( calendar::once_every( message_freq ) && u_see && autodoc ) {
3327                 p->add_msg_player_or_npc( m_info,
3328                                           _( "The Autodoc is stitching you back up." ),
3329                                           _( "The Autodoc is stitching <npcname> back up." ) );
3330             }
3331         }
3332     } else {
3333         if( calendar::once_every( message_freq ) && u_see && autodoc ) {
3334             p->add_msg_player_or_npc( m_bad,
3335                                       _( "The Autodoc is moving erratically through the rest of its program, not actually stitching your wounds." ),
3336                                       _( "The Autodoc is moving erratically through the rest of its program, not actually stitching <npcname>'s wounds." ) );
3337         }
3338     }
3339 
3340     // Makes sure NPC is still under anesthesia
3341     if( p->has_effect( effect_narcosis ) ) {
3342         const time_duration remaining_time = p->get_effect_dur( effect_narcosis );
3343         if( remaining_time < time_left ) {
3344             const time_duration top_off_time = time_left - remaining_time;
3345             p->add_effect( effect_narcosis, top_off_time );
3346             p->add_effect( effect_sleep, top_off_time );
3347         }
3348     } else {
3349         p->add_effect( effect_narcosis, time_left );
3350         p->add_effect( effect_sleep, time_left );
3351     }
3352 }
3353 
operation_finish(player_activity * act,player * p)3354 void activity_handlers::operation_finish( player_activity *act, player *p )
3355 {
3356     map &here = get_map();
3357     if( act->str_values[3] == "true" ) {
3358         if( act->values[1] > 0 ) {
3359             add_msg( m_good,
3360                      _( "The Autodoc returns to its resting position after successfully performing the operation." ) );
3361             const std::list<tripoint> autodocs = here.find_furnitures_with_flag_in_radius( p->pos(), 1,
3362                                                  flag_AUTODOC );
3363             sounds::sound( autodocs.front(), 10, sounds::sound_t::music,
3364                            _( "a short upbeat jingle: \"Operation successful\"" ), true,
3365                            "Autodoc",
3366                            "success" );
3367         } else {
3368             add_msg( m_bad,
3369                      _( "The Autodoc jerks back to its resting position after failing the operation." ) );
3370             const std::list<tripoint> autodocs = here.find_furnitures_with_flag_in_radius( p->pos(), 1,
3371                                                  flag_AUTODOC );
3372             sounds::sound( autodocs.front(), 10, sounds::sound_t::music,
3373                            _( "a sad beeping noise: \"Operation failed\"" ), true,
3374                            "Autodoc",
3375                            "failure" );
3376         }
3377     } else {
3378         if( act->values[1] > 0 ) {
3379             add_msg( m_good,
3380                      _( "The operation is a success." ) );
3381         } else {
3382             add_msg( m_bad,
3383                      _( "The operation is a failure." ) );
3384         }
3385     }
3386     p->remove_effect( effect_under_operation );
3387     act->set_to_null();
3388 }
3389 
churn_finish(player_activity * act,player * p)3390 void activity_handlers::churn_finish( player_activity *act, player *p )
3391 {
3392     map &here = get_map();
3393     p->add_msg_if_player( _( "You finish churning up the earth here." ) );
3394     here.ter_set( here.getlocal( act->placement ), t_dirtmound );
3395     // Go back to what we were doing before
3396     // could be player zone activity, or could be NPC multi-farming
3397     act->set_to_null();
3398     resume_for_multi_activities( *p );
3399 }
3400 
plant_seed_finish(player_activity * act,player * p)3401 void activity_handlers::plant_seed_finish( player_activity *act, player *p )
3402 {
3403     map &here = get_map();
3404     tripoint examp = here.getlocal( act->placement );
3405     const itype_id seed_id( act->str_values[0] );
3406     std::list<item> used_seed;
3407     if( item::count_by_charges( seed_id ) ) {
3408         used_seed = p->use_charges( seed_id, 1 );
3409     } else {
3410         used_seed = p->use_amount( seed_id, 1 );
3411     }
3412     if( !used_seed.empty() ) {
3413         used_seed.front().set_age( 0_turns );
3414         if( used_seed.front().has_var( "activity_var" ) ) {
3415             used_seed.front().erase_var( "activity_var" );
3416         }
3417         used_seed.front().set_flag( json_flag_HIDDEN_ITEM );
3418         here.add_item_or_charges( examp, used_seed.front() );
3419         if( here.has_flag_furn( flag_PLANTABLE, examp ) ) {
3420             here.furn_set( examp, furn_str_id( here.furn( examp )->plant->transform ) );
3421         } else {
3422             here.set( examp, t_dirt, f_plant_seed );
3423         }
3424         p->add_msg_player_or_npc( _( "You plant some %s." ), _( "<npcname> plants some %s." ),
3425                                   item::nname( seed_id ) );
3426     }
3427     // Go back to what we were doing before
3428     // could be player zone activity, or could be NPC multi-farming
3429     act->set_to_null();
3430     resume_for_multi_activities( *p );
3431 }
3432 
build_do_turn(player_activity * act,player * p)3433 void activity_handlers::build_do_turn( player_activity *act, player *p )
3434 {
3435     map &here = get_map();
3436     partial_con *pc = here.partial_con_at( here.getlocal( act->placement ) );
3437     // Maybe the player and the NPC are working on the same construction at the same time
3438     if( !pc ) {
3439         if( p->is_npc() ) {
3440             // if player completes the work while NPC still in activity loop
3441             p->activity = player_activity();
3442             p->set_moves( 0 );
3443         } else {
3444             p->cancel_activity();
3445         }
3446         add_msg( m_info, _( "%s did not find an unfinished construction at the activity spot." ),
3447                  p->disp_name() );
3448         return;
3449     }
3450     // if you ( or NPC ) are finishing someone else's started construction...
3451     const construction &built = pc->id.obj();
3452     if( !p->has_trait( trait_DEBUG_HS ) && !p->meets_skill_requirements( built ) ) {
3453         add_msg( m_info, _( "%s can't work on this construction anymore." ), p->disp_name() );
3454         p->cancel_activity();
3455         if( p->is_npc() ) {
3456             p->activity = player_activity();
3457             p->set_moves( 0 );
3458         }
3459         return;
3460     }
3461     // item_counter represents the percent progress relative to the base batch time
3462     // stored precise to 5 decimal places ( e.g. 67.32 percent would be stored as 6732000 )
3463     const int old_counter = pc->counter;
3464 
3465     // Base moves for construction with no speed modifier or assistants
3466     // Must ensure >= 1 so we don't divide by 0;
3467     const double base_total_moves = std::max( 1, built.time );
3468     // Current expected total moves, includes construction speed modifiers and assistants
3469     const double cur_total_moves = std::max( 1, built.adjusted_time() );
3470     // Delta progress in moves adjusted for current crafting speed
3471     const double delta_progress = p->get_moves() * base_total_moves / cur_total_moves;
3472     // Current progress in moves
3473     const double current_progress = old_counter * base_total_moves / 10000000.0 +
3474                                     delta_progress;
3475     // Current progress as a percent of base_total_moves to 2 decimal places
3476     pc->counter = std::round( current_progress / base_total_moves * 10000000.0 );
3477 
3478     p->set_moves( 0 );
3479 
3480     pc->counter = std::min( pc->counter, 10000000 );
3481     // If construction_progress has reached 100% or more
3482     if( pc->counter >= 10000000 ) {
3483         // Activity is canceled in complete_construction()
3484         complete_construction( p );
3485     }
3486 }
3487 
tidy_up_do_turn(player_activity * act,player * p)3488 void activity_handlers::tidy_up_do_turn( player_activity *act, player *p )
3489 {
3490     generic_multi_activity_handler( *act, *p );
3491 }
3492 
multiple_fish_do_turn(player_activity * act,player * p)3493 void activity_handlers::multiple_fish_do_turn( player_activity *act, player *p )
3494 {
3495     generic_multi_activity_handler( *act, *p );
3496 }
3497 
multiple_construction_do_turn(player_activity * act,player * p)3498 void activity_handlers::multiple_construction_do_turn( player_activity *act, player *p )
3499 {
3500     generic_multi_activity_handler( *act, *p );
3501 }
3502 
multiple_mine_do_turn(player_activity * act,player * p)3503 void activity_handlers::multiple_mine_do_turn( player_activity *act, player *p )
3504 {
3505     generic_multi_activity_handler( *act, *p );
3506 }
3507 
multiple_chop_planks_do_turn(player_activity * act,player * p)3508 void activity_handlers::multiple_chop_planks_do_turn( player_activity *act, player *p )
3509 {
3510     generic_multi_activity_handler( *act, *p );
3511 }
3512 
multiple_butcher_do_turn(player_activity * act,player * p)3513 void activity_handlers::multiple_butcher_do_turn( player_activity *act, player *p )
3514 {
3515     generic_multi_activity_handler( *act, *p );
3516 }
3517 
vehicle_deconstruction_do_turn(player_activity * act,player * p)3518 void activity_handlers::vehicle_deconstruction_do_turn( player_activity *act, player *p )
3519 {
3520     generic_multi_activity_handler( *act, *p );
3521 }
3522 
vehicle_repair_do_turn(player_activity * act,player * p)3523 void activity_handlers::vehicle_repair_do_turn( player_activity *act, player *p )
3524 {
3525     generic_multi_activity_handler( *act, *p );
3526 }
3527 
chop_trees_do_turn(player_activity * act,player * p)3528 void activity_handlers::chop_trees_do_turn( player_activity *act, player *p )
3529 {
3530     generic_multi_activity_handler( *act, *p );
3531 }
3532 
multiple_farm_do_turn(player_activity * act,player * p)3533 void activity_handlers::multiple_farm_do_turn( player_activity *act, player *p )
3534 {
3535     generic_multi_activity_handler( *act, *p );
3536 }
3537 
fetch_do_turn(player_activity * act,player * p)3538 void activity_handlers::fetch_do_turn( player_activity *act, player *p )
3539 {
3540     generic_multi_activity_handler( *act, *p );
3541 }
3542 
vibe_finish(player_activity * act,player * p)3543 void activity_handlers::vibe_finish( player_activity *act, player *p )
3544 {
3545     p->add_msg_if_player( m_good, _( "You feel much better." ) );
3546     p->add_morale( MORALE_FEELING_GOOD, 10, 40 );
3547     act->set_to_null();
3548 }
3549 
atm_finish(player_activity * act,player *)3550 void activity_handlers::atm_finish( player_activity *act, player * )
3551 {
3552     // ATM sets index to 0 to indicate it's finished.
3553     if( !act->index ) {
3554         act->set_to_null();
3555     }
3556 }
3557 
eat_menu_finish(player_activity *,player *)3558 void activity_handlers::eat_menu_finish( player_activity *, player * )
3559 {
3560     // Only exists to keep the eat activity alive between turns
3561 }
3562 
hacksaw_do_turn(player_activity * act,player *)3563 void activity_handlers::hacksaw_do_turn( player_activity *act, player * )
3564 {
3565     sfx::play_activity_sound( "tool", "hacksaw", sfx::get_heard_volume( act->placement ) );
3566     if( calendar::once_every( 1_minutes ) ) {
3567         //~ Sound of a metal sawing tool at work!
3568         sounds::sound( act->placement, 15, sounds::sound_t::destructive_activity, _( "grnd grnd grnd" ) );
3569     }
3570 }
3571 
hacksaw_finish(player_activity * act,player * p)3572 void activity_handlers::hacksaw_finish( player_activity *act, player *p )
3573 {
3574     const tripoint &pos = act->placement;
3575     map &here = get_map();
3576     const ter_id ter = here.ter( pos );
3577 
3578     if( here.furn( pos ) == f_rack ) {
3579         here.furn_set( pos, f_null );
3580         here.spawn_item( p->pos(), itype_pipe, rng( 1, 3 ) );
3581         here.spawn_item( p->pos(), itype_steel_chunk );
3582     } else if( ter == t_chainfence || ter == t_chaingate_c || ter == t_chaingate_l ) {
3583         here.ter_set( pos, t_dirt );
3584         here.spawn_item( p->pos(), itype_pipe, 6 );
3585         here.spawn_item( p->pos(), itype_wire, 20 );
3586     } else if( ter == t_chainfence_posts ) {
3587         here.ter_set( pos, t_dirt );
3588         here.spawn_item( p->pos(), itype_pipe, 6 );
3589     } else if( ter == t_window_bars_alarm ) {
3590         here.ter_set( pos, t_window_alarm );
3591         here.spawn_item( p->pos(), itype_rebar, rng( 1, 8 ) );
3592     } else if( ter == t_window_bars_curtains || ter == t_window_bars_domestic ) {
3593         here.ter_set( pos, t_window_domestic );
3594         here.spawn_item( p->pos(), itype_rebar, rng( 1, 8 ) );
3595     } else if( ter == t_window_bars ) {
3596         here.ter_set( pos, t_window_empty );
3597         here.spawn_item( p->pos(), itype_rebar, rng( 1, 8 ) );
3598     } else if( ter == t_window_enhanced ) {
3599         here.ter_set( pos, t_window_reinforced );
3600         here.spawn_item( p->pos(), itype_spike, rng( 1, 4 ) );
3601     } else if( ter == t_window_enhanced_noglass ) {
3602         here.ter_set( pos, t_window_reinforced_noglass );
3603         here.spawn_item( p->pos(), itype_spike, rng( 1, 4 ) );
3604     } else if( ter == t_reb_cage ) {
3605         here.ter_set( pos, t_pit );
3606         here.spawn_item( p->pos(), itype_spike, 19 );
3607         here.spawn_item( p->pos(), itype_scrap, 8 );
3608     } else if( ter == t_bars ) {
3609         if( here.ter( pos + point_east ) == t_sewage || here.ter( pos + point_south )
3610             == t_sewage ||
3611             here.ter( pos + point_west ) == t_sewage || here.ter( pos + point_north ) ==
3612             t_sewage ) {
3613             here.ter_set( pos, t_sewage );
3614             here.spawn_item( p->pos(), itype_pipe, 3 );
3615         } else {
3616             here.ter_set( pos, t_floor );
3617             here.spawn_item( p->pos(), itype_pipe, 3 );
3618         }
3619     } else if( ter == t_door_bar_c || ter == t_door_bar_locked ) {
3620         here.ter_set( pos, t_mdoor_frame );
3621         here.spawn_item( p->pos(), itype_pipe, 12 );
3622     } else if( ter == t_metal_grate_window || ter == t_metal_grate_window_with_curtain ||
3623                ter == t_metal_grate_window_with_curtain_open ) {
3624         here.ter_set( pos, t_window_reinforced );
3625         here.spawn_item( p->pos(), itype_pipe, rng( 1, 12 ) );
3626         here.spawn_item( p->pos(), itype_sheet_metal, 4 );
3627     } else if( ter == t_metal_grate_window_noglass ||
3628                ter == t_metal_grate_window_with_curtain_noglass ||
3629                ter == t_metal_grate_window_with_curtain_open_noglass ) {
3630         here.ter_set( pos, t_window_reinforced_noglass );
3631         here.spawn_item( p->pos(), itype_pipe, rng( 1, 12 ) );
3632         here.spawn_item( p->pos(), itype_sheet_metal, 4 );
3633     }
3634 
3635     p->add_msg_if_player( m_good, _( "You finish cutting the metal." ) );
3636 
3637     act->set_to_null();
3638 }
3639 
pry_nails_do_turn(player_activity * act,player *)3640 void activity_handlers::pry_nails_do_turn( player_activity *act, player * )
3641 {
3642     sfx::play_activity_sound( "tool", "hammer", sfx::get_heard_volume( act->placement ) );
3643 }
3644 
pry_nails_finish(player_activity * act,player * p)3645 void activity_handlers::pry_nails_finish( player_activity *act, player *p )
3646 {
3647     const tripoint &pnt = act->placement;
3648     map &here = get_map();
3649     const ter_id type = here.ter( pnt );
3650 
3651     int nails = 0;
3652     int boards = 0;
3653     ter_id newter;
3654     if( type == t_fence ) {
3655         nails = 6;
3656         boards = 3;
3657         newter = t_fence_post;
3658         p->add_msg_if_player( _( "You pry out the fence post." ) );
3659     } else if( type == t_window_boarded ) {
3660         nails = 8;
3661         boards = 4;
3662         newter = t_window_frame;
3663         p->add_msg_if_player( _( "You pry the boards from the window." ) );
3664     } else if( type == t_window_boarded_noglass ) {
3665         nails = 8;
3666         boards = 4;
3667         newter = t_window_empty;
3668         p->add_msg_if_player( _( "You pry the boards from the window frame." ) );
3669     } else if( type == t_door_boarded || type == t_door_boarded_damaged ||
3670                type == t_rdoor_boarded || type == t_rdoor_boarded_damaged ||
3671                type == t_door_boarded_peep || type == t_door_boarded_damaged_peep ) {
3672         nails = 8;
3673         boards = 4;
3674         if( type == t_door_boarded ) {
3675             newter = t_door_c;
3676         } else if( type == t_door_boarded_damaged ) {
3677             newter = t_door_b;
3678         } else if( type == t_door_boarded_peep ) {
3679             newter = t_door_c_peep;
3680         } else if( type == t_door_boarded_damaged_peep ) {
3681             newter = t_door_b_peep;
3682         } else if( type == t_rdoor_boarded ) {
3683             newter = t_rdoor_c;
3684         } else { // if (type == t_rdoor_boarded_damaged)
3685             newter = t_rdoor_b;
3686         }
3687         p->add_msg_if_player( _( "You pry the boards from the door." ) );
3688     }
3689     p->practice( skill_fabrication, 1, 1 );
3690     here.spawn_item( p->pos(), itype_nail, 0, nails );
3691     here.spawn_item( p->pos(), itype_2x4, boards );
3692     here.ter_set( pnt, newter );
3693     act->set_to_null();
3694 }
3695 
chop_tree_do_turn(player_activity * act,player *)3696 void activity_handlers::chop_tree_do_turn( player_activity *act, player * )
3697 {
3698     map &here = get_map();
3699     sfx::play_activity_sound( "tool", "axe", sfx::get_heard_volume( here.getlocal( act->placement ) ) );
3700     if( calendar::once_every( 1_minutes ) ) {
3701         //~ Sound of a wood chopping tool at work!
3702         sounds::sound( here.getlocal( act->placement ), 15, sounds::sound_t::activity, _( "CHK!" ) );
3703     }
3704 }
3705 
chop_tree_finish(player_activity * act,player * p)3706 void activity_handlers::chop_tree_finish( player_activity *act, player *p )
3707 {
3708     map &here = get_map();
3709     const tripoint &pos = here.getlocal( act->placement );
3710 
3711     tripoint direction;
3712     if( !p->is_npc() ) {
3713         if( p->backlog.empty() || p->backlog.front().id() != ACT_MULTIPLE_CHOP_TREES ) {
3714             while( true ) {
3715                 if( const cata::optional<tripoint> dir = choose_direction(
3716                             _( "Select a direction for the tree to fall in." ) ) ) {
3717                     direction = *dir;
3718                     break;
3719                 }
3720                 // try again
3721             }
3722         }
3723     } else {
3724         for( const tripoint &elem : here.points_in_radius( pos, 1 ) ) {
3725             bool cantuse = false;
3726             tripoint direc = elem - pos;
3727             tripoint proposed_to = pos + point( 3 * direction.x, 3 * direction.y );
3728             std::vector<tripoint> rough_tree_line = line_to( pos, proposed_to );
3729             for( const tripoint &elem : rough_tree_line ) {
3730                 if( g->critter_at( elem ) ) {
3731                     cantuse = true;
3732                     break;
3733                 }
3734             }
3735             if( !cantuse ) {
3736                 direction = direc;
3737             }
3738         }
3739     }
3740 
3741     const tripoint to = pos + 3 * direction.xy() + point( rng( -1, 1 ), rng( -1, 1 ) );
3742     std::vector<tripoint> tree = line_to( pos, to, rng( 1, 8 ) );
3743     for( const tripoint &elem : tree ) {
3744         here.batter( elem, 300, 5 );
3745         here.ter_set( elem, t_trunk );
3746     }
3747 
3748     here.ter_set( pos, t_stump );
3749     p->add_msg_if_player( m_good, _( "You finish chopping down a tree." ) );
3750     // sound of falling tree
3751     sfx::play_variant_sound( "misc", "timber",
3752                              sfx::get_heard_volume( here.getlocal( act->placement ) ) );
3753     get_event_bus().send<event_type::cuts_tree>( p->getID() );
3754     act->set_to_null();
3755     resume_for_multi_activities( *p );
3756 }
3757 
chop_logs_finish(player_activity * act,player * p)3758 void activity_handlers::chop_logs_finish( player_activity *act, player *p )
3759 {
3760     map &here = get_map();
3761     const tripoint &pos = here.getlocal( act->placement );
3762     int log_quan;
3763     int stick_quan;
3764     int splint_quan;
3765     if( here.ter( pos ) == t_trunk ) {
3766         log_quan = rng( 2, 3 );
3767         stick_quan = rng( 0, 1 );
3768         splint_quan = 0;
3769     } else if( here.ter( pos ) == t_stump ) {
3770         log_quan = rng( 0, 2 );
3771         stick_quan = 0;
3772         splint_quan = rng( 5, 15 );
3773     } else {
3774         log_quan = 0;
3775         stick_quan = 0;
3776         splint_quan = 0;
3777     }
3778     for( int i = 0; i != log_quan; ++i ) {
3779         item obj( itype_log, calendar::turn );
3780         obj.set_var( "activity_var", p->name );
3781         here.add_item_or_charges( pos, obj );
3782     }
3783     for( int i = 0; i != stick_quan; ++i ) {
3784         item obj( itype_stick_long, calendar::turn );
3785         obj.set_var( "activity_var", p->name );
3786         here.add_item_or_charges( pos, obj );
3787     }
3788     for( int i = 0; i != splint_quan; ++i ) {
3789         item obj( itype_splinter, calendar::turn );
3790         obj.set_var( "activity_var", p->name );
3791         here.add_item_or_charges( pos, obj );
3792     }
3793     here.ter_set( pos, t_dirt );
3794     p->add_msg_if_player( m_good, _( "You finish chopping wood." ) );
3795 
3796     act->set_to_null();
3797     resume_for_multi_activities( *p );
3798 }
3799 
chop_planks_finish(player_activity * act,player * p)3800 void activity_handlers::chop_planks_finish( player_activity *act, player *p )
3801 {
3802     const int max_planks = 10;
3803     /** @EFFECT_FABRICATION increases number of planks cut from a log */
3804     int planks = normal_roll( 2 + p->get_skill_level( skill_id( "fabrication" ) ), 1 );
3805     int wasted_planks = max_planks - planks;
3806     int scraps = rng( wasted_planks, wasted_planks * 3 );
3807     planks = std::min( planks, max_planks );
3808 
3809     map &here = get_map();
3810     if( planks > 0 ) {
3811         here.spawn_item( here.getlocal( act->placement ), itype_2x4, planks, 0, calendar::turn );
3812         p->add_msg_if_player( m_good, _( "You produce %d planks." ), planks );
3813     }
3814     if( scraps > 0 ) {
3815         here.spawn_item( here.getlocal( act->placement ), itype_splinter, scraps, 0, calendar::turn );
3816         p->add_msg_if_player( m_good, _( "You produce %d splinters." ), scraps );
3817     }
3818     if( planks < max_planks / 2 ) {
3819         p->add_msg_if_player( m_bad, _( "You waste a lot of the wood." ) );
3820     }
3821     act->set_to_null();
3822     resume_for_multi_activities( *p );
3823 }
3824 
jackhammer_do_turn(player_activity * act,player *)3825 void activity_handlers::jackhammer_do_turn( player_activity *act, player * )
3826 {
3827     map &here = get_map();
3828     sfx::play_activity_sound( "tool", "jackhammer",
3829                               sfx::get_heard_volume( here.getlocal( act->placement ) ) );
3830     if( calendar::once_every( 1_minutes ) ) {
3831         sounds::sound( here.getlocal( act->placement ), 15, sounds::sound_t::destructive_activity,
3832                        //~ Sound of a jackhammer at work!
3833                        _( "TATATATATATATAT!" ) );
3834     }
3835 }
3836 
jackhammer_finish(player_activity * act,player * p)3837 void activity_handlers::jackhammer_finish( player_activity *act, player *p )
3838 {
3839     map &here = get_map();
3840     const tripoint &pos = here.getlocal( act->placement );
3841 
3842     here.destroy( pos, true );
3843 
3844     p->add_msg_player_or_npc( m_good,
3845                               _( "You finish drilling." ),
3846                               _( "<npcname> finishes drilling." ) );
3847     act->set_to_null();
3848     if( !act->targets.empty() ) {
3849         item &it = *act->targets.front();
3850         p->consume_charges( it, it.ammo_required() );
3851     } else {
3852         debugmsg( "jackhammer activity targets empty" );
3853     }
3854     if( resume_for_multi_activities( *p ) ) {
3855         for( item &elem : here.i_at( pos ) ) {
3856             elem.set_var( "activity_var", p->name );
3857         }
3858     }
3859 }
3860 
fill_pit_do_turn(player_activity * act,player *)3861 void activity_handlers::fill_pit_do_turn( player_activity *act, player * )
3862 {
3863     sfx::play_activity_sound( "tool", "shovel", 100 );
3864     if( calendar::once_every( 1_minutes ) ) {
3865         //~ Sound of a shovel filling a pit or mound at work!
3866         sounds::sound( act->placement, 10, sounds::sound_t::activity, _( "hsh!" ) );
3867     }
3868 }
3869 
fill_pit_finish(player_activity * act,player * p)3870 void activity_handlers::fill_pit_finish( player_activity *act, player *p )
3871 {
3872     const tripoint &pos = act->placement;
3873     map &here = get_map();
3874     const ter_id ter = here.ter( pos );
3875     const ter_id old_ter = ter;
3876 
3877     if( ter == t_pit || ter == t_pit_spiked || ter == t_pit_glass ||
3878         ter == t_pit_corpsed ) {
3879         here.ter_set( pos, t_pit_shallow );
3880     } else {
3881         here.ter_set( pos, t_dirt );
3882     }
3883     p->add_msg_if_player( m_good, _( "You finish filling up %s." ), old_ter.obj().name() );
3884 
3885     act->set_to_null();
3886 }
3887 
play_with_pet_finish(player_activity * act,player * p)3888 void activity_handlers::play_with_pet_finish( player_activity *act, player *p )
3889 {
3890     p->add_morale( MORALE_PLAY_WITH_PET, rng( 3, 10 ), 10, 5_hours, 25_minutes );
3891     p->add_msg_if_player( m_good, _( "Playing with your %s has lifted your spirits a bit." ),
3892                           act->str_values[0] );
3893     act->set_to_null();
3894 }
3895 
shaving_finish(player_activity * act,player * p)3896 void activity_handlers::shaving_finish( player_activity *act, player *p )
3897 {
3898     p->add_msg_if_player( _( "You open up your kit and shave." ) );
3899     p->add_morale( MORALE_SHAVE, 8, 8, 240_minutes, 3_minutes );
3900     act->set_to_null();
3901 }
3902 
haircut_finish(player_activity * act,player * p)3903 void activity_handlers::haircut_finish( player_activity *act, player *p )
3904 {
3905     p->add_msg_if_player( _( "You give your hair a trim." ) );
3906     p->add_morale( MORALE_HAIRCUT, 3, 3, 480_minutes, 3_minutes );
3907     act->set_to_null();
3908 }
3909 
get_sorted_tiles_by_distance(const tripoint & abspos,const std::unordered_set<tripoint> & tiles)3910 std::vector<tripoint> get_sorted_tiles_by_distance( const tripoint &abspos,
3911         const std::unordered_set<tripoint> &tiles )
3912 {
3913     const auto cmp = [abspos]( tripoint a, tripoint b ) {
3914         const int da = rl_dist( abspos, a );
3915         const int db = rl_dist( abspos, b );
3916 
3917         return da < db;
3918     };
3919 
3920     std::vector<tripoint> sorted( tiles.begin(), tiles.end() );
3921     std::sort( sorted.begin(), sorted.end(), cmp );
3922 
3923     return sorted;
3924 }
3925 
3926 template<typename fn>
cleanup_tiles(std::unordered_set<tripoint> & tiles,fn & cleanup)3927 static void cleanup_tiles( std::unordered_set<tripoint> &tiles, fn &cleanup )
3928 {
3929     auto it = tiles.begin();
3930     map &here = get_map();
3931     while( it != tiles.end() ) {
3932         auto current = it++;
3933 
3934         const tripoint &tile_loc = here.getlocal( *current );
3935 
3936         if( cleanup( tile_loc ) ) {
3937             tiles.erase( current );
3938         }
3939     }
3940 }
3941 
perform_zone_activity_turn(player * p,const zone_type_id & ztype,const std::function<bool (const tripoint &)> & tile_filter,const std::function<void (player & p,const tripoint &)> & tile_action,const std::string & finished_msg)3942 static void perform_zone_activity_turn( player *p,
3943                                         const zone_type_id &ztype,
3944                                         const std::function<bool( const tripoint & )> &tile_filter,
3945                                         const std::function<void ( player &p, const tripoint & )> &tile_action,
3946                                         const std::string &finished_msg )
3947 {
3948     const zone_manager &mgr = zone_manager::get_manager();
3949     map &here = get_map();
3950     const tripoint abspos = here.getabs( p->pos() );
3951     std::unordered_set<tripoint> unsorted_tiles = mgr.get_near( ztype, abspos );
3952 
3953     cleanup_tiles( unsorted_tiles, tile_filter );
3954 
3955     // sort remaining tiles by distance
3956     const std::vector<tripoint> &tiles = get_sorted_tiles_by_distance( abspos, unsorted_tiles );
3957 
3958     for( const tripoint &tile : tiles ) {
3959         const tripoint &tile_loc = here.getlocal( tile );
3960 
3961         std::vector<tripoint> route = here.route( p->pos(), tile_loc, p->get_pathfinding_settings(),
3962                                       p->get_path_avoid() );
3963         if( route.size() > 1 ) {
3964             route.pop_back();
3965 
3966             p->set_destination( route, p->activity );
3967             p->activity.set_to_null();
3968             return;
3969         } else {
3970             // we are at destination already
3971             /* Perform action */
3972             tile_action( *p, tile_loc );
3973             if( p->moves <= 0 ) {
3974                 return;
3975             }
3976         }
3977     }
3978     add_msg( m_info, finished_msg );
3979     p->activity.set_to_null();
3980 }
3981 
fertilize_plot_do_turn(player_activity * act,player * p)3982 void activity_handlers::fertilize_plot_do_turn( player_activity *act, player *p )
3983 {
3984     itype_id fertilizer;
3985     auto check_fertilizer = [&]( bool ask_user = true ) -> void {
3986         if( act->str_values.empty() )
3987         {
3988             act->str_values.push_back( "" );
3989         }
3990         fertilizer = itype_id( act->str_values[0] );
3991 
3992         /* If unspecified, or if we're out of what we used before, ask */
3993         if( ask_user && ( fertilizer.is_empty() || !p->has_charges( fertilizer, 1 ) ) )
3994         {
3995             fertilizer = iexamine::choose_fertilizer( *p, "plant",
3996                     false /* Don't confirm action with player */ );
3997             act->str_values[0] = fertilizer.str();
3998         }
3999     };
4000 
4001     auto have_fertilizer = [&]() {
4002         return !fertilizer.is_empty() && p->has_charges( fertilizer, 1 );
4003     };
4004 
4005     const auto reject_tile = [&]( const tripoint & tile ) {
4006         check_fertilizer();
4007         ret_val<bool> can_fert = iexamine::can_fertilize( *p, tile, fertilizer );
4008         return !can_fert.success();
4009     };
4010 
4011     const auto fertilize = [&]( player & p, const tripoint & tile ) {
4012         check_fertilizer();
4013         if( have_fertilizer() ) {
4014             iexamine::fertilize_plant( p, tile, fertilizer );
4015             if( !have_fertilizer() ) {
4016                 add_msg( m_info, _( "You have run out of %s." ), item::nname( fertilizer ) );
4017             }
4018         }
4019     };
4020 
4021     check_fertilizer();
4022     if( !have_fertilizer() ) {
4023         act->set_to_null();
4024         return;
4025     }
4026 
4027     perform_zone_activity_turn( p,
4028                                 zone_type_FARM_PLOT,
4029                                 reject_tile,
4030                                 fertilize,
4031                                 _( "You fertilized every plot you could." ) );
4032 }
4033 
robot_control_do_turn(player_activity * act,player * p)4034 void activity_handlers::robot_control_do_turn( player_activity *act, player *p )
4035 {
4036     if( act->monsters.empty() ) {
4037         debugmsg( "No monster assigned in ACT_ROBOT_CONTROL" );
4038         act->set_to_null();
4039         return;
4040     }
4041     const shared_ptr_fast<monster> z = act->monsters[0].lock();
4042 
4043     if( !z || !iuse::robotcontrol_can_target( p, *z ) ) {
4044         p->add_msg_if_player( _( "Target lost.  IFF override failed." ) );
4045         act->set_to_null();
4046         return;
4047     }
4048 
4049     // TODO: Add some kind of chance of getting the target's attention
4050 }
4051 
robot_control_finish(player_activity * act,player * p)4052 void activity_handlers::robot_control_finish( player_activity *act, player *p )
4053 {
4054     act->set_to_null();
4055 
4056     if( act->monsters.empty() ) {
4057         debugmsg( "No monster assigned in ACT_ROBOT_CONTROL" );
4058         return;
4059     }
4060 
4061     shared_ptr_fast<monster> z = act->monsters[0].lock();
4062     act->monsters.clear();
4063 
4064     if( !z || !iuse::robotcontrol_can_target( p, *z ) ) {
4065         p->add_msg_if_player( _( "Target lost.  IFF override failed." ) );
4066         return;
4067     }
4068 
4069     p->add_msg_if_player( _( "You unleash your override attack on the %s." ), z->name() );
4070 
4071     /** @EFFECT_INT increases chance of successful robot reprogramming, vs difficulty */
4072     /** @EFFECT_COMPUTER increases chance of successful robot reprogramming, vs difficulty */
4073     const int computer_skill = p->get_skill_level( skill_id( "computer" ) );
4074     const float randomized_skill = rng( 2, p->int_cur ) + computer_skill;
4075     float success = computer_skill - 3 * z->type->difficulty / randomized_skill;
4076     if( z->has_flag( MF_RIDEABLE_MECH ) ) {
4077         success = randomized_skill - rng( 1, 11 );
4078     }
4079     // rideable mechs are not hostile, they have no AI, they do not resist control as much.
4080     if( success >= 0 ) {
4081         p->add_msg_if_player( _( "You successfully override the %s's IFF protocols!" ),
4082                               z->name() );
4083         z->friendly = -1;
4084         if( z->has_flag( MF_RIDEABLE_MECH ) ) {
4085             z->add_effect( effect_pet, 1_turns, true );
4086         }
4087     } else if( success >= -2 ) {
4088         //A near success
4089         p->add_msg_if_player( _( "The %s short circuits as you attempt to reprogram it!" ), z->name() );
4090         //damage it a little
4091         z->apply_damage( p, bodypart_id( "torso" ), rng( 1, 10 ) );
4092         if( z->is_dead() ) {
4093             p->practice( skill_id( "computer" ), 10 );
4094             // Do not do the other effects if the robot died
4095             return;
4096         }
4097         if( one_in( 3 ) ) {
4098             p->add_msg_if_player( _( "…and turns friendly!" ) );
4099             //did the robot became friendly permanently?
4100             if( one_in( 3 ) ) {
4101                 //it did
4102                 z->friendly = -1;
4103             } else {
4104                 // it didn't
4105                 z->friendly = rng( 5, 40 );
4106             }
4107         }
4108     } else {
4109         p->add_msg_if_player( _( "…but the robot refuses to acknowledge you as an ally!" ) );
4110     }
4111     p->practice( skill_computer, 10 );
4112 }
4113 
tree_communion_do_turn(player_activity * act,player * p)4114 void activity_handlers::tree_communion_do_turn( player_activity *act, player *p )
4115 {
4116     // There's an initial rooting process.
4117     if( act->values.front() > 0 ) {
4118         act->values.front() -= 1;
4119         if( act->values.front() == 0 ) {
4120             if( p->has_trait( trait_id( trait_SPIRITUAL ) ) ) {
4121                 p->add_msg_if_player( m_good, _( "The ancient tree spirits answer your call." ) );
4122             } else {
4123                 p->add_msg_if_player( m_good, _( "Your communion with the trees has begun." ) );
4124             }
4125         }
4126         return;
4127     }
4128     // Information is received every minute.
4129     if( !calendar::once_every( 1_minutes ) ) {
4130         return;
4131     }
4132     // Breadth-first search forest tiles until one reveals new overmap tiles.
4133     std::queue<tripoint_abs_omt> q;
4134     std::unordered_set<tripoint_abs_omt> seen;
4135     tripoint_abs_omt loc = p->global_omt_location();
4136     q.push( loc );
4137     seen.insert( loc );
4138     const std::function<bool( const oter_id & )> filter = []( const oter_id & ter ) {
4139         return ter.obj().is_wooded() || ter.obj().get_name() == "field";
4140     };
4141     while( !q.empty() ) {
4142         tripoint_abs_omt tpt = q.front();
4143         if( overmap_buffer.reveal( tpt, 3, filter ) ) {
4144             if( p->has_trait( trait_SPIRITUAL ) ) {
4145                 p->add_morale( MORALE_TREE_COMMUNION, 2, 30, 8_hours, 6_hours );
4146             } else {
4147                 p->add_morale( MORALE_TREE_COMMUNION, 1, 15, 2_hours, 1_hours );
4148             }
4149             if( one_in( 128 ) ) {
4150                 p->add_msg_if_player( "%s", SNIPPET.random_from_category( "tree_communion" ).value_or(
4151                                           translation() ) );
4152             }
4153             return;
4154         }
4155         for( const tripoint_abs_omt &neighbor : points_in_radius( tpt, 1 ) ) {
4156             if( seen.find( neighbor ) != seen.end() ) {
4157                 continue;
4158             }
4159             seen.insert( neighbor );
4160             if( !overmap_buffer.ter( neighbor ).obj().is_wooded() ) {
4161                 continue;
4162             }
4163             q.push( neighbor );
4164         }
4165         q.pop();
4166     }
4167     p->add_msg_if_player( m_info, _( "The trees have shown you what they will." ) );
4168     act->set_to_null();
4169 }
4170 
blood_magic(player * p,int cost)4171 static void blood_magic( player *p, int cost )
4172 {
4173     std::vector<uilist_entry> uile;
4174     std::vector<bodypart_id> parts;
4175     int i = 0;
4176     for( const bodypart_id &bp : p->get_all_body_parts( get_body_part_flags::only_main ) ) {
4177         const int hp_cur = p->get_part_hp_cur( bp );
4178         uilist_entry entry( i, hp_cur > cost, i + 49, body_part_hp_bar_ui_text( bp ) );
4179 
4180         const std::pair<std::string, nc_color> &hp = get_hp_bar( hp_cur, p->get_part_hp_max( bp ) );
4181         entry.ctxt = colorize( hp.first, hp.second );
4182         uile.emplace_back( entry );
4183         parts.push_back( bp );
4184         i++;
4185     }
4186     int action = -1;
4187     while( action < 0 ) {
4188         action = uilist( _( "Choose part\nto draw blood from." ), uile );
4189     }
4190     p->mod_part_hp_cur( parts[action], - cost );
4191     p->mod_pain( std::max( 1, cost / 3 ) );
4192 }
4193 
spellcasting_finish(player_activity * act,player * p)4194 void activity_handlers::spellcasting_finish( player_activity *act, player *p )
4195 {
4196     act->set_to_null();
4197     const int level_override = act->get_value( 0 );
4198     spell_id sp( act->name );
4199 
4200     // if level is -1 then we know it's a player spell, otherwise we build it from the ground up
4201     spell temp_spell( sp );
4202     spell &spell_being_cast = ( level_override == -1 ) ? p->magic->get_spell( sp ) : temp_spell;
4203 
4204     // if level != 1 then we need to set the spell's level
4205     if( level_override != -1 ) {
4206         spell_being_cast.set_level( level_override );
4207     }
4208 
4209     const bool no_fail = act->get_value( 1 ) == 1;
4210     const bool no_mana = act->get_value( 2 ) == 0;
4211 
4212     // choose target for spell (if the spell has a range > 0)
4213     tripoint target = p->pos();
4214     bool target_is_valid = false;
4215     if( spell_being_cast.range() > 0 && !spell_being_cast.is_valid_target( spell_target::none ) &&
4216         !spell_being_cast.has_flag( spell_flag::RANDOM_TARGET ) ) {
4217         do {
4218             avatar &you = *p->as_avatar();
4219             std::vector<tripoint> trajectory = target_handler::mode_spell( you, spell_being_cast, no_fail,
4220                                                no_mana );
4221             if( !trajectory.empty() ) {
4222                 target = trajectory.back();
4223                 target_is_valid = spell_being_cast.is_valid_target( *p, target );
4224                 if( !( spell_being_cast.is_valid_target( spell_target::ground ) || p->sees( target ) ) ) {
4225                     target_is_valid = false;
4226                 }
4227             } else {
4228                 target_is_valid = false;
4229             }
4230             if( !target_is_valid ) {
4231                 if( query_yn( _( "Stop casting spell?  Time spent will be lost." ) ) ) {
4232                     return;
4233                 }
4234             }
4235         } while( !target_is_valid );
4236     } else if( spell_being_cast.has_flag( spell_flag::RANDOM_TARGET ) ) {
4237         const cata::optional<tripoint> target_ = spell_being_cast.random_valid_target( *p, p->pos() );
4238         if( !target_ ) {
4239             p->add_msg_if_player( game_message_params{ m_bad, gmf_bypass_cooldown },
4240                                   _( "Your spell can't find a suitable target." ) );
4241             return;
4242         }
4243         target = *target_;
4244     }
4245 
4246     // no turning back now. it's all said and done.
4247     bool success = no_fail || rng_float( 0.0f, 1.0f ) >= spell_being_cast.spell_fail( *p );
4248     int exp_gained = spell_being_cast.casting_exp( *p );
4249     if( !success ) {
4250         p->add_msg_if_player( game_message_params{ m_bad, gmf_bypass_cooldown },
4251                               _( "You lose your concentration!" ) );
4252         if( !spell_being_cast.is_max_level() && level_override == -1 ) {
4253             // still get some experience for trying
4254             spell_being_cast.gain_exp( exp_gained / 5 );
4255             p->add_msg_if_player( m_good, _( "You gain %i experience.  New total %i." ), exp_gained / 5,
4256                                   spell_being_cast.xp() );
4257         }
4258         return;
4259     }
4260 
4261     if( spell_being_cast.has_flag( spell_flag::VERBAL ) ) {
4262         sounds::sound( p->pos(), p->get_shout_volume() / 2, sounds::sound_t::speech, _( "cast a spell" ),
4263                        false );
4264     }
4265 
4266     p->add_msg_if_player( spell_being_cast.message(), spell_being_cast.name() );
4267 
4268     spell_being_cast.cast_all_effects( *p, target );
4269 
4270     if( !no_mana ) {
4271         // pay the cost
4272         int cost = spell_being_cast.energy_cost( *p );
4273         switch( spell_being_cast.energy_source() ) {
4274             case magic_energy_type::mana:
4275                 p->magic->mod_mana( *p, -cost );
4276                 break;
4277             case magic_energy_type::stamina:
4278                 p->mod_stamina( -cost );
4279                 break;
4280             case magic_energy_type::bionic:
4281                 p->mod_power_level( -units::from_kilojoule( cost ) );
4282                 break;
4283             case magic_energy_type::hp:
4284                 blood_magic( p, cost );
4285                 break;
4286             case magic_energy_type::fatigue:
4287                 p->mod_fatigue( cost );
4288                 break;
4289             case magic_energy_type::none:
4290             default:
4291                 break;
4292         }
4293 
4294         spell_being_cast.use_components( *p );
4295     }
4296     if( level_override == -1 ) {
4297         if( !spell_being_cast.is_max_level() ) {
4298             // reap the reward
4299             int old_level = spell_being_cast.get_level();
4300             if( old_level == 0 ) {
4301                 spell_being_cast.gain_level();
4302                 p->add_msg_if_player( m_good,
4303                                       _( "Something about how this spell works just clicked!  You gained a level!" ) );
4304             } else {
4305                 spell_being_cast.gain_exp( exp_gained );
4306                 p->add_msg_if_player( m_good, _( "You gain %i experience.  New total %i." ), exp_gained,
4307                                       spell_being_cast.xp() );
4308             }
4309             if( spell_being_cast.get_level() != old_level ) {
4310                 // Level 0-1 message is printed above - notify player when leveling up further
4311                 if( old_level > 0 ) {
4312                     p->add_msg_if_player( m_good, _( "You gained a level in %s!" ), spell_being_cast.name() );
4313                 }
4314             }
4315         }
4316     }
4317 }
4318 
study_spell_do_turn(player_activity * act,player * p)4319 void activity_handlers::study_spell_do_turn( player_activity *act, player *p )
4320 {
4321     // Stop if there is not enough light to study
4322     if( p->fine_detail_vision_mod() > 4 ) {
4323         act->values[2] = -1;
4324         act->moves_left = 0;
4325         return;
4326     }
4327     // str_value 1 is "study" if we already know the spell, and want to study it more
4328     if( act->get_str_value( 1 ) == "study" ) {
4329         spell &studying = p->magic->get_spell( spell_id( act->name ) );
4330         // If we are studying to gain a level, keep studying until level changes
4331         if( act->get_str_value( 0 ) == "gain_level" ) {
4332             if( studying.get_level() < act->get_value( 1 ) ) {
4333                 act->moves_left = 1000000;
4334             } else {
4335                 act->moves_left = 0;
4336             }
4337         }
4338         const int old_level = studying.get_level();
4339         // Gain some experience from studying
4340         const int xp = roll_remainder( studying.exp_modifier( *p ) / to_turns<float>( 6_seconds ) );
4341         act->values[0] += xp;
4342         studying.gain_exp( xp );
4343         p->practice( studying.skill(), xp, studying.get_difficulty() );
4344 
4345         // Notify player if the spell leveled up
4346         if( studying.get_level() > old_level ) {
4347             p->add_msg_if_player( m_good, _( "You gained a level in %s!" ), studying.name() );
4348         }
4349     }
4350 }
4351 
study_spell_finish(player_activity * act,player * p)4352 void activity_handlers::study_spell_finish( player_activity *act, player *p )
4353 {
4354     act->set_to_null();
4355     const int total_exp_gained = act->get_value( 0 );
4356 
4357     if( act->get_str_value( 1 ) == "study" ) {
4358         p->add_msg_if_player( m_good, _( "You gained %i experience from your study session." ),
4359                               total_exp_gained );
4360     } else if( act->get_str_value( 1 ) == "learn" && act->values[2] == 0 ) {
4361         p->magic->learn_spell( act->name, *p );
4362     }
4363     if( act->values[2] == -1 ) {
4364         p->add_msg_if_player( m_bad, _( "It's too dark to read." ) );
4365     }
4366 }
4367 
4368 //This is just used for robofac_intercom_mission_2
mind_splicer_finish(player_activity * act,player * p)4369 void activity_handlers::mind_splicer_finish( player_activity *act, player *p )
4370 {
4371     act->set_to_null();
4372 
4373     if( act->targets.size() != 1 || !act->targets[0] ) {
4374         debugmsg( "Incompatible arguments to: activity_handlers::mind_splicer_finish" );
4375         return;
4376     }
4377     item &data_card = *act->targets[0];
4378     p->add_msg_if_player( m_info, _( "…you finally find the memory banks." ) );
4379     p->add_msg_if_player( m_info, _( "The kit makes a copy of the data inside the bionic." ) );
4380     data_card.contents.clear_items();
4381     data_card.put_in( item( itype_mind_scan_robofac ), item_pocket::pocket_type::SOFTWARE );
4382 }
4383