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