The Battle for Wesnoth  1.19.24+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  // Create a temp texture a bit larger than we want. This allows us to compose the minimap and then
108  // scale the whole result down the desired destination texture size.
109  texture minimap(map_width, map_height, SDL_TEXTUREACCESS_TARGET);
110  SDL_SetTextureScaleMode(minimap, SDL_SCALEMODE_NEAREST);
111  if(!minimap) {
112  return nullptr;
113  }
114 
115  {
116  // Point rendering to the temp minimap texture.
117  const draw::render_target_setter target_setter{minimap};
118 
119  // Clear the minimap texture, as some of it can be left transparent.
120  draw::clear();
121 
122  //
123  // Terrain
124  //
125  if(preferences_minimap_draw_terrain) {
126  map.for_each_loc([&](const map_location& loc) {
127  const bool highlighted = reach_map && reach_map->count(loc) != 0 && !shrouded(loc);
128 
129  const terrain_type& terrain_info = shrouded(loc)
131  : map.get_terrain_info(loc);
132 
133  // Destination rect for drawing the current hex.
134  rect dest = get_dst_rect(loc);
135 
136  //
137  // Draw map terrain using either terrain images...
138  //
139  if(preferences_minimap_terrain_coding) {
140  if(!terrain_info.minimap_image().empty()) {
141  const std::string base_file = "terrain/" + terrain_info.minimap_image() + ".png";
142  const texture& tile = image::get_texture(base_file); // image::HEXED
143 
144  draw::blit(tile, dest);
145 
146  // NOTE: we skip the overlay when base is missing (to avoid hiding the error)
147  if(tile && terrain_info.is_combined() && !terrain_info.minimap_image_overlay().empty()) {
148  const std::string overlay_file = "terrain/" + terrain_info.minimap_image_overlay() + ".png";
149  const texture& overlay = image::get_texture(overlay_file); // image::HEXED
150 
151  // TODO: crop/center overlays?
152  draw::blit(overlay, dest);
153  }
154 
155  // FIXME: use shaders instead of textures for this once we can actually do that
156  using namespace std::string_literals;
157 
158  if(fogged(loc)) {
159  // Hex-shaped texture to apply #000000 at 40% opacity
160  static const texture fog_overlay = image::get_texture("terrain/minimap-fog.png"s);
161  draw::blit(fog_overlay, dest);
162  }
163 
164  if(highlighted) {
165  // Hex-shaped texture to apply #ffffff at 40% opacity
166  static const texture fog_overlay = image::get_texture("terrain/minimap-highlight.png"s);
167  draw::blit(fog_overlay, dest);
168  }
169  }
170  } else {
171  //
172  // ... or color coding.
173  //
174  color_t col(0, 0, 0, 0);
175 
176  // Despite its name, game_config::team_rgb_range isn't just team colors,
177  // it has "red", "lightblue", "cave", "reef", "fungus", etc.
178  auto it = game_config::team_rgb_range.find(terrain_info.id());
179  if(it != game_config::team_rgb_range.end()) {
180  col = it->second.rep();
181  }
182 
183  bool first = true;
184 
185  for(const auto& underlying_terrain : terrain_info.union_type()) {
186  const std::string& terrain_id = map.get_terrain_info(underlying_terrain).id();
187 
188  it = game_config::team_rgb_range.find(terrain_id);
189  if(it == game_config::team_rgb_range.end()) {
190  return;
191  }
192 
193  color_t tmp = it->second.rep();
194 
195  if(fogged(loc)) {
196  tmp.r = std::max(0, tmp.r - 50);
197  tmp.g = std::max(0, tmp.g - 50);
198  tmp.b = std::max(0, tmp.b - 50);
199  }
200 
201  if(highlighted) {
202  tmp.r = std::min(255, tmp.r + 50);
203  tmp.g = std::min(255, tmp.g + 50);
204  tmp.b = std::min(255, tmp.b + 50);
205  }
206 
207  if(first) {
208  first = false;
209  col = tmp;
210  } else {
211  col.r = col.r - (col.r - tmp.r) / 2;
212  col.g = col.g - (col.g - tmp.g) / 2;
213  col.b = col.b - (col.b - tmp.b) / 2;
214  }
215  }
216 
217  dest.w = scale * 3 / 4;
218  draw::fill(dest, col);
219  }
220  });
221  }
222 
223  //
224  // Villages
225  //
226  if(preferences_minimap_draw_villages && !is_blindfolded) {
227  for(const map_location& loc : map.villages()) {
228  if(vw && (vw->shrouded(loc) || vw->fogged(loc))) {
229  continue;
230  }
231 
232  color_t col(255, 255, 255);
233 
234  // TODO: Add a key to [game_config][colors] for this
235  auto iter = game_config::team_rgb_range.find("white");
236  if(iter != game_config::team_rgb_range.end()) {
237  col = iter->second.min();
238  }
239 
240  // Check needed for mp create dialog
241  const int side_num = resources::gameboard ? resources::gameboard->village_owner(loc) : 0;
242 
243  if(side_num > 0) {
244  if(preferences_minimap_unit_coding || !vw) {
245  col = team::get_minimap_color(side_num);
246  } else {
247  if(vw->owns_village(loc)) {
248  col = game_config::color_info(prefs::get().unmoved_color()).rep();
249  } else if(vw->is_enemy(side_num)) {
250  col = game_config::color_info(prefs::get().enemy_color()).rep();
251  } else {
252  col = game_config::color_info(prefs::get().allied_color()).rep();
253  }
254  }
255  }
256 
257  rect dest = get_dst_rect(loc);
258  dest.w = scale * 3 / 4;
259 
260  draw::fill(dest, col);
261  }
262  }
263 
264  //
265  // Units
266  //
267  if(units && preferences_minimap_draw_units && !is_blindfolded) {
268  for(const auto& u : *units) {
269  const map_location& u_loc = u.get_location();
270  const int side = u.side();
271  const bool is_enemy = vw && vw->is_enemy(side);
272 
273  if((vw && vw->fogged(u_loc)) || (is_enemy && disp && u.invisible(u_loc)) || u.get_hidden()) {
274  continue;
275  }
276 
277  color_t col = team::get_minimap_color(side);
278 
279  if(!preferences_minimap_unit_coding) {
280  auto status = orb_status::allied;
281 
282  if(is_enemy) {
283  status = orb_status::enemy;
284  } else if(vw && vw->side() == side) {
285  status = disp->context().unit_orb_status(u);
286  } else {
287  // no-op, status is already set to orb_status::allied;
288  }
289 
291  }
292 
293  rect fillrect = get_dst_rect(u_loc);
294  fillrect.w = scale * 3 / 4;
295 
296  draw::fill(fillrect, col);
297  }
298  }
299  }
300 
301  DBG_DP << "done generating minimap";
302 
303  return [minimap](rect dst) {
304  const auto [raw_w, raw_h] = minimap.get_raw_size();
305 
306  // Check which dimensions needs to be shrunk more
307  const double scale_ratio = std::min<double>(
308  dst.w * 1.0 / raw_w,
309  dst.h * 1.0 / raw_h
310  );
311 
312  // Preserve map aspect ratio within the requested area
313  const int scaled_w = static_cast<int>(raw_w * scale_ratio);
314  const int scaled_h = static_cast<int>(raw_h * scale_ratio);
315 
316  // Attempt to center the map in the requested area
317  dst.x = std::max(dst.x, dst.x + (dst.w - scaled_w) / 2);
318  dst.y = std::max(dst.y, dst.y + (dst.h - scaled_h) / 2);
319  dst.w = scaled_w;
320  dst.h = scaled_h;
321 
322  draw::blit(minimap, dst);
323 
324  // Let the caller know where the minimap *actually* ended up being drawn
325  return dst;
326  };
327 }
328 
329 }
static bool is_enemy(std::size_t side, std::size_t other_side)
Definition: abilities.cpp:1038
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:460
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:109
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:171
void clear()
Clear the current render target.
Definition: draw.cpp:52
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:62
void blit(const texture &tex, const ::rect &dst)
Draws a texture, or part of a texture, at the given location.
Definition: draw.cpp:409
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:955
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