The Battle for Wesnoth  1.19.21+dev
minimap.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 #define GETTEXT_DOMAIN "wesnoth-lib"
17 
18 #include "minimap.hpp"
19 
20 #include "color.hpp"
21 #include "display.hpp"
22 #include "draw.hpp"
23 #include "game_board.hpp"
24 #include "gettext.hpp"
25 #include "picture.hpp"
26 #include "log.hpp"
27 #include "map/map.hpp"
29 #include "resources.hpp"
30 #include "team.hpp"
31 #include "terrain/type_data.hpp"
32 #include "units/unit.hpp"
33 
34 static lg::log_domain log_display("display");
35 #define DBG_DP LOG_STREAM(debug, log_display)
36 #define WRN_DP LOG_STREAM(warn, log_display)
37 
38 namespace image {
39 
41  const gamemap& map,
42  const team* vw,
43  const unit_map* units,
44  const std::map<map_location, unsigned int>* reach_map,
45  bool ignore_terrain_disabled)
46 {
47  // Drawing mode flags.
48  const bool preferences_minimap_draw_terrain = prefs::get().minimap_draw_terrain() || ignore_terrain_disabled;
49  const bool preferences_minimap_terrain_coding = prefs::get().minimap_terrain_coding();
50  const bool preferences_minimap_draw_villages = prefs::get().minimap_draw_villages();
51  const bool preferences_minimap_draw_units = prefs::get().minimap_draw_units();
52  const bool preferences_minimap_unit_coding = prefs::get().minimap_movement_coding();
53 
54  auto get_scale = [&](){
55  const int size_max = std::max(map.h(), map.w());
56  if(!preferences_minimap_draw_terrain || !preferences_minimap_terrain_coding) {
57  return 4;
58  } else if(size_max > 60) {
59  return 8;
60  } else if(size_max > 40) {
61  return 16;
62  } else {
63  return 24;
64  }
65  };
66  const int scale = get_scale();
67 
68  DBG_DP << "Creating minimap: " << static_cast<int>(map.w() * scale * 0.75) << ", " << map.h() * scale;
69 
70  const std::size_t map_width = static_cast<std::size_t>(std::max(0, map.w())) * scale * 3 / 4;
71  const std::size_t map_height = static_cast<std::size_t>(std::max(0, map.h())) * scale;
72 
73  // No map!
74  if(map_width == 0 || map_height == 0) {
75  return nullptr;
76  }
77 
78  // Nothing to draw!
79  if(!preferences_minimap_draw_villages && !preferences_minimap_draw_terrain) {
80  return nullptr;
81  }
82 
83  const display* disp = display::get_singleton();
84  const bool is_blindfolded = disp && disp->is_blindfolded();
85 
86  const auto shrouded = [&](const map_location& loc) {
87  return is_blindfolded || (vw && vw->shrouded(loc));
88  };
89 
90  const auto fogged = [&](const map_location& loc) {
91  // Shrouded hex are not considered fogged (no need to fog a black image)
92  return vw && !shrouded(loc) && vw->fogged(loc);
93  };
94 
95  // Gets a destination rect for drawing at the given coordinates.
96  // We need a balanced shift up and down of the hexes.
97  // If not, only the bottom half-hexes are clipped and it looks asymmetrical.
98  const auto get_dst_rect = [scale](const map_location& loc) {
99  return rect {
100  loc.x * scale * 3 / 4 - (scale / 4),
101  loc.y * scale + scale / 4 * (is_odd(loc.x) ? 1 : -1) - (scale / 4),
102  scale,
103  scale
104  };
105  };
106 
107  // We want to draw the minimap with NN scaling.
108  set_texture_scale_quality("nearest");
109 
110  // Create a temp texture a bit larger than we want. This allows us to compose the minimap and then
111  // scale the whole result down the desired destination texture size.
112  texture minimap(map_width, map_height, SDL_TEXTUREACCESS_TARGET);
113  if(!minimap) {
114  return nullptr;
115  }
116 
117  {
118  // Point rendering to the temp minimap texture.
119  const draw::render_target_setter target_setter{minimap};
120 
121  // Clear the minimap texture, as some of it can be left transparent.
122  draw::clear();
123 
124  //
125  // Terrain
126  //
127  if(preferences_minimap_draw_terrain) {
128  map.for_each_loc([&](const map_location& loc) {
129  const bool highlighted = reach_map && reach_map->count(loc) != 0 && !shrouded(loc);
130 
131  const terrain_type& terrain_info = shrouded(loc)
133  : map.get_terrain_info(loc);
134 
135  // Destination rect for drawing the current hex.
136  rect dest = get_dst_rect(loc);
137 
138  //
139  // Draw map terrain using either terrain images...
140  //
141  if(preferences_minimap_terrain_coding) {
142  if(!terrain_info.minimap_image().empty()) {
143  const std::string base_file = "terrain/" + terrain_info.minimap_image() + ".png";
144  const texture& tile = image::get_texture(base_file); // image::HEXED
145 
146  draw::blit(tile, dest);
147 
148  // NOTE: we skip the overlay when base is missing (to avoid hiding the error)
149  if(tile && terrain_info.is_combined() && !terrain_info.minimap_image_overlay().empty()) {
150  const std::string overlay_file = "terrain/" + terrain_info.minimap_image_overlay() + ".png";
151  const texture& overlay = image::get_texture(overlay_file); // image::HEXED
152 
153  // TODO: crop/center overlays?
154  draw::blit(overlay, dest);
155  }
156 
157  // FIXME: use shaders instead of textures for this once we can actually do that
158  using namespace std::string_literals;
159 
160  if(fogged(loc)) {
161  // Hex-shaped texture to apply #000000 at 40% opacity
162  static const texture fog_overlay = image::get_texture("terrain/minimap-fog.png"s);
163  draw::blit(fog_overlay, dest);
164  }
165 
166  if(highlighted) {
167  // Hex-shaped texture to apply #ffffff at 40% opacity
168  static const texture fog_overlay = image::get_texture("terrain/minimap-highlight.png"s);
169  draw::blit(fog_overlay, dest);
170  }
171  }
172  } else {
173  //
174  // ... or color coding.
175  //
176  color_t col(0, 0, 0, 0);
177 
178  // Despite its name, game_config::team_rgb_range isn't just team colors,
179  // it has "red", "lightblue", "cave", "reef", "fungus", etc.
180  auto it = game_config::team_rgb_range.find(terrain_info.id());
181  if(it != game_config::team_rgb_range.end()) {
182  col = it->second.rep();
183  }
184 
185  bool first = true;
186 
187  for(const auto& underlying_terrain : terrain_info.union_type()) {
188  const std::string& terrain_id = map.get_terrain_info(underlying_terrain).id();
189 
190  it = game_config::team_rgb_range.find(terrain_id);
191  if(it == game_config::team_rgb_range.end()) {
192  return;
193  }
194 
195  color_t tmp = it->second.rep();
196 
197  if(fogged(loc)) {
198  tmp.r = std::max(0, tmp.r - 50);
199  tmp.g = std::max(0, tmp.g - 50);
200  tmp.b = std::max(0, tmp.b - 50);
201  }
202 
203  if(highlighted) {
204  tmp.r = std::min(255, tmp.r + 50);
205  tmp.g = std::min(255, tmp.g + 50);
206  tmp.b = std::min(255, tmp.b + 50);
207  }
208 
209  if(first) {
210  first = false;
211  col = tmp;
212  } else {
213  col.r = col.r - (col.r - tmp.r) / 2;
214  col.g = col.g - (col.g - tmp.g) / 2;
215  col.b = col.b - (col.b - tmp.b) / 2;
216  }
217  }
218 
219  dest.w = scale * 3 / 4;
220  draw::fill(dest, col);
221  }
222  });
223  }
224 
225  //
226  // Villages
227  //
228  if(preferences_minimap_draw_villages && !is_blindfolded) {
229  for(const map_location& loc : map.villages()) {
230  if(vw && (vw->shrouded(loc) || vw->fogged(loc))) {
231  continue;
232  }
233 
234  color_t col(255, 255, 255);
235 
236  // TODO: Add a key to [game_config][colors] for this
237  auto iter = game_config::team_rgb_range.find("white");
238  if(iter != game_config::team_rgb_range.end()) {
239  col = iter->second.min();
240  }
241 
242  // Check needed for mp create dialog
243  const int side_num = resources::gameboard ? resources::gameboard->village_owner(loc) : 0;
244 
245  if(side_num > 0) {
246  if(preferences_minimap_unit_coding || !vw) {
247  col = team::get_minimap_color(side_num);
248  } else {
249  if(vw->owns_village(loc)) {
250  col = game_config::color_info(prefs::get().unmoved_color()).rep();
251  } else if(vw->is_enemy(side_num)) {
252  col = game_config::color_info(prefs::get().enemy_color()).rep();
253  } else {
254  col = game_config::color_info(prefs::get().allied_color()).rep();
255  }
256  }
257  }
258 
259  rect dest = get_dst_rect(loc);
260  dest.w = scale * 3 / 4;
261 
262  draw::fill(dest, col);
263  }
264  }
265 
266  //
267  // Units
268  //
269  if(units && preferences_minimap_draw_units && !is_blindfolded) {
270  for(const auto& u : *units) {
271  const map_location& u_loc = u.get_location();
272  const int side = u.side();
273  const bool is_enemy = vw && vw->is_enemy(side);
274 
275  if((vw && vw->fogged(u_loc)) || (is_enemy && disp && u.invisible(u_loc)) || u.get_hidden()) {
276  continue;
277  }
278 
279  color_t col = team::get_minimap_color(side);
280 
281  if(!preferences_minimap_unit_coding) {
282  auto status = orb_status::allied;
283 
284  if(is_enemy) {
285  status = orb_status::enemy;
286  } else if(vw && vw->side() == side) {
287  status = disp->context().unit_orb_status(u);
288  } else {
289  // no-op, status is already set to orb_status::allied;
290  }
291 
293  }
294 
295  rect fillrect = get_dst_rect(u_loc);
296  fillrect.w = scale * 3 / 4;
297 
298  draw::fill(fillrect, col);
299  }
300  }
301  }
302 
303  DBG_DP << "done generating minimap";
304 
305  return [minimap](rect dst) {
306  const auto [raw_w, raw_h] = minimap.get_raw_size();
307 
308  // Check which dimensions needs to be shrunk more
309  const double scale_ratio = std::min<double>(
310  dst.w * 1.0 / raw_w,
311  dst.h * 1.0 / raw_h
312  );
313 
314  // Preserve map aspect ratio within the requested area
315  const int scaled_w = static_cast<int>(raw_w * scale_ratio);
316  const int scaled_h = static_cast<int>(raw_h * scale_ratio);
317 
318  // Attempt to center the map in the requested area
319  dst.x = std::max(dst.x, dst.x + (dst.w - scaled_w) / 2);
320  dst.y = std::max(dst.y, dst.y + (dst.h - scaled_h) / 2);
321  dst.w = scaled_w;
322  dst.h = scaled_h;
323 
324  draw::blit(minimap, dst);
325 
326  // Let the caller know where the minimap *actually* ended up being drawn
327  return dst;
328  };
329 }
330 
331 }
static bool is_enemy(std::size_t side, std::size_t other_side)
Definition: abilities.cpp:1037
map_location loc
Definition: move.cpp:172
color_t rep() const
High-contrast shade, intended for the minimap markers.
Definition: color_range.hpp:88
orb_status unit_orb_status(const unit &u) const
Returns an enumurated summary of whether this unit can move and/or attack.
int village_owner(const map_location &loc) const
Given the location of a village, will return the 1-based number of the team that currently owns it,...
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Definition: display.hpp:88
bool is_blindfolded() const
Definition: display.cpp:447
const display_context & context() const
Definition: display.hpp:184
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:102
A class to manage automatic restoration of the render target.
Definition: draw.hpp:478
int w() const
Effective map width.
Definition: map.hpp:50
int h() const
Effective map height.
Definition: map.hpp:53
void for_each_loc(const F &f) const
Definition: map.hpp:140
Encapsulates the map of the game.
Definition: map.hpp:176
const std::vector< map_location > & villages() const
Return a list of the locations of villages on the map.
Definition: map.hpp:232
const terrain_type & get_terrain_info(const t_translation::terrain_code &terrain) const
Definition: map.cpp:78
static prefs & get()
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:74
static color_t get_minimap_color(int side)
Definition: team.cpp:948
int side() const
Definition: team.hpp:179
bool is_enemy(int n) const
Definition: team.hpp:267
bool owns_village(const map_location &loc) const
Definition: team.hpp:176
bool shrouded(const map_location &loc) const
Definition: team.cpp:636
bool fogged(const map_location &loc) const
Definition: team.cpp:645
const std::string & minimap_image() const
Definition: terrain.hpp:45
bool is_combined() const
True for instances created by the terrain_code(base, overlay) constructor.
Definition: terrain.hpp:177
const std::string & id() const
Definition: terrain.hpp:52
const t_translation::ter_list & union_type() const
Definition: terrain.hpp:87
const std::string & minimap_image_overlay() const
Definition: terrain.hpp:46
Wrapper class to encapsulate creation and management of an SDL_Texture.
Definition: texture.hpp:33
point get_raw_size() const
The raw internal texture size.
Definition: texture.cpp:99
Container associating units to locations.
Definition: map.hpp:98
map_display and display: classes which take care of displaying the map and game-data on the screen.
Drawing functions, for drawing things on the screen.
Standard logging facilities (interface).
constexpr bool is_odd(T num)
Definition: math.hpp:34
static lg::log_domain log_display("display")
#define DBG_DP
Definition: minimap.cpp:35
void rect(const ::rect &rect)
Draw a rectangle.
Definition: draw.cpp:159
void clear()
Clear the current render target.
Definition: draw.cpp:42
void fill(const ::rect &rect, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
Fill an area with the given colour.
Definition: draw.cpp:52
void blit(const texture &tex, const ::rect &dst)
Draws a texture, or part of a texture, at the given location.
Definition: draw.cpp:394
std::map< std::string, color_range, std::less<> > team_rgb_range
Colors defined by WML [color_range] tags.
const color_range & color_info(std::string_view name)
Functions to load and save images from/to disk.
std::function< rect(rect)> prep_minimap_for_rendering(const gamemap &map, const team *vw, const unit_map *units, const std::map< map_location, unsigned int > *reach_map, bool ignore_terrain_disabled)
Prepares the minimap texture and returns a function which will render it to the current rendering tar...
Definition: minimap.cpp:40
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:953
std::string get_orb_color(orb_status os)
Wrapper for the various prefs::get().unmoved_color(), moved_color(), etc methods, using the enum inst...
Definition: orb_status.cpp:40
game_board * gameboard
Definition: resources.cpp:20
const terrain_code VOID_TERRAIN
VOID_TERRAIN is used for shrouded hexes.
void scale(size_t factor, const uint32_t *src, uint32_t *trg, int srcWidth, int srcHeight, ColorFormat colFmt, const ScalerCfg &cfg=ScalerCfg(), int yFirst=0, int yLast=std::numeric_limits< int >::max())
Definition: xbrz.cpp:1175
@ allied
Belongs to a friendly side.
@ enemy
Belongs to a non-friendly side; normally visualised by not displaying an orb.
rect dst
Location on the final composed sheet.
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
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:49
static map_location::direction s
void set_texture_scale_quality(const char *value)
Sets the texture scale quality.
Definition: texture.hpp:224