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( ¤t_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, ¤t_time );
3290 std::strftime( buffer, suffix_len, "%Y-%m-%d-%H-%M-%S", ¤t_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 ¢er )
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 ¢er, 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 ¬e_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 ¢er, 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 ¢er,
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 ¢er,
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
__anon3bbb6fc54302( player * ) 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