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 ¬e )
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 ¤t )
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 ¬e,
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 ¢er )
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 ¢er )
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 ¬es, 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 ¬e = 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 ¢er,
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 ¬e_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 ¢er, 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