1 #include "construction.h"
2
3 #include <algorithm>
4 #include <array>
5 #include <cstddef>
6 #include <iterator>
7 #include <memory>
8 #include <numeric>
9 #include <utility>
10
11 #include "action.h"
12 #include "activity_type.h"
13 #include "avatar.h"
14 #include "build_reqs.h"
15 #include "calendar.h"
16 #include "cata_utility.h"
17 #include "character.h"
18 #include "colony.h"
19 #include "color.h"
20 #include "construction_category.h"
21 #include "construction_group.h"
22 #include "coordinates.h"
23 #include "cursesdef.h"
24 #include "debug.h"
25 #include "enums.h"
26 #include "event.h"
27 #include "event_bus.h"
28 #include "game.h"
29 #include "game_constants.h"
30 #include "input.h"
31 #include "inventory.h"
32 #include "item.h"
33 #include "item_group.h"
34 #include "item_stack.h"
35 #include "iuse.h"
36 #include "json.h"
37 #include "map.h"
38 #include "map_iterator.h"
39 #include "mapdata.h"
40 #include "memory_fast.h"
41 #include "messages.h"
42 #include "morale_types.h"
43 #include "mtype.h"
44 #include "npc.h"
45 #include "options.h"
46 #include "output.h"
47 #include "player.h"
48 #include "player_activity.h"
49 #include "point.h"
50 #include "requirements.h"
51 #include "rng.h"
52 #include "skill.h"
53 #include "string_formatter.h"
54 #include "string_input_popup.h"
55 #include "trap.h"
56 #include "ui_manager.h"
57 #include "uistate.h"
58 #include "units.h"
59 #include "veh_type.h"
60 #include "vehicle.h"
61 #include "vpart_position.h"
62
63 class read_only_visitable;
64
65 static const activity_id ACT_BUILD( "ACT_BUILD" );
66 static const activity_id ACT_MULTIPLE_CONSTRUCTION( "ACT_MULTIPLE_CONSTRUCTION" );
67
68 static const construction_category_id construction_category_ALL( "ALL" );
69 static const construction_category_id construction_category_FILTER( "FILTER" );
70 static const construction_category_id construction_category_REPAIR( "REPAIR" );
71
72 static const itype_id itype_2x4( "2x4" );
73 static const itype_id itype_nail( "nail" );
74 static const itype_id itype_sheet( "sheet" );
75 static const itype_id itype_stick( "stick" );
76 static const itype_id itype_string_36( "string_36" );
77
78 static const trap_str_id tr_firewood_source( "tr_firewood_source" );
79 static const trap_str_id tr_practice_target( "tr_practice_target" );
80 static const trap_str_id tr_unfinished_construction( "tr_unfinished_construction" );
81
82 static const skill_id skill_electronics( "electronics" );
83 static const skill_id skill_fabrication( "fabrication" );
84
85 static const quality_id qual_CUT( "CUT" );
86
87 static const trait_id trait_DEBUG_HS( "DEBUG_HS" );
88 static const trait_id trait_PAINRESIST_TROGLO( "PAINRESIST_TROGLO" );
89 static const trait_id trait_SPIRITUAL( "SPIRITUAL" );
90 static const trait_id trait_STOCKY_TROGLO( "STOCKY_TROGLO" );
91
92 static const std::string flag_FLAT( "FLAT" );
93 static const std::string flag_INITIAL_PART( "INITIAL_PART" );
94 static const std::string flag_SUPPORTS_ROOF( "SUPPORTS_ROOF" );
95 static const std::string flag_NO_FLOOR( "NO_FLOOR" );
96
97 static bool finalized = false;
98
99 // Construction functions.
100 namespace construct
101 {
102 // Checks for whether terrain mod can proceed
check_nothing(const tripoint &)103 static bool check_nothing( const tripoint & )
104 {
105 return true;
106 }
107 bool check_empty( const tripoint & ); // tile is empty
108 bool check_support( const tripoint & ); // at least two orthogonal supports
109 bool check_stable( const tripoint & ); // tile below has a flag SUPPORTS_ROOF
110 bool check_empty_stable( const tripoint & ); // tile is empty, tile below has a flag SUPPORTS_ROOF
111 bool check_nofloor_above( const tripoint & ); // tile above has a flag NO_FLOOR
112 bool check_deconstruct( const tripoint & ); // either terrain or furniture must be deconstructible
113 bool check_empty_up_OK( const tripoint & ); // tile is empty and below OVERMAP_HEIGHT
114 bool check_up_OK( const tripoint & ); // tile is below OVERMAP_HEIGHT
115 bool check_down_OK( const tripoint & ); // tile is above OVERMAP_DEPTH
116 bool check_no_trap( const tripoint & );
117 bool check_ramp_low( const tripoint & );
118 bool check_ramp_high( const tripoint & );
119
120 // Special actions to be run post-terrain-mod
done_nothing(const tripoint &)121 static void done_nothing( const tripoint & ) {}
122 void done_trunk_plank( const tripoint & );
123 void done_grave( const tripoint & );
124 void done_vehicle( const tripoint & );
125 void done_deconstruct( const tripoint & );
126 void done_digormine_stair( const tripoint &, bool );
127 void done_dig_stair( const tripoint & );
128 void done_mine_downstair( const tripoint & );
129 void done_mine_upstair( const tripoint & );
130 void done_wood_stairs( const tripoint & );
131 void done_window_curtains( const tripoint & );
132 void done_extract_maybe_revert_to_dirt( const tripoint & );
133 void done_mark_firewood( const tripoint & );
134 void done_mark_practice_target( const tripoint & );
135 void done_ramp_low( const tripoint & );
136 void done_ramp_high( const tripoint & );
137
138 void failure_standard( const tripoint & );
139 void failure_deconstruct( const tripoint & );
140 } // namespace construct
141
142 static std::vector<construction> constructions;
143 static std::map<construction_str_id, construction_id> construction_id_map;
144
145 // Helper functions, nobody but us needs to call these.
146 static bool can_construct( const construction_group_str_id &group );
147 static bool can_construct( const construction &con );
148 static bool player_can_build( player &p, const read_only_visitable &inv,
149 const construction_group_str_id &group );
150 static bool player_can_see_to_build( player &p, const construction_group_str_id &group );
151 static void place_construction( const construction_group_str_id &group );
152
153 // Color standardization for string streams
154 static const deferred_color color_title = def_c_light_red; //color for titles
155 static const deferred_color color_data = def_c_cyan; //color for data parts
156
has_pre_terrain(const construction & con,const tripoint & p)157 static bool has_pre_terrain( const construction &con, const tripoint &p )
158 {
159 if( con.pre_terrain.empty() ) {
160 return true;
161 }
162
163 map &here = get_map();
164 if( con.pre_is_furniture ) {
165 furn_id f = furn_id( con.pre_terrain );
166 return here.furn( p ) == f;
167 } else {
168 ter_id t = ter_id( con.pre_terrain );
169 return here.ter( p ) == t;
170 }
171 }
172
has_pre_terrain(const construction & con)173 static bool has_pre_terrain( const construction &con )
174 {
175 tripoint avatar_pos = get_player_character().pos();
176 for( const tripoint &p : get_map().points_in_radius( avatar_pos, 1 ) ) {
177 if( p != avatar_pos && has_pre_terrain( con, p ) ) {
178 return true;
179 }
180 }
181 return false;
182 }
183
standardize_construction_times(const int time)184 void standardize_construction_times( const int time )
185 {
186 if( !finalized ) {
187 debugmsg( "standardize_construction_times called before finalization" );
188 return;
189 }
190 for( auto &c : constructions ) {
191 c.time = time;
192 }
193 }
194
constructions_by_group(const construction_group_str_id & group)195 static std::vector<construction *> constructions_by_group( const construction_group_str_id &group )
196 {
197 if( !finalized ) {
198 debugmsg( "constructions_by_group called before finalization" );
199 return {};
200 }
201 std::vector<construction *> result;
202 for( auto &constructions_a : constructions ) {
203 if( constructions_a.group == group ) {
204 result.push_back( &constructions_a );
205 }
206 }
207 return result;
208 }
209
load_available_constructions(std::vector<construction_group_str_id> & available,std::map<construction_category_id,std::vector<construction_group_str_id>> & cat_available,bool hide_unconstructable)210 static void load_available_constructions( std::vector<construction_group_str_id> &available,
211 std::map<construction_category_id, std::vector<construction_group_str_id>> &cat_available,
212 bool hide_unconstructable )
213 {
214 cat_available.clear();
215 available.clear();
216 if( !finalized ) {
217 debugmsg( "load_available_constructions called before finalization" );
218 return;
219 }
220 avatar &player_character = get_avatar();
221 for( auto &it : constructions ) {
222 if( it.on_display && ( !hide_unconstructable ||
223 ( can_construct( it ) &&
224 player_can_build( player_character, player_character.crafting_inventory(), it ) ) ) ) {
225 bool already_have_it = false;
226 for( auto &avail_it : available ) {
227 if( avail_it == it.group ) {
228 already_have_it = true;
229 break;
230 }
231 }
232 if( !already_have_it ) {
233 available.push_back( it.group );
234 cat_available[it.category].push_back( it.group );
235 }
236 }
237 }
238 }
239
draw_grid(const catacurses::window & w,const int list_width)240 static void draw_grid( const catacurses::window &w, const int list_width )
241 {
242 draw_border( w );
243 mvwprintz( w, point( 2, 0 ), c_light_red, _( " Construction " ) );
244 // draw internal lines
245 mvwvline( w, point( list_width, 1 ), LINE_XOXO, getmaxy( w ) - 2 );
246 mvwhline( w, point( 1, 2 ), LINE_OXOX, list_width );
247 // draw intersections
248 mvwputch( w, point( list_width, 0 ), c_light_gray, LINE_OXXX );
249 mvwputch( w, point( list_width, getmaxy( w ) - 1 ), c_light_gray, LINE_XXOX );
250 mvwputch( w, point( 0, 2 ), c_light_gray, LINE_XXXO );
251 mvwputch( w, point( list_width, 2 ), c_light_gray, LINE_XOXX );
252
253 wnoutrefresh( w );
254 }
255
construction_color(const construction_group_str_id & group,bool highlight)256 static nc_color construction_color( const construction_group_str_id &group, bool highlight )
257 {
258 nc_color col = c_dark_gray;
259 Character &player_character = get_player_character();
260 if( player_character.has_trait( trait_DEBUG_HS ) ) {
261 col = c_white;
262 } else if( can_construct( group ) ) {
263 construction *con_first = nullptr;
264 std::vector<construction *> cons = constructions_by_group( group );
265 const inventory &total_inv = player_character.crafting_inventory();
266 for( auto &con : cons ) {
267 if( con->requirements->can_make_with_inventory( total_inv, is_crafting_component ) ) {
268 con_first = con;
269 break;
270 }
271 }
272 if( con_first != nullptr ) {
273 col = c_white;
274 for( const auto &pr : con_first->required_skills ) {
275 int s_lvl = player_character.get_skill_level( pr.first );
276 if( s_lvl < pr.second ) {
277 col = c_red;
278 } else if( s_lvl < pr.second * 1.25 ) {
279 col = c_light_blue;
280 }
281 }
282 }
283 }
284 return highlight ? hilite( col ) : col;
285 }
286
get_constructions()287 const std::vector<construction> &get_constructions()
288 {
289 if( !finalized ) {
290 debugmsg( "get_constructions called before finalization" );
291 static std::vector<construction> fake_constructions;
292 return fake_constructions;
293 }
294 return constructions;
295 }
296
construction_menu(const bool blueprint)297 construction_id construction_menu( const bool blueprint )
298 {
299 if( !finalized ) {
300 debugmsg( "construction_menu called before finalization" );
301 return construction_id( -1 );
302 }
303 static bool hide_unconstructable = false;
304 // only display constructions the player can theoretically perform
305 std::vector<construction_group_str_id> available;
306 std::map<construction_category_id, std::vector<construction_group_str_id>> cat_available;
307 load_available_constructions( available, cat_available, hide_unconstructable );
308
309 if( available.empty() ) {
310 popup( _( "You can not construct anything here." ) );
311 return construction_id( -1 );
312 }
313
314 int w_height = 0;
315 int w_width = 0;
316 catacurses::window w_con;
317
318 int w_list_width = 0;
319 int w_list_height = 0;
320 const int w_list_x0 = 1;
321 catacurses::window w_list;
322
323 std::vector<std::string> notes;
324 int pos_x = 0;
325 int available_window_width = 0;
326 int available_buffer_height = 0;
327
328 construction_id ret( -1 );
329
330 bool update_info = true;
331 bool update_cat = true;
332 bool isnew = true;
333 int tabindex = 0;
334 int select = 0;
335 int offset = 0;
336 bool exit = false;
337 construction_category_id category_id;
338 std::vector<construction_group_str_id> constructs;
339 //storage for the color text so it can be scrolled
340 std::vector< std::vector < std::string > > construct_buffers;
341 std::vector<std::string> full_construct_buffer;
342 std::vector<int> construct_buffer_breakpoints;
343 int total_project_breakpoints = 0;
344 int current_construct_breakpoint = 0;
345 avatar &player_character = get_avatar();
346 const inventory &total_inv = player_character.crafting_inventory();
347
348 input_context ctxt( "CONSTRUCTION" );
349 ctxt.register_action( "UP", to_translation( "Move cursor up" ) );
350 ctxt.register_action( "DOWN", to_translation( "Move cursor down" ) );
351 ctxt.register_action( "RIGHT", to_translation( "Move tab right" ) );
352 ctxt.register_action( "LEFT", to_translation( "Move tab left" ) );
353 ctxt.register_action( "PAGE_UP", to_translation( "Fast scroll up" ) );
354 ctxt.register_action( "PAGE_DOWN", to_translation( "Fast scroll down" ) );
355 ctxt.register_action( "SCROLL_STAGE_UP" );
356 ctxt.register_action( "SCROLL_STAGE_DOWN" );
357 ctxt.register_action( "CONFIRM" );
358 ctxt.register_action( "TOGGLE_UNAVAILABLE_CONSTRUCTIONS" );
359 ctxt.register_action( "QUIT" );
360 ctxt.register_action( "HELP_KEYBINDINGS" );
361 ctxt.register_action( "FILTER" );
362 ctxt.register_action( "RESET_FILTER" );
363
364 const std::vector<construction_category> &construct_cat = construction_categories::get_all();
365 const int tabcount = static_cast<int>( construction_category::count() );
366
367 std::string filter;
368
369 const nc_color color_stage = c_white;
370 ui_adaptor ui;
371
372 const auto recalc_buffer = [&]() {
373 //leave room for top and bottom UI text
374 available_buffer_height = w_height - 3 - 3 - static_cast<int>( notes.size() );
375
376 if( !constructs.empty() ) {
377 if( select >= static_cast<int>( constructs.size() ) ) {
378 select = 0;
379 }
380 const construction_group_str_id ¤t_group = constructs[select];
381
382 //construct the project list buffer
383
384 // Print stages and their requirement.
385 std::vector<construction *> options = constructions_by_group( current_group );
386
387 construct_buffers.clear();
388 current_construct_breakpoint = 0;
389 construct_buffer_breakpoints.clear();
390 full_construct_buffer.clear();
391 int stage_counter = 0;
392 for( std::vector<construction *>::iterator it = options.begin();
393 it != options.end(); ++it ) {
394 stage_counter++;
395 construction *current_con = *it;
396 if( hide_unconstructable && !can_construct( *current_con ) ) {
397 continue;
398 }
399 // Update the cached availability of components and tools in the requirement object
400 current_con->requirements->can_make_with_inventory( total_inv, is_crafting_component );
401
402 std::vector<std::string> current_buffer;
403
404 const auto add_folded = [&]( const std::vector<std::string> &folded ) {
405 current_buffer.insert( current_buffer.end(), folded.begin(), folded.end() );
406 };
407 const auto add_line = [&]( const std::string & line ) {
408 add_folded( foldstring( line, available_window_width ) );
409 };
410
411 // display final product name only if more than one step.
412 // Assume single stage constructions should be clear
413 // in their title what their result is.
414 if( !current_con->post_terrain.empty() && options.size() > 1 ) {
415 //also print out stage number when multiple stages are available
416 std::string current_line = string_format( _( "Stage/Variant #%d: " ), stage_counter );
417
418 // print name of the result of each stage
419 std::string result_string;
420 if( current_con->post_is_furniture ) {
421 result_string = furn_str_id( current_con->post_terrain ).obj().name();
422 } else {
423 result_string = ter_str_id( current_con->post_terrain ).obj().name();
424 }
425 current_line += colorize( result_string, color_title );
426 add_line( current_line );
427
428 // display description of the result for multi-stages
429 current_line = _( "Result: " );
430 if( current_con->post_is_furniture ) {
431 current_line += colorize(
432 furn_str_id( current_con->post_terrain ).obj().description,
433 color_data
434 );
435 } else {
436 current_line += colorize(
437 ter_str_id( current_con->post_terrain ).obj().description,
438 color_data
439 );
440 }
441 add_line( current_line );
442
443 // display description of the result for single stages
444 } else if( !current_con->post_terrain.empty() ) {
445 std::string current_line = _( "Result: " );
446 if( current_con->post_is_furniture ) {
447 current_line += colorize(
448 furn_str_id( current_con->post_terrain ).obj().description,
449 color_data
450 );
451 } else {
452 current_line += colorize(
453 ter_str_id( current_con->post_terrain ).obj().description,
454 color_data
455 );
456 }
457 add_line( current_line );
458 }
459
460 // display required skill and difficulty
461 if( current_con->required_skills.empty() ) {
462 add_line( _( "N/A" ) );
463 } else {
464 std::string current_line = _( "Required skills: " ) + enumerate_as_string(
465 current_con->required_skills.begin(), current_con->required_skills.end(),
466 [&player_character]( const std::pair<skill_id, int> &skill ) {
467 nc_color col;
468 int s_lvl = player_character.get_skill_level( skill.first );
469 if( s_lvl < skill.second ) {
470 col = c_red;
471 } else if( s_lvl < skill.second * 1.25 ) {
472 col = c_light_blue;
473 } else {
474 col = c_green;
475 }
476
477 return colorize( string_format( "%s (%d)", skill.first.obj().name(), skill.second ), col );
478 }, enumeration_conjunction::none );
479 add_line( current_line );
480 }
481
482 // TODO: Textify pre_flags to provide a bit more information.
483 // Example: First step of dig pit could say something about
484 // requiring diggable ground.
485 if( !current_con->pre_terrain.empty() ) {
486 std::string require_string;
487 if( current_con->pre_is_furniture ) {
488 require_string = furn_str_id( current_con->pre_terrain )->name();
489 } else {
490 require_string = ter_str_id( current_con->pre_terrain )->name();
491 }
492 nc_color pre_color = has_pre_terrain( *current_con ) ? c_green : c_red;
493 add_line( _( "Requires: " ) + colorize( require_string, pre_color ) );
494 }
495 if( !current_con->pre_note.empty() ) {
496 add_line( _( "Annotation: " ) + colorize( current_con->pre_note, color_data ) );
497 }
498 // get pre-folded versions of the rest of the construction project to be displayed later
499
500 // get time needed
501 add_folded( current_con->get_folded_time_string( available_window_width ) );
502
503 add_folded( current_con->requirements->get_folded_tools_list( available_window_width, color_stage,
504 total_inv ) );
505
506 add_folded( current_con->requirements->get_folded_components_list( available_window_width,
507 color_stage, total_inv, is_crafting_component ) );
508
509 construct_buffers.push_back( current_buffer );
510 }
511
512 //determine where the printing starts for each project, so it can be scrolled to those points
513 size_t current_buffer_location = 0;
514 for( size_t i = 0; i < construct_buffers.size(); i++ ) {
515 construct_buffer_breakpoints.push_back( static_cast<int>( current_buffer_location ) );
516 full_construct_buffer.insert( full_construct_buffer.end(), construct_buffers[i].begin(),
517 construct_buffers[i].end() );
518
519 //handle text too large for one screen
520 if( construct_buffers[i].size() > static_cast<size_t>( available_buffer_height ) ) {
521 construct_buffer_breakpoints.push_back( static_cast<int>( current_buffer_location +
522 static_cast<size_t>( available_buffer_height ) ) );
523 }
524 current_buffer_location += construct_buffers[i].size();
525 if( i < construct_buffers.size() - 1 ) {
526 full_construct_buffer.push_back( std::string() );
527 current_buffer_location++;
528 }
529 }
530 total_project_breakpoints = static_cast<int>( construct_buffer_breakpoints.size() );
531 }
532 };
533
534 ui.on_screen_resize( [&]( ui_adaptor & ui ) {
535 w_height = TERMY;
536 if( static_cast<int>( available.size() ) + 2 < w_height ) {
537 w_height = available.size() + 2;
538 }
539 if( w_height < FULL_SCREEN_HEIGHT ) {
540 w_height = FULL_SCREEN_HEIGHT;
541 }
542
543 w_width = std::max( FULL_SCREEN_WIDTH, TERMX * 2 / 3 );
544 const int w_y0 = ( TERMY > w_height ) ? ( TERMY - w_height ) / 2 : 0;
545 const int w_x0 = ( TERMX > w_width ) ? ( TERMX - w_width ) / 2 : 0;
546 w_con = catacurses::newwin( w_height, w_width, point( w_x0, w_y0 ) );
547
548 w_list_width = static_cast<int>( .375 * w_width );
549 w_list_height = w_height - 4;
550 w_list = catacurses::newwin( w_list_height, w_list_width,
551 point( w_x0 + w_list_x0, w_y0 + 3 ) );
552
553 pos_x = w_list_width + w_list_x0 + 2;
554 available_window_width = w_width - pos_x - 1;
555
556 recalc_buffer();
557
558 ui.position_from_window( w_con );
559 } );
560 ui.mark_resize();
561
562 ui.on_redraw( [&]( const ui_adaptor & ) {
563 draw_grid( w_con, w_list_width + w_list_x0 );
564
565 // Erase existing tab selection & list of constructions
566 mvwhline( w_con, point_south_east, ' ', w_list_width );
567 werase( w_list );
568 // Print new tab listing
569 // NOLINTNEXTLINE(cata-use-named-point-constants)
570 mvwprintz( w_con, point( 1, 1 ), c_yellow, "<< %s >>", construct_cat[tabindex].name() );
571 // Determine where in the master list to start printing
572 calcStartPos( offset, select, w_list_height, constructs.size() );
573 // Print the constructions between offset and max (or how many will fit)
574 cata::optional<point> cursor_pos;
575 for( size_t i = 0; static_cast<int>( i ) < w_list_height &&
576 ( i + offset ) < constructs.size(); i++ ) {
577 int current = i + offset;
578 const construction_group_str_id &group = constructs[current];
579 bool highlight = ( current == select );
580 const point print_from( 0, i );
581 if( highlight ) {
582 cursor_pos = print_from;
583 }
584 trim_and_print( w_list, print_from, w_list_width,
585 construction_color( group, highlight ), group->name() );
586 }
587
588 // Clear out lines for tools & materials
589 for( int i = 1; i < w_height - 1; i++ ) {
590 mvwhline( w_con, point( pos_x, i ), ' ', available_window_width );
591 }
592
593 // print the hotkeys regardless of if there are constructions
594 for( size_t i = 0; i < notes.size(); ++i ) {
595 trim_and_print( w_con, point( pos_x,
596 w_height - 1 - static_cast<int>( notes.size() ) + static_cast<int>( i ) ),
597 available_window_width, c_white, notes[i] );
598 }
599
600 if( !constructs.empty() ) {
601 if( select >= static_cast<int>( constructs.size() ) ) {
602 select = 0;
603 }
604 const construction_group_str_id ¤t_group = constructs[select];
605 // Print construction name
606 trim_and_print( w_con, point( pos_x, 1 ), available_window_width, c_white, current_group->name() );
607
608 if( current_construct_breakpoint > 0 ) {
609 // Print previous stage indicator if breakpoint is past the beginning
610 trim_and_print( w_con, point( pos_x, 2 ), available_window_width, c_white,
611 _( "Press [<color_yellow>%s</color>] to show previous stage(s)." ),
612 ctxt.get_desc( "SCROLL_STAGE_UP" ) );
613 }
614 if( static_cast<size_t>( construct_buffer_breakpoints[current_construct_breakpoint] +
615 available_buffer_height ) < full_construct_buffer.size() ) {
616 // Print next stage indicator if more breakpoints are remaining after screen height
617 trim_and_print( w_con, point( pos_x, w_height - 2 - static_cast<int>( notes.size() ) ),
618 available_window_width, c_white,
619 _( "Press [<color_yellow>%s</color>] to show next stage(s)." ),
620 ctxt.get_desc( "SCROLL_STAGE_DOWN" ) );
621 }
622 // Leave room for above/below indicators
623 int ypos = 3;
624 nc_color stored_color = color_stage;
625 for( size_t i = static_cast<size_t>( construct_buffer_breakpoints[current_construct_breakpoint] );
626 i < full_construct_buffer.size(); i++ ) {
627 //the value of 3 is from leaving room at the top of window
628 if( ypos > available_buffer_height + 3 ) {
629 break;
630 }
631 print_colored_text( w_con, point( w_list_width + w_list_x0 + 2, ypos++ ), stored_color, color_stage,
632 full_construct_buffer[i] );
633 }
634 }
635
636 draw_scrollbar( w_con, select, w_list_height, constructs.size(), point( 0, 3 ) );
637 wnoutrefresh( w_con );
638
639 // place the cursor at the selected construction name as expected by screen readers
640 if( cursor_pos ) {
641 wmove( w_list, cursor_pos.value() );
642 }
643 wnoutrefresh( w_list );
644 } );
645
646 do {
647 if( update_cat ) {
648 update_cat = false;
649 construction_group_str_id last_construction = construction_group_str_id::NULL_ID();
650 if( isnew ) {
651 filter = uistate.construction_filter;
652 tabindex = uistate.construction_tab.is_valid()
653 ? uistate.construction_tab.id().to_i() : 0;
654 if( uistate.last_construction.is_valid() ) {
655 last_construction = uistate.last_construction;
656 }
657 } else if( select >= 0 && static_cast<size_t>( select ) < constructs.size() ) {
658 last_construction = constructs[select];
659 }
660 category_id = construct_cat[tabindex].id;
661 if( category_id == construction_category_ALL ) {
662 constructs = available;
663 } else if( category_id == construction_category_FILTER ) {
664 constructs.clear();
665 std::copy_if( available.begin(), available.end(),
666 std::back_inserter( constructs ),
667 [&]( const construction_group_str_id & group ) {
668 return lcmatch( group->name(), filter );
669 } );
670 } else {
671 constructs = cat_available[category_id];
672 }
673 select = 0;
674 if( last_construction ) {
675 const auto it = std::find( constructs.begin(), constructs.end(),
676 last_construction );
677 if( it != constructs.end() ) {
678 select = std::distance( constructs.begin(), it );
679 }
680 }
681 }
682 isnew = false;
683
684 if( update_info ) {
685 update_info = false;
686
687 notes.clear();
688 if( tabindex == tabcount - 1 && !filter.empty() ) {
689 notes.push_back( string_format( _( "Press [<color_red>%s</color>] to clear filter." ),
690 ctxt.get_desc( "RESET_FILTER" ) ) );
691 }
692 notes.push_back( string_format( _( "Press [<color_yellow>%s or %s</color>] to tab." ),
693 ctxt.get_desc( "LEFT" ),
694 ctxt.get_desc( "RIGHT" ) ) );
695 notes.push_back( string_format( _( "Press [<color_yellow>%s</color>] to search." ),
696 ctxt.get_desc( "FILTER" ) ) );
697 if( !hide_unconstructable ) {
698 notes.push_back( string_format(
699 _( "Press [<color_yellow>%s</color>] to hide unavailable constructions." ),
700 ctxt.get_desc( "TOGGLE_UNAVAILABLE_CONSTRUCTIONS" ) ) );
701 } else {
702 notes.push_back( string_format(
703 _( "Press [<color_red>%s</color>] to show unavailable constructions." ),
704 ctxt.get_desc( "TOGGLE_UNAVAILABLE_CONSTRUCTIONS" ) ) );
705 }
706 notes.push_back( string_format(
707 _( "Press [<color_yellow>%s</color>] to view and edit keybindings." ),
708 ctxt.get_desc( "HELP_KEYBINDINGS" ) ) );
709
710 recalc_buffer();
711 } // Finished updating
712
713 ui_manager::redraw();
714
715 const std::string action = ctxt.handle_input();
716 const int recmax = static_cast<int>( constructs.size() );
717 const int scroll_rate = recmax > 20 ? 10 : 3;
718 if( action == "FILTER" ) {
719 string_input_popup popup;
720 popup
721 .title( _( "Search" ) )
722 .width( 50 )
723 .description( _( "Filter" ) )
724 .max_length( 100 )
725 .text( tabindex == tabcount - 1 ? filter : std::string() )
726 .query();
727 if( popup.confirmed() ) {
728 filter = popup.text();
729 uistate.construction_filter = filter;
730 update_info = true;
731 update_cat = true;
732 tabindex = tabcount - 1;
733 }
734 } else if( action == "RESET_FILTER" ) {
735 if( tabindex == tabcount - 1 && !filter.empty() ) {
736 filter.clear();
737 uistate.construction_filter.clear();
738 update_info = true;
739 update_cat = true;
740 }
741 } else if( action == "DOWN" ) {
742 update_info = true;
743 if( select < recmax - 1 ) {
744 select++;
745 } else {
746 select = 0;
747 }
748 } else if( action == "UP" ) {
749 update_info = true;
750 if( select > 0 ) {
751 select--;
752 } else {
753 select = recmax - 1;
754 }
755 } else if( action == "PAGE_DOWN" ) {
756 update_info = true;
757 if( select == recmax - 1 ) {
758 select = 0;
759 } else if( select + scroll_rate >= recmax ) {
760 select = recmax - 1;
761 } else {
762 select += +scroll_rate;
763 }
764 } else if( action == "PAGE_UP" ) {
765 update_info = true;
766 if( select == 0 ) {
767 select = recmax - 1;
768 } else if( select <= scroll_rate ) {
769 select = 0;
770 } else {
771 select += -scroll_rate;
772 }
773 } else if( action == "LEFT" ) {
774 update_info = true;
775 update_cat = true;
776 tabindex--;
777 if( tabindex < 0 ) {
778 tabindex = tabcount - 1;
779 }
780 } else if( action == "RIGHT" ) {
781 update_info = true;
782 update_cat = true;
783 tabindex = ( tabindex + 1 ) % tabcount;
784 } else if( action == "SCROLL_STAGE_UP" ) {
785 if( current_construct_breakpoint > 0 ) {
786 current_construct_breakpoint--;
787 }
788 if( current_construct_breakpoint < 0 ) {
789 current_construct_breakpoint = 0;
790 }
791 } else if( action == "SCROLL_STAGE_DOWN" ) {
792 if( current_construct_breakpoint < total_project_breakpoints - 1 ) {
793 current_construct_breakpoint++;
794 }
795 if( current_construct_breakpoint >= total_project_breakpoints ) {
796 current_construct_breakpoint = total_project_breakpoints - 1;
797 }
798 } else if( action == "QUIT" ) {
799 exit = true;
800 } else if( action == "TOGGLE_UNAVAILABLE_CONSTRUCTIONS" ) {
801 update_info = true;
802 update_cat = true;
803 hide_unconstructable = !hide_unconstructable;
804 offset = 0;
805 load_available_constructions( available, cat_available, hide_unconstructable );
806 } else if( action == "CONFIRM" ) {
807 if( constructs.empty() || select >= static_cast<int>( constructs.size() ) ) {
808 // Nothing to be done here
809 continue;
810 }
811 if( !blueprint ) {
812 if( player_can_build( player_character, total_inv, constructs[select] ) ) {
813 if( !player_can_see_to_build( player_character, constructs[select] ) ) {
814 add_msg( m_info, _( "It is too dark to construct right now." ) );
815 } else {
816 ui.reset();
817 place_construction( constructs[select] );
818 uistate.last_construction = constructs[select];
819 }
820 exit = true;
821 } else {
822 popup( _( "You can't build that!" ) );
823 update_info = true;
824 }
825 } else {
826 // get the index of the overall constructions list from current_group
827 const std::vector<construction> &list_constructions = get_constructions();
828 for( int i = 0; i < static_cast<int>( list_constructions.size() ); ++i ) {
829 if( constructs[select] == list_constructions[i].group ) {
830 ret = construction_id( i );
831 break;
832 }
833 }
834 exit = true;
835 }
836 }
837 } while( !exit );
838
839 uistate.construction_tab = int_id<construction_category>( tabindex ).id();
840
841 return ret;
842 }
843
player_can_build(player & p,const read_only_visitable & inv,const construction_group_str_id & group)844 bool player_can_build( player &p, const read_only_visitable &inv,
845 const construction_group_str_id &group )
846 {
847 // check all with the same group to see if player can build any
848 std::vector<construction *> cons = constructions_by_group( group );
849 for( auto &con : cons ) {
850 if( player_can_build( p, inv, *con ) ) {
851 return true;
852 }
853 }
854 return false;
855 }
856
player_can_build(player & p,const read_only_visitable & inv,const construction & con)857 bool player_can_build( player &p, const read_only_visitable &inv, const construction &con )
858 {
859 if( p.has_trait( trait_DEBUG_HS ) ) {
860 return true;
861 }
862
863 if( !p.meets_skill_requirements( con ) ) {
864 return false;
865 }
866
867 return con.requirements->can_make_with_inventory( inv, is_crafting_component );
868 }
869
player_can_see_to_build(player & p,const construction_group_str_id & group)870 bool player_can_see_to_build( player &p, const construction_group_str_id &group )
871 {
872 if( p.fine_detail_vision_mod() < 4 || p.has_trait( trait_DEBUG_HS ) ) {
873 return true;
874 }
875 std::vector<construction *> cons = constructions_by_group( group );
876 for( construction *&con : cons ) {
877 if( con->dark_craftable ) {
878 return true;
879 }
880 }
881 return false;
882 }
883
can_construct(const construction_group_str_id & group)884 bool can_construct( const construction_group_str_id &group )
885 {
886 // check all with the same group to see if player can build any
887 std::vector<construction *> cons = constructions_by_group( group );
888 for( auto &con : cons ) {
889 if( can_construct( *con ) ) {
890 return true;
891 }
892 }
893 return false;
894 }
895
can_construct(const construction & con,const tripoint & p)896 bool can_construct( const construction &con, const tripoint &p )
897 {
898 // see if the special pre-function checks out
899 bool place_okay = con.pre_special( p );
900 // see if the terrain type checks out
901 place_okay &= has_pre_terrain( con, p );
902 // see if the flags check out
903 place_okay &= std::all_of( con.pre_flags.begin(), con.pre_flags.end(),
904 [&p]( const std::string & flag ) {
905 return get_map().has_flag( flag, p );
906 } );
907 // make sure the construction would actually do something
908 if( !con.post_terrain.empty() ) {
909 map &here = get_map();
910 if( con.post_is_furniture ) {
911 furn_id f = furn_id( con.post_terrain );
912 place_okay &= here.furn( p ) != f;
913 } else {
914 ter_id t = ter_id( con.post_terrain );
915 place_okay &= here.ter( p ) != t;
916 }
917 }
918 return place_okay;
919 }
920
can_construct(const construction & con)921 bool can_construct( const construction &con )
922 {
923 tripoint avatar_pos = get_player_character().pos();
924 for( const tripoint &p : get_map().points_in_radius( avatar_pos, 1 ) ) {
925 if( p != avatar_pos && can_construct( con, p ) ) {
926 return true;
927 }
928 }
929 return false;
930 }
931
place_construction(const construction_group_str_id & group)932 void place_construction( const construction_group_str_id &group )
933 {
934 avatar &player_character = get_avatar();
935 const inventory &total_inv = player_character.crafting_inventory();
936
937 std::vector<construction *> cons = constructions_by_group( group );
938 std::map<tripoint, const construction *> valid;
939 map &here = get_map();
940 for( const tripoint &p : here.points_in_radius( player_character.pos(), 1 ) ) {
941 for( const auto *con : cons ) {
942 if( p != player_character.pos() && can_construct( *con, p ) &&
943 player_can_build( player_character, total_inv, *con ) ) {
944 valid[ p ] = con;
945 }
946 }
947 }
948
949 shared_ptr_fast<game::draw_callback_t> draw_valid = make_shared_fast<game::draw_callback_t>( [&]() {
950 map &here = get_map();
951 for( auto &elem : valid ) {
952 here.drawsq( g->w_terrain, player_character, elem.first, true, false,
953 player_character.pos() + player_character.view_offset );
954 }
955 } );
956 g->add_draw_callback( draw_valid );
957
958 const cata::optional<tripoint> pnt_ = choose_adjacent( _( "Construct where?" ) );
959 if( !pnt_ ) {
960 return;
961 }
962 const tripoint pnt = *pnt_;
963
964 if( valid.find( pnt ) == valid.end() ) {
965 cons.front()->explain_failure( pnt );
966 return;
967 }
968 // Maybe there is already a partial_con on an existing trap, that isn't caught by the usual trap-checking.
969 // because the pre-requisite construction is already a trap anyway.
970 // This shouldn't normally happen, unless it's a spike pit being built on a pit for example.
971 partial_con *pre_c = here.partial_con_at( pnt );
972 if( pre_c ) {
973 add_msg( m_info,
974 _( "There is already an unfinished construction there, examine it to continue working on it" ) );
975 return;
976 }
977 std::list<item> used;
978 const construction &con = *valid.find( pnt )->second;
979 // create the partial construction struct
980 partial_con pc;
981 pc.id = con.id;
982 // Set the trap that has the examine function
983 // Special handling for constructions that take place on existing traps.
984 // Basically just don't add the unfinished construction trap.
985 // TODO: handle this cleaner, instead of adding a special case to pit iexamine.
986 if( here.tr_at( pnt ).is_null() ) {
987 here.trap_set( pnt, tr_unfinished_construction );
988 }
989 // Use up the components
990 for( const auto &it : con.requirements->get_components() ) {
991 std::list<item> tmp = player_character.consume_items( it, 1, is_crafting_component );
992 used.splice( used.end(), tmp );
993 }
994 pc.components = used;
995 here.partial_con_set( pnt, pc );
996 for( const auto &it : con.requirements->get_tools() ) {
997 player_character.consume_tools( it );
998 }
999 player_character.assign_activity( ACT_BUILD );
1000 player_character.activity.placement = here.getabs( pnt );
1001 }
1002
complete_construction(player * p)1003 void complete_construction( player *p )
1004 {
1005 if( !finalized ) {
1006 debugmsg( "complete_construction called before finalization" );
1007 return;
1008 }
1009 map &here = get_map();
1010 const tripoint terp = here.getlocal( p->activity.placement );
1011 partial_con *pc = here.partial_con_at( terp );
1012 if( !pc ) {
1013 debugmsg( "No partial construction found at activity placement in complete_construction()" );
1014 if( here.tr_at( terp ) == tr_unfinished_construction ) {
1015 here.remove_trap( terp );
1016 }
1017 if( p->is_npc() ) {
1018 npc *guy = dynamic_cast<npc *>( p );
1019 guy->current_activity_id = activity_id::NULL_ID();
1020 guy->revert_after_activity();
1021 guy->set_moves( 0 );
1022 }
1023 return;
1024 }
1025 const construction &built = pc->id.obj();
1026 const auto award_xp = [&]( player & c ) {
1027 for( const auto &pr : built.required_skills ) {
1028 c.practice( pr.first, static_cast<int>( ( 10 + 15 * pr.second ) * ( 1 + built.time / 180000.0 ) ),
1029 static_cast<int>( pr.second * 1.25 ) );
1030 }
1031 };
1032
1033 award_xp( *p );
1034 // Friendly NPCs gain exp from assisting or watching...
1035 // TODO: NPCs watching other NPCs do stuff and learning from it
1036 if( p->is_player() ) {
1037 for( auto &elem : get_avatar().get_crafting_helpers() ) {
1038 if( elem->meets_skill_requirements( built ) ) {
1039 add_msg( m_info, _( "%s assists you with the work…" ), elem->name );
1040 } else {
1041 //NPC near you isn't skilled enough to help
1042 add_msg( m_info, _( "%s watches you work…" ), elem->name );
1043 }
1044
1045 award_xp( *elem );
1046 }
1047 }
1048 if( here.tr_at( terp ) == tr_unfinished_construction ) {
1049 here.remove_trap( terp );
1050 }
1051 here.partial_con_remove( terp );
1052 // Some constructions are allowed to have items left on the tile.
1053 if( built.post_flags.count( "keep_items" ) == 0 ) {
1054 // Move any items that have found their way onto the construction site.
1055 std::vector<tripoint> dump_spots;
1056 for( const tripoint &pt : here.points_in_radius( terp, 1 ) ) {
1057 if( here.can_put_items( pt ) && pt != terp ) {
1058 dump_spots.push_back( pt );
1059 }
1060 }
1061 if( !dump_spots.empty() ) {
1062 tripoint dump_spot = random_entry( dump_spots );
1063 map_stack items = here.i_at( terp );
1064 for( map_stack::iterator it = items.begin(); it != items.end(); ) {
1065 here.add_item_or_charges( dump_spot, *it );
1066 it = items.erase( it );
1067 }
1068 } else {
1069 debugmsg( "No space to displace items from construction finishing" );
1070 }
1071 }
1072 // Make the terrain change
1073 if( !built.post_terrain.empty() ) {
1074 if( built.post_is_furniture ) {
1075 here.furn_set( terp, furn_str_id( built.post_terrain ) );
1076 } else {
1077 here.ter_set( terp, ter_str_id( built.post_terrain ) );
1078 // Make a roof if constructed terrain should have it and it's an open air
1079 if( construct::check_up_OK( terp ) ) {
1080 const int_id<ter_t> post_terrain = ter_id( built.post_terrain );
1081 if( post_terrain->roof ) {
1082 const tripoint top = terp + tripoint_above;
1083 if( here.ter( top ) == t_open_air ) {
1084 here.ter_set( top, ter_id( post_terrain->roof ) );
1085 }
1086 }
1087 }
1088 }
1089 }
1090
1091 // Spawn byproducts
1092 if( built.byproduct_item_group ) {
1093 here.spawn_items( p->pos(), item_group::items_from( *built.byproduct_item_group, calendar::turn ) );
1094 }
1095
1096 add_msg( m_info, _( "%s finished construction: %s." ), p->disp_name( false, true ),
1097 built.group->name() );
1098 // clear the activity
1099 p->activity.set_to_null();
1100
1101 // This comes after clearing the activity, in case the function interrupts
1102 // activities
1103 built.post_special( terp );
1104 // npcs will automatically resume backlog, players wont.
1105 if( p->is_player() && !p->backlog.empty() &&
1106 p->backlog.front().id() == ACT_MULTIPLE_CONSTRUCTION ) {
1107 p->backlog.clear();
1108 p->assign_activity( ACT_MULTIPLE_CONSTRUCTION );
1109 }
1110 }
1111
check_empty(const tripoint & p)1112 bool construct::check_empty( const tripoint &p )
1113 {
1114 map &here = get_map();
1115 // @TODO should check for *visible* traps only. But calling code must
1116 // first know how to handle constructing on top of an invisible trap!
1117 return ( here.has_flag( flag_FLAT, p ) && !here.has_furn( p ) &&
1118 g->is_empty( p ) && here.tr_at( p ).is_null() &&
1119 here.i_at( p ).empty() && !here.veh_at( p ) );
1120 }
1121
get_orthogonal_neighbors(const tripoint & p)1122 static inline std::array<tripoint, 4> get_orthogonal_neighbors( const tripoint &p )
1123 {
1124 return {{
1125 p + point_north,
1126 p + point_south,
1127 p + point_west,
1128 p + point_east
1129 }};
1130 }
1131
check_support(const tripoint & p)1132 bool construct::check_support( const tripoint &p )
1133 {
1134 map &here = get_map();
1135 // need two or more orthogonally adjacent supports
1136 if( here.impassable( p ) ) {
1137 return false;
1138 }
1139 int num_supports = 0;
1140 for( const tripoint &nb : get_orthogonal_neighbors( p ) ) {
1141 if( here.has_flag( flag_SUPPORTS_ROOF, nb ) ) {
1142 num_supports++;
1143 }
1144 }
1145 return num_supports >= 2;
1146 }
1147
check_stable(const tripoint & p)1148 bool construct::check_stable( const tripoint &p )
1149 {
1150 return get_map().has_flag( flag_SUPPORTS_ROOF, p + tripoint_below );
1151 }
1152
check_empty_stable(const tripoint & p)1153 bool construct::check_empty_stable( const tripoint &p )
1154 {
1155 return check_empty( p ) && check_stable( p );
1156 }
1157
check_nofloor_above(const tripoint & p)1158 bool construct::check_nofloor_above( const tripoint &p )
1159 {
1160 return get_map().has_flag( flag_NO_FLOOR, p + tripoint_above );
1161 }
1162
check_deconstruct(const tripoint & p)1163 bool construct::check_deconstruct( const tripoint &p )
1164 {
1165 map &here = get_map();
1166 if( here.has_furn( p.xy() ) ) {
1167 return here.furn( p.xy() ).obj().deconstruct.can_do;
1168 }
1169 // terrain can only be deconstructed when there is no furniture in the way
1170 return here.ter( p.xy() ).obj().deconstruct.can_do;
1171 }
1172
check_empty_up_OK(const tripoint & p)1173 bool construct::check_empty_up_OK( const tripoint &p )
1174 {
1175 return check_empty( p ) && check_up_OK( p );
1176 }
1177
check_up_OK(const tripoint &)1178 bool construct::check_up_OK( const tripoint & )
1179 {
1180 // You're not going above +OVERMAP_HEIGHT.
1181 return ( get_map().get_abs_sub().z < OVERMAP_HEIGHT );
1182 }
1183
check_down_OK(const tripoint &)1184 bool construct::check_down_OK( const tripoint & )
1185 {
1186 // You're not going below -OVERMAP_DEPTH.
1187 return ( get_map().get_abs_sub().z > -OVERMAP_DEPTH );
1188 }
1189
check_no_trap(const tripoint & p)1190 bool construct::check_no_trap( const tripoint &p )
1191 {
1192 return get_map().tr_at( p ).is_null();
1193 }
1194
check_ramp_high(const tripoint & p)1195 bool construct::check_ramp_high( const tripoint &p )
1196 {
1197 if( check_empty_stable( p ) && check_up_OK( p ) && check_nofloor_above( p ) ) {
1198 for( const point &car_d : four_cardinal_directions ) {
1199 // check adjacent points on the z-level above for a completed down ramp
1200 if( get_map().has_flag( TFLAG_RAMP_DOWN, p + car_d + tripoint_above ) ) {
1201 return true;
1202 }
1203 }
1204 }
1205 return false;
1206 }
1207
check_ramp_low(const tripoint & p)1208 bool construct::check_ramp_low( const tripoint &p )
1209 {
1210 return check_empty_stable( p ) && check_up_OK( p ) && check_nofloor_above( p );
1211 }
1212
done_trunk_plank(const tripoint &)1213 void construct::done_trunk_plank( const tripoint &/*p*/ )
1214 {
1215 int num_logs = rng( 2, 3 );
1216 Character &player_character = get_player_character();
1217 for( int i = 0; i < num_logs; ++i ) {
1218 iuse::cut_log_into_planks( player_character );
1219 }
1220 }
1221
done_grave(const tripoint & p)1222 void construct::done_grave( const tripoint &p )
1223 {
1224 Character &player_character = get_player_character();
1225 map &here = get_map();
1226 map_stack its = here.i_at( p );
1227 for( item it : its ) {
1228 if( it.is_corpse() ) {
1229 if( it.get_corpse_name().empty() ) {
1230 if( it.get_mtype()->has_flag( MF_HUMAN ) ) {
1231 if( player_character.has_trait( trait_SPIRITUAL ) ) {
1232 player_character.add_morale( MORALE_FUNERAL, 50, 75, 1_days, 1_hours );
1233 add_msg( m_good,
1234 _( "You feel relieved after providing last rites for this human being, whose name is lost in the Cataclysm." ) );
1235 } else {
1236 add_msg( m_neutral, _( "You bury remains of a human, whose name is lost in the Cataclysm." ) );
1237 }
1238 }
1239 } else {
1240 if( player_character.has_trait( trait_SPIRITUAL ) ) {
1241 player_character.add_morale( MORALE_FUNERAL, 50, 75, 1_days, 1_hours );
1242 add_msg( m_good,
1243 _( "You feel sadness, but also relief after providing last rites for %s, whose name you will keep in your memory." ),
1244 it.get_corpse_name() );
1245 } else {
1246 add_msg( m_neutral,
1247 _( "You bury remains of %s, who joined uncounted masses perished in the Cataclysm." ),
1248 it.get_corpse_name() );
1249 }
1250 }
1251 get_event_bus().send<event_type::buries_corpse>(
1252 player_character.getID(), it.get_mtype()->id, it.get_corpse_name() );
1253 }
1254 }
1255 if( player_character.has_quality( qual_CUT ) ) {
1256 iuse::handle_ground_graffiti( player_character, nullptr, _( "Inscribe something on the grave?" ),
1257 p );
1258 } else {
1259 add_msg( m_neutral,
1260 _( "Unfortunately you don't have anything sharp to place an inscription on the grave." ) );
1261 }
1262
1263 here.destroy_furn( p, true );
1264 }
1265
vpart_from_item(const itype_id & item_id)1266 static vpart_id vpart_from_item( const itype_id &item_id )
1267 {
1268 for( const auto &e : vpart_info::all() ) {
1269 const vpart_info &vp = e.second;
1270 if( vp.base_item == item_id && vp.has_flag( flag_INITIAL_PART ) ) {
1271 return vp.get_id();
1272 }
1273 }
1274 // The INITIAL_PART flag is optional, if no part (based on the given item) has it, just use the
1275 // first part that is based in the given item (this is fine for example if there is only one
1276 // such type anyway).
1277 for( const auto &e : vpart_info::all() ) {
1278 const vpart_info &vp = e.second;
1279 if( vp.base_item == item_id ) {
1280 return vp.get_id();
1281 }
1282 }
1283 debugmsg( "item %s used by construction is not base item of any vehicle part!", item_id.c_str() );
1284 static const vpart_id frame_id( "frame_vertical_2" );
1285 return frame_id;
1286 }
1287
done_vehicle(const tripoint & p)1288 void construct::done_vehicle( const tripoint &p )
1289 {
1290 std::string name = string_input_popup()
1291 .title( _( "Enter new vehicle name:" ) )
1292 .width( 20 )
1293 .query_string();
1294 if( name.empty() ) {
1295 name = _( "Car" );
1296 }
1297
1298 map &here = get_map();
1299 vehicle *veh = here.add_vehicle( vproto_id( "none" ), p, 270_degrees, 0, 0 );
1300
1301 if( !veh ) {
1302 debugmsg( "error constructing vehicle" );
1303 return;
1304 }
1305 veh->name = name;
1306 veh->install_part( point_zero, vpart_from_item( get_avatar().lastconsumed ) );
1307
1308 // Update the vehicle cache immediately,
1309 // or the vehicle will be invisible for the first couple of turns.
1310 here.add_vehicle_to_cache( veh );
1311 }
1312
done_deconstruct(const tripoint & p)1313 void construct::done_deconstruct( const tripoint &p )
1314 {
1315 map &here = get_map();
1316 // TODO: Make this the argument
1317 if( here.has_furn( p ) ) {
1318 const furn_t &f = here.furn( p ).obj();
1319 if( !f.deconstruct.can_do ) {
1320 add_msg( m_info, _( "That %s can not be disassembled!" ), f.name() );
1321 return;
1322 }
1323 Character &player_character = get_player_character();
1324 if( f.id.id() == furn_str_id( "f_console_broken" ) ) {
1325 if( player_character.get_skill_level( skill_electronics ) >= 1 ) {
1326 player_character.practice( skill_electronics, 20, 4 );
1327 }
1328 }
1329 if( f.id.id() == furn_str_id( "f_console" ) ) {
1330 if( player_character.get_skill_level( skill_electronics ) >= 1 ) {
1331 player_character.practice( skill_electronics, 40, 8 );
1332 }
1333 }
1334 if( f.id.id() == furn_str_id( "f_machinery_electronic" ) ) {
1335 if( player_character.get_skill_level( skill_electronics ) >= 1 ) {
1336 player_character.practice( skill_electronics, 40, 8 );
1337 }
1338 }
1339 if( f.deconstruct.furn_set.str().empty() ) {
1340 here.furn_set( p, f_null );
1341 } else {
1342 here.furn_set( p, f.deconstruct.furn_set );
1343 }
1344 add_msg( _( "The %s is disassembled." ), f.name() );
1345 here.spawn_items( p, item_group::items_from( f.deconstruct.drop_group, calendar::turn ) );
1346 // HACK: Hack alert.
1347 // Signs have cosmetics associated with them on the submap since
1348 // furniture can't store dynamic data to disk. To prevent writing
1349 // mysteriously appearing for a sign later built here, remove the
1350 // writing from the submap.
1351 here.delete_signage( p );
1352 } else {
1353 const ter_t &t = here.ter( p ).obj();
1354 if( !t.deconstruct.can_do ) {
1355 add_msg( _( "That %s can not be disassembled!" ), t.name() );
1356 return;
1357 }
1358 if( t.deconstruct.deconstruct_above ) {
1359 const tripoint top = p + tripoint_above;
1360 if( here.has_furn( top ) ) {
1361 add_msg( _( "That %s can not be disassembled, since there is furniture above it." ), t.name() );
1362 return;
1363 }
1364 done_deconstruct( top );
1365 }
1366 avatar &player_character = get_avatar();
1367 if( t.id.id() == t_console_broken ) {
1368 if( player_character.get_skill_level( skill_electronics ) >= 1 ) {
1369 player_character.practice( skill_electronics, 20, 4 );
1370 }
1371 }
1372 if( t.id.id() == t_console ) {
1373 if( player_character.get_skill_level( skill_electronics ) >= 1 ) {
1374 player_character.practice( skill_electronics, 40, 8 );
1375 }
1376 }
1377 here.ter_set( p, t.deconstruct.ter_set );
1378 add_msg( _( "The %s is disassembled." ), t.name() );
1379 here.spawn_items( p, item_group::items_from( t.deconstruct.drop_group, calendar::turn ) );
1380 }
1381 }
1382
unroll_digging(const int numer_of_2x4s)1383 static void unroll_digging( const int numer_of_2x4s )
1384 {
1385 // refund components!
1386 item rope( "rope_30" );
1387 map &here = get_map();
1388 tripoint avatar_pos = get_player_character().pos();
1389 here.add_item_or_charges( avatar_pos, rope );
1390 // presuming 2x4 to conserve lumber.
1391 here.spawn_item( avatar_pos, itype_2x4, numer_of_2x4s );
1392 }
1393
done_digormine_stair(const tripoint & p,bool dig)1394 void construct::done_digormine_stair( const tripoint &p, bool dig )
1395 {
1396 map &here = get_map();
1397 // TODO: fix point types
1398 const tripoint_abs_ms abs_pos( here.getabs( p ) );
1399 const tripoint_abs_sm pos_sm = project_to<coords::sm>( abs_pos );
1400 tinymap tmpmap;
1401 tmpmap.load( pos_sm + tripoint_below, false );
1402 // TODO: fix point types
1403 const tripoint local_tmp = tmpmap.getlocal( abs_pos.raw() );
1404
1405 Character &player_character = get_player_character();
1406 bool dig_muts = player_character.has_trait( trait_PAINRESIST_TROGLO ) ||
1407 player_character.has_trait( trait_STOCKY_TROGLO );
1408
1409 int no_mut_penalty = dig_muts ? 10 : 0;
1410 int mine_penalty = dig ? 0 : 10;
1411 player_character.mod_stored_nutr( 5 + mine_penalty + no_mut_penalty );
1412 player_character.mod_thirst( 5 + mine_penalty + no_mut_penalty );
1413 player_character.mod_fatigue( 10 + mine_penalty + no_mut_penalty );
1414
1415 if( tmpmap.ter( local_tmp ) == t_lava ) {
1416 if( !( query_yn( _( "The rock feels much warmer than normal. Proceed?" ) ) ) ) {
1417 here.ter_set( p, t_pit ); // You dug down a bit before detecting the problem
1418 unroll_digging( dig ? 8 : 12 );
1419 } else {
1420 add_msg( m_warning, _( "You just tunneled into lava!" ) );
1421 get_event_bus().send<event_type::digs_into_lava>();
1422 here.ter_set( p, t_hole );
1423 }
1424
1425 return;
1426 }
1427
1428 bool impassable = tmpmap.impassable( local_tmp );
1429 if( !impassable ) {
1430 add_msg( _( "You dig into a preexisting space, and improvise a ladder." ) );
1431 } else if( dig ) {
1432 add_msg( _( "You dig a stairway, adding sturdy timbers and a rope for safety." ) );
1433 } else {
1434 add_msg( _( "You drill out a passage, heading deeper underground." ) );
1435 }
1436 here.ter_set( p, t_stairs_down ); // There's the top half
1437 // Again, need to use submap-local coordinates.
1438 tmpmap.ter_set( local_tmp, impassable ? t_stairs_up : t_ladder_up ); // and there's the bottom half.
1439 // And save to the center coordinate of the current active map.
1440 tmpmap.save();
1441 }
1442
done_dig_stair(const tripoint & p)1443 void construct::done_dig_stair( const tripoint &p )
1444 {
1445 done_digormine_stair( p, true );
1446 }
1447
done_mine_downstair(const tripoint & p)1448 void construct::done_mine_downstair( const tripoint &p )
1449 {
1450 done_digormine_stair( p, false );
1451 }
1452
done_mine_upstair(const tripoint & p)1453 void construct::done_mine_upstair( const tripoint &p )
1454 {
1455 map &here = get_map();
1456 // TODO: fix point types
1457 const tripoint_abs_ms abs_pos( here.getabs( p ) );
1458 const tripoint_abs_sm pos_sm = project_to<coords::sm>( abs_pos );
1459 tinymap tmpmap;
1460 tmpmap.load( pos_sm + tripoint_above, false );
1461 // TODO: fix point types
1462 const tripoint local_tmp = tmpmap.getlocal( abs_pos.raw() );
1463
1464 if( tmpmap.ter( local_tmp ) == t_lava ) {
1465 here.ter_set( p.xy(), t_rock_floor ); // You dug a bit before discovering the problem
1466 add_msg( m_warning, _( "The rock overhead feels hot. You decide *not* to mine magma." ) );
1467 unroll_digging( 12 );
1468 return;
1469 }
1470
1471 if( tmpmap.has_flag_ter( TFLAG_SHALLOW_WATER, local_tmp ) ||
1472 tmpmap.has_flag_ter( TFLAG_DEEP_WATER, local_tmp ) ) {
1473 here.ter_set( p.xy(), t_rock_floor ); // You dug a bit before discovering the problem
1474 add_msg( m_warning, _( "The rock above is rather damp. You decide *not* to mine water." ) );
1475 unroll_digging( 12 );
1476 return;
1477 }
1478
1479 Character &player_character = get_player_character();
1480 bool dig_muts = player_character.has_trait( trait_PAINRESIST_TROGLO ) ||
1481 player_character.has_trait( trait_STOCKY_TROGLO );
1482
1483 int no_mut_penalty = dig_muts ? 15 : 0;
1484 player_character.mod_stored_nutr( 20 + no_mut_penalty );
1485 player_character.mod_thirst( 20 + no_mut_penalty );
1486 player_character.mod_fatigue( 25 + no_mut_penalty );
1487
1488 add_msg( _( "You drill out a passage, heading for the surface." ) );
1489 here.ter_set( p.xy(), t_stairs_up ); // There's the bottom half
1490 // We need to write to submap-local coordinates.
1491 tmpmap.ter_set( local_tmp, t_stairs_down ); // and there's the top half.
1492 tmpmap.save();
1493 }
1494
done_wood_stairs(const tripoint & p)1495 void construct::done_wood_stairs( const tripoint &p )
1496 {
1497 const tripoint top = p + tripoint_above;
1498 get_map().ter_set( top, ter_id( "t_wood_stairs_down" ) );
1499 }
1500
done_window_curtains(const tripoint &)1501 void construct::done_window_curtains( const tripoint & )
1502 {
1503 map &here = get_map();
1504 tripoint avatar_pos = get_player_character().pos();
1505 // copied from iexamine::curtains
1506 here.spawn_item( avatar_pos, itype_nail, 1, 4 );
1507 here.spawn_item( avatar_pos, itype_sheet, 2 );
1508 here.spawn_item( avatar_pos, itype_stick );
1509 here.spawn_item( avatar_pos, itype_string_36 );
1510 add_msg( _( "After boarding up the window the curtains and curtain rod are left." ) );
1511 }
1512
done_extract_maybe_revert_to_dirt(const tripoint & p)1513 void construct::done_extract_maybe_revert_to_dirt( const tripoint &p )
1514 {
1515 map &here = get_map();
1516 if( one_in( 10 ) ) {
1517 here.ter_set( p, t_dirt );
1518 }
1519
1520 if( here.ter( p ) == t_clay ) {
1521 add_msg( _( "You gather some clay." ) );
1522 } else if( here.ter( p ) == t_sand ) {
1523 add_msg( _( "You gather some sand." ) );
1524 } else {
1525 // Fall through to an undefined material.
1526 add_msg( _( "You gather some materials." ) );
1527 }
1528 }
1529
done_mark_firewood(const tripoint & p)1530 void construct::done_mark_firewood( const tripoint &p )
1531 {
1532 get_map().trap_set( p, tr_firewood_source );
1533 }
1534
done_mark_practice_target(const tripoint & p)1535 void construct::done_mark_practice_target( const tripoint &p )
1536 {
1537 get_map().trap_set( p, tr_practice_target );
1538 }
1539
done_ramp_low(const tripoint & p)1540 void construct::done_ramp_low( const tripoint &p )
1541 {
1542 const tripoint top = p + tripoint_above;
1543 get_map().ter_set( top, ter_id( "t_ramp_down_low" ) );
1544 }
1545
done_ramp_high(const tripoint & p)1546 void construct::done_ramp_high( const tripoint &p )
1547 {
1548 const tripoint top = p + tripoint_above;
1549 get_map().ter_set( top, ter_id( "t_ramp_down_high" ) );
1550 }
1551
failure_standard(const tripoint &)1552 void construct::failure_standard( const tripoint & )
1553 {
1554 add_msg( m_info, _( "You cannot build there!" ) );
1555 }
1556
failure_deconstruct(const tripoint &)1557 void construct::failure_deconstruct( const tripoint & )
1558 {
1559 add_msg( m_info, _( "You cannot deconstruct this!" ) );
1560 }
1561
1562 template <typename T>
assign_or_debugmsg(T & dest,const std::string & fun_id,const std::map<std::string,T> & possible)1563 void assign_or_debugmsg( T &dest, const std::string &fun_id,
1564 const std::map<std::string, T> &possible )
1565 {
1566 const auto iter = possible.find( fun_id );
1567 if( iter != possible.end() ) {
1568 dest = iter->second;
1569 } else {
1570 dest = possible.find( "" )->second;
1571 const std::string list_available = enumerate_as_string( possible.begin(), possible.end(),
1572 []( const std::pair<std::string, T> &pr ) {
1573 return pr.first;
1574 } );
1575 debugmsg( "Unknown function: %s, available values are %s", fun_id.c_str(), list_available );
1576 }
1577 }
1578
load_construction(const JsonObject & jo)1579 void load_construction( const JsonObject &jo )
1580 {
1581 construction con;
1582 // These ids are only temporary. The actual ids are determined in finalize_construction,
1583 // after removing blacklisted constructions.
1584 con.id = construction_id( -1 );
1585 con.str_id = construction_str_id( jo.get_string( "id" ) );
1586 if( con.str_id.is_null() ) {
1587 jo.throw_error( "Null construction id specified", "id" );
1588 } else if( construction_id_map.find( con.str_id ) != construction_id_map.end() ) {
1589 jo.throw_error( "Duplicate construction id", "id" );
1590 }
1591
1592 jo.get_member( "group" ).read( con.group );
1593 if( jo.has_member( "required_skills" ) ) {
1594 for( JsonArray arr : jo.get_array( "required_skills" ) ) {
1595 con.required_skills[skill_id( arr.get_string( 0 ) )] = arr.get_int( 1 );
1596 }
1597 } else {
1598 skill_id legacy_skill( jo.get_string( "skill", skill_fabrication.str() ) );
1599 int legacy_diff = jo.get_int( "difficulty" );
1600 con.required_skills[ legacy_skill ] = legacy_diff;
1601 }
1602
1603 con.category = construction_category_id( jo.get_string( "category", "OTHER" ) );
1604 if( jo.has_int( "time" ) ) {
1605 con.time = to_moves<int>( time_duration::from_minutes( jo.get_int( "time" ) ) );
1606 } else if( jo.has_string( "time" ) ) {
1607 con.time = to_moves<int>( read_from_json_string<time_duration>( *jo.get_raw( "time" ),
1608 time_duration::units ) );
1609 }
1610
1611 const requirement_id req_id( "inline_construction_" + con.str_id.str() );
1612 requirement_data::load_requirement( jo, req_id );
1613 con.requirements = req_id;
1614
1615 if( jo.has_string( "using" ) ) {
1616 con.reqs_using = { { requirement_id( jo.get_string( "using" ) ), 1} };
1617 } else if( jo.has_array( "using" ) ) {
1618 for( JsonArray cur : jo.get_array( "using" ) ) {
1619 con.reqs_using.emplace_back( requirement_id( cur.get_string( 0 ) ), cur.get_int( 1 ) );
1620 }
1621 }
1622
1623 jo.read( "pre_note", con.pre_note );
1624 con.pre_terrain = jo.get_string( "pre_terrain", "" );
1625 if( con.pre_terrain.size() > 1
1626 && con.pre_terrain[0] == 'f'
1627 && con.pre_terrain[1] == '_' ) {
1628 con.pre_is_furniture = true;
1629 }
1630
1631 con.post_terrain = jo.get_string( "post_terrain", "" );
1632 if( con.post_terrain.size() > 1
1633 && con.post_terrain[0] == 'f'
1634 && con.post_terrain[1] == '_' ) {
1635 con.post_is_furniture = true;
1636 }
1637
1638 con.pre_flags = jo.get_tags( "pre_flags" );
1639
1640 con.post_flags = jo.get_tags( "post_flags" );
1641
1642 if( jo.has_member( "byproducts" ) ) {
1643 con.byproduct_item_group = item_group::load_item_group( jo.get_member( "byproducts" ),
1644 "collection", "byproducts of construction " + con.str_id.str() );
1645 }
1646
1647 static const std::map<std::string, std::function<bool( const tripoint & )>> pre_special_map = {{
1648 { "", construct::check_nothing },
1649 { "check_empty", construct::check_empty },
1650 { "check_support", construct::check_support },
1651 { "check_stable", construct::check_stable },
1652 { "check_empty_stable", construct::check_empty_stable },
1653 { "check_nofloor_above", construct::check_nofloor_above },
1654 { "check_deconstruct", construct::check_deconstruct },
1655 { "check_empty_up_OK", construct::check_empty_up_OK },
1656 { "check_up_OK", construct::check_up_OK },
1657 { "check_down_OK", construct::check_down_OK },
1658 { "check_no_trap", construct::check_no_trap },
1659 { "check_ramp_low", construct::check_ramp_low },
1660 { "check_ramp_high", construct::check_ramp_high }
1661 }
1662 };
1663 static const std::map<std::string, std::function<void( const tripoint & )>> post_special_map = {{
1664 { "", construct::done_nothing },
1665 { "done_trunk_plank", construct::done_trunk_plank },
1666 { "done_grave", construct::done_grave },
1667 { "done_vehicle", construct::done_vehicle },
1668 { "done_deconstruct", construct::done_deconstruct },
1669 { "done_dig_stair", construct::done_dig_stair },
1670 { "done_mine_downstair", construct::done_mine_downstair },
1671 { "done_mine_upstair", construct::done_mine_upstair },
1672 { "done_wood_stairs", construct::done_wood_stairs },
1673 { "done_window_curtains", construct::done_window_curtains },
1674 { "done_extract_maybe_revert_to_dirt", construct::done_extract_maybe_revert_to_dirt },
1675 { "done_mark_firewood", construct::done_mark_firewood },
1676 { "done_mark_practice_target", construct::done_mark_practice_target },
1677 { "done_ramp_low", construct::done_ramp_low },
1678 { "done_ramp_high", construct::done_ramp_high }
1679 }
1680 };
1681 std::map<std::string, std::function<void( const tripoint & )>> explain_fail_map;
1682 if( jo.has_string( "pre_special" ) &&
1683 jo.get_string( "pre_special" ) == std::string( "check_deconstruct" ) ) {
1684 explain_fail_map[""] = construct::failure_deconstruct;
1685 } else {
1686 explain_fail_map[""] = construct::failure_standard;
1687 }
1688
1689 assign_or_debugmsg( con.pre_special, jo.get_string( "pre_special", "" ), pre_special_map );
1690 assign_or_debugmsg( con.post_special, jo.get_string( "post_special", "" ), post_special_map );
1691 assign_or_debugmsg( con.explain_failure, jo.get_string( "explain_failure", "" ), explain_fail_map );
1692 con.vehicle_start = jo.get_bool( "vehicle_start", false );
1693
1694 con.on_display = jo.get_bool( "on_display", true );
1695 con.dark_craftable = jo.get_bool( "dark_craftable", false );
1696
1697 constructions.push_back( con );
1698 construction_id_map.emplace( con.str_id, con.id );
1699 }
1700
reset_constructions()1701 void reset_constructions()
1702 {
1703 constructions.clear();
1704 construction_id_map.clear();
1705 finalized = false;
1706 }
1707
check_constructions()1708 void check_constructions()
1709 {
1710 for( size_t i = 0; i < constructions.size(); i++ ) {
1711 const construction &c = constructions[ i ];
1712 const std::string display_name = "construction " + c.str_id.str();
1713 for( const auto &pr : c.required_skills ) {
1714 if( !pr.first.is_valid() ) {
1715 debugmsg( "Unknown skill %s in %s", pr.first.c_str(), display_name );
1716 }
1717 }
1718
1719 if( !c.requirements.is_valid() ) {
1720 debugmsg( "%s has missing requirement data %s",
1721 display_name, c.requirements.c_str() );
1722 }
1723
1724 if( !c.pre_terrain.empty() ) {
1725 if( c.pre_is_furniture ) {
1726 if( !furn_str_id( c.pre_terrain ).is_valid() ) {
1727 debugmsg( "Unknown pre_terrain (furniture) %s in %s", c.pre_terrain, display_name );
1728 }
1729 } else if( !ter_str_id( c.pre_terrain ).is_valid() ) {
1730 debugmsg( "Unknown pre_terrain (terrain) %s in %s", c.pre_terrain, display_name );
1731 }
1732 }
1733 if( !c.post_terrain.empty() ) {
1734 if( c.post_is_furniture ) {
1735 if( !furn_str_id( c.post_terrain ).is_valid() ) {
1736 debugmsg( "Unknown post_terrain (furniture) %s in %s", c.post_terrain, display_name );
1737 }
1738 } else if( !ter_str_id( c.post_terrain ).is_valid() ) {
1739 debugmsg( "Unknown post_terrain (terrain) %s in %s", c.post_terrain, display_name );
1740 }
1741 }
1742 if( c.id != construction_id( i ) ) {
1743 debugmsg( "%s has id %u, but should have %u",
1744 display_name, c.id.to_i(), i );
1745 }
1746 if( construction_id_map.find( c.str_id ) == construction_id_map.end() ) {
1747 debugmsg( "%s is an invalid string id",
1748 display_name );
1749 } else if( construction_id_map[c.str_id] != construction_id( i ) ) {
1750 debugmsg( "%s points to int id %u, but should point to %u",
1751 display_name, construction_id_map[c.str_id].to_i(), i );
1752 }
1753 }
1754 }
1755
print_time(const catacurses::window & w,const point & p,int width,nc_color col) const1756 int construction::print_time( const catacurses::window &w, const point &p, int width,
1757 nc_color col ) const
1758 {
1759 std::string text = get_time_string();
1760 return fold_and_print( w, p, width, col, text );
1761 }
1762
time_scale() const1763 float construction::time_scale() const
1764 {
1765 //incorporate construction time scaling
1766 if( get_option<int>( "CONSTRUCTION_SCALING" ) == 0 ) {
1767 return calendar::season_ratio();
1768 } else {
1769 return get_option<int>( "CONSTRUCTION_SCALING" ) / 100.0;
1770 }
1771 }
1772
adjusted_time() const1773 int construction::adjusted_time() const
1774 {
1775 int final_time = time;
1776 int assistants = 0;
1777
1778 for( auto &elem : get_avatar().get_crafting_helpers() ) {
1779 if( elem->meets_skill_requirements( *this ) ) {
1780 assistants++;
1781 }
1782 }
1783
1784 if( assistants >= 2 ) {
1785 final_time *= 0.4f;
1786 } else if( assistants == 1 ) {
1787 final_time *= 0.75f;
1788 }
1789
1790 final_time *= time_scale();
1791
1792 return final_time;
1793 }
1794
get_time_string() const1795 std::string construction::get_time_string() const
1796 {
1797 const time_duration turns = time_duration::from_moves( adjusted_time() );
1798 return _( "Time to complete: " ) + colorize( to_string( turns ), color_data );
1799 }
1800
get_folded_time_string(int width) const1801 std::vector<std::string> construction::get_folded_time_string( int width ) const
1802 {
1803 std::string time_text = get_time_string();
1804 std::vector<std::string> folded_time = foldstring( time_text, width );
1805 return folded_time;
1806 }
1807
finalize_constructions()1808 void finalize_constructions()
1809 {
1810 std::vector<item_comp> frame_items;
1811 for( const auto &e : vpart_info::all() ) {
1812 const vpart_info &vp = e.second;
1813 if( !vp.has_flag( flag_INITIAL_PART ) ) {
1814 continue;
1815 }
1816 frame_items.push_back( item_comp( vp.base_item, 1 ) );
1817 }
1818
1819 if( frame_items.empty() ) {
1820 debugmsg( "No valid frames detected for vehicle construction" );
1821 }
1822
1823 for( construction &con : constructions ) {
1824 if( !con.group.is_valid() ) {
1825 debugmsg( "Invalid construction group (%s) defined for construction (%s)",
1826 con.group.str(), con.str_id.str() );
1827 }
1828 if( con.vehicle_start ) {
1829 const_cast<requirement_data &>( con.requirements.obj() ).get_components().push_back( frame_items );
1830 }
1831 bool is_valid_construction_category = false;
1832 for( const construction_category &cc : construction_categories::get_all() ) {
1833 if( con.category == cc.id ) {
1834 is_valid_construction_category = true;
1835 break;
1836 }
1837 }
1838 if( !is_valid_construction_category ) {
1839 debugmsg( "Invalid construction category (%s) defined for construction (%s)", con.category.str(),
1840 con.str_id.str() );
1841 }
1842 requirement_data requirements_ = std::accumulate(
1843 con.reqs_using.begin(), con.reqs_using.end(), *con.requirements );
1844
1845 requirement_data::save_requirement( requirements_, con.requirements );
1846 con.reqs_using.clear();
1847 }
1848
1849 constructions.erase( std::remove_if( constructions.begin(), constructions.end(),
1850 [&]( const construction & c ) {
1851 return c.requirements->is_blacklisted();
1852 } ), constructions.end() );
1853
1854 construction_id_map.clear();
1855 for( size_t i = 0; i < constructions.size(); i++ ) {
1856 constructions[ i ].id = construction_id( i );
1857 construction_id_map.emplace( constructions[i].str_id, constructions[i].id );
1858 }
1859
1860 finalized = true;
1861 }
1862
get_build_reqs_for_furn_ter_ids(const std::pair<std::map<ter_id,int>,std::map<furn_id,int>> & changed_ids)1863 build_reqs get_build_reqs_for_furn_ter_ids(
1864 const std::pair<std::map<ter_id, int>, std::map<furn_id, int>> &changed_ids )
1865 {
1866 build_reqs total_reqs;
1867
1868 if( !finalized ) {
1869 debugmsg( "get_build_reqs_for_furn_ter_ids called before finalization" );
1870 return total_reqs;
1871 }
1872 std::map<construction_id, int> total_builds;
1873
1874 // iteratively recurse through the pre-terrains until the pre-terrain is empty, adding
1875 // the constructions to the total_builds map
1876 const auto add_builds = [&total_builds]( const construction & build, int count ) {
1877 if( total_builds.find( build.id ) == total_builds.end() ) {
1878 total_builds[build.id] = 0;
1879 }
1880 total_builds[build.id] += count;
1881 std::string build_pre_ter = build.pre_terrain;
1882 while( !build_pre_ter.empty() ) {
1883 for( const construction &pre_build : constructions ) {
1884 if( pre_build.category == construction_category_REPAIR ) {
1885 continue;
1886 }
1887 if( pre_build.post_terrain == build_pre_ter ) {
1888 if( total_builds.find( pre_build.id ) == total_builds.end() ) {
1889 total_builds[pre_build.id] = 0;
1890 }
1891 total_builds[pre_build.id] += count;
1892 build_pre_ter = pre_build.pre_terrain;
1893 break;
1894 }
1895 }
1896 break;
1897 }
1898 };
1899
1900 // go through the list of terrains and add their constructions and any pre-constructions
1901 // to the map of total builds
1902 for( const auto &ter_data : changed_ids.first ) {
1903 for( const construction &build : constructions ) {
1904 if( build.post_terrain.empty() || build.post_is_furniture ||
1905 build.category == construction_category_REPAIR ) {
1906 continue;
1907 }
1908 if( ter_id( build.post_terrain ) == ter_data.first ) {
1909 add_builds( build, ter_data.second );
1910 break;
1911 }
1912 }
1913 }
1914 // same, but for furniture
1915 for( const auto &furn_data : changed_ids.second ) {
1916 for( const construction &build : constructions ) {
1917 if( build.post_terrain.empty() || !build.post_is_furniture ||
1918 build.category == construction_category_REPAIR ) {
1919 continue;
1920 }
1921 if( furn_id( build.post_terrain ) == furn_data.first ) {
1922 add_builds( build, furn_data.second );
1923 break;
1924 }
1925 }
1926 }
1927
1928 for( const auto &build_data : total_builds ) {
1929 const construction &build = build_data.first.obj();
1930 const int count = build_data.second;
1931 total_reqs.time += build.time * count;
1932 if( total_reqs.reqs.find( build.requirements ) == total_reqs.reqs.end() ) {
1933 total_reqs.reqs[build.requirements] = 0;
1934 }
1935 total_reqs.reqs[build.requirements] += count;
1936 for( const auto &req_skill : build.required_skills ) {
1937 if( total_reqs.skills.find( req_skill.first ) == total_reqs.skills.end() ) {
1938 total_reqs.skills[req_skill.first] = req_skill.second;
1939 } else if( total_reqs.skills[req_skill.first] < req_skill.second ) {
1940 total_reqs.skills[req_skill.first] = req_skill.second;
1941 }
1942 }
1943 }
1944
1945 return total_reqs;
1946 }
1947
1948 static const construction null_construction {};
1949
1950 template <>
id() const1951 const construction_str_id &construction_id::id() const
1952 {
1953 if( !finalized ) {
1954 debugmsg( "construction_id::id called before finalization" );
1955 return construction_str_id::NULL_ID();
1956 } else if( is_valid() ) {
1957 return constructions[to_i()].str_id;
1958 } else {
1959 if( to_i() != -1 ) {
1960 debugmsg( "Invalid construction id %d", to_i() );
1961 }
1962 return construction_str_id::NULL_ID();
1963 }
1964 }
1965
1966 template <>
obj() const1967 const construction &construction_id::obj() const
1968 {
1969 if( !finalized ) {
1970 debugmsg( "construction_id::obj called before finalization" );
1971 return null_construction;
1972 } else if( is_valid() ) {
1973 return constructions[to_i()];
1974 } else {
1975 debugmsg( "Invalid construction id %d", to_i() );
1976 return null_construction;
1977 }
1978 }
1979
1980 template <>
is_valid() const1981 bool construction_id::is_valid() const
1982 {
1983 if( !finalized ) {
1984 debugmsg( "construction_id::is_valid called before finalization" );
1985 return false;
1986 }
1987 return to_i() >= 0 && static_cast<size_t>( to_i() ) < constructions.size();
1988 }
1989
1990 template <>
id() const1991 construction_id construction_str_id::id() const
1992 {
1993 if( !finalized ) {
1994 debugmsg( "construction_str_id::id called before finalization" );
1995 return construction_id( -1 );
1996 }
1997 auto it = construction_id_map.find( *this );
1998 if( it != construction_id_map.end() ) {
1999 return it->second;
2000 } else {
2001 if( !is_null() ) {
2002 debugmsg( "Invalid construction str id %s", str() );
2003 }
2004 return construction_id( -1 );
2005 }
2006 }
2007
2008 template <>
obj() const2009 const construction &construction_str_id::obj() const
2010 {
2011 if( !finalized ) {
2012 debugmsg( "construction_str_id::obj called before finalization" );
2013 return null_construction;
2014 }
2015 auto it = construction_id_map.find( *this );
2016 if( it != construction_id_map.end() ) {
2017 return it->second.obj();
2018 } else {
2019 debugmsg( "Invalid construction str id %s", str() );
2020 return null_construction;
2021 }
2022 }
2023
2024 template <>
is_valid() const2025 bool construction_str_id::is_valid() const
2026 {
2027 if( !finalized ) {
2028 debugmsg( "construction_str_id::is_valid called before finalization" );
2029 return false;
2030 }
2031 return construction_id_map.find( *this ) != construction_id_map.end();
2032 }
2033