1 /*
2    Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
3    Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY.
11 
12    See the COPYING file for more details.
13 */
14 
15 #define GETTEXT_DOMAIN "wesnoth-lib"
16 
17 #include "minimap.hpp"
18 
19 #include "color.hpp"
20 #include "display.hpp"
21 #include "game_board.hpp"
22 #include "gettext.hpp"
23 #include "picture.hpp"
24 #include "log.hpp"
25 #include "map/map.hpp"
26 #include "preferences/general.hpp"
27 #include "resources.hpp"
28 #include "sdl/surface.hpp"
29 #include "team.hpp"
30 #include "terrain/type_data.hpp"
31 
32 static lg::log_domain log_display("display");
33 #define DBG_DP LOG_STREAM(debug, log_display)
34 #define WRN_DP LOG_STREAM(warn, log_display)
35 
36 
37 namespace image {
38 
getMinimap(int w,int h,const gamemap & map,const team * vw,const std::map<map_location,unsigned int> * reach_map,bool ignore_terrain_disabled)39 surface getMinimap(int w, int h, const gamemap &map, const team *vw, const std::map<map_location,unsigned int> *reach_map, bool ignore_terrain_disabled)
40 {
41 	const terrain_type_data & tdata = *map.tdata();
42 
43 
44 	const bool preferences_minimap_draw_terrain = preferences::minimap_draw_terrain() || ignore_terrain_disabled;
45 	const bool preferences_minimap_terrain_coding = preferences::minimap_terrain_coding();
46 	const bool preferences_minimap_draw_villages = preferences::minimap_draw_villages();
47 	const bool preferences_minimap_unit_coding = preferences::minimap_movement_coding();
48 
49 	const int scale = (preferences_minimap_draw_terrain && preferences_minimap_terrain_coding) ? 24 : 4;
50 
51 	DBG_DP << "creating minimap " << int(map.w()*scale*0.75) << "," << map.h()*scale << "\n";
52 
53 	const size_t map_width = map.w()*scale*3/4;
54 	const size_t map_height = map.h()*scale;
55 	if(map_width == 0 || map_height == 0) {
56 		return surface(nullptr);
57 	}
58 
59 	if(!preferences_minimap_draw_villages && !preferences_minimap_draw_terrain)
60 	{
61 		//return if there is nothing to draw.
62 		//(optimisation)
63 		double ratio = std::min<double>( w*1.0 / map_width, h*1.0 / map_height);
64 		return surface(map_width * ratio, map_height * ratio);
65 	}
66 
67 	surface minimap(map_width, map_height);
68 	if(minimap == nullptr)
69 		return surface(nullptr);
70 
71 	typedef mini_terrain_cache_map cache_map;
72 	cache_map *normal_cache = &mini_terrain_cache;
73 	cache_map *fog_cache = &mini_fogged_terrain_cache;
74 	cache_map *highlight_cache = &mini_highlighted_terrain_cache;
75 
76 	for(int y = 0; y <= map.total_height(); ++y)
77 		for(int x = 0; x <= map.total_width(); ++x) {
78 
79 			const map_location loc(x,y);
80 			if(!map.on_board_with_border(loc))
81 				continue;
82 
83 			const bool shrouded = (display::get_singleton() != nullptr && display::get_singleton()->is_blindfolded()) || (vw != nullptr && vw->shrouded(loc));
84 			// shrouded hex are not considered fogged (no need to fog a black image)
85 			const bool fogged = (vw != nullptr && !shrouded && vw->fogged(loc));
86 
87 			const bool highlighted = reach_map && reach_map->count(loc) != 0 && !shrouded;
88 
89 			const t_translation::terrain_code terrain = shrouded ?
90 					t_translation::VOID_TERRAIN : map[loc];
91 			const terrain_type& terrain_info = tdata.get_terrain_info(terrain);
92 
93 			// we need a balanced shift up and down of the hexes.
94 			// if not, only the bottom half-hexes are clipped
95 			// and it looks asymmetrical.
96 
97 			SDL_Rect maprect {
98 					x * scale * 3 / 4 - (scale / 4)
99 					, y * scale + scale / 4 * (is_odd(x) ? 1 : -1) - (scale / 4)
100 					, 0
101 					, 0
102 			};
103 
104 			if (preferences_minimap_draw_terrain) {
105 
106 				if (preferences_minimap_terrain_coding) {
107 
108 					surface surf(nullptr);
109 
110 					bool need_fogging = false;
111 					bool need_highlighting = false;
112 
113 					cache_map* cache = fogged ? fog_cache : normal_cache;
114 					if (highlighted)
115 						cache = highlight_cache;
116 					cache_map::iterator i = cache->find(terrain);
117 
118 					if (fogged && i == cache->end()) {
119 						// we don't have the fogged version in cache
120 						// try the normal cache and ask fogging the image
121 						cache = normal_cache;
122 						i = cache->find(terrain);
123 						need_fogging = true;
124 					}
125 
126 					if (highlighted && i == cache->end()) {
127 						// we don't have the highlighted version in cache
128 						// try the normal cache and ask fogging the image
129 						cache = normal_cache;
130 						i = cache->find(terrain);
131 						need_highlighting = true;
132 					}
133 
134 					if(i == cache->end() && !terrain_info.minimap_image().empty()) {
135 						std::string base_file =
136 								"terrain/" + terrain_info.minimap_image() + ".png";
137 						surface tile = get_image(base_file,image::HEXED);
138 
139 						//Compose images of base and overlay if necessary
140 						// NOTE we also skip overlay when base is missing (to avoid hiding the error)
141 						if(tile != nullptr && tdata.get_terrain_info(terrain).is_combined() && !terrain_info.minimap_image_overlay().empty()) {
142 							std::string overlay_file =
143 									"terrain/" + terrain_info.minimap_image_overlay() + ".png";
144 							surface overlay = get_image(overlay_file,image::HEXED);
145 
146 							if(overlay != nullptr && overlay != tile) {
147 								surface combined(tile->w, tile->h);
148 								SDL_Rect r {0,0,0,0};
149 								sdl_blit(tile, nullptr, combined, &r);
150 								r.x = std::max(0, (tile->w - overlay->w)/2);
151 								r.y = std::max(0, (tile->h - overlay->h)/2);
152 								sdl_blit(overlay, nullptr, combined, &r);
153 								tile = combined;
154 							}
155 						}
156 
157 						surf = scale_surface_sharp(tile, scale, scale);
158 
159 						i = normal_cache->emplace(terrain, surf).first;
160 					}
161 
162 					if (i != cache->end())
163 					{
164 						surf = i->second;
165 
166 						if (need_fogging) {
167 							surf = adjust_surface_color(surf, -50, -50, -50);
168 							fog_cache->emplace(terrain, surf);
169 						}
170 
171 						if (need_highlighting) {
172 							surf = adjust_surface_color(surf, 50, 50, 50);
173 							highlight_cache->emplace(terrain, surf);
174 						}
175 					}
176 
177 					if(surf != nullptr)
178 						sdl_blit(surf, nullptr, minimap, &maprect);
179 
180 				} else {
181 
182 					color_t col;
183 					std::map<std::string, color_range>::const_iterator it = game_config::team_rgb_range.find(terrain_info.id());
184 					if (it == game_config::team_rgb_range.end()) {
185 						col = color_t(0,0,0,0);
186 					} else
187 						col = it->second.rep();
188 
189 					bool first = true;
190 					const t_translation::ter_list& underlying_terrains = tdata.underlying_union_terrain(terrain);
191 					for(const t_translation::terrain_code& underlying_terrain : underlying_terrains) {
192 
193 						const std::string& terrain_id = tdata.get_terrain_info(underlying_terrain).id();
194 						it = game_config::team_rgb_range.find(terrain_id);
195 						if (it == game_config::team_rgb_range.end())
196 							continue;
197 
198 						color_t tmp = it->second.rep();
199 
200 						if (fogged) {
201 							if (tmp.b < 50) tmp.b = 0;
202 							else tmp.b -= 50;
203 							if (tmp.g < 50) tmp.g = 0;
204 							else tmp.g -= 50;
205 							if (tmp.r < 50) tmp.r = 0;
206 							else tmp.r -= 50;
207 						}
208 
209 						if (highlighted) {
210 							if (tmp.b > 205) tmp.b = 255;
211 							else tmp.b += 50;
212 							if (tmp.g > 205) tmp.g = 255;
213 							else tmp.g += 50;
214 							if (tmp.r > 205) tmp.r = 255;
215 							else tmp.r += 50;
216 						}
217 
218 						if (first) {
219 							first = false;
220 							col = tmp;
221 						} else {
222 							col.r = col.r - (col.r - tmp.r)/2;
223 							col.g = col.g - (col.g - tmp.g)/2;
224 							col.b = col.b - (col.b - tmp.b)/2;
225 						}
226 					}
227 					SDL_Rect fillrect {maprect.x, maprect.y, scale * 3/4, scale};
228 					const uint32_t mapped_col = SDL_MapRGB(minimap->format,col.r,col.g,col.b);
229 					sdl::fill_surface_rect(minimap, &fillrect, mapped_col);
230 				}
231 			}
232 
233 			if (terrain_info.is_village() && preferences_minimap_draw_villages) {
234 
235 				int side = (resources::gameboard ? resources::gameboard->village_owner(loc) : -1); //check needed for mp create dialog
236 
237 				// TODO: Add a key to [game_config][colors] for this
238 				auto iter = game_config::team_rgb_range.find("white");
239 				color_t col(255,255,255);
240 				if(iter != game_config::team_rgb_range.end()) {
241 					col = iter->second.min();
242 				}
243 
244 				if (!fogged) {
245 					if (side > -1) {
246 
247 						if (preferences_minimap_unit_coding || !vw ) {
248 							col = team::get_minimap_color(side + 1);
249 						} else {
250 
251 							if (vw->owns_village(loc))
252 								col = game_config::color_info(preferences::unmoved_color()).rep();
253 							else if (vw->is_enemy(side + 1))
254 								col = game_config::color_info(preferences::enemy_color()).rep();
255 							else
256 								col = game_config::color_info(preferences::allied_color()).rep();
257 						}
258 					}
259 				}
260 
261 				SDL_Rect fillrect {
262 						maprect.x
263 						, maprect.y
264 						, scale * 3/4
265 						, scale
266 				};
267 
268 				const uint32_t mapped_col = SDL_MapRGB(minimap->format,col.r,col.g,col.b);
269 				sdl::fill_surface_rect(minimap, &fillrect, mapped_col);
270 
271 			}
272 
273 		}
274 
275 	double wratio = w*1.0 / minimap->w;
276 	double hratio = h*1.0 / minimap->h;
277 	double ratio = std::min<double>(wratio, hratio);
278 
279 	minimap = scale_surface_sharp(minimap,
280 		static_cast<int>(minimap->w * ratio), static_cast<int>(minimap->h * ratio));
281 
282 	DBG_DP << "done generating minimap\n";
283 
284 	return minimap;
285 }
286 
287 
288 }
289