The Battle for Wesnoth  1.19.13+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  const int scale = (preferences_minimap_draw_terrain && preferences_minimap_terrain_coding) ? 24 : 4;
55 
56  DBG_DP << "Creating minimap: " << static_cast<int>(map.w() * scale * 0.75) << ", " << map.h() * scale;
57 
58  const std::size_t map_width = static_cast<std::size_t>(std::max(0, map.w())) * scale * 3 / 4;
59  const std::size_t map_height = static_cast<std::size_t>(std::max(0, map.h())) * scale;
60 
61  // No map!
62  if(map_width == 0 || map_height == 0) {
63  return nullptr;
64  }
65 
66  // Nothing to draw!
67  if(!preferences_minimap_draw_villages && !preferences_minimap_draw_terrain) {
68  return nullptr;
69  }
70 
71  const display* disp = display::get_singleton();
72  const bool is_blindfolded = disp && disp->is_blindfolded();
73 
74  const auto shrouded = [&](const map_location& loc) {
75  return is_blindfolded || (vw && vw->shrouded(loc));
76  };
77 
78  const auto fogged = [&](const map_location& loc) {
79  // Shrouded hex are not considered fogged (no need to fog a black image)
80  return vw && !shrouded(loc) && vw->fogged(loc);
81  };
82 
83  // Gets a destination rect for drawing at the given coordinates.
84  // We need a balanced shift up and down of the hexes.
85  // If not, only the bottom half-hexes are clipped and it looks asymmetrical.
86  const auto get_dst_rect = [scale](const map_location& loc) {
87  return rect {
88  loc.x * scale * 3 / 4 - (scale / 4),
89  loc.y * scale + scale / 4 * (is_odd(loc.x) ? 1 : -1) - (scale / 4),
90  scale,
91  scale
92  };
93  };
94 
95  // We want to draw the minimap with NN scaling.
96  set_texture_scale_quality("nearest");
97 
98  // Create a temp texture a bit larger than we want. This allows us to compose the minimap and then
99  // scale the whole result down the desired destination texture size.
100  texture minimap(map_width, map_height, SDL_TEXTUREACCESS_TARGET);
101  if(!minimap) {
102  return nullptr;
103  }
104 
105  {
106  // Point rendering to the temp minimap texture.
107  const draw::render_target_setter target_setter{minimap};
108 
109  // Clear the minimap texture, as some of it can be left transparent.
110  draw::clear();
111 
112  //
113  // Terrain
114  //
115  if(preferences_minimap_draw_terrain) {
116  map.for_each_loc([&](const map_location& loc) {
117  const bool highlighted = reach_map && reach_map->count(loc) != 0 && !shrouded(loc);
118 
119  const t_translation::terrain_code terrain = shrouded(loc) ? t_translation::VOID_TERRAIN : map[loc];
120  const terrain_type& terrain_info = map.tdata()->get_terrain_info(terrain);
121 
122  // Destination rect for drawing the current hex.
123  rect dest = get_dst_rect(loc);
124 
125  //
126  // Draw map terrain using either terrain images...
127  //
128  if(preferences_minimap_terrain_coding) {
129  if(!terrain_info.minimap_image().empty()) {
130  const std::string base_file = "terrain/" + terrain_info.minimap_image() + ".png";
131  const texture& tile = image::get_texture(base_file); // image::HEXED
132 
133  draw::blit(tile, dest);
134 
135  // NOTE: we skip the overlay when base is missing (to avoid hiding the error)
136  if(tile && terrain_info.is_combined() && !terrain_info.minimap_image_overlay().empty()) {
137  const std::string overlay_file = "terrain/" + terrain_info.minimap_image_overlay() + ".png";
138  const texture& overlay = image::get_texture(overlay_file); // image::HEXED
139 
140  // TODO: crop/center overlays?
141  draw::blit(overlay, dest);
142  }
143 
144  // FIXME: use shaders instead of textures for this once we can actually do that
145  using namespace std::string_literals;
146 
147  if(fogged(loc)) {
148  // Hex-shaped texture to apply #000000 at 40% opacity
149  static const texture fog_overlay = image::get_texture("terrain/minimap-fog.png"s);
150  draw::blit(fog_overlay, dest);
151  }
152 
153  if(highlighted) {
154  // Hex-shaped texture to apply #ffffff at 40% opacity
155  static const texture fog_overlay = image::get_texture("terrain/minimap-highlight.png"s);
156  draw::blit(fog_overlay, dest);
157  }
158  }
159  } else {
160  //
161  // ... or color coding.
162  //
163  color_t col(0, 0, 0, 0);
164 
165  // Despite its name, game_config::team_rgb_range isn't just team colors,
166  // it has "red", "lightblue", "cave", "reef", "fungus", etc.
167  auto it = game_config::team_rgb_range.find(terrain_info.id());
168  if(it != game_config::team_rgb_range.end()) {
169  col = it->second.rep();
170  }
171 
172  bool first = true;
173 
174  for(const auto& underlying_terrain : map.tdata()->underlying_union_terrain(terrain)) {
175  const std::string& terrain_id = map.tdata()->get_terrain_info(underlying_terrain).id();
176 
177  it = game_config::team_rgb_range.find(terrain_id);
178  if(it == game_config::team_rgb_range.end()) {
179  return;
180  }
181 
182  color_t tmp = it->second.rep();
183 
184  if(fogged(loc)) {
185  tmp.r = std::max(0, tmp.r - 50);
186  tmp.g = std::max(0, tmp.g - 50);
187  tmp.b = std::max(0, tmp.b - 50);
188  }
189 
190  if(highlighted) {
191  tmp.r = std::min(255, tmp.r + 50);
192  tmp.g = std::min(255, tmp.g + 50);
193  tmp.b = std::min(255, tmp.b + 50);
194  }
195 
196  if(first) {
197  first = false;
198  col = tmp;
199  } else {
200  col.r = col.r - (col.r - tmp.r) / 2;
201  col.g = col.g - (col.g - tmp.g) / 2;
202  col.b = col.b - (col.b - tmp.b) / 2;
203  }
204  }
205 
206  dest.w = scale * 3 / 4;
207  draw::fill(dest, col);
208  }
209  });
210  }
211 
212  //
213  // Villages
214  //
215  if(preferences_minimap_draw_villages && !is_blindfolded) {
216  for(const map_location& loc : map.villages()) {
217  if(vw && (vw->shrouded(loc) || vw->fogged(loc))) {
218  continue;
219  }
220 
221  color_t col(255, 255, 255);
222 
223  // TODO: Add a key to [game_config][colors] for this
224  auto iter = game_config::team_rgb_range.find("white");
225  if(iter != game_config::team_rgb_range.end()) {
226  col = iter->second.min();
227  }
228 
229  // Check needed for mp create dialog
230  const int side_num = resources::gameboard ? resources::gameboard->village_owner(loc) : 0;
231 
232  if(side_num > 0) {
233  if(preferences_minimap_unit_coding || !vw) {
234  col = team::get_minimap_color(side_num);
235  } else {
236  if(vw->owns_village(loc)) {
237  col = game_config::color_info(prefs::get().unmoved_color()).rep();
238  } else if(vw->is_enemy(side_num)) {
239  col = game_config::color_info(prefs::get().enemy_color()).rep();
240  } else {
241  col = game_config::color_info(prefs::get().allied_color()).rep();
242  }
243  }
244  }
245 
246  rect dest = get_dst_rect(loc);
247  dest.w = scale * 3 / 4;
248 
249  draw::fill(dest, col);
250  }
251  }
252 
253  //
254  // Units
255  //
256  if(units && preferences_minimap_draw_units && !is_blindfolded) {
257  for(const auto& u : *units) {
258  const map_location& u_loc = u.get_location();
259  const int side = u.side();
260  const bool is_enemy = vw && vw->is_enemy(side);
261 
262  if((vw && vw->fogged(u_loc)) || (is_enemy && disp && u.invisible(u_loc)) || u.get_hidden()) {
263  continue;
264  }
265 
266  color_t col = team::get_minimap_color(side);
267 
268  if(!preferences_minimap_unit_coding) {
269  auto status = orb_status::allied;
270 
271  if(is_enemy) {
272  status = orb_status::enemy;
273  } else if(vw && vw->side() == side) {
274  status = disp->context().unit_orb_status(u);
275  } else {
276  // no-op, status is already set to orb_status::allied;
277  }
278 
280  }
281 
282  rect fillrect = get_dst_rect(u_loc);
283  fillrect.w = scale * 3 / 4;
284 
285  draw::fill(fillrect, col);
286  }
287  }
288  }
289 
290  DBG_DP << "done generating minimap";
291 
292  return [minimap](rect dst) {
293  const auto [raw_w, raw_h] = minimap.get_raw_size();
294 
295  // Check which dimensions needs to be shrunk more
296  const double scale_ratio = std::min<double>(
297  dst.w * 1.0 / raw_w,
298  dst.h * 1.0 / raw_h
299  );
300 
301  // Preserve map aspect ratio within the requested area
302  const int scaled_w = static_cast<int>(raw_w * scale_ratio);
303  const int scaled_h = static_cast<int>(raw_h * scale_ratio);
304 
305  // Attempt to center the map in the requested area
306  dst.x = std::max(dst.x, dst.x + (dst.w - scaled_w) / 2);
307  dst.y = std::max(dst.y, dst.y + (dst.h - scaled_h) / 2);
308  dst.w = scaled_w;
309  dst.h = scaled_h;
310 
311  draw::blit(minimap, dst);
312 
313  // Let the caller know where the minimap *actually* ended up being drawn
314  return dst;
315  };
316 }
317 
318 }
map_location loc
Definition: move.cpp:172
color_t rep() const
High-contrast shade, intended for the minimap markers.
Definition: color_range.hpp:94
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:136
Encapsulates the map of the game.
Definition: map.hpp:172
const std::vector< map_location > & villages() const
Return a list of the locations of villages on the map.
Definition: map.hpp:237
const std::shared_ptr< terrain_type_data > & tdata() const
Definition: map.hpp:204
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:949
int side() const
Definition: team.hpp:179
bool is_enemy(int n) const
Definition: team.hpp:266
bool owns_village(const map_location &loc) const
Definition: team.hpp:176
bool shrouded(const map_location &loc) const
Definition: team.cpp:635
bool fogged(const map_location &loc) const
Definition: team.cpp:644
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:170
const std::string & id() const
Definition: terrain.hpp:52
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:36
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:942
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:61
Encapsulates the map of the game.
Definition: location.hpp:45
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:49
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...
Definition: translation.hpp:49
static map_location::direction s
void set_texture_scale_quality(const char *value)
Sets the texture scale quality.
Definition: texture.hpp:224