1 #include "mission_companion.h"
2 
3 #include <algorithm>
4 #include <cstdlib>
5 #include <functional>
6 #include <list>
7 #include <map>
8 #include <memory>
9 #include <set>
10 #include <tuple>
11 #include <unordered_map>
12 #include <utility>
13 #include <vector>
14 
15 #include "basecamp.h"
16 #include "bodypart.h"
17 #include "calendar.h"
18 #include "cata_assert.h"
19 #include "catacharset.h"
20 #include "character.h"
21 #include "colony.h"
22 #include "color.h"
23 #include "coordinates.h"
24 #include "creature.h"
25 #include "cursesdef.h"
26 #include "debug.h"
27 #include "enums.h"
28 #include "faction.h"
29 #include "faction_camp.h"
30 #include "game.h"
31 #include "game_constants.h"
32 #include "input.h"
33 #include "inventory.h"
34 #include "item.h"
35 #include "item_group.h"
36 #include "item_stack.h"
37 #include "itype.h"
38 #include "line.h"
39 #include "map.h"
40 #include "map_iterator.h"
41 #include "mapdata.h"
42 #include "memory_fast.h"
43 #include "messages.h"
44 #include "monster.h"
45 #include "mtype.h"
46 #include "npc.h"
47 #include "optional.h"
48 #include "output.h"
49 #include "overmap.h"
50 #include "overmapbuffer.h"
51 #include "point.h"
52 #include "rng.h"
53 #include "skill.h"
54 #include "string_formatter.h"
55 #include "translations.h"
56 #include "ui.h"
57 #include "ui_manager.h"
58 #include "value_ptr.h"
59 #include "weather.h"
60 #include "weighted_list.h"
61 
62 class character_id;
63 
64 static const efftype_id effect_riding( "riding" );
65 
66 static const itype_id itype_fungal_seeds( "fungal_seeds" );
67 static const itype_id itype_marloss_seed( "marloss_seed" );
68 
69 static const skill_id skill_bashing( "bashing" );
70 static const skill_id skill_cutting( "cutting" );
71 static const skill_id skill_dodge( "dodge" );
72 static const skill_id skill_fabrication( "fabrication" );
73 static const skill_id skill_gun( "gun" );
74 static const skill_id skill_melee( "melee" );
75 static const skill_id skill_stabbing( "stabbing" );
76 static const skill_id skill_survival( "survival" );
77 static const skill_id skill_unarmed( "unarmed" );
78 
79 static const trait_id trait_DEBUG_HS( "DEBUG_HS" );
80 static const trait_id trait_NPC_MISSION_LEV_1( "NPC_MISSION_LEV_1" );
81 static const trait_id trait_NPC_CONSTRUCTION_LEV_1( "NPC_CONSTRUCTION_LEV_1" );
82 static const trait_id trait_NPC_CONSTRUCTION_LEV_2( "NPC_CONSTRUCTION_LEV_2" );
83 
84 struct comp_rank {
85     int industry;
86     int combat;
87     int survival;
88 };
89 
mission_data()90 mission_data::mission_data()
91 {
92     for( int tab_num = base_camps::TAB_MAIN; tab_num != base_camps::TAB_NW + 3; tab_num++ ) {
93         std::vector<mission_entry> k;
94         entries.push_back( k );
95     }
96 }
97 
98 namespace talk_function
99 {
100 void scavenger_patrol( mission_data &mission_key, npc &p );
101 void scavenger_raid( mission_data &mission_key, npc &p );
102 void commune_menial( mission_data &mission_key, npc &p );
103 void commune_carpentry( mission_data &mission_key, npc &p );
104 void commune_farmfield( mission_data &mission_key, npc &p );
105 void commune_forage( mission_data &mission_key, npc &p );
106 void commune_refuge_caravan( mission_data &mission_key, npc &p );
107 bool handle_outpost_mission( const mission_entry &cur_key, npc &p );
108 } // namespace talk_function
109 
companion_mission(npc & p)110 void talk_function::companion_mission( npc &p )
111 {
112     mission_data mission_key;
113 
114     std::string role_id = p.companion_mission_role_id;
115     const tripoint_abs_omt omt_pos = p.global_omt_location();
116     std::string title = _( "Outpost Missions" );
117     if( role_id == "SCAVENGER" ) {
118         title = _( "Junk Shop Missions" );
119         scavenger_patrol( mission_key, p );
120         if( p.has_trait( trait_NPC_MISSION_LEV_1 ) ) {
121             scavenger_raid( mission_key, p );
122         }
123     } else if( role_id == "COMMUNE CROPS" ) {
124         title = _( "Agricultural Missions" );
125         commune_farmfield( mission_key, p );
126         commune_forage( mission_key, p );
127         commune_refuge_caravan( mission_key, p );
128     } else if( role_id == "FOREMAN" ) {
129         title = _( "Construction Missions" );
130         commune_menial( mission_key, p );
131         if( p.has_trait( trait_NPC_MISSION_LEV_1 ) ) {
132             commune_carpentry( mission_key, p );
133         }
134     } else if( role_id == "REFUGEE MERCHANT" ) {
135         title = _( "Free Merchant Missions" );
136         commune_refuge_caravan( mission_key, p );
137     } else {
138         return;
139     }
140     if( display_and_choose_opts( mission_key, omt_pos, role_id, title ) ) {
141         handle_outpost_mission( mission_key.cur_key, p );
142     }
143 }
144 
scavenger_patrol(mission_data & mission_key,npc & p)145 void talk_function::scavenger_patrol( mission_data &mission_key, npc &p )
146 {
147     std::string entry = _( "Profit: $25-$500\nDanger: Low\nTime: 10 hour missions\n\n"
148                            "Assigning one of your allies to patrol the surrounding wilderness "
149                            "and isolated buildings presents the opportunity to build survival "
150                            "skills while engaging in relatively safe combat against isolated "
151                            "creatures." );
152     mission_key.add( "Assign Scavenging Patrol", _( "Assign Scavenging Patrol" ), entry );
153     std::vector<npc_ptr> npc_list = companion_list( p, "_scavenging_patrol" );
154     if( !npc_list.empty() ) {
155         entry = _( "Profit: $25-$500\nDanger: Low\nTime: 10 hour missions\n\nPatrol Roster:\n" );
156         for( auto &elem : npc_list ) {
157             entry = entry + "  " + elem->name + " [" + std::to_string( to_hours<int>( calendar::turn -
158                     elem->companion_mission_time ) ) + _( " hours]\n" );
159         }
160         entry = entry + _( "\n\nDo you wish to bring your allies back into your party?" );
161         mission_key.add( "Retrieve Scavenging Patrol", _( "Retrieve Scavenging Patrol" ), entry );
162     }
163 }
164 
scavenger_raid(mission_data & mission_key,npc & p)165 void talk_function::scavenger_raid( mission_data &mission_key, npc &p )
166 {
167     std::string entry = _( "Profit: $200-$1000\nDanger: Medium\nTime: 10 hour missions\n\n"
168                            "Scavenging raids target formerly populated areas to loot as many "
169                            "valuable items as possible before being surrounded by the undead.  "
170                            "Combat is to be expected and assistance from the rest of the party "
171                            "can't be guaranteed.  The rewards are greater and there is a chance "
172                            "of the companion bringing back items." );
173     mission_key.add( "Assign Scavenging Raid", _( "Assign Scavenging Raid" ), entry );
174     std::vector<npc_ptr> npc_list = companion_list( p, "_scavenging_raid" );
175     if( !npc_list.empty() ) {
176         entry = _( "Profit: $200-$1000\nDanger: Medium\nTime: 10 hour missions\n\n"
177                    "Raid Roster:\n" );
178         for( auto &elem : npc_list ) {
179             entry = entry + "  " + elem->name + " [" + std::to_string( to_hours<int>( calendar::turn -
180                     elem->companion_mission_time ) ) + _( " hours]\n" );
181         }
182         entry = entry + _( "\n\nDo you wish to bring your allies back into your party?" );
183         mission_key.add( "Retrieve Scavenging Raid", _( "Retrieve Scavenging Raid" ), entry );
184     }
185 }
186 
commune_menial(mission_data & mission_key,npc & p)187 void talk_function::commune_menial( mission_data &mission_key, npc &p )
188 {
189     mission_key.add( "Assign Ally to Menial Labor", _( "Assign Ally to Menial Labor" ) );
190     std::vector<npc_ptr> npc_list = companion_list( p, "_labor" );
191     if( !npc_list.empty() ) {
192         std::string entry = _( "Profit: $8/hour\nDanger: Minimal\nTime: 1 hour minimum\n\n"
193                                "Assigning one of your allies to menial labor is a safe way to teach "
194                                "them basic skills and build reputation with the outpost.  Don't expect "
195                                "much of a reward though.\n\nLabor Roster:\n" );
196         for( auto &elem : npc_list ) {
197             entry = entry + "  " + elem->name + " [" + std::to_string( to_hours<int>( calendar::turn -
198                     elem->companion_mission_time ) ) + _( " hours]\n" );
199         }
200         entry = entry + _( "\n\nDo you wish to bring your allies back into your party?" );
201         mission_key.add( "Recover Ally from Menial Labor", _( "Recover Ally from Menial Labor" ),
202                          entry );
203     }
204 }
205 
commune_carpentry(mission_data & mission_key,npc & p)206 void talk_function::commune_carpentry( mission_data &mission_key, npc &p )
207 {
208     std::string entry = _( "Profit: $12/hour\nDanger: Minimal\nTime: 1 hour minimum\n\n"
209                            "Carpentry work requires more skill than menial labor while offering "
210                            "modestly improved pay.  It is unlikely that your companions will face "
211                            "combat but there are hazards working on makeshift buildings." );
212     mission_key.add( "Assign Ally to Carpentry Work", _( "Assign Ally to Carpentry Work" ), entry );
213     std::vector<npc_ptr>  npc_list = companion_list( p, "_carpenter" );
214     if( !npc_list.empty() ) {
215         entry = _( "Profit: $12/hour\nDanger: Minimal\nTime: 1 hour minimum\n\nLabor Roster:\n" );
216         for( auto &elem : npc_list ) {
217             entry = entry + "  " + elem->name + " [" + std::to_string( to_hours<int>( calendar::turn -
218                     elem->companion_mission_time ) ) + _( " hours]\n" );
219         }
220         entry = entry + _( "\n\nDo you wish to bring your allies back into your party?" );
221         mission_key.add( "Recover Ally from Carpentry Work",
222                          _( "Recover Ally from Carpentry Work" ), entry );
223     }
224 }
225 
commune_farmfield(mission_data & mission_key,npc & p)226 void talk_function::commune_farmfield( mission_data &mission_key, npc &p )
227 {
228     if( !p.has_trait( trait_NPC_CONSTRUCTION_LEV_1 ) ) {
229         std::string entry = _( "Cost: $1000\n\n\n"
230                                "                .........\n" // NOLINT(cata-text-style)
231                                "                .........\n" // NOLINT(cata-text-style)
232                                "                .........\n" // NOLINT(cata-text-style)
233                                "                .........\n" // NOLINT(cata-text-style)
234                                "                .........\n" // NOLINT(cata-text-style)
235                                "                .........\n" // NOLINT(cata-text-style)
236                                "                ..#....**\n" // NOLINT(cata-text-style)
237                                "                ..#Ov..**\n" // NOLINT(cata-text-style)
238                                "                ...O|....\n\n" // NOLINT(cata-text-style)
239                                "We're willing to let you purchase a field at a substantial "
240                                "discount to use for your own agricultural enterprises.  We'll "
241                                "plow it for you so you know exactly what is yours… after you "
242                                "have a field you can hire workers to plant or harvest crops for "
243                                "you.  If the crop is something we have a demand for, we'll be "
244                                "willing to liquidate it." );
245         mission_key.add( "Purchase East Field", _( "Purchase East Field" ), entry );
246     }
247     if( p.has_trait( trait_NPC_CONSTRUCTION_LEV_1 ) && !p.has_trait( trait_NPC_CONSTRUCTION_LEV_2 ) ) {
248         std::string entry = _( "Cost: $5500\n\n"
249                                "\n              ........." // NOLINT(cata-text-style)
250                                "\n              ........." // NOLINT(cata-text-style)
251                                "\n              ........." // NOLINT(cata-text-style)
252                                "\n              ........." // NOLINT(cata-text-style)
253                                "\n              ........." // NOLINT(cata-text-style)
254                                "\n              ........." // NOLINT(cata-text-style)
255                                "\n              ..#....**" // NOLINT(cata-text-style)
256                                "\n              ..#Ov..**" // NOLINT(cata-text-style)
257                                "\n              ...O|....\n\n" // NOLINT(cata-text-style)
258                                "Protecting your field with a sturdy picket fence will keep most "
259                                "wildlife from nibbling your crops apart.  You can expect yields to "
260                                "increase." );
261         mission_key.add( "Upgrade East Field I", _( "Upgrade East Field I" ), entry );
262     }
263 
264     if( p.has_trait( trait_NPC_CONSTRUCTION_LEV_1 ) ) {
265         std::string entry = _( "Cost: $3.00/plot\n\n"
266                                "\n              ........." // NOLINT(cata-text-style)
267                                "\n              ........." // NOLINT(cata-text-style)
268                                "\n              ........." // NOLINT(cata-text-style)
269                                "\n              ........." // NOLINT(cata-text-style)
270                                "\n              ........." // NOLINT(cata-text-style)
271                                "\n              ........." // NOLINT(cata-text-style)
272                                "\n              ..#....**" // NOLINT(cata-text-style)
273                                "\n              ..#Ov..**" // NOLINT(cata-text-style)
274                                "\n              ...O|....\n\n" // NOLINT(cata-text-style)
275                                "We'll plant the field with your choice of crop if you are willing "
276                                "to finance it.  When the crop is ready to harvest you can have us "
277                                "liquidate it or harvest it for you." );
278         mission_key.add( "Plant East Field", _( "Plant East Field" ), entry );
279         entry = _( "Cost: $2.00/plot\n\n"
280                    "\n              ........." // NOLINT(cata-text-style)
281                    "\n              ........." // NOLINT(cata-text-style)
282                    "\n              ........." // NOLINT(cata-text-style)
283                    "\n              ........." // NOLINT(cata-text-style)
284                    "\n              ........." // NOLINT(cata-text-style)
285                    "\n              ........." // NOLINT(cata-text-style)
286                    "\n              ..#....**" // NOLINT(cata-text-style)
287                    "\n              ..#Ov..**" // NOLINT(cata-text-style)
288                    "\n              ...O|....\n\n" // NOLINT(cata-text-style)
289                    "You can either have us liquidate the crop and give you the cash or pay us to "
290                    "harvest it for you." );
291         mission_key.add( "Harvest East Field", _( "Harvest East Field" ), entry );
292     }
293 }
294 
commune_forage(mission_data & mission_key,npc & p)295 void talk_function::commune_forage( mission_data &mission_key, npc &p )
296 {
297     std::string entry = _( "Profit: $10/hour\nDanger: Low\nTime: 4 hour minimum\n\n"
298                            "Foraging for food involves dispatching a companion to search the "
299                            "surrounding wilderness for wild edibles.  Combat will be avoided but "
300                            "encounters with wild animals are to be expected.  The low pay is "
301                            "supplemented with the odd item as a reward for particularly large "
302                            "hauls." );
303     mission_key.add( "Assign Ally to Forage for Food", _( "Assign Ally to Forage for Food" ),
304                      entry );
305     std::vector<npc_ptr> npc_list = companion_list( p, "_forage" );
306     if( !npc_list.empty() ) {
307         entry = _( "Profit: $10/hour\nDanger: Low\nTime: 4 hour minimum\n\nLabor Roster:\n" );
308         for( auto &elem : npc_list ) {
309             entry = entry + "  " + elem->name + " [" + std::to_string( to_hours<int>( calendar::turn -
310                     elem->companion_mission_time ) ) + _( " hours]\n" );
311         }
312         entry = entry + _( "\n\nDo you wish to bring your allies back into your party?" );
313         mission_key.add( "Recover Ally from Foraging", _( "Recover Ally from Foraging" ), entry );
314     }
315 }
316 
commune_refuge_caravan(mission_data & mission_key,npc & p)317 void talk_function::commune_refuge_caravan( mission_data &mission_key, npc &p )
318 {
319     std::string entry = _( "Profit: $18/hour\nDanger: High\nTime: UNKNOWN\n\n"
320                            "Adding companions to the caravan team increases the likelihood of "
321                            "success.  By nature, caravans are extremely tempting targets for "
322                            "raiders or hostile groups so only a strong party is recommended.  The "
323                            "rewards are significant for those participating but are even more "
324                            "important for the factions that profit.\n\n"
325                            "The commune is sending food to the Free Merchants in the Refugee "
326                            "Center as part of a tax and in exchange for skilled labor." );
327     mission_key.add( "Caravan Commune-Refugee Center", _( "Caravan Commune-Refugee Center" ),
328                      entry );
329     std::vector<npc_ptr> npc_list = companion_list( p, "_commune_refugee_caravan" );
330     std::vector<npc_ptr> npc_list_aux;
331     if( !npc_list.empty() ) {
332         entry = _( "Profit: $18/hour\nDanger: High\nTime: UNKNOWN\n\n"
333                    "\nRoster:\n" );
334         for( auto &elem : npc_list ) {
335             if( elem->companion_mission_time == calendar::before_time_starts ) {
336                 entry = entry + "  " + elem->name + _( " [READY]\n" );
337                 npc_list_aux.push_back( elem );
338             } else if( calendar::turn >= elem->companion_mission_time ) {
339                 entry = entry + "  " + elem->name + _( " [COMPLETE]\n" );
340             } else {
341                 entry = entry + "  " + elem->name + " [" + std::to_string( std::abs( to_hours<int>
342                         ( calendar::turn - elem->companion_mission_time ) ) ) + _( " Hours]\n" );
343             }
344         }
345         if( !npc_list_aux.empty() ) {
346             std::string entry_aux = _( "Profit: $18/hour\nDanger: High\nTime: UNKNOWN\n\n"
347                                        "\nRoster:\n" );
348             const std::string entry_suffix = _( " [READY]\n" );
349             for( auto &elem : npc_list_aux ) {
350                 if( elem->companion_mission_time == calendar::before_time_starts ) {
351                     entry_aux = entry_aux + "  " + elem->name + entry_suffix;
352                 }
353             }
354             entry_aux = entry_aux + _( "\n\n"
355                                        "The caravan will contain two or three additional members "
356                                        "from the commune, are you ready to depart?" );
357             mission_key.add( "Begin Commune-Refugee Center Run",
358                              _( "Begin Commune-Refugee Center Run" ), entry );
359         }
360         entry = entry + _( "\n\nDo you wish to bring your allies back into your party?" );
361         mission_key.add( "Recover Commune-Refugee Center", _( "Recover Commune-Refugee Center" ),
362                          entry );
363     }
364 }
365 
display_and_choose_opts(mission_data & mission_key,const tripoint_abs_omt & omt_pos,const std::string & role_id,const std::string & title)366 bool talk_function::display_and_choose_opts(
367     mission_data &mission_key, const tripoint_abs_omt &omt_pos, const std::string &role_id,
368     const std::string &title )
369 {
370     if( mission_key.entries.empty() ) {
371         popup( _( "There are no missions at this colony.  Press Spacebar…" ) );
372         return false;
373     }
374 
375     int TITLE_TAB_HEIGHT = 0;
376     if( role_id == "FACTION_CAMP" ) {
377         TITLE_TAB_HEIGHT = 1;
378     }
379 
380     base_camps::tab_mode tab_mode = base_camps::TAB_MAIN;
381 
382     size_t sel = 0;
383 
384     // The following are for managing the right pane scrollbar.
385     size_t info_offset = 0;
386     nc_color col = c_white;
387     std::vector<std::string> name_text;
388     std::vector<std::string> mission_text;
389 
390     input_context ctxt( "FACTIONS" );
391     ctxt.register_action( "UP", to_translation( "Move cursor up" ) );
392     ctxt.register_action( "DOWN", to_translation( "Move cursor down" ) );
393     ctxt.register_action( "NEXT_TAB" );
394     ctxt.register_action( "PREV_TAB" );
395     ctxt.register_action( "PAGE_UP" );
396     ctxt.register_action( "PAGE_DOWN" );
397     ctxt.register_action( "CONFIRM" );
398     ctxt.register_action( "QUIT" );
399     ctxt.register_action( "HELP_KEYBINDINGS" );
400     std::vector<mission_entry> cur_key_list;
401 
402     auto reset_cur_key_list = [&]() {
403         cur_key_list = mission_key.entries[0];
404         for( const auto &k : mission_key.entries[1] ) {
405             bool has = false;
406             for( const auto &keys : cur_key_list ) {
407                 if( k.id == keys.id ) {
408                     has = true;
409                     break;
410                 }
411             }
412             if( !has ) {
413                 cur_key_list.push_back( k );
414             }
415         }
416     };
417 
418     reset_cur_key_list();
419 
420     if( cur_key_list.empty() ) {
421         popup( _( "There are no missions at this colony.  Press Spacebar…" ) );
422         return false;
423     }
424 
425     size_t part_y = 0;
426     size_t part_x = 0;
427     size_t maxy = 0;
428     size_t maxx = 0;
429     size_t info_height = 0;
430     size_t info_width = 0;
431 
432     catacurses::window w_list;
433     catacurses::window w_tabs;
434     catacurses::window w_info;
435 
436     ui_adaptor ui;
437     ui.on_screen_resize( [&]( ui_adaptor & ui ) {
438         part_y = TERMY > FULL_SCREEN_HEIGHT ? ( TERMY - FULL_SCREEN_HEIGHT ) / 4 : 0;
439         part_x = TERMX > FULL_SCREEN_WIDTH ? ( TERMX - FULL_SCREEN_WIDTH ) / 4 : 0;
440         maxy = part_y ? TERMY - 2 * part_y : FULL_SCREEN_HEIGHT;
441         maxx = part_x ? TERMX - 2 * part_x : FULL_SCREEN_WIDTH;
442         info_height = maxy - 3;
443         info_width = maxx - 1 - MAX_FAC_NAME_SIZE;
444 
445         w_list = catacurses::newwin( maxy, maxx,
446                                      point( part_x, part_y + TITLE_TAB_HEIGHT ) );
447         w_info = catacurses::newwin( info_height, info_width,
448                                      point( part_x + MAX_FAC_NAME_SIZE, part_y + TITLE_TAB_HEIGHT + 1 ) );
449 
450         if( role_id == "FACTION_CAMP" ) {
451             w_tabs = catacurses::newwin( TITLE_TAB_HEIGHT, maxx, point( part_x, part_y ) );
452             ui.position( point( part_x, part_y ), point( maxx, maxy + TITLE_TAB_HEIGHT ) );
453         } else {
454             ui.position( point( part_x, part_y + TITLE_TAB_HEIGHT ), point( maxx, maxy ) );
455         }
456     } );
457     ui.mark_resize();
458 
459     ui.on_redraw( [&]( const ui_adaptor & ) {
460         werase( w_list );
461         draw_border( w_list );
462         // NOLINTNEXTLINE(cata-use-named-point-constants)
463         mvwprintz( w_list, point( 1, 1 ), c_white, name_mission_tabs( omt_pos, role_id, title,
464                    tab_mode ) );
465 
466         std::vector<std::vector<std::string>> folded_names;
467         size_t folded_names_lines = 0;
468         for( const auto &cur_key_entry : cur_key_list ) {
469             std::vector<std::string> f_name = foldstring( cur_key_entry.name_display, MAX_FAC_NAME_SIZE - 5,
470                                               ' ' );
471             folded_names_lines += f_name.size();
472             folded_names.emplace_back( f_name );
473         }
474 
475         int name_offset = 0;
476         size_t sel_pos = 0;
477         // Translate from entry index to line index
478         for( size_t i = 0; i < sel; i++ ) {
479             sel_pos += folded_names[i].size();
480         }
481 
482         calcStartPos( name_offset, sel_pos, info_height, folded_names_lines );
483 
484         int name_index = 0;
485         // Are we so far down the list that we bump into the end?
486         bool last_section = folded_names_lines < info_height ||
487                             folded_names_lines - info_height <= static_cast<size_t>( name_offset );
488 
489         // Translate back from desired line index to the corresponding entry, making sure to round up
490         // near the end to ensure the last line gets included.
491         if( name_offset > 0 ) {
492             for( size_t i = 0; i < cur_key_list.size(); i++ ) {
493                 name_offset -= folded_names[i].size();
494                 if( name_offset <= 0 ) {
495                     if( name_offset == 0 || last_section ) {
496                         name_index++;
497                     }
498                     break;
499                 }
500                 name_index++;
501             }
502         }
503 
504         size_t list_line = 2;
505         for( size_t current = name_index; list_line < info_height + 2 &&
506              current < cur_key_list.size(); current++ ) {
507             nc_color col = ( current == sel ? h_white : c_white );
508             //highlight important missions
509             for( const auto &k : mission_key.entries[0] ) {
510                 if( cur_key_list[current].id == k.id ) {
511                     col = ( current == sel ? h_white : c_yellow );
512                     break;
513                 }
514             }
515             //dull uncraftable items
516             for( const auto &k : mission_key.entries[10] ) {
517                 if( cur_key_list[current].id == k.id ) {
518                     col = ( current == sel ? h_white : c_dark_gray );
519                     break;
520                 }
521             }
522             std::vector<std::string> &name_text = folded_names[current];
523             if( list_line + name_text.size() > info_height + 2 ) {
524                 break;  //  Skip last entry that would spill over into the bar.
525             }
526             for( size_t name_line = 0; name_line < name_text.size(); name_line++ ) {
527                 print_colored_text( w_list, point( name_line ? 5 : 1, list_line ),
528                                     col, col, name_text[name_line] );
529                 list_line += 1;
530             }
531         }
532 
533         if( folded_names_lines > info_height ) {
534             scrollbar()
535             .offset_x( 0 )
536             .offset_y( 1 )
537             .content_size( folded_names_lines )
538             .viewport_pos( sel_pos )
539             .viewport_size( info_height )
540             .apply( w_list );
541         }
542         wnoutrefresh( w_list );
543         werase( w_info );
544 
545         // Fold mission text, store it for scrolling
546         mission_text = foldstring( mission_key.cur_key.text, info_width - 2, ' ' );
547         if( info_height >= mission_text.size() ) {
548             info_offset = 0;
549         } else if( info_offset + info_height > mission_text.size() ) {
550             info_offset = mission_text.size() - info_height;
551         }
552         if( mission_text.size() > info_height ) {
553             scrollbar()
554             .offset_x( info_width - 1 )
555             .offset_y( 0 )
556             .content_size( mission_text.size() )
557             .viewport_pos( info_offset )
558             .viewport_size( info_height )
559             .apply( w_info );
560         }
561         const size_t end_line = std::min( info_height, mission_text.size() - info_offset );
562 
563         // Display the current subset of the mission text.
564         for( size_t start_line = 0; start_line < end_line; start_line++ ) {
565             print_colored_text( w_info, point( 0, start_line ), col, col,
566                                 mission_text[start_line + info_offset] );
567         }
568 
569         wnoutrefresh( w_info );
570 
571         if( role_id == "FACTION_CAMP" ) {
572             werase( w_tabs );
573             draw_camp_tabs( w_tabs, tab_mode, mission_key.entries );
574             wnoutrefresh( w_tabs );
575         }
576     } );
577 
578     while( true ) {
579         mission_key.cur_key = cur_key_list[sel];
580         ui_manager::redraw();
581         const std::string action = ctxt.handle_input();
582         if( action == "DOWN" ) {
583             if( sel == cur_key_list.size() - 1 ) {
584                 sel = 0;    // Wrap around
585             } else {
586                 sel++;
587             }
588             info_offset = 0;
589         } else if( action == "UP" ) {
590             if( sel == 0 ) {
591                 sel = cur_key_list.size() - 1;    // Wrap around
592             } else {
593                 sel--;
594             }
595             info_offset = 0;
596         } else if( action == "PAGE_UP" ) {
597             if( info_offset > 0 ) {
598                 info_offset--;
599             }
600         } else if( action == "PAGE_DOWN" ) {
601             info_offset++;
602         } else if( action == "NEXT_TAB" && role_id == "FACTION_CAMP" ) {
603             sel = 0;
604             info_offset = 0;
605 
606             do {
607                 if( tab_mode == base_camps::TAB_NW ) {
608                     tab_mode = base_camps::TAB_MAIN;
609                     reset_cur_key_list();
610                 } else {
611                     tab_mode = static_cast<base_camps::tab_mode>( tab_mode + 1 );
612                     cur_key_list = mission_key.entries[tab_mode + 1];
613                 }
614             } while( cur_key_list.empty() );
615         } else if( action == "PREV_TAB" && role_id == "FACTION_CAMP" ) {
616             sel = 0;
617             info_offset = 0;
618 
619             do {
620                 if( tab_mode == base_camps::TAB_MAIN ) {
621                     tab_mode = base_camps::TAB_NW;
622                 } else {
623                     tab_mode = static_cast<base_camps::tab_mode>( tab_mode - 1 );
624                 }
625 
626                 if( tab_mode == base_camps::TAB_MAIN ) {
627                     reset_cur_key_list();
628                 } else {
629                     cur_key_list = mission_key.entries[tab_mode + 1];
630                 }
631             } while( cur_key_list.empty() );
632         } else if( action == "QUIT" ) {
633             mission_entry dud;
634             dud.id = "NONE";
635             dud.name_display = "NONE";
636             mission_key.cur_key = dud;
637             break;
638         } else if( action == "CONFIRM" ) {
639             if( mission_key.cur_key.possible ) {
640                 break;
641             } else {
642                 continue;
643             }
644         }
645     }
646     return true;
647 }
648 
handle_outpost_mission(const mission_entry & cur_key,npc & p)649 bool talk_function::handle_outpost_mission( const mission_entry &cur_key, npc &p )
650 {
651     if( cur_key.id == "Caravan Commune-Refugee Center" ) {
652         individual_mission( p, _( "joins the caravan team…" ), "_commune_refugee_caravan", true );
653     }
654     if( cur_key.id == "Begin Commune-Refugee Center Run" ) {
655         caravan_depart( p, "evac_center_18", "_commune_refugee_caravan" );
656     }
657     if( cur_key.id == "Recover Commune-Refugee Center" ) {
658         caravan_return( p, "evac_center_18", "_commune_refugee_caravan" );
659     }
660     if( cur_key.id == "Purchase East Field" ) {
661         field_build_1( p );
662     }
663     if( cur_key.id == "Upgrade East Field I" ) {
664         field_build_2( p );
665     }
666     if( cur_key.id == "Plant East Field" ) {
667         field_plant( p, "ranch_camp_63" );
668     }
669     if( cur_key.id == "Harvest East Field" ) {
670         field_harvest( p, "ranch_camp_63" );
671     }
672     if( cur_key.id == "Assign Scavenging Patrol" ) {
673         individual_mission( p, _( "departs on the scavenging patrol…" ), "_scavenging_patrol" );
674     }
675     if( cur_key.id == "Retrieve Scavenging Patrol" ) {
676         scavenging_patrol_return( p );
677     }
678     if( cur_key.id == "Assign Scavenging Raid" ) {
679         individual_mission( p, _( "departs on the scavenging raid…" ), "_scavenging_raid" );
680     }
681     if( cur_key.id == "Retrieve Scavenging Raid" ) {
682         scavenging_raid_return( p );
683     }
684     if( cur_key.id == "Assign Ally to Menial Labor" ) {
685         individual_mission( p, _( "departs to work as a laborer…" ), "_labor" );
686     }
687     if( cur_key.id == "Recover Ally from Menial Labor" ) {
688         labor_return( p );
689     }
690 
691     if( cur_key.id == "Assign Ally to Carpentry Work" ) {
692         individual_mission( p, _( "departs to work as a carpenter…" ), "_carpenter" );
693     }
694     if( cur_key.id == "Recover Ally from Carpentry Work" ) {
695         carpenter_return( p );
696     }
697     if( cur_key.id == "Assign Ally to Forage for Food" ) {
698         individual_mission( p, _( "departs to forage for food…" ), "_forage" );
699     }
700     if( cur_key.id == "Recover Ally from Foraging" ) {
701         forage_return( p );
702     }
703 
704     return true;
705 }
706 
individual_mission(npc & p,const std::string & desc,const std::string & miss_id,bool group,const std::vector<item * > & equipment,const std::map<skill_id,int> & required_skills)707 npc_ptr talk_function::individual_mission( npc &p, const std::string &desc,
708         const std::string &miss_id, bool group, const std::vector<item *> &equipment,
709         const std::map<skill_id, int> &required_skills )
710 {
711     const tripoint_abs_omt omt_pos = p.global_omt_location();
712     return individual_mission( omt_pos, p.companion_mission_role_id, desc, miss_id, group,
713                                equipment, required_skills );
714 }
individual_mission(const tripoint_abs_omt & omt_pos,const std::string & role_id,const std::string & desc,const std::string & miss_id,bool group,const std::vector<item * > & equipment,const std::map<skill_id,int> & required_skills)715 npc_ptr talk_function::individual_mission( const tripoint_abs_omt &omt_pos,
716         const std::string &role_id, const std::string &desc,
717         const std::string &miss_id, bool group, const std::vector<item *> &equipment,
718         const std::map<skill_id, int> &required_skills )
719 {
720     npc_ptr comp = companion_choose( required_skills );
721     if( comp == nullptr ) {
722         return comp;
723     }
724     // make sure, for now, that NPCs dismount their horse before going on a mission.
725     if( comp->has_effect( effect_riding ) ) {
726         comp->npc_dismount();
727     }
728     Character &player_character = get_player_character();
729     //Ensure we have someone to give equipment to before we lose it
730     for( item *i : equipment ) {
731         comp->companion_mission_inv.add_item( *i );
732         //comp->i_add(*i);
733         if( item::count_by_charges( i->typeId() ) ) {
734             player_character.use_charges( i->typeId(), i->charges );
735         } else {
736             player_character.use_amount( i->typeId(), 1 );
737         }
738     }
739     if( comp->in_vehicle ) {
740         get_map().unboard_vehicle( comp->pos() );
741     }
742     popup( "%s %s", comp->name, desc );
743     comp->set_companion_mission( omt_pos, role_id, miss_id );
744     if( group ) {
745         comp->companion_mission_time = calendar::before_time_starts;
746     } else {
747         comp->companion_mission_time = calendar::turn;
748     }
749     g->reload_npcs();
750     g->validate_npc_followers();
751     cata_assert( !comp->is_active() );
752     return comp;
753 }
754 
caravan_depart(npc & p,const std::string & dest,const std::string & id)755 void talk_function::caravan_depart( npc &p, const std::string &dest, const std::string &id )
756 {
757     std::vector<npc_ptr> npc_list = companion_list( p, id );
758     int distance = caravan_dist( dest );
759     time_duration time = 20_minutes + distance * 10_minutes;
760     popup( _( "The caravan departs with an estimated total travel time of %d hours…" ),
761            to_hours<int>( time ) );
762 
763     for( auto &elem : npc_list ) {
764         if( elem->companion_mission_time == calendar::before_time_starts ) {
765             //Adds a 10% error in estimated travel time
766             elem->companion_mission_time = calendar::turn + time * rng_float( -1.1, 1.1 );
767         }
768     }
769 }
770 
771 //Could be expanded to actually path to the site, just returns the distance
caravan_dist(const std::string & dest)772 int talk_function::caravan_dist( const std::string &dest )
773 {
774     Character &player_character = get_player_character();
775     const tripoint_abs_omt site =
776         overmap_buffer.find_closest( player_character.global_omt_location(), dest, 0, false );
777     int distance = rl_dist( player_character.global_omt_location(), site );
778     return distance;
779 }
780 
caravan_return(npc & p,const std::string & dest,const std::string & id)781 void talk_function::caravan_return( npc &p, const std::string &dest, const std::string &id )
782 {
783     npc_ptr comp = companion_choose_return( p, id, calendar::turn );
784     if( comp == nullptr ) {
785         return;
786     }
787     if( comp->companion_mission_time == calendar::before_time_starts ) {
788         popup( _( "%s returns to your party." ), comp->name );
789         companion_return( *comp );
790         return;
791     }
792     //So we have chosen to return an individual or party who went on the mission
793     //Everyone who was on the mission will have the same companion_mission_time
794     //and will simulate the mission and return together
795     std::vector<npc_ptr> caravan_party;
796     std::vector<npc_ptr> bandit_party;
797     std::vector<npc_ptr> npc_list = companion_list( p, id );
798     const int rand_caravan_size = rng( 1, 3 );
799     caravan_party.reserve( npc_list.size() + rand_caravan_size );
800     for( int i = 0; i < rand_caravan_size; i++ ) {
801         caravan_party.push_back( temp_npc( string_id<npc_template>( "commune_guard" ) ) );
802     }
803     for( auto &elem : npc_list ) {
804         if( elem->companion_mission_time == comp->companion_mission_time ) {
805             caravan_party.push_back( elem );
806         }
807     }
808 
809     int distance = caravan_dist( dest );
810     int time = 200 + distance * 100;
811     int experience = rng( 10, time / 300 );
812 
813     const int rand_bandit_size = rng( 1, 3 );
814     bandit_party.reserve( rand_bandit_size * 2 );
815     for( int i = 0; i < rand_bandit_size * 2; i++ ) {
816         bandit_party.push_back( temp_npc( string_id<npc_template>( "bandit" ) ) );
817         bandit_party.push_back( temp_npc( string_id<npc_template>( "thug" ) ) );
818     }
819 
820     if( one_in( 3 ) ) {
821         if( one_in( 2 ) ) {
822             popup( _( "A bandit party approaches the caravan in the open!" ) );
823             force_on_force( caravan_party, "caravan", bandit_party, "band", 1 );
824         } else if( one_in( 3 ) ) {
825             popup( _( "A bandit party attacks the caravan while it it's camped!" ) );
826             force_on_force( caravan_party, "caravan", bandit_party, "band", 2 );
827         } else {
828             popup( _( "The caravan walks into a bandit ambush!" ) );
829             force_on_force( caravan_party, "caravan", bandit_party, "band", -1 );
830         }
831     }
832 
833     int money = 0;
834     for( const auto &elem : caravan_party ) {
835         //Scrub temporary party members and the dead
836         if( elem->get_part_hp_cur( bodypart_id( "torso" ) ) == 0 && elem->has_companion_mission() ) {
837             overmap_buffer.remove_npc( comp->getID() );
838             money += ( time / 600 ) * 9;
839         } else if( elem->has_companion_mission() ) {
840             money += ( time / 600 ) * 18;
841             companion_skill_trainer( *elem, "combat", experience * 10_minutes, 10 );
842             companion_return( *elem );
843         }
844     }
845 
846     if( money != 0 ) {
847         get_player_character().cash += ( 100 * money );
848         popup( _( "The caravan party has returned.  Your share of the profits are $%d!" ), money );
849     } else {
850         popup( _( "The caravan was a disaster and your companions never made it home…" ) );
851     }
852 }
853 
854 //A random NPC on one team attacks a random monster on the opposite
attack_random(const std::vector<npc_ptr> & attacker,const std::vector<monster * > & group)855 void talk_function::attack_random( const std::vector<npc_ptr> &attacker,
856                                    const std::vector< monster * > &group )
857 {
858     if( attacker.empty() || group.empty() ) {
859         return;
860     }
861     const auto att = random_entry( attacker );
862     monster *def = random_entry( group );
863     att->melee_attack_abstract( *def, false, matec_id( "" ) );
864     if( def->get_hp() <= 0 ) {
865         popup( _( "%s is wasted by %s!" ), def->type->nname(), att->name );
866     }
867 }
868 
869 //A random monster on one side attacks a random NPC on the other
attack_random(const std::vector<monster * > & group,const std::vector<npc_ptr> & defender)870 void talk_function::attack_random( const std::vector< monster * > &group,
871                                    const std::vector<npc_ptr> &defender )
872 {
873     if( defender.empty() || group.empty() ) {
874         return;
875     }
876     monster *att = random_entry( group );
877     const auto def = random_entry( defender );
878     att->melee_attack( *def );
879     //monster mon;
880     if( def->get_part_hp_cur( bodypart_id( "torso" ) ) <= 0 || def->is_dead() ) {
881         popup( _( "%s is wasted by %s!" ), def->name, att->type->nname() );
882     }
883 }
884 
885 //A random NPC on one team attacks a random NPC on the opposite
attack_random(const std::vector<npc_ptr> & attacker,const std::vector<npc_ptr> & defender)886 void talk_function::attack_random( const std::vector<npc_ptr> &attacker,
887                                    const std::vector<npc_ptr> &defender )
888 {
889     if( attacker.empty() || defender.empty() ) {
890         return;
891     }
892     const auto att = random_entry( attacker );
893     const auto def = random_entry( defender );
894     const skill_id best = att->best_skill();
895     int best_score = 1;
896     if( best ) {
897         best_score = att->get_skill_level( best );
898     }
899     ///\EFFECT_DODGE_NPC increases avoidance of random attacks
900     if( rng( -1, best_score ) >= rng( 0, def->get_skill_level( skill_dodge ) ) ) {
901         def->set_part_hp_cur( bodypart_id( "torso" ), 0 );
902         popup( _( "%s is wasted by %s!" ), def->name, att->name );
903     } else {
904         popup( _( "%s dodges %s's attack!" ), def->name, att->name );
905     }
906 }
907 
908 //Used to determine when to retreat, might want to add in a random factor so that engagements aren't
909 //drawn out wars of attrition
combat_score(const std::vector<npc_ptr> & group)910 int talk_function::combat_score( const std::vector<npc_ptr> &group )
911 {
912     int score = 0;
913     for( const auto &elem : group ) {
914         if( elem->get_part_hp_cur( bodypart_id( "torso" ) ) != 0 ) {
915             const skill_id best = elem->best_skill();
916             if( best ) {
917                 score += elem->get_skill_level( best );
918             } else {
919                 score += 1;
920             }
921         }
922     }
923     return score;
924 }
925 
combat_score(const std::vector<monster * > & group)926 int talk_function::combat_score( const std::vector< monster * > &group )
927 {
928     int score = 0;
929     for( const auto &elem : group ) {
930         if( elem->get_hp() > 0 ) {
931             score += elem->type->difficulty;
932         }
933     }
934     return score;
935 }
936 
temp_npc(const string_id<npc_template> & type)937 npc_ptr talk_function::temp_npc( const string_id<npc_template> &type )
938 {
939     npc_ptr temp = make_shared_fast<npc>();
940     temp->normalize();
941     temp->load_npc_template( type );
942     return temp;
943 }
944 
945 //The field is designed as more of a convenience than a profit opportunity.
field_build_1(npc & p)946 void talk_function::field_build_1( npc &p )
947 {
948     Character &player_character = get_player_character();
949     if( player_character.cash < 100000 ) {
950         popup( _( "I'm sorry, you don't have enough money." ) );
951         return;
952     }
953     p.set_mutation( trait_NPC_CONSTRUCTION_LEV_1 );
954     player_character.cash += -100000;
955     const tripoint_abs_omt site =
956         overmap_buffer.find_closest( player_character.global_omt_location(), "ranch_camp_63", 20,
957                                      false );
958     tinymap bay;
959     bay.load( project_to<coords::sm>( site ), false );
960     bay.draw_square_ter( t_dirt, point( 5, 4 ), point( 15, 14 ) );
961     bay.draw_square_ter( t_dirtmound, point( 6, 5 ), point( 6, 13 ) );
962     bay.draw_square_ter( t_dirtmound, point( 8, 5 ), point( 8, 13 ) );
963     bay.draw_square_ter( t_dirtmound, point( 10, 5 ), point( 10, 13 ) );
964     bay.draw_square_ter( t_dirtmound, point( 12, 5 ), point( 12, 13 ) );
965     bay.draw_square_ter( t_dirtmound, point( 14, 5 ), point( 14, 13 ) );
966     bay.save();
967     popup( _( "%s jots your name down on a ledger and yells out to nearby laborers to begin "
968               "plowing your new field." ), p.name );
969 }
970 
971 //Really expensive, but that is so you can't tear down the fence and sell the wood for a profit!
field_build_2(npc & p)972 void talk_function::field_build_2( npc &p )
973 {
974     Character &player_character = get_player_character();
975     if( player_character.cash < 550000 ) {
976         popup( _( "I'm sorry, you don't have enough money." ) );
977         return;
978     }
979     p.set_mutation( trait_NPC_CONSTRUCTION_LEV_2 );
980     player_character.cash += -550000;
981     const tripoint_abs_omt site =
982         overmap_buffer.find_closest( player_character.global_omt_location(), "ranch_camp_63", 20,
983                                      false );
984     tinymap bay;
985     bay.load( project_to<coords::sm>( site ), false );
986     bay.draw_square_ter( t_fence, point( 4, 3 ), point( 16, 3 ) );
987     bay.draw_square_ter( t_fence, point( 4, 15 ), point( 16, 15 ) );
988     bay.draw_square_ter( t_fence, point( 4, 3 ), point( 4, 15 ) );
989     bay.draw_square_ter( t_fence, point( 16, 3 ), point( 16, 15 ) );
990     bay.draw_square_ter( t_fencegate_c, point( 10, 3 ), point( 10, 3 ) );
991     bay.draw_square_ter( t_fencegate_c, point( 10, 15 ), point( 10, 15 ) );
992     bay.draw_square_ter( t_fencegate_c, point( 4, 9 ), point( 4, 9 ) );
993     bay.save();
994     popup( _( "After counting your money %s directs a nearby laborer to begin constructing a "
995               "fence around your plot…" ), p.name );
996 }
997 
field_plant(npc & p,const std::string & place)998 void talk_function::field_plant( npc &p, const std::string &place )
999 {
1000     Character &player_character = get_player_character();
1001     if( !warm_enough_to_plant( player_character.pos() ) ) {
1002         popup( _( "It is too cold to plant anything now." ) );
1003         return;
1004     }
1005     std::vector<item *> seed_inv = player_character.items_with( []( const item & itm ) {
1006         return itm.is_seed() && itm.typeId() != itype_marloss_seed &&
1007                itm.typeId() != itype_fungal_seeds;
1008     } );
1009     if( seed_inv.empty() ) {
1010         popup( _( "You have no seeds to plant!" ) );
1011         return;
1012     }
1013 
1014     int empty_plots = 0;
1015     int free_seeds = 0;
1016 
1017     std::vector<itype_id> seed_types;
1018     std::vector<std::string> seed_names;
1019     for( auto &seed : seed_inv ) {
1020         if( std::find( seed_types.begin(), seed_types.end(), seed->typeId() ) == seed_types.end() ) {
1021             seed_types.push_back( seed->typeId() );
1022             seed_names.push_back( seed->tname() );
1023         }
1024     }
1025     // Choose seed if applicable
1026     const int seed_index = uilist( _( "Which seeds do you wish to have planted?" ), seed_names );
1027     // Did we cancel?
1028     if( seed_index < 0 || static_cast<size_t>( seed_index ) >= seed_types.size() ) {
1029         popup( _( "You saved your seeds for later." ) );
1030         return;
1031     }
1032 
1033     const auto &seed_id = seed_types[seed_index];
1034     if( item::count_by_charges( seed_id ) ) {
1035         free_seeds = player_character.charges_of( seed_id );
1036     } else {
1037         free_seeds = player_character.amount_of( seed_id );
1038     }
1039 
1040     //Now we need to find how many free plots we have to plant in...
1041     const tripoint_abs_omt site = overmap_buffer.find_closest(
1042                                       player_character.global_omt_location(), place, 20, false );
1043     tinymap bay;
1044     bay.load( project_to<coords::sm>( site ), false );
1045     for( const tripoint &plot : bay.points_on_zlevel() ) {
1046         if( bay.ter( plot ) == t_dirtmound ) {
1047             empty_plots++;
1048         }
1049     }
1050 
1051     if( empty_plots == 0 ) {
1052         popup( _( "You have no room to plant seeds…" ) );
1053         return;
1054     }
1055 
1056     int limiting_number = free_seeds;
1057     if( free_seeds > empty_plots ) {
1058         limiting_number = empty_plots;
1059     }
1060 
1061     signed int a = limiting_number * 300;
1062     if( a > player_character.cash ) {
1063         popup( _( "I'm sorry, you don't have enough money to plant those seeds…" ) );
1064         return;
1065     }
1066     if( !query_yn( _( "Do you wish to have %d %s planted here for $%d?" ), limiting_number,
1067                    seed_names[seed_index], limiting_number * 3 ) ) {
1068         return;
1069     }
1070 
1071     //Plant the actual seeds
1072     for( const tripoint &plot : bay.points_on_zlevel() ) {
1073         if( bay.ter( plot ) == t_dirtmound && limiting_number > 0 ) {
1074             std::list<item> used_seed;
1075             if( item::count_by_charges( seed_id ) ) {
1076                 used_seed = player_character.use_charges( seed_id, 1 );
1077             } else {
1078                 used_seed = player_character.use_amount( seed_id, 1 );
1079             }
1080             used_seed.front().set_age( 0_turns );
1081             bay.add_item_or_charges( plot, used_seed.front() );
1082             bay.set( plot, t_dirt, f_plant_seed );
1083             limiting_number--;
1084         }
1085     }
1086     bay.draw_square_ter( t_fence, point( 4, 3 ), point( 16, 3 ) );
1087     bay.save();
1088     popup( _( "After counting your money and collecting your seeds, %s calls forth a labor party "
1089               "to plant your field." ), p.name );
1090 }
1091 
field_harvest(npc & p,const std::string & place)1092 void talk_function::field_harvest( npc &p, const std::string &place )
1093 {
1094     Character &player_character = get_player_character();
1095     //First we need a list of plants that can be harvested...
1096     const tripoint_abs_omt site = overmap_buffer.find_closest(
1097                                       player_character.global_omt_location(), place, 20, false );
1098     tinymap bay;
1099     item tmp;
1100     std::vector<itype_id> seed_types;
1101     std::vector<itype_id> plant_types;
1102     std::vector<std::string> plant_names;
1103     bay.load( project_to<coords::sm>( site ), false );
1104     for( const tripoint &plot : bay.points_on_zlevel() ) {
1105         map_stack items = bay.i_at( plot );
1106         if( bay.furn( plot ) == furn_str_id( "f_plant_harvest" ) && !items.empty() ) {
1107             // Can't use item_stack::only_item() since there might be fertilizer
1108             map_stack::iterator seed = std::find_if( items.begin(), items.end(), []( const item & it ) {
1109                 return it.is_seed();
1110             } );
1111 
1112             if( seed != items.end() ) {
1113                 const islot_seed &seed_data = *seed->type->seed;
1114                 tmp = item( seed_data.fruit_id, calendar::turn );
1115                 bool check = false;
1116                 for( const std::string &elem : plant_names ) {
1117                     if( elem == tmp.type_name( 3 ) ) {
1118                         check = true;
1119                     }
1120                 }
1121                 if( !check ) {
1122                     plant_types.push_back( tmp.typeId() );
1123                     plant_names.push_back( tmp.type_name( 3 ) );
1124                     seed_types.push_back( seed->typeId() );
1125                 }
1126             }
1127         }
1128     }
1129     if( plant_names.empty() ) {
1130         popup( _( "There aren't any plants that are ready to harvest…" ) );
1131         return;
1132     }
1133     // Choose the crop to harvest
1134     const int plant_index = uilist( _( "Which plants do you want to have harvested?" ),
1135                                     plant_names );
1136     // Did we cancel?
1137     if( plant_index < 0 || static_cast<size_t>( plant_index ) >= plant_types.size() ) {
1138         popup( _( "You decided to hold off for now…" ) );
1139         return;
1140     }
1141 
1142     int number_plots = 0;
1143     int number_plants = 0;
1144     int number_seeds = 0;
1145     int skillLevel = 2;
1146     if( p.has_trait( trait_NPC_CONSTRUCTION_LEV_2 ) ) {
1147         skillLevel += 2;
1148     }
1149 
1150     for( const tripoint &plot : bay.points_on_zlevel() ) {
1151         if( bay.furn( plot ) == furn_str_id( "f_plant_harvest" ) ) {
1152             // Can't use item_stack::only_item() since there might be fertilizer
1153             map_stack items = bay.i_at( plot );
1154             map_stack::iterator seed = std::find_if( items.begin(), items.end(), []( const item & it ) {
1155                 return it.is_seed();
1156             } );
1157 
1158             if( seed != items.end() ) {
1159                 const islot_seed &seed_data = *seed->type->seed;
1160                 tmp = item( seed_data.fruit_id, calendar::turn );
1161                 if( tmp.typeId() == plant_types[plant_index] ) {
1162                     number_plots++;
1163                     bay.i_clear( plot );
1164                     bay.furn_set( plot, f_null );
1165                     bay.ter_set( plot, t_dirtmound );
1166                     int plantCount = rng( skillLevel / 2, skillLevel );
1167                     if( plantCount >= 9 ) {
1168                         plantCount = 9;
1169                     } else if( plantCount <= 0 ) {
1170                         plantCount = 1;
1171                     }
1172                     number_plants += plantCount;
1173                     number_seeds += std::max( 1, rng( plantCount / 4, plantCount / 2 ) );
1174                 }
1175             }
1176         }
1177     }
1178     bay.save();
1179     tmp = item( plant_types[plant_index], calendar::turn );
1180     int money = ( number_plants * tmp.price( true ) - number_plots * 2 ) / 100;
1181     bool liquidate = false;
1182 
1183     signed int a = number_plots * 2;
1184     if( a > player_character.cash ) {
1185         liquidate = true;
1186         popup( _( "You don't have enough to pay the workers to harvest the crop so you are forced "
1187                   "to sell…" ) );
1188     } else {
1189         liquidate = query_yn( _( "Do you wish to sell the crop of %d %s for a profit of $%d?" ),
1190                               number_plants, plant_names[plant_index], money );
1191     }
1192 
1193     //Add fruit
1194     if( liquidate ) {
1195         add_msg( _( "The %s are sold for $%d…" ), plant_names[plant_index], money );
1196         player_character.cash += ( number_plants * tmp.price( true ) - number_plots * 2 ) / 100;
1197     } else {
1198         if( tmp.count_by_charges() ) {
1199             tmp.charges = 1;
1200         }
1201         for( int i = 0; i < number_plants; ++i ) {
1202             //Should be dropped at your feet once greedy companions can be controlled
1203             player_character.i_add( tmp );
1204         }
1205         add_msg( _( "You receive %d %s…" ), number_plants, plant_names[plant_index] );
1206     }
1207     tmp = item( seed_types[plant_index], calendar::turn );
1208     const islot_seed &seed_data = *tmp.type->seed;
1209     if( seed_data.spawn_seeds ) {
1210         if( tmp.count_by_charges() ) {
1211             tmp.charges = 1;
1212         }
1213         for( int i = 0; i < number_seeds; ++i ) {
1214             player_character.i_add( tmp );
1215         }
1216         add_msg( _( "You receive %d %s…" ), number_seeds, tmp.type_name( 3 ) );
1217     }
1218 
1219 }
1220 
scavenging_combat_skill(npc & p,int bonus,bool guns)1221 static int scavenging_combat_skill( npc &p, int bonus, bool guns )
1222 {
1223     // the following doxygen aliases do not yet exist. this is marked for future reference
1224     ///\EFFECT_MELEE_NPC affects scavenging_patrol results
1225     ///\EFFECT_SURVIVAL_NPC affects scavenging_patrol results
1226     ///\EFFECT_BASHING_NPC affects scavenging_patrol results
1227     ///\EFFECT_CUTTING_NPC affects scavenging_patrol results
1228     ///\EFFECT_GUN_NPC affects scavenging_patrol results
1229     ///\EFFECT_STABBING_NPC affects scavenging_patrol results
1230     ///\EFFECT_UNARMED_NPC affects scavenging_patrol results
1231     ///\EFFECT_DODGE_NPC affects scavenging_patrol results
1232     return bonus + p.get_skill_level( skill_melee ) + .5 * p.get_skill_level( skill_survival ) +
1233            p.get_skill_level( skill_bashing ) + p.get_skill_level( skill_cutting ) +
1234            ( guns ? p.get_skill_level( skill_gun ) : 0 ) + p.get_skill_level( skill_stabbing ) +
1235            p.get_skill_level( skill_unarmed ) + p.get_skill_level( skill_dodge );
1236 }
1237 
scavenging_patrol_return(npc & p)1238 bool talk_function::scavenging_patrol_return( npc &p )
1239 {
1240     npc_ptr comp = companion_choose_return( p, "_scavenging_patrol",
1241                                             calendar::turn - 10_hours );
1242     if( comp == nullptr ) {
1243         return false;
1244     }
1245     int experience = rng( 5, 20 );
1246     if( one_in( 4 ) ) {
1247         popup( _( "While scavenging, %s's party suddenly found itself set upon by a large mob of "
1248                   "undead…" ), comp->name );
1249         int skill = scavenging_combat_skill( *comp, 4, true );
1250         if( one_in( 6 ) ) {
1251             popup( _( "Through quick thinking the group was able to evade combat!" ) );
1252         } else {
1253             popup( _( "Combat took place in close quarters, focusing on melee skills…" ) );
1254             int monsters = rng( 8, 30 );
1255             if( skill * rng_float( .60, 1.4 ) > .35 * monsters * rng_float( .6, 1.4 ) ) {
1256                 popup( _( "Through brute force the party smashed through the group of %d"
1257                           " undead!" ), monsters );
1258                 experience += rng( 2, 10 );
1259             } else {
1260                 popup( _( "Unfortunately they were overpowered by the undead…  I'm sorry." ) );
1261                 overmap_buffer.remove_npc( comp->getID() );
1262                 return false;
1263             }
1264         }
1265     }
1266 
1267     Character &player_character = get_player_character();
1268     int money = rng( 25, 450 );
1269     player_character.cash += money * 100;
1270 
1271     companion_skill_trainer( *comp, "combat", experience * 10_minutes, 10 );
1272     popup( _( "%s returns from patrol having earned $%d and a fair bit of experience…" ),
1273            comp->name, money );
1274     if( one_in( 10 ) ) {
1275         popup( _( "%s was impressed with %s's performance and gave you a small bonus ( $100 )" ),
1276                p.name, comp->name );
1277         player_character.cash += 10000;
1278     }
1279     if( one_in( 10 ) && !p.has_trait( trait_NPC_MISSION_LEV_1 ) ) {
1280         p.set_mutation( trait_NPC_MISSION_LEV_1 );
1281         popup( _( "%s feels more confident in your abilities and is willing to let you "
1282                   "participate in daring raids." ), p.name );
1283     }
1284     companion_return( *comp );
1285     return true;
1286 }
1287 
scavenging_raid_return(npc & p)1288 bool talk_function::scavenging_raid_return( npc &p )
1289 {
1290     npc_ptr comp = companion_choose_return( p, "_scavenging_raid",
1291                                             calendar::turn - 10_hours );
1292     if( comp == nullptr ) {
1293         return false;
1294     }
1295     int experience = rng( 10, 20 );
1296     if( one_in( 2 ) ) {
1297         popup( _( "While scavenging, %s's party suddenly found itself set upon by a large mob of "
1298                   "undead…" ), comp->name );
1299         int skill = scavenging_combat_skill( *comp, 4, true );
1300         if( one_in( 6 ) ) {
1301             popup( _( "Through quick thinking the group was able to evade combat!" ) );
1302         } else {
1303             popup( _( "Combat took place in close quarters, focusing on melee skills…" ) );
1304             int monsters = rng( 8, 30 );
1305             if( skill * rng_float( .60, 1.4 ) > ( .35 * monsters * rng_float( .6, 1.4 ) ) ) {
1306                 popup( _( "Through brute force the party smashed through the group of %d "
1307                           "undead!" ), monsters );
1308                 experience += rng( 2, 10 );
1309             } else {
1310                 popup( _( "Unfortunately they were overpowered by the undead…  I'm sorry." ) );
1311                 overmap_buffer.remove_npc( comp->getID() );
1312                 return false;
1313             }
1314         }
1315     }
1316     Character &player_character = get_player_character();
1317     //The loot value needs to be added to the faction - what the player is payed
1318     tripoint_abs_omt loot_location = player_character.global_omt_location();
1319     // Only check at the ground floor.
1320     loot_location.z() = 0;
1321     for( int i = 0; i < rng( 2, 3 ); i++ ) {
1322         const tripoint_abs_omt site = overmap_buffer.find_closest(
1323                                           loot_location, "house", 0, false, ot_match_type::prefix );
1324         overmap_buffer.reveal( site, 2 );
1325         loot_building( site );
1326     }
1327 
1328     int money = rng( 200, 900 );
1329     player_character.cash += money * 100;
1330 
1331     companion_skill_trainer( *comp, "combat", experience * 10_minutes, 10 );
1332     popup( _( "%s returns from the raid having earned $%d and a fair bit of experience…" ),
1333            comp->name, money );
1334     if( one_in( 20 ) ) {
1335         popup( _( "%s was impressed with %s's performance and gave you a small bonus ( $100 )" ),
1336                p.name, comp->name );
1337         player_character.cash += 10000;
1338     }
1339     if( one_in( 2 ) ) {
1340         item_group_id itemlist( "npc_misc" );
1341         if( one_in( 8 ) ) {
1342             itemlist = item_group_id( "npc_weapon_random" );
1343         }
1344         item result = item_group::item_from( itemlist );
1345         if( !result.is_null() ) {
1346             popup( _( "%s returned with a %s for you!" ), comp->name, result.tname() );
1347             player_character.i_add( result );
1348         }
1349     }
1350     companion_return( *comp );
1351     return true;
1352 }
1353 
labor_return(npc & p)1354 bool talk_function::labor_return( npc &p )
1355 {
1356     npc_ptr comp = companion_choose_return( p, "_labor", calendar::turn - 1_hours );
1357     if( comp == nullptr ) {
1358         return false;
1359     }
1360 
1361     Character &player_character = get_player_character();
1362     float hours = to_hours<float>( calendar::turn - comp->companion_mission_time );
1363     int money = 8 * hours;
1364     player_character.cash += money * 100;
1365 
1366     companion_skill_trainer( *comp, "menial", calendar::turn - comp->companion_mission_time, 1 );
1367 
1368     popup( _( "%s returns from working as a laborer having earned $%d and a bit of experience…" ),
1369            comp->name, money );
1370     companion_return( *comp );
1371     if( hours >= 8 && one_in( 8 ) && !p.has_trait( trait_NPC_MISSION_LEV_1 ) ) {
1372         p.set_mutation( trait_NPC_MISSION_LEV_1 );
1373         popup( _( "%s feels more confident in your companions and is willing to let them "
1374                   "participate in advanced tasks." ), p.name );
1375     }
1376 
1377     return true;
1378 }
1379 
carpenter_return(npc & p)1380 bool talk_function::carpenter_return( npc &p )
1381 {
1382     npc_ptr comp = companion_choose_return( p, "_carpenter",
1383                                             calendar::turn - 1_hours );
1384     if( comp == nullptr ) {
1385         return false;
1386     }
1387 
1388     if( one_in( 20 ) ) {
1389         // the following doxygen aliases do not yet exist. this is marked for future reference
1390 
1391         ///\EFFECT_FABRICATION_NPC affects carpenter mission results
1392 
1393         ///\EFFECT_DODGE_NPC affects carpenter mission results
1394 
1395         ///\EFFECT_SURVIVAL_NPC affects carpenter mission results
1396         int skill_1 = comp->get_skill_level( skill_fabrication );
1397         int skill_2 = comp->get_skill_level( skill_dodge );
1398         int skill_3 = comp->get_skill_level( skill_survival );
1399         popup( _( "While %s was framing a building one of the walls began to collapse…" ),
1400                comp->name );
1401         if( skill_1 > rng( 1, 8 ) ) {
1402             popup( _( "In the blink of an eye, %s threw a brace up and averted a disaster." ),
1403                    comp->name );
1404         } else if( skill_2 > rng( 1, 8 ) ) {
1405             popup( _( "Darting out a window, %s escaped the collapse." ), comp->name );
1406         } else if( skill_3 > rng( 1, 8 ) ) {
1407             popup( _( "%s didn't make it out in time…" ), comp->name );
1408             popup( _( "but %s was rescued from the debris with only minor injuries!" ),
1409                    comp->name );
1410         } else {
1411             popup( _( "%s didn't make it out in time…" ), comp->name );
1412             popup( _( "Everyone who was trapped under the collapsing roof died…" ) );
1413             popup( _( "I'm sorry, there is nothing we could do." ) );
1414             overmap_buffer.remove_npc( comp->getID() );
1415             return false;
1416         }
1417     }
1418 
1419     float hours = to_hours<float>( calendar::turn - comp->companion_mission_time );
1420     int money = 12 * hours;
1421     get_player_character().cash += money * 100;
1422 
1423     companion_skill_trainer( *comp, "construction", calendar::turn -
1424                              comp->companion_mission_time, 2 );
1425 
1426     popup( _( "%s returns from working as a carpenter having earned $%d and a bit of "
1427               "experience…" ), comp->name, money );
1428     companion_return( *comp );
1429     return true;
1430 }
1431 
forage_return(npc & p)1432 bool talk_function::forage_return( npc &p )
1433 {
1434     npc_ptr comp = companion_choose_return( p, "_forage", calendar::turn - 4_hours );
1435     if( comp == nullptr ) {
1436         return false;
1437     }
1438 
1439     if( one_in( 10 ) ) {
1440         popup( _( "While foraging, a beast began to stalk %s…" ), comp->name );
1441         // the following doxygen aliases do not yet exist. this is marked for future reference
1442 
1443         ///\EFFECT_SURVIVAL_NPC affects forage mission results
1444 
1445         ///\EFFECT_DODGE_NPC affects forage mission results
1446         int skill_1 = comp->get_skill_level( skill_survival );
1447         int skill_2 = comp->get_skill_level( skill_dodge );
1448         if( skill_1 > rng( -2, 8 ) ) {
1449             popup( _( "Alerted by a rustle, %s fled to the safety of the outpost!" ), comp->name );
1450         } else if( skill_2 > rng( -2, 8 ) ) {
1451             popup( _( "As soon as the cougar sprang %s darted to the safety of the outpost!" ),
1452                    comp->name );
1453         } else {
1454             popup( _( "%s was caught unaware and was forced to fight the creature at close "
1455                       "range!" ), comp->name );
1456             int skill = scavenging_combat_skill( *comp, 0, false );
1457             int monsters = rng( 0, 10 );
1458             if( skill * rng_float( .80, 1.2 ) > monsters * rng_float( .8, 1.2 ) ) {
1459                 if( one_in( 2 ) ) {
1460                     popup( _( "%s was able to scare off the bear after delivering a nasty "
1461                               "blow!" ), comp->name );
1462                 } else {
1463                     popup( _( "%s beat the cougar into a bloody pulp!" ), comp->name );
1464                 }
1465             } else {
1466                 if( one_in( 2 ) ) {
1467                     popup( _( "%s was able to hold off the first wolf but the others that were "
1468                               "skulking in the tree line caught up…" ), comp->name );
1469                     popup( _( "I'm sorry, there wasn't anything we could do…" ) );
1470                 } else {
1471                     popup( _( "We… we don't know what exactly happened but we found %s's gear "
1472                               "ripped and bloody…" ), comp->name );
1473                     popup( _( "I fear your companion won't be returning." ) );
1474                 }
1475                 overmap_buffer.remove_npc( comp->getID() );
1476                 return false;
1477             }
1478         }
1479     }
1480 
1481     Character &player_character = get_player_character();
1482     float hours = to_hours<float>( calendar::turn - comp->companion_mission_time );
1483     int money = 10 * hours;
1484     player_character.cash += money * 100;
1485 
1486     companion_skill_trainer( *comp, "gathering", calendar::turn -
1487                              comp->companion_mission_time, 2 );
1488 
1489     popup( _( "%s returns from working as a forager having earned $%d and a bit of "
1490               "experience…" ), comp->name, money );
1491     // the following doxygen aliases do not yet exist. this is marked for future reference
1492 
1493     ///\EFFECT_SURVIVAL_NPC affects forage mission results
1494     int skill = comp->get_skill_level( skill_survival );
1495     if( skill > rng_float( -.5, 8 ) ) {
1496         item_group_id itemlist = item_group_id( "farming_seeds" );
1497         if( one_in( 2 ) ) {
1498             switch( season_of_year( calendar::turn ) ) {
1499                 case SPRING:
1500                     itemlist = item_group_id( "forage_spring" );
1501                     break;
1502                 case SUMMER:
1503                     itemlist = item_group_id( "forage_summer" );
1504                     break;
1505                 case AUTUMN:
1506                     itemlist = item_group_id( "forage_autumn" );
1507                     break;
1508                 case WINTER:
1509                     itemlist = item_group_id( "forage_winter" );
1510                     break;
1511                 default:
1512                     debugmsg( "Invalid season" );
1513             }
1514         }
1515         item result = item_group::item_from( itemlist );
1516         if( !result.is_null() ) {
1517             popup( _( "%s returned with a %s for you!" ), comp->name, result.tname() );
1518             player_character.i_add( result );
1519         }
1520         if( one_in( 6 ) && !p.has_trait( trait_NPC_MISSION_LEV_1 ) ) {
1521             p.set_mutation( trait_NPC_MISSION_LEV_1 );
1522             popup( _( "%s feels more confident in your companions and is willing to let them "
1523                       "participate in advanced tasks." ), p.name );
1524         }
1525     }
1526     companion_return( *comp );
1527     return true;
1528 }
1529 
companion_om_combat_check(const std::vector<npc_ptr> & group,const tripoint_abs_omt & om_tgt,bool try_engage)1530 bool talk_function::companion_om_combat_check( const std::vector<npc_ptr> &group,
1531         const tripoint_abs_omt &om_tgt, bool try_engage )
1532 {
1533     if( overmap_buffer.is_safe( om_tgt ) ) {
1534         //Should work but is_safe is always returning true regardless of what is there.
1535         //return true;
1536     }
1537 
1538     tripoint_abs_sm sm_tgt = project_to<coords::sm>( om_tgt );
1539 
1540     tinymap target_bay;
1541     target_bay.load( sm_tgt, false );
1542     std::vector< monster * > monsters_around;
1543     for( int x = 0; x < 2; x++ ) {
1544         for( int y = 0; y < 2; y++ ) {
1545             tripoint_abs_sm sm = sm_tgt + point( x, y );
1546             point_abs_om omp;
1547             tripoint_om_sm local_sm;
1548             std::tie( omp, local_sm ) = project_remain<coords::om>( sm );
1549             overmap &omi = overmap_buffer.get( omp );
1550 
1551             auto monster_bucket = omi.monster_map.equal_range( local_sm );
1552             std::for_each( monster_bucket.first,
1553             monster_bucket.second, [&]( std::pair<const tripoint_om_sm, monster> &monster_entry ) {
1554                 monster &this_monster = monster_entry.second;
1555                 monsters_around.push_back( &this_monster );
1556             } );
1557         }
1558     }
1559     float avg_survival = 0.0f;
1560     for( const auto &guy : group ) {
1561         avg_survival += guy->get_skill_level( skill_survival );
1562     }
1563     avg_survival = avg_survival / group.size();
1564 
1565     monster mon;
1566 
1567     std::vector< monster * > monsters_fighting;
1568     for( monster *mons : monsters_around ) {
1569         if( mons->get_hp() <= 0 ) {
1570             continue;
1571         }
1572         int d_modifier = avg_survival - mons->type->difficulty;
1573         int roll = rng( 1, 20 ) + d_modifier;
1574         if( roll > 10 ) {
1575             if( try_engage ) {
1576                 mons->death_drops = false;
1577                 monsters_fighting.push_back( mons );
1578             }
1579         } else {
1580             mons->death_drops = false;
1581             monsters_fighting.push_back( mons );
1582         }
1583     }
1584 
1585     if( !monsters_fighting.empty() ) {
1586         bool outcome = force_on_force( group, "Patrol", monsters_fighting, "attacking monsters",
1587                                        rng( -1, 2 ) );
1588         for( monster *mons : monsters_fighting ) {
1589             mons->death_drops = true;
1590         }
1591         return outcome;
1592     }
1593     return true;
1594 }
1595 
force_on_force(const std::vector<npc_ptr> & defender,const std::string & def_desc,const std::vector<monster * > & monsters_fighting,const std::string & att_desc,int advantage)1596 bool talk_function::force_on_force( const std::vector<npc_ptr> &defender,
1597                                     const std::string &def_desc,
1598                                     const std::vector< monster * > &monsters_fighting,
1599                                     const std::string &att_desc, int advantage )
1600 {
1601     std::string adv;
1602     if( advantage < 0 ) {
1603         adv = ", attacker advantage";
1604     } else if( advantage > 0 ) {
1605         adv = ", defender advantage";
1606     }
1607     Character &player_character = get_player_character();
1608     faction *yours = player_character.get_faction();
1609     //Find out why your followers don't have your faction...
1610     popup( _( "Engagement between %d members of %s %s and %d %s%s!" ), defender.size(),
1611            yours->name, def_desc, monsters_fighting.size(), att_desc, adv );
1612     int defense = 0;
1613     int attack = 0;
1614     int att_init = 0;
1615     int def_init = 0;
1616     while( true ) {
1617         std::vector< monster * > remaining_mon;
1618         for( const auto &elem : monsters_fighting ) {
1619             if( elem->get_hp() > 0 ) {
1620                 remaining_mon.push_back( elem );
1621             }
1622         }
1623         std::vector<npc_ptr> remaining_def;
1624         for( const auto &elem : defender ) {
1625             if( !elem->is_dead() && elem->get_part_hp_cur( bodypart_id( "torso" ) ) >= 0 &&
1626                 elem->get_part_hp_cur( bodypart_id( "head" ) ) >= 0 ) {
1627                 remaining_def.push_back( elem );
1628             }
1629         }
1630 
1631         defense = combat_score( remaining_def );
1632         attack = combat_score( remaining_mon );
1633         if( attack > defense * 3 ) {
1634             attack_random( remaining_mon, remaining_def );
1635             if( defense == 0 || ( remaining_def.size() == 1 && remaining_def[0]->is_dead() ) ) {
1636                 //Here too...
1637                 popup( _( "%s forces are destroyed!" ), yours->name );
1638             } else {
1639                 //Again, no faction for your followers
1640                 popup( _( "%s forces retreat from combat!" ), yours->name );
1641             }
1642             return false;
1643         } else if( attack * 3 < defense ) {
1644             attack_random( remaining_def, remaining_mon );
1645             if( attack == 0 || ( remaining_mon.size() == 1 && remaining_mon[0]->get_hp() == 0 ) ) {
1646                 popup( _( "The monsters are destroyed!" ) );
1647             } else {
1648                 popup( _( "The monsters disengage!" ) );
1649             }
1650             return true;
1651         } else {
1652             def_init = rng( 1, 6 ) + advantage;
1653             att_init = rng( 1, 6 );
1654             if( def_init >= att_init ) {
1655                 attack_random( remaining_mon, remaining_def );
1656             }
1657             if( def_init <= att_init ) {
1658                 attack_random( remaining_def, remaining_mon );
1659             }
1660         }
1661     }
1662 }
1663 
force_on_force(const std::vector<npc_ptr> & defender,const std::string & def_desc,const std::vector<npc_ptr> & attacker,const std::string & att_desc,int advantage)1664 void talk_function::force_on_force( const std::vector<npc_ptr> &defender,
1665                                     const std::string &def_desc,
1666                                     const std::vector<npc_ptr> &attacker,
1667                                     const std::string &att_desc, int advantage )
1668 {
1669     std::string adv;
1670     if( advantage < 0 ) {
1671         adv = ", attacker advantage";
1672     } else if( advantage > 0 ) {
1673         adv = ", defender advantage";
1674     }
1675     popup( _( "Engagement between %d members of %s %s and %d members of %s %s%s!" ),
1676            defender.size(), defender[0]->get_faction()->name, def_desc, attacker.size(),
1677            attacker[0]->get_faction()->name, att_desc, adv );
1678     int defense = 0;
1679     int attack = 0;
1680     int att_init = 0;
1681     int def_init = 0;
1682     while( true ) {
1683         std::vector<npc_ptr> remaining_att;
1684         for( const auto &elem : attacker ) {
1685             if( elem->get_part_hp_cur( bodypart_id( "torso" ) ) != 0 ) {
1686                 remaining_att.push_back( elem );
1687             }
1688         }
1689         std::vector<npc_ptr> remaining_def;
1690         for( const auto &elem : defender ) {
1691             if( elem->get_part_hp_cur( bodypart_id( "torso" ) ) != 0 ) {
1692                 remaining_def.push_back( elem );
1693             }
1694         }
1695 
1696         defense = combat_score( remaining_def );
1697         attack = combat_score( remaining_att );
1698         if( attack > defense * 3 ) {
1699             attack_random( remaining_att, remaining_def );
1700             if( defense == 0 || ( remaining_def.size() == 1 &&
1701                                   remaining_def[0]->get_part_hp_cur( bodypart_id( "torso" ) ) == 0 ) ) {
1702                 popup( _( "%s forces are destroyed!" ), defender[0]->get_faction()->name );
1703             } else {
1704                 popup( _( "%s forces retreat from combat!" ), defender[0]->get_faction()->name );
1705             }
1706             return;
1707         } else if( attack * 3 < defense ) {
1708             attack_random( remaining_def, remaining_att );
1709             if( attack == 0 || ( remaining_att.size() == 1 &&
1710                                  remaining_att[0]->get_part_hp_cur( bodypart_id( "torso" ) ) == 0 ) ) {
1711                 popup( _( "%s forces are destroyed!" ), attacker[0]->get_faction()->name );
1712             } else {
1713                 popup( _( "%s forces retreat from combat!" ), attacker[0]->get_faction()->name );
1714             }
1715             return;
1716         } else {
1717             def_init = rng( 1, 6 ) + advantage;
1718             att_init = rng( 1, 6 );
1719             if( def_init >= att_init ) {
1720                 attack_random( remaining_att, remaining_def );
1721             }
1722             if( def_init <= att_init ) {
1723                 attack_random( remaining_def, remaining_att );
1724             }
1725         }
1726     }
1727 }
1728 
1729 // skill training support functions
companion_skill_trainer(npc & comp,const std::string & skill_tested,time_duration time_worked,int difficulty)1730 void talk_function::companion_skill_trainer( npc &comp, const std::string &skill_tested,
1731         time_duration time_worked, int difficulty )
1732 {
1733     difficulty = std::max( 1, difficulty );
1734     int checks = 1 + to_minutes<int>( time_worked ) / 10;
1735 
1736     weighted_int_list<skill_id> skill_practice;
1737     if( skill_tested == "combat" ) {
1738         const skill_id best_skill = comp.best_skill();
1739         if( best_skill ) {
1740             skill_practice.add( best_skill, 30 );
1741         }
1742     }
1743     for( Skill &sk : Skill::skills ) {
1744         skill_practice.add( sk.ident(), sk.get_companion_skill_practice( skill_tested ) );
1745     }
1746     if( skill_practice.empty() ) {
1747         comp.practice( skill_id( skill_tested ), difficulty * to_minutes<int>( time_worked ) / 10 );
1748     } else {
1749         for( int i = 0; i < checks; i++ ) {
1750             skill_id *ident = skill_practice.pick();
1751             if( ident ) {
1752                 comp.practice( *ident, difficulty );
1753             }
1754         }
1755     }
1756 }
1757 
companion_skill_trainer(npc & comp,const skill_id & skill_tested,time_duration time_worked,int difficulty)1758 void talk_function::companion_skill_trainer( npc &comp, const skill_id &skill_tested,
1759         time_duration time_worked, int difficulty )
1760 {
1761     difficulty = std::max( 1, difficulty );
1762     comp.practice( skill_tested, difficulty * to_minutes<int>( time_worked ) / 10 );
1763 }
1764 
companion_return(npc & comp)1765 void talk_function::companion_return( npc &comp )
1766 {
1767     cata_assert( !comp.is_active() );
1768     comp.reset_companion_mission();
1769     comp.companion_mission_time = calendar::before_time_starts;
1770     comp.companion_mission_time_ret = calendar::before_time_starts;
1771     Character &player_character = get_player_character();
1772     map &here = get_map();
1773     for( size_t i = 0; i < comp.companion_mission_inv.size(); i++ ) {
1774         for( const auto &it : comp.companion_mission_inv.const_stack( i ) ) {
1775             if( !it.count_by_charges() || it.charges > 0 ) {
1776                 here.add_item_or_charges( player_character.pos(), it );
1777             }
1778         }
1779     }
1780     comp.companion_mission_inv.clear();
1781     comp.companion_mission_points.clear();
1782     // npc *may* be active, or not if outside the reality bubble
1783     g->reload_npcs();
1784 }
1785 
companion_list(const npc & p,const std::string & mission_id,bool contains)1786 std::vector<npc_ptr> talk_function::companion_list( const npc &p, const std::string &mission_id,
1787         bool contains )
1788 {
1789     std::vector<npc_ptr> available;
1790     const tripoint_abs_omt omt_pos = p.global_omt_location();
1791     for( const auto &elem : overmap_buffer.get_companion_mission_npcs() ) {
1792         npc_companion_mission c_mission = elem->get_companion_mission();
1793         if( c_mission.position == omt_pos && c_mission.mission_id == mission_id &&
1794             c_mission.role_id == p.companion_mission_role_id ) {
1795             available.push_back( elem );
1796         } else if( contains && c_mission.mission_id.find( mission_id ) != std::string::npos ) {
1797             available.push_back( elem );
1798         }
1799     }
1800     return available;
1801 }
1802 
companion_combat_rank(const npc & p)1803 static int companion_combat_rank( const npc &p )
1804 {
1805     int combat = 2 * p.get_dex() + 3 * p.get_str() + 2 * p.get_per() + p.get_int();
1806     for( const Skill &sk : Skill::skills ) {
1807         combat += p.get_skill_level( sk.ident() ) * sk.companion_combat_rank_factor();
1808     }
1809     return combat * std::min( p.get_dex(), 32 ) * std::min( p.get_str(), 32 ) / 64;
1810 }
1811 
companion_survival_rank(const npc & p)1812 static int companion_survival_rank( const npc &p )
1813 {
1814     int survival = 2 * p.get_dex() + p.get_str() + 2 * p.get_per() + 1.5 * p.get_int();
1815     for( const Skill &sk : Skill::skills ) {
1816         survival += p.get_skill_level( sk.ident() ) * sk.companion_survival_rank_factor();
1817     }
1818     return survival * std::min( p.get_dex(), 32 ) * std::min( p.get_per(), 32 ) / 64;
1819 }
1820 
companion_industry_rank(const npc & p)1821 static int companion_industry_rank( const npc &p )
1822 {
1823     int industry = p.get_dex() + p.get_str() + p.get_per() + 3 * p.get_int();
1824     for( const Skill &sk : Skill::skills ) {
1825         industry += p.get_skill_level( sk.ident() ) * sk.companion_industry_rank_factor();
1826     }
1827     return industry * std::min( p.get_int(), 32 ) / 8;
1828 }
1829 
companion_sort_compare(const npc_ptr & first,const npc_ptr & second)1830 static bool companion_sort_compare( const npc_ptr &first, const npc_ptr &second )
1831 {
1832     return companion_combat_rank( *first ) > companion_combat_rank( *second );
1833 }
1834 
companion_sort(comp_list available,const std::map<skill_id,int> & required_skills)1835 comp_list talk_function::companion_sort( comp_list available,
1836         const std::map<skill_id, int> &required_skills )
1837 {
1838     if( required_skills.empty() ) {
1839         std::sort( available.begin(), available.end(), companion_sort_compare );
1840         return available;
1841     }
1842     skill_id hardest_skill;
1843     int hardest_diff = -1;
1844     for( const std::pair<const skill_id, int> &req_skill : required_skills ) {
1845         if( req_skill.second > hardest_diff ) {
1846             hardest_diff = req_skill.second;
1847             hardest_skill = req_skill.first;
1848         }
1849     }
1850 
1851     struct companion_sort_skill {
1852         explicit companion_sort_skill( const skill_id  &skill_tested ) {
1853             req_skill = skill_tested;
1854         }
1855 
1856         bool operator()( const npc_ptr &first, const npc_ptr &second ) {
1857             return first->get_skill_level( req_skill ) > second->get_skill_level( req_skill );
1858         }
1859 
1860         skill_id req_skill;
1861     };
1862     std::sort( available.begin(), available.end(), companion_sort_skill( hardest_skill ) );
1863 
1864     return available;
1865 }
1866 
companion_rank(const std::vector<npc_ptr> & available,bool adj)1867 std::vector<comp_rank> talk_function::companion_rank( const std::vector<npc_ptr> &available,
1868         bool adj )
1869 {
1870     std::vector<comp_rank> raw;
1871     int max_combat = 0;
1872     int max_survival = 0;
1873     int max_industry = 0;
1874     for( const auto &e : available ) {
1875         comp_rank r;
1876         r.combat = companion_combat_rank( *e );
1877         r.survival = companion_survival_rank( *e );
1878         r.industry = companion_industry_rank( *e );
1879         raw.push_back( r );
1880         if( r.combat > max_combat ) {
1881             max_combat = r.combat;
1882         }
1883         if( r.survival > max_survival ) {
1884             max_survival = r.survival;
1885         }
1886         if( r.industry > max_industry ) {
1887             max_industry = r.industry;
1888         }
1889     }
1890 
1891     if( !adj ) {
1892         return raw;
1893     }
1894 
1895     std::vector<comp_rank> adjusted;
1896     for( const auto &entry : raw ) {
1897         comp_rank r;
1898         r.combat = max_combat ? 100 * entry.combat / max_combat : 0;
1899         r.survival = max_survival ? 100 * entry.survival / max_survival : 0;
1900         r.industry = max_industry ? 100 * entry.industry / max_industry : 0;
1901         adjusted.push_back( r );
1902     }
1903     return adjusted;
1904 }
1905 
companion_choose(const std::map<skill_id,int> & required_skills)1906 npc_ptr talk_function::companion_choose( const std::map<skill_id, int> &required_skills )
1907 {
1908     Character &player_character = get_player_character();
1909     std::vector<npc_ptr> available;
1910     cata::optional<basecamp *> bcp = overmap_buffer.find_camp(
1911                                          player_character.global_omt_location().xy() );
1912 
1913     for( const character_id &elem : g->get_follower_list() ) {
1914         npc_ptr guy = overmap_buffer.find_npc( elem );
1915         if( !guy ) {
1916             continue;
1917         }
1918         npc_companion_mission c_mission = guy->get_companion_mission();
1919         // get non-assigned visible followers
1920         if( player_character.posz() == guy->posz() && !guy->has_companion_mission() &&
1921             !guy->is_travelling() &&
1922             ( rl_dist( player_character.pos(), guy->pos() ) <= SEEX * 2 ) &&
1923             player_character.sees( guy->pos() ) ) {
1924             available.push_back( guy );
1925         } else if( bcp ) {
1926             basecamp *player_camp = *bcp;
1927             std::vector<npc_ptr> camp_npcs = player_camp->get_npcs_assigned();
1928             if( std::any_of( camp_npcs.begin(), camp_npcs.end(),
1929             [guy]( const npc_ptr & i ) {
1930             return ( ( i == guy ) && ( !guy->has_companion_mission() ) );
1931             } ) ) {
1932                 available.push_back( guy );
1933             }
1934         } else {
1935             const tripoint_abs_omt guy_omt_pos = guy->global_omt_location();
1936             cata::optional<basecamp *> guy_camp = overmap_buffer.find_camp( guy_omt_pos.xy() );
1937             if( guy_camp ) {
1938                 // get NPCs assigned to guard a remote base
1939                 basecamp *temp_camp = *guy_camp;
1940                 std::vector<npc_ptr> assigned_npcs = temp_camp->get_npcs_assigned();
1941                 if( std::any_of( assigned_npcs.begin(), assigned_npcs.end(),
1942                 [guy]( const npc_ptr & i ) {
1943                 return ( ( i == guy ) && ( !guy->has_companion_mission() ) );
1944                 } ) ) {
1945                     available.push_back( guy );
1946                 }
1947             }
1948         }
1949     }
1950     if( available.empty() ) {
1951         popup( _( "You don't have any companions to send out…" ) );
1952         return nullptr;
1953     }
1954     std::vector<uilist_entry> npc_menu;
1955     available = companion_sort( available, required_skills );
1956     std::vector<comp_rank> rankings = companion_rank( available );
1957 
1958     int x = 0;
1959     std::string menu_header = left_justify( _( "Who do you want to send?" ), 51 );
1960     if( required_skills.empty() ) {
1961         menu_header += _( "[ COMBAT : SURVIVAL : INDUSTRY ]" );
1962     }
1963     for( const npc_ptr &e : available ) {
1964         std::string npc_desc;
1965         bool can_do = true;
1966         if( e->mission == NPC_MISSION_GUARD_ALLY ) {
1967             //~ %1$s: npc name
1968             npc_desc = string_format( pgettext( "companion", "%1$s (Guarding)" ), e->name );
1969         } else {
1970             npc_desc = e->name;
1971         }
1972         if( required_skills.empty() ) {
1973             npc_desc = string_format( pgettext( "companion ranking", "%s [ %4d : %4d : %4d ]" ),
1974                                       left_justify( npc_desc, 51 ), rankings[x].combat,
1975                                       rankings[x].survival, rankings[x].industry );
1976         } else {
1977             npc_desc = left_justify( npc_desc, 51 );
1978             bool first = true;
1979             for( const std::pair<const skill_id, int> &skill_tested : required_skills ) {
1980                 if( first ) {
1981                     first = false;
1982                 } else {
1983                     npc_desc += ", ";
1984                 }
1985                 skill_id skill_tested_id = skill_tested.first;
1986                 int skill_level = skill_tested.second;
1987                 if( skill_level == 0 ) {
1988                     //~ %1$s: skill name, %2$d: companion skill level
1989                     npc_desc += string_format( pgettext( "companion skill", "%1$s %2$d" ),
1990                                                skill_tested_id.obj().name(),
1991                                                e->get_skill_level( skill_tested_id ) );
1992                 } else {
1993                     //~ %1$s: skill name, %2$d: companion skill level, %3$d: skill requirement
1994                     npc_desc += string_format( pgettext( "companion skill", "%1$s %2$d/%3$d" ),
1995                                                skill_tested_id.obj().name(),
1996                                                e->get_skill_level( skill_tested_id ),
1997                                                skill_level );
1998                     can_do &= e->get_skill_level( skill_tested_id ) >= skill_level;
1999                 }
2000             }
2001         }
2002         uilist_entry npc_entry = uilist_entry( x, can_do, x, npc_desc );
2003         npc_menu.push_back( npc_entry );
2004         x++;
2005     }
2006     const size_t npc_choice = uilist( menu_header, npc_menu );
2007     if( npc_choice >= available.size() ) {
2008         popup( _( "You choose to send no one…" ), npc_choice );
2009         return nullptr;
2010     }
2011 
2012     return available[npc_choice];
2013 }
2014 
companion_choose_return(const npc & p,const std::string & mission_id,const time_point & deadline)2015 npc_ptr talk_function::companion_choose_return( const npc &p, const std::string &mission_id,
2016         const time_point &deadline )
2017 {
2018     const tripoint_abs_omt omt_pos = p.global_omt_location();
2019     const std::string &role_id = p.companion_mission_role_id;
2020     return companion_choose_return( omt_pos, role_id, mission_id, deadline );
2021 }
companion_choose_return(const tripoint_abs_omt & omt_pos,const std::string & role_id,const std::string & mission_id,const time_point & deadline,const bool by_mission)2022 npc_ptr talk_function::companion_choose_return( const tripoint_abs_omt &omt_pos,
2023         const std::string &role_id,
2024         const std::string &mission_id,
2025         const time_point &deadline,
2026         const bool by_mission )
2027 {
2028     std::vector<npc_ptr> available;
2029     Character &player_character = get_player_character();
2030     for( npc_ptr &guy : overmap_buffer.get_companion_mission_npcs() ) {
2031         npc_companion_mission c_mission = guy->get_companion_mission();
2032         if( c_mission.position != omt_pos ||
2033             ( by_mission && c_mission.mission_id != mission_id ) || c_mission.role_id != role_id ) {
2034             continue;
2035         }
2036         if( player_character.has_trait( trait_DEBUG_HS ) ) {
2037             available.push_back( guy );
2038         } else if( deadline == calendar::before_time_starts ) {
2039             if( guy->companion_mission_time_ret <= calendar::turn ) {
2040                 available.push_back( guy );
2041             }
2042         } else if( guy->companion_mission_time <= deadline ) {
2043             available.push_back( guy );
2044         }
2045     }
2046 
2047     if( available.empty() ) {
2048         popup( _( "You don't have any companions ready to return…" ) );
2049         return nullptr;
2050     }
2051 
2052     if( available.size() == 1 ) {
2053         return available[0];
2054     }
2055 
2056     std::vector<std::string> npcs;
2057     for( auto &elem : available ) {
2058         npcs.push_back( ( elem )->name );
2059     }
2060     const size_t npc_choice = uilist( _( "Who should return?" ), npcs );
2061     if( npc_choice < available.size() ) {
2062         return available[npc_choice];
2063     }
2064     popup( _( "No one returns to your party…" ) );
2065     return nullptr;
2066 }
2067 
2068 //Smash stuff, steal valuables, and change map maker
loot_building(const tripoint_abs_omt & site)2069 void talk_function::loot_building( const tripoint_abs_omt &site )
2070 {
2071     tinymap bay;
2072     bay.load( project_to<coords::sm>( site ), false );
2073     for( const tripoint &p : bay.points_on_zlevel() ) {
2074         const ter_id t = bay.ter( p );
2075         //Open all the doors, doesn't need to be exhaustive
2076         if( t == t_door_c || t == t_door_c_peep || t == t_door_b
2077             || t == t_door_boarded || t == t_door_boarded_damaged
2078             || t == t_rdoor_boarded || t == t_rdoor_boarded_damaged
2079             || t == t_door_boarded_peep || t == t_door_boarded_damaged_peep ) {
2080             bay.ter_set( p, t_door_o );
2081         } else if( t == t_door_locked || t == t_door_locked_peep || t == t_door_locked_alarm ) {
2082             const map_bash_info &bash = bay.ter( p ).obj().bash;
2083             bay.ter_set( p, bash.ter_set );
2084             bay.spawn_items( p, item_group::items_from( bash.drop_group, calendar::turn ) );
2085         } else if( t == t_door_metal_c || t == t_door_metal_locked || t == t_door_metal_pickable ) {
2086             bay.ter_set( p, t_door_metal_o );
2087         } else if( t == t_door_glass_c ) {
2088             bay.ter_set( p, t_door_glass_o );
2089         } else if( t == t_wall && one_in( 25 ) ) {
2090             const map_bash_info &bash = bay.ter( p ).obj().bash;
2091             bay.ter_set( p, bash.ter_set );
2092             bay.spawn_items( p, item_group::items_from( bash.drop_group, calendar::turn ) );
2093             bay.collapse_at( p, false );
2094         }
2095         //Smash easily breakable stuff
2096         else if( ( t == t_window || t == t_window_taped || t == t_window_domestic ||
2097                    t == t_window_boarded_noglass || t == t_window_domestic_taped ||
2098                    t == t_window_alarm_taped || t == t_window_boarded ||
2099                    t == t_curtains || t == t_window_alarm ||
2100                    t == t_window_no_curtains || t == t_window_no_curtains_taped )
2101                  && one_in( 4 ) ) {
2102             const map_bash_info &bash = bay.ter( p ).obj().bash;
2103             bay.ter_set( p, bash.ter_set );
2104             bay.spawn_items( p, item_group::items_from( bash.drop_group, calendar::turn ) );
2105         } else if( ( t == t_wall_glass || t == t_wall_glass_alarm ) && one_in( 3 ) ) {
2106             const map_bash_info &bash = bay.ter( p ).obj().bash;
2107             bay.ter_set( p, bash.ter_set );
2108             bay.spawn_items( p, item_group::items_from( bash.drop_group, calendar::turn ) );
2109         } else if( bay.has_furn( p ) && bay.furn( p ).obj().bash.str_max != -1 && one_in( 10 ) ) {
2110             const map_bash_info &bash = bay.furn( p ).obj().bash;
2111             bay.furn_set( p, bash.furn_set );
2112             bay.delete_signage( p );
2113             bay.spawn_items( p, item_group::items_from( bash.drop_group, calendar::turn ) );
2114         }
2115         //Kill zombies!  Only works against pre-spawned enemies at the moment...
2116         Creature *critter = g->critter_at( p );
2117         if( critter != nullptr ) {
2118             critter->die( nullptr );
2119         }
2120         //Hoover up tasty items!
2121         map_stack items = bay.i_at( p );
2122         for( map_stack::iterator it = items.begin(); it != items.end(); ) {
2123             if( ( ( it->is_food() || it->is_food_container() ) && !one_in( 8 ) ) ||
2124                 ( it->made_of( phase_id::LIQUID ) && !one_in( 8 ) ) ||
2125                 ( it->price( true ) > 1000 && !one_in( 4 ) ) || one_in( 5 ) ) {
2126                 it = items.erase( it );
2127             } else {
2128                 ++it;
2129             }
2130         }
2131     }
2132     bay.save();
2133     overmap_buffer.ter_set( site, oter_id( "looted_building" ) );
2134 }
2135 
add(const std::string & id,const std::string & name_display,const std::string & text)2136 void mission_data::add( const std::string &id, const std::string &name_display,
2137                         const std::string &text )
2138 {
2139     add( id, name_display, cata::nullopt, text, false, true );
2140 }
add_return(const std::string & id,const std::string & name_display,const cata::optional<point> & dir,const std::string & text,bool possible)2141 void mission_data::add_return( const std::string &id, const std::string &name_display,
2142                                const cata::optional<point> &dir, const std::string &text, bool possible )
2143 {
2144     add( id, name_display, dir, text, true, possible );
2145 }
add_start(const std::string & id,const std::string & name_display,const cata::optional<point> & dir,const std::string & text,bool possible)2146 void mission_data::add_start( const std::string &id, const std::string &name_display,
2147                               const cata::optional<point> &dir, const std::string &text, bool possible )
2148 {
2149     add( id, name_display, dir, text, false, possible );
2150 }
add(const std::string & id,const std::string & name_display,const cata::optional<point> & dir,const std::string & text,bool priority,bool possible)2151 void mission_data::add( const std::string &id, const std::string &name_display,
2152                         const cata::optional<point> &dir, const std::string &text,
2153                         bool priority, bool possible )
2154 {
2155     mission_entry miss;
2156     miss.id = id;
2157     if( name_display.empty() ) {
2158         miss.name_display = id;
2159     } else {
2160         miss.name_display = name_display;
2161     }
2162     miss.dir = dir;
2163     miss.text = text;
2164     miss.priority = priority;
2165     miss.possible = possible;
2166 
2167     if( priority ) {
2168         entries[0].push_back( miss );
2169     }
2170     if( !possible ) {
2171         entries[10].push_back( miss );
2172     }
2173     const point direction = dir ? *dir : base_camps::base_dir;
2174     const int tab_order = base_camps::all_directions.at( direction ).tab_order;
2175     entries[tab_order + 1].emplace_back( miss );
2176 }
2177