1 #include "faction_camp.h" // IWYU pragma: associated
2 
3 #include <algorithm>
4 #include <cstddef>
5 #include <functional>
6 #include <list>
7 #include <map>
8 #include <memory>
9 #include <new>
10 #include <set>
11 #include <string>
12 #include <unordered_set>
13 #include <vector>
14 
15 #include "activity_handlers.h"
16 #include "activity_type.h"
17 #include "avatar.h"
18 #include "basecamp.h"
19 #include "calendar.h"
20 #include "cata_utility.h"
21 #include "catacharset.h"
22 #include "character.h"
23 #include "clzones.h"
24 #include "colony.h"
25 #include "color.h"
26 #include "coordinate_conversions.h"
27 #include "coordinates.h"
28 #include "cursesdef.h"
29 #include "debug.h"
30 #include "enums.h"
31 #include "faction.h"
32 #include "game.h"
33 #include "game_constants.h"
34 #include "iexamine.h"
35 #include "input.h"
36 #include "inventory.h"
37 #include "item.h"
38 #include "item_contents.h"
39 #include "item_group.h"
40 #include "item_pocket.h"
41 #include "item_stack.h"
42 #include "itype.h"
43 #include "kill_tracker.h"
44 #include "line.h"
45 #include "map.h"
46 #include "map_iterator.h"
47 #include "mapdata.h"
48 #include "mapgen_functions.h"
49 #include "memory_fast.h"
50 #include "messages.h"
51 #include "mission.h"
52 #include "mission_companion.h"
53 #include "npc.h"
54 #include "npctalk.h"
55 #include "omdata.h"
56 #include "optional.h"
57 #include "output.h"
58 #include "overmap.h"
59 #include "overmap_ui.h"
60 #include "overmapbuffer.h"
61 #include "pimpl.h"
62 #include "player_activity.h"
63 #include "point.h"
64 #include "recipe.h"
65 #include "recipe_groups.h"
66 #include "requirements.h"
67 #include "rng.h"
68 #include "skill.h"
69 #include "stomach.h"
70 #include "string_formatter.h"
71 #include "string_input_popup.h"
72 #include "translations.h"
73 #include "type_id.h"
74 #include "ui.h"
75 #include "ui_manager.h"
76 #include "units.h"
77 #include "value_ptr.h"
78 #include "visitable.h"
79 #include "weather.h"
80 #include "weighted_list.h"
81 
82 class character_id;
83 
84 static const activity_id ACT_MOVE_LOOT( "ACT_MOVE_LOOT" );
85 
86 static const itype_id itype_fungal_seeds( "fungal_seeds" );
87 static const itype_id itype_log( "log" );
88 static const itype_id itype_marloss_seed( "marloss_seed" );
89 
90 static const std::string flag_PLOWABLE( "PLOWABLE" );
91 static const std::string flag_TREE( "TREE" );
92 
93 static const zone_type_id zone_type_CAMP_FOOD( "CAMP_FOOD" );
94 static const zone_type_id zone_type_CAMP_STORAGE( "CAMP_STORAGE" );
95 
96 static const skill_id skill_bashing( "bashing" );
97 static const skill_id skill_cutting( "cutting" );
98 static const skill_id skill_dodge( "dodge" );
99 static const skill_id skill_fabrication( "fabrication" );
100 static const skill_id skill_gun( "gun" );
101 static const skill_id skill_melee( "melee" );
102 static const skill_id skill_speech( "speech" );
103 static const skill_id skill_stabbing( "stabbing" );
104 static const skill_id skill_survival( "survival" );
105 static const skill_id skill_swimming( "swimming" );
106 static const skill_id skill_traps( "traps" );
107 static const skill_id skill_unarmed( "unarmed" );
108 
109 static const mtype_id mon_bear( "mon_bear" );
110 static const mtype_id mon_beaver( "mon_beaver" );
111 static const mtype_id mon_black_rat( "mon_black_rat" );
112 static const mtype_id mon_chicken( "mon_chicken" );
113 static const mtype_id mon_chipmunk( "mon_chipmunk" );
114 static const mtype_id mon_cockatrice( "mon_cockatrice" );
115 static const mtype_id mon_cougar( "mon_cougar" );
116 static const mtype_id mon_cow( "mon_cow" );
117 static const mtype_id mon_coyote( "mon_coyote" );
118 static const mtype_id mon_deer( "mon_deer" );
119 static const mtype_id mon_duck( "mon_duck" );
120 static const mtype_id mon_fox_gray( "mon_fox_gray" );
121 static const mtype_id mon_fox_red( "mon_fox_red" );
122 static const mtype_id mon_groundhog( "mon_groundhog" );
123 static const mtype_id mon_grouse( "mon_grouse" );
124 static const mtype_id mon_hare( "mon_hare" );
125 static const mtype_id mon_lemming( "mon_lemming" );
126 static const mtype_id mon_mink( "mon_mink" );
127 static const mtype_id mon_moose( "mon_moose" );
128 static const mtype_id mon_muskrat( "mon_muskrat" );
129 static const mtype_id mon_opossum( "mon_opossum" );
130 static const mtype_id mon_otter( "mon_otter" );
131 static const mtype_id mon_pheasant( "mon_pheasant" );
132 static const mtype_id mon_pig( "mon_pig" );
133 static const mtype_id mon_rabbit( "mon_rabbit" );
134 static const mtype_id mon_squirrel( "mon_squirrel" );
135 static const mtype_id mon_turkey( "mon_turkey" );
136 static const mtype_id mon_weasel( "mon_weasel" );
137 static const mtype_id mon_wolf( "mon_wolf" );
138 
139 static const trait_id trait_DEBUG_HS( "DEBUG_HS" );
140 
141 struct mass_volume {
142     units::mass wgt = 0_gram;
143     units::volume vol = 0_ml;
144     int count = 0;
145 };
146 
147 namespace base_camps
148 {
149 // eventually this will include the start and return functions
150 struct miss_data {
151     std::string miss_id;
152     translation desc;
153     translation action;
154     std::string ret_miss_id;
155     translation ret_desc;
156 };
157 
158 recipe_id select_camp_option( const std::map<recipe_id, translation> &pos_options,
159                               const std::string &option );
160 
161 // eventually this will move to JSON
162 std::map<std::string, miss_data> miss_info = {{
163         {
164             "_faction_upgrade_camp", {
165                 "Upgrade Camp", to_translation( "Upgrade camp" ),
166                 to_translation( "Working to expand your camp!\n" ),
167                 "Recover Ally from Upgrading", to_translation( "Recover Ally from Upgrading" )
168             }
169         },
170         {
171             "_faction_camp_recall", {
172                 "Emergency Recall", to_translation( "Emergency Recall" ),
173                 to_translation( "Lost in the ether!\n" ),
174                 "Emergency Recall", to_translation( "Emergency Recall" )
175             }
176         },
177         {
178             "_faction_camp_crafting_", {
179                 "Craft Item", to_translation( "Craft Item" ),
180                 to_translation( "Busy crafting!\n" ),
181                 " (Finish) Crafting", to_translation( " (Finish) Crafting" )
182             }
183         },
184         {
185             "traveling", {
186                 "Traveling", to_translation( "Traveling" ),
187                 to_translation( "Busy traveling!\n" ),
188                 "Recall ally from traveling", to_translation( "Recall ally from traveling" )
189             }
190         },
191         {
192             "_faction_camp_gathering", {
193                 "Gather Materials", to_translation( "Gather Materials" ),
194                 to_translation( "Searching for materials to upgrade the camp.\n" ),
195                 "Recover Ally from Gathering", to_translation( "Recover Ally from Gathering" )
196             }
197         },
198         {
199             "_faction_camp_firewood", {
200                 "Collect Firewood", to_translation( "Collect Firewood" ),
201                 to_translation( "Searching for firewood.\n" ),
202                 "Recover Firewood Gatherers", to_translation( "Recover Firewood Gatherers" )
203             }
204         },
205         {
206             "_faction_camp_menial", {
207                 "Menial Labor", to_translation( "Menial Labor" ),
208                 to_translation( "Performing menial labor…\n" ),
209                 "Recover Menial Laborer", to_translation( "Recover Menial Laborer" )
210             }
211         },
212         {
213             "_faction_camp_expansion", {
214                 "Expand Base", to_translation( "Expand Base" ),
215                 to_translation( "Surveying for expansion…\n" ),
216                 "Recover Surveyor", to_translation( "Recover Surveyor" )
217             }
218         },
219         {
220             "_faction_camp_cut_log", {
221                 "Cut Logs", to_translation( "Cut Logs" ),
222                 to_translation( "Cutting logs in the woods…\n" ),
223                 "Recover Log Cutter", to_translation( "Recover Log Cutter" )
224             }
225         },
226         {
227             "_faction_camp_clearcut", {
228                 "Clearcut", to_translation( "Clear a forest" ),
229                 to_translation( "Clearing a forest…\n" ),
230                 "Recover Clearcutter", to_translation( "Recover Clearcutter" )
231             }
232         },
233         {
234             "_faction_camp_hide_site", {
235                 "Setup Hide Site", to_translation( "Setup Hide Site" ),
236                 to_translation( "Setting up a hide site…\n" ),
237                 "Recover Hide Setup", to_translation( "Recover Hide Setup" )
238             }
239         },
240         {
241             "_faction_camp_hide_trans", {
242                 "Relay Hide Site", to_translation( "Relay Hide Site" ),
243                 to_translation( "Transferring gear to a hide site…\n" ),
244                 "Recover Hide Relay", to_translation( "Recover Hide Relay" )
245             }
246         },
247         {
248             "_faction_camp_foraging", {
249                 "Camp Forage", to_translation( "Forage for plants" ),
250                 to_translation( "Foraging for edible plants.\n" ),
251                 "Recover Foragers", to_translation( "Recover Foragers" )
252             }
253         },
254         {
255             "_faction_camp_trapping", {
256                 "Trap Small Game", to_translation( "Trap Small Game" ),
257                 to_translation( "Trapping Small Game.\n" ),
258                 "Recover Trappers", to_translation( "Recover Trappers" )
259             }
260         },
261         {
262             "_faction_camp_hunting", {
263                 "Hunt Large Animals", to_translation( "Hunt Large Animals" ),
264                 to_translation( "Hunting large animals.\n" ),
265                 "Recover Hunter", to_translation( "Recover Hunter" )
266             }
267         },
268         {
269             "_faction_camp_om_fortifications", {
270                 "Construct Map Fort", to_translation( "Construct Map Fortifications" ),
271                 to_translation( "Constructing fortifications…\n" ),
272                 "Finish Map Fort", to_translation( "Finish Map Fortifications" )
273             }
274         },
275         {
276             "_faction_camp_recruit_0", {
277                 "Recruit Companions", to_translation( "Recruit Companions" ),
278                 to_translation( "Searching for recruits.\n" ),
279                 "Recover Recruiter", to_translation( "Recover Recruiter" )
280             }
281         },
282         {
283             "_faction_camp_scout_0", {
284                 "Scout Mission", to_translation( "Scout Mission" ),
285                 to_translation( "Scouting the region.\n" ),
286                 "Recover Scout", to_translation( "Recover Scout" )
287             }
288         },
289         {
290             "_faction_camp_combat_0", {
291                 "Combat Patrol", to_translation( "Combat Patrol" ),
292                 to_translation( "Patrolling the region.\n" ),
293                 "Recover Combat Patrol", to_translation( "Recover Combat Patrol" )
294             }
295         },
296         {
297             "_faction_upgrade_exp_", {
298                 " Expansion Upgrade", to_translation( " Expansion Upgrade" ),
299                 to_translation( "Working to upgrade your expansions!\n" ),
300                 "Recover Ally", to_translation( "Recover Ally" )
301             }
302         },
303         {
304             "_faction_exp_chop_shop_", {
305                 " Chop Shop", to_translation( " Chop Shop" ),
306                 to_translation( "Working at the chop shop…\n" ),
307                 " (Finish) Chop Shop", to_translation( " (Finish) Chop Shop" )
308             }
309         },
310         {
311             "_faction_exp_kitchen_cooking_", {
312                 " Kitchen Cooking", to_translation( " Kitchen Cooking" ),
313                 to_translation( "Working in your kitchen!\n" ),
314                 " (Finish) Cooking", to_translation( " (Finish) Cooking" )
315             }
316         },
317         {
318             "_faction_exp_blacksmith_crafting_", {
319                 " Blacksmithing", to_translation( " Blacksmithing" ),
320                 to_translation( "Working in your blacksmith shop!\n" ),
321                 " (Finish) Smithing", to_translation( " (Finish) Smithing" )
322             }
323         },
324         {
325             "_faction_exp_plow_", {
326                 " Plow Fields", to_translation( " Plow Fields" ),
327                 to_translation( "Working to plow your fields!\n" ),
328                 " (Finish) Plow Fields", to_translation( " (Finish) Plow fields" )
329             }
330         },
331         {
332             "_faction_exp_plant_", {
333                 " Plant Fields", to_translation( " Plant Fields" ),
334                 to_translation( "Working to plant your fields!\n" ),
335                 " (Finish) Plant Fields", to_translation( " (Finish) Plant Fields" )
336             }
337         },
338         {
339             "_faction_exp_harvest_", {
340                 " Harvest Fields", to_translation( " Harvest Fields" ),
341                 to_translation( "Working to harvest your fields!\n" ),
342                 " (Finish) Harvest Fields", to_translation( " (Finish) Harvest Fields" )
343             }
344         },
345         {
346             "_faction_exp_farm_crafting_", {
347                 " Farm Crafting", to_translation( " Farm Crafting" ),
348                 to_translation( "Working on your farm!\n" ),
349                 " (Finish) Crafting", to_translation( " (Finish) Crafting" )
350             }
351         }
352     }
353 };
354 } // namespace base_camps
355 
356 /**** Forward declaration of utility functions */
357 /**
358  * Counts or destroys and drops the bash items of all terrain that matches @ref t in the map tile
359  * @param comp NPC companion
360  * @param omt_tgt the targeted OM tile
361  * @param t terrain you are looking for
362  * @param chance chance of destruction, 0 to 100
363  * @param estimate if true, non-destructive count of the furniture
364  * @param bring_back force the destruction of the furniture and bring back the drop items
365  */
366 static int om_harvest_ter( npc &comp, const tripoint_abs_omt &omt_tgt, const ter_id &t,
367                            int chance = 100,
368                            bool estimate = false, bool bring_back = true );
369 // om_harvest_ter helper function that counts the furniture instances
370 static int om_harvest_ter_est( npc &comp, const tripoint_abs_omt &omt_tgt, const ter_id &t,
371                                int chance = 100 );
372 static int om_harvest_ter_break( npc &comp, const tripoint_abs_omt &omt_tgt, const ter_id &t,
373                                  int chance = 100 );
374 /// Collects all items in @ref omt_tgt with a @ref chance between 0 - 1.0, returns total
375 /// mass and volume
376 /// @ref take, whether you take the item or count it
377 static mass_volume om_harvest_itm( const npc_ptr &comp, const tripoint_abs_omt &omt_tgt,
378                                    int chance = 100,
379                                    bool take = true );
380 static void apply_camp_ownership( const tripoint &camp_pos, int radius );
381 /*
382  * Counts or cuts trees into trunks and trunks into logs
383  * @param omt_tgt the targeted OM tile
384  * @param chance chance of destruction, 0 to 100
385  * @param estimate if true, non-destructive count of trees
386  * @force_cut_trunk if true and estimate is false, chop tree trunks into logs
387  */
388 static int om_cutdown_trees( const tripoint_abs_omt &omt_tgt, int chance = 100,
389                              bool estimate = false,
390                              bool force_cut_trunk = true );
391 static int om_cutdown_trees_est( const tripoint_abs_omt &omt_tgt, int chance = 100 );
392 static int om_cutdown_trees_logs( const tripoint_abs_omt &omt_tgt, int chance = 100 );
393 static int om_cutdown_trees_trunks( const tripoint_abs_omt &omt_tgt, int chance = 100 );
394 
395 /// Creates an improvised shelter at @ref omt_tgt and dumps the @ref itms into the building
396 static bool om_set_hide_site( npc &comp, const tripoint_abs_omt &omt_tgt,
397                               const std::vector<item *> &itms,
398                               const std::vector<item *> &itms_rem = {} );
399 /**
400  * Opens the overmap so that you can select points for missions or constructions.
401  * @param omt_pos where your camp is, used for calculating travel distances
402  * @param min_range
403  * @param range max number of OM tiles the user can select
404  * @param possible_om_types requires the user to reselect if the OM picked isn't in the list
405  * @param must_see whether the user can select points in the unknown/fog of war
406  * @param popup_notice toggles if the user should be shown ranges before being allowed to pick
407  * @param source if you are selecting multiple points this is where the OM is centered to start
408  * @param bounce
409  */
410 static tripoint_abs_omt om_target_tile(
411     const tripoint_abs_omt &omt_pos, int min_range = 1, int range = 1,
412     const std::vector<std::string> &possible_om_types = {}, ot_match_type match_type =
413         ot_match_type::exact, bool must_see = true,
414     bool popup_notice = true,
415     const tripoint_abs_omt &source = tripoint_abs_omt( -999, -999, -999 ),
416     bool bounce = false );
417 static void om_range_mark( const tripoint_abs_omt &origin, int range, bool add_notes = true,
418                            const std::string &message = "Y;X: MAX RANGE" );
419 static void om_line_mark(
420     const tripoint_abs_omt &origin, const tripoint_abs_omt &dest, bool add_notes = true,
421     const std::string &message = "R;X: PATH" );
422 static std::vector<tripoint_abs_omt> om_companion_path(
423     const tripoint_abs_omt &start, int range_start = 90, bool bounce = true );
424 /**
425  * Can be used to calculate total trip time for an NPC mission or just the traveling portion.
426  * Doesn't use the pathingalgorithms yet.
427  * @param omt_pos start point
428  * @param omt_tgt target point
429  * @param work how much time the NPC will stay at the target
430  * @param trips how many trips back and forth the NPC will make
431  */
432 static time_duration companion_travel_time_calc( const tripoint_abs_omt &pos,
433         const tripoint_abs_omt &tgt,
434         time_duration work, int trips = 1, int haulage = 0 );
435 static time_duration companion_travel_time_calc(
436     const std::vector<tripoint_abs_omt> &journey, time_duration work, int trips = 1,
437     int haulage = 0 );
438 /// Determines how many round trips a given NPC @ref comp will take to move all of the
439 /// items @ref itms
440 static int om_carry_weight_to_trips( const std::vector<item *> &itms,
441                                      const npc_ptr &comp = nullptr );
442 /// Determines how many trips it takes to move @ref mass and @ref volume of items
443 /// with @ref carry_mass and @ref carry_volume moved per trip
444 static int om_carry_weight_to_trips( const units::mass &mass, const units::volume &volume,
445                                      const units::mass &carry_mass, const units::volume &carry_volume );
446 /// Formats the variables into a standard looking description to be displayed in a ynquery window
447 static std::string camp_trip_description( const time_duration &total_time,
448         const time_duration &working_time,
449         const time_duration &travel_time,
450         int distance, int trips, int need_food );
451 
452 /// Changes the faction food supply by @ref change, 0 returns total food supply, a negative
453 /// total food supply hurts morale
454 static int camp_food_supply( int change = 0, bool return_days = false );
455 /// Same as above but takes a time_duration and consumes from faction food supply for that
456 /// duration of work
457 static int camp_food_supply( time_duration work );
458 /// Returns the total charges of food time_duration @ref work costs
459 static int time_to_food( time_duration work );
460 /// Changes the faction respect for you by @ref change, returns respect
461 static int camp_discipline( int change = 0 );
462 /// Changes the faction opinion for you by @ref change, returns opinion
463 static int camp_morale( int change = 0 );
464 /*
465  * check if a companion survives a random encounter
466  * @param comp the companion
467  * @param situation what the survivor is doing
468  * @param favor a number added to the survivor's skills to see if he can avoid the encounter
469  * @param threat a number indicating how dangerous the encounter is
470  * TODO: Convert to JSON basic on dynamic line type structure
471  */
472 static bool survive_random_encounter( npc &comp, std::string &situation, int favor, int threat );
473 
update_time_left(std::string & entry,const comp_list & npc_list)474 static bool update_time_left( std::string &entry, const comp_list &npc_list )
475 {
476     bool avail = false;
477     Character &player_character = get_player_character();
478     for( const auto &comp : npc_list ) {
479         if( comp->companion_mission_time_ret < calendar::turn ) {
480             entry = entry +  _( " [DONE]\n" );
481             avail = true;
482         } else {
483             entry = entry + " [" +
484                     to_string( comp->companion_mission_time_ret - calendar::turn ) +
485                     _( " left]\n" );
486             avail = player_character.has_trait( trait_DEBUG_HS );
487         }
488     }
489     entry = entry + _( "\n\nDo you wish to bring your allies back into your party?" );
490     return avail;
491 }
492 
update_time_fixed(std::string & entry,const comp_list & npc_list,const time_duration & duration)493 static bool update_time_fixed( std::string &entry, const comp_list &npc_list,
494                                const time_duration &duration )
495 {
496     bool avail = false;
497     for( const auto &comp : npc_list ) {
498         time_duration elapsed = calendar::turn - comp->companion_mission_time;
499         entry = entry + " " +  comp->name + " [" + to_string( elapsed ) + "/" +
500                 to_string( duration ) + "]\n";
501         avail |= elapsed >= duration;
502     }
503     entry = entry + _( "\n\nDo you wish to bring your allies back into your party?" );
504     return avail;
505 }
506 
get_basecamp(npc & p,const std::string & camp_type="default")507 static cata::optional<basecamp *> get_basecamp( npc &p, const std::string &camp_type = "default" )
508 {
509 
510     tripoint_abs_omt omt_pos = p.global_omt_location();
511     cata::optional<basecamp *> bcp = overmap_buffer.find_camp( omt_pos.xy() );
512     if( bcp ) {
513         return bcp;
514     }
515     get_map().add_camp( omt_pos, "faction_camp" );
516     bcp = overmap_buffer.find_camp( omt_pos.xy() );
517     if( !bcp ) {
518         return cata::nullopt;
519     }
520     basecamp *temp_camp = *bcp;
521     temp_camp->define_camp( omt_pos, camp_type );
522     return temp_camp;
523 }
524 
select_camp_option(const std::map<recipe_id,translation> & pos_options,const std::string & option)525 recipe_id base_camps::select_camp_option( const std::map<recipe_id, translation> &pos_options,
526         const std::string &option )
527 {
528     if( pos_options.size() == 1 ) {
529         return pos_options.begin()->first;
530     }
531 
532     std::vector<std::string> pos_names;
533     int choice = 0;
534 
535     for( const auto &it : pos_options ) {
536         pos_names.push_back( it.second.translated() );
537     }
538 
539     std::sort( pos_names.begin(), pos_names.end(), localized_compare );
540 
541     choice = uilist( option, pos_names );
542 
543     if( choice < 0 || static_cast<size_t>( choice ) >= pos_names.size() ) {
544         popup( _( "You choose to wait…" ) );
545         return recipe_id::NULL_ID();
546     }
547 
548     std::string selected_name = pos_names[choice];
549 
550     std::map<recipe_id, translation>::const_iterator iter = find_if( pos_options.begin(),
551     pos_options.end(), [selected_name]( const std::pair<const recipe_id, translation> &node ) {
552         return node.second.translated() == selected_name;
553     } );
554     return iter->first;
555 }
556 
start_camp(npc & p)557 void talk_function::start_camp( npc &p )
558 {
559     const tripoint_abs_omt omt_pos = p.global_omt_location();
560     const oter_id &omt_ref = overmap_buffer.ter( omt_pos );
561 
562     const auto &pos_camps = recipe_group::get_recipes_by_id( "all_faction_base_types",
563                             omt_ref.id().c_str() );
564     if( pos_camps.empty() ) {
565         popup( _( "You cannot build a camp here." ) );
566         return;
567     }
568     const recipe_id camp_type = base_camps::select_camp_option( pos_camps,
569                                 _( "Select a camp type:" ) );
570     if( !camp_type ) {
571         return;
572     }
573 
574     for( const auto &om_near : om_building_region( omt_pos, 3 ) ) {
575         const oter_id &om_type = oter_id( om_near.first );
576         if( is_ot_match( "faction_base", om_type, ot_match_type::contains ) ) {
577             popup( _( "You are too close to another camp!" ) );
578             return;
579         }
580     }
581     const recipe &making = camp_type.obj();
582     if( !run_mapgen_update_func( making.get_blueprint(), omt_pos ) ) {
583         popup( _( "%s failed to start the %s basecamp, perhaps there is a vehicle in the way." ),
584                p.disp_name(), making.get_blueprint() );
585         return;
586     }
587     get_basecamp( p, camp_type.str() );
588 }
589 
recover_camp(npc & p)590 void talk_function::recover_camp( npc &p )
591 {
592     const tripoint_abs_omt omt_pos = p.global_omt_location();
593     const std::string &omt_ref = overmap_buffer.ter( omt_pos ).id().c_str();
594     if( omt_ref.find( "faction_base_camp" ) == std::string::npos ) {
595         popup( _( "There is no faction camp here to recover!" ) );
596         return;
597     }
598     get_basecamp( p );
599 }
600 
remove_overseer(npc & p)601 void talk_function::remove_overseer( npc &p )
602 {
603     size_t suffix = p.name.find( _( ", Camp Manager" ) );
604     if( suffix != std::string::npos ) {
605         p.name = p.name.substr( 0, suffix );
606     }
607 
608     add_msg( _( "%s has abandoned the camp." ), p.name );
609     p.companion_mission_role_id.clear();
610     stop_guard( p );
611 }
612 
basecamp_mission(npc & p)613 void talk_function::basecamp_mission( npc &p )
614 {
615     const std::string title = _( "Base Missions" );
616     const tripoint_abs_omt omt_pos = p.global_omt_location();
617     mission_data mission_key;
618 
619     cata::optional<basecamp *> temp_camp = get_basecamp( p );
620     if( !temp_camp ) {
621         return;
622     }
623     basecamp *bcp = *temp_camp;
624     bcp->set_by_radio( get_avatar().dialogue_by_radio );
625     if( bcp->get_dumping_spot() == tripoint_zero ) {
626         map &here = get_map();
627         auto &mgr = zone_manager::get_manager();
628         if( here.check_vehicle_zones( here.get_abs_sub().z ) ) {
629             mgr.cache_vzones();
630         }
631         tripoint src_loc;
632         const tripoint abspos = p.global_square_location();
633         if( mgr.has_near( zone_type_CAMP_STORAGE, abspos, 60 ) ) {
634             const std::unordered_set<tripoint> &src_set = mgr.get_near( zone_type_CAMP_STORAGE, abspos );
635             const std::vector<tripoint> &src_sorted = get_sorted_tiles_by_distance( abspos, src_set );
636             // Find the nearest unsorted zone to dump objects at
637             if( !src_sorted.empty() ) {
638                 src_loc = here.getlocal( src_sorted.front() );
639             }
640         }
641         bcp->set_dumping_spot( here.getabs( src_loc ) );
642     }
643     bcp->get_available_missions( mission_key );
644     if( display_and_choose_opts( mission_key, omt_pos, base_camps::id, title ) ) {
645         bcp->handle_mission( mission_key.cur_key.id, mission_key.cur_key.dir );
646     }
647 }
648 
add_available_recipes(mission_data & mission_key,const point & dir,const std::map<recipe_id,translation> & craft_recipes)649 void basecamp::add_available_recipes( mission_data &mission_key, const point &dir,
650                                       const std::map<recipe_id, translation> &craft_recipes )
651 {
652     const std::string dir_id = base_camps::all_directions.at( dir ).id;
653     const std::string dir_abbr = base_camps::all_directions.at( dir ).bracket_abbr.translated();
654     for( const auto &recipe_data : craft_recipes ) {
655         const std::string id = dir_id + recipe_data.first.str();
656         const std::string &title_e = dir_abbr + recipe_data.second;
657         const std::string &entry = craft_description( recipe_data.first );
658         const recipe &recp = recipe_data.first.obj();
659         bool craftable = recp.deduped_requirements().can_make_with_inventory(
660                              _inv, recp.get_component_filter() );
661         mission_key.add_start( id, title_e, dir, entry, craftable );
662     }
663 }
664 
get_available_missions_by_dir(mission_data & mission_key,const point & dir)665 void basecamp::get_available_missions_by_dir( mission_data &mission_key, const point &dir )
666 {
667     std::string entry;
668 
669     const std::string dir_id = base_camps::all_directions.at( dir ).id;
670     const std::string dir_abbr = base_camps::all_directions.at( dir ).bracket_abbr.translated();
671     const tripoint_abs_omt omt_trg = omt_pos + dir;
672 
673     if( dir != base_camps::base_dir ) {
674         // return legacy workers
675         comp_list npc_list = get_mission_workers( "_faction_upgrade_exp_" + dir_id );
676         if( !npc_list.empty() ) {
677             const base_camps::miss_data &miss_info = base_camps::miss_info["_faction_upgrade_exp_"];
678             entry = miss_info.action.translated();
679             bool avail = update_time_left( entry, npc_list );
680             mission_key.add_return( "Recover Ally, " + dir_id + " Expansion",
681                                     _( "Recover Ally, " ) + dir_abbr + _( " Expansion" ), dir,
682                                     entry, avail );
683         }
684         // Generate upgrade missions for expansions
685         std::vector<basecamp_upgrade> upgrades = available_upgrades( dir );
686 
687         std::sort( upgrades.begin(), upgrades.end(), []( const basecamp_upgrade & p,
688                    const basecamp_upgrade & q )->bool {return p.name.translated_lt( q.name ); } );
689 
690         for( const basecamp_upgrade &upgrade : upgrades ) {
691             const base_camps::miss_data &miss_info = base_camps::miss_info["_faction_upgrade_exp_"];
692             comp_list npc_list = get_mission_workers( upgrade.bldg + "_faction_upgrade_exp_" +
693                                  dir_id );
694             if( npc_list.empty() ) {
695                 entry = om_upgrade_description( upgrade.bldg );
696                 mission_key.add_start( dir_id + miss_info.miss_id + upgrade.bldg,
697                                        dir_abbr + miss_info.desc + " " + upgrade.name, dir, entry,
698                                        upgrade.avail );
699             } else {
700                 entry = miss_info.action.translated();
701                 bool avail = update_time_left( entry, npc_list );
702                 mission_key.add_return( "Recover Ally, " + dir_id + " Expansion" + upgrade.bldg,
703                                         _( "Recover Ally, " ) + dir_abbr + _( " Expansion" ) +
704                                         " " + upgrade.name, dir, entry, avail );
705             }
706         }
707     }
708 
709     if( has_provides( "gathering", dir ) ) {
710         std::string gather_bldg = "null";
711         comp_list npc_list = get_mission_workers( "_faction_camp_gathering" );
712         const base_camps::miss_data &miss_info = base_camps::miss_info[ "_faction_camp_gathering" ];
713         entry = string_format( _( "Notes:\n"
714                                   "Send a companion to gather materials for the next camp "
715                                   "upgrade.\n\n"
716                                   "Skill used: survival\n"
717                                   "Difficulty: N/A\n"
718                                   "Gathering Possibilities:\n"
719                                   "%s\n"
720                                   "Risk: Very Low\n"
721                                   "Time: 3 Hours, Repeated\n"
722                                   "Positions: %d/3\n" ), gathering_description( gather_bldg ),
723                                npc_list.size() );
724         mission_key.add_start( miss_info.miss_id, miss_info.desc.translated(), dir,
725                                entry, npc_list.size() < 3 );
726         if( !npc_list.empty() ) {
727             entry = miss_info.action.translated();
728             bool avail = update_time_fixed( entry, npc_list, 3_hours );
729             mission_key.add_return( miss_info.ret_miss_id, miss_info.ret_desc.translated(),
730                                     dir, entry, avail );
731         }
732     }
733     if( has_provides( "firewood", dir ) ) {
734         comp_list npc_list = get_mission_workers( "_faction_camp_firewood" );
735         const base_camps::miss_data &miss_info = base_camps::miss_info[ "_faction_camp_firewood" ];
736         entry = string_format( _( "Notes:\n"
737                                   "Send a companion to gather light brush and stout branches.\n\n"
738                                   "Skill used: survival\n"
739                                   "Difficulty: N/A\n"
740                                   "Gathering Possibilities:\n"
741                                   "> stout branches\n"
742                                   "> withered plants\n"
743                                   "> splintered wood\n\n"
744                                   "Risk: Very Low\n"
745                                   "Time: 3 Hours, Repeated\n"
746                                   "Positions: %d/3\n" ), npc_list.size() );
747         mission_key.add_start( miss_info.miss_id, miss_info.desc.translated(), cata::nullopt,
748                                entry, npc_list.size() < 3 );
749         if( !npc_list.empty() ) {
750             entry = miss_info.action.translated();
751             bool avail = update_time_fixed( entry, npc_list, 3_hours );
752             mission_key.add_return( miss_info.ret_miss_id, miss_info.ret_desc.translated(),
753                                     cata::nullopt, entry, avail );
754         }
755     }
756     if( has_provides( "sorting", dir ) ) {
757         comp_list npc_list = get_mission_workers( "_faction_camp_menial" );
758         const base_camps::miss_data &miss_info = base_camps::miss_info[ "_faction_camp_menial" ];
759         entry = string_format( _( "Notes:\n"
760                                   "Send a companion to do low level chores and sort "
761                                   "supplies.\n\n"
762                                   "Skill used: fabrication\n"
763                                   "Difficulty: N/A\n"
764                                   "Effects:\n"
765                                   "> Material left in the unsorted loot zone will be sorted "
766                                   "into a defined loot zone."
767                                   "\n\nRisk: None\n"
768                                   "Time: 3 Hours\n"
769                                   "Positions: %d/1\n" ), npc_list.size() );
770         mission_key.add_start( miss_info.miss_id, miss_info.desc.translated(), cata::nullopt,
771                                entry, npc_list.empty() );
772         if( !npc_list.empty() ) {
773             entry = miss_info.action.translated();
774             bool avail = update_time_left( entry, npc_list );
775             mission_key.add_return( miss_info.ret_miss_id, miss_info.ret_desc.translated(),
776                                     cata::nullopt, entry, avail );
777         }
778     }
779 
780     if( has_provides( "logging", dir ) ) {
781         comp_list npc_list = get_mission_workers( "_faction_camp_cut_log" );
782         const base_camps::miss_data &miss_info = base_camps::miss_info[ "_faction_camp_cut_log" ];
783         entry = string_format( _( "Notes:\n"
784                                   "Send a companion to a nearby forest to cut logs.\n\n"
785                                   "Skill used: fabrication\n"
786                                   "Difficulty: 1\n"
787                                   "Effects:\n"
788                                   "> 50%% of trees/trunks at the forest position will be "
789                                   "cut down.\n"
790                                   "> 100%% of total material will be brought back.\n"
791                                   "> Repeatable with diminishing returns.\n"
792                                   "> Will eventually turn forests into fields.\n"
793                                   "Risk: None\n"
794                                   "Time: 6 Hour Base + Travel Time + Cutting Time\n"
795                                   "Positions: %d/1\n" ), npc_list.size() );
796         mission_key.add_start( miss_info.miss_id, miss_info.desc.translated(), cata::nullopt,
797                                entry, npc_list.empty() );
798         if( !npc_list.empty() ) {
799             entry = miss_info.action.translated();
800             bool avail = update_time_left( entry, npc_list );
801             mission_key.add_return( miss_info.ret_miss_id, miss_info.ret_desc.translated(),
802                                     cata::nullopt, entry, avail );
803         }
804     }
805 
806     if( has_provides( "logging", dir ) ) {
807         comp_list npc_list = get_mission_workers( "_faction_camp_clearcut" );
808         const base_camps::miss_data &miss_info = base_camps::miss_info[ "_faction_camp_clearcut" ];
809         entry = string_format( _( "Notes:\n"
810                                   "Send a companion to a clear a nearby forest.\n\n"
811                                   "Skill used: fabrication\n"
812                                   "Difficulty: 1\n"
813                                   "Effects:\n"
814                                   "> 95%% of trees/trunks at the forest position"
815                                   " will be cut down.\n"
816                                   "> 0%% of total material will be brought back.\n"
817                                   "> Forest should become a field tile.\n"
818                                   "> Useful for clearing land for another faction camp.\n\n"
819                                   "Risk: None\n"
820                                   "Time: 6 Hour Base + Travel Time + Cutting Time\n"
821                                   "Positions: %d/1\n" ), npc_list.size() );
822         mission_key.add_start( miss_info.miss_id, miss_info.desc.translated(), cata::nullopt,
823                                entry, npc_list.empty() );
824         if( !npc_list.empty() ) {
825             entry = miss_info.action.translated();
826             bool avail = update_time_left( entry, npc_list );
827             mission_key.add_return( miss_info.ret_miss_id, miss_info.ret_desc.translated(),
828                                     cata::nullopt, entry, avail );
829         }
830     }
831 
832     if( has_provides( "relaying", dir ) ) {
833         comp_list npc_list = get_mission_workers( "_faction_camp_hide_site" );
834         const base_camps::miss_data &miss_info = base_camps::miss_info[ "_faction_camp_hide_site" ];
835         entry = string_format( _( "Notes:\n"
836                                   "Send a companion to build an improvised shelter and stock it "
837                                   "with equipment at a distant map location.\n\n"
838                                   "Skill used: survival\n"
839                                   "Difficulty: 3\n"
840                                   "Effects:\n"
841                                   "> Good for setting up resupply or contingency points.\n"
842                                   "> Gear is left unattended and could be stolen.\n"
843                                   "> Time dependent on weight of equipment being sent forward.\n\n"
844                                   "Risk: Medium\n"
845                                   "Time: 6 Hour Construction + Travel\n"
846                                   "Positions: %d/1\n" ), npc_list.size() );
847         mission_key.add_start( miss_info.miss_id, miss_info.desc.translated(), cata::nullopt,
848                                entry, npc_list.empty() );
849         if( !npc_list.empty() ) {
850             entry = miss_info.action.translated();
851             bool avail = update_time_left( entry, npc_list );
852             mission_key.add_return( miss_info.ret_miss_id, miss_info.ret_desc.translated(),
853                                     cata::nullopt, entry, avail );
854         }
855     }
856 
857     if( has_provides( "relaying", dir ) ) {
858         comp_list npc_list = get_mission_workers( "_faction_camp_hide_trans" );
859         const base_camps::miss_data &miss_info = base_camps::miss_info[ "_faction_camp_hide_trans" ];
860         entry = string_format( _( "Notes:\n"
861                                   "Push gear out to a hide site or bring gear back from one.\n\n"
862                                   "Skill used: survival\n"
863                                   "Difficulty: 1\n"
864                                   "Effects:\n"
865                                   "> Good for returning equipment you left in the hide site "
866                                   "shelter.\n"
867                                   "> Gear is left unattended and could be stolen.\n"
868                                   "> Time dependent on weight of equipment being sent forward or "
869                                   "back.\n\n"
870                                   "Risk: Medium\n"
871                                   "Time: 1 Hour Base + Travel\n"
872                                   "Positions: %d/1\n" ), npc_list.size() );
873         mission_key.add_start( miss_info.miss_id, miss_info.desc.translated(), cata::nullopt, entry,
874                                npc_list.empty() );
875         if( !npc_list.empty() ) {
876             entry = miss_info.action.translated();
877             bool avail = update_time_left( entry, npc_list );
878             mission_key.add_return( miss_info.ret_miss_id, miss_info.ret_desc.translated(),
879                                     cata::nullopt, entry, avail );
880         }
881     }
882 
883     if( has_provides( "foraging", dir ) ) {
884         comp_list npc_list = get_mission_workers( "_faction_camp_foraging" );
885         const base_camps::miss_data &miss_info = base_camps::miss_info[ "_faction_camp_foraging" ];
886         entry = string_format( _( "Notes:\n"
887                                   "Send a companion to forage for edible plants.\n\n"
888                                   "Skill used: survival\n"
889                                   "Difficulty: N/A\n"
890                                   "Foraging Possibilities:\n"
891                                   "> wild vegetables\n"
892                                   "> fruits and nuts depending on season\n"
893                                   "May produce less food than consumed!\n"
894                                   "Risk: Very Low\n"
895                                   "Time: 4 Hours, Repeated\n"
896                                   "Positions: %d/3\n" ), npc_list.size() );
897         mission_key.add_start( miss_info.miss_id, miss_info.desc.translated(), dir,
898                                entry, npc_list.size() < 3 );
899         if( !npc_list.empty() ) {
900             entry = miss_info.action.translated();
901             bool avail = update_time_fixed( entry, npc_list, 4_hours );
902             mission_key.add_return( miss_info.ret_miss_id, miss_info.ret_desc.translated(),
903                                     dir, entry, avail );
904         }
905     }
906 
907     if( has_provides( "trapping", dir ) ) {
908         comp_list npc_list = get_mission_workers( "_faction_camp_trapping" );
909         const base_camps::miss_data &miss_info = base_camps::miss_info[ "_faction_camp_trapping" ];
910         entry = string_format( _( "Notes:\n"
911                                   "Send a companion to set traps for small game.\n\n"
912                                   "Skill used: trapping\n"
913                                   "Difficulty: N/A\n"
914                                   "Trapping Possibilities:\n"
915                                   "> small and tiny animal corpses\n"
916                                   "May produce less food than consumed!\n"
917                                   "Risk: Low\n"
918                                   "Time: 6 Hours, Repeated\n"
919                                   "Positions: %d/2\n" ), npc_list.size() );
920         mission_key.add_start( miss_info.miss_id, miss_info.desc.translated(), dir,
921                                entry, npc_list.size() < 2 );
922         if( !npc_list.empty() ) {
923             entry = miss_info.action.translated();
924             bool avail = update_time_fixed( entry, npc_list, 6_hours );
925             mission_key.add_return( miss_info.ret_miss_id, miss_info.ret_desc.translated(),
926                                     dir, entry, avail );
927         }
928     }
929 
930     if( has_provides( "hunting", dir ) ) {
931         comp_list npc_list = get_mission_workers( "_faction_camp_hunting" );
932         const base_camps::miss_data &miss_info = base_camps::miss_info[ "_faction_camp_hunting" ];
933         entry = string_format( _( "Notes:\n"
934                                   "Send a companion to hunt large animals.\n\n"
935                                   "Skill used: marksmanship\n"
936                                   "Difficulty: N/A\n"
937                                   "Hunting Possibilities:\n"
938                                   "> small, medium, or large animal corpses\n"
939                                   "May produce less food than consumed!\n"
940                                   "Risk: Medium\n"
941                                   "Time: 6 Hours, Repeated\n"
942                                   "Positions: %d/1\n" ), npc_list.size() );
943         mission_key.add_start( miss_info.miss_id, miss_info.desc.translated(), dir,
944                                entry, npc_list.empty() );
945         if( !npc_list.empty() ) {
946             entry = miss_info.action.translated();
947             bool avail = update_time_fixed( entry, npc_list, 6_hours );
948             mission_key.add_return( miss_info.ret_miss_id, miss_info.ret_desc.translated(),
949                                     dir, entry, avail );
950         }
951     }
952 
953     if( has_provides( "walls", dir ) ) {
954         comp_list npc_list = get_mission_workers( "_faction_camp_om_fortifications" );
955         const base_camps::miss_data &miss_info =
956             base_camps::miss_info[ "_faction_camp_om_fortifications" ];
957         entry = om_upgrade_description( "faction_wall_level_N_0" );
958         mission_key.add_start( "Construct Map Fort", _( "Construct Map Fortifications" ),
959                                dir, entry, npc_list.empty() );
960         entry = om_upgrade_description( "faction_wall_level_N_1" );
961         mission_key.add_start( "Construct Trench", _( "Construct Spiked Trench" ), dir, entry,
962                                npc_list.empty() );
963         if( !npc_list.empty() ) {
964             entry = miss_info.action.translated();
965             bool avail = update_time_left( entry, npc_list );
966             mission_key.add_return( miss_info.ret_miss_id, miss_info.ret_desc.translated(),
967                                     cata::nullopt, entry, avail );
968         }
969     }
970 
971     if( has_provides( "recruiting", dir ) ) {
972         comp_list npc_list = get_mission_workers( "_faction_camp_recruit_0" );
973         const base_camps::miss_data &miss_info = base_camps::miss_info[ "_faction_camp_recruit_0" ];
974         entry = recruit_description( npc_list.size() );
975         mission_key.add_start( miss_info.miss_id, miss_info.desc.translated(), dir, entry,
976                                npc_list.empty() );
977         if( !npc_list.empty() ) {
978             entry = miss_info.action.translated();
979             bool avail = update_time_left( entry, npc_list );
980             mission_key.add_return( miss_info.ret_miss_id, miss_info.ret_desc.translated(),
981                                     cata::nullopt, entry, avail );
982         }
983     }
984 
985     if( has_provides( "scouting", dir ) ) {
986         comp_list npc_list = get_mission_workers( "_faction_camp_scout_0" );
987         const base_camps::miss_data &miss_info = base_camps::miss_info[ "_faction_camp_scout_0" ];
988         entry = string_format( _( "Notes:\n"
989                                   "Send a companion out into the great unknown.  High survival "
990                                   "skills are needed to avoid combat but you should expect an "
991                                   "encounter or two.\n\n"
992                                   "Skill used: survival\n"
993                                   "Difficulty: 3\n"
994                                   "Effects:\n"
995                                   "> Select checkpoints to customize path.\n"
996                                   "> Reveals terrain around the path.\n"
997                                   "> Can bounce off hide sites to extend range.\n\n"
998                                   "Risk: High\n"
999                                   "Time: Travel\n"
1000                                   "Positions: %d/3\n" ), npc_list.size() );
1001         mission_key.add_start( miss_info.miss_id, miss_info.desc.translated(), dir,
1002                                entry, npc_list.size() < 3 );
1003         if( !npc_list.empty() ) {
1004             entry = miss_info.action.translated();
1005             bool avail = update_time_left( entry, npc_list );
1006             mission_key.add_return( miss_info.ret_miss_id,  miss_info.ret_desc.translated(),
1007                                     cata::nullopt, entry, avail );
1008         }
1009     }
1010 
1011     if( has_provides( "patrolling", dir ) ) {
1012         comp_list npc_list = get_mission_workers( "_faction_camp_combat_0" );
1013         const base_camps::miss_data &miss_info = base_camps::miss_info[ "_faction_camp_combat_0" ];
1014         entry = string_format( _( "Notes:\n"
1015                                   "Send a companion to purge the wasteland.  Their goal is to "
1016                                   "kill anything hostile they encounter and return when "
1017                                   "their wounds are too great or the odds are stacked against "
1018                                   "them.\n\n"
1019                                   "Skill used: survival\n"
1020                                   "Difficulty: 4\n"
1021                                   "Effects:\n"
1022                                   "> Pulls creatures encountered into combat instead of "
1023                                   "fleeing.\n"
1024                                   "> Select checkpoints to customize path.\n"
1025                                   "> Can bounce off hide sites to extend range.\n\n"
1026                                   "Risk: Very High\n"
1027                                   "Time: Travel\n"
1028                                   "Positions: %d/3\n" ), npc_list.size() );
1029         mission_key.add_start( miss_info.miss_id, miss_info.desc.translated(), cata::nullopt,
1030                                entry, npc_list.size() < 3 );
1031         if( !npc_list.empty() ) {
1032             entry = miss_info.action.translated();
1033             bool avail = update_time_left( entry, npc_list );
1034             mission_key.add_return( miss_info.ret_miss_id, miss_info.ret_desc.translated(),
1035                                     cata::nullopt, entry, avail );
1036         }
1037     }
1038 
1039     if( has_provides( "dismantling", dir ) ) {
1040         comp_list npc_list = get_mission_workers( "_faction_exp_chop_shop_" + dir_id );
1041         const base_camps::miss_data &miss_info = base_camps::miss_info[ "_faction_exp_chop_shop_" ];
1042         entry = _( "Notes:\n"
1043                    "Have a companion attempt to completely dissemble a vehicle into "
1044                    "components.\n\n"
1045                    "Skill used: mechanics\n"
1046                    "Difficulty: 2\n"
1047                    "Effects:\n"
1048                    "> Removed parts placed on the furniture in the garage.\n"
1049                    "> Skill plays a huge role to determine what is salvaged.\n\n"
1050                    "Risk: None\n"
1051                    "Time: 5 days\n" );
1052         mission_key.add_start( dir_id + miss_info.miss_id, dir_abbr + miss_info.desc, dir, entry,
1053                                npc_list.empty() );
1054         if( !npc_list.empty() ) {
1055             entry = miss_info.action.translated();
1056             bool avail = update_time_left( entry, npc_list );
1057             mission_key.add_return( dir_id + miss_info.ret_miss_id,
1058                                     dir_abbr + miss_info.ret_desc, dir, entry, avail );
1059         }
1060     }
1061 
1062     std::map<recipe_id, translation> craft_recipes = recipe_deck( dir );
1063     comp_list npc_list = get_mission_workers( "_faction_camp_crafting_" + dir_id );
1064     const base_camps::miss_data &miss_info =
1065         base_camps::miss_info["_faction_camp_crafting_"];
1066     if( !npc_list.empty() ) {
1067         entry = miss_info.action.translated();
1068         bool avail = update_time_left( entry, npc_list );
1069         mission_key.add_return( dir_id + miss_info.ret_miss_id,
1070                                 dir_abbr + miss_info.ret_desc, dir, entry, avail );
1071     }
1072 
1073     if( has_provides( "kitchen", dir ) ) {
1074         comp_list npc_list = get_mission_workers( "_faction_exp_kitchen_cooking_" + dir_id );
1075         const base_camps::miss_data &miss_info =
1076             base_camps::miss_info[ "_faction_exp_kitchen_cooking_" ];
1077         if( npc_list.empty() ) {
1078             add_available_recipes( mission_key, dir, craft_recipes );
1079         } else {
1080             entry = miss_info.action.translated();
1081             bool avail = update_time_left( entry, npc_list );
1082             mission_key.add_return( dir_id + miss_info.ret_miss_id,
1083                                     dir_abbr + miss_info.ret_desc, dir, entry, avail );
1084         }
1085     }
1086 
1087     if( has_provides( "blacksmith", dir ) ) {
1088         comp_list npc_list = get_mission_workers( "_faction_exp_blacksmith_crafting_" + dir_id );
1089         const base_camps::miss_data &miss_info =
1090             base_camps::miss_info[ "_faction_exp_blacksmith_crafting_" ];
1091         if( npc_list.empty() ) {
1092             add_available_recipes( mission_key, dir, craft_recipes );
1093         } else {
1094             entry = miss_info.action.translated();
1095             bool avail = update_time_left( entry, npc_list );
1096             mission_key.add_return( dir_id + miss_info.ret_miss_id,
1097                                     dir_abbr + miss_info.ret_desc, dir, entry, avail );
1098         }
1099     }
1100 
1101     if( has_provides( "farming", dir ) ) {
1102         size_t plots = 0;
1103         comp_list npc_list = get_mission_workers( "_faction_exp_plow_" + dir_id );
1104         const base_camps::miss_data &miss_info = base_camps::miss_info[ "_faction_exp_plow_" ];
1105         if( npc_list.empty() ) {
1106             entry = _( "Notes:\n"
1107                        "Plow any spaces that have reverted to dirt or grass.\n\n" ) +
1108                     farm_description( omt_trg, plots, farm_ops::plow ) +
1109                     _( "\n\n"
1110                        "Skill used: fabrication\n"
1111                        "Difficulty: N/A\n"
1112                        "Effects:\n"
1113                        "> Restores only the plots created in the last expansion upgrade.\n"
1114                        "> Does not damage existing crops.\n\n"
1115                        "Risk: None\n"
1116                        "Time: 5 Min / Plot\n"
1117                        "Positions: 0/1\n" );
1118             mission_key.add_start( dir_id + miss_info.miss_id, dir_abbr + miss_info.desc, dir,
1119                                    entry, plots > 0 );
1120         } else {
1121             entry = miss_info.action.translated();
1122             bool avail = update_time_left( entry, npc_list );
1123             mission_key.add_return( dir_id + miss_info.ret_miss_id,
1124                                     dir_abbr + miss_info.ret_desc, dir, entry, avail );
1125         }
1126     }
1127     if( has_provides( "farming", dir ) ) {
1128         size_t plots = 0;
1129         comp_list npc_list = get_mission_workers( "_faction_exp_plant_" + dir_id );
1130         const base_camps::miss_data &miss_info = base_camps::miss_info[ "_faction_exp_plant_" ];
1131         if( npc_list.empty() ) {
1132             entry = _( "Notes:\n"
1133                        "Plant designated seeds in the spaces that have already been "
1134                        "tilled.\n\n" ) +
1135                     farm_description( omt_trg, plots, farm_ops::plant ) +
1136                     _( "\n\n"
1137                        "Skill used: survival\n"
1138                        "Difficulty: N/A\n"
1139                        "Effects:\n"
1140                        "> Choose which seed type or all of your seeds.\n"
1141                        "> Stops when out of seeds or planting locations.\n"
1142                        "> Will plant in ALL dirt mounds in the expansion.\n\n"
1143                        "Risk: None\n"
1144                        "Time: 1 Min / Plot\n"
1145                        "Positions: 0/1\n" );
1146             mission_key.add_start( dir_id + miss_info.miss_id,
1147                                    dir_abbr + miss_info.desc, dir, entry,
1148                                    plots > 0 && warm_enough_to_plant( omt_trg ) );
1149         } else {
1150             entry = miss_info.action.translated();
1151             bool avail = update_time_left( entry, npc_list );
1152             mission_key.add_return( dir_id + miss_info.ret_miss_id,
1153                                     dir_abbr + miss_info.ret_desc, dir, entry, avail );
1154         }
1155     }
1156     if( has_provides( "farming", dir ) ) {
1157         size_t plots = 0;
1158         comp_list npc_list = get_mission_workers( "_faction_exp_harvest_" + dir_id );
1159         const base_camps::miss_data &miss_info = base_camps::miss_info[ "_faction_exp_harvest_" ];
1160         if( npc_list.empty() ) {
1161             entry = _( "Notes:\n"
1162                        "Harvest any plants that are ripe and bring the produce back.\n\n" ) +
1163                     farm_description( omt_trg, plots, farm_ops::harvest ) +
1164                     _( "\n\n"
1165                        "Skill used: survival\n"
1166                        "Difficulty: N/A\n"
1167                        "Effects:\n"
1168                        "> Will dump all harvesting products onto your location.\n\n"
1169                        "Risk: None\n"
1170                        "Time: 3 Min / Plot\n"
1171                        "Positions: 0/1\n" );
1172             mission_key.add_start( dir_id + miss_info.miss_id,
1173                                    dir_abbr + miss_info.desc, dir, entry,
1174                                    plots > 0 );
1175         } else {
1176             entry = miss_info.action.translated();
1177             bool avail = update_time_left( entry, npc_list );
1178             mission_key.add_return( dir_id + miss_info.ret_miss_id,
1179                                     dir_abbr + miss_info.ret_desc, dir, entry, avail );
1180         }
1181     }
1182 
1183     if( has_provides( "reseeding", dir ) ) {
1184         comp_list npc_list = get_mission_workers( "_faction_exp_farm_crafting_" + dir_id );
1185         const base_camps::miss_data &miss_info =
1186             base_camps::miss_info[ "_faction_exp_farm_crafting_" ];
1187         if( npc_list.empty() ) {
1188             add_available_recipes( mission_key, dir, craft_recipes );
1189         } else {
1190             entry = miss_info.action.translated();
1191             bool avail = update_time_left( entry, npc_list );
1192             mission_key.add_return( dir_id + miss_info.ret_miss_id,
1193                                     dir_abbr + miss_info.ret_desc, dir, entry, avail );
1194         }
1195     }
1196 }
1197 
get_available_missions(mission_data & mission_key)1198 void basecamp::get_available_missions( mission_data &mission_key )
1199 {
1200     std::string entry;
1201 
1202     const point &base_dir = base_camps::base_dir;
1203     const base_camps::direction_data &base_data = base_camps::all_directions.at( base_dir );
1204     const std::string base_dir_id = base_data.id;
1205     reset_camp_resources();
1206 
1207     // Handling for the central tile
1208     // return legacy workers
1209     comp_list legacy_npc_list = get_mission_workers( "_faction_upgrade_camp" );
1210     if( !legacy_npc_list.empty() ) {
1211         const base_camps::miss_data &miss_info = base_camps::miss_info[ "_faction_upgrade_camp" ];
1212         entry = miss_info.action.translated();
1213         bool avail = update_time_left( entry, legacy_npc_list );
1214         mission_key.add_return( miss_info.ret_miss_id, miss_info.ret_desc.translated(),
1215                                 base_camps::base_dir, entry, avail );
1216     }
1217     std::vector<basecamp_upgrade> upgrades = available_upgrades( base_camps::base_dir );
1218 
1219     std::sort( upgrades.begin(), upgrades.end(), []( const basecamp_upgrade & p,
1220                const basecamp_upgrade & q )->bool {return p.name.translated_lt( q.name ); } );
1221 
1222     for( const basecamp_upgrade &upgrade : upgrades ) {
1223         const base_camps::miss_data &miss_info = base_camps::miss_info["_faction_upgrade_camp"];
1224         comp_list npc_list = get_mission_workers( upgrade.bldg + "_faction_upgrade_camp" );
1225         if( npc_list.empty() && !upgrade.in_progress ) {
1226             entry = om_upgrade_description( upgrade.bldg );
1227             mission_key.add_start( miss_info.miss_id + upgrade.bldg,
1228                                    miss_info.desc + " " + upgrade.name, base_camps::base_dir,
1229                                    entry, upgrade.avail );
1230         } else if( !npc_list.empty() && upgrade.in_progress ) {
1231             entry = miss_info.action.translated();
1232             bool avail = update_time_left( entry, npc_list );
1233             mission_key.add_return( miss_info.ret_miss_id + upgrade.bldg,
1234                                     miss_info.ret_desc + " " + upgrade.name, base_camps::base_dir,
1235                                     entry, avail );
1236         }
1237     }
1238 
1239     // Missions that belong exclusively to the central tile
1240     comp_list npc_list = get_mission_workers( "_faction_camp_crafting_" + base_dir_id );
1241     const base_camps::miss_data &miss_info = base_camps::miss_info[ "_faction_camp_crafting_" ];
1242     //This handles all crafting by the base, regardless of level
1243     if( npc_list.empty() ) {
1244         std::map<recipe_id, translation> craft_recipes = recipe_deck( base_camps::base_dir );
1245         add_available_recipes( mission_key, base_camps::base_dir, craft_recipes );
1246     } else {
1247         const std::string base_dir_abbr = base_data.bracket_abbr.translated();
1248         entry = miss_info.action.translated();
1249         bool avail = update_time_left( entry, npc_list );
1250         mission_key.add_return( base_dir_id + miss_info.ret_miss_id,
1251                                 base_dir_abbr + miss_info.ret_desc, base_camps::base_dir, entry,
1252                                 avail );
1253     }
1254     if( can_expand() ) {
1255         comp_list npc_list = get_mission_workers( "_faction_camp_expansion" );
1256         const base_camps::miss_data &miss_info = base_camps::miss_info[ "_faction_camp_expansion" ];
1257         entry = string_format( _( "Notes:\n"
1258                                   "Your base has become large enough to support an expansion.  "
1259                                   "Expansions open up new opportunities but can be expensive and "
1260                                   "time consuming.  Pick them carefully, only 8 can be built at "
1261                                   "each camp.\n\n"
1262                                   "Skill used: N/A\n"
1263                                   "Effects:\n"
1264                                   "> Choose any one of the available expansions.  Starting with "
1265                                   "a farm is always a solid choice since food is used to support "
1266                                   "companion missions and little is needed to get it going.  "
1267                                   "With minimal investment, a mechanic can be useful as a "
1268                                   "chop-shop to rapidly dismantle large vehicles, and a forge "
1269                                   "provides the resources to make charcoal.\n\n"
1270                                   "NOTE: Actions available through expansions are located in "
1271                                   "separate tabs of the Camp Manager window.\n\n"
1272                                   "Risk: None\n"
1273                                   "Time: 3 Hours\n"
1274                                   "Positions: %d/1\n" ), npc_list.size() );
1275         mission_key.add_start( miss_info.miss_id, miss_info.desc.translated(),
1276                                base_camps::base_dir, entry, npc_list.empty() );
1277         if( !npc_list.empty() ) {
1278             entry = miss_info.action.translated();
1279             bool avail = update_time_left( entry, npc_list );
1280             mission_key.add_return( miss_info.ret_miss_id, miss_info.ret_desc.translated(),
1281                                     base_camps::base_dir, entry, avail );
1282         }
1283     }
1284 
1285     if( !by_radio ) {
1286         entry = string_format( _( "Notes:\n"
1287                                   "Distribute food to your follower and fill you larders.  "
1288                                   "Place the food you wish to distribute in the camp food zone.  "
1289                                   "You must have a camp food zone, and a camp storage zone, "
1290                                   "or you will be prompted to create them using the zone manager.\n"
1291                                   "Effects:\n"
1292                                   "> Increases your faction's food supply value which in "
1293                                   "turn is used to pay laborers for their time\n\n"
1294                                   "Must have enjoyability >= -6\n"
1295                                   "Perishable food liquidated at penalty depending on "
1296                                   "upgrades and rot time:\n"
1297                                   "> Rotten: 0%%\n"
1298                                   "> Rots in < 2 days: 60%%\n"
1299                                   "> Rots in < 5 days: 80%%\n\n"
1300                                   "Total faction food stock: %d kcal\nor %d day's rations" ),
1301                                camp_food_supply(), camp_food_supply( 0, true ) );
1302         mission_key.add( "Distribute Food", _( "Distribute Food" ), entry );
1303         validate_assignees();
1304         entry = string_format( _( "Notes:\n"
1305                                   "Assign repeating job duties to NPCs stationed here.\n"
1306                                   "Difficulty: N/A\n"
1307                                   "Effects:\n"
1308                                   "\n\nRisk: None\n"
1309                                   "Time: Ongoing" ) );
1310         mission_key.add( "Assign Jobs", _( "Assign Jobs" ), entry );
1311         entry = string_format( _( "Notes:\n"
1312                                   "Assign followers to work at this camp." ) );
1313         mission_key.add( "Assign Workers", _( "Assign Workers" ), entry );
1314         entry = _( "Notes:\nAbandon this camp" );
1315         mission_key.add( "Abandon Camp", _( "Abandon Camp" ), entry );
1316     }
1317     // Missions assigned to the central tile that could be done by an expansion
1318     get_available_missions_by_dir( mission_key, base_camps::base_dir );
1319 
1320     // Loop over expansions
1321     for( const point &dir : directions ) {
1322         get_available_missions_by_dir( mission_key, dir );
1323     }
1324 
1325     if( !camp_workers.empty() ) {
1326         const base_camps::miss_data &miss_info = base_camps::miss_info[ "_faction_camp_recall" ];
1327         entry = string_format( _( "Notes:\n"
1328                                   "Cancel a current mission and force the immediate return of a "
1329                                   "companion.  No work will be done on the mission and all "
1330                                   "resources used on the mission will be lost.\n\n"
1331                                   "WARNING: All resources used on the mission will be lost and "
1332                                   "no work will be done.  Only use this mission to recover a "
1333                                   "companion who cannot otherwise be recovered.\n\n"
1334                                   "Companions must be on missions for at least 24 hours before "
1335                                   "emergency recall becomes available." ) );
1336         bool avail = update_time_fixed( entry, camp_workers, 24_hours );
1337         mission_key.add_return( miss_info.ret_miss_id, miss_info.ret_desc.translated(),
1338                                 base_camps::base_dir, entry, avail );
1339     }
1340 }
1341 
handle_mission(const std::string & miss_id,const cata::optional<point> & opt_miss_dir)1342 bool basecamp::handle_mission( const std::string &miss_id,
1343                                const cata::optional<point> &opt_miss_dir )
1344 {
1345     const point &miss_dir = opt_miss_dir ? *opt_miss_dir : base_camps::base_dir;
1346 
1347     if( miss_id == "Distribute Food" ) {
1348         distribute_food();
1349     }
1350 
1351     if( miss_id.size() > 12 && miss_id.substr( 0, 12 ) == "Upgrade Camp" ) {
1352         const std::string bldg = miss_id.substr( 12 );
1353         start_upgrade( bldg, base_camps::base_dir, bldg + "_faction_upgrade_camp" );
1354     } else if( miss_id == "Recover Ally from Upgrading" ) {
1355         upgrade_return( base_camps::base_dir, "_faction_upgrade_camp" );
1356     } else if( miss_id.size() > 27 && miss_id.substr( 0, 27 ) == "Recover Ally from Upgrading" ) {
1357         const std::string bldg = miss_id.substr( 27 );
1358         upgrade_return( base_camps::base_dir, bldg + "_faction_upgrade_camp", bldg );
1359     }
1360 
1361     if( miss_id == "Menial Labor" ) {
1362         start_menial_labor();
1363     } else if( miss_id == "Recover Menial Laborer" ) {
1364         menial_return();
1365     }
1366 
1367     if( miss_id == "Assign Jobs" ) {
1368         job_assignment_ui();
1369     }
1370     if( miss_id == "Assign Workers" ) {
1371         worker_assignment_ui();
1372     }
1373     if( miss_id == "Abandon Camp" ) {
1374         abandon_camp();
1375     }
1376 
1377     if( miss_id == "Expand Base" ) {
1378         start_mission( "_faction_camp_expansion", 3_hours, true,
1379                        _( "departs to survey land…" ), false, {}, skill_gun, 0 );
1380     } else if( miss_id == "Recover Surveyor" ) {
1381         survey_return();
1382     }
1383 
1384     if( miss_id == "Gather Materials" ) {
1385         start_mission( "_faction_camp_gathering", 3_hours, true,
1386                        _( "departs to search for materials…" ), false, {}, skill_survival, 0 );
1387     } else if( miss_id == "Recover Ally from Gathering" ) {
1388         gathering_return( "_faction_camp_gathering", 3_hours );
1389     }
1390 
1391     if( miss_id == "Collect Firewood" ) {
1392         start_mission( "_faction_camp_firewood", 3_hours, true,
1393                        _( "departs to search for firewood…" ), false, {}, skill_survival, 0 );
1394     } else if( miss_id == "Recover Firewood Gatherers" ) {
1395         gathering_return( "_faction_camp_firewood", 3_hours );
1396     }
1397 
1398     if( miss_id == "Cut Logs" ) {
1399         start_cut_logs();
1400     } else if( miss_id == "Recover Log Cutter" ) {
1401         const std::string msg = _( "returns from working in the woods…" );
1402         mission_return( "_faction_camp_cut_log", 6_hours, true, msg, "construction", 2 );
1403     }
1404 
1405     if( miss_id == "Clearcut" ) {
1406         start_clearcut();
1407     } else if( miss_id == "Recover Clearcutter" ) {
1408         const std::string msg = _( "returns from working in the woods…" );
1409         mission_return( "_faction_camp_clearcut", 6_hours, true, msg, "construction", 1 );
1410     }
1411 
1412     if( miss_id == "Setup Hide Site" ) {
1413         start_setup_hide_site();
1414     } else if( miss_id == "Recover Hide Setup" ) {
1415         const std::string msg = _( "returns from working on the hide site…" );
1416         mission_return( "_faction_camp_hide_site", 3_hours, true, msg, "survival", 3 );
1417     }
1418 
1419     if( miss_id == "Relay Hide Site" ) {
1420         start_relay_hide_site();
1421     } else if( miss_id == "Recover Hide Transport" ) {
1422         const std::string msg = _( "returns from shuttling gear between the hide site…" );
1423         mission_return( "_faction_camp_hide_trans", 3_hours, true, msg, "survival", 3 );
1424     }
1425 
1426     if( miss_id == "Camp Forage" ) {
1427         start_mission( "_faction_camp_foraging", 4_hours, true,
1428                        _( "departs to search for edible plants…" ), false, {}, skill_survival, 0 );
1429     } else if( miss_id == "Recover Foragers" ) {
1430         gathering_return( "_faction_camp_foraging", 4_hours );
1431     }
1432     if( miss_id == "Trap Small Game" ) {
1433         start_mission( "_faction_camp_trapping", 6_hours, true,
1434                        _( "departs to set traps for small animals…" ), false, {}, skill_traps, 0 );
1435     } else if( miss_id == "Recover Trappers" ) {
1436         gathering_return( "_faction_camp_trapping", 6_hours );
1437     }
1438 
1439     if( miss_id == "Hunt Large Animals" ) {
1440         start_mission( "_faction_camp_hunting", 6_hours, true,
1441                        _( "departs to hunt for meat…" ), false, {}, skill_gun, 0 );
1442     } else if( miss_id == "Recover Hunter" ) {
1443         gathering_return( "_faction_camp_hunting", 6_hours );
1444     }
1445 
1446     if( miss_id == "Construct Map Fort" || miss_id == "Construct Trench" ) {
1447         std::string bldg_exp = "faction_wall_level_N_0";
1448         if( miss_id == "Construct Trench" ) {
1449             bldg_exp = "faction_wall_level_N_1";
1450         }
1451         start_fortifications( bldg_exp );
1452     } else if( miss_id == "Finish Map Fort" ) {
1453         fortifications_return();
1454     }
1455 
1456     if( miss_id == "Recruit Companions" ) {
1457         start_mission( "_faction_camp_recruit_0", 4_days, true,
1458                        _( "departs to search for recruits…" ), false, {}, skill_gun, 0 );
1459     } else if( miss_id == "Recover Recruiter" ) {
1460         recruit_return( "_faction_camp_recruit_0", recruit_evaluation() );
1461     }
1462 
1463     if( miss_id == "Scout Mission" ) {
1464         start_combat_mission( "_faction_camp_scout_0" );
1465     } else if( miss_id == "Combat Patrol" ) {
1466         start_combat_mission( "_faction_camp_combat_0" );
1467     } else if( miss_id == "Recover Scout" ) {
1468         combat_mission_return( "_faction_camp_scout_0" );
1469     } else if( miss_id == "Recover Combat Patrol" ) {
1470         combat_mission_return( "_faction_camp_combat_0" );
1471     }
1472 
1473     const std::string base_dir_id = base_camps::all_directions.at( base_camps::base_dir ).id;
1474     const std::string miss_dir_id = base_camps::all_directions.at( miss_dir ).id;
1475     const tripoint_abs_omt omt_trg = omt_pos + miss_dir;
1476 
1477     if( miss_id.substr( 0, 18 + miss_dir_id.size() ) == miss_dir_id + " Expansion Upgrade" ) {
1478         const std::string bldg = miss_id.substr( 18 + miss_dir_id.size() );
1479         start_upgrade( bldg, miss_dir, bldg + "_faction_upgrade_exp_" + miss_dir_id );
1480     } else if( miss_id == "Recover Ally, " + miss_dir_id + " Expansion" ) {
1481         upgrade_return( miss_dir, "_faction_upgrade_exp_" + miss_dir_id );
1482     } else {
1483         const std::string search_str = "Recover Ally, " + miss_dir_id + " Expansion";
1484         size_t search_len = search_str.size();
1485         if( miss_id.size() > search_len && miss_id.substr( 0, search_len ) == search_str ) {
1486             const std::string bldg = miss_id.substr( search_len );
1487             upgrade_return( miss_dir, bldg + "_faction_upgrade_exp_" + miss_dir_id, bldg );
1488         }
1489     }
1490 
1491     start_crafting( miss_id, miss_dir, "BASE", "_faction_camp_crafting_" );
1492     start_crafting( miss_id, miss_dir, "FARM", "_faction_exp_farm_crafting_" );
1493     if( miss_id == base_dir_id + " (Finish) Crafting" ) {
1494         const std::string msg = _( "returns to you with something…" );
1495         mission_return( "_faction_camp_crafting_" + miss_dir_id, 1_minutes, true, msg,
1496                         "construction", 2 );
1497     } else if( miss_id == miss_dir_id + " (Finish) Crafting" ) {
1498         const std::string craft_msg = _( "returns to you with something…" );
1499         mission_return( "_faction_camp_crafting_" + miss_dir_id, 1_minutes, true, craft_msg,
1500                         "construction", 2 );
1501         const std::string msg = _( "returns from your farm with something…" );
1502         mission_return( "_faction_exp_farm_crafting_" + miss_dir_id, 1_minutes, true, msg,
1503                         "construction", 2 );
1504     }
1505 
1506     start_crafting( miss_id, miss_dir, "COOK", "_faction_exp_kitchen_cooking_" );
1507     if( miss_id == miss_dir_id + " (Finish) Cooking" ) {
1508         const std::string msg = _( "returns from your kitchen with something…" );
1509         mission_return( "_faction_exp_kitchen_cooking_" + miss_dir_id, 1_minutes,
1510                         true, msg, "cooking", 2 );
1511     }
1512 
1513     start_crafting( miss_id, miss_dir, "SMITH", "_faction_exp_blacksmith_crafting_" );
1514     if( miss_id == miss_dir_id + " (Finish) Smithing" ) {
1515         const std::string msg = _( "returns from your blacksmith shop with something…" );
1516         mission_return( "_faction_exp_blacksmith_crafting_" + miss_dir_id, 1_minutes,
1517                         true, msg, "fabrication", 2 );
1518     }
1519     if( miss_id == miss_dir_id + " Plow Fields" ) {
1520         start_farm_op( miss_dir, omt_trg, farm_ops::plow );
1521     } else if( miss_id == miss_dir_id + " (Finish) Plow Fields" ) {
1522         farm_return( "_faction_exp_plow_" + miss_dir_id, omt_trg, farm_ops::plow );
1523     }
1524 
1525     if( miss_id == miss_dir_id + " Plant Fields" ) {
1526         start_farm_op( miss_dir, omt_trg, farm_ops::plant );
1527     } else if( miss_id == miss_dir_id + " (Finish) Plant Fields" ) {
1528         farm_return( "_faction_exp_plant_" + miss_dir_id, omt_trg, farm_ops::plant );
1529     }
1530 
1531     if( miss_id == miss_dir_id + " Harvest Fields" ) {
1532         start_farm_op( miss_dir, omt_trg, farm_ops::harvest );
1533     }  else if( miss_id == miss_dir_id + " (Finish) Harvest Fields" ) {
1534         farm_return( "_faction_exp_harvest_" + miss_dir_id, omt_trg, farm_ops::harvest );
1535     }
1536 
1537     if( miss_id == miss_dir_id + " Chop Shop" || miss_id == miss_dir_id + " (Finish) Chop Shop" ) {
1538         debugmsg( "Obsolete Function.  Use disassemble zone instead." );
1539     }
1540 
1541     if( miss_id == "Emergency Recall" ) {
1542         emergency_recall();
1543     }
1544 
1545     return true;
1546 
1547 }
1548 
1549 // camp faction companion mission start functions
start_mission(const std::string & miss_id,time_duration duration,bool must_feed,const std::string & desc,bool,const std::vector<item * > & equipment,const skill_id & skill_tested,int skill_level)1550 npc_ptr basecamp::start_mission( const std::string &miss_id, time_duration duration,
1551                                  bool must_feed, const std::string &desc, bool /*group*/,
1552                                  const std::vector<item *> &equipment,
1553                                  const skill_id &skill_tested, int skill_level )
1554 {
1555     std::map<skill_id, int> required_skills;
1556     required_skills[ skill_tested ] = skill_level;
1557     return start_mission( miss_id, duration, must_feed, desc, false, equipment, required_skills );
1558 }
1559 
start_mission(const std::string & miss_id,time_duration duration,bool must_feed,const std::string & desc,bool,const std::vector<item * > & equipment,const std::map<skill_id,int> & required_skills)1560 npc_ptr basecamp::start_mission( const std::string &miss_id, time_duration duration,
1561                                  bool must_feed, const std::string &desc, bool /*group*/,
1562                                  const std::vector<item *> &equipment,
1563                                  const std::map<skill_id, int> &required_skills )
1564 {
1565     if( must_feed && camp_food_supply() < time_to_food( duration ) ) {
1566         popup( _( "You don't have enough food stored to feed your companion." ) );
1567         return nullptr;
1568     }
1569     npc_ptr comp = talk_function::individual_mission( omt_pos, base_camps::id, desc, miss_id,
1570                    false, equipment, required_skills );
1571     if( comp != nullptr ) {
1572         comp->companion_mission_time_ret = calendar::turn + duration;
1573         if( must_feed ) {
1574             camp_food_supply( duration );
1575         }
1576     }
1577     return comp;
1578 }
1579 
start_upgrade(const std::string & bldg,const point & dir,const std::string & key)1580 void basecamp::start_upgrade( const std::string &bldg, const point &dir,
1581                               const std::string &key )
1582 {
1583     const recipe &making = recipe_id( bldg ).obj();
1584     //Stop upgrade if you don't have materials
1585     if( making.deduped_requirements().can_make_with_inventory(
1586             _inv, making.get_component_filter() ) ) {
1587         bool must_feed = bldg != "faction_base_camp_1";
1588 
1589         basecamp_action_components components( making, 1, *this );
1590         if( !components.choose_components() ) {
1591             return;
1592         }
1593 
1594         time_duration work_days = base_camps::to_workdays( making.batch_duration(
1595                                       get_player_character() ) );
1596         npc_ptr comp = nullptr;
1597         if( making.required_skills.empty() ) {
1598             if( making.skill_used.is_valid() ) {
1599                 comp = start_mission( key, work_days, must_feed,
1600                                       _( "begins to upgrade the camp…" ), false, {},
1601                                       making.skill_used, making.difficulty );
1602             } else {
1603                 comp = start_mission( key, work_days, must_feed,
1604                                       _( "begins to upgrade the camp…" ), false, {} );
1605             }
1606         } else {
1607             comp = start_mission( key, work_days, must_feed, _( "begins to upgrade the camp…" ),
1608                                   false, {}, making.required_skills );
1609         }
1610         if( comp == nullptr ) {
1611             return;
1612         }
1613         components.consume_components();
1614         update_in_progress( bldg, dir );
1615     } else {
1616         popup( _( "You don't have the materials for the upgrade." ) );
1617     }
1618 }
1619 
abandon_camp()1620 void basecamp::abandon_camp()
1621 {
1622     validate_assignees();
1623     for( npc_ptr &guy : overmap_buffer.get_companion_mission_npcs( 10 ) ) {
1624         npc_companion_mission c_mission = guy->get_companion_mission();
1625         if( c_mission.role_id != base_camps::id ) {
1626             continue;
1627         }
1628         const std::string return_msg = _( "responds to the emergency recall…" );
1629         finish_return( *guy, false, return_msg, "menial", 0, true );
1630     }
1631     for( npc_ptr &guy : get_npcs_assigned() ) {
1632         talk_function::stop_guard( *guy );
1633     }
1634     overmap_buffer.remove_camp( *this );
1635     map &here = get_map();
1636     const tripoint sm_pos = omt_to_sm_copy( omt_pos.raw() );
1637     const tripoint ms_pos = sm_to_ms_copy( sm_pos );
1638     // We cannot use bb_pos here, because bb_pos may be {0,0,0} if you haven't examined the bulletin board on camp ever.
1639     // here.remove_submap_camp( here.getlocal( bb_pos ) );
1640     here.remove_submap_camp( here.getlocal( ms_pos ) );
1641     add_msg( m_info, _( "You abandon %s." ), name );
1642 }
1643 
worker_assignment_ui()1644 void basecamp::worker_assignment_ui()
1645 {
1646     int entries_per_page = 0;
1647     catacurses::window w_followers;
1648 
1649     ui_adaptor ui;
1650     ui.on_screen_resize( [&]( ui_adaptor & ui ) {
1651         const point term( TERMY > FULL_SCREEN_HEIGHT ? ( TERMY - FULL_SCREEN_HEIGHT ) / 2 : 0,
1652                           TERMX > FULL_SCREEN_WIDTH ? ( TERMX - FULL_SCREEN_WIDTH ) / 2 : 0 );
1653 
1654         w_followers = catacurses::newwin( FULL_SCREEN_HEIGHT, FULL_SCREEN_WIDTH,
1655                                           point( term.y, term.x ) );
1656         entries_per_page = FULL_SCREEN_HEIGHT - 5;
1657 
1658         ui.position_from_window( w_followers );
1659     } );
1660     ui.mark_resize();
1661 
1662     size_t selection = 0;
1663     input_context ctxt( "FACTION_MANAGER" );
1664     ctxt.register_action( "INSPECT_NPC" );
1665     ctxt.register_updown();
1666     ctxt.register_action( "CONFIRM" );
1667     ctxt.register_action( "QUIT" );
1668     ctxt.register_action( "HELP_KEYBINDINGS" );
1669     validate_assignees();
1670     g->validate_npc_followers();
1671 
1672     std::vector<npc *> followers;
1673     npc *cur_npc = nullptr;
1674 
1675     ui.on_redraw( [&]( const ui_adaptor & ) {
1676         werase( w_followers );
1677 
1678         // entries_per_page * page number
1679         const size_t top_of_page = entries_per_page * ( selection / entries_per_page );
1680 
1681         for( int i = 0; i < FULL_SCREEN_HEIGHT - 2; i++ ) {
1682             mvwputch( w_followers, point( 45, i ), BORDER_COLOR, LINE_XOXO );
1683         }
1684         draw_border( w_followers );
1685         const nc_color col = c_white;
1686         const std::string no_npcs = _( "You have no companions following you." );
1687         if( !followers.empty() ) {
1688             draw_scrollbar( w_followers, selection, entries_per_page, followers.size(),
1689                             point( 0, 3 ) );
1690             for( size_t i = top_of_page; i < followers.size(); i++ ) {
1691                 const int y = i - top_of_page + 3;
1692                 trim_and_print( w_followers, point( 1, y ), 43, selection == i ? hilite( col ) : col,
1693                                 followers[i]->disp_name() );
1694             }
1695         } else {
1696             mvwprintz( w_followers, point( 1, 4 ), c_light_red, no_npcs );
1697         }
1698         mvwprintz( w_followers, point( 1, FULL_SCREEN_HEIGHT - 2 ), c_light_gray,
1699                    _( "Press %s to inspect this follower." ), ctxt.get_desc( "INSPECT_NPC" ) );
1700         mvwprintz( w_followers, point( 1, FULL_SCREEN_HEIGHT - 1 ), c_light_gray,
1701                    _( "Press %s to assign this follower to this camp." ), ctxt.get_desc( "CONFIRM" ) );
1702         wnoutrefresh( w_followers );
1703     } );
1704 
1705     while( true ) {
1706         // create a list of npcs stationed at this camp
1707         followers.clear();
1708         for( const character_id &elem : g->get_follower_list() ) {
1709             shared_ptr_fast<npc> npc_to_get = overmap_buffer.find_npc( elem );
1710             if( !npc_to_get || !npc_to_get->is_following() ) {
1711                 continue;
1712             }
1713             npc *npc_to_add = npc_to_get.get();
1714             followers.push_back( npc_to_add );
1715         }
1716         cur_npc = nullptr;
1717         if( !followers.empty() ) {
1718             cur_npc = followers[selection];
1719         }
1720 
1721         ui_manager::redraw();
1722         const std::string action = ctxt.handle_input();
1723         if( action == "INSPECT_NPC" ) {
1724             if( cur_npc ) {
1725                 cur_npc->disp_info();
1726             }
1727         } else if( action == "DOWN" ) {
1728             selection++;
1729             if( selection >= followers.size() ) {
1730                 selection = 0;
1731             }
1732         } else if( action == "UP" ) {
1733             if( selection == 0 ) {
1734                 selection = followers.empty() ? 0 : followers.size() - 1;
1735             } else {
1736                 selection--;
1737             }
1738         } else if( action == "CONFIRM" ) {
1739             if( !followers.empty() && cur_npc ) {
1740                 talk_function::assign_camp( *cur_npc );
1741             }
1742         } else if( action == "QUIT" ) {
1743             break;
1744         }
1745     }
1746 }
1747 
job_assignment_ui()1748 void basecamp::job_assignment_ui()
1749 {
1750     int entries_per_page = 0;
1751     catacurses::window w_jobs;
1752 
1753     ui_adaptor ui;
1754     ui.on_screen_resize( [&]( ui_adaptor & ui ) {
1755         const point term( TERMY > FULL_SCREEN_HEIGHT ? ( TERMY - FULL_SCREEN_HEIGHT ) / 2 : 0,
1756                           TERMX > FULL_SCREEN_WIDTH ? ( TERMX - FULL_SCREEN_WIDTH ) / 2 : 0 );
1757 
1758         w_jobs = catacurses::newwin( FULL_SCREEN_HEIGHT, FULL_SCREEN_WIDTH,
1759                                      point( term.y, term.x ) );
1760 
1761         entries_per_page = FULL_SCREEN_HEIGHT - 5;
1762 
1763         ui.position_from_window( w_jobs );
1764     } );
1765     ui.mark_resize();
1766 
1767     size_t selection = 0;
1768     input_context ctxt( "FACTION_MANAGER" );
1769     ctxt.register_action( "INSPECT_NPC" );
1770     ctxt.register_updown();
1771     ctxt.register_action( "CONFIRM" );
1772     ctxt.register_action( "QUIT" );
1773     ctxt.register_action( "HELP_KEYBINDINGS" );
1774     validate_assignees();
1775 
1776     std::vector<npc *> stationed_npcs;
1777     npc *cur_npc = nullptr;
1778 
1779     ui.on_redraw( [&]( const ui_adaptor & ) {
1780         werase( w_jobs );
1781         const size_t top_of_page = entries_per_page * ( selection / entries_per_page );
1782         for( int i = 0; i < FULL_SCREEN_HEIGHT - 2; i++ ) {
1783             mvwputch( w_jobs, point( 45, i ), BORDER_COLOR, LINE_XOXO );
1784         }
1785         draw_border( w_jobs );
1786         mvwprintz( w_jobs, point( 46, 1 ), c_white, _( "Job/Priority" ) );
1787         const nc_color col = c_white;
1788         const std::string no_npcs = _( "There are no npcs stationed here" );
1789         if( !stationed_npcs.empty() ) {
1790             draw_scrollbar( w_jobs, selection, entries_per_page, stationed_npcs.size(),
1791                             point( 0, 3 ) );
1792             for( size_t i = top_of_page; i < stationed_npcs.size(); i++ ) {
1793                 const int y = i - top_of_page + 3;
1794                 trim_and_print( w_jobs, point( 1, y ), 43, selection == i ? hilite( col ) : col,
1795                                 stationed_npcs[i]->disp_name() );
1796             }
1797             if( selection < stationed_npcs.size() ) {
1798                 int start_y = 3;
1799                 if( cur_npc ) {
1800                     if( cur_npc->has_job() ) {
1801                         for( activity_id &elem : cur_npc->job.get_prioritised_vector() ) {
1802                             const int priority = cur_npc->job.get_priority_of_job( elem );
1803                             player_activity test_act = player_activity( elem );
1804                             mvwprintz( w_jobs, point( 46, start_y ), c_light_gray, string_format( _( "%s : %s" ),
1805                                        test_act.get_verb(), std::to_string( priority ) ) );
1806                             start_y++;
1807                         }
1808                     } else {
1809                         mvwprintz( w_jobs, point( 46, start_y ), c_light_red, _( "No current job." ) );
1810                     }
1811                 }
1812             } else {
1813                 mvwprintz( w_jobs, point( 46, 4 ), c_light_red, no_npcs );
1814             }
1815         } else {
1816             mvwprintz( w_jobs, point( 46, 4 ), c_light_red, no_npcs );
1817         }
1818         mvwprintz( w_jobs, point( 1, FULL_SCREEN_HEIGHT - 2 ), c_light_gray,
1819                    _( "Press %s to inspect this follower." ), ctxt.get_desc( "INSPECT_NPC" ) );
1820         mvwprintz( w_jobs, point( 1, FULL_SCREEN_HEIGHT - 1 ), c_light_gray,
1821                    _( "Press %s to change this workers job priorities." ), ctxt.get_desc( "CONFIRM" ) );
1822         wnoutrefresh( w_jobs );
1823     } );
1824 
1825     while( true ) {
1826         // create a list of npcs stationed at this camp
1827         stationed_npcs.clear();
1828         for( const auto &elem : get_npcs_assigned() ) {
1829             if( elem ) {
1830                 stationed_npcs.push_back( elem.get() );
1831             }
1832         }
1833         cur_npc = nullptr;
1834         // entries_per_page * page number
1835         if( !stationed_npcs.empty() ) {
1836             cur_npc = stationed_npcs[selection];
1837         }
1838 
1839         ui_manager::redraw();
1840 
1841         const std::string action = ctxt.handle_input();
1842         if( action == "INSPECT_NPC" ) {
1843             if( cur_npc ) {
1844                 cur_npc->disp_info();
1845             }
1846         } else if( action == "DOWN" ) {
1847             selection++;
1848             if( selection >= stationed_npcs.size() ) {
1849                 selection = 0;
1850             }
1851         } else if( action == "UP" ) {
1852             if( selection == 0 ) {
1853                 selection = stationed_npcs.empty() ? 0 : stationed_npcs.size() - 1;
1854             } else {
1855                 selection--;
1856             }
1857         } else if( action == "CONFIRM" ) {
1858             if( cur_npc ) {
1859                 while( true ) {
1860                     uilist smenu;
1861                     smenu.text = _( "Assign job priority ( 0 to disable )" );
1862                     int count = 0;
1863                     std::vector<activity_id> job_vec = cur_npc->job.get_prioritised_vector();
1864                     smenu.addentry( count, true, 'C', _( "Clear all priorities" ) );
1865                     count++;
1866                     for( const activity_id &elem : job_vec ) {
1867                         player_activity test_act = player_activity( elem );
1868                         const int priority = cur_npc->job.get_priority_of_job( elem );
1869                         smenu.addentry( count, true, MENU_AUTOASSIGN, string_format( _( "%s : %s" ), test_act.get_verb(),
1870                                         std::to_string( priority ) ) );
1871                         count++;
1872                     }
1873                     smenu.query();
1874                     if( smenu.ret == UILIST_CANCEL ) {
1875                         break;
1876                     } else if( smenu.ret == 0 ) {
1877                         cur_npc->job.clear_all_priorities();
1878                     } else if( smenu.ret > 0 && smenu.ret <= static_cast<int>( job_vec.size() ) ) {
1879                         activity_id sel_job = job_vec[smenu.ret - 1];
1880                         player_activity test_act = player_activity( sel_job );
1881                         const std::string formatted = string_format( _( "Priority for %s " ), test_act.get_verb() );
1882                         const int amount = string_input_popup()
1883                                            .title( formatted )
1884                                            .width( 20 )
1885                                            .only_digits( true )
1886                                            .query_int();
1887                         cur_npc->job.set_task_priority( sel_job, amount );
1888                     } else {
1889                         break;
1890                     }
1891                 }
1892             }
1893         } else if( action == "QUIT" ) {
1894             break;
1895         }
1896     }
1897 }
1898 
start_menial_labor()1899 void basecamp::start_menial_labor()
1900 {
1901     if( camp_food_supply() < time_to_food( 3_hours ) ) {
1902         popup( _( "You don't have enough food stored to feed your companion." ) );
1903         return;
1904     }
1905     shared_ptr_fast<npc> comp = talk_function::companion_choose();
1906     if( comp == nullptr ) {
1907         return;
1908     }
1909     validate_sort_points();
1910 
1911     comp->assign_activity( ACT_MOVE_LOOT );
1912     popup( _( "%s goes off to clean toilets and sort loot." ), comp->disp_name() );
1913 }
1914 
start_cut_logs()1915 void basecamp::start_cut_logs()
1916 {
1917     std::vector<std::string> log_sources = { "forest", "forest_thick", "forest_water", "forest_trail" };
1918     popup( _( "Forests and swamps are the only valid cutting locations." ) );
1919     tripoint_abs_omt forest = om_target_tile( omt_pos, 1, 50, log_sources, ot_match_type::type );
1920     if( forest != tripoint_abs_omt( -999, -999, -999 ) ) {
1921         standard_npc sample_npc( "Temp" );
1922         sample_npc.set_fake( true );
1923         int tree_est = om_cutdown_trees_est( forest, 50 );
1924         int tree_young_est = om_harvest_ter_est( sample_npc, forest,
1925                              ter_id( "t_tree_young" ), 50 );
1926         int dist = rl_dist( forest.xy(), omt_pos.xy() );
1927         //Very roughly what the player does + 6 hours for prep, clean up, breaks
1928         time_duration chop_time = 6_hours + 1_hours * tree_est + 7_minutes * tree_young_est;
1929         int haul_items = 2 * tree_est + 3 * tree_young_est;
1930         time_duration travel_time = companion_travel_time_calc( forest, omt_pos, 0_minutes,
1931                                     2, haul_items );
1932         time_duration work_time = travel_time + chop_time;
1933         if( !query_yn( _( "Trip Estimate:\n%s" ), camp_trip_description( work_time,
1934                        chop_time, travel_time, dist, 2, time_to_food( work_time ) ) ) ) {
1935             return;
1936         }
1937 
1938         npc_ptr comp = start_mission( "_faction_camp_cut_log", work_time, true,
1939                                       _( "departs to cut logs…" ), false, {},
1940                                       skill_fabrication, 2 );
1941         if( comp != nullptr ) {
1942             om_cutdown_trees_logs( forest, 50 );
1943             om_harvest_ter( *comp, forest, ter_id( "t_tree_young" ), 50 );
1944             mass_volume harvest = om_harvest_itm( comp, forest, 95 );
1945             // recalculate trips based on actual load and capacity
1946             travel_time = companion_travel_time_calc( forest, omt_pos, 0_minutes, 2,
1947                           harvest.count );
1948             work_time = travel_time + chop_time;
1949             comp->companion_mission_time_ret = calendar::turn + work_time;
1950             //If we cleared a forest...
1951             if( om_cutdown_trees_est( forest ) < 5 ) {
1952                 const oter_id &omt_trees = overmap_buffer.ter( forest );
1953                 //Do this for swamps "forest_wet" if we have a swamp without trees...
1954                 if( omt_trees.id() != "forest_wet" ) {
1955                     overmap_buffer.ter_set( forest, oter_id( "field" ) );
1956                 }
1957             }
1958         }
1959     }
1960 }
1961 
start_clearcut()1962 void basecamp::start_clearcut()
1963 {
1964     std::vector<std::string> log_sources = { "forest", "forest_thick", "forest_trail" };
1965     popup( _( "Forests are the only valid cutting locations." ) );
1966     tripoint_abs_omt forest = om_target_tile( omt_pos, 1, 50, log_sources, ot_match_type::type );
1967     if( forest != tripoint_abs_omt( -999, -999, -999 ) ) {
1968         standard_npc sample_npc( "Temp" );
1969         sample_npc.set_fake( true );
1970         int tree_est = om_cutdown_trees_est( forest, 95 );
1971         int tree_young_est = om_harvest_ter_est( sample_npc, forest,
1972                              ter_id( "t_tree_young" ), 95 );
1973         int dist = rl_dist( forest.xy(), omt_pos.xy() );
1974         //Very roughly what the player does + 6 hours for prep, clean up, breaks
1975         time_duration chop_time = 6_hours + 1_hours * tree_est + 7_minutes * tree_young_est;
1976         time_duration travel_time = companion_travel_time_calc( forest, omt_pos, 0_minutes, 2 );
1977         time_duration work_time = travel_time + chop_time;
1978         if( !query_yn( _( "Trip Estimate:\n%s" ), camp_trip_description( work_time,
1979                        chop_time, travel_time, dist, 2, time_to_food( work_time ) ) ) ) {
1980             return;
1981         }
1982 
1983         npc_ptr comp = start_mission( "_faction_camp_clearcut", work_time,
1984                                       true, _( "departs to clear a forest…" ), false, {},
1985                                       skill_fabrication, 1 );
1986         if( comp != nullptr ) {
1987             om_cutdown_trees_trunks( forest, 95 );
1988             om_harvest_ter_break( *comp, forest, ter_id( "t_tree_young" ), 95 );
1989             //If we cleared a forest...
1990             if( om_cutdown_trees_est( forest ) < 5 ) {
1991                 overmap_buffer.ter_set( forest, oter_id( "field" ) );
1992             }
1993         }
1994     }
1995 }
1996 
start_setup_hide_site()1997 void basecamp::start_setup_hide_site()
1998 {
1999     std::vector<std::string> hide_locations = { "forest", "forest_thick", "forest_water", "forest_trail"
2000                                                 "field"
2001                                               };
2002     popup( _( "Forests, swamps, and fields are valid hide site locations." ) );
2003     tripoint_abs_omt forest = om_target_tile( omt_pos, 10, 90, hide_locations, ot_match_type::type,
2004                               true, true, omt_pos, true );
2005     if( forest != tripoint_abs_omt( -999, -999, -999 ) ) {
2006         int dist = rl_dist( forest.xy(), omt_pos.xy() );
2007         inventory tgt_inv = *get_player_character().inv;
2008         std::vector<item *> pos_inv = tgt_inv.items_with( []( const item & itm ) {
2009             return !itm.can_revive();
2010         } );
2011         if( !pos_inv.empty() ) {
2012             std::vector<item *> losing_equipment = give_equipment( pos_inv,
2013                                                    _( "Do you wish to give your companion "
2014                                                            "additional items?" ) );
2015             int trips = om_carry_weight_to_trips( losing_equipment );
2016             int haulage = trips <= 2 ? 0 : losing_equipment.size();
2017             time_duration build_time = 6_hours;
2018             time_duration travel_time = companion_travel_time_calc( forest, omt_pos, 0_minutes,
2019                                         2, haulage );
2020             time_duration work_time = travel_time + build_time;
2021             if( !query_yn( _( "Trip Estimate:\n%s" ), camp_trip_description( work_time,
2022                            build_time, travel_time, dist, trips, time_to_food( work_time ) ) ) ) {
2023                 return;
2024             }
2025             npc_ptr comp = start_mission( "_faction_camp_hide_site", work_time, true,
2026                                           _( "departs to build a hide site…" ), false, {},
2027                                           skill_survival, 3 );
2028             if( comp != nullptr ) {
2029                 trips = om_carry_weight_to_trips( losing_equipment, comp );
2030                 haulage = trips <= 2 ? 0 : losing_equipment.size();
2031                 work_time = companion_travel_time_calc( forest, omt_pos, 0_minutes, 2, haulage ) +
2032                             build_time;
2033                 comp->companion_mission_time_ret = calendar::turn + work_time;
2034                 om_set_hide_site( *comp, forest, losing_equipment );
2035             }
2036         } else {
2037             popup( _( "You need equipment to setup a hide site…" ) );
2038         }
2039     }
2040 }
2041 
start_relay_hide_site()2042 void basecamp::start_relay_hide_site()
2043 {
2044     std::vector<std::string> hide_locations = { "faction_hide_site_0" };
2045     popup( _( "You must select an existing hide site." ) );
2046     tripoint_abs_omt forest = om_target_tile( omt_pos, 10, 90, hide_locations, ot_match_type::exact,
2047                               true, true, omt_pos, true );
2048     if( forest != tripoint_abs_omt( -999, -999, -999 ) ) {
2049         int dist = rl_dist( forest.xy(), omt_pos.xy() );
2050         inventory tgt_inv = *get_player_character().inv;
2051         std::vector<item *> pos_inv = tgt_inv.items_with( []( const item & itm ) {
2052             return !itm.can_revive();
2053         } );
2054         std::vector<item *> losing_equipment;
2055         if( !pos_inv.empty() ) {
2056             losing_equipment = give_equipment( pos_inv,
2057                                                _( "Do you wish to give your companion "
2058                                                   "additional items?" ) );
2059         }
2060         //Check items in improvised shelters at hide site
2061         tinymap target_bay;
2062         target_bay.load( project_to<coords::sm>( forest ), false );
2063         std::vector<item *> hide_inv;
2064         for( item &i : target_bay.i_at( point( 11, 10 ) ) ) {
2065             hide_inv.push_back( &i );
2066         }
2067         std::vector<item *> gaining_equipment;
2068         if( !hide_inv.empty() ) {
2069             gaining_equipment = give_equipment( hide_inv, _( "Bring gear back?" ) );
2070         }
2071         if( !losing_equipment.empty() || !gaining_equipment.empty() ) {
2072             //Only get charged the greater trips since return is free for both
2073             int trips = std::max( om_carry_weight_to_trips( gaining_equipment ),
2074                                   om_carry_weight_to_trips( losing_equipment ) );
2075             int haulage = trips <= 2 ? 0 : std::max( gaining_equipment.size(),
2076                           losing_equipment.size() );
2077             time_duration build_time = 6_hours;
2078             time_duration travel_time = companion_travel_time_calc( forest, omt_pos, 0_minutes,
2079                                         trips, haulage );
2080             time_duration work_time = travel_time + build_time;
2081             if( !query_yn( _( "Trip Estimate:\n%s" ), camp_trip_description( work_time, build_time,
2082                            travel_time, dist, trips, time_to_food( work_time ) ) ) ) {
2083                 return;
2084             }
2085 
2086             npc_ptr comp = start_mission( "_faction_camp_hide_trans", work_time, true,
2087                                           _( "departs for the hide site…" ), false, {},
2088                                           skill_survival, 3 );
2089             if( comp != nullptr ) {
2090                 // recalculate trips based on actual load
2091                 trips = std::max( om_carry_weight_to_trips( gaining_equipment, comp ),
2092                                   om_carry_weight_to_trips( losing_equipment, comp ) );
2093                 int haulage = trips <= 2 ? 0 : std::max( gaining_equipment.size(),
2094                               losing_equipment.size() );
2095                 work_time = companion_travel_time_calc( forest, omt_pos, 0_minutes, trips,
2096                                                         haulage ) + build_time;
2097                 comp->companion_mission_time_ret = calendar::turn + work_time;
2098                 om_set_hide_site( *comp, forest, losing_equipment, gaining_equipment );
2099             }
2100         } else {
2101             popup( _( "You need equipment to transport between the hide site…" ) );
2102         }
2103     }
2104 }
2105 
start_fortifications(std::string & bldg_exp)2106 void basecamp::start_fortifications( std::string &bldg_exp )
2107 {
2108     std::vector<std::string> allowed_locations = {
2109         "forest", "forest_thick", "forest_water", "forest_trail", "field"
2110     };
2111     popup( _( "Select a start and end point.  Line must be straight.  Fields, forests, and "
2112               "swamps are valid fortification locations.  In addition to existing fortification "
2113               "constructions." ) );
2114     tripoint_abs_omt start = om_target_tile( omt_pos, 2, 90, allowed_locations, ot_match_type::type );
2115     popup( _( "Select an end point." ) );
2116     tripoint_abs_omt stop = om_target_tile( omt_pos, 2, 90, allowed_locations, ot_match_type::type,
2117                                             true, false, start );
2118     if( start != tripoint_abs_omt( -999, -999, -999 ) &&
2119         stop != tripoint_abs_omt( -999, -999, -999 ) ) {
2120         const recipe &making = recipe_id( bldg_exp ).obj();
2121         bool change_x = ( start.x() != stop.x() );
2122         bool change_y = ( start.y() != stop.y() );
2123         if( change_x && change_y ) {
2124             popup( "Construction line must be straight!" );
2125             return;
2126         }
2127         if( bldg_exp == "faction_wall_level_N_1" ) {
2128             std::vector<tripoint_abs_omt> tmp_line = line_to( stop, start );
2129             int line_count = tmp_line.size();
2130             int yes_count = 0;
2131             for( auto elem : tmp_line ) {
2132                 if( std::find( fortifications.begin(), fortifications.end(), elem ) != fortifications.end() ) {
2133                     yes_count += 1;
2134                 }
2135             }
2136             if( yes_count < line_count ) {
2137                 popup( _( "Spiked pits must be built over existing trenches!" ) );
2138                 return;
2139             }
2140         }
2141         std::vector<tripoint_abs_omt> fortify_om;
2142         if( ( change_x && stop.x() < start.x() ) || ( change_y && stop.y() < start.y() ) ) {
2143             //line_to doesn't include the origin point
2144             fortify_om.push_back( stop );
2145             std::vector<tripoint_abs_omt> tmp_line = line_to( stop, start );
2146             fortify_om.insert( fortify_om.end(), tmp_line.begin(), tmp_line.end() );
2147         } else {
2148             fortify_om.push_back( start );
2149             std::vector<tripoint_abs_omt> tmp_line = line_to( start, stop );
2150             fortify_om.insert( fortify_om.end(), tmp_line.begin(), tmp_line.end() );
2151         }
2152         int trips = 0;
2153         time_duration build_time = 0_hours;
2154         time_duration travel_time = 0_hours;
2155         int dist = 0;
2156         for( auto fort_om : fortify_om ) {
2157             bool valid = false;
2158             const oter_id &omt_ref = overmap_buffer.ter( fort_om );
2159             for( const std::string &pos_om : allowed_locations ) {
2160                 if( omt_ref.id().c_str() == pos_om ) {
2161                     valid = true;
2162                     break;
2163                 }
2164             }
2165 
2166             if( !valid ) {
2167                 popup( _( "Invalid terrain in construction path." ) );
2168                 return;
2169             }
2170             trips += 2;
2171             build_time += making.batch_duration( get_player_character() );
2172             dist += rl_dist( fort_om.xy(), omt_pos.xy() );
2173             travel_time += companion_travel_time_calc( fort_om, omt_pos, 0_minutes, 2 );
2174         }
2175         time_duration total_time = base_camps::to_workdays( travel_time + build_time );
2176         int need_food = time_to_food( total_time );
2177         if( !query_yn( _( "Trip Estimate:\n%s" ), camp_trip_description( total_time, build_time,
2178                        travel_time, dist, trips, need_food ) ) ) {
2179             return;
2180         } else if( !making.deduped_requirements().can_make_with_inventory( _inv,
2181                    making.get_component_filter(), ( fortify_om.size() * 2 ) - 2 ) ) {
2182             popup( _( "You don't have the material to build the fortification." ) );
2183             return;
2184         }
2185 
2186         const int batch_size = fortify_om.size() * 2 - 2;
2187         basecamp_action_components components( making, batch_size, *this );
2188         if( !components.choose_components() ) {
2189             return;
2190         }
2191 
2192         npc_ptr comp = start_mission( "_faction_camp_om_fortifications", total_time, true,
2193                                       _( "begins constructing fortifications…" ), false, {},
2194                                       making.required_skills );
2195         if( comp != nullptr ) {
2196             components.consume_components();
2197             comp->companion_mission_role_id = bldg_exp;
2198             for( auto pt : fortify_om ) {
2199                 comp->companion_mission_points.push_back( pt );
2200             }
2201         }
2202     }
2203 }
2204 
start_combat_mission(const std::string & miss)2205 void basecamp::start_combat_mission( const std::string &miss )
2206 {
2207     popup( _( "Select checkpoints until you reach maximum range or select the last point again "
2208               "to end." ) );
2209     tripoint_abs_omt start = omt_pos;
2210     std::vector<tripoint_abs_omt> scout_points = om_companion_path( start, 90, true );
2211     if( scout_points.empty() ) {
2212         return;
2213     }
2214     int dist = scout_points.size();
2215     int trips = 2;
2216     time_duration travel_time = companion_travel_time_calc( scout_points, 0_minutes, trips );
2217     if( !query_yn( _( "Trip Estimate:\n%s" ), camp_trip_description( 0_hours, 0_hours,
2218                    travel_time, dist, trips, time_to_food( travel_time ) ) ) ) {
2219         return;
2220     }
2221     npc_ptr comp = start_mission( miss, travel_time, true, _( "departs on patrol…" ),
2222                                   false, {}, skill_survival, 3 );
2223     if( comp != nullptr ) {
2224         comp->companion_mission_points = scout_points;
2225     }
2226 }
2227 
2228 // the structure of this function has driven (at least) two devs insane
2229 // recipe_deck returns a map of recipe ids to descriptions
2230 // it first checks whether the mission id starts with the correct direction prefix,
2231 // and then search for the mission id without direction prefix in the recipes
2232 // if there's a match, the player has selected a crafting mission
start_crafting(const std::string & cur_id,const point & cur_dir,const std::string & type,const std::string & miss_id)2233 void basecamp::start_crafting( const std::string &cur_id, const point &cur_dir,
2234                                const std::string &type, const std::string &miss_id )
2235 {
2236     const std::string cur_dir_id = base_camps::all_directions.at( cur_dir ).id;
2237     const std::map<recipe_id, translation> &recipes = recipe_deck( type );
2238     if( cur_id.substr( 0, cur_dir_id.size() ) != cur_dir_id ) {
2239         // not a crafting mission or has the wrong direction
2240         return;
2241     }
2242     const auto it = recipes.find( recipe_id( cur_id.substr( cur_dir_id.size() ) ) );
2243     if( it != recipes.end() ) {
2244         const recipe &making = it->first.obj();
2245 
2246         if( !making.deduped_requirements().can_make_with_inventory(
2247                 _inv, making.get_component_filter() ) ) {
2248             popup( _( "You don't have the materials to craft that" ) );
2249             return;
2250         }
2251 
2252         int batch_size = 1;
2253         string_input_popup popup_input;
2254         int batch_max = recipe_batch_max( making );
2255         const std::string title = string_format( _( "Batch crafting %s [MAX: %d]: " ),
2256                                   making.result_name(), batch_max );
2257         popup_input.title( title ).edit( batch_size );
2258 
2259         if( popup_input.canceled() || batch_size <= 0 ) {
2260             return;
2261         }
2262         if( batch_size > recipe_batch_max( making ) ) {
2263             popup( _( "Your batch is too large!" ) );
2264             return;
2265         }
2266 
2267         basecamp_action_components components( making, batch_size, *this );
2268         if( !components.choose_components() ) {
2269             return;
2270         }
2271 
2272         time_duration work_days = base_camps::to_workdays( making.batch_duration( get_player_character(),
2273                                   batch_size ) );
2274         npc_ptr comp = start_mission( miss_id + cur_dir_id, work_days, true,
2275                                       _( "begins to work…" ), false, {},
2276                                       making.required_skills );
2277         if( comp != nullptr ) {
2278             components.consume_components();
2279             for( const item &results : making.create_results( batch_size ) ) {
2280                 comp->companion_mission_inv.add_item( results );
2281                 for( const item &byproducts : making.create_byproducts() ) {
2282                     comp->companion_mission_inv.add_item( byproducts );
2283                 }
2284             }
2285         }
2286         return;
2287     }
2288 }
2289 
farm_valid_seed(const item & itm)2290 static bool farm_valid_seed( const item &itm )
2291 {
2292     return itm.is_seed() && itm.typeId() != itype_marloss_seed && itm.typeId() != itype_fungal_seeds;
2293 }
2294 
farm_action(const tripoint_abs_omt & omt_tgt,farm_ops op,const npc_ptr & comp=nullptr)2295 static std::pair<size_t, std::string> farm_action( const tripoint_abs_omt &omt_tgt, farm_ops op,
2296         const npc_ptr &comp = nullptr )
2297 {
2298     size_t plots_cnt = 0;
2299     std::string crops;
2300 
2301     const auto is_dirtmound = []( const tripoint & pos, tinymap & bay1, tinymap & bay2 ) {
2302         return ( bay1.ter( pos ) == t_dirtmound ) && ( !bay2.has_furn( pos ) );
2303     };
2304     const auto is_unplowed = []( const tripoint & pos, tinymap & farm_map ) {
2305         const ter_id &farm_ter = farm_map.ter( pos );
2306         return farm_ter->has_flag( flag_PLOWABLE );
2307     };
2308 
2309     std::set<std::string> plant_names;
2310     std::vector<item *> seed_inv;
2311     if( comp ) {
2312         seed_inv = comp->companion_mission_inv.items_with( farm_valid_seed );
2313     }
2314 
2315     //farm_json is what the area should look like according to jsons
2316     tinymap farm_json;
2317     // We're probably going to rotate this tinymap to match the actual map.
2318     // Let's make sure we don't move NPCs around when doing this.
2319     farm_json.no_rotate_npcs = true;
2320     // TODO: fix point types
2321     farm_json.generate( project_to<coords::sm>( omt_tgt ).raw(), calendar::turn );
2322     //farm_map is what the area actually looks like
2323     tinymap farm_map;
2324     farm_map.load( project_to<coords::sm>( omt_tgt ), false );
2325     tripoint mapmin = tripoint( 0, 0, omt_tgt.z() );
2326     tripoint mapmax = tripoint( 2 * SEEX - 1, 2 * SEEY - 1, omt_tgt.z() );
2327     bool done_planting = false;
2328     Character &player_character = get_player_character();
2329     map &here = get_map();
2330     for( const tripoint &pos : farm_map.points_in_rectangle( mapmin, mapmax ) ) {
2331         if( done_planting ) {
2332             break;
2333         }
2334         switch( op ) {
2335             case farm_ops::plow:
2336                 //Needs to be plowed to match json
2337                 if( is_dirtmound( pos, farm_json, farm_map ) && is_unplowed( pos, farm_map ) ) {
2338                     plots_cnt += 1;
2339                     if( comp ) {
2340                         farm_map.ter_set( pos, t_dirtmound );
2341                     }
2342                 }
2343                 break;
2344             case farm_ops::plant:
2345                 if( is_dirtmound( pos, farm_map, farm_map ) ) {
2346                     plots_cnt += 1;
2347                     if( comp ) {
2348                         if( seed_inv.empty() ) {
2349                             done_planting = true;
2350                             break;
2351                         }
2352                         item *tmp_seed = seed_inv.back();
2353                         seed_inv.pop_back();
2354                         std::list<item> used_seed;
2355                         if( tmp_seed->count_by_charges() ) {
2356                             used_seed.push_back( *tmp_seed );
2357                             tmp_seed->charges -= 1;
2358                             if( tmp_seed->charges > 0 ) {
2359                                 seed_inv.push_back( tmp_seed );
2360                             }
2361                         }
2362                         used_seed.front().set_age( 0_turns );
2363                         farm_map.add_item_or_charges( pos, used_seed.front() );
2364                         farm_map.set( pos, t_dirt, f_plant_seed );
2365                     }
2366                 }
2367                 break;
2368             case farm_ops::harvest:
2369                 if( farm_map.furn( pos ) == f_plant_harvest ) {
2370                     // Can't use item_stack::only_item() since there might be fertilizer
2371                     map_stack items = farm_map.i_at( pos );
2372                     const map_stack::iterator seed = std::find_if( items.begin(), items.end(), []( const item & it ) {
2373                         return it.is_seed();
2374                     } );
2375                     if( seed != items.end() && farm_valid_seed( *seed ) ) {
2376                         plots_cnt += 1;
2377                         if( comp ) {
2378                             int skillLevel = comp->get_skill_level( skill_survival );
2379                             ///\EFFECT_SURVIVAL increases number of plants harvested from a seed
2380                             int plant_cnt = rng( skillLevel / 2, skillLevel );
2381                             plant_cnt = std::min( std::max( plant_cnt, 1 ), 9 );
2382                             int seed_cnt = std::max( 1, rng( plant_cnt / 4, plant_cnt / 2 ) );
2383                             for( auto &i : iexamine::get_harvest_items( *seed->type, plant_cnt,
2384                                     seed_cnt, true ) ) {
2385                                 here.add_item_or_charges( player_character.pos(), i );
2386                             }
2387                             farm_map.i_clear( pos );
2388                             farm_map.furn_set( pos, f_null );
2389                             farm_map.ter_set( pos, t_dirt );
2390                         } else {
2391                             plant_names.insert( item::nname( itype_id( seed->type->seed->fruit_id ) ) );
2392                         }
2393                     }
2394                 }
2395                 break;
2396             default:
2397                 // let the callers handle no op argument
2398                 break;
2399         }
2400     }
2401     if( comp ) {
2402         farm_map.save();
2403     }
2404 
2405     int total_c = 0;
2406     for( const std::string &i : plant_names ) {
2407         if( total_c < 5 ) {
2408             crops += "    " + i + "\n";
2409             total_c++;
2410         } else if( total_c == 5 ) {
2411             crops += _( "+ more\n" );
2412             break;
2413         }
2414     }
2415 
2416     return std::make_pair( plots_cnt, crops );
2417 }
2418 
start_farm_op(const point & dir,const tripoint_abs_omt & omt_tgt,farm_ops op)2419 void basecamp::start_farm_op( const point &dir, const tripoint_abs_omt &omt_tgt, farm_ops op )
2420 {
2421     const std::string &dir_id = base_camps::all_directions.at( dir ).id;
2422     std::pair<size_t, std::string> farm_data = farm_action( omt_tgt, op );
2423     size_t plots_cnt = farm_data.first;
2424     if( !plots_cnt ) {
2425         return;
2426     }
2427 
2428     time_duration work = 0_minutes;
2429     switch( op ) {
2430         case farm_ops::harvest:
2431             work += 3_minutes * plots_cnt;
2432             start_mission( "_faction_exp_harvest_" + dir_id, work, true,
2433                            _( "begins to harvest the field…" ), false, {}, skill_survival, 1 );
2434             break;
2435         case farm_ops::plant: {
2436             std::vector<item *> seed_inv = _inv.items_with( farm_valid_seed );
2437             if( seed_inv.empty() ) {
2438                 popup( _( "You have no additional seeds to give your companions…" ) );
2439                 return;
2440             }
2441             std::vector<item *> plant_these = give_equipment( seed_inv,
2442                                               _( "Which seeds do you wish to have planted?" ) );
2443             size_t seed_cnt = 0;
2444             for( item *seeds : plant_these ) {
2445                 seed_cnt += seeds->count();
2446             }
2447             size_t plots_seeded = std::min( seed_cnt, plots_cnt );
2448             if( !seed_cnt ) {
2449                 return;
2450             }
2451             work += 1_minutes * plots_seeded;
2452             start_mission( "_faction_exp_plant_" + dir_id, work, true,
2453                            _( "begins planting the field…" ), false, plant_these,
2454                            skill_survival, 1 );
2455             break;
2456         }
2457         case farm_ops::plow:
2458             work += 5_minutes * plots_cnt;
2459             start_mission( "_faction_exp_plow_" + dir_id, work, true,
2460                            _( "begins plowing the field…" ), false, {} );
2461             break;
2462         default:
2463             debugmsg( "Farm operations called with no operation" );
2464     }
2465 }
2466 
2467 // camp faction companion mission recovery functions
companion_choose_return(const std::string & miss_id,time_duration min_duration)2468 npc_ptr basecamp::companion_choose_return( const std::string &miss_id,
2469         time_duration min_duration )
2470 {
2471     return talk_function::companion_choose_return( omt_pos, base_camps::id, miss_id,
2472             calendar::turn - min_duration );
2473 }
finish_return(npc & comp,const bool fixed_time,const std::string & return_msg,const std::string & skill,int difficulty,const bool cancel)2474 void basecamp::finish_return( npc &comp, const bool fixed_time, const std::string &return_msg,
2475                               const std::string &skill, int difficulty, const bool cancel )
2476 {
2477     popup( "%s %s", comp.name, return_msg );
2478     // this is the time the mission was expected to take, or did take for fixed time missions
2479     time_duration reserve_time = comp.companion_mission_time_ret - comp.companion_mission_time;
2480     time_duration mission_time = reserve_time;
2481     if( !fixed_time ) {
2482         mission_time = calendar::turn - comp.companion_mission_time;
2483     }
2484     if( !cancel ) {
2485         talk_function::companion_skill_trainer( comp, skill, mission_time, difficulty );
2486     }
2487 
2488     // companions subtracted food when they started the mission, but didn't mod their hunger for
2489     // that food.  so add it back in.
2490     int need_food = time_to_food( mission_time - reserve_time );
2491     if( camp_food_supply() < need_food ) {
2492         popup( _( "Your companion seems disappointed that your pantry is empty…" ) );
2493     }
2494     int avail_food = std::min( need_food, camp_food_supply() ) + time_to_food( reserve_time );
2495     // movng all the logic from talk_function::companion return here instead of polluting
2496     // mission_companion
2497     comp.reset_companion_mission();
2498     comp.companion_mission_time = calendar::before_time_starts;
2499     comp.companion_mission_time_ret = calendar::before_time_starts;
2500     if( !cancel ) {
2501         for( size_t i = 0; i < comp.companion_mission_inv.size(); i++ ) {
2502             for( const auto &it : comp.companion_mission_inv.const_stack( i ) ) {
2503                 if( !it.count_by_charges() || it.charges > 0 ) {
2504                     place_results( it );
2505                 }
2506             }
2507         }
2508     }
2509     comp.companion_mission_inv.clear();
2510     comp.companion_mission_points.clear();
2511     // npc *may* be active, or not if outside the reality bubble
2512     g->reload_npcs();
2513     validate_assignees();
2514 
2515     camp_food_supply( -need_food );
2516     comp.mod_hunger( -avail_food );
2517     comp.mod_stored_kcal( avail_food );
2518     if( has_water() ) {
2519         comp.set_thirst( 0 );
2520     }
2521     comp.set_fatigue( 0 );
2522     comp.set_sleep_deprivation( 0 );
2523 }
2524 
mission_return(const std::string & miss_id,time_duration min_duration,bool fixed_time,const std::string & return_msg,const std::string & skill,int difficulty)2525 npc_ptr basecamp::mission_return( const std::string &miss_id, time_duration min_duration,
2526                                   bool fixed_time, const std::string &return_msg,
2527                                   const std::string &skill, int difficulty )
2528 {
2529     npc_ptr comp = companion_choose_return( miss_id, min_duration );
2530     if( comp != nullptr ) {
2531         finish_return( *comp, fixed_time, return_msg, skill, difficulty );
2532     }
2533     return comp;
2534 }
2535 
emergency_recall()2536 npc_ptr basecamp::emergency_recall()
2537 {
2538     npc_ptr comp = talk_function::companion_choose_return( omt_pos, base_camps::id, "",
2539                    calendar::turn - 24_hours, false );
2540     if( comp != nullptr ) {
2541         const std::string return_msg = _( "responds to the emergency recall…" );
2542         finish_return( *comp, false, return_msg, "menial", 0, true );
2543     }
2544     return comp;
2545 
2546 }
2547 
upgrade_return(const point & dir,const std::string & miss)2548 bool basecamp::upgrade_return( const point &dir, const std::string &miss )
2549 {
2550     const std::string bldg = next_upgrade( dir, 1 );
2551     if( bldg == "null" ) {
2552         return false;
2553     }
2554     return upgrade_return( dir, miss, bldg );
2555 }
2556 
upgrade_return(const point & dir,const std::string & miss,const std::string & bldg)2557 bool basecamp::upgrade_return( const point &dir, const std::string &miss,
2558                                const std::string &bldg )
2559 {
2560     auto e = expansions.find( dir );
2561     if( e == expansions.end() ) {
2562         return false;
2563     }
2564     const tripoint_abs_omt upos = e->second.pos;
2565     const recipe &making = recipe_id( bldg ).obj();
2566 
2567     time_duration work_days = base_camps::to_workdays( making.batch_duration(
2568                                   get_player_character() ) );
2569     npc_ptr comp = companion_choose_return( miss, work_days );
2570 
2571     if( comp == nullptr ) {
2572         return false;
2573     }
2574     if( !run_mapgen_update_func( making.get_blueprint(), upos ) ) {
2575         popup( _( "%s failed to build the %s upgrade, perhaps there is a vehicle in the way." ),
2576                comp->disp_name(),
2577                making.get_blueprint() );
2578         return false;
2579     }
2580     update_provides( bldg, e->second );
2581     update_resources( bldg );
2582 
2583     const std::string msg = _( "returns from upgrading the camp having earned a bit of "
2584                                "experience…" );
2585     finish_return( *comp, false, msg, "construction", making.difficulty );
2586 
2587     return true;
2588 }
2589 
menial_return()2590 bool basecamp::menial_return()
2591 {
2592     const std::string msg = _( "returns from doing the dirty work to keep the camp running…" );
2593     npc_ptr comp = mission_return( "_faction_camp_menial", 3_hours, true, msg, "menial", 2 );
2594     if( comp == nullptr ) {
2595         return false;
2596     }
2597     comp->revert_after_activity();
2598     return true;
2599 }
2600 
gathering_return(const std::string & task,time_duration min_time)2601 bool basecamp::gathering_return( const std::string &task, time_duration min_time )
2602 {
2603     npc_ptr comp = companion_choose_return( task, min_time );
2604     if( comp == nullptr ) {
2605         return false;
2606     }
2607 
2608     std::string task_description = _( "gathering materials" );
2609     int danger = 20;
2610     int favor = 2;
2611     int threat = 10;
2612     std::string skill_group = "gathering";
2613     int skill = 2 * comp->get_skill_level( skill_survival ) + comp->per_cur;
2614     int checks_per_cycle = 6;
2615     if( task == "_faction_camp_foraging" ) {
2616         task_description = _( "foraging for edible plants" );
2617         danger = 15;
2618         checks_per_cycle = 12;
2619     } else if( task == "_faction_camp_trapping" ) {
2620         task_description = _( "trapping small animals" );
2621         favor = 1;
2622         danger = 15;
2623         skill_group = "trapping";
2624         skill = 2 * comp->get_skill_level( skill_traps ) + comp->per_cur;
2625         checks_per_cycle = 4;
2626     } else if( task == "_faction_camp_hunting" ) {
2627         task_description = _( "hunting for meat" );
2628         danger = 10;
2629         favor = 0;
2630         skill_group = "hunting";
2631         skill = 1.5 * comp->get_skill_level( skill_gun ) + comp->per_cur / 2.0;
2632         threat = 12;
2633         checks_per_cycle = 2;
2634     }
2635 
2636     time_duration mission_time = calendar::turn - comp->companion_mission_time;
2637     if( one_in( danger ) && !survive_random_encounter( *comp, task_description, favor, threat ) ) {
2638         return false;
2639     }
2640     const std::string msg = string_format( _( "returns from %s carrying supplies and has a bit "
2641                                            "more experience…" ), task_description );
2642     finish_return( *comp, false, msg, skill_group, 1 );
2643 
2644     item_group_id itemlist( "forest" );
2645     if( task == "_faction_camp_firewood" ) {
2646         itemlist = item_group_id( "gathering_faction_base_camp_firewood" );
2647     } else if( task == "_faction_camp_gathering" ) {
2648         itemlist = get_gatherlist();
2649     } else if( task == "_faction_camp_foraging" ) {
2650         switch( season_of_year( calendar::turn ) ) {
2651             case SPRING:
2652                 itemlist = item_group_id( "foraging_faction_camp_spring" );
2653                 break;
2654             case SUMMER:
2655                 itemlist = item_group_id( "foraging_faction_camp_summer" );
2656                 break;
2657             case AUTUMN:
2658                 itemlist = item_group_id( "foraging_faction_camp_autumn" );
2659                 break;
2660             case WINTER:
2661                 itemlist = item_group_id( "foraging_faction_camp_winter" );
2662                 break;
2663             default:
2664                 debugmsg( "Invalid season" );
2665         }
2666     }
2667     if( task == "_faction_camp_trapping" || task == "_faction_camp_hunting" ) {
2668         hunting_results( skill, task, checks_per_cycle * mission_time / min_time, 30 );
2669     } else {
2670         search_results( skill, itemlist, checks_per_cycle * mission_time / min_time, 15 );
2671     }
2672 
2673     return true;
2674 }
2675 
fortifications_return()2676 void basecamp::fortifications_return()
2677 {
2678     npc_ptr comp = companion_choose_return( "_faction_camp_om_fortifications", 3_hours );
2679     if( comp != nullptr ) {
2680         std::string build_n = "faction_wall_level_N_0";
2681         std::string build_e = "faction_wall_level_E_0";
2682         std::string build_s = "faction_wall_level_S_0";
2683         std::string build_w = "faction_wall_level_W_0";
2684         if( comp->companion_mission_role_id == "faction_wall_level_N_1" ) {
2685             build_n = "faction_wall_level_N_1";
2686             build_e = "faction_wall_level_E_1";
2687             build_s = "faction_wall_level_S_1";
2688             build_w = "faction_wall_level_W_1";
2689         }
2690         std::string build_first = build_e;
2691         std::string build_second = build_w;
2692         bool build_dir_NS = comp->companion_mission_points[0].y() !=
2693                             comp->companion_mission_points[1].y();
2694         if( build_dir_NS ) {
2695             build_first = build_s;
2696             build_second = build_n;
2697         }
2698         //Add fences
2699         auto build_point = comp->companion_mission_points;
2700         for( size_t pt = 0; pt < build_point.size(); pt++ ) {
2701             //First point is always at top or west since they are built in a line and sorted
2702             if( pt == 0 ) {
2703                 run_mapgen_update_func( build_first, build_point[pt] );
2704             } else if( pt == build_point.size() - 1 ) {
2705                 run_mapgen_update_func( build_second, build_point[pt] );
2706             } else {
2707                 run_mapgen_update_func( build_first, build_point[pt] );
2708                 run_mapgen_update_func( build_second, build_point[pt] );
2709             }
2710             if( comp->companion_mission_role_id == "faction_wall_level_N_0" ) {
2711                 tripoint_abs_omt fort_point = build_point[pt];
2712                 fortifications.push_back( fort_point );
2713             }
2714         }
2715         const std::string msg = _( "returns from constructing fortifications…" );
2716         finish_return( *comp, true, msg, "construction", 2 );
2717     }
2718 }
2719 
recruit_return(const std::string & task,int score)2720 void basecamp::recruit_return( const std::string &task, int score )
2721 {
2722     const std::string msg = _( "returns from searching for recruits with "
2723                                "a bit more experience…" );
2724     npc_ptr comp = mission_return( task, 4_days, true, msg, "recruiting", 2 );
2725     if( comp == nullptr ) {
2726         return;
2727     }
2728 
2729     npc_ptr recruit;
2730     //Success of finding an NPC to recruit, based on survival/tracking
2731     int skill = comp->get_skill_level( skill_survival );
2732     if( rng( 1, 20 ) + skill > 17 ) {
2733         recruit = make_shared_fast<npc>();
2734         recruit->normalize();
2735         recruit->randomize();
2736         popup( _( "%s encountered %s…" ), comp->name, recruit->name );
2737     } else {
2738         popup( _( "%s didn't find anyone to recruit…" ), comp->name );
2739         return;
2740     }
2741     //Chance of convincing them to come back
2742     skill = ( 100 * comp->get_skill_level( skill_speech ) + score ) / 100;
2743     if( rng( 1, 20 ) + skill  > 19 ) {
2744         popup( _( "%s convinced %s to hear a recruitment offer from you…" ), comp->name,
2745                recruit->name );
2746     } else {
2747         popup( _( "%s wasn't interested in anything %s had to offer…" ), recruit->name,
2748                comp->name );
2749         return;
2750     }
2751     //Stat window
2752     int rec_m = 0;
2753     int appeal = rng( -5, 3 ) + std::min( skill / 3, 3 );
2754     int food_desire = rng( 0, 5 );
2755     while( true ) {
2756         std::string description = _( "NPC Overview:\n\n" );
2757         description += string_format( _( "Name:  %s\n\n" ), right_justify( recruit->name, 20 ) );
2758         description += string_format( _( "Strength:        %10d\n" ), recruit->str_max );
2759         description += string_format( _( "Dexterity:       %10d\n" ), recruit->dex_max );
2760         description += string_format( _( "Intelligence:    %10d\n" ), recruit->int_max );
2761         description += string_format( _( "Perception:      %10d\n\n" ), recruit->per_max );
2762         description += _( "Top 3 Skills:\n" );
2763 
2764         const auto skillslist = Skill::get_skills_sorted_by(
2765         [&]( const Skill & a, const Skill & b ) {
2766             const int level_a = recruit->get_skill_level( a.ident() );
2767             const int level_b = recruit->get_skill_level( b.ident() );
2768             return localized_compare( std::make_pair( -level_a, a.name() ),
2769                                       std::make_pair( -level_b, b.name() ) );
2770         } );
2771 
2772         description += string_format( "%s:          %4d\n", right_justify( skillslist[0]->name(), 12 ),
2773                                       recruit->get_skill_level( skillslist[0]->ident() ) );
2774         description += string_format( "%s:          %4d\n", right_justify( skillslist[1]->name(), 12 ),
2775                                       recruit->get_skill_level( skillslist[1]->ident() ) );
2776         description += string_format( "%s:          %4d\n\n", right_justify( skillslist[2]->name(), 12 ),
2777                                       recruit->get_skill_level( skillslist[2]->ident() ) );
2778 
2779         description += _( "Asking for:\n" );
2780         description += string_format( _( "> Food:     %10d days\n\n" ), food_desire );
2781         description += string_format( _( "Faction Food:%9d days\n\n" ),
2782                                       camp_food_supply( 0, true ) );
2783         description += string_format( _( "Recruit Chance: %10d%%\n\n" ),
2784                                       std::min( 100 * ( 10 + appeal ) / 20, 100 ) );
2785         description += _( "Select an option:" );
2786 
2787         std::vector<std::string> rec_options;
2788         rec_options.push_back( _( "Increase Food" ) );
2789         rec_options.push_back( _( "Decrease Food" ) );
2790         rec_options.push_back( _( "Make Offer" ) );
2791         rec_options.push_back( _( "Not Interested" ) );
2792 
2793         rec_m = uilist( description, rec_options );
2794         if( rec_m < 0 || rec_m == 3 || static_cast<size_t>( rec_m ) >= rec_options.size() ) {
2795             popup( _( "You decide you aren't interested…" ) );
2796             return;
2797         }
2798 
2799         if( rec_m == 0 && food_desire + 1 <= camp_food_supply( 0, true ) ) {
2800             food_desire++;
2801             appeal++;
2802         }
2803         if( rec_m == 1 ) {
2804             if( food_desire > 0 ) {
2805                 food_desire--;
2806                 appeal--;
2807             }
2808         }
2809         if( rec_m == 2 ) {
2810             break;
2811         }
2812     }
2813     // Roll for recruitment
2814     if( rng( 1, 20 ) + appeal >= 10 ) {
2815         popup( _( "%s has been convinced to join!" ), recruit->name );
2816     } else {
2817         popup( _( "%s wasn't interested…" ), recruit->name );
2818         // nullptr;
2819         return;
2820     }
2821     // Time durations always subtract from camp food supply
2822     camp_food_supply( 1_days * food_desire );
2823     avatar &player_character = get_avatar();
2824     recruit->spawn_at_precise( get_map().get_abs_sub().xy(),
2825                                player_character.pos() + point( -4, -4 ) );
2826     overmap_buffer.insert_npc( recruit );
2827     recruit->form_opinion( player_character );
2828     recruit->mission = NPC_MISSION_NULL;
2829     recruit->add_new_mission( mission::reserve_random( ORIGIN_ANY_NPC,
2830                               recruit->global_omt_location(),
2831                               recruit->getID() ) );
2832     talk_function::follow( *recruit );
2833     g->load_npcs();
2834 }
2835 
combat_mission_return(const std::string & miss)2836 void basecamp::combat_mission_return( const std::string &miss )
2837 {
2838     npc_ptr comp = companion_choose_return( miss, 3_hours );
2839     if( comp != nullptr ) {
2840         bool patrolling = miss == "_faction_camp_combat_0";
2841         comp_list patrol;
2842         npc_ptr guy = overmap_buffer.find_npc( comp->getID() );
2843         if( guy ) {
2844             patrol.push_back( guy );
2845         }
2846         for( auto pt : comp->companion_mission_points ) {
2847             const oter_id &omt_ref = overmap_buffer.ter( pt );
2848             int swim = comp->get_skill_level( skill_swimming );
2849             if( is_river( omt_ref ) && swim < 2 ) {
2850                 if( swim == 0 ) {
2851                     popup( _( "Your companion hit a river and didn't know how to swim…" ) );
2852                 } else {
2853                     popup( _( "Your companion hit a river and didn't know how to swim well "
2854                               "enough to cross…" ) );
2855                 }
2856                 break;
2857             }
2858             comp->death_drops = false;
2859             bool outcome = talk_function::companion_om_combat_check( patrol, pt, patrolling );
2860             comp->death_drops = true;
2861             if( outcome ) {
2862                 overmap_buffer.reveal( pt, 2 );
2863             } else if( comp->is_dead() ) {
2864                 popup( _( "%s didn't return from patrol…" ), comp->name );
2865                 comp->place_corpse( pt );
2866                 overmap_buffer.add_note( pt, "DEAD NPC" );
2867                 overmap_buffer.remove_npc( comp->getID() );
2868                 return;
2869             }
2870         }
2871         const std::string msg = _( "returns from patrol…" );
2872         finish_return( *comp, true, msg, "combat", 4 );
2873     }
2874 }
2875 
survey_return()2876 bool basecamp::survey_return()
2877 {
2878     npc_ptr comp = companion_choose_return( "_faction_camp_expansion", 3_hours );
2879     if( comp == nullptr ) {
2880         return false;
2881     }
2882 
2883     popup( _( "Select a tile up to %d tiles away." ), 1 );
2884     const tripoint_abs_omt where( ui::omap::choose_point() );
2885     if( where == overmap::invalid_tripoint ) {
2886         return false;
2887     }
2888 
2889     int dist = rl_dist( where.xy(), omt_pos.xy() );
2890     if( dist != 1 ) {
2891         popup( _( "You must select a tile within %d range of the camp" ), 1 );
2892         return false;
2893     }
2894     if( omt_pos.z() != where.z() ) {
2895         popup( _( "Expansions must be on the same level as the camp" ) );
2896         return false;
2897     }
2898     const point dir = talk_function::om_simple_dir( omt_pos, where );
2899     if( expansions.find( dir ) != expansions.end() ) {
2900         popup( _( "You already have an expansion at that location" ) );
2901         return false;
2902     }
2903 
2904     const oter_id &omt_ref = overmap_buffer.ter( where );
2905     const auto &pos_expansions = recipe_group::get_recipes_by_id( "all_faction_base_expansions",
2906                                  omt_ref.id().c_str() );
2907     if( pos_expansions.empty() ) {
2908         popup( _( "You can't build any expansions in a %s." ), omt_ref.id().c_str() );
2909         return false;
2910     }
2911 
2912     const recipe_id expansion_type = base_camps::select_camp_option( pos_expansions,
2913                                      _( "Select an expansion:" ) );
2914 
2915     if( !run_mapgen_update_func( expansion_type.str(), where ) ) {
2916         popup( _( "%s failed to add the %s expansion, perhaps there is a vehicle in the way." ),
2917                comp->disp_name(),
2918                expansion_type->blueprint_name() );
2919         return false;
2920     }
2921     overmap_buffer.ter_set( where, oter_id( expansion_type.str() ) );
2922     add_expansion( expansion_type.str(), where, dir );
2923     const std::string msg = _( "returns from surveying for the expansion." );
2924     finish_return( *comp, true, msg, "construction", 2 );
2925     return true;
2926 }
2927 
farm_return(const std::string & task,const tripoint_abs_omt & omt_tgt,farm_ops op)2928 bool basecamp::farm_return( const std::string &task, const tripoint_abs_omt &omt_tgt, farm_ops op )
2929 {
2930     const std::string msg = _( "returns from working your fields…" );
2931     npc_ptr comp = companion_choose_return( task, 15_minutes );
2932     if( comp == nullptr ) {
2933         return false;
2934     }
2935 
2936     farm_action( omt_tgt, op, comp );
2937 
2938     Character &player_character = get_player_character();
2939     //Give any seeds the NPC didn't use back to you.
2940     for( size_t i = 0; i < comp->companion_mission_inv.size(); i++ ) {
2941         for( const auto &it : comp->companion_mission_inv.const_stack( i ) ) {
2942             if( it.charges > 0 ) {
2943                 player_character.i_add( it );
2944             }
2945         }
2946     }
2947     finish_return( *comp, true, msg, "survival", 2 );
2948     return true;
2949 }
2950 
2951 // window manipulation
draw_camp_tabs(const catacurses::window & win,const base_camps::tab_mode cur_tab,const std::vector<std::vector<mission_entry>> & entries)2952 void talk_function::draw_camp_tabs( const catacurses::window &win,
2953                                     const base_camps::tab_mode cur_tab,
2954                                     const std::vector<std::vector<mission_entry>> &entries )
2955 {
2956     werase( win );
2957     const int width = getmaxx( win );
2958     mvwhline( win, point( 0, 2 ), LINE_OXOX, width );
2959 
2960     std::vector<std::string> tabs( base_camps::all_directions.size() );
2961     for( const auto &direction : base_camps::all_directions ) {
2962         tabs.at( direction.second.tab_order ) = direction.second.tab_title.translated();
2963     }
2964     const int tab_step = 3;
2965     int tab_space = 1;
2966     int tab_x = 0;
2967     for( auto &t : tabs ) {
2968         bool tab_empty = entries[tab_x + 1].empty();
2969         draw_subtab( win, tab_space, t, tab_x == cur_tab, false, tab_empty );
2970         tab_space += tab_step + utf8_width( t );
2971         tab_x++;
2972     }
2973     wnoutrefresh( win );
2974 }
2975 
name_mission_tabs(const tripoint_abs_omt & omt_pos,const std::string & role_id,const std::string & cur_title,base_camps::tab_mode cur_tab)2976 std::string talk_function::name_mission_tabs(
2977     const tripoint_abs_omt &omt_pos, const std::string &role_id,
2978     const std::string &cur_title, base_camps::tab_mode cur_tab )
2979 {
2980     if( role_id != base_camps::id ) {
2981         return cur_title;
2982     }
2983     cata::optional<basecamp *> temp_camp = overmap_buffer.find_camp( omt_pos.xy() );
2984     if( !temp_camp ) {
2985         return cur_title;
2986     }
2987     basecamp *bcp = *temp_camp;
2988     for( const auto &direction : base_camps::all_directions ) {
2989         if( cur_tab == direction.second.tab_order ) {
2990             return bcp->expansion_tab( direction.first );
2991         }
2992     }
2993     return bcp->expansion_tab( base_camps::base_dir );
2994 }
2995 
2996 // recipes and craft support functions
recipe_batch_max(const recipe & making) const2997 int basecamp::recipe_batch_max( const recipe &making ) const
2998 {
2999     int max_batch = 0;
3000     const int max_checks = 9;
3001     for( size_t batch_size = 1000; batch_size > 0; batch_size /= 10 ) {
3002         for( int iter = 0; iter < max_checks; iter++ ) {
3003             time_duration work_days = base_camps::to_workdays( making.batch_duration(
3004                                           get_player_character(), max_batch + batch_size ) );
3005             int food_req = time_to_food( work_days );
3006             bool can_make = making.deduped_requirements().can_make_with_inventory(
3007                                 _inv, making.get_component_filter(), max_batch + batch_size );
3008             if( can_make && camp_food_supply() > food_req ) {
3009                 max_batch += batch_size;
3010             } else {
3011                 break;
3012             }
3013         }
3014     }
3015     return max_batch;
3016 }
3017 
search_results(int skill,const item_group_id & group_id,int attempts,int difficulty)3018 void basecamp::search_results( int skill, const item_group_id &group_id, int attempts,
3019                                int difficulty )
3020 {
3021     for( int i = 0; i < attempts; i++ ) {
3022         if( skill > rng( 0, difficulty ) ) {
3023             item result = item_group::item_from( group_id, calendar::turn );
3024             if( !result.is_null() ) {
3025                 place_results( result );
3026             }
3027         }
3028     }
3029 }
3030 
hunting_results(int skill,const std::string & task,int attempts,int difficulty)3031 void basecamp::hunting_results( int skill, const std::string &task, int attempts, int difficulty )
3032 {
3033     // no item groups for corpses, so we'll have to improvise
3034     weighted_int_list<mtype_id> hunting_targets;
3035     hunting_targets.add( mon_beaver, 10 );
3036     hunting_targets.add( mon_fox_red, 10 );
3037     hunting_targets.add( mon_fox_gray, 10 );
3038     hunting_targets.add( mon_mink, 5 );
3039     hunting_targets.add( mon_muskrat, 10 );
3040     hunting_targets.add( mon_otter, 10 );
3041     hunting_targets.add( mon_duck, 10 );
3042     hunting_targets.add( mon_cockatrice, 1 );
3043     if( task == "_faction_camp_trapping" ) {
3044         hunting_targets.add( mon_black_rat, 40 );
3045         hunting_targets.add( mon_chipmunk, 30 );
3046         hunting_targets.add( mon_groundhog, 30 );
3047         hunting_targets.add( mon_hare, 20 );
3048         hunting_targets.add( mon_lemming, 40 );
3049         hunting_targets.add( mon_opossum, 10 );
3050         hunting_targets.add( mon_rabbit, 20 );
3051         hunting_targets.add( mon_squirrel, 20 );
3052         hunting_targets.add( mon_weasel, 20 );
3053         hunting_targets.add( mon_chicken, 10 );
3054         hunting_targets.add( mon_grouse, 10 );
3055         hunting_targets.add( mon_pheasant, 10 );
3056         hunting_targets.add( mon_turkey, 20 );
3057     } else if( task == "_faction_camp_hunting" ) {
3058         hunting_targets.add( mon_chicken, 20 );
3059         // good luck hunting upland game birds without dogs
3060         hunting_targets.add( mon_grouse, 2 );
3061         hunting_targets.add( mon_pheasant, 2 );
3062         hunting_targets.add( mon_turkey, 10 );
3063         hunting_targets.add( mon_bear, 1 );
3064         hunting_targets.add( mon_cougar, 5 );
3065         hunting_targets.add( mon_cow, 1 );
3066         hunting_targets.add( mon_coyote, 15 );
3067         hunting_targets.add( mon_deer, 2 );
3068         hunting_targets.add( mon_moose, 1 );
3069         hunting_targets.add( mon_pig, 1 );
3070         hunting_targets.add( mon_wolf, 10 );
3071     }
3072     for( int i = 0; i < attempts; i++ ) {
3073         if( skill > rng( 0, difficulty ) ) {
3074             const mtype_id *target = hunting_targets.pick();
3075             item result = item::make_corpse( *target, calendar::turn, "" );
3076             if( !result.is_null() ) {
3077                 place_results( result );
3078             }
3079         }
3080     }
3081 }
3082 
om_harvest_ter_est(npc & comp,const tripoint_abs_omt & omt_tgt,const ter_id & t,int chance)3083 int om_harvest_ter_est( npc &comp, const tripoint_abs_omt &omt_tgt, const ter_id &t, int chance )
3084 {
3085     return om_harvest_ter( comp, omt_tgt, t, chance, true, false );
3086 }
om_harvest_ter_break(npc & comp,const tripoint_abs_omt & omt_tgt,const ter_id & t,int chance)3087 int om_harvest_ter_break( npc &comp, const tripoint_abs_omt &omt_tgt, const ter_id &t, int chance )
3088 {
3089     return om_harvest_ter( comp, omt_tgt, t, chance, false, false );
3090 }
om_harvest_ter(npc & comp,const tripoint_abs_omt & omt_tgt,const ter_id & t,int chance,bool estimate,bool bring_back)3091 int om_harvest_ter( npc &comp, const tripoint_abs_omt &omt_tgt, const ter_id &t, int chance,
3092                     bool estimate, bool bring_back )
3093 {
3094     const ter_t &ter_tgt = t.obj();
3095     tinymap target_bay;
3096     target_bay.load( project_to<coords::sm>( omt_tgt ), false );
3097     int harvested = 0;
3098     int total = 0;
3099     tripoint mapmin = tripoint( 0, 0, omt_tgt.z() );
3100     tripoint mapmax = tripoint( 2 * SEEX - 1, 2 * SEEY - 1, omt_tgt.z() );
3101     for( const tripoint &p : target_bay.points_in_rectangle( mapmin, mapmax ) ) {
3102         if( target_bay.ter( p ) == t && x_in_y( chance, 100 ) ) {
3103             total++;
3104             if( estimate ) {
3105                 continue;
3106             }
3107             if( bring_back ) {
3108                 for( const item &itm : item_group::items_from( ter_tgt.bash.drop_group,
3109                         calendar::turn ) ) {
3110                     comp.companion_mission_inv.push_back( itm );
3111                 }
3112                 harvested++;
3113                 target_bay.ter_set( p, ter_tgt.bash.ter_set );
3114             }
3115         }
3116     }
3117     target_bay.save();
3118     if( bring_back ) {
3119         return harvested;
3120     }
3121     return total;
3122 }
3123 
om_cutdown_trees_est(const tripoint_abs_omt & omt_tgt,int chance)3124 int om_cutdown_trees_est( const tripoint_abs_omt &omt_tgt, int chance )
3125 {
3126     return om_cutdown_trees( omt_tgt, chance, true, false );
3127 }
om_cutdown_trees_logs(const tripoint_abs_omt & omt_tgt,int chance)3128 int om_cutdown_trees_logs( const tripoint_abs_omt &omt_tgt, int chance )
3129 {
3130     return om_cutdown_trees( omt_tgt, chance, false, true );
3131 }
om_cutdown_trees_trunks(const tripoint_abs_omt & omt_tgt,int chance)3132 int om_cutdown_trees_trunks( const tripoint_abs_omt &omt_tgt, int chance )
3133 {
3134     return om_cutdown_trees( omt_tgt, chance, false, false );
3135 }
om_cutdown_trees(const tripoint_abs_omt & omt_tgt,int chance,bool estimate,bool force_cut_trunk)3136 int om_cutdown_trees( const tripoint_abs_omt &omt_tgt, int chance, bool estimate,
3137                       bool force_cut_trunk )
3138 {
3139     tinymap target_bay;
3140     target_bay.load( project_to<coords::sm>( omt_tgt ), false );
3141     int harvested = 0;
3142     int total = 0;
3143     tripoint mapmin = tripoint( 0, 0, omt_tgt.z() );
3144     tripoint mapmax = tripoint( 2 * SEEX - 1, 2 * SEEY - 1, omt_tgt.z() );
3145     for( const tripoint &p : target_bay.points_in_rectangle( mapmin, mapmax ) ) {
3146         if( target_bay.ter( p ).obj().has_flag( flag_TREE ) && rng( 0, 100 ) < chance ) {
3147             total++;
3148             if( estimate ) {
3149                 continue;
3150             }
3151             // get a random number that is either 1 or -1
3152             point dir( 3 * ( 2 * rng( 0, 1 ) - 1 ) + rng( -1, 1 ), 3 * rng( -1, 1 ) + rng( -1, 1 ) );
3153             tripoint to = p + tripoint( dir, omt_tgt.z() );
3154             std::vector<tripoint> tree = line_to( p, to, rng( 1, 8 ) );
3155             for( auto &elem : tree ) {
3156                 target_bay.destroy( elem );
3157                 target_bay.ter_set( elem, t_trunk );
3158             }
3159             target_bay.ter_set( p, t_dirt );
3160             harvested++;
3161         }
3162     }
3163     if( estimate ) {
3164         return total;
3165     }
3166     if( !force_cut_trunk ) {
3167         target_bay.save();
3168         return harvested;
3169     }
3170     // having cut down the trees, cut the trunks into logs
3171     for( const tripoint &p : target_bay.points_in_rectangle( mapmin, mapmax ) ) {
3172         if( target_bay.ter( p ) == ter_id( "t_trunk" ) ) {
3173             target_bay.ter_set( p, t_dirt );
3174             target_bay.spawn_item( p, itype_log, rng( 2, 3 ), 0, calendar::turn );
3175             harvested++;
3176         }
3177     }
3178     target_bay.save();
3179     return harvested;
3180 }
3181 
om_harvest_itm(const npc_ptr & comp,const tripoint_abs_omt & omt_tgt,int chance,bool take)3182 mass_volume om_harvest_itm( const npc_ptr &comp, const tripoint_abs_omt &omt_tgt, int chance,
3183                             bool take )
3184 {
3185     tinymap target_bay;
3186     target_bay.load( project_to<coords::sm>( omt_tgt ), false );
3187     units::mass harvested_m = 0_gram;
3188     units::volume harvested_v = 0_ml;
3189     units::mass total_m = 0_gram;
3190     units::volume total_v = 0_ml;
3191     int total_num = 0;
3192     int harvested_num = 0;
3193     tripoint mapmin = tripoint( 0, 0, omt_tgt.z() );
3194     tripoint mapmax = tripoint( 2 * SEEX - 1, 2 * SEEY - 1, omt_tgt.z() );
3195     for( const tripoint &p : target_bay.points_in_rectangle( mapmin, mapmax ) ) {
3196         for( const item &i : target_bay.i_at( p ) ) {
3197             total_m += i.weight( true );
3198             total_v += i.volume( true );
3199             total_num += 1;
3200             if( take && x_in_y( chance, 100 ) ) {
3201                 if( comp ) {
3202                     comp->companion_mission_inv.push_back( i );
3203                 }
3204                 harvested_m += i.weight( true );
3205                 harvested_v += i.volume( true );
3206                 harvested_num += 1;
3207             }
3208         }
3209         if( take ) {
3210             target_bay.i_clear( p );
3211         }
3212     }
3213     target_bay.save();
3214     mass_volume results;
3215     if( take ) {
3216         results.wgt = harvested_m;
3217         results.vol = harvested_v;
3218         results.count = harvested_num;
3219     } else {
3220         results.wgt = total_m;
3221         results.vol = total_v;
3222         results.count = total_num;
3223     }
3224     return results;
3225 }
3226 
om_target_tile(const tripoint_abs_omt & omt_pos,int min_range,int range,const std::vector<std::string> & possible_om_types,ot_match_type match_type,bool must_see,bool popup_notice,const tripoint_abs_omt & source,bool bounce)3227 tripoint_abs_omt om_target_tile( const tripoint_abs_omt &omt_pos, int min_range, int range,
3228                                  const std::vector<std::string> &possible_om_types, ot_match_type match_type, bool must_see,
3229                                  bool popup_notice, const tripoint_abs_omt &source, bool bounce )
3230 {
3231     bool errors = false;
3232     if( popup_notice ) {
3233         popup( _( "Select a location between  %d and  %d tiles away." ), min_range, range );
3234     }
3235 
3236     std::vector<std::string> bounce_locations = { "faction_hide_site_0" };
3237 
3238     tripoint_abs_omt where;
3239     om_range_mark( omt_pos, range );
3240     om_range_mark( omt_pos, min_range, true, "Y;X: MIN RANGE" );
3241     if( source == tripoint_abs_omt( -999, -999, -999 ) ) {
3242         where = ui::omap::choose_point();
3243     } else {
3244         where = ui::omap::choose_point( source );
3245     }
3246     om_range_mark( omt_pos, range, false );
3247     om_range_mark( omt_pos, min_range, false, "Y;X: MIN RANGE" );
3248 
3249     if( where == overmap::invalid_tripoint ) {
3250         return tripoint_abs_omt( -999, -999, -999 );
3251     }
3252     int dist = rl_dist( where.xy(), omt_pos.xy() );
3253     if( dist > range || dist < min_range ) {
3254         popup( _( "You must select a target between %d and %d range from the base.  Range: %d" ),
3255                min_range, range, dist );
3256         errors = true;
3257     }
3258 
3259     tripoint_abs_omt omt_tgt = where;
3260 
3261     const oter_id &omt_ref = overmap_buffer.ter( omt_tgt );
3262 
3263     if( must_see && !overmap_buffer.seen( omt_tgt ) ) {
3264         errors = true;
3265         popup( _( "You must be able to see the target that you select." ) );
3266     }
3267 
3268     if( !errors ) {
3269         for( const std::string &pos_om : bounce_locations ) {
3270             if( bounce && omt_ref.id().c_str() == pos_om && range > 5 ) {
3271                 if( query_yn( _( "Do you want to bounce off this location to extend range?" ) ) ) {
3272                     om_line_mark( omt_pos, omt_tgt );
3273                     tripoint_abs_omt dest =
3274                         om_target_tile( omt_tgt, 2, range * .75, possible_om_types, match_type, true, false,
3275                                         omt_tgt, true );
3276                     om_line_mark( omt_pos, omt_tgt, false );
3277                     return dest;
3278                 }
3279             }
3280         }
3281 
3282         if( possible_om_types.empty() ) {
3283             return omt_tgt;
3284         }
3285 
3286         for( const std::string &pos_om : possible_om_types ) {
3287             if( is_ot_match( pos_om, omt_ref, match_type ) ) {
3288                 return omt_tgt;
3289             }
3290         }
3291     }
3292 
3293     return om_target_tile( omt_pos, min_range, range, possible_om_types, match_type );
3294 }
3295 
om_range_mark(const tripoint_abs_omt & origin,int range,bool add_notes,const std::string & message)3296 void om_range_mark( const tripoint_abs_omt &origin, int range, bool add_notes,
3297                     const std::string &message )
3298 {
3299     std::vector<tripoint_abs_omt> note_pts;
3300     //North Limit
3301     for( int x = origin.x() - range; x < origin.x() + range + 1; x++ ) {
3302         note_pts.push_back( tripoint_abs_omt( x, origin.y() - range, origin.z() ) );
3303     }
3304     //South
3305     for( int x = origin.x() - range; x < origin.x() + range + 1; x++ ) {
3306         note_pts.push_back( tripoint_abs_omt( x, origin.y() + range, origin.z() ) );
3307     }
3308     //West
3309     for( int y = origin.y() - range; y < origin.y() + range + 1; y++ ) {
3310         note_pts.push_back( tripoint_abs_omt( origin.x() - range, y, origin.z() ) );
3311     }
3312     //East
3313     for( int y = origin.y() - range; y < origin.y() + range + 1; y++ ) {
3314         note_pts.push_back( tripoint_abs_omt( origin.x() + range, y, origin.z() ) );
3315     }
3316 
3317     for( auto pt : note_pts ) {
3318         if( add_notes ) {
3319             if( !overmap_buffer.has_note( pt ) ) {
3320                 overmap_buffer.add_note( pt, message );
3321             }
3322         } else {
3323             if( overmap_buffer.has_note( pt ) && overmap_buffer.note( pt ) == message ) {
3324                 overmap_buffer.delete_note( pt );
3325             }
3326         }
3327     }
3328 }
3329 
om_line_mark(const tripoint_abs_omt & origin,const tripoint_abs_omt & dest,bool add_notes,const std::string & message)3330 void om_line_mark( const tripoint_abs_omt &origin, const tripoint_abs_omt &dest, bool add_notes,
3331                    const std::string &message )
3332 {
3333     std::vector<tripoint_abs_omt> note_pts = line_to( origin, dest );
3334 
3335     for( auto pt : note_pts ) {
3336         if( add_notes ) {
3337             if( !overmap_buffer.has_note( pt ) ) {
3338                 overmap_buffer.add_note( pt, message );
3339             }
3340         } else {
3341             if( overmap_buffer.has_note( pt ) && overmap_buffer.note( pt ) == message ) {
3342                 overmap_buffer.delete_note( pt );
3343             }
3344         }
3345     }
3346 }
3347 
get_mission_action_string(const std::string & input_mission)3348 std::string get_mission_action_string( const std::string &input_mission )
3349 {
3350     const base_camps::miss_data &miss_info = base_camps::miss_info[ input_mission ];
3351     return miss_info.action.translated();
3352 
3353 }
3354 
om_set_hide_site(npc & comp,const tripoint_abs_omt & omt_tgt,const std::vector<item * > & itms,const std::vector<item * > & itms_rem)3355 bool om_set_hide_site( npc &comp, const tripoint_abs_omt &omt_tgt,
3356                        const std::vector<item *> &itms,
3357                        const std::vector<item *> &itms_rem )
3358 {
3359     tinymap target_bay;
3360     target_bay.load( project_to<coords::sm>( omt_tgt ), false );
3361     target_bay.ter_set( point( 11, 10 ), t_improvised_shelter );
3362     for( item *i : itms_rem ) {
3363         comp.companion_mission_inv.add_item( *i );
3364         target_bay.i_rem( point( 11, 10 ), i );
3365     }
3366     Character &player_character = get_player_character();
3367     for( const item *i : itms ) {
3368         target_bay.add_item_or_charges( point( 11, 10 ), *i );
3369         player_character.use_amount( i->typeId(), 1 );
3370     }
3371     target_bay.save();
3372 
3373     overmap_buffer.ter_set( omt_tgt, oter_id( "faction_hide_site_0" ) );
3374 
3375     overmap_buffer.reveal( omt_tgt.xy(), 3, 0 );
3376     return true;
3377 }
3378 
3379 // path and travel time
companion_travel_time_calc(const tripoint_abs_omt & omt_pos,const tripoint_abs_omt & omt_tgt,time_duration work,int trips,int haulage)3380 time_duration companion_travel_time_calc( const tripoint_abs_omt &omt_pos,
3381         const tripoint_abs_omt &omt_tgt, time_duration work, int trips, int haulage )
3382 {
3383     std::vector<tripoint_abs_omt> journey = line_to( omt_pos, omt_tgt );
3384     return companion_travel_time_calc( journey, work, trips, haulage );
3385 }
3386 
companion_travel_time_calc(const std::vector<tripoint_abs_omt> & journey,time_duration work,int trips,int haulage)3387 time_duration companion_travel_time_calc( const std::vector<tripoint_abs_omt> &journey,
3388         time_duration work, int trips, int haulage )
3389 {
3390     int one_way = 0;
3391     for( const tripoint_abs_omt &om : journey ) {
3392         const oter_id &omt_ref = overmap_buffer.ter( om );
3393         std::string om_id = omt_ref.id().c_str();
3394         //Player walks 1 om is roughly 30 seconds
3395         if( om_id == "field" ) {
3396             one_way += 30 + 30 * haulage;
3397         } else if( is_ot_match( "forest_trail", omt_ref, ot_match_type::type ) ) {
3398             one_way += 35 + 30 * haulage;
3399         } else if( om_id == "forest" ) {
3400             one_way += 40 + 30 * haulage;
3401         } else if( om_id == "forest_thick" ) {
3402             one_way += 50 + 30 * haulage;
3403         } else if( om_id == "forest_water" ) {
3404             one_way += 60 + 30 * haulage;
3405         } else if( is_river( omt_ref ) ) {
3406             // hauling stuff over a river is slow, because you have to portage most items
3407             one_way += 200 + 40 * haulage;
3408         } else {
3409             one_way += 40 + 30 * haulage;
3410         }
3411     }
3412     return work + one_way * trips * 1_seconds;
3413 }
3414 
om_carry_weight_to_trips(const units::mass & mass,const units::volume & volume,const units::mass & carry_mass,const units::volume & carry_volume)3415 int om_carry_weight_to_trips( const units::mass &mass, const units::volume &volume,
3416                               const units::mass &carry_mass, const units::volume &carry_volume )
3417 {
3418     int trips_m = 1 + mass / carry_mass;
3419     int trips_v = 1 + volume / carry_volume;
3420     // return the number of round trips
3421     return 2 * std::max( trips_m, trips_v );
3422 }
3423 
om_carry_weight_to_trips(const std::vector<item * > & itms,const npc_ptr & comp)3424 int om_carry_weight_to_trips( const std::vector<item *> &itms, const npc_ptr &comp )
3425 {
3426     units::mass total_m = 0_gram;
3427     units::volume total_v = 0_ml;
3428     for( const auto &i : itms ) {
3429         total_m += i->weight( true );
3430         total_v += i->volume( true );
3431     }
3432     units::mass max_m = comp ? comp->weight_capacity() - comp->weight_carried() : 30_kilogram;
3433     //Assume an additional pack will be carried in addition to normal gear
3434     units::volume sack_v = item( itype_id( "makeshift_sling" ) ).contents.total_container_capacity();
3435     units::volume max_v = comp ? comp->free_space() : sack_v;
3436     max_v += sack_v;
3437     return om_carry_weight_to_trips( total_m, total_v, max_m, max_v );
3438 }
3439 
om_companion_path(const tripoint_abs_omt & start,int range_start,bool bounce)3440 std::vector<tripoint_abs_omt> om_companion_path( const tripoint_abs_omt &start, int range_start,
3441         bool bounce )
3442 {
3443     std::vector<tripoint_abs_omt> scout_points;
3444     tripoint_abs_omt last = start;
3445     int range = range_start;
3446     int def_range = range_start;
3447     while( range > 3 ) {
3448         tripoint_abs_omt spt = om_target_tile( last, 0, range, {}, ot_match_type::exact, false, true, last,
3449                                                false );
3450         if( spt == tripoint_abs_omt( -999, -999, -999 ) ) {
3451             scout_points.clear();
3452             return scout_points;
3453         }
3454         if( last == spt ) {
3455             break;
3456         }
3457         std::vector<tripoint_abs_omt> note_pts = line_to( last, spt );
3458         scout_points.insert( scout_points.end(), note_pts.begin(), note_pts.end() );
3459         om_line_mark( last, spt );
3460         range -= rl_dist( spt.xy(), last.xy() );
3461         last = spt;
3462 
3463         const oter_id &omt_ref = overmap_buffer.ter( last );
3464 
3465         if( bounce && omt_ref.id() == "faction_hide_site_0" ) {
3466             range = def_range * .75;
3467             def_range = range;
3468         }
3469     }
3470     for( auto pt : scout_points ) {
3471         om_line_mark( pt, pt, false );
3472     }
3473     return scout_points;
3474 }
3475 
3476 // camp utility functions
3477 // mission support functions
give_equipment(std::vector<item * > equipment,const std::string & msg)3478 std::vector<item *> basecamp::give_equipment( std::vector<item *> equipment,
3479         const std::string &msg )
3480 {
3481     std::vector<item *> equipment_lost;
3482     do {
3483         std::vector<std::string> names;
3484         names.reserve( equipment.size() );
3485         for( auto &i : equipment ) {
3486             names.push_back( i->tname() + " [" + std::to_string( i->charges ) + "]" );
3487         }
3488 
3489         // Choose item if applicable
3490         const int i_index = uilist( msg, names );
3491         if( i_index < 0 || static_cast<size_t>( i_index ) >= equipment.size() ) {
3492             return equipment_lost;
3493         }
3494         equipment_lost.push_back( equipment[i_index] );
3495         equipment.erase( equipment.begin() + i_index );
3496     } while( !equipment.empty() );
3497     return equipment_lost;
3498 }
3499 
validate_sort_points()3500 bool basecamp::validate_sort_points()
3501 {
3502     auto &mgr = zone_manager::get_manager();
3503     map &here = get_map();
3504     if( here.check_vehicle_zones( here.get_abs_sub().z ) ) {
3505         mgr.cache_vzones();
3506     }
3507     tripoint src_loc = here.getlocal( bb_pos ) + point_north;
3508     const tripoint abspos = here.getabs( get_player_character().pos() );
3509     if( !mgr.has_near( zone_type_CAMP_STORAGE, abspos, 60 ) ||
3510         !mgr.has_near( zone_type_CAMP_FOOD, abspos, 60 ) ) {
3511         if( query_yn( _( "You do not have sufficient sort zones.  Do you want to add them?" ) ) ) {
3512             return set_sort_points();
3513         } else {
3514             return false;
3515         }
3516     } else {
3517         const std::unordered_set<tripoint> &src_set = mgr.get_near( zone_type_CAMP_STORAGE, abspos );
3518         const std::vector<tripoint> &src_sorted = get_sorted_tiles_by_distance( abspos, src_set );
3519         // Find the nearest unsorted zone to dump objects at
3520         if( !src_sorted.empty() ) {
3521             src_loc = here.getlocal( src_sorted.front() );
3522         }
3523     }
3524     set_dumping_spot( here.getabs( src_loc ) );
3525     return true;
3526 }
3527 
set_sort_points()3528 bool basecamp::set_sort_points()
3529 {
3530     popup( _( "Sorting zones have changed.  Please create some sorting zones.  "
3531               "You must create a camp food zone, and a camp storage zone." ) );
3532     g->zones_manager();
3533     return validate_sort_points();
3534 }
3535 
3536 // camp analysis functions
om_building_region(const tripoint_abs_omt & omt_pos,int range,bool purge)3537 std::vector<std::pair<std::string, tripoint_abs_omt>> talk_function::om_building_region(
3538             const tripoint_abs_omt &omt_pos, int range, bool purge )
3539 {
3540     std::vector<std::pair<std::string, tripoint_abs_omt>> om_camp_region;
3541     for( const tripoint_abs_omt &omt_near_pos : points_in_radius( omt_pos, range ) ) {
3542         const oter_id &omt_rnear = overmap_buffer.ter( omt_near_pos );
3543         std::string om_rnear_id = omt_rnear.id().c_str();
3544         if( !purge || ( om_rnear_id.find( "faction_base_" ) != std::string::npos &&
3545                         om_rnear_id.find( "faction_base_camp" ) == std::string::npos ) ) {
3546             om_camp_region.push_back( std::make_pair( om_rnear_id, omt_near_pos ) );
3547         }
3548     }
3549     return om_camp_region;
3550 }
3551 
om_simple_dir(const tripoint_abs_omt & omt_pos,const tripoint_abs_omt & omt_tar)3552 point talk_function::om_simple_dir( const tripoint_abs_omt &omt_pos,
3553                                     const tripoint_abs_omt &omt_tar )
3554 {
3555     point_rel_omt diff = omt_tar.xy() - omt_pos.xy();
3556     return { clamp( diff.x(), -1, 1 ), clamp( diff.y(), -1, 1 ) };
3557 }
3558 
3559 // mission descriptions
camp_trip_description(const time_duration & total_time,const time_duration & working_time,const time_duration & travel_time,int distance,int trips,int need_food)3560 std::string camp_trip_description( const time_duration &total_time,
3561                                    const time_duration &working_time,
3562                                    const time_duration &travel_time, int distance, int trips,
3563                                    int need_food )
3564 {
3565     std::string entry = "\n";
3566     //A square is roughly 3 m
3567     int dist_m = distance * SEEX * 2 * 3;
3568     if( dist_m > 1000 ) {
3569         entry += string_format( _( ">Distance:%15.2f (km)\n" ), dist_m / 1000.0 );
3570         entry += string_format( _( ">One Way: %15d (trips)\n" ), trips );
3571         entry += string_format( _( ">Covered: %15.2f (km)\n" ), dist_m / 1000.0 * trips );
3572     } else {
3573         entry += string_format( _( ">Distance:%15d (m)\n" ), dist_m );
3574         entry += string_format( _( ">One Way: %15d (trips)\n" ), trips );
3575         entry += string_format( _( ">Covered: %15d (m)\n" ), dist_m * trips );
3576     }
3577     entry += string_format( _( ">Travel:  %s\n" ), right_justify( to_string( travel_time ), 23 ) );
3578     entry += string_format( _( ">Working: %s\n" ), right_justify( to_string( working_time ), 23 ) );
3579     entry += "----                   ----\n";
3580     entry += string_format( _( "Total:    %s\n" ), right_justify( to_string( total_time ), 23 ) );
3581     entry += string_format( _( "Food:     %15d (kcal)\n\n" ), need_food );
3582     return entry;
3583 }
3584 
craft_description(const recipe_id & itm)3585 std::string basecamp::craft_description( const recipe_id &itm )
3586 {
3587     const recipe &making = itm.obj();
3588 
3589     std::vector<std::string> component_print_buffer;
3590     int pane = FULL_SCREEN_WIDTH;
3591     const requirement_data &req = making.simple_requirements();
3592     auto tools = req.get_folded_tools_list( pane, c_white, _inv, 1 );
3593     auto comps = req.get_folded_components_list( pane, c_white, _inv,
3594                  making.get_component_filter(), 1 );
3595 
3596     component_print_buffer.insert( component_print_buffer.end(), tools.begin(), tools.end() );
3597     component_print_buffer.insert( component_print_buffer.end(), comps.begin(), comps.end() );
3598 
3599     std::string comp;
3600     for( auto &elem : component_print_buffer ) {
3601         comp = comp + elem + "\n";
3602     }
3603     comp = string_format( _( "Skill used: %s\nDifficulty: %d\n%s\nTime: %s\n" ),
3604                           making.skill_used.obj().name(), making.difficulty, comp,
3605                           to_string( base_camps::to_workdays( making.batch_duration( get_player_character() ) ) ) );
3606     return comp;
3607 }
3608 
recruit_evaluation(int & sbase,int & sexpansions,int & sfaction,int & sbonus) const3609 int basecamp::recruit_evaluation( int &sbase, int &sexpansions, int &sfaction, int &sbonus ) const
3610 {
3611     auto e = expansions.find( base_camps::base_dir );
3612     if( e == expansions.end() ) {
3613         sbase = 0;
3614         sexpansions = 0;
3615         sfaction = 0;
3616         sbonus = 0;
3617         return 0;
3618     }
3619     sbase = e->second.cur_level * 5;
3620     sexpansions = expansions.size() * 2;
3621 
3622     //How could we ever starve?
3623     //More than 5 farms at recruiting base
3624     int farm = 0;
3625     for( const point &dir : directions ) {
3626         if( has_provides( "farming", dir ) ) {
3627             farm++;
3628         }
3629     }
3630     sfaction = std::min( camp_food_supply() / 10000, 10 );
3631     sfaction += std::min( camp_discipline() / 10, 5 );
3632     sfaction += std::min( camp_morale() / 10, 5 );
3633 
3634     //Secret or Hidden Bonus
3635     //Please avoid openly discussing so that there is some mystery to the system
3636     sbonus = 0;
3637     if( farm >= 5 ) {
3638         sbonus += 10;
3639     }
3640     //More machine than man
3641     //Bionics count > 10, respect > 75
3642     if( get_player_character().get_bionics().size() > 10 && camp_discipline() > 75 ) {
3643         sbonus += 10;
3644     }
3645     //Survival of the fittest
3646     if( g->get_kill_tracker().npc_kill_count() > 10 ) {
3647         sbonus += 10;
3648     }
3649     return sbase + sexpansions + sfaction + sbonus;
3650 }
recruit_evaluation() const3651 int basecamp::recruit_evaluation() const
3652 {
3653     int sbase;
3654     int sexpansions;
3655     int sfaction;
3656     int sbonus;
3657     return recruit_evaluation( sbase, sexpansions, sfaction, sbonus );
3658 }
3659 
recruit_description(int npc_count)3660 std::string basecamp::recruit_description( int npc_count )
3661 {
3662     int sbase;
3663     int sexpansions;
3664     int sfaction;
3665     int sbonus;
3666     int total = recruit_evaluation( sbase, sexpansions, sfaction, sbonus );
3667     std::string desc = string_format( _( "Notes:\n"
3668                                          "Recruiting additional followers is very dangerous and "
3669                                          "expensive.  The outcome is heavily dependent on the "
3670                                          "skill of the  companion you send and the appeal of "
3671                                          "your base.\n\n"
3672                                          "Skill used: speech\n"
3673                                          "Difficulty: 2\n"
3674                                          "Base Score:                   +%3d%%\n"
3675                                          "> Expansion Bonus:            +%3d%%\n"
3676                                          "> Faction Bonus:              +%3d%%\n"
3677                                          "> Special Bonus:              +%3d%%\n\n"
3678                                          "Total: Skill                  +%3d%%\n\n"
3679                                          "Risk: High\n"
3680                                          "Time: 4 Days\n"
3681                                          "Positions: %d/1\n" ), sbase, sexpansions, sfaction,
3682                                       sbonus, total, npc_count );
3683     return desc;
3684 }
3685 
gathering_description(const std::string & bldg)3686 std::string basecamp::gathering_description( const std::string &bldg )
3687 {
3688     item_group_id itemlist;
3689     if( item_group::group_is_defined( item_group_id( "gathering_" + bldg ) ) ) {
3690         itemlist = item_group_id( "gathering_" + bldg );
3691     } else {
3692         itemlist = item_group_id( "forest" );
3693     }
3694     std::string output;
3695 
3696     // Functions like the debug item group tester but only rolls 6 times so the player
3697     // doesn't have perfect knowledge
3698     std::map<std::string, int> itemnames;
3699     for( size_t a = 0; a < 6; a++ ) {
3700         const auto items = item_group::items_from( itemlist, calendar::turn );
3701         for( const item &it : items ) {
3702             itemnames[it.display_name()]++;
3703         }
3704     }
3705     // Invert the map to get sorting!
3706     std::multimap<int, std::string> itemnames2;
3707     for( const auto &e : itemnames ) {
3708         itemnames2.insert( std::pair<int, std::string>( e.second, e.first ) );
3709     }
3710     for( const auto &e : itemnames2 ) {
3711         output = output + "> " + e.second + "\n";
3712     }
3713     return output;
3714 }
3715 
farm_description(const tripoint_abs_omt & farm_pos,size_t & plots_count,farm_ops operation)3716 std::string basecamp::farm_description( const tripoint_abs_omt &farm_pos, size_t &plots_count,
3717                                         farm_ops operation )
3718 {
3719     std::pair<size_t, std::string> farm_data = farm_action( farm_pos, operation );
3720     std::string entry;
3721     plots_count = farm_data.first;
3722     switch( operation ) {
3723         case farm_ops::harvest:
3724             entry += _( "Harvestable: " ) + std::to_string( plots_count ) + "\n" + farm_data.second;
3725             break;
3726         case farm_ops::plant:
3727             entry += _( "Ready for Planting: " ) + std::to_string( plots_count ) + "\n";
3728             break;
3729         case farm_ops::plow:
3730             entry += _( "Needs Plowing: " ) + std::to_string( plots_count ) + "\n";
3731             break;
3732         default:
3733             debugmsg( "Farm operations called with no operation" );
3734             break;
3735     }
3736     return entry;
3737 }
3738 
3739 // food supply
camp_food_supply(int change,bool return_days)3740 int camp_food_supply( int change, bool return_days )
3741 {
3742     faction *yours = get_player_character().get_faction();
3743     yours->food_supply += change;
3744     if( yours->food_supply < 0 ) {
3745         yours->likes_u += yours->food_supply / 1250;
3746         yours->respects_u += yours->food_supply / 625;
3747         yours->food_supply = 0;
3748     }
3749     if( return_days ) {
3750         return yours->food_supply / 2500;
3751     }
3752 
3753     return yours->food_supply;
3754 }
3755 
camp_food_supply(time_duration work)3756 int camp_food_supply( time_duration work )
3757 {
3758     return camp_food_supply( -time_to_food( work ) );
3759 }
3760 
time_to_food(time_duration work)3761 int time_to_food( time_duration work )
3762 {
3763     return 2500 * to_hours<int>( work ) / 24;
3764 }
3765 
3766 // mission support
distribute_food()3767 bool basecamp::distribute_food()
3768 {
3769     if( !validate_sort_points() ) {
3770         popup( _( "You do not have a camp food zone.  Aborting…" ) );
3771         return false;
3772     }
3773 
3774     map &here = get_map();
3775     auto &mgr = zone_manager::get_manager();
3776     if( here.check_vehicle_zones( here.get_abs_sub().z ) ) {
3777         mgr.cache_vzones();
3778     }
3779     const tripoint &abspos = get_dumping_spot();
3780     const std::unordered_set<tripoint> &z_food = mgr.get_near( zone_type_CAMP_FOOD, abspos, 60 );
3781 
3782     double quick_rot = 0.6 + ( has_provides( "pantry" ) ? 0.1 : 0 );
3783     double slow_rot = 0.8 + ( has_provides( "pantry" ) ? 0.05 : 0 );
3784     int total = 0;
3785 
3786     const auto rot_multip = [&]( const item & it, item * const container ) {
3787         if( !it.goes_bad() ) {
3788             return 1.;
3789         }
3790         float spoil_mod = 1.0f;
3791         if( container ) {
3792             if( item_pocket *const pocket = container->contained_where( it ) ) {
3793                 spoil_mod = pocket->spoil_multiplier();
3794             }
3795         }
3796         // Container seals and prevents any spoilage.
3797         if( spoil_mod == 0 ) {
3798             return 1.;
3799         }
3800         // @TODO: this does not handle fridges or things like root cellar, but maybe it shouldn't.
3801         const time_duration rots_in = ( it.get_shelf_life() - it.get_rot() ) / spoil_mod;
3802         if( rots_in >= 5_days ) {
3803             return 1.;
3804         } else if( rots_in >= 2_days ) {
3805             return slow_rot;
3806         } else {
3807             return quick_rot;
3808         }
3809     };
3810     const auto consume_non_recursive = [&]( item & it, item * const container ) {
3811         if( !it.is_comestible() ) {
3812             return false;
3813         }
3814         // Stuff like butchery refuse and other disgusting stuff
3815         if( it.get_comestible_fun() < -6 ) {
3816             return false;
3817         }
3818         if( it.rotten() ) {
3819             return false;
3820         }
3821         const int kcal = it.get_comestible()->default_nutrition.kcal() * it.count() * rot_multip( it,
3822                          container );
3823         if( kcal <= 0 ) {
3824             // can happen if calories is low and rot is high.
3825             return false;
3826         }
3827         total += kcal;
3828         return true;
3829     };
3830 
3831     // Returns whether the item should be removed from the map.
3832     const auto consume = [&]( item & it, item * const container ) {
3833         if( it.is_food_container() ) {
3834             std::vector<item *> to_remove;
3835             it.visit_items( [&]( item * content, item * parent ) {
3836                 if( consume_non_recursive( *content, parent ) ) {
3837                     to_remove.push_back( content );
3838                     return VisitResponse::SKIP;
3839                 }
3840                 return VisitResponse::NEXT;
3841             } );
3842             if( to_remove.empty() ) {
3843                 return false;
3844             }
3845             for( item *const food : to_remove ) {
3846                 it.remove_item( *food );
3847             }
3848             it.on_contents_changed();
3849             return false;
3850         }
3851         return consume_non_recursive( it, container );
3852     };
3853     for( const tripoint &p_food_stock_abs : z_food ) {
3854         // @FIXME: this will not handle zones in vehicle
3855         const tripoint p_food_stock = here.getlocal( p_food_stock_abs );
3856         map_stack items = here.i_at( p_food_stock );
3857         for( auto iter = items.begin(); iter != items.end(); ) {
3858             if( consume( *iter, nullptr ) ) {
3859                 iter = items.erase( iter );
3860             } else {
3861                 ++iter;
3862             }
3863         }
3864     }
3865 
3866     if( total <= 0 ) {
3867         popup( _( "No suitable items are located at the drop points…" ) );
3868         return false;
3869     }
3870 
3871     popup( _( "You distribute %d kcal worth of food to your companions." ), total );
3872     camp_food_supply( total );
3873     return true;
3874 }
3875 
3876 // morale
camp_discipline(int change)3877 int camp_discipline( int change )
3878 {
3879     faction *yours = get_player_character().get_faction();
3880     yours->respects_u += change;
3881     return yours->respects_u;
3882 }
3883 
camp_morale(int change)3884 int camp_morale( int change )
3885 {
3886     faction *yours = get_player_character().get_faction();
3887     yours->likes_u += change;
3888     return yours->likes_u;
3889 }
3890 
place_results(const item & result)3891 void basecamp::place_results( const item &result )
3892 {
3893     if( by_radio ) {
3894         tinymap target_bay;
3895         target_bay.load( project_to<coords::sm>( omt_pos ), false );
3896         const tripoint &new_spot = target_bay.getlocal( get_dumping_spot() );
3897         target_bay.add_item_or_charges( new_spot, result, true );
3898         apply_camp_ownership( new_spot, 10 );
3899         target_bay.save();
3900     } else {
3901         map &here = get_map();
3902         auto &mgr = zone_manager::get_manager();
3903         if( here.check_vehicle_zones( here.get_abs_sub().z ) ) {
3904             mgr.cache_vzones();
3905         }
3906         Character &player_character = get_player_character();
3907         const tripoint abspos = here.getabs( player_character.pos() );
3908         if( mgr.has_near( zone_type_CAMP_STORAGE, abspos ) ) {
3909             const std::unordered_set<tripoint> &src_set = mgr.get_near( zone_type_CAMP_STORAGE, abspos );
3910             const std::vector<tripoint> &src_sorted = get_sorted_tiles_by_distance( abspos, src_set );
3911             // Find the nearest unsorted zone to dump objects at
3912             for( const tripoint &src : src_sorted ) {
3913                 const tripoint &src_loc = here.getlocal( src );
3914                 here.add_item_or_charges( src_loc, result, true );
3915                 apply_camp_ownership( src_loc, 10 );
3916                 break;
3917             }
3918             //or dump them at players feet
3919         } else {
3920             here.add_item_or_charges( player_character.pos(), result, true );
3921             apply_camp_ownership( player_character.pos(), 0 );
3922         }
3923     }
3924 }
3925 
apply_camp_ownership(const tripoint & camp_pos,int radius)3926 void apply_camp_ownership( const tripoint &camp_pos, int radius )
3927 {
3928     map &here = get_map();
3929     for( const tripoint &p : here.points_in_rectangle( camp_pos + point( -radius, -radius ),
3930             camp_pos + point( radius, radius ) ) ) {
3931         map_stack items = here.i_at( p.xy() );
3932         for( item &elem : items ) {
3933             elem.set_owner( get_player_character() );
3934         }
3935     }
3936 }
3937 
3938 // combat and danger
3939 // this entire system is stupid
survive_random_encounter(npc & comp,std::string & situation,int favor,int threat)3940 bool survive_random_encounter( npc &comp, std::string &situation, int favor, int threat )
3941 {
3942     popup( _( "While %s, a silent specter approaches %s…" ), situation, comp.name );
3943     int skill_1 = comp.get_skill_level( skill_survival );
3944     int skill_2 = comp.get_skill_level( skill_speech );
3945     if( skill_1 + favor > rng( 0, 10 ) ) {
3946         popup( _( "%s notices the antlered horror and slips away before it gets too close." ),
3947                comp.name );
3948         talk_function::companion_skill_trainer( comp, "gathering", 10_minutes, 10 - favor );
3949     } else if( skill_2 + favor > rng( 0, 10 ) ) {
3950         popup( _( "Another survivor approaches %s asking for directions." ), comp.name );
3951         popup( _( "Fearful that he may be an agent of some hostile faction, "
3952                   "%s doesn't mention the camp." ), comp.name );
3953         popup( _( "The two part on friendly terms and the survivor isn't seen again." ) );
3954         talk_function::companion_skill_trainer( comp, "recruiting", 10_minutes, 10 - favor );
3955     } else {
3956         popup( _( "%s didn't detect the ambush until it was too late!" ), comp.name );
3957         int skill = comp.get_skill_level( skill_melee ) +
3958                     0.5 * comp.get_skill_level( skill_survival ) +
3959                     comp.get_skill_level( skill_bashing ) +
3960                     comp.get_skill_level( skill_cutting ) +
3961                     comp.get_skill_level( skill_stabbing ) +
3962                     comp.get_skill_level( skill_unarmed ) + comp.get_skill_level( skill_dodge );
3963         int monsters = rng( 0, threat );
3964         if( skill * rng( 8, 12 ) > ( monsters * rng( 8, 12 ) ) ) {
3965             if( one_in( 2 ) ) {
3966                 popup( _( "The bull moose charged %s from the tree line…" ), comp.name );
3967                 popup( _( "Despite being caught off guard %s was able to run away until the "
3968                           "moose gave up pursuit." ), comp.name );
3969             } else {
3970                 popup( _( "The jabberwock grabbed %s by the arm from behind and "
3971                           "began to scream." ), comp.name );
3972                 popup( _( "Terrified, %s spun around and delivered a massive kick "
3973                           "to the creature's torso…" ), comp.name );
3974                 popup( _( "Collapsing into a pile of gore, %s walked away unscathed…" ),
3975                        comp.name );
3976                 popup( _( "(Sounds like bullshit, you wonder what really happened.)" ) );
3977             }
3978             talk_function::companion_skill_trainer( comp, "combat", 10_minutes, 10 - favor );
3979         } else {
3980             if( one_in( 2 ) ) {
3981                 popup( _( "%s turned to find the hideous black eyes of a giant wasp "
3982                           "staring back from only a few feet away…" ), comp.name );
3983                 popup( _( "The screams were terrifying, there was nothing anyone could do." ) );
3984             } else {
3985                 popup( _( "Pieces of %s were found strewn across a few bushes." ), comp.name );
3986                 popup( _( "(You wonder if your companions are fit to work on their own…)" ) );
3987             }
3988             overmap_buffer.remove_npc( comp.getID() );
3989             return false;
3990         }
3991     }
3992     return true;
3993 }
3994