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