The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
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
34static 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
38namespace 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_units && !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
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)
map_location loc
Definition move.cpp:172
color_t rep() const
High-contrast against typical terrain colors, intended for the minimap markers.
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:453
static display * get_singleton()
Returns the display object if a display object exists.
Definition display.hpp:102
const display_context & context() const
Definition display.hpp:187
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:182
bool is_enemy(int n) const
Definition team.hpp:270
bool owns_village(const map_location &loc) const
Definition team.hpp:179
bool shrouded(const map_location &loc) const
Definition team.cpp:636
bool fogged(const map_location &loc) const
Definition team.cpp:645
bool is_combined() const
True for instances created by the terrain_code(base, overlay) constructor.
Definition terrain.hpp:177
const std::string & minimap_image_overlay() const
Definition terrain.hpp:46
const std::string & minimap_image() const
Definition terrain.hpp:45
const std::string & id() const
Definition terrain.hpp:52
const t_translation::ter_list & union_type() const
Definition terrain.hpp:87
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
#define DBG_DP
Definition display.cpp:79
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")
void clear()
Clear the current render target.
Definition draw.cpp:52
void fill()
Definition draw.cpp:115
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...
game_board * gameboard
Definition resources.cpp:20
const terrain_code VOID_TERRAIN
VOID_TERRAIN is used for shrouded hexes.
@ 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