1 #include "overmap_ui.h"
2 
3 #include <functional>
4 #include <algorithm>
5 #include <array>
6 #include <chrono>
7 #include <cstddef>
8 #include <functional>
9 #include <iosfwd>
10 #include <list>
11 #include <map>
12 #include <memory>
13 #include <new>
14 #include <ratio>
15 #include <set>
16 #include <string>
17 #include <tuple>
18 #include <unordered_map>
19 #include <utility>
20 #include <vector>
21 
22 #include "activity_actor_definitions.h"
23 #include "avatar.h"
24 #include "basecamp.h"
25 #include "calendar.h"
26 #include "cata_utility.h"
27 #include "catacharset.h"
28 #include "character.h"
29 #include "clzones.h"
30 #include "color.h"
31 #include "coordinates.h"
32 #include "cuboid_rectangle.h"
33 #include "cursesdef.h"
34 #include "enums.h"
35 #include "game.h"
36 #include "game_constants.h"
37 #include "game_ui.h"
38 #include "input.h"
39 #include "line.h"
40 #include "map.h"
41 #include "map_iterator.h"
42 #include "mapbuffer.h"
43 #include "memory_fast.h"
44 #include "mission.h"
45 #include "mongroup.h"
46 #include "npc.h"
47 #include "omdata.h"
48 #include "optional.h"
49 #include "options.h"
50 #include "output.h"
51 #include "overmap.h"
52 #include "overmap_types.h"
53 #include "overmapbuffer.h"
54 #include "player_activity.h"
55 #include "point.h"
56 #include "regional_settings.h"
57 #include "rng.h"
58 #include "sounds.h"
59 #include "string_formatter.h"
60 #include "string_input_popup.h"
61 #include "translations.h"
62 #include "type_id.h"
63 #include "ui.h"
64 #include "ui_manager.h"
65 #include "uistate.h"
66 #include "units.h"
67 #include "vehicle.h"
68 #include "vpart_position.h"
69 #include "weather.h"
70 #include "weather_gen.h"
71 #include "weather_type.h"
72 
73 class character_id;
74 
75 static const activity_id ACT_TRAVELLING( "ACT_TRAVELLING" );
76 
77 static const mongroup_id GROUP_FOREST( "GROUP_FOREST" );
78 
79 static const trait_id trait_DEBUG_NIGHTVISION( "DEBUG_NIGHTVISION" );
80 
81 #if defined(__ANDROID__)
82 #include <SDL_keyboard.h>
83 #endif
84 
85 static constexpr int UILIST_MAP_NOTE_DELETED = -2047;
86 static constexpr int UILIST_MAP_NOTE_EDITED = -2048;
87 
88 static constexpr int max_note_length = 450;
89 static constexpr int max_note_display_length = 45;
90 
91 /** Note preview map width without borders. Odd number. */
92 static const int npm_width = 3;
93 /** Note preview map height without borders. Odd number. */
94 static const int npm_height = 3;
95 
96 namespace overmap_ui
97 {
98 // {note symbol, note color, offset to text}
get_note_display_info(const std::string & note)99 static std::tuple<char, nc_color, size_t> get_note_display_info( const std::string &note )
100 {
101     std::tuple<char, nc_color, size_t> result {'N', c_yellow, 0};
102     bool set_color  = false;
103     bool set_symbol = false;
104 
105     size_t pos = 0;
106     for( int i = 0; i < 2; ++i ) {
107         // find the first non-whitespace non-delimiter
108         pos = note.find_first_not_of( " :;", pos, 3 );
109         if( pos == std::string::npos ) {
110             return result;
111         }
112 
113         // find the first following delimiter
114         const auto end = note.find_first_of( " :;", pos, 3 );
115         if( end == std::string::npos ) {
116             return result;
117         }
118 
119         // set color or symbol
120         if( !set_symbol && note[end] == ':' ) {
121             std::get<0>( result ) = note[end - 1];
122             std::get<2>( result ) = end + 1;
123             set_symbol = true;
124         } else if( !set_color && note[end] == ';' ) {
125             std::get<1>( result ) = get_note_color( note.substr( pos, end - pos ) );
126             std::get<2>( result ) = end + 1;
127             set_color = true;
128         }
129 
130         pos = end + 1;
131     }
132 
133     return result;
134 }
135 
get_overmap_neighbors(const tripoint_abs_omt & current)136 static std::array<std::pair<nc_color, std::string>, npm_width *npm_height> get_overmap_neighbors(
137     const tripoint_abs_omt &current )
138 {
139     const bool has_debug_vision = get_player_character().has_trait( trait_DEBUG_NIGHTVISION );
140 
141     std::array<std::pair<nc_color, std::string>, npm_width *npm_height> map_around;
142     int index = 0;
143     const point shift( npm_width / 2, npm_height / 2 );
144     for( const tripoint_abs_omt &dest :
145          tripoint_range<tripoint_abs_omt>( current - shift, current + shift ) ) {
146         nc_color ter_color = c_black;
147         std::string ter_sym = " ";
148         const bool see = has_debug_vision || overmap_buffer.seen( dest );
149         if( see ) {
150             // Only load terrain if we can actually see it
151             oter_id cur_ter = overmap_buffer.ter( dest );
152             ter_color = cur_ter->get_color();
153             ter_sym = cur_ter->get_symbol();
154         } else {
155             ter_color = c_dark_gray;
156             ter_sym = "#";
157         }
158         map_around[index++] = std::make_pair( ter_color, ter_sym );
159     }
160     return map_around;
161 }
162 
update_note_preview(const std::string & note,const std::array<std::pair<nc_color,std::string>,npm_width * npm_height> & map_around,const std::tuple<catacurses::window *,catacurses::window *,catacurses::window * > & preview_windows)163 static void update_note_preview( const std::string &note,
164                                  const std::array<std::pair<nc_color, std::string>, npm_width *npm_height> &map_around,
165                                  const std::tuple<catacurses::window *, catacurses::window *, catacurses::window *>
166                                  &preview_windows )
167 {
168     auto om_symbol = get_note_display_info( note );
169     const nc_color note_color = std::get<1>( om_symbol );
170     const char symbol = std::get<0>( om_symbol );
171     const std::string note_text = note.substr( std::get<2>( om_symbol ), std::string::npos );
172 
173     catacurses::window *w_preview = std::get<0>( preview_windows );
174     catacurses::window *w_preview_title = std::get<1>( preview_windows );
175     catacurses::window *w_preview_map   = std::get<2>( preview_windows );
176 
177     draw_border( *w_preview );
178     // NOLINTNEXTLINE(cata-use-named-point-constants)
179     mvwprintz( *w_preview, point( 1, 1 ), c_white, _( "Note preview" ) );
180     wnoutrefresh( *w_preview );
181 
182     werase( *w_preview_title );
183     nc_color default_color = c_unset;
184     print_colored_text( *w_preview_title, point_zero, default_color, note_color, note_text,
185                         report_color_error::no );
186     int note_text_width = utf8_width( note_text );
187     mvwputch( *w_preview_title, point( note_text_width, 0 ), c_white, LINE_XOXO );
188     for( int i = 0; i < note_text_width; i++ ) {
189         mvwputch( *w_preview_title, point( i, 1 ), c_white, LINE_OXOX );
190     }
191     mvwputch( *w_preview_title, point( note_text_width, 1 ), c_white, LINE_XOOX );
192     wnoutrefresh( *w_preview_title );
193 
194     const int npm_offset_x = 1;
195     const int npm_offset_y = 1;
196     werase( *w_preview_map );
197     draw_border( *w_preview_map, c_yellow );
198     for( int i = 0; i < npm_height; i++ ) {
199         for( int j = 0; j < npm_width; j++ ) {
200             const auto &ter = map_around[i * npm_width + j];
201             mvwputch( *w_preview_map, point( j + npm_offset_x, i + npm_offset_y ), ter.first, ter.second );
202         }
203     }
204     mvwputch( *w_preview_map, point( npm_width / 2 + npm_offset_x, npm_height / 2 + npm_offset_y ),
205               note_color, symbol );
206     wnoutrefresh( *w_preview_map );
207 }
208 
get_weather_at_point(const tripoint_abs_omt & pos)209 static weather_type_id get_weather_at_point( const tripoint_abs_omt &pos )
210 {
211     // Weather calculation is a bit expensive, so it's cached here.
212     static std::map<tripoint_abs_omt, weather_type_id> weather_cache;
213     static time_point last_weather_display = calendar::before_time_starts;
214     if( last_weather_display != calendar::turn ) {
215         last_weather_display = calendar::turn;
216         weather_cache.clear();
217     }
218     auto iter = weather_cache.find( pos );
219     if( iter == weather_cache.end() ) {
220         // TODO: fix point types
221         const tripoint abs_ms_pos = project_to<coords::ms>( pos ).raw();
222         const auto &wgen = overmap_buffer.get_settings( pos ).weather;
223         const auto weather = wgen.get_weather_conditions( abs_ms_pos, calendar::turn, g->get_seed(),
224                              g->weather.next_instance_allowed );
225         iter = weather_cache.insert( std::make_pair( pos, weather ) ).first;
226     }
227     return iter->second;
228 }
229 
get_scent_glyph(const tripoint_abs_omt & pos,nc_color & ter_color,std::string & ter_sym)230 static bool get_scent_glyph( const tripoint_abs_omt &pos, nc_color &ter_color,
231                              std::string &ter_sym )
232 {
233     scent_trace possible_scent = overmap_buffer.scent_at( pos );
234     if( possible_scent.creation_time != calendar::before_time_starts ) {
235         color_manager &color_list = get_all_colors();
236         int i = 0;
237         time_duration scent_age = calendar::turn - possible_scent.creation_time;
238         while( i < num_colors && scent_age > 0_turns ) {
239             i++;
240             scent_age /= 10;
241         }
242         ter_color = color_list.get( static_cast<color_id>( i ) );
243         int scent_strength = possible_scent.initial_strength;
244         char c = '0';
245         while( c <= '9' && scent_strength > 0 ) {
246             c++;
247             scent_strength /= 10;
248         }
249         ter_sym = std::string( 1, c );
250         return true;
251     }
252     // but it makes no scents!
253     return false;
254 }
255 
draw_city_labels(const catacurses::window & w,const tripoint_abs_omt & center)256 static void draw_city_labels( const catacurses::window &w, const tripoint_abs_omt &center )
257 {
258     const int win_x_max = getmaxx( w );
259     const int win_y_max = getmaxy( w );
260     const int sm_radius = std::max( win_x_max, win_y_max );
261 
262     const point screen_center_pos( win_x_max / 2, win_y_max / 2 );
263 
264     for( const auto &element : overmap_buffer.get_cities_near(
265              project_to<coords::sm>( center ), sm_radius ) ) {
266         const point_abs_omt city_pos =
267             project_to<coords::omt>( element.abs_sm_pos.xy() );
268         const point_rel_omt screen_pos( city_pos - center.xy() + screen_center_pos );
269 
270         const int text_width = utf8_width( element.city->name, true );
271         const int text_x_min = screen_pos.x() - text_width / 2;
272         const int text_x_max = text_x_min + text_width;
273         const int text_y = screen_pos.y();
274 
275         if( text_x_min < 0 ||
276             text_x_max > win_x_max ||
277             text_y < 0 ||
278             text_y > win_y_max ) {
279             continue;   // outside of the window bounds.
280         }
281 
282         if( screen_center_pos.x >= ( text_x_min - 1 ) &&
283             screen_center_pos.x <= ( text_x_max ) &&
284             screen_center_pos.y >= ( text_y - 1 ) &&
285             screen_center_pos.y <= ( text_y + 1 ) ) {
286             continue;   // right under the cursor.
287         }
288 
289         if( !overmap_buffer.seen( tripoint_abs_omt( city_pos, center.z() ) ) ) {
290             continue;   // haven't seen it.
291         }
292 
293         mvwprintz( w, point( text_x_min, text_y ), i_yellow, element.city->name );
294     }
295 }
296 
draw_camp_labels(const catacurses::window & w,const tripoint_abs_omt & center)297 static void draw_camp_labels( const catacurses::window &w, const tripoint_abs_omt &center )
298 {
299     const int win_x_max = getmaxx( w );
300     const int win_y_max = getmaxy( w );
301     const int sm_radius = std::max( win_x_max, win_y_max );
302 
303     const point screen_center_pos( win_x_max / 2, win_y_max / 2 );
304 
305     for( const auto &element : overmap_buffer.get_camps_near(
306              project_to<coords::sm>( center ), sm_radius ) ) {
307         const point_abs_omt camp_pos( element.camp->camp_omt_pos().xy() );
308         const point screen_pos( ( camp_pos - center.xy() ).raw() + screen_center_pos );
309         const int text_width = utf8_width( element.camp->name, true );
310         const int text_x_min = screen_pos.x - text_width / 2;
311         const int text_x_max = text_x_min + text_width;
312         const int text_y = screen_pos.y;
313         const std::string camp_name = element.camp->name;
314         if( text_x_min < 0 ||
315             text_x_max > win_x_max ||
316             text_y < 0 ||
317             text_y > win_y_max ) {
318             continue;   // outside of the window bounds.
319         }
320 
321         if( screen_center_pos.x >= ( text_x_min - 1 ) &&
322             screen_center_pos.x <= ( text_x_max ) &&
323             screen_center_pos.y >= ( text_y - 1 ) &&
324             screen_center_pos.y <= ( text_y + 1 ) ) {
325             continue;   // right under the cursor.
326         }
327 
328         if( !overmap_buffer.seen( tripoint_abs_omt( camp_pos, center.z() ) ) ) {
329             continue;   // haven't seen it.
330         }
331 
332         mvwprintz( w, point( text_x_min, text_y ), i_white, camp_name );
333     }
334 }
335 
336 class map_notes_callback : public uilist_callback
337 {
338     private:
339         overmapbuffer::t_notes_vector _notes;
340         int _z;
341         int _selected = 0;
342 
343         catacurses::window w_preview;
344         catacurses::window w_preview_title;
345         catacurses::window w_preview_map;
346         std::tuple<catacurses::window *, catacurses::window *, catacurses::window *> preview_windows;
347         ui_adaptor ui;
348 
point_selected()349         point_abs_omt point_selected() {
350             return _notes[_selected].first;
351         }
note_location()352         tripoint_abs_omt note_location() {
353             return tripoint_abs_omt( point_selected(), _z );
354         }
355     public:
map_notes_callback(const overmapbuffer::t_notes_vector & notes,int z)356         map_notes_callback( const overmapbuffer::t_notes_vector &notes, int z )
357             : _notes( notes ), _z( z ) {
358             ui.on_screen_resize( [this]( ui_adaptor & ui ) {
359                 w_preview = catacurses::newwin( npm_height + 2, max_note_display_length - npm_width - 1,
360                                                 point( npm_width + 2, 2 ) );
361                 w_preview_title = catacurses::newwin( 2, max_note_display_length + 1, point_zero );
362                 w_preview_map = catacurses::newwin( npm_height + 2, npm_width + 2, point( 0, 2 ) );
363                 preview_windows = std::make_tuple( &w_preview, &w_preview_title, &w_preview_map );
364 
365                 ui.position( point_zero, point( max_note_display_length + 1, npm_height + 4 ) );
366             } );
367             ui.mark_resize();
368 
369             ui.on_redraw( [this]( const ui_adaptor & ) {
370                 if( _selected >= 0 && static_cast<size_t>( _selected ) < _notes.size() ) {
371                     const tripoint_abs_omt note_pos = note_location();
372                     const auto map_around = get_overmap_neighbors( note_pos );
373                     update_note_preview( overmap_buffer.note( note_pos ), map_around, preview_windows );
374                 } else {
375                     update_note_preview( {}, {}, preview_windows );
376                 }
377             } );
378         }
key(const input_context & ctxt,const input_event & event,int,uilist * menu)379         bool key( const input_context &ctxt, const input_event &event, int, uilist *menu ) override {
380             _selected = menu->selected;
381             if( _selected >= 0 && _selected < static_cast<int>( _notes.size() ) ) {
382                 const std::string &action = ctxt.input_to_action( event );
383                 if( action == "DELETE_NOTE" ) {
384                     if( overmap_buffer.has_note( note_location() ) &&
385                         query_yn( _( "Really delete note?" ) ) ) {
386                         overmap_buffer.delete_note( note_location() );
387                     }
388                     menu->ret = UILIST_MAP_NOTE_DELETED;
389                     return true;
390                 }
391                 if( action == "EDIT_NOTE" ) {
392                     create_note( note_location() );
393                     menu->ret = UILIST_MAP_NOTE_EDITED;
394                     return true;
395                 }
396                 if( action == "MARK_DANGER" ) {
397                     // NOLINTNEXTLINE(cata-text-style): No need for two whitespaces
398                     if( query_yn( _( "Mark area as dangerous ( to avoid on automove paths? )" ) ) ) {
399                         const int max_amount = 20;
400                         // NOLINTNEXTLINE(cata-text-style): No need for two whitespaces
401                         const std::string popupmsg = _( "Danger radius in overmap squares? ( 0-20 )" );
402                         int amount = string_input_popup()
403                                      .title( popupmsg )
404                                      .width( 20 )
405                                      .text( "0" )
406                                      .only_digits( true )
407                                      .query_int();
408                         if( amount > -1 && amount <= max_amount ) {
409                             overmap_buffer.mark_note_dangerous( note_location(), amount, true );
410                             menu->ret = UILIST_MAP_NOTE_EDITED;
411                             return true;
412                         }
413                     } else if( overmap_buffer.is_marked_dangerous( note_location() ) &&
414                                query_yn( _( "Remove dangerous mark?" ) ) ) {
415                         overmap_buffer.mark_note_dangerous( note_location(), 0, false );
416                     }
417                 }
418             }
419             return false;
420         }
select(uilist * menu)421         void select( uilist *menu ) override {
422             _selected = menu->selected;
423             ui.invalidate_ui();
424         }
425 };
426 
draw_notes(const tripoint_abs_omt & origin)427 static point_abs_omt draw_notes( const tripoint_abs_omt &origin )
428 {
429     point_abs_omt result( point_min );
430 
431     bool refresh = true;
432     uilist nmenu;
433     while( refresh ) {
434         refresh = false;
435         nmenu.color_error( false );
436         nmenu.init();
437         nmenu.desc_enabled = true;
438         nmenu.input_category = "OVERMAP_NOTES";
439         nmenu.additional_actions.emplace_back( "DELETE_NOTE", translation() );
440         nmenu.additional_actions.emplace_back( "EDIT_NOTE", translation() );
441         nmenu.additional_actions.emplace_back( "MARK_DANGER", translation() );
442         const input_context ctxt( nmenu.input_category, keyboard_mode::keycode );
443         nmenu.text = string_format(
444                          _( "<%s> - center on note, <%s> - edit note, <%s> - mark as dangerous, <%s> - delete note, <%s> - close window" ),
445                          colorize( ctxt.get_desc( "CONFIRM", 1 ), c_yellow ),
446                          colorize( ctxt.get_desc( "EDIT_NOTE", 1 ), c_yellow ),
447                          colorize( ctxt.get_desc( "MARK_DANGER", 1 ), c_red ),
448                          colorize( ctxt.get_desc( "DELETE_NOTE", 1 ), c_yellow ),
449                          colorize( ctxt.get_desc( "QUIT", 1 ), c_yellow )
450                      );
451         int row = 0;
452         overmapbuffer::t_notes_vector notes = overmap_buffer.get_all_notes( origin.z() );
453         nmenu.title = string_format( _( "Map notes (%d)" ), notes.size() );
454         for( const auto &point_with_note : notes ) {
455             const point_abs_omt p = point_with_note.first;
456             if( p == origin.xy() ) {
457                 nmenu.selected = row;
458             }
459             const std::string &note = point_with_note.second;
460             auto om_symbol = get_note_display_info( note );
461             const nc_color note_color = std::get<1>( om_symbol );
462             const std::string note_symbol = std::string( 1, std::get<0>( om_symbol ) );
463             const std::string note_text = note.substr( std::get<2>( om_symbol ), std::string::npos );
464             point_abs_omt p_omt( p );
465             const point_abs_omt p_player = get_player_character().global_omt_location().xy();
466             const int distance_player = rl_dist( p_player, p_omt );
467             const point_abs_sm sm_pos = project_to<coords::sm>( p_omt );
468             const point_abs_om p_om = project_to<coords::om>( p_omt );
469             const std::string location_desc =
470                 overmap_buffer.get_description_at( tripoint_abs_sm( sm_pos, origin.z() ) );
471             const bool is_dangerous =
472                 overmap_buffer.is_marked_dangerous( tripoint_abs_omt( p, origin.z() ) );
473             nmenu.addentry_desc(
474                 string_format( _( "[%s] %s" ), colorize( note_symbol, note_color ), note_text ),
475                 string_format(
476                     _( "<color_red>LEVEL %i, %d'%d, %d'%d</color>: %s "
477                        "(Distance: <color_white>%d</color>) <color_red>%s</color>" ),
478                     origin.z(), p_om.x(), p_omt.x(), p_om.y(), p_omt.y(), location_desc,
479                     distance_player, is_dangerous ? "DANGEROUS AREA!" : "" ) );
480             nmenu.entries[row].ctxt =
481                 string_format( _( "<color_light_gray>Distance: </color><color_white>%d</color>" ),
482                                distance_player );
483             row++;
484         }
485         map_notes_callback cb( notes, origin.z() );
486         nmenu.callback = &cb;
487         nmenu.query();
488         if( nmenu.ret == UILIST_MAP_NOTE_DELETED || nmenu.ret == UILIST_MAP_NOTE_EDITED ) {
489             refresh = true;
490         } else if( nmenu.ret >= 0 && nmenu.ret < static_cast<int>( notes.size() ) ) {
491             result = notes[nmenu.ret].first;
492             refresh = false;
493         }
494     }
495     return result;
496 }
497 
draw(const catacurses::window & w,const catacurses::window & wbar,const tripoint_abs_omt & center,const tripoint_abs_omt & orig,bool blink,bool show_explored,bool fast_scroll,input_context * inp_ctxt,const draw_data_t & data)498 void draw(
499     const catacurses::window &w, const catacurses::window &wbar, const tripoint_abs_omt &center,
500     const tripoint_abs_omt &orig, bool blink, bool show_explored, bool fast_scroll,
501     input_context *inp_ctxt, const draw_data_t &data )
502 {
503     const int om_map_width  = OVERMAP_WINDOW_WIDTH;
504     const int om_map_height = OVERMAP_WINDOW_HEIGHT;
505     const int om_half_width = om_map_width / 2;
506     const int om_half_height = om_map_height / 2;
507     const bool viewing_weather =
508         ( ( data.debug_weather || data.visible_weather ) && center.z() == 10 );
509 
510     avatar &player_character = get_avatar();
511     // Target of current mission
512     const tripoint_abs_omt target = player_character.get_active_mission_target();
513     const bool has_target = target != overmap::invalid_tripoint;
514     // seen status & terrain of center position
515     bool csee = false;
516     oter_id ccur_ter = oter_str_id::NULL_ID();
517     // Debug vision allows seeing everything
518     const bool has_debug_vision = player_character.has_trait( trait_DEBUG_NIGHTVISION );
519     // sight_points is hoisted for speed reasons.
520     const int sight_points = !has_debug_vision ?
521                              player_character.overmap_sight_range( g->light_level( player_character.posz() ) ) :
522                              100;
523     // Whether showing hordes is currently enabled
524     const bool showhordes = uistate.overmap_show_hordes;
525 
526     const oter_id forest = oter_str_id( "forest" ).id();
527 
528     std::string sZoneName;
529     tripoint_abs_omt tripointZone( -1, -1, -1 );
530     const auto &zones = zone_manager::get_manager();
531 
532     if( data.iZoneIndex != -1 ) {
533         const auto &zone = zones.get_zones()[data.iZoneIndex].get();
534         sZoneName = zone.get_name();
535         // TODO: fix point types
536         tripointZone = project_to<coords::omt>(
537                            tripoint_abs_ms( zone.get_center_point() ) );
538     }
539 
540     // If we're debugging monster groups, find the monster group we've selected
541     const mongroup *mgroup = nullptr;
542     std::vector<mongroup *> mgroups;
543     if( data.debug_mongroup ) {
544         mgroups = overmap_buffer.monsters_at( center );
545         for( const auto &mgp : mgroups ) {
546             mgroup = mgp;
547             if( mgp->horde ) {
548                 break;
549             }
550         }
551     }
552 
553     // A small LRU cache: most oter_id's occur in clumps like forests of swamps.
554     // This cache helps avoid much more costly lookups in the full hashmap.
555     constexpr size_t cache_size = 8; // used below to calculate the next index
556     std::array<std::pair<oter_id, oter_t const *>, cache_size> cache {{}};
557     size_t cache_next = 0;
558 
559     const auto set_color_and_symbol = [&]( const oter_id & cur_ter, const tripoint_abs_omt & omp,
560     std::string & ter_sym, nc_color & ter_color ) {
561         // First see if we have the oter_t cached
562         oter_t const *info = nullptr;
563         for( const auto &c : cache ) {
564             if( c.first == cur_ter ) {
565                 info = c.second;
566                 break;
567             }
568         }
569         // Nope, look in the hash map next
570         if( !info ) {
571             info = &cur_ter.obj();
572             cache[cache_next] = std::make_pair( cur_ter, info );
573             cache_next = ( cache_next + 1 ) % cache_size;
574         }
575         // Ok, we found something
576         if( info ) {
577             const bool explored = show_explored && overmap_buffer.is_explored( omp );
578             ter_color = explored ? c_dark_gray : info->get_color( uistate.overmap_show_land_use_codes );
579             ter_sym = info->get_symbol( uistate.overmap_show_land_use_codes );
580         }
581     };
582 
583     const tripoint_abs_omt corner = center - point( om_half_width, om_half_height );
584 
585     // For use with place_special: cache the color and symbol of each submap
586     // and record the bounds to optimize lookups below
587     std::unordered_map<point_rel_omt, std::pair<std::string, nc_color>> special_cache;
588 
589     point_rel_omt s_begin;
590     point_rel_omt s_end;
591     if( blink && uistate.place_special ) {
592         for( const auto &s_ter : uistate.place_special->terrains ) {
593             if( s_ter.p.z == 0 ) {
594                 // TODO: fix point types
595                 const point_rel_omt rp( om_direction::rotate( s_ter.p.xy(), uistate.omedit_rotation ) );
596                 const oter_id oter = s_ter.terrain->get_rotated( uistate.omedit_rotation );
597 
598                 special_cache.insert( std::make_pair(
599                                           rp, std::make_pair( oter->get_symbol(), oter->get_color() ) ) );
600 
601                 s_begin.x() = std::min( s_begin.x(), rp.x() );
602                 s_begin.y() = std::min( s_begin.y(), rp.y() );
603                 s_end.x() = std::max( s_end.x(), rp.x() );
604                 s_end.y() = std::max( s_end.y(), rp.y() );
605             }
606         }
607     }
608 
609     // Cache NPCs since time to draw them is linear (per seen tile) with their count
610     struct npc_coloring {
611         nc_color color;
612         size_t count = 0;
613     };
614     std::vector<tripoint_abs_omt> path_route;
615     std::vector<tripoint_abs_omt> player_path_route;
616     std::unordered_map<tripoint_abs_omt, npc_coloring> npc_color;
617     if( blink ) {
618         // get seen NPCs
619         const auto &npcs = overmap_buffer.get_npcs_near_player( sight_points );
620         for( const auto &np : npcs ) {
621             if( np->posz() != center.z() ) {
622                 continue;
623             }
624 
625             const tripoint_abs_omt pos = np->global_omt_location();
626             if( has_debug_vision || overmap_buffer.seen( pos ) ) {
627                 auto iter = npc_color.find( pos );
628                 nc_color np_color = np->basic_symbol_color();
629                 if( iter == npc_color.end() ) {
630                     npc_color[ pos ] = { np_color, 1 };
631                 } else {
632                     iter->second.count++;
633                     // Randomly change to new NPC's color
634                     if( iter->second.color != np_color && one_in( iter->second.count ) ) {
635                         iter->second.color = np_color;
636                     }
637                 }
638             }
639         }
640         std::vector<npc *> followers;
641         // get friendly followers
642         for( const character_id &elem : g->get_follower_list() ) {
643             shared_ptr_fast<npc> npc_to_get = overmap_buffer.find_npc( elem );
644             if( !npc_to_get ) {
645                 continue;
646             }
647             npc *npc_to_add = npc_to_get.get();
648             followers.push_back( npc_to_add );
649         }
650         // get all traveling NPCs for the debug menu to show pathfinding routes.
651         for( auto &elem : overmap_buffer.get_npcs_near_player( 200 ) ) {
652             if( !elem ) {
653                 continue;
654             }
655             npc *npc_to_add = elem.get();
656             if( npc_to_add->mission == NPC_MISSION_TRAVELLING && !npc_to_add->omt_path.empty() ) {
657                 for( auto &elem : npc_to_add->omt_path ) {
658                     path_route.push_back( tripoint_abs_omt( elem.xy(), npc_to_add->posz() ) );
659                 }
660             }
661         }
662         for( auto &elem : player_character.omt_path ) {
663             tripoint_abs_omt tri_to_add( elem.xy(), player_character.posz() );
664             player_path_route.push_back( tri_to_add );
665         }
666         for( const auto &np : followers ) {
667             if( np->posz() != center.z() ) {
668                 continue;
669             }
670             const tripoint_abs_omt pos = np->global_omt_location();
671             auto iter = npc_color.find( pos );
672             nc_color np_color = np->basic_symbol_color();
673             if( iter == npc_color.end() ) {
674                 npc_color[ pos ] = { np_color, 1 };
675             } else {
676                 iter->second.count++;
677                 // Randomly change to new NPC's color
678                 if( iter->second.color != np_color && one_in( iter->second.count ) ) {
679                     iter->second.color = np_color;
680                 }
681             }
682         }
683     }
684 
685     for( int i = 0; i < om_map_width; ++i ) {
686         for( int j = 0; j < om_map_height; ++j ) {
687             const tripoint_abs_omt omp = corner + point( i, j );
688 
689             oter_id cur_ter = oter_str_id::NULL_ID();
690             nc_color ter_color = c_black;
691             std::string ter_sym = " ";
692 
693             const bool see = has_debug_vision || overmap_buffer.seen( omp );
694             if( see ) {
695                 // Only load terrain if we can actually see it
696                 cur_ter = overmap_buffer.ter( omp );
697             }
698 
699             // Check if location is within player line-of-sight
700             const bool los = see && player_character.overmap_los( omp, sight_points );
701             const bool los_sky = player_character.overmap_los( omp, sight_points * 2 );
702             int mycount = std::count( path_route.begin(), path_route.end(), omp );
703             bool player_path_count = false;
704             std::vector<tripoint_abs_omt>::iterator it =
705                 std::find( player_path_route.begin(), player_path_route.end(), omp );
706             if( it != player_path_route.end() ) {
707                 player_path_count = true;
708             }
709             if( blink && omp == orig ) {
710                 // Display player pos, should always be visible
711                 ter_color = player_character.symbol_color();
712                 ter_sym = "@";
713             } else if( viewing_weather && ( data.debug_weather || los_sky ) ) {
714                 const weather_type_id type = get_weather_at_point( omp );
715                 ter_color = type->map_color;
716                 ter_sym = type->get_symbol();
717             } else if( data.debug_scent && get_scent_glyph( omp, ter_color, ter_sym ) ) {
718                 // get_scent_glyph has changed ter_color and ter_sym if omp has a scent
719             } else if( blink && has_target && omp.xy() == target.xy() ) {
720                 // Mission target, display always, player should know where it is anyway.
721                 ter_color = c_red;
722                 ter_sym = "*";
723                 if( target.z() > center.z() ) {
724                     ter_sym = "^";
725                 } else if( target.z() < center.z() ) {
726                     ter_sym = "v";
727                 }
728             } else if( blink && uistate.overmap_show_map_notes && overmap_buffer.has_note( omp ) ) {
729                 // Display notes in all situations, even when not seen
730                 std::tie( ter_sym, ter_color, std::ignore ) =
731                     get_note_display_info( overmap_buffer.note( omp ) );
732             } else if( !see ) {
733                 // All cases above ignore the seen-status,
734                 ter_color = c_dark_gray;
735                 ter_sym   = "#";
736                 // All cases below assume that see is true.
737             } else if( blink && npc_color.count( omp ) != 0 ) {
738                 // Visible NPCs are cached already
739                 ter_color = npc_color[ omp ].color;
740                 ter_sym   = "@";
741             } else if( blink && mycount != 0 && g->debug_pathfinding ) {
742                 ter_color = c_red;
743                 ter_sym   = "!";
744             } else if( blink && player_path_count ) {
745                 ter_color = c_blue;
746                 ter_sym = "!";
747             } else if( blink && showhordes && los &&
748                        overmap_buffer.get_horde_size( omp ) >= HORDE_VISIBILITY_SIZE ) {
749                 // Display Hordes only when within player line-of-sight
750                 ter_color = c_green;
751                 ter_sym   = overmap_buffer.get_horde_size( omp ) > HORDE_VISIBILITY_SIZE * 2 ? "Z" : "z";
752             } else if( blink && overmap_buffer.has_vehicle( omp ) ) {
753                 // Display Vehicles only when player can see the location
754                 ter_color = c_cyan;
755                 ter_sym   = "c";
756             } else if( !sZoneName.empty() && tripointZone.xy() == omp.xy() ) {
757                 ter_color = c_yellow;
758                 ter_sym   = "Z";
759             } else if( !uistate.overmap_show_forest_trails && cur_ter &&
760                        is_ot_match( "forest_trail", cur_ter, ot_match_type::type ) ) {
761                 // If forest trails shouldn't be displayed, and this is a forest trail, then
762                 // instead render it like a forest.
763                 set_color_and_symbol( forest, omp, ter_sym, ter_color );
764             } else {
765                 // Nothing special, but is visible to the player.
766                 set_color_and_symbol( cur_ter, omp, ter_sym, ter_color );
767             }
768 
769             // Are we debugging monster groups?
770             if( blink && data.debug_mongroup ) {
771                 // Check if this tile is the target of the currently selected group
772 
773                 // Convert to position within overmap
774                 point_abs_om abs_om;
775                 point_om_omt omp_in_om;
776                 std::tie( abs_om, omp_in_om ) = project_remain<coords::om>( omp.xy() );
777                 if( mgroup && project_to<coords::omt>( mgroup->target.xy() ) ==
778                     omp_in_om ) {
779                     ter_color = c_red;
780                     ter_sym = "x";
781                 } else {
782                     const auto &groups = overmap_buffer.monsters_at( omp );
783                     for( const mongroup *mgp : groups ) {
784                         if( mgp->type == GROUP_FOREST ) {
785                             // Don't flood the map with forest creatures.
786                             continue;
787                         }
788                         if( mgp->horde ) {
789                             // Hordes show as +
790                             ter_sym = "+";
791                             break;
792                         } else {
793                             // Regular groups show as -
794                             ter_sym = "-";
795                         }
796                     }
797                     // Set the color only if we encountered an eligible group.
798                     if( ter_sym == "+" || ter_sym == "-" ) {
799                         if( los ) {
800                             ter_color = c_light_blue;
801                         } else {
802                             ter_color = c_blue;
803                         }
804                     }
805                 }
806             }
807 
808             // Preview for place_terrain or place_special
809             if( uistate.place_terrain || uistate.place_special ) {
810                 if( blink && uistate.place_terrain && omp.xy() == center.xy() ) {
811                     ter_color = uistate.place_terrain->get_color();
812                     ter_sym = uistate.place_terrain->get_symbol();
813                 } else if( blink && uistate.place_special ) {
814                     const point_rel_omt from_center = omp.xy() - center.xy();
815                     if( from_center.x() >= s_begin.x() && from_center.x() <= s_end.x() &&
816                         from_center.y() >= s_begin.y() && from_center.y() <= s_end.y() ) {
817                         const auto sm = special_cache.find( from_center );
818 
819                         if( sm != special_cache.end() ) {
820                             ter_color = sm->second.second;
821                             ter_sym = sm->second.first;
822                         }
823                     }
824                 }
825                 // Highlight areas that already have been generated
826                 // TODO: fix point types
827                 if( MAPBUFFER.lookup_submap( project_to<coords::sm>( omp ).raw() ) ) {
828                     ter_color = red_background( ter_color );
829                 }
830             }
831 
832             if( omp.xy() == center.xy() && !uistate.place_special ) {
833                 csee = see;
834                 ccur_ter = cur_ter;
835                 mvwputch_hi( w, point( i, j ), ter_color, ter_sym );
836             } else {
837                 mvwputch( w, point( i, j ), ter_color, ter_sym );
838             }
839         }
840     }
841 
842     if( center.z() == 0 && uistate.overmap_show_city_labels ) {
843         draw_city_labels( w, center );
844         draw_camp_labels( w, center );
845     }
846 
847     half_open_rectangle<point_abs_omt> screen_bounds(
848         corner.xy(), corner.xy() + point( om_map_width, om_map_height ) );
849 
850     if( has_target && blink && !screen_bounds.contains( target.xy() ) ) {
851         point_rel_omt marker = clamp( target.xy(), screen_bounds ) - corner.xy();
852         std::string marker_sym = " ";
853 
854         switch( direction_from( center.xy(), target.xy() ) ) {
855             case direction::NORTH:
856                 marker_sym = "^";
857                 break;
858             case direction::NORTHEAST:
859                 marker_sym = LINE_OOXX_S;
860                 break;
861             case direction::EAST:
862                 marker_sym = ">";
863                 break;
864             case direction::SOUTHEAST:
865                 marker_sym = LINE_XOOX_S;
866                 break;
867             case direction::SOUTH:
868                 marker_sym = "v";
869                 break;
870             case direction::SOUTHWEST:
871                 marker_sym = LINE_XXOO_S;
872                 break;
873             case direction::WEST:
874                 marker_sym = "<";
875                 break;
876             case direction::NORTHWEST:
877                 marker_sym = LINE_OXXO_S;
878                 break;
879             default:
880                 break; //Do nothing
881         }
882         mvwputch( w, marker.raw(), c_red, marker_sym );
883     }
884 
885     std::vector<std::pair<nc_color, std::string>> corner_text;
886 
887     if( uistate.overmap_show_map_notes ) {
888         const std::string &note_text = overmap_buffer.note( center );
889         if( !note_text.empty() ) {
890             const std::tuple<char, nc_color, size_t> note_info = get_note_display_info(
891                         note_text );
892             const size_t pos = std::get<2>( note_info );
893             if( pos != std::string::npos ) {
894                 corner_text.emplace_back( std::get<1>( note_info ), note_text.substr( pos ) );
895             }
896             if( overmap_buffer.is_marked_dangerous( center ) ) {
897                 corner_text.emplace_back( c_red, _( "DANGEROUS AREA!" ) );
898             }
899         }
900     }
901 
902     for( const auto &npc : overmap_buffer.get_npcs_near_omt( center, 0 ) ) {
903         if( !npc->marked_for_death ) {
904             corner_text.emplace_back( npc->basic_symbol_color(), npc->name );
905         }
906     }
907 
908     for( auto &v : overmap_buffer.get_vehicle( center ) ) {
909         corner_text.emplace_back( c_white, v.name );
910     }
911 
912     if( !corner_text.empty() ) {
913         int maxlen = 0;
914         for( const auto &line : corner_text ) {
915             maxlen = std::max( maxlen, utf8_width( line.second, true ) );
916         }
917 
918         mvwputch( w, point_south_east, c_white, LINE_OXXO );
919         for( int i = 0; i <= maxlen; i++ ) {
920             mvwputch( w, point( i + 2, 1 ), c_white, LINE_OXOX );
921         }
922         mvwputch( w, point( 1, corner_text.size() + 2 ), c_white, LINE_XXOO );
923         const std::string spacer( maxlen, ' ' );
924         for( size_t i = 0; i < corner_text.size(); i++ ) {
925             const auto &pr = corner_text[ i ];
926             // clear line, print line, print vertical line on each side.
927             mvwputch( w, point( 1, i + 2 ), c_white, LINE_XOXO );
928             mvwprintz( w, point( 2, i + 2 ), c_yellow, spacer );
929             nc_color default_color = c_unset;
930             print_colored_text( w, point( 2, i + 2 ), default_color, pr.first, pr.second,
931                                 report_color_error::no );
932             mvwputch( w, point( maxlen + 2, i + 2 ), c_white, LINE_XOXO );
933         }
934         mvwputch( w, point( maxlen + 2, 1 ), c_white, LINE_OOXX );
935         for( int i = 0; i <= maxlen; i++ ) {
936             mvwputch( w, point( i + 2, corner_text.size() + 2 ), c_white, LINE_OXOX );
937         }
938         mvwputch( w, point( maxlen + 2, corner_text.size() + 2 ), c_white, LINE_XOOX );
939     }
940 
941     if( !sZoneName.empty() && tripointZone.xy() == center.xy() ) {
942         std::string sTemp = _( "Zone:" );
943         sTemp += " " + sZoneName;
944 
945         const int length = utf8_width( sTemp );
946         for( int i = 0; i <= length; i++ ) {
947             mvwputch( w, point( i, om_map_height - 2 ), c_white, LINE_OXOX );
948         }
949 
950         mvwprintz( w, point( 0, om_map_height - 1 ), c_yellow, sTemp );
951         mvwputch( w, point( length, om_map_height - 2 ), c_white, LINE_OOXX );
952         mvwputch( w, point( length, om_map_height - 1 ), c_white, LINE_XOXO );
953     }
954 
955     // Draw the vertical line
956     for( int j = 0; j < TERMY; j++ ) {
957         mvwputch( wbar, point( 0, j ), c_white, LINE_XOXO );
958     }
959 
960     // Clear the legend
961     for( int i = 1; i < getmaxx( wbar ); i++ ) {
962         for( int j = 0; j < TERMY; j++ ) {
963             mvwputch( wbar, point( i, j ), c_black, ' ' );
964         }
965     }
966 
967     // Draw text describing the overmap tile at the cursor position.
968     int lines = 1;
969     if( csee && !viewing_weather ) {
970         if( !mgroups.empty() ) {
971             int line_number = 6;
972             for( const auto &mgroup : mgroups ) {
973                 mvwprintz( wbar, point( 3, line_number++ ),
974                            c_blue, "  Species: %s", mgroup->type.c_str() );
975                 mvwprintz( wbar, point( 3, line_number++ ),
976                            c_blue, "# monsters: %d", mgroup->population + mgroup->monsters.size() );
977                 if( !mgroup->horde ) {
978                     continue;
979                 }
980                 mvwprintz( wbar, point( 3, line_number++ ),
981                            c_blue, "  Interest: %d", mgroup->interest );
982                 mvwprintz( wbar, point( 3, line_number ),
983                            c_blue, "  Target: %s", mgroup->target.to_string() );
984                 mvwprintz( wbar, point( 3, line_number++ ),
985                            c_red, "x" );
986             }
987         } else {
988             const auto &ter = ccur_ter.obj();
989             const auto sm_pos = project_to<coords::sm>( center );
990 
991             // NOLINTNEXTLINE(cata-use-named-point-constants)
992             mvwputch( wbar, point( 1, 1 ), ter.get_color(), ter.get_symbol() );
993 
994             lines = fold_and_print( wbar, point( 3, 1 ), getmaxx( wbar ) - 3, c_light_gray,
995                                     overmap_buffer.get_description_at( sm_pos ) );
996         }
997     } else if( viewing_weather ) {
998         const bool weather_is_visible = ( data.debug_weather ||
999                                           player_character.overmap_los( center, sight_points * 2 ) );
1000         if( weather_is_visible ) {
1001             // NOLINTNEXTLINE(cata-use-named-point-constants)
1002             mvwprintz( wbar, point( 1, 1 ), get_weather_at_point( center )->color,
1003                        get_weather_at_point( center )->name.translated() );
1004         } else {
1005             // NOLINTNEXTLINE(cata-use-named-point-constants)
1006             mvwprintz( wbar, point( 1, 1 ), c_dark_gray, _( "# Unexplored" ) );
1007         }
1008     } else {
1009         // NOLINTNEXTLINE(cata-use-named-point-constants)
1010         mvwprintz( wbar, point( 1, 1 ), c_dark_gray, _( "# Unexplored" ) );
1011     }
1012 
1013     if( data.debug_editor ) {
1014         mvwprintz( wbar, point( 1, ++lines ), c_white, _( "oter: %s" ), ccur_ter.id().str() );
1015         mvwprintz( wbar, point( 1, ++lines ), c_white,
1016                    _( "oter_type: %s" ), ccur_ter->get_type_id().str() );
1017     }
1018 
1019     if( has_target ) {
1020         const int distance = rl_dist( center, target );
1021         mvwprintz( wbar, point( 1, ++lines ), c_white, _( "Distance to active mission:" ) );
1022         mvwprintz( wbar, point( 1, ++lines ), c_white, _( "%d tiles" ), distance );
1023 
1024         const int above_below = target.z() - orig.z();
1025         std::string msg;
1026         if( above_below > 0 ) {
1027             msg = _( "Above us" );
1028         } else if( above_below < 0 ) {
1029             msg = _( "Below us" );
1030         }
1031         if( above_below != 0 ) {
1032             mvwprintz( wbar, point( 1, ++lines ), c_white, _( "%s" ), msg );
1033         }
1034     }
1035 
1036     //Show mission targets on this location
1037     for( auto &mission : player_character.get_active_missions() ) {
1038         if( mission->get_target() == center ) {
1039             mvwprintz( wbar, point( 1, ++lines ), c_white, mission->name() );
1040         }
1041     }
1042 
1043     mvwprintz( wbar, point( 1, 12 ), c_magenta, _( "Use movement keys to pan." ) );
1044     mvwprintz( wbar, point( 1, 13 ), c_magenta, _( "Press W to preview route." ) );
1045     mvwprintz( wbar, point( 1, 14 ), c_magenta, _( "Press again to confirm." ) );
1046     if( inp_ctxt != nullptr ) {
1047         int y = 16;
1048 
1049         const auto print_hint = [&]( const std::string & action, nc_color color = c_magenta ) {
1050             y += fold_and_print( wbar, point( 1, y ), getmaxx( wbar ) - 1, color, string_format( _( "%s - %s" ),
1051                                  inp_ctxt->get_desc( action ),
1052                                  inp_ctxt->get_action_name( action ) ) );
1053         };
1054 
1055         if( data.debug_editor ) {
1056             print_hint( "PLACE_TERRAIN", c_light_blue );
1057             print_hint( "PLACE_SPECIAL", c_light_blue );
1058             ++y;
1059         }
1060 
1061         const bool show_overlays = uistate.overmap_show_overlays || uistate.overmap_blinking;
1062         const bool is_explored = overmap_buffer.is_explored( center );
1063 
1064         print_hint( "LEVEL_UP" );
1065         print_hint( "LEVEL_DOWN" );
1066         print_hint( "CENTER" );
1067         print_hint( "SEARCH" );
1068         print_hint( "CREATE_NOTE" );
1069         print_hint( "DELETE_NOTE" );
1070         print_hint( "LIST_NOTES" );
1071         print_hint( "MISSIONS" );
1072         print_hint( "TOGGLE_MAP_NOTES", uistate.overmap_show_map_notes ? c_pink : c_magenta );
1073         print_hint( "TOGGLE_BLINKING", uistate.overmap_blinking ? c_pink : c_magenta );
1074         print_hint( "TOGGLE_OVERLAYS", show_overlays ? c_pink : c_magenta );
1075         print_hint( "TOGGLE_LAND_USE_CODES", uistate.overmap_show_land_use_codes ? c_pink : c_magenta );
1076         print_hint( "TOGGLE_CITY_LABELS", uistate.overmap_show_city_labels ? c_pink : c_magenta );
1077         print_hint( "TOGGLE_HORDES", uistate.overmap_show_hordes ? c_pink : c_magenta );
1078         print_hint( "TOGGLE_EXPLORED", is_explored ? c_pink : c_magenta );
1079         print_hint( "TOGGLE_FAST_SCROLL", fast_scroll ? c_pink : c_magenta );
1080         print_hint( "TOGGLE_FOREST_TRAILS", uistate.overmap_show_forest_trails ? c_pink : c_magenta );
1081         print_hint( "HELP_KEYBINDINGS" );
1082         print_hint( "QUIT" );
1083     }
1084 
1085     point_abs_omt abs_omt = center.xy();
1086     point_abs_om om;
1087     point_om_omt omt;
1088     std::tie( om, omt ) = project_remain<coords::om>( abs_omt );
1089     mvwprintz( wbar, point( 1, getmaxy( wbar ) - 1 ), c_red,
1090                _( "LEVEL %i, %d'%d, %d'%d" ), center.z(), om.x(), omt.x(), om.y(), omt.y() );
1091 
1092     // draw nice crosshair around the cursor
1093     if( blink && !uistate.place_terrain && !uistate.place_special ) {
1094         mvwputch( w, point( om_half_width - 1, om_half_height - 1 ), c_light_gray, LINE_OXXO );
1095         mvwputch( w, point( om_half_width + 1, om_half_height - 1 ), c_light_gray, LINE_OOXX );
1096         mvwputch( w, point( om_half_width - 1, om_half_height + 1 ), c_light_gray, LINE_XXOO );
1097         mvwputch( w, point( om_half_width + 1, om_half_height + 1 ), c_light_gray, LINE_XOOX );
1098     }
1099     // Done with all drawing!
1100     wnoutrefresh( wbar );
1101     wmove( w, point( om_half_width, om_half_height ) );
1102     wnoutrefresh( w );
1103 }
1104 
create_note(const tripoint_abs_omt & curs)1105 void create_note( const tripoint_abs_omt &curs )
1106 {
1107     std::string color_notes = string_format( "%s\n\n\n",
1108                               _( "Add a note to the map.  "
1109                                  "For a custom GLYPH or COLOR follow the examples below.  "
1110                                  "Default GLYPH and COLOR looks like this: "
1111                                  "<color_yellow>N</color>" ) );
1112 
1113     color_notes += _( "Color codes: " );
1114     for( const std::pair<const std::string, note_color> &color_pair : get_note_color_names() ) {
1115         // The color index is not translatable, but the name is.
1116         //~ %1$s: note color abbreviation, %2$s: note color name
1117         color_notes += string_format( pgettext( "note color", "%1$s:%2$s, " ), color_pair.first,
1118                                       colorize( color_pair.second.name, color_pair.second.color ) );
1119     }
1120 
1121     std::string helper_text = string_format( ".\n\n%s\n%s\n%s\n\n",
1122                               _( "Type GLYPH<color_yellow>:</color>TEXT to set a custom glyph." ),
1123                               _( "Type COLOR<color_yellow>;</color>TEXT to set a custom color." ),
1124                               // NOLINTNEXTLINE(cata-text-style): literal exclamation mark
1125                               _( "Examples: B:Base | g;Loot | !:R;Minefield" ) );
1126     color_notes = color_notes.replace( color_notes.end() - 2, color_notes.end(),
1127                                        helper_text );
1128     std::string title = _( "Note:" );
1129 
1130     const std::string old_note = overmap_buffer.note( curs );
1131     std::string new_note = old_note;
1132     auto map_around = get_overmap_neighbors( curs );
1133 
1134     catacurses::window w_preview;
1135     catacurses::window w_preview_title;
1136     catacurses::window w_preview_map;
1137     std::tuple<catacurses::window *, catacurses::window *, catacurses::window *> preview_windows;
1138 
1139     ui_adaptor ui;
1140     ui.on_screen_resize( [&]( ui_adaptor & ui ) {
1141         w_preview = catacurses::newwin( npm_height + 2,
1142                                         max_note_display_length - npm_width - 1,
1143                                         point( npm_width + 2, 2 ) );
1144         w_preview_title = catacurses::newwin( 2, max_note_display_length + 1,
1145                                               point_zero );
1146         w_preview_map = catacurses::newwin( npm_height + 2, npm_width + 2,
1147                                             point( 0, 2 ) );
1148         preview_windows = std::make_tuple( &w_preview, &w_preview_title, &w_preview_map );
1149 
1150         ui.position( point_zero, point( max_note_display_length + 1, npm_height + 4 ) );
1151     } );
1152     ui.mark_resize();
1153 
1154     ui.on_redraw( [&]( const ui_adaptor & ) {
1155         update_note_preview( new_note, map_around, preview_windows );
1156     } );
1157 
1158     bool esc_pressed = false;
1159     string_input_popup input_popup;
1160     input_popup
1161     .title( title )
1162     .width( max_note_length )
1163     .text( new_note )
1164     .description( color_notes )
1165     .title_color( c_white )
1166     .desc_color( c_light_gray )
1167     .string_color( c_yellow )
1168     .identifier( "map_note" );
1169 
1170     do {
1171         new_note = input_popup.query_string( false );
1172         if( input_popup.canceled() ) {
1173             new_note = old_note;
1174             esc_pressed = true;
1175             break;
1176         } else if( input_popup.confirmed() ) {
1177             break;
1178         }
1179         ui.invalidate_ui();
1180     } while( true );
1181 
1182     if( !esc_pressed && new_note.empty() && !old_note.empty() ) {
1183         if( query_yn( _( "Really delete note?" ) ) ) {
1184             overmap_buffer.delete_note( curs );
1185         }
1186     } else if( !esc_pressed && old_note != new_note ) {
1187         overmap_buffer.add_note( curs, new_note );
1188     }
1189 }
1190 
1191 // if false, search yielded no results
search(const ui_adaptor & om_ui,tripoint_abs_omt & curs,const tripoint_abs_omt & orig)1192 static bool search( const ui_adaptor &om_ui, tripoint_abs_omt &curs, const tripoint_abs_omt &orig )
1193 {
1194     std::string term = string_input_popup()
1195                        .title( _( "Search term:" ) )
1196                        .description( _( "Multiple entries separated with comma (,). Excludes starting with hyphen (-)." ) )
1197                        .query_string();
1198     if( term.empty() ) {
1199         return false;
1200     }
1201 
1202     std::vector<point_abs_omt> locations;
1203     std::vector<point_abs_om> overmap_checked;
1204 
1205     const int radius = OMAPX; // arbitrary
1206     for( const tripoint_abs_omt &p : points_in_radius( curs, radius ) ) {
1207         overmap_with_local_coords om_loc = overmap_buffer.get_existing_om_global( p );
1208 
1209         if( om_loc ) {
1210             tripoint_om_omt om_relative = om_loc.local;
1211             point_abs_om om_cache = project_to<coords::om>( p.xy() );
1212 
1213             if( std::find( overmap_checked.begin(), overmap_checked.end(),
1214                            om_cache ) == overmap_checked.end() ) {
1215                 overmap_checked.push_back( om_cache );
1216                 std::vector<point_abs_omt> notes = om_loc.om->find_notes( curs.z(), term );
1217                 locations.insert( locations.end(), notes.begin(), notes.end() );
1218             }
1219 
1220             if( om_loc.om->seen( om_relative ) &&
1221                 match_include_exclude( om_loc.om->ter( om_relative )->get_name(), term ) ) {
1222                 locations.push_back( project_combine( om_loc.om->pos(), om_relative.xy() ) );
1223             }
1224         }
1225     }
1226 
1227     if( locations.empty() ) {
1228         sfx::play_variant_sound( "menu_error", "default", 100 );
1229         popup( _( "No results found." ) );
1230         return false;
1231     }
1232 
1233     std::sort( locations.begin(), locations.end(),
1234     [&]( const point_abs_omt & lhs, const point_abs_omt & rhs ) {
1235         return trig_dist( curs, tripoint_abs_omt( lhs, curs.z() ) ) <
1236                trig_dist( curs, tripoint_abs_omt( rhs, curs.z() ) );
1237     } );
1238 
1239     int i = 0;
1240     //Navigate through results
1241     const tripoint_abs_omt prev_curs = curs;
1242 
1243     catacurses::window w_search;
1244 
1245     ui_adaptor ui;
1246     int search_width = OVERMAP_LEGEND_WIDTH - 1;
1247     ui.on_screen_resize( [&]( ui_adaptor & ui ) {
1248         w_search = catacurses::newwin( 13, search_width, point( TERMX - search_width, 3 ) );
1249 
1250         ui.position_from_window( w_search );
1251     } );
1252     ui.mark_resize();
1253 
1254     input_context ctxt( "OVERMAP_SEARCH" );
1255     ctxt.register_action( "NEXT_TAB", to_translation( "Next result" ) );
1256     ctxt.register_action( "PREV_TAB", to_translation( "Previous result" ) );
1257     ctxt.register_action( "CONFIRM" );
1258     ctxt.register_action( "QUIT" );
1259     ctxt.register_action( "HELP_KEYBINDINGS" );
1260     ctxt.register_action( "ANY_INPUT" );
1261 
1262     ui.on_redraw( [&]( const ui_adaptor & ) {
1263         //Draw search box
1264 
1265         int a = utf8_width( _( "Search:" ) );
1266         int b = utf8_width( _( "Result:" ) );
1267         int c = utf8_width( _( "Results:" ) );
1268         int d = utf8_width( _( "Direction:" ) );
1269         int align_width = 0;
1270         int align_w_value[4] = { a, b, c, d};
1271         for( int n : align_w_value ) {
1272             if( n > align_width ) {
1273                 align_width = n + 2;
1274             }
1275         }
1276 
1277         // NOLINTNEXTLINE(cata-use-named-point-constants)
1278         mvwprintz( w_search, point( 1, 1 ), c_light_blue, _( "Search:" ) );
1279         mvwprintz( w_search, point( align_width, 1 ), c_light_red, "%s", term );
1280 
1281         mvwprintz( w_search, point( 1, 2 ), c_light_blue,
1282                    locations.size() == 1 ? _( "Result:" ) : _( "Results:" ) );
1283         mvwprintz( w_search, point( align_width, 2 ), c_light_red, "%d/%d     ", i + 1,
1284                    locations.size() );
1285 
1286         mvwprintz( w_search, point( 1, 3 ), c_light_blue, _( "Direction:" ) );
1287         mvwprintz( w_search, point( align_width, 3 ), c_light_red, "%d %s",
1288                    static_cast<int>( trig_dist( orig, tripoint_abs_omt( locations[i], orig.z() ) ) ),
1289                    direction_name_short( direction_from( orig, tripoint_abs_omt( locations[i], orig.z() ) ) ) );
1290 
1291         if( locations.size() > 1 ) {
1292             fold_and_print( w_search, point( 1, 6 ), search_width, c_white,
1293                             _( "Press [<color_yellow>%s</color>] or [<color_yellow>%s</color>] "
1294                                "to cycle through search results." ),
1295                             ctxt.get_desc( "NEXT_TAB" ), ctxt.get_desc( "PREV_TAB" ) );
1296         }
1297         fold_and_print( w_search, point( 1, 10 ), search_width, c_white,
1298                         _( "Press [<color_yellow>%s</color>] to confirm." ), ctxt.get_desc( "CONFIRM" ) );
1299         fold_and_print( w_search, point( 1, 11 ), search_width, c_white,
1300                         _( "Press [<color_yellow>%s</color>] to quit." ), ctxt.get_desc( "QUIT" ) );
1301         draw_border( w_search );
1302         wnoutrefresh( w_search );
1303     } );
1304 
1305     std::string action;
1306     do {
1307         curs.x() = locations[i].x();
1308         curs.y() = locations[i].y();
1309         om_ui.invalidate_ui();
1310         ui_manager::redraw();
1311         action = ctxt.handle_input( BLINK_SPEED );
1312         if( uistate.overmap_blinking ) {
1313             uistate.overmap_show_overlays = !uistate.overmap_show_overlays;
1314         }
1315         if( action == "NEXT_TAB" ) {
1316             i = ( i + 1 ) % locations.size();
1317         } else if( action == "PREV_TAB" ) {
1318             i = ( i + locations.size() - 1 ) % locations.size();
1319         } else if( action == "QUIT" ) {
1320             curs = prev_curs;
1321             om_ui.invalidate_ui();
1322         }
1323     } while( action != "CONFIRM" && action != "QUIT" );
1324     return true;
1325 }
1326 
place_ter_or_special(const ui_adaptor & om_ui,tripoint_abs_omt & curs,const std::string & om_action)1327 static void place_ter_or_special( const ui_adaptor &om_ui, tripoint_abs_omt &curs,
1328                                   const std::string &om_action )
1329 {
1330     uilist pmenu;
1331     // This simplifies overmap_special selection using uilist
1332     std::vector<const overmap_special *> oslist;
1333     const bool terrain = om_action == "PLACE_TERRAIN";
1334 
1335     if( terrain ) {
1336         pmenu.title = _( "Select terrain to place:" );
1337         for( const oter_t &oter : overmap_terrains::get_all() ) {
1338             const std::string entry_text = string_format(
1339                                                _( "sym: [ %s %s ], color: [ %s %s], name: [ %s ], id: [ %s ]" ),
1340                                                colorize( oter.get_symbol(), oter.get_color() ),
1341                                                colorize( oter.get_symbol( true ), oter.get_color( true ) ),
1342                                                colorize( string_from_color( oter.get_color() ), oter.get_color() ),
1343                                                colorize( string_from_color( oter.get_color( true ) ), oter.get_color( true ) ),
1344                                                colorize( oter.get_name(), oter.get_color() ),
1345                                                colorize( oter.id.str(), c_white ) );
1346             pmenu.addentry( oter.id.id().to_i(), true, 0, entry_text );
1347         }
1348     } else {
1349         pmenu.title = _( "Select special to place:" );
1350         for( const overmap_special &elem : overmap_specials::get_all() ) {
1351             oslist.push_back( &elem );
1352             const std::string entry_text = elem.id.str();
1353             pmenu.addentry( oslist.size() - 1, true, 0, entry_text );
1354         }
1355     }
1356     pmenu.query();
1357 
1358     if( pmenu.ret >= 0 ) {
1359         catacurses::window w_editor;
1360 
1361         ui_adaptor ui;
1362         ui.on_screen_resize( [&]( ui_adaptor & ui ) {
1363             w_editor = catacurses::newwin( 15, 27, point( TERMX - 27, 3 ) );
1364 
1365             ui.position_from_window( w_editor );
1366         } );
1367         ui.mark_resize();
1368 
1369         input_context ctxt( "OVERMAP_EDITOR" );
1370         ctxt.register_directions();
1371         ctxt.register_action( "CONFIRM" );
1372         ctxt.register_action( "ROTATE" );
1373         ctxt.register_action( "QUIT" );
1374         ctxt.register_action( "HELP_KEYBINDINGS" );
1375         ctxt.register_action( "ANY_INPUT" );
1376 
1377         if( terrain ) {
1378             uistate.place_terrain = &oter_id( pmenu.ret ).obj();
1379         } else {
1380             uistate.place_special = oslist[pmenu.ret];
1381         }
1382         // TODO: Unify these things.
1383         const bool can_rotate = terrain ? uistate.place_terrain->is_rotatable() :
1384                                 uistate.place_special->rotatable;
1385 
1386         uistate.omedit_rotation = om_direction::type::none;
1387         // If user chose an already rotated submap, figure out its direction
1388         if( terrain && can_rotate ) {
1389             for( om_direction::type r : om_direction::all ) {
1390                 if( uistate.place_terrain->id.id() == uistate.place_terrain->get_rotated( r ) ) {
1391                     uistate.omedit_rotation = r;
1392                     break;
1393                 }
1394             }
1395         }
1396 
1397         ui.on_redraw( [&]( const ui_adaptor & ) {
1398             draw_border( w_editor );
1399             if( terrain ) {
1400                 // NOLINTNEXTLINE(cata-use-named-point-constants)
1401                 mvwprintz( w_editor, point( 1, 1 ), c_white, _( "Place overmap terrain:" ) );
1402                 mvwprintz( w_editor, point( 1, 2 ), c_light_blue, "                         " );
1403                 mvwprintz( w_editor, point( 1, 2 ), c_light_blue, uistate.place_terrain->id.c_str() );
1404             } else {
1405                 mvwprintz( w_editor, point_south_east, c_white, _( "Place overmap special:" ) );
1406                 mvwprintz( w_editor, point( 1, 2 ), c_light_blue, "                         " );
1407                 mvwprintz( w_editor, point( 1, 2 ), c_light_blue, uistate.place_special->id.c_str() );
1408             }
1409             const std::string &rotation = om_direction::name( uistate.omedit_rotation );
1410 
1411             mvwprintz( w_editor, point( 1, 3 ), c_light_gray, "                         " );
1412             mvwprintz( w_editor, point( 1, 3 ), c_light_gray, _( "Rotation: %s %s" ), rotation,
1413                        can_rotate ? "" : _( "(fixed)" ) );
1414             mvwprintz( w_editor, point( 1, 5 ), c_red, _( "Areas highlighted in red" ) );
1415             mvwprintz( w_editor, point( 1, 6 ), c_red, _( "already have map content" ) );
1416             // NOLINTNEXTLINE(cata-text-style): single space after period for compactness
1417             mvwprintz( w_editor, point( 1, 7 ), c_red, _( "generated. Their overmap" ) );
1418             mvwprintz( w_editor, point( 1, 8 ), c_red, _( "id will change, but not" ) );
1419             mvwprintz( w_editor, point( 1, 9 ), c_red, _( "their contents." ) );
1420             if( ( terrain && uistate.place_terrain->is_rotatable() ) ||
1421                 ( !terrain && uistate.place_special->rotatable ) ) {
1422                 mvwprintz( w_editor, point( 1, 11 ), c_white, _( "[%s] Rotate" ),
1423                            ctxt.get_desc( "ROTATE" ) );
1424             }
1425             mvwprintz( w_editor, point( 1, 12 ), c_white, _( "[%s] Apply" ),
1426                        ctxt.get_desc( "CONFIRM" ) );
1427             mvwprintz( w_editor, point( 1, 13 ), c_white, _( "[ESCAPE/Q] Cancel" ) );
1428             wnoutrefresh( w_editor );
1429         } );
1430 
1431         std::string action;
1432         do {
1433             om_ui.invalidate_ui();
1434             ui_manager::redraw();
1435 
1436             action = ctxt.handle_input( BLINK_SPEED );
1437 
1438             if( const cata::optional<tripoint> vec = ctxt.get_direction( action ) ) {
1439                 curs += vec->xy();
1440             } else if( action == "CONFIRM" ) { // Actually modify the overmap
1441                 if( terrain ) {
1442                     overmap_buffer.ter_set( curs, uistate.place_terrain->id.id() );
1443                     overmap_buffer.set_seen( curs, true );
1444                 } else {
1445                     overmap_buffer.place_special( *uistate.place_special, curs, uistate.omedit_rotation, false, true );
1446                     for( const overmap_special_terrain &s_ter : uistate.place_special->terrains ) {
1447                         const tripoint_abs_omt pos =
1448                             curs + om_direction::rotate( s_ter.p, uistate.omedit_rotation );
1449                         overmap_buffer.set_seen( pos, true );
1450                     }
1451                 }
1452                 break;
1453             } else if( action == "ROTATE" && can_rotate ) {
1454                 uistate.omedit_rotation = om_direction::turn_right( uistate.omedit_rotation );
1455                 if( terrain ) {
1456                     uistate.place_terrain = &uistate.place_terrain->get_rotated( uistate.omedit_rotation ).obj();
1457                 }
1458             }
1459             if( uistate.overmap_blinking ) {
1460                 uistate.overmap_show_overlays = !uistate.overmap_show_overlays;
1461             }
1462         } while( action != "QUIT" );
1463 
1464         uistate.place_terrain = nullptr;
1465         uistate.place_special = nullptr;
1466     }
1467 }
1468 
display(const tripoint_abs_omt & orig,const draw_data_t & data=draw_data_t ())1469 static tripoint_abs_omt display( const tripoint_abs_omt &orig,
1470                                  const draw_data_t &data = draw_data_t() )
1471 {
1472     background_pane bg_pane;
1473 
1474     ui_adaptor ui;
1475     ui.on_screen_resize( []( ui_adaptor & ui ) {
1476         /**
1477          * Handle possibly different overmap font size
1478          */
1479         OVERMAP_LEGEND_WIDTH = clamp( TERMX / 5, 28, 55 );
1480         OVERMAP_WINDOW_HEIGHT = TERMY;
1481         OVERMAP_WINDOW_WIDTH = TERMX - OVERMAP_LEGEND_WIDTH;
1482         to_overmap_font_dimension( OVERMAP_WINDOW_WIDTH, OVERMAP_WINDOW_HEIGHT );
1483 
1484         /* please do not change point( TERMX - OVERMAP_LEGEND_WIDTH, 0 ) to point( OVERMAP_WINDOW_WIDTH, 0 ) */
1485         /* because overmap legend will be absent */
1486         g->w_omlegend = catacurses::newwin( TERMY, OVERMAP_LEGEND_WIDTH,
1487                                             point( TERMX - OVERMAP_LEGEND_WIDTH, 0 ) );
1488         g->w_overmap = catacurses::newwin( OVERMAP_WINDOW_HEIGHT, OVERMAP_WINDOW_WIDTH, point_zero );
1489 
1490         ui.position_from_window( catacurses::stdscr );
1491     } );
1492     ui.mark_resize();
1493 
1494     tripoint_abs_omt ret = overmap::invalid_tripoint;
1495     tripoint_abs_omt curs( orig );
1496 
1497     if( data.select != tripoint_abs_omt( -1, -1, -1 ) ) {
1498         curs = data.select;
1499     }
1500     // Configure input context for navigating the map.
1501     input_context ictxt( "OVERMAP" );
1502     ictxt.register_action( "ANY_INPUT" );
1503     ictxt.register_directions();
1504     ictxt.register_action( "CONFIRM" );
1505     ictxt.register_action( "LEVEL_UP" );
1506     ictxt.register_action( "LEVEL_DOWN" );
1507     ictxt.register_action( "HELP_KEYBINDINGS" );
1508     ictxt.register_action( "MOUSE_MOVE" );
1509     ictxt.register_action( "SELECT" );
1510     ictxt.register_action( "CHOOSE_DESTINATION" );
1511 
1512     // Actions whose keys we want to display.
1513     ictxt.register_action( "CENTER" );
1514     ictxt.register_action( "CREATE_NOTE" );
1515     ictxt.register_action( "DELETE_NOTE" );
1516     ictxt.register_action( "SEARCH" );
1517     ictxt.register_action( "LIST_NOTES" );
1518     ictxt.register_action( "TOGGLE_MAP_NOTES" );
1519     ictxt.register_action( "TOGGLE_BLINKING" );
1520     ictxt.register_action( "TOGGLE_OVERLAYS" );
1521     ictxt.register_action( "TOGGLE_HORDES" );
1522     ictxt.register_action( "TOGGLE_LAND_USE_CODES" );
1523     ictxt.register_action( "TOGGLE_CITY_LABELS" );
1524     ictxt.register_action( "TOGGLE_EXPLORED" );
1525     ictxt.register_action( "TOGGLE_FAST_SCROLL" );
1526     ictxt.register_action( "TOGGLE_FOREST_TRAILS" );
1527     ictxt.register_action( "MISSIONS" );
1528 
1529     if( data.debug_editor ) {
1530         ictxt.register_action( "PLACE_TERRAIN" );
1531         ictxt.register_action( "PLACE_SPECIAL" );
1532     }
1533     ictxt.register_action( "QUIT" );
1534     std::string action;
1535     bool show_explored = true;
1536     bool fast_scroll = false; /* fast scroll state should reset every time overmap UI is opened */
1537     int fast_scroll_offset = get_option<int>( "FAST_SCROLL_OFFSET" );
1538     cata::optional<tripoint> mouse_pos;
1539     std::chrono::time_point<std::chrono::steady_clock> last_blink = std::chrono::steady_clock::now();
1540 
1541     ui.on_redraw( [&]( const ui_adaptor & ) {
1542         draw( g->w_overmap, g->w_omlegend, curs, orig, uistate.overmap_show_overlays,
1543               show_explored, fast_scroll, &ictxt, data );
1544     } );
1545 
1546     do {
1547         ui_manager::redraw();
1548 #if (defined TILES || defined _WIN32 || defined WINDOWS )
1549         int scroll_timeout = get_option<int>( "EDGE_SCROLL" );
1550         // If EDGE_SCROLL is disabled, it will have a value of -1.
1551         // blinking won't work if handle_input() is passed a negative integer.
1552         if( scroll_timeout < 0 ) {
1553             scroll_timeout = BLINK_SPEED;
1554         }
1555         action = ictxt.handle_input( scroll_timeout );
1556 #else
1557         action = ictxt.handle_input( BLINK_SPEED );
1558 #endif
1559         if( const cata::optional<tripoint> vec = ictxt.get_direction( action ) ) {
1560             int scroll_d = fast_scroll ? fast_scroll_offset : 1;
1561             curs += vec->xy() * scroll_d;
1562         } else if( action == "MOUSE_MOVE" || action == "TIMEOUT" ) {
1563             tripoint edge_scroll = g->mouse_edge_scrolling_overmap( ictxt );
1564             if( edge_scroll != tripoint_zero ) {
1565                 if( action == "MOUSE_MOVE" ) {
1566                     edge_scroll *= 2;
1567                 }
1568                 curs += edge_scroll;
1569             }
1570         } else if( action == "SELECT" && ( mouse_pos = ictxt.get_coordinates( g->w_overmap ) ) ) {
1571             curs += mouse_pos->xy();
1572         } else if( action == "CENTER" ) {
1573             curs = orig;
1574         } else if( action == "LEVEL_DOWN" && curs.z() > -OVERMAP_DEPTH ) {
1575             curs.z() -= 1;
1576         } else if( action == "LEVEL_UP" && curs.z() < OVERMAP_HEIGHT ) {
1577             curs.z() += 1;
1578         } else if( action == "CONFIRM" ) {
1579             ret = curs;
1580         } else if( action == "QUIT" ) {
1581             ret = overmap::invalid_tripoint;
1582         } else if( action == "CREATE_NOTE" ) {
1583             create_note( curs );
1584         } else if( action == "DELETE_NOTE" ) {
1585             if( overmap_buffer.has_note( curs ) && query_yn( _( "Really delete note?" ) ) ) {
1586                 overmap_buffer.delete_note( curs );
1587             }
1588         } else if( action == "LIST_NOTES" ) {
1589             const point_abs_omt p = draw_notes( curs );
1590             if( p != point_abs_omt( point_min ) ) {
1591                 curs.x() = p.x();
1592                 curs.y() = p.y();
1593             }
1594         } else if( action == "CHOOSE_DESTINATION" ) {
1595             path_type ptype;
1596             ptype.only_known_by_player = true;
1597             ptype.avoid_danger = true;
1598             avatar &player_character = get_avatar();
1599             bool in_vehicle = player_character.in_vehicle && player_character.controlling_vehicle;
1600             map &here = get_map();
1601             const optional_vpart_position vp = here.veh_at( player_character.pos() );
1602             if( vp && in_vehicle ) {
1603                 vehicle &veh = vp->vehicle();
1604                 if( veh.can_float() && veh.is_watercraft() && veh.is_in_water() ) {
1605                     ptype.only_water = true;
1606                 } else if( veh.is_rotorcraft() && veh.is_flying_in_air() ) {
1607                     ptype.only_air = true;
1608                 } else {
1609                     ptype.only_road = true;
1610                 }
1611             } else {
1612                 const oter_id oter = overmap_buffer.ter( curs );
1613                 // going to or coming from a water tile
1614                 if( is_river_or_lake( oter ) || here.has_flag( "SWIMMABLE", player_character.pos() ) ) {
1615                     ptype.amphibious = true;
1616                 }
1617             }
1618             const tripoint_abs_omt player_omt_pos = player_character.global_omt_location();
1619             if( !player_character.omt_path.empty() && player_character.omt_path.front() == curs ) {
1620                 std::string confirm_msg;
1621                 if( player_character.weight_carried() > player_character.weight_capacity() ) {
1622                     confirm_msg = _( "You are overburdened, are you sure you want to travel (it may be painful)?" );
1623                 } else {
1624                     confirm_msg = _( "Travel to this point?" );
1625                 }
1626                 if( query_yn( confirm_msg ) ) {
1627                     // renew the path incase of a leftover dangling path point
1628                     player_character.omt_path = overmap_buffer.get_npc_path( player_omt_pos, curs, ptype );
1629                     if( player_character.in_vehicle && player_character.controlling_vehicle ) {
1630                         vehicle *player_veh = veh_pointer_or_null( here.veh_at( player_character.pos() ) );
1631                         player_veh->omt_path = player_character.omt_path;
1632                         player_veh->is_autodriving = true;
1633                         player_character.assign_activity( player_activity( autodrive_activity_actor() ) );
1634                     } else {
1635                         player_character.reset_move_mode();
1636                         player_character.assign_activity( ACT_TRAVELLING );
1637                     }
1638                     action = "QUIT";
1639                 }
1640             }
1641             if( curs == player_omt_pos ) {
1642                 player_character.omt_path.clear();
1643             } else {
1644                 player_character.omt_path = overmap_buffer.get_npc_path( player_omt_pos, curs, ptype );
1645             }
1646         } else if( action == "TOGGLE_BLINKING" ) {
1647             uistate.overmap_blinking = !uistate.overmap_blinking;
1648             // if we turn off overmap blinking, show overlays and explored status
1649             if( !uistate.overmap_blinking ) {
1650                 uistate.overmap_show_overlays = true;
1651             } else {
1652                 show_explored = true;
1653             }
1654         } else if( action == "TOGGLE_OVERLAYS" ) {
1655             // if we are currently blinking, turn blinking off.
1656             if( uistate.overmap_blinking ) {
1657                 uistate.overmap_blinking = false;
1658                 uistate.overmap_show_overlays = false;
1659                 show_explored = false;
1660             } else {
1661                 uistate.overmap_show_overlays = !uistate.overmap_show_overlays;
1662                 show_explored = !show_explored;
1663             }
1664         } else if( action == "TOGGLE_LAND_USE_CODES" ) {
1665             uistate.overmap_show_land_use_codes = !uistate.overmap_show_land_use_codes;
1666         } else if( action == "TOGGLE_MAP_NOTES" ) {
1667             uistate.overmap_show_map_notes = !uistate.overmap_show_map_notes;
1668         } else if( action == "TOGGLE_HORDES" ) {
1669             uistate.overmap_show_hordes = !uistate.overmap_show_hordes;
1670         } else if( action == "TOGGLE_CITY_LABELS" ) {
1671             uistate.overmap_show_city_labels = !uistate.overmap_show_city_labels;
1672         } else if( action == "TOGGLE_EXPLORED" ) {
1673             overmap_buffer.toggle_explored( curs );
1674         } else if( action == "TOGGLE_FAST_SCROLL" ) {
1675             fast_scroll = !fast_scroll;
1676         } else if( action == "TOGGLE_FOREST_TRAILS" ) {
1677             uistate.overmap_show_forest_trails = !uistate.overmap_show_forest_trails;
1678         } else if( action == "SEARCH" ) {
1679             if( !search( ui, curs, orig ) ) {
1680                 continue;
1681             }
1682         } else if( action == "PLACE_TERRAIN" || action == "PLACE_SPECIAL" ) {
1683             place_ter_or_special( ui, curs, action );
1684         } else if( action == "MISSIONS" ) {
1685             g->list_missions();
1686         }
1687 
1688         std::chrono::time_point<std::chrono::steady_clock> now = std::chrono::steady_clock::now();
1689         if( now > last_blink + std::chrono::milliseconds( BLINK_SPEED ) ) {
1690             if( uistate.overmap_blinking ) {
1691                 uistate.overmap_show_overlays = !uistate.overmap_show_overlays;
1692             }
1693             last_blink = now;
1694         }
1695     } while( action != "QUIT" && action != "CONFIRM" );
1696     return ret;
1697 }
1698 
1699 } // namespace overmap_ui
1700 
display()1701 void ui::omap::display()
1702 {
1703     overmap_ui::display( get_player_character().global_omt_location(), overmap_ui::draw_data_t() );
1704 }
1705 
display_hordes()1706 void ui::omap::display_hordes()
1707 {
1708     overmap_ui::draw_data_t data;
1709     data.debug_mongroup = true;
1710     overmap_ui::display( get_player_character().global_omt_location(), data );
1711 }
1712 
display_weather()1713 void ui::omap::display_weather()
1714 {
1715     overmap_ui::draw_data_t data;
1716     data.debug_weather = true;
1717     tripoint_abs_omt pos = get_player_character().global_omt_location();
1718     pos.z() = 10;
1719     overmap_ui::display( pos, data );
1720 }
1721 
display_visible_weather()1722 void ui::omap::display_visible_weather()
1723 {
1724     overmap_ui::draw_data_t data;
1725     data.visible_weather = true;
1726     tripoint_abs_omt pos = get_player_character().global_omt_location();
1727     pos.z() = 10;
1728     overmap_ui::display( pos, data );
1729 }
1730 
display_scents()1731 void ui::omap::display_scents()
1732 {
1733     overmap_ui::draw_data_t data;
1734     data.debug_scent = true;
1735     overmap_ui::display( get_player_character().global_omt_location(), data );
1736 }
1737 
display_editor()1738 void ui::omap::display_editor()
1739 {
1740     overmap_ui::draw_data_t data;
1741     data.debug_editor = true;
1742     overmap_ui::display( get_player_character().global_omt_location(), data );
1743 }
1744 
display_zones(const tripoint_abs_omt & center,const tripoint_abs_omt & select,const int iZoneIndex)1745 void ui::omap::display_zones( const tripoint_abs_omt &center, const tripoint_abs_omt &select,
1746                               const int iZoneIndex )
1747 {
1748     overmap_ui::draw_data_t data;
1749     data.select = select;
1750     data.iZoneIndex = iZoneIndex;
1751     overmap_ui::display( center, data );
1752 }
1753 
choose_point()1754 tripoint_abs_omt ui::omap::choose_point()
1755 {
1756     return overmap_ui::display( get_player_character().global_omt_location() );
1757 }
1758 
choose_point(const tripoint_abs_omt & origin)1759 tripoint_abs_omt ui::omap::choose_point( const tripoint_abs_omt &origin )
1760 {
1761     return overmap_ui::display( origin );
1762 }
1763 
choose_point(int z)1764 tripoint_abs_omt ui::omap::choose_point( int z )
1765 {
1766     tripoint_abs_omt loc = get_player_character().global_omt_location();
1767     loc.z() = z;
1768     return overmap_ui::display( loc );
1769 }
1770