1 #include "crafting.h"
2 
3 #include <algorithm>
4 #include <climits>
5 #include <cmath>
6 #include <cstdint>
7 #include <cstdlib>
8 #include <functional>
9 #include <iosfwd>
10 #include <limits>
11 #include <map>
12 #include <memory>
13 #include <new>
14 #include <set>
15 #include <string>
16 #include <tuple>
17 #include <type_traits>
18 #include <utility>
19 #include <vector>
20 
21 #include "activity_actor_definitions.h"
22 #include "activity_handlers.h"
23 #include "avatar.h"
24 #include "bionics.h"
25 #include "calendar.h"
26 #include "cata_assert.h"
27 #include "cata_utility.h"
28 #include "character.h"
29 #include "colony.h"
30 #include "color.h"
31 #include "craft_command.h"
32 #include "crafting_gui.h"
33 #include "debug.h"
34 #include "enum_traits.h"
35 #include "enums.h"
36 #include "faction.h"
37 #include "flag.h"
38 #include "game.h"
39 #include "game_constants.h"
40 #include "game_inventory.h"
41 #include "handle_liquid.h"
42 #include "inventory.h"
43 #include "item.h"
44 #include "item_contents.h"
45 #include "item_location.h"
46 #include "item_pocket.h"
47 #include "item_stack.h"
48 #include "itype.h"
49 #include "iuse.h"
50 #include "line.h"
51 #include "map.h"
52 #include "map_iterator.h"
53 #include "map_selector.h"
54 #include "mapdata.h"
55 #include "messages.h"
56 #include "mutation.h"
57 #include "npc.h"
58 #include "optional.h"
59 #include "options.h"
60 #include "output.h"
61 #include "pimpl.h"
62 #include "player_activity.h"
63 #include "point.h"
64 #include "proficiency.h"
65 #include "recipe.h"
66 #include "recipe_dictionary.h"
67 #include "requirements.h"
68 #include "ret_val.h"
69 #include "rng.h"
70 #include "string_formatter.h"
71 #include "string_input_popup.h"
72 #include "translations.h"
73 #include "type_id.h"
74 #include "ui.h"
75 #include "units.h"
76 #include "value_ptr.h"
77 #include "veh_type.h"
78 #include "vehicle.h"
79 #include "vehicle_selector.h"
80 #include "visitable.h"
81 #include "vpart_position.h"
82 #include "weather.h"
83 
84 static const activity_id ACT_DISASSEMBLE( "ACT_DISASSEMBLE" );
85 
86 static const efftype_id effect_contacts( "contacts" );
87 
88 static const itype_id itype_disassembly( "disassembly" );
89 static const itype_id itype_plut_cell( "plut_cell" );
90 
91 static const skill_id skill_electronics( "electronics" );
92 static const skill_id skill_tailor( "tailor" );
93 
94 static const trait_id trait_BURROW( "BURROW" );
95 static const trait_id trait_DEBUG_CNF( "DEBUG_CNF" );
96 static const trait_id trait_DEBUG_HS( "DEBUG_HS" );
97 static const trait_id trait_HYPEROPIC( "HYPEROPIC" );
98 
99 static const std::string flag_BLIND_EASY( "BLIND_EASY" );
100 static const std::string flag_BLIND_HARD( "BLIND_HARD" );
101 static const std::string flag_FULL_MAGAZINE( "FULL_MAGAZINE" );
102 static const std::string flag_NO_RESIZE( "NO_RESIZE" );
103 static const std::string flag_UNCRAFT_LIQUIDS_CONTAINED( "UNCRAFT_LIQUIDS_CONTAINED" );
104 static const std::string flag_UNCRAFT_SINGLE_CHARGE( "UNCRAFT_SINGLE_CHARGE" );
105 
106 class basecamp;
107 
crafting_allowed(const Character & p,const recipe & rec)108 static bool crafting_allowed( const Character &p, const recipe &rec )
109 {
110     if( p.morale_crafting_speed_multiplier( rec ) <= 0.0f ) {
111         add_msg( m_info, _( "Your morale is too low to craft such a difficult thing…" ) );
112         return false;
113     }
114 
115     if( p.lighting_craft_speed_multiplier( rec ) <= 0.0f ) {
116         add_msg( m_info, _( "You can't see to craft!" ) );
117         return false;
118     }
119 
120     if( rec.category == "CC_BUILDING" ) {
121         add_msg( m_info, _( "Overmap terrain building recipes are not implemented yet!" ) );
122         return false;
123     }
124     return true;
125 }
126 
lighting_craft_speed_multiplier(const recipe & rec) const127 float Character::lighting_craft_speed_multiplier( const recipe &rec ) const
128 {
129     // negative is bright, 0 is just bright enough, positive is dark, +7.0f is pitch black
130     float darkness = fine_detail_vision_mod() - 4.0f;
131     if( darkness <= 0.0f ) {
132         return 1.0f; // it's bright, go for it
133     }
134     bool rec_blind = rec.has_flag( flag_BLIND_HARD ) || rec.has_flag( flag_BLIND_EASY );
135     if( darkness > 0 && !rec_blind ) {
136         return 0.0f; // it's dark and this recipe can't be crafted in the dark
137     }
138     if( rec.has_flag( flag_BLIND_EASY ) ) {
139         // 100% speed in well lit area at skill+0
140         // 25% speed in pitch black at skill+0
141         // skill+2 removes speed penalty
142         return 1.0f - ( darkness / ( 7.0f / 0.75f ) ) * std::max( 0,
143                 2 - exceeds_recipe_requirements( rec ) ) / 2.0f;
144     }
145     if( rec.has_flag( flag_BLIND_HARD ) && exceeds_recipe_requirements( rec ) >= 2 ) {
146         // 100% speed in well lit area at skill+2
147         // 25% speed in pitch black at skill+2
148         // skill+8 removes speed penalty
149         return 1.0f - ( darkness / ( 7.0f / 0.75f ) ) * std::max( 0,
150                 8 - exceeds_recipe_requirements( rec ) ) / 6.0f;
151     }
152     return 0.0f; // it's dark and you could craft this if you had more skill
153 }
154 
morale_crafting_speed_multiplier(const recipe & rec) const155 float Character::morale_crafting_speed_multiplier( const recipe &rec ) const
156 {
157     int morale = get_morale_level();
158     if( morale >= 0 ) {
159         // No bonus for being happy yet
160         return 1.0f;
161     }
162 
163     // Harder jobs are more frustrating, even when skilled
164     // For each skill where skill=difficulty, multiply effective morale by 200%
165     float morale_mult = std::max( 1.0f, 2.0f * rec.difficulty / std::max( 1,
166                                   get_skill_level( rec.skill_used ) ) );
167     for( const auto &pr : rec.required_skills ) {
168         morale_mult *= std::max( 1.0f, 2.0f * pr.second / std::max( 1, get_skill_level( pr.first ) ) );
169     }
170 
171     // Halve speed at -50 effective morale, quarter at -150
172     float morale_effect = 1.0f + ( morale_mult * morale ) / -50.0f;
173 
174     return 1.0f / morale_effect;
175 }
176 
177 template<typename T>
lerped_multiplier(const T & value,const T & low,const T & high)178 static float lerped_multiplier( const T &value, const T &low, const T &high )
179 {
180     // No effect if less than allowed value
181     if( value < low ) {
182         return 1.0f;
183     }
184     // Bottom out at 25% speed
185     if( value > high ) {
186         return 0.25f;
187     }
188     // Linear interpolation between high and low
189     // y = y0 + ( x - x0 ) * ( y1 - y0 ) / ( x1 - x0 )
190     return 1.0f + ( value - low ) * ( 0.25f - 1.0f ) / ( high - low );
191 }
192 
workbench_crafting_speed_multiplier(const item & craft,const cata::optional<tripoint> & loc)193 static float workbench_crafting_speed_multiplier( const item &craft,
194         const cata::optional<tripoint> &loc )
195 {
196     float multiplier;
197     units::mass allowed_mass;
198     units::volume allowed_volume;
199 
200     map &here = get_map();
201     if( !loc ) {
202         // cata::nullopt indicates crafting from inventory
203         // Use values from f_fake_bench_hands
204         const furn_t &f = string_id<furn_t>( "f_fake_bench_hands" ).obj();
205         multiplier = f.workbench->multiplier;
206         allowed_mass = f.workbench->allowed_mass;
207         allowed_volume = f.workbench->allowed_volume;
208     } else if( const cata::optional<vpart_reference> vp = here.veh_at(
209                    *loc ).part_with_feature( "WORKBENCH", true ) ) {
210         // Vehicle workbench
211         const vpart_info &vp_info = vp->part().info();
212         if( const cata::optional<vpslot_workbench> &wb_info = vp_info.get_workbench_info() ) {
213             multiplier = wb_info->multiplier;
214             allowed_mass = wb_info->allowed_mass;
215             allowed_volume = wb_info->allowed_volume;
216         } else {
217             debugmsg( "part '%S' with WORKBENCH flag has no workbench info", vp->part().name() );
218             return 0.0f;
219         }
220     } else if( here.furn( *loc ).obj().workbench ) {
221         // Furniture workbench
222         const furn_t &f = here.furn( *loc ).obj();
223         multiplier = f.workbench->multiplier;
224         allowed_mass = f.workbench->allowed_mass;
225         allowed_volume = f.workbench->allowed_volume;
226     } else {
227         // Ground
228         const furn_t &f = string_id<furn_t>( "f_ground_crafting_spot" ).obj();
229         multiplier = f.workbench->multiplier;
230         allowed_mass = f.workbench->allowed_mass;
231         allowed_volume = f.workbench->allowed_volume;
232     }
233 
234     const units::mass &craft_mass = craft.weight();
235     const units::volume &craft_volume = craft.volume();
236 
237     multiplier *= lerped_multiplier( craft_mass, allowed_mass, 1000_kilogram );
238     multiplier *= lerped_multiplier( craft_volume, allowed_volume, 1000_liter );
239 
240     return multiplier;
241 }
242 
crafting_speed_multiplier(const recipe & rec,bool in_progress) const243 float Character::crafting_speed_multiplier( const recipe &rec, bool in_progress ) const
244 {
245     const float result = morale_crafting_speed_multiplier( rec ) *
246                          lighting_craft_speed_multiplier( rec );
247     // Can't start if we'd need 300% time, but we can still finish the job
248     if( !in_progress && result < 0.33f ) {
249         return 0.0f;
250     }
251     // If we're working below 10% speed, just give up
252     if( result < 0.1f ) {
253         return 0.0f;
254     }
255 
256     return result;
257 }
258 
crafting_speed_multiplier(const item & craft,const cata::optional<tripoint> & loc) const259 float Character::crafting_speed_multiplier( const item &craft,
260         const cata::optional<tripoint> &loc ) const
261 {
262     if( !craft.is_craft() ) {
263         debugmsg( "Can't calculate crafting speed multiplier of non-craft '%s'", craft.tname() );
264         return 1.0f;
265     }
266 
267     const recipe &rec = craft.get_making();
268 
269     const float light_multi = lighting_craft_speed_multiplier( rec );
270     const float bench_multi = workbench_crafting_speed_multiplier( craft, loc );
271     const float morale_multi = morale_crafting_speed_multiplier( rec );
272     const float mut_multi = mutation_value( "crafting_speed_multiplier" );
273 
274     const float total_multi = light_multi * bench_multi * morale_multi * mut_multi;
275 
276     if( light_multi <= 0.0f ) {
277         add_msg_if_player( m_bad, _( "You can no longer see well enough to keep crafting." ) );
278         return 0.0f;
279     }
280     if( bench_multi <= 0.1f || ( bench_multi <= 0.33f && total_multi <= 0.2f ) ) {
281         add_msg_if_player( m_bad, _( "The %s is too large and/or heavy to work on.  You may want to"
282                                      " use a workbench or a smaller batch size" ), craft.tname() );
283         return 0.0f;
284     }
285     if( morale_multi <= 0.2f || ( morale_multi <= 0.33f && total_multi <= 0.2f ) ) {
286         add_msg_if_player( m_bad, _( "Your morale is too low to continue crafting." ) );
287         return 0.0f;
288     }
289 
290     // If we're working below 20% speed, just give up
291     if( total_multi <= 0.2f ) {
292         add_msg_if_player( m_bad, _( "You are too frustrated to continue and just give up." ) );
293         return 0.0f;
294     }
295 
296     if( calendar::once_every( 1_hours ) && total_multi < 0.75f ) {
297         if( light_multi <= 0.5f ) {
298             add_msg_if_player( m_bad, _( "You can't see well and are working slowly." ) );
299         }
300         if( bench_multi <= 0.5f ) {
301             add_msg_if_player( m_bad,
302                                _( "The %s is too large and/or heavy to work on comfortably.  You are"
303                                   " working slowly." ), craft.tname() );
304         }
305         if( morale_multi <= 0.5f ) {
306             add_msg_if_player( m_bad, _( "You can't focus and are working slowly." ) );
307         }
308     }
309 
310     return total_multi;
311 }
312 
has_morale_to_craft() const313 bool Character::has_morale_to_craft() const
314 {
315     return get_morale_level() >= -50;
316 }
317 
craft(const cata::optional<tripoint> & loc)318 void Character::craft( const cata::optional<tripoint> &loc )
319 {
320     int batch_size = 0;
321     const recipe *rec = select_crafting_recipe( batch_size );
322     if( rec ) {
323         if( crafting_allowed( *this, *rec ) ) {
324             make_craft( rec->ident(), batch_size, loc );
325         }
326     }
327 }
328 
recraft(const cata::optional<tripoint> & loc)329 void Character::recraft( const cata::optional<tripoint> &loc )
330 {
331     if( lastrecipe.str().empty() ) {
332         popup( _( "Craft something first" ) );
333     } else if( making_would_work( lastrecipe, last_batch ) ) {
334         last_craft->execute( loc );
335     }
336 }
337 
long_craft(const cata::optional<tripoint> & loc)338 void Character::long_craft( const cata::optional<tripoint> &loc )
339 {
340     int batch_size = 0;
341     const recipe *rec = select_crafting_recipe( batch_size );
342     if( rec ) {
343         if( crafting_allowed( *this, *rec ) ) {
344             make_all_craft( rec->ident(), batch_size, loc );
345         }
346     }
347 }
348 
making_would_work(const recipe_id & id_to_make,int batch_size)349 bool Character::making_would_work( const recipe_id &id_to_make, int batch_size )
350 {
351     const auto &making = *id_to_make;
352     if( !( making && crafting_allowed( *this, making ) ) ) {
353         return false;
354     }
355 
356     if( !can_make( &making, batch_size ) ) {
357         std::string buffer = _( "You can no longer make that craft!" );
358         buffer += "\n";
359         buffer += making.simple_requirements().list_missing();
360         popup( buffer, PF_NONE );
361         return false;
362     }
363 
364     return check_eligible_containers_for_crafting( making, batch_size );
365 }
366 
available_assistant_count(const recipe & rec) const367 int Character::available_assistant_count( const recipe &rec ) const
368 // NPCs around you should assist in batch production if they have the skills
369 {
370     // TODO: Cache them in activity, include them in modifier calculations
371     const auto helpers = get_crafting_helpers();
372     return std::count_if( helpers.begin(), helpers.end(),
373     [&]( const npc * np ) {
374         return np->get_skill_level( rec.skill_used ) >= rec.difficulty;
375     } );
376 }
377 
expected_time_to_craft(const recipe & rec,int batch_size) const378 int64_t Character::expected_time_to_craft( const recipe &rec, int batch_size ) const
379 {
380     const size_t assistants = available_assistant_count( rec );
381     float modifier = crafting_speed_multiplier( rec );
382     return rec.batch_time( *this, batch_size, modifier, assistants );
383 }
384 
check_eligible_containers_for_crafting(const recipe & rec,int batch_size) const385 bool Character::check_eligible_containers_for_crafting( const recipe &rec, int batch_size ) const
386 {
387     std::vector<const item *> conts = get_eligible_containers_for_crafting();
388     const std::vector<item> res = rec.create_results( batch_size );
389     const std::vector<item> bps = rec.create_byproducts( batch_size );
390     std::vector<item> all;
391     all.reserve( res.size() + bps.size() );
392     all.insert( all.end(), res.begin(), res.end() );
393     all.insert( all.end(), bps.begin(), bps.end() );
394 
395     map &here = get_map();
396     for( const item &prod : all ) {
397         if( !prod.made_of( phase_id::LIQUID ) ) {
398             continue;
399         }
400 
401         // we go through half-filled containers first, then go through empty containers if we need
402         std::sort( conts.begin(), conts.end(), item_ptr_compare_by_charges );
403 
404         int charges_to_store = prod.charges;
405         for( const item *cont : conts ) {
406             if( charges_to_store <= 0 ) {
407                 break;
408             }
409 
410             charges_to_store -= cont->get_remaining_capacity_for_liquid( prod, true );
411         }
412 
413         // also check if we're currently in a vehicle that has the necessary storage
414         if( charges_to_store > 0 ) {
415             if( optional_vpart_position vp = here.veh_at( pos() ) ) {
416                 const itype_id &ftype = prod.typeId();
417                 int fuel_cap = vp->vehicle().fuel_capacity( ftype );
418                 int fuel_amnt = vp->vehicle().fuel_left( ftype );
419 
420                 if( fuel_cap >= 0 ) {
421                     int fuel_space_left = fuel_cap - fuel_amnt;
422                     charges_to_store -= fuel_space_left;
423                 }
424             }
425         }
426 
427         if( charges_to_store > 0 ) {
428             if( !query_yn(
429                     _( "You don't have anything in which to store %s and may have to pour it out as soon as it is prepared!  Proceed?" ),
430                     prod.tname() ) ) {
431                 return false;
432             }
433         }
434     }
435 
436     return true;
437 }
438 
is_container_eligible_for_crafting(const item & cont,bool allow_bucket)439 static bool is_container_eligible_for_crafting( const item &cont, bool allow_bucket )
440 {
441     if( cont.is_watertight_container() && cont.contents.num_item_stacks() <= 1 && ( allow_bucket ||
442             !cont.will_spill() ) ) {
443         return !cont.is_container_full( allow_bucket );
444     }
445     return false;
446 }
447 
get_eligible_containers_recursive(const item & cont,bool allow_bucket)448 static std::vector<const item *> get_eligible_containers_recursive( const item &cont,
449         bool allow_bucket )
450 {
451     std::vector<const item *> ret;
452 
453     if( is_container_eligible_for_crafting( cont, allow_bucket ) ) {
454         ret.push_back( &cont );
455     }
456     for( const item *it : cont.contents.all_items_top( item_pocket::pocket_type::CONTAINER ) ) {
457         //buckets are never allowed when inside another container
458         std::vector<const item *> inside = get_eligible_containers_recursive( *it, false );
459         ret.insert( ret.end(), inside.begin(), inside.end() );
460     }
461     return ret;
462 }
463 
get_eligible_containers_for_crafting() const464 std::vector<const item *> Character::get_eligible_containers_for_crafting() const
465 {
466     std::vector<const item *> conts;
467 
468     conts = get_eligible_containers_recursive( weapon, true );
469 
470     for( const auto &it : worn ) {
471         std::vector<const item *> eligible = get_eligible_containers_recursive( it, false );
472         conts.insert( conts.begin(), eligible.begin(), eligible.end() );
473     }
474 
475     map &here = get_map();
476     // get all potential containers within PICKUP_RANGE tiles including vehicles
477     for( const tripoint &loc : closest_points_first( pos(), PICKUP_RANGE ) ) {
478         // can not reach this -> can not access its contents
479         if( pos() != loc && !here.clear_path( pos(), loc, PICKUP_RANGE, 1, 100 ) ) {
480             continue;
481         }
482         if( here.accessible_items( loc ) ) {
483             for( const item &it : here.i_at( loc ) ) {
484                 std::vector<const item *> eligible = get_eligible_containers_recursive( it, true );
485                 conts.insert( conts.begin(), eligible.begin(), eligible.end() );
486             }
487         }
488 
489         if( const cata::optional<vpart_reference> vp = here.veh_at( loc ).part_with_feature( "CARGO",
490                 true ) ) {
491             for( const auto &it : vp->vehicle().get_items( vp->part_index() ) ) {
492                 std::vector<const item *> eligible = get_eligible_containers_recursive( it, true );
493                 conts.insert( conts.begin(), eligible.begin(), eligible.end() );
494             }
495         }
496     }
497 
498     return conts;
499 }
500 
can_make(const recipe * r,int batch_size)501 bool Character::can_make( const recipe *r, int batch_size )
502 {
503     const inventory &crafting_inv = crafting_inventory();
504 
505     if( has_recipe( r, crafting_inv, get_crafting_helpers() ) < 0 ) {
506         return false;
507     }
508 
509     if( !r->character_has_required_proficiencies( *this ) ) {
510         return false;
511     }
512 
513     return r->deduped_requirements().can_make_with_inventory(
514                crafting_inv, r->get_component_filter(), batch_size );
515 }
516 
can_start_craft(const recipe * rec,recipe_filter_flags flags,int batch_size)517 bool Character::can_start_craft( const recipe *rec, recipe_filter_flags flags, int batch_size )
518 {
519     if( !rec ) {
520         return false;
521     }
522 
523     if( !rec->character_has_required_proficiencies( *this ) ) {
524         return false;
525     }
526 
527     const inventory &inv = crafting_inventory();
528     return rec->deduped_requirements().can_make_with_inventory(
529                inv, rec->get_component_filter( flags ), batch_size, craft_flags::start_only );
530 }
531 
crafting_inventory(bool clear_path) const532 const inventory &Character::crafting_inventory( bool clear_path ) const
533 {
534     return crafting_inventory( tripoint_zero, PICKUP_RANGE, clear_path );
535 }
536 
crafting_inventory(const tripoint & src_pos,int radius,bool clear_path) const537 const inventory &Character::crafting_inventory( const tripoint &src_pos, int radius,
538         bool clear_path ) const
539 {
540     tripoint inv_pos = src_pos;
541     if( src_pos == tripoint_zero ) {
542         inv_pos = pos();
543     }
544     if( moves == crafting_cache.moves
545         && radius == crafting_cache.radius
546         && calendar::turn == crafting_cache.time
547         && inv_pos == crafting_cache.position ) {
548         return *crafting_cache.crafting_inventory;
549     }
550     crafting_cache.crafting_inventory->clear();
551     if( radius >= 0 ) {
552         crafting_cache.crafting_inventory->form_from_map( inv_pos, radius, this, false, clear_path );
553     }
554 
555     // TODO: Add a const overload of all_items_loc() that returns something like
556     // vector<const_item_location> in order to get rid of the const_cast here.
557     for( const item_location &it : const_cast<Character *>( this )->all_items_loc() ) {
558         // can't craft with containers that have items in them
559         if( !it->contents.empty_container() ) {
560             continue;
561         }
562         crafting_cache.crafting_inventory->add_item( *it );
563     }
564 
565     for( const bionic &bio : *my_bionics ) {
566         const bionic_data &bio_data = bio.info();
567         if( ( !bio_data.activated || bio.powered ) &&
568             !bio_data.fake_item.is_empty() ) {
569             *crafting_cache.crafting_inventory += item( bio.info().fake_item,
570                                                   calendar::turn, units::to_kilojoule( get_power_level() ) );
571         }
572     }
573     if( has_trait( trait_BURROW ) ) {
574         *crafting_cache.crafting_inventory += item( "pickaxe", calendar::turn );
575         *crafting_cache.crafting_inventory += item( "shovel", calendar::turn );
576     }
577 
578     crafting_cache.moves = moves;
579     crafting_cache.time = calendar::turn;
580     crafting_cache.position = inv_pos;
581     crafting_cache.radius = radius;
582     return *crafting_cache.crafting_inventory;
583 }
584 
invalidate_crafting_inventory()585 void Character::invalidate_crafting_inventory()
586 {
587     crafting_cache.time = calendar::before_time_starts;
588 }
589 
make_craft(const recipe_id & id_to_make,int batch_size,const cata::optional<tripoint> & loc)590 void Character::make_craft( const recipe_id &id_to_make, int batch_size,
591                             const cata::optional<tripoint> &loc )
592 {
593     make_craft_with_command( id_to_make, batch_size, false, loc );
594 }
595 
make_all_craft(const recipe_id & id_to_make,int batch_size,const cata::optional<tripoint> & loc)596 void Character::make_all_craft( const recipe_id &id_to_make, int batch_size,
597                                 const cata::optional<tripoint> &loc )
598 {
599     make_craft_with_command( id_to_make, batch_size, true, loc );
600 }
601 
make_craft_with_command(const recipe_id & id_to_make,int batch_size,bool is_long,const cata::optional<tripoint> & loc)602 void Character::make_craft_with_command( const recipe_id &id_to_make, int batch_size, bool is_long,
603         const cata::optional<tripoint> &loc )
604 {
605     const auto &recipe_to_make = *id_to_make;
606 
607     if( !recipe_to_make ) {
608         return;
609     }
610 
611     *last_craft = craft_command( &recipe_to_make, batch_size, is_long, this, loc );
612     last_craft->execute();
613 }
614 
615 // @param offset is the index of the created item in the range [0, batch_size-1],
616 // it makes sure that the used items are distributed equally among the new items.
set_components(std::list<item> & components,const std::list<item> & used,const int batch_size,const size_t offset)617 static void set_components( std::list<item> &components, const std::list<item> &used,
618                             const int batch_size, const size_t offset )
619 {
620     if( batch_size <= 1 ) {
621         components.insert( components.begin(), used.begin(), used.end() );
622         return;
623     }
624     // This count does *not* include items counted by charges!
625     size_t non_charges_counter = 0;
626     for( const item &tmp : used ) {
627         if( tmp.count_by_charges() ) {
628             components.push_back( tmp );
629             // This assumes all (count-by-charges) items of the same type have been merged into one,
630             // which has a charges value that can be evenly divided by batch_size.
631             components.back().charges = tmp.charges / batch_size;
632         } else {
633             if( ( non_charges_counter + offset ) % batch_size == 0 ) {
634                 components.push_back( tmp );
635             }
636             non_charges_counter++;
637         }
638     }
639 }
640 
wield_craft(Character & p,item & craft)641 static cata::optional<item_location> wield_craft( Character &p, item &craft )
642 {
643     if( p.wield( craft ) ) {
644         if( p.weapon.invlet ) {
645             p.add_msg_if_player( m_info, _( "Wielding %c - %s" ), p.weapon.invlet, p.weapon.display_name() );
646         } else {
647             p.add_msg_if_player( m_info, _( "Wielding - %s" ), p.weapon.display_name() );
648         }
649         return item_location( p, &p.weapon );
650     }
651     return cata::nullopt;
652 }
653 
set_item_inventory(Character & p,item & newit)654 static item *set_item_inventory( Character &p, item &newit )
655 {
656     item *ret_val = nullptr;
657     if( newit.made_of( phase_id::LIQUID ) ) {
658         liquid_handler::handle_all_liquid( newit, PICKUP_RANGE );
659     } else {
660         p.inv->assign_empty_invlet( newit, p );
661         // We might not have space for the item
662         if( !p.can_pickVolume( newit ) ) { //Accounts for result_mult
663             put_into_vehicle_or_drop( p, item_drop_reason::too_large, { newit } );
664         } else if( !p.can_pickWeight( newit, !get_option<bool>( "DANGEROUS_PICKUPS" ) ) ) {
665             put_into_vehicle_or_drop( p, item_drop_reason::too_heavy, { newit } );
666         } else {
667             ret_val = &p.i_add( newit );
668             add_msg( m_info, "%c - %s", ret_val->invlet == 0 ? ' ' : ret_val->invlet,
669                      ret_val->tname() );
670         }
671     }
672     return ret_val;
673 }
674 
675 /**
676  * Helper for @ref set_item_map_or_vehicle
677  * This is needed to still get a valid item_location if overflow occurs
678  */
set_item_map(const tripoint & loc,item & newit)679 static item_location set_item_map( const tripoint &loc, item &newit )
680 {
681     // Includes loc
682     for( const tripoint &tile : closest_points_first( loc, 2 ) ) {
683         // Pass false to disallow overflow, null_item_reference indicates failure.
684         item *it_on_map = &get_map().add_item_or_charges( tile, newit, false );
685         if( it_on_map != &null_item_reference() ) {
686             return item_location( map_cursor( tile ), it_on_map );
687         }
688     }
689     debugmsg( "Could not place %s on map near (%d, %d, %d)", newit.tname(), loc.x, loc.y, loc.z );
690     return item_location();
691 }
692 
693 /**
694  * Set an item on the map or in a vehicle and return the new location
695  */
set_item_map_or_vehicle(const Character & p,const tripoint & loc,item & newit)696 static item_location set_item_map_or_vehicle( const Character &p, const tripoint &loc, item &newit )
697 {
698     map &here = get_map();
699     if( const cata::optional<vpart_reference> vp = here.veh_at( loc ).part_with_feature( "CARGO",
700             false ) ) {
701 
702         if( const cata::optional<vehicle_stack::iterator> it = vp->vehicle().add_item( vp->part_index(),
703                 newit ) ) {
704             p.add_msg_player_or_npc(
705                 pgettext( "item, furniture", "You put the %1$s on the %2$s." ),
706                 pgettext( "item, furniture", "<npcname> puts the %1$s on the %2$s." ),
707                 ( *it )->tname(), vp->part().name() );
708 
709             return item_location( vehicle_cursor( vp->vehicle(), vp->part_index() ), & **it );
710         }
711 
712         // Couldn't add the in progress craft to the target part, so drop it to the map.
713         p.add_msg_player_or_npc(
714             pgettext( "furniture, item", "Not enough space on the %s. You drop the %s on the ground." ),
715             pgettext( "furniture, item", "Not enough space on the %s. <npcname> drops the %s on the ground." ),
716             vp->part().name(), newit.tname() );
717 
718         return set_item_map( loc, newit );
719 
720     } else {
721         if( here.has_furn( loc ) ) {
722             const furn_t &workbench = here.furn( loc ).obj();
723             p.add_msg_player_or_npc(
724                 pgettext( "item, furniture", "You put the %1$s on the %2$s." ),
725                 pgettext( "item, furniture", "<npcname> puts the %1$s on the %2$s." ),
726                 newit.tname(), workbench.name() );
727         } else {
728             p.add_msg_player_or_npc(
729                 pgettext( "item", "You put the %s on the ground." ),
730                 pgettext( "item", "<npcname> puts the %s on the ground." ),
731                 newit.tname() );
732         }
733         return set_item_map( loc, newit );
734     }
735 }
736 
start_craft(craft_command & command,const cata::optional<tripoint> & loc)737 void Character::start_craft( craft_command &command, const cata::optional<tripoint> &loc )
738 {
739     if( command.empty() ) {
740         debugmsg( "Attempted to start craft with empty command" );
741         return;
742     }
743 
744     item craft = command.create_in_progress_craft();
745     const recipe &making = craft.get_making();
746     if( get_skill_level( command.get_skill_id() ) > making.difficulty * 1.25 ) {
747         handle_skill_warning( command.get_skill_id(), true );
748     }
749 
750     // In case we were wearing something just consumed
751     if( !craft.components.empty() ) {
752         calc_encumbrance();
753     }
754 
755     item_location craft_in_world;
756 
757     // Check if we are standing next to a workbench. If so, just use that.
758     float best_bench_multi = 0.0f;
759     cata::optional<tripoint> target = loc;
760     map &here = get_map();
761     for( const tripoint &adj : here.points_in_radius( pos(), 1 ) ) {
762         if( here.dangerous_field_at( adj ) ) {
763             continue;
764         }
765         if( const cata::value_ptr<furn_workbench_info> &wb = here.furn( adj ).obj().workbench ) {
766             if( wb->multiplier > best_bench_multi ) {
767                 best_bench_multi = wb->multiplier;
768                 target = adj;
769             }
770         } else if( const cata::optional<vpart_reference> vp = here.veh_at(
771                        adj ).part_with_feature( "WORKBENCH", true ) ) {
772             if( const cata::optional<vpslot_workbench> &wb_info = vp->part().info().get_workbench_info() ) {
773                 if( wb_info->multiplier > best_bench_multi ) {
774                     best_bench_multi = wb_info->multiplier;
775                     target = adj;
776                 }
777             } else {
778                 debugmsg( "part '%S' with WORKBENCH flag has no workbench info", vp->part().name() );
779             }
780         }
781     }
782 
783     // Crafting without a workbench
784     if( !target ) {
785         if( !has_two_arms() ) {
786             craft_in_world = set_item_map_or_vehicle( *this, pos(), craft );
787         } else if( !has_wield_conflicts( craft ) ) {
788             if( cata::optional<item_location> it_loc = wield_craft( *this, craft ) ) {
789                 craft_in_world = *it_loc;
790             }  else {
791                 // This almost certianly shouldn't happen
792                 put_into_vehicle_or_drop( *this, item_drop_reason::tumbling, {craft} );
793             }
794         } else {
795             enum option : int {
796                 WIELD_CRAFT = 0,
797                 DROP_CRAFT,
798                 STASH,
799                 DROP
800             };
801 
802             uilist amenu;
803             amenu.text = string_format( pgettext( "in progress craft", "What to do with the %s?" ),
804                                         craft.display_name() );
805 
806             amenu.addentry( WIELD_CRAFT, can_unwield( weapon ).success(),
807                             '1', _( "Dispose of your wielded %s and start working." ), weapon.tname() );
808             amenu.addentry( DROP_CRAFT, true, '2', _( "Put it down and start working." ) );
809             const bool can_stash = can_pickVolume( craft ) &&
810                                    can_pickWeight( craft, !get_option<bool>( "DANGEROUS_PICKUPS" ) );
811             amenu.addentry( STASH, can_stash, '3', _( "Store it in your inventory." ) );
812             amenu.addentry( DROP, true, '4', _( "Drop it on the ground." ) );
813 
814             amenu.query();
815             const option choice = amenu.ret == UILIST_CANCEL ? DROP : static_cast<option>( amenu.ret );
816             switch( choice ) {
817                 case WIELD_CRAFT: {
818                     if( cata::optional<item_location> it_loc = wield_craft( *this, craft ) ) {
819                         craft_in_world = *it_loc;
820                     } else {
821                         // This almost certainly shouldn't happen
822                         put_into_vehicle_or_drop( *this, item_drop_reason::tumbling, {craft} );
823                     }
824                     break;
825                 }
826                 case DROP_CRAFT: {
827                     craft_in_world = set_item_map_or_vehicle( *this, pos(), craft );
828                     break;
829                 }
830                 case STASH: {
831                     set_item_inventory( *this, craft );
832                     break;
833                 }
834                 case DROP: {
835                     put_into_vehicle_or_drop( *this, item_drop_reason::deliberate, {craft} );
836                     break;
837                 }
838             }
839         }
840     } else {
841         // We have a workbench, put the item there.
842         craft_in_world = set_item_map_or_vehicle( *this, *target, craft );
843     }
844 
845     if( !craft_in_world.get_item() ) {
846         add_msg_if_player( _( "Wield and activate the %s to start crafting." ), craft.tname() );
847         return;
848     }
849 
850     assign_activity( player_activity( craft_activity_actor( craft_in_world, command.is_long() ) ) );
851 
852     add_msg_player_or_npc(
853         pgettext( "in progress craft", "You start working on the %s." ),
854         pgettext( "in progress craft", "<npcname> starts working on the %s." ),
855         craft.tname() );
856 }
857 
craft_skill_gain(const item & craft,const int & num_practice_ticks)858 void Character::craft_skill_gain( const item &craft, const int &num_practice_ticks )
859 {
860     if( !craft.is_craft() ) {
861         debugmsg( "craft_skill_check() called on non-craft '%s.' Aborting.", craft.tname() );
862         return;
863     }
864 
865     const recipe &making = craft.get_making();
866     const int batch_size = craft.charges;
867 
868     std::vector<npc *> helpers = get_crafting_helpers();
869 
870     if( making.skill_used ) {
871         const int skill_cap = static_cast<int>( making.difficulty * 1.25 );
872         practice( making.skill_used, num_practice_ticks, skill_cap, true );
873 
874         // NPCs assisting or watching should gain experience...
875         for( auto &helper : helpers ) {
876             //If the NPC can understand what you are doing, they gain more exp
877             if( helper->get_skill_level( making.skill_used ) >= making.difficulty ) {
878                 helper->practice( making.skill_used, roll_remainder( num_practice_ticks / 2.0 ),
879                                   skill_cap );
880                 if( batch_size > 1 && one_in( 300 ) ) {
881                     add_msg( m_info, _( "%s assists with crafting…" ), helper->name );
882                 }
883                 if( batch_size == 1 && one_in( 300 ) ) {
884                     add_msg( m_info, _( "%s could assist you with a batch…" ), helper->name );
885                 }
886                 // NPCs around you understand the skill used better
887             } else {
888                 helper->practice( making.skill_used, roll_remainder( num_practice_ticks / 10.0 ),
889                                   skill_cap );
890                 if( one_in( 300 ) ) {
891                     add_msg( m_info, _( "%s watches you craft…" ), helper->name );
892                 }
893             }
894         }
895     }
896 }
897 
craft_proficiency_gain(const item & craft,const time_duration & time)898 void Character::craft_proficiency_gain( const item &craft, const time_duration &time )
899 {
900     if( !craft.is_craft() ) {
901         debugmsg( "craft_proficiency_gain() called on non-craft %s", craft.tname() );
902         return;
903     }
904 
905     const recipe &making = craft.get_making();
906     std::vector<npc *> helpers = get_crafting_helpers();
907 
908     // The proficiency, and the multiplier on the time we learn it for
909     std::vector<std::tuple<proficiency_id, float, cata::optional<time_duration>>> subjects;
910     for( const recipe_proficiency &prof : making.proficiencies ) {
911         if( !_proficiencies->has_learned( prof.id ) &&
912             prof.id->can_learn() &&
913             _proficiencies->has_prereqs( prof.id ) ) {
914             std::tuple<proficiency_id, float, cata::optional<time_duration>> subject( prof.id,
915                     prof.learning_time_mult / prof.time_multiplier, prof.max_experience );
916             subjects.push_back( subject );
917         }
918     }
919 
920     int amount = to_seconds<int>( time );
921     if( !subjects.empty() ) {
922         amount /= subjects.size();
923     }
924     time_duration learn_time = time_duration::from_seconds( amount );
925 
926     int npc_helper_bonus = 1;
927     for( npc *helper : helpers ) {
928         for( const std::tuple<proficiency_id, float, cata::optional<time_duration>> &subject : subjects ) {
929             if( helper->has_proficiency( std::get<0>( subject ) ) ) {
930                 // NPCs who know the proficiency and help teach you faster
931                 npc_helper_bonus = 2;
932             }
933             helper->practice_proficiency( std::get<0>( subject ), std::get<1>( subject ) * learn_time,
934                                           std::get<2>( subject ) );
935         }
936     }
937 
938     for( const std::tuple<proficiency_id, float, cata::optional<time_duration>> &subject : subjects ) {
939         practice_proficiency( std::get<0>( subject ),
940                               learn_time * std::get<1>( subject ) * npc_helper_bonus, std::get<2>( subject ) );
941     }
942 }
943 
crafting_success_roll(const recipe & making) const944 double Character::crafting_success_roll( const recipe &making ) const
945 {
946     if( has_trait( trait_DEBUG_CNF ) ) {
947         return 1.0;
948     }
949     int secondary_dice = 0;
950     int secondary_difficulty = 0;
951     for( const auto &pr : making.required_skills ) {
952         secondary_dice += get_skill_level( pr.first );
953         secondary_difficulty += pr.second;
954     }
955 
956     // # of dice is 75% primary skill, 25% secondary (unless secondary is null)
957     int skill_dice;
958     if( secondary_difficulty > 0 ) {
959         skill_dice = get_skill_level( making.skill_used ) * 3 + secondary_dice;
960     } else {
961         skill_dice = get_skill_level( making.skill_used ) * 4;
962     }
963 
964     for( const npc *np : get_crafting_helpers() ) {
965         if( np->get_skill_level( making.skill_used ) >=
966             get_skill_level( making.skill_used ) ) {
967             // NPC assistance is worth half a skill level
968             skill_dice += 2;
969             add_msg_if_player( m_info, _( "%s helps with crafting…" ), np->name );
970             break;
971         }
972     }
973 
974     // farsightedness can impose a penalty on electronics and tailoring success
975     // it's equivalent to a 2-rank electronics penalty, 1-rank tailoring
976     if( has_trait( trait_HYPEROPIC ) && !worn_with_flag( flag_FIX_FARSIGHT ) &&
977         !has_effect( effect_contacts ) ) {
978         int main_rank_penalty = 0;
979         if( making.skill_used == skill_electronics ) {
980             main_rank_penalty = 2;
981         } else if( making.skill_used == skill_tailor ) {
982             main_rank_penalty = 1;
983         }
984         skill_dice -= main_rank_penalty * 4;
985     }
986 
987     // It's tough to craft with paws.  Fortunately it's just a matter of grip and fine-motor,
988     // not inability to see what you're doing
989     for( const trait_id &mut : get_mutations() ) {
990         for( const std::pair<const skill_id, int> &skib : mut->craft_skill_bonus ) {
991             if( making.skill_used == skib.first ) {
992                 skill_dice += skib.second;
993             }
994         }
995     }
996 
997     // Sides on dice is 16 plus your current intelligence
998     ///\EFFECT_INT increases crafting success chance
999     const int skill_sides = 16 + int_cur;
1000 
1001     int diff_dice;
1002     if( secondary_difficulty > 0 ) {
1003         diff_dice = making.difficulty * 3 + secondary_difficulty;
1004     } else {
1005         // Since skill level is * 4 also
1006         diff_dice = making.difficulty * 4;
1007     }
1008 
1009     const int diff_sides = 24; // 16 + 8 (default intelligence)
1010 
1011     const double skill_roll = dice( skill_dice, skill_sides );
1012     const double diff_roll = dice( diff_dice, diff_sides );
1013 
1014     if( diff_roll == 0 ) {
1015         // Automatic success
1016         return 2;
1017     }
1018 
1019     return ( skill_roll / diff_roll ) / making.proficiency_failure_maluses( *this );
1020 }
1021 
get_next_failure_point() const1022 int item::get_next_failure_point() const
1023 {
1024     if( !is_craft() ) {
1025         debugmsg( "get_next_failure_point() called on non-craft '%s.'  Aborting.", tname() );
1026         return INT_MAX;
1027     }
1028     return craft_data_->next_failure_point >= 0 ? craft_data_->next_failure_point : INT_MAX;
1029 }
1030 
set_next_failure_point(const Character & crafter)1031 void item::set_next_failure_point( const Character &crafter )
1032 {
1033     if( !is_craft() ) {
1034         debugmsg( "set_next_failure_point() called on non-craft '%s.'  Aborting.", tname() );
1035         return;
1036     }
1037 
1038     const int percent = 10000000;
1039     const int failure_point_delta = crafter.crafting_success_roll( get_making() ) * percent;
1040 
1041     craft_data_->next_failure_point = item_counter + failure_point_delta;
1042 }
1043 
destroy_random_component(item & craft,const Character & crafter)1044 static void destroy_random_component( item &craft, const Character &crafter )
1045 {
1046     if( craft.components.empty() ) {
1047         debugmsg( "destroy_random_component() called on craft with no components!  Aborting" );
1048         return;
1049     }
1050 
1051     item destroyed = random_entry_removed( craft.components );
1052 
1053     crafter.add_msg_player_or_npc( _( "You mess up and destroy the %s." ),
1054                                    _( "<npcname> messes up and destroys the %s" ), destroyed.tname() );
1055 }
1056 
handle_craft_failure(Character & crafter)1057 bool item::handle_craft_failure( Character &crafter )
1058 {
1059     if( !is_craft() ) {
1060         debugmsg( "handle_craft_failure() called on non-craft '%s.'  Aborting.", tname() );
1061         return false;
1062     }
1063 
1064     const double success_roll = crafter.crafting_success_roll( get_making() );
1065     const int starting_components = this->components.size();
1066     // Destroy at most 75% of the components, always a chance of losing 1 though
1067     const size_t max_destroyed = std::max<size_t>( 1, components.size() * 3 / 4 );
1068     for( size_t i = 0; i < max_destroyed; i++ ) {
1069         // This shouldn't happen
1070         if( components.empty() ) {
1071             break;
1072         }
1073         // If we roll success, skip destroying a component
1074         if( x_in_y( success_roll, 1.0 ) ) {
1075             continue;
1076         }
1077         destroy_random_component( *this, crafter );
1078     }
1079     if( starting_components > 0 && this->components.empty() ) {
1080         // The craft had components and all of them were destroyed.
1081         return true;
1082     }
1083 
1084     // If a loss happens, minimum 25% progress lost, average 35%.  Falls off exponentially
1085     // Loss is scaled by the success roll
1086     const double percent_progress_loss = rng_exponential( 0.25, 0.35 ) * ( 1.0 - success_roll );
1087     // Ensure only positive losses have an effect on progress
1088     if( percent_progress_loss > 0.0 ) {
1089         const int progress_loss = item_counter * percent_progress_loss;
1090         crafter.add_msg_player_or_npc( _( "You mess up and lose %d%% progress." ),
1091                                        _( "<npcname> messes up and loses %d%% progress." ), progress_loss / 100000 );
1092         item_counter = clamp( item_counter - progress_loss, 0, 10000000 );
1093     }
1094 
1095     set_next_failure_point( crafter );
1096 
1097     // Check if we can consume a new component and continue
1098     if( !crafter.can_continue_craft( *this ) ) {
1099         crafter.cancel_activity();
1100     }
1101     return false;
1102 }
1103 
get_continue_reqs() const1104 requirement_data item::get_continue_reqs() const
1105 {
1106     if( !is_craft() ) {
1107         debugmsg( "get_continue_reqs() called on non-craft '%s.'  Aborting.", tname() );
1108         return requirement_data();
1109     }
1110     return requirement_data::continue_requirements( craft_data_->comps_used, components );
1111 }
1112 
inherit_flags(const item & parent,const recipe & making)1113 void item::inherit_flags( const item &parent, const recipe &making )
1114 {
1115     // default behavior is to resize the clothing, which happens elsewhere
1116     if( making.has_flag( flag_NO_RESIZE ) ) {
1117         //If item is crafted from poor-fit components, the result is poorly fitted too
1118         if( parent.has_flag( flag_VARSIZE ) ) {
1119             unset_flag( flag_FIT );
1120         }
1121         //If item is crafted from perfect-fit components, the result is perfectly fitted too
1122         if( parent.has_flag( flag_FIT ) ) {
1123             set_flag( flag_FIT );
1124         }
1125     }
1126     for( const flag_id &f : parent.get_flags() ) {
1127         if( f->craft_inherit() ) {
1128             set_flag( f );
1129         }
1130     }
1131     for( const flag_id &f : parent.type->get_flags() ) {
1132         if( f->craft_inherit() ) {
1133             set_flag( f );
1134         }
1135     }
1136     if( parent.has_flag( flag_HIDDEN_POISON ) ) {
1137         poison = parent.poison;
1138     }
1139 }
1140 
inherit_flags(const std::list<item> & parents,const recipe & making)1141 void item::inherit_flags( const std::list<item> &parents, const recipe &making )
1142 {
1143     for( const item &parent : parents ) {
1144         inherit_flags( parent, making );
1145     }
1146 }
1147 
complete_craft(item & craft,const cata::optional<tripoint> & loc)1148 void Character::complete_craft( item &craft, const cata::optional<tripoint> &loc )
1149 {
1150     if( !craft.is_craft() ) {
1151         debugmsg( "complete_craft() called on non-craft '%s.'  Aborting.", craft.tname() );
1152         return;
1153     }
1154 
1155     const recipe &making = craft.get_making();
1156     const int batch_size = craft.charges;
1157     std::list<item> &used = craft.components;
1158     const double relative_rot = craft.get_relative_rot();
1159 
1160     // Set up the new item, and assign an inventory letter if available
1161     std::vector<item> newits = making.create_results( batch_size );
1162 
1163     const bool should_heat = making.hot_result();
1164     const bool remove_raw = making.removes_raw();
1165 
1166     bool first = true;
1167     size_t newit_counter = 0;
1168     for( item &newit : newits ) {
1169 
1170         // Points to newit unless newit is a non-empty container, then it points to newit's contents.
1171         // Necessary for things like canning soup; sometimes we want to operate on the soup, not the can.
1172         item &food_contained = !newit.contents.empty() ? newit.contents.only_item() : newit;
1173 
1174         // messages, learning of recipe, food spoilage calculation only once
1175         if( first ) {
1176             first = false;
1177             // TODO: reconsider recipe memorization
1178             if( knows_recipe( &making ) ) {
1179                 add_msg( _( "You craft %s from memory." ), making.result_name() );
1180             } else {
1181                 add_msg( _( "You craft %s using a book as a reference." ), making.result_name() );
1182                 // If we made it, but we don't know it,
1183                 // we're making it from a book and have a chance to learn it.
1184                 // Base expected time to learn is 1000*(difficulty^4)/skill/int moves.
1185                 // This means time to learn is greatly decreased with higher skill level,
1186                 // but also keeps going up as difficulty goes up.
1187                 // Worst case is lvl 10, which will typically take
1188                 // 10^4/10 (1,000) minutes, or about 16 hours of crafting it to learn.
1189                 int difficulty = has_recipe( &making, crafting_inventory(), get_crafting_helpers() );
1190                 ///\EFFECT_INT increases chance to learn recipe when crafting from a book
1191                 const double learning_speed =
1192                     std::max( get_skill_level( making.skill_used ), 1 ) *
1193                     std::max( get_int(), 1 );
1194                 const double time_to_learn = 1000 * 8 * std::pow( difficulty, 4 ) / learning_speed;
1195                 if( x_in_y( making.time_to_craft_moves( *this ),  time_to_learn ) ) {
1196                     learn_recipe( &making );
1197                     add_msg( m_good, _( "You memorized the recipe for %s!" ),
1198                              making.result_name() );
1199                 }
1200             }
1201         }
1202 
1203         // Newly-crafted items are perfect by default. Inspect their materials to see if they shouldn't be
1204         food_contained.inherit_flags( used, making );
1205 
1206         for( const flag_id &flag : making.flags_to_delete ) {
1207             food_contained.unset_flag( flag );
1208         }
1209 
1210         // Don't store components for things made by charges,
1211         // Don't store components for things that can't be uncrafted.
1212         if( recipe_dictionary::get_uncraft( making.result() ) && !food_contained.count_by_charges() &&
1213             making.is_reversible() ) {
1214             // Setting this for items counted by charges gives only problems:
1215             // those items are automatically merged everywhere (map/vehicle/inventory),
1216             // which would either lose this information or merge it somehow.
1217             set_components( food_contained.components, used, batch_size, newit_counter );
1218             newit_counter++;
1219         } else if( food_contained.is_food() && !food_contained.has_flag( flag_NUTRIENT_OVERRIDE ) ) {
1220             // if a component item has "cooks_like" it will be replaced by that item as a component
1221             for( item &comp : used ) {
1222                 // only comestibles have cooks_like.  any other type of item will throw an exception, so filter those out
1223                 if( comp.is_comestible() && !comp.get_comestible()->cooks_like.is_empty() ) {
1224                     comp = item( comp.get_comestible()->cooks_like, comp.birthday(), comp.charges );
1225                 }
1226                 // If this recipe is cooked, components are no longer raw.
1227                 if( should_heat || remove_raw ) {
1228                     comp.set_flag_recursive( flag_COOKED );
1229                 }
1230             }
1231 
1232             // use a copy of the used list so that the byproducts don't build up over iterations (#38071)
1233             std::list<item> usedbp = used;
1234             // byproducts get stored as a "component" but with a byproduct flag for consumption purposes
1235             if( making.has_byproducts() ) {
1236                 for( item &byproduct : making.create_byproducts( batch_size ) ) {
1237                     byproduct.set_flag( flag_BYPRODUCT );
1238                     usedbp.push_back( byproduct );
1239                 }
1240             }
1241             // store components for food recipes that do not have the override flag
1242             set_components( food_contained.components, usedbp, batch_size, newit_counter );
1243 
1244             // store the number of charges the recipe would create with batch size 1.
1245             if( &newit != &food_contained ) {  // If a canned/contained item was crafted…
1246                 // … the container holds exactly one completion of the recipe, no matter the batch size.
1247                 food_contained.recipe_charges = food_contained.charges;
1248             } else { // Otherwise, the item is already stacked so we need to divide by batch size.
1249                 newit.recipe_charges = newit.charges / batch_size;
1250             }
1251             newit_counter++;
1252         }
1253 
1254         if( food_contained.has_temperature() ) {
1255             if( food_contained.goes_bad() ) {
1256                 food_contained.set_relative_rot( relative_rot );
1257             }
1258             if( should_heat ) {
1259                 food_contained.heat_up();
1260             } else {
1261                 // Really what we should be doing is averaging the temperatures
1262                 // between the recipe components if we don't have a heat tool, but
1263                 // that's kind of hard.  For now just set the item to 20 C
1264                 // and reset the temperature, don't
1265                 // forget byproducts below either when you fix this.
1266                 //
1267                 // Temperature is not functional for non-foods
1268                 food_contained.set_item_temperature( 293.15 );
1269             }
1270         }
1271 
1272         // If the recipe has a `FULL_MAGAZINE` flag, fill it with ammo
1273         if( newit.is_magazine() && making.has_flag( flag_FULL_MAGAZINE ) ) {
1274             newit.ammo_set( newit.ammo_default(),
1275                             newit.ammo_capacity( item::find_type( newit.ammo_default() )->ammo->type ) );
1276         }
1277 
1278         newit.set_owner( get_faction()->id );
1279         // If these aren't equal, newit is a container, so finalize its contents too.
1280         if( &newit != &food_contained ) {
1281             food_contained.set_owner( get_faction()->id );
1282         }
1283 
1284         if( newit.made_of( phase_id::LIQUID ) ) {
1285             liquid_handler::handle_all_liquid( newit, PICKUP_RANGE );
1286         } else if( !loc && !has_wield_conflicts( craft ) &&
1287                    can_wield( newit ).success() ) {
1288             wield_craft( *this, newit );
1289         } else {
1290             set_item_map_or_vehicle( *this, loc.value_or( pos() ), newit );
1291         }
1292     }
1293 
1294     if( making.has_byproducts() ) {
1295         std::vector<item> bps = making.create_byproducts( batch_size );
1296         for( auto &bp : bps ) {
1297             if( bp.has_temperature() ) {
1298                 if( bp.goes_bad() ) {
1299                     bp.set_relative_rot( relative_rot );
1300                 }
1301                 if( should_heat ) {
1302                     bp.heat_up();
1303                 } else {
1304                     bp.set_item_temperature( 293.15 );
1305                 }
1306             }
1307             bp.set_owner( get_faction()->id );
1308             if( bp.made_of( phase_id::LIQUID ) ) {
1309                 liquid_handler::handle_all_liquid( bp, PICKUP_RANGE );
1310             } else if( !loc ) {
1311                 set_item_inventory( *this, bp );
1312             } else {
1313                 set_item_map_or_vehicle( *this, loc.value_or( pos() ), bp );
1314             }
1315         }
1316     }
1317 
1318     inv->restack( *this );
1319 }
1320 
can_continue_craft(item & craft)1321 bool Character::can_continue_craft( item &craft )
1322 {
1323     if( !craft.is_craft() ) {
1324         debugmsg( "complete_craft() called on non-craft '%s.'  Aborting.", craft.tname() );
1325         return false;
1326     }
1327 
1328     return can_continue_craft( craft, craft.get_continue_reqs() );
1329 }
1330 
can_continue_craft(item & craft,const requirement_data & continue_reqs)1331 bool Character::can_continue_craft( item &craft, const requirement_data &continue_reqs )
1332 {
1333     const recipe &rec = craft.get_making();
1334     if( !rec.character_has_required_proficiencies( *this ) ) {
1335         return false;
1336     }
1337 
1338     // Avoid building an inventory from the map if we don't have to, as it is expensive
1339     if( !continue_reqs.is_empty() ) {
1340 
1341         std::function<bool( const item & )> filter = rec.get_component_filter();
1342         const std::function<bool( const item & )> no_rotten_filter =
1343             rec.get_component_filter( recipe_filter_flags::no_rotten );
1344         // continue_reqs are for all batches at once
1345         const int batch_size = 1;
1346 
1347         if( !continue_reqs.can_make_with_inventory( crafting_inventory(), filter, batch_size ) ) {
1348             std::string buffer = _( "You don't have the required components to continue crafting!" );
1349             buffer += "\n";
1350             buffer += continue_reqs.list_missing();
1351             popup( buffer, PF_NONE );
1352             return false;
1353         }
1354 
1355         std::string buffer = _( "Consume the missing components and continue crafting?" );
1356         buffer += "\n";
1357         buffer += continue_reqs.list_all();
1358         if( !query_yn( buffer ) ) {
1359             return false;
1360         }
1361 
1362         if( continue_reqs.can_make_with_inventory( crafting_inventory(), no_rotten_filter,
1363                 batch_size ) ) {
1364             filter = no_rotten_filter;
1365         } else {
1366             if( !query_yn( _( "Some components required to continue are rotten.\n"
1367                               "Continue crafting anyway?" ) ) ) {
1368                 return false;
1369             }
1370         }
1371 
1372         inventory map_inv;
1373         map_inv.form_from_map( pos(), PICKUP_RANGE, this );
1374 
1375         std::vector<comp_selection<item_comp>> item_selections;
1376         for( const auto &it : continue_reqs.get_components() ) {
1377             comp_selection<item_comp> is = select_item_component( it, batch_size, map_inv, true, filter );
1378             if( is.use_from == usage_from::cancel ) {
1379                 cancel_activity();
1380                 add_msg( _( "You stop crafting." ) );
1381                 return false;
1382             }
1383             item_selections.push_back( is );
1384         }
1385         for( const auto &it : item_selections ) {
1386             craft.components.splice( craft.components.end(), consume_items( it, batch_size, filter ) );
1387         }
1388     }
1389 
1390     if( !craft.has_tools_to_continue() ) {
1391 
1392         const std::vector<std::vector<tool_comp>> &tool_reqs = rec.simple_requirements().get_tools();
1393         const int batch_size = craft.charges;
1394 
1395         std::vector<std::vector<tool_comp>> adjusted_tool_reqs;
1396         for( const std::vector<tool_comp> &alternatives : tool_reqs ) {
1397             std::vector<tool_comp> adjusted_alternatives;
1398             for( const tool_comp &alternative : alternatives ) {
1399                 tool_comp adjusted_alternative = alternative;
1400                 if( adjusted_alternative.count > 0 ) {
1401                     adjusted_alternative.count *= batch_size;
1402                     // Only for the next 5% progress
1403                     adjusted_alternative.count = std::max( adjusted_alternative.count / 20, 1 );
1404                 }
1405                 adjusted_alternatives.push_back( adjusted_alternative );
1406             }
1407             adjusted_tool_reqs.push_back( adjusted_alternatives );
1408         }
1409 
1410         const requirement_data tool_continue_reqs( adjusted_tool_reqs,
1411                 std::vector<std::vector<quality_requirement>>(),
1412                 std::vector<std::vector<item_comp>>() );
1413 
1414         if( !tool_continue_reqs.can_make_with_inventory( crafting_inventory(), return_true<item> ) ) {
1415             std::string buffer = _( "You don't have the necessary tools to continue crafting!" );
1416             buffer += "\n";
1417             buffer += tool_continue_reqs.list_missing();
1418             popup( buffer, PF_NONE );
1419             return false;
1420         }
1421 
1422         inventory map_inv;
1423         map_inv.form_from_map( pos(), PICKUP_RANGE, this );
1424 
1425         std::vector<comp_selection<tool_comp>> new_tool_selections;
1426         for( const std::vector<tool_comp> &alternatives : tool_reqs ) {
1427             comp_selection<tool_comp> selection = select_tool_component( alternatives, batch_size,
1428             map_inv, true, true, []( int charges ) {
1429                 return charges / 20;
1430             } );
1431             if( selection.use_from == usage_from::cancel ) {
1432                 return false;
1433             }
1434             new_tool_selections.push_back( selection );
1435         }
1436 
1437         craft.set_cached_tool_selections( new_tool_selections );
1438         craft.set_tools_to_continue( true );
1439     }
1440 
1441     return true;
1442 }
select_requirements(const std::vector<const requirement_data * > & alternatives,int batch,const read_only_visitable & inv,const std::function<bool (const item &)> & filter) const1443 const requirement_data *Character::select_requirements(
1444     const std::vector<const requirement_data *> &alternatives, int batch,
1445     const read_only_visitable &inv,
1446     const std::function<bool( const item & )> &filter ) const
1447 {
1448     cata_assert( !alternatives.empty() );
1449     if( alternatives.size() == 1 || !is_avatar() ) {
1450         return alternatives.front();
1451     }
1452 
1453     uilist menu;
1454 
1455     for( const requirement_data *req : alternatives ) {
1456         // Write with a large width and then just re-join the lines, because
1457         // uilist does its own wrapping and we want to rely on that.
1458         std::vector<std::string> component_lines =
1459             req->get_folded_components_list( TERMX - 4, c_light_gray, inv, filter, batch, "",
1460                                              requirement_display_flags::no_unavailable );
1461         menu.addentry_desc( "", join( component_lines, "\n" ) );
1462     }
1463 
1464     menu.allow_cancel = true;
1465     menu.desc_enabled = true;
1466     menu.title = _( "Use which selection of components?" );
1467     menu.query();
1468 
1469     if( menu.ret < 0 || static_cast<size_t>( menu.ret ) >= alternatives.size() ) {
1470         return nullptr;
1471     }
1472 
1473     return alternatives[menu.ret];
1474 }
1475 
1476 /* selection of component if a recipe requirement has multiple options (e.g. 'duct tap' or 'welder') */
select_item_component(const std::vector<item_comp> & components,int batch,read_only_visitable & map_inv,bool can_cancel,const std::function<bool (const item &)> & filter,bool player_inv)1477 comp_selection<item_comp> Character::select_item_component( const std::vector<item_comp>
1478         &components,
1479         int batch, read_only_visitable &map_inv, bool can_cancel,
1480         const std::function<bool( const item & )> &filter, bool player_inv )
1481 {
1482     std::vector<item_comp> player_has;
1483     std::vector<item_comp> map_has;
1484     std::vector<item_comp> mixed;
1485 
1486     comp_selection<item_comp> selected;
1487 
1488     for( const auto &component : components ) {
1489         itype_id type = component.type;
1490         int count = ( component.count > 0 ) ? component.count * batch : std::abs( component.count );
1491 
1492         if( item::count_by_charges( type ) && count > 0 ) {
1493             int map_charges = map_inv.charges_of( type, INT_MAX, filter );
1494 
1495             // If map has infinite charges, just use them
1496             if( map_charges == item::INFINITE_CHARGES ) {
1497                 selected.use_from = usage_from::map;
1498                 selected.comp = component;
1499                 return selected;
1500             }
1501             if( player_inv ) {
1502                 int player_charges = charges_of( type, INT_MAX, filter );
1503                 bool found = false;
1504                 if( player_charges >= count ) {
1505                     player_has.push_back( component );
1506                     found = true;
1507                 }
1508                 if( map_charges >= count ) {
1509                     map_has.push_back( component );
1510                     found = true;
1511                 }
1512                 if( !found && player_charges + map_charges >= count ) {
1513                     mixed.push_back( component );
1514                 }
1515             } else {
1516                 if( map_charges >= count ) {
1517                     map_has.push_back( component );
1518                 }
1519             }
1520         } else { // Counting by units, not charges
1521 
1522             // Can't use pseudo items as components
1523             if( player_inv ) {
1524                 bool found = false;
1525                 const item item_sought( type );
1526                 if( item_sought.is_software() && count_softwares( type ) > 0 ) {
1527                     player_has.push_back( component );
1528                     found = true;
1529                 } else if( has_amount( type, count, false, filter ) ) {
1530                     player_has.push_back( component );
1531                     found = true;
1532                 }
1533                 if( map_inv.has_components( type, count, filter ) ) {
1534                     map_has.push_back( component );
1535                     found = true;
1536                 }
1537                 if( !found &&
1538                     amount_of( type, false, std::numeric_limits<int>::max(), filter ) +
1539                     map_inv.amount_of( type, false, std::numeric_limits<int>::max(), filter ) >= count ) {
1540                     mixed.push_back( component );
1541                 }
1542             } else {
1543                 if( map_inv.has_components( type, count, filter ) ) {
1544                     map_has.push_back( component );
1545                 }
1546             }
1547         }
1548     }
1549 
1550     /* select 1 component to use */
1551     if( player_has.size() + map_has.size() + mixed.size() == 1 ) { // Only 1 choice
1552         if( player_has.size() == 1 ) {
1553             selected.use_from = usage_from::player;
1554             selected.comp = player_has[0];
1555         } else if( map_has.size() == 1 ) {
1556             selected.use_from = usage_from::map;
1557             selected.comp = map_has[0];
1558         } else {
1559             selected.use_from = usage_from::both;
1560             selected.comp = mixed[0];
1561         }
1562     } else if( is_npc() ) {
1563         if( !player_has.empty() ) {
1564             selected.use_from = usage_from::player;
1565             selected.comp = player_has[0];
1566         } else if( !map_has.empty() ) {
1567             selected.use_from = usage_from::map;
1568             selected.comp = map_has[0];
1569         } else {
1570             debugmsg( "Attempted a recipe with no available components!" );
1571             selected.use_from = usage_from::cancel;
1572             return selected;
1573         }
1574     } else { // Let the player pick which component they want to use
1575         uilist cmenu;
1576         // Populate options with the names of the items
1577         for( auto &map_ha : map_has ) { // Index 0-(map_has.size()-1)
1578             std::string tmpStr = string_format( _( "%s (%d/%d nearby)" ),
1579                                                 item::nname( map_ha.type ),
1580                                                 ( map_ha.count * batch ),
1581                                                 item::count_by_charges( map_ha.type ) ?
1582                                                 map_inv.charges_of( map_ha.type, INT_MAX, filter ) :
1583                                                 map_inv.amount_of( map_ha.type, false, INT_MAX, filter ) );
1584             cmenu.addentry( tmpStr );
1585         }
1586         for( auto &player_ha : player_has ) { // Index map_has.size()-(map_has.size()+player_has.size()-1)
1587             std::string tmpStr = string_format( _( "%s (%d/%d on person)" ),
1588                                                 item::nname( player_ha.type ),
1589                                                 ( player_ha.count * batch ),
1590                                                 item::count_by_charges( player_ha.type ) ?
1591                                                 charges_of( player_ha.type, INT_MAX, filter ) :
1592                                                 amount_of( player_ha.type, false, INT_MAX, filter ) );
1593             cmenu.addentry( tmpStr );
1594         }
1595         for( auto &component : mixed ) {
1596             // Index player_has.size()-(map_has.size()+player_has.size()+mixed.size()-1)
1597             int available = item::count_by_charges( component.type ) ?
1598                             map_inv.charges_of( component.type, INT_MAX, filter ) +
1599                             charges_of( component.type, INT_MAX, filter ) :
1600                             map_inv.amount_of( component.type, false, INT_MAX, filter ) +
1601                             amount_of( component.type, false, INT_MAX, filter );
1602             std::string tmpStr = string_format( _( "%s (%d/%d nearby & on person)" ),
1603                                                 item::nname( component.type ),
1604                                                 component.count * batch,
1605                                                 available );
1606             cmenu.addentry( tmpStr );
1607         }
1608 
1609         // Unlike with tools, it's a bad thing if there aren't any components available
1610         if( cmenu.entries.empty() ) {
1611             if( player_inv ) {
1612                 if( has_trait( trait_DEBUG_HS ) ) {
1613                     selected.use_from = usage_from::player;
1614                     return selected;
1615                 }
1616             }
1617             debugmsg( "Attempted a recipe with no available components!" );
1618             selected.use_from = usage_from::cancel;
1619             return selected;
1620         }
1621 
1622         cmenu.allow_cancel = can_cancel;
1623 
1624         // Get the selection via a menu popup
1625         cmenu.title = _( "Use which component?" );
1626         cmenu.query();
1627 
1628         if( cmenu.ret < 0 ||
1629             static_cast<size_t>( cmenu.ret ) >= map_has.size() + player_has.size() + mixed.size() ) {
1630             selected.use_from = usage_from::cancel;
1631             return selected;
1632         }
1633 
1634         size_t uselection = static_cast<size_t>( cmenu.ret );
1635         if( uselection < map_has.size() ) {
1636             selected.use_from = usage_from::map;
1637             selected.comp = map_has[uselection];
1638         } else if( uselection < map_has.size() + player_has.size() ) {
1639             uselection -= map_has.size();
1640             selected.use_from = usage_from::player;
1641             selected.comp = player_has[uselection];
1642         } else {
1643             uselection -= map_has.size() + player_has.size();
1644             selected.use_from = usage_from::both;
1645             selected.comp = mixed[uselection];
1646         }
1647     }
1648 
1649     return selected;
1650 }
1651 
1652 // Prompts player to empty all newly-unsealed containers in inventory
1653 // Called after something that might have opened containers (making them buckets) but not emptied them
empty_buckets(Character & p)1654 static void empty_buckets( Character &p )
1655 {
1656     // First grab (remove) all items that are non-empty buckets and not wielded
1657     auto buckets = p.remove_items_with( [&p]( const item & it ) {
1658         return it.is_bucket_nonempty() && &it != &p.weapon;
1659     }, INT_MAX );
1660     for( auto &it : buckets ) {
1661         for( const item *in : it.contents.all_items_top() ) {
1662             drop_or_handle( *in, p );
1663         }
1664 
1665         it.contents.clear_items();
1666         drop_or_handle( it, p );
1667     }
1668 }
1669 
consume_items(const comp_selection<item_comp> & is,int batch,const std::function<bool (const item &)> & filter)1670 std::list<item> Character::consume_items( const comp_selection<item_comp> &is, int batch,
1671         const std::function<bool( const item & )> &filter )
1672 {
1673     return consume_items( get_map(), is, batch, filter, pos(), PICKUP_RANGE );
1674 }
1675 
consume_items(map & m,const comp_selection<item_comp> & is,int batch,const std::function<bool (const item &)> & filter,const tripoint & origin,int radius)1676 std::list<item> Character::consume_items( map &m, const comp_selection<item_comp> &is, int batch,
1677         const std::function<bool( const item & )> &filter,
1678         const tripoint &origin, int radius )
1679 {
1680     std::list<item> ret;
1681 
1682     if( has_trait( trait_DEBUG_HS ) ) {
1683         return ret;
1684     }
1685 
1686     item_comp selected_comp = is.comp;
1687 
1688     const tripoint &loc = origin;
1689     const bool by_charges = item::count_by_charges( selected_comp.type ) && selected_comp.count > 0;
1690     // Count given to use_amount/use_charges, changed by those functions!
1691     int real_count = ( selected_comp.count > 0 ) ? selected_comp.count * batch : std::abs(
1692                          selected_comp.count );
1693     // First try to get everything from the map, than (remaining amount) from player
1694     if( is.use_from & usage_from::map ) {
1695         if( by_charges ) {
1696             std::list<item> tmp = m.use_charges( loc, radius, selected_comp.type, real_count, filter );
1697             ret.splice( ret.end(), tmp );
1698         } else {
1699             std::list<item> tmp = m.use_amount( loc, radius, selected_comp.type, real_count, filter );
1700             remove_ammo( tmp, *this );
1701             ret.splice( ret.end(), tmp );
1702         }
1703     }
1704     if( is.use_from & usage_from::player ) {
1705         if( by_charges ) {
1706             std::list<item> tmp = use_charges( selected_comp.type, real_count, filter );
1707             ret.splice( ret.end(), tmp );
1708         } else {
1709             std::list<item> tmp = use_amount( selected_comp.type, real_count, filter );
1710             remove_ammo( tmp, *this );
1711             ret.splice( ret.end(), tmp );
1712         }
1713     }
1714     // condense those items into one
1715     if( by_charges && ret.size() > 1 ) {
1716         std::list<item>::iterator b = ret.begin();
1717         b++;
1718         while( ret.size() > 1 ) {
1719             ret.front().charges += b->charges;
1720             b = ret.erase( b );
1721         }
1722     }
1723     lastconsumed = selected_comp.type;
1724     empty_buckets( *this );
1725     return ret;
1726 }
1727 
1728 /* This call is in-efficient when doing it for multiple items with the same map inventory.
1729 In that case, consider using select_item_component with 1 pre-created map inventory, and then passing the results
1730 to consume_items */
consume_items(const std::vector<item_comp> & components,int batch,const std::function<bool (const item &)> & filter)1731 std::list<item> Character::consume_items( const std::vector<item_comp> &components, int batch,
1732         const std::function<bool( const item & )> &filter )
1733 {
1734     inventory map_inv;
1735     map_inv.form_from_map( pos(), PICKUP_RANGE, this );
1736     return consume_items( select_item_component( components, batch, map_inv, false, filter ), batch,
1737                           filter );
1738 }
1739 
consume_software_container(const itype_id & software_id)1740 bool Character::consume_software_container( const itype_id &software_id )
1741 {
1742     for( item_location it : all_items_loc() ) {
1743         if( !it.get_item() ) {
1744             continue;
1745         }
1746         if( it.get_item()->is_software_storage() ) {
1747             for( const item *soft : it.get_item()->softwares() ) {
1748                 if( soft->typeId() == software_id ) {
1749                     it.remove_item();
1750                     return true;
1751                 }
1752             }
1753         }
1754     }
1755     return false;
1756 }
1757 
1758 comp_selection<tool_comp>
select_tool_component(const std::vector<tool_comp> & tools,int batch,read_only_visitable & map_inv,bool can_cancel,bool player_inv,const std::function<int (int)> & charges_required_modifier)1759 Character::select_tool_component( const std::vector<tool_comp> &tools, int batch,
1760                                   read_only_visitable &map_inv, bool can_cancel, bool player_inv,
1761                                   const std::function<int( int )> &charges_required_modifier )
1762 {
1763 
1764     comp_selection<tool_comp> selected;
1765     auto calc_charges = [&]( const tool_comp & t, bool ui = false ) {
1766         const int full_craft_charges = item::find_type( t.type )->charge_factor() * t.count * batch;
1767         const int modified_charges = charges_required_modifier( full_craft_charges );
1768         return std::max( ui ? full_craft_charges : modified_charges, 1 );
1769     };
1770 
1771     bool found_nocharge = false;
1772     std::vector<tool_comp> player_has;
1773     std::vector<tool_comp> map_has;
1774     std::vector<tool_comp> both_has;
1775     // Use charges of any tools that require charges used
1776     for( auto it = tools.begin(); it != tools.end() && !found_nocharge; ++it ) {
1777         itype_id type = it->type;
1778         if( it->count > 0 ) {
1779             const int count = calc_charges( *it );
1780             if( player_inv && crafting_inventory( pos(), -1 ).has_charges( type, count ) ) {
1781                 player_has.push_back( *it );
1782             }
1783             if( map_inv.has_charges( type, count ) ) {
1784                 map_has.push_back( *it );
1785             }
1786             // Needed for tools that can have power in a different location, such as a UPS.
1787             // Will only populate if no other options were found.
1788             if( player_inv && player_has.size() + map_has.size() == 0 &&
1789                 crafting_inventory().has_charges( type, count ) ) {
1790                 both_has.push_back( *it );
1791             }
1792         } else if( ( player_inv && has_amount( type, 1 ) ) || map_inv.has_tools( type, 1 ) ) {
1793             selected.comp = *it;
1794             found_nocharge = true;
1795         }
1796     }
1797     if( found_nocharge ) {
1798         selected.use_from = usage_from::none;
1799         return selected;    // Default to using a tool that doesn't require charges
1800     }
1801 
1802     if( ( both_has.size() + player_has.size() + map_has.size() == 1 ) || is_npc() ) {
1803         if( !both_has.empty() ) {
1804             selected.use_from = usage_from::both;
1805             selected.comp = both_has[0];
1806         } else if( !map_has.empty() ) {
1807             selected.use_from = usage_from::map;
1808             selected.comp = map_has[0];
1809         } else if( !player_has.empty() ) {
1810             selected.use_from = usage_from::player;
1811             selected.comp = player_has[0];
1812         } else {
1813             selected.use_from = usage_from::none;
1814             return selected;
1815         }
1816     } else { // Variety of options, list them and pick one
1817         // Populate the list
1818         uilist tmenu;
1819         for( auto &map_ha : map_has ) {
1820             if( item::find_type( map_ha.type )->maximum_charges() > 1 ) {
1821                 std::string tmpStr = string_format( _( "%s (%d/%d charges nearby)" ),
1822                                                     item::nname( map_ha.type ), calc_charges( map_ha, true ),
1823                                                     map_inv.charges_of( map_ha.type ) );
1824                 tmenu.addentry( tmpStr );
1825             } else {
1826                 std::string tmpStr = item::nname( map_ha.type ) + _( " (nearby)" );
1827                 tmenu.addentry( tmpStr );
1828             }
1829         }
1830         for( auto &player_ha : player_has ) {
1831             if( item::find_type( player_ha.type )->maximum_charges() > 1 ) {
1832                 std::string tmpStr = string_format( _( "%s (%d/%d charges on person)" ),
1833                                                     item::nname( player_ha.type ), calc_charges( player_ha, true ),
1834                                                     charges_of( player_ha.type ) );
1835                 tmenu.addentry( tmpStr );
1836             } else {
1837                 tmenu.addentry( item::nname( player_ha.type ) );
1838             }
1839         }
1840         for( auto &both_ha : both_has ) {
1841             if( item::find_type( both_ha.type )->maximum_charges() > 1 ) {
1842                 std::string tmpStr = string_format( _( "%s (%d/%d charges nearby or on person)" ),
1843                                                     item::nname( both_ha.type ), calc_charges( both_ha, true ),
1844                                                     charges_of( both_ha.type ) );
1845                 tmenu.addentry( tmpStr );
1846             } else {
1847                 std::string tmpStr = item::nname( both_ha.type ) + _( " (at hand)" );
1848                 tmenu.addentry( tmpStr );
1849             }
1850         }
1851 
1852         if( tmenu.entries.empty() ) {  // This SHOULD only happen if cooking with a fire,
1853             selected.use_from = usage_from::none;
1854             return selected;    // and the fire goes out.
1855         }
1856 
1857         tmenu.allow_cancel = can_cancel;
1858 
1859         // Get selection via a popup menu
1860         tmenu.title = _( "Use which tool?" );
1861         tmenu.query();
1862 
1863         if( tmenu.ret < 0 || static_cast<size_t>( tmenu.ret ) >= map_has.size()
1864             + player_has.size() + both_has.size() ) {
1865             selected.use_from = usage_from::cancel;
1866             return selected;
1867         }
1868 
1869         size_t uselection = static_cast<size_t>( tmenu.ret );
1870         if( uselection < map_has.size() ) {
1871             selected.use_from = usage_from::map;
1872             selected.comp = map_has[uselection];
1873         } else if( uselection < map_has.size() + player_has.size() ) {
1874             uselection -= map_has.size();
1875             selected.use_from = usage_from::player;
1876             selected.comp = player_has[uselection];
1877         } else {
1878             uselection -= map_has.size() + player_has.size();
1879             selected.use_from = usage_from::both;
1880             selected.comp = both_has[uselection];
1881         }
1882     }
1883 
1884     return selected;
1885 }
1886 
craft_consume_tools(item & craft,int multiplier,bool start_craft)1887 bool Character::craft_consume_tools( item &craft, int multiplier, bool start_craft )
1888 {
1889     if( !craft.is_craft() ) {
1890         debugmsg( "craft_consume_tools() called on non-craft '%s.' Aborting.", craft.tname() );
1891         return false;
1892     }
1893     if( has_trait( trait_DEBUG_HS ) ) {
1894         return true;
1895     }
1896 
1897     const auto calc_charges = [&craft, &start_craft, &multiplier]( int charges ) {
1898         int ret = charges;
1899 
1900         if( ret <= 0 ) {
1901             return ret;
1902         }
1903 
1904         // Account for batch size
1905         ret *= craft.charges;
1906 
1907         // Only for the next 5% progress
1908         ret /= 20;
1909 
1910         // In case more than 5% progress was accomplished in one turn
1911         ret *= multiplier;
1912 
1913         // If just starting consume the remainder as well
1914         if( start_craft ) {
1915             ret += ( charges * craft.charges ) % 20;
1916         }
1917         return ret;
1918     };
1919 
1920     // First check if we still have our cached selections
1921     const std::vector<comp_selection<tool_comp>> &cached_tool_selections =
1922                 craft.get_cached_tool_selections();
1923 
1924     inventory map_inv;
1925     map_inv.form_from_map( pos(), PICKUP_RANGE, this );
1926 
1927     for( const comp_selection<tool_comp> &tool_sel : cached_tool_selections ) {
1928         itype_id type = tool_sel.comp.type;
1929         if( tool_sel.comp.count > 0 ) {
1930             const int count = calc_charges( tool_sel.comp.count );
1931             switch( tool_sel.use_from ) {
1932                 case usage_from::player:
1933                     if( !has_charges( type, count ) ) {
1934                         add_msg_player_or_npc(
1935                             _( "You have insufficient %s charges and can't continue crafting" ),
1936                             _( "<npcname> has insufficient %s charges and can't continue crafting" ),
1937                             item::nname( type ) );
1938                         craft.set_tools_to_continue( false );
1939                         return false;
1940                     }
1941                     break;
1942                 case usage_from::map:
1943                     if( !map_inv.has_charges( type, count ) ) {
1944                         add_msg_player_or_npc(
1945                             _( "You have insufficient %s charges and can't continue crafting" ),
1946                             _( "<npcname> has insufficient %s charges and can't continue crafting" ),
1947                             item::nname( type ) );
1948                         craft.set_tools_to_continue( false );
1949                         return false;
1950                     }
1951                     break;
1952                 case usage_from::both:
1953                     if( !( crafting_inventory() ).has_charges( type, count ) ) {
1954                         add_msg_player_or_npc(
1955                             _( "You have insufficient %s charges and can't continue crafting" ),
1956                             _( "<npcname> has insufficient %s charges and can't continue crafting" ),
1957                             item::nname( type ) );
1958                         craft.set_tools_to_continue( false );
1959                         return false;
1960                     }
1961                 case usage_from::none:
1962                 case usage_from::cancel:
1963                 case usage_from::num_usages_from:
1964                     break;
1965             }
1966         } else if( !has_amount( type, 1 ) && !map_inv.has_tools( type, 1 ) ) {
1967             add_msg_player_or_npc(
1968                 _( "You no longer have a %s and can't continue crafting" ),
1969                 _( "<npcname> no longer has a %s and can't continue crafting" ),
1970                 item::nname( type ) );
1971             craft.set_tools_to_continue( false );
1972             return false;
1973         }
1974     }
1975 
1976     // We have the selections, so consume them
1977     for( const comp_selection<tool_comp> &tool : cached_tool_selections ) {
1978         comp_selection<tool_comp> to_consume = tool;
1979         to_consume.comp.count = calc_charges( to_consume.comp.count );
1980         consume_tools( to_consume, 1 );
1981     }
1982     return true;
1983 }
1984 
consume_tools(const comp_selection<tool_comp> & tool,int batch)1985 void Character::consume_tools( const comp_selection<tool_comp> &tool, int batch )
1986 {
1987     consume_tools( get_map(), tool, batch, pos(), PICKUP_RANGE );
1988 }
1989 
1990 /* we use this if we selected the tool earlier */
consume_tools(map & m,const comp_selection<tool_comp> & tool,int batch,const tripoint & origin,int radius,basecamp * bcp)1991 void Character::consume_tools( map &m, const comp_selection<tool_comp> &tool, int batch,
1992                                const tripoint &origin, int radius, basecamp *bcp )
1993 {
1994     if( has_trait( trait_DEBUG_HS ) ) {
1995         return;
1996     }
1997 
1998     const itype *tmp = item::find_type( tool.comp.type );
1999     int quantity = tool.comp.count * batch * tmp->charge_factor();
2000     if( tool.use_from == usage_from::both ) {
2001         use_charges( tool.comp.type, quantity, radius );
2002     } else if( tool.use_from == usage_from::player ) {
2003         use_charges( tool.comp.type, quantity );
2004     } else if( tool.use_from == usage_from::map ) {
2005         m.use_charges( origin, radius, tool.comp.type, quantity, return_true<item>, bcp );
2006         // Map::use_charges() does not handle UPS charges.
2007         if( quantity > 0 ) {
2008             use_charges( tool.comp.type, quantity, radius );
2009         }
2010     }
2011 
2012     // else, usage_from::none (or usage_from::cancel), so we don't use up any tools;
2013 }
2014 
2015 /* This call is in-efficient when doing it for multiple items with the same map inventory.
2016 In that case, consider using select_tool_component with 1 pre-created map inventory, and then passing the results
2017 to consume_tools */
consume_tools(const std::vector<tool_comp> & tools,int batch)2018 void Character::consume_tools( const std::vector<tool_comp> &tools, int batch )
2019 {
2020     inventory map_inv;
2021     map_inv.form_from_map( pos(), PICKUP_RANGE, this );
2022     consume_tools( select_tool_component( tools, batch, map_inv ), batch );
2023 }
2024 
can_disassemble(const item & obj,const read_only_visitable & inv) const2025 ret_val<bool> Character::can_disassemble( const item &obj, const read_only_visitable &inv ) const
2026 {
2027     if( !obj.is_disassemblable() ) {
2028         return ret_val<bool>::make_failure( _( "You cannot disassemble this." ) );
2029     }
2030 
2031     const recipe &r = recipe_dictionary::get_uncraft( ( obj.typeId() == itype_disassembly ) ?
2032                       obj.components.front().typeId() : obj.typeId() );
2033 
2034     // check sufficient light
2035     if( lighting_craft_speed_multiplier( r ) == 0.0f ) {
2036         return ret_val<bool>::make_failure( _( "You can't see to craft!" ) );
2037     }
2038 
2039     // refuse to disassemble rotten items
2040     const item *food = obj.get_food();
2041     if( ( obj.goes_bad() && obj.rotten() ) || ( food && food->goes_bad() && food->rotten() ) ) {
2042         return ret_val<bool>::make_failure( _( "It's rotten, I'm not taking that apart." ) );
2043     }
2044 
2045     // refuse to disassemble items containing monsters/pets
2046     std::string monster = obj.get_var( "contained_name" );
2047     if( !monster.empty() ) {
2048         return ret_val<bool>::make_failure( _( "You must remove the %s before you can disassemble this." ),
2049                                             monster );
2050     }
2051 
2052     if( !obj.is_ammo() ) { //we get ammo quantity to disassemble later on
2053         if( obj.count_by_charges() && !r.has_flag( flag_UNCRAFT_SINGLE_CHARGE ) ) {
2054             // Create a new item to get the default charges
2055             int qty = r.create_result().charges;
2056             if( obj.charges < qty ) {
2057                 const char *msg = ngettext( "You need at least %d charge of %s.",
2058                                             "You need at least %d charges of %s.", qty );
2059                 return ret_val<bool>::make_failure( msg, qty, obj.tname() );
2060             }
2061         }
2062     }
2063     const auto &dis = r.disassembly_requirements();
2064 
2065     for( const auto &opts : dis.get_qualities() ) {
2066         for( const auto &qual : opts ) {
2067             if( !qual.has( inv, return_true<item> ) ) {
2068                 // Here should be no dot at the end of the string as 'to_string()' provides it.
2069                 return ret_val<bool>::make_failure( _( "You need %s" ), qual.to_string() );
2070             }
2071         }
2072     }
2073 
2074     for( const auto &opts : dis.get_tools() ) {
2075         const bool found = std::any_of( opts.begin(), opts.end(),
2076         [&]( const tool_comp & tool ) {
2077             return ( tool.count <= 0 && inv.has_tools( tool.type, 1 ) ) ||
2078                    ( tool.count  > 0 && inv.has_charges( tool.type, tool.count ) );
2079         } );
2080 
2081         if( !found ) {
2082             const tool_comp &tool_required = opts.front();
2083             if( tool_required.count <= 0 ) {
2084                 return ret_val<bool>::make_failure( _( "You need %s." ),
2085                                                     item::nname( tool_required.type ) );
2086             } else {
2087                 //~ %1$s: tool name, %2$d: needed charges
2088                 return ret_val<bool>::make_failure( ngettext( "You need a %1$s with %2$d charge.",
2089                                                     "You need a %1$s with %2$d charges.", tool_required.count ),
2090                                                     item::nname( tool_required.type ),
2091                                                     tool_required.count );
2092             }
2093         }
2094     }
2095 
2096     return ret_val<bool>::make_success();
2097 }
2098 
create_in_progress_disassembly(item_location target)2099 item_location Character::create_in_progress_disassembly( item_location target )
2100 {
2101     const auto &r = recipe_dictionary::get_uncraft( target->typeId() );
2102     item &orig_item = *target.get_item();
2103 
2104     // Remove any batteries, ammo, contents and mods first
2105     remove_ammo( orig_item, *this );
2106     remove_radio_mod( orig_item, *this );
2107     if( orig_item.is_container() ) {
2108         orig_item.spill_contents( pos() );
2109     }
2110     if( orig_item.count_by_charges() ) {
2111         // remove the charges that one would get from crafting it
2112         if( orig_item.is_ammo() && !r.has_flag( "UNCRAFT_BY_QUANTITY" ) ) {
2113             //subtract selected number of rounds to disassemble
2114             orig_item.charges -= activity.position;
2115         } else {
2116             orig_item.charges -= r.create_result().charges;
2117         }
2118     }
2119     item new_disassembly( &r, orig_item );
2120     // remove the item, except when it's counted by charges and still has some
2121     if( !orig_item.count_by_charges() || orig_item.charges <= 0 ) {
2122         target.remove_item();
2123     }
2124 
2125     item_location disassembly_in_world;
2126 
2127     if( !has_two_arms() ) {
2128         disassembly_in_world = set_item_map_or_vehicle( *this, pos(), new_disassembly );
2129     } else if( !has_wield_conflicts( new_disassembly ) ) {
2130         if( cata::optional<item_location> it_loc = wield_craft( *this, new_disassembly ) ) {
2131             disassembly_in_world = *it_loc;
2132         }  else {
2133             put_into_vehicle_or_drop( *this, item_drop_reason::tumbling, {new_disassembly} );
2134         }
2135     } else {
2136         enum option : int {
2137             WIELD_CRAFT = 0,
2138             DROP_CRAFT,
2139             STASH,
2140             DROP
2141         };
2142 
2143         uilist amenu;
2144         amenu.text = string_format( pgettext( "in progress disassembly", "What to do with the %s?" ),
2145                                     new_disassembly.display_name() );
2146 
2147         amenu.addentry( WIELD_CRAFT, can_unwield( weapon ).success(),
2148                         '1', _( "Dispose of your wielded %s and start working." ), weapon.tname() );
2149         amenu.addentry( DROP_CRAFT, true, '2', _( "Put it down and start working." ) );
2150         const bool can_stash = can_pickVolume( new_disassembly ) &&
2151                                can_pickWeight( new_disassembly, !get_option<bool>( "DANGEROUS_PICKUPS" ) );
2152         amenu.addentry( STASH, can_stash, '3', _( "Store it in your inventory." ) );
2153         amenu.addentry( DROP, true, '4', _( "Drop it on the ground." ) );
2154 
2155         amenu.query();
2156         const option choice = amenu.ret == UILIST_CANCEL ? DROP : static_cast<option>( amenu.ret );
2157         switch( choice ) {
2158             case WIELD_CRAFT: {
2159                 if( cata::optional<item_location> it_loc = wield_craft( *this, new_disassembly ) ) {
2160                     disassembly_in_world = *it_loc;
2161                 } else {
2162                     put_into_vehicle_or_drop( *this, item_drop_reason::tumbling, {new_disassembly} );
2163                 }
2164                 break;
2165             }
2166             case DROP_CRAFT: {
2167                 disassembly_in_world = set_item_map_or_vehicle( *this, pos(), new_disassembly );
2168                 break;
2169             }
2170             case STASH: {
2171                 set_item_inventory( *this, new_disassembly );
2172                 break;
2173             }
2174             case DROP: {
2175                 put_into_vehicle_or_drop( *this, item_drop_reason::deliberate, {new_disassembly} );
2176                 break;
2177             }
2178         }
2179     }
2180 
2181     if( !disassembly_in_world.get_item() ) {
2182         add_msg_if_player( _( "Wield and activate the %s to start disassembling." ),
2183                            new_disassembly.tname() );
2184         return item_location::nowhere;
2185     }
2186 
2187     return disassembly_in_world;
2188 }
2189 
disassemble()2190 bool Character::disassemble()
2191 {
2192     return disassemble( game_menus::inv::disassemble( *this ), false );
2193 }
2194 
disassemble(item_location target,bool interactive)2195 bool Character::disassemble( item_location target, bool interactive )
2196 {
2197     if( !target ) {
2198         add_msg( _( "Never mind." ) );
2199         return false;
2200     }
2201 
2202     const item &obj = *target;
2203     const auto ret = can_disassemble( obj, crafting_inventory() );
2204 
2205     if( !ret.success() ) {
2206         add_msg_if_player( m_info, "%s", ret.c_str() );
2207         return false;
2208     }
2209 
2210     avatar &player_character = get_avatar();
2211     const auto &r = ( obj.typeId() == itype_disassembly ) ? obj.get_making() :
2212                     recipe_dictionary::get_uncraft( obj.typeId() );
2213     if( !obj.is_owned_by( player_character, true ) ) {
2214         if( !query_yn( _( "Disassembling the %s may anger the people who own it, continue?" ),
2215                        obj.tname() ) ) {
2216             return false;
2217         } else {
2218             if( obj.get_owner() ) {
2219                 std::vector<npc *> witnesses;
2220                 for( npc &elem : g->all_npcs() ) {
2221                     if( rl_dist( elem.pos(), player_character.pos() ) < MAX_VIEW_DISTANCE && elem.get_faction() &&
2222                         obj.is_owned_by( elem ) && elem.sees( player_character.pos() ) ) {
2223                         elem.say( "<witnessed_thievery>", 7 );
2224                         npc *npc_to_add = &elem;
2225                         witnesses.push_back( npc_to_add );
2226                     }
2227                 }
2228                 if( !witnesses.empty() ) {
2229                     if( player_character.add_faction_warning( obj.get_owner() ) ) {
2230                         for( npc *elem : witnesses ) {
2231                             elem->make_angry();
2232                         }
2233                     }
2234                 }
2235             }
2236         }
2237     }
2238     // last chance to back out
2239     if( interactive && get_option<bool>( "QUERY_DISASSEMBLE" ) && obj.typeId() != itype_disassembly ) {
2240         std::string list;
2241         const auto components = obj.get_uncraft_components();
2242         for( const auto &component : components ) {
2243             list += "- " + component.to_string() + "\n";
2244         }
2245         if( !r.learn_by_disassembly.empty() && !knows_recipe( &r ) && can_decomp_learn( r ) ) {
2246             if( !query_yn(
2247                     _( "Disassembling the %s may yield:\n%s\nReally disassemble?\nYou feel you may be able to understand this object's construction.\n" ),
2248                     colorize( obj.tname(), obj.color_in_inventory() ),
2249                     list ) ) {
2250                 return false;
2251             }
2252         } else if( !query_yn( _( "Disassembling the %s may yield:\n%s\nReally disassemble?" ),
2253                               colorize( obj.tname(), obj.color_in_inventory() ),
2254                               list ) ) {
2255             return false;
2256         }
2257     }
2258 
2259     if( activity.id() != ACT_DISASSEMBLE ) {
2260         player_activity new_act;
2261         // If we're disassembling ammo, prompt the player to specify amount
2262         // This could be extended more generally in the future
2263         int num_dis = 0;
2264         if( obj.is_ammo() && !r.has_flag( "UNCRAFT_BY_QUANTITY" ) ) {
2265             string_input_popup popup_input;
2266             const std::string title = string_format( _( "Disassemble how many %s [MAX: %d]: " ),
2267                                       obj.type_name( 1 ), obj.charges );
2268             popup_input.title( title ).edit( num_dis );
2269             if( popup_input.canceled() || num_dis <= 0 ) {
2270                 add_msg( _( "Never mind." ) );
2271                 return false;
2272             }
2273         }
2274         if( num_dis != 0 ) {
2275             new_act = player_activity( disassemble_activity_actor( r.time_to_craft_moves( *this,
2276                                        recipe_time_flag::ignore_proficiencies ) * num_dis ) );
2277         } else {
2278             new_act = player_activity( disassemble_activity_actor( r.time_to_craft_moves( *this,
2279                                        recipe_time_flag::ignore_proficiencies ) ) );
2280         }
2281         new_act.targets.emplace_back( std::move( target ) );
2282 
2283         // index is used as a bool that indicates if we want recursive uncraft.
2284         new_act.index = false;
2285         // Unused position attribute used to store ammo to disassemble
2286         new_act.position = std::min( num_dis, obj.charges );
2287         assign_activity( new_act );
2288     } else {
2289         // index is used as a bool that indicates if we want recursive uncraft.
2290         activity.index = false;
2291         activity.targets.emplace_back( std::move( target ) );
2292 
2293         if( activity.moves_left <= 0 ) {
2294             activity.moves_left = r.time_to_craft_moves( *this, recipe_time_flag::ignore_proficiencies );
2295         }
2296     }
2297 
2298     return true;
2299 }
2300 
disassemble_all(bool one_pass)2301 void Character::disassemble_all( bool one_pass )
2302 {
2303     // Reset all the activity values
2304     assign_activity( player_activity(), true );
2305 
2306     bool found_any = false;
2307     std::vector<item_location> to_disassemble;
2308     for( item &it : get_map().i_at( pos() ) ) {
2309         to_disassemble.push_back( item_location( map_cursor( pos() ), &it ) );
2310     }
2311     for( item_location &it_loc : to_disassemble ) {
2312         // Prevent disassembling an in process disassembly because it could have been created by a previous iteration of this loop
2313         // and choosing to place it on the ground
2314         if( disassemble( it_loc, false ) ) {
2315             found_any = true;
2316         }
2317     }
2318 
2319     // index is used as a bool that indicates if we want recursive uncraft.
2320     // Upon calling complete_disassemble, if we have no targets left,
2321     // we will call this function again.
2322     activity.index = !one_pass;
2323 
2324     if( !found_any ) {
2325         // Reset the activity - don't loop if there is nothing to do
2326         activity = player_activity();
2327     }
2328 }
2329 
complete_disassemble(item_location target)2330 void Character::complete_disassemble( item_location target )
2331 {
2332     // Disassembly has 2 parallel vectors:
2333     // item location, and recipe id
2334     item temp = target.get_item()->components.front();
2335     const recipe &rec = recipe_dictionary::get_uncraft( temp.typeId() );
2336 
2337     if( rec ) {
2338         complete_disassemble( target, rec );
2339     } else {
2340         debugmsg( "bad disassembly recipe: %d", temp.type_name() );
2341         activity.set_to_null();
2342         return;
2343     }
2344 
2345     // If we have no more targets end the activity or start a second round
2346     if( activity.targets.empty() ) {
2347         // index is used as a bool that indicates if we want recursive uncraft.
2348         if( activity.index ) {
2349             disassemble_all( false );
2350             return;
2351         } else {
2352             // No more targets
2353             activity.set_to_null();
2354             return;
2355         }
2356     }
2357     item *next_item = activity.targets.back().get_item();
2358     if( !next_item || next_item->is_null() ) {
2359         debugmsg( "bad item" );
2360         activity.set_to_null();
2361         return;
2362     }
2363     // Set get and set duration of next uncraft
2364     const recipe &next_recipe = recipe_dictionary::get_uncraft( ( next_item->typeId() ==
2365                                 itype_disassembly ) ? next_item->components.front().typeId() : next_item->typeId() );
2366 
2367     if( !next_recipe ) {
2368         debugmsg( "bad disassembly recipe" );
2369         activity.set_to_null();
2370         return;
2371     }
2372     int num_dis = 1;
2373     const item &obj = *activity.targets.back().get_item();
2374     if( obj.is_ammo() && !next_recipe.has_flag( "UNCRAFT_BY_QUANTITY" ) ) {
2375         string_input_popup popup_input;
2376         const std::string title = string_format( _( "Disassemble how many %s [MAX: %d]: " ),
2377                                   obj.type_name( 1 ), obj.charges );
2378         popup_input.title( title ).edit( num_dis );
2379         if( popup_input.canceled() || num_dis <= 0 ) {
2380             add_msg( _( "Never mind." ) );
2381             activity.set_to_null();
2382             return;
2383         }
2384     }
2385     player_activity new_act = player_activity( disassemble_activity_actor(
2386                                   next_recipe.time_to_craft_moves( *this,
2387                                           recipe_time_flag::ignore_proficiencies ) * num_dis ) );
2388     new_act.targets = activity.targets;
2389     new_act.index = activity.index;
2390     new_act.position = std::min( num_dis, obj.charges );
2391     assign_activity( new_act );
2392 }
2393 
complete_disassemble(item_location & target,const recipe & dis)2394 void Character::complete_disassemble( item_location &target, const recipe &dis )
2395 {
2396     // Get the proper recipe - the one for disassembly, not assembly
2397     const requirement_data dis_requirements = dis.disassembly_requirements();
2398     item &org_item = target.get_item()->components.front();
2399     const bool filthy = org_item.is_filthy();
2400     const tripoint loc = target.position();
2401 
2402     // Make a copy to keep its data (damage/components) even after it
2403     // has been removed.
2404     item dis_item = org_item;
2405 
2406     float component_success_chance = std::min( std::pow( 0.8, dis_item.damage_level() ), 1.0 );
2407 
2408     add_msg( _( "You disassemble the %s into its components." ), dis_item.tname() );
2409 
2410     // Get rid of the disassembly item
2411     target.remove_item();
2412 
2413     // Consume tool charges
2414     for( const auto &it : dis_requirements.get_tools() ) {
2415         consume_tools( it );
2416     }
2417 
2418     // add the components to the map
2419     // Player skills should determine how many components are returned
2420 
2421     int skill_dice = 2 + get_skill_level( dis.skill_used ) * 3;
2422     skill_dice += get_skill_level( dis.skill_used );
2423 
2424     // Sides on dice is 16 plus your current intelligence
2425     ///\EFFECT_INT increases success rate for disassembling items
2426     int skill_sides = 16 + int_cur;
2427 
2428     int diff_dice = dis.difficulty;
2429     int diff_sides = 24; // 16 + 8 (default intelligence)
2430 
2431     // disassembly only nets a bit of practice
2432     if( dis.skill_used ) {
2433         practice( dis.skill_used, ( dis.difficulty ) * 2, dis.difficulty );
2434     }
2435 
2436     // If the components aren't empty, we want items exactly identical to them
2437     // Even if the best-fit recipe does not involve those items
2438     std::list<item> components = dis_item.components;
2439 
2440     // If the components are empty, item is the default kind and made of default components
2441     if( components.empty() ) {
2442         const bool uncraft_liquids_contained = dis.has_flag( flag_UNCRAFT_LIQUIDS_CONTAINED );
2443         for( const auto &altercomps : dis_requirements.get_components() ) {
2444             const item_comp &comp = altercomps.front();
2445             int compcount = comp.count;
2446             item newit( comp.type, calendar::turn );
2447             //If ammo, overwrite component count with selected quantity of ammo
2448             if( dis_item.is_ammo() ) {
2449                 compcount *= activity.position;
2450             } else if( dis_item.count_by_charges() && dis.has_flag( flag_UNCRAFT_SINGLE_CHARGE ) ) {
2451                 // Counted-by-charge items that can be disassembled individually
2452                 // have their component count multiplied by the number of charges.
2453                 compcount *= std::min( dis_item.charges, dis.create_result().charges );
2454             }
2455             const bool is_liquid = newit.made_of( phase_id::LIQUID );
2456             if( uncraft_liquids_contained && is_liquid && newit.charges != 0 ) {
2457                 // Spawn liquid item in its default container
2458                 compcount = compcount / newit.charges;
2459                 if( compcount != 0 ) {
2460                     newit = newit.in_its_container();
2461                 }
2462             } else {
2463                 // Compress liquids and counted-by-charges items into one item,
2464                 // they are added together on the map anyway and handle_liquid
2465                 // should only be called once to put it all into a container at once.
2466                 if( newit.count_by_charges() || is_liquid ) {
2467                     newit.charges = compcount;
2468                     compcount = 1;
2469                 } else if( !newit.craft_has_charges() && newit.charges > 0 ) {
2470                     // tools that can be unloaded should be created unloaded,
2471                     // tools that can't be unloaded will keep their default charges.
2472                     newit.charges = 0;
2473                 }
2474             }
2475 
2476             // If the recipe has a `FULL_MAGAZINE` flag, spawn any magazines full of ammo
2477             if( newit.is_magazine() && dis.has_flag( flag_FULL_MAGAZINE ) ) {
2478                 newit.ammo_set( newit.ammo_default(),
2479                                 newit.ammo_capacity( item::find_type( newit.ammo_default() )->ammo->type ) );
2480             }
2481 
2482             for( ; compcount > 0; compcount-- ) {
2483                 components.emplace_back( newit );
2484             }
2485         }
2486     }
2487 
2488     std::list<item> drop_items;
2489 
2490     for( const item &newit : components ) {
2491         const bool comp_success = ( dice( skill_dice, skill_sides ) > dice( diff_dice,  diff_sides ) );
2492         if( dis.difficulty != 0 && !comp_success ) {
2493             add_msg( m_bad, _( "You fail to recover %s." ), newit.tname() );
2494             continue;
2495         }
2496         const bool dmg_success = component_success_chance > rng_float( 0, 1 );
2497         if( !dmg_success ) {
2498             // Show reason for failure (damaged item, tname contains the damage adjective)
2499             //~ %1s - material, %2$s - disassembled item
2500             add_msg( m_bad, _( "You fail to recover %1$s from the %2$s." ), newit.tname(),
2501                      dis_item.tname() );
2502             continue;
2503         }
2504         // Use item from components list, or (if not contained)
2505         // use newit, the default constructed.
2506         item act_item = newit;
2507 
2508         if( act_item.has_temperature() ) {
2509             act_item.set_item_temperature( temp_to_kelvin( get_weather().get_temperature( loc ) ) );
2510         }
2511 
2512         // Refitted clothing disassembles into refitted components (when applicable)
2513         if( dis_item.has_flag( flag_FIT ) && act_item.has_flag( flag_VARSIZE ) ) {
2514             act_item.set_flag( flag_FIT );
2515         }
2516 
2517         if( filthy ) {
2518             act_item.set_flag( flag_FILTHY );
2519         }
2520 
2521         for( std::list<item>::iterator a = dis_item.components.begin(); a != dis_item.components.end();
2522              ++a ) {
2523             if( a->type == newit.type ) {
2524                 act_item = *a;
2525                 dis_item.components.erase( a );
2526                 break;
2527             }
2528         }
2529 
2530         if( act_item.made_of( phase_id::LIQUID ) ) {
2531             liquid_handler::handle_all_liquid( act_item, PICKUP_RANGE );
2532         } else {
2533             drop_items.push_back( act_item );
2534         }
2535     }
2536 
2537     put_into_vehicle_or_drop( *this, item_drop_reason::deliberate, drop_items );
2538 
2539     if( !dis.learn_by_disassembly.empty() && !knows_recipe( &dis ) ) {
2540         if( can_decomp_learn( dis ) ) {
2541             // TODO: make this depend on intelligence
2542             if( one_in( 4 ) ) {
2543                 // TODO: change to forward an id or a reference
2544                 learn_recipe( &dis.ident().obj() );
2545                 add_msg( m_good, _( "You learned a recipe for %s from disassembling it!" ),
2546                          dis_item.tname() );
2547             } else {
2548                 add_msg( m_info, _( "You might be able to learn a recipe for %s if you disassemble another." ),
2549                          dis_item.tname() );
2550             }
2551         } else {
2552             add_msg( m_info, _( "If you had better skills, you might learn a recipe next time." ) );
2553         }
2554     }
2555 }
2556 
remove_ammo(std::list<item> & dis_items,Character & p)2557 void remove_ammo( std::list<item> &dis_items, Character &p )
2558 {
2559     for( auto &dis_item : dis_items ) {
2560         remove_ammo( dis_item, p );
2561     }
2562 }
2563 
drop_or_handle(const item & newit,Character & p)2564 void drop_or_handle( const item &newit, Character &p )
2565 {
2566     if( newit.made_of( phase_id::LIQUID ) && p.is_player() ) { // TODO: what about NPCs?
2567         liquid_handler::handle_all_liquid( newit, PICKUP_RANGE );
2568     } else {
2569         item tmp( newit );
2570         p.i_add_or_drop( tmp );
2571     }
2572 }
2573 
remove_ammo(item & dis_item,Character & p)2574 void remove_ammo( item &dis_item, Character &p )
2575 {
2576     dis_item.remove_items_with( [&p]( const item & it ) {
2577         if( it.is_irremovable() ) {
2578             return false;
2579         }
2580         drop_or_handle( it, p );
2581         return true;
2582     } );
2583 
2584     if( dis_item.has_flag( flag_NO_UNLOAD ) ) {
2585         return;
2586     }
2587     if( dis_item.is_gun() && !dis_item.ammo_current().is_null() ) {
2588         item ammodrop( dis_item.ammo_current(), calendar::turn );
2589         ammodrop.charges = dis_item.charges;
2590         drop_or_handle( ammodrop, p );
2591         dis_item.charges = 0;
2592     }
2593     if( dis_item.is_tool() && dis_item.charges > 0 && !dis_item.ammo_current().is_null() ) {
2594         item ammodrop( dis_item.ammo_current(), calendar::turn );
2595         ammodrop.charges = dis_item.charges;
2596         if( dis_item.ammo_current() == itype_plut_cell ) {
2597             ammodrop.charges /= PLUTONIUM_CHARGES;
2598         }
2599         drop_or_handle( ammodrop, p );
2600         dis_item.charges = 0;
2601     }
2602 }
2603 
get_crafting_helpers() const2604 std::vector<npc *> Character::get_crafting_helpers() const
2605 {
2606     return g->get_npcs_if( [this]( const npc & guy ) {
2607         // NPCs can help craft if awake, taking orders, within pickup range and have clear path
2608         return !guy.in_sleep_state() && guy.is_obeying( *this ) &&
2609                rl_dist( guy.pos(), pos() ) < PICKUP_RANGE &&
2610                get_map().clear_path( pos(), guy.pos(), PICKUP_RANGE, 1, 100 );
2611     } );
2612 }
2613