The Battle for Wesnoth  1.19.28+dev
game_display.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2025
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * During a game, show map & info-panels at top+right.
19  */
20 
21 #include "game_display.hpp"
22 
23 #include <cstdint>
24 #include <utility>
25 
26 
27 #include "cursor.hpp"
28 #include "display_chat_manager.hpp"
29 #include "fake_unit_manager.hpp"
30 #include "floating_label.hpp"
31 #include "game_board.hpp"
32 #include "picture.hpp"
34 #include "log.hpp"
35 #include "map/map.hpp"
36 #include "font/standard_colors.hpp"
37 #include "reports.hpp"
38 #include "resources.hpp"
39 #include "tod_manager.hpp"
40 #include "color.hpp"
41 #include "synced_context.hpp"
42 #include "units/unit.hpp"
43 #include "units/drawer.hpp"
44 #include "units/types.hpp"
45 #include "utils/general.hpp"
46 #include "whiteboard/manager.hpp"
47 #include "overlay.hpp"
48 #include "draw.hpp"
49 
50 static lg::log_domain log_display("display");
51 #define ERR_DP LOG_STREAM(err, log_display)
52 #define LOG_DP LOG_STREAM(info, log_display)
53 #define DBG_DP LOG_STREAM(debug, log_display)
54 
55 static lg::log_domain log_engine("engine");
56 #define ERR_NG LOG_STREAM(err, log_engine)
57 
58 
60  std::weak_ptr<wb::manager> wb,
61  reports& reports_object,
62  const std::string& theme_id,
63  const config& level)
64  : display(&board, std::move(wb), reports_object, theme_id, level)
65  , overlay_map_()
66  , attack_indicator_src_()
67  , attack_indicator_dst_()
68  , route_()
69  , displayedUnitHex_()
70  , first_turn_(true)
71  , in_game_(false)
72  , chat_man_(new display_chat_manager(*this))
73  , mode_(RUNNING)
74  , needs_rebuild_(false)
75  , flash_fade_route_()
76  , flash_fade_route_start_time_()
77  , flash_fade_route_active_(false)
78 {
79 }
80 
82 {
84  return;
85  }
86 
87  auto now = std::chrono::steady_clock::now();
88  auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - flash_fade_route_start_time_);
89 
90  // Clean expired footpath
91  if(elapsed >= FLASH_FADE_ROUTE_TOTAL_DURATION) {
92  for(const auto& step : flash_fade_route_.steps) {
93  invalidate(step);
94  }
95 
97  flash_fade_route_.steps.clear();
98  flash_fade_route_.marks.clear();
99  return;
100  }
101 
102  // Trigger redraw of active path
103  for(const auto& step : flash_fade_route_.steps) {
104  invalidate(step);
105  }
106 }
107 
109 {
110  // Clear existing flash fading footprints from the screen
112  for(const auto& step : flash_fade_route_.steps) {
113  invalidate(step);
114  }
115  }
116 
117  // Reset the fading route data
118  flash_fade_route_.steps = remaining_route.steps;
119  flash_fade_route_.marks = remaining_route.marks;
120 
121  // Start the animation cycle
122  if(!flash_fade_route_.steps.empty()) {
124  flash_fade_route_start_time_ = std::chrono::steady_clock::now();
125 
126  // Trigger a redraw for all hexes in the new route
127  for(const auto& step : flash_fade_route_.steps) {
128  invalidate(step);
129  }
130  }
131 }
132 
134 {
135  try {
136  chat_man_->prune_chat_messages(true);
137  } catch(...) {
138  DBG_DP << "Caught exception in game_display destructor: " << utils::get_unknown_exception_type();
139  }
140 }
141 
143 {
144  if(!first_turn_) {
147 
148  if(old_tod.image_mask != tod.image_mask) {
149  fade_tod_mask(old_tod.image_mask, tod.image_mask);
150  }
151  }
152 
153  first_turn_ = false;
154 
155  update_tod();
156 }
157 
159 {
160  if(hex.valid() && fogged(hex)) {
161  return;
162  }
163  display::select_hex(hex);
164 
165  display_unit_hex(hex);
166 }
167 
169 {
170  wb::future_map_if future(!synced_context::is_synced()); /**< Lasts for whole method. */
171 
173  if (u) {
174  displayedUnitHex_ = hex;
175  invalidate_unit();
176  } else {
178  if (u) {
179  // mouse moved from unit hex to non-unit hex
180  if (context().units().count(selectedHex_)) {
182  invalidate_unit();
183  }
184  }
185  }
186 
189 }
190 
191 
193 {
194  if (!hex.valid())
195  return;
196 
197  wb::future_map_if future(!synced_context::is_synced()); /**< Lasts for whole method. */
198 
200  if (u) {
201  displayedUnitHex_ = hex;
202  invalidate_unit();
203  }
204 }
205 
207 {
208  if (src == displayedUnitHex_) {
210  invalidate_unit();
211  }
212 }
213 
214 void game_display::scroll_to_leader(int side, SCROLL_TYPE scroll_type,bool force)
215 {
217 
218  if(leader.valid() && leader->is_visible_to_team(viewing_team(), false)) {
219  scroll_to_tile(leader->get_location(), scroll_type, true, force);
220  }
221 }
222 
224 {
225  display::update();
226 
227  if (std::shared_ptr<wb::manager> w = wb_.lock()) {
228  w->pre_draw();
230  }
232  /**
233  * @todo FIXME: must modify changed, but best to do it at the
234  * floating_label level
235  */
236  chat_man_->prune_chat_messages();
237 }
238 
239 
241 {
242  display::render();
243 
244  if (std::shared_ptr<wb::manager> w = wb_.lock()) {
245  w->post_draw();
246  }
247 }
248 
250 {
252  if (fake_unit_man_->empty()) {
253  return;
254  }
255  unit_drawer drawer = unit_drawer(*this);
256 
257  for(const unit* temp_unit : *fake_unit_man_) {
258  const map_location& loc = temp_unit->get_location();
259  if(utils::contains(invalidated_, loc) && unit_can_draw_here(loc, *temp_unit)) {
260  drawer.redraw_unit(*temp_unit);
261  }
262  }
263 }
264 
265 namespace
266 {
267 const image::locator mouseover_normal_top
268  {"misc/hover-hex-top.png", "~RC(magenta>gold)"};
269 const image::locator mouseover_normal_bot
270  {"misc/hover-hex-bottom.png", "~RC(magenta>gold)"};
271 
272 const image::locator mouseover_enemy_top
273  {"misc/hover-hex-enemy-top.png", "~RC(magenta>red)"};
274 const image::locator mouseover_enemy_bot
275  {"misc/hover-hex-enemy-bottom.png", "~RC(magenta>red)"};
276 
277 const image::locator mouseover_self_top
278  {"misc/hover-hex-top.png", "~RC(magenta>green)"};
279 const image::locator mouseover_self_bot
280  {"misc/hover-hex-bottom.png", "~RC(magenta>green)"};
281 
282 const image::locator mouseover_ally_top
283  {"misc/hover-hex-top.png", "~RC(magenta>lightblue)"};
284 const image::locator mouseover_ally_bot
285  {"misc/hover-hex-bottom.png", "~RC(magenta>lightblue)"};
286 
287 /**
288  * Function to return 2 half-hex footsteps images for the given location.
289  * Only loc is on the current route set by set_route.
290  */
291 std::vector<texture> footsteps_images(const map_location& loc, const pathfind::marked_route& route, const display_context* dc)
292 {
293  std::vector<texture> res;
294 
295  if (route.steps.size() < 2) {
296  return res; // no real "route"
297  }
298 
299  std::vector<map_location>::const_iterator i =
300  std::find(route.steps.begin(),route.steps.end(),loc);
301 
302  if( i == route.steps.end()) {
303  return res; // not on the route
304  }
305 
306  // Determine which set of footprint images the unit uses
307  std::string footprints;
308  int move_cost = 1;
309  const unit_map::const_iterator u = dc->units().find(route.steps.front());
310  if(u != dc->units().end()) {
311  move_cost = u->movement_cost(dc->map().get_terrain(loc));
312  footprints = u->type().footprints();
313 
314  // Fall back to the unit's race, which a [unit] may have overridden
315  if(footprints.empty()) {
316  footprints = u->race()->footprints();
317  }
318  }
319 
320  if(footprints.empty()) {
321  footprints = "default";
322  }
323  const std::string foot_speed_prefix = "footsteps/" + footprints + "/";
324 
325  // Custom colour per movement-cost step: no tint, then brown-orange, then strong red at 3+.
326  std::string color_mod;
327  if(move_cost == 2) {
328  color_mod = "~CS(-10,-70,-200)";
329  } else if(move_cost >= 3) {
330  color_mod = "~CS(0,-255,-255)";
331  }
332 
333  texture teleport;
334 
335  // We draw 2 half-hex (with possibly different directions),
336  // but skip the first for the first step.
337  const int first_half = (i == route.steps.begin()) ? 1 : 0;
338  // and the second for the last step
339  const int second_half = (i+1 == route.steps.end()) ? 0 : 1;
340 
341  for (int h = first_half; h <= second_half; ++h) {
342  const std::string sense( h==0 ? "-in" : "-out" );
343 
344  if (!tiles_adjacent(*(i+(h-1)), *(i+h))) {
345  std::string teleport_image =
347  teleport = image::get_texture(teleport_image, image::HEXED);
348  continue;
349  }
350 
351  // In function of the half, use the incoming or outgoing direction
352  map_location::direction dir = (i+(h-1))->get_relative_dir(*(i+h));
353 
354  std::string rotate;
356  // No image, take the opposite direction and do a 180 rotation
357  dir = i->get_opposite_direction(dir);
358  rotate = "~FL(horiz)~FL(vert)";
359  }
360 
361  const std::string image = foot_speed_prefix + "footprint"
362  + sense + "-" + i->write_direction(dir)
363  + ".png" + rotate + color_mod;
364 
365  res.push_back(image::get_texture(image, image::HEXED));
366  }
367 
368  // we draw teleport image (if any) in last
369  if (teleport != nullptr) res.push_back(teleport);
370 
371  return res;
372 }
373 
374 struct flash_fade_step_renderer
375 {
376  std::vector<texture> images;
377  std::chrono::steady_clock::time_point start_time;
378 
379  void operator()(const rect& dest) const {
380  auto now = std::chrono::steady_clock::now();
381  auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start_time);
382 
384  // PHASE 1: White Flash (Additive)
385  float intensity = 1.0f - (static_cast<float>(elapsed.count()) /
387  uint8_t flash_val = static_cast<uint8_t>(intensity * 255);
388 
389  for(texture t : images) {
390  draw::blit(t, dest); // Base
391 
392  t.set_blend_mode(SDL_BLENDMODE_ADD);
393  t.set_alpha_mod(flash_val);
394  draw::blit(t, dest); // Additive pass
395  draw::blit(t, dest); // Additive pass
396 
397  t.set_blend_mode(SDL_BLENDMODE_BLEND);
398  t.set_alpha_mod(255);
399  }
400  }
402  // PHASE 2: Alpha Fade
403  auto fade_elapsed = elapsed - game_display::FLASH_FADE_ROUTE_FLASH_DURATION;
404  float progress = static_cast<float>(fade_elapsed.count()) /
406 
407  uint8_t alpha = static_cast<uint8_t>((1.0f - progress) * 255);
408 
409  for(texture t : images) {
410  t.set_alpha_mod(alpha);
411  draw::blit(t, dest);
412  t.set_alpha_mod(255);
413  }
414  }
415  }
416 };
417 
418 } //anonymous namespace
419 
421 {
422  const bool on_map = context().map().on_board(loc);
423  const bool is_shrouded = shrouded(loc);
424 
426 
427  if(cursor::get() == cursor::WAIT) {
428  // Interaction is disabled, so we don't need anything else
429  return;
430  }
431 
432  if(on_map && loc == mouseoverHex_ && !map_screenshot_) {
434  const unit* u = context().get_visible_unit(loc, viewing_team());
435  if(u != nullptr) {
436  hex_top_layer = drawing_layer::mouseover_top;
437  }
438 
439  const image::locator* mo_top_path;
440  const image::locator* mo_bot_path;
441 
442  if(u == nullptr) {
443  mo_top_path = &mouseover_normal_top;
444  mo_bot_path = &mouseover_normal_bot;
445  } else if(viewing_team().is_enemy(u->side())) {
446  mo_top_path = &mouseover_enemy_top;
447  mo_bot_path = &mouseover_enemy_bot;
448  } else if(viewing_team().side() == u->side()) {
449  mo_top_path = &mouseover_self_top;
450  mo_bot_path = &mouseover_self_bot;
451  } else {
452  mo_top_path = &mouseover_ally_top;
453  mo_bot_path = &mouseover_ally_bot;
454  }
455 
456  drawing_buffer_add(hex_top_layer, loc,
457  [tex = image::get_texture(*mo_top_path, image::HEXED)](const rect& dest) { draw::blit(tex, dest); });
458 
460  [tex = image::get_texture(*mo_bot_path, image::HEXED)](const rect& dest) { draw::blit(tex, dest); });
461  }
462 
463  // Draw reach_map information.
464  bool reachable = reach_map_.find(loc) != reach_map_.end();
465  // Darken unreachable
466  // We remove the reachability mask of the unit that we want to attack.
467  if(!is_shrouded && !reach_map_.empty() && !reachable && loc != attack_indicator_dst_) {
468  std::string darken_suffix = prefs::get().reach_map_darken_tex() ? "" : "~WIPE_ALPHA()";
469 
471  uint8_t darken_opacity = prefs::get().reach_map_darken_opacity() * (255.0/100.0);
472  darken_tex.set_alpha_mod(darken_opacity);
473 
475  [darken_tex](const rect& dest) {
476  draw::blit(darken_tex, dest);
477  });
478  }
479  // Highlight reachable
480  if(!is_shrouded && !reach_map_.empty() && reachable) {
481  // draw the reachmap tint below units and high terrain graphics
482  std::string color = prefs::get().reach_map_color();
483  texture tint_tex = image::get_texture(game_config::reach_map_prefix + ".png~RC(magenta>"+color+")", image::HEXED);
484  uint8_t tint_opacity = prefs::get().reach_map_tint_opacity() * (255.0/100.0);
485  tint_tex.set_alpha_mod(tint_opacity);
486 
488  draw::blit(tint_tex, dest);
489  });
490 
491  // We remove the reachmap border mask of the hovered hex to avoid weird interactions with other visual objects.
492  if(loc != mouseoverHex_) {
493  // draw the highlight borders on top of units and terrain
495  for(const texture& t : images) {
496  draw::blit(t, dest);
497  }
498  });
499  }
500  }
501 
502  // Check if Whiteboard is active to avoid overlapping path visualizations.
503  if(std::shared_ptr<wb::manager> w = wb_.lock()) {
504  w->draw_hex(loc);
505 
506  if(!(w->is_active() && w->has_temp_move())) {
507 
508  // Draw standard footsteps for the current turn's route.
509  std::vector<texture> footstepImages = footsteps_images(loc, route_, dc_);
510  if(!footstepImages.empty()) {
511  drawing_buffer_add(drawing_layer::footsteps, loc, [images = std::move(footstepImages)](const rect& dest) {
512  for(const texture& t : images) {
513  draw::blit(t, dest);
514  }
515  });
516  }
517 
518  // Draw the flash fade effect for queued (future turn) moves.
520  std::vector<texture> images = footsteps_images(loc, flash_fade_route_, dc_);
521  if(!images.empty()) {
523  flash_fade_step_renderer{ std::move(images), flash_fade_route_start_time_ });
524  }
525  }
526  }
527  }
528 
529  // Draw the attack direction indicator
530  if(on_map && loc == attack_indicator_src_) {
532  [tex = image::get_texture("misc/attack-indicator-src-" + attack_indicator_direction() + ".png", image::HEXED)](const rect& dest)
533  { draw::blit(tex, dest); }
534  );
535  } else if(on_map && loc == attack_indicator_dst_) {
537  [tex = image::get_texture("misc/attack-indicator-dst-" + attack_indicator_direction() + ".png", image::HEXED)](const rect& dest)
538  { draw::blit(tex, dest); }
539  );
540  }
541 
542  // Linger overlay unconditionally otherwise it might give glitches
543  // so it's drawn over the shroud and fog.
544  if(mode_ != RUNNING) {
547  [tex = image::get_texture(linger, image::TOD_COLORED)](const rect& dest) { draw::blit(tex, dest); });
548  }
549 
550  if(on_map && loc == selectedHex_ && !game_config::images::selected.empty()) {
553  [tex = image::get_texture(selected, image::HEXED)](const rect& dest) { draw::blit(tex, dest); });
554  }
555 
556  // Show def% and turn to reach info
557  if(!is_shrouded && on_map) {
559  }
560 }
561 
563 {
565 }
566 
568 {
570 }
571 
573 {
574  display::layout();
575 
576  // We need teams for the reports below
577  if(context().teams().empty()) {
578  return;
579  }
580 
581  refresh_report("report_clock");
582  refresh_report("report_battery");
583  refresh_report("report_countdown");
584 
586  {
587  wb::future_map future; // start planned unit map scope
588 
589  // We display the unit the mouse is over if it is over a unit,
590  // otherwise we display the unit that is selected.
591  for (const std::string &name : reports_object_->report_list()) {
592  refresh_report(name);
593  }
594  invalidateGameStatus_ = false;
595  }
596 }
597 
598 
600 {
601  if(mode != mode_) {
602  mode_ = mode;
603  invalidate_all();
604  }
605 }
606 
608 {
609  // Search if there is a mark here
611 
612  std::shared_ptr<wb::manager> wb = wb_.lock();
613 
614  // Don't use empty route or the first step (the unit will be there)
615  if(w != route_.marks.end()
616  && !route_.steps.empty() && route_.steps.front() != loc) {
617  const unit_map::const_iterator un =
618  (wb && wb->get_temp_move_unit().valid()) ?
619  wb->get_temp_move_unit() : context().units().find(route_.steps.front());
620  if(un != context().units().end()) {
621  // Display the def% of this terrain
622  int def = 100 - un->defense_modifier(context().map().get_terrain(loc), loc);
623  std::stringstream def_text;
624  def_text << def << "%";
625 
626  color_t color = game_config::red_to_green(def, false);
627 
628  // simple mark (no turn point) use smaller font
629  int def_font = w->second.turns > 0 ? 18 : 16;
630  draw_text_in_hex(loc, drawing_layer::move_info, def_text.str(), def_font, color);
631 
633  [inv = w->second.invisible, zoc = w->second.zoc, cap = w->second.capture](const rect& dest) {
634  if(inv) {
635  draw::blit(image::get_texture(image::locator{"misc/hidden.png"}, image::HEXED), dest);
636  }
637 
638  if(zoc) {
639  draw::blit(image::get_texture(image::locator{"misc/zoc.png"}, image::HEXED), dest);
640  }
641 
642  if(cap) {
643  draw::blit(image::get_texture(image::locator{"misc/capture.png"}, image::HEXED), dest);
644  }
645  });
646 
647  //we display turn info only if different from a simple last "1"
648  if (w->second.turns > 1 || (w->second.turns == 1 && loc != route_.steps.back())) {
649  std::stringstream turns_text;
650  turns_text << w->second.turns;
651  draw_text_in_hex(loc, drawing_layer::move_info, turns_text.str(), 17, font::NORMAL_COLOR, 0.5,0.8);
652  }
653 
654  // The hex is full now, so skip the "show enemy moves"
655  return;
656  }
657  }
658  // When out-of-turn, it's still interesting to check out the terrain defs of the selected unit
659  else if (selectedHex_.valid() && loc == mouseoverHex_)
660  {
661  const unit_map::const_iterator selectedUnit = resources::gameboard->find_visible_unit(selectedHex_,viewing_team());
662  const unit_map::const_iterator mouseoveredUnit = resources::gameboard->find_visible_unit(mouseoverHex_,viewing_team());
663  if(selectedUnit != context().units().end() && mouseoveredUnit == context().units().end()) {
664  // Display the def% of this terrain
665  int def = 100 - selectedUnit->defense_modifier(context().map().get_terrain(loc), loc);
666  std::stringstream def_text;
667  def_text << def << "%";
668 
669  color_t color = game_config::red_to_green(def, false);
670 
671  // use small font
672  int def_font = 16;
673  draw_text_in_hex(loc, drawing_layer::move_info, def_text.str(), def_font, color);
674  }
675  }
676 
677  if (!reach_map_.empty()) {
678  reach_map::iterator reach = reach_map_.find(loc);
679  if (reach != reach_map_.end() && reach->second > 1) {
680  const std::string num = std::to_string(reach->second);
681  draw_text_in_hex(loc, drawing_layer::move_info, num, 16, font::YELLOW_COLOR);
682  }
683  }
684 }
685 
687 {
689  highlight_another_reach(paths_list);
690 }
691 
693  const map_location& goal)
694 {
695  // Fold endpoints of routes into reachability map.
696  for (const pathfind::paths::step &dest : paths_list.destinations) {
697  reach_map_[dest.curr]++;
698  }
699  reach_map_changed_ = true;
700 
701  if(goal != map_location::null_location() && paths_list.destinations.contains(goal)) {
702  const auto& path_to_goal = paths_list.destinations.get_path(paths_list.destinations.find(goal));
703  const map_location enemy_unit_location = path_to_goal[0];
704  units_that_can_reach_goal_.insert(enemy_unit_location);
705  }
706 }
707 
709 {
711  if(!reach_map_.empty()) {
712  reach_map_.clear();
713  reach_map_changed_ = true;
714  return true;
715  } else {
716  return false;
717  }
718 }
719 
721 {
722  for(const map_location& step : route_.steps) {
723  invalidate(step);
724  }
725 }
726 
728 {
730 
731  if(route != nullptr) {
732  route_ = *route;
733  } else {
734  route_.steps.clear();
735  route_.marks.clear();
736  }
737 
739 }
740 
741 void game_display::float_label(const map_location& loc, const std::string& text, const color_t& color)
742 {
743  if(prefs::get().floating_labels() == false || fogged(loc)) {
744  return;
745  }
746 
747  rect loc_rect = get_location_rect(loc);
748 
749  using namespace std::chrono_literals;
750  const auto lifetime = 1s / turbo_speed();
751 
752  // Base speed is 100 pixels per second, taken in milliseconds
753  const double pixels_per_millisecond = 0.1 * turbo_speed() * get_zoom_factor();
754 
755  font::floating_label flabel(text);
757  flabel.set_color(color);
758  flabel.set_position(loc_rect.center().x, loc_rect.y); // middle of top edge
759  flabel.set_move(0, -pixels_per_millisecond); // moving up
760  flabel.set_lifetime(0ms, std::chrono::round<std::chrono::milliseconds>(lifetime));
762 
763  font::add_floating_label(flabel);
764 }
765 
767 {
771 
774 
777  }
778 }
779 
781 {
783 }
784 
786 {
787  in_game_ = true;
788  create_buttons();
789  invalidate_all();
790 }
791 
793  if (b) {
794  needs_rebuild_ = true;
795  }
796 }
797 
799  if (needs_rebuild_) {
800  needs_rebuild_ = false;
802  invalidate_all();
803  rebuild_all();
804  return true;
805  }
806  return false;
807 }
808 
810 {
811  return overlay_map_;
812 }
813 
814 std::vector<texture> game_display::get_reachmap_images(const map_location& loc) const
815 {
816  std::vector<std::string> names;
817  const auto adjacent = get_adjacent_tiles(loc);
818 
819  enum visibility { REACH = 0, ENEMY = 1, CLEAR = 2 };
820  std::array<visibility, 6> tiles;
821 
822  for(int i = 0; i < 6; ++i) {
823  // look for units adjacent to loc
824  std::string test_location = std::to_string(adjacent[i].x) + "," + std::to_string(adjacent[i].y);
825  const unit *u = context().get_visible_unit(adjacent[i], viewing_team());
826  if(reach_map_.find(adjacent[i]) != reach_map_.end()) {
827  DBG_DP << test_location << " is REACHABLE";
828  tiles[i] = REACH;
829  }
830  /**
831  * Make sure there is a unit selected or displayed when drawing the reachmap with enemy detection.
832  * Enemy detection needs to be "disabled" when the reach_map_team_index_ failsafes to viewing_team_index.
833  */
835  DBG_DP << test_location << " is NOT REACHABLE";
836  tiles[i] = CLEAR;
837  }
838  // Grab the reachmap-context team index updated in "display::process_reachmap_changes()" and test for adjacent enemy units
839  else if(u != nullptr && !u->incapacitated() && resources::gameboard->get_team(display::reach_map_team_index_).is_enemy(u->side())) {
840  DBG_DP << test_location << " has an attackable ENEMY";
841  tiles[i] = ENEMY;
842  } else {
843  DBG_DP << test_location << " is NOT REACHABLE";
844  tiles[i] = CLEAR;
845  }
846  }
847 
848  // Find out if location is in the inner part of reachmap (surrounded by reach)
849  int s;
850  for(s = 0; s != 6; ++s) {
851  if(tiles[s] != REACH) {
852  break;
853  }
854  }
855 
856  if(s == 6) {
857  // Completely surrounded by reach. This may have a special graphic.
858  DBG_DP << "Tried completely surrounding";
859  std::string name = game_config::reach_map_prefix + "-all.png";
860  if(image::exists(name)) {
861  names.push_back(std::move(name));
862  }
863  }
864 
865  // Find all the directions overlap occurs from
866  for(int i = 0, cap1 = 0; cap1 != 6; ++cap1) {
867  if(tiles[i] != REACH) {
868  std::ostringstream stream;
869  std::string suffix;
870  std::string name;
872 
873  std::string color = prefs::get().reach_map_color();
874  std::string enemy_color = prefs::get().reach_map_enemy_color();
875 
876  if(tiles[i] == ENEMY) {
877  suffix = ".png~RC(magenta>"+enemy_color+")";
878  } else {
879  suffix = ".png~RC(magenta>"+color+")";
880  }
881 
882  for(int cap2 = 0; tiles[i] != REACH && cap2 != 6; i = (i + 1) % 6, ++cap2) {
884  if(!image::exists(stream.str() + ".png")) {
885  DBG_DP << "Image does not exist: " << stream.str() + ".png on " << loc;
886  // If we don't have any surface at all,
887  // then move onto the next overlapped area
888  if(name.empty()) {
889  i = (i + 1) % 6;
890  }
891  break;
892  } else {
893  name = stream.str();
894  }
895  }
896 
897  if(!name.empty()) {
898  names.push_back(name + suffix);
899  }
900  } else {
901  i = (i + 1) % 6;
902  }
903  }
904 
905  // now get the textures
906  std::vector<texture> res;
907  uint8_t border_opacity = prefs::get().reach_map_border_opacity() * (255.0/100.0);
908 
909  for(const std::string& name : names) {
910  DBG_DP << "Pushing: " << name;
911  if(texture tex = image::get_texture(name, image::HEXED)) {
912  tex.set_alpha_mod(border_opacity);
913  res.push_back(std::move(tex));
914  }
915  }
916 
917  return res;
918 }
static bool is_enemy(std::size_t side, std::size_t other_side)
Definition: abilities.cpp:1124
map_location loc
Definition: move.cpp:172
const std::vector< map_location > & route_
Definition: move.cpp:386
double t
Definition: astarsearch.cpp:63
std::vector< std::string > names
Definition: build_info.cpp:74
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:157
Abstract class for exposing game data that doesn't depend on the GUI, however which for historical re...
const unit * get_visible_unit(const map_location &loc, const team &current_team, bool see_all=false) const
virtual const gamemap & map() const =0
virtual const unit_map & units() const =0
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Definition: display.hpp:88
const team & viewing_team() const
Definition: display.cpp:337
bool map_screenshot_
Used to indicate to drawing functions that we are doing a map screenshot.
Definition: display.hpp:862
void draw_text_in_hex(const map_location &loc, const drawing_layer layer, const std::string &text, std::size_t font_size, color_t color, double x_in_hex=0.5, double y_in_hex=0.5)
Draw text on a hex.
Definition: display.cpp:1358
std::size_t viewing_team_index_
Definition: display.hpp:713
std::map< map_location, std::vector< overlay > > overlay_map
Definition: display.hpp:884
map_location selectedHex_
Definition: display.hpp:761
void recalculate_minimap()
Schedule the minimap for recalculation.
Definition: display.cpp:1465
bool unit_can_draw_here(const map_location &loc, const unit &unit) const
Returns true if there is no exclusive draw request for loc, or if there is, that it's for unit.
Definition: display.cpp:381
virtual void render() override
Update offscreen render buffers.
Definition: display.cpp:2251
void fade_tod_mask(const std::string &old, const std::string &new_)
ToD mask smooth fade.
Definition: display.cpp:2046
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition: display.cpp:2927
double turbo_speed() const
Definition: display.cpp:1965
bool reach_map_changed_
Definition: display.hpp:879
static double get_zoom_factor()
Returns the current zoom factor.
Definition: display.hpp:262
void invalidate_game_status()
Function to invalidate the game status displayed on the sidebar.
Definition: display.hpp:308
void rebuild_all()
Rebuild all dynamic terrain.
Definition: display.cpp:428
virtual void layout() override
Finalize screen layout.
Definition: display.cpp:2220
virtual void highlight_hex(map_location hex)
Definition: display.cpp:1389
void update_tod(const time_of_day *tod_override=nullptr)
Applies r,g,b coloring to the map.
Definition: display.cpp:387
void process_reachmap_changes()
Definition: display.cpp:3098
void scroll_to_tile(const map_location &loc, SCROLL_TYPE scroll_type=ONSCREEN, bool check_fogged=true, bool force=true)
Scroll such that location loc is on-screen.
Definition: display.cpp:1815
map_location mouseoverHex_
Definition: display.hpp:762
bool fogged(const map_location &loc) const
Returns true if location (x,y) is covered in fog.
Definition: display.cpp:669
void invalidate_all()
Function to invalidate all tiles.
Definition: display.cpp:2920
void drawing_buffer_add(const drawing_layer layer, const map_location &loc, decltype(draw_helper::do_draw) draw_func)
Add an item to the drawing buffer.
Definition: display.cpp:1241
std::size_t reach_map_team_index_
Definition: display.hpp:881
const display_context & context() const
Definition: display.hpp:187
rect get_location_rect(const map_location &loc) const
Returns the on-screen rect corresponding to a loc.
Definition: display.cpp:685
std::set< map_location > invalidated_
Definition: display.hpp:751
void create_buttons()
Definition: display.cpp:831
bool invalidateGameStatus_
Definition: display.hpp:739
virtual void draw_invalidated()
Only called when there's actual redrawing to do.
Definition: display.cpp:2407
reports * reports_object_
Definition: display.hpp:741
virtual void update() override
Update animations and internal state.
Definition: display.cpp:2203
virtual void draw_hex(const map_location &loc)
Redraws a single gamemap location.
Definition: display.cpp:2443
const std::unique_ptr< fake_unit_manager > fake_unit_man_
Definition: display.hpp:732
const display_context * dc_
Definition: display.hpp:670
bool shrouded(const map_location &loc) const
Returns true if location (x,y) is covered in shroud.
Definition: display.cpp:664
bool dont_show_all_
Definition: display.hpp:714
void refresh_report(const std::string &report_name, const config *new_cfg=nullptr)
Update the given report.
Definition: display.cpp:2676
std::weak_ptr< wb::manager > wb_
Definition: display.hpp:672
virtual void select_hex(map_location hex)
Definition: display.cpp:1381
reach_map reach_map_
Definition: display.hpp:877
void set_move(double xmove, double ymove)
void set_position(double xpos, double ypos)
void set_lifetime(const std::chrono::milliseconds &lifetime, const std::chrono::milliseconds &fadeout=std::chrono::milliseconds{100})
void set_color(const color_t &color)
void set_scroll_mode(LABEL_SCROLL_MODE scroll)
void set_font_size(int font_size)
Game board class.
Definition: game_board.hpp:47
team & get_team(int i)
Definition: game_board.hpp:91
unit_map::iterator find_visible_unit(const map_location &loc, const team &current_team, bool see_all=false)
Definition: game_board.cpp:172
virtual void draw_invalidated() override
Only called when there's actual redrawing to do.
void draw_movement_info(const map_location &loc)
Draws the movement info (turns available) for a given location.
void display_unit_hex(map_location hex)
Change the unit to be displayed in the sidebar.
void invalidate_unit_after_move(const map_location &src, const map_location &dst)
Same as invalidate_unit() if moving the displayed unit.
std::string attack_indicator_direction() const
Function to get attack direction suffix.
game_mode mode_
void flash_fade_route(const pathfind::marked_route &route)
Visualizes a path by flashing it white then fade out over 2 seconds.
virtual void highlight_hex(map_location hex) override
Function to highlight a location.
map_location displayedUnitHex_
void invalidate_route()
std::vector< texture > get_reachmap_images(const map_location &loc) const
void scroll_to_leader(int side, SCROLL_TYPE scroll_type=ONSCREEN, bool force=true)
Scrolls to the leader of a certain side.
static constexpr std::chrono::milliseconds FLASH_FADE_ROUTE_FLASH_DURATION
void set_game_mode(const game_mode mode)
virtual void draw_hex(const map_location &loc) override
Redraws a single gamemap location.
void set_attack_indicator(const map_location &src, const map_location &dst)
Set the attack direction indicator.
const std::unique_ptr< display_chat_manager > chat_man_
pathfind::marked_route route_
virtual void render() override
TLD render() override.
void set_route(const pathfind::marked_route *route)
Sets the route along which footsteps are drawn to show movement of a unit.
virtual overlay_map & get_overlays() override
Inherited from display.
static constexpr std::chrono::milliseconds FLASH_FADE_ROUTE_TOTAL_DURATION
void update_flash_fade_route()
static constexpr std::chrono::milliseconds FLASH_FADE_ROUTE_FADE_DURATION
bool flash_fade_route_active_
std::set< map_location > units_that_can_reach_goal_
void invalidate_unit()
Function to invalidate that unit status displayed on the sidebar.
virtual void select_hex(map_location hex) override
Function to display a location as selected.
pathfind::marked_route flash_fade_route_
void highlight_reach(const pathfind::paths &paths_list)
Sets the paths that are currently displayed as available for the unit to move along.
void new_turn()
Update lighting settings.
void clear_attack_indicator()
virtual const time_of_day & get_time_of_day(const map_location &loc) const override
void highlight_another_reach(const pathfind::paths &paths_list, const map_location &goal=map_location::null_location())
Add more paths to highlight.
map_location attack_indicator_dst_
game_mode
Sets the linger mode for the display.
@ RUNNING
no linger overlay, show fog and shroud.
std::chrono::steady_clock::time_point flash_fade_route_start_time_
bool unhighlight_reach()
Reset highlighting of paths.
void needs_rebuild(bool b)
Sets whether the screen (map visuals) needs to be rebuilt.
overlay_map overlay_map_
bool maybe_rebuild()
Rebuilds the screen if needs_rebuild(true) was previously called, and resets the flag.
virtual void layout() override
TLD layout() override.
virtual bool has_time_area() const override
game_display(game_board &board, std::weak_ptr< wb::manager > wb, reports &reports_object, const std::string &theme_id, const config &level)
virtual void update() override
TLD update() override.
map_location attack_indicator_src_
void float_label(const map_location &loc, const std::string &text, const color_t &color)
Function to float a label above a tile.
terrain_code get_terrain(const map_location &loc) const
Looks up terrain at a particular location.
Definition: map.cpp:265
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:347
Generic locator abstracting the location of an image.
Definition: picture.hpp:59
int reach_map_darken_opacity()
static prefs & get()
bool reach_map_darken_tex()
int reach_map_tint_opacity()
std::string reach_map_color()
std::string reach_map_enemy_color()
int reach_map_border_opacity()
const std::set< std::string > & report_list()
Definition: reports.cpp:1917
static bool is_synced()
bool is_enemy(int n) const
Definition: team.hpp:270
Wrapper class to encapsulate creation and management of an SDL_Texture.
Definition: texture.hpp:33
void set_alpha_mod(uint8_t alpha)
Alpha modifier.
Definition: texture.cpp:146
bool has_time_area() const
const time_of_day & get_time_of_day(int for_turn=0) const
Returns global time of day for the passed turn.
Definition: tod_manager.hpp:57
const time_of_day & get_previous_time_of_day() const
void redraw_unit(const unit &u) const
draw a unit.
Definition: drawer.cpp:202
unit_iterator end()
Definition: map.hpp:428
unit_iterator find(std::size_t id)
Definition: map.cpp:302
unit_iterator find_leader(int side)
Definition: map.cpp:320
This class represents a single unit of a specific type.
Definition: unit.hpp:39
Drawing functions, for drawing things on the screen.
drawing_layer
@ linger_overlay
The overlay used for the linger mode.
@ reachmap_highlight
Overlay on reachable hexes.
@ footsteps
Footsteps showing path from unit to mouse.
@ attack_indicator
Layer which holds the attack indicator.
@ reachmap_border
Overlay border of reachable hexes.
@ reachmap_darken
Overlay on unreachable hexes.
@ mouseover_top
Top half of image following the mouse.
@ mouseover_bottom
Bottom half of image following the mouse.
@ move_info
Movement info (defense%, etc...)
@ selected_hex
Image on the selected unit.
std::size_t i
Definition: function.cpp:1031
static lg::log_domain log_engine("engine")
static lg::log_domain log_display("display")
#define DBG_DP
bool incapacitated() const
Check if the unit has been petrified.
Definition: unit.hpp:826
int side() const
The side this unit belongs to.
Definition: unit.hpp:249
T end(const std::pair< T, T > &p)
void get_adjacent_tiles(const map_location &a, utils::span< map_location, 6 > res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:513
bool tiles_adjacent(const map_location &a, const map_location &b)
Function which tells if two locations are adjacent.
Definition: location.cpp:541
Standard logging facilities (interface).
bool is_shrouded(const display *disp, const map_location &loc)
Our definition of map labels being obscured is if the tile is obscured, or the tile below is obscured...
Definition: label.cpp:33
CURSOR_TYPE get()
Definition: cursor.cpp:185
@ WAIT
Definition: cursor.hpp:28
void blit(const texture &tex, const ::rect &dst)
Draws a texture, or part of a texture, at the given location.
Definition: draw.cpp:409
const int SIZE_FLOAT_LABEL
Definition: constants.cpp:32
const color_t YELLOW_COLOR
int add_floating_label(const floating_label &flabel)
add a label floating on the screen above everything else.
const color_t NORMAL_COLOR
@ ANCHOR_LABEL_MAP
std::string selected
std::string unreachable
std::string foot_teleport_enter
std::string foot_teleport_exit
std::string reach_map_prefix
Definition: game_config.cpp:58
color_t red_to_green(double val, bool for_text)
Return a color corresponding to the value val red for val=0.0 to green for val=100....
Functions to load and save images from/to disk.
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition: picture.cpp:855
@ HEXED
Standard hexagonal tile mask applied, removing portions that don't fit.
Definition: picture.hpp:175
@ TOD_COLORED
Same as HEXED, but with Time of Day color tint applied.
Definition: picture.hpp:177
texture get_texture(const image::locator &i_locator, TYPE type, bool skip_cache)
Returns an image texture suitable for hardware-accelerated rendering.
Definition: picture.cpp:955
Unit and team statistics.
::tod_manager * tod_manager
Definition: resources.cpp:29
game_board * gameboard
Definition: resources.cpp:20
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:88
std::string get_unknown_exception_type()
Utility function for finding the type of thing caught with catch(...).
Definition: general.cpp:23
auto * find(Container &container, const Value &value)
Convenience wrapper for using find on a container without needing to comare to end()
Definition: general.hpp:142
Definition: display.hpp:45
std::string to_string(const Range &range, const Func &op)
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
int w
Definition: pathfind.cpp:188
rect dst
Location on the final composed sheet.
rect src
Non-transparent portion of the surface to compose.
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:51
Encapsulates the map of the game.
Definition: location.hpp:46
static std::string write_direction(direction dir)
Definition: location.cpp:154
bool valid() const
Definition: location.hpp:111
direction
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:48
static const map_location & null_location()
Definition: location.hpp:103
Structure which holds a single route and marks for special events.
Definition: pathfind.hpp:142
std::vector< map_location > & steps
Definition: pathfind.hpp:187
std::vector< map_location > get_path(const const_iterator &) const
Returns the path going from the source point (included) to the destination point j (excluded).
Definition: pathfind.cpp:497
bool contains(const map_location &) const
Definition: pathfind.cpp:514
const_iterator find(const map_location &) const
Definition: pathfind.cpp:478
map_location curr
Definition: pathfind.hpp:89
Object which contains all the possible locations a unit can move to, with associated best routes to t...
Definition: pathfind.hpp:73
dest_vect destinations
Definition: pathfind.hpp:101
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:49
constexpr point center() const
The center point of the rectangle, accounting for origin.
Definition: rect.hpp:106
Object which defines a time of day with associated bonuses, image, sounds etc.
Definition: time_of_day.hpp:57
std::string image_mask
The image that is to be laid over all images while this time of day lasts.
Definition: time_of_day.hpp:96
bool valid() const
Definition: map.hpp:273
Applies the planned unit map for the duration of the struct's life.
Definition: manager.hpp:253
static map_location::direction s
#define h
#define b