1 #include "game.h"
2 
3 #include <functional>
4 #include <clocale>
5 #include <algorithm>
6 #include <bitset>
7 #include <chrono>
8 #include <climits>
9 #include <cmath>
10 #include <cstddef>
11 #include <cstdint>
12 #include <cstdio>
13 #include <cstdlib>
14 #include <ctime>
15 #include <cwctype>
16 #include <exception>
17 #include <iostream>
18 #include <iterator>
19 #include <limits>
20 #include <map>
21 #include <memory>
22 #include <numeric>
23 #include <queue>
24 #include <ratio>
25 #include <set>
26 #include <sstream>
27 #include <string>
28 #include <tuple>
29 #include <type_traits>
30 #include <unordered_map>
31 #include <unordered_set>
32 #include <utility>
33 #include <vector>
34 
35 #include "achievement.h"
36 #include "action.h"
37 #include "activity_actor_definitions.h"
38 #include "activity_handlers.h"
39 #include "activity_type.h"
40 #include "auto_note.h"
41 #include "auto_pickup.h"
42 #include "avatar.h"
43 #include "avatar_action.h"
44 #include "basecamp.h"
45 #include "bionics.h"
46 #include "bodypart.h"
47 #include "butchery_requirements.h"
48 #include "cached_options.h"
49 #include "cata_assert.h"
50 #include "cata_utility.h"
51 #include "cata_variant.h"
52 #include "catacharset.h"
53 #include "character.h"
54 #include "character_martial_arts.h"
55 #include "clzones.h"
56 #include "colony.h"
57 #include "color.h"
58 #include "computer_session.h"
59 #include "construction.h"
60 #include "construction_group.h"
61 #include "coordinate_conversions.h"
62 #include "coordinates.h"
63 #include "creature_tracker.h"
64 #include "cuboid_rectangle.h"
65 #include "cursesport.h" // IWYU pragma: keep
66 #include "damage.h"
67 #include "debug.h"
68 #include "dependency_tree.h"
69 #include "dialogue_chatbin.h"
70 #include "editmap.h"
71 #include "enums.h"
72 #include "event.h"
73 #include "event_bus.h"
74 #include "explosion.h"
75 #include "faction.h"
76 #include "field.h"
77 #include "field_type.h"
78 #include "filesystem.h"
79 #include "flag.h"
80 #include "game_constants.h"
81 #include "game_inventory.h"
82 #include "game_ui.h"
83 #include "gamemode.h"
84 #include "gates.h"
85 #include "get_version.h"
86 #include "harvest.h"
87 #include "help.h"
88 #include "iexamine.h"
89 #include "init.h"
90 #include "input.h"
91 #include "inventory.h"
92 #include "item.h"
93 #include "item_category.h"
94 #include "item_contents.h"
95 #include "item_location.h"
96 #include "item_pocket.h"
97 #include "item_stack.h"
98 #include "itype.h"
99 #include "iuse.h"
100 #include "iuse_actor.h"
101 #include "json.h"
102 #include "kill_tracker.h"
103 #include "level_cache.h"
104 #include "lightmap.h"
105 #include "line.h"
106 #include "live_view.h"
107 #include "loading_ui.h"
108 #include "location.h"
109 #include "magic.h"
110 #include "make_static.h"
111 #include "map.h"
112 #include "map_item_stack.h"
113 #include "map_iterator.h"
114 #include "map_selector.h"
115 #include "mapbuffer.h"
116 #include "mapdata.h"
117 #include "mapsharing.h"
118 #include "memorial_logger.h"
119 #include "messages.h"
120 #include "mission.h"
121 #include "mod_manager.h"
122 #include "monattack.h"
123 #include "monexamine.h"
124 #include "monstergenerator.h"
125 #include "morale_types.h"
126 #include "move_mode.h"
127 #include "mtype.h"
128 #include "npc.h"
129 #include "npc_class.h"
130 #include "omdata.h"
131 #include "options.h"
132 #include "output.h"
133 #include "overmap.h"
134 #include "overmap_ui.h"
135 #include "overmapbuffer.h"
136 #include "panels.h"
137 #include "past_games_info.h"
138 #include "path_info.h"
139 #include "pathfinding.h"
140 #include "pickup.h"
141 #include "player.h"
142 #include "player_activity.h"
143 #include "popup.h"
144 #include "profession.h"
145 #include "recipe.h"
146 #include "recipe_dictionary.h"
147 #include "ret_val.h"
148 #include "rng.h"
149 #include "safemode_ui.h"
150 #include "scenario.h"
151 #include "scent_map.h"
152 #include "scores_ui.h"
153 #include "sdltiles.h" // IWYU pragma: keep
154 #include "sounds.h"
155 #include "start_location.h"
156 #include "stats_tracker.h"
157 #include "string_formatter.h"
158 #include "string_input_popup.h"
159 #include "submap.h"
160 #include "talker.h"
161 #include "tileray.h"
162 #include "timed_event.h"
163 #include "translations.h"
164 #include "trap.h"
165 #include "ui.h"
166 #include "ui_manager.h"
167 #include "uistate.h"
168 #include "units.h"
169 #include "value_ptr.h"
170 #include "veh_interact.h"
171 #include "veh_type.h"
172 #include "vehicle.h"
173 #include "viewer.h"
174 #include "vpart_position.h"
175 #include "vpart_range.h"
176 #include "wcwidth.h"
177 #include "weather.h"
178 #include "weather_type.h"
179 #include "worldfactory.h"
180 
181 class computer;
182 
183 #if defined(TILES)
184 #include "cata_tiles.h"
185 #endif // TILES
186 
187 #if defined(_WIN32)
188 #if 1 // HACK: Hack to prevent reordering of #include "platform_win.h" by IWYU
189 #   include "platform_win.h"
190 #endif
191 #   include <tchar.h>
192 #endif
193 
194 #define dbg(x) DebugLog((x),D_GAME) << __FILE__ << ":" << __LINE__ << ": "
195 
196 static constexpr int DANGEROUS_PROXIMITY = 5;
197 
198 static const activity_id ACT_OPERATION( "ACT_OPERATION" );
199 
200 static const mtype_id mon_manhack( "mon_manhack" );
201 
202 static const skill_id skill_melee( "melee" );
203 static const skill_id skill_dodge( "dodge" );
204 static const skill_id skill_gun( "gun" );
205 static const skill_id skill_firstaid( "firstaid" );
206 static const skill_id skill_survival( "survival" );
207 
208 static const species_id species_PLANT( "PLANT" );
209 
210 static const efftype_id effect_adrenaline_mycus( "adrenaline_mycus" );
211 static const efftype_id effect_blind( "blind" );
212 static const efftype_id effect_bouldering( "bouldering" );
213 static const efftype_id effect_contacts( "contacts" );
214 static const efftype_id effect_controlled( "controlled" );
215 static const efftype_id effect_docile( "docile" );
216 static const efftype_id effect_downed( "downed" );
217 static const efftype_id effect_drunk( "drunk" );
218 static const efftype_id effect_flu( "flu" );
219 static const efftype_id effect_infected( "infected" );
220 static const efftype_id effect_laserlocked( "laserlocked" );
221 static const efftype_id effect_no_sight( "no_sight" );
222 static const efftype_id effect_npc_suspend( "npc_suspend" );
223 static const efftype_id effect_onfire( "onfire" );
224 static const efftype_id effect_paid( "paid" );
225 static const efftype_id effect_pet( "pet" );
226 static const efftype_id effect_ridden( "ridden" );
227 static const efftype_id effect_riding( "riding" );
228 static const efftype_id effect_sleep( "sleep" );
229 static const efftype_id effect_stunned( "stunned" );
230 static const efftype_id effect_sensor_stun( "sensor_stun" );
231 static const efftype_id effect_tetanus( "tetanus" );
232 static const efftype_id effect_tied( "tied" );
233 static const efftype_id effect_winded( "winded" );
234 static const efftype_id effect_fungus( "fungus" );
235 
236 static const bionic_id bio_remote( "bio_remote" );
237 
238 static const itype_id itype_battery( "battery" );
239 static const itype_id itype_disassembly( "disassembly" );
240 static const itype_id itype_grapnel( "grapnel" );
241 static const itype_id itype_holybook_bible1( "holybook_bible1" );
242 static const itype_id itype_holybook_bible2( "holybook_bible2" );
243 static const itype_id itype_holybook_bible3( "holybook_bible3" );
244 static const itype_id itype_manhole_cover( "manhole_cover" );
245 static const itype_id itype_remotevehcontrol( "remotevehcontrol" );
246 static const itype_id itype_rm13_armor_on( "rm13_armor_on" );
247 static const itype_id itype_rope_30( "rope_30" );
248 static const itype_id itype_swim_fins( "swim_fins" );
249 
250 static const trait_id trait_BADKNEES( "BADKNEES" );
251 static const trait_id trait_ILLITERATE( "ILLITERATE" );
252 static const trait_id trait_INFIMMUNE( "INFIMMUNE" );
253 static const trait_id trait_INFRESIST( "INFRESIST" );
254 static const trait_id trait_LEG_TENT_BRACE( "LEG_TENT_BRACE" );
255 static const trait_id trait_M_IMMUNE( "M_IMMUNE" );
256 static const trait_id trait_PARKOUR( "PARKOUR" );
257 static const trait_id trait_VINES2( "VINES2" );
258 static const trait_id trait_VINES3( "VINES3" );
259 static const trait_id trait_THICKSKIN( "THICKSKIN" );
260 static const trait_id trait_NPC_STATIC_NPC( "NPC_STATIC_NPC" );
261 static const trait_id trait_NPC_STARTING_NPC( "NPC_STARTING_NPC" );
262 
263 static const trap_str_id tr_unfinished_construction( "tr_unfinished_construction" );
264 
265 static const faction_id faction_your_followers( "your_followers" );
266 
267 static const flag_id json_flag_SPLINT( "SPLINT" );
268 
269 #if defined(__ANDROID__)
270 extern std::map<std::string, std::list<input_event>> quick_shortcuts_map;
271 extern bool add_best_key_for_action_to_quick_shortcuts( action_id action,
272         const std::string &category, bool back );
273 extern bool add_key_to_quick_shortcuts( int key, const std::string &category, bool back );
274 #endif
275 
276 //The one and only game instance
277 std::unique_ptr<game> g;
278 
279 //The one and only uistate instance
280 uistatedata uistate;
281 
is_valid_in_w_terrain(const point & p)282 bool is_valid_in_w_terrain( const point &p )
283 {
284     return p.x >= 0 && p.x < TERRAIN_WINDOW_WIDTH && p.y >= 0 && p.y < TERRAIN_WINDOW_HEIGHT;
285 }
286 
achievement_attained(const achievement * a,bool achievements_enabled)287 static void achievement_attained( const achievement *a, bool achievements_enabled )
288 {
289     if( achievements_enabled ) {
290         add_msg( m_good, _( "You completed the achievement \"%s\"." ),
291                  a->name() );
292         std::string popup_option = get_option<std::string>( "ACHIEVEMENT_COMPLETED_POPUP" );
293         bool show_popup;
294         if( test_mode || popup_option == "never" ) {
295             show_popup = false;
296         } else if( popup_option == "always" ) {
297             show_popup = true;
298         } else if( popup_option == "first" ) {
299             const achievement_completion_info *past_info = get_past_games().achievement( a->id );
300             show_popup = !past_info || past_info->games_completed.empty();
301         } else {
302             debugmsg( "Unexpected ACHIEVEMENT_COMPLETED_POPUP option value %s", popup_option );
303             show_popup = false;
304         }
305 
306         if( show_popup ) {
307             std::string message = colorize( _( "Achievement completed!" ), c_light_green );
308             message += "\n\n";
309             message += get_achievements().ui_text_for( a );
310             message += "\n";
311             message += colorize( _( "Achievement completion popups can be\nconfigured via the "
312                                     "Interface options" ), c_dark_gray );
313             popup( message );
314         }
315     }
316     get_event_bus().send<event_type::player_gets_achievement>( a->id, achievements_enabled );
317 }
318 
achievement_failed(const achievement * a,bool achievements_enabled)319 static void achievement_failed( const achievement *a, bool achievements_enabled )
320 {
321     if( !a->is_conduct() ) {
322         return;
323     }
324     if( achievements_enabled ) {
325         add_msg( m_bad, _( "You lost the conduct \"%s\"." ), a->name() );
326     }
327     get_event_bus().send<event_type::player_fails_conduct>( a->id, achievements_enabled );
328 }
329 
330 // This is the main game set-up process.
game()331 game::game() :
332     liveview( *liveview_ptr ),
333     scent_ptr( *this ),
334     achievements_tracker_ptr( *stats_tracker_ptr, achievement_attained, achievement_failed, true ),
335     m( *map_ptr ),
336     u( *u_ptr ),
337     scent( *scent_ptr ),
338     timed_events( *timed_event_manager_ptr ),
339     uquit( QUIT_NO ),
340     safe_mode( SAFE_MODE_ON ),
341     u_shared_ptr( &u, null_deleter{} ),
342     next_npc_id( 1 ),
343     next_mission_id( 1 ),
344     remoteveh_cache_time( calendar::before_time_starts ),
345     tileset_zoom( DEFAULT_TILESET_ZOOM ),
346     last_mouse_edge_scroll( std::chrono::steady_clock::now() )
347 {
348     first_redraw_since_waiting_started = true;
349     reset_light_level();
350     events().subscribe( &*stats_tracker_ptr );
351     events().subscribe( &*kill_tracker_ptr );
352     events().subscribe( &*memorial_logger_ptr );
353     events().subscribe( &*achievements_tracker_ptr );
354     events().subscribe( &*spell_events_ptr );
355     world_generator = std::make_unique<worldfactory>();
356     // do nothing, everything that was in here is moved to init_data() which is called immediately after g = new game; in main.cpp
357     // The reason for this move is so that g is not uninitialized when it gets to installing the parts into vehicles.
358 }
359 
360 game::~game() = default;
361 
362 // Load everything that will not depend on any mods
load_static_data()363 void game::load_static_data()
364 {
365     // UI stuff, not mod-specific per definition
366     inp_mngr.init();            // Load input config JSON
367     // Init mappings for loading the json stuff
368     DynamicDataLoader::get_instance();
369     fullscreen = false;
370     was_fullscreen = false;
371     show_panel_adm = false;
372     panel_manager::get_manager().init();
373 
374     // These functions do not load stuff from json.
375     // The content they load/initialize is hardcoded into the program.
376     // Therefore they can be loaded here.
377     // If this changes (if they load data from json), they have to
378     // be moved to game::load_mod or game::load_core_data
379 
380     get_auto_pickup().load_global();
381     get_safemode().load_global();
382 }
383 
check_mod_data(const std::vector<mod_id> & opts,loading_ui & ui)384 bool game::check_mod_data( const std::vector<mod_id> &opts, loading_ui &ui )
385 {
386     auto &tree = world_generator->get_mod_manager().get_tree();
387 
388     // deduplicated list of mods to check
389     std::set<mod_id> check( opts.begin(), opts.end() );
390 
391     // if no specific mods specified check all non-obsolete mods
392     if( check.empty() ) {
393         for( const mod_id &e : world_generator->get_mod_manager().all_mods() ) {
394             if( !e->obsolete ) {
395                 check.emplace( e );
396             }
397         }
398     }
399 
400     if( check.empty() ) {
401         world_generator->set_active_world( nullptr );
402         world_generator->init();
403         const std::vector<mod_id> mods_empty;
404         WORLDPTR test_world = world_generator->make_new_world( mods_empty );
405         world_generator->set_active_world( test_world );
406 
407         // if no loadable mods then test core data only
408         try {
409             load_core_data( ui );
410             DynamicDataLoader::get_instance().finalize_loaded_data( ui );
411         } catch( const std::exception &err ) {
412             std::cerr << "Error loading data from json: " << err.what() << std::endl;
413         }
414 
415         std::string world_name = world_generator->active_world->world_name;
416         world_generator->delete_world( world_name, true );
417 
418         MAPBUFFER.reset();
419         overmap_buffer.clear();
420     }
421 
422     for( const auto &e : check ) {
423         world_generator->set_active_world( nullptr );
424         world_generator->init();
425         const std::vector<mod_id> mods_empty;
426         WORLDPTR test_world = world_generator->make_new_world( mods_empty );
427         world_generator->set_active_world( test_world );
428 
429         if( !e.is_valid() ) {
430             std::cerr << "Unknown mod: " << e.str() << std::endl;
431             return false;
432         }
433 
434         const MOD_INFORMATION &mod = *e;
435 
436         if( !tree.is_available( mod.ident ) ) {
437             std::cerr << "Missing dependencies: " << mod.name() << "\n"
438                       << tree.get_node( mod.ident )->s_errors() << std::endl;
439             return false;
440         }
441 
442         std::cout << "Checking mod " << mod.name() << " [" << mod.ident.str() << "]" << std::endl;
443 
444         try {
445             load_core_data( ui );
446 
447             // Load any dependencies
448             for( auto &dep : tree.get_dependencies_of_X_as_strings( mod.ident ) ) {
449                 load_data_from_dir( dep->path, dep->ident.str(), ui );
450             }
451 
452             // Load mod itself
453             load_data_from_dir( mod.path, mod.ident.str(), ui );
454             DynamicDataLoader::get_instance().finalize_loaded_data( ui );
455         } catch( const std::exception &err ) {
456             std::cerr << "Error loading data: " << err.what() << std::endl;
457         }
458 
459         std::string world_name = world_generator->active_world->world_name;
460         world_generator->delete_world( world_name, true );
461 
462         MAPBUFFER.reset();
463         overmap_buffer.clear();
464     }
465     return true;
466 }
467 
is_core_data_loaded() const468 bool game::is_core_data_loaded() const
469 {
470     return DynamicDataLoader::get_instance().is_data_finalized();
471 }
472 
load_core_data(loading_ui & ui)473 void game::load_core_data( loading_ui &ui )
474 {
475     // core data can be loaded only once and must be first
476     // anyway.
477     DynamicDataLoader::get_instance().unload_data();
478 
479     load_data_from_dir( PATH_INFO::jsondir(), "core", ui );
480 }
481 
load_data_from_dir(const std::string & path,const std::string & src,loading_ui & ui)482 void game::load_data_from_dir( const std::string &path, const std::string &src, loading_ui &ui )
483 {
484     DynamicDataLoader::get_instance().load_data_from_path( path, src, ui );
485 }
486 
487 #if !(defined(_WIN32) || defined(TILES))
488 // in ncurses_def.cpp
489 void check_encoding();
490 void ensure_term_size();
491 #endif
492 
init_ui()493 void game_ui::init_ui()
494 {
495     // clear the screen
496     static bool first_init = true;
497 
498     if( first_init ) {
499 #if !(defined(_WIN32) || defined(TILES))
500         check_encoding();
501 #endif
502 
503         first_init = false;
504 
505 #if defined(TILES)
506         //class variable to track the option being active
507         //only set once, toggle action is used to change during game
508         pixel_minimap_option = get_option<bool>( "PIXEL_MINIMAP" );
509 #endif // TILES
510     }
511 
512     // First get TERMX, TERMY
513 #if defined(TILES) || defined(_WIN32)
514     TERMX = get_terminal_width();
515     TERMY = get_terminal_height();
516 
517     get_options().get_option( "TERMINAL_X" ).setValue( TERMX * get_scaling_factor() );
518     get_options().get_option( "TERMINAL_Y" ).setValue( TERMY * get_scaling_factor() );
519     get_options().save();
520 #else
521     ensure_term_size();
522 
523     TERMY = getmaxy( catacurses::stdscr );
524     TERMX = getmaxx( catacurses::stdscr );
525 
526     // try to make FULL_SCREEN_HEIGHT symmetric according to TERMY
527     if( TERMY % 2 ) {
528         FULL_SCREEN_HEIGHT = 25;
529     } else {
530         FULL_SCREEN_HEIGHT = 24;
531     }
532 #endif
533 }
534 
toggle_fullscreen()535 void game::toggle_fullscreen()
536 {
537 #if !defined(TILES)
538     fullscreen = !fullscreen;
539     mark_main_ui_adaptor_resize();
540 #else
541     toggle_fullscreen_window();
542 #endif
543 }
544 
toggle_pixel_minimap()545 void game::toggle_pixel_minimap()
546 {
547 #if defined(TILES)
548     if( pixel_minimap_option ) {
549         clear_window_area( w_pixel_minimap );
550     }
551     pixel_minimap_option = !pixel_minimap_option;
552     mark_main_ui_adaptor_resize();
553 #endif // TILES
554 }
555 
reload_tileset()556 void game::reload_tileset()
557 {
558 #if defined(TILES)
559     try {
560         tilecontext->reinit();
561         tilecontext->load_tileset( get_option<std::string>( "TILES" ), false, true );
562         tilecontext->do_tile_loading_report();
563     } catch( const std::exception &err ) {
564         popup( _( "Loading the tileset failed: %s" ), err.what() );
565     }
566     g->reset_zoom();
567 #endif // TILES
568 }
569 
570 // temporarily switch out of fullscreen for functions that rely
571 // on displaying some part of the sidebar
temp_exit_fullscreen()572 void game::temp_exit_fullscreen()
573 {
574     if( fullscreen ) {
575         was_fullscreen = true;
576         toggle_fullscreen();
577     } else {
578         was_fullscreen = false;
579     }
580 }
581 
reenter_fullscreen()582 void game::reenter_fullscreen()
583 {
584     if( was_fullscreen ) {
585         if( !fullscreen ) {
586             toggle_fullscreen();
587         }
588     }
589 }
590 
591 /*
592  * Initialize more stuff after mapbuffer is loaded.
593  */
setup()594 void game::setup()
595 {
596     loading_ui ui( true );
597     {
598         background_pane background;
599         static_popup popup;
600         popup.message( "%s", _( "Please wait while the world data loads…\nLoading core data" ) );
601         ui_manager::redraw();
602         refresh_display();
603 
604         load_core_data( ui );
605     }
606 
607     load_world_modfiles( ui );
608 
609     m = map( true );
610 
611     next_npc_id = character_id( 1 );
612     next_mission_id = 1;
613     new_game = true;
614     uquit = QUIT_NO;   // We haven't quit the game
615     bVMonsterLookFire = true;
616 
617     // invalidate calendar caches in case we were previously playing
618     // a different world
619     calendar::set_eternal_season( ::get_option<bool>( "ETERNAL_SEASON" ) );
620     calendar::set_season_length( ::get_option<int>( "SEASON_LENGTH" ) );
621 
622     weather.weather_id = WEATHER_CLEAR;
623     // Weather shift in 30
624     weather.nextweather = calendar::start_of_cataclysm + time_duration::from_hours(
625                               get_option<int>( "INITIAL_TIME" ) ) + 30_minutes;
626 
627     turnssincelastmon = 0_turns; //Auto safe mode init
628 
629     sounds::reset_sounds();
630     clear_zombies();
631     coming_to_stairs.clear();
632     active_npc.clear();
633     faction_manager_ptr->clear();
634     mission::clear_all();
635     Messages::clear_messages();
636     timed_events = timed_event_manager();
637 
638     SCT.vSCT.clear(); //Delete pending messages
639 
640     stats().clear();
641     // reset kill counts
642     kill_tracker_ptr->clear();
643     achievements_tracker_ptr->clear();
644     // reset follower list
645     follower_ids.clear();
646     scent.reset();
647 
648     remoteveh_cache_time = calendar::before_time_starts;
649     remoteveh_cache = nullptr;
650     // back to menu for save loading, new game etc
651 }
652 
has_gametype() const653 bool game::has_gametype() const
654 {
655     return gamemode && gamemode->id() != special_game_type::NONE;
656 }
657 
gametype() const658 special_game_type game::gametype() const
659 {
660     return gamemode ? gamemode->id() : special_game_type::NONE;
661 }
662 
load_map(const tripoint_abs_sm & pos_sm)663 void game::load_map( const tripoint_abs_sm &pos_sm )
664 {
665     m.load( pos_sm, true );
666 }
667 
668 // Set up all default values for a new game
start_game()669 bool game::start_game()
670 {
671     if( !gamemode ) {
672         gamemode = std::make_unique<special_game>();
673     }
674 
675     seed = rng_bits();
676     new_game = true;
677     start_calendar();
678     weather.nextweather = calendar::turn;
679     safe_mode = ( get_option<bool>( "SAFEMODE" ) ? SAFE_MODE_ON : SAFE_MODE_OFF );
680     mostseen = 0; // ...and mostseen is 0, we haven't seen any monsters yet.
681     get_safemode().load_global();
682 
683     init_autosave();
684 
685     background_pane background;
686     static_popup popup;
687     popup.message( "%s", _( "Please wait as we build your world" ) );
688     ui_manager::redraw();
689     refresh_display();
690 
691     load_master();
692     u.setID( assign_npc_id() ); // should be as soon as possible, but *after* load_master
693 
694     const start_location &start_loc = u.random_start_location ? scen->random_start_location().obj() :
695                                       u.start_location.obj();
696     const tripoint_abs_omt omtstart = start_loc.find_player_initial_location();
697     if( omtstart == overmap::invalid_tripoint ) {
698         return false;
699     }
700     start_loc.prepare_map( omtstart );
701 
702     if( scen->has_map_extra() ) {
703         // Map extras can add monster spawn points and similar and should be done before the main
704         // map is loaded.
705         start_loc.add_map_extra( omtstart, scen->get_map_extra() );
706     }
707 
708     tripoint_abs_sm lev = project_to<coords::sm>( omtstart );
709     // The player is centered in the map, but lev[xyz] refers to the top left point of the map
710     lev -= point( HALF_MAPSIZE, HALF_MAPSIZE );
711     load_map( lev );
712 
713     int level = m.get_abs_sub().z;
714     m.invalidate_map_cache( level );
715     m.build_map_cache( level );
716     // Do this after the map cache has been built!
717     start_loc.place_player( u );
718     // ...but then rebuild it, because we want visibility cache to avoid spawning monsters in sight
719     m.invalidate_map_cache( level );
720     m.build_map_cache( level );
721     // Start the overmap with out immediate neighborhood visible, this needs to be after place_player
722     overmap_buffer.reveal( u.global_omt_location().xy(),
723                            get_option<int>( "DISTANCE_INITIAL_VISIBILITY" ), 0 );
724 
725     u.moves = 0;
726     u.process_turn(); // process_turn adds the initial move points
727     u.set_stamina( u.get_stamina_max() );
728     weather.temperature = SPRING_TEMPERATURE;
729     weather.update_weather();
730     u.next_climate_control_check = calendar::before_time_starts; // Force recheck at startup
731     u.last_climate_control_ret = false;
732 
733     //Reset character safe mode/pickup rules
734     get_auto_pickup().clear_character_rules();
735     get_safemode().clear_character_rules();
736     get_auto_notes_settings().clear();
737     get_auto_notes_settings().default_initialize();
738 
739     // spawn the starting NPC, assuming it's not disallowed by the scenario
740     if( !get_scenario()->has_flag( "LONE_START" ) ) {
741         create_starting_npcs();
742     }
743     //Load NPCs. Set nearby npcs to active.
744     load_npcs();
745     // Spawn the monsters
746     const bool spawn_near =
747         get_option<bool>( "BLACK_ROAD" ) || get_scenario()->has_flag( "SUR_START" );
748     // Surrounded start ones
749     if( spawn_near ) {
750         start_loc.surround_with_monsters( omtstart, mongroup_id( "GROUP_ZOMBIE" ), 70 );
751     }
752 
753     m.spawn_monsters( !spawn_near ); // Static monsters
754 
755     // Make sure that no monsters are near the player
756     // This can happen in lab starts
757     if( !spawn_near ) {
758         for( monster &critter : all_monsters() ) {
759             if( rl_dist( critter.pos(), u.pos() ) <= 5 ||
760                 m.clear_path( critter.pos(), u.pos(), 40, 1, 100 ) ) {
761                 remove_zombie( critter );
762             }
763         }
764     }
765 
766     //Create mutation_category_level
767     u.set_highest_cat_level();
768     //Calculate mutation drench protection stats
769     u.drench_mut_calc();
770     if( scen->has_flag( "FIRE_START" ) ) {
771         start_loc.burn( omtstart, 3, 3 );
772     }
773     if( scen->has_flag( "INFECTED" ) ) {
774         u.add_effect( effect_infected, 1_turns, get_player_character().random_body_part(), true );
775     }
776     if( scen->has_flag( "FUNGAL_INFECTION" ) ) {
777         u.add_effect( effect_fungus, 1_turns, get_player_character().random_body_part(), true );
778     }
779     if( scen->has_flag( "BAD_DAY" ) ) {
780         u.add_effect( effect_flu, 1000_minutes );
781         u.add_effect( effect_drunk, 270_minutes );
782         u.add_morale( MORALE_FEELING_BAD, -100, -100, 50_minutes, 50_minutes );
783     }
784     if( scen->has_flag( "HELI_CRASH" ) ) {
785         start_loc.handle_heli_crash( u );
786         bool success = false;
787         for( wrapped_vehicle v : m.get_vehicles() ) {
788             std::string name = v.v->type.str();
789             std::string search = std::string( "helicopter" );
790             if( name.find( search ) != std::string::npos ) {
791                 for( const vpart_reference &vp : v.v->get_any_parts( VPFLAG_CONTROLS ) ) {
792                     const tripoint pos = vp.pos();
793                     u.setpos( pos );
794 
795                     // Delete the items that would have spawned here from a "corpse"
796                     for( int sp : v.v->parts_at_relative( vp.mount(), true ) ) {
797                         vehicle_stack here = v.v->get_items( sp );
798 
799                         for( auto iter = here.begin(); iter != here.end(); ) {
800                             iter = here.erase( iter );
801                         }
802                     }
803 
804                     auto mons = critter_tracker->find( pos );
805                     if( mons != nullptr ) {
806                         critter_tracker->remove( *mons );
807                     }
808 
809                     success = true;
810                     break;
811                 }
812                 if( success ) {
813                     v.v->name = "Bird Wreckage";
814                     break;
815                 }
816             }
817         }
818     }
819     for( auto &e : u.inv_dump() ) {
820         e->set_owner( get_player_character() );
821     }
822     // Now that we're done handling coordinates, ensure the player's submap is in the center of the map
823     update_map( u );
824     // Profession pets
825     for( const mtype_id &elem : u.starting_pets ) {
826         if( monster *const mon = place_critter_around( elem, u.pos(), 5 ) ) {
827             mon->friendly = -1;
828             mon->add_effect( effect_pet, 1_turns, true );
829         } else {
830             add_msg_debug( "cannot place starting pet, no space!" );
831         }
832     }
833     if( u.starting_vehicle &&
834         !place_vehicle_nearby( u.starting_vehicle, u.global_omt_location().xy(), 1, 30,
835                                std::vector<std::string> {} ) ) {
836         debugmsg( "could not place starting vehicle" );
837     }
838     // Assign all of this scenario's missions to the player.
839     for( const mission_type_id &m : scen->missions() ) {
840         mission *new_mission = mission::reserve_new( m, character_id() );
841         new_mission->assign( u );
842     }
843 
844     get_event_bus().send<event_type::game_start>( u.getID(), u.name, u.male, u.prof->ident(),
845             u.custom_profession, getVersionString() );
846     time_played_at_last_load = std::chrono::seconds( 0 );
847     time_of_last_load = std::chrono::steady_clock::now();
848     tripoint_abs_omt abs_omt = u.global_omt_location();
849     const oter_id &cur_ter = overmap_buffer.ter( abs_omt );
850     get_event_bus().send<event_type::avatar_enters_omt>( abs_omt.raw(), cur_ter );
851     return true;
852 }
853 
place_vehicle_nearby(const vproto_id & id,const point_abs_omt & origin,int min_distance,int max_distance,const std::vector<std::string> & omt_search_types)854 vehicle *game::place_vehicle_nearby(
855     const vproto_id &id, const point_abs_omt &origin, int min_distance,
856     int max_distance, const std::vector<std::string> &omt_search_types )
857 {
858     std::vector<std::string> search_types = omt_search_types;
859     if( search_types.empty() ) {
860         vehicle veh( id );
861         if( veh.max_ground_velocity() > 0 ) {
862             search_types.push_back( "road" );
863             search_types.push_back( "field" );
864         } else if( veh.can_float() ) {
865             search_types.push_back( "river" );
866             search_types.push_back( "lake" );
867         } else {
868             // some default locations
869             search_types.push_back( "road" );
870             search_types.push_back( "field" );
871         }
872     }
873     for( const std::string &search_type : search_types ) {
874         omt_find_params find_params;
875         find_params.types.emplace_back( search_type, ot_match_type::type );
876         // find nearest road
877         find_params.min_distance = min_distance;
878         find_params.search_range = max_distance;
879         // if player spawns underground, park their car on the surface.
880         const tripoint_abs_omt omt_origin( origin, 0 );
881         for( const tripoint_abs_omt &goal : overmap_buffer.find_all( omt_origin, find_params ) ) {
882             // try place vehicle there.
883             tinymap target_map;
884             target_map.load( project_to<coords::sm>( goal ), false );
885             const tripoint tinymap_center( SEEX, SEEY, goal.z() );
886             static constexpr std::array<units::angle, 4> angles = {{
887                     0_degrees, 90_degrees, 180_degrees, 270_degrees
888                 }
889             };
890             vehicle *veh = target_map.add_vehicle(
891                                id, tinymap_center, random_entry( angles ), rng( 50, 80 ), 0, false );
892             if( veh ) {
893                 tripoint abs_local = m.getlocal( target_map.getabs( tinymap_center ) );
894                 veh->sm_pos =  ms_to_sm_remain( abs_local );
895                 veh->pos = abs_local.xy();
896 
897                 // Track the player's spawn vehicle.
898                 veh->tracking_on = true;
899                 overmap_buffer.add_vehicle( veh );
900 
901                 target_map.save();
902                 return veh;
903             }
904         }
905     }
906     return nullptr;
907 }
908 
909 //Make any nearby overmap npcs active, and put them in the right location.
load_npcs()910 void game::load_npcs()
911 {
912     const int radius = HALF_MAPSIZE - 1;
913     const tripoint abs_sub = get_map().get_abs_sub();
914     // uses submap coordinates
915     std::vector<shared_ptr_fast<npc>> just_added;
916     for( const auto &temp : overmap_buffer.get_npcs_near_player( radius ) ) {
917         const character_id &id = temp->getID();
918         const auto found = std::find_if( active_npc.begin(), active_npc.end(),
919         [id]( const shared_ptr_fast<npc> &n ) {
920             return n->getID() == id;
921         } );
922         if( found != active_npc.end() ) {
923             continue;
924         }
925         if( temp->is_active() ) {
926             continue;
927         }
928         if( temp->has_companion_mission() ) {
929             continue;
930         }
931 
932         const tripoint sm_loc = temp->global_sm_location();
933         // NPCs who are out of bounds before placement would be pushed into bounds
934         // This can cause NPCs to teleport around, so we don't want that
935         if( sm_loc.x < abs_sub.x || sm_loc.x >= abs_sub.x + MAPSIZE ||
936             sm_loc.y < abs_sub.y || sm_loc.y >= abs_sub.y + MAPSIZE ||
937             ( sm_loc.z != abs_sub.z && !m.has_zlevels() ) ) {
938             continue;
939         }
940 
941         add_msg_debug( "game::load_npcs: Spawning static NPC, %d:%d:%d (%d:%d:%d)",
942                        abs_sub.x, abs_sub.y, abs_sub.z, sm_loc.x, sm_loc.y, sm_loc.z );
943         temp->place_on_map();
944         if( !m.inbounds( temp->pos() ) ) {
945             continue;
946         }
947         // In the rare case the npc was marked for death while
948         // it was on the overmap. Kill it.
949         if( temp->marked_for_death ) {
950             temp->die( nullptr );
951         } else {
952             active_npc.push_back( temp );
953             just_added.push_back( temp );
954         }
955     }
956 
957     for( const auto &npc : just_added ) {
958         npc->on_load();
959     }
960 
961     npcs_dirty = false;
962 }
963 
unload_npcs()964 void game::unload_npcs()
965 {
966     for( const auto &npc : active_npc ) {
967         npc->on_unload();
968     }
969 
970     active_npc.clear();
971 }
972 
reload_npcs()973 void game::reload_npcs()
974 {
975     // TODO: Make it not invoke the "on_unload" command for the NPCs that will be loaded anyway
976     // and not invoke "on_load" for those NPCs that avoided unloading this way.
977     unload_npcs();
978     load_npcs();
979 }
980 
get_kill_tracker() const981 const kill_tracker &game::get_kill_tracker() const
982 {
983     return *kill_tracker_ptr;
984 }
985 
create_starting_npcs()986 void game::create_starting_npcs()
987 {
988     //We don't want more than one starting npc per starting location
989     const int radius = 1;
990     if( !overmap_buffer.get_npcs_near_player( radius ).empty() ) {
991         return; //There is already an NPC in this starting location
992     }
993 
994     shared_ptr_fast<npc> tmp = make_shared_fast<npc>();
995     tmp->normalize();
996     tmp->randomize( one_in( 2 ) ? NC_DOCTOR : NC_NONE );
997     tmp->spawn_at_precise( get_map().get_abs_sub().xy(), u.pos() - point_south_east );
998     overmap_buffer.insert_npc( tmp );
999     tmp->form_opinion( u );
1000     tmp->set_attitude( NPCATT_NULL );
1001     //This sets the NPC mission. This NPC remains in the starting location.
1002     tmp->mission = NPC_MISSION_SHELTER;
1003     tmp->chatbin.first_topic = "TALK_SHELTER";
1004     tmp->toggle_trait( trait_id( "NPC_STARTING_NPC" ) );
1005     tmp->set_fac( faction_id( "no_faction" ) );
1006     //One random starting NPC mission
1007     tmp->add_new_mission( mission::reserve_random( ORIGIN_OPENER_NPC, tmp->global_omt_location(),
1008                           tmp->getID() ) );
1009 }
1010 
cleanup_at_end()1011 bool game::cleanup_at_end()
1012 {
1013     if( uquit == QUIT_DIED || uquit == QUIT_SUICIDE ) {
1014         // Put (non-hallucinations) into the overmap so they are not lost.
1015         for( monster &critter : all_monsters() ) {
1016             despawn_monster( critter );
1017         }
1018         // Reset NPC factions and disposition
1019         reset_npc_dispositions();
1020         // Save the factions', missions and set the NPC's overmap coordinates
1021         // Npcs are saved in the overmap.
1022         save_factions_missions_npcs(); //missions need to be saved as they are global for all saves.
1023 
1024         // and the overmap, and the local map.
1025         save_maps(); //Omap also contains the npcs who need to be saved.
1026     }
1027 
1028     if( uquit == QUIT_DIED || uquit == QUIT_SUICIDE ) {
1029         std::vector<std::string> vRip;
1030 
1031         int iMaxWidth = 0;
1032         int iNameLine = 0;
1033         int iInfoLine = 0;
1034 
1035         if( u.has_amount( itype_holybook_bible1, 1 ) || u.has_amount( itype_holybook_bible2, 1 ) ||
1036             u.has_amount( itype_holybook_bible3, 1 ) ) {
1037             if( !( u.has_trait( trait_id( "CANNIBAL" ) ) || u.has_trait( trait_id( "PSYCHOPATH" ) ) ) ) {
1038                 vRip.emplace_back( "               _______  ___" );
1039                 vRip.emplace_back( "              <       `/   |" );
1040                 vRip.emplace_back( "               >  _     _ (" );
1041                 vRip.emplace_back( "              |  |_) | |_) |" );
1042                 vRip.emplace_back( "              |  | \\ | |   |" );
1043                 vRip.emplace_back( "   ______.__%_|            |_________  __" );
1044                 vRip.emplace_back( " _/                                  \\|  |" );
1045                 iNameLine = vRip.size();
1046                 vRip.emplace_back( "|                                        <" );
1047                 vRip.emplace_back( "|                                        |" );
1048                 iMaxWidth = utf8_width( vRip.back() );
1049                 vRip.emplace_back( "|                                        |" );
1050                 vRip.emplace_back( "|_____.-._____              __/|_________|" );
1051                 vRip.emplace_back( "              |            |" );
1052                 iInfoLine = vRip.size();
1053                 vRip.emplace_back( "              |            |" );
1054                 vRip.emplace_back( "              |           <" );
1055                 vRip.emplace_back( "              |            |" );
1056                 vRip.emplace_back( "              |   _        |" );
1057                 vRip.emplace_back( "              |__/         |" );
1058                 vRip.emplace_back( "             % / `--.      |%" );
1059                 vRip.emplace_back( "         * .%%|          -< @%%%" ); // NOLINT(cata-text-style)
1060                 vRip.emplace_back( "         `\\%`@|            |@@%@%%" );
1061                 vRip.emplace_back( "       .%%%@@@|%     `   % @@@%%@%%%%" );
1062                 vRip.emplace_back( "  _.%%%%%%@@@@@@%%%__/\\%@@%%@@@@@@@%%%%%%" );
1063 
1064             } else {
1065                 vRip.emplace_back( "               _______  ___" );
1066                 vRip.emplace_back( "              |       \\/   |" );
1067                 vRip.emplace_back( "              |            |" );
1068                 vRip.emplace_back( "              |            |" );
1069                 iInfoLine = vRip.size();
1070                 vRip.emplace_back( "              |            |" );
1071                 vRip.emplace_back( "              |            |" );
1072                 vRip.emplace_back( "              |            |" );
1073                 vRip.emplace_back( "              |            |" );
1074                 vRip.emplace_back( "              |           <" );
1075                 vRip.emplace_back( "              |   _        |" );
1076                 vRip.emplace_back( "              |__/         |" );
1077                 vRip.emplace_back( "   ______.__%_|            |__________  _" );
1078                 vRip.emplace_back( " _/                                   \\| \\" );
1079                 iNameLine = vRip.size();
1080                 vRip.emplace_back( "|                                         <" );
1081                 vRip.emplace_back( "|                                         |" );
1082                 iMaxWidth = utf8_width( vRip.back() );
1083                 vRip.emplace_back( "|                                         |" );
1084                 vRip.emplace_back( "|_____.-._______            __/|__________|" );
1085                 vRip.emplace_back( "             % / `_-.   _  |%" );
1086                 vRip.emplace_back( "         * .%%|  |_) | |_)< @%%%" ); // NOLINT(cata-text-style)
1087                 vRip.emplace_back( "         `\\%`@|  | \\ | |   |@@%@%%" );
1088                 vRip.emplace_back( "       .%%%@@@|%     `   % @@@%%@%%%%" );
1089                 vRip.emplace_back( "  _.%%%%%%@@@@@@%%%__/\\%@@%%@@@@@@@%%%%%%" );
1090             }
1091         } else {
1092             vRip.emplace_back( R"(           _________  ____           )" );
1093             vRip.emplace_back( R"(         _/         `/    \_         )" );
1094             vRip.emplace_back( R"(       _/      _     _      \_.      )" );
1095             vRip.emplace_back( R"(     _%\      |_) | |_)       \_     )" );
1096             vRip.emplace_back( R"(   _/ \/      | \ | |           \_   )" );
1097             vRip.emplace_back( R"( _/                               \_ )" );
1098             vRip.emplace_back( R"(|                                   |)" );
1099             iNameLine = vRip.size();
1100             vRip.emplace_back( R"( )                                 < )" );
1101             vRip.emplace_back( R"(|                                   |)" );
1102             vRip.emplace_back( R"(|                                   |)" );
1103             vRip.emplace_back( R"(|   _                               |)" );
1104             vRip.emplace_back( R"(|__/                                |)" );
1105             iMaxWidth = utf8_width( vRip.back() );
1106             vRip.emplace_back( R"( / `--.                             |)" );
1107             vRip.emplace_back( R"(|                                  ( )" );
1108             iInfoLine = vRip.size();
1109             vRip.emplace_back( R"(|                                   |)" );
1110             vRip.emplace_back( R"(|                                   |)" );
1111             vRip.emplace_back( R"(|     %                         .   |)" );
1112             vRip.emplace_back( R"(|  @`                            %% |)" );
1113             vRip.emplace_back( R"(| %@%@%\                *      %`%@%|)" );
1114             vRip.emplace_back( R"(%%@@@.%@%\%%            `\  %%.%%@@%@)" );
1115             vRip.emplace_back( R"(@%@@%%%%%@@@@@@%%%%%%%%@@%%@@@%%%@%%@)" );
1116         }
1117 
1118         const int iOffsetX = TERMX > FULL_SCREEN_WIDTH ? ( TERMX - FULL_SCREEN_WIDTH ) / 2 : 0;
1119         const int iOffsetY = TERMY > FULL_SCREEN_HEIGHT ? ( TERMY - FULL_SCREEN_HEIGHT ) / 2 : 0;
1120 
1121         catacurses::window w_rip = catacurses::newwin( FULL_SCREEN_HEIGHT, FULL_SCREEN_WIDTH,
1122                                    point( iOffsetX, iOffsetY ) );
1123         draw_border( w_rip );
1124 
1125         sfx::do_player_death_hurt( get_player_character(), true );
1126         sfx::fade_audio_group( sfx::group::weather, 2000 );
1127         sfx::fade_audio_group( sfx::group::time_of_day, 2000 );
1128         sfx::fade_audio_group( sfx::group::context_themes, 2000 );
1129         sfx::fade_audio_group( sfx::group::fatigue, 2000 );
1130 
1131         for( size_t iY = 0; iY < vRip.size(); ++iY ) {
1132             size_t iX = 0;
1133             const char *str = vRip[iY].data();
1134             for( int slen = vRip[iY].size(); slen > 0; ) {
1135                 const uint32_t cTemp = UTF8_getch( &str, &slen );
1136                 if( cTemp != U' ' ) {
1137                     nc_color ncColor = c_light_gray;
1138 
1139                     if( cTemp == U'%' ) {
1140                         ncColor = c_green;
1141 
1142                     } else if( cTemp == U'_' || cTemp == U'|' ) {
1143                         ncColor = c_white;
1144 
1145                     } else if( cTemp == U'@' ) {
1146                         ncColor = c_brown;
1147 
1148                     } else if( cTemp == U'*' ) {
1149                         ncColor = c_red;
1150                     }
1151 
1152                     mvwputch( w_rip, point( iX + FULL_SCREEN_WIDTH / 2 - ( iMaxWidth / 2 ), iY + 1 ), ncColor,
1153                               cTemp );
1154                 }
1155                 iX += mk_wcwidth( cTemp );
1156             }
1157         }
1158 
1159         std::string sTemp;
1160 
1161         center_print( w_rip, iInfoLine++, c_white, _( "Survived:" ) );
1162 
1163         const time_duration survived = calendar::turn - calendar::start_of_game;
1164         const int minutes = to_minutes<int>( survived ) % 60;
1165         const int hours = to_hours<int>( survived ) % 24;
1166         const int days = to_days<int>( survived );
1167 
1168         if( days > 0 ) {
1169             sTemp = string_format( "%dd %dh %dm", days, hours, minutes );
1170         } else if( hours > 0 ) {
1171             sTemp = string_format( "%dh %dm", hours, minutes );
1172         } else {
1173             sTemp = string_format( "%dm", minutes );
1174         }
1175 
1176         center_print( w_rip, iInfoLine++, c_white, sTemp );
1177 
1178         const int iTotalKills = get_kill_tracker().monster_kill_count();
1179 
1180         sTemp = _( "Kills:" );
1181         mvwprintz( w_rip, point( FULL_SCREEN_WIDTH / 2 - 5, 1 + iInfoLine++ ), c_light_gray,
1182                    ( sTemp + " " ) );
1183         wprintz( w_rip, c_magenta, "%d", iTotalKills );
1184 
1185         sTemp = _( "In memory of:" );
1186         mvwprintz( w_rip, point( FULL_SCREEN_WIDTH / 2 - utf8_width( sTemp ) / 2, iNameLine++ ),
1187                    c_light_gray,
1188                    sTemp );
1189 
1190         sTemp = u.name;
1191         mvwprintz( w_rip, point( FULL_SCREEN_WIDTH / 2 - utf8_width( sTemp ) / 2, iNameLine++ ), c_white,
1192                    sTemp );
1193 
1194         sTemp = _( "Last Words:" );
1195         mvwprintz( w_rip, point( FULL_SCREEN_WIDTH / 2 - utf8_width( sTemp ) / 2, iNameLine++ ),
1196                    c_light_gray,
1197                    sTemp );
1198 
1199         int iStartX = FULL_SCREEN_WIDTH / 2 - ( ( iMaxWidth - 4 ) / 2 );
1200         std::string sLastWords = string_input_popup()
1201                                  .window( w_rip, point( iStartX, iNameLine ), iStartX + iMaxWidth - 4 - 1 )
1202                                  .max_length( iMaxWidth - 4 - 1 )
1203                                  .query_string();
1204         death_screen();
1205         const bool is_suicide = uquit == QUIT_SUICIDE;
1206         std::chrono::seconds time_since_load =
1207             std::chrono::duration_cast<std::chrono::seconds>(
1208                 std::chrono::steady_clock::now() - time_of_last_load );
1209         std::chrono::seconds total_time_played = time_played_at_last_load + time_since_load;
1210         events().send<event_type::game_over>( is_suicide, sLastWords, total_time_played );
1211         // Struck the save_player_data here to forestall Weirdness
1212         move_save_to_graveyard();
1213         write_memorial_file( sLastWords );
1214         memorial().clear();
1215         std::vector<std::string> characters = list_active_characters();
1216         // remove current player from the active characters list, as they are dead
1217         std::vector<std::string>::iterator curchar = std::find( characters.begin(),
1218                 characters.end(), u.name );
1219         if( curchar != characters.end() ) {
1220             characters.erase( curchar );
1221         }
1222 
1223         if( characters.empty() ) {
1224             bool queryDelete = false;
1225             bool queryReset = false;
1226 
1227             if( get_option<std::string>( "WORLD_END" ) == "query" ) {
1228                 bool decided = false;
1229                 std::string buffer = _( "Warning: NPC interactions and some other global flags "
1230                                         "will not all reset when starting a new character in an "
1231                                         "already-played world.  This can lead to some strange "
1232                                         "behavior.\n\n"
1233                                         "Are you sure you wish to keep this world?"
1234                                       );
1235 
1236                 while( !decided ) {
1237                     uilist smenu;
1238                     smenu.allow_cancel = false;
1239                     smenu.addentry( 0, true, 'r', "%s", _( "Reset world" ) );
1240                     smenu.addentry( 1, true, 'd', "%s", _( "Delete world" ) );
1241                     smenu.addentry( 2, true, 'k', "%s", _( "Keep world" ) );
1242                     smenu.query();
1243 
1244                     switch( smenu.ret ) {
1245                         case 0:
1246                             queryReset = true;
1247                             decided = true;
1248                             break;
1249                         case 1:
1250                             queryDelete = true;
1251                             decided = true;
1252                             break;
1253                         case 2:
1254                             decided = query_yn( buffer );
1255                             break;
1256                     }
1257                 }
1258             }
1259 
1260             if( queryDelete || get_option<std::string>( "WORLD_END" ) == "delete" ) {
1261                 world_generator->delete_world( world_generator->active_world->world_name, true );
1262 
1263             } else if( queryReset || get_option<std::string>( "WORLD_END" ) == "reset" ) {
1264                 world_generator->delete_world( world_generator->active_world->world_name, false );
1265             }
1266         } else if( get_option<std::string>( "WORLD_END" ) != "keep" ) {
1267             std::string tmpmessage;
1268             for( auto &character : characters ) {
1269                 tmpmessage += "\n  ";
1270                 tmpmessage += character;
1271             }
1272             popup( _( "World retained.  Characters remaining:%s" ), tmpmessage );
1273         }
1274         if( gamemode ) {
1275             gamemode = std::make_unique<special_game>(); // null gamemode or something..
1276         }
1277     }
1278 
1279     //Reset any offset due to driving
1280     set_driving_view_offset( point_zero );
1281 
1282     //clear all sound channels
1283     sfx::fade_audio_channel( sfx::channel::any, 300 );
1284     sfx::fade_audio_group( sfx::group::weather, 300 );
1285     sfx::fade_audio_group( sfx::group::time_of_day, 300 );
1286     sfx::fade_audio_group( sfx::group::context_themes, 300 );
1287     sfx::fade_audio_group( sfx::group::fatigue, 300 );
1288 
1289     zone_manager::get_manager().clear();
1290 
1291     MAPBUFFER.reset();
1292     overmap_buffer.clear();
1293 
1294 #if defined(__ANDROID__)
1295     quick_shortcuts_map.clear();
1296 #endif
1297     return true;
1298 }
1299 
veh_lumi(vehicle & veh)1300 static int veh_lumi( vehicle &veh )
1301 {
1302     float veh_luminance = 0.0f;
1303     float iteration = 1.0f;
1304     auto lights = veh.lights( true );
1305 
1306     for( const vehicle_part *pt : lights ) {
1307         const auto &vp = pt->info();
1308         if( vp.has_flag( VPFLAG_CONE_LIGHT ) ||
1309             vp.has_flag( VPFLAG_WIDE_CONE_LIGHT ) ) {
1310             veh_luminance += vp.bonus / iteration;
1311             iteration = iteration * 1.1f;
1312         }
1313     }
1314     // Calculation: see lightmap.cpp
1315     return LIGHT_RANGE( ( veh_luminance * 3 ) );
1316 }
1317 
calc_driving_offset(vehicle * veh)1318 void game::calc_driving_offset( vehicle *veh )
1319 {
1320     if( veh == nullptr || !get_option<bool>( "DRIVING_VIEW_OFFSET" ) ) {
1321         set_driving_view_offset( point_zero );
1322         return;
1323     }
1324     const int g_light_level = static_cast<int>( light_level( u.posz() ) );
1325     const int light_sight_range = u.sight_range( g_light_level );
1326     int sight = std::max( veh_lumi( *veh ), light_sight_range );
1327 
1328     // The maximal offset will leave at least this many tiles
1329     // between the PC and the edge of the main window.
1330     static const int border_range = 2;
1331     point max_offset( ( getmaxx( w_terrain ) + 1 ) / 2 - border_range - 1,
1332                       ( getmaxy( w_terrain ) + 1 ) / 2 - border_range - 1 );
1333 
1334     // velocity at or below this results in no offset at all
1335     static const float min_offset_vel = 1 * vehicles::vmiph_per_tile;
1336     // velocity at or above this results in maximal offset
1337     static const float max_offset_vel = std::min( max_offset.y, max_offset.x ) *
1338                                         vehicles::vmiph_per_tile;
1339     float velocity = veh->velocity;
1340     rl_vec2d offset = veh->move_vec();
1341     if( !veh->skidding && veh->player_in_control( u ) &&
1342         std::abs( veh->cruise_velocity - veh->velocity ) < 7 * vehicles::vmiph_per_tile ) {
1343         // Use the cruise controlled velocity, but only if
1344         // it is not too different from the actual velocity.
1345         // The actual velocity changes too often (see above slowdown).
1346         // Using it makes would make the offset change far too often.
1347         offset = veh->face_vec();
1348         velocity = veh->cruise_velocity;
1349     }
1350     float rel_offset;
1351     if( std::fabs( velocity ) < min_offset_vel ) {
1352         rel_offset = 0;
1353     } else if( std::fabs( velocity ) > max_offset_vel ) {
1354         rel_offset = ( velocity > 0 ) ? 1 : -1;
1355     } else {
1356         rel_offset = ( velocity - min_offset_vel ) / ( max_offset_vel - min_offset_vel );
1357     }
1358     // Squeeze into the corners, by making the offset vector longer,
1359     // the PC is still in view as long as both offset.x and
1360     // offset.y are <= 1
1361     if( std::fabs( offset.x ) > std::fabs( offset.y ) && std::fabs( offset.x ) > 0.2 ) {
1362         offset.y /= std::fabs( offset.x );
1363         offset.x = ( offset.x > 0 ) ? +1 : -1;
1364     } else if( std::fabs( offset.y ) > 0.2 ) {
1365         offset.x /= std::fabs( offset.y );
1366         offset.y = offset.y > 0 ? +1 : -1;
1367     }
1368     offset.x *= rel_offset;
1369     offset.y *= rel_offset;
1370     offset.x *= max_offset.x;
1371     offset.y *= max_offset.y;
1372     // [ ----@---- ] sight=6
1373     // [ --@------ ] offset=2
1374     // [ -@------# ] offset=3
1375     // can see sights square in every direction, total visible area is
1376     // (2*sight+1)x(2*sight+1), but the window is only
1377     // getmaxx(w_terrain) x getmaxy(w_terrain)
1378     // The area outside of the window is maxoff (sight-getmax/2).
1379     // If that value is <= 0, the whole visible area fits the window.
1380     // don't apply the view offset at all.
1381     // If the offset is > maxoff, only apply at most maxoff, everything
1382     // above leads to invisible area in front of the car.
1383     // It will display (getmax/2+offset) squares in one direction and
1384     // (getmax/2-offset) in the opposite direction (centered on the PC).
1385     const point maxoff( ( sight * 2 + 1 - getmaxx( w_terrain ) ) / 2,
1386                         ( sight * 2 + 1 - getmaxy( w_terrain ) ) / 2 );
1387     if( maxoff.x <= 0 ) {
1388         offset.x = 0;
1389     } else if( offset.x > 0 && offset.x > maxoff.x ) {
1390         offset.x = maxoff.x;
1391     } else if( offset.x < 0 && -offset.x > maxoff.x ) {
1392         offset.x = -maxoff.x;
1393     }
1394     if( maxoff.y <= 0 ) {
1395         offset.y = 0;
1396     } else if( offset.y > 0 && offset.y > maxoff.y ) {
1397         offset.y = maxoff.y;
1398     } else if( offset.y < 0 && -offset.y > maxoff.y ) {
1399         offset.y = -maxoff.y;
1400     }
1401 
1402     // Turn the offset into a vector that increments the offset toward the desired position
1403     // instead of setting it there instantly, should smooth out jerkiness.
1404     const point offset_difference( -driving_view_offset + point( offset.x, offset.y ) );
1405 
1406     const point offset_sign( ( offset_difference.x < 0 ) ? -1 : 1,
1407                              ( offset_difference.y < 0 ) ? -1 : 1 );
1408     // Shift the current offset in the direction of the calculated offset by one tile
1409     // per draw event, but snap to calculated offset if we're close enough to avoid jitter.
1410     offset.x = ( std::abs( offset_difference.x ) > 1 ) ?
1411                ( driving_view_offset.x + offset_sign.x ) : offset.x;
1412     offset.y = ( std::abs( offset_difference.y ) > 1 ) ?
1413                ( driving_view_offset.y + offset_sign.y ) : offset.y;
1414 
1415     set_driving_view_offset( point( offset.x, offset.y ) );
1416 }
1417 
1418 // MAIN GAME LOOP
1419 // Returns true if game is over (death, saved, quit, etc)
do_turn()1420 bool game::do_turn()
1421 {
1422     if( is_game_over() ) {
1423         return cleanup_at_end();
1424     }
1425     // Actual stuff
1426     if( new_game ) {
1427         new_game = false;
1428     } else {
1429         gamemode->per_turn();
1430         calendar::turn += 1_turns;
1431     }
1432 
1433     // starting a new turn, clear out temperature cache
1434     weather.temperature_cache.clear();
1435 
1436     if( npcs_dirty ) {
1437         load_npcs();
1438     }
1439 
1440     timed_events.process();
1441     mission::process_all();
1442     // If controlling a vehicle that is owned by someone else
1443     if( u.in_vehicle && u.controlling_vehicle ) {
1444         vehicle *veh = veh_pointer_or_null( m.veh_at( u.pos() ) );
1445         if( veh && !veh->handle_potential_theft( dynamic_cast<player &>( u ), true ) ) {
1446             veh->handle_potential_theft( dynamic_cast<player &>( u ), false, false );
1447         }
1448     }
1449     // If riding a horse - chance to spook
1450     if( u.is_mounted() ) {
1451         u.check_mount_is_spooked();
1452     }
1453     if( calendar::once_every( 1_days ) ) {
1454         overmap_buffer.process_mongroups();
1455     }
1456 
1457     // Move hordes every 2.5 min
1458     if( calendar::once_every( time_duration::from_minutes( 2.5 ) ) ) {
1459         overmap_buffer.move_hordes();
1460         // Hordes that reached the reality bubble need to spawn,
1461         // make them spawn in invisible areas only.
1462         m.spawn_monsters( false );
1463     }
1464 
1465     debug_hour_timer.print_time();
1466 
1467     u.update_body();
1468 
1469     // Auto-save if autosave is enabled
1470     if( get_option<bool>( "AUTOSAVE" ) &&
1471         calendar::once_every( 1_turns * get_option<int>( "AUTOSAVE_TURNS" ) ) &&
1472         !u.is_dead_state() ) {
1473         autosave();
1474     }
1475 
1476     weather.update_weather();
1477     reset_light_level();
1478 
1479     perhaps_add_random_npc();
1480     process_activity();
1481     // Process NPC sound events before they move or they hear themselves talking
1482     for( npc &guy : all_npcs() ) {
1483         if( rl_dist( guy.pos(), u.pos() ) < MAX_VIEW_DISTANCE ) {
1484             sounds::process_sound_markers( &guy );
1485         }
1486     }
1487 
1488     // Process sound events into sound markers for display to the player.
1489     sounds::process_sound_markers( &u );
1490 
1491     if( u.is_deaf() ) {
1492         sfx::do_hearing_loss();
1493     }
1494 
1495     if( !u.has_effect( efftype_id( "sleep" ) ) || uquit == QUIT_WATCH ) {
1496         if( u.moves > 0 || uquit == QUIT_WATCH ) {
1497             while( u.moves > 0 || uquit == QUIT_WATCH ) {
1498                 cleanup_dead();
1499                 mon_info_update();
1500                 // Process any new sounds the player caused during their turn.
1501                 for( npc &guy : all_npcs() ) {
1502                     if( rl_dist( guy.pos(), u.pos() ) < MAX_VIEW_DISTANCE ) {
1503                         sounds::process_sound_markers( &guy );
1504                     }
1505                 }
1506                 sounds::process_sound_markers( &u );
1507                 if( !u.activity && !u.has_distant_destination() && uquit != QUIT_WATCH ) {
1508                     wait_popup.reset();
1509                     ui_manager::redraw();
1510                 }
1511 
1512                 if( handle_action() ) {
1513                     ++moves_since_last_save;
1514                     u.action_taken();
1515                 }
1516 
1517                 if( is_game_over() ) {
1518                     return cleanup_at_end();
1519                 }
1520 
1521                 if( uquit == QUIT_WATCH ) {
1522                     break;
1523                 }
1524                 if( u.activity ) {
1525                     process_activity();
1526                 }
1527             }
1528             // Reset displayed sound markers now that the turn is over.
1529             // We only want this to happen if the player had a chance to examine the sounds.
1530             sounds::reset_markers();
1531         } else {
1532             // Rate limit key polling to 10 times a second.
1533             static auto start = std::chrono::time_point_cast<std::chrono::milliseconds>(
1534                                     std::chrono::system_clock::now() );
1535             const auto now = std::chrono::time_point_cast<std::chrono::milliseconds>(
1536                                  std::chrono::system_clock::now() );
1537             if( ( now - start ).count() > 100 ) {
1538                 handle_key_blocking_activity();
1539                 start = now;
1540             }
1541 
1542             mon_info_update();
1543 
1544             // If player is performing a task, a monster is dangerously close,
1545             // and monster can reach to the player or it has some sort of a ranged attack,
1546             // warn them regardless of previous safemode warnings
1547             if( u.activity && !u.has_activity( activity_id( "ACT_AIM" ) ) &&
1548                 u.activity.moves_left > 0 &&
1549                 !u.activity.is_distraction_ignored( distraction_type::hostile_spotted_near ) ) {
1550                 Creature *hostile_critter = is_hostile_very_close( true );
1551 
1552                 if( hostile_critter != nullptr ) {
1553                     cancel_activity_or_ignore_query( distraction_type::hostile_spotted_near,
1554                                                      string_format( _( "The %s is dangerously close!" ),
1555                                                              hostile_critter->get_name() ) );
1556                 }
1557             }
1558 
1559         }
1560     }
1561 
1562     if( driving_view_offset.x != 0 || driving_view_offset.y != 0 ) {
1563         // Still have a view offset, but might not be driving anymore,
1564         // or the option has been deactivated,
1565         // might also happen when someone dives from a moving car.
1566         // or when using the handbrake.
1567         vehicle *veh = veh_pointer_or_null( m.veh_at( u.pos() ) );
1568         calc_driving_offset( veh );
1569     }
1570 
1571     // No-scent debug mutation has to be processed here or else it takes time to start working
1572     if( !u.has_flag( STATIC( json_character_flag( "NO_SCENT" ) ) ) ) {
1573         scent.set( u.pos(), u.scent, u.get_type_of_scent() );
1574         overmap_buffer.set_scent( u.global_omt_location(),  u.scent );
1575     }
1576     scent.update( u.pos(), m );
1577 
1578     // We need floor cache before checking falling 'n stuff
1579     m.build_floor_caches();
1580 
1581     m.process_falling();
1582     m.vehmove();
1583     m.process_fields();
1584     m.process_items();
1585     explosion_handler::process_explosions();
1586     m.creature_in_field( u );
1587 
1588     // Apply sounds from previous turn to monster and NPC AI.
1589     sounds::process_sounds();
1590     const int levz = m.get_abs_sub().z;
1591     // Update vision caches for monsters. If this turns out to be expensive,
1592     // consider a stripped down cache just for monsters.
1593     m.build_map_cache( levz, true );
1594     monmove();
1595     if( calendar::once_every( 5_minutes ) ) {
1596         overmap_npc_move();
1597     }
1598     if( calendar::once_every( 10_seconds ) ) {
1599         for( const tripoint &elem : m.get_furn_field_locations() ) {
1600             const furn_t &furn = *m.furn( elem );
1601             for( const emit_id &e : furn.emissions ) {
1602                 m.emit_field( elem, e );
1603             }
1604         }
1605         for( const tripoint &elem : m.get_ter_field_locations() ) {
1606             const ter_t &ter = *m.ter( elem );
1607             for( const emit_id &e : ter.emissions ) {
1608                 m.emit_field( elem, e );
1609             }
1610         }
1611     }
1612     update_stair_monsters();
1613     mon_info_update();
1614     u.process_turn();
1615     if( u.moves < 0 && get_option<bool>( "FORCE_REDRAW" ) ) {
1616         ui_manager::redraw();
1617         refresh_display();
1618     }
1619 
1620     if( levz >= 0 && !u.is_underwater() ) {
1621         handle_weather_effects( weather.weather_id );
1622     }
1623 
1624     const bool player_is_sleeping = u.has_effect( effect_sleep );
1625     bool wait_redraw = false;
1626     std::string wait_message;
1627     time_duration wait_refresh_rate;
1628     if( player_is_sleeping ) {
1629         wait_redraw = true;
1630         wait_message = _( "Wait till you wake up…" );
1631         wait_refresh_rate = 30_minutes;
1632     } else if( const cata::optional<std::string> progress = u.activity.get_progress_message( u ) ) {
1633         wait_redraw = true;
1634         wait_message = *progress;
1635         if( u.activity.is_interruptible() && u.activity.interruptable_with_kb ) {
1636             wait_message += string_format( _( "\n%s to interrupt" ), press_x( ACTION_PAUSE ) );
1637         }
1638         wait_refresh_rate = 5_minutes;
1639     }
1640     if( wait_redraw ) {
1641         if( first_redraw_since_waiting_started || calendar::once_every( 1_minutes ) ) {
1642             if( first_redraw_since_waiting_started || calendar::once_every( wait_refresh_rate ) ) {
1643                 ui_manager::redraw();
1644             }
1645 
1646             // Avoid redrawing the main UI every time due to invalidation
1647             ui_adaptor dummy( ui_adaptor::disable_uis_below {} );
1648             wait_popup = std::make_unique<static_popup>();
1649             wait_popup->on_top( true ).wait_message( "%s", wait_message );
1650             ui_manager::redraw();
1651             refresh_display();
1652             first_redraw_since_waiting_started = false;
1653         }
1654     } else {
1655         // Nothing to wait for now
1656         wait_popup.reset();
1657         first_redraw_since_waiting_started = true;
1658     }
1659 
1660     u.update_bodytemp();
1661     u.update_body_wetness( *weather.weather_precise );
1662     u.apply_wetness_morale( weather.temperature );
1663 
1664     if( calendar::once_every( 1_minutes ) ) {
1665         u.update_morale();
1666     }
1667 
1668     if( calendar::once_every( 9_turns ) ) {
1669         u.check_and_recover_morale();
1670     }
1671 
1672     if( !u.is_deaf() ) {
1673         sfx::remove_hearing_loss();
1674     }
1675     sfx::do_danger_music();
1676     sfx::do_vehicle_engine_sfx();
1677     sfx::do_vehicle_exterior_engine_sfx();
1678     sfx::do_fatigue();
1679 
1680     // reset player noise
1681     u.volume = 0;
1682 
1683     return false;
1684 }
1685 
set_driving_view_offset(const point & p)1686 void game::set_driving_view_offset( const point &p )
1687 {
1688     // remove the previous driving offset,
1689     // store the new offset and apply the new offset.
1690     u.view_offset.x -= driving_view_offset.x;
1691     u.view_offset.y -= driving_view_offset.y;
1692     driving_view_offset.x = p.x;
1693     driving_view_offset.y = p.y;
1694     u.view_offset.x += driving_view_offset.x;
1695     u.view_offset.y += driving_view_offset.y;
1696 }
1697 
process_activity()1698 void game::process_activity()
1699 {
1700     if( !u.activity ) {
1701         return;
1702     }
1703 
1704     while( u.moves > 0 && u.activity ) {
1705         u.activity.do_turn( u );
1706     }
1707 }
1708 
catch_a_monster(monster * fish,const tripoint & pos,player * p,const time_duration & catch_duration)1709 void game::catch_a_monster( monster *fish, const tripoint &pos, player *p,
1710                             const time_duration &catch_duration ) // catching function
1711 {
1712     //spawn the corpse, rotten by a part of the duration
1713     m.add_item_or_charges( pos, item::make_corpse( fish->type->id, calendar::turn + rng( 0_turns,
1714                            catch_duration ) ) );
1715     if( u.sees( pos ) ) {
1716         u.add_msg_if_player( m_good, _( "You caught a %s." ), fish->type->nname() );
1717     }
1718     //quietly kill the caught
1719     fish->no_corpse_quiet = true;
1720     fish->die( p );
1721 }
1722 
cancel_auto_move(player & p,const std::string & text)1723 static bool cancel_auto_move( player &p, const std::string &text )
1724 {
1725     if( p.has_destination() && query_yn( _( "%s, cancel Auto-move?" ), text ) )  {
1726         add_msg( m_warning, _( "%s. Auto-move canceled" ), text );
1727         if( !p.omt_path.empty() ) {
1728             p.omt_path.clear();
1729         }
1730         p.clear_destination();
1731         return true;
1732     }
1733     return false;
1734 }
1735 
cancel_activity_or_ignore_query(const distraction_type type,const std::string & text)1736 bool game::cancel_activity_or_ignore_query( const distraction_type type, const std::string &text )
1737 {
1738     if( u.has_distant_destination() ) {
1739         if( cancel_auto_move( u, text ) ) {
1740             return true;
1741         } else {
1742             u.set_destination( u.get_auto_move_route(), player_activity( activity_id( "ACT_TRAVELLING" ) ) );
1743             return false;
1744         }
1745     }
1746     if( !u.activity || u.activity.is_distraction_ignored( type ) ) {
1747         return false;
1748     }
1749     const bool force_uc = get_option<bool>( "FORCE_CAPITAL_YN" );
1750     const auto &allow_key = force_uc ? input_context::disallow_lower_case_or_non_modified_letters
1751                             : input_context::allow_all_keys;
1752 
1753     const auto &action = query_popup()
1754                          .preferred_keyboard_mode( keyboard_mode::keycode )
1755                          .context( "CANCEL_ACTIVITY_OR_IGNORE_QUERY" )
1756                          .message( force_uc && !is_keycode_mode_supported() ?
1757                                    pgettext( "cancel_activity_or_ignore_query",
1758                                            "<color_light_red>%s %s (Case Sensitive)</color>" ) :
1759                                    pgettext( "cancel_activity_or_ignore_query",
1760                                            "<color_light_red>%s %s</color>" ),
1761                                    text, u.activity.get_stop_phrase() )
1762                          .option( "YES", allow_key )
1763                          .option( "NO", allow_key )
1764                          .option( "IGNORE", allow_key )
1765                          .query()
1766                          .action;
1767 
1768     if( action == "YES" ) {
1769         u.cancel_activity();
1770         return true;
1771     }
1772     if( action == "IGNORE" ) {
1773         u.activity.ignore_distraction( type );
1774         for( auto &activity : u.backlog ) {
1775             activity.ignore_distraction( type );
1776         }
1777     }
1778 
1779     ui_manager::redraw();
1780     refresh_display();
1781 
1782     return false;
1783 }
1784 
cancel_activity_query(const std::string & text)1785 bool game::cancel_activity_query( const std::string &text )
1786 {
1787     if( u.has_distant_destination() ) {
1788         if( cancel_auto_move( u, text ) ) {
1789             return true;
1790         } else {
1791             u.set_destination( u.get_auto_move_route(), player_activity( activity_id( "ACT_TRAVELLING" ) ) );
1792             return false;
1793         }
1794     }
1795     if( !u.activity ) {
1796         return false;
1797     }
1798     if( query_yn( "%s %s", text, u.activity.get_stop_phrase() ) ) {
1799         u.cancel_activity();
1800         u.clear_destination();
1801         u.resume_backlog_activity();
1802         return true;
1803     }
1804     return false;
1805 }
1806 
get_seed() const1807 unsigned int game::get_seed() const
1808 {
1809     return seed;
1810 }
1811 
set_npcs_dirty()1812 void game::set_npcs_dirty()
1813 {
1814     npcs_dirty = true;
1815 }
1816 
set_critter_died()1817 void game::set_critter_died()
1818 {
1819     critter_died = true;
1820 }
1821 
maptile_field_intensity(maptile & mt,field_type_id fld)1822 static int maptile_field_intensity( maptile &mt, field_type_id fld )
1823 {
1824     const field_entry *field_ptr = mt.find_field( fld );
1825 
1826     return field_ptr == nullptr ? 0 : field_ptr->get_field_intensity();
1827 }
1828 
get_heat_radiation(const tripoint & location,bool direct)1829 int get_heat_radiation( const tripoint &location, bool direct )
1830 {
1831     // Direct heat from fire sources
1832     // Cache fires to avoid scanning the map around us bp times
1833     // Stored as intensity-distance pairs
1834     int temp_mod = 0;
1835     int best_fire = 0;
1836     Character &player_character = get_player_character();
1837     map &here = get_map();
1838     for( const tripoint &dest : here.points_in_radius( location, 6 ) ) {
1839         int heat_intensity = 0;
1840 
1841         maptile mt = here.maptile_at( dest );
1842 
1843         int ffire = maptile_field_intensity( mt, fd_fire );
1844         if( ffire > 0 ) {
1845             heat_intensity = ffire;
1846         } else  {
1847             heat_intensity = here.ter( dest )->heat_radiation;
1848         }
1849         if( heat_intensity == 0 ) {
1850             // No heat source here
1851             continue;
1852         }
1853         if( player_character.pos() == location ) {
1854             if( !here.pl_line_of_sight( dest, -1 ) ) {
1855                 continue;
1856             }
1857         } else if( !here.sees( location, dest, -1 ) ) {
1858             continue;
1859         }
1860         // Ensure fire_dist >= 1 to avoid divide-by-zero errors.
1861         const int fire_dist = std::max( 1, square_dist( dest, location ) );
1862         temp_mod += 6 * heat_intensity * heat_intensity / fire_dist;
1863         if( fire_dist <= 1 ) {
1864             // Extend limbs/lean over a single adjacent fire to warm up
1865             best_fire = std::max( best_fire, heat_intensity );
1866         }
1867     }
1868     if( direct ) {
1869         return best_fire;
1870     }
1871     return temp_mod;
1872 }
1873 
get_convection_temperature(const tripoint & location)1874 int get_convection_temperature( const tripoint &location )
1875 {
1876     int temp_mod = 0;
1877     map &here = get_map();
1878     // Directly on lava tiles
1879     int lava_mod = here.tr_at( location ) == tr_lava ?
1880                    fd_fire->get_intensity_level().convection_temperature_mod : 0;
1881     // Modifier from fields
1882     for( auto fd : here.field_at( location ) ) {
1883         // Nullify lava modifier when there is open fire
1884         if( fd.first.obj().has_fire ) {
1885             lava_mod = 0;
1886         }
1887         temp_mod += fd.second.get_intensity_level().convection_temperature_mod;
1888     }
1889     return temp_mod + lava_mod;
1890 }
1891 
assign_mission_id()1892 int game::assign_mission_id()
1893 {
1894     int ret = next_mission_id;
1895     next_mission_id++;
1896     return ret;
1897 }
1898 
find_npc(character_id id)1899 npc *game::find_npc( character_id id )
1900 {
1901     return overmap_buffer.find_npc( id ).get();
1902 }
1903 
add_npc_follower(const character_id & id)1904 void game::add_npc_follower( const character_id &id )
1905 {
1906     follower_ids.insert( id );
1907     u.follower_ids.insert( id );
1908 }
1909 
remove_npc_follower(const character_id & id)1910 void game::remove_npc_follower( const character_id &id )
1911 {
1912     follower_ids.erase( id );
1913     u.follower_ids.erase( id );
1914 }
1915 
update_faction_api(npc * guy)1916 static void update_faction_api( npc *guy )
1917 {
1918     if( guy->get_faction_ver() < 2 ) {
1919         guy->set_fac( faction_your_followers );
1920         guy->set_faction_ver( 2 );
1921     }
1922 }
1923 
validate_linked_vehicles()1924 void game::validate_linked_vehicles()
1925 {
1926     for( auto &veh : m.get_vehicles() ) {
1927         vehicle *v = veh.v;
1928         if( v->tow_data.other_towing_point != tripoint_zero ) {
1929             vehicle *other_v = veh_pointer_or_null( m.veh_at( v->tow_data.other_towing_point ) );
1930             if( other_v ) {
1931                 // the other vehicle is towing us.
1932                 v->tow_data.set_towing( other_v, v );
1933                 v->tow_data.other_towing_point = tripoint_zero;
1934             }
1935         }
1936     }
1937 }
1938 
validate_mounted_npcs()1939 void game::validate_mounted_npcs()
1940 {
1941     for( monster &m : all_monsters() ) {
1942         if( m.has_effect( effect_ridden ) && m.mounted_player_id.is_valid() ) {
1943             player *mounted_pl = g->critter_by_id<player>( m.mounted_player_id );
1944             if( !mounted_pl ) {
1945                 // Target no longer valid.
1946                 m.mounted_player_id = character_id();
1947                 m.remove_effect( effect_ridden );
1948                 continue;
1949             }
1950             mounted_pl->mounted_creature = shared_from( m );
1951             mounted_pl->setpos( m.pos() );
1952             mounted_pl->add_effect( effect_riding, 1_turns, true );
1953             m.mounted_player = mounted_pl;
1954         }
1955     }
1956 }
1957 
validate_npc_followers()1958 void game::validate_npc_followers()
1959 {
1960     // Make sure visible followers are in the list.
1961     const std::vector<npc *> visible_followers = get_npcs_if( [&]( const npc & guy ) {
1962         return guy.is_player_ally();
1963     } );
1964     for( npc *guy : visible_followers ) {
1965         update_faction_api( guy );
1966         add_npc_follower( guy->getID() );
1967     }
1968     // Make sure overmapbuffered NPC followers are in the list.
1969     for( const auto &temp_guy : overmap_buffer.get_npcs_near_player( 300 ) ) {
1970         npc *guy = temp_guy.get();
1971         if( guy->is_player_ally() ) {
1972             update_faction_api( guy );
1973             add_npc_follower( guy->getID() );
1974         }
1975     }
1976     // Make sure that serialized player followers sync up with game list
1977     for( const auto &temp_id : u.follower_ids ) {
1978         add_npc_follower( temp_id );
1979     }
1980 }
1981 
validate_camps()1982 void game::validate_camps()
1983 {
1984     basecamp camp = m.hoist_submap_camp( u.pos() );
1985     if( camp.is_valid() ) {
1986         overmap_buffer.add_camp( camp );
1987         m.remove_submap_camp( u.pos() );
1988     } else if( camp.camp_omt_pos() != tripoint_abs_omt() ) {
1989         std::string camp_name = _( "Faction Camp" );
1990         camp.set_name( camp_name );
1991         overmap_buffer.add_camp( camp );
1992         m.remove_submap_camp( u.pos() );
1993     }
1994 }
1995 
get_follower_list()1996 std::set<character_id> game::get_follower_list()
1997 {
1998     return follower_ids;
1999 }
2000 
handle_key_blocking_activity()2001 void game::handle_key_blocking_activity()
2002 {
2003     if( ( u.activity && u.activity.moves_left > 0 ) || ( u.has_destination() &&
2004             !u.omt_path.empty() ) ) {
2005         input_context ctxt = get_default_mode_input_context();
2006         const std::string action = ctxt.handle_input( 0 );
2007         bool refresh = true;
2008         if( action == "pause" ) {
2009             if( u.activity.interruptable_with_kb ) {
2010                 cancel_activity_query( _( "Confirm:" ) );
2011             }
2012         } else if( action == "player_data" ) {
2013             u.disp_info();
2014         } else if( action == "messages" ) {
2015             Messages::display_messages();
2016         } else if( action == "help" ) {
2017             get_help().display_help();
2018         } else if( action != "HELP_KEYBINDINGS" ) {
2019             refresh = false;
2020         }
2021         if( refresh ) {
2022             ui_manager::redraw();
2023             refresh_display();
2024         }
2025     }
2026 }
2027 
rate_action_change_side(const avatar & you,const item & it)2028 static hint_rating rate_action_change_side( const avatar &you, const item &it )
2029 {
2030     if( !it.is_sided() ) {
2031         return hint_rating::cant;
2032     }
2033 
2034     return you.is_worn( it ) ? hint_rating::good : hint_rating::iffy;
2035 }
2036 
rate_action_disassemble(avatar & you,const item & it)2037 static hint_rating rate_action_disassemble( avatar &you, const item &it )
2038 {
2039     if( you.can_disassemble( it, you.crafting_inventory() ).success() ) {
2040         // Possible right now
2041         return hint_rating::good;
2042     } else if( it.is_disassemblable() ) {
2043         // Potentially possible, but we currently lack requirements
2044         return hint_rating::iffy;
2045     } else {
2046         // Never possible
2047         return hint_rating::cant;
2048     }
2049 }
2050 
rate_action_eat(const avatar & you,const item & it)2051 static hint_rating rate_action_eat( const avatar &you, const item &it )
2052 {
2053     if( !you.can_consume( it ) ) {
2054         return hint_rating::cant;
2055     }
2056 
2057     const auto rating = you.will_eat( it );
2058     if( rating.success() ) {
2059         return hint_rating::good;
2060     } else if( rating.value() == INEDIBLE || rating.value() == INEDIBLE_MUTATION ) {
2061         return hint_rating::cant;
2062     }
2063 
2064     return hint_rating::iffy;
2065 }
2066 
rate_action_mend(const avatar &,const item & it)2067 static hint_rating rate_action_mend( const avatar &, const item &it )
2068 {
2069     // TODO: check also if item damage could be repaired via a tool
2070     if( !it.faults.empty() ) {
2071         return hint_rating::good;
2072     }
2073     return it.faults_potential().empty() ? hint_rating::cant : hint_rating::iffy;
2074 }
2075 
rate_action_read(const avatar & you,const item & it)2076 static hint_rating rate_action_read( const avatar &you, const item &it )
2077 {
2078     if( !it.is_book() ) {
2079         return hint_rating::cant;
2080     }
2081 
2082     std::vector<std::string> dummy;
2083     return you.get_book_reader( it, dummy ) ? hint_rating::good : hint_rating::iffy;
2084 }
2085 
rate_action_take_off(const avatar & you,const item & it)2086 static hint_rating rate_action_take_off( const avatar &you, const item &it )
2087 {
2088     if( !it.is_armor() || it.has_flag( flag_NO_TAKEOFF ) ) {
2089         return hint_rating::cant;
2090     }
2091 
2092     if( you.is_worn( it ) ) {
2093         return hint_rating::good;
2094     }
2095 
2096     return hint_rating::iffy;
2097 }
2098 
rate_action_use(const avatar & you,const item & it)2099 static hint_rating rate_action_use( const avatar &you, const item &it )
2100 {
2101     if( it.is_tool() ) {
2102         return it.ammo_sufficient() ? hint_rating::good : hint_rating::iffy;
2103     } else if( it.is_gunmod() ) {
2104         /** @EFFECT_GUN >0 allows rating estimates for gun modifications */
2105         if( you.get_skill_level( skill_gun ) == 0 ) {
2106             return hint_rating::iffy;
2107         } else {
2108             return hint_rating::good;
2109         }
2110     } else if( it.is_food() || it.is_medication() || it.is_book() || it.is_armor() ) {
2111         if( it.is_medication() && !you.can_use_heal_item( it ) ) {
2112             return hint_rating::cant;
2113         }
2114         // The rating is subjective, could be argued as hint_rating::cant or hint_rating::good as well
2115         return hint_rating::iffy;
2116     } else if( it.type->has_use() ) {
2117         return hint_rating::good;
2118     }
2119 
2120     return hint_rating::cant;
2121 }
2122 
rate_action_wear(const avatar & you,const item & it)2123 static hint_rating rate_action_wear( const avatar &you, const item &it )
2124 {
2125     if( !it.is_armor() ) {
2126         return hint_rating::cant;
2127     }
2128 
2129     if( you.is_worn( it ) ) {
2130         return hint_rating::iffy;
2131     }
2132 
2133     return you.can_wear( it ).success() ? hint_rating::good : hint_rating::iffy;
2134 }
2135 
rate_action_wield(const avatar & you,const item & it)2136 static hint_rating rate_action_wield( const avatar &you, const item &it )
2137 {
2138     return you.can_wield( it ).success() ? hint_rating::good : hint_rating::iffy;
2139 }
2140 
rate_action_insert(const avatar & you,const item_location & loc)2141 static hint_rating rate_action_insert( const avatar &you, const item_location &loc )
2142 {
2143     if( loc->will_spill_if_unsealed()
2144         && loc.where() != item_location::type::map
2145         && !you.is_wielding( *loc ) ) {
2146 
2147         return hint_rating::cant;
2148     }
2149     return hint_rating::good;
2150 }
2151 
2152 /* item submenu for 'i' and '/'
2153 * It use draw_item_info to draw item info and action menu
2154 *
2155 * @param locThisItem the item
2156 * @param iStartX Left coordinate of the item info window
2157 * @param iWidth width of the item info window (height = height of terminal)
2158 * @return getch
2159 */
inventory_item_menu(item_location locThisItem,const std::function<int ()> & iStartX,const std::function<int ()> & iWidth,const inventory_item_menu_position position)2160 int game::inventory_item_menu( item_location locThisItem,
2161                                const std::function<int()> &iStartX,
2162                                const std::function<int()> &iWidth,
2163                                const inventory_item_menu_position position )
2164 {
2165     int cMenu = static_cast<int>( '+' );
2166 
2167     item &oThisItem = *locThisItem;
2168     if( u.has_item( oThisItem ) ) {
2169 #if defined(__ANDROID__)
2170         if( get_option<bool>( "ANDROID_INVENTORY_AUTOADD" ) ) {
2171             add_key_to_quick_shortcuts( oThisItem.invlet, "INVENTORY", false );
2172         }
2173 #endif
2174 
2175         std::vector<iteminfo> vThisItem;
2176         std::vector<iteminfo> vDummy;
2177 
2178         const bool bHPR = get_auto_pickup().has_rule( &oThisItem );
2179         const hint_rating rate_drop_item = u.weapon.has_flag( flag_NO_UNWIELD ) ? hint_rating::cant :
2180                                            hint_rating::good;
2181 
2182         uilist action_menu;
2183         action_menu.allow_anykey = true;
2184         const auto addentry = [&]( const char key, const std::string & text, const hint_rating hint ) {
2185             // The char is used as retval from the uilist *and* as hotkey.
2186             action_menu.addentry( key, true, key, text );
2187             auto &entry = action_menu.entries.back();
2188             switch( hint ) {
2189                 case hint_rating::cant:
2190                     entry.text_color = c_light_gray;
2191                     break;
2192                 case hint_rating::iffy:
2193                     entry.text_color = c_light_red;
2194                     break;
2195                 case hint_rating::good:
2196                     entry.text_color = c_light_green;
2197                     break;
2198             }
2199         };
2200         addentry( 'a', pgettext( "action", "activate" ), rate_action_use( u, oThisItem ) );
2201         addentry( 'R', pgettext( "action", "read" ), rate_action_read( u, oThisItem ) );
2202         addentry( 'E', pgettext( "action", "eat" ), rate_action_eat( u, oThisItem ) );
2203         addentry( 'W', pgettext( "action", "wear" ), rate_action_wear( u, oThisItem ) );
2204         addentry( 'w', pgettext( "action", "wield" ), rate_action_wield( u, oThisItem ) );
2205         addentry( 't', pgettext( "action", "throw" ), rate_action_wield( u, oThisItem ) );
2206         addentry( 'c', pgettext( "action", "change side" ), rate_action_change_side( u, oThisItem ) );
2207         addentry( 'T', pgettext( "action", "take off" ), rate_action_take_off( u, oThisItem ) );
2208         addentry( 'd', pgettext( "action", "drop" ), rate_drop_item );
2209         addentry( 'U', pgettext( "action", "unload" ), u.rate_action_unload( oThisItem ) );
2210         addentry( 'r', pgettext( "action", "reload" ), u.rate_action_reload( oThisItem ) );
2211         addentry( 'p', pgettext( "action", "part reload" ), u.rate_action_reload( oThisItem ) );
2212         addentry( 'm', pgettext( "action", "mend" ), rate_action_mend( u, oThisItem ) );
2213         addentry( 'D', pgettext( "action", "disassemble" ), rate_action_disassemble( u, oThisItem ) );
2214         if( oThisItem.has_pockets() ) {
2215             addentry( 'i', pgettext( "action", "insert" ), rate_action_insert( u, locThisItem ) );
2216             if( oThisItem.contents.num_item_stacks() > 0 ) {
2217                 addentry( 'o', pgettext( "action", "open" ), hint_rating::good );
2218             }
2219             addentry( 'v', pgettext( "action", "pocket autopickup settings" ), hint_rating::good );
2220         }
2221 
2222         if( oThisItem.is_favorite ) {
2223             addentry( 'f', pgettext( "action", "unfavorite" ), hint_rating::good );
2224         } else {
2225             addentry( 'f', pgettext( "action", "favorite" ), hint_rating::good );
2226         }
2227 
2228         addentry( '=', pgettext( "action", "reassign" ), hint_rating::good );
2229 
2230         if( bHPR ) {
2231             addentry( '-', _( "Autopickup" ), hint_rating::iffy );
2232         } else {
2233             addentry( '+', _( "Autopickup" ), hint_rating::good );
2234         }
2235 
2236         int iScrollPos = 0;
2237         oThisItem.info( true, vThisItem );
2238 
2239         action_menu.w_y_setup = 0;
2240         action_menu.w_x_setup = [&]( const int popup_width ) -> int {
2241             switch( position )
2242             {
2243                 default:
2244                 case RIGHT_TERMINAL_EDGE:
2245                     return 0;
2246                 case LEFT_OF_INFO:
2247                     return iStartX() - popup_width;
2248                 case RIGHT_OF_INFO:
2249                     return iStartX() + iWidth();
2250                 case LEFT_TERMINAL_EDGE:
2251                     return TERMX - popup_width;
2252             }
2253         };
2254         // Filtering isn't needed, the number of entries is manageable.
2255         action_menu.filtering = false;
2256         // Default menu border color is different, this matches the border of the item info window.
2257         action_menu.border_color = BORDER_COLOR;
2258 
2259         item_info_data data( oThisItem.tname(), oThisItem.type_name(), vThisItem, vDummy, iScrollPos );
2260         data.without_getch = true;
2261 
2262         catacurses::window w_info;
2263         int iScrollHeight = 0;
2264 
2265         std::unique_ptr<ui_adaptor> ui = std::make_unique<ui_adaptor>();
2266         ui->on_screen_resize( [&]( ui_adaptor & ui ) {
2267             w_info = catacurses::newwin( TERMY, iWidth(), point( iStartX(), 0 ) );
2268             iScrollHeight = TERMY - 2;
2269             ui.position_from_window( w_info );
2270         } );
2271         ui->mark_resize();
2272 
2273         ui->on_redraw( [&]( const ui_adaptor & ) {
2274             draw_item_info( w_info, data );
2275         } );
2276 
2277         action_menu.additional_actions = {
2278             { "RIGHT", translation() }
2279         };
2280 
2281         bool exit = false;
2282         do {
2283             const int prev_selected = action_menu.selected;
2284             action_menu.query( false );
2285             if( action_menu.ret >= 0 ) {
2286                 cMenu = action_menu.ret; /* Remember: hotkey == retval, see addentry above. */
2287             } else if( action_menu.ret == UILIST_UNBOUND && action_menu.ret_act == "RIGHT" ) {
2288                 // Simulate KEY_RIGHT == '\n' (confirm currently selected entry) for compatibility with old version.
2289                 // TODO: ideally this should be done in the uilist, maybe via a callback.
2290                 cMenu = action_menu.ret = action_menu.entries[action_menu.selected].retval;
2291             } else if( action_menu.ret_act == "PAGE_UP" || action_menu.ret_act == "PAGE_DOWN" ) {
2292                 cMenu = action_menu.ret_act == "PAGE_UP" ? KEY_PPAGE : KEY_NPAGE;
2293                 // Prevent the menu from scrolling with this key. TODO: Ideally the menu
2294                 // could be instructed to ignore these two keys instead of scrolling.
2295                 action_menu.selected = prev_selected;
2296                 action_menu.fselected = prev_selected;
2297                 action_menu.vshift = 0;
2298             } else {
2299                 cMenu = 0;
2300             }
2301 
2302             if( action_menu.ret != UILIST_WAIT_INPUT && action_menu.ret != UILIST_UNBOUND ) {
2303                 exit = true;
2304                 ui = nullptr;
2305             }
2306 
2307             switch( cMenu ) {
2308                 case 'a': {
2309                     contents_change_handler handler;
2310                     handler.unseal_pocket_containing( locThisItem );
2311                     avatar_action::use_item( u, locThisItem );
2312                     handler.handle_by( u );
2313                     break;
2314                 }
2315                 case 'E':
2316                     avatar_action::eat( u, locThisItem );
2317                     break;
2318                 case 'W': {
2319                     contents_change_handler handler;
2320                     handler.unseal_pocket_containing( locThisItem );
2321                     u.wear( locThisItem );
2322                     handler.handle_by( u );
2323                     break;
2324                 }
2325                 case 'w':
2326                     if( u.can_wield( *locThisItem ).success() ) {
2327                         contents_change_handler handler;
2328                         handler.unseal_pocket_containing( locThisItem );
2329                         wield( locThisItem );
2330                         handler.handle_by( u );
2331                     } else {
2332                         add_msg( m_info, "%s", u.can_wield( *locThisItem ).c_str() );
2333                     }
2334                     break;
2335                 case 't': {
2336                     contents_change_handler handler;
2337                     handler.unseal_pocket_containing( locThisItem );
2338                     avatar_action::plthrow( u, locThisItem );
2339                     handler.handle_by( u );
2340                     break;
2341                 }
2342                 case 'c':
2343                     u.change_side( locThisItem );
2344                     break;
2345                 case 'T':
2346                     u.takeoff( oThisItem );
2347                     break;
2348                 case 'd':
2349                     u.drop( locThisItem, u.pos() );
2350                     break;
2351                 case 'U':
2352                     u.unload( locThisItem );
2353                     break;
2354                 case 'r':
2355                     reload( locThisItem );
2356                     break;
2357                 case 'p':
2358                     reload( locThisItem, true );
2359                     break;
2360                 case 'm':
2361                     avatar_action::mend( u, locThisItem );
2362                     break;
2363                 case 'R':
2364                     u.read( oThisItem );
2365                     break;
2366                 case 'D':
2367                     u.disassemble( locThisItem, false );
2368                     break;
2369                 case 'f':
2370                     oThisItem.is_favorite = !oThisItem.is_favorite;
2371                     if( locThisItem.has_parent() ) {
2372                         item_location parent = locThisItem.parent_item();
2373                         item_pocket *const pocket = parent->contained_where( oThisItem );
2374                         if( pocket ) {
2375                             pocket->restack();
2376                         } else {
2377                             debugmsg( "parent container does not contain item" );
2378                         }
2379                     }
2380                     break;
2381                 case 'v':
2382                     if( oThisItem.has_pockets() ) {
2383                         oThisItem.contents.favorite_settings_menu( oThisItem.tname( 1, false ) );
2384                     }
2385                     break;
2386                 case 'i':
2387                     if( oThisItem.has_pockets() ) {
2388                         game_menus::inv::insert_items( u, locThisItem );
2389                     }
2390                     break;
2391                 case 'o':
2392                     if( oThisItem.has_pockets() && oThisItem.contents.num_item_stacks() > 0 ) {
2393                         game_menus::inv::common( locThisItem, u );
2394                     }
2395                     break;
2396                 case '=':
2397                     game_menus::inv::reassign_letter( u, oThisItem );
2398                     break;
2399                 case KEY_PPAGE:
2400                     iScrollPos -= iScrollHeight;
2401                     if( ui ) {
2402                         ui->invalidate_ui();
2403                     }
2404                     break;
2405                 case KEY_NPAGE:
2406                     iScrollPos += iScrollHeight;
2407                     if( ui ) {
2408                         ui->invalidate_ui();
2409                     }
2410                     break;
2411                 case '+':
2412                     if( !bHPR ) {
2413                         get_auto_pickup().add_rule( &oThisItem );
2414                         add_msg( m_info, _( "'%s' added to character pickup rules." ), oThisItem.tname( 1,
2415                                  false ) );
2416                     }
2417                     break;
2418                 case '-':
2419                     if( bHPR ) {
2420                         get_auto_pickup().remove_rule( &oThisItem );
2421                         add_msg( m_info, _( "'%s' removed from character pickup rules." ), oThisItem.tname( 1,
2422                                  false ) );
2423                     }
2424                     break;
2425                 default:
2426                     break;
2427             }
2428         } while( !exit );
2429     }
2430     return cMenu;
2431 }
2432 
2433 // Checks input to see if mouse was moved and handles the mouse view box accordingly.
2434 // Returns true if input requires breaking out into a game action.
handle_mouseview(input_context & ctxt,std::string & action)2435 bool game::handle_mouseview( input_context &ctxt, std::string &action )
2436 {
2437     cata::optional<tripoint> liveview_pos;
2438 
2439     do {
2440         action = ctxt.handle_input();
2441         if( action == "MOUSE_MOVE" ) {
2442             const cata::optional<tripoint> mouse_pos = ctxt.get_coordinates( w_terrain );
2443             if( mouse_pos && ( !liveview_pos || *mouse_pos != *liveview_pos ) ) {
2444                 liveview_pos = mouse_pos;
2445                 liveview.show( *liveview_pos );
2446             } else if( !mouse_pos ) {
2447                 liveview_pos.reset();
2448                 liveview.hide();
2449             }
2450             ui_manager::redraw();
2451         }
2452     } while( action == "MOUSE_MOVE" ); // Freeze animation when moving the mouse
2453 
2454     if( action != "TIMEOUT" ) {
2455         // Keyboard event, break out of animation loop
2456         liveview.hide();
2457         return false;
2458     }
2459 
2460     // Mouse movement or un-handled key
2461     return true;
2462 }
2463 
mouse_edge_scrolling(input_context & ctxt,const int speed,const tripoint & last,bool iso)2464 std::pair<tripoint, tripoint> game::mouse_edge_scrolling( input_context &ctxt, const int speed,
2465         const tripoint &last, bool iso )
2466 {
2467     const int rate = get_option<int>( "EDGE_SCROLL" );
2468     auto ret = std::make_pair( tripoint_zero, last );
2469     if( rate == -1 ) {
2470         // Fast return when the option is disabled.
2471         return ret;
2472     }
2473     // Ensure the parameters are used even if the #if below is false
2474     ( void ) ctxt;
2475     ( void ) speed;
2476     ( void ) iso;
2477 #if (defined TILES || defined _WIN32 || defined WINDOWS)
2478     auto now = std::chrono::steady_clock::now();
2479     if( now < last_mouse_edge_scroll + std::chrono::milliseconds( rate ) ) {
2480         return ret;
2481     } else {
2482         last_mouse_edge_scroll = now;
2483     }
2484     const input_event event = ctxt.get_raw_input();
2485     if( event.type == input_event_t::mouse ) {
2486         const point threshold( projected_window_width() / 100, projected_window_height() / 100 );
2487         if( event.mouse_pos.x <= threshold.x ) {
2488             ret.first.x -= speed;
2489             if( iso ) {
2490                 ret.first.y -= speed;
2491             }
2492         } else if( event.mouse_pos.x >= projected_window_width() - threshold.x ) {
2493             ret.first.x += speed;
2494             if( iso ) {
2495                 ret.first.y += speed;
2496             }
2497         }
2498         if( event.mouse_pos.y <= threshold.y ) {
2499             ret.first.y -= speed;
2500             if( iso ) {
2501                 ret.first.x += speed;
2502             }
2503         } else if( event.mouse_pos.y >= projected_window_height() - threshold.y ) {
2504             ret.first.y += speed;
2505             if( iso ) {
2506                 ret.first.x -= speed;
2507             }
2508         }
2509         ret.second = ret.first;
2510     } else if( event.type == input_event_t::timeout ) {
2511         ret.first = ret.second;
2512     }
2513 #endif
2514     return ret;
2515 }
2516 
mouse_edge_scrolling_terrain(input_context & ctxt)2517 tripoint game::mouse_edge_scrolling_terrain( input_context &ctxt )
2518 {
2519     auto ret = mouse_edge_scrolling( ctxt, std::max( DEFAULT_TILESET_ZOOM / tileset_zoom, 1 ),
2520                                      last_mouse_edge_scroll_vector_terrain, tile_iso );
2521     last_mouse_edge_scroll_vector_terrain = ret.second;
2522     last_mouse_edge_scroll_vector_overmap = tripoint_zero;
2523     return ret.first;
2524 }
2525 
mouse_edge_scrolling_overmap(input_context & ctxt)2526 tripoint game::mouse_edge_scrolling_overmap( input_context &ctxt )
2527 {
2528     // overmap has no iso mode
2529     auto ret = mouse_edge_scrolling( ctxt, 2, last_mouse_edge_scroll_vector_overmap, false );
2530     last_mouse_edge_scroll_vector_overmap = ret.second;
2531     last_mouse_edge_scroll_vector_terrain = tripoint_zero;
2532     return ret.first;
2533 }
2534 
get_default_mode_input_context()2535 input_context get_default_mode_input_context()
2536 {
2537     input_context ctxt( "DEFAULTMODE", keyboard_mode::keycode );
2538     // Because those keys move the character, they don't pan, as their original name says
2539     ctxt.set_iso( true );
2540     ctxt.register_action( "UP", to_translation( "Move North" ) );
2541     ctxt.register_action( "RIGHTUP", to_translation( "Move Northeast" ) );
2542     ctxt.register_action( "RIGHT", to_translation( "Move East" ) );
2543     ctxt.register_action( "RIGHTDOWN", to_translation( "Move Southeast" ) );
2544     ctxt.register_action( "DOWN", to_translation( "Move South" ) );
2545     ctxt.register_action( "LEFTDOWN", to_translation( "Move Southwest" ) );
2546     ctxt.register_action( "LEFT", to_translation( "Move West" ) );
2547     ctxt.register_action( "LEFTUP", to_translation( "Move Northwest" ) );
2548     ctxt.register_action( "pause" );
2549     ctxt.register_action( "LEVEL_DOWN", to_translation( "Descend Stairs" ) );
2550     ctxt.register_action( "LEVEL_UP", to_translation( "Ascend Stairs" ) );
2551     ctxt.register_action( "toggle_map_memory" );
2552     ctxt.register_action( "center" );
2553     ctxt.register_action( "shift_n" );
2554     ctxt.register_action( "shift_ne" );
2555     ctxt.register_action( "shift_e" );
2556     ctxt.register_action( "shift_se" );
2557     ctxt.register_action( "shift_s" );
2558     ctxt.register_action( "shift_sw" );
2559     ctxt.register_action( "shift_w" );
2560     ctxt.register_action( "shift_nw" );
2561     ctxt.register_action( "cycle_move" );
2562     ctxt.register_action( "reset_move" );
2563     ctxt.register_action( "toggle_run" );
2564     ctxt.register_action( "toggle_crouch" );
2565     ctxt.register_action( "open_movement" );
2566     ctxt.register_action( "open" );
2567     ctxt.register_action( "close" );
2568     ctxt.register_action( "smash" );
2569     ctxt.register_action( "loot" );
2570     ctxt.register_action( "examine" );
2571     ctxt.register_action( "advinv" );
2572     ctxt.register_action( "pickup" );
2573     ctxt.register_action( "pickup_feet" );
2574     ctxt.register_action( "grab" );
2575     ctxt.register_action( "haul" );
2576     ctxt.register_action( "butcher" );
2577     ctxt.register_action( "chat" );
2578     ctxt.register_action( "look" );
2579     ctxt.register_action( "peek" );
2580     ctxt.register_action( "listitems" );
2581     ctxt.register_action( "zones" );
2582     ctxt.register_action( "inventory" );
2583     ctxt.register_action( "compare" );
2584     ctxt.register_action( "organize" );
2585     ctxt.register_action( "apply" );
2586     ctxt.register_action( "apply_wielded" );
2587     ctxt.register_action( "wear" );
2588     ctxt.register_action( "take_off" );
2589     ctxt.register_action( "eat" );
2590     ctxt.register_action( "open_consume" );
2591     ctxt.register_action( "read" );
2592     ctxt.register_action( "wield" );
2593     ctxt.register_action( "pick_style" );
2594     ctxt.register_action( "reload_item" );
2595     ctxt.register_action( "reload_weapon" );
2596     ctxt.register_action( "reload_wielded" );
2597     ctxt.register_action( "unload" );
2598     ctxt.register_action( "throw" );
2599     ctxt.register_action( "fire" );
2600     ctxt.register_action( "cast_spell" );
2601     ctxt.register_action( "fire_burst" );
2602     ctxt.register_action( "select_fire_mode" );
2603     ctxt.register_action( "drop" );
2604     ctxt.register_action( "drop_adj" );
2605     ctxt.register_action( "bionics" );
2606     ctxt.register_action( "mutations" );
2607     ctxt.register_action( "sort_armor" );
2608     ctxt.register_action( "wait" );
2609     ctxt.register_action( "craft" );
2610     ctxt.register_action( "recraft" );
2611     ctxt.register_action( "long_craft" );
2612     ctxt.register_action( "construct" );
2613     ctxt.register_action( "disassemble" );
2614     ctxt.register_action( "sleep" );
2615     ctxt.register_action( "control_vehicle" );
2616     ctxt.register_action( "auto_travel_mode" );
2617     ctxt.register_action( "safemode" );
2618     ctxt.register_action( "autosafe" );
2619     ctxt.register_action( "autoattack" );
2620     ctxt.register_action( "ignore_enemy" );
2621     ctxt.register_action( "whitelist_enemy" );
2622     ctxt.register_action( "workout" );
2623     ctxt.register_action( "save" );
2624     ctxt.register_action( "quicksave" );
2625 #if !defined(RELEASE)
2626     ctxt.register_action( "quickload" );
2627 #endif
2628     ctxt.register_action( "SUICIDE" );
2629     ctxt.register_action( "player_data" );
2630     ctxt.register_action( "map" );
2631     ctxt.register_action( "sky" );
2632     ctxt.register_action( "missions" );
2633     ctxt.register_action( "factions" );
2634     ctxt.register_action( "scores" );
2635     ctxt.register_action( "morale" );
2636     ctxt.register_action( "messages" );
2637     ctxt.register_action( "help" );
2638     ctxt.register_action( "HELP_KEYBINDINGS" );
2639     ctxt.register_action( "open_options" );
2640     ctxt.register_action( "open_autopickup" );
2641     ctxt.register_action( "open_autonotes" );
2642     ctxt.register_action( "open_safemode" );
2643     ctxt.register_action( "open_color" );
2644     ctxt.register_action( "open_world_mods" );
2645     ctxt.register_action( "debug" );
2646     ctxt.register_action( "debug_scent" );
2647     ctxt.register_action( "debug_scent_type" );
2648     ctxt.register_action( "debug_temp" );
2649     ctxt.register_action( "debug_visibility" );
2650     ctxt.register_action( "debug_lighting" );
2651     ctxt.register_action( "debug_radiation" );
2652     ctxt.register_action( "debug_hour_timer" );
2653     ctxt.register_action( "debug_mode" );
2654     ctxt.register_action( "zoom_out" );
2655     ctxt.register_action( "zoom_in" );
2656 #if !defined(__ANDROID__)
2657     ctxt.register_action( "toggle_fullscreen" );
2658 #endif
2659     ctxt.register_action( "toggle_pixel_minimap" );
2660     ctxt.register_action( "toggle_panel_adm" );
2661     ctxt.register_action( "reload_tileset" );
2662     ctxt.register_action( "toggle_auto_features" );
2663     ctxt.register_action( "toggle_auto_pulp_butcher" );
2664     ctxt.register_action( "toggle_auto_mining" );
2665     ctxt.register_action( "toggle_auto_foraging" );
2666     ctxt.register_action( "toggle_auto_pickup" );
2667     ctxt.register_action( "toggle_thief_mode" );
2668     ctxt.register_action( "action_menu" );
2669     ctxt.register_action( "main_menu" );
2670     ctxt.register_action( "item_action_menu" );
2671     ctxt.register_action( "ANY_INPUT" );
2672     ctxt.register_action( "COORDINATE" );
2673     ctxt.register_action( "MOUSE_MOVE" );
2674     ctxt.register_action( "SELECT" );
2675     ctxt.register_action( "SEC_SELECT" );
2676     return ctxt;
2677 }
2678 
remoteveh()2679 vehicle *game::remoteveh()
2680 {
2681     if( calendar::turn == remoteveh_cache_time ) {
2682         return remoteveh_cache;
2683     }
2684     remoteveh_cache_time = calendar::turn;
2685     std::stringstream remote_veh_string( u.get_value( "remote_controlling_vehicle" ) );
2686     if( remote_veh_string.str().empty() ||
2687         ( !u.has_active_bionic( bio_remote ) && !u.has_active_item( itype_remotevehcontrol ) ) ) {
2688         remoteveh_cache = nullptr;
2689     } else {
2690         tripoint vp;
2691         remote_veh_string >> vp.x >> vp.y >> vp.z;
2692         vehicle *veh = veh_pointer_or_null( m.veh_at( vp ) );
2693         if( veh && veh->fuel_left( itype_battery, true ) > 0 ) {
2694             remoteveh_cache = veh;
2695         } else {
2696             remoteveh_cache = nullptr;
2697         }
2698     }
2699     return remoteveh_cache;
2700 }
2701 
setremoteveh(vehicle * veh)2702 void game::setremoteveh( vehicle *veh )
2703 {
2704     remoteveh_cache_time = calendar::turn;
2705     remoteveh_cache = veh;
2706     if( veh != nullptr && !u.has_active_bionic( bio_remote ) &&
2707         !u.has_active_item( itype_remotevehcontrol ) ) {
2708         debugmsg( "Tried to set remote vehicle without bio_remote or remotevehcontrol" );
2709         veh = nullptr;
2710     }
2711 
2712     if( veh == nullptr ) {
2713         u.remove_value( "remote_controlling_vehicle" );
2714         return;
2715     }
2716 
2717     std::stringstream remote_veh_string;
2718     const tripoint vehpos = veh->global_pos3();
2719     remote_veh_string << vehpos.x << ' ' << vehpos.y << ' ' << vehpos.z;
2720     u.set_value( "remote_controlling_vehicle", remote_veh_string.str() );
2721 }
2722 
try_get_left_click_action(action_id & act,const tripoint & mouse_target)2723 bool game::try_get_left_click_action( action_id &act, const tripoint &mouse_target )
2724 {
2725     bool new_destination = true;
2726     if( !destination_preview.empty() ) {
2727         auto &final_destination = destination_preview.back();
2728         if( final_destination.x == mouse_target.x && final_destination.y == mouse_target.y ) {
2729             // Second click
2730             new_destination = false;
2731             u.set_destination( destination_preview );
2732             destination_preview.clear();
2733             act = u.get_next_auto_move_direction();
2734             if( act == ACTION_NULL ) {
2735                 // Something went wrong
2736                 u.clear_destination();
2737                 return false;
2738             }
2739         }
2740     }
2741 
2742     if( new_destination ) {
2743         destination_preview = m.route( u.pos(), mouse_target, u.get_pathfinding_settings(),
2744                                        u.get_path_avoid() );
2745         return false;
2746     }
2747 
2748     return true;
2749 }
2750 
try_get_right_click_action(action_id & act,const tripoint & mouse_target)2751 bool game::try_get_right_click_action( action_id &act, const tripoint &mouse_target )
2752 {
2753     const bool cleared_destination = !destination_preview.empty();
2754     u.clear_destination();
2755     destination_preview.clear();
2756 
2757     if( cleared_destination ) {
2758         // Produce no-op if auto-move had just been cleared on this action
2759         // e.g. from a previous single left mouse click. This has the effect
2760         // of right-click canceling an auto-move before it is initiated.
2761         return false;
2762     }
2763 
2764     const bool is_adjacent = square_dist( mouse_target.xy(), point( u.posx(), u.posy() ) ) <= 1;
2765     const bool is_self = square_dist( mouse_target.xy(), point( u.posx(), u.posy() ) ) <= 0;
2766     if( const monster *const mon = critter_at<monster>( mouse_target ) ) {
2767         if( !u.sees( *mon ) ) {
2768             add_msg( _( "Nothing relevant here." ) );
2769             return false;
2770         }
2771 
2772         if( !u.weapon.is_gun() ) {
2773             add_msg( m_info, _( "You are not wielding a ranged weapon." ) );
2774             return false;
2775         }
2776 
2777         // TODO: Add weapon range check. This requires weapon to be reloaded.
2778 
2779         act = ACTION_FIRE;
2780     } else if( is_adjacent &&
2781                m.close_door( tripoint( mouse_target.xy(), u.posz() ), !m.is_outside( u.pos() ),
2782                              true ) ) {
2783         act = ACTION_CLOSE;
2784     } else if( is_self ) {
2785         act = ACTION_PICKUP;
2786     } else if( is_adjacent ) {
2787         act = ACTION_EXAMINE;
2788     } else {
2789         add_msg( _( "Nothing relevant here." ) );
2790         return false;
2791     }
2792 
2793     return true;
2794 }
2795 
is_game_over()2796 bool game::is_game_over()
2797 {
2798     if( uquit == QUIT_WATCH ) {
2799         // deny player movement and dodging
2800         u.moves = 0;
2801         // prevent pain from updating
2802         u.set_pain( 0 );
2803         // prevent dodging
2804         u.dodges_left = 0;
2805         return false;
2806     }
2807     if( uquit == QUIT_DIED ) {
2808         if( u.in_vehicle ) {
2809             m.unboard_vehicle( u.pos() );
2810         }
2811         u.place_corpse();
2812         return true;
2813     }
2814     if( uquit == QUIT_SUICIDE ) {
2815         if( u.in_vehicle ) {
2816             m.unboard_vehicle( u.pos() );
2817         }
2818         return true;
2819     }
2820     if( uquit != QUIT_NO ) {
2821         return true;
2822     }
2823     // is_dead_state() already checks hp_torso && hp_head, no need to for loop it
2824     if( u.is_dead_state() ) {
2825         Messages::deactivate();
2826         if( get_option<std::string>( "DEATHCAM" ) == "always" ) {
2827             uquit = QUIT_WATCH;
2828         } else if( get_option<std::string>( "DEATHCAM" ) == "ask" ) {
2829             uquit = query_yn( _( "Watch the last moments of your life…?" ) ) ?
2830                     QUIT_WATCH : QUIT_DIED;
2831         } else if( get_option<std::string>( "DEATHCAM" ) == "never" ) {
2832             uquit = QUIT_DIED;
2833         } else {
2834             // Something funky happened here, just die.
2835             dbg( D_ERROR ) << "no deathcam option given to options, defaulting to QUIT_DIED";
2836             uquit = QUIT_DIED;
2837         }
2838         return is_game_over();
2839     }
2840     return false;
2841 }
2842 
death_screen()2843 void game::death_screen()
2844 {
2845     gamemode->game_over();
2846     Messages::display_messages();
2847     show_scores_ui( *achievements_tracker_ptr, stats(), get_kill_tracker() );
2848     disp_NPC_epilogues();
2849     follower_ids.clear();
2850     display_faction_epilogues();
2851 }
2852 
move_save_to_graveyard()2853 void game::move_save_to_graveyard()
2854 {
2855     const std::string &save_dir      = PATH_INFO::world_base_save_path();
2856     const std::string &graveyard_dir = PATH_INFO::graveyarddir();
2857     const std::string &prefix        = base64_encode( u.name ) + ".";
2858 
2859     if( !assure_dir_exist( graveyard_dir ) ) {
2860         debugmsg( "could not create graveyard path '%s'", graveyard_dir );
2861     }
2862 
2863     const auto save_files = get_files_from_path( prefix, save_dir );
2864     if( save_files.empty() ) {
2865         debugmsg( "could not find save files in '%s'", save_dir );
2866     }
2867 
2868     for( const auto &src_path : save_files ) {
2869         const std::string dst_path = graveyard_dir +
2870                                      src_path.substr( src_path.rfind( '/' ), std::string::npos );
2871 
2872         if( rename_file( src_path, dst_path ) ) {
2873             continue;
2874         }
2875 
2876         debugmsg( "could not rename file '%s' to '%s'", src_path, dst_path );
2877 
2878         if( remove_file( src_path ) ) {
2879             continue;
2880         }
2881 
2882         debugmsg( "could not remove file '%s'", src_path );
2883     }
2884 }
2885 
load_master()2886 void game::load_master()
2887 {
2888     using namespace std::placeholders;
2889     const auto datafile = PATH_INFO::world_base_save_path() + "/" + SAVE_MASTER;
2890     read_from_file_optional( datafile, std::bind( &game::unserialize_master, this, _1 ) );
2891 }
2892 
load(const std::string & world)2893 bool game::load( const std::string &world )
2894 {
2895     world_generator->init();
2896     const WORLDPTR wptr = world_generator->get_world( world );
2897     if( !wptr ) {
2898         return false;
2899     }
2900     if( wptr->world_saves.empty() ) {
2901         debugmsg( "world '%s' contains no saves", world );
2902         return false;
2903     }
2904 
2905     try {
2906         world_generator->set_active_world( wptr );
2907         g->setup();
2908         g->load( wptr->world_saves.front() );
2909     } catch( const std::exception &err ) {
2910         debugmsg( "cannot load world '%s': %s", world, err.what() );
2911         return false;
2912     }
2913 
2914     return true;
2915 }
2916 
load(const save_t & name)2917 bool game::load( const save_t &name )
2918 {
2919     background_pane background;
2920     static_popup popup;
2921     popup.message( "%s", _( "Please wait…\nLoading the save…" ) );
2922     ui_manager::redraw();
2923     refresh_display();
2924 
2925     using namespace std::placeholders;
2926 
2927     const std::string worldpath = PATH_INFO::world_base_save_path() + "/";
2928     const std::string playerpath = worldpath + name.base_path();
2929 
2930     // Now load up the master game data; factions (and more?)
2931     load_master();
2932     u = avatar();
2933     u.name = name.player_name();
2934     // This should be initialized more globally (in player/Character constructor)
2935     u.weapon = item();
2936     if( !read_from_file( playerpath + SAVE_EXTENSION, std::bind( &game::unserialize, this, _1 ) ) ) {
2937         return false;
2938     }
2939 
2940     read_from_file_optional_json( playerpath + SAVE_EXTENSION_MAP_MEMORY, [&]( JsonIn & jsin ) {
2941         u.deserialize_map_memory( jsin );
2942     } );
2943 
2944     read_from_file_optional( worldpath + name.base_path() + SAVE_EXTENSION_LOG,
2945                              std::bind( &memorial_logger::load, &memorial(), _1 ) );
2946 
2947 #if defined(__ANDROID__)
2948     read_from_file_optional( worldpath + name.base_path() + SAVE_EXTENSION_SHORTCUTS,
2949                              std::bind( &game::load_shortcuts, this, _1 ) );
2950 #endif
2951 
2952     // Now that the player's worn items are updated, their sight limits need to be
2953     // recalculated. (This would be cleaner if u.worn were private.)
2954     u.recalc_sight_limits();
2955 
2956     if( !gamemode ) {
2957         gamemode = std::make_unique<special_game>();
2958     }
2959 
2960     safe_mode = get_option<bool>( "SAFEMODE" ) ? SAFE_MODE_ON : SAFE_MODE_OFF;
2961     mostseen = 0; // ...and mostseen is 0, we haven't seen any monsters yet.
2962 
2963     init_autosave();
2964     get_auto_pickup().load_character(); // Load character auto pickup rules
2965     get_auto_notes_settings().load();   // Load character auto notes settings
2966     get_safemode().load_character(); // Load character safemode rules
2967     zone_manager::get_manager().load_zones(); // Load character world zones
2968     read_from_file_optional( PATH_INFO::world_base_save_path() + "/uistate.json", [](
2969     std::istream & stream ) {
2970         JsonIn jsin( stream );
2971         uistate.deserialize( jsin );
2972     } );
2973     reload_npcs();
2974     validate_npc_followers();
2975     validate_mounted_npcs();
2976     validate_camps();
2977     validate_linked_vehicles();
2978     update_map( u );
2979     for( auto &e : u.inv_dump() ) {
2980         e->set_owner( get_player_character() );
2981     }
2982     // legacy, needs to be here as we access the map.
2983     if( !u.getID().is_valid() ) {
2984         // player does not have a real id, so assign a new one,
2985         u.setID( assign_npc_id() );
2986         // The vehicle stores the IDs of the boarded players, so update it, too.
2987         if( u.in_vehicle ) {
2988             if( const cata::optional<vpart_reference> vp = m.veh_at(
2989                         u.pos() ).part_with_feature( "BOARDABLE", true ) ) {
2990                 vp->part().passenger_id = u.getID();
2991             }
2992         }
2993     }
2994 
2995     // populate calendar caches now, after active world is set, but before we do
2996     // anything else, to ensure they pick up the correct value from the save's
2997     // worldoptions
2998     calendar::set_eternal_season( ::get_option<bool>( "ETERNAL_SEASON" ) );
2999     calendar::set_season_length( ::get_option<int>( "SEASON_LENGTH" ) );
3000 
3001     u.reset();
3002 
3003     events().send<event_type::game_load>( getVersionString() );
3004     time_of_last_load = std::chrono::steady_clock::now();
3005     time_played_at_last_load = std::chrono::seconds( 0 );
3006     cata::optional<event_multiset::summaries_type::value_type> last_save =
3007         stats().get_events( event_type::game_save ).last();
3008     if( last_save ) {
3009         auto time_played_it = last_save->first.find( "total_time_played" );
3010         if( time_played_it != last_save->first.end() &&
3011             time_played_it->second.type() == cata_variant_type::chrono_seconds ) {
3012             time_played_at_last_load = time_played_it->second.get<std::chrono::seconds>();
3013         }
3014     }
3015 
3016     return true;
3017 }
3018 
load_world_modfiles(loading_ui & ui)3019 void game::load_world_modfiles( loading_ui &ui )
3020 {
3021     auto &mods = world_generator->active_world->active_mod_order;
3022 
3023     // remove any duplicates whilst preserving order (fixes #19385)
3024     std::set<mod_id> found;
3025     mods.erase( std::remove_if( mods.begin(), mods.end(), [&found]( const mod_id & e ) {
3026         if( found.count( e ) ) {
3027             return true;
3028         } else {
3029             found.insert( e );
3030             return false;
3031         }
3032     } ), mods.end() );
3033 
3034     // require at least one core mod (saves before version 6 may implicitly require dda pack)
3035     if( std::none_of( mods.begin(), mods.end(), []( const mod_id & e ) {
3036     return e->core;
3037 } ) ) {
3038         mods.insert( mods.begin(), mod_id( "dda" ) );
3039     }
3040 
3041     // this code does not care about mod dependencies,
3042     // it assumes that those dependencies are static and
3043     // are resolved during the creation of the world.
3044     // That means world->active_mod_order contains a list
3045     // of mods in the correct order.
3046     load_packs( _( "Loading files" ), mods, ui );
3047 
3048     // Load additional mods from that world-specific folder
3049     load_data_from_dir( PATH_INFO::world_base_save_path() + "/mods", "custom", ui );
3050 
3051     DynamicDataLoader::get_instance().finalize_loaded_data( ui );
3052 }
3053 
load_packs(const std::string & msg,const std::vector<mod_id> & packs,loading_ui & ui)3054 bool game::load_packs( const std::string &msg, const std::vector<mod_id> &packs, loading_ui &ui )
3055 {
3056     ui.new_context( msg );
3057     std::vector<mod_id> missing;
3058     std::vector<mod_id> available;
3059 
3060     for( const mod_id &e : packs ) {
3061         if( e.is_valid() ) {
3062             available.emplace_back( e );
3063             ui.add_entry( e->name() );
3064         } else {
3065             missing.push_back( e );
3066         }
3067     }
3068 
3069     ui.show();
3070     for( const auto &e : available ) {
3071         const MOD_INFORMATION &mod = *e;
3072         load_data_from_dir( mod.path, mod.ident.str(), ui );
3073 
3074         ui.proceed();
3075     }
3076 
3077     for( const auto &e : missing ) {
3078         debugmsg( "unknown content %s", e.c_str() );
3079     }
3080 
3081     return missing.empty();
3082 }
3083 
reset_npc_dispositions()3084 void game::reset_npc_dispositions()
3085 {
3086     for( character_id elem : follower_ids ) {
3087         shared_ptr_fast<npc> npc_to_get = overmap_buffer.find_npc( elem );
3088         if( !npc_to_get )  {
3089             continue;
3090         }
3091         npc *npc_to_add = npc_to_get.get();
3092         npc_to_add->chatbin.clear_all();
3093         npc_to_add->mission = NPC_MISSION_NULL;
3094         npc_to_add->set_attitude( NPCATT_NULL );
3095         npc_to_add->op_of_u.anger = 0;
3096         npc_to_add->op_of_u.fear = 0;
3097         npc_to_add->op_of_u.trust = 0;
3098         npc_to_add->op_of_u.value = 0;
3099         npc_to_add->op_of_u.owed = 0;
3100         npc_to_add->set_fac( faction_id( "no_faction" ) );
3101         npc_to_add->add_new_mission( mission::reserve_random( ORIGIN_ANY_NPC,
3102                                      npc_to_add->global_omt_location(),
3103                                      npc_to_add->getID() ) );
3104 
3105     }
3106 
3107 }
3108 
3109 //Saves all factions and missions and npcs.
save_factions_missions_npcs()3110 bool game::save_factions_missions_npcs()
3111 {
3112     std::string masterfile = PATH_INFO::world_base_save_path() + "/" + SAVE_MASTER;
3113     return write_to_file( masterfile, [&]( std::ostream & fout ) {
3114         serialize_master( fout );
3115     }, _( "factions data" ) );
3116 }
3117 
save_maps()3118 bool game::save_maps()
3119 {
3120     try {
3121         m.save();
3122         overmap_buffer.save(); // can throw
3123         MAPBUFFER.save(); // can throw
3124         return true;
3125     } catch( const std::exception &err ) {
3126         popup( _( "Failed to save the maps: %s" ), err.what() );
3127         return false;
3128     }
3129 }
3130 
save_player_data()3131 bool game::save_player_data()
3132 {
3133     const std::string playerfile = PATH_INFO::player_base_save_path();
3134 
3135     const bool saved_data = write_to_file( playerfile + SAVE_EXTENSION, [&]( std::ostream & fout ) {
3136         serialize( fout );
3137     }, _( "player data" ) );
3138     const bool saved_map_memory = write_to_file( playerfile + SAVE_EXTENSION_MAP_MEMORY, [&](
3139     std::ostream & fout ) {
3140         JsonOut jsout( fout );
3141         u.serialize_map_memory( jsout );
3142     }, _( "player map memory" ) );
3143     const bool saved_log = write_to_file( playerfile + SAVE_EXTENSION_LOG, [&](
3144     std::ostream & fout ) {
3145         memorial().save( fout );
3146     }, _( "player memorial" ) );
3147 #if defined(__ANDROID__)
3148     const bool saved_shortcuts = write_to_file( playerfile + SAVE_EXTENSION_SHORTCUTS, [&](
3149     std::ostream & fout ) {
3150         save_shortcuts( fout );
3151     }, _( "quick shortcuts" ) );
3152 #endif
3153 
3154     return saved_data && saved_map_memory && saved_log
3155 #if defined(__ANDROID__)
3156            && saved_shortcuts
3157 #endif
3158            ;
3159 }
3160 
events()3161 event_bus &game::events()
3162 {
3163     return *event_bus_ptr;
3164 }
3165 
stats()3166 stats_tracker &game::stats()
3167 {
3168     return *stats_tracker_ptr;
3169 }
3170 
achievements()3171 achievements_tracker &game::achievements()
3172 {
3173     return *achievements_tracker_ptr;
3174 }
3175 
memorial()3176 memorial_logger &game::memorial()
3177 {
3178     return *memorial_logger_ptr;
3179 }
3180 
spell_events_subscriber()3181 spell_events &game::spell_events_subscriber()
3182 {
3183     return *spell_events_ptr;
3184 }
3185 
save()3186 bool game::save()
3187 {
3188     std::chrono::seconds time_since_load =
3189         std::chrono::duration_cast<std::chrono::seconds>(
3190             std::chrono::steady_clock::now() - time_of_last_load );
3191     std::chrono::seconds total_time_played = time_played_at_last_load + time_since_load;
3192     events().send<event_type::game_save>( time_since_load, total_time_played );
3193     try {
3194         if( !save_player_data() ||
3195             !save_factions_missions_npcs() ||
3196             !save_maps() ||
3197             !get_auto_pickup().save_character() ||
3198             !get_auto_notes_settings().save() ||
3199             !get_safemode().save_character() ||
3200         !write_to_file( PATH_INFO::world_base_save_path() + "/uistate.json", [&]( std::ostream & fout ) {
3201         JsonOut jsout( fout );
3202             uistate.serialize( jsout );
3203         }, _( "uistate data" ) ) ) {
3204             return false;
3205         } else {
3206             world_generator->active_world->add_save( save_t::from_player_name( u.name ) );
3207             return true;
3208         }
3209     } catch( std::ios::failure & ) {
3210         popup( _( "Failed to save game data" ) );
3211         return false;
3212     }
3213 }
3214 
list_active_characters()3215 std::vector<std::string> game::list_active_characters()
3216 {
3217     std::vector<std::string> saves;
3218     for( auto &worldsave : world_generator->active_world->world_saves ) {
3219         saves.push_back( worldsave.player_name() );
3220     }
3221     return saves;
3222 }
3223 
3224 /**
3225  * Writes information about the character out to a text file timestamped with
3226  * the time of the file was made. This serves as a record of the character's
3227  * state at the time the memorial was made (usually upon death) and
3228  * accomplishments in a human-readable format.
3229  */
write_memorial_file(std::string sLastWords)3230 void game::write_memorial_file( std::string sLastWords )
3231 {
3232     const std::string &memorial_dir = PATH_INFO::memorialdir();
3233     const std::string &memorial_active_world_dir = memorial_dir + utf8_to_native(
3234                 world_generator->active_world->world_name ) + "/";
3235 
3236     //Check if both dirs exist. Nested assure_dir_exist fails if the first dir of the nested dir does not exist.
3237     if( !assure_dir_exist( memorial_dir ) ) {
3238         dbg( D_ERROR ) << "game:write_memorial_file: Unable to make memorial directory.";
3239         debugmsg( "Could not make '%s' directory", memorial_dir );
3240         return;
3241     }
3242 
3243     if( !assure_dir_exist( memorial_active_world_dir ) ) {
3244         dbg( D_ERROR ) <<
3245                        "game:write_memorial_file: Unable to make active world directory in memorial directory.";
3246         debugmsg( "Could not make '%s' directory", memorial_active_world_dir );
3247         return;
3248     }
3249 
3250     // <name>-YYYY-MM-DD-HH-MM-SS.txt
3251     //       123456789012345678901234 ~> 24 chars + a null
3252     constexpr size_t suffix_len   = 24 + 1;
3253     constexpr size_t max_name_len = FILENAME_MAX - suffix_len;
3254 
3255     const size_t name_len = u.name.size();
3256     // Here -1 leaves space for the ~
3257     const size_t truncated_name_len = ( name_len >= max_name_len ) ? ( max_name_len - 1 ) : name_len;
3258 
3259     std::ostringstream memorial_file_path;
3260     memorial_file_path << memorial_active_world_dir;
3261 
3262     if( get_options().has_option( "ENCODING_CONV" ) && !get_option<bool>( "ENCODING_CONV" ) ) {
3263         // Use the default locale to replace non-printable characters with _ in the player name.
3264         std::locale locale {"C"};
3265         std::replace_copy_if( std::begin( u.name ), std::begin( u.name ) + truncated_name_len,
3266                               std::ostream_iterator<char>( memorial_file_path ),
3267         [&]( const char c ) {
3268             return !std::isgraph( c, locale );
3269         }, '_' );
3270     } else {
3271         memorial_file_path << utf8_to_native( u.name );
3272     }
3273 
3274     // Add a ~ if the player name was actually truncated.
3275     memorial_file_path << ( ( truncated_name_len != name_len ) ? "~-" : "-" );
3276 
3277     // Add a timestamp for uniqueness.
3278 
3279 #if defined(_WIN32)
3280     SYSTEMTIME current_time;
3281     GetLocalTime( &current_time );
3282     memorial_file_path << string_format( "%d-%02d-%02d-%02d-%02d-%02d",
3283                                          current_time.wYear, current_time.wMonth, current_time.wDay,
3284                                          current_time.wHour, current_time.wMinute, current_time.wSecond );
3285 #else
3286     char buffer[suffix_len] {};
3287     std::time_t t = std::time( nullptr );
3288     tm current_time;
3289     localtime_r( &t, &current_time );
3290     std::strftime( buffer, suffix_len, "%Y-%m-%d-%H-%M-%S", &current_time );
3291     memorial_file_path << buffer;
3292 #endif
3293 
3294     const std::string text_path_string = memorial_file_path.str() + ".txt";
3295     const std::string json_path_string = memorial_file_path.str() + ".json";
3296 
3297     write_to_file( text_path_string, [&]( std::ostream & fout ) {
3298         memorial().write_text_memorial( fout, sLastWords );
3299     }, _( "player memorial" ) );
3300 
3301     write_to_file( json_path_string, [&]( std::ostream & fout ) {
3302         memorial().write_json_memorial( fout );
3303     }, _( "player memorial" ) );
3304 }
3305 
disp_NPC_epilogues()3306 void game::disp_NPC_epilogues()
3307 {
3308     // TODO: This search needs to be expanded to all NPCs
3309     for( character_id elem : follower_ids ) {
3310         shared_ptr_fast<npc> guy = overmap_buffer.find_npc( elem );
3311         if( !guy ) {
3312             continue;
3313         }
3314         const auto new_win = []() {
3315             return catacurses::newwin( FULL_SCREEN_HEIGHT, FULL_SCREEN_WIDTH,
3316                                        point( std::max( 0, ( TERMX - FULL_SCREEN_WIDTH ) / 2 ),
3317                                               std::max( 0, ( TERMY - FULL_SCREEN_HEIGHT ) / 2 ) ) );
3318         };
3319         scrollable_text( new_win, guy->disp_name(), guy->get_epilogue() );
3320     }
3321 }
3322 
display_faction_epilogues()3323 void game::display_faction_epilogues()
3324 {
3325     for( const auto &elem : faction_manager_ptr->all() ) {
3326         if( elem.second.known_by_u ) {
3327             const std::vector<std::string> epilogue = elem.second.epilogue();
3328             if( !epilogue.empty() ) {
3329                 const auto new_win = []() {
3330                     return catacurses::newwin( FULL_SCREEN_HEIGHT, FULL_SCREEN_WIDTH,
3331                                                point( std::max( 0, ( TERMX - FULL_SCREEN_WIDTH ) / 2 ),
3332                                                       std::max( 0, ( TERMY - FULL_SCREEN_HEIGHT ) / 2 ) ) );
3333                 };
3334                 scrollable_text( new_win, elem.second.name,
3335                                  std::accumulate( epilogue.begin() + 1, epilogue.end(), epilogue.front(),
3336                 []( const std::string & lhs, const std::string & rhs ) -> std::string {
3337                     return lhs + "\n" + rhs;
3338                 } ) );
3339             }
3340         }
3341     }
3342 }
3343 
3344 struct npc_dist_to_player {
3345     const tripoint_abs_omt ppos{};
npc_dist_to_playernpc_dist_to_player3346     npc_dist_to_player() : ppos( get_player_character().global_omt_location() ) { }
3347     // Operator overload required to leverage sort API.
operator ()npc_dist_to_player3348     bool operator()( const shared_ptr_fast<npc> &a,
3349                      const shared_ptr_fast<npc> &b ) const {
3350         const tripoint_abs_omt apos = a->global_omt_location();
3351         const tripoint_abs_omt bpos = b->global_omt_location();
3352         return square_dist( ppos.xy(), apos.xy() ) <
3353                square_dist( ppos.xy(), bpos.xy() );
3354     }
3355 };
3356 
disp_NPCs()3357 void game::disp_NPCs()
3358 {
3359     const tripoint_abs_omt ppos = u.global_omt_location();
3360     const tripoint &lpos = u.pos();
3361     const int scan_range = 120;
3362     std::vector<shared_ptr_fast<npc>> npcs = overmap_buffer.get_npcs_near_player( scan_range );
3363     std::sort( npcs.begin(), npcs.end(), npc_dist_to_player() );
3364 
3365     catacurses::window w;
3366     ui_adaptor ui;
3367     ui.on_screen_resize( [&]( ui_adaptor & ui ) {
3368         w = catacurses::newwin( FULL_SCREEN_HEIGHT, FULL_SCREEN_WIDTH,
3369                                 point( TERMX > FULL_SCREEN_WIDTH ? ( TERMX - FULL_SCREEN_WIDTH ) / 2 : 0,
3370                                        TERMY > FULL_SCREEN_HEIGHT ? ( TERMY - FULL_SCREEN_HEIGHT ) / 2 : 0 ) );
3371         ui.position_from_window( w );
3372     } );
3373     ui.mark_resize();
3374     ui.on_redraw( [&]( const ui_adaptor & ) {
3375         werase( w );
3376         mvwprintz( w, point_zero, c_white, _( "Your overmap position: %s" ), ppos.to_string() );
3377         // NOLINTNEXTLINE(cata-use-named-point-constants)
3378         mvwprintz( w, point( 0, 1 ), c_white, _( "Your local position: %s" ), lpos.to_string() );
3379         size_t i;
3380         int static_npc_count = 0;
3381         for( i = 0; i < npcs.size(); i++ ) {
3382             if(
3383                 npcs[i]->has_trait( trait_NPC_STARTING_NPC ) || npcs[i]->has_trait( trait_NPC_STATIC_NPC ) ) {
3384                 static_npc_count++;
3385             }
3386         }
3387         mvwprintz( w, point( 0, 2 ), c_white, _( "Total NPCs within %d OMTs: %d.  %d are static NPCs." ),
3388                    scan_range, npcs.size(), static_npc_count );
3389         for( i = 0; i < 20 && i < npcs.size(); i++ ) {
3390             const tripoint_abs_omt apos = npcs[i]->global_omt_location();
3391             mvwprintz( w, point( 0, i + 4 ), c_white, "%s: %s", npcs[i]->name,
3392                        apos.to_string() );
3393         }
3394         for( const monster &m : all_monsters() ) {
3395             mvwprintz( w, point( 0, i + 4 ), c_white, "%s: %d, %d, %d", m.name(),
3396                        m.posx(), m.posy(), m.posz() );
3397             ++i;
3398         }
3399         wnoutrefresh( w );
3400     } );
3401 
3402     input_context ctxt( "DISP_NPCS" );
3403     ctxt.register_action( "CONFIRM" );
3404     ctxt.register_action( "QUIT" );
3405     ctxt.register_action( "HELP_KEYBINDINGS" );
3406     bool stop = false;
3407     while( !stop ) {
3408         ui_manager::redraw();
3409         const std::string action = ctxt.handle_input();
3410         if( action == "CONFIRM" || action == "QUIT" ) {
3411             stop = true;
3412         }
3413     }
3414 }
3415 
3416 // A little helper to draw footstep glyphs.
draw_footsteps(const catacurses::window & window,const tripoint & offset)3417 static void draw_footsteps( const catacurses::window &window, const tripoint &offset )
3418 {
3419     for( const auto &footstep : sounds::get_footstep_markers() ) {
3420         char glyph = '?';
3421         if( footstep.z != offset.z ) { // Here z isn't an offset, but a coordinate
3422             glyph = footstep.z > offset.z ? '^' : 'v';
3423         }
3424 
3425         mvwputch( window, footstep.xy() + offset.xy(), c_yellow, glyph );
3426     }
3427 }
3428 
create_or_get_main_ui_adaptor()3429 shared_ptr_fast<ui_adaptor> game::create_or_get_main_ui_adaptor()
3430 {
3431     shared_ptr_fast<ui_adaptor> ui = main_ui_adaptor.lock();
3432     if( !ui ) {
3433         main_ui_adaptor = ui = make_shared_fast<ui_adaptor>();
3434         ui->on_redraw( []( const ui_adaptor & ) {
3435             g->draw();
3436         } );
3437         ui->on_screen_resize( [this]( ui_adaptor & ui ) {
3438             // remove some space for the sidebar, this is the maximal space
3439             // (using standard font) that the terrain window can have
3440             const int sidebar_left = panel_manager::get_manager().get_width_left();
3441             const int sidebar_right = panel_manager::get_manager().get_width_right();
3442 
3443             TERRAIN_WINDOW_HEIGHT = TERMY;
3444             TERRAIN_WINDOW_WIDTH = TERMX - ( sidebar_left + sidebar_right );
3445             TERRAIN_WINDOW_TERM_WIDTH = TERRAIN_WINDOW_WIDTH;
3446             TERRAIN_WINDOW_TERM_HEIGHT = TERRAIN_WINDOW_HEIGHT;
3447 
3448             /**
3449              * In tiles mode w_terrain can have a different font (with a different
3450              * tile dimension) or can be drawn by cata_tiles which uses tiles that again
3451              * might have a different dimension then the normal font used everywhere else.
3452              *
3453              * TERRAIN_WINDOW_WIDTH/TERRAIN_WINDOW_HEIGHT defines how many squares can
3454              * be displayed in w_terrain (using it's specific tile dimension), not
3455              * including partially drawn squares at the right/bottom. You should
3456              * use it whenever you want to draw specific squares in that window or to
3457              * determine whether a specific square is draw on screen (or outside the screen
3458              * and needs scrolling).
3459              *
3460              * TERRAIN_WINDOW_TERM_WIDTH/TERRAIN_WINDOW_TERM_HEIGHT defines the size of
3461              * w_terrain in the standard font dimension (the font that everything else uses).
3462              * You usually don't have to use it, expect for positioning of windows,
3463              * because the window positions use the standard font dimension.
3464              *
3465              * The code here calculates size available for w_terrain, caps it at
3466              * max_view_size (the maximal view range than any character can have at
3467              * any time).
3468              * It is stored in TERRAIN_WINDOW_*.
3469              */
3470             to_map_font_dimension( TERRAIN_WINDOW_WIDTH, TERRAIN_WINDOW_HEIGHT );
3471 
3472             // Position of the player in the terrain window, it is always in the center
3473             POSX = TERRAIN_WINDOW_WIDTH / 2;
3474             POSY = TERRAIN_WINDOW_HEIGHT / 2;
3475 
3476             w_terrain = w_terrain_ptr = catacurses::newwin( TERRAIN_WINDOW_HEIGHT, TERRAIN_WINDOW_WIDTH,
3477                                         point( sidebar_left, 0 ) );
3478 
3479             // minimap is always MINIMAP_WIDTH x MINIMAP_HEIGHT in size
3480             w_minimap = w_minimap_ptr = catacurses::newwin( MINIMAP_HEIGHT, MINIMAP_WIDTH, point_zero );
3481 
3482             // need to init in order to avoid crash. gets updated by the panel code.
3483             w_pixel_minimap = catacurses::newwin( 1, 1, point_zero );
3484 
3485             ui.position_from_window( catacurses::stdscr );
3486         } );
3487         ui->mark_resize();
3488     }
3489     return ui;
3490 }
3491 
invalidate_main_ui_adaptor() const3492 void game::invalidate_main_ui_adaptor() const
3493 {
3494     shared_ptr_fast<ui_adaptor> ui = main_ui_adaptor.lock();
3495     if( ui ) {
3496         ui->invalidate_ui();
3497     }
3498 }
3499 
mark_main_ui_adaptor_resize() const3500 void game::mark_main_ui_adaptor_resize() const
3501 {
3502     shared_ptr_fast<ui_adaptor> ui = main_ui_adaptor.lock();
3503     if( ui ) {
3504         ui->mark_resize();
3505     }
3506 }
3507 
draw_callback_t(const std::function<void ()> & cb)3508 game::draw_callback_t::draw_callback_t( const std::function<void()> &cb )
3509     : cb( cb )
3510 {
3511 }
3512 
~draw_callback_t()3513 game::draw_callback_t::~draw_callback_t()
3514 {
3515     if( added ) {
3516         g->invalidate_main_ui_adaptor();
3517     }
3518 }
3519 
operator ()()3520 void game::draw_callback_t::operator()()
3521 {
3522     if( cb ) {
3523         cb();
3524     }
3525 }
3526 
add_draw_callback(const shared_ptr_fast<draw_callback_t> & cb)3527 void game::add_draw_callback( const shared_ptr_fast<draw_callback_t> &cb )
3528 {
3529     draw_callbacks.erase(
3530         std::remove_if( draw_callbacks.begin(), draw_callbacks.end(),
3531     []( const weak_ptr_fast<draw_callback_t> &cbw ) {
3532         return cbw.expired();
3533     } ),
3534     draw_callbacks.end()
3535     );
3536     draw_callbacks.emplace_back( cb );
3537     cb->added = true;
3538     invalidate_main_ui_adaptor();
3539 }
3540 
3541 static void draw_trail( const tripoint &start, const tripoint &end, bool bDrawX );
3542 
create_zone_callback(const cata::optional<tripoint> & zone_start,const cata::optional<tripoint> & zone_end,const bool & zone_blink,const bool & zone_cursor)3543 static shared_ptr_fast<game::draw_callback_t> create_zone_callback(
3544     const cata::optional<tripoint> &zone_start,
3545     const cata::optional<tripoint> &zone_end,
3546     const bool &zone_blink,
3547     const bool &zone_cursor
3548 )
3549 {
3550     return make_shared_fast<game::draw_callback_t>(
3551     [&]() {
3552         if( zone_cursor ) {
3553             if( zone_end ) {
3554                 g->draw_cursor( zone_end.value() );
3555             } else if( zone_start ) {
3556                 g->draw_cursor( zone_start.value() );
3557             }
3558         }
3559         if( zone_blink && zone_start && zone_end ) {
3560             avatar &player_character = get_avatar();
3561             const point offset2( player_character.view_offset.xy() +
3562                                  point( player_character.posx() - getmaxx( g->w_terrain ) / 2,
3563                                         player_character.posy() - getmaxy( g->w_terrain ) / 2 ) );
3564 
3565             tripoint offset;
3566 #if defined(TILES)
3567             if( use_tiles ) {
3568                 offset = tripoint_zero; //TILES
3569             } else {
3570 #endif
3571                 offset = tripoint( offset2, 0 ); //CURSES
3572 #if defined(TILES)
3573             }
3574 #endif
3575 
3576             const tripoint start( std::min( zone_start->x, zone_end->x ),
3577                                   std::min( zone_start->y, zone_end->y ),
3578                                   zone_end->z );
3579             const tripoint end( std::max( zone_start->x, zone_end->x ),
3580                                 std::max( zone_start->y, zone_end->y ),
3581                                 zone_end->z );
3582             g->draw_zones( start, end, offset );
3583         }
3584     } );
3585 }
3586 
create_trail_callback(const cata::optional<tripoint> & trail_start,const cata::optional<tripoint> & trail_end,const bool & trail_end_x)3587 static shared_ptr_fast<game::draw_callback_t> create_trail_callback(
3588     const cata::optional<tripoint> &trail_start,
3589     const cata::optional<tripoint> &trail_end,
3590     const bool &trail_end_x
3591 )
3592 {
3593     return make_shared_fast<game::draw_callback_t>(
3594     [&]() {
3595         if( trail_start && trail_end ) {
3596             draw_trail( trail_start.value(), trail_end.value(), trail_end_x );
3597         }
3598     } );
3599 }
3600 
draw()3601 void game::draw()
3602 {
3603     if( test_mode ) {
3604         return;
3605     }
3606 
3607     //temporary fix for updating visibility for minimap
3608     ter_view_p.z = ( u.pos() + u.view_offset ).z;
3609     m.build_map_cache( ter_view_p.z );
3610     m.update_visibility_cache( ter_view_p.z );
3611 
3612     werase( w_terrain );
3613     draw_ter();
3614     for( auto it = draw_callbacks.begin(); it != draw_callbacks.end(); ) {
3615         shared_ptr_fast<draw_callback_t> cb = it->lock();
3616         if( cb ) {
3617             ( *cb )();
3618             ++it;
3619         } else {
3620             it = draw_callbacks.erase( it );
3621         }
3622     }
3623     wnoutrefresh( w_terrain );
3624 
3625     draw_panels( true );
3626 
3627     // This breaks stuff in the SDL port, see
3628     // https://github.com/CleverRaven/Cataclysm-DDA/issues/45910
3629 #if !defined(TILES)
3630     // Ensure that the cursor lands on the character when everything is drawn.
3631     // This allows screen readers to describe the area around the player, making it
3632     // much easier to play with them
3633     // (e.g. for blind players)
3634     wmove( w_terrain, -u.view_offset.xy() + point{POSX, POSY} );
3635     wnoutrefresh( w_terrain );
3636 #endif
3637 }
3638 
draw_panels(bool force_draw)3639 void game::draw_panels( bool force_draw )
3640 {
3641     static int previous_turn = -1;
3642     const int current_turn = to_turns<int>( calendar::turn - calendar::turn_zero );
3643     const bool draw_this_turn = current_turn > previous_turn || force_draw;
3644     auto &mgr = panel_manager::get_manager();
3645     int y = 0;
3646     const bool sidebar_right = get_option<std::string>( "SIDEBAR_POSITION" ) == "right";
3647     int spacer = get_option<bool>( "SIDEBAR_SPACERS" ) ? 1 : 0;
3648     int log_height = 0;
3649     for( const window_panel &panel : mgr.get_current_layout().panels() ) {
3650         if( panel.get_height() != -2 && panel.toggle && panel.render() ) {
3651             log_height += panel.get_height() + spacer;
3652         }
3653     }
3654     log_height = std::max( TERMY - log_height, 3 );
3655     for( const window_panel &panel : mgr.get_current_layout().panels() ) {
3656         if( panel.render() ) {
3657             // height clamped to window height.
3658             int h = std::min( panel.get_height(), TERMY - y );
3659             if( h == -2 ) {
3660                 h = log_height;
3661             }
3662             h += spacer;
3663             if( panel.toggle && panel.render() && h > 0 ) {
3664                 if( panel.always_draw || draw_this_turn ) {
3665                     panel.draw( u, catacurses::newwin( h, panel.get_width(),
3666                                                        point( sidebar_right ? TERMX - panel.get_width() : 0, y ) ) );
3667                 }
3668                 if( show_panel_adm ) {
3669                     const std::string panel_name = panel.get_name();
3670                     const int panel_name_width = utf8_width( panel_name );
3671                     catacurses::window label = catacurses::newwin( 1, panel_name_width, point( sidebar_right ?
3672                                                TERMX - panel.get_width() - panel_name_width - 1 : panel.get_width() + 1, y ) );
3673                     werase( label );
3674                     mvwprintz( label, point_zero, c_light_red, panel_name );
3675                     wnoutrefresh( label );
3676                     label = catacurses::newwin( h, 1,
3677                                                 point( sidebar_right ? TERMX - panel.get_width() - 1 : panel.get_width(), y ) );
3678                     werase( label );
3679                     if( h == 1 ) {
3680                         mvwputch( label, point_zero, c_light_red, LINE_OXOX );
3681                     } else {
3682                         mvwputch( label, point_zero, c_light_red, LINE_OXXX );
3683                         for( int i = 1; i < h - 1; i++ ) {
3684                             mvwputch( label, point( 0, i ), c_light_red, LINE_XOXO );
3685                         }
3686                         mvwputch( label, point( 0, h - 1 ), c_light_red, sidebar_right ? LINE_XXOO : LINE_XOOX );
3687                     }
3688                     wnoutrefresh( label );
3689                 }
3690                 y += h;
3691             }
3692         }
3693     }
3694     previous_turn = current_turn;
3695 }
3696 
draw_pixel_minimap(const catacurses::window & w)3697 void game::draw_pixel_minimap( const catacurses::window &w )
3698 {
3699     w_pixel_minimap = w;
3700 }
3701 
draw_critter(const Creature & critter,const tripoint & center)3702 void game::draw_critter( const Creature &critter, const tripoint &center )
3703 {
3704     const int my = POSY + ( critter.posy() - center.y );
3705     const int mx = POSX + ( critter.posx() - center.x );
3706     if( !is_valid_in_w_terrain( point( mx, my ) ) ) {
3707         return;
3708     }
3709     if( critter.posz() != center.z && m.has_zlevels() ) {
3710         static constexpr tripoint up_tripoint( tripoint_above );
3711         if( critter.posz() == center.z - 1 &&
3712             ( debug_mode || u.sees( critter ) ) &&
3713             m.valid_move( critter.pos(), critter.pos() + up_tripoint, false, true ) ) {
3714             // Monster is below
3715             // TODO: Make this show something more informative than just green 'v'
3716             // TODO: Allow looking at this mon with look command
3717             // TODO: Redraw this after weather etc. animations
3718             mvwputch( w_terrain, point( mx, my ), c_green_cyan, 'v' );
3719         }
3720         return;
3721     }
3722     if( u.sees( critter ) || &critter == &u ) {
3723         critter.draw( w_terrain, center.xy(), false );
3724         return;
3725     }
3726 
3727     if( u.sees_with_infrared( critter ) || u.sees_with_specials( critter ) ) {
3728         mvwputch( w_terrain, point( mx, my ), c_red, '?' );
3729     }
3730 }
3731 
is_in_viewport(const tripoint & p,int margin) const3732 bool game::is_in_viewport( const tripoint &p, int margin ) const
3733 {
3734     const tripoint diff( u.pos() + u.view_offset - p );
3735 
3736     return ( std::abs( diff.x ) <= getmaxx( w_terrain ) / 2 - margin ) &&
3737            ( std::abs( diff.y ) <= getmaxy( w_terrain ) / 2 - margin );
3738 }
3739 
draw_ter(const bool draw_sounds)3740 void game::draw_ter( const bool draw_sounds )
3741 {
3742     draw_ter( u.pos() + u.view_offset, is_looking,
3743               draw_sounds );
3744 }
3745 
draw_ter(const tripoint & center,const bool looking,const bool draw_sounds)3746 void game::draw_ter( const tripoint &center, const bool looking, const bool draw_sounds )
3747 {
3748     ter_view_p = center;
3749 
3750     m.draw( w_terrain, center );
3751 
3752     if( draw_sounds ) {
3753         draw_footsteps( w_terrain, tripoint( -center.x, -center.y, center.z ) + point( POSX, POSY ) );
3754     }
3755 
3756     for( Creature &critter : all_creatures() ) {
3757         draw_critter( critter, center );
3758     }
3759 
3760     if( !destination_preview.empty() && u.view_offset.z == 0 ) {
3761         // Draw auto-move preview trail
3762         const tripoint &final_destination = destination_preview.back();
3763         tripoint line_center = u.pos() + u.view_offset;
3764         draw_line( final_destination, line_center, destination_preview, true );
3765         mvwputch( w_terrain, final_destination.xy() - u.view_offset.xy() + point( POSX - u.posx(),
3766                   POSY - u.posy() ), c_white, 'X' );
3767     }
3768 
3769     if( u.controlling_vehicle && !looking ) {
3770         draw_veh_dir_indicator( false );
3771         draw_veh_dir_indicator( true );
3772     }
3773 }
3774 
get_veh_dir_indicator_location(bool next) const3775 cata::optional<tripoint> game::get_veh_dir_indicator_location( bool next ) const
3776 {
3777     if( !get_option<bool>( "VEHICLE_DIR_INDICATOR" ) ) {
3778         return cata::nullopt;
3779     }
3780     const optional_vpart_position vp = m.veh_at( u.pos() );
3781     if( !vp ) {
3782         return cata::nullopt;
3783     }
3784     vehicle *const veh = &vp->vehicle();
3785     rl_vec2d face = next ? veh->dir_vec() : veh->face_vec();
3786     float r = 10.0f;
3787     return tripoint( static_cast<int>( r * face.x ), static_cast<int>( r * face.y ), u.pos().z );
3788 }
3789 
draw_veh_dir_indicator(bool next)3790 void game::draw_veh_dir_indicator( bool next )
3791 {
3792     if( const cata::optional<tripoint> indicator_offset = get_veh_dir_indicator_location( next ) ) {
3793         nc_color col = next ? c_white : c_dark_gray;
3794         mvwputch( w_terrain, indicator_offset->xy() - u.view_offset.xy() + point( POSX, POSY ), col, 'X' );
3795     }
3796 }
3797 
draw_minimap()3798 void game::draw_minimap()
3799 {
3800 
3801     // Draw the box
3802     werase( w_minimap );
3803     draw_border( w_minimap );
3804 
3805     const tripoint_abs_omt curs = u.global_omt_location();
3806     const point_abs_omt curs2( curs.xy() );
3807     const tripoint_abs_omt targ = u.get_active_mission_target();
3808     bool drew_mission = targ == overmap::invalid_tripoint;
3809 
3810     const int levz = m.get_abs_sub().z;
3811     for( int i = -2; i <= 2; i++ ) {
3812         for( int j = -2; j <= 2; j++ ) {
3813             const point_abs_omt om( curs2 + point( i, j ) );
3814             nc_color ter_color;
3815             tripoint_abs_omt omp( om, levz );
3816             std::string ter_sym;
3817             const bool seen = overmap_buffer.seen( omp );
3818             const bool vehicle_here = overmap_buffer.has_vehicle( omp );
3819             if( overmap_buffer.has_note( omp ) ) {
3820 
3821                 const std::string &note_text = overmap_buffer.note( omp );
3822 
3823                 ter_color = c_yellow;
3824                 ter_sym = "N";
3825 
3826                 int symbolIndex = note_text.find( ':' );
3827                 int colorIndex = note_text.find( ';' );
3828 
3829                 bool symbolFirst = symbolIndex < colorIndex;
3830 
3831                 if( colorIndex > -1 && symbolIndex > -1 ) {
3832                     if( symbolFirst ) {
3833                         if( colorIndex > 4 ) {
3834                             colorIndex = -1;
3835                         }
3836                         if( symbolIndex > 1 ) {
3837                             symbolIndex = -1;
3838                             colorIndex = -1;
3839                         }
3840                     } else {
3841                         if( symbolIndex > 4 ) {
3842                             symbolIndex = -1;
3843                         }
3844                         if( colorIndex > 2 ) {
3845                             colorIndex = -1;
3846                         }
3847                     }
3848                 } else if( colorIndex > 2 ) {
3849                     colorIndex = -1;
3850                 } else if( symbolIndex > 1 ) {
3851                     symbolIndex = -1;
3852                 }
3853 
3854                 if( symbolIndex > -1 ) {
3855                     int symbolStart = 0;
3856                     if( colorIndex > -1 && !symbolFirst ) {
3857                         symbolStart = colorIndex + 1;
3858                     }
3859                     ter_sym = note_text.substr( symbolStart, symbolIndex - symbolStart ).c_str()[0];
3860                 }
3861 
3862                 if( colorIndex > -1 ) {
3863 
3864                     int colorStart = 0;
3865 
3866                     if( symbolIndex > -1 && symbolFirst ) {
3867                         colorStart = symbolIndex + 1;
3868                     }
3869 
3870                     std::string sym = note_text.substr( colorStart, colorIndex - colorStart );
3871 
3872                     if( sym.length() == 2 ) {
3873                         if( sym == "br" ) {
3874                             ter_color = c_brown;
3875                         } else if( sym == "lg" ) {
3876                             ter_color = c_light_gray;
3877                         } else if( sym == "dg" ) {
3878                             ter_color = c_dark_gray;
3879                         }
3880                     } else {
3881                         char colorID = sym.c_str()[0];
3882                         if( colorID == 'r' ) {
3883                             ter_color = c_light_red;
3884                         } else if( colorID == 'R' ) {
3885                             ter_color = c_red;
3886                         } else if( colorID == 'g' ) {
3887                             ter_color = c_light_green;
3888                         } else if( colorID == 'G' ) {
3889                             ter_color = c_green;
3890                         } else if( colorID == 'b' ) {
3891                             ter_color = c_light_blue;
3892                         } else if( colorID == 'B' ) {
3893                             ter_color = c_blue;
3894                         } else if( colorID == 'W' ) {
3895                             ter_color = c_white;
3896                         } else if( colorID == 'C' ) {
3897                             ter_color = c_cyan;
3898                         } else if( colorID == 'c' ) {
3899                             ter_color = c_light_cyan;
3900                         } else if( colorID == 'P' ) {
3901                             ter_color = c_pink;
3902                         } else if( colorID == 'm' ) {
3903                             ter_color = c_magenta;
3904                         }
3905                     }
3906                 }
3907             } else if( !seen ) {
3908                 ter_sym = " ";
3909                 ter_color = c_black;
3910             } else if( vehicle_here ) {
3911                 ter_color = c_cyan;
3912                 ter_sym = "c";
3913             } else {
3914                 const oter_id &cur_ter = overmap_buffer.ter( omp );
3915                 ter_sym = cur_ter->get_symbol();
3916                 if( overmap_buffer.is_explored( omp ) ) {
3917                     ter_color = c_dark_gray;
3918                 } else {
3919                     ter_color = cur_ter->get_color();
3920                 }
3921             }
3922             if( !drew_mission && targ.xy() == omp.xy() ) {
3923                 // If there is a mission target, and it's not on the same
3924                 // overmap terrain as the player character, mark it.
3925                 // TODO: Inform player if the mission is above or below
3926                 drew_mission = true;
3927                 if( i != 0 || j != 0 ) {
3928                     ter_color = red_background( ter_color );
3929                 }
3930             }
3931             if( i == 0 && j == 0 ) {
3932                 mvwputch_hi( w_minimap, point( 3, 3 ), ter_color, ter_sym );
3933             } else {
3934                 mvwputch( w_minimap, point( 3 + i, 3 + j ), ter_color, ter_sym );
3935             }
3936         }
3937     }
3938 
3939     // Print arrow to mission if we have one!
3940     if( !drew_mission ) {
3941         double slope = curs2.x() != targ.x() ?
3942                        static_cast<double>( targ.y() - curs2.y() ) / ( targ.x() - curs2.x() ) : 4;
3943 
3944         if( curs2.x() == targ.x() || std::fabs( slope ) > 3.5 ) { // Vertical slope
3945             if( targ.y() > curs2.y() ) {
3946                 mvwputch( w_minimap, point( 3, 6 ), c_red, "*" );
3947             } else {
3948                 mvwputch( w_minimap, point( 3, 0 ), c_red, "*" );
3949             }
3950         } else {
3951             int arrowx = -1;
3952             int arrowy = -1;
3953             if( std::fabs( slope ) >= 1. ) { // y diff is bigger!
3954                 arrowy = targ.y() > curs2.y() ? 6 : 0;
3955                 arrowx =
3956                     static_cast<int>( 3 + 3 * ( targ.y() > curs2.y() ? slope : ( 0 - slope ) ) );
3957                 if( arrowx < 0 ) {
3958                     arrowx = 0;
3959                 }
3960                 if( arrowx > 6 ) {
3961                     arrowx = 6;
3962                 }
3963             } else {
3964                 arrowx = targ.x() > curs2.x() ? 6 : 0;
3965                 arrowy = static_cast<int>( 3 + 3 * ( targ.x() > curs2.x() ? slope : -slope ) );
3966                 if( arrowy < 0 ) {
3967                     arrowy = 0;
3968                 }
3969                 if( arrowy > 6 ) {
3970                     arrowy = 6;
3971                 }
3972             }
3973             char glyph = '*';
3974             if( targ.z() > u.posz() ) {
3975                 glyph = '^';
3976             } else if( targ.z() < u.posz() ) {
3977                 glyph = 'v';
3978             }
3979 
3980             mvwputch( w_minimap, point( arrowx, arrowy ), c_red, glyph );
3981         }
3982     }
3983 
3984     Character &player_character = get_player_character();
3985     const int sight_points = player_character.overmap_sight_range( g->light_level(
3986                                  player_character.posz() ) );
3987     for( int i = -3; i <= 3; i++ ) {
3988         for( int j = -3; j <= 3; j++ ) {
3989             if( i > -3 && i < 3 && j > -3 && j < 3 ) {
3990                 continue; // only do hordes on the border, skip inner map
3991             }
3992             const tripoint_abs_omt omp( curs2 + point( i, j ), levz );
3993             if( overmap_buffer.get_horde_size( omp ) >= HORDE_VISIBILITY_SIZE ) {
3994                 if( overmap_buffer.seen( omp )
3995                     && player_character.overmap_los( omp, sight_points ) ) {
3996                     mvwputch( w_minimap, point( i + 3, j + 3 ), c_green,
3997                               overmap_buffer.get_horde_size( omp ) > HORDE_VISIBILITY_SIZE * 2 ? 'Z' : 'z' );
3998                 }
3999             }
4000         }
4001     }
4002 
4003     wnoutrefresh( w_minimap );
4004 }
4005 
natural_light_level(const int zlev) const4006 float game::natural_light_level( const int zlev ) const
4007 {
4008     // ignore while underground or above limits
4009     if( zlev > OVERMAP_HEIGHT || zlev < 0 ) {
4010         return LIGHT_AMBIENT_MINIMAL;
4011     }
4012 
4013     if( latest_lightlevels[zlev] > -std::numeric_limits<float>::max() ) {
4014         // Already found the light level for now?
4015         return latest_lightlevels[zlev];
4016     }
4017 
4018     float ret = LIGHT_AMBIENT_MINIMAL;
4019 
4020     // Sunlight/moonlight related stuff
4021     if( !weather.lightning_active ) {
4022         ret = sunlight( calendar::turn );
4023     } else {
4024         // Recent lightning strike has lit the area
4025         ret = default_daylight_level();
4026     }
4027 
4028     ret += get_weather().weather_id->light_modifier;
4029 
4030     // Artifact light level changes here. Even though some of these only have an effect
4031     // aboveground it is cheaper performance wise to simply iterate through the entire
4032     // list once instead of twice.
4033     float mod_ret = -1;
4034     // Each artifact change does std::max(mod_ret, new val) since a brighter end value
4035     // will trump a lower one.
4036     if( const timed_event *e = timed_events.get( timed_event_type::DIM ) ) {
4037         // timed_event_type::DIM slowly dims the natural sky level, then relights it.
4038         const time_duration left = e->when - calendar::turn;
4039         // timed_event_type::DIM has an occurrence date of turn + 50, so the first 25 dim it,
4040         if( left > 25_turns ) {
4041             mod_ret = std::max( static_cast<double>( mod_ret ), ( ret * ( left - 25_turns ) ) / 25_turns );
4042             // and the last 25 scale back towards normal.
4043         } else {
4044             mod_ret = std::max( static_cast<double>( mod_ret ), ( ret * ( 25_turns - left ) ) / 25_turns );
4045         }
4046     }
4047     if( timed_events.queued( timed_event_type::ARTIFACT_LIGHT ) ) {
4048         // timed_event_type::ARTIFACT_LIGHT causes everywhere to become as bright as day.
4049         mod_ret = std::max<float>( ret, default_daylight_level() );
4050     }
4051     // If we had a changed light level due to an artifact event then it overwrites
4052     // the natural light level.
4053     if( mod_ret > -1 ) {
4054         ret = mod_ret;
4055     }
4056 
4057     // Cap everything to our minimum light level
4058     ret = std::max<float>( LIGHT_AMBIENT_MINIMAL, ret );
4059 
4060     latest_lightlevels[zlev] = ret;
4061 
4062     return ret;
4063 }
4064 
light_level(const int zlev) const4065 unsigned char game::light_level( const int zlev ) const
4066 {
4067     const float light = natural_light_level( zlev );
4068     return LIGHT_RANGE( light );
4069 }
4070 
reset_light_level()4071 void game::reset_light_level()
4072 {
4073     for( float &lev : latest_lightlevels ) {
4074         lev = -std::numeric_limits<float>::max();
4075     }
4076 }
4077 
4078 //Gets the next free ID, also used for player ID's.
assign_npc_id()4079 character_id game::assign_npc_id()
4080 {
4081     character_id ret = next_npc_id;
4082     ++next_npc_id;
4083     return ret;
4084 }
4085 
is_hostile_nearby()4086 Creature *game::is_hostile_nearby()
4087 {
4088     int distance = ( get_option<int>( "SAFEMODEPROXIMITY" ) <= 0 ) ? MAX_VIEW_DISTANCE :
4089                    get_option<int>( "SAFEMODEPROXIMITY" );
4090     return is_hostile_within( distance );
4091 }
4092 
is_hostile_very_close(bool dangerous)4093 Creature *game::is_hostile_very_close( bool dangerous )
4094 {
4095     return is_hostile_within( DANGEROUS_PROXIMITY, dangerous );
4096 }
4097 
is_hostile_within(int distance,bool dangerous)4098 Creature *game::is_hostile_within( int distance, bool dangerous )
4099 {
4100     for( auto &critter : u.get_visible_creatures( distance ) ) {
4101         if( u.attitude_to( *critter ) == Creature::Attitude::HOSTILE ) {
4102             if( dangerous ) {
4103                 if( critter->is_ranged_attacker() ) {
4104                     return critter;
4105                 }
4106 
4107                 const pathfinding_settings pf_settings = pathfinding_settings { 8, distance, distance * 2, 4, true, false, true, false, false };
4108                 static const std::set<tripoint> path_avoid = {};
4109 
4110                 if( !get_map().route( u.pos(), critter->pos(), pf_settings, path_avoid ).empty() ) {
4111                     return critter;
4112                 }
4113                 continue;
4114             }
4115             return critter;
4116         }
4117     }
4118 
4119     return nullptr;
4120 }
4121 
get_fishable_locations(int distance,const tripoint & fish_pos)4122 std::unordered_set<tripoint> game::get_fishable_locations( int distance, const tripoint &fish_pos )
4123 {
4124     // We're going to get the contiguous fishable terrain starting at
4125     // the provided fishing location (e.g. where a line was cast or a fish
4126     // trap was set), and then check whether or not fishable monsters are
4127     // actually in those locations. This will help us ensure that we're
4128     // getting our fish from the location that we're ACTUALLY fishing,
4129     // rather than just somewhere in the vicinity.
4130 
4131     std::unordered_set<tripoint> visited;
4132 
4133     const tripoint fishing_boundary_min( fish_pos + point( -distance, -distance ) );
4134     const tripoint fishing_boundary_max( fish_pos + point( distance, distance ) );
4135 
4136     const inclusive_cuboid<tripoint> fishing_boundaries(
4137         fishing_boundary_min, fishing_boundary_max );
4138 
4139     const auto get_fishable_terrain = [&]( tripoint starting_point,
4140     std::unordered_set<tripoint> &fishable_terrain ) {
4141         std::queue<tripoint> to_check;
4142         to_check.push( starting_point );
4143         while( !to_check.empty() ) {
4144             const tripoint current_point = to_check.front();
4145             to_check.pop();
4146 
4147             // We've been here before, so bail.
4148             if( visited.find( current_point ) != visited.end() ) {
4149                 continue;
4150             }
4151 
4152             // This point is out of bounds, so bail.
4153             if( !fishing_boundaries.contains( current_point ) ) {
4154                 continue;
4155             }
4156 
4157             // Mark this point as visited.
4158             visited.emplace( current_point );
4159 
4160             if( m.has_flag( "FISHABLE", current_point ) ) {
4161                 fishable_terrain.emplace( current_point );
4162                 to_check.push( current_point + point_south );
4163                 to_check.push( current_point + point_north );
4164                 to_check.push( current_point + point_east );
4165                 to_check.push( current_point + point_west );
4166             }
4167         }
4168     };
4169 
4170     // Starting at the provided location, get our fishable terrain
4171     // and populate a set with those locations which we'll then use
4172     // to determine if any fishable monsters are in those locations.
4173     std::unordered_set<tripoint> fishable_points;
4174     get_fishable_terrain( fish_pos, fishable_points );
4175 
4176     return fishable_points;
4177 }
4178 
get_fishable_monsters(std::unordered_set<tripoint> & fishable_locations)4179 std::vector<monster *> game::get_fishable_monsters( std::unordered_set<tripoint>
4180         &fishable_locations )
4181 {
4182     std::vector<monster *> unique_fish;
4183     for( monster &critter : all_monsters() ) {
4184         // If it is fishable...
4185         if( critter.has_flag( MF_FISHABLE ) ) {
4186             const tripoint critter_pos = critter.pos();
4187             // ...and it is in a fishable location.
4188             if( fishable_locations.find( critter_pos ) != fishable_locations.end() ) {
4189                 unique_fish.push_back( &critter );
4190             }
4191         }
4192     }
4193 
4194     return unique_fish;
4195 }
4196 
4197 // Print monster info to the given window
mon_info(const catacurses::window & w,int hor_padding)4198 void game::mon_info( const catacurses::window &w, int hor_padding )
4199 {
4200     const monster_visible_info &mon_visible = u.get_mon_visible();
4201     const auto &unique_types = mon_visible.unique_types;
4202     const auto &unique_mons = mon_visible.unique_mons;
4203     const auto &dangerous = mon_visible.dangerous;
4204 
4205     const int width = getmaxx( w ) - 2 * hor_padding;
4206     const int maxheight = getmaxy( w );
4207 
4208     const int startrow = 0;
4209 
4210     // Print the direction headings
4211     // Reminder:
4212     // 7 0 1    unique_types uses these indices;
4213     // 6 8 2    0-7 are provide by direction_from()
4214     // 5 4 3    8 is used for local monsters (for when we explain them below)
4215 
4216     const std::array<std::string, 8> dir_labels = {{
4217             _( "North:" ), _( "NE:" ), _( "East:" ), _( "SE:" ),
4218             _( "South:" ), _( "SW:" ), _( "West:" ), _( "NW:" )
4219         }
4220     };
4221     std::array<int, 8> widths;
4222     for( int i = 0; i < 8; i++ ) {
4223         widths[i] = utf8_width( dir_labels[i] );
4224     }
4225     std::array<int, 8> xcoords;
4226     const std::array<int, 8> ycoords = {{ 0, 0, 1, 2, 2, 2, 1, 0 }};
4227     xcoords[0] = xcoords[4] = width / 3;
4228     xcoords[1] = xcoords[3] = xcoords[2] = ( width / 3 ) * 2;
4229     xcoords[5] = xcoords[6] = xcoords[7] = 0;
4230     //for the alignment of the 1,2,3 rows on the right edge (East - NE)
4231     xcoords[2] -= widths[2] - widths[1];
4232     for( int i = 0; i < 8; i++ ) {
4233         nc_color c = unique_types[i].empty() && unique_mons[i].empty() ? c_dark_gray
4234                      : ( dangerous[i] ? c_light_red : c_light_gray );
4235         mvwprintz( w, point( xcoords[i] + hor_padding, ycoords[i] + startrow ), c, dir_labels[i] );
4236     }
4237 
4238     // Print the symbols of all monsters in all directions.
4239     for( int i = 0; i < 8; i++ ) {
4240         point pr( xcoords[i] + widths[i] + 1, ycoords[i] + startrow );
4241 
4242         // The list of symbols needs a space on each end.
4243         int symroom = ( width / 3 ) - widths[i] - 2;
4244         const int typeshere_npc = unique_types[i].size();
4245         const int typeshere_mon = unique_mons[i].size();
4246         const int typeshere = typeshere_mon + typeshere_npc;
4247         for( int j = 0; j < typeshere && j < symroom; j++ ) {
4248             nc_color c;
4249             std::string sym;
4250             if( symroom < typeshere && j == symroom - 1 ) {
4251                 // We've run out of room!
4252                 c = c_white;
4253                 sym = "+";
4254             } else if( j < typeshere_npc ) {
4255                 switch( unique_types[i][j]->get_attitude() ) {
4256                     case NPCATT_KILL:
4257                         c = c_red;
4258                         break;
4259                     case NPCATT_FOLLOW:
4260                         c = c_light_green;
4261                         break;
4262                     default:
4263                         c = c_pink;
4264                         break;
4265                 }
4266                 sym = "@";
4267             } else {
4268                 const mtype &mt = *unique_mons[i][j - typeshere_npc];
4269                 c = mt.color;
4270                 sym = mt.sym;
4271             }
4272             mvwprintz( w, pr, c, sym );
4273 
4274             pr.x++;
4275         }
4276     }
4277 
4278     // Now we print their full names!
4279 
4280     std::set<const mtype *> listed_mons;
4281 
4282     // Start printing monster names on row 4. Rows 0-2 are for labels, and row 3
4283     // is blank.
4284     point pr( hor_padding, 4 + startrow );
4285 
4286     // Print monster names, starting with those at location 8 (nearby).
4287     for( int j = 8; j >= 0 && pr.y < maxheight; j-- ) {
4288         // Separate names by some number of spaces (more for local monsters).
4289         int namesep = ( j == 8 ? 2 : 1 );
4290         for( const mtype *type : unique_mons[j] ) {
4291             if( pr.y >= maxheight ) {
4292                 // no space to print to anyway
4293                 break;
4294             }
4295             if( listed_mons.count( type ) > 0 ) {
4296                 // this type is already printed.
4297                 continue;
4298             }
4299             listed_mons.insert( type );
4300 
4301             const mtype &mt = *type;
4302             const std::string name = mt.nname();
4303 
4304             // Move to the next row if necessary. (The +2 is for the "Z ").
4305             if( pr.x + 2 + utf8_width( name ) >= width ) {
4306                 pr.y++;
4307                 pr.x = hor_padding;
4308             }
4309 
4310             if( pr.y < maxheight ) { // Don't print if we've overflowed
4311                 mvwprintz( w, pr, mt.color, mt.sym );
4312                 pr.x += 2; // symbol and space
4313                 nc_color danger = c_dark_gray;
4314                 if( mt.difficulty >= 30 ) {
4315                     danger = c_red;
4316                 } else if( mt.difficulty >= 16 ) {
4317                     danger = c_light_red;
4318                 } else if( mt.difficulty >= 8 ) {
4319                     danger = c_white;
4320                 } else if( mt.agro > 0 ) {
4321                     danger = c_light_gray;
4322                 }
4323                 mvwprintz( w, pr, danger, name );
4324                 pr.x += utf8_width( name ) + namesep;
4325             }
4326         }
4327     }
4328 }
4329 
mon_info_update()4330 void game::mon_info_update( )
4331 {
4332     int newseen = 0;
4333     const int safe_proxy_dist = get_option<int>( "SAFEMODEPROXIMITY" );
4334     const int iProxyDist = ( safe_proxy_dist <= 0 ) ? MAX_VIEW_DISTANCE :
4335                            safe_proxy_dist;
4336 
4337     monster_visible_info &mon_visible = u.get_mon_visible();
4338     auto &new_seen_mon = mon_visible.new_seen_mon;
4339     auto &unique_types = mon_visible.unique_types;
4340     auto &unique_mons = mon_visible.unique_mons;
4341     auto &dangerous = mon_visible.dangerous;
4342 
4343     // 7 0 1    unique_types uses these indices;
4344     // 6 8 2    0-7 are provide by direction_from()
4345     // 5 4 3    8 is used for local monsters (for when we explain them below)
4346     for( auto &t : unique_types ) {
4347         t.clear();
4348     }
4349     for( auto &m : unique_mons ) {
4350         m.clear();
4351     }
4352     std::fill_n( dangerous, 8, false );
4353 
4354     const tripoint view = u.pos() + u.view_offset;
4355     new_seen_mon.clear();
4356 
4357     static time_point previous_turn = calendar::turn_zero;
4358     const time_duration sm_ignored_turns =
4359         time_duration::from_turns( get_option<int>( "SAFEMODEIGNORETURNS" ) );
4360 
4361     for( Creature *c : u.get_visible_creatures( MAPSIZE_X ) ) {
4362         monster *m = dynamic_cast<monster *>( c );
4363         npc *p = dynamic_cast<npc *>( c );
4364         const direction dir_to_mon = direction_from( view.xy(), point( c->posx(), c->posy() ) );
4365         const int mx = POSX + ( c->posx() - view.x );
4366         const int my = POSY + ( c->posy() - view.y );
4367         int index = 8;
4368         if( !is_valid_in_w_terrain( point( mx, my ) ) ) {
4369             // for compatibility with old code, see diagram below, it explains the values for index,
4370             // also might need revisiting one z-levels are in.
4371             switch( dir_to_mon ) {
4372                 case direction::ABOVENORTHWEST:
4373                 case direction::NORTHWEST:
4374                 case direction::BELOWNORTHWEST:
4375                     index = 7;
4376                     break;
4377                 case direction::ABOVENORTH:
4378                 case direction::NORTH:
4379                 case direction::BELOWNORTH:
4380                     index = 0;
4381                     break;
4382                 case direction::ABOVENORTHEAST:
4383                 case direction::NORTHEAST:
4384                 case direction::BELOWNORTHEAST:
4385                     index = 1;
4386                     break;
4387                 case direction::ABOVEWEST:
4388                 case direction::WEST:
4389                 case direction::BELOWWEST:
4390                     index = 6;
4391                     break;
4392                 case direction::ABOVECENTER:
4393                 case direction::CENTER:
4394                 case direction::BELOWCENTER:
4395                     index = 8;
4396                     break;
4397                 case direction::ABOVEEAST:
4398                 case direction::EAST:
4399                 case direction::BELOWEAST:
4400                     index = 2;
4401                     break;
4402                 case direction::ABOVESOUTHWEST:
4403                 case direction::SOUTHWEST:
4404                 case direction::BELOWSOUTHWEST:
4405                     index = 5;
4406                     break;
4407                 case direction::ABOVESOUTH:
4408                 case direction::SOUTH:
4409                 case direction::BELOWSOUTH:
4410                     index = 4;
4411                     break;
4412                 case direction::ABOVESOUTHEAST:
4413                 case direction::SOUTHEAST:
4414                 case direction::BELOWSOUTHEAST:
4415                     index = 3;
4416                     break;
4417             }
4418         }
4419 
4420         bool need_processing = false;
4421         const bool safemode_empty = get_safemode().empty();
4422 
4423         if( m != nullptr ) {
4424             //Safemode monster check
4425             monster &critter = *m;
4426 
4427             const monster_attitude matt = critter.attitude( &u );
4428             const int mon_dist = rl_dist( u.pos(), critter.pos() );
4429             if( !safemode_empty ) {
4430                 need_processing = get_safemode().check_monster(
4431                                       critter.name(),
4432                                       critter.attitude_to( u ),
4433                                       mon_dist ) == rule_state::BLACKLISTED;
4434             } else {
4435                 need_processing =  MATT_ATTACK == matt || MATT_FOLLOW == matt;
4436             }
4437             if( need_processing ) {
4438                 if( index < 8 && critter.sees( get_player_character() ) ) {
4439                     dangerous[index] = true;
4440                 }
4441 
4442                 if( !safemode_empty || mon_dist <= iProxyDist ) {
4443                     bool passmon = false;
4444                     if( critter.ignoring > 0 ) {
4445                         if( safe_mode != SAFE_MODE_ON ) {
4446                             critter.ignoring = 0;
4447                         } else if( ( sm_ignored_turns == time_duration() ||
4448                                      ( critter.lastseen_turn &&
4449                                        *critter.lastseen_turn > calendar::turn - sm_ignored_turns ) ) &&
4450                                    ( mon_dist > critter.ignoring / 2 || mon_dist < 6 ) ) {
4451                             passmon = true;
4452                         }
4453                         critter.lastseen_turn = calendar::turn;
4454                     }
4455 
4456                     if( !passmon ) {
4457                         newseen++;
4458                         new_seen_mon.push_back( shared_from( critter ) );
4459                     }
4460                 }
4461             }
4462 
4463             std::vector<const mtype *> &vec = unique_mons[index];
4464             if( std::find( vec.begin(), vec.end(), critter.type ) == vec.end() ) {
4465                 vec.push_back( critter.type );
4466             }
4467         } else if( p != nullptr ) {
4468             //Safe mode NPC check
4469 
4470             const int npc_dist = rl_dist( u.pos(), p->pos() );
4471             if( !safemode_empty ) {
4472                 need_processing = get_safemode().check_monster(
4473                                       get_safemode().npc_type_name(),
4474                                       p->attitude_to( u ),
4475                                       npc_dist ) == rule_state::BLACKLISTED ;
4476             } else {
4477                 need_processing = npc_dist <= iProxyDist &&
4478                                   p->get_attitude() == NPCATT_KILL;
4479             }
4480             if( need_processing ) {
4481                 newseen++;
4482             }
4483             unique_types[index].push_back( p );
4484         }
4485     }
4486 
4487     if( newseen > mostseen ) {
4488         if( newseen - mostseen == 1 ) {
4489             if( !new_seen_mon.empty() ) {
4490                 monster &critter = *new_seen_mon.back();
4491                 cancel_activity_or_ignore_query( distraction_type::hostile_spotted_far,
4492                                                  string_format( _( "%s spotted!" ), critter.name() ) );
4493                 if( u.has_trait( trait_id( "M_DEFENDER" ) ) && critter.type->in_species( species_PLANT ) ) {
4494                     add_msg( m_warning, _( "We have detected a %s - an enemy of the Mycus!" ), critter.name() );
4495                     if( !u.has_effect( effect_adrenaline_mycus ) ) {
4496                         u.add_effect( effect_adrenaline_mycus, 30_minutes );
4497                     } else if( u.get_effect_int( effect_adrenaline_mycus ) == 1 ) {
4498                         // Triffids present.  We ain't got TIME to adrenaline comedown!
4499                         u.add_effect( effect_adrenaline_mycus, 15_minutes );
4500                         u.mod_pain( 3 ); // Does take it out of you, though
4501                         add_msg( m_info, _( "Our fibers strain with renewed wrath!" ) );
4502                     }
4503                 }
4504             } else {
4505                 //Hostile NPC
4506                 cancel_activity_or_ignore_query( distraction_type::hostile_spotted_far,
4507                                                  _( "Hostile survivor spotted!" ) );
4508             }
4509         } else {
4510             cancel_activity_or_ignore_query( distraction_type::hostile_spotted_far, _( "Monsters spotted!" ) );
4511         }
4512         turnssincelastmon = 0_turns;
4513         if( safe_mode == SAFE_MODE_ON ) {
4514             set_safe_mode( SAFE_MODE_STOP );
4515         }
4516     } else if( calendar::turn > previous_turn && get_option<bool>( "AUTOSAFEMODE" ) &&
4517                newseen == 0 ) { // Auto-safe mode, but only if it's a new turn
4518         turnssincelastmon += calendar::turn - previous_turn;
4519         time_duration auto_safe_mode =
4520             time_duration::from_turns( get_option<int>( "AUTOSAFEMODETURNS" ) );
4521         if( turnssincelastmon >= auto_safe_mode && safe_mode == SAFE_MODE_OFF ) {
4522             set_safe_mode( SAFE_MODE_ON );
4523             add_msg( m_info, _( "Safe mode ON!" ) );
4524         }
4525     }
4526 
4527     if( newseen == 0 && safe_mode == SAFE_MODE_STOP ) {
4528         set_safe_mode( SAFE_MODE_ON );
4529     }
4530 
4531     previous_turn = calendar::turn;
4532     mostseen = newseen;
4533 }
4534 
cleanup_dead()4535 void game::cleanup_dead()
4536 {
4537     // Dead monsters need to stay in the tracker until everything else that needs to die does so
4538     // This is because dying monsters can still interact with other dying monsters (@ref Creature::killer)
4539     bool monster_is_dead = critter_tracker->kill_marked_for_death();
4540 
4541     bool npc_is_dead = false;
4542     // can't use all_npcs as that does not include dead ones
4543     for( const auto &n : active_npc ) {
4544         if( n->is_dead() ) {
4545             n->die( nullptr ); // make sure this has been called to create corpses etc.
4546             npc_is_dead = true;
4547         }
4548     }
4549 
4550     if( monster_is_dead ) {
4551         // From here on, pointers to creatures get invalidated as dead creatures get removed.
4552         critter_tracker->remove_dead();
4553     }
4554 
4555     if( npc_is_dead ) {
4556         for( auto it = active_npc.begin(); it != active_npc.end(); ) {
4557             if( ( *it )->is_dead() ) {
4558                 remove_npc_follower( ( *it )->getID() );
4559                 overmap_buffer.remove_npc( ( *it )->getID() );
4560                 it = active_npc.erase( it );
4561             } else {
4562                 it++;
4563             }
4564         }
4565     }
4566 
4567     critter_died = false;
4568 }
4569 
monmove()4570 void game::monmove()
4571 {
4572     cleanup_dead();
4573 
4574     for( monster &critter : all_monsters() ) {
4575         // Critters in impassable tiles get pushed away, unless it's not impassable for them
4576         if( !critter.is_dead() && m.impassable( critter.pos() ) && !critter.can_move_to( critter.pos() ) ) {
4577             dbg( D_ERROR ) << "game:monmove: " << critter.name()
4578                            << " can't move to its location!  (" << critter.posx()
4579                            << ":" << critter.posy() << ":" << critter.posz() << "), "
4580                            << m.tername( critter.pos() );
4581             add_msg_debug( "%s can't move to its location!  (%d,%d,%d), %s", critter.name(),
4582                            critter.posx(), critter.posy(), critter.posz(), m.tername( critter.pos() ) );
4583             bool okay = false;
4584             for( const tripoint &dest : m.points_in_radius( critter.pos(), 3 ) ) {
4585                 if( critter.can_move_to( dest ) && is_empty( dest ) ) {
4586                     critter.setpos( dest );
4587                     okay = true;
4588                     break;
4589                 }
4590             }
4591             if( !okay ) {
4592                 // die of "natural" cause (overpopulation is natural)
4593                 critter.die( nullptr );
4594             }
4595         }
4596 
4597         if( !critter.is_dead() ) {
4598             critter.process_turn();
4599         }
4600 
4601         m.creature_in_field( critter );
4602         if( calendar::once_every( 1_days ) ) {
4603             if( critter.has_flag( MF_MILKABLE ) ) {
4604                 critter.refill_udders();
4605             }
4606             critter.try_biosignature();
4607             critter.try_reproduce();
4608         }
4609         while( critter.moves > 0 && !critter.is_dead() && !critter.has_effect( effect_ridden ) ) {
4610             critter.made_footstep = false;
4611             // Controlled critters don't make their own plans
4612             if( !critter.has_effect( effect_controlled ) ) {
4613                 // Formulate a path to follow
4614                 critter.plan();
4615             } else {
4616                 critter.moves = 0;
4617                 break;
4618             }
4619             critter.move(); // Move one square, possibly hit u
4620             critter.process_triggers();
4621             m.creature_in_field( critter );
4622         }
4623 
4624         if( !critter.is_dead() &&
4625             u.has_active_bionic( bionic_id( "bio_alarm" ) ) &&
4626             u.get_power_level() >= 25_kJ &&
4627             rl_dist( u.pos(), critter.pos() ) <= 5 &&
4628             !critter.is_hallucination() ) {
4629             u.mod_power_level( -25_kJ );
4630             add_msg( m_warning, _( "Your motion alarm goes off!" ) );
4631             cancel_activity_or_ignore_query( distraction_type::motion_alarm,
4632                                              _( "Your motion alarm goes off!" ) );
4633             if( u.has_effect( efftype_id( "sleep" ) ) ) {
4634                 u.wake_up();
4635             }
4636         }
4637     }
4638 
4639     cleanup_dead();
4640 
4641     // The remaining monsters are all alive, but may be outside of the reality bubble.
4642     // If so, despawn them. This is not the same as dying, they will be stored for later and the
4643     // monster::die function is not called.
4644     for( monster &critter : all_monsters() ) {
4645         if( critter.posx() < 0 - ( MAPSIZE_X ) / 6 ||
4646             critter.posy() < 0 - ( MAPSIZE_Y ) / 6 ||
4647             critter.posx() > ( MAPSIZE_X * 7 ) / 6 ||
4648             critter.posy() > ( MAPSIZE_Y * 7 ) / 6 ) {
4649             despawn_monster( critter );
4650         }
4651     }
4652 
4653     // Now, do active NPCs.
4654     for( npc &guy : g->all_npcs() ) {
4655         int turns = 0;
4656         if( guy.is_mounted() ) {
4657             guy.check_mount_is_spooked();
4658         }
4659         m.creature_in_field( guy );
4660         if( !guy.has_effect( effect_npc_suspend ) ) {
4661             guy.process_turn();
4662         }
4663         while( !guy.is_dead() && ( !guy.in_sleep_state() || guy.activity.id() == ACT_OPERATION ) &&
4664                guy.moves > 0 && turns < 10 ) {
4665             int moves = guy.moves;
4666             guy.move();
4667             if( moves == guy.moves ) {
4668                 // Count every time we exit npc::move() without spending any moves.
4669                 turns++;
4670             }
4671 
4672             // Turn on debug mode when in infinite loop
4673             // It has to be done before the last turn, otherwise
4674             // there will be no meaningful debug output.
4675             if( turns == 9 ) {
4676                 debugmsg( "NPC %s entered infinite loop.  Turning on debug mode",
4677                           guy.name );
4678                 debug_mode = true;
4679             }
4680         }
4681 
4682         // If we spun too long trying to decide what to do (without spending moves),
4683         // Invoke cognitive suspension to prevent an infinite loop.
4684         if( turns == 10 ) {
4685             add_msg( _( "%s faints!" ), guy.name );
4686             guy.reboot();
4687         }
4688 
4689         if( !guy.is_dead() ) {
4690             guy.npc_update_body();
4691         }
4692     }
4693     cleanup_dead();
4694 }
4695 
overmap_npc_move()4696 void game::overmap_npc_move()
4697 {
4698     std::vector<npc *> travelling_npcs;
4699     static constexpr int move_search_radius = 600;
4700     for( auto &elem : overmap_buffer.get_npcs_near_player( move_search_radius ) ) {
4701         if( !elem ) {
4702             continue;
4703         }
4704         npc *npc_to_add = elem.get();
4705         if( ( !npc_to_add->is_active() || rl_dist( u.pos(), npc_to_add->pos() ) > SEEX * 2 ) &&
4706             npc_to_add->mission == NPC_MISSION_TRAVELLING ) {
4707             travelling_npcs.push_back( npc_to_add );
4708         }
4709     }
4710     bool npcs_need_reload = false;
4711     for( auto &elem : travelling_npcs ) {
4712         if( elem->has_omt_destination() ) {
4713             if( !elem->omt_path.empty() ) {
4714                 if( rl_dist( elem->omt_path.back(), elem->global_omt_location() ) > 2 ) {
4715                     // recalculate path, we got distracted doing something else probably
4716                     elem->omt_path.clear();
4717                 } else if( elem->omt_path.back() == elem->global_omt_location() ) {
4718                     elem->omt_path.pop_back();
4719                 }
4720             }
4721             if( elem->omt_path.empty() ) {
4722                 elem->omt_path = overmap_buffer.get_npc_path( elem->global_omt_location(), elem->goal );
4723                 if( elem->omt_path.empty() ) { // goal is unreachable, or already reached goal, reset it
4724                     elem->goal = npc::no_goal_point;
4725                 }
4726             } else {
4727                 // TODO: fix point types
4728                 elem->travel_overmap( project_to<coords::sm>( elem->omt_path.back() ).raw() );
4729                 npcs_need_reload = true;
4730             }
4731         }
4732         if( !elem->has_omt_destination() && calendar::once_every( 1_hours ) && one_in( 3 ) ) {
4733             // travelling destination is reached/not set, try different one
4734             elem->set_omt_destination();
4735         }
4736     }
4737     if( npcs_need_reload ) {
4738         reload_npcs();
4739     }
4740 }
4741 
4742 /* Knockback target at t by force number of tiles in direction from s to t
4743    stun > 0 indicates base stun duration, and causes impact stun; stun == -1 indicates only impact stun
4744    dam_mult multiplies impact damage, bash effect on impact, and sound level on impact */
4745 
knockback(const tripoint & s,const tripoint & t,int force,int stun,int dam_mult)4746 void game::knockback( const tripoint &s, const tripoint &t, int force, int stun, int dam_mult )
4747 {
4748     std::vector<tripoint> traj;
4749     traj.clear();
4750     traj = line_to( s, t, 0, 0 );
4751     traj.insert( traj.begin(), s ); // how annoying, line_to() doesn't include the originating point!
4752     traj = continue_line( traj, force );
4753     traj.insert( traj.begin(), t ); // how annoying, continue_line() doesn't either!
4754 
4755     knockback( traj, stun, dam_mult );
4756 }
4757 
4758 /* Knockback target at traj.front() along line traj; traj should already have considered knockback distance.
4759    stun > 0 indicates base stun duration, and causes impact stun; stun == -1 indicates only impact stun
4760    dam_mult multiplies impact damage, bash effect on impact, and sound level on impact */
4761 
knockback(std::vector<tripoint> & traj,int stun,int dam_mult)4762 void game::knockback( std::vector<tripoint> &traj, int stun, int dam_mult )
4763 {
4764     // TODO: make the force parameter actually do something.
4765     // the header file says higher force causes more damage.
4766     // perhaps that is what it should do?
4767     tripoint tp = traj.front();
4768     if( !critter_at( tp ) ) {
4769         debugmsg( _( "Nothing at (%d,%d,%d) to knockback!" ), tp.x, tp.y, tp.z );
4770         return;
4771     }
4772     std::size_t force_remaining = traj.size();
4773     if( monster *const targ = critter_at<monster>( tp, true ) ) {
4774         if( stun > 0 ) {
4775             targ->add_effect( effect_stunned, 1_turns * stun );
4776             add_msg( _( "%s was stunned!" ), targ->name() );
4777         }
4778         for( size_t i = 1; i < traj.size(); i++ ) {
4779             if( m.impassable( traj[i].xy() ) ) {
4780                 targ->setpos( traj[i - 1] );
4781                 force_remaining = traj.size() - i;
4782                 if( stun != 0 ) {
4783                     targ->add_effect( effect_stunned, 1_turns * force_remaining );
4784                     add_msg( _( "%s was stunned!" ), targ->name() );
4785                     add_msg( _( "%s slammed into an obstacle!" ), targ->name() );
4786                     targ->apply_damage( nullptr, bodypart_id( "torso" ), dam_mult * force_remaining );
4787                     targ->check_dead_state();
4788                 }
4789                 m.bash( traj[i], 2 * dam_mult * force_remaining );
4790                 break;
4791             } else if( critter_at( traj[i] ) ) {
4792                 targ->setpos( traj[i - 1] );
4793                 force_remaining = traj.size() - i;
4794                 if( stun != 0 ) {
4795                     targ->add_effect( effect_stunned, 1_turns * force_remaining );
4796                     add_msg( _( "%s was stunned!" ), targ->name() );
4797                 }
4798                 traj.erase( traj.begin(), traj.begin() + i );
4799                 if( critter_at<monster>( traj.front() ) ) {
4800                     add_msg( _( "%s collided with something else and sent it flying!" ),
4801                              targ->name() );
4802                 } else if( npc *const guy = critter_at<npc>( traj.front() ) ) {
4803                     if( guy->male ) {
4804                         add_msg( _( "%s collided with someone else and sent him flying!" ),
4805                                  targ->name() );
4806                     } else {
4807                         add_msg( _( "%s collided with someone else and sent her flying!" ),
4808                                  targ->name() );
4809                     }
4810                 } else if( u.pos() == traj.front() ) {
4811                     add_msg( m_bad, _( "%s collided with you and sent you flying!" ), targ->name() );
4812                 }
4813                 knockback( traj, stun, dam_mult );
4814                 break;
4815             }
4816             targ->setpos( traj[i] );
4817             if( m.has_flag( "LIQUID", targ->pos() ) && !targ->can_drown() && !targ->is_dead() ) {
4818                 targ->die( nullptr );
4819                 if( u.sees( *targ ) ) {
4820                     add_msg( _( "The %s drowns!" ), targ->name() );
4821                 }
4822             }
4823             if( !m.has_flag( "LIQUID", targ->pos() ) && targ->has_flag( MF_AQUATIC ) &&
4824                 !targ->is_dead() ) {
4825                 targ->die( nullptr );
4826                 if( u.sees( *targ ) ) {
4827                     add_msg( _( "The %s flops around and dies!" ), targ->name() );
4828                 }
4829             }
4830         }
4831     } else if( npc *const targ = critter_at<npc>( tp ) ) {
4832         if( stun > 0 ) {
4833             targ->add_effect( effect_stunned, 1_turns * stun );
4834             add_msg( _( "%s was stunned!" ), targ->name );
4835         }
4836         for( size_t i = 1; i < traj.size(); i++ ) {
4837             if( m.impassable( traj[i].xy() ) ) { // oops, we hit a wall!
4838                 targ->setpos( traj[i - 1] );
4839                 force_remaining = traj.size() - i;
4840                 if( stun != 0 ) {
4841                     targ->add_effect( effect_stunned, 1_turns * force_remaining );
4842                     if( targ->has_effect( effect_stunned ) ) {
4843                         add_msg( _( "%s was stunned!" ), targ->name );
4844                     }
4845 
4846                     std::array<bodypart_id, 8> bps = {{
4847                             bodypart_id( "head" ),
4848                             bodypart_id( "arm_l" ), bodypart_id( "arm_r" ),
4849                             bodypart_id( "hand_l" ), bodypart_id( "hand_r" ),
4850                             bodypart_id( "torso" ),
4851                             bodypart_id( "leg_l" ), bodypart_id( "leg_r" )
4852                         }
4853                     };
4854                     for( const bodypart_id &bp : bps ) {
4855                         if( one_in( 2 ) ) {
4856                             targ->deal_damage( nullptr, bp, damage_instance( damage_type::BASH, force_remaining * dam_mult ) );
4857                         }
4858                     }
4859                     targ->check_dead_state();
4860                 }
4861                 m.bash( traj[i], 2 * dam_mult * force_remaining );
4862                 break;
4863             } else if( critter_at( traj[i] ) ) {
4864                 targ->setpos( traj[i - 1] );
4865                 force_remaining = traj.size() - i;
4866                 if( stun != 0 ) {
4867                     add_msg( _( "%s was stunned!" ), targ->name );
4868                     targ->add_effect( effect_stunned, 1_turns * force_remaining );
4869                 }
4870                 traj.erase( traj.begin(), traj.begin() + i );
4871                 const tripoint &traj_front = traj.front();
4872                 if( critter_at<monster>( traj_front ) ) {
4873                     add_msg( _( "%s collided with something else and sent it flying!" ),
4874                              targ->name );
4875                 } else if( npc *const guy = critter_at<npc>( traj_front ) ) {
4876                     if( guy->male ) {
4877                         add_msg( _( "%s collided with someone else and sent him flying!" ),
4878                                  targ->name );
4879                     } else {
4880                         add_msg( _( "%s collided with someone else and sent her flying!" ),
4881                                  targ->name );
4882                     }
4883                 } else if( u.posx() == traj_front.x && u.posy() == traj_front.y &&
4884                            ( u.has_trait( trait_LEG_TENT_BRACE ) && ( !u.footwear_factor() ||
4885                                    ( u.footwear_factor() == .5 && one_in( 2 ) ) ) ) ) {
4886                     add_msg( _( "%s collided with you, and barely dislodges your tentacles!" ), targ->name );
4887                 } else if( u.posx() == traj_front.x && u.posy() == traj_front.y ) {
4888                     add_msg( m_bad, _( "%s collided with you and sent you flying!" ), targ->name );
4889                 }
4890                 knockback( traj, stun, dam_mult );
4891                 break;
4892             }
4893             targ->setpos( traj[i] );
4894         }
4895     } else if( u.pos() == tp ) {
4896         if( stun > 0 ) {
4897             u.add_effect( effect_stunned, 1_turns * stun );
4898             add_msg( m_bad, ngettext( "You were stunned for %d turn!",
4899                                       "You were stunned for %d turns!",
4900                                       stun ),
4901                      stun );
4902         }
4903         for( size_t i = 1; i < traj.size(); i++ ) {
4904             if( m.impassable( traj[i] ) ) { // oops, we hit a wall!
4905                 u.setpos( traj[i - 1] );
4906                 force_remaining = traj.size() - i;
4907                 if( stun != 0 ) {
4908                     if( u.has_effect( effect_stunned ) ) {
4909                         add_msg( m_bad, ngettext( "You were stunned AGAIN for %d turn!",
4910                                                   "You were stunned AGAIN for %d turns!",
4911                                                   force_remaining ),
4912                                  force_remaining );
4913                     } else {
4914                         add_msg( m_bad, ngettext( "You were stunned for %d turn!",
4915                                                   "You were stunned for %d turns!",
4916                                                   force_remaining ),
4917                                  force_remaining );
4918                     }
4919                     u.add_effect( effect_stunned, 1_turns * force_remaining );
4920                     std::array<bodypart_id, 8> bps = {{
4921                             bodypart_id( "head" ),
4922                             bodypart_id( "arm_l" ), bodypart_id( "arm_r" ),
4923                             bodypart_id( "hand_l" ), bodypart_id( "hand_r" ),
4924                             bodypart_id( "torso" ),
4925                             bodypart_id( "leg_l" ), bodypart_id( "leg_r" )
4926                         }
4927                     };
4928                     for( const bodypart_id &bp : bps ) {
4929                         if( one_in( 2 ) ) {
4930                             u.deal_damage( nullptr, bp, damage_instance( damage_type::BASH, force_remaining * dam_mult ) );
4931                         }
4932                     }
4933                     u.check_dead_state();
4934                 }
4935                 m.bash( traj[i], 2 * dam_mult * force_remaining );
4936                 break;
4937             } else if( critter_at( traj[i] ) ) {
4938                 u.setpos( traj[i - 1] );
4939                 force_remaining = traj.size() - i;
4940                 if( stun != 0 ) {
4941                     if( u.has_effect( effect_stunned ) ) {
4942                         add_msg( m_bad, ngettext( "You were stunned AGAIN for %d turn!",
4943                                                   "You were stunned AGAIN for %d turns!",
4944                                                   force_remaining ),
4945                                  force_remaining );
4946                     } else {
4947                         add_msg( m_bad, ngettext( "You were stunned for %d turn!",
4948                                                   "You were stunned for %d turns!",
4949                                                   force_remaining ),
4950                                  force_remaining );
4951                     }
4952                     u.add_effect( effect_stunned, 1_turns * force_remaining );
4953                 }
4954                 traj.erase( traj.begin(), traj.begin() + i );
4955                 if( critter_at<monster>( traj.front() ) ) {
4956                     add_msg( _( "You collided with something and sent it flying!" ) );
4957                 } else if( npc *const guy = critter_at<npc>( traj.front() ) ) {
4958                     if( guy->male ) {
4959                         add_msg( _( "You collided with someone and sent him flying!" ) );
4960                     } else {
4961                         add_msg( _( "You collided with someone and sent her flying!" ) );
4962                     }
4963                 }
4964                 knockback( traj, stun, dam_mult );
4965                 break;
4966             }
4967             if( m.has_flag( "LIQUID", u.pos() ) && force_remaining == 0 ) {
4968                 avatar_action::swim( m, u, u.pos() );
4969             } else {
4970                 u.setpos( traj[i] );
4971             }
4972         }
4973     }
4974 }
4975 
use_computer(const tripoint & p)4976 void game::use_computer( const tripoint &p )
4977 {
4978     if( u.has_trait( trait_id( "ILLITERATE" ) ) ) {
4979         add_msg( m_info, _( "You can not read a computer screen!" ) );
4980         return;
4981     }
4982     if( u.is_blind() ) {
4983         // we don't have screen readers in game
4984         add_msg( m_info, _( "You can not see a computer screen!" ) );
4985         return;
4986     }
4987     if( u.has_trait( trait_id( "HYPEROPIC" ) ) && !u.worn_with_flag( flag_FIX_FARSIGHT ) &&
4988         !u.has_effect( effect_contacts ) &&
4989         !u.has_flag( STATIC( json_character_flag( "ENHANCED_VISION" ) ) ) ) {
4990         add_msg( m_info, _( "You'll need to put on reading glasses before you can see the screen." ) );
4991         return;
4992     }
4993 
4994     computer *used = m.computer_at( p );
4995 
4996     if( used == nullptr ) {
4997         if( m.has_flag( "CONSOLE", p ) ) { //Console without map data
4998             add_msg( m_bad, _( "The console doesn't display anything coherent." ) );
4999         } else {
5000             dbg( D_ERROR ) << "game:use_computer: Tried to use computer at (" <<
5001                            p.x << ", " << p.y << ", " << p.z << ") - none there";
5002             debugmsg( "Tried to use computer at (%d, %d, %d) - none there", p.x, p.y, p.z );
5003         }
5004         return;
5005     }
5006 
5007     computer_session( *used ).use();
5008 }
5009 
5010 template<typename T>
critter_at(const tripoint & p,bool allow_hallucination)5011 T *game::critter_at( const tripoint &p, bool allow_hallucination )
5012 {
5013     if( const shared_ptr_fast<monster> mon_ptr = critter_tracker->find( p ) ) {
5014         if( !allow_hallucination && mon_ptr->is_hallucination() ) {
5015             return nullptr;
5016         }
5017         // if we wanted to check for an NPC / player / avatar,
5018         // there is sometimes a monster AND an NPC/player there at the same time.
5019         // because the NPC/player etc may be riding that monster.
5020         // so only return the monster if we were actually looking for a monster.
5021         // otherwise, keep looking for the rider.
5022         // critter_at<creature> or critter_at() with no template will still default to returning monster first,
5023         // which is ok for the occasions where that happens.
5024         if( !mon_ptr->has_effect( effect_ridden ) || ( std::is_same<T, monster>::value ||
5025                 std::is_same<T, Creature>::value || std::is_same<T, const monster>::value ||
5026                 std::is_same<T, const Creature>::value ) ) {
5027             return dynamic_cast<T *>( mon_ptr.get() );
5028         }
5029     }
5030     if( !std::is_same<T, npc>::value && !std::is_same<T, const npc>::value ) {
5031         if( p == u.pos() ) {
5032             return dynamic_cast<T *>( &u );
5033         }
5034     }
5035     for( auto &cur_npc : active_npc ) {
5036         if( cur_npc->pos() == p && !cur_npc->is_dead() ) {
5037             return dynamic_cast<T *>( cur_npc.get() );
5038         }
5039     }
5040     return nullptr;
5041 }
5042 
5043 template<typename T>
critter_at(const tripoint & p,bool allow_hallucination) const5044 const T *game::critter_at( const tripoint &p, bool allow_hallucination ) const
5045 {
5046     return const_cast<game *>( this )->critter_at<T>( p, allow_hallucination );
5047 }
5048 
5049 template const monster *game::critter_at<monster>( const tripoint &, bool ) const;
5050 template const npc *game::critter_at<npc>( const tripoint &, bool ) const;
5051 template const player *game::critter_at<player>( const tripoint &, bool ) const;
5052 template const avatar *game::critter_at<avatar>( const tripoint &, bool ) const;
5053 template avatar *game::critter_at<avatar>( const tripoint &, bool );
5054 template const Character *game::critter_at<Character>( const tripoint &, bool ) const;
5055 template Character *game::critter_at<Character>( const tripoint &, bool );
5056 template const Creature *game::critter_at<Creature>( const tripoint &, bool ) const;
5057 
5058 template<typename T>
shared_from(const T & critter)5059 shared_ptr_fast<T> game::shared_from( const T &critter )
5060 {
5061     if( static_cast<const Creature *>( &critter ) == static_cast<const Creature *>( &u ) ) {
5062         // u is not stored in a shared_ptr, but it won't go out of scope anyway
5063         return std::dynamic_pointer_cast<T>( u_shared_ptr );
5064     }
5065     if( critter.is_monster() ) {
5066         if( const shared_ptr_fast<monster> mon_ptr = critter_tracker->find( critter.pos() ) ) {
5067             if( static_cast<const Creature *>( mon_ptr.get() ) == static_cast<const Creature *>( &critter ) ) {
5068                 return std::dynamic_pointer_cast<T>( mon_ptr );
5069             }
5070         }
5071     }
5072     if( critter.is_npc() ) {
5073         for( auto &cur_npc : active_npc ) {
5074             if( static_cast<const Creature *>( cur_npc.get() ) == static_cast<const Creature *>( &critter ) ) {
5075                 return std::dynamic_pointer_cast<T>( cur_npc );
5076             }
5077         }
5078     }
5079     return nullptr;
5080 }
5081 
5082 template shared_ptr_fast<Creature> game::shared_from<Creature>( const Creature & );
5083 template shared_ptr_fast<Character> game::shared_from<Character>( const Character & );
5084 template shared_ptr_fast<player> game::shared_from<player>( const player & );
5085 template shared_ptr_fast<avatar> game::shared_from<avatar>( const avatar & );
5086 template shared_ptr_fast<monster> game::shared_from<monster>( const monster & );
5087 template shared_ptr_fast<npc> game::shared_from<npc>( const npc & );
5088 
5089 template<typename T>
critter_by_id(const character_id & id)5090 T *game::critter_by_id( const character_id &id )
5091 {
5092     if( id == u.getID() ) {
5093         // player is always alive, therefore no is-dead check
5094         return dynamic_cast<T *>( &u );
5095     }
5096     return find_npc( id );
5097 }
5098 
5099 // monsters don't have ids
5100 template Character *game::critter_by_id<Character>( const character_id & );
5101 template player *game::critter_by_id<player>( const character_id & );
5102 template npc *game::critter_by_id<npc>( const character_id & );
5103 template Creature *game::critter_by_id<Creature>( const character_id & );
5104 
can_place_monster(const monster & mon,const tripoint & p)5105 static bool can_place_monster( const monster &mon, const tripoint &p )
5106 {
5107     if( const monster *const critter = g->critter_at<monster>( p ) ) {
5108         // Creature_tracker handles this. The hallucination monster will simply vanish
5109         if( !critter->is_hallucination() ) {
5110             return false;
5111         }
5112     }
5113     // Although monsters can sometimes exist on the same place as a Character (e.g. ridden horse),
5114     // it is usually wrong. So don't allow it.
5115     if( g->critter_at<Character>( p ) ) {
5116         return false;
5117     }
5118     return mon.will_move_to( p );
5119 }
5120 
choose_where_to_place_monster(const monster & mon,const tripoint_range<tripoint> & range)5121 static cata::optional<tripoint> choose_where_to_place_monster( const monster &mon,
5122         const tripoint_range<tripoint> &range )
5123 {
5124     return random_point( range, [&]( const tripoint & p ) {
5125         return can_place_monster( mon, p );
5126     } );
5127 }
5128 
place_critter_at(const mtype_id & id,const tripoint & p)5129 monster *game::place_critter_at( const mtype_id &id, const tripoint &p )
5130 {
5131     return place_critter_around( id, p, 0 );
5132 }
5133 
place_critter_at(const shared_ptr_fast<monster> & mon,const tripoint & p)5134 monster *game::place_critter_at( const shared_ptr_fast<monster> &mon, const tripoint &p )
5135 {
5136     return place_critter_around( mon, p, 0 );
5137 }
5138 
place_critter_around(const mtype_id & id,const tripoint & center,const int radius)5139 monster *game::place_critter_around( const mtype_id &id, const tripoint &center, const int radius )
5140 {
5141     // TODO: change this into an assert, it must never happen.
5142     if( id.is_null() ) {
5143         return nullptr;
5144     }
5145     shared_ptr_fast<monster> mon = make_shared_fast<monster>( id );
5146     mon->ammo = mon->type->starting_ammo;
5147     return place_critter_around( mon, center, radius );
5148 }
5149 
place_critter_around(const shared_ptr_fast<monster> & mon,const tripoint & center,const int radius,bool forced)5150 monster *game::place_critter_around( const shared_ptr_fast<monster> &mon,
5151                                      const tripoint &center,
5152                                      const int radius,
5153                                      bool forced )
5154 {
5155     cata::optional<tripoint> where;
5156     if( forced || can_place_monster( *mon, center ) ) {
5157         where = center;
5158     }
5159 
5160     // This loop ensures the monster is placed as close to the center as possible,
5161     // but all places that equally far from the center have the same probability.
5162     for( int r = 1; r <= radius && !where; ++r ) {
5163         where = choose_where_to_place_monster( *mon, m.points_in_radius( center, r ) );
5164     }
5165 
5166     if( !where ) {
5167         return nullptr;
5168     }
5169     mon->spawn( *where );
5170     return critter_tracker->add( mon ) ? mon.get() : nullptr;
5171 }
5172 
place_critter_within(const mtype_id & id,const tripoint_range<tripoint> & range)5173 monster *game::place_critter_within( const mtype_id &id, const tripoint_range<tripoint> &range )
5174 {
5175     // TODO: change this into an assert, it must never happen.
5176     if( id.is_null() ) {
5177         return nullptr;
5178     }
5179     return place_critter_within( make_shared_fast<monster>( id ), range );
5180 }
5181 
place_critter_within(const shared_ptr_fast<monster> & mon,const tripoint_range<tripoint> & range)5182 monster *game::place_critter_within( const shared_ptr_fast<monster> &mon,
5183                                      const tripoint_range<tripoint> &range )
5184 {
5185     const cata::optional<tripoint> where = choose_where_to_place_monster( *mon, range );
5186     if( !where ) {
5187         return nullptr;
5188     }
5189     mon->spawn( *where );
5190     return critter_tracker->add( mon ) ? mon.get() : nullptr;
5191 }
5192 
num_creatures() const5193 size_t game::num_creatures() const
5194 {
5195     // Plus one for the player.
5196     return critter_tracker->size() + active_npc.size() + 1;
5197 }
5198 
update_zombie_pos(const monster & critter,const tripoint & pos)5199 bool game::update_zombie_pos( const monster &critter, const tripoint &pos )
5200 {
5201     return critter_tracker->update_pos( critter, pos );
5202 }
5203 
remove_zombie(const monster & critter)5204 void game::remove_zombie( const monster &critter )
5205 {
5206     critter_tracker->remove( critter );
5207 }
5208 
clear_zombies()5209 void game::clear_zombies()
5210 {
5211     critter_tracker->clear();
5212 }
5213 
find_nearby_spawn_point(const Character & target,const mtype_id & mt,int min_radius,int max_radius,tripoint & point)5214 bool game::find_nearby_spawn_point( const Character &target, const mtype_id &mt, int min_radius,
5215                                     int max_radius, tripoint &point )
5216 {
5217     tripoint target_point;
5218     //find a legal outdoor place to spawn based on the specified radius,
5219     //we just try a bunch of random points and use the first one that works, it none do then no spawn
5220     for( int attempts = 0; attempts < 15; attempts++ ) {
5221         target_point = target.pos() + tripoint( rng( -max_radius, max_radius ),
5222                                                 rng( -max_radius, max_radius ), 0 );
5223         if( can_place_monster( monster( mt->id ), target_point ) &&
5224             get_map().is_outside( target_point ) &&
5225             rl_dist( target_point, get_player_character().pos() ) > min_radius ) {
5226             point = target_point;
5227             return true;
5228         }
5229     }
5230     return false;
5231 }
5232 
5233 /**
5234  * Attempts to spawn a hallucination at given location.
5235  * Returns false if the hallucination couldn't be spawned for whatever reason, such as
5236  * a monster already in the target square.
5237  * @return Whether or not a hallucination was successfully spawned.
5238  */
spawn_hallucination(const tripoint & p)5239 bool game::spawn_hallucination( const tripoint &p )
5240 {
5241     if( one_in( 100 ) ) {
5242         shared_ptr_fast<npc> tmp = make_shared_fast<npc>();
5243         tmp->normalize();
5244         tmp->randomize( NC_HALLU );
5245         tmp->spawn_at_precise( get_map().get_abs_sub().xy(), p );
5246         if( !critter_at( p, true ) ) {
5247             overmap_buffer.insert_npc( tmp );
5248             load_npcs();
5249             return true;
5250         } else {
5251             return false;
5252         }
5253     }
5254 
5255     return spawn_hallucination( p, MonsterGenerator::generator().get_valid_hallucination() );
5256 }
5257 /**
5258  * Attempts to spawn a hallucination at given location.
5259  * Returns false if the hallucination couldn't be spawned for whatever reason, such as
5260  * a monster already in the target square.
5261  * @return Whether or not a hallucination was successfully spawned.
5262  */
spawn_hallucination(const tripoint & p,const mtype_id & mt)5263 bool game::spawn_hallucination( const tripoint &p, const mtype_id &mt )
5264 {
5265     const shared_ptr_fast<monster> phantasm = make_shared_fast<monster>( mt );
5266     phantasm->hallucination = true;
5267     phantasm->spawn( p );
5268 
5269     //Don't attempt to place phantasms inside of other creatures
5270     if( !critter_at( phantasm->pos(), true ) ) {
5271         return critter_tracker->add( phantasm );
5272     } else {
5273         return false;
5274     }
5275 }
5276 
swap_critters(Creature & a,Creature & b)5277 bool game::swap_critters( Creature &a, Creature &b )
5278 {
5279     if( &a == &b ) {
5280         // No need to do anything, but print a debugmsg anyway
5281         debugmsg( "Tried to swap %s with itself", a.disp_name() );
5282         return true;
5283     }
5284     if( critter_at( a.pos() ) != &a ) {
5285         debugmsg( "Tried to swap when it would cause a collision between %s and %s.",
5286                   b.disp_name(), critter_at( a.pos() )->disp_name() );
5287         return false;
5288     }
5289     if( critter_at( b.pos() ) != &b ) {
5290         debugmsg( "Tried to swap when it would cause a collision between %s and %s.",
5291                   a.disp_name(), critter_at( b.pos() )->disp_name() );
5292         return false;
5293     }
5294     // Simplify by "sorting" the arguments
5295     // Only the first argument can be u
5296     // If swapping player/npc with a monster, monster is second
5297     bool a_first = a.is_player() ||
5298                    ( a.is_npc() && !b.is_player() );
5299     Creature &first  = a_first ? a : b;
5300     Creature &second = a_first ? b : a;
5301     // Possible options:
5302     // both first and second are monsters
5303     // second is a monster, first is a player or an npc
5304     // first is a player, second is an npc
5305     // both first and second are npcs
5306     if( first.is_monster() ) {
5307         monster *m1 = dynamic_cast< monster * >( &first );
5308         monster *m2 = dynamic_cast< monster * >( &second );
5309         if( m1 == nullptr || m2 == nullptr || m1 == m2 ) {
5310             debugmsg( "Couldn't swap two monsters" );
5311             return false;
5312         }
5313 
5314         critter_tracker->swap_positions( *m1, *m2 );
5315         return true;
5316     }
5317 
5318     player *u_or_npc = dynamic_cast< player * >( &first );
5319     player *other_npc = dynamic_cast< player * >( &second );
5320 
5321     if( u_or_npc->in_vehicle ) {
5322         m.unboard_vehicle( u_or_npc->pos() );
5323     }
5324 
5325     if( other_npc && other_npc->in_vehicle ) {
5326         m.unboard_vehicle( other_npc->pos() );
5327     }
5328 
5329     tripoint temp = second.pos();
5330     second.setpos( first.pos() );
5331 
5332     if( first.is_player() ) {
5333         walk_move( temp );
5334     } else {
5335         first.setpos( temp );
5336         if( m.veh_at( u_or_npc->pos() ).part_with_feature( VPFLAG_BOARDABLE, true ) ) {
5337             m.board_vehicle( u_or_npc->pos(), u_or_npc );
5338         }
5339     }
5340 
5341     if( other_npc && m.veh_at( other_npc->pos() ).part_with_feature( VPFLAG_BOARDABLE, true ) ) {
5342         m.board_vehicle( other_npc->pos(), other_npc );
5343     }
5344     return true;
5345 }
5346 
is_empty(const tripoint & p)5347 bool game::is_empty( const tripoint &p )
5348 {
5349     return ( m.passable( p ) || m.has_flag( "LIQUID", p ) ) &&
5350            critter_at( p ) == nullptr;
5351 }
5352 
is_in_sunlight(const tripoint & p)5353 bool game::is_in_sunlight( const tripoint &p )
5354 {
5355     return ( m.is_outside( p ) && light_level( p.z ) >= 40 && !is_night( calendar::turn ) &&
5356              get_weather().weather_id->sun_intensity >= sun_intensity_type::normal );
5357 }
5358 
is_sheltered(const tripoint & p)5359 bool game::is_sheltered( const tripoint &p )
5360 {
5361     const optional_vpart_position vp = m.veh_at( p );
5362 
5363     return ( !m.is_outside( p ) ||
5364              p.z < 0 ||
5365              ( vp && vp->is_inside() ) );
5366 }
5367 
revive_corpse(const tripoint & p,item & it)5368 bool game::revive_corpse( const tripoint &p, item &it )
5369 {
5370     if( !it.is_corpse() ) {
5371         debugmsg( "Tried to revive a non-corpse." );
5372         return false;
5373     }
5374     // If this is not here, the game may attempt to spawn a monster before the map exists,
5375     // leading to it querying for furniture, and crashing.
5376     if( g->new_game ) {
5377         return false;
5378     }
5379     shared_ptr_fast<monster> newmon_ptr = make_shared_fast<monster>
5380                                           ( it.get_mtype()->id );
5381     if( it.has_var( "zombie_form" ) ) { // if the monster can reanimate has a zombie
5382         newmon_ptr = make_shared_fast<monster>( mtype_id( it.get_var( "zombie_form" ) ) );
5383     }
5384     monster &critter = *newmon_ptr;
5385     critter.init_from_item( it );
5386     if( critter.get_hp() < 1 ) {
5387         // Failed reanimation due to corpse being too burned
5388         return false;
5389     }
5390     if( it.has_flag( flag_FIELD_DRESS ) || it.has_flag( flag_FIELD_DRESS_FAILED ) ||
5391         it.has_flag( flag_QUARTERED ) ) {
5392         // Failed reanimation due to corpse being butchered
5393         return false;
5394     }
5395 
5396     critter.no_extra_death_drops = true;
5397     critter.add_effect( effect_downed, 5_turns, true );
5398 
5399     if( it.get_var( "no_ammo" ) == "no_ammo" ) {
5400         for( auto &ammo : critter.ammo ) {
5401             ammo.second = 0;
5402         }
5403     }
5404 
5405     return place_critter_at( newmon_ptr, p );
5406 }
5407 
save_cyborg(item * cyborg,const tripoint & couch_pos,player & installer)5408 void game::save_cyborg( item *cyborg, const tripoint &couch_pos, player &installer )
5409 {
5410     int damage = cyborg->damage();
5411     int dmg_lvl = cyborg->damage_level();
5412     int difficulty = 12;
5413 
5414     if( damage != 0 ) {
5415 
5416         popup( _( "WARNING: Patient's body is damaged.  Difficulty of the procedure is increased by %s." ),
5417                dmg_lvl );
5418 
5419         // Damage of the cyborg increases difficulty
5420         difficulty += dmg_lvl;
5421     }
5422 
5423     int chance_of_success = bionic_success_chance( true, -1, difficulty, installer );
5424     int success = chance_of_success - rng( 1, 100 );
5425 
5426     if( !get_avatar().query_yn(
5427             _( "WARNING: %i percent chance of SEVERE damage to all body parts!  Continue anyway?" ),
5428             100 - static_cast<int>( chance_of_success ) ) ) {
5429         return;
5430     }
5431 
5432     if( success > 0 ) {
5433         add_msg( m_good, _( "Successfully removed Personality override." ) );
5434         add_msg( m_bad, _( "Autodoc immediately destroys the CBM upon removal." ) );
5435 
5436         m.i_rem( couch_pos, cyborg );
5437 
5438         const string_id<npc_template> npc_cyborg( "cyborg_rescued" );
5439         shared_ptr_fast<npc> tmp = make_shared_fast<npc>();
5440         tmp->normalize();
5441         tmp->load_npc_template( npc_cyborg );
5442         tmp->spawn_at_precise( get_map().get_abs_sub().xy(), couch_pos );
5443         overmap_buffer.insert_npc( tmp );
5444         tmp->hurtall( dmg_lvl * 10, nullptr );
5445         tmp->add_effect( effect_downed, rng( 1_turns, 4_turns ), false, 0, true );
5446         load_npcs();
5447 
5448     } else {
5449         const int failure_level = static_cast<int>( std::sqrt( std::abs( success ) * 4.0 * difficulty /
5450                                   installer.bionics_adjusted_skill( true, 12 ) ) );
5451         const int fail_type = std::min( 5, failure_level );
5452         switch( fail_type ) {
5453             case 1:
5454             case 2:
5455                 add_msg( m_info, _( "The removal fails." ) );
5456                 add_msg( m_bad, _( "The body is damaged." ) );
5457                 cyborg->set_damage( damage + 1000 );
5458                 break;
5459             case 3:
5460             case 4:
5461                 add_msg( m_info, _( "The removal fails badly." ) );
5462                 add_msg( m_bad, _( "The body is badly damaged!" ) );
5463                 cyborg->set_damage( damage + 2000 );
5464                 break;
5465             case 5:
5466                 add_msg( m_info, _( "The removal is a catastrophe." ) );
5467                 add_msg( m_bad, _( "The body is destroyed!" ) );
5468                 m.i_rem( couch_pos, cyborg );
5469                 break;
5470             default:
5471                 break;
5472         }
5473 
5474     }
5475 
5476 }
5477 
exam_vehicle(vehicle & veh,const point & c)5478 void game::exam_vehicle( vehicle &veh, const point &c )
5479 {
5480     if( veh.magic ) {
5481         add_msg( m_info, _( "This is your %s" ), veh.name );
5482         return;
5483     }
5484     player_activity act = veh_interact::run( veh, c );
5485     if( act ) {
5486         u.moves = 0;
5487         u.assign_activity( act );
5488     }
5489 }
5490 
forced_door_closing(const tripoint & p,const ter_id & door_type,int bash_dmg)5491 bool game::forced_door_closing( const tripoint &p, const ter_id &door_type, int bash_dmg )
5492 {
5493     // TODO: Z
5494     const int &x = p.x;
5495     const int &y = p.y;
5496     const std::string &door_name = door_type.obj().name();
5497     point kb( x, y ); // and when moving items out of the way
5498     const auto valid_location = [&]( const tripoint & p ) {
5499         return g->is_empty( p );
5500     };
5501     if( const cata::optional<tripoint> pos = random_point( m.points_in_radius( p, 2 ),
5502             valid_location ) ) {
5503         kb.x = -pos->x + x + x;
5504         kb.y = -pos->y + y + y;
5505     }
5506     const tripoint kbp( kb, p.z );
5507     if( kbp == p ) {
5508         // can't pushback any creatures anywhere, that means the door can't close.
5509         return false;
5510     }
5511     const bool can_see = u.sees( tripoint( x, y, p.z ) );
5512     player *npc_or_player = critter_at<player>( tripoint( x, y, p.z ), false );
5513     if( npc_or_player != nullptr ) {
5514         if( bash_dmg <= 0 ) {
5515             return false;
5516         }
5517         if( npc_or_player->is_npc() && can_see ) {
5518             add_msg( _( "The %1$s hits the %2$s." ), door_name, npc_or_player->name );
5519         } else if( npc_or_player->is_player() ) {
5520             add_msg( m_bad, _( "The %s hits you." ), door_name );
5521         }
5522         if( npc_or_player->activity ) {
5523             npc_or_player->cancel_activity();
5524         }
5525         // TODO: make the npc angry?
5526         npc_or_player->hitall( bash_dmg, 0, nullptr );
5527         knockback( kbp, p, std::max( 1, bash_dmg / 10 ), -1, 1 );
5528         // TODO: perhaps damage/destroy the gate
5529         // if the npc was really big?
5530     }
5531     if( monster *const mon_ptr = critter_at<monster>( p ) ) {
5532         monster &critter = *mon_ptr;
5533         if( bash_dmg <= 0 ) {
5534             return false;
5535         }
5536         if( can_see ) {
5537             add_msg( _( "The %1$s hits the %2$s." ), door_name, critter.name() );
5538         }
5539         if( critter.type->size <= creature_size::small ) {
5540             critter.die_in_explosion( nullptr );
5541         } else {
5542             critter.apply_damage( nullptr, bodypart_id( "torso" ), bash_dmg );
5543             critter.check_dead_state();
5544         }
5545         if( !critter.is_dead() && critter.type->size >= creature_size::huge ) {
5546             // big critters simply prevent the gate from closing
5547             // TODO: perhaps damage/destroy the gate
5548             // if the critter was really big?
5549             return false;
5550         }
5551         if( !critter.is_dead() ) {
5552             // Still alive? Move the critter away so the door can close
5553             knockback( kbp, p, std::max( 1, bash_dmg / 10 ), -1, 1 );
5554             if( critter_at( p ) ) {
5555                 return false;
5556             }
5557         }
5558     }
5559     if( const optional_vpart_position vp = m.veh_at( p ) ) {
5560         if( bash_dmg <= 0 ) {
5561             return false;
5562         }
5563         vp->vehicle().damage( vp->part_index(), bash_dmg );
5564         if( m.veh_at( p ) ) {
5565             // Check again in case all parts at the door tile
5566             // have been destroyed, if there is still a vehicle
5567             // there, the door can not be closed
5568             return false;
5569         }
5570     }
5571     if( bash_dmg < 0 && !m.i_at( point( x, y ) ).empty() ) {
5572         return false;
5573     }
5574     if( bash_dmg == 0 ) {
5575         for( item &elem : m.i_at( point( x, y ) ) ) {
5576             if( elem.made_of( phase_id::LIQUID ) ) {
5577                 // Liquids are OK, will be destroyed later
5578                 continue;
5579             } else if( elem.volume() < 250_ml ) {
5580                 // Dito for small items, will be moved away
5581                 continue;
5582             }
5583             // Everything else prevents the door from closing
5584             return false;
5585         }
5586     }
5587 
5588     m.ter_set( point( x, y ), door_type );
5589     if( m.has_flag( "NOITEM", point( x, y ) ) ) {
5590         map_stack items = m.i_at( point( x, y ) );
5591         for( map_stack::iterator it = items.begin(); it != items.end(); ) {
5592             if( it->made_of( phase_id::LIQUID ) ) {
5593                 it = items.erase( it );
5594                 continue;
5595             }
5596             if( it->made_of( material_id( "glass" ) ) && one_in( 2 ) ) {
5597                 if( can_see ) {
5598                     add_msg( m_warning, _( "A %s shatters!" ), it->tname() );
5599                 } else {
5600                     add_msg( m_warning, _( "Something shatters!" ) );
5601                 }
5602                 it = items.erase( it );
5603                 continue;
5604             }
5605             m.add_item_or_charges( kbp, *it );
5606             it = items.erase( it );
5607         }
5608     }
5609     return true;
5610 }
5611 
open_gate(const tripoint & p)5612 void game::open_gate( const tripoint &p )
5613 {
5614     gates::open_gate( p, u );
5615 }
5616 
moving_vehicle_dismount(const tripoint & dest_loc)5617 void game::moving_vehicle_dismount( const tripoint &dest_loc )
5618 {
5619     const optional_vpart_position vp = m.veh_at( u.pos() );
5620     if( !vp ) {
5621         debugmsg( "Tried to exit non-existent vehicle." );
5622         return;
5623     }
5624     vehicle *const veh = &vp->vehicle();
5625     if( u.pos() == dest_loc ) {
5626         debugmsg( "Need somewhere to dismount towards." );
5627         return;
5628     }
5629     tileray ray( dest_loc.xy() + point( -u.posx(), -u.posy() ) );
5630     // TODO:: make dir() const correct!
5631     const units::angle d = ray.dir();
5632     add_msg( _( "You dive from the %s." ), veh->name );
5633     m.unboard_vehicle( u.pos() );
5634     u.moves -= 200;
5635     // Dive three tiles in the direction of tox and toy
5636     fling_creature( &u, d, 30, true );
5637     // Hit the ground according to vehicle speed
5638     if( !m.has_flag( "SWIMMABLE", u.pos() ) ) {
5639         if( veh->velocity > 0 ) {
5640             fling_creature( &u, veh->face.dir(), veh->velocity / static_cast<float>( 100 ) );
5641         } else {
5642             fling_creature( &u, veh->face.dir() + 180_degrees,
5643                             -( veh->velocity ) / static_cast<float>( 100 ) );
5644         }
5645     }
5646 }
5647 
control_vehicle()5648 void game::control_vehicle()
5649 {
5650     static const itype_id fuel_type_animal( "animal" );
5651     int veh_part = -1;
5652     vehicle *veh = remoteveh();
5653     if( veh == nullptr ) {
5654         if( const optional_vpart_position vp = m.veh_at( u.pos() ) ) {
5655             veh = &vp->vehicle();
5656             veh_part = vp->part_index();
5657         }
5658     }
5659     if( veh != nullptr && veh->player_in_control( u ) &&
5660         veh->avail_part_with_feature( veh_part, "CONTROLS" ) >= 0 ) {
5661         veh->use_controls( u.pos() );
5662     } else if( veh && veh->player_in_control( u ) &&
5663                veh->avail_part_with_feature( veh_part, "CONTROL_ANIMAL" ) >= 0 ) {
5664         u.controlling_vehicle = false;
5665         add_msg( m_info, _( "You let go of the reins." ) );
5666     } else if( veh && ( veh->avail_part_with_feature( veh_part, "CONTROLS" ) >= 0 ||
5667                         ( veh->avail_part_with_feature( veh_part, "CONTROL_ANIMAL" ) >= 0 &&
5668                           veh->has_engine_type( fuel_type_animal, false ) && veh->has_harnessed_animal() ) ) &&
5669                u.in_vehicle ) {
5670         if( !veh->interact_vehicle_locked() ) {
5671             veh->handle_potential_theft( dynamic_cast<player &>( u ) );
5672             return;
5673         }
5674         if( veh->engine_on ) {
5675             if( !veh->handle_potential_theft( dynamic_cast<player &>( u ) ) ) {
5676                 return;
5677             }
5678             u.controlling_vehicle = true;
5679             add_msg( _( "You take control of the %s." ), veh->name );
5680         } else {
5681             if( !veh->handle_potential_theft( dynamic_cast<player &>( u ) ) ) {
5682                 return;
5683             }
5684             veh->start_engines( true );
5685         }
5686     } else {    // Start looking for nearby vehicle controls.
5687         int num_valid_controls = 0;
5688         cata::optional<tripoint> vehicle_position;
5689         cata::optional<vpart_reference> vehicle_controls;
5690         for( const tripoint &elem : m.points_in_radius( get_player_character().pos(), 1 ) ) {
5691             if( const optional_vpart_position vp = m.veh_at( elem ) ) {
5692                 const cata::optional<vpart_reference> controls = vp.value().part_with_feature( "CONTROLS", true );
5693                 if( controls ) {
5694                     num_valid_controls++;
5695                     vehicle_position = elem;
5696                     vehicle_controls = controls;
5697                 }
5698             }
5699         }
5700         if( num_valid_controls < 1 ) {
5701             add_msg( _( "No vehicle controls found." ) );
5702             return;
5703         } else if( num_valid_controls > 1 ) {
5704             vehicle_position = choose_adjacent( _( "Control vehicle where?" ) );
5705             if( !vehicle_position ) {
5706                 return;
5707             }
5708             const optional_vpart_position vp = m.veh_at( *vehicle_position );
5709             if( vp ) {
5710                 vehicle_controls = vp.value().part_with_feature( "CONTROLS", true );
5711                 if( !vehicle_controls ) {
5712                     add_msg( _( "The vehicle doesn't have controls there." ) );
5713                     return;
5714                 }
5715             } else {
5716                 add_msg( _( "No vehicle there." ) );
5717                 return;
5718             }
5719         }
5720         // If we hit neither of those, there's only one set of vehicle controls, which should already have been found.
5721         if( vehicle_controls ) {
5722             veh = &vehicle_controls->vehicle();
5723             if( !veh->handle_potential_theft( dynamic_cast<player &>( u ) ) ) {
5724                 return;
5725             }
5726             veh->use_controls( *vehicle_position );
5727             //May be folded up (destroyed), so need to re-get it
5728             veh = g->remoteveh();
5729         }
5730     }
5731     if( veh ) {
5732         // If we reached here, we gained control of a vehicle.
5733         // Clear the map memory for the area covered by the vehicle to eliminate ghost vehicles.
5734         // FIXME: change map memory to memorize all memorizable objects and only erase vehicle part memory.
5735         for( const tripoint &target : veh->get_points() ) {
5736             u.clear_memorized_tile( m.getabs( target ) );
5737             m.set_memory_seen_cache_dirty( target );
5738         }
5739         veh->is_following = false;
5740         veh->is_patrolling = false;
5741         veh->autopilot_on = false;
5742         veh->is_autodriving = false;
5743     }
5744 }
5745 
npc_menu(npc & who)5746 bool game::npc_menu( npc &who )
5747 {
5748     enum choices : int {
5749         talk = 0,
5750         swap_pos,
5751         push,
5752         examine_wounds,
5753         use_item,
5754         sort_armor,
5755         attack,
5756         disarm,
5757         steal
5758     };
5759 
5760     const bool obeys = debug_mode || ( who.is_player_ally() && !who.in_sleep_state() );
5761 
5762     uilist amenu;
5763 
5764     amenu.text = string_format( _( "What to do with %s?" ), who.disp_name() );
5765     amenu.addentry( talk, true, 't', _( "Talk" ) );
5766     amenu.addentry( swap_pos, obeys && !who.is_mounted() &&
5767                     !u.is_mounted(), 's', _( "Swap positions" ) );
5768     amenu.addentry( push, obeys && !who.is_mounted(), 'p', _( "Push away" ) );
5769     amenu.addentry( examine_wounds, true, 'w', _( "Examine wounds" ) );
5770     amenu.addentry( use_item, true, 'i', _( "Use item on" ) );
5771     amenu.addentry( sort_armor, true, 'r', _( "Sort armor" ) );
5772     amenu.addentry( attack, true, 'a', _( "Attack" ) );
5773     if( !who.is_player_ally() ) {
5774         amenu.addentry( disarm, who.is_armed(), 'd', _( "Disarm" ) );
5775         amenu.addentry( steal, !who.is_enemy(), 'S', _( "Steal" ) );
5776     }
5777 
5778     amenu.query();
5779 
5780     const int choice = amenu.ret;
5781     if( choice == talk ) {
5782         u.talk_to( get_talker_for( who ) );
5783     } else if( choice == swap_pos ) {
5784         if( !prompt_dangerous_tile( who.pos() ) ) {
5785             return true;
5786         }
5787         // TODO: Make NPCs protest when displaced onto dangerous crap
5788         add_msg( _( "You swap places with %s." ), who.name );
5789         swap_critters( u, who );
5790         // TODO: Make that depend on stuff
5791         u.mod_moves( -200 );
5792     } else if( choice == push ) {
5793         // TODO: Make NPCs protest when displaced onto dangerous crap
5794         tripoint oldpos = who.pos();
5795         who.move_away_from( u.pos(), true );
5796         u.mod_moves( -20 );
5797         if( oldpos != who.pos() ) {
5798             add_msg( _( "%s moves out of the way." ), who.name );
5799         } else {
5800             add_msg( m_warning, _( "%s has nowhere to go!" ), who.name );
5801         }
5802     } else if( choice == examine_wounds ) {
5803         ///\EFFECT_PER slightly increases precision when examining NPCs' wounds
5804 
5805         ///\EFFECT_FIRSTAID increases precision when examining NPCs' wounds
5806         const bool precise = u.get_skill_level( skill_firstaid ) * 4 + u.per_cur >= 20;
5807         who.body_window( _( "Limbs of: " ) + who.disp_name(), true, precise, 0, 0, 0, 0.0f, 0.0f, 0.0f,
5808                          0.0f, 0.0f );
5809     } else if( choice == use_item ) {
5810         static const std::string heal_string( "heal" );
5811         const auto will_accept = [&who]( const item & it ) {
5812             if( it.has_flag( json_flag_SPLINT ) && who.can_wear( it ).success() ) {
5813                 return true;
5814             }
5815             const use_function *use_fun = it.get_use( heal_string );
5816             if( use_fun == nullptr ) {
5817                 return false;
5818             }
5819 
5820             const auto *actor = dynamic_cast<const heal_actor *>( use_fun->get_actor_ptr() );
5821 
5822             return actor != nullptr &&
5823                    actor->limb_power >= 0 &&
5824                    actor->head_power >= 0 &&
5825                    actor->torso_power >= 0;
5826         };
5827         item_location loc = game_menus::inv::titled_filter_menu( will_accept, u, _( "Use which item?" ) );
5828 
5829         if( !loc ) {
5830             add_msg( _( "Never mind" ) );
5831             return false;
5832         }
5833         item &used = *loc;
5834         if( used.has_flag( json_flag_SPLINT ) ) {
5835             std::string reason = _( "Nope." );
5836             who.wear_if_wanted( used, reason );
5837         } else {
5838             bool did_use = u.invoke_item( &used, heal_string, who.pos() );
5839             if( did_use ) {
5840                 // Note: exiting a body part selection menu counts as use here
5841                 u.mod_moves( -300 );
5842             }
5843         }
5844     } else if( choice == sort_armor ) {
5845         who.sort_armor();
5846         u.mod_moves( -100 );
5847     } else if( choice == attack ) {
5848         if( who.is_enemy() || query_yn( _( "You may be attacked!  Proceed?" ) ) ) {
5849             u.melee_attack( who, true );
5850             who.on_attacked( u );
5851         }
5852     } else if( choice == disarm ) {
5853         if( who.is_enemy() || query_yn( _( "You may be attacked!  Proceed?" ) ) ) {
5854             u.disarm( who );
5855         }
5856     } else if( choice == steal && query_yn( _( "You may be attacked!  Proceed?" ) ) ) {
5857         u.steal( who );
5858     }
5859 
5860     return true;
5861 }
5862 
examine()5863 void game::examine()
5864 {
5865     // if we are driving a vehicle, examine the
5866     // current tile without asking.
5867     const optional_vpart_position vp = m.veh_at( u.pos() );
5868     if( vp && vp->vehicle().player_in_control( u ) ) {
5869         examine( u.pos() );
5870         return;
5871     }
5872 
5873     const cata::optional<tripoint> examp_ = choose_adjacent_highlight( _( "Examine where?" ),
5874                                             _( "There is nothing that can be examined nearby." ),
5875                                             ACTION_EXAMINE, false );
5876     if( !examp_ ) {
5877         return;
5878     }
5879     u.manual_examine = true;
5880     examine( *examp_ );
5881     u.manual_examine = false;
5882 }
5883 
get_fire_fuel_string(const tripoint & examp)5884 static std::string get_fire_fuel_string( const tripoint &examp )
5885 {
5886     map &here = get_map();
5887     if( here.has_flag( TFLAG_FIRE_CONTAINER, examp ) ) {
5888         field_entry *fire = here.get_field( examp, fd_fire );
5889         if( fire ) {
5890             std::string ss;
5891             ss += _( "There is a fire here." );
5892             ss += " ";
5893             if( fire->get_field_intensity() > 1 ) {
5894                 ss += _( "It's too big and unpredictable to evaluate how long it will last." );
5895                 return ss;
5896             }
5897             time_duration fire_age = fire->get_field_age();
5898             // half-life inclusion
5899             int mod = 5 - get_player_character().get_skill_level( skill_survival );
5900             mod = std::max( mod, 0 );
5901             if( fire_age >= 0_turns ) {
5902                 if( mod >= 4 ) { // = survival level 0-1
5903                     ss += _( "It's going to go out soon without extra fuel." );
5904                     return ss;
5905                 } else {
5906                     fire_age = 30_minutes - fire_age;
5907                     if( to_string_approx( fire_age - fire_age * mod / 5 ) == to_string_approx(
5908                             fire_age + fire_age * mod / 5 ) ) {
5909                         ss += string_format(
5910                                   _( "Without extra fuel it might burn yet for maybe %s, but might also go out sooner." ),
5911                                   to_string_approx( fire_age - fire_age * mod / 5 ) );
5912                     } else {
5913                         ss += string_format(
5914                                   _( "Without extra fuel it might burn yet for between %s to %s, but might also go out sooner." ),
5915                                   to_string_approx( fire_age - fire_age * mod / 5 ),
5916                                   to_string_approx( fire_age + fire_age * mod / 5 ) );
5917                     }
5918                     return ss;
5919                 }
5920             } else {
5921                 fire_age = fire_age * -1 + 30_minutes;
5922                 if( mod >= 4 ) { // = survival level 0-1
5923                     if( fire_age <= 1_hours ) {
5924                         ss += _( "It's quite decent and looks like it'll burn for a bit without extra fuel." );
5925                         return ss;
5926                     } else if( fire_age <= 3_hours ) {
5927                         ss += _( "It looks solid, and will burn for a few hours without extra fuel." );
5928                         return ss;
5929                     } else {
5930                         ss += _( "It's very well supplied and even without extra fuel might burn for at least a part of a day." );
5931                         return ss;
5932                     }
5933                 } else {
5934                     if( to_string_approx( fire_age - fire_age * mod / 5 ) == to_string_approx(
5935                             fire_age + fire_age * mod / 5 ) ) {
5936                         ss += string_format( _( "Without extra fuel it will burn for about %s." ),
5937                                              to_string_approx( fire_age - fire_age * mod / 5 ) );
5938                     } else {
5939                         ss += string_format( _( "Without extra fuel it will burn for between %s to %s." ),
5940                                              to_string_approx( fire_age - fire_age * mod / 5 ),
5941                                              to_string_approx( fire_age + fire_age * mod / 5 ) );
5942                     }
5943                     return ss;
5944                 }
5945             }
5946         }
5947     }
5948     return {};
5949 }
5950 
examine(const tripoint & examp)5951 void game::examine( const tripoint &examp )
5952 {
5953     if( disable_robot( examp ) ) {
5954         return;
5955     }
5956 
5957     Creature *c = critter_at( examp );
5958     if( c != nullptr ) {
5959         monster *mon = dynamic_cast<monster *>( c );
5960         if( mon != nullptr ) {
5961             add_msg( _( "There is a %s." ), mon->get_name() );
5962             if( mon->has_effect( effect_pet ) && !u.is_mounted() ) {
5963                 if( monexamine::pet_menu( *mon ) ) {
5964                     return;
5965                 }
5966             } else if( mon->has_flag( MF_RIDEABLE_MECH ) && !mon->has_effect( effect_pet ) ) {
5967                 if( monexamine::mech_hack( *mon ) ) {
5968                     return;
5969                 }
5970             } else if( mon->has_flag( MF_PAY_BOT ) ) {
5971                 if( monexamine::pay_bot( *mon ) ) {
5972                     return;
5973                 }
5974             } else if( mon->attitude_to( u ) == Creature::Attitude::FRIENDLY && !u.is_mounted() ) {
5975                 if( monexamine::mfriend_menu( *mon ) ) {
5976                     return;
5977                 }
5978             }
5979         } else if( u.is_mounted() ) {
5980             add_msg( m_warning, _( "You cannot do that while mounted." ) );
5981         }
5982         npc *np = dynamic_cast<npc *>( c );
5983         if( np != nullptr && !u.is_mounted() ) {
5984             if( npc_menu( *np ) ) {
5985                 return;
5986             }
5987         } else if( np != nullptr && u.is_mounted() ) {
5988             add_msg( m_warning, _( "You cannot do that while mounted." ) );
5989         }
5990     }
5991 
5992     const optional_vpart_position vp = m.veh_at( examp );
5993     if( vp ) {
5994         if( !u.is_mounted() || u.mounted_creature->has_flag( MF_RIDEABLE_MECH ) ) {
5995             vp->vehicle().interact_with( *vp );
5996             return;
5997         } else {
5998             add_msg( m_warning, _( "You cannot interact with a vehicle while mounted." ) );
5999         }
6000     }
6001 
6002     if( m.has_flag( "CONSOLE", examp ) && !u.is_mounted() ) {
6003         use_computer( examp );
6004         return;
6005     } else if( m.has_flag( "CONSOLE", examp ) && u.is_mounted() ) {
6006         add_msg( m_warning, _( "You cannot use a console while mounted." ) );
6007     }
6008     const furn_t &xfurn_t = m.furn( examp ).obj();
6009     const ter_t &xter_t = m.ter( examp ).obj();
6010 
6011     const tripoint player_pos = u.pos();
6012 
6013     if( m.has_furn( examp ) && !u.is_mounted() ) {
6014         xfurn_t.examine( u, examp );
6015     } else if( m.has_furn( examp ) && u.is_mounted() ) {
6016         add_msg( m_warning, _( "You cannot do that while mounted." ) );
6017     } else {
6018         if( !u.is_mounted() ) {
6019             xter_t.examine( u, examp );
6020         } else if( u.is_mounted() && !xter_t.can_examine() ) {
6021             xter_t.examine( u, examp );
6022         } else {
6023             add_msg( m_warning, _( "You cannot do that while mounted." ) );
6024         }
6025     }
6026 
6027     // Did the player get moved? Bail out if so; our examp probably
6028     // isn't valid anymore.
6029     if( player_pos != u.pos() ) {
6030         return;
6031     }
6032 
6033     bool none = true;
6034     if( xter_t.can_examine() || xfurn_t.can_examine() ) {
6035         none = false;
6036     }
6037 
6038     // trap::iexamine will handle the invisible traps.
6039     m.tr_at( examp ).examine( examp );
6040 
6041     // In case of teleport trap or somesuch
6042     if( player_pos != u.pos() ) {
6043         return;
6044     }
6045 
6046     // Feedback for fire lasting time, this can be judged while mounted
6047     const std::string fire_fuel = get_fire_fuel_string( examp );
6048     if( !fire_fuel.empty() ) {
6049         add_msg( fire_fuel );
6050     }
6051 
6052     if( m.has_flag( "SEALED", examp ) ) {
6053         if( none ) {
6054             if( m.has_flag( "UNSTABLE", examp ) ) {
6055                 add_msg( _( "The %s is too unstable to remove anything." ), m.name( examp ) );
6056             } else {
6057                 add_msg( _( "The %s is firmly sealed." ), m.name( examp ) );
6058             }
6059         }
6060     } else {
6061         //examp has no traps, is a container and doesn't have a special examination function
6062         if( m.tr_at( examp ).is_null() && m.i_at( examp ).empty() &&
6063             m.has_flag( "CONTAINER", examp ) && none ) {
6064             add_msg( _( "It is empty." ) );
6065         } else if( ( m.has_flag( TFLAG_FIRE_CONTAINER, examp ) &&
6066                      xfurn_t.has_examine( iexamine::fireplace ) ) ||
6067                    xfurn_t.has_examine( iexamine::workbench ) ) {
6068             return;
6069         } else {
6070             sounds::process_sound_markers( &u );
6071             if( !u.is_mounted() && !m.has_flag( "NO_PICKUP_ON_EXAMINE", examp ) ) {
6072                 Pickup::pick_up( examp, 0 );
6073             }
6074         }
6075     }
6076 }
6077 
pickup()6078 void game::pickup()
6079 {
6080     const cata::optional<tripoint> examp_ = choose_adjacent_highlight( _( "Pickup where?" ),
6081                                             _( "There is nothing to pick up nearby." ),
6082                                             ACTION_PICKUP, false );
6083     if( !examp_ ) {
6084         return;
6085     }
6086     pickup( *examp_ );
6087 }
6088 
pickup(const tripoint & p)6089 void game::pickup( const tripoint &p )
6090 {
6091     // Highlight target
6092     shared_ptr_fast<game::draw_callback_t> hilite_cb = make_shared_fast<game::draw_callback_t>( [&]() {
6093         m.drawsq( w_terrain, u, p, true, true, u.pos() + u.view_offset );
6094     } );
6095     add_draw_callback( hilite_cb );
6096 
6097     Pickup::pick_up( p, 0 );
6098 }
6099 
pickup_feet()6100 void game::pickup_feet()
6101 {
6102     Pickup::pick_up( u.pos(), 1 );
6103 }
6104 
6105 //Shift player by one tile, look_around(), then restore previous position.
6106 //represents carefully peeking around a corner, hence the large move cost.
peek()6107 void game::peek()
6108 {
6109     const cata::optional<tripoint> p = choose_direction( _( "Peek where?" ), true );
6110     if( !p ) {
6111         return;
6112     }
6113 
6114     if( p->z != 0 ) {
6115         const tripoint old_pos = u.pos();
6116         vertical_move( p->z, false, true );
6117 
6118         if( old_pos != u.pos() ) {
6119             vertical_move( p->z * -1, false, true );
6120         } else {
6121             return;
6122         }
6123     }
6124 
6125     if( m.impassable( u.pos() + *p ) ) {
6126         return;
6127     }
6128 
6129     peek( u.pos() + *p );
6130 }
6131 
peek(const tripoint & p)6132 void game::peek( const tripoint &p )
6133 {
6134     u.moves -= 200;
6135     tripoint prev = u.pos();
6136     u.setpos( p );
6137     tripoint center = p;
6138     const look_around_result result = look_around( /*show_window=*/true, center, center, false, false,
6139                                       true );
6140     u.setpos( prev );
6141 
6142     if( result.peek_action && *result.peek_action == PA_BLIND_THROW ) {
6143         item_location loc;
6144         avatar_action::plthrow( u, loc, p );
6145     }
6146     m.invalidate_map_cache( p.z );
6147 }
6148 ////////////////////////////////////////////////////////////////////////////////////////////
look_debug()6149 cata::optional<tripoint> game::look_debug()
6150 {
6151     editmap edit;
6152     return edit.edit();
6153 }
6154 ////////////////////////////////////////////////////////////////////////////////////////////
6155 
draw_look_around_cursor(const tripoint & lp,const visibility_variables & cache)6156 void game::draw_look_around_cursor( const tripoint &lp, const visibility_variables &cache )
6157 {
6158     if( !liveview.is_enabled() ) {
6159 #if defined( TILES )
6160         if( is_draw_tiles_mode() ) {
6161             draw_cursor( lp );
6162             return;
6163         }
6164 #endif
6165         const tripoint view_center = u.pos() + u.view_offset;
6166         visibility_type visibility = visibility_type::HIDDEN;
6167         const bool inbounds = m.inbounds( lp );
6168         if( inbounds ) {
6169             visibility = m.get_visibility( m.apparent_light_at( lp, cache ), cache );
6170         }
6171         if( visibility == visibility_type::CLEAR ) {
6172             const Creature *const creature = critter_at( lp, true );
6173             if( creature != nullptr && u.sees( *creature ) ) {
6174                 creature->draw( w_terrain, view_center, true );
6175             } else {
6176                 m.drawsq( w_terrain, u, lp, true, true, view_center );
6177             }
6178         } else {
6179             std::string visibility_indicator;
6180             nc_color visibility_indicator_color = c_white;
6181             switch( visibility ) {
6182                 case visibility_type::CLEAR:
6183                     // Already handled by the outer if statement
6184                     break;
6185                 case visibility_type::BOOMER:
6186                 case visibility_type::BOOMER_DARK:
6187                     visibility_indicator = '#';
6188                     visibility_indicator_color = c_pink;
6189                     break;
6190                 case visibility_type::DARK:
6191                     visibility_indicator = '#';
6192                     visibility_indicator_color = c_dark_gray;
6193                     break;
6194                 case visibility_type::LIT:
6195                     visibility_indicator = '#';
6196                     visibility_indicator_color = c_light_gray;
6197                     break;
6198                 case visibility_type::HIDDEN:
6199                     visibility_indicator = 'x';
6200                     visibility_indicator_color = c_white;
6201                     break;
6202             }
6203 
6204             const tripoint screen_pos = point( POSX, POSY ) + lp - view_center;
6205             mvwputch( w_terrain, screen_pos.xy(), visibility_indicator_color, visibility_indicator );
6206         }
6207     }
6208 }
6209 
print_all_tile_info(const tripoint & lp,const catacurses::window & w_look,const std::string & area_name,int column,int & line,const int last_line,const visibility_variables & cache)6210 void game::print_all_tile_info( const tripoint &lp, const catacurses::window &w_look,
6211                                 const std::string &area_name, int column,
6212                                 int &line,
6213                                 const int last_line,
6214                                 const visibility_variables &cache )
6215 {
6216     visibility_type visibility = visibility_type::HIDDEN;
6217     const bool inbounds = m.inbounds( lp );
6218     if( inbounds ) {
6219         visibility = m.get_visibility( m.apparent_light_at( lp, cache ), cache );
6220     }
6221     const Creature *creature = critter_at( lp, true );
6222     switch( visibility ) {
6223         case visibility_type::CLEAR: {
6224             const optional_vpart_position vp = m.veh_at( lp );
6225             print_terrain_info( lp, w_look, area_name, column, line );
6226             print_fields_info( lp, w_look, column, line );
6227             print_trap_info( lp, w_look, column, line );
6228             print_creature_info( creature, w_look, column, line, last_line );
6229             print_vehicle_info( veh_pointer_or_null( vp ), vp ? vp->part_index() : -1, w_look, column, line,
6230                                 last_line );
6231             print_items_info( lp, w_look, column, line, last_line );
6232             print_graffiti_info( lp, w_look, column, line, last_line );
6233         }
6234         break;
6235         case visibility_type::BOOMER:
6236         case visibility_type::BOOMER_DARK:
6237         case visibility_type::DARK:
6238         case visibility_type::LIT:
6239         case visibility_type::HIDDEN:
6240             print_visibility_info( w_look, column, line, visibility );
6241 
6242             if( creature != nullptr ) {
6243                 std::vector<std::string> buf;
6244                 if( u.sees_with_infrared( *creature ) ) {
6245                     creature->describe_infrared( buf );
6246                 } else if( u.sees_with_specials( *creature ) ) {
6247                     creature->describe_specials( buf );
6248                 }
6249                 for( const std::string &s : buf ) {
6250                     mvwprintw( w_look, point( 1, ++line ), s );
6251                 }
6252             }
6253             break;
6254     }
6255     if( !inbounds ) {
6256         return;
6257     }
6258     const int max_width = getmaxx( w_look ) - column - 1;
6259 
6260     auto this_sound = sounds::sound_at( lp );
6261     if( !this_sound.empty() ) {
6262         const int lines = fold_and_print( w_look, point( 1, ++line ), max_width, c_light_gray,
6263                                           _( "From here you heard %s" ),
6264                                           this_sound );
6265         line += lines - 1;
6266     } else {
6267         // Check other z-levels
6268         tripoint tmp = lp;
6269         for( tmp.z = -OVERMAP_DEPTH; tmp.z <= OVERMAP_HEIGHT; tmp.z++ ) {
6270             if( tmp.z == lp.z ) {
6271                 continue;
6272             }
6273 
6274             auto zlev_sound = sounds::sound_at( tmp );
6275             if( !zlev_sound.empty() ) {
6276                 const int lines = fold_and_print( w_look, point( 1, ++line ), max_width, c_light_gray,
6277                                                   tmp.z > lp.z ?
6278                                                   _( "From above you heard %s" ) : _( "From below you heard %s" ), zlev_sound );
6279                 line += lines - 1;
6280             }
6281         }
6282     }
6283 }
6284 
print_visibility_info(const catacurses::window & w_look,int column,int & line,visibility_type visibility)6285 void game::print_visibility_info( const catacurses::window &w_look, int column, int &line,
6286                                   visibility_type visibility )
6287 {
6288     const char *visibility_message = nullptr;
6289     switch( visibility ) {
6290         case visibility_type::CLEAR:
6291             visibility_message = _( "Clearly visible." );
6292             break;
6293         case visibility_type::BOOMER:
6294             visibility_message = _( "A bright pink blur." );
6295             break;
6296         case visibility_type::BOOMER_DARK:
6297             visibility_message = _( "A pink blur." );
6298             break;
6299         case visibility_type::DARK:
6300             visibility_message = _( "Darkness." );
6301             break;
6302         case visibility_type::LIT:
6303             visibility_message = _( "Bright light." );
6304             break;
6305         case visibility_type::HIDDEN:
6306             visibility_message = _( "Unseen." );
6307             break;
6308     }
6309 
6310     mvwprintz( w_look, point( line, column ), c_light_gray, visibility_message );
6311     line += 2;
6312 }
6313 
print_terrain_info(const tripoint & lp,const catacurses::window & w_look,const std::string & area_name,int column,int & line)6314 void game::print_terrain_info( const tripoint &lp, const catacurses::window &w_look,
6315                                const std::string &area_name, int column, int &line )
6316 {
6317     const int max_width = getmaxx( w_look ) - column - 1;
6318 
6319     // Print OMT type and terrain type on first line, or first two lines if can't fit in one line.
6320     const std::string tile = m.tername( lp );
6321     if( utf8_width( tile ) + utf8_width( area_name ) + 1 > max_width ) {
6322         trim_and_print( w_look, point( column, line++ ), max_width, c_white, area_name );
6323         trim_and_print( w_look, point( column, line++ ), max_width, c_light_gray, tile );
6324     } else {
6325         mvwprintz( w_look, point( column, line ), c_white, area_name );
6326         mvwprintz( w_look, point( column + utf8_width( area_name ) + 1, line++ ), c_light_gray, tile );
6327     }
6328 
6329     // Furniture on second line if any.
6330     if( m.has_furn( lp ) ) {
6331         mvwprintz( w_look, point( column, ++line ), c_light_blue, m.furnname( lp ) );
6332     }
6333 
6334     // Cover percentage from terrain and furniture next.
6335     fold_and_print( w_look, point( column, ++line ), max_width, c_light_gray, _( "Cover: %d%%" ),
6336                     m.coverage( lp ) );
6337     // Terrain and furniture flags next. These can be several lines for some combinations of
6338     // furnitures and terrains.
6339     std::vector<std::string> lines = foldstring( m.features( lp ), max_width );
6340     int numlines = lines.size();
6341     for( int i = 0; i < numlines; i++ ) {
6342         mvwprintz( w_look, point( column, ++line ), c_light_gray, lines[i] );
6343     }
6344 
6345     // Move cost from terrain and furniture and vehicle parts.
6346     // Vehicle part information is printed in a different function.
6347     if( m.impassable( lp ) ) {
6348         mvwprintz( w_look, point( column, ++line ), c_light_red, _( "Impassable" ) );
6349     } else {
6350         mvwprintz( w_look, point( column, ++line ), c_light_gray, _( "Move cost: %d" ),
6351                    m.move_cost( lp ) * 50 );
6352     }
6353 
6354     // Next print the string on any SIGN flagged furniture if any.
6355     std::string signage = m.get_signage( lp );
6356     if( !signage.empty() ) {
6357         std::string sign_string = u.has_trait( trait_ILLITERATE ) ? "???" : signage;
6358         const int lines = fold_and_print( w_look, point( column, ++line ), max_width, c_light_gray,
6359                                           _( "Sign: %s" ), sign_string );
6360         line += lines - 1;
6361     }
6362 
6363     // Print light level on the selected tile.
6364     std::pair<std::string, nc_color> ll = get_light_level( std::max( 1.0,
6365                                           LIGHT_AMBIENT_LIT - m.ambient_light_at( lp ) + 1.0 ) );
6366     mvwprintz( w_look, point( column, ++line ), c_light_gray, _( "Lighting: " ) );
6367     mvwprintz( w_look, point( column + utf8_width( _( "Lighting: " ) ), line ), ll.second, ll.first );
6368 
6369     // Print the terrain and any furntiure on the tile below and whether it is walkable.
6370     if( m.has_zlevels() && lp.z > -OVERMAP_DEPTH && !m.has_floor( lp ) ) {
6371         tripoint below( lp.xy(), lp.z - 1 );
6372         std::string tile_below = m.tername( below );
6373         if( m.has_furn( below ) ) {
6374             tile_below += ", " + m.furnname( below );
6375         }
6376 
6377         if( !m.has_floor_or_support( lp ) ) {
6378             fold_and_print( w_look, point( column, ++line ), max_width, c_dark_gray,
6379                             _( "Below: %s; No support" ), tile_below );
6380         } else {
6381             fold_and_print( w_look, point( column, ++line ), max_width, c_dark_gray, _( "Below: %s; Walkable" ),
6382                             tile_below );
6383         }
6384     }
6385 
6386     ++line;
6387 }
6388 
print_fields_info(const tripoint & lp,const catacurses::window & w_look,int column,int & line)6389 void game::print_fields_info( const tripoint &lp, const catacurses::window &w_look, int column,
6390                               int &line )
6391 {
6392     const field &tmpfield = m.field_at( lp );
6393     for( const auto &fld : tmpfield ) {
6394         const field_entry &cur = fld.second;
6395         if( fld.first.obj().has_fire && ( m.has_flag( TFLAG_FIRE_CONTAINER, lp ) ||
6396                                           m.ter( lp ) == t_pit_shallow || m.ter( lp ) == t_pit ) ) {
6397             const int max_width = getmaxx( w_look ) - column - 2;
6398             int lines = fold_and_print( w_look, point( column, ++line ), max_width, cur.color(),
6399                                         get_fire_fuel_string( lp ) ) - 1;
6400             line += lines;
6401         } else {
6402             mvwprintz( w_look, point( column, ++line ), cur.color(), cur.name() );
6403         }
6404     }
6405 
6406     int size = std::distance( tmpfield.begin(), tmpfield.end() );
6407     if( size > 0 ) {
6408         mvwprintz( w_look, point( column, ++line ), c_white, "\n" );
6409     }
6410 }
6411 
print_trap_info(const tripoint & lp,const catacurses::window & w_look,const int column,int & line)6412 void game::print_trap_info( const tripoint &lp, const catacurses::window &w_look, const int column,
6413                             int &line )
6414 {
6415     const trap &tr = m.tr_at( lp );
6416     if( tr.can_see( lp, u ) ) {
6417         partial_con *pc = m.partial_con_at( lp );
6418         std::string tr_name;
6419         if( pc && tr == tr_unfinished_construction ) {
6420             const construction &built = pc->id.obj();
6421             tr_name = string_format( _( "Unfinished task: %s, %d%% complete" ), built.group->name(),
6422                                      pc->counter / 100000 );
6423         } else {
6424             tr_name = tr.name();
6425         }
6426 
6427         mvwprintz( w_look, point( column, ++line ), tr.color, tr_name );
6428     }
6429 
6430     ++line;
6431 }
6432 
print_creature_info(const Creature * creature,const catacurses::window & w_look,const int column,int & line,const int last_line)6433 void game::print_creature_info( const Creature *creature, const catacurses::window &w_look,
6434                                 const int column, int &line, const int last_line )
6435 {
6436     int vLines = last_line - line;
6437     if( creature != nullptr && ( u.sees( *creature ) || creature == &u ) ) {
6438         line = creature->print_info( w_look, ++line, vLines, column );
6439     }
6440 }
6441 
print_vehicle_info(const vehicle * veh,int veh_part,const catacurses::window & w_look,const int column,int & line,const int last_line)6442 void game::print_vehicle_info( const vehicle *veh, int veh_part, const catacurses::window &w_look,
6443                                const int column, int &line, const int last_line )
6444 {
6445     if( veh ) {
6446         // Print the name of the vehicle.
6447         mvwprintz( w_look, point( column, ++line ), c_light_gray, _( "Vehicle: " ) );
6448         mvwprintz( w_look, point( column + utf8_width( _( "Vehicle: " ) ), line ), c_white, "%s",
6449                    veh->name );
6450         // Then the list of parts on that tile.
6451         line = veh->print_part_list( w_look, ++line, last_line, getmaxx( w_look ), veh_part );
6452     }
6453 }
6454 
print_items_info(const tripoint & lp,const catacurses::window & w_look,const int column,int & line,const int last_line)6455 void game::print_items_info( const tripoint &lp, const catacurses::window &w_look, const int column,
6456                              int &line,
6457                              const int last_line )
6458 {
6459     if( !m.sees_some_items( lp, u ) ) {
6460         return;
6461     } else if( m.has_flag( "CONTAINER", lp ) && !m.could_see_items( lp, u ) ) {
6462         mvwprintw( w_look, point( column, ++line ), _( "You cannot see what is inside of it." ) );
6463     } else if( u.has_effect( effect_blind ) || u.worn_with_flag( flag_BLIND ) ) {
6464         mvwprintz( w_look, point( column, ++line ), c_yellow,
6465                    _( "There's something there, but you can't see what it is." ) );
6466         return;
6467     } else {
6468         std::map<std::string, std::pair<int, nc_color>> item_names;
6469         for( item &item : m.i_at( lp ) ) {
6470             ++item_names[item.tname()].first;
6471             item_names[item.tname()].second = item.color_in_inventory();
6472         }
6473 
6474         const int max_width = getmaxx( w_look ) - column - 1;
6475         for( auto it = item_names.begin(); it != item_names.end(); ++it ) {
6476             // last line but not last item
6477             if( line + 1 >= last_line && std::next( it ) != item_names.end() ) {
6478                 mvwprintz( w_look, point( column, ++line ), c_yellow, _( "More items here…" ) );
6479                 break;
6480             }
6481 
6482             if( it->second.first > 1 ) {
6483                 trim_and_print( w_look, point( column, ++line ), max_width, it->second.second,
6484                                 pgettext( "%s is the name of the item.  %d is the quantity of that item.", "%s [%d]" ),
6485                                 it->first.c_str(), it->second.first );
6486             } else {
6487                 trim_and_print( w_look, point( column, ++line ), max_width, it->second.second, it->first );
6488             }
6489         }
6490     }
6491 }
6492 
print_graffiti_info(const tripoint & lp,const catacurses::window & w_look,const int column,int & line,const int last_line)6493 void game::print_graffiti_info( const tripoint &lp, const catacurses::window &w_look,
6494                                 const int column, int &line,
6495                                 const int last_line )
6496 {
6497     if( line > last_line ) {
6498         return;
6499     }
6500 
6501     const int max_width = getmaxx( w_look ) - column - 2;
6502     if( m.has_graffiti_at( lp ) ) {
6503         const int lines = fold_and_print( w_look, point( column, ++line ), max_width, c_light_gray,
6504                                           m.ter( lp ) == t_grave_new ? _( "Graffiti: %s" ) : _( "Inscription: %s" ),
6505                                           m.graffiti_at( lp ) );
6506         line += lines - 1;
6507     }
6508 }
6509 
check_zone(const zone_type_id & type,const tripoint & where) const6510 bool game::check_zone( const zone_type_id &type, const tripoint &where ) const
6511 {
6512     return zone_manager::get_manager().has( type, m.getabs( where ) );
6513 }
6514 
check_near_zone(const zone_type_id & type,const tripoint & where) const6515 bool game::check_near_zone( const zone_type_id &type, const tripoint &where ) const
6516 {
6517     return zone_manager::get_manager().has_near( type, m.getabs( where ) );
6518 }
6519 
is_zones_manager_open() const6520 bool game::is_zones_manager_open() const
6521 {
6522     return zones_manager_open;
6523 }
6524 
zones_manager_shortcuts(const catacurses::window & w_info)6525 static void zones_manager_shortcuts( const catacurses::window &w_info )
6526 {
6527     werase( w_info );
6528 
6529     int tmpx = 1;
6530     tmpx += shortcut_print( w_info, point( tmpx, 1 ), c_white, c_light_green, _( "<A>dd" ) ) + 2;
6531     tmpx += shortcut_print( w_info, point( tmpx, 1 ), c_white, c_light_green, _( "<R>emove" ) ) + 2;
6532     tmpx += shortcut_print( w_info, point( tmpx, 1 ), c_white, c_light_green, _( "<E>nable" ) ) + 2;
6533     shortcut_print( w_info, point( tmpx, 1 ), c_white, c_light_green, _( "<D>isable" ) );
6534 
6535     tmpx = 1;
6536     tmpx += shortcut_print( w_info, point( tmpx, 2 ), c_white, c_light_green,
6537                             _( "<+-> Move up/down" ) ) + 2;
6538     shortcut_print( w_info, point( tmpx, 2 ), c_white, c_light_green, _( "<Enter>-Edit" ) );
6539 
6540     tmpx = 1;
6541     tmpx += shortcut_print( w_info, point( tmpx, 3 ), c_white, c_light_green,
6542                             _( "<S>how all / hide distant" ) ) + 2;
6543     shortcut_print( w_info, point( tmpx, 3 ), c_white, c_light_green, _( "<M>ap" ) );
6544 
6545     wnoutrefresh( w_info );
6546 }
6547 
zones_manager_draw_borders(const catacurses::window & w_border,const catacurses::window & w_info_border,const int iInfoHeight,const int width)6548 static void zones_manager_draw_borders( const catacurses::window &w_border,
6549                                         const catacurses::window &w_info_border,
6550                                         const int iInfoHeight, const int width )
6551 {
6552     for( int i = 1; i < TERMX; ++i ) {
6553         if( i < width ) {
6554             mvwputch( w_border, point( i, 0 ), c_light_gray, LINE_OXOX ); // -
6555             mvwputch( w_border, point( i, TERMY - iInfoHeight - 1 ), c_light_gray,
6556                       LINE_OXOX ); // -
6557         }
6558 
6559         if( i < TERMY - iInfoHeight ) {
6560             mvwputch( w_border, point( 0, i ), c_light_gray, LINE_XOXO ); // |
6561             mvwputch( w_border, point( width - 1, i ), c_light_gray, LINE_XOXO ); // |
6562         }
6563     }
6564 
6565     mvwputch( w_border, point_zero, c_light_gray, LINE_OXXO ); // |^
6566     mvwputch( w_border, point( width - 1, 0 ), c_light_gray, LINE_OOXX ); // ^|
6567 
6568     mvwputch( w_border, point( 0, TERMY - iInfoHeight - 1 ), c_light_gray,
6569               LINE_XXXO ); // |-
6570     mvwputch( w_border, point( width - 1, TERMY - iInfoHeight - 1 ), c_light_gray,
6571               LINE_XOXX ); // -|
6572 
6573     mvwprintz( w_border, point( 2, 0 ), c_white, _( "Zones manager" ) );
6574     wnoutrefresh( w_border );
6575 
6576     for( int j = 0; j < iInfoHeight - 1; ++j ) {
6577         mvwputch( w_info_border, point( 0, j ), c_light_gray, LINE_XOXO );
6578         mvwputch( w_info_border, point( width - 1, j ), c_light_gray, LINE_XOXO );
6579     }
6580 
6581     for( int j = 0; j < width - 1; ++j ) {
6582         mvwputch( w_info_border, point( j, iInfoHeight - 1 ), c_light_gray, LINE_OXOX );
6583     }
6584 
6585     mvwputch( w_info_border, point( 0, iInfoHeight - 1 ), c_light_gray, LINE_XXOO );
6586     mvwputch( w_info_border, point( width - 1, iInfoHeight - 1 ), c_light_gray, LINE_XOOX );
6587     wnoutrefresh( w_info_border );
6588 }
6589 
zones_manager()6590 void game::zones_manager()
6591 {
6592     const tripoint stored_view_offset = u.view_offset;
6593 
6594     u.view_offset = tripoint_zero;
6595 
6596     const int zone_ui_height = 12;
6597     const int zone_options_height = 7;
6598 
6599     const int width = 45;
6600 
6601     int offsetX = 0;
6602     int max_rows = 0;
6603 
6604     catacurses::window w_zones;
6605     catacurses::window w_zones_border;
6606     catacurses::window w_zones_info;
6607     catacurses::window w_zones_info_border;
6608     catacurses::window w_zones_options;
6609 
6610     bool show = true;
6611 
6612     ui_adaptor ui;
6613     ui.on_screen_resize( [&]( ui_adaptor & ui ) {
6614         if( !show ) {
6615             ui.position( point_zero, point_zero );
6616             return;
6617         }
6618         offsetX = get_option<std::string>( "SIDEBAR_POSITION" ) != "left" ?
6619                   TERMX - width : 0;
6620         const int w_zone_height = TERMY - zone_ui_height;
6621         max_rows = w_zone_height - 2;
6622         w_zones = catacurses::newwin( w_zone_height - 2, width - 2,
6623                                       point( offsetX + 1, 1 ) );
6624         w_zones_border = catacurses::newwin( w_zone_height, width,
6625                                              point( offsetX, 0 ) );
6626         w_zones_info = catacurses::newwin( zone_ui_height - zone_options_height - 1,
6627                                            width - 2, point( offsetX + 1, w_zone_height ) );
6628         w_zones_info_border = catacurses::newwin( zone_ui_height, width,
6629                               point( offsetX, w_zone_height ) );
6630         w_zones_options = catacurses::newwin( zone_options_height - 1, width - 2,
6631                                               point( offsetX + 1, TERMY - zone_options_height ) );
6632 
6633         ui.position( point( offsetX, 0 ), point( width, TERMY ) );
6634     } );
6635     ui.mark_resize();
6636 
6637     std::string action;
6638     input_context ctxt( "ZONES_MANAGER" );
6639     ctxt.register_cardinal();
6640     ctxt.register_action( "PAGE_UP", to_translation( "Fast scroll up" ) );
6641     ctxt.register_action( "PAGE_DOWN", to_translation( "Fast scroll down" ) );
6642     ctxt.register_action( "CONFIRM" );
6643     ctxt.register_action( "QUIT" );
6644     ctxt.register_action( "ADD_ZONE" );
6645     ctxt.register_action( "REMOVE_ZONE" );
6646     ctxt.register_action( "MOVE_ZONE_UP" );
6647     ctxt.register_action( "MOVE_ZONE_DOWN" );
6648     ctxt.register_action( "SHOW_ZONE_ON_MAP" );
6649     ctxt.register_action( "ENABLE_ZONE" );
6650     ctxt.register_action( "DISABLE_ZONE" );
6651     ctxt.register_action( "SHOW_ALL_ZONES" );
6652     ctxt.register_action( "HELP_KEYBINDINGS" );
6653 
6654     auto &mgr = zone_manager::get_manager();
6655     int start_index = 0;
6656     int active_index = 0;
6657     bool blink = false;
6658     bool stuff_changed = false;
6659     bool show_all_zones = false;
6660     int zone_cnt = 0;
6661 
6662     // get zones on the same z-level, with distance between player and
6663     // zone center point <= 50 or all zones, if show_all_zones is true
6664     auto get_zones = [&]() {
6665         std::vector<zone_manager::ref_zone_data> zones;
6666         if( show_all_zones ) {
6667             zones = mgr.get_zones();
6668         } else {
6669             const tripoint &u_abs_pos = m.getabs( u.pos() );
6670             for( zone_manager::ref_zone_data &ref : mgr.get_zones() ) {
6671                 const tripoint &zone_abs_pos = ref.get().get_center_point();
6672                 if( u_abs_pos.z == zone_abs_pos.z && rl_dist( u_abs_pos, zone_abs_pos ) <= 50 ) {
6673                     zones.emplace_back( ref );
6674                 }
6675             }
6676         }
6677         zone_cnt = static_cast<int>( zones.size() );
6678         return zones;
6679     };
6680 
6681     auto zones = get_zones();
6682 
6683     auto zones_manager_options = [&]() {
6684         werase( w_zones_options );
6685 
6686         if( zone_cnt > 0 ) {
6687             const auto &zone = zones[active_index].get();
6688 
6689             if( zone.has_options() ) {
6690                 const auto &descriptions = zone.get_options().get_descriptions();
6691 
6692                 // NOLINTNEXTLINE(cata-use-named-point-constants)
6693                 mvwprintz( w_zones_options, point( 1, 0 ), c_white, _( "Options" ) );
6694 
6695                 int y = 1;
6696                 for( const auto &desc : descriptions ) {
6697                     mvwprintz( w_zones_options, point( 3, y ), c_white, desc.first );
6698                     mvwprintz( w_zones_options, point( 20, y ), c_white, desc.second );
6699                     y++;
6700                 }
6701             }
6702         }
6703 
6704         wnoutrefresh( w_zones_options );
6705     };
6706 
6707     cata::optional<tripoint> zone_start;
6708     cata::optional<tripoint> zone_end;
6709     bool zone_blink = false;
6710     bool zone_cursor = false;
6711     shared_ptr_fast<draw_callback_t> zone_cb = create_zone_callback(
6712                 zone_start, zone_end, zone_blink, zone_cursor );
6713     add_draw_callback( zone_cb );
6714 
6715     auto query_position =
6716     [&]() -> cata::optional<std::pair<tripoint, tripoint>> {
6717         on_out_of_scope invalidate_current_ui( [&]()
6718         {
6719             ui.mark_resize();
6720         } );
6721         restore_on_out_of_scope<bool> show_prev( show );
6722         restore_on_out_of_scope<cata::optional<tripoint>> zone_start_prev( zone_start );
6723         restore_on_out_of_scope<cata::optional<tripoint>> zone_end_prev( zone_end );
6724         show = false;
6725         zone_start = cata::nullopt;
6726         zone_end = cata::nullopt;
6727         ui.mark_resize();
6728 
6729         static_popup popup;
6730         popup.on_top( true );
6731         popup.message( "%s", _( "Select first point." ) );
6732 
6733         tripoint center = u.pos() + u.view_offset;
6734 
6735         const look_around_result first = look_around( /*show_window=*/false, center, center, false, true,
6736                 false );
6737         if( first.position )
6738         {
6739             popup.message( "%s", _( "Select second point." ) );
6740 
6741             const look_around_result second = look_around( /*show_window=*/false, center, *first.position,
6742                     true, true, false );
6743             if( second.position ) {
6744                 tripoint first_abs = m.getabs( tripoint( std::min( first.position->x,
6745                                                second.position->x ),
6746                                                std::min( first.position->y, second.position->y ),
6747                                                std::min( first.position->z,
6748                                                        second.position->z ) ) );
6749                 tripoint second_abs = m.getabs( tripoint( std::max( first.position->x,
6750                                                 second.position->x ),
6751                                                 std::max( first.position->y, second.position->y ),
6752                                                 std::max( first.position->z,
6753                                                         second.position->z ) ) );
6754                 return std::pair<tripoint, tripoint>( first_abs, second_abs );
6755             }
6756         }
6757 
6758         return cata::nullopt;
6759     };
6760 
6761     ui.on_redraw( [&]( const ui_adaptor & ) {
6762         if( !show ) {
6763             return;
6764         }
6765         zones_manager_draw_borders( w_zones_border, w_zones_info_border, zone_ui_height, width );
6766         zones_manager_shortcuts( w_zones_info );
6767 
6768         if( zone_cnt == 0 ) {
6769             werase( w_zones );
6770             mvwprintz( w_zones, point( 2, 5 ), c_white, _( "No Zones defined." ) );
6771 
6772         } else {
6773             werase( w_zones );
6774 
6775             calcStartPos( start_index, active_index, max_rows, zone_cnt );
6776 
6777             draw_scrollbar( w_zones_border, active_index, max_rows, zone_cnt, point_south );
6778             wnoutrefresh( w_zones_border );
6779 
6780             int iNum = 0;
6781 
6782             tripoint player_absolute_pos = m.getabs( u.pos() );
6783 
6784             //Display saved zones
6785             for( auto &i : zones ) {
6786                 if( iNum >= start_index &&
6787                     iNum < start_index + ( ( max_rows > zone_cnt ) ? zone_cnt : max_rows ) ) {
6788                     const auto &zone = i.get();
6789 
6790                     nc_color colorLine = ( zone.get_enabled() ) ? c_white : c_light_gray;
6791 
6792                     if( iNum == active_index ) {
6793                         mvwprintz( w_zones, point( 0, iNum - start_index ), c_yellow, "%s", ">>" );
6794                         colorLine = ( zone.get_enabled() ) ? c_light_green : c_green;
6795                     }
6796 
6797                     //Draw Zone name
6798                     mvwprintz( w_zones, point( 3, iNum - start_index ), colorLine,
6799                                trim_by_length( zone.get_name(), 15 ) );
6800 
6801                     //Draw Type name
6802                     mvwprintz( w_zones, point( 20, iNum - start_index ), colorLine,
6803                                mgr.get_name_from_type( zone.get_type() ) );
6804 
6805                     tripoint center = zone.get_center_point();
6806 
6807                     //Draw direction + distance
6808                     mvwprintz( w_zones, point( 32, iNum - start_index ), colorLine, "%*d %s",
6809                                5, static_cast<int>( trig_dist( player_absolute_pos, center ) ),
6810                                direction_name_short( direction_from( player_absolute_pos,
6811                                                      center ) ) );
6812 
6813                     //Draw Vehicle Indicator
6814                     mvwprintz( w_zones, point( 41, iNum - start_index ), colorLine,
6815                                zone.get_is_vehicle() ? "*" : "" );
6816                 }
6817                 iNum++;
6818             }
6819 
6820             // Display zone options
6821             zones_manager_options();
6822         }
6823 
6824         wnoutrefresh( w_zones );
6825     } );
6826 
6827     const int scroll_rate = zone_cnt > 20 ? 10 : 3;
6828     zones_manager_open = true;
6829     do {
6830         if( action == "ADD_ZONE" ) {
6831             do { // not a loop, just for quick bailing out if canceled
6832                 const auto maybe_id = mgr.query_type();
6833                 if( !maybe_id.has_value() ) {
6834                     break;
6835                 }
6836 
6837                 const zone_type_id &id = maybe_id.value();
6838                 auto options = zone_options::create( id );
6839 
6840                 if( !options->query_at_creation() ) {
6841                     break;
6842                 }
6843 
6844                 auto default_name = options->get_zone_name_suggestion();
6845                 if( default_name.empty() ) {
6846                     default_name = mgr.get_name_from_type( id );
6847                 }
6848                 const auto maybe_name = mgr.query_name( default_name );
6849                 if( !maybe_name.has_value() ) {
6850                     break;
6851                 }
6852                 const std::string &name = maybe_name.value();
6853 
6854                 const auto position = query_position();
6855                 if( !position ) {
6856                     break;
6857                 }
6858 
6859                 mgr.add( name, id, get_player_character().get_faction()->id, false, true,
6860                          position->first, position->second, options );
6861 
6862                 zones = get_zones();
6863                 active_index = zone_cnt - 1;
6864 
6865                 stuff_changed = true;
6866             } while( false );
6867 
6868             blink = false;
6869         } else if( action == "SHOW_ALL_ZONES" ) {
6870             show_all_zones = !show_all_zones;
6871             zones = get_zones();
6872             active_index = 0;
6873         } else if( zone_cnt > 0 ) {
6874             if( action == "UP" ) {
6875                 active_index--;
6876                 if( active_index < 0 ) {
6877                     active_index = zone_cnt - 1;
6878                 }
6879                 blink = false;
6880             } else if( action == "DOWN" ) {
6881                 active_index++;
6882                 if( active_index >= zone_cnt ) {
6883                     active_index = 0;
6884                 }
6885                 blink = false;
6886             } else if( action == "PAGE_DOWN" ) {
6887                 if( active_index == zone_cnt - 1 ) {
6888                     active_index = 0;
6889                 } else if( active_index + scroll_rate >= zone_cnt ) {
6890                     active_index = zone_cnt - 1;
6891                 } else {
6892                     active_index += +scroll_rate;
6893                 }
6894                 blink = false;
6895             } else if( action == "PAGE_UP" ) {
6896                 if( active_index == 0 ) {
6897                     active_index = zone_cnt - 1;
6898                 } else if( active_index <= scroll_rate ) {
6899                     active_index = 0;
6900                 } else {
6901                     active_index += -scroll_rate;
6902                 }
6903                 blink = false;
6904             } else if( action == "REMOVE_ZONE" ) {
6905                 if( active_index < zone_cnt ) {
6906                     mgr.remove( zones[active_index] );
6907                     zones = get_zones();
6908                     active_index--;
6909 
6910                     if( active_index < 0 ) {
6911                         active_index = 0;
6912                     }
6913                 }
6914                 blink = false;
6915                 stuff_changed = true;
6916 
6917             } else if( action == "CONFIRM" ) {
6918                 auto &zone = zones[active_index].get();
6919 
6920                 uilist as_m;
6921                 as_m.text = _( "What do you want to change:" );
6922                 as_m.entries.emplace_back( 1, true, '1', _( "Edit name" ) );
6923                 as_m.entries.emplace_back( 2, true, '2', _( "Edit type" ) );
6924                 as_m.entries.emplace_back( 3, zone.get_options().has_options(), '3',
6925                                            zone.get_type() == zone_type_id( "LOOT_CUSTOM" ) ? _( "Edit filter" ) : _( "Edit options" ) );
6926                 as_m.entries.emplace_back( 4, !zone.get_is_vehicle(), '4', _( "Edit position" ) );
6927                 as_m.query();
6928 
6929                 switch( as_m.ret ) {
6930                     case 1:
6931                         if( zone.set_name() ) {
6932                             stuff_changed = true;
6933                         }
6934                         break;
6935                     case 2:
6936                         if( zone.set_type() ) {
6937                             stuff_changed = true;
6938                         }
6939                         break;
6940                     case 3:
6941                         if( zone.get_options().query() ) {
6942                             stuff_changed = true;
6943                         }
6944                         break;
6945                     case 4: {
6946                         const auto pos = query_position();
6947                         if( pos && ( pos->first != zone.get_start_point() ||
6948                                      pos->second != zone.get_end_point() ) ) {
6949                             zone.set_position( *pos );
6950                             stuff_changed = true;
6951                         }
6952                     }
6953                     break;
6954                     default:
6955                         break;
6956                 }
6957 
6958                 blink = false;
6959             } else if( action == "MOVE_ZONE_UP" && zone_cnt > 1 ) {
6960                 if( active_index < zone_cnt - 1 ) {
6961                     mgr.swap( zones[active_index], zones[active_index + 1] );
6962                     zones = get_zones();
6963                     active_index++;
6964                 }
6965                 blink = false;
6966                 stuff_changed = true;
6967 
6968             } else if( action == "MOVE_ZONE_DOWN" && zone_cnt > 1 ) {
6969                 if( active_index > 0 ) {
6970                     mgr.swap( zones[active_index], zones[active_index - 1] );
6971                     zones = get_zones();
6972                     active_index--;
6973                 }
6974                 blink = false;
6975                 stuff_changed = true;
6976 
6977             } else if( action == "SHOW_ZONE_ON_MAP" ) {
6978                 //show zone position on overmap;
6979                 tripoint_abs_omt player_overmap_position = u.global_omt_location();
6980                 // TODO: fix point types
6981                 tripoint_abs_omt zone_overmap( ms_to_omt_copy( zones[active_index].get().get_center_point() ) );
6982 
6983                 ui::omap::display_zones( player_overmap_position, zone_overmap, active_index );
6984             } else if( action == "ENABLE_ZONE" ) {
6985                 zones[active_index].get().set_enabled( true );
6986 
6987                 stuff_changed = true;
6988 
6989             } else if( action == "DISABLE_ZONE" ) {
6990                 zones[active_index].get().set_enabled( false );
6991 
6992                 stuff_changed = true;
6993             }
6994         }
6995 
6996         if( zone_cnt > 0 ) {
6997             blink = !blink;
6998             const auto &zone = zones[active_index].get();
6999             zone_start = m.getlocal( zone.get_start_point() );
7000             zone_end = m.getlocal( zone.get_end_point() );
7001             ctxt.set_timeout( BLINK_SPEED );
7002         } else {
7003             blink = false;
7004             zone_start = zone_end = cata::nullopt;
7005             ctxt.reset_timeout();
7006         }
7007 
7008         // Actually accessed from the terrain overlay callback `zone_cb` in the
7009         // call to `ui_manager::redraw`.
7010         //NOLINTNEXTLINE(clang-analyzer-deadcode.DeadStores)
7011         zone_blink = blink;
7012         invalidate_main_ui_adaptor();
7013 
7014         ui_manager::redraw();
7015 
7016         //Wait for input
7017         action = ctxt.handle_input();
7018     } while( action != "QUIT" );
7019     zones_manager_open = false;
7020     ctxt.reset_timeout();
7021     zone_cb = nullptr;
7022 
7023     if( stuff_changed ) {
7024         auto &zones = zone_manager::get_manager();
7025         if( query_yn( _( "Save changes?" ) ) ) {
7026             zones.save_zones();
7027         } else {
7028             zones.load_zones();
7029         }
7030 
7031         zones.cache_data();
7032     }
7033 
7034     u.view_offset = stored_view_offset;
7035 }
7036 
pre_print_all_tile_info(const tripoint & lp,const catacurses::window & w_info,int & first_line,const int last_line,const visibility_variables & cache)7037 void game::pre_print_all_tile_info( const tripoint &lp, const catacurses::window &w_info,
7038                                     int &first_line, const int last_line,
7039                                     const visibility_variables &cache )
7040 {
7041     // get global area info according to look_around caret position
7042     // TODO: fix point types
7043     const oter_id &cur_ter_m = overmap_buffer.ter( tripoint_abs_omt( ms_to_omt_copy( m.getabs(
7044                                    lp ) ) ) );
7045     // we only need the area name and then pass it to print_all_tile_info() function below
7046     const std::string area_name = cur_ter_m->get_name();
7047     print_all_tile_info( lp, w_info, area_name, 1, first_line, last_line, cache );
7048 }
7049 
look_around()7050 cata::optional<tripoint> game::look_around()
7051 {
7052     tripoint center = u.pos() + u.view_offset;
7053     look_around_result result = look_around( /*show_window=*/true, center, center, false, false,
7054                                 false );
7055     return result.position;
7056 }
7057 
look_around(const bool show_window,tripoint & center,const tripoint & start_point,bool has_first_point,bool select_zone,bool peeking)7058 look_around_result game::look_around( const bool show_window, tripoint &center,
7059                                       const tripoint &start_point, bool has_first_point, bool select_zone, bool peeking )
7060 {
7061     bVMonsterLookFire = false;
7062     // TODO: Make this `true`
7063     const bool allow_zlev_move = m.has_zlevels() && get_option<bool>( "FOV_3D" );
7064 
7065     temp_exit_fullscreen();
7066 
7067     tripoint lp = start_point; // cursor
7068     int &lx = lp.x;
7069     int &ly = lp.y;
7070     int &lz = lp.z;
7071 
7072     int soffset = get_option<int>( "FAST_SCROLL_OFFSET" );
7073     bool fast_scroll = false;
7074 
7075     std::unique_ptr<ui_adaptor> ui;
7076     catacurses::window w_info;
7077     if( show_window ) {
7078         ui = std::make_unique<ui_adaptor>();
7079         ui->on_screen_resize( [&]( ui_adaptor & ui ) {
7080             int panel_width = panel_manager::get_manager().get_current_layout().panels().begin()->get_width();
7081             int height = pixel_minimap_option ? TERMY - getmaxy( w_pixel_minimap ) : TERMY;
7082 
7083             // If particularly small, base height on panel width irrespective of other elements.
7084             // Value here is attempting to get a square-ish result assuming 1x2 proportioned font.
7085             if( height < panel_width / 2 ) {
7086                 height = panel_width / 2;
7087             }
7088 
7089             int la_y = 0;
7090             int la_x = TERMX - panel_width;
7091             std::string position = get_option<std::string>( "LOOKAROUND_POSITION" );
7092             if( position == "left" ) {
7093                 if( get_option<std::string>( "SIDEBAR_POSITION" ) == "right" ) {
7094                     la_x = panel_manager::get_manager().get_width_left();
7095                 } else {
7096                     la_x = panel_manager::get_manager().get_width_left() - panel_width;
7097                 }
7098             }
7099             int la_h = height;
7100             int la_w = panel_width;
7101             w_info = catacurses::newwin( la_h, la_w, point( la_x, la_y ) );
7102 
7103             ui.position_from_window( w_info );
7104         } );
7105         ui->mark_resize();
7106     }
7107 
7108     dbg( D_PEDANTIC_INFO ) << ": calling handle_input()";
7109 
7110     std::string action;
7111     input_context ctxt( "LOOK" );
7112     ctxt.set_iso( true );
7113     ctxt.register_directions();
7114     ctxt.register_action( "COORDINATE" );
7115     ctxt.register_action( "LEVEL_UP" );
7116     ctxt.register_action( "LEVEL_DOWN" );
7117     ctxt.register_action( "TOGGLE_FAST_SCROLL" );
7118     ctxt.register_action( "EXTENDED_DESCRIPTION" );
7119     ctxt.register_action( "SELECT" );
7120     if( peeking ) {
7121         ctxt.register_action( "throw_blind" );
7122     }
7123     if( !select_zone ) {
7124         ctxt.register_action( "TRAVEL_TO" );
7125         ctxt.register_action( "LIST_ITEMS" );
7126     }
7127     ctxt.register_action( "MOUSE_MOVE" );
7128     ctxt.register_action( "CENTER" );
7129 
7130     ctxt.register_action( "debug_scent" );
7131     ctxt.register_action( "debug_scent_type" );
7132     ctxt.register_action( "debug_temp" );
7133     ctxt.register_action( "debug_visibility" );
7134     ctxt.register_action( "debug_lighting" );
7135     ctxt.register_action( "debug_radiation" );
7136     ctxt.register_action( "debug_hour_timer" );
7137     ctxt.register_action( "CONFIRM" );
7138     ctxt.register_action( "QUIT" );
7139     ctxt.register_action( "HELP_KEYBINDINGS" );
7140     ctxt.register_action( "zoom_out" );
7141     ctxt.register_action( "zoom_in" );
7142     ctxt.register_action( "toggle_pixel_minimap" );
7143 
7144     const int old_levz = m.get_abs_sub().z;
7145     const int min_levz = std::max( old_levz - fov_3d_z_range, -OVERMAP_DEPTH );
7146     const int max_levz = std::min( old_levz + fov_3d_z_range, OVERMAP_HEIGHT );
7147 
7148     m.update_visibility_cache( old_levz );
7149     const visibility_variables &cache = m.get_visibility_variables_cache();
7150 
7151     bool blink = true;
7152     look_around_result result;
7153 
7154     shared_ptr_fast<draw_callback_t> ter_indicator_cb;
7155 
7156     if( show_window && ui ) {
7157         ui->on_redraw( [&]( const ui_adaptor & ) {
7158             werase( w_info );
7159             draw_border( w_info );
7160 
7161             center_print( w_info, 0, c_white, string_format( _( "< <color_green>Look Around</color> >" ) ) );
7162 
7163             std::string fast_scroll_text = string_format( _( "%s - %s" ),
7164                                            ctxt.get_desc( "TOGGLE_FAST_SCROLL" ),
7165                                            ctxt.get_action_name( "TOGGLE_FAST_SCROLL" ) );
7166             std::string pixel_minimap_text = string_format( _( "%s - %s" ),
7167                                              ctxt.get_desc( "toggle_pixel_minimap" ),
7168                                              ctxt.get_action_name( "toggle_pixel_minimap" ) );
7169             mvwprintz( w_info, point( 1, getmaxy( w_info ) - 1 ), fast_scroll ? c_light_green : c_green,
7170                        fast_scroll_text );
7171             right_print( w_info, getmaxy( w_info ) - 1, 1, pixel_minimap_option ? c_light_green : c_green,
7172                          pixel_minimap_text );
7173 
7174             int first_line = 1;
7175             const int last_line = getmaxy( w_info ) - 2;
7176             pre_print_all_tile_info( lp, w_info, first_line, last_line, cache );
7177 
7178             wnoutrefresh( w_info );
7179         } );
7180         ter_indicator_cb = make_shared_fast<draw_callback_t>( [&]() {
7181             draw_look_around_cursor( lp, cache );
7182         } );
7183         add_draw_callback( ter_indicator_cb );
7184     }
7185 
7186     cata::optional<tripoint> zone_start;
7187     cata::optional<tripoint> zone_end;
7188     bool zone_blink = false;
7189     bool zone_cursor = true;
7190     shared_ptr_fast<draw_callback_t> zone_cb = create_zone_callback( zone_start, zone_end, zone_blink,
7191             zone_cursor );
7192     add_draw_callback( zone_cb );
7193 
7194     is_looking = true;
7195     const tripoint prev_offset = u.view_offset;
7196     do {
7197         u.view_offset = center - u.pos();
7198         if( select_zone ) {
7199             if( has_first_point ) {
7200                 zone_start = start_point;
7201                 zone_end = lp;
7202             } else {
7203                 zone_start = lp;
7204                 zone_end = cata::nullopt;
7205             }
7206             // Actually accessed from the terrain overlay callback `zone_cb` in the
7207             // call to `ui_manager::redraw`.
7208             //NOLINTNEXTLINE(clang-analyzer-deadcode.DeadStores)
7209             zone_blink = blink;
7210         }
7211         invalidate_main_ui_adaptor();
7212         ui_manager::redraw();
7213 
7214         if( select_zone && has_first_point ) {
7215             ctxt.set_timeout( BLINK_SPEED );
7216         }
7217 
7218         //Wait for input
7219         // only specify a timeout here if "EDGE_SCROLL" is enabled
7220         // otherwise use the previously set timeout
7221         const tripoint edge_scroll = mouse_edge_scrolling_terrain( ctxt );
7222         const int scroll_timeout = get_option<int>( "EDGE_SCROLL" );
7223         const bool edge_scrolling = edge_scroll != tripoint_zero && scroll_timeout >= 0;
7224         if( edge_scrolling ) {
7225             action = ctxt.handle_input( scroll_timeout );
7226         } else {
7227             action = ctxt.handle_input();
7228         }
7229         if( action == "LIST_ITEMS" ) {
7230             list_items_monsters();
7231         } else if( action == "TOGGLE_FAST_SCROLL" ) {
7232             fast_scroll = !fast_scroll;
7233         } else if( action == "toggle_pixel_minimap" ) {
7234             toggle_pixel_minimap();
7235 
7236             if( show_window && ui ) {
7237                 ui->mark_resize();
7238             }
7239         } else if( action == "LEVEL_UP" || action == "LEVEL_DOWN" ) {
7240             if( !allow_zlev_move ) {
7241                 continue;
7242             }
7243 
7244             const int dz = ( action == "LEVEL_UP" ? 1 : -1 );
7245             lz = clamp( lz + dz, min_levz, max_levz );
7246             center.z = clamp( center.z + dz, min_levz, max_levz );
7247 
7248             add_msg_debug( "levx: %d, levy: %d, levz: %d",
7249                            get_map().get_abs_sub().x, get_map().get_abs_sub().y, center.z );
7250             u.view_offset.z = center.z - u.posz();
7251             m.invalidate_map_cache( center.z );
7252             if( select_zone && has_first_point ) { // is blinking
7253                 blink = true; // Always draw blink symbols when moving cursor
7254             }
7255         } else if( action == "TRAVEL_TO" ) {
7256             if( !u.sees( lp ) ) {
7257                 add_msg( _( "You can't see that destination." ) );
7258                 continue;
7259             }
7260 
7261             auto route = m.route( u.pos(), lp, u.get_pathfinding_settings(), u.get_path_avoid() );
7262             if( route.size() > 1 ) {
7263                 route.pop_back();
7264                 u.set_destination( route );
7265             } else {
7266                 add_msg( m_info, _( "You can't travel there." ) );
7267                 continue;
7268             }
7269         } else if( action == "debug_scent" || action == "debug_scent_type" ) {
7270             if( !MAP_SHARING::isCompetitive() || MAP_SHARING::isDebugger() ) {
7271                 display_scent();
7272             }
7273         } else if( action == "debug_temp" ) {
7274             if( !MAP_SHARING::isCompetitive() || MAP_SHARING::isDebugger() ) {
7275                 display_temperature();
7276             }
7277         } else if( action == "debug_lighting" ) {
7278             if( !MAP_SHARING::isCompetitive() || MAP_SHARING::isDebugger() ) {
7279                 display_lighting();
7280             }
7281         } else if( action == "debug_transparency" ) {
7282             if( !MAP_SHARING::isCompetitive() || MAP_SHARING::isDebugger() ) {
7283                 display_transparency();
7284             }
7285         } else if( action == "display_reachability_zones" ) {
7286             if( !MAP_SHARING::isCompetitive() || MAP_SHARING::isDebugger() ) {
7287                 display_reachability_zones();
7288             }
7289         } else if( action == "debug_radiation" ) {
7290             if( !MAP_SHARING::isCompetitive() || MAP_SHARING::isDebugger() ) {
7291                 display_radiation();
7292             }
7293         } else if( action == "debug_hour_timer" ) {
7294             toggle_debug_hour_timer();
7295         } else if( action == "EXTENDED_DESCRIPTION" ) {
7296             extended_description( lp );
7297         } else if( action == "CENTER" ) {
7298             center = u.pos();
7299             lp = u.pos();
7300             u.view_offset.z = 0;
7301         } else if( action == "MOUSE_MOVE" || action == "TIMEOUT" ) {
7302             // This block is structured this way so that edge scroll can work
7303             // whether the mouse is moving at the edge or simply stationary
7304             // at the edge. But even if edge scroll isn't in play, there's
7305             // other things for us to do here.
7306 
7307             if( edge_scrolling ) {
7308                 if( action == "MOUSE_MOVE" ) {
7309                     center += edge_scroll * 2;
7310                 } else {
7311                     center += edge_scroll;
7312                 }
7313                 if( select_zone && has_first_point ) { // is blinking
7314                     blink = true; // Always draw blink symbols when moving cursor
7315                 }
7316             } else if( action == "MOUSE_MOVE" ) {
7317                 const cata::optional<tripoint> mouse_pos = ctxt.get_coordinates( w_terrain );
7318                 if( mouse_pos ) {
7319                     lx = mouse_pos->x;
7320                     ly = mouse_pos->y;
7321                 }
7322                 if( select_zone && has_first_point ) { // is blinking
7323                     blink = true; // Always draw blink symbols when moving cursor
7324                 }
7325             } else if( action == "TIMEOUT" ) {
7326                 blink = !blink;
7327             }
7328         } else if( cata::optional<tripoint> vec = ctxt.get_direction( action ) ) {
7329             if( fast_scroll ) {
7330                 vec->x *= soffset;
7331                 vec->y *= soffset;
7332             }
7333 
7334             lx = lx + vec->x;
7335             ly = ly + vec->y;
7336             center.x = center.x + vec->x;
7337             center.y = center.y + vec->y;
7338             if( select_zone && has_first_point ) { // is blinking
7339                 blink = true; // Always draw blink symbols when moving cursor
7340             }
7341         } else if( action == "throw_blind" ) {
7342             result.peek_action = PA_BLIND_THROW;
7343         } else if( action == "zoom_in" ) {
7344             center.x = lp.x;
7345             center.y = lp.y;
7346             zoom_in();
7347         } else if( action == "zoom_out" ) {
7348             center.x = lp.x;
7349             center.y = lp.y;
7350             zoom_out();
7351         }
7352     } while( action != "QUIT" && action != "CONFIRM" && action != "SELECT" && action != "TRAVEL_TO" &&
7353              action != "throw_blind" );
7354 
7355     if( m.has_zlevels() && center.z != old_levz ) {
7356         m.invalidate_map_cache( old_levz );
7357         m.build_map_cache( old_levz );
7358         u.view_offset.z = 0;
7359     }
7360 
7361     ctxt.reset_timeout();
7362     u.view_offset = prev_offset;
7363     zone_cb = nullptr;
7364     is_looking = false;
7365 
7366     reenter_fullscreen();
7367     bVMonsterLookFire = true;
7368 
7369     if( action == "CONFIRM" || action == "SELECT" ) {
7370         result.position = lp;
7371     }
7372 
7373     return result;
7374 }
7375 
find_nearby_items(int iRadius)7376 std::vector<map_item_stack> game::find_nearby_items( int iRadius )
7377 {
7378     std::map<std::string, map_item_stack> temp_items;
7379     std::vector<map_item_stack> ret;
7380     std::vector<std::string> item_order;
7381 
7382     if( u.is_blind() ) {
7383         return ret;
7384     }
7385 
7386     for( auto &points_p_it : closest_points_first( u.pos(), iRadius ) ) {
7387         if( points_p_it.y >= u.posy() - iRadius && points_p_it.y <= u.posy() + iRadius &&
7388             u.sees( points_p_it ) &&
7389             m.sees_some_items( points_p_it, u ) ) {
7390 
7391             for( item &elem : m.i_at( points_p_it ) ) {
7392                 const std::string name = elem.tname();
7393                 const tripoint relative_pos = points_p_it - u.pos();
7394 
7395                 if( std::find( item_order.begin(), item_order.end(), name ) == item_order.end() ) {
7396                     item_order.push_back( name );
7397                     temp_items[name] = map_item_stack( &elem, relative_pos );
7398                 } else {
7399                     temp_items[name].add_at_pos( &elem, relative_pos );
7400                 }
7401             }
7402         }
7403     }
7404 
7405     for( auto &elem : item_order ) {
7406         ret.push_back( temp_items[elem] );
7407     }
7408 
7409     return ret;
7410 }
7411 
draw_trail(const tripoint & start,const tripoint & end,const bool bDrawX)7412 void draw_trail( const tripoint &start, const tripoint &end, const bool bDrawX )
7413 {
7414     std::vector<tripoint> pts;
7415     avatar &player_character = get_avatar();
7416     tripoint center = player_character.pos() + player_character.view_offset;
7417     if( start != end ) {
7418         //Draw trail
7419         pts = line_to( start, end, 0, 0 );
7420     } else {
7421         //Draw point
7422         pts.emplace_back( start );
7423     }
7424 
7425     g->draw_line( end, center, pts );
7426     if( bDrawX ) {
7427         char sym = 'X';
7428         if( end.z > center.z ) {
7429             sym = '^';
7430         } else if( end.z < center.z ) {
7431             sym = 'v';
7432         }
7433         if( pts.empty() ) {
7434             mvwputch( g->w_terrain, point( POSX, POSY ), c_white, sym );
7435         } else {
7436             mvwputch( g->w_terrain, pts.back().xy() - player_character.view_offset.xy() +
7437                       point( POSX - player_character.posx(), POSY - player_character.posy() ),
7438                       c_white, sym );
7439         }
7440     }
7441 }
7442 
draw_trail_to_square(const tripoint & t,bool bDrawX)7443 void game::draw_trail_to_square( const tripoint &t, bool bDrawX )
7444 {
7445     ::draw_trail( u.pos(), u.pos() + t, bDrawX );
7446 }
7447 
centerlistview(const tripoint & active_item_position,int ui_width)7448 static void centerlistview( const tripoint &active_item_position, int ui_width )
7449 {
7450     player &u = get_avatar();
7451     if( get_option<std::string>( "SHIFT_LIST_ITEM_VIEW" ) != "false" ) {
7452         u.view_offset.z = active_item_position.z;
7453         if( get_option<std::string>( "SHIFT_LIST_ITEM_VIEW" ) == "centered" ) {
7454             u.view_offset.x = active_item_position.x;
7455             u.view_offset.y = active_item_position.y;
7456         } else {
7457             point pos( active_item_position.xy() + point( POSX, POSY ) );
7458 
7459             // item/monster list UI is on the right, so get the difference between its width
7460             // and the width of the sidebar on the right (if any)
7461             int sidebar_right_adjusted = ui_width - panel_manager::get_manager().get_width_right();
7462             // if and only if that difference is greater than zero, use that as offset
7463             int right_offset = sidebar_right_adjusted > 0 ? sidebar_right_adjusted : 0;
7464 
7465             // Convert offset to tile counts, calculate adjusted terrain window width
7466             // This lets us account for possible differences in terrain width between
7467             // the normal sidebar and the list-all-whatever display.
7468             to_map_font_dim_width( right_offset );
7469             int terrain_width = TERRAIN_WINDOW_WIDTH - right_offset;
7470 
7471             if( pos.x < 0 ) {
7472                 u.view_offset.x = pos.x;
7473             } else if( pos.x >= terrain_width ) {
7474                 u.view_offset.x = pos.x - ( terrain_width - 1 );
7475             } else {
7476                 u.view_offset.x = 0;
7477             }
7478 
7479             if( pos.y < 0 ) {
7480                 u.view_offset.y = pos.y;
7481             } else if( pos.y >= TERRAIN_WINDOW_HEIGHT ) {
7482                 u.view_offset.y = pos.y - ( TERRAIN_WINDOW_HEIGHT - 1 );
7483             } else {
7484                 u.view_offset.y = 0;
7485             }
7486         }
7487     }
7488 
7489 }
7490 
7491 #if defined(TILES)
7492 static constexpr int MAXIMUM_ZOOM_LEVEL = 4;
7493 #endif
zoom_out()7494 void game::zoom_out()
7495 {
7496 #if defined(TILES)
7497     if( tileset_zoom > MAXIMUM_ZOOM_LEVEL ) {
7498         tileset_zoom = tileset_zoom / 2;
7499     } else {
7500         tileset_zoom = 64;
7501     }
7502     rescale_tileset( tileset_zoom );
7503 #endif
7504 }
7505 
zoom_in()7506 void game::zoom_in()
7507 {
7508 #if defined(TILES)
7509     if( tileset_zoom == 64 ) {
7510         tileset_zoom = MAXIMUM_ZOOM_LEVEL;
7511     } else {
7512         tileset_zoom = tileset_zoom * 2;
7513     }
7514     rescale_tileset( tileset_zoom );
7515 #endif
7516 }
7517 
reset_zoom()7518 void game::reset_zoom()
7519 {
7520 #if defined(TILES)
7521     tileset_zoom = DEFAULT_TILESET_ZOOM;
7522     rescale_tileset( tileset_zoom );
7523 #endif // TILES
7524 }
7525 
get_moves_since_last_save() const7526 int game::get_moves_since_last_save() const
7527 {
7528     return moves_since_last_save;
7529 }
7530 
get_user_action_counter() const7531 int game::get_user_action_counter() const
7532 {
7533     return user_action_counter;
7534 }
7535 
7536 #if defined(TILES)
take_screenshot(const std::string & path) const7537 bool game::take_screenshot( const std::string &path ) const
7538 {
7539     return save_screenshot( path );
7540 }
7541 #else
take_screenshot(const std::string &) const7542 bool game::take_screenshot( const std::string &/*path*/ ) const
7543 {
7544     return false;
7545 }
7546 #endif
7547 
7548 //helper method so we can keep list_items shorter
reset_item_list_state(const catacurses::window & window,int height,bool bRadiusSort)7549 void game::reset_item_list_state( const catacurses::window &window, int height, bool bRadiusSort )
7550 {
7551     const int width = getmaxx( window );
7552     for( int i = 1; i < TERMX; i++ ) {
7553         if( i < width ) {
7554             mvwputch( window, point( i, 0 ), c_light_gray, LINE_OXOX ); // -
7555             mvwputch( window, point( i, TERMY - height - 1 ), c_light_gray,
7556                       LINE_OXOX ); // -
7557         }
7558 
7559         if( i < TERMY - height ) {
7560             mvwputch( window, point( 0, i ), c_light_gray, LINE_XOXO ); // |
7561             mvwputch( window, point( width - 1, i ), c_light_gray, LINE_XOXO ); // |
7562         }
7563     }
7564 
7565     mvwputch( window, point_zero, c_light_gray, LINE_OXXO ); // |^
7566     mvwputch( window, point( width - 1, 0 ), c_light_gray, LINE_OOXX ); // ^|
7567 
7568     mvwputch( window, point( 0, TERMY - height - 1 ), c_light_gray,
7569               LINE_XXXO ); // |-
7570     mvwputch( window, point( width - 1, TERMY - height - 1 ), c_light_gray,
7571               LINE_XOXX ); // -|
7572 
7573     mvwprintz( window, point( 2, 0 ), c_light_green, "<Tab> " );
7574     wprintz( window, c_white, _( "Items" ) );
7575 
7576     std::string sSort;
7577     if( bRadiusSort ) {
7578         //~ Sort type: distance.
7579         sSort = _( "<s>ort: dist" );
7580     } else {
7581         //~ Sort type: category.
7582         sSort = _( "<s>ort: cat" );
7583     }
7584 
7585     int letters = utf8_width( sSort );
7586 
7587     shortcut_print( window, point( getmaxx( window ) - letters, 0 ), c_white, c_light_green, sSort );
7588 
7589     std::vector<std::string> tokens;
7590     if( !sFilter.empty() ) {
7591         tokens.emplace_back( _( "<R>eset" ) );
7592     }
7593 
7594     tokens.emplace_back( _( "<E>xamine" ) );
7595     tokens.emplace_back( _( "<C>ompare" ) );
7596     tokens.emplace_back( _( "<F>ilter" ) );
7597     tokens.emplace_back( _( "<+/->Priority" ) );
7598 
7599     int gaps = tokens.size() + 1;
7600     letters = 0;
7601     int n = tokens.size();
7602     for( int i = 0; i < n; i++ ) {
7603         letters += utf8_width( tokens[i] ) - 2; //length ignores < >
7604     }
7605 
7606     int usedwidth = letters;
7607     const int gap_spaces = ( width - usedwidth ) / gaps;
7608     usedwidth += gap_spaces * gaps;
7609     point pos( gap_spaces + ( width - usedwidth ) / 2, TERMY - height - 1 );
7610 
7611     for( int i = 0; i < n; i++ ) {
7612         pos.x += shortcut_print( window, pos, c_white, c_light_green,
7613                                  tokens[i] ) + gap_spaces;
7614     }
7615 }
7616 
list_items_monsters()7617 void game::list_items_monsters()
7618 {
7619     std::vector<Creature *> mons = u.get_visible_creatures( current_daylight_level( calendar::turn ) );
7620     // whole reality bubble
7621     const std::vector<map_item_stack> items = find_nearby_items( 60 );
7622 
7623     if( mons.empty() && items.empty() ) {
7624         add_msg( m_info, _( "You don't see any items or monsters around you!" ) );
7625         return;
7626     }
7627 
7628     std::sort( mons.begin(), mons.end(), [&]( const Creature * lhs, const Creature * rhs ) {
7629         const Creature::Attitude att_lhs = lhs->attitude_to( u );
7630         const Creature::Attitude att_rhs = rhs->attitude_to( u );
7631 
7632         return att_lhs < att_rhs || ( att_lhs == att_rhs
7633                                       && rl_dist( u.pos(), lhs->pos() ) < rl_dist( u.pos(), rhs->pos() ) );
7634     } );
7635 
7636     // If the current list is empty, switch to the non-empty list
7637     if( uistate.vmenu_show_items ) {
7638         if( items.empty() ) {
7639             uistate.vmenu_show_items = false;
7640         }
7641     } else if( mons.empty() ) {
7642         uistate.vmenu_show_items = true;
7643     }
7644 
7645     temp_exit_fullscreen();
7646     game::vmenu_ret ret;
7647     while( true ) {
7648         ret = uistate.vmenu_show_items ? list_items( items ) : list_monsters( mons );
7649         if( ret == game::vmenu_ret::CHANGE_TAB ) {
7650             uistate.vmenu_show_items = !uistate.vmenu_show_items;
7651         } else {
7652             break;
7653         }
7654     }
7655 
7656     if( ret == game::vmenu_ret::FIRE ) {
7657         avatar_action::fire_wielded_weapon( u );
7658     }
7659     reenter_fullscreen();
7660 }
7661 
list_items(const std::vector<map_item_stack> & item_list)7662 game::vmenu_ret game::list_items( const std::vector<map_item_stack> &item_list )
7663 {
7664     std::vector<map_item_stack> ground_items = item_list;
7665     int iInfoHeight = 0;
7666     int iMaxRows = 0;
7667     int width = 0;
7668     int max_name_width = 0;
7669 
7670     //find max length of item name and resize window width
7671     for( const map_item_stack &cur_item : ground_items ) {
7672         const int item_len = utf8_width( remove_color_tags( cur_item.example->display_name() ) ) + 15;
7673         if( item_len > max_name_width ) {
7674             max_name_width = item_len;
7675         }
7676     }
7677 
7678     tripoint active_pos;
7679     map_item_stack *activeItem = nullptr;
7680 
7681     catacurses::window w_items;
7682     catacurses::window w_items_border;
7683     catacurses::window w_item_info;
7684 
7685     ui_adaptor ui;
7686     ui.on_screen_resize( [&]( ui_adaptor & ui ) {
7687         iInfoHeight = std::min( 25, TERMY / 2 );
7688         iMaxRows = TERMY - iInfoHeight - 2;
7689 
7690         width = clamp( max_name_width, 45, TERMX / 3 );
7691 
7692         const int offsetX = TERMX - width;
7693 
7694         w_items = catacurses::newwin( TERMY - 2 - iInfoHeight,
7695                                       width - 2, point( offsetX + 1, 1 ) );
7696         w_items_border = catacurses::newwin( TERMY - iInfoHeight,
7697                                              width, point( offsetX, 0 ) );
7698         w_item_info = catacurses::newwin( iInfoHeight, width,
7699                                           point( offsetX, TERMY - iInfoHeight ) );
7700 
7701         if( activeItem ) {
7702             centerlistview( active_pos, width );
7703         }
7704 
7705         ui.position( point( offsetX, 0 ), point( width, TERMY ) );
7706     } );
7707     ui.mark_resize();
7708 
7709     // use previously selected sorting method
7710     bool sort_radius = uistate.list_item_sort != 2;
7711     bool addcategory = !sort_radius;
7712 
7713     // reload filter/priority settings on the first invocation, if they were active
7714     if( !uistate.list_item_init ) {
7715         if( uistate.list_item_filter_active ) {
7716             sFilter = uistate.list_item_filter;
7717         }
7718         if( uistate.list_item_downvote_active ) {
7719             list_item_downvote = uistate.list_item_downvote;
7720         }
7721         if( uistate.list_item_priority_active ) {
7722             list_item_upvote = uistate.list_item_priority;
7723         }
7724         uistate.list_item_init = true;
7725     }
7726 
7727     //this stores only those items that match our filter
7728     std::vector<map_item_stack> filtered_items =
7729         !sFilter.empty() ? filter_item_stacks( ground_items, sFilter ) : ground_items;
7730     int highPEnd = list_filter_high_priority( filtered_items, list_item_upvote );
7731     int lowPStart = list_filter_low_priority( filtered_items, highPEnd, list_item_downvote );
7732     int iItemNum = ground_items.size();
7733 
7734     const tripoint stored_view_offset = u.view_offset;
7735 
7736     u.view_offset = tripoint_zero;
7737 
7738     int iActive = 0; // Item index that we're looking at
7739     bool refilter = true;
7740     int page_num = 0;
7741     int iCatSortNum = 0;
7742     int iScrollPos = 0;
7743     std::map<int, std::string> mSortCategory;
7744 
7745     std::string action;
7746     input_context ctxt( "LIST_ITEMS" );
7747     ctxt.register_action( "UP", to_translation( "Move cursor up" ) );
7748     ctxt.register_action( "DOWN", to_translation( "Move cursor down" ) );
7749     ctxt.register_action( "LEFT", to_translation( "Previous item" ) );
7750     ctxt.register_action( "RIGHT", to_translation( "Next item" ) );
7751     ctxt.register_action( "PAGE_UP", to_translation( "Fast scroll up" ) );
7752     ctxt.register_action( "PAGE_DOWN", to_translation( "Fast scroll down" ) );
7753     ctxt.register_action( "SCROLL_ITEM_INFO_DOWN" );
7754     ctxt.register_action( "SCROLL_ITEM_INFO_UP" );
7755     ctxt.register_action( "NEXT_TAB" );
7756     ctxt.register_action( "PREV_TAB" );
7757     ctxt.register_action( "HELP_KEYBINDINGS" );
7758     ctxt.register_action( "QUIT" );
7759     ctxt.register_action( "FILTER" );
7760     ctxt.register_action( "RESET_FILTER" );
7761     ctxt.register_action( "EXAMINE" );
7762     ctxt.register_action( "COMPARE" );
7763     ctxt.register_action( "PRIORITY_INCREASE" );
7764     ctxt.register_action( "PRIORITY_DECREASE" );
7765     ctxt.register_action( "SORT" );
7766     ctxt.register_action( "TRAVEL_TO" );
7767 
7768     cata::optional<item_filter_type> filter_type;
7769 
7770     ui.on_redraw( [&]( const ui_adaptor & ) {
7771         reset_item_list_state( w_items_border, iInfoHeight, sort_radius );
7772 
7773         if( ground_items.empty() ) {
7774             wnoutrefresh( w_items_border );
7775             mvwprintz( w_items, point( 2, 10 ), c_white, _( "You don't see any items around you!" ) );
7776         } else {
7777             int iStartPos = 0;
7778             werase( w_items );
7779             calcStartPos( iStartPos, iActive, iMaxRows, iItemNum );
7780             int iNum = 0;
7781             bool high = false;
7782             bool low = false;
7783             int index = 0;
7784             int iCatSortOffset = 0;
7785 
7786             for( int i = 0; i < iStartPos; i++ ) {
7787                 if( !mSortCategory[i].empty() ) {
7788                     iNum++;
7789                 }
7790             }
7791             for( auto iter = filtered_items.begin(); iter != filtered_items.end(); ++index ) {
7792                 if( highPEnd > 0 && index < highPEnd + iCatSortOffset ) {
7793                     high = true;
7794                     low = false;
7795                 } else if( index >= lowPStart + iCatSortOffset ) {
7796                     high = false;
7797                     low = true;
7798                 } else {
7799                     high = false;
7800                     low = false;
7801                 }
7802 
7803                 if( iNum >= iStartPos && iNum < iStartPos + ( iMaxRows > iItemNum ? iItemNum : iMaxRows ) ) {
7804                     int iThisPage = 0;
7805                     if( !mSortCategory[iNum].empty() ) {
7806                         iCatSortOffset++;
7807                         mvwprintz( w_items, point( 1, iNum - iStartPos ), c_magenta, mSortCategory[iNum] );
7808                     } else {
7809                         if( iNum == iActive ) {
7810                             iThisPage = page_num;
7811                         }
7812                         std::string sText;
7813                         if( iter->vIG.size() > 1 ) {
7814                             sText += string_format( "[%d/%d] (%d) ", iThisPage + 1, iter->vIG.size(), iter->totalcount );
7815                         }
7816                         sText += iter->example->tname();
7817                         if( iter->vIG[iThisPage].count > 1 ) {
7818                             sText += string_format( "[%d]", iter->vIG[iThisPage].count );
7819                         }
7820 
7821                         nc_color col = c_light_green;
7822                         if( iNum != iActive ) {
7823                             if( high ) {
7824                                 col = c_yellow;
7825                             } else if( low ) {
7826                                 col = c_red;
7827                             } else {
7828                                 col = iter->example->color_in_inventory();
7829                             }
7830                         }
7831                         trim_and_print( w_items, point( 1, iNum - iStartPos ), width - 9, col, sText );
7832                         const int numw = iItemNum > 9 ? 2 : 1;
7833                         const int x = iter->vIG[iThisPage].pos.x;
7834                         const int y = iter->vIG[iThisPage].pos.y;
7835                         mvwprintz( w_items, point( width - 6 - numw, iNum - iStartPos ),
7836                                    iNum == iActive ? c_light_green : c_light_gray,
7837                                    "%*d %s", numw, rl_dist( point_zero, point( x, y ) ),
7838                                    direction_name_short( direction_from( point_zero, point( x, y ) ) ) );
7839                         ++iter;
7840                     }
7841                 } else {
7842                     ++iter;
7843                 }
7844                 iNum++;
7845             }
7846             iNum = 0;
7847             for( int i = 0; i < iActive; i++ ) {
7848                 if( !mSortCategory[i].empty() ) {
7849                     iNum++;
7850                 }
7851             }
7852             mvwprintz( w_items_border, point( ( width - 9 ) / 2 + ( iItemNum > 9 ? 0 : 1 ), 0 ),
7853                        c_light_green, " %*d", iItemNum > 9 ? 2 : 1, iItemNum > 0 ? iActive - iNum + 1 : 0 );
7854             wprintz( w_items_border, c_white, " / %*d ", iItemNum > 9 ? 2 : 1, iItemNum - iCatSortNum );
7855             werase( w_item_info );
7856 
7857             if( iItemNum > 0 && activeItem ) {
7858                 std::vector<iteminfo> vThisItem;
7859                 std::vector<iteminfo> vDummy;
7860                 activeItem->vIG[page_num].it->info( true, vThisItem );
7861 
7862                 item_info_data dummy( "", "", vThisItem, vDummy, iScrollPos );
7863                 dummy.without_getch = true;
7864                 dummy.without_border = true;
7865 
7866                 draw_item_info( w_item_info, dummy );
7867             }
7868             draw_scrollbar( w_items_border, iActive, iMaxRows, iItemNum, point_south );
7869             wnoutrefresh( w_items_border );
7870         }
7871 
7872         const bool bDrawLeft = ground_items.empty() || filtered_items.empty() || !activeItem || filter_type;
7873         draw_custom_border( w_item_info, bDrawLeft, 1, 1, 1, LINE_XXXO, LINE_XOXX, 1, 1 );
7874 
7875         if( iItemNum > 0 && activeItem ) {
7876             // print info window title: < item name >
7877             mvwprintw( w_item_info, point( 2, 0 ), "< " );
7878             trim_and_print( w_item_info, point( 4, 0 ), width - 8,
7879                             activeItem->vIG[page_num].it->color_in_inventory(),
7880                             activeItem->vIG[page_num].it->display_name() );
7881             wprintw( w_item_info, " >" );
7882         }
7883 
7884         wnoutrefresh( w_items );
7885         wnoutrefresh( w_item_info );
7886 
7887         if( filter_type ) {
7888             draw_item_filter_rules( w_item_info, 0, iInfoHeight - 1, filter_type.value() );
7889         }
7890     } );
7891 
7892     cata::optional<tripoint> trail_start;
7893     cata::optional<tripoint> trail_end;
7894     bool trail_end_x = false;
7895     shared_ptr_fast<draw_callback_t> trail_cb = create_trail_callback( trail_start, trail_end,
7896             trail_end_x );
7897     add_draw_callback( trail_cb );
7898 
7899     do {
7900         if( action == "COMPARE" && activeItem ) {
7901             game_menus::inv::compare( u, active_pos );
7902         } else if( action == "FILTER" ) {
7903             filter_type = item_filter_type::FILTER;
7904             ui.invalidate_ui();
7905             string_input_popup()
7906             .title( _( "Filter:" ) )
7907             .width( 55 )
7908             .description( _( "UP: history, CTRL-U: clear line, ESC: abort, ENTER: save" ) )
7909             .identifier( "item_filter" )
7910             .max_length( 256 )
7911             .edit( sFilter );
7912             refilter = true;
7913             addcategory = !sort_radius;
7914             uistate.list_item_filter_active = !sFilter.empty();
7915             filter_type = cata::nullopt;
7916         } else if( action == "RESET_FILTER" ) {
7917             sFilter.clear();
7918             filtered_items = ground_items;
7919             refilter = true;
7920             uistate.list_item_filter_active = false;
7921             addcategory = !sort_radius;
7922         } else if( action == "EXAMINE" && !filtered_items.empty() && activeItem ) {
7923             std::vector<iteminfo> vThisItem;
7924             std::vector<iteminfo> vDummy;
7925             activeItem->vIG[page_num].it->info( true, vThisItem );
7926 
7927             item_info_data info_data( activeItem->vIG[page_num].it->tname(),
7928                                       activeItem->vIG[page_num].it->type_name(), vThisItem,
7929                                       vDummy );
7930             info_data.handle_scrolling = true;
7931 
7932             draw_item_info( [&]() -> catacurses::window {
7933                 return catacurses::newwin( TERMY, width - 5, point_zero );
7934             }, info_data );
7935         } else if( action == "PRIORITY_INCREASE" ) {
7936             filter_type = item_filter_type::HIGH_PRIORITY;
7937             ui.invalidate_ui();
7938             list_item_upvote = string_input_popup()
7939                                .title( _( "High Priority:" ) )
7940                                .width( 55 )
7941                                .text( list_item_upvote )
7942                                .description( _( "UP: history, CTRL-U clear line, ESC: abort, ENTER: save" ) )
7943                                .identifier( "list_item_priority" )
7944                                .max_length( 256 )
7945                                .query_string();
7946             refilter = true;
7947             addcategory = !sort_radius;
7948             uistate.list_item_priority_active = !list_item_upvote.empty();
7949             filter_type = cata::nullopt;
7950         } else if( action == "PRIORITY_DECREASE" ) {
7951             filter_type = item_filter_type::LOW_PRIORITY;
7952             ui.invalidate_ui();
7953             list_item_downvote = string_input_popup()
7954                                  .title( _( "Low Priority:" ) )
7955                                  .width( 55 )
7956                                  .text( list_item_downvote )
7957                                  .description( _( "UP: history, CTRL-U clear line, ESC: abort, ENTER: save" ) )
7958                                  .identifier( "list_item_downvote" )
7959                                  .max_length( 256 )
7960                                  .query_string();
7961             refilter = true;
7962             addcategory = !sort_radius;
7963             uistate.list_item_downvote_active = !list_item_downvote.empty();
7964             filter_type = cata::nullopt;
7965         } else if( action == "SORT" ) {
7966             if( sort_radius ) {
7967                 sort_radius = false;
7968                 addcategory = true;
7969                 uistate.list_item_sort = 2; // list is sorted by category
7970             } else {
7971                 sort_radius = true;
7972                 uistate.list_item_sort = 1; // list is sorted by distance
7973             }
7974             highPEnd = -1;
7975             lowPStart = -1;
7976             iCatSortNum = 0;
7977 
7978             mSortCategory.clear();
7979             refilter = true;
7980         } else if( action == "TRAVEL_TO" && activeItem ) {
7981             if( !u.sees( u.pos() + active_pos ) ) {
7982                 add_msg( _( "You can't see that destination." ) );
7983             }
7984             auto route = m.route( u.pos(), u.pos() + active_pos, u.get_pathfinding_settings(),
7985                                   u.get_path_avoid() );
7986             if( route.size() > 1 ) {
7987                 route.pop_back();
7988                 u.set_destination( route );
7989                 break;
7990             } else {
7991                 add_msg( m_info, _( "You can't travel there." ) );
7992             }
7993         }
7994         if( uistate.list_item_sort == 1 ) {
7995             ground_items = item_list;
7996         } else if( uistate.list_item_sort == 2 ) {
7997             std::sort( ground_items.begin(), ground_items.end(), map_item_stack::map_item_stack_sort );
7998         }
7999 
8000         if( refilter ) {
8001             refilter = false;
8002             filtered_items = filter_item_stacks( ground_items, sFilter );
8003             highPEnd = list_filter_high_priority( filtered_items, list_item_upvote );
8004             lowPStart = list_filter_low_priority( filtered_items, highPEnd, list_item_downvote );
8005             iActive = 0;
8006             page_num = 0;
8007             iItemNum = filtered_items.size();
8008         }
8009 
8010         if( addcategory ) {
8011             addcategory = false;
8012             iCatSortNum = 0;
8013             mSortCategory.clear();
8014             if( highPEnd > 0 ) {
8015                 mSortCategory[0] = _( "HIGH PRIORITY" );
8016                 iCatSortNum++;
8017             }
8018             std::string last_cat_name;
8019             for( int i = std::max( 0, highPEnd );
8020                  i < std::min( lowPStart, static_cast<int>( filtered_items.size() ) ); i++ ) {
8021                 const std::string &cat_name =
8022                     filtered_items[i].example->get_category_of_contents().name();
8023                 if( cat_name != last_cat_name ) {
8024                     mSortCategory[i + iCatSortNum++] = cat_name;
8025                     last_cat_name = cat_name;
8026                 }
8027             }
8028             if( lowPStart < static_cast<int>( filtered_items.size() ) ) {
8029                 mSortCategory[lowPStart + iCatSortNum++] = _( "LOW PRIORITY" );
8030             }
8031             if( !mSortCategory[0].empty() ) {
8032                 iActive++;
8033             }
8034             iItemNum = static_cast<int>( filtered_items.size() ) + iCatSortNum;
8035         }
8036 
8037         const int item_info_scroll_lines = catacurses::getmaxy( w_item_info ) - 4;
8038         const int scroll_rate = iItemNum > 20 ? 10 : 3;
8039 
8040         if( action == "UP" ) {
8041             do {
8042                 iActive--;
8043             } while( !mSortCategory[iActive].empty() );
8044             iScrollPos = 0;
8045             page_num = 0;
8046             if( iActive < 0 ) {
8047                 iActive = iItemNum - 1;
8048             }
8049         } else if( action == "DOWN" ) {
8050             do {
8051                 iActive++;
8052             } while( !mSortCategory[iActive].empty() );
8053             iScrollPos = 0;
8054             page_num = 0;
8055             if( iActive >= iItemNum ) {
8056                 iActive = mSortCategory[0].empty() ? 0 : 1;
8057             }
8058         } else if( action == "PAGE_DOWN" ) {
8059             iScrollPos = 0;
8060             page_num = 0;
8061             if( iActive == iItemNum - 1 ) {
8062                 iActive = mSortCategory[0].empty() ? 0 : 1;
8063             } else if( iActive + scroll_rate >= iItemNum ) {
8064                 iActive = iItemNum - 1;
8065             } else {
8066                 iActive += +scroll_rate - 1;
8067                 do {
8068                     iActive++;
8069                 } while( !mSortCategory[iActive].empty() );
8070             }
8071         } else if( action == "PAGE_UP" ) {
8072             iScrollPos = 0;
8073             page_num = 0;
8074             if( mSortCategory[0].empty() ? iActive == 0 : iActive == 1 ) {
8075                 iActive = iItemNum - 1;
8076             } else if( iActive <= scroll_rate ) {
8077                 iActive = mSortCategory[0].empty() ? 0 : 1;
8078             } else {
8079                 iActive += -scroll_rate + 1;
8080                 do {
8081                     iActive--;
8082                 } while( !mSortCategory[iActive].empty() );
8083             }
8084         } else if( action == "RIGHT" ) {
8085             if( !filtered_items.empty() && activeItem ) {
8086                 if( ++page_num >= static_cast<int>( activeItem->vIG.size() ) ) {
8087                     page_num = activeItem->vIG.size() - 1;
8088                 }
8089             }
8090         } else if( action == "LEFT" ) {
8091             page_num = std::max( 0, page_num - 1 );
8092         } else if( action == "SCROLL_ITEM_INFO_UP" ) {
8093             iScrollPos -= item_info_scroll_lines;
8094         } else if( action == "SCROLL_ITEM_INFO_DOWN" ) {
8095             iScrollPos += item_info_scroll_lines;
8096         } else if( action == "NEXT_TAB" || action == "PREV_TAB" ) {
8097             u.view_offset = stored_view_offset;
8098             return game::vmenu_ret::CHANGE_TAB;
8099         }
8100 
8101         active_pos = tripoint_zero;
8102         activeItem = nullptr;
8103 
8104         if( mSortCategory[iActive].empty() ) {
8105             auto iter = filtered_items.begin();
8106             for( int iNum = 0; iter != filtered_items.end() && iNum < iActive; iNum++ ) {
8107                 if( mSortCategory[iNum].empty() ) {
8108                     ++iter;
8109                 }
8110             }
8111             if( iter != filtered_items.end() ) {
8112                 active_pos = iter->vIG[page_num].pos;
8113                 activeItem = &( *iter );
8114             }
8115         }
8116 
8117         if( activeItem ) {
8118             centerlistview( active_pos, width );
8119             trail_start = u.pos();
8120             trail_end = u.pos() + active_pos;
8121             // Actually accessed from the terrain overlay callback `trail_cb` in the
8122             // call to `ui_manager::redraw`.
8123             //NOLINTNEXTLINE(clang-analyzer-deadcode.DeadStores)
8124             trail_end_x = true;
8125         } else {
8126             u.view_offset = stored_view_offset;
8127             trail_start = trail_end = cata::nullopt;
8128         }
8129         invalidate_main_ui_adaptor();
8130 
8131         ui_manager::redraw();
8132 
8133         action = ctxt.handle_input();
8134     } while( action != "QUIT" );
8135 
8136     u.view_offset = stored_view_offset;
8137     return game::vmenu_ret::QUIT;
8138 }
8139 
list_monsters(const std::vector<Creature * > & monster_list)8140 game::vmenu_ret game::list_monsters( const std::vector<Creature *> &monster_list )
8141 {
8142     const int iInfoHeight = 15;
8143     const int width = 45;
8144     int offsetX = 0;
8145     int iMaxRows = 0;
8146 
8147     catacurses::window w_monsters;
8148     catacurses::window w_monsters_border;
8149     catacurses::window w_monster_info;
8150     catacurses::window w_monster_info_border;
8151 
8152     Creature *cCurMon = nullptr;
8153     tripoint iActivePos;
8154 
8155     bool hide_ui = false;
8156 
8157     ui_adaptor ui;
8158     ui.on_screen_resize( [&]( ui_adaptor & ui ) {
8159         if( hide_ui ) {
8160             ui.position( point_zero, point_zero );
8161         } else {
8162             offsetX = TERMX - width;
8163             iMaxRows = TERMY - iInfoHeight - 1;
8164 
8165             w_monsters = catacurses::newwin( iMaxRows, width - 2, point( offsetX + 1,
8166                                              1 ) );
8167             w_monsters_border = catacurses::newwin( iMaxRows + 1, width, point( offsetX,
8168                                                     0 ) );
8169             w_monster_info = catacurses::newwin( iInfoHeight - 2, width - 2,
8170                                                  point( offsetX + 1, TERMY - iInfoHeight + 1 ) );
8171             w_monster_info_border = catacurses::newwin( iInfoHeight, width, point( offsetX,
8172                                     TERMY - iInfoHeight ) );
8173 
8174             if( cCurMon ) {
8175                 centerlistview( iActivePos, width );
8176             }
8177 
8178             ui.position( point( offsetX, 0 ), point( width, TERMY ) );
8179         }
8180     } );
8181     ui.mark_resize();
8182 
8183     const int max_gun_range = u.weapon.gun_range( &u );
8184 
8185     const tripoint stored_view_offset = u.view_offset;
8186     u.view_offset = tripoint_zero;
8187 
8188     int iActive = 0; // monster index that we're looking at
8189 
8190     std::string action;
8191     input_context ctxt( "LIST_MONSTERS" );
8192     ctxt.register_action( "UP", to_translation( "Move cursor up" ) );
8193     ctxt.register_action( "DOWN", to_translation( "Move cursor down" ) );
8194     ctxt.register_action( "PAGE_UP", to_translation( "Fast scroll up" ) );
8195     ctxt.register_action( "PAGE_DOWN", to_translation( "Fast scroll down" ) );
8196     ctxt.register_action( "NEXT_TAB" );
8197     ctxt.register_action( "PREV_TAB" );
8198     ctxt.register_action( "SAFEMODE_BLACKLIST_ADD" );
8199     ctxt.register_action( "SAFEMODE_BLACKLIST_REMOVE" );
8200     ctxt.register_action( "QUIT" );
8201     if( bVMonsterLookFire ) {
8202         ctxt.register_action( "look" );
8203         ctxt.register_action( "fire" );
8204     }
8205     ctxt.register_action( "HELP_KEYBINDINGS" );
8206 
8207     // first integer is the row the attitude category string is printed in the menu
8208     std::map<int, Creature::Attitude> mSortCategory;
8209 
8210     for( int i = 0, last_attitude = -1; i < static_cast<int>( monster_list.size() ); i++ ) {
8211         const Creature::Attitude attitude = monster_list[i]->attitude_to( u );
8212         if( static_cast<int>( attitude ) != last_attitude ) {
8213             mSortCategory[i + mSortCategory.size()] = attitude;
8214             last_attitude = static_cast<int>( attitude );
8215         }
8216     }
8217 
8218     ui.on_redraw( [&]( const ui_adaptor & ) {
8219         if( !hide_ui ) {
8220             draw_custom_border( w_monsters_border, 1, 1, 1, 1, 1, 1, LINE_XOXO, LINE_XOXO );
8221             draw_custom_border( w_monster_info_border, 1, 1, 1, 1, LINE_XXXO, LINE_XOXX, 1, 1 );
8222 
8223             mvwprintz( w_monsters_border, point( 2, 0 ), c_light_green, "<Tab> " );
8224             wprintz( w_monsters_border, c_white, _( "Monsters" ) );
8225 
8226             if( monster_list.empty() ) {
8227                 werase( w_monsters );
8228                 mvwprintz( w_monsters, point( 2, iMaxRows / 3 ), c_white,
8229                            _( "You don't see any monsters around you!" ) );
8230             } else {
8231                 werase( w_monsters );
8232 
8233                 const int iNumMonster = monster_list.size();
8234                 const int iMenuSize = monster_list.size() + mSortCategory.size();
8235 
8236                 const int numw = iNumMonster > 999 ? 4 :
8237                                  iNumMonster > 99  ? 3 :
8238                                  iNumMonster > 9   ? 2 : 1;
8239 
8240                 // given the currently selected monster iActive. get the selected row
8241                 int iSelPos = iActive;
8242                 for( auto &ia : mSortCategory ) {
8243                     int index = ia.first;
8244                     if( index <= iSelPos ) {
8245                         ++iSelPos;
8246                     } else {
8247                         break;
8248                     }
8249                 }
8250                 int iStartPos = 0;
8251                 // use selected row get the start row
8252                 calcStartPos( iStartPos, iSelPos, iMaxRows - 1, iMenuSize );
8253 
8254                 // get first visible monster and category
8255                 int iCurMon = iStartPos;
8256                 auto CatSortIter = mSortCategory.cbegin();
8257                 while( CatSortIter != mSortCategory.cend() && CatSortIter->first < iStartPos ) {
8258                     ++CatSortIter;
8259                     --iCurMon;
8260                 }
8261 
8262                 const int endY = std::min<int>( iMaxRows - 1, iMenuSize );
8263                 for( int y = 0; y < endY; ++y ) {
8264                     if( CatSortIter != mSortCategory.cend() ) {
8265                         const int iCurPos = iStartPos + y;
8266                         const int iCatPos = CatSortIter->first;
8267                         if( iCurPos == iCatPos ) {
8268                             const std::string cat_name = Creature::get_attitude_ui_data(
8269                                                              CatSortIter->second ).first.translated();
8270                             mvwprintz( w_monsters, point( 1, y ), c_magenta, cat_name );
8271                             ++CatSortIter;
8272                             continue;
8273                         }
8274                     }
8275                     // select current monster
8276                     Creature *critter = monster_list[iCurMon];
8277                     const bool selected = iCurMon == iActive;
8278                     ++iCurMon;
8279                     if( critter->sees( u ) ) {
8280                         mvwprintz( w_monsters, point( 0, y ), c_yellow, "!" );
8281                     }
8282                     bool is_npc = false;
8283                     const monster *m = dynamic_cast<monster *>( critter );
8284                     const npc     *p = dynamic_cast<npc *>( critter );
8285                     nc_color name_color = critter->basic_symbol_color();
8286 
8287                     if( selected ) {
8288                         name_color = hilite( name_color );
8289                     }
8290 
8291                     if( m != nullptr ) {
8292                         trim_and_print( w_monsters, point( 1, y ), width - 26, name_color, m->name() );
8293                     } else {
8294                         trim_and_print( w_monsters, point( 1, y ), width - 26, name_color, critter->disp_name() );
8295                         is_npc = true;
8296                     }
8297 
8298                     if( selected && !get_safemode().empty() ) {
8299                         const std::string monName = is_npc ? get_safemode().npc_type_name() : m->name();
8300 
8301                         std::string sSafemode;
8302                         if( get_safemode().has_rule( monName, Creature::Attitude::ANY ) ) {
8303                             sSafemode = _( "<R>emove from safemode Blacklist" );
8304                         } else {
8305                             sSafemode = _( "<A>dd to safemode Blacklist" );
8306                         }
8307 
8308                         shortcut_print( w_monsters, point( 2, getmaxy( w_monsters ) - 1 ),
8309                                         c_white, c_light_green, sSafemode );
8310                     }
8311 
8312                     nc_color color = c_white;
8313                     std::string sText;
8314 
8315                     if( m != nullptr ) {
8316                         m->get_HP_Bar( color, sText );
8317                     } else {
8318                         std::tie( sText, color ) =
8319                             ::get_hp_bar( critter->get_hp(), critter->get_hp_max(), false );
8320                     }
8321                     mvwprintz( w_monsters, point( width - 25, y ), color, sText );
8322                     const int bar_max_width = 5;
8323                     const int bar_width = utf8_width( sText );
8324                     for( int i = 0; i < bar_max_width - bar_width; ++i ) {
8325                         mvwprintz( w_monsters, point( width - 21 - i, y ), c_white, "." );
8326                     }
8327 
8328                     if( m != nullptr ) {
8329                         const auto att = m->get_attitude();
8330                         sText = att.first;
8331                         color = att.second;
8332                     } else if( p != nullptr ) {
8333                         sText = npc_attitude_name( p->get_attitude() );
8334                         color = p->symbol_color();
8335                     }
8336                     mvwprintz( w_monsters, point( width - 19, y ), color, sText );
8337 
8338                     const int mon_dist = rl_dist( u.pos(), critter->pos() );
8339                     const int numd = mon_dist > 999 ? 4 :
8340                                      mon_dist > 99 ? 3 :
8341                                      mon_dist > 9 ? 2 : 1;
8342 
8343                     trim_and_print( w_monsters, point( width - ( 8 + numd ), y ), 6 + numd,
8344                                     selected ? c_light_green : c_light_gray,
8345                                     "%*d %s",
8346                                     numd, mon_dist,
8347                                     direction_name_short( direction_from( u.pos(), critter->pos() ) ) );
8348                 }
8349 
8350                 mvwprintz( w_monsters_border, point( ( width / 2 ) - numw - 2, 0 ), c_light_green, " %*d", numw,
8351                            iActive + 1 );
8352                 wprintz( w_monsters_border, c_white, " / %*d ", numw, static_cast<int>( monster_list.size() ) );
8353 
8354                 werase( w_monster_info );
8355                 if( cCurMon ) {
8356                     cCurMon->print_info( w_monster_info, 1, iInfoHeight - 3, 1 );
8357                 }
8358 
8359                 draw_custom_border( w_monster_info_border, 1, 1, 1, 1, LINE_XXXO, LINE_XOXX, 1, 1 );
8360 
8361                 if( bVMonsterLookFire ) {
8362                     mvwprintw( w_monster_info_border, point_east, "< " );
8363                     wprintz( w_monster_info_border, c_light_green, ctxt.press_x( "look" ) );
8364                     wprintz( w_monster_info_border, c_light_gray, " %s", _( "to look around" ) );
8365 
8366                     if( cCurMon && rl_dist( u.pos(), cCurMon->pos() ) <= max_gun_range ) {
8367                         wprintw( w_monster_info_border, " " );
8368                         wprintz( w_monster_info_border, c_light_green, ctxt.press_x( "fire" ) );
8369                         wprintz( w_monster_info_border, c_light_gray, " %s", _( "to shoot" ) );
8370                     }
8371                     wprintw( w_monster_info_border, " >" );
8372                 }
8373 
8374                 draw_scrollbar( w_monsters_border, iActive, iMaxRows, static_cast<int>( monster_list.size() ),
8375                                 point_south );
8376             }
8377 
8378             wnoutrefresh( w_monsters_border );
8379             wnoutrefresh( w_monster_info_border );
8380             wnoutrefresh( w_monsters );
8381             wnoutrefresh( w_monster_info );
8382         }
8383     } );
8384 
8385     cata::optional<tripoint> trail_start;
8386     cata::optional<tripoint> trail_end;
8387     bool trail_end_x = false;
8388     shared_ptr_fast<draw_callback_t> trail_cb = create_trail_callback( trail_start, trail_end,
8389             trail_end_x );
8390     add_draw_callback( trail_cb );
8391     const int recmax = static_cast<int>( monster_list.size() );
8392     const int scroll_rate = recmax > 20 ? 10 : 3;
8393 
8394     do {
8395         if( action == "UP" ) {
8396             iActive--;
8397             if( iActive < 0 ) {
8398                 if( monster_list.empty() ) {
8399                     iActive = 0;
8400                 } else {
8401                     iActive = recmax - 1;
8402                 }
8403             }
8404         } else if( action == "DOWN" ) {
8405             iActive++;
8406             if( iActive >= recmax ) {
8407                 iActive = 0;
8408             }
8409         } else if( action == "PAGE_UP" ) {
8410             if( iActive == 0 ) {
8411                 iActive = recmax - 1;
8412             } else if( iActive <= scroll_rate ) {
8413                 iActive = 0;
8414             } else {
8415                 iActive += -scroll_rate;
8416             }
8417         } else if( action == "PAGE_DOWN" ) {
8418             if( iActive == recmax - 1 ) {
8419                 iActive = 0;
8420             } else if( iActive + scroll_rate >= recmax ) {
8421                 iActive = recmax - 1;
8422             } else {
8423                 iActive += +scroll_rate;
8424             }
8425         } else if( action == "NEXT_TAB" || action == "PREV_TAB" ) {
8426             u.view_offset = stored_view_offset;
8427             return game::vmenu_ret::CHANGE_TAB;
8428         } else if( action == "SAFEMODE_BLACKLIST_REMOVE" ) {
8429             const monster *m = dynamic_cast<monster *>( cCurMon );
8430             const std::string monName = ( m != nullptr ) ? m->name() : "human";
8431 
8432             if( get_safemode().has_rule( monName, Creature::Attitude::ANY ) ) {
8433                 get_safemode().remove_rule( monName, Creature::Attitude::ANY );
8434             }
8435         } else if( action == "SAFEMODE_BLACKLIST_ADD" ) {
8436             if( !get_safemode().empty() ) {
8437                 const monster *m = dynamic_cast<monster *>( cCurMon );
8438                 const std::string monName = ( m != nullptr ) ? m->name() : "human";
8439 
8440                 get_safemode().add_rule( monName, Creature::Attitude::ANY, get_option<int>( "SAFEMODEPROXIMITY" ),
8441                                          rule_state::BLACKLISTED );
8442             }
8443         } else if( action == "look" ) {
8444             hide_ui = true;
8445             ui.mark_resize();
8446             look_around();
8447             hide_ui = false;
8448             ui.mark_resize();
8449         } else if( action == "fire" ) {
8450             if( cCurMon != nullptr && rl_dist( u.pos(), cCurMon->pos() ) <= max_gun_range ) {
8451                 u.last_target = shared_from( *cCurMon );
8452                 u.recoil = MAX_RECOIL;
8453                 u.view_offset = stored_view_offset;
8454                 return game::vmenu_ret::FIRE;
8455             }
8456         }
8457 
8458         if( iActive >= 0 && static_cast<size_t>( iActive ) < monster_list.size() ) {
8459             cCurMon = monster_list[iActive];
8460             iActivePos = cCurMon->pos() - u.pos();
8461             centerlistview( iActivePos, width );
8462             trail_start = u.pos();
8463             trail_end = cCurMon->pos();
8464             // Actually accessed from the terrain overlay callback `trail_cb` in the
8465             // call to `ui_manager::redraw`.
8466             //NOLINTNEXTLINE(clang-analyzer-deadcode.DeadStores)
8467             trail_end_x = false;
8468         } else {
8469             cCurMon = nullptr;
8470             iActivePos = tripoint_zero;
8471             u.view_offset = stored_view_offset;
8472             trail_start = trail_end = cata::nullopt;
8473         }
8474         invalidate_main_ui_adaptor();
8475 
8476         ui_manager::redraw();
8477 
8478         action = ctxt.handle_input();
8479     } while( action != "QUIT" );
8480 
8481     u.view_offset = stored_view_offset;
8482 
8483     return game::vmenu_ret::QUIT;
8484 }
8485 
drop()8486 void game::drop()
8487 {
8488     u.drop( game_menus::inv::multidrop( u ), u.pos() );
8489 }
8490 
drop_in_direction()8491 void game::drop_in_direction()
8492 {
8493     if( const cata::optional<tripoint> pnt = choose_adjacent( _( "Drop where?" ) ) ) {
8494         u.drop( game_menus::inv::multidrop( u ), *pnt );
8495     }
8496 }
8497 
8498 // Used to set up the first Hotkey in the display set
get_initial_hotkey(const size_t menu_index)8499 static cata::optional<input_event> get_initial_hotkey( const size_t menu_index )
8500 {
8501     return menu_index == 0
8502            ? hotkey_for_action( ACTION_BUTCHER, /*maximum_modifier_count=*/1 )
8503            : cata::nullopt;
8504 }
8505 
8506 // Returns a vector of pairs.
8507 //    Pair.first is the iterator to the first item with a unique tname.
8508 //    Pair.second is the number of equivalent items per unique tname
8509 // There are options for optimization here, but the function is hit infrequently
8510 // enough that optimizing now is not a useful time expenditure.
generate_butcher_stack_display(const std::vector<map_stack::iterator> & its)8511 static std::vector<std::pair<map_stack::iterator, int>> generate_butcher_stack_display(
8512             const std::vector<map_stack::iterator> &its )
8513 {
8514     std::vector<std::pair<map_stack::iterator, int>> result;
8515     std::vector<std::string> result_strings;
8516     result.reserve( its.size() );
8517     result_strings.reserve( its.size() );
8518 
8519     for( const map_stack::iterator &it : its ) {
8520         const std::string tname = it->tname();
8521         size_t s = 0;
8522         // Search for the index with a string equivalent to tname
8523         for( ; s < result_strings.size(); ++s ) {
8524             if( result_strings[s] == tname ) {
8525                 break;
8526             }
8527         }
8528         // If none is found, this is a unique tname so we need to add
8529         // the tname to string vector, and make an empty result pair.
8530         // Has the side effect of making 's' a valid index
8531         if( s == result_strings.size() ) {
8532             // make a new entry
8533             result.emplace_back( it, 0 );
8534             // Also push new entry string
8535             result_strings.push_back( tname );
8536         }
8537         // Increase count result pair at index s
8538         ++result[s].second;
8539     }
8540 
8541     return result;
8542 }
8543 
8544 // Corpses are always individual items
8545 // Just add them individually to the menu
add_corpses(uilist & menu,const std::vector<map_stack::iterator> & its,size_t & menu_index)8546 static void add_corpses( uilist &menu, const std::vector<map_stack::iterator> &its,
8547                          size_t &menu_index )
8548 {
8549     cata::optional<input_event> hotkey = get_initial_hotkey( menu_index );
8550 
8551     for( const map_stack::iterator &it : its ) {
8552         menu.addentry( menu_index++, true, hotkey, it->get_mtype()->nname() );
8553         hotkey = cata::nullopt;
8554     }
8555 }
8556 
8557 // Salvagables stack so we need to pass in a stack vector rather than an item index vector
add_salvagables(uilist & menu,const std::vector<std::pair<map_stack::iterator,int>> & stacks,size_t & menu_index,const salvage_actor & salvage_iuse)8558 static void add_salvagables( uilist &menu,
8559                              const std::vector<std::pair<map_stack::iterator, int>> &stacks,
8560                              size_t &menu_index, const salvage_actor &salvage_iuse )
8561 {
8562     if( !stacks.empty() ) {
8563         cata::optional<input_event> hotkey = get_initial_hotkey( menu_index );
8564 
8565         for( const auto &stack : stacks ) {
8566             const item &it = *stack.first;
8567 
8568             //~ Name and number of items listed for cutting up
8569             const auto &msg = string_format( pgettext( "butchery menu", "Cut up %s (%d)" ),
8570                                              it.tname(), stack.second );
8571             menu.addentry_col( menu_index++, true, hotkey, msg,
8572                                to_string_clipped( time_duration::from_turns( salvage_iuse.time_to_cut_up( it ) / 100 ) ) );
8573             hotkey = cata::nullopt;
8574         }
8575     }
8576 }
8577 
8578 // Disassemblables stack so we need to pass in a stack vector rather than an item index vector
add_disassemblables(uilist & menu,const std::vector<std::pair<map_stack::iterator,int>> & stacks,size_t & menu_index)8579 static void add_disassemblables( uilist &menu,
8580                                  const std::vector<std::pair<map_stack::iterator, int>> &stacks, size_t &menu_index )
8581 {
8582     if( !stacks.empty() ) {
8583         cata::optional<input_event> hotkey = get_initial_hotkey( menu_index );
8584 
8585         for( const auto &stack : stacks ) {
8586             const item &it = *stack.first;
8587 
8588             //~ Name, number of items and time to complete disassembling
8589             const auto &msg = string_format( pgettext( "butchery menu", "%s (%d)" ),
8590                                              it.tname(), stack.second );
8591             recipe uncraft_recipe;
8592             if( it.typeId() == itype_disassembly ) {
8593                 uncraft_recipe = it.get_making();
8594             } else {
8595                 uncraft_recipe = recipe_dictionary::get_uncraft( it.typeId() );
8596             }
8597             menu.addentry_col( menu_index++, true, hotkey, msg,
8598                                to_string_clipped( uncraft_recipe.time_to_craft( get_player_character(),
8599                                                   recipe_time_flag::ignore_proficiencies ) ) );
8600             hotkey = cata::nullopt;
8601         }
8602     }
8603 }
8604 
8605 // Butchery sub-menu and time calculation
butcher_submenu(const std::vector<map_stack::iterator> & corpses,int index=-1)8606 static void butcher_submenu( const std::vector<map_stack::iterator> &corpses, int index = -1 )
8607 {
8608     avatar &player_character = get_avatar();
8609     auto cut_time = [&]( butcher_type bt ) {
8610         int time_to_cut = 0;
8611         if( index != -1 ) {
8612             const mtype &corpse = *corpses[index]->get_mtype();
8613             const float factor = corpse.harvest->get_butchery_requirements().get_fastest_requirements(
8614                                      player_character.crafting_inventory(),
8615                                      corpse.size, bt ).first;
8616             time_to_cut = butcher_time_to_cut( player_character, *corpses[index], bt ) * factor;
8617         } else {
8618             for( const map_stack::iterator &it : corpses ) {
8619                 const mtype &corpse = *it->get_mtype();
8620                 const float factor = corpse.harvest->get_butchery_requirements().get_fastest_requirements(
8621                                          player_character.crafting_inventory(),
8622                                          corpse.size, bt ).first;
8623                 time_to_cut += butcher_time_to_cut( player_character, *it, bt ) * factor;
8624             }
8625         }
8626         return to_string_clipped( time_duration::from_moves( time_to_cut ) );
8627     };
8628     const bool enough_light = player_character.fine_detail_vision_mod() <= 4;
8629 
8630     const int factor = player_character.max_quality( quality_id( "BUTCHER" ) );
8631     const std::string msgFactor = factor > INT_MIN
8632                                   ? string_format( _( "Your best tool has <color_cyan>%d butchering</color>." ), factor )
8633                                   :  _( "You have no butchering tool." );
8634 
8635     const int factorD = player_character.max_quality( quality_id( "CUT_FINE" ) );
8636     const std::string msgFactorD = factorD > INT_MIN
8637                                    ? string_format( _( "Your best tool has <color_cyan>%d fine cutting</color>." ), factorD )
8638                                    :  _( "You have no fine cutting tool." );
8639 
8640     bool has_blood = false;
8641     bool has_skin = false;
8642     bool has_organs = false;
8643 
8644     if( index != -1 ) {
8645         const mtype *dead_mon = corpses[index]->get_mtype();
8646         if( dead_mon ) {
8647             for( const harvest_entry &entry : dead_mon->harvest.obj() ) {
8648                 if( entry.type == "skin" && !corpses[index]->has_flag( flag_SKINNED ) ) {
8649                     has_skin = true;
8650                 }
8651                 if( entry.type == "offal" && !( corpses[index]->has_flag( flag_QUARTERED ) ||
8652                                                 corpses[index]->has_flag( flag_FIELD_DRESS ) ||
8653                                                 corpses[index]->has_flag( flag_FIELD_DRESS_FAILED ) ) ) {
8654                     has_organs = true;
8655                 }
8656                 if( entry.type == "blood" && !( corpses[index]->has_flag( flag_QUARTERED ) ||
8657                                                 corpses[index]->has_flag( flag_FIELD_DRESS ) ||
8658                                                 corpses[index]->has_flag( flag_FIELD_DRESS_FAILED ) || corpses[index]->has_flag( flag_BLED ) ) ) {
8659                     has_blood = true;
8660                 }
8661             }
8662         }
8663     }
8664 
8665     uilist smenu;
8666     smenu.desc_enabled = true;
8667     smenu.text = _( "Choose type of butchery:" );
8668 
8669     const std::string cannot_see = colorize( _( "can't see!" ), c_red );
8670 
8671     smenu.addentry_col( static_cast<int>( butcher_type::QUICK ), enough_light,
8672                         'B', _( "Quick butchery" ),
8673                         enough_light ? cut_time( butcher_type::QUICK ) : cannot_see,
8674                         string_format( "%s  %s",
8675                                        _( "This technique is used when you are in a hurry, "
8676                                           "but still want to harvest something from the corpse. "
8677                                           " Yields are lower as you don't try to be precise, "
8678                                           "but it's useful if you don't want to set up a workshop.  "
8679                                           "Prevents zombies from raising." ),
8680                                        msgFactor ) );
8681     smenu.addentry_col( static_cast<int>( butcher_type::FULL ), enough_light,
8682                         'b', _( "Full butchery" ),
8683                         enough_light ? cut_time( butcher_type::FULL ) : cannot_see,
8684                         string_format( "%s  %s",
8685                                        _( "This technique is used to properly butcher a corpse, "
8686                                           "and requires a rope & a tree or a butchering rack, "
8687                                           "a flat surface (for ex. a table, a leather tarp, etc.) "
8688                                           "and good tools.  Yields are plentiful and varied, "
8689                                           "but it is time consuming." ),
8690                                        msgFactor ) );
8691     smenu.addentry_col( static_cast<int>( butcher_type::FIELD_DRESS ), enough_light && has_organs,
8692                         'f', _( "Field dress corpse" ),
8693                         enough_light ? ( has_organs ? cut_time( butcher_type::FIELD_DRESS ) :
8694                                          colorize( _( "has no organs" ), c_red ) ) : cannot_see,
8695                         string_format( "%s  %s",
8696                                        _( "Technique that involves removing internal organs and "
8697                                           "viscera to protect the corpse from rotting from inside.  "
8698                                           "Yields internal organs.  Carcass will be lighter and will "
8699                                           "stay fresh longer.  Can be combined with other methods for "
8700                                           "better effects." ),
8701                                        msgFactor ) );
8702     smenu.addentry_col( static_cast<int>( butcher_type::SKIN ), enough_light && has_skin,
8703                         's', _( "Skin corpse" ),
8704                         enough_light ? ( has_skin ? cut_time( butcher_type::SKIN ) : colorize( _( "has no skin" ),
8705                                          c_red ) ) : cannot_see,
8706                         string_format( "%s  %s",
8707                                        _( "Skinning a corpse is an involved and careful process that "
8708                                           "usually takes some time.  You need skill and an appropriately "
8709                                           "sharp and precise knife to do a good job.  Some corpses are "
8710                                           "too small to yield a full-sized hide and will instead produce "
8711                                           "scraps that can be used in other ways." ),
8712                                        msgFactor ) );
8713     smenu.addentry_col( static_cast<int>( butcher_type::BLEED ), enough_light && has_blood,
8714                         'l', _( "Bleed corpse" ),
8715                         enough_light ? ( has_blood ? cut_time( butcher_type::BLEED ) : colorize( _( "has no blood" ),
8716                                          c_red ) ) : cannot_see,
8717                         string_format( "%s  %s",
8718                                        _( "Bleeding involves severing the carotid arteries and jugular "
8719                                           "veins, or the blood vessels from which they arise.  "
8720                                           "You need skill and an appropriately sharp and precise knife "
8721                                           "to do a good job." ),
8722                                        msgFactor ) );
8723     smenu.addentry_col( static_cast<int>( butcher_type::QUARTER ), enough_light,
8724                         'k', _( "Quarter corpse" ),
8725                         enough_light ? cut_time( butcher_type::QUARTER ) : cannot_see,
8726                         string_format( "%s  %s",
8727                                        _( "By quartering a previously field dressed corpse you will "
8728                                           "acquire four parts with reduced weight and volume.  It "
8729                                           "may help in transporting large game.  This action destroys "
8730                                           "skin, hide, pelt, etc., so don't use it if you want to "
8731                                           "harvest them later." ),
8732                                        msgFactor ) );
8733     smenu.addentry_col( static_cast<int>( butcher_type::DISMEMBER ), true,
8734                         'm', _( "Dismember corpse" ),
8735                         cut_time( butcher_type::DISMEMBER ),
8736                         string_format( "%s  %s",
8737                                        _( "If you're aiming to just destroy a body outright and don't "
8738                                           "care about harvesting it, dismembering it will hack it apart "
8739                                           "in a very short amount of time but yields little to no usable flesh." ),
8740                                        msgFactor ) );
8741     smenu.addentry_col( static_cast<int>( butcher_type::DISSECT ), enough_light,
8742                         'd', _( "Dissect corpse" ),
8743                         enough_light ? cut_time( butcher_type::DISSECT ) : cannot_see,
8744                         string_format( "%s  %s",
8745                                        _( "By careful dissection of the corpse, you will examine it for "
8746                                           "possible bionic implants, or discrete organs and harvest them "
8747                                           "if possible.  Requires scalpel-grade cutting tools, ruins "
8748                                           "corpse, and consumes a lot of time.  Your medical knowledge "
8749                                           "is most useful here." ),
8750                                        msgFactorD ) );
8751     smenu.query();
8752     switch( smenu.ret ) {
8753         case static_cast<int>( butcher_type::QUICK ):
8754             player_character.assign_activity( activity_id( "ACT_BUTCHER" ), 0, true );
8755             break;
8756         case static_cast<int>( butcher_type::FULL ):
8757             player_character.assign_activity( activity_id( "ACT_BUTCHER_FULL" ), 0, true );
8758             break;
8759         case static_cast<int>( butcher_type::FIELD_DRESS ):
8760             player_character.assign_activity( activity_id( "ACT_FIELD_DRESS" ), 0, true );
8761             break;
8762         case static_cast<int>( butcher_type::SKIN ):
8763             player_character.assign_activity( activity_id( "ACT_SKIN" ), 0, true );
8764             break;
8765         case static_cast<int>( butcher_type::BLEED ) :
8766             player_character.assign_activity( activity_id( "ACT_BLEED" ), 0, true );
8767             break;
8768         case static_cast<int>( butcher_type::QUARTER ):
8769             player_character.assign_activity( activity_id( "ACT_QUARTER" ), 0, true );
8770             break;
8771         case static_cast<int>( butcher_type::DISMEMBER ):
8772             player_character.assign_activity( activity_id( "ACT_DISMEMBER" ), 0, true );
8773             break;
8774         case static_cast<int>( butcher_type::DISSECT ):
8775             player_character.assign_activity( activity_id( "ACT_DISSECT" ), 0, true );
8776             break;
8777         default:
8778             return;
8779     }
8780 }
8781 
butcher()8782 void game::butcher()
8783 {
8784     static const std::string salvage_string = "salvage";
8785     if( u.controlling_vehicle ) {
8786         add_msg( m_info, _( "You can't butcher while driving!" ) );
8787         return;
8788     }
8789 
8790     const int factor = u.max_quality( quality_id( "BUTCHER" ) );
8791     const int factorD = u.max_quality( quality_id( "CUT_FINE" ) );
8792     const std::string no_knife_msg = _( "You don't have a butchering tool." );
8793     const std::string no_corpse_msg = _( "There are no corpses here to butcher." );
8794 
8795     //You can't butcher on sealed terrain- you have to smash/shovel/etc it open first
8796     if( m.has_flag( "SEALED", u.pos() ) ) {
8797         if( m.sees_some_items( u.pos(), u ) ) {
8798             add_msg( m_info, _( "You can't access the items here." ) );
8799         } else if( factor > INT_MIN || factorD > INT_MIN ) {
8800             add_msg( m_info, no_corpse_msg );
8801         } else {
8802             add_msg( m_info, no_knife_msg );
8803         }
8804         return;
8805     }
8806 
8807     const item *first_item_without_tools = nullptr;
8808     // Indices of relevant items
8809     std::vector<map_stack::iterator> corpses;
8810     std::vector<map_stack::iterator> disassembles;
8811     std::vector<map_stack::iterator> salvageables;
8812     map_stack items = m.i_at( u.pos() );
8813     const inventory &crafting_inv = u.crafting_inventory();
8814 
8815     // TODO: Properly handle different material whitelists
8816     // TODO: Improve quality of this section
8817     auto salvage_filter = []( item it ) {
8818         const item *usable = it.get_usable_item( salvage_string );
8819         return usable != nullptr;
8820     };
8821 
8822     std::vector< item * > salvage_tools = u.items_with( salvage_filter );
8823     int salvage_tool_index = INT_MIN;
8824     item *salvage_tool = nullptr;
8825     const salvage_actor *salvage_iuse = nullptr;
8826     if( !salvage_tools.empty() ) {
8827         salvage_tool = salvage_tools.front();
8828         salvage_tool_index = u.get_item_position( salvage_tool );
8829         item *usable = salvage_tool->get_usable_item( salvage_string );
8830         salvage_iuse = dynamic_cast<const salvage_actor *>(
8831                            usable->get_use( salvage_string )->get_actor_ptr() );
8832     }
8833 
8834     // Reserve capacity for each to hold entire item set if necessary to prevent
8835     // reallocations later on
8836     corpses.reserve( items.size() );
8837     salvageables.reserve( items.size() );
8838     disassembles.reserve( items.size() );
8839 
8840     // Split into corpses, disassemble-able, and salvageable items
8841     // It's not much additional work to just generate a corpse list and
8842     // clear it later, but does make the splitting process nicer.
8843     for( map_stack::iterator it = items.begin(); it != items.end(); ++it ) {
8844         if( it->is_corpse() ) {
8845             corpses.push_back( it );
8846         } else {
8847             if( ( salvage_tool_index != INT_MIN ) && salvage_iuse->valid_to_cut_up( *it ) ) {
8848                 salvageables.push_back( it );
8849             }
8850             if( u.can_disassemble( *it, crafting_inv ).success() ) {
8851                 disassembles.push_back( it );
8852             } else if( !first_item_without_tools ) {
8853                 first_item_without_tools = &*it;
8854             }
8855         }
8856     }
8857 
8858     // Clear corpses if butcher and dissect factors are INT_MIN
8859     if( factor == INT_MIN && factorD == INT_MIN ) {
8860         corpses.clear();
8861     }
8862 
8863     if( corpses.empty() && disassembles.empty() && salvageables.empty() ) {
8864         if( factor > INT_MIN || factorD > INT_MIN ) {
8865             add_msg( m_info, no_corpse_msg );
8866         } else {
8867             add_msg( m_info, no_knife_msg );
8868         }
8869 
8870         if( first_item_without_tools ) {
8871             add_msg( m_info, _( "You don't have the necessary tools to disassemble any items here." ) );
8872             // Just for the "You need x to disassemble y" messages
8873             const auto ret = u.can_disassemble( *first_item_without_tools, crafting_inv );
8874             if( !ret.success() ) {
8875                 add_msg( m_info, "%s", ret.c_str() );
8876             }
8877         }
8878         return;
8879     }
8880 
8881     Creature *hostile_critter = is_hostile_very_close( true );
8882     if( hostile_critter != nullptr ) {
8883         if( !query_yn( _( "You see %s nearby!  Start butchering anyway?" ),
8884                        hostile_critter->disp_name() ) ) {
8885             return;
8886         }
8887     }
8888 
8889     // Magic indices for special butcher options
8890     enum : int {
8891         MULTISALVAGE = MAX_ITEM_IN_SQUARE + 1,
8892         MULTIBUTCHER,
8893         MULTIDISASSEMBLE_ONE,
8894         MULTIDISASSEMBLE_ALL,
8895         NUM_BUTCHER_ACTIONS
8896     };
8897     // What are we butchering (i.e.. which vector to pick indices from)
8898     enum {
8899         BUTCHER_CORPSE,
8900         BUTCHER_DISASSEMBLE,
8901         BUTCHER_SALVAGE,
8902         BUTCHER_OTHER // For multisalvage etc.
8903     } butcher_select = BUTCHER_CORPSE;
8904     // Index to std::vector of iterators...
8905     int indexer_index = 0;
8906 
8907     // Generate the indexed stacks so we can display them nicely
8908     const auto disassembly_stacks = generate_butcher_stack_display( disassembles );
8909     const auto salvage_stacks = generate_butcher_stack_display( salvageables );
8910     // Always ask before cutting up/disassembly, but not before butchery
8911     size_t ret = 0;
8912     if( !corpses.empty() || !disassembles.empty() || !salvageables.empty() ) {
8913         uilist kmenu;
8914         kmenu.text = _( "Choose corpse to butcher / item to disassemble" );
8915 
8916         size_t i = 0;
8917         // Add corpses, disassembleables, and salvagables to the UI
8918         add_corpses( kmenu, corpses, i );
8919         add_disassemblables( kmenu, disassembly_stacks, i );
8920         if( salvage_iuse && !salvageables.empty() ) {
8921             add_salvagables( kmenu, salvage_stacks, i, *salvage_iuse );
8922         }
8923 
8924         if( corpses.size() > 1 ) {
8925             kmenu.addentry( MULTIBUTCHER, true, 'b', _( "Butcher everything" ) );
8926         }
8927         if( disassembles.size() > 1 ) {
8928             int time_to_disassemble_once = 0;
8929             int time_to_disassemble_recursive = 0;
8930             for( const auto &stack : disassembly_stacks ) {
8931                 recipe uncraft_recipe;
8932                 if( stack.first->typeId() == itype_disassembly ) {
8933                     uncraft_recipe = stack.first->get_making();
8934                 } else {
8935                     uncraft_recipe = recipe_dictionary::get_uncraft( stack.first->typeId() );
8936                 }
8937 
8938                 const int time = uncraft_recipe.time_to_craft_moves(
8939                                      get_player_character(), recipe_time_flag::ignore_proficiencies );
8940                 time_to_disassemble_once += time * stack.second;
8941                 if( stack.first->typeId() == itype_disassembly ) {
8942                     item test( uncraft_recipe.result(), calendar::turn, 1 );
8943                     time_to_disassemble_recursive += test.get_recursive_disassemble_moves(
8944                                                          get_player_character() ) * stack.second;
8945                 } else {
8946                     time_to_disassemble_recursive += stack.first->get_recursive_disassemble_moves(
8947                                                          get_player_character() ) * stack.second;
8948                 }
8949 
8950             }
8951 
8952             kmenu.addentry_col( MULTIDISASSEMBLE_ONE, true, 'D', _( "Disassemble everything once" ),
8953                                 to_string_clipped( time_duration::from_moves( time_to_disassemble_once ) ) );
8954             kmenu.addentry_col( MULTIDISASSEMBLE_ALL, true, 'd', _( "Disassemble everything recursively" ),
8955                                 to_string_clipped( time_duration::from_moves( time_to_disassemble_recursive ) ) );
8956         }
8957         if( salvage_iuse && salvageables.size() > 1 ) {
8958             int time_to_salvage = 0;
8959             for( const auto &stack : salvage_stacks ) {
8960                 time_to_salvage += salvage_iuse->time_to_cut_up( *stack.first ) * stack.second;
8961             }
8962 
8963             kmenu.addentry_col( MULTISALVAGE, true, 'z', _( "Cut up everything" ),
8964                                 to_string_clipped( time_duration::from_moves( time_to_salvage ) ) );
8965         }
8966 
8967         kmenu.query();
8968 
8969         if( kmenu.ret < 0 || kmenu.ret >= NUM_BUTCHER_ACTIONS ) {
8970             return;
8971         }
8972 
8973         ret = static_cast<size_t>( kmenu.ret );
8974         if( ret >= MULTISALVAGE && ret < NUM_BUTCHER_ACTIONS ) {
8975             butcher_select = BUTCHER_OTHER;
8976             indexer_index = ret;
8977         } else if( ret < corpses.size() ) {
8978             butcher_select = BUTCHER_CORPSE;
8979             indexer_index = ret;
8980         } else if( ret < corpses.size() + disassembly_stacks.size() ) {
8981             butcher_select = BUTCHER_DISASSEMBLE;
8982             indexer_index = ret - corpses.size();
8983         } else if( ret < corpses.size() + disassembly_stacks.size() + salvage_stacks.size() ) {
8984             butcher_select = BUTCHER_SALVAGE;
8985             indexer_index = ret - corpses.size() - disassembly_stacks.size();
8986         } else {
8987             debugmsg( "Invalid butchery index: %d", ret );
8988             return;
8989         }
8990     }
8991 
8992     if( !u.has_morale_to_craft() ) {
8993         if( butcher_select == BUTCHER_CORPSE || indexer_index == MULTIBUTCHER ) {
8994             add_msg( m_info,
8995                      _( "You are not in the mood and the prospect of guts and blood on your hands convinces you to turn away." ) );
8996         } else {
8997             add_msg( m_info,
8998                      _( "You are not in the mood and the prospect of work stops you before you begin." ) );
8999         }
9000         return;
9001     }
9002     const std::vector<npc *> helpers = u.get_crafting_helpers();
9003     for( std::size_t i = 0; i < helpers.size() && i < 3; i++ ) {
9004         add_msg( m_info, _( "%s helps with this task…" ), helpers[i]->name );
9005     }
9006     switch( butcher_select ) {
9007         case BUTCHER_OTHER:
9008             switch( indexer_index ) {
9009                 case MULTISALVAGE:
9010                     u.assign_activity( activity_id( "ACT_LONGSALVAGE" ), 0, salvage_tool_index );
9011                     break;
9012                 case MULTIBUTCHER:
9013                     butcher_submenu( corpses );
9014                     for( map_stack::iterator &it : corpses ) {
9015                         u.activity.targets.emplace_back( map_cursor( u.pos() ), &*it );
9016                     }
9017                     break;
9018                 case MULTIDISASSEMBLE_ONE:
9019                     u.disassemble_all( true );
9020                     break;
9021                 case MULTIDISASSEMBLE_ALL:
9022                     u.disassemble_all( false );
9023                     break;
9024                 default:
9025                     debugmsg( "Invalid butchery type: %d", indexer_index );
9026                     return;
9027             }
9028             break;
9029         case BUTCHER_CORPSE: {
9030             butcher_submenu( corpses, indexer_index );
9031             u.activity.targets.emplace_back( map_cursor( u.pos() ), &*corpses[indexer_index] );
9032         }
9033         break;
9034         case BUTCHER_DISASSEMBLE: {
9035             // Pick index of first item in the disassembly stack
9036             item *const target = &*disassembly_stacks[indexer_index].first;
9037             u.disassemble( item_location( map_cursor( u.pos() ), target ), true );
9038         }
9039         break;
9040         case BUTCHER_SALVAGE: {
9041             if( !salvage_iuse || !salvage_tool ) {
9042                 debugmsg( "null salve_iuse or salvage_tool" );
9043             } else {
9044                 // Pick index of first item in the salvage stack
9045                 item *const target = &*salvage_stacks[indexer_index].first;
9046                 item_location item_loc( map_cursor( u.pos() ), target );
9047                 salvage_iuse->cut_up( u, *salvage_tool, item_loc );
9048             }
9049         }
9050         break;
9051     }
9052 }
9053 
reload(item_location & loc,bool prompt,bool empty)9054 void game::reload( item_location &loc, bool prompt, bool empty )
9055 {
9056     item *it = loc.get_item();
9057 
9058     // bows etc. do not need to reload. select favorite ammo for them instead
9059     if( it->has_flag( flag_RELOAD_AND_SHOOT ) ) {
9060         item::reload_option opt = u.select_ammo( *it, prompt );
9061         if( !opt ) {
9062             return;
9063         } else if( u.ammo_location && opt.ammo == u.ammo_location ) {
9064             u.ammo_location = item_location();
9065         } else {
9066             u.ammo_location = opt.ammo;
9067         }
9068         return;
9069     }
9070 
9071     switch( u.rate_action_reload( *it ) ) {
9072         case hint_rating::iffy:
9073             if( ( it->is_ammo_container() || it->is_magazine() ) && it->ammo_remaining() > 0 &&
9074                 it->remaining_ammo_capacity() == 0 ) {
9075                 add_msg( m_info, _( "The %s is already fully loaded!" ), it->tname() );
9076                 return;
9077             }
9078             if( it->is_ammo_belt() ) {
9079                 const auto &linkage = it->type->magazine->linkage;
9080                 if( linkage && !u.has_charges( *linkage, 1 ) ) {
9081                     add_msg( m_info, _( "You need at least one %s to reload the %s!" ),
9082                              item::nname( *linkage, 1 ), it->tname() );
9083                     return;
9084                 }
9085             }
9086             if( it->is_watertight_container() && it->is_container_full() ) {
9087                 add_msg( m_info, _( "The %s is already full!" ), it->tname() );
9088                 return;
9089             }
9090 
9091         // intentional fall-through
9092 
9093         case hint_rating::cant:
9094             add_msg( m_info, _( "You can't reload a %s!" ), it->tname() );
9095             return;
9096 
9097         case hint_rating::good:
9098             break;
9099     }
9100 
9101     bool use_loc = true;
9102     if( !it->has_flag( flag_ALLOWS_REMOTE_USE ) ) {
9103         it = loc.obtain( u ).get_item();
9104         if( !it ) {
9105             add_msg( _( "Never mind." ) );
9106             return;
9107         }
9108         use_loc = false;
9109     }
9110 
9111     // for holsters and ammo pouches try to reload any contained item
9112     if( it->type->can_use( "holster" ) && it->contents.num_item_stacks() == 1 ) {
9113         it = &it->contents.only_item();
9114     }
9115 
9116     item::reload_option opt = u.ammo_location && it->can_reload_with( u.ammo_location->typeId() ) ?
9117                               item::reload_option( &u, it, it, u.ammo_location ) :
9118                               u.select_ammo( *it, prompt, empty );
9119 
9120     if( opt.ammo.get_item() == nullptr || ( opt.ammo.get_item()->is_frozen_liquid() &&
9121                                             !u.crush_frozen_liquid( opt.ammo ) ) ) {
9122         return;
9123     }
9124 
9125     if( opt ) {
9126         int moves = opt.moves();
9127         if( it->get_var( "dirt", 0 ) > 7800 ) {
9128             add_msg( m_warning, _( "You struggle to reload the fouled %s." ), it->tname() );
9129             moves += 2500;
9130         }
9131 
9132         std::vector<item_location> targets;
9133         if( use_loc ) {
9134             targets.emplace_back( loc );
9135         } else {
9136             targets.emplace_back( u, const_cast<item *>( opt.target ) );
9137         }
9138         targets.push_back( std::move( opt.ammo ) );
9139 
9140         u.assign_activity( player_activity( reload_activity_actor( moves, opt.qty(), targets ) ) );
9141 
9142     }
9143 }
9144 
9145 // Reload something.
reload_item()9146 void game::reload_item()
9147 {
9148     item_location item_loc = inv_map_splice( [&]( const item & it ) {
9149         return u.rate_action_reload( it ) == hint_rating::good;
9150     }, _( "Reload item" ), 1, _( "You have nothing to reload." ) );
9151 
9152     if( !item_loc ) {
9153         add_msg( _( "Never mind." ) );
9154         return;
9155     }
9156 
9157     reload( item_loc );
9158 }
9159 
reload_wielded(bool prompt)9160 void game::reload_wielded( bool prompt )
9161 {
9162     if( u.weapon.is_null() || !u.weapon.is_reloadable() ) {
9163         add_msg( _( "You aren't holding something you can reload." ) );
9164         return;
9165     }
9166     item_location item_loc = item_location( u, &u.weapon );
9167     reload( item_loc, prompt );
9168 }
9169 
reload_weapon(bool try_everything)9170 void game::reload_weapon( bool try_everything )
9171 {
9172     // As a special streamlined activity, hitting reload repeatedly should:
9173     // Reload wielded gun
9174     // First reload a magazine if necessary.
9175     // Then load said magazine into gun.
9176     // Reload magazines that are compatible with the current gun.
9177     // Reload other guns in inventory.
9178     // Reload misc magazines in inventory.
9179     std::vector<item_location> reloadables = u.find_reloadables();
9180     std::sort( reloadables.begin(), reloadables.end(),
9181     [this]( const item_location & a, const item_location & b ) {
9182         const item *ap = a.get_item();
9183         const item *bp = b.get_item();
9184         // Current wielded weapon comes first.
9185         if( this->u.is_wielding( *bp ) ) {
9186             return false;
9187         }
9188         if( this->u.is_wielding( *ap ) ) {
9189             return true;
9190         }
9191         // Second sort by affiliation with wielded gun
9192         const std::set<itype_id> compatible_magazines = this->u.weapon.magazine_compatible();
9193         const bool mag_ap = compatible_magazines.count( ap->typeId() ) > 0;
9194         const bool mag_bp = compatible_magazines.count( bp->typeId() ) > 0;
9195         if( mag_ap != mag_bp ) {
9196             return mag_ap;
9197         }
9198         // Third sort by gun vs magazine,
9199         if( ap->is_gun() != bp->is_gun() ) {
9200             return ap->is_gun();
9201         }
9202         // Finally sort by speed to reload.
9203         return ( ap->get_reload_time() * ( ap->remaining_ammo_capacity() ) ) <
9204                ( bp->get_reload_time() * ( bp->remaining_ammo_capacity() ) );
9205     } );
9206     for( item_location &candidate : reloadables ) {
9207         std::vector<item::reload_option> ammo_list;
9208         u.list_ammo( *candidate.get_item(), ammo_list, false );
9209         if( !ammo_list.empty() ) {
9210             reload( candidate, false, false );
9211             return;
9212         }
9213     }
9214     // Just for testing, bail out here to avoid unwanted side effects.
9215     if( !try_everything ) {
9216         return;
9217     }
9218     // If we make it here and haven't found anything to reload, start looking elsewhere.
9219     vehicle *veh = veh_pointer_or_null( m.veh_at( u.pos() ) );
9220     turret_data turret;
9221     if( veh && ( turret = veh->turret_query( u.pos() ) ) && turret.can_reload() ) {
9222         item::reload_option opt = u.select_ammo( *turret.base(), true );
9223         std::vector<item_location> targets;
9224         if( opt ) {
9225             const int moves = opt.moves();
9226             targets.emplace_back( turret.base() );
9227             targets.push_back( std::move( opt.ammo ) );
9228             u.assign_activity( player_activity( reload_activity_actor( moves, opt.qty(), targets ) ) );
9229         }
9230         return;
9231     }
9232 
9233     reload_item();
9234 }
9235 
wield(item_location loc)9236 void game::wield( item_location loc )
9237 {
9238     if( !loc ) {
9239         debugmsg( "ERROR: tried to wield null item" );
9240         return;
9241     }
9242     if( &u.weapon != &*loc && u.weapon.has_item( *loc ) ) {
9243         add_msg( m_info, _( "You need to put the bag away before trying to wield something from it." ) );
9244         return;
9245     }
9246     if( u.has_wield_conflicts( *loc ) ) {
9247         const bool is_unwielding = u.is_wielding( *loc );
9248         const auto ret = u.can_unwield( *loc );
9249 
9250         if( !ret.success() ) {
9251             add_msg( m_info, "%s", ret.c_str() );
9252         }
9253 
9254         if( !u.unwield() ) {
9255             return;
9256         }
9257 
9258         if( is_unwielding ) {
9259             if( !u.martial_arts_data->selected_is_none() ) {
9260                 u.martial_arts_data->martialart_use_message( u );
9261             }
9262             return;
9263         }
9264     }
9265     const auto ret = u.can_wield( *loc );
9266     if( !ret.success() ) {
9267         add_msg( m_info, "%s", ret.c_str() );
9268     }
9269     // Need to do this here because holster_actor::use() checks if/where the item is worn
9270     item &target = *loc.get_item();
9271     if( target.get_use( "holster" ) && !target.contents.empty() ) {
9272         //~ %1$s: holster name
9273         if( query_yn( pgettext( "holster", "Draw from %1$s?" ),
9274                       target.tname() ) ) {
9275             u.invoke_item( &target );
9276             return;
9277         }
9278     }
9279 
9280     // Can't use loc.obtain() here because that would cause things to spill.
9281     item to_wield = *loc.get_item();
9282     item_location::type location_type = loc.where();
9283     tripoint pos = loc.position();
9284     const int obtain_cost = loc.obtain_cost( u );
9285     int worn_index = INT_MIN;
9286     if( u.is_worn( *loc.get_item() ) ) {
9287         auto ret = u.can_takeoff( *loc.get_item() );
9288         if( !ret.success() ) {
9289             add_msg( m_info, "%s", ret.c_str() );
9290             return;
9291         }
9292         int item_pos = u.get_item_position( loc.get_item() );
9293         if( item_pos != INT_MIN ) {
9294             worn_index = Character::worn_position_to_index( item_pos );
9295         }
9296     }
9297     loc.remove_item();
9298     if( !u.wield( to_wield, obtain_cost ) ) {
9299         switch( location_type ) {
9300             case item_location::type::container:
9301                 // this will not cause things to spill, as it is inside another item
9302                 loc = loc.obtain( u );
9303                 wield( loc );
9304                 break;
9305             case item_location::type::character:
9306                 if( worn_index != INT_MIN ) {
9307                     auto it = u.worn.begin();
9308                     std::advance( it, worn_index );
9309                     u.worn.insert( it, to_wield );
9310                 } else {
9311                     u.i_add( to_wield );
9312                 }
9313                 break;
9314             case item_location::type::map:
9315                 m.add_item( pos, to_wield );
9316                 break;
9317             case item_location::type::vehicle: {
9318                 const cata::optional<vpart_reference> vp = m.veh_at( pos ).part_with_feature( "CARGO", false );
9319                 // If we fail to return the item to the vehicle for some reason, add it to the map instead.
9320                 if( !vp || !( vp->vehicle().add_item( vp->part_index(), to_wield ) ) ) {
9321                     m.add_item( pos, to_wield );
9322                 }
9323                 break;
9324             }
9325             case item_location::type::invalid:
9326                 debugmsg( "Failed wield from invalid item location" );
9327                 break;
9328         }
9329         return;
9330     }
9331 }
9332 
wield()9333 void game::wield()
9334 {
9335     item_location loc = game_menus::inv::wield( u );
9336 
9337     if( loc ) {
9338         wield( loc );
9339     } else {
9340         add_msg( _( "Never mind." ) );
9341     }
9342 }
9343 
check_safe_mode_allowed(bool repeat_safe_mode_warnings)9344 bool game::check_safe_mode_allowed( bool repeat_safe_mode_warnings )
9345 {
9346     if( !repeat_safe_mode_warnings && safe_mode_warning_logged ) {
9347         // Already warned player since safe_mode_warning_logged is set.
9348         return false;
9349     }
9350 
9351     std::string msg_ignore = press_x( ACTION_IGNORE_ENEMY );
9352     if( !msg_ignore.empty() ) {
9353         std::wstring msg_ignore_wide = utf8_to_wstr( msg_ignore );
9354         // Operate on a wide-char basis to prevent corrupted multi-byte string
9355         msg_ignore_wide[0] = towlower( msg_ignore_wide[0] );
9356         msg_ignore = wstr_to_utf8( msg_ignore_wide );
9357     }
9358 
9359     if( u.has_effect( effect_laserlocked ) ) {
9360         // Automatic and mandatory safemode.  Make BLOODY sure the player notices!
9361         if( u.get_int_base() < 5 || u.has_trait( trait_id( "PROF_CHURL" ) ) ) {
9362             add_msg( game_message_params{ m_warning, gmf_bypass_cooldown },
9363                      _( "There's an angry red dot on your body, %s to brush it off." ), msg_ignore );
9364         } else {
9365             add_msg( game_message_params{ m_warning, gmf_bypass_cooldown },
9366                      _( "You are being laser-targeted, %s to ignore." ), msg_ignore );
9367         }
9368         safe_mode_warning_logged = true;
9369         return false;
9370     }
9371     if( safe_mode != SAFE_MODE_STOP ) {
9372         return true;
9373     }
9374     // Currently driving around, ignore the monster, they have no chance against a proper car anyway (-:
9375     if( u.controlling_vehicle && !get_option<bool>( "SAFEMODEVEH" ) ) {
9376         return true;
9377     }
9378     // Monsters around and we don't want to run
9379     std::string spotted_creature_name;
9380     const monster_visible_info &mon_visible = u.get_mon_visible();
9381     const auto &new_seen_mon = mon_visible.new_seen_mon;
9382 
9383     if( new_seen_mon.empty() ) {
9384         // naming consistent with code in game::mon_info
9385         spotted_creature_name = _( "a survivor" );
9386         get_safemode().lastmon_whitelist = get_safemode().npc_type_name();
9387     } else {
9388         spotted_creature_name = new_seen_mon.back()->name();
9389         get_safemode().lastmon_whitelist = spotted_creature_name;
9390     }
9391 
9392     std::string whitelist;
9393     if( !get_safemode().empty() ) {
9394         whitelist = string_format( _( " or %s to whitelist the monster" ),
9395                                    press_x( ACTION_WHITELIST_ENEMY ) );
9396     }
9397 
9398     const std::string msg_safe_mode = press_x( ACTION_TOGGLE_SAFEMODE );
9399     add_msg( game_message_params{ m_warning, gmf_bypass_cooldown },
9400              _( "Spotted %1$s--safe mode is on!  (%2$s to turn it off, %3$s to ignore monster%4$s)" ),
9401              spotted_creature_name, msg_safe_mode, msg_ignore, whitelist );
9402     safe_mode_warning_logged = true;
9403     return false;
9404 }
9405 
set_safe_mode(safe_mode_type mode)9406 void game::set_safe_mode( safe_mode_type mode )
9407 {
9408     safe_mode = mode;
9409     safe_mode_warning_logged = false;
9410 }
9411 
disable_robot(const tripoint & p)9412 bool game::disable_robot( const tripoint &p )
9413 {
9414     monster *const mon_ptr = critter_at<monster>( p );
9415     if( !mon_ptr ) {
9416         return false;
9417     }
9418     monster &critter = *mon_ptr;
9419     if( ( critter.friendly == 0 && !critter.has_effect( effect_sensor_stun ) ) ||
9420         critter.has_flag( MF_RIDEABLE_MECH ) ||
9421         ( critter.has_flag( MF_PAY_BOT ) && critter.has_effect( effect_paid ) ) ) {
9422         // Can only disable / reprogram friendly or stunned monsters
9423         return false;
9424     }
9425     const mtype_id mid = critter.type->id;
9426     const itype_id mon_item_id = critter.type->revert_to_itype;
9427     if( !mon_item_id.is_empty() &&
9428         query_yn( _( "Deactivate the %s?" ), critter.name() ) ) {
9429 
9430         u.moves -= 100;
9431         m.add_item_or_charges( p, critter.to_item() );
9432         if( !critter.has_flag( MF_INTERIOR_AMMO ) ) {
9433             for( std::pair<const itype_id, int> &ammodef : critter.ammo ) {
9434                 if( ammodef.second > 0 ) {
9435                     m.spawn_item( p.xy(), ammodef.first, 1, ammodef.second, calendar::turn );
9436                 }
9437             }
9438         }
9439         remove_zombie( critter );
9440         return true;
9441     }
9442     // Manhacks are special, they have their own menu here.
9443     if( mid == mon_manhack ) {
9444         int choice = UILIST_CANCEL;
9445         if( critter.has_effect( effect_docile ) ) {
9446             choice = uilist( _( "Reprogram the manhack?" ), { _( "Engage targets." ) } );
9447         } else {
9448             choice = uilist( _( "Reprogram the manhack?" ), { _( "Follow me." ) } );
9449         }
9450         switch( choice ) {
9451             case 0:
9452                 if( critter.has_effect( effect_docile ) ) {
9453                     critter.remove_effect( effect_docile );
9454                     if( one_in( 3 ) ) {
9455                         add_msg( _( "The %s hovers momentarily as it surveys the area." ),
9456                                  critter.name() );
9457                     }
9458                 } else {
9459                     critter.add_effect( effect_docile, 1_turns, true );
9460                     if( one_in( 3 ) ) {
9461                         add_msg( _( "The %s lets out a whirring noise and starts to follow you." ),
9462                                  critter.name() );
9463                     }
9464                 }
9465                 u.moves -= 100;
9466                 return true;
9467             default:
9468                 break;
9469         }
9470     }
9471     return false;
9472 }
9473 
is_dangerous_tile(const tripoint & dest_loc) const9474 bool game::is_dangerous_tile( const tripoint &dest_loc ) const
9475 {
9476     return !( get_dangerous_tile( dest_loc ).empty() );
9477 }
9478 
prompt_dangerous_tile(const tripoint & dest_loc) const9479 bool game::prompt_dangerous_tile( const tripoint &dest_loc ) const
9480 {
9481     std::vector<std::string> harmful_stuff = get_dangerous_tile( dest_loc );
9482 
9483     if( !harmful_stuff.empty() &&
9484         !query_yn( _( "Really step into %s?" ), enumerate_as_string( harmful_stuff ) ) ) {
9485         return false;
9486     }
9487     if( !harmful_stuff.empty() && u.is_mounted() && m.tr_at( dest_loc ) == tr_ledge ) {
9488         add_msg( m_warning, _( "Your %s refuses to move over that ledge!" ),
9489                  u.mounted_creature->get_name() );
9490         return false;
9491     }
9492     return true;
9493 }
9494 
get_dangerous_tile(const tripoint & dest_loc) const9495 std::vector<std::string> game::get_dangerous_tile( const tripoint &dest_loc ) const
9496 {
9497     if( u.is_blind() ) {
9498         return {}; // blinded players don't see dangerous tiles
9499     }
9500 
9501     std::vector<std::string> harmful_stuff;
9502     const field fields_here = m.field_at( u.pos() );
9503     const auto veh_here = m.veh_at( u.pos() ).part_with_feature( "BOARDABLE", true );
9504     const auto veh_dest = m.veh_at( dest_loc ).part_with_feature( "BOARDABLE", true );
9505     const bool veh_here_inside = veh_here && veh_here->is_inside();
9506     const bool veh_dest_inside = veh_dest && veh_dest->is_inside();
9507 
9508     for( const std::pair<const field_type_id, field_entry> &e : m.field_at( dest_loc ) ) {
9509         if( !u.is_dangerous_field( e.second ) ) {
9510             continue;
9511         }
9512 
9513         const bool has_field_here = fields_here.find_field( e.first ) != nullptr;
9514         const bool empty_effects = e.second.field_effects().empty();
9515 
9516         // if the field is dangerous but has no effects apparently this
9517         // means effects are hardcoded in map_field.cpp so we should...
9518         bool danger_dest = empty_effects; // ... warn if effects are empty
9519         bool danger_here = has_field_here && empty_effects;
9520         for( const field_effect &fe : e.second.field_effects() ) {
9521             if( !danger_dest ) {
9522                 danger_dest = true;
9523                 if( fe.immune_in_vehicle && veh_dest ) {
9524                     danger_dest = false;
9525                 } else if( fe.immune_inside_vehicle && veh_dest_inside ) {
9526                     danger_dest = false;
9527                 } else if( fe.immune_outside_vehicle && !veh_dest_inside ) {
9528                     danger_dest = false;
9529                 }
9530             }
9531             if( has_field_here && !danger_here ) {
9532                 danger_here = true;
9533                 if( fe.immune_in_vehicle && veh_here ) {
9534                     danger_here = false;
9535                 } else if( fe.immune_inside_vehicle && veh_here_inside ) {
9536                     danger_here = false;
9537                 } else if( fe.immune_outside_vehicle && !veh_here_inside ) {
9538                     danger_here = false;
9539                 }
9540             }
9541         }
9542 
9543         // don't warn if already in a field of the same type
9544         if( !danger_dest || danger_here ) {
9545             continue;
9546         }
9547 
9548         harmful_stuff.push_back( e.second.name() );
9549     }
9550 
9551     const trap &tr = m.tr_at( dest_loc );
9552     // HACK: Hack for now, later ledge should stop being a trap
9553     // Note: in non-z-level mode, ledges obey different rules and so should be handled as regular traps
9554     if( tr == tr_ledge && m.has_zlevels() ) {
9555         if( !veh_dest ) {
9556             harmful_stuff.emplace_back( tr.name() );
9557         }
9558     } else if( tr.can_see( dest_loc, u ) && !tr.is_benign() && !veh_dest ) {
9559         harmful_stuff.emplace_back( tr.name() );
9560     }
9561 
9562     static const std::set< bodypart_str_id > sharp_bps = {
9563         body_part_eyes, body_part_mouth, body_part_head,
9564         body_part_leg_l, body_part_leg_r, body_part_foot_l,
9565         body_part_foot_r, body_part_arm_l, body_part_arm_r,
9566         body_part_hand_l, body_part_hand_r, body_part_torso
9567     };
9568 
9569     const auto sharp_bp_check = [this]( bodypart_id bp ) {
9570         return u.immune_to( bp, { damage_type::CUT, 10 } );
9571     };
9572 
9573     if( m.has_flag( "ROUGH", dest_loc ) && !m.has_flag( "ROUGH", u.pos() ) && !veh_dest &&
9574         ( u.get_armor_bash( bodypart_id( "foot_l" ) ) < 5 ||
9575           u.get_armor_bash( bodypart_id( "foot_r" ) ) < 5 ) ) {
9576         harmful_stuff.emplace_back( m.name( dest_loc ) );
9577     } else if( m.has_flag( "SHARP", dest_loc ) && !m.has_flag( "SHARP", u.pos() ) && !( u.in_vehicle ||
9578                m.veh_at( dest_loc ) ) &&
9579                u.dex_cur < 78 && !std::all_of( sharp_bps.begin(), sharp_bps.end(), sharp_bp_check ) ) {
9580         harmful_stuff.emplace_back( m.name( dest_loc ) );
9581     }
9582 
9583     return harmful_stuff;
9584 }
9585 
walk_move(const tripoint & dest_loc,const bool via_ramp,const bool furniture_move)9586 bool game::walk_move( const tripoint &dest_loc, const bool via_ramp, const bool furniture_move )
9587 {
9588     if( m.has_flag_ter( TFLAG_SMALL_PASSAGE, dest_loc ) ) {
9589         if( u.get_size() > creature_size::medium ) {
9590             add_msg( m_warning, _( "You can't fit there." ) );
9591             return false; // character too large to fit through a tight passage
9592         }
9593         if( u.is_mounted() ) {
9594             monster *mount = u.mounted_creature.get();
9595             if( mount->get_size() > creature_size::medium ) {
9596                 add_msg( m_warning, _( "Your mount can't fit there." ) );
9597                 return false; // char's mount is too large for tight passages
9598             }
9599         }
9600     }
9601 
9602     if( u.is_mounted() ) {
9603         monster *mons = u.mounted_creature.get();
9604         if( mons->has_flag( MF_RIDEABLE_MECH ) ) {
9605             if( !mons->check_mech_powered() ) {
9606                 add_msg( m_bad, _( "Your %s refuses to move as its batteries have been drained." ),
9607                          mons->get_name() );
9608                 return false;
9609             }
9610         }
9611         if( !mons->move_effects( false ) ) {
9612             add_msg( m_bad, _( "You cannot move as your %s isn't able to move." ), mons->get_name() );
9613             return false;
9614         }
9615     }
9616     const optional_vpart_position vp_here = m.veh_at( u.pos() );
9617     const optional_vpart_position vp_there = m.veh_at( dest_loc );
9618 
9619     bool pushing = false; // moving -into- grabbed tile; skip check for move_cost > 0
9620     bool pulling = false; // moving -away- from grabbed tile; check for move_cost > 0
9621     bool shifting_furniture = false; // moving furniture and staying still; skip check for move_cost > 0
9622 
9623     const tripoint furn_pos = u.pos() + u.grab_point;
9624     const tripoint furn_dest = dest_loc + u.grab_point;
9625 
9626     bool grabbed = u.get_grab_type() != object_type::NONE;
9627     if( grabbed ) {
9628         const tripoint dp = dest_loc - u.pos();
9629         pushing = dp ==  u.grab_point;
9630         pulling = dp == -u.grab_point;
9631     }
9632     if( grabbed && dest_loc.z != u.posz() ) {
9633         add_msg( m_warning, _( "You let go of the grabbed object." ) );
9634         grabbed = false;
9635         u.grab( object_type::NONE );
9636     }
9637 
9638     // Now make sure we're actually holding something
9639     const vehicle *grabbed_vehicle = nullptr;
9640     if( grabbed && u.get_grab_type() == object_type::FURNITURE ) {
9641         // We only care about shifting, because it's the only one that can change our destination
9642         if( m.has_furn( u.pos() + u.grab_point ) ) {
9643             shifting_furniture = !pushing && !pulling;
9644         } else {
9645             // We were grabbing a furniture that isn't there
9646             grabbed = false;
9647         }
9648     } else if( grabbed && u.get_grab_type() == object_type::VEHICLE ) {
9649         grabbed_vehicle = veh_pointer_or_null( m.veh_at( u.pos() + u.grab_point ) );
9650         if( grabbed_vehicle == nullptr ) {
9651             // We were grabbing a vehicle that isn't there anymore
9652             grabbed = false;
9653         }
9654     } else if( grabbed ) {
9655         // We were grabbing something WEIRD, let's pretend we weren't
9656         grabbed = false;
9657     }
9658     if( u.grab_point != tripoint_zero && !grabbed && !furniture_move ) {
9659         add_msg( m_warning, _( "Can't find grabbed object." ) );
9660         u.grab( object_type::NONE );
9661     }
9662 
9663     if( m.impassable( dest_loc ) && !pushing && !shifting_furniture ) {
9664         if( vp_there && u.mounted_creature && u.mounted_creature->has_flag( MF_RIDEABLE_MECH ) &&
9665             vp_there->vehicle().handle_potential_theft( dynamic_cast<player &>( u ) ) ) {
9666             tripoint diff = dest_loc - u.pos();
9667             if( diff.x < 0 ) {
9668                 diff.x -= 2;
9669             } else if( diff.x > 0 ) {
9670                 diff.x += 2;
9671             }
9672             if( diff.y < 0 ) {
9673                 diff.y -= 2;
9674             } else if( diff.y > 0 ) {
9675                 diff.y += 2;
9676             }
9677             u.mounted_creature->shove_vehicle( dest_loc + diff.xy(),
9678                                                dest_loc );
9679         }
9680         return false;
9681     }
9682     if( vp_there && !vp_there->vehicle().handle_potential_theft( dynamic_cast<player &>( u ) ) ) {
9683         return false;
9684     }
9685     if( u.is_mounted() && !pushing && vp_there ) {
9686         add_msg( m_warning, _( "You cannot board a vehicle whilst riding." ) );
9687         return false;
9688     }
9689     u.set_underwater( false );
9690 
9691     if( !shifting_furniture && !pushing && is_dangerous_tile( dest_loc ) ) {
9692         std::vector<std::string> harmful_stuff = get_dangerous_tile( dest_loc );
9693         if( get_option<std::string>( "DANGEROUS_TERRAIN_WARNING_PROMPT" ) == "ALWAYS" &&
9694             !prompt_dangerous_tile( dest_loc ) ) {
9695             return true;
9696         } else if( get_option<std::string>( "DANGEROUS_TERRAIN_WARNING_PROMPT" ) == "RUNNING" &&
9697                    ( !u.is_running() || !prompt_dangerous_tile( dest_loc ) ) ) {
9698             add_msg( m_warning,
9699                      _( "Stepping into that %1$s looks risky.  Run into it if you wish to enter anyway." ),
9700                      enumerate_as_string( harmful_stuff ) );
9701             return true;
9702         } else if( get_option<std::string>( "DANGEROUS_TERRAIN_WARNING_PROMPT" ) == "CROUCHING" &&
9703                    ( !u.is_crouching() || !prompt_dangerous_tile( dest_loc ) ) ) {
9704             add_msg( m_warning,
9705                      _( "Stepping into that %1$s looks risky.  Crouch and move into it if you wish to enter anyway." ),
9706                      enumerate_as_string( harmful_stuff ) );
9707             return true;
9708         } else if( get_option<std::string>( "DANGEROUS_TERRAIN_WARNING_PROMPT" ) == "NEVER" &&
9709                    !u.is_running() ) {
9710             add_msg( m_warning,
9711                      _( "Stepping into that %1$s looks risky.  Run into it if you wish to enter anyway." ),
9712                      enumerate_as_string( harmful_stuff ) );
9713             return true;
9714         }
9715     }
9716     // Used to decide whether to print a 'moving is slow message
9717     const int mcost_from = m.move_cost( u.pos() ); //calculate this _before_ calling grabbed_move
9718 
9719     int modifier = 0;
9720     if( grabbed && u.get_grab_type() == object_type::FURNITURE && u.pos() + u.grab_point == dest_loc ) {
9721         modifier = -m.furn( dest_loc ).obj().movecost;
9722     }
9723 
9724     int multiplier = 1;
9725     if( u.is_on_ground() ) {
9726         multiplier *= 3;
9727     }
9728 
9729     const int mcost = m.combined_movecost( u.pos(), dest_loc, grabbed_vehicle, modifier,
9730                                            via_ramp ) * multiplier;
9731 
9732     if( !furniture_move && grabbed_move( dest_loc - u.pos(), via_ramp ) ) {
9733         return true;
9734     } else if( mcost == 0 ) {
9735         return false;
9736     }
9737     bool diag = trigdist && u.posx() != dest_loc.x && u.posy() != dest_loc.y;
9738     const int previous_moves = u.moves;
9739     if( u.is_mounted() ) {
9740         auto *crit = u.mounted_creature.get();
9741         if( !crit->has_flag( MF_RIDEABLE_MECH ) &&
9742             ( m.has_flag_ter_or_furn( "MOUNTABLE", dest_loc ) ||
9743               m.has_flag_ter_or_furn( "BARRICADABLE_DOOR", dest_loc ) ||
9744               m.has_flag_ter_or_furn( "OPENCLOSE_INSIDE", dest_loc ) ||
9745               m.has_flag_ter_or_furn( "BARRICADABLE_DOOR_DAMAGED", dest_loc ) ||
9746               m.has_flag_ter_or_furn( "BARRICADABLE_DOOR_REINFORCED", dest_loc ) ) ) {
9747             add_msg( m_warning, _( "You cannot pass obstacles whilst mounted." ) );
9748             return false;
9749         }
9750         const double base_moves = u.run_cost( mcost, diag ) * 100.0 / crit->get_speed();
9751         const double encumb_moves = u.get_weight() / 4800.0_gram;
9752         u.moves -= static_cast<int>( std::ceil( base_moves + encumb_moves ) );
9753         crit->use_mech_power( -u.current_movement_mode()->mech_power_use() );
9754     } else {
9755         u.moves -= u.run_cost( mcost, diag );
9756         /**
9757         TODO:
9758         This should really use the mounted creatures stamina, if mounted.
9759         Monsters don't currently have stamina however.
9760         For the time being just don't burn players stamina when mounted.
9761         */
9762         if( grabbed_vehicle == nullptr || grabbed_vehicle->wheelcache.empty() ) {
9763             //Burn normal amount of stamina if no vehicle grabbed or vehicle lacks wheels
9764             u.burn_move_stamina( previous_moves - u.moves );
9765         } else {
9766             //Burn half as much stamina if vehicle has wheels, without changing move time
9767             u.burn_move_stamina( 0.50 * ( previous_moves - u.moves ) );
9768         }
9769     }
9770     // Max out recoil & reset aim point
9771     u.recoil = MAX_RECOIL;
9772     u.last_target_pos = cata::nullopt;
9773 
9774     // Print a message if movement is slow
9775     const int mcost_to = m.move_cost( dest_loc ); //calculate this _after_ calling grabbed_move
9776     const bool fungus = m.has_flag_ter_or_furn( "FUNGUS", u.pos() ) ||
9777                         m.has_flag_ter_or_furn( "FUNGUS",
9778                                 dest_loc ); //fungal furniture has no slowing effect on mycus characters
9779     const bool slowed = ( ( !u.has_trait( trait_PARKOUR ) && ( mcost_to > 2 || mcost_from > 2 ) ) ||
9780                           mcost_to > 4 || mcost_from > 4 ) &&
9781                         !( u.has_trait( trait_M_IMMUNE ) && fungus );
9782     if( slowed && !u.is_mounted() ) {
9783         // Unless u.pos() has a higher movecost than dest_loc, state that dest_loc is the cause
9784         if( mcost_to >= mcost_from ) {
9785             if( auto displayed_part = vp_there.part_displayed() ) {
9786                 add_msg( m_warning, _( "Moving onto this %s is slow!" ),
9787                          displayed_part->part().name() );
9788                 sfx::do_obstacle( displayed_part->part().info().get_id().str() );
9789             } else {
9790                 add_msg( m_warning, _( "Moving onto this %s is slow!" ), m.name( dest_loc ) );
9791                 sfx::do_obstacle( m.ter( dest_loc ).id().str() );
9792             }
9793         } else {
9794             if( auto displayed_part = vp_here.part_displayed() ) {
9795                 add_msg( m_warning, _( "Moving off of this %s is slow!" ),
9796                          displayed_part->part().name() );
9797                 sfx::do_obstacle( displayed_part->part().info().get_id().str() );
9798             } else {
9799                 add_msg( m_warning, _( "Moving off of this %s is slow!" ), m.name( u.pos() ) );
9800                 sfx::do_obstacle( m.ter( u.pos() ).id().str() );
9801             }
9802         }
9803     }
9804     if( !u.is_mounted() && u.has_trait( trait_id( "LEG_TENT_BRACE" ) ) &&
9805         ( !u.footwear_factor() ||
9806           ( u.footwear_factor() == .5 && one_in( 2 ) ) ) ) {
9807         // DX and IN are long suits for Cephalopods,
9808         // so this shouldn't cause too much hardship
9809         // Presumed that if it's swimmable, they're
9810         // swimming and won't stick
9811         ///\EFFECT_DEX decreases chance of tentacles getting stuck to the ground
9812 
9813         ///\EFFECT_INT decreases chance of tentacles getting stuck to the ground
9814         if( !m.has_flag( "SWIMMABLE", dest_loc ) && one_in( 80 + u.dex_cur + u.int_cur ) ) {
9815             add_msg( _( "Your tentacles stick to the ground, but you pull them free." ) );
9816             u.mod_fatigue( 1 );
9817         }
9818     }
9819 
9820     if( !u.has_trait( trait_id( "DEBUG_SILENT" ) ) ) {
9821         int volume = u.is_stealthy() ? 3 : 6;
9822         volume *= u.mutation_value( "noise_modifier" );
9823         if( volume > 0 ) {
9824             if( u.is_wearing( itype_rm13_armor_on ) ) {
9825                 volume = 2;
9826             } else if( u.has_bionic( bionic_id( "bio_ankles" ) ) ) {
9827                 volume = 12;
9828             }
9829 
9830             volume *= u.current_movement_mode()->sound_mult();
9831             if( u.is_mounted() ) {
9832                 monster *mons = u.mounted_creature.get();
9833                 switch( mons->get_size() ) {
9834                     case creature_size::tiny:
9835                         volume = 0; // No sound for the tinies
9836                         break;
9837                     case creature_size::small:
9838                         volume /= 3;
9839                         break;
9840                     case creature_size::medium:
9841                         break;
9842                     case creature_size::large:
9843                         volume *= 1.5;
9844                         break;
9845                     case creature_size::huge:
9846                         volume *= 2;
9847                         break;
9848                     default:
9849                         break;
9850                 }
9851                 if( mons->has_flag( MF_LOUDMOVES ) ) {
9852                     volume += 6;
9853                 }
9854                 sounds::sound( dest_loc, volume, sounds::sound_t::movement, mons->type->get_footsteps(), false,
9855                                "none", "none" );
9856             } else {
9857                 sounds::sound( dest_loc, volume, sounds::sound_t::movement, _( "footsteps" ), true,
9858                                "none", "none" );    // Sound of footsteps may awaken nearby monsters
9859             }
9860             sfx::do_footstep();
9861         }
9862 
9863     }
9864 
9865     if( m.has_flag_ter_or_furn( TFLAG_HIDE_PLACE, dest_loc ) ) {
9866         add_msg( m_good, _( "You are hiding in the %s." ), m.name( dest_loc ) );
9867     }
9868 
9869     tripoint oldpos = u.pos();
9870     tripoint old_abs_pos = m.getabs( oldpos );
9871 
9872     bool moving = dest_loc != oldpos;
9873 
9874     point submap_shift = place_player( dest_loc );
9875     point ms_shift = sm_to_ms_copy( submap_shift );
9876     oldpos = oldpos - ms_shift;
9877 
9878     if( moving ) {
9879         cata_event_dispatch::avatar_moves( old_abs_pos, u, m );
9880     }
9881 
9882     if( pulling ) {
9883         const tripoint shifted_furn_pos = furn_pos - ms_shift;
9884         const tripoint shifted_furn_dest = furn_dest - ms_shift;
9885         const time_duration fire_age = m.get_field_age( shifted_furn_pos, fd_fire );
9886         const int fire_intensity = m.get_field_intensity( shifted_furn_pos, fd_fire );
9887         m.remove_field( shifted_furn_pos, fd_fire );
9888         m.set_field_intensity( shifted_furn_dest, fd_fire, fire_intensity );
9889         m.set_field_age( shifted_furn_dest, fd_fire, fire_age );
9890     }
9891 
9892     if( u.is_hauling() ) {
9893         start_hauling( oldpos );
9894     }
9895 
9896     on_move_effects();
9897 
9898     return true;
9899 }
9900 
place_player(const tripoint & dest_loc)9901 point game::place_player( const tripoint &dest_loc )
9902 {
9903     const optional_vpart_position vp1 = m.veh_at( dest_loc );
9904     if( const cata::optional<std::string> label = vp1.get_label() ) {
9905         add_msg( m_info, _( "Label here: %s" ), *label );
9906     }
9907     std::string signage = m.get_signage( dest_loc );
9908     if( !signage.empty() ) {
9909         if( !u.has_trait( trait_ILLITERATE ) ) {
9910             add_msg( m_info, _( "The sign says: %s" ), signage );
9911         } else {
9912             add_msg( m_info, _( "There is a sign here, but you are unable to read it." ) );
9913         }
9914     }
9915     if( m.has_graffiti_at( dest_loc ) ) {
9916         if( !u.has_trait( trait_ILLITERATE ) ) {
9917             add_msg( m_info, _( "Written here: %s" ), m.graffiti_at( dest_loc ) );
9918         } else {
9919             add_msg( m_info, _( "Something is written here, but you are unable to read it." ) );
9920         }
9921     }
9922     // TODO: Move the stuff below to a Character method so that NPCs can reuse it
9923     if( m.has_flag( "ROUGH", dest_loc ) && ( !u.in_vehicle ) && ( !u.is_mounted() ) ) {
9924         if( one_in( 5 ) && u.get_armor_bash( bodypart_id( "foot_l" ) ) < rng( 2, 5 ) ) {
9925             add_msg( m_bad, _( "You hurt your left foot on the %s!" ),
9926                      m.has_flag_ter( "ROUGH", dest_loc ) ? m.tername( dest_loc ) : m.furnname(
9927                          dest_loc ) );
9928             u.deal_damage( nullptr, bodypart_id( "foot_l" ), damage_instance( damage_type::CUT, 1 ) );
9929         }
9930         if( one_in( 5 ) && u.get_armor_bash( bodypart_id( "foot_r" ) ) < rng( 2, 5 ) ) {
9931             add_msg( m_bad, _( "You hurt your right foot on the %s!" ),
9932                      m.has_flag_ter( "ROUGH", dest_loc ) ? m.tername( dest_loc ) : m.furnname(
9933                          dest_loc ) );
9934             u.deal_damage( nullptr, bodypart_id( "foot_l" ), damage_instance( damage_type::CUT, 1 ) );
9935         }
9936     }
9937     ///\EFFECT_DEX increases chance of avoiding cuts on sharp terrain
9938     if( m.has_flag( "SHARP", dest_loc ) && !one_in( 3 ) && !x_in_y( 1 + u.dex_cur / 2.0, 40 ) &&
9939         ( !u.in_vehicle && !m.veh_at( dest_loc ) ) && ( !u.has_trait( trait_PARKOUR ) ||
9940                 one_in( 4 ) ) && ( u.has_trait( trait_THICKSKIN ) ? !one_in( 8 ) : true ) ) {
9941         if( u.is_mounted() ) {
9942             add_msg( _( "Your %s gets cut!" ), u.mounted_creature->get_name() );
9943             u.mounted_creature->apply_damage( nullptr, bodypart_id( "torso" ), rng( 1, 10 ) );
9944         } else {
9945             const bodypart_id bp = u.get_random_body_part();
9946             if( u.deal_damage( nullptr, bp, damage_instance( damage_type::CUT, rng( 1,
9947                                10 ) ) ).total_damage() > 0 ) {
9948                 //~ 1$s - bodypart name in accusative, 2$s is terrain name.
9949                 add_msg( m_bad, _( "You cut your %1$s on the %2$s!" ),
9950                          body_part_name_accusative( bp ),
9951                          m.has_flag_ter( "SHARP", dest_loc ) ? m.tername( dest_loc ) : m.furnname(
9952                              dest_loc ) );
9953                 if( ( u.has_trait( trait_INFRESIST ) ) && ( one_in( 1024 ) ) ) {
9954                     u.add_effect( effect_tetanus, 1_turns, true );
9955                 } else if( ( !u.has_trait( trait_INFIMMUNE ) || !u.has_trait( trait_INFRESIST ) ) &&
9956                            ( one_in( 256 ) ) ) {
9957                     u.add_effect( effect_tetanus, 1_turns, true );
9958                 }
9959             }
9960         }
9961     }
9962     if( m.has_flag( "UNSTABLE", dest_loc ) && !u.is_mounted() ) {
9963         u.add_effect( effect_bouldering, 1_turns, true );
9964     } else if( u.has_effect( effect_bouldering ) ) {
9965         u.remove_effect( effect_bouldering );
9966     }
9967     if( m.has_flag_ter_or_furn( TFLAG_NO_SIGHT, dest_loc ) ) {
9968         u.add_effect( effect_no_sight, 1_turns, true );
9969     } else if( u.has_effect( effect_no_sight ) ) {
9970         u.remove_effect( effect_no_sight );
9971     }
9972 
9973     // If we moved out of the nonant, we need update our map data
9974     if( m.has_flag( "SWIMMABLE", dest_loc ) && u.has_effect( effect_onfire ) ) {
9975         add_msg( _( "The water puts out the flames!" ) );
9976         u.remove_effect( effect_onfire );
9977         if( u.is_mounted() ) {
9978             monster *mon = u.mounted_creature.get();
9979             if( mon->has_effect( effect_onfire ) ) {
9980                 mon->remove_effect( effect_onfire );
9981             }
9982         }
9983     }
9984 
9985     if( monster *const mon_ptr = critter_at<monster>( dest_loc ) ) {
9986         // We displaced a monster. It's probably a bug if it wasn't a friendly mon...
9987         // Immobile monsters can't be displaced.
9988         monster &critter = *mon_ptr;
9989         // TODO: handling for ridden creatures other than players mount.
9990         if( !critter.has_effect( effect_ridden ) ) {
9991             if( u.is_mounted() ) {
9992                 std::vector<tripoint> maybe_valid;
9993                 for( const tripoint &jk : m.points_in_radius( critter.pos(), 1 ) ) {
9994                     if( is_empty( jk ) ) {
9995                         maybe_valid.push_back( jk );
9996                     }
9997                 }
9998                 bool moved = false;
9999                 while( !maybe_valid.empty() ) {
10000                     if( critter.move_to( random_entry_removed( maybe_valid ) ) ) {
10001                         add_msg( _( "You push the %s out of the way." ), critter.name() );
10002                         moved = true;
10003                     }
10004                 }
10005                 if( !moved ) {
10006                     add_msg( _( "There is no room to push the %s out of the way." ), critter.name() );
10007                     return u.pos().xy();
10008                 }
10009             } else {
10010                 // Force the movement even though the player is there right now.
10011                 const bool moved = critter.move_to( u.pos(), /*force=*/false, /*step_on_critter=*/true );
10012                 if( moved ) {
10013                     add_msg( _( "You displace the %s." ), critter.name() );
10014                 } else {
10015                     add_msg( _( "You cannot move the %s out of the way." ), critter.name() );
10016                     return u.pos().xy();
10017                 }
10018             }
10019         } else if( !u.has_effect( effect_riding ) ) {
10020             add_msg( _( "You cannot move the %s out of the way." ), critter.name() );
10021             return u.pos().xy();
10022         }
10023     }
10024 
10025     // If the player is in a vehicle, unboard them from the current part
10026     if( u.in_vehicle ) {
10027         m.unboard_vehicle( u.pos() );
10028     }
10029     // Move the player
10030     // Start with z-level, to make it less likely that old functions (2D ones) freak out
10031     if( m.has_zlevels() && dest_loc.z != m.get_abs_sub().z ) {
10032         vertical_shift( dest_loc.z );
10033     }
10034 
10035     if( u.is_hauling() && ( !m.can_put_items( dest_loc ) ||
10036                             m.has_flag( TFLAG_DEEP_WATER, dest_loc ) ||
10037                             vp1 ) ) {
10038         u.stop_hauling();
10039     }
10040     u.setpos( dest_loc );
10041     if( u.is_mounted() ) {
10042         monster *mon = u.mounted_creature.get();
10043         mon->setpos( dest_loc );
10044         mon->process_triggers();
10045         m.creature_in_field( *mon );
10046     }
10047     point submap_shift = update_map( u );
10048     // Important: don't use dest_loc after this line. `update_map` may have shifted the map
10049     // and dest_loc was not adjusted and therefore is still in the un-shifted system and probably wrong.
10050     // If you must use it you can calculate the position in the new, shifted system with
10051     // adjusted_pos = ( old_pos.x - submap_shift.x * SEEX, old_pos.y - submap_shift.y * SEEY, old_pos.z )
10052 
10053     //Auto pulp or butcher and Auto foraging
10054     if( get_option<bool>( "AUTO_FEATURES" ) && mostseen == 0  && !u.is_mounted() ) {
10055         static const direction adjacentDir[8] = { direction::NORTH, direction::NORTHEAST, direction::EAST, direction::SOUTHEAST, direction::SOUTH, direction::SOUTHWEST, direction::WEST, direction::NORTHWEST };
10056 
10057         const std::string forage_type = get_option<std::string>( "AUTO_FORAGING" );
10058         if( forage_type != "off" ) {
10059             const auto forage = [&]( const tripoint & pos ) {
10060                 const ter_t &xter_t = *m.ter( pos );
10061                 const furn_t &xfurn_t = *m.furn( pos );
10062                 const bool forage_everything = forage_type == "both";
10063                 const bool forage_bushes = forage_everything || forage_type == "bushes";
10064                 const bool forage_trees = forage_everything || forage_type == "trees";
10065                 if( !xter_t.can_examine() ) {
10066                     return;
10067                 } else if( ( forage_bushes && xter_t.has_examine( iexamine::shrub_marloss ) ) ||
10068                            ( forage_bushes && xter_t.has_examine( iexamine::shrub_wildveggies ) ) ||
10069                            ( forage_bushes && xter_t.has_examine( iexamine::harvest_ter_nectar ) ) ||
10070                            ( forage_trees && xter_t.has_examine( iexamine::tree_marloss ) ) ||
10071                            ( forage_trees && xter_t.has_examine( iexamine::harvest_ter ) ) ||
10072                            ( forage_trees && xter_t.has_examine( iexamine::harvest_ter_nectar ) )
10073                          ) {
10074                     xter_t.examine( u, pos );
10075                 } else if( ( forage_everything && xfurn_t.has_examine( iexamine::harvest_furn ) ) ||
10076                            ( forage_everything && xfurn_t.has_examine( iexamine::harvest_furn_nectar ) )
10077                          ) {
10078                     xfurn_t.examine( u, pos );
10079                 }
10080             };
10081 
10082             for( const direction &elem : adjacentDir ) {
10083                 forage( u.pos() + direction_XY( elem ) );
10084             }
10085         }
10086 
10087         const std::string pulp_butcher = get_option<std::string>( "AUTO_PULP_BUTCHER" );
10088         if( pulp_butcher == "butcher" && u.max_quality( quality_id( "BUTCHER" ) ) > INT_MIN ) {
10089             std::vector<item *> corpses;
10090 
10091             for( item &it : m.i_at( u.pos() ) ) {
10092                 corpses.push_back( &it );
10093             }
10094 
10095             if( !corpses.empty() ) {
10096                 u.assign_activity( activity_id( "ACT_BUTCHER" ), 0, true );
10097                 for( item *it : corpses ) {
10098                     u.activity.targets.emplace_back( map_cursor( u.pos() ), it );
10099                 }
10100             }
10101         } else if( pulp_butcher == "pulp" || pulp_butcher == "pulp_adjacent" ||
10102                    pulp_butcher == "pulp_zombie_only" || pulp_butcher == "pulp_adjacent_zombie_only" ) {
10103             const auto pulp = [&]( const tripoint & pos ) {
10104                 for( const item &maybe_corpse : m.i_at( pos ) ) {
10105                     if( maybe_corpse.is_corpse() && maybe_corpse.can_revive() &&
10106                         !maybe_corpse.get_mtype()->bloodType().obj().has_acid ) {
10107 
10108                         if( pulp_butcher == "pulp_zombie_only" || pulp_butcher == "pulp_adjacent_zombie_only" ) {
10109                             if( !maybe_corpse.get_mtype()->has_flag( MF_REVIVES ) ) {
10110                                 continue;
10111                             }
10112                         }
10113 
10114                         u.assign_activity( activity_id( "ACT_PULP" ), calendar::INDEFINITELY_LONG, 0 );
10115                         u.activity.placement = m.getabs( pos );
10116                         u.activity.auto_resume = true;
10117                         u.activity.str_values.push_back( "auto_pulp_no_acid" );
10118                         return;
10119                     }
10120                 }
10121             };
10122 
10123             if( pulp_butcher == "pulp_adjacent" || pulp_butcher == "pulp_adjacent_zombie_only" ) {
10124                 for( const direction &elem : adjacentDir ) {
10125                     pulp( u.pos() + direction_XY( elem ) );
10126                 }
10127             } else {
10128                 pulp( u.pos() );
10129             }
10130         }
10131     }
10132 
10133     //Autopickup
10134     if( !u.is_mounted() && get_option<bool>( "AUTO_PICKUP" ) && !u.is_hauling() &&
10135         ( !get_option<bool>( "AUTO_PICKUP_SAFEMODE" ) || mostseen == 0 ) &&
10136         ( m.has_items( u.pos() ) || get_option<bool>( "AUTO_PICKUP_ADJACENT" ) ) ) {
10137         Pickup::pick_up( u.pos(), -1 );
10138     }
10139 
10140     // If the new tile is a boardable part, board it
10141     if( vp1.part_with_feature( "BOARDABLE", true ) && !u.is_mounted() ) {
10142         m.board_vehicle( u.pos(), &u );
10143     }
10144 
10145     // Traps!
10146     // Try to detect.
10147     u.search_surroundings();
10148     if( u.is_mounted() ) {
10149         m.creature_on_trap( *u.mounted_creature );
10150     } else {
10151         m.creature_on_trap( u );
10152     }
10153     // Drench the player if swimmable
10154     if( m.has_flag( "SWIMMABLE", u.pos() ) &&
10155         !( u.is_mounted() || ( u.in_vehicle && vp1->vehicle().can_float() ) ) ) {
10156         u.drench( 80, { { body_part_foot_l, body_part_foot_r, body_part_leg_l, body_part_leg_r } },
10157         false );
10158     }
10159 
10160     // List items here
10161     if( !m.has_flag( "SEALED", u.pos() ) ) {
10162         if( get_option<bool>( "NO_AUTO_PICKUP_ZONES_LIST_ITEMS" ) ||
10163             !check_zone( zone_type_id( "NO_AUTO_PICKUP" ), u.pos() ) ) {
10164             if( u.is_blind() && !m.i_at( u.pos() ).empty() ) {
10165                 add_msg( _( "There's something here, but you can't see what it is." ) );
10166             } else if( m.has_items( u.pos() ) ) {
10167                 std::vector<std::string> names;
10168                 std::vector<size_t> counts;
10169                 std::vector<item> items;
10170                 for( item &tmpitem : m.i_at( u.pos() ) ) {
10171 
10172                     std::string next_tname = tmpitem.tname();
10173                     std::string next_dname = tmpitem.display_name();
10174                     bool by_charges = tmpitem.count_by_charges();
10175                     bool got_it = false;
10176                     for( size_t i = 0; i < names.size(); ++i ) {
10177                         if( by_charges && next_tname == names[i] ) {
10178                             counts[i] += tmpitem.charges;
10179                             got_it = true;
10180                             break;
10181                         } else if( next_dname == names[i] ) {
10182                             counts[i] += 1;
10183                             got_it = true;
10184                             break;
10185                         }
10186                     }
10187                     if( !got_it ) {
10188                         if( by_charges ) {
10189                             names.push_back( tmpitem.tname( tmpitem.charges ) );
10190                             counts.push_back( tmpitem.charges );
10191                         } else {
10192                             names.push_back( tmpitem.display_name( 1 ) );
10193                             counts.push_back( 1 );
10194                         }
10195                         items.push_back( tmpitem );
10196                     }
10197                     if( names.size() > 10 ) {
10198                         break;
10199                     }
10200                 }
10201                 for( size_t i = 0; i < names.size(); ++i ) {
10202                     if( !items[i].count_by_charges() ) {
10203                         names[i] = items[i].display_name( counts[i] );
10204                     } else {
10205                         names[i] = items[i].tname( counts[i] );
10206                     }
10207                 }
10208                 int and_the_rest = 0;
10209                 for( size_t i = 0; i < names.size(); ++i ) {
10210                     //~ number of items: "<number> <item>"
10211                     std::string fmt = ngettext( "%1$d %2$s", "%1$d %2$s", counts[i] );
10212                     names[i] = string_format( fmt, counts[i], names[i] );
10213                     // Skip the first two.
10214                     if( i > 1 ) {
10215                         and_the_rest += counts[i];
10216                     }
10217                 }
10218                 if( names.size() == 1 ) {
10219                     add_msg( _( "You see here %s." ), names[0] );
10220                 } else if( names.size() == 2 ) {
10221                     add_msg( _( "You see here %s and %s." ), names[0], names[1] );
10222                 } else if( names.size() == 3 ) {
10223                     add_msg( _( "You see here %s, %s, and %s." ), names[0], names[1], names[2] );
10224                 } else if( and_the_rest < 7 ) {
10225                     add_msg( ngettext( "You see here %s, %s and %d more item.",
10226                                        "You see here %s, %s and %d more items.",
10227                                        and_the_rest ),
10228                              names[0], names[1], and_the_rest );
10229                 } else {
10230                     add_msg( _( "You see here %s and many more items." ), names[0] );
10231                 }
10232             }
10233         }
10234     }
10235 
10236     if( ( vp1.part_with_feature( "CONTROL_ANIMAL", true ) ||
10237           vp1.part_with_feature( "CONTROLS", true ) ) && u.in_vehicle && !u.is_mounted() ) {
10238         add_msg( _( "There are vehicle controls here." ) );
10239         if( !u.has_trait( trait_id( "WAYFARER" ) ) ) {
10240             add_msg( m_info, _( "%s to drive." ), press_x( ACTION_CONTROL_VEHICLE ) );
10241         }
10242     } else if( vp1.part_with_feature( "CONTROLS", true ) && u.in_vehicle &&
10243                u.is_mounted() ) {
10244         add_msg( _( "There are vehicle controls here but you cannot reach them whilst mounted." ) );
10245     }
10246     return submap_shift;
10247 }
10248 
place_player_overmap(const tripoint_abs_omt & om_dest)10249 void game::place_player_overmap( const tripoint_abs_omt &om_dest )
10250 {
10251     // if player is teleporting around, they don't bring their horse with them
10252     if( u.is_mounted() ) {
10253         u.remove_effect( effect_riding );
10254         u.mounted_creature->remove_effect( effect_ridden );
10255         u.mounted_creature = nullptr;
10256     }
10257     // offload the active npcs.
10258     unload_npcs();
10259     for( monster &critter : all_monsters() ) {
10260         despawn_monster( critter );
10261     }
10262     if( u.in_vehicle ) {
10263         m.unboard_vehicle( u.pos() );
10264     }
10265     const int minz = m.has_zlevels() ? -OVERMAP_DEPTH : m.get_abs_sub().z;
10266     const int maxz = m.has_zlevels() ? OVERMAP_HEIGHT : m.get_abs_sub().z;
10267     for( int z = minz; z <= maxz; z++ ) {
10268         m.clear_vehicle_list( z );
10269     }
10270     m.rebuild_vehicle_level_caches();
10271     m.access_cache( m.get_abs_sub().z ).map_memory_seen_cache.reset();
10272     // offset because load_map expects the coordinates of the top left corner, but the
10273     // player will be centered in the middle of the map.
10274     const tripoint_abs_sm map_sm_pos =
10275         project_to<coords::sm>( om_dest ) - point( HALF_MAPSIZE, HALF_MAPSIZE );
10276     const tripoint player_pos( u.pos().xy(), map_sm_pos.z() );
10277     load_map( map_sm_pos );
10278     load_npcs();
10279     m.spawn_monsters( true ); // Static monsters
10280     update_overmap_seen();
10281     // update weather now as it could be different on the new location
10282     weather.nextweather = calendar::turn;
10283     place_player( player_pos );
10284 }
10285 
phasing_move(const tripoint & dest_loc,const bool via_ramp)10286 bool game::phasing_move( const tripoint &dest_loc, const bool via_ramp )
10287 {
10288     if( !u.has_active_bionic( bionic_id( "bio_probability_travel" ) ) ||
10289         u.get_power_level() < 250_kJ ) {
10290         return false;
10291     }
10292 
10293     if( dest_loc.z != u.posz() && !via_ramp ) {
10294         // No vertical phasing yet
10295         return false;
10296     }
10297 
10298     //probability travel through walls but not water
10299     tripoint dest = dest_loc;
10300     // tile is impassable
10301     int tunneldist = 0;
10302     const point d( sgn( dest.x - u.posx() ), sgn( dest.y - u.posy() ) );
10303     while( m.impassable( dest ) ||
10304            ( critter_at( dest ) != nullptr && tunneldist > 0 ) ) {
10305         //add 1 to tunnel distance for each impassable tile in the line
10306         tunneldist += 1;
10307         //Being dimensionally anchored prevents quantum shenanigans.
10308         if( u.worn_with_flag( flag_DIMENSIONAL_ANCHOR ) ||
10309             u.has_effect_with_flag( flag_DIMENSIONAL_ANCHOR ) ) {
10310             u.add_msg_if_player( m_info, _( "You are repelled by the barrier!" ) );
10311             u.mod_power_level( -250_kJ ); //cost of tunneling one tile.
10312             return false;
10313         }
10314         if( tunneldist * 250_kJ >
10315             u.get_power_level() ) { //oops, not enough energy! Tunneling costs 250 bionic power per impassable tile
10316             add_msg( _( "You try to quantum tunnel through the barrier but are reflected!  Try again with more energy!" ) );
10317             u.mod_power_level( -250_kJ );
10318             return false;
10319         }
10320 
10321         if( tunneldist > 24 ) {
10322             add_msg( m_info, _( "It's too dangerous to tunnel that far!" ) );
10323             u.mod_power_level( -250_kJ );
10324             return false;
10325         }
10326 
10327         dest.x += d.x;
10328         dest.y += d.y;
10329     }
10330 
10331     if( tunneldist != 0 ) {
10332         if( u.in_vehicle ) {
10333             m.unboard_vehicle( u.pos() );
10334         }
10335 
10336         add_msg( _( "You quantum tunnel through the %d-tile wide barrier!" ), tunneldist );
10337         //tunneling costs 250 bionic power per impassable tile
10338         u.mod_power_level( -( tunneldist * 250_kJ ) );
10339         u.moves -= 100; //tunneling costs 100 moves
10340         u.setpos( dest );
10341 
10342         if( m.veh_at( u.pos() ).part_with_feature( "BOARDABLE", true ) ) {
10343             m.board_vehicle( u.pos(), &u );
10344         }
10345 
10346         u.grab( object_type::NONE );
10347         on_move_effects();
10348         m.creature_on_trap( u );
10349         return true;
10350     }
10351 
10352     return false;
10353 }
10354 
can_move_furniture(tripoint fdest,const tripoint & dp)10355 bool game::can_move_furniture( tripoint fdest, const tripoint &dp )
10356 {
10357     const bool pulling_furniture = dp == -u.grab_point;
10358     const bool has_floor = m.has_floor( fdest );
10359     return  m.passable( fdest ) &&
10360             critter_at<npc>( fdest ) == nullptr &&
10361             critter_at<monster>( fdest ) == nullptr &&
10362             ( !pulling_furniture || is_empty( u.pos() + dp ) ) &&
10363             ( !has_floor || m.has_flag( "FLAT", fdest ) ) &&
10364             !m.has_furn( fdest ) &&
10365             !m.veh_at( fdest ) &&
10366             ( !has_floor || m.tr_at( fdest ).is_null() );
10367 }
10368 
grabbed_furn_move_time(const tripoint & dp)10369 int game::grabbed_furn_move_time( const tripoint &dp )
10370 {
10371     // Furniture: pull, push, or standing still and nudging object around.
10372     // Can push furniture out of reach.
10373     tripoint fpos = u.pos() + u.grab_point;
10374     // supposed position of grabbed furniture
10375     if( !m.has_furn( fpos ) ) {
10376         return 0;
10377     }
10378 
10379     tripoint fdest = fpos + dp; // intended destination of furniture.
10380 
10381     const bool canmove = can_move_furniture( fdest, dp );
10382     const furn_t furntype = m.furn( fpos ).obj();
10383     const int dst_items = m.i_at( fdest ).size();
10384 
10385     const bool only_liquid_items = std::all_of( m.i_at( fdest ).begin(), m.i_at( fdest ).end(),
10386     [&]( item & liquid_item ) {
10387         return liquid_item.made_of_from_type( phase_id::LIQUID );
10388     } );
10389 
10390     const bool dst_item_ok = !m.has_flag( "NOITEM", fdest ) &&
10391                              !m.has_flag( "SWIMMABLE", fdest ) &&
10392                              !m.has_flag( "DESTROY_ITEM", fdest ) &&
10393                              only_liquid_items;
10394     const bool src_item_ok = m.furn( fpos ).obj().has_flag( "CONTAINER" ) ||
10395                              m.furn( fpos ).obj().has_flag( "FIRE_CONTAINER" ) ||
10396                              m.furn( fpos ).obj().has_flag( "SEALED" );
10397 
10398     int str_req = furntype.move_str_req;
10399     // Factor in weight of items contained in the furniture.
10400     units::mass furniture_contents_weight = 0_gram;
10401     for( item &contained_item : m.i_at( fpos ) ) {
10402         furniture_contents_weight += contained_item.weight();
10403     }
10404     str_req += furniture_contents_weight / 4_kilogram;
10405 
10406     const float weary_mult = 1.0f / u.exertion_adjusted_move_multiplier();
10407     if( !canmove ) {
10408         return 50 * weary_mult;
10409     } else if( str_req > u.get_str() &&
10410                one_in( std::max( 20 - str_req - u.get_str(), 2 ) ) ) {
10411         return 100 * weary_mult;
10412     } else if( !src_item_ok && !dst_item_ok && dst_items > 0 ) {
10413         return 50 * weary_mult;
10414     }
10415     int moves_total = 0;
10416     moves_total = str_req * 10;
10417     // Additional penalty if we can't comfortably move it.
10418     if( str_req > u.get_str() ) {
10419         int move_penalty = std::pow( str_req, 2.0 ) + 100.0;
10420         if( move_penalty <= 1000 ) {
10421             if( u.get_str() >= str_req - 3 ) {
10422                 moves_total += std::max( 3000, move_penalty * 10 ) * weary_mult;
10423             } else {
10424                 moves_total += 100 * weary_mult;
10425                 return moves_total;
10426             }
10427         }
10428         moves_total += move_penalty;
10429     }
10430     return moves_total;
10431 }
10432 
grabbed_furn_move(const tripoint & dp)10433 bool game::grabbed_furn_move( const tripoint &dp )
10434 {
10435     // Furniture: pull, push, or standing still and nudging object around.
10436     // Can push furniture out of reach.
10437     tripoint fpos = u.pos() + u.grab_point;
10438     // supposed position of grabbed furniture
10439     if( !m.has_furn( fpos ) ) {
10440         // Where did it go? We're grabbing thin air so reset.
10441         add_msg( m_info, _( "No furniture at grabbed point." ) );
10442         u.grab( object_type::NONE );
10443         return false;
10444     }
10445 
10446     const bool pushing_furniture = dp ==  u.grab_point;
10447     const bool pulling_furniture = dp == -u.grab_point;
10448     const bool shifting_furniture = !pushing_furniture && !pulling_furniture;
10449 
10450     tripoint fdest = fpos + dp; // intended destination of furniture.
10451 
10452     // Unfortunately, game::is_empty fails for tiles we're standing on,
10453     // which will forbid pulling, so:
10454     const bool canmove = can_move_furniture( fdest, dp );
10455     // @TODO: it should be possible to move over invisible traps. This should probably
10456     // trigger the trap.
10457     // The current check (no move if trap) allows a player to detect invisible traps by
10458     // attempting to move stuff onto it.
10459 
10460     const furn_t furntype = m.furn( fpos ).obj();
10461     const int src_items = m.i_at( fpos ).size();
10462     const int dst_items = m.i_at( fdest ).size();
10463 
10464     const bool only_liquid_items = std::all_of( m.i_at( fdest ).begin(), m.i_at( fdest ).end(),
10465     [&]( item & liquid_item ) {
10466         return liquid_item.made_of_from_type( phase_id::LIQUID );
10467     } );
10468 
10469     const bool dst_item_ok = !m.has_flag( "NOITEM", fdest ) &&
10470                              !m.has_flag( "SWIMMABLE", fdest ) &&
10471                              !m.has_flag( "DESTROY_ITEM", fdest );
10472 
10473     const bool src_item_ok = m.furn( fpos ).obj().has_flag( "CONTAINER" ) ||
10474                              m.furn( fpos ).obj().has_flag( "FIRE_CONTAINER" ) ||
10475                              m.furn( fpos ).obj().has_flag( "SEALED" );
10476 
10477     const int fire_intensity = m.get_field_intensity( fpos, fd_fire );
10478     time_duration fire_age = m.get_field_age( fpos, fd_fire );
10479 
10480     int str_req = furntype.move_str_req;
10481     // Factor in weight of items contained in the furniture.
10482     units::mass furniture_contents_weight = 0_gram;
10483     for( item &contained_item : m.i_at( fpos ) ) {
10484         furniture_contents_weight += contained_item.weight();
10485     }
10486     str_req += furniture_contents_weight / 4_kilogram;
10487 
10488     if( !canmove ) {
10489         // TODO: What is something?
10490         add_msg( _( "The %s collides with something." ), furntype.name() );
10491         return true;
10492         ///\EFFECT_STR determines ability to drag furniture
10493     } else if( str_req > u.get_str() &&
10494                one_in( std::max( 20 - str_req - u.get_str(), 2 ) ) ) {
10495         add_msg( m_bad, _( "You strain yourself trying to move the heavy %s!" ),
10496                  furntype.name() );
10497         u.mod_pain( 1 ); // Hurt ourselves.
10498         return true; // furniture and or obstacle wins.
10499     } else if( !src_item_ok && !only_liquid_items && dst_items > 0 ) {
10500         add_msg( _( "There's stuff in the way." ) );
10501         return true;
10502     }
10503 
10504     // Additional penalty if we can't comfortably move it.
10505     if( str_req > u.get_str() ) {
10506         int move_penalty = std::pow( str_req, 2.0 ) + 100.0;
10507         if( move_penalty <= 1000 ) {
10508             if( u.get_str() >= str_req - 3 ) {
10509                 add_msg( m_bad, _( "The %s is really heavy!" ), furntype.name() );
10510                 if( one_in( 3 ) ) {
10511                     add_msg( m_bad, _( "You fail to move the %s." ), furntype.name() );
10512                     return true;
10513                 }
10514             } else {
10515                 add_msg( m_bad, _( "The %s is too heavy for you to budge." ), furntype.name() );
10516                 return true;
10517             }
10518         }
10519         if( move_penalty > 500 ) {
10520             add_msg( _( "Moving the heavy %s is taking a lot of time!" ),
10521                      furntype.name() );
10522         } else if( move_penalty > 200 ) {
10523             if( one_in( 3 ) ) { // Nag only occasionally.
10524                 add_msg( _( "It takes some time to move the heavy %s." ),
10525                          furntype.name() );
10526             }
10527         }
10528     }
10529     sounds::sound( fdest, furntype.move_str_req * 2, sounds::sound_t::movement,
10530                    _( "a scraping noise." ), true, "misc", "scraping" );
10531 
10532     // Actually move the furniture.
10533     m.furn_set( fdest, m.furn( fpos ) );
10534     m.furn_set( fpos, f_null, true );
10535 
10536     if( fire_intensity == 1 && !pulling_furniture ) {
10537         m.remove_field( fpos, fd_fire );
10538         m.set_field_intensity( fdest, fd_fire, fire_intensity );
10539         m.set_field_age( fdest, fd_fire, fire_age );
10540     }
10541 
10542     // Is there is only liquids on the ground, remove them after moving furniture.
10543     if( dst_items > 0 && only_liquid_items ) {
10544         m.i_clear( fdest );
10545     }
10546 
10547     if( src_items > 0 ) { // Move the stuff inside.
10548         if( dst_item_ok && src_item_ok ) {
10549             // Assume contents of both cells are legal, so we can just swap contents.
10550             std::list<item> temp;
10551             std::move( m.i_at( fpos ).begin(), m.i_at( fpos ).end(),
10552                        std::back_inserter( temp ) );
10553             m.i_clear( fpos );
10554             for( auto item_iter = m.i_at( fdest ).begin();
10555                  item_iter != m.i_at( fdest ).end(); ++item_iter ) {
10556                 m.i_at( fpos ).insert( *item_iter );
10557             }
10558             m.i_clear( fdest );
10559             for( auto &cur_item : temp ) {
10560                 m.i_at( fdest ).insert( cur_item );
10561             }
10562         } else {
10563             add_msg( _( "Stuff spills from the %s!" ), furntype.name() );
10564         }
10565     }
10566 
10567     if( !m.has_floor( fdest ) && !m.has_flag( "FLAT", fdest ) ) {
10568         std::string danger_tile = enumerate_as_string( get_dangerous_tile( fdest ) );
10569         add_msg( _( "You let go of the %1$s as it falls down the %2$s." ), furntype.name(), danger_tile );
10570         u.grab( object_type::NONE );
10571         m.drop_furniture( fdest );
10572         return true;
10573     }
10574 
10575     if( shifting_furniture ) {
10576         // We didn't move
10577         tripoint d_sum = u.grab_point + dp;
10578         if( std::abs( d_sum.x ) < 2 && std::abs( d_sum.y ) < 2 ) {
10579             u.grab_point = d_sum; // furniture moved relative to us
10580         } else { // we pushed furniture out of reach
10581             add_msg( _( "You let go of the %s." ), furntype.name() );
10582             u.grab( object_type::NONE );
10583         }
10584         return true; // We moved furniture but stayed still.
10585     }
10586 
10587     if( pushing_furniture && m.impassable( fpos ) ) {
10588         // Not sure how that chair got into a wall, but don't let player follow.
10589         add_msg( _( "You let go of the %1$s as it slides past %2$s." ),
10590                  furntype.name(), m.tername( fdest ) );
10591         u.grab( object_type::NONE );
10592         return true;
10593     }
10594 
10595     return false;
10596 }
10597 
grabbed_move(const tripoint & dp,const bool via_ramp)10598 bool game::grabbed_move( const tripoint &dp, const bool via_ramp )
10599 {
10600     if( u.get_grab_type() == object_type::NONE ) {
10601         return false;
10602     }
10603 
10604     if( dp.z != 0 ) {
10605         // No dragging stuff up/down stairs yet!
10606         return false;
10607     }
10608 
10609     // vehicle: pulling, pushing, or moving around the grabbed object.
10610     if( u.get_grab_type() == object_type::VEHICLE ) {
10611         return grabbed_veh_move( dp );
10612     }
10613 
10614     if( u.get_grab_type() == object_type::FURNITURE ) {
10615         u.assign_activity( player_activity( move_furniture_activity_actor( dp, via_ramp ) ) );
10616         return true;
10617     }
10618 
10619     add_msg( m_info, _( "Nothing at grabbed point %d,%d,%d or bad grabbed object type." ),
10620              u.grab_point.x, u.grab_point.y, u.grab_point.z );
10621     u.grab( object_type::NONE );
10622     return false;
10623 }
10624 
on_move_effects()10625 void game::on_move_effects()
10626 {
10627     // TODO: Move this to a character method
10628     if( !u.is_mounted() ) {
10629         const item muscle( "muscle" );
10630         for( const bionic_id &bid : u.get_bionic_fueled_with( muscle ) ) {
10631             if( u.has_active_bionic( bid ) ) {// active power gen
10632                 u.mod_power_level( units::from_kilojoule( muscle.fuel_energy() ) * bid->fuel_efficiency );
10633             } else if( u.has_bionic( bid ) ) {// passive power gen
10634                 u.mod_power_level( units::from_kilojoule( muscle.fuel_energy() ) * bid->passive_fuel_efficiency );
10635             }
10636         }
10637 
10638         if( u.has_active_bionic( bionic_id( "bio_jointservo" ) ) ) {
10639             if( u.is_running() ) {
10640                 u.mod_power_level( -55_J );
10641             } else {
10642                 u.mod_power_level( -35_J );
10643             }
10644         }
10645     }
10646 
10647     if( u.is_running() ) {
10648         if( !u.can_run() ) {
10649             u.toggle_run_mode();
10650         }
10651         if( u.get_stamina() < u.get_stamina_max() / 5 && one_in( u.get_stamina() ) ) {
10652             u.add_effect( effect_winded, 10_turns );
10653         }
10654     }
10655 
10656     // apply martial art move bonuses
10657     u.martial_arts_data->ma_onmove_effects( u );
10658 
10659     sfx::do_ambient();
10660 }
10661 
on_options_changed()10662 void game::on_options_changed()
10663 {
10664 #if defined(TILES)
10665     tilecontext->on_options_changed();
10666 #endif
10667 }
10668 
fling_creature(Creature * c,const units::angle & dir,float flvel,bool controlled)10669 void game::fling_creature( Creature *c, const units::angle &dir, float flvel, bool controlled )
10670 {
10671     if( c == nullptr ) {
10672         debugmsg( "game::fling_creature invoked on null target" );
10673         return;
10674     }
10675 
10676     if( c->is_dead_state() ) {
10677         // Flinging a corpse causes problems, don't enable without testing
10678         return;
10679     }
10680 
10681     if( c->is_hallucination() ) {
10682         // Don't fling hallucinations
10683         return;
10684     }
10685 
10686     int steps = 0;
10687     bool thru = true;
10688     const bool is_u = ( c == &u );
10689     // Don't animate critters getting bashed if animations are off
10690     const bool animate = is_u || get_option<bool>( "ANIMATIONS" );
10691 
10692     player *p = dynamic_cast<player *>( c );
10693 
10694     tileray tdir( dir );
10695     int range = flvel / 10;
10696     tripoint pt = c->pos();
10697     while( range > 0 ) {
10698         c->underwater = false;
10699         // TODO: Check whenever it is actually in the viewport
10700         // or maybe even just redraw the changed tiles
10701         bool seen = is_u || u.sees( *c ); // To avoid redrawing when not seen
10702         tdir.advance();
10703         pt.x = c->posx() + tdir.dx();
10704         pt.y = c->posy() + tdir.dy();
10705         float force = 0.0f;
10706 
10707         if( monster *const mon_ptr = critter_at<monster>( pt ) ) {
10708             monster &critter = *mon_ptr;
10709             // Approximate critter's "stopping power" with its max hp
10710             force = std::min<float>( 1.5f * critter.type->hp, flvel );
10711             const int damage = rng( force, force * 2.0f ) / 6;
10712             c->impact( damage, pt );
10713             // Multiply zed damage by 6 because no body parts
10714             const int zed_damage = std::max( 0,
10715                                              ( damage - critter.get_armor_bash( bodypart_id( "torso" ) ) ) * 6 );
10716             // TODO: Pass the "flinger" here - it's not the flung critter that deals damage
10717             critter.apply_damage( c, bodypart_id( "torso" ), zed_damage );
10718             critter.check_dead_state();
10719             if( !critter.is_dead() ) {
10720                 thru = false;
10721             }
10722         } else if( m.impassable( pt ) ) {
10723             if( !m.veh_at( pt ).obstacle_at_part() ) {
10724                 force = std::min<float>( m.bash_strength( pt ), flvel );
10725             } else {
10726                 // No good way of limiting force here
10727                 // Keep it 1 less than maximum to make the impact hurt
10728                 // but to keep the target flying after it
10729                 force = flvel - 1;
10730             }
10731             const int damage = rng( force, force * 2.0f ) / 9;
10732             c->impact( damage, pt );
10733             if( m.is_bashable( pt ) ) {
10734                 // Only go through if we successfully make the tile passable
10735                 m.bash( pt, flvel );
10736                 thru = m.passable( pt );
10737             } else {
10738                 thru = false;
10739             }
10740         }
10741 
10742         // If the critter dies during flinging, moving it around causes debugmsgs
10743         if( c->is_dead_state() ) {
10744             return;
10745         }
10746 
10747         flvel -= force;
10748         if( thru ) {
10749             if( p != nullptr ) {
10750                 if( p->in_vehicle ) {
10751                     m.unboard_vehicle( p->pos() );
10752                 }
10753                 // If we're flinging the player around, make sure the map stays centered on them.
10754                 if( is_u ) {
10755                     update_map( pt.x, pt.y );
10756                 } else {
10757                     p->setpos( pt );
10758                 }
10759             } else if( !critter_at( pt ) ) {
10760                 // Dying monster doesn't always leave an empty tile (blob spawning etc.)
10761                 // Just don't setpos if it happens - next iteration will do so
10762                 // or the monster will stop a tile before the unpassable one
10763                 c->setpos( pt );
10764             }
10765         } else {
10766             // Don't zero flvel - count this as slamming both the obstacle and the ground
10767             // although at lower velocity
10768             break;
10769         }
10770         range--;
10771         steps++;
10772         if( animate && ( seen || u.sees( *c ) ) ) {
10773             invalidate_main_ui_adaptor();
10774             ui_manager::redraw_invalidated();
10775             refresh_display();
10776         }
10777     }
10778 
10779     // Fall down to the ground - always on the last reached tile
10780     if( !m.has_flag( "SWIMMABLE", c->pos() ) ) {
10781         const trap &trap_under_creature = m.tr_at( c->pos() );
10782         // Didn't smash into a wall or a floor so only take the fall damage
10783         if( thru && trap_under_creature == tr_ledge ) {
10784             m.creature_on_trap( *c, false );
10785         } else {
10786             // Fall on ground
10787             int force = rng( flvel, flvel * 2 ) / 9;
10788             if( controlled ) {
10789                 force = std::max( force / 2 - 5, 0 );
10790             }
10791             if( force > 0 ) {
10792                 int dmg = c->impact( force, c->pos() );
10793                 // TODO: Make landing damage the floor
10794                 m.bash( c->pos(), dmg / 4, false, false, false );
10795             }
10796             // Always apply traps to creature i.e. bear traps, tele traps etc.
10797             m.creature_on_trap( *c, false );
10798         }
10799     } else {
10800         c->underwater = true;
10801         if( is_u ) {
10802             if( controlled ) {
10803                 add_msg( _( "You dive into water." ) );
10804             } else {
10805                 add_msg( m_warning, _( "You fall into water." ) );
10806             }
10807         }
10808     }
10809 }
10810 
point_selection_menu(const std::vector<tripoint> & pts)10811 static cata::optional<tripoint> point_selection_menu( const std::vector<tripoint> &pts )
10812 {
10813     if( pts.empty() ) {
10814         debugmsg( "point_selection_menu called with empty point set" );
10815         return cata::nullopt;
10816     }
10817 
10818     if( pts.size() == 1 ) {
10819         return pts[0];
10820     }
10821 
10822     const tripoint &upos = get_player_character().pos();
10823     uilist pmenu;
10824     pmenu.title = _( "Climb where?" );
10825     int num = 0;
10826     for( const tripoint &pt : pts ) {
10827         // TODO: Sort the menu so that it can be used with numpad directions
10828         const std::string &direction = direction_name( direction_from( upos.xy(), pt.xy() ) );
10829         // TODO: Inform player what is on said tile
10830         // But don't just print terrain name (in many cases it will be "open air")
10831         pmenu.addentry( num++, true, MENU_AUTOASSIGN, _( "Climb %s" ), direction );
10832     }
10833 
10834     pmenu.query();
10835     const int ret = pmenu.ret;
10836     if( ret < 0 || ret >= num ) {
10837         return cata::nullopt;
10838     }
10839 
10840     return pts[ret];
10841 }
10842 
find_empty_spot_nearby(const tripoint & pos)10843 static cata::optional<tripoint> find_empty_spot_nearby( const tripoint &pos )
10844 {
10845     map &here = get_map();
10846     for( const tripoint &p : here.points_in_radius( pos, 1 ) ) {
10847         if( p == pos ) {
10848             continue;
10849         }
10850         if( here.impassable( p ) ) {
10851             continue;
10852         }
10853         if( g->critter_at( p ) ) {
10854             continue;
10855         }
10856         return p;
10857     }
10858     return cata::nullopt;
10859 }
10860 
vertical_move(int movez,bool force,bool peeking)10861 void game::vertical_move( int movez, bool force, bool peeking )
10862 {
10863     if( u.is_mounted() ) {
10864         monster *mons = u.mounted_creature.get();
10865         if( mons->has_flag( MF_RIDEABLE_MECH ) ) {
10866             if( !mons->check_mech_powered() ) {
10867                 add_msg( m_bad, _( "Your %s refuses to move as its batteries have been drained." ),
10868                          mons->get_name() );
10869                 return;
10870             }
10871         }
10872     }
10873 
10874     map &here = get_map();
10875     // > and < are used for diving underwater.
10876     if( here.has_flag( "SWIMMABLE", u.pos() ) && here.has_flag( TFLAG_DEEP_WATER, u.pos() ) ) {
10877         if( movez == -1 ) {
10878             if( u.is_underwater() ) {
10879                 add_msg( m_info, _( "You are already underwater!" ) );
10880                 return;
10881             }
10882             if( u.worn_with_flag( flag_FLOTATION ) ) {
10883                 add_msg( m_info, _( "You can't dive while wearing a flotation device." ) );
10884                 return;
10885             }
10886             u.set_underwater( true );
10887             ///\EFFECT_STR increases breath-holding capacity while diving
10888             u.oxygen = 30 + 2 * u.str_cur;
10889             add_msg( _( "You dive underwater!" ) );
10890         } else {
10891             if( u.swim_speed() < 500 || u.shoe_type_count( itype_swim_fins ) ) {
10892                 u.set_underwater( false );
10893                 add_msg( _( "You surface." ) );
10894             } else {
10895                 add_msg( m_info, _( "You try to surface but can't!" ) );
10896             }
10897         }
10898         u.moves -= 100;
10899         return;
10900     }
10901 
10902     // Force means we're going down, even if there's no staircase, etc.
10903     bool climbing = false;
10904     int move_cost = 100;
10905     tripoint stairs( u.posx(), u.posy(), u.posz() + movez );
10906     if( here.has_zlevels() && !force && movez == 1 && !here.has_flag( "GOES_UP", u.pos() ) ) {
10907         // Climbing
10908         if( here.has_floor_or_support( stairs ) ) {
10909             add_msg( m_info, _( "You can't climb here - there's a ceiling above your head." ) );
10910             return;
10911         }
10912 
10913         const int cost = u.climbing_cost( u.pos(), stairs );
10914         const bool can_climb_here = cost > 0 ||
10915                                     u.has_trait_flag( STATIC( json_character_flag( "CLIMB_NO_LADDER" ) ) );
10916         if( !can_climb_here ) {
10917             add_msg( m_info, _( "You can't climb here - you need walls and/or furniture to brace against." ) );
10918             return;
10919         }
10920 
10921         std::vector<tripoint> pts;
10922         for( const auto &pt : here.points_in_radius( stairs, 1 ) ) {
10923             if( here.passable( pt ) &&
10924                 here.has_floor_or_support( pt ) ) {
10925                 pts.push_back( pt );
10926             }
10927         }
10928 
10929         if( pts.empty() ) {
10930             add_msg( m_info,
10931                      _( "You can't climb here - there is no terrain above you that would support your weight." ) );
10932             return;
10933         } else {
10934             // TODO: Make it an extended action
10935             climbing = true;
10936             move_cost = cost;
10937 
10938             const cata::optional<tripoint> pnt = point_selection_menu( pts );
10939             if( !pnt ) {
10940                 return;
10941             }
10942             stairs = *pnt;
10943         }
10944     }
10945 
10946     if( !force && movez == -1 && !here.has_flag( "GOES_DOWN", u.pos() ) ) {
10947         add_msg( m_info, _( "You can't go down here!" ) );
10948         return;
10949     } else if( !climbing && !force && movez == 1 && !here.has_flag( "GOES_UP", u.pos() ) ) {
10950         add_msg( m_info, _( "You can't go up here!" ) );
10951         return;
10952     }
10953 
10954     if( force ) {
10955         // Let go of a grabbed cart.
10956         u.grab( object_type::NONE );
10957     } else if( u.grab_point != tripoint_zero ) {
10958         add_msg( m_info, _( "You can't drag things up and down stairs." ) );
10959         return;
10960     }
10961 
10962     // Because abs_sub will change when vertical_shift (here.has_zlevels() == true)
10963     // is called or when the map is loaded on new z-level (== false).
10964     // This caches the z-level we start the movement on (current) and the level we're want to end.
10965     const int z_before = m.get_abs_sub().z;
10966     const int z_after = m.get_abs_sub().z + movez;
10967     if( z_after < -OVERMAP_DEPTH || z_after > OVERMAP_HEIGHT ) {
10968         debugmsg( "Tried to move outside allowed range of z-levels" );
10969         return;
10970     }
10971 
10972     if( !u.move_effects( false ) ) {
10973         u.moves -= 100;
10974         return;
10975     }
10976 
10977     if( here.has_flag( "UNSTABLE", u.pos() ) ) {
10978         u.moves -= 500;
10979         if( movez == 1 && slip_down() ) {
10980             return;
10981         }
10982     }
10983 
10984     // Check if there are monsters are using the stairs.
10985     bool slippedpast = false;
10986     if( !here.has_zlevels() && !coming_to_stairs.empty() && !force ) {
10987         // TODO: Allow travel if zombie couldn't reach stairs, but spawn him when we go up.
10988         add_msg( m_warning, _( "You try to use the stairs.  Suddenly you are blocked by a %s!" ),
10989                  coming_to_stairs[0].name() );
10990         // Roll.
10991         ///\EFFECT_DEX increases chance of moving past monsters on stairs
10992 
10993         ///\EFFECT_DODGE increases chance of moving past monsters on stairs
10994         int dexroll = dice( 6, u.dex_cur + u.get_skill_level( skill_dodge ) * 2 );
10995         ///\EFFECT_STR increases chance of moving past monsters on stairs
10996 
10997         ///\EFFECT_MELEE increases chance of moving past monsters on stairs
10998         int strroll = dice( 3, u.str_cur + u.get_skill_level( skill_melee ) * 1.5 );
10999         if( coming_to_stairs.size() > 4 ) {
11000             add_msg( _( "The are a lot of them on the %s!" ), here.tername( u.pos() ) );
11001             dexroll /= 4;
11002             strroll /= 2;
11003         } else if( coming_to_stairs.size() > 1 ) {
11004             add_msg( m_warning, _( "There's something else behind it!" ) );
11005             dexroll /= 2;
11006         }
11007 
11008         if( dexroll < 14 || strroll < 12 ) {
11009             update_stair_monsters();
11010             u.moves -= 100;
11011             return;
11012         }
11013 
11014         add_msg( _( "You manage to slip past!" ) );
11015         slippedpast = true;
11016         u.moves -= 100;
11017     }
11018 
11019     // Shift the map up or down
11020 
11021     std::unique_ptr<map> tmp_map_ptr;
11022     if( !here.has_zlevels() ) {
11023         tmp_map_ptr = std::make_unique<map>();
11024     }
11025 
11026     map &maybetmp = here.has_zlevels() ? m : *( tmp_map_ptr.get() );
11027     if( here.has_zlevels() ) {
11028         // We no longer need to shift the map here! What joy
11029     } else {
11030         // TODO: fix point types
11031         maybetmp.load( tripoint_abs_sm( point_abs_sm( here.get_abs_sub().xy() ), z_after ), false );
11032     }
11033 
11034     // Find the corresponding staircase
11035     bool rope_ladder = false;
11036     // TODO: Remove the stairfinding, make the mapgen gen aligned maps
11037     if( !force && !climbing ) {
11038         const cata::optional<tripoint> pnt = find_or_make_stairs( maybetmp, z_after, rope_ladder, peeking );
11039         if( !pnt ) {
11040             return;
11041         }
11042         stairs = *pnt;
11043     }
11044 
11045     if( !force ) {
11046         monstairz = z_before;
11047     }
11048     // Save all monsters that can reach the stairs, remove them from the tracker,
11049     // then despawn the remaining monsters. Because it's a vertical shift, all
11050     // monsters are out of the bounds of the map and will despawn.
11051     shared_ptr_fast<monster> stored_mount;
11052     if( u.is_mounted() && !here.has_zlevels() ) {
11053         // Store a *copy* of the mount, so we can remove the original monster instance
11054         // from the tracker before the map shifts.
11055         // Map shifting would otherwise just despawn the mount and would later respawn it.
11056         stored_mount = make_shared_fast<monster>( *u.mounted_creature );
11057         critter_tracker->remove( *u.mounted_creature );
11058     }
11059     if( !here.has_zlevels() ) {
11060         const tripoint to = u.pos();
11061         for( monster &critter : all_monsters() ) {
11062             // if its a ladder instead of stairs - most zombies can't climb that.
11063             // unless that have a special flag to allow them to do so.
11064             if( ( here.has_flag( "DIFFICULT_Z", u.pos() ) && !critter.climbs() ) ||
11065                 critter.has_effect( effect_ridden ) ||
11066                 critter.has_effect( effect_tied ) ) {
11067                 continue;
11068             }
11069             int turns = critter.turns_to_reach( to.xy() );
11070             if( turns < 10 && coming_to_stairs.size() < 8 && critter.will_reach( to.xy() )
11071                 && !slippedpast ) {
11072                 critter.staircount = 10 + turns;
11073                 critter.on_unload();
11074                 coming_to_stairs.push_back( critter );
11075                 remove_zombie( critter );
11076             }
11077         }
11078         auto mons = critter_tracker->find( u.pos() );
11079         if( mons != nullptr ) {
11080             critter_tracker->remove( *mons );
11081         }
11082         shift_monsters( tripoint( 0, 0, movez ) );
11083     }
11084 
11085     std::vector<shared_ptr_fast<npc>> npcs_to_bring;
11086     std::vector<monster *> monsters_following;
11087     if( !here.has_zlevels() && std::abs( movez ) == 1 ) {
11088         std::copy_if( active_npc.begin(), active_npc.end(), back_inserter( npcs_to_bring ),
11089         [this]( const shared_ptr_fast<npc> &np ) {
11090             return np->is_walking_with() && !np->is_mounted() && !np->in_sleep_state() &&
11091                    rl_dist( np->pos(), u.pos() ) < 2;
11092         } );
11093     }
11094 
11095     if( here.has_zlevels() && std::abs( movez ) == 1 ) {
11096         bool ladder = here.has_flag( "DIFFICULT_Z", u.pos() );
11097         for( monster &critter : all_monsters() ) {
11098             if( ladder && !critter.climbs() ) {
11099                 continue;
11100             }
11101             Creature *target = critter.attack_target();
11102             if( ( target && target->is_avatar() ) || ( !critter.has_effect( effect_ridden ) &&
11103                     critter.has_effect( effect_pet ) && critter.friendly == -1 &&
11104                     !critter.has_effect( effect_tied ) ) ) {
11105                 monsters_following.push_back( &critter );
11106             }
11107         }
11108     }
11109 
11110     if( u.is_mounted() ) {
11111         monster *crit = u.mounted_creature.get();
11112         if( crit->has_flag( MF_RIDEABLE_MECH ) ) {
11113             crit->use_mech_power( -u.current_movement_mode()->mech_power_use() - 1 );
11114         }
11115     } else {
11116         u.moves -= move_cost;
11117     }
11118     for( const auto &np : npcs_to_bring ) {
11119         if( np->in_vehicle ) {
11120             here.unboard_vehicle( np->pos() );
11121         }
11122     }
11123     const tripoint old_pos = u.pos();
11124     const tripoint old_abs_pos = here.getabs( old_pos );
11125     point submap_shift;
11126     vertical_shift( z_after );
11127     if( !force ) {
11128         submap_shift = update_map( stairs.x, stairs.y );
11129     }
11130 
11131     // if an NPC or monster is on the stiars when player ascends/descends
11132     // they may end up merged on th esame tile, do some displacement to resolve that.
11133     // if, in the weird case of it not being possible to displace;
11134     // ( how did the player even manage to approach the stairs, if so? )
11135     // then nothing terrible happens, its just weird.
11136     if( critter_at<npc>( u.pos(), true ) || critter_at<monster>( u.pos(), true ) ) {
11137         std::string crit_name;
11138         bool player_displace = false;
11139         cata::optional<tripoint> displace = find_empty_spot_nearby( u.pos() );
11140         if( displace.has_value() ) {
11141             npc *guy = g->critter_at<npc>( u.pos(), true );
11142             if( guy ) {
11143                 crit_name = guy->get_name();
11144                 tripoint old_pos = guy->pos();
11145                 if( !guy->is_enemy() ) {
11146                     guy->move_away_from( u.pos(), true );
11147                     if( old_pos != guy->pos() ) {
11148                         add_msg( _( "%s moves out of the way for you." ), guy->get_name() );
11149                     }
11150                 } else {
11151                     player_displace = true;
11152                 }
11153             }
11154             monster *mon = g->critter_at<monster>( u.pos(), true );
11155             // if the monster is ridden by the player or an NPC:
11156             // Dont displace them. If they are mounted by a friendly NPC,
11157             // then the NPC will already have been displaced just above.
11158             // if they are ridden by the player, we want them to coexist on same tile
11159             if( mon && !mon->mounted_player ) {
11160                 crit_name = mon->get_name();
11161                 if( mon->friendly == -1 ) {
11162                     mon->setpos( *displace );
11163                     add_msg( _( "Your %s moves out of the way for you." ), mon->get_name() );
11164                 } else {
11165                     player_displace = true;
11166                 }
11167             }
11168             if( player_displace ) {
11169                 u.setpos( *displace );
11170                 u.moves -= 20;
11171                 add_msg( _( "You push past %s blocking the way." ), crit_name );
11172             }
11173         } else {
11174             debugmsg( "Failed to find a spot to displace into." );
11175         }
11176     }
11177 
11178     // Now that we know the player's destination position, we can move their mount as well
11179     if( u.is_mounted() ) {
11180         if( stored_mount ) {
11181             cata_assert( !here.has_zlevels() );
11182             stored_mount->spawn( u.pos() );
11183             if( critter_tracker->add( stored_mount ) ) {
11184                 u.mounted_creature = stored_mount;
11185             }
11186         } else {
11187             u.mounted_creature->setpos( u.pos() );
11188         }
11189     }
11190 
11191     if( !npcs_to_bring.empty() ) {
11192         // Would look nicer randomly scrambled
11193         std::vector<tripoint> candidates = closest_points_first( u.pos(), 1 );
11194         candidates.erase( std::remove_if( candidates.begin(), candidates.end(),
11195         [this]( const tripoint & c ) {
11196             return !is_empty( c );
11197         } ), candidates.end() );
11198 
11199         for( const auto &np : npcs_to_bring ) {
11200             const auto found = std::find_if( candidates.begin(), candidates.end(),
11201             [np, &here]( const tripoint & c ) {
11202                 // @TODO NPC should appear on top of invisible traps (and trigger them),
11203                 // instead of magically choosing tiles without dangerous traps.
11204                 return !np->is_dangerous_fields( here.field_at( c ) ) && here.tr_at( c ).is_benign();
11205             } );
11206             if( found != candidates.end() ) {
11207                 // TODO: De-uglify
11208                 np->setpos( *found );
11209                 np->place_on_map();
11210                 np->setpos( *found );
11211                 candidates.erase( found );
11212             }
11213 
11214             if( candidates.empty() ) {
11215                 break;
11216             }
11217         }
11218 
11219         reload_npcs();
11220     }
11221 
11222     // This ugly check is here because of stair teleport bullshit
11223     // TODO: Remove stair teleport bullshit
11224     if( rl_dist( u.pos(), old_pos ) <= 1 ) {
11225         for( monster *m : monsters_following ) {
11226             m->set_dest( u.pos() );
11227         }
11228     }
11229 
11230     if( rope_ladder ) {
11231         here.ter_set( u.pos(), t_rope_up );
11232     }
11233 
11234     if( here.ter( stairs ) == t_manhole_cover ) {
11235         here.spawn_item( stairs + point( rng( -1, 1 ), rng( -1, 1 ) ), itype_manhole_cover );
11236         here.ter_set( stairs, t_manhole );
11237     }
11238 
11239     // Wouldn't work and may do strange things
11240     if( u.is_hauling() && !here.has_zlevels() ) {
11241         add_msg( _( "You cannot haul items here." ) );
11242         u.stop_hauling();
11243     }
11244 
11245     if( u.is_hauling() ) {
11246         const tripoint adjusted_pos = old_pos - sm_to_ms_copy( submap_shift );
11247         start_hauling( adjusted_pos );
11248     }
11249 
11250     here.invalidate_map_cache( here.get_abs_sub().z );
11251     // Upon force movement, traps can not be avoided.
11252     here.creature_on_trap( u, !force );
11253 
11254     cata_event_dispatch::avatar_moves( old_abs_pos, u, m );
11255 }
11256 
start_hauling(const tripoint & pos)11257 void game::start_hauling( const tripoint &pos )
11258 {
11259     // Find target items and quantities thereof for the new activity
11260     const std::vector<item_location> target_items = m.get_haulable_items( pos );
11261     // Quantity of 0 means move all
11262     const std::vector<int> quantities( target_items.size(), 0 );
11263 
11264     if( target_items.empty() ) {
11265         // Nothing to haul
11266         u.stop_hauling();
11267         return;
11268     }
11269 
11270     // Whether the destination is inside a vehicle (not supported)
11271     const bool to_vehicle = false;
11272     // Destination relative to the player
11273     const tripoint relative_destination{};
11274 
11275     u.assign_activity( player_activity( move_items_activity_actor(
11276                                             target_items,
11277                                             quantities,
11278                                             to_vehicle,
11279                                             relative_destination
11280                                         ) ) );
11281 }
11282 
find_or_make_stairs(map & mp,const int z_after,bool & rope_ladder,bool peeking)11283 cata::optional<tripoint> game::find_or_make_stairs( map &mp, const int z_after, bool &rope_ladder,
11284         bool peeking )
11285 {
11286     const int omtilesz = SEEX * 2;
11287     real_coords rc( m.getabs( point( u.posx(), u.posy() ) ) );
11288     tripoint omtile_align_start( m.getlocal( rc.begin_om_pos() ), z_after );
11289     tripoint omtile_align_end( omtile_align_start + point( -1 + omtilesz, -1 + omtilesz ) );
11290 
11291     // Try to find the stairs.
11292     cata::optional<tripoint> stairs;
11293     int best = INT_MAX;
11294     const int movez = z_after - m.get_abs_sub().z;
11295     const bool going_down_1 = movez == -1;
11296     const bool going_up_1 = movez == 1;
11297     // If there are stairs on the same x and y as we currently are, use those
11298     if( going_down_1 && mp.has_flag( TFLAG_GOES_UP, u.pos() + tripoint_below ) ) {
11299         stairs.emplace( u.pos() + tripoint_below );
11300     }
11301     if( going_up_1 && mp.has_flag( TFLAG_GOES_DOWN, u.pos() + tripoint_above ) ) {
11302         stairs.emplace( u.pos() + tripoint_above );
11303     }
11304     // We did not find stairs directly above or below, so search the map for them
11305     if( !stairs.has_value() ) {
11306         for( const tripoint &dest : m.points_in_rectangle( omtile_align_start, omtile_align_end ) ) {
11307             if( rl_dist( u.pos(), dest ) <= best &&
11308                 ( ( going_down_1 && mp.has_flag( TFLAG_GOES_UP, dest ) ) ||
11309                   ( going_up_1 && ( mp.has_flag( TFLAG_GOES_DOWN, dest ) ||
11310                                     mp.ter( dest ) == t_manhole_cover ) ) ||
11311                   ( ( movez == 2 || movez == -2 ) && mp.ter( dest ) == t_elevator ) ) ) {
11312                 stairs.emplace( dest );
11313                 best = rl_dist( u.pos(), dest );
11314             }
11315         }
11316     }
11317 
11318     if( stairs.has_value() ) {
11319         if( Creature *blocking_creature = critter_at( stairs.value() ) ) {
11320             npc *guy = dynamic_cast<npc *>( blocking_creature );
11321             monster *mon = dynamic_cast<monster *>( blocking_creature );
11322             bool would_move = ( guy && !guy->is_enemy() ) || ( mon && mon->friendly == -1 );
11323             bool can_displace = find_empty_spot_nearby( *stairs ).has_value();
11324             std::string cr_name = blocking_creature->get_name();
11325             std::string msg;
11326             if( guy ) {
11327                 //~ %s is the name of hostile NPC
11328                 msg = string_format( _( "%s is in the way!" ), cr_name );
11329             } else {
11330                 //~ %s is some monster
11331                 msg = string_format( _( "There's a %s in the way!" ), cr_name );
11332             }
11333 
11334             if( ( peeking && !would_move ) || !can_displace || ( !would_move && !query_yn(
11335                         //~ %s is a warning about monster/hostile NPC in the way, e.g. "There's a zombie in the way!"
11336                         _( "%s  Attempt to push past?  You may have to fight your way back up." ), msg ) ) ) {
11337                 add_msg( msg );
11338                 return cata::nullopt;
11339             }
11340         }
11341         return stairs;
11342     }
11343 
11344     // No stairs found! Try to make some
11345     rope_ladder = false;
11346     stairs.emplace( u.pos() );
11347     stairs->z = z_after;
11348     // Check the destination area for lava.
11349     if( mp.ter( *stairs ) == t_lava ) {
11350         if( movez < 0 &&
11351             !query_yn(
11352                 _( "There is a LOT of heat coming out of there, even the stairs have melted away.  Jump down?  You won't be able to get back up." ) ) ) {
11353             return cata::nullopt;
11354         } else if( movez > 0 &&
11355                    !query_yn(
11356                        _( "There is a LOT of heat coming out of there.  Push through the half-molten rocks and ascend?  You will not be able to get back down." ) ) ) {
11357             return cata::nullopt;
11358         }
11359 
11360         return stairs;
11361     }
11362 
11363     if( movez > 0 ) {
11364         if( !mp.has_flag( "GOES_DOWN", *stairs ) ) {
11365             if( !query_yn( _( "You may be unable to return back down these stairs.  Continue up?" ) ) ) {
11366                 return cata::nullopt;
11367             }
11368         }
11369         // Manhole covers need this to work
11370         // Maybe require manhole cover here and fail otherwise?
11371         return stairs;
11372     }
11373 
11374     if( mp.impassable( *stairs ) ) {
11375         popup( _( "Halfway down, the way down becomes blocked off." ) );
11376         return cata::nullopt;
11377     }
11378 
11379     if( u.has_trait( trait_id( "WEB_RAPPEL" ) ) ) {
11380         if( query_yn( _( "There is a sheer drop halfway down.  Web-descend?" ) ) ) {
11381             rope_ladder = true;
11382             if( ( rng( 4, 8 ) ) < u.get_skill_level( skill_dodge ) ) {
11383                 add_msg( _( "You attach a web and dive down headfirst, flipping upright and landing on your feet." ) );
11384             } else {
11385                 add_msg( _( "You securely web up and work your way down, lowering yourself safely." ) );
11386             }
11387         } else {
11388             return cata::nullopt;
11389         }
11390     } else if( u.has_trait( trait_VINES2 ) || u.has_trait( trait_VINES3 ) ) {
11391         if( query_yn( _( "There is a sheer drop halfway down.  Use your vines to descend?" ) ) ) {
11392             if( u.has_trait( trait_VINES2 ) ) {
11393                 if( query_yn( _( "Detach a vine?  It'll hurt, but you'll be able to climb back up…" ) ) ) {
11394                     rope_ladder = true;
11395                     add_msg( m_bad, _( "You descend on your vines, though leaving a part of you behind stings." ) );
11396                     u.mod_pain( 5 );
11397                     u.apply_damage( nullptr, bodypart_id( "torso" ), 5 );
11398                     u.mod_stored_nutr( 10 );
11399                     u.mod_thirst( 10 );
11400                 } else {
11401                     add_msg( _( "You gingerly descend using your vines." ) );
11402                 }
11403             } else {
11404                 add_msg( _( "You effortlessly lower yourself and leave a vine rooted for future use." ) );
11405                 rope_ladder = true;
11406                 u.mod_stored_nutr( 10 );
11407                 u.mod_thirst( 10 );
11408             }
11409         } else {
11410             return cata::nullopt;
11411         }
11412     } else if( u.has_amount( itype_grapnel, 1 ) ) {
11413         if( query_yn( _( "There is a sheer drop halfway down.  Climb your grappling hook down?" ) ) ) {
11414             rope_ladder = true;
11415             u.use_amount( itype_grapnel, 1 );
11416         } else {
11417             return cata::nullopt;
11418         }
11419     } else if( u.has_amount( itype_rope_30, 1 ) ) {
11420         if( query_yn( _( "There is a sheer drop halfway down.  Climb your rope down?" ) ) ) {
11421             rope_ladder = true;
11422             u.use_amount( itype_rope_30, 1 );
11423         } else {
11424             return cata::nullopt;
11425         }
11426     } else if( !query_yn( _( "There is a sheer drop halfway down.  Jump?" ) ) ) {
11427         return cata::nullopt;
11428     }
11429 
11430     return stairs;
11431 }
11432 
vertical_shift(const int z_after)11433 void game::vertical_shift( const int z_after )
11434 {
11435     if( z_after < -OVERMAP_DEPTH || z_after > OVERMAP_HEIGHT ) {
11436         debugmsg( "Tried to get z-level %d outside allowed range of %d-%d",
11437                   z_after, -OVERMAP_DEPTH, OVERMAP_HEIGHT );
11438         return;
11439     }
11440 
11441     // TODO: Implement dragging stuff up/down
11442     u.grab( object_type::NONE );
11443 
11444     scent.reset();
11445 
11446     u.setz( z_after );
11447     const tripoint abs_sub = m.get_abs_sub();
11448     const int z_before = abs_sub.z;
11449     if( !m.has_zlevels() ) {
11450         m.access_cache( z_before ).vehicle_list.clear();
11451         m.access_cache( z_before ).zone_vehicles.clear();
11452         m.access_cache( z_before ).map_memory_seen_cache.reset();
11453         m.set_transparency_cache_dirty( z_before );
11454         m.set_outside_cache_dirty( z_before );
11455         // TODO: fix point types
11456         m.load( tripoint_abs_sm( point_abs_sm( abs_sub.xy() ), z_after ), true );
11457         shift_monsters( tripoint( 0, 0, z_after - z_before ) );
11458         reload_npcs();
11459         m.rebuild_vehicle_level_caches();
11460     } else {
11461         // Shift the map itself
11462         m.vertical_shift( z_after );
11463     }
11464 
11465     m.spawn_monsters( true );
11466     // this may be required after a vertical shift if z-levels are not enabled
11467     // the critter is unloaded/loaded, and it needs to reconstruct its rider data after being reloaded.
11468     validate_mounted_npcs();
11469     vertical_notes( z_before, z_after );
11470 }
11471 
vertical_notes(int z_before,int z_after)11472 void game::vertical_notes( int z_before, int z_after )
11473 {
11474     if( z_before == z_after || !get_option<bool>( "AUTO_NOTES" ) ||
11475         !get_option<bool>( "AUTO_NOTES_STAIRS" ) ) {
11476         return;
11477     }
11478 
11479     if( !m.inbounds_z( z_before ) || !m.inbounds_z( z_after ) ) {
11480         debugmsg( "game::vertical_notes invalid arguments: z_before == %d, z_after == %d",
11481                   z_before, z_after );
11482         return;
11483     }
11484     // Figure out where we know there are up/down connectors
11485     // Fill in all the tiles we know about (e.g. subway stations)
11486     static const int REVEAL_RADIUS = 40;
11487     for( const tripoint_abs_omt &p : points_in_radius( u.global_omt_location(), REVEAL_RADIUS ) ) {
11488         const tripoint_abs_omt cursp_before( p.xy(), z_before );
11489         const tripoint_abs_omt cursp_after( p.xy(), z_after );
11490 
11491         if( !overmap_buffer.seen( cursp_before ) ) {
11492             continue;
11493         }
11494         if( overmap_buffer.has_note( cursp_after ) ) {
11495             // Already has a note -> never add an AUTO-note
11496             continue;
11497         }
11498         const oter_id &ter = overmap_buffer.ter( cursp_before );
11499         const oter_id &ter2 = overmap_buffer.ter( cursp_after );
11500         if( z_after > z_before && ter->has_flag( oter_flags::known_up ) &&
11501             !ter2->has_flag( oter_flags::known_down ) ) {
11502             overmap_buffer.set_seen( cursp_after, true );
11503             overmap_buffer.add_note( cursp_after, string_format( ">:W;%s", _( "AUTO: goes down" ) ) );
11504         } else if( z_after < z_before && ter->has_flag( oter_flags::known_down ) &&
11505                    !ter2->has_flag( oter_flags::known_up ) ) {
11506             overmap_buffer.set_seen( cursp_after, true );
11507             overmap_buffer.add_note( cursp_after, string_format( "<:W;%s", _( "AUTO: goes up" ) ) );
11508         }
11509     }
11510 }
11511 
update_map(Character & p)11512 point game::update_map( Character &p )
11513 {
11514     point p2( p.posx(), p.posy() );
11515     return update_map( p2.x, p2.y );
11516 }
11517 
update_map(int & x,int & y)11518 point game::update_map( int &x, int &y )
11519 {
11520     point shift;
11521 
11522     while( x < HALF_MAPSIZE_X ) {
11523         x += SEEX;
11524         shift.x--;
11525     }
11526     while( x >= HALF_MAPSIZE_X + SEEX ) {
11527         x -= SEEX;
11528         shift.x++;
11529     }
11530     while( y < HALF_MAPSIZE_Y ) {
11531         y += SEEY;
11532         shift.y--;
11533     }
11534     while( y >= HALF_MAPSIZE_Y + SEEY ) {
11535         y -= SEEY;
11536         shift.y++;
11537     }
11538 
11539     if( shift == point_zero ) {
11540         // adjust player position
11541         u.setpos( tripoint( x, y, m.get_abs_sub().z ) );
11542         // Update what parts of the world map we can see
11543         // We need this call because even if the map hasn't shifted we may have changed z-level and can now see farther
11544         // TODO: only make this call if we changed z-level
11545         update_overmap_seen();
11546         // Not actually shifting the submaps, all the stuff below would do nothing
11547         return point_zero;
11548     }
11549 
11550     // this handles loading/unloading submaps that have scrolled on or off the viewport
11551     // NOLINTNEXTLINE(cata-use-named-point-constants)
11552     inclusive_rectangle<point> size_1( point( -1, -1 ), point( 1, 1 ) );
11553     point remaining_shift = shift;
11554     while( remaining_shift != point_zero ) {
11555         point this_shift = clamp( remaining_shift, size_1 );
11556         m.shift( this_shift );
11557         remaining_shift -= this_shift;
11558     }
11559 
11560     // Shift monsters
11561     shift_monsters( tripoint( shift, 0 ) );
11562     const point shift_ms = sm_to_ms_copy( shift );
11563     u.shift_destination( -shift_ms );
11564 
11565     // Shift NPCs
11566     for( auto it = active_npc.begin(); it != active_npc.end(); ) {
11567         ( *it )->shift( shift );
11568         if( ( *it )->posx() < 0 || ( *it )->posx() >= MAPSIZE_X ||
11569             ( *it )->posy() < 0 || ( *it )->posy() >= MAPSIZE_Y ) {
11570             //Remove the npc from the active list. It remains in the overmap list.
11571             ( *it )->on_unload();
11572             it = active_npc.erase( it );
11573         } else {
11574             it++;
11575         }
11576     }
11577 
11578     scent.shift( shift_ms );
11579 
11580     // Also ensure the player is on current z-level
11581     // m.get_abs_sub().z should later be removed, when there is no longer such a thing
11582     // as "current z-level"
11583     u.setpos( tripoint( x, y, m.get_abs_sub().z ) );
11584 
11585     // Only do the loading after all coordinates have been shifted.
11586 
11587     // Check for overmap saved npcs that should now come into view.
11588     // Put those in the active list.
11589     load_npcs();
11590 
11591     // Make sure map cache is consistent since it may have shifted.
11592     if( m.has_zlevels() ) {
11593         for( int zlev = -OVERMAP_DEPTH; zlev <= OVERMAP_HEIGHT; ++zlev ) {
11594             m.invalidate_map_cache( zlev );
11595         }
11596     } else {
11597         m.invalidate_map_cache( m.get_abs_sub().z );
11598     }
11599     m.build_map_cache( m.get_abs_sub().z );
11600 
11601     // Spawn monsters if appropriate
11602     // This call will generate new monsters in addition to loading, so it's placed after NPC loading
11603     m.spawn_monsters( false ); // Static monsters
11604 
11605     // Update what parts of the world map we can see
11606     update_overmap_seen();
11607 
11608     return shift;
11609 }
11610 
update_overmap_seen()11611 void game::update_overmap_seen()
11612 {
11613     const tripoint_abs_omt ompos = u.global_omt_location();
11614     const int dist = u.overmap_sight_range( light_level( u.posz() ) );
11615     const int dist_squared = dist * dist;
11616     // We can always see where we're standing
11617     overmap_buffer.set_seen( ompos, true );
11618     for( const tripoint_abs_omt &p : points_in_radius( ompos, dist ) ) {
11619         const point_rel_omt delta = p.xy() - ompos.xy();
11620         const int h_squared = delta.x() * delta.x() + delta.y() * delta.y();
11621         if( trigdist && h_squared > dist_squared ) {
11622             continue;
11623         }
11624         if( delta == point_rel_omt() ) {
11625             // 1. This case is already handled outside of the loop
11626             // 2. Calculating multiplier would cause division by zero
11627             continue;
11628         }
11629         // If circular distances are enabled, scale overmap distances by the diagonality of the sight line.
11630         point abs_delta = delta.raw().abs();
11631         int max_delta = std::max( abs_delta.x, abs_delta.y );
11632         const float multiplier = trigdist ? std::sqrt( h_squared ) / max_delta : 1;
11633         const std::vector<tripoint_abs_omt> line = line_to( ompos, p );
11634         float sight_points = dist;
11635         for( auto it = line.begin();
11636              it != line.end() && sight_points >= 0; ++it ) {
11637             const oter_id &ter = overmap_buffer.ter( *it );
11638             sight_points -= static_cast<int>( ter->get_see_cost() ) * multiplier;
11639         }
11640         if( sight_points >= 0 ) {
11641             tripoint_abs_omt seen( p );
11642             do {
11643                 overmap_buffer.set_seen( seen, true );
11644                 --seen.z();
11645             } while( seen.z() >= 0 );
11646         }
11647     }
11648 }
11649 
replace_stair_monsters()11650 void game::replace_stair_monsters()
11651 {
11652     for( auto &elem : coming_to_stairs ) {
11653         elem.staircount = 0;
11654         const tripoint pnt( elem.pos().xy(), m.get_abs_sub().z );
11655         place_critter_around( make_shared_fast<monster>( elem ), pnt, 10 );
11656     }
11657 
11658     coming_to_stairs.clear();
11659 }
11660 
11661 // TODO: abstract out the location checking code
11662 // TODO: refactor so zombies can follow up and down stairs instead of this mess
update_stair_monsters()11663 void game::update_stair_monsters()
11664 {
11665     // Search for the stairs closest to the player.
11666     std::vector<int> stairx;
11667     std::vector<int> stairy;
11668     std::vector<int> stairdist;
11669 
11670     const bool from_below = monstairz < m.get_abs_sub().z;
11671 
11672     if( coming_to_stairs.empty() ) {
11673         return;
11674     }
11675 
11676     if( m.has_zlevels() ) {
11677         debugmsg( "%d monsters coming to stairs on a map with z-levels",
11678                   coming_to_stairs.size() );
11679         coming_to_stairs.clear();
11680     }
11681 
11682     for( const tripoint &dest : m.points_on_zlevel( u.posz() ) ) {
11683         if( ( from_below && m.has_flag( "GOES_DOWN", dest ) ) ||
11684             ( !from_below && m.has_flag( "GOES_UP", dest ) ) ) {
11685             stairx.push_back( dest.x );
11686             stairy.push_back( dest.y );
11687             stairdist.push_back( rl_dist( dest, u.pos() ) );
11688         }
11689     }
11690     if( stairdist.empty() ) {
11691         return;         // Found no stairs?
11692     }
11693 
11694     // Find closest stairs.
11695     size_t si = 0;
11696     for( size_t i = 0; i < stairdist.size(); i++ ) {
11697         if( stairdist[i] < stairdist[si] ) {
11698             si = i;
11699         }
11700     }
11701 
11702     // Find up to 4 stairs for distance stairdist[si] +1
11703     std::vector<int> nearest;
11704     nearest.push_back( si );
11705     for( size_t i = 0; i < stairdist.size() && nearest.size() < 4; i++ ) {
11706         if( ( i != si ) && ( stairdist[i] <= stairdist[si] + 1 ) ) {
11707             nearest.push_back( i );
11708         }
11709     }
11710     // Randomize the stair choice
11711     si = random_entry_ref( nearest );
11712 
11713     // Attempt to spawn zombies.
11714     for( size_t i = 0; i < coming_to_stairs.size(); i++ ) {
11715         point mpos( stairx[si], stairy[si] );
11716         monster &critter = coming_to_stairs[i];
11717         const tripoint dest {
11718             mpos, get_map().get_abs_sub().z
11719         };
11720 
11721         // We might be not be visible.
11722         if( ( critter.posx() < 0 - ( MAPSIZE_X ) / 6 ||
11723               critter.posy() < 0 - ( MAPSIZE_Y ) / 6 ||
11724               critter.posx() > ( MAPSIZE_X * 7 ) / 6 ||
11725               critter.posy() > ( MAPSIZE_Y * 7 ) / 6 ) ) {
11726             continue;
11727         }
11728 
11729         critter.staircount -= 4;
11730         // Let the player know zombies are trying to come.
11731         if( u.sees( dest ) ) {
11732             std::string dump;
11733             if( critter.staircount > 4 ) {
11734                 dump += string_format( _( "You see a %s on the stairs" ), critter.name() );
11735             } else {
11736                 if( critter.staircount > 0 ) {
11737                     dump += ( from_below ?
11738                               //~ The <monster> is almost at the <bottom/top> of the <terrain type>!
11739                               string_format( _( "The %1$s is almost at the top of the %2$s!" ),
11740                                              critter.name(),
11741                                              m.tername( dest ) ) :
11742                               string_format( _( "The %1$s is almost at the bottom of the %2$s!" ),
11743                                              critter.name(),
11744                                              m.tername( dest ) ) );
11745                 }
11746             }
11747 
11748             add_msg( m_warning, dump );
11749         } else {
11750             sounds::sound( dest, 5, sounds::sound_t::movement,
11751                            _( "a sound nearby from the stairs!" ), true, "misc", "stairs_movement" );
11752         }
11753 
11754         if( critter.staircount > 0 ) {
11755             continue;
11756         }
11757 
11758         if( is_empty( dest ) ) {
11759             critter.spawn( dest );
11760             critter.staircount = 0;
11761             place_critter_at( make_shared_fast<monster>( critter ), dest );
11762             if( u.sees( dest ) ) {
11763                 if( !from_below ) {
11764                     add_msg( m_warning, _( "The %1$s comes down the %2$s!" ),
11765                              critter.name(),
11766                              m.tername( dest ) );
11767                 } else {
11768                     add_msg( m_warning, _( "The %1$s comes up the %2$s!" ),
11769                              critter.name(),
11770                              m.tername( dest ) );
11771                 }
11772             }
11773             coming_to_stairs.erase( coming_to_stairs.begin() + i );
11774             continue;
11775         } else if( u.pos() == dest ) {
11776             // Monster attempts to push player of stairs
11777             point push( point_north_west );
11778             int tries = 0;
11779 
11780             // the critter is now right on top of you and will attack unless
11781             // it can find a square to push you into with one of his tries.
11782             const int creature_push_attempts = 9;
11783             const int player_throw_resist_chance = 3;
11784 
11785             critter.spawn( dest );
11786             while( tries < creature_push_attempts ) {
11787                 tries++;
11788                 push.x = rng( -1, 1 );
11789                 push.y = rng( -1, 1 );
11790                 point ipos( mpos + push );
11791                 tripoint pos( ipos, m.get_abs_sub().z );
11792                 if( ( push.x != 0 || push.y != 0 ) && !critter_at( pos ) &&
11793                     critter.can_move_to( pos ) ) {
11794                     bool resiststhrow = ( u.is_throw_immune() ) ||
11795                                         ( u.has_trait( trait_LEG_TENT_BRACE ) );
11796                     if( resiststhrow && one_in( player_throw_resist_chance ) ) {
11797                         u.moves -= 25; // small charge for avoiding the push altogether
11798                         add_msg( _( "The %s fails to push you back!" ),
11799                                  critter.name() );
11800                         return; //judo or leg brace prevent you from getting pushed at all
11801                     }
11802                     // Not accounting for tentacles latching on, so..
11803                     // Something is about to happen, lets charge half a move
11804                     u.moves -= 50;
11805                     if( resiststhrow && ( u.is_throw_immune() ) ) {
11806                         //we have a judoka who isn't getting pushed but counterattacking now.
11807                         mattack::thrown_by_judo( &critter );
11808                         return;
11809                     }
11810                     std::string msg;
11811                     ///\EFFECT_DODGE reduces chance of being downed when pushed off the stairs
11812                     if( !( resiststhrow ) && ( u.get_dodge() + rng( 0, 3 ) < 12 ) ) {
11813                         // dodge 12 - never get downed
11814                         // 11.. avoid 75%; 10.. avoid 50%; 9.. avoid 25%
11815                         u.add_effect( effect_downed, 2_turns );
11816                         msg = _( "The %s pushed you back hard!" );
11817                     } else {
11818                         msg = _( "The %s pushed you back!" );
11819                     }
11820                     add_msg( m_warning, msg.c_str(), critter.name() );
11821                     u.setx( u.posx() + push.x );
11822                     u.sety( u.posy() + push.y );
11823                     return;
11824                 }
11825             }
11826             add_msg( m_warning,
11827                      _( "The %s tried to push you back but failed!  It attacks you!" ),
11828                      critter.name() );
11829             critter.melee_attack( u );
11830             u.moves -= 50;
11831             return;
11832         } else if( monster *const mon_ptr = critter_at<monster>( dest ) ) {
11833             // Monster attempts to displace a monster from the stairs
11834             monster &other = *mon_ptr;
11835             critter.spawn( dest );
11836 
11837             // the critter is now right on top of another and will push it
11838             // if it can find a square to push it into inside of his tries.
11839             const int creature_push_attempts = 9;
11840             const int creature_throw_resist = 4;
11841 
11842             int tries = 0;
11843             point push2;
11844             while( tries < creature_push_attempts ) {
11845                 tries++;
11846                 push2.x = rng( -1, 1 );
11847                 push2.y = rng( -1, 1 );
11848                 point ipos2( mpos + push2 );
11849                 tripoint pos( ipos2, m.get_abs_sub().z );
11850                 if( ( push2.x == 0 && push2.y == 0 ) || ( ( ipos2.x == u.posx() ) && ( ipos2.y == u.posy() ) ) ) {
11851                     continue;
11852                 }
11853                 if( !critter_at( pos ) && other.can_move_to( pos ) ) {
11854                     other.setpos( tripoint( ipos2, m.get_abs_sub().z ) );
11855                     other.moves -= 50;
11856                     std::string msg;
11857                     if( one_in( creature_throw_resist ) ) {
11858                         other.add_effect( effect_downed, 2_turns );
11859                         msg = _( "The %1$s pushed the %2$s hard." );
11860                     } else {
11861                         msg = _( "The %1$s pushed the %2$s." );
11862                     }
11863                     add_msg( m_neutral, msg, critter.name(), other.name() );
11864                     return;
11865                 }
11866             }
11867             return;
11868         }
11869     }
11870 }
11871 
despawn_monster(monster & critter)11872 void game::despawn_monster( monster &critter )
11873 {
11874     if( !critter.is_hallucination() ) {
11875         // hallucinations aren't stored, they come and go as they like,
11876         overmap_buffer.despawn_monster( critter );
11877     }
11878 
11879     critter.on_unload();
11880     remove_zombie( critter );
11881     // simulate it being dead so further processing of it (e.g. in monmove) will yield
11882     critter.set_hp( 0 );
11883 }
11884 
shift_monsters(const tripoint & shift)11885 void game::shift_monsters( const tripoint &shift )
11886 {
11887     // If either shift argument is non-zero, we're shifting.
11888     if( shift == tripoint_zero ) {
11889         return;
11890     }
11891     for( monster &critter : all_monsters() ) {
11892         if( shift.xy() != point_zero ) {
11893             critter.shift( shift.xy() );
11894         }
11895 
11896         if( m.inbounds( critter.pos() ) && ( shift.z == 0 || m.has_zlevels() ) ) {
11897             // We're inbounds, so don't despawn after all.
11898             // No need to shift Z-coordinates, they are absolute
11899             continue;
11900         }
11901         // Either a vertical shift or the critter is now outside of the reality bubble,
11902         // anyway: it must be saved and removed.
11903         despawn_monster( critter );
11904     }
11905     // The order in which zombies are shifted may cause zombies to briefly exist on
11906     // the same square. This messes up the mon_at cache, so we need to rebuild it.
11907     critter_tracker->rebuild_cache();
11908 }
11909 
perhaps_add_random_npc()11910 void game::perhaps_add_random_npc()
11911 {
11912     if( !calendar::once_every( 1_hours ) ) {
11913         return;
11914     }
11915     // Create a new NPC?
11916 
11917     double spawn_time = get_option<float>( "NPC_SPAWNTIME" );
11918     if( spawn_time == 0.0 ) {
11919         return;
11920     }
11921 
11922     // spawn algorithm is a chance per hour, but the config is specified in average days
11923     // actual chance per hour is (100 / 24 ) / days
11924     static constexpr double days_to_rate_factor = 100.0 / 24;
11925     double spawn_rate = days_to_rate_factor / spawn_time;
11926     static constexpr int radius_spawn_range = 90;
11927     std::vector<shared_ptr_fast<npc>> npcs = overmap_buffer.get_npcs_near_player( radius_spawn_range );
11928     size_t npc_num = npcs.size();
11929     for( auto &npc : npcs ) {
11930         if( npc->has_trait( trait_NPC_STATIC_NPC ) || npc->has_trait( trait_NPC_STARTING_NPC ) ) {
11931             npc_num--;
11932         }
11933     }
11934 
11935     if( npc_num > 0 ) {
11936         // 100%, 80%, 64%, 52%, 41%, 33%...
11937         spawn_rate *= std::pow( 0.8f, npc_num );
11938     }
11939 
11940     if( !x_in_y( spawn_rate, 100 ) ) {
11941         return;
11942     }
11943     bool spawn_allowed = false;
11944     tripoint_abs_omt spawn_point;
11945     int counter = 0;
11946     while( !spawn_allowed ) {
11947         if( counter >= 10 ) {
11948             return;
11949         }
11950         const tripoint_abs_omt u_omt = u.global_omt_location();
11951         spawn_point = u_omt + point( rng( -radius_spawn_range, radius_spawn_range ),
11952                                      rng( -radius_spawn_range, radius_spawn_range ) );
11953         spawn_point.z() = 0;
11954         const oter_id oter = overmap_buffer.ter( spawn_point );
11955         // shouldn't spawn on lakes or rivers.
11956         if( !is_river_or_lake( oter ) ) {
11957             spawn_allowed = true;
11958         }
11959         counter += 1;
11960     }
11961     shared_ptr_fast<npc> tmp = make_shared_fast<npc>();
11962     tmp->normalize();
11963     tmp->randomize();
11964     std::string new_fac_id = "solo_";
11965     new_fac_id += tmp->name;
11966     // create a new "lone wolf" faction for this one NPC
11967     faction *new_solo_fac = faction_manager_ptr->add_new_faction( tmp->name, faction_id( new_fac_id ),
11968                             faction_id( "no_faction" ) );
11969     tmp->set_fac( new_solo_fac ? new_solo_fac->id : faction_id( "no_faction" ) );
11970     // adds the npc to the correct overmap.
11971     // Only spawn random NPCs on z-level 0
11972     // TODO: fix point types
11973     tripoint submap_spawn = omt_to_sm_copy( spawn_point.raw() );
11974     tmp->spawn_at_sm( tripoint( submap_spawn.xy(), 0 ) );
11975     overmap_buffer.insert_npc( tmp );
11976     tmp->form_opinion( u );
11977     tmp->mission = NPC_MISSION_NULL;
11978     tmp->long_term_goal_action();
11979     tmp->add_new_mission( mission::reserve_random( ORIGIN_ANY_NPC, tmp->global_omt_location(),
11980                           tmp->getID() ) );
11981     // This will make the new NPC active- if its nearby to the player
11982     load_npcs();
11983 }
11984 
display_overlay_state(const action_id action)11985 bool game::display_overlay_state( const action_id action )
11986 {
11987     return displaying_overlays && *displaying_overlays == action;
11988 }
11989 
display_toggle_overlay(const action_id action)11990 void game::display_toggle_overlay( const action_id action )
11991 {
11992     if( display_overlay_state( action ) ) {
11993         displaying_overlays.reset();
11994     } else {
11995         displaying_overlays = action;
11996     }
11997 }
11998 
display_scent()11999 void game::display_scent()
12000 {
12001     if( use_tiles ) {
12002         display_toggle_overlay( ACTION_DISPLAY_SCENT );
12003     } else {
12004         int div;
12005         bool got_value = query_int( div, _( "Set the Scent Map sensitivity to (0 to cancel)?" ) );
12006         if( !got_value || div < 1 ) {
12007             add_msg( _( "Never mind." ) );
12008             return;
12009         }
12010         shared_ptr_fast<game::draw_callback_t> scent_cb = make_shared_fast<game::draw_callback_t>( [&]() {
12011             scent.draw( w_terrain, div * 2, u.pos() + u.view_offset );
12012         } );
12013         g->add_draw_callback( scent_cb );
12014 
12015         ui_manager::redraw();
12016         inp_mngr.wait_for_any_key();
12017     }
12018 }
12019 
display_temperature()12020 void game::display_temperature()
12021 {
12022     if( use_tiles ) {
12023         display_toggle_overlay( ACTION_DISPLAY_TEMPERATURE );
12024     }
12025 }
12026 
display_vehicle_ai()12027 void game::display_vehicle_ai()
12028 {
12029     if( use_tiles ) {
12030         display_toggle_overlay( ACTION_DISPLAY_VEHICLE_AI );
12031     }
12032 }
12033 
display_visibility()12034 void game::display_visibility()
12035 {
12036     if( use_tiles ) {
12037         display_toggle_overlay( ACTION_DISPLAY_VISIBILITY );
12038         if( display_overlay_state( ACTION_DISPLAY_VISIBILITY ) ) {
12039             std::vector< tripoint > locations;
12040             uilist creature_menu;
12041             int num_creatures = 0;
12042             creature_menu.addentry( num_creatures++, true, MENU_AUTOASSIGN, "%s", _( "You" ) );
12043             locations.emplace_back( get_player_character().pos() ); // add player first.
12044             for( const Creature &critter : g->all_creatures() ) {
12045                 if( critter.is_player() ) {
12046                     continue;
12047                 }
12048                 creature_menu.addentry( num_creatures++, true, MENU_AUTOASSIGN, critter.disp_name() );
12049                 locations.emplace_back( critter.pos() );
12050             }
12051 
12052             pointmenu_cb callback( locations );
12053             creature_menu.callback = &callback;
12054             creature_menu.w_y_setup = 0;
12055             creature_menu.query();
12056             if( creature_menu.ret >= 0 && static_cast<size_t>( creature_menu.ret ) < locations.size() ) {
12057                 Creature *creature = critter_at<Creature>( locations[creature_menu.ret] );
12058                 displaying_visibility_creature = creature;
12059             }
12060         } else {
12061             displaying_visibility_creature = nullptr;
12062         }
12063     }
12064 }
12065 
toggle_debug_hour_timer()12066 void game::toggle_debug_hour_timer()
12067 {
12068     debug_hour_timer.toggle();
12069 }
12070 
toggle()12071 void game::debug_hour_timer::toggle()
12072 {
12073     enabled = !enabled;
12074     start_time = cata::nullopt;
12075     add_msg( string_format( "debug timer %s", enabled ? "enabled" : "disabled" ) );
12076 }
12077 
print_time()12078 void game::debug_hour_timer::print_time()
12079 {
12080     if( enabled ) {
12081         if( calendar::once_every( time_duration::from_hours( 1 ) ) ) {
12082             const IRLTimeMs now = std::chrono::time_point_cast<std::chrono::milliseconds>(
12083                                       std::chrono::system_clock::now() );
12084             if( start_time ) {
12085                 add_msg( "in-game hour took: %d ms", ( now - *start_time ).count() );
12086             } else {
12087                 add_msg( "starting debug timer" );
12088             }
12089             start_time = now;
12090         }
12091     }
12092 }
12093 
display_lighting()12094 void game::display_lighting()
12095 {
12096     if( use_tiles ) {
12097         display_toggle_overlay( ACTION_DISPLAY_LIGHTING );
12098         if( !g->display_overlay_state( ACTION_DISPLAY_LIGHTING ) ) {
12099             return;
12100         }
12101         uilist lighting_menu;
12102         std::vector<std::string> lighting_menu_strings{
12103             "Global lighting conditions"
12104         };
12105 
12106         int count = 0;
12107         for( const auto &menu_str : lighting_menu_strings ) {
12108             lighting_menu.addentry( count++, true, MENU_AUTOASSIGN, "%s", menu_str );
12109         }
12110 
12111         lighting_menu.w_y_setup = 0;
12112         lighting_menu.query();
12113         if( ( lighting_menu.ret >= 0 ) &&
12114             ( static_cast<size_t>( lighting_menu.ret ) < lighting_menu_strings.size() ) ) {
12115             g->displaying_lighting_condition = lighting_menu.ret;
12116         }
12117     }
12118 }
12119 
display_radiation()12120 void game::display_radiation()
12121 {
12122     if( use_tiles ) {
12123         display_toggle_overlay( ACTION_DISPLAY_RADIATION );
12124     }
12125 }
12126 
display_transparency()12127 void game::display_transparency()
12128 {
12129     if( use_tiles ) {
12130         display_toggle_overlay( ACTION_DISPLAY_TRANSPARENCY );
12131     }
12132 }
12133 
12134 // Debug menu: asks which reachability cache to display
display_reachability_zones()12135 void game::display_reachability_zones()
12136 {
12137     if( use_tiles ) {
12138         display_toggle_overlay( ACTION_DISPLAY_REACHABILITY_ZONES );
12139         if( display_overlay_state( ACTION_DISPLAY_REACHABILITY_ZONES ) ) {
12140             const auto &menu_popup = [&]( int prev_value,
12141             const std::vector<std::string> &items ) -> cata::optional<int> {
12142                 uilist menu;
12143                 int count = 0;
12144                 for( const auto &menu_str : items )
12145                 {
12146                     menu.addentry( count++, true, MENU_AUTOASSIGN, "%s", menu_str );
12147                 }
12148                 menu.selected = prev_value;
12149                 menu.w_y_setup = 0;
12150                 menu.query();
12151                 if( menu.ret < 0 )
12152                 {
12153                     return cata::nullopt;
12154                 }
12155                 return menu.ret;
12156             };
12157             static_assert(
12158                 static_cast<int>( enum_traits<reachability_cache_quadrant >::last ) == 3,
12159                 "Debug menu expects at least 4 elements in the `quadrant` enum."
12160             );
12161             cata::optional<int> cache =
12162                 menu_popup( debug_rz_display.r_cache_vertical, { "Horizontal", "Vertical (upward)" } );
12163             cata::optional<int> quadrant;
12164             if( cache ) {
12165                 quadrant =
12166                     menu_popup( static_cast<int>( debug_rz_display.quadrant ),
12167                                 /**/{ "NE", "SE", "SW", "NW" } );
12168             }
12169             if( cache && quadrant ) {
12170                 debug_rz_display.r_cache_vertical = *cache;
12171                 debug_rz_display.quadrant = static_cast<reachability_cache_quadrant>( *quadrant );
12172             } else { // user cancelled selection, toggle overlay off
12173                 display_toggle_overlay( ACTION_DISPLAY_REACHABILITY_ZONES );
12174             }
12175         }
12176     }
12177 }
12178 
init_autosave()12179 void game::init_autosave()
12180 {
12181     moves_since_last_save = 0;
12182     last_save_timestamp = time( nullptr );
12183 }
12184 
quicksave()12185 void game::quicksave()
12186 {
12187     //Don't autosave if the player hasn't done anything since the last autosave/quicksave,
12188     if( !moves_since_last_save ) {
12189         return;
12190     }
12191     add_msg( m_info, _( "Saving game, this may take a while" ) );
12192 
12193     static_popup popup;
12194     popup.message( "%s", _( "Saving game, this may take a while" ) );
12195     ui_manager::redraw();
12196     refresh_display();
12197 
12198     time_t now = time( nullptr ); //timestamp for start of saving procedure
12199 
12200     //perform save
12201     save();
12202     //Now reset counters for autosaving, so we don't immediately autosave after a quicksave or autosave.
12203     moves_since_last_save = 0;
12204     last_save_timestamp = now;
12205 }
12206 
quickload()12207 void game::quickload()
12208 {
12209     const WORLDPTR active_world = world_generator->active_world;
12210     if( active_world == nullptr ) {
12211         return;
12212     }
12213 
12214     if( active_world->save_exists( save_t::from_player_name( u.name ) ) ) {
12215         if( moves_since_last_save != 0 ) { // See if we need to reload anything
12216             MAPBUFFER.reset();
12217             overmap_buffer.clear();
12218             try {
12219                 setup();
12220             } catch( const std::exception &err ) {
12221                 debugmsg( "Error: %s", err.what() );
12222             }
12223             load( save_t::from_player_name( u.name ) );
12224         }
12225     } else {
12226         popup_getkey( _( "No saves for %s yet." ), u.name );
12227     }
12228 }
12229 
autosave()12230 void game::autosave()
12231 {
12232     //Don't autosave if the min-autosave interval has not passed since the last autosave/quicksave.
12233     if( time( nullptr ) < last_save_timestamp + 60 * get_option<int>( "AUTOSAVE_MINUTES" ) ) {
12234         return;
12235     }
12236     quicksave();    //Driving checks are handled by quicksave()
12237 }
12238 
start_calendar()12239 void game::start_calendar()
12240 {
12241     int initial_days = get_option<int>( "INITIAL_DAY" );
12242     if( initial_days == -1 ) {
12243         // 0 - 363 for a 91 day season
12244         initial_days = rng( 0, get_option<int>( "SEASON_LENGTH" ) * 4 - 1 );
12245     }
12246     calendar::start_of_cataclysm = calendar::turn_zero + 1_days * initial_days;
12247     // Configured starting date overridden by scenario, calendar::start is left as Spring 1
12248     calendar::start_of_game = calendar::turn_zero
12249                               + 1_hours * scen->initial_hour()
12250                               + 1_days * scen->initial_day()
12251                               + get_option<int>( "SEASON_LENGTH" ) * 1_days * scen->initial_season()
12252                               + 4 * get_option<int>( "SEASON_LENGTH" ) * 1_days * ( scen->initial_year() - 1 );
12253     if( calendar::start_of_game < calendar::start_of_cataclysm ) {
12254         // Hotfix to prevent game start  from occuring before the cataclysm.
12255         // Should be replaced with full refactor of the start date
12256         calendar::start_of_game = calendar::start_of_cataclysm
12257                                   + 1_hours * scen->initial_hour();
12258     }
12259     calendar::start_of_game += 1_days * get_option<int>( "SPAWN_DELAY" );
12260     calendar::turn = calendar::start_of_game;
12261     calendar::initial_season = static_cast<season_type>( ( to_days<int>( calendar::start_of_game -
12262                                calendar::turn_zero ) / get_option<int>( "SEASON_LENGTH" ) ) % 4 );
12263 }
12264 
get_cur_om() const12265 overmap &game::get_cur_om() const
12266 {
12267     // The player is located in the middle submap of the map.
12268     const tripoint sm = m.get_abs_sub() + tripoint( HALF_MAPSIZE, HALF_MAPSIZE, 0 );
12269     const tripoint pos_om = sm_to_om_copy( sm );
12270     // TODO: fix point types
12271     return overmap_buffer.get( point_abs_om( pos_om.xy() ) );
12272 }
12273 
allies()12274 std::vector<npc *> game::allies()
12275 {
12276     return get_npcs_if( [&]( const npc & guy ) {
12277         if( !guy.is_hallucination() ) {
12278             return guy.is_ally( get_player_character() );
12279         } else {
12280             return false;
12281         }
12282     } );
12283 }
12284 
get_creatures_if(const std::function<bool (const Creature &)> & pred)12285 std::vector<Creature *> game::get_creatures_if( const std::function<bool( const Creature & )>
12286         &pred )
12287 {
12288     std::vector<Creature *> result;
12289     for( Creature &critter : all_creatures() ) {
12290         if( pred( critter ) ) {
12291             result.push_back( &critter );
12292         }
12293     }
12294     return result;
12295 }
12296 
get_npcs_if(const std::function<bool (const npc &)> & pred)12297 std::vector<npc *> game::get_npcs_if( const std::function<bool( const npc & )> &pred )
12298 {
12299     std::vector<npc *> result;
12300     for( npc &guy : all_npcs() ) {
12301         if( pred( guy ) ) {
12302             result.push_back( &guy );
12303         }
12304     }
12305     return result;
12306 }
12307 
12308 template<>
valid()12309 bool game::non_dead_range<monster>::iterator::valid()
12310 {
12311     current = iter->lock();
12312     return current && !current->is_dead();
12313 }
12314 
12315 template<>
valid()12316 bool game::non_dead_range<npc>::iterator::valid()
12317 {
12318     current = iter->lock();
12319     return current && !current->is_dead();
12320 }
12321 
12322 template<>
valid()12323 bool game::non_dead_range<Creature>::iterator::valid()
12324 {
12325     current = iter->lock();
12326     // There is no Creature::is_dead function, so we can't write
12327     // return current && !current->is_dead();
12328     if( !current ) {
12329         return false;
12330     }
12331     const Creature *const critter = current.get();
12332     if( critter->is_monster() ) {
12333         return !static_cast<const monster *>( critter )->is_dead();
12334     }
12335     if( critter->is_npc() ) {
12336         return !static_cast<const npc *>( critter )->is_dead();
12337     }
12338     return true; // must be the avatar
12339 }
12340 
monster_range(game & game_ref)12341 game::monster_range::monster_range( game &game_ref )
12342 {
12343     const auto &monsters = game_ref.critter_tracker->get_monsters_list();
12344     items.insert( items.end(), monsters.begin(), monsters.end() );
12345 }
12346 
Creature_range(game & game_ref)12347 game::Creature_range::Creature_range( game &game_ref ) : u( &game_ref.u, []( player * ) { } )
12348 {
12349     const auto &monsters = game_ref.critter_tracker->get_monsters_list();
12350     items.insert( items.end(), monsters.begin(), monsters.end() );
12351     items.insert( items.end(), game_ref.active_npc.begin(), game_ref.active_npc.end() );
12352     items.push_back( u );
12353 }
12354 
npc_range(game & game_ref)12355 game::npc_range::npc_range( game &game_ref )
12356 {
12357     items.insert( items.end(), game_ref.active_npc.begin(), game_ref.active_npc.end() );
12358 }
12359 
all_creatures()12360 game::Creature_range game::all_creatures()
12361 {
12362     return Creature_range( *this );
12363 }
12364 
all_monsters()12365 game::monster_range game::all_monsters()
12366 {
12367     return monster_range( *this );
12368 }
12369 
all_npcs()12370 game::npc_range game::all_npcs()
12371 {
12372     return npc_range( *this );
12373 }
12374 
get_creature_if(const std::function<bool (const Creature &)> & pred)12375 Creature *game::get_creature_if( const std::function<bool( const Creature & )> &pred )
12376 {
12377     for( Creature &critter : all_creatures() ) {
12378         if( pred( critter ) ) {
12379             return &critter;
12380         }
12381     }
12382     return nullptr;
12383 }
12384 
player_base_save_path()12385 std::string PATH_INFO::player_base_save_path()
12386 {
12387     return PATH_INFO::world_base_save_path() + "/" + base64_encode( get_player_character().name );
12388 }
12389 
world_base_save_path()12390 std::string PATH_INFO::world_base_save_path()
12391 {
12392     if( world_generator->active_world == nullptr ) {
12393         return PATH_INFO::savedir();
12394     }
12395     return world_generator->active_world->folder_path();
12396 }
12397 
shift_destination_preview(const point & delta)12398 void game::shift_destination_preview( const point &delta )
12399 {
12400     for( tripoint &p : destination_preview ) {
12401         p += delta;
12402     }
12403 }
12404 
slip_down(bool check_for_traps)12405 bool game::slip_down( bool check_for_traps )
12406 {
12407     ///\EFFECT_DEX decreases chances of slipping while climbing
12408     ///\EFFECT_STR decreases chances of slipping while climbing
12409     int climb = u.dex_cur + u.str_cur;
12410 
12411     if( u.has_trait( trait_PARKOUR ) ) {
12412         climb *= 2;
12413         add_msg( m_info, _( "Your skill in parkour makes it easier to climb." ) );
12414     }
12415     if( u.has_trait( trait_BADKNEES ) ) {
12416         climb /= 2;
12417         add_msg( m_info, _( "Your bad knees make it difficult to climb." ) );
12418     }
12419 
12420     // Climbing is difficult with wet hands and feet.
12421     float wet_penalty = 1.0f;
12422 
12423     if( u.get_part_wetness( bodypart_id( "foot_l" ) ) > 0 ||
12424         u.get_part_wetness( bodypart_id( "foot_r" ) ) > 0 ) {
12425         wet_penalty += .5;
12426         add_msg( m_info, _( "Your wet feet make it harder to climb." ) );
12427     }
12428 
12429     if( u.get_part_wetness( bodypart_id( "hand_l" ) ) > 0 ||
12430         u.get_part_wetness( bodypart_id( "hand_r" ) ) > 0 ) {
12431         wet_penalty += .5;
12432         add_msg( m_info, _( "Your wet hands make it harder to climb." ) );
12433     }
12434 
12435     // Apply wetness penalty
12436     climb /= wet_penalty;
12437 
12438     // Being weighed down makes it easier for you to slip.
12439     const double weight_ratio = u.weight_carried() / u.weight_capacity();
12440     climb -= roll_remainder( 8.0 * weight_ratio );
12441 
12442     if( weight_ratio >= 1 ) {
12443         add_msg( m_info, _( "Your carried weight tries to drag you down." ) );
12444     } else if( weight_ratio > .75 ) {
12445         add_msg( m_info, _( "You strain to climb with the weight of your possessions." ) );
12446     } else if( weight_ratio > .5 ) {
12447         add_msg( m_info, _( "You feel the weight of your luggage makes it more difficult to climb." ) );
12448     } else if( weight_ratio > .25 ) {
12449         add_msg( m_info, _( "Your carried weight makes it a little harder to climb." ) );
12450     }
12451 
12452     if( one_in( climb ) ) {
12453         add_msg( m_bad, _( "You slip while climbing and fall down." ) );
12454         if( climb <= 1 ) {
12455             add_msg( m_bad, _( "Climbing is impossible in your current state." ) );
12456         }
12457         if( check_for_traps ) {
12458             m.creature_on_trap( u );
12459         }
12460         return true;
12461     }
12462     return false;
12463 }
12464 
12465 namespace cata_event_dispatch
12466 {
avatar_moves(const tripoint & old_abs_pos,const avatar & u,const map & m)12467 void avatar_moves( const tripoint &old_abs_pos, const avatar &u, const map &m )
12468 {
12469     const tripoint &new_pos = u.pos();
12470     const tripoint &new_abs_pos = m.getabs( new_pos );
12471     mtype_id mount_type;
12472     if( u.is_mounted() ) {
12473         mount_type = u.mounted_creature->type->id;
12474     }
12475     get_event_bus().send<event_type::avatar_moves>( mount_type, m.ter( new_pos ).id(),
12476             u.current_movement_mode(), u.is_underwater(), new_pos.z );
12477 
12478     // TODO: fix point types
12479     const tripoint_abs_omt old_abs_omt( ms_to_omt_copy( old_abs_pos ) );
12480     const tripoint_abs_omt new_abs_omt( ms_to_omt_copy( new_abs_pos ) );
12481     if( old_abs_omt != new_abs_omt ) {
12482         const oter_id &cur_ter = overmap_buffer.ter( new_abs_omt );
12483         get_event_bus().send<event_type::avatar_enters_omt>( new_abs_omt.raw(), cur_ter );
12484     }
12485 }
12486 } // namespace cata_event_dispatch
12487 
get_achievements()12488 achievements_tracker &get_achievements()
12489 {
12490     return g->achievements();
12491 }
12492 
get_player_character()12493 Character &get_player_character()
12494 {
12495     return g->u;
12496 }
12497 
get_player_location()12498 location &get_player_location()
12499 {
12500     return g->u;
12501 }
12502 
get_player_view()12503 viewer &get_player_view()
12504 {
12505     return g->u;
12506 }
12507 
get_avatar()12508 avatar &get_avatar()
12509 {
12510     return g->u;
12511 }
12512 
get_map()12513 map &get_map()
12514 {
12515     return g->m;
12516 }
12517 
get_event_bus()12518 event_bus &get_event_bus()
12519 {
12520     return g->events();
12521 }
12522 
get_memorial()12523 memorial_logger &get_memorial()
12524 {
12525     return g->memorial();
12526 }
12527 
get_scenario()12528 const scenario *get_scenario()
12529 {
12530     return g->scen;
12531 }
set_scenario(const scenario * new_scenario)12532 void set_scenario( const scenario *new_scenario )
12533 {
12534     g->scen = new_scenario;
12535 }
12536 
get_scent()12537 scent_map &get_scent()
12538 {
12539     return g->scent;
12540 }
12541 
get_stats()12542 stats_tracker &get_stats()
12543 {
12544     return g->stats();
12545 }
12546 
get_timed_events()12547 timed_event_manager &get_timed_events()
12548 {
12549     return g->timed_events;
12550 }
12551 
get_weather()12552 weather_manager &get_weather()
12553 {
12554     return g->weather;
12555 }
12556