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 &current_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 &current_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