The Battle for Wesnoth  1.19.7+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"
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<size_t>(std::max(0, map.w())) * scale * 3 / 4;
59  const std::size_t map_height = static_cast<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 && 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  using namespace std::string_literals;
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"s);
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"s);
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)) {
239  col = game_config::color_info(prefs::get().unmoved_color()).rep();
240  } else if(vw->is_enemy(side_num)) {
241  col = game_config::color_info(prefs::get().enemy_color()).rep();
242  } else {
243  col = game_config::color_info(prefs::get().allied_color()).rep();
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->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 }
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:97
bool is_blindfolded() const
Definition: display.cpp:458
const display_context & context() const
Definition: display.hpp:193
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:111
A class to manage automatic restoration of the render target.
Definition: draw.hpp:470
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:75
static color_t get_minimap_color(int side)
Definition: team.cpp:964
int side() const
Definition: team.hpp:175
bool is_enemy(int n) const
Definition: team.hpp:229
bool owns_village(const map_location &loc) const
Definition: team.hpp:172
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
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 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:920
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:1170
@ 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:59
Encapsulates the map of the game.
Definition: location.hpp:45
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
static map_location::direction s
void set_texture_scale_quality(const char *value)
Sets the texture scale quality.
Definition: texture.hpp:227