The Battle for Wesnoth  1.19.0-dev
minimap.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
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"
28 #include "preferences/general.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 = preferences::minimap_draw_terrain() || ignore_terrain_disabled;
49  const bool preferences_minimap_terrain_coding = preferences::minimap_terrain_coding();
50  const bool preferences_minimap_draw_villages = preferences::minimap_draw_villages();
51  const bool preferences_minimap_draw_units = preferences::minimap_draw_units();
52  const bool preferences_minimap_unit_coding = preferences::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 = std::max(0, map.w()) * scale * 3 / 4;
59  const std::size_t map_height = 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 && map.tdata()->get_terrain_info(terrain).is_combined()
137  && !terrain_info.minimap_image_overlay().empty())
138  {
139  const std::string overlay_file = "terrain/" + terrain_info.minimap_image_overlay() + ".png";
140  const texture& overlay = image::get_texture(overlay_file); // image::HEXED
141 
142  // TODO: crop/center overlays?
143  draw::blit(overlay, dest);
144  }
145 
146  // FIXME: use shaders instead of textures for this once we can actually do that
147 
148  if(fogged(loc)) {
149  // Hex-shaped texture to apply #000000 at 40% opacity
150  static const texture fog_overlay = image::get_texture("terrain/minimap-fog.png");
151  draw::blit(fog_overlay, dest);
152  }
153 
154  if(highlighted) {
155  // Hex-shaped texture to apply #ffffff at 40% opacity
156  static const texture fog_overlay = image::get_texture("terrain/minimap-highlight.png");
157  draw::blit(fog_overlay, dest);
158  }
159  }
160  } else {
161  //
162  // ... or color coding.
163  //
164  color_t col(0, 0, 0, 0);
165 
166  // Despite its name, game_config::team_rgb_range isn't just team colors,
167  // it has "red", "lightblue", "cave", "reef", "fungus", etc.
168  auto it = game_config::team_rgb_range.find(terrain_info.id());
169  if(it != game_config::team_rgb_range.end()) {
170  col = it->second.rep();
171  }
172 
173  bool first = true;
174 
175  for(const auto& underlying_terrain : map.tdata()->underlying_union_terrain(terrain)) {
176  const std::string& terrain_id = map.tdata()->get_terrain_info(underlying_terrain).id();
177 
178  it = game_config::team_rgb_range.find(terrain_id);
179  if(it == game_config::team_rgb_range.end()) {
180  return;
181  }
182 
183  color_t tmp = it->second.rep();
184 
185  if(fogged(loc)) {
186  tmp.r = std::max(0, tmp.r - 50);
187  tmp.g = std::max(0, tmp.g - 50);
188  tmp.b = std::max(0, tmp.b - 50);
189  }
190 
191  if(highlighted) {
192  tmp.r = std::min(255, tmp.r + 50);
193  tmp.g = std::min(255, tmp.g + 50);
194  tmp.b = std::min(255, tmp.b + 50);
195  }
196 
197  if(first) {
198  first = false;
199  col = tmp;
200  } else {
201  col.r = col.r - (col.r - tmp.r) / 2;
202  col.g = col.g - (col.g - tmp.g) / 2;
203  col.b = col.b - (col.b - tmp.b) / 2;
204  }
205  }
206 
207  dest.w = scale * 3 / 4;
208  draw::fill(dest, col);
209  }
210  });
211  }
212 
213  //
214  // Villages
215  //
216  if(preferences_minimap_draw_villages) {
217  for(const map_location& loc : map.villages()) {
218  if(is_blindfolded || (vw && (vw->shrouded(loc) || vw->fogged(loc)))) {
219  continue;
220  }
221 
222  color_t col(255, 255, 255);
223 
224  // TODO: Add a key to [game_config][colors] for this
225  auto iter = game_config::team_rgb_range.find("white");
226  if(iter != game_config::team_rgb_range.end()) {
227  col = iter->second.min();
228  }
229 
230  // Check needed for mp create dialog
231  const int side_num = resources::gameboard ? resources::gameboard->village_owner(loc) : 0;
232 
233  if(side_num > 0) {
234  if(preferences_minimap_unit_coding || !vw) {
235  col = team::get_minimap_color(side_num);
236  } else {
237  if(vw->owns_village(loc)) {
239  } else if(vw->is_enemy(side_num)) {
241  } else {
243  }
244  }
245  }
246 
247  rect dest = get_dst_rect(loc);
248  dest.w = scale * 3 / 4;
249 
250  draw::fill(dest, col);
251  }
252  }
253 
254  //
255  // Units
256  //
257  if(units && preferences_minimap_draw_units && !is_blindfolded) {
258  for(const auto& u : *units) {
259  const map_location& u_loc = u.get_location();
260  const int side = u.side();
261  const bool is_enemy = vw && vw->is_enemy(side);
262 
263  if((vw && vw->fogged(u_loc)) || (is_enemy && disp && u.invisible(u_loc)) || u.get_hidden()) {
264  continue;
265  }
266 
267  color_t col = team::get_minimap_color(side);
268 
269  if(!preferences_minimap_unit_coding) {
270  auto status = orb_status::allied;
271 
272  if(is_enemy) {
273  status = orb_status::enemy;
274  } else if(vw && vw->side() == side) {
275  status = disp->get_disp_context().unit_orb_status(u);
276  } else {
277  // no-op, status is already set to orb_status::allied;
278  }
279 
281  }
282 
283  rect fillrect = get_dst_rect(u_loc);
284  fillrect.w = scale * 3 / 4;
285 
286  draw::fill(fillrect, col);
287  }
288  }
289  }
290 
291  DBG_DP << "done generating minimap";
292 
293  return [minimap](rect dst) {
294  const auto [raw_w, raw_h] = minimap.get_raw_size();
295 
296  // Check which dimensions needs to be shrunk more
297  const double scale_ratio = std::min<double>(
298  dst.w * 1.0 / raw_w,
299  dst.h * 1.0 / raw_h
300  );
301 
302  // Preserve map aspect ratio within the requested area
303  const int scaled_w = static_cast<int>(raw_w * scale_ratio);
304  const int scaled_h = static_cast<int>(raw_h * scale_ratio);
305 
306  // Attempt to center the map in the requested area
307  dst.x = std::max(dst.x, dst.x + (dst.w - scaled_w) / 2);
308  dst.y = std::max(dst.y, dst.y + (dst.h - scaled_h) / 2);
309  dst.w = scaled_w;
310  dst.h = scaled_h;
311 
312  draw::blit(minimap, dst);
313 
314  // Let the caller know where the minimap *actually* ended up being drawn
315  return dst;
316  };
317 }
318 
319 }
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:81
const display_context & get_disp_context() const
Definition: display.hpp:183
bool is_blindfolded() const
Definition: display.cpp:477
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:95
A class to manage automatic restoration of the render target.
Definition: draw.hpp:433
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
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:964
int side() const
Definition: team.hpp:174
bool is_enemy(int n) const
Definition: team.hpp:229
bool owns_village(const map_location &loc) const
Definition: team.hpp:171
bool shrouded(const map_location &loc) const
Definition: team.cpp:650
bool fogged(const map_location &loc) const
Definition: team.cpp:659
const std::string & minimap_image() const
Definition: terrain.hpp:45
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:112
Container associating units to locations.
Definition: map.hpp:98
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 fill(const SDL_Rect &rect, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
Fill an area with the given colour.
Definition: draw.cpp:50
void clear()
Clear the current render target.
Definition: draw.cpp:40
void blit(const texture &tex, const SDL_Rect &dst)
Draws a texture, or part of a texture, at the given location.
Definition: draw.cpp:310
void rect(const SDL_Rect &rect)
Draw a rectangle.
Definition: draw.cpp:150
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:960
std::string get_orb_color(orb_status os)
Wrapper for the various preferences::unmoved_color(), moved_color(), etc methods, using the enum inst...
Definition: orb_status.cpp:40
bool minimap_movement_coding()
Definition: general.cpp:827
bool minimap_draw_villages()
Definition: general.cpp:857
bool minimap_terrain_coding()
Definition: general.cpp:837
bool minimap_draw_terrain()
Definition: general.cpp:867
std::string enemy_color()
Definition: general.cpp:342
std::string allied_color()
Definition: general.cpp:322
std::string unmoved_color()
Definition: general.cpp:362
bool minimap_draw_units()
Definition: general.cpp:847
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, const ScalerCfg &cfg=ScalerCfg(), int yFirst=0, int yLast=std::numeric_limits< int >::max())
Definition: xbrz.cpp:1189
@ allied
Belongs to a friendly side.
@ enemy
Belongs to a non-friendly side; normally visualised by not displaying an orb.
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:59
Encapsulates the map of the game.
Definition: location.hpp:38
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:47
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
void set_texture_scale_quality(const char *value)
Sets the texture scale quality.
Definition: texture.hpp:227