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