1 //       _________ __                 __
2 //      /   _____//  |_____________ _/  |______     ____  __ __  ______
3 //      \_____  \\   __\_  __ \__  \\   __\__  \   / ___\|  |  \/  ___/
4 //      /        \|  |  |  | \// __ \|  |  / __ \_/ /_/  >  |  /\___ |
5 //     /_______  /|__|  |__|  (____  /__| (____  /\___  /|____//____  >
6 //             \/                  \/          \//_____/            \/
7 //  ______________________                           ______________________
8 //                        T H E   W A R   B E G I N S
9 //         Stratagus - A free fantasy real time strategy game engine
10 //
11 /**@name minimap.cpp - The minimap. */
12 //
13 //      (c) Copyright 1998-2011 by Lutz Sammer and Jimmy Salmon and Pali Rohár
14 //
15 //      This program is free software; you can redistribute it and/or modify
16 //      it under the terms of the GNU General Public License as published by
17 //      the Free Software Foundation; only version 2 of the License.
18 //
19 //      This program is distributed in the hope that it will be useful,
20 //      but WITHOUT ANY WARRANTY; without even the implied warranty of
21 //      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 //      GNU General Public License for more details.
23 //
24 //      You should have received a copy of the GNU General Public License
25 //      along with this program; if not, write to the Free Software
26 //      Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
27 //      02111-1307, USA.
28 //
29 
30 //@{
31 
32 /*----------------------------------------------------------------------------
33 --  Includes
34 ----------------------------------------------------------------------------*/
35 
36 #include <string.h>
37 
38 #include "stratagus.h"
39 
40 #include "minimap.h"
41 
42 #include "editor.h"
43 #include "map.h"
44 #include "player.h"
45 #include "settings.h"
46 #include "unit.h"
47 #include "unit_manager.h"
48 #include "ui.h"
49 #include "unittype.h"
50 #include "video.h"
51 
52 /*----------------------------------------------------------------------------
53 --  Defines
54 ----------------------------------------------------------------------------*/
55 
56 /// integer scale factor
57 static constexpr int MINIMAP_FAC           {16 * 3};
58 /// unit attacked are shown red for at least this amount of cycles
59 static constexpr int ATTACK_RED_DURATION   {1 * CYCLES_PER_SECOND};
60 /// unit attacked are shown blinking for this amount of cycles
61 static constexpr int ATTACK_BLINK_DURATION {7 * CYCLES_PER_SECOND};
62 
63 static constexpr int SCALE_PRECISION       {100};
64 
65 
66 
67 
68 /*----------------------------------------------------------------------------
69 --  Variables
70 ----------------------------------------------------------------------------*/
71 
72 SDL_Surface 	   *MinimapSurface{nullptr};        /// generated minimap
73 static SDL_Surface *MinimapTerrainSurface{nullptr}; /// generated minimap terrain
74 static SDL_Surface *MinimapFogSurface{nullptr};		/// generated minimap fog of war
75 
76 static int *Minimap2MapX;                  /// fast conversion table
77 static int *Minimap2MapY;                  /// fast conversion table
78 static int Map2MinimapX[MaxMapWidth];      /// fast conversion table
79 static int Map2MinimapY[MaxMapHeight];     /// fast conversion table
80 
81 // MinimapScale:
82 // 32x32 64x64 96x96 128x128 256x256 512x512 ...
83 // *4 *2 *4/3   *1 *1/2 *1/4
84 static int MinimapScaleX;                  /// Minimap scale to fit into window
85 static int MinimapScaleY;                  /// Minimap scale to fit into window
86 
87 #define MAX_MINIMAP_EVENTS 8
88 
89 struct MinimapEvent {
90 	PixelPos pos;
91 	int Size;
92 	Uint32 Color;
93 } MinimapEvents[MAX_MINIMAP_EVENTS];
94 int NumMinimapEvents;
95 
96 
97 /*----------------------------------------------------------------------------
98 -- Functions
99 ----------------------------------------------------------------------------*/
100 
101 
102 /**
103 **  Create a mini-map from the tiles of the map.
104 **
105 **  @todo Scaling and scrolling the minmap is currently not supported.
106 */
Create()107 void CMinimap::Create()
108 {
109 	// Scale to biggest value.
110 	const int n = std::max(std::max(Map.Info.MapWidth, Map.Info.MapHeight), 32);
111 
112 	MinimapScaleX = (W * MINIMAP_FAC + n - 1) / n;
113 	MinimapScaleY = (H * MINIMAP_FAC + n - 1) / n;
114 
115 	XOffset = (W - (Map.Info.MapWidth * MinimapScaleX) / MINIMAP_FAC + 1) / 2;
116 	YOffset = (H - (Map.Info.MapHeight * MinimapScaleY) / MINIMAP_FAC + 1) / 2;
117 
118 	DebugPrint("MinimapScale %d %d (%d %d), X off %d, Y off %d\n" _C_
119 			   MinimapScaleX / MINIMAP_FAC _C_ MinimapScaleY / MINIMAP_FAC _C_
120 			   MinimapScaleX _C_ MinimapScaleY _C_
121 			   XOffset _C_ YOffset);
122 
123 	//
124 	// Calculate minimap fast lookup tables.
125 	//
126 	Minimap2MapX = new int[W * H];
127 	memset(Minimap2MapX, 0, W * H * sizeof(int));
128 	Minimap2MapY = new int[W * H];
129 	memset(Minimap2MapY, 0, W * H * sizeof(int));
130 	for (int i = XOffset; i < W - XOffset; ++i) {
131 		Minimap2MapX[i] = ((i - XOffset) * MINIMAP_FAC) / MinimapScaleX;
132 	}
133 	for (int i = YOffset; i < H - YOffset; ++i) {
134 		Minimap2MapY[i] = (((i - YOffset) * MINIMAP_FAC) / MinimapScaleY) * Map.Info.MapWidth;
135 	}
136 	for (int i = 0; i < Map.Info.MapWidth; ++i) {
137 		Map2MinimapX[i] = (i * MinimapScaleX) / MINIMAP_FAC;
138 	}
139 	for (int i = 0; i < Map.Info.MapHeight; ++i) {
140 		Map2MinimapY[i] = (i * MinimapScaleY) / MINIMAP_FAC;
141 	}
142 
143 	// Palette updated from UpdateMinimapTerrain()
144 	SDL_PixelFormat *f 	  = Map.TileGraphic->Surface->format;
145 	MinimapTerrainSurface = SDL_CreateRGBSurface(SDL_SWSURFACE, W, H, f->BitsPerPixel, f->Rmask, f->Gmask, f->Bmask, f->Amask);
146 	MinimapSurface 		  = SDL_CreateRGBSurface(SDL_SWSURFACE, W, H, 32, RMASK, GMASK, BMASK, 0);
147 	MinimapFogSurface 	  = SDL_CreateRGBSurface(SDL_SWSURFACE, W, H, 32, RMASK, GMASK, BMASK, AMASK);
148 
149     SDL_SetSurfaceBlendMode(MinimapFogSurface, SDL_BLENDMODE_BLEND);
150 
151 	const uint32_t fogColorSolid = FogOfWar.GetFogColorSDL() | (uint32_t(0xFF) << ASHIFT);
152 	SDL_FillRect(MinimapFogSurface, NULL, fogColorSolid);
153 
154 	UpdateTerrain();
155 
156 	NumMinimapEvents = 0;
157 }
158 
159 /**
160 **  Calculate the tile graphic pixel
161 */
GetTileGraphicPixel(int xofs,int yofs,int mx,int my,int scalex,int scaley,int bpp)162 static inline Uint8 *GetTileGraphicPixel(int xofs, int yofs, int mx, int my, int scalex, int scaley, int bpp)
163 {
164 	Uint8 *pixels = (Uint8 *)Map.TileGraphic->Surface->pixels;
165 	int x = (xofs + 7 + ((mx * SCALE_PRECISION) % scalex) / SCALE_PRECISION * 8);
166 	int y = (yofs + 6 + ((my * SCALE_PRECISION) % scaley) / SCALE_PRECISION * 8);
167 	return &pixels[x * bpp + y * Map.TileGraphic->Surface->pitch];
168 }
169 
170 /**
171 **  Update a mini-map from the tiles of the map.
172 */
UpdateTerrain()173 void CMinimap::UpdateTerrain()
174 {
175 	int scalex = MinimapScaleX * SCALE_PRECISION / MINIMAP_FAC;
176 	if (!scalex) {
177 		scalex = 1;
178 	}
179 	int scaley = MinimapScaleY * SCALE_PRECISION / MINIMAP_FAC;
180 	if (!scaley) {
181 		scaley = 1;
182 	}
183 	const int bpp = Map.TileGraphic->Surface->format->BytesPerPixel;
184 
185 	if (bpp == 1) {
186 		SDL_SetPaletteColors(MinimapTerrainSurface->format->palette,
187 							 Map.TileGraphic->Surface->format->palette->colors, 0, 256);
188 	}
189 
190 	const int tilepitch = Map.TileGraphic->Surface->w / PixelTileSize.x;
191 
192 	Assert(SDL_MUSTLOCK(MinimapTerrainSurface) == 0);
193 	Assert(SDL_MUSTLOCK(Map.TileGraphic->Surface) == 0);
194 
195 	//
196 	//  Pixel 7,6 7,14, 15,6 15,14 are taken for the minimap picture.
197 	//
198 	for (int my = YOffset; my < H - YOffset; ++my) {
199 		for (int mx = XOffset; mx < W - XOffset; ++mx) {
200 			const int tile = Map.Fields[Minimap2MapX[mx] + Minimap2MapY[my]].getGraphicTile();
201 			const int xofs = PixelTileSize.x * (tile % tilepitch);
202 			const int yofs = PixelTileSize.y * (tile / tilepitch);
203 
204 			if (bpp == 1) {
205 				((Uint8 *)MinimapTerrainSurface->pixels)[mx + my * MinimapTerrainSurface->pitch] =
206 					*GetTileGraphicPixel(xofs, yofs, mx, my, scalex, scaley, bpp);
207 			} else if (bpp == 3) {
208 				Uint8 *d = &((Uint8 *)MinimapTerrainSurface->pixels)[mx * bpp + my * MinimapTerrainSurface->pitch];
209 				Uint8 *s = GetTileGraphicPixel(xofs, yofs, mx, my, scalex, scaley, bpp);
210 				*d++ = *s++;
211 				*d++ = *s++;
212 				*d++ = *s++;
213 			} else {
214 				*(Uint32 *)&((Uint8 *)MinimapTerrainSurface->pixels)[mx * bpp + my * MinimapTerrainSurface->pitch] =
215 					*(Uint32 *)GetTileGraphicPixel(xofs, yofs, mx, my, scalex, scaley, bpp);
216 			}
217 
218 		}
219 	}
220 }
221 
222 
223 /**
224 ** Set fog of war opacity (alpha chanel values) for different levels of visibility
225 **
226 ** @param explored  alpha channel value for explored tiles
227 ** @param revealed  alpha channel value for revealed tiles (when the map revealed)
228 ** @param unseen    alpha channel value for unseen tiles
229 **
230 */
SetFogOpacityLevels(const uint8_t explored,const uint8_t revealed,const uint8_t unseen)231 void CMinimap::SetFogOpacityLevels(const uint8_t explored, const uint8_t revealed, const uint8_t unseen)
232 {
233     this->Settings.FogExploredOpacity = explored;
234     this->Settings.FogRevealedOpacity = revealed;
235     this->Settings.FogUnseenOpacity   = unseen;
236 }
237 
238 /**
239 **  Update a single minimap tile after a change
240 **
241 **  @param pos  The map position to update in the minimap
242 */
UpdateXY(const Vec2i & pos)243 void CMinimap::UpdateXY(const Vec2i &pos)
244 {
245 	if (!MinimapTerrainSurface) {
246 		return;
247 	}
248 
249 	int scalex = MinimapScaleX * SCALE_PRECISION / MINIMAP_FAC;
250 	if (scalex == 0) {
251 		scalex = 1;
252 	}
253 	int scaley = MinimapScaleY * SCALE_PRECISION / MINIMAP_FAC;
254 	if (scaley == 0) {
255 		scaley = 1;
256 	}
257 
258 	const int tilepitch = Map.TileGraphic->Surface->w / PixelTileSize.x;
259 	const int bpp = Map.TileGraphic->Surface->format->BytesPerPixel;
260 
261 	//
262 	//  Pixel 7,6 7,14, 15,6 15,14 are taken for the minimap picture.
263 	//
264 
265 	const int ty = pos.y * Map.Info.MapWidth;
266 	const int tx = pos.x;
267 	for (int my = YOffset; my < H - YOffset; ++my) {
268 		const int y = Minimap2MapY[my];
269 		if (y < ty) {
270 			continue;
271 		}
272 		if (y > ty) {
273 			break;
274 		}
275 
276 		for (int mx = XOffset; mx < W - XOffset; ++mx) {
277 			const int x = Minimap2MapX[mx];
278 
279 			if (x < tx) {
280 				continue;
281 			}
282 			if (x > tx) {
283 				break;
284 			}
285 
286 			int tile = Map.Fields[x + y].playerInfo.SeenTile;
287 			if (!tile) {
288 				tile = Map.Fields[x + y].getGraphicTile();
289 			}
290 
291 			const int xofs = PixelTileSize.x * (tile % tilepitch);
292 			const int yofs = PixelTileSize.y * (tile / tilepitch);
293 
294 			const int index = mx * bpp + my * MinimapTerrainSurface->pitch;
295 			Uint8 *s = GetTileGraphicPixel(xofs, yofs, mx, my, scalex, scaley, bpp);
296 			if (bpp == 1) {
297 				((Uint8 *)MinimapTerrainSurface->pixels)[index] = *s;
298 			} else if (bpp == 3) {
299 				Uint8 *d = &((Uint8 *)MinimapTerrainSurface->pixels)[index];
300 
301 				*d++ = *s++;
302 				*d++ = *s++;
303 				*d++ = *s++;
304 			} else {
305 				*(Uint32 *)&((Uint8 *)MinimapTerrainSurface->pixels)[index] = *(Uint32 *)s;
306 			}
307 		}
308 	}
309 }
310 
311 /**
312 **  Draw a unit on the minimap.
313 */
DrawUnitOn(CUnit & unit,int red_phase)314 static void DrawUnitOn(CUnit &unit, int red_phase)
315 {
316 	const CUnitType *type;
317 
318 	if (Editor.Running || ReplayRevealMap || unit.IsVisible(*ThisPlayer) || unit.Player->IsRevealed()) {
319 		type = unit.Type;
320 	} else {
321 		type = unit.Seen.Type;
322 		// This will happen for radar if the unit has not been seen and we
323 		// have it on radar.
324 		if (!type) {
325 			type = unit.Type;
326 		}
327 	}
328 
329 	Uint32 color;
330 	if (unit.Player->Index == PlayerNumNeutral) {
331 		color = Video.MapRGB(TheScreen->format, type->NeutralMinimapColorRGB);
332 	} else if (unit.Player == ThisPlayer && !Editor.Running) {
333 		if (unit.Attacked && unit.Attacked + ATTACK_BLINK_DURATION > GameCycle &&
334 			(red_phase || unit.Attacked + ATTACK_RED_DURATION > GameCycle)) {
335 			color = ColorRed;
336 		} else if (UI.Minimap.ShowSelected && unit.Selected) {
337 			color = ColorWhite;
338 		} else {
339 			color = ColorGreen;
340 		}
341 	} else {
342 		color = PlayerColors[GameSettings.Presets[unit.Player->Index].PlayerColor][0];
343 	}
344 
345 	int mx = 1 + UI.Minimap.XOffset + Map2MinimapX[unit.tilePos.x];
346 	int my = 1 + UI.Minimap.YOffset + Map2MinimapY[unit.tilePos.y];
347 	int w = Map2MinimapX[type->TileWidth];
348 	if (mx + w >= UI.Minimap.W) { // clip right side
349 		w = UI.Minimap.W - mx;
350 	}
351 	int h0 = Map2MinimapY[type->TileHeight];
352 	if (my + h0 >= UI.Minimap.H) { // clip bottom side
353 		h0 = UI.Minimap.H - my;
354 	}
355 	int bpp = 0;
356 	SDL_Color c;
357 	bpp = MinimapSurface->format->BytesPerPixel;
358 	SDL_GetRGB(color, TheScreen->format, &c.r, &c.g, &c.b);
359 	while (w-- >= 0) {
360 		int h = h0;
361 		while (h-- >= 0) {
362 			const unsigned int index = (mx + w) * bpp + (my + h) * MinimapSurface->pitch;
363 			if (bpp == 2) {
364 				*(Uint16 *)&((Uint8 *)MinimapSurface->pixels)[index] = color;
365 			} else {
366 				*(Uint32 *)&((Uint8 *)MinimapSurface->pixels)[index] = color;
367 			}
368 		}
369 	}
370 }
371 
372 /**
373 **  Update the minimap with the current game information
374 */
Update()375 void CMinimap::Update()
376 {
377 	static int red_phase;
378 
379 	int red_phase_changed = red_phase != (int)((FrameCounter / FRAMES_PER_SECOND) & 1);
380 	if (red_phase_changed) {
381 		red_phase = !red_phase;
382 	}
383 
384 	// Clear Minimap background if not transparent
385 	if (!Transparent) {
386 		SDL_FillRect(MinimapSurface, NULL, SDL_MapRGB(MinimapSurface->format, 0, 0, 0));
387 	}
388 
389 	//
390 	// Draw the terrain
391 	//
392 	if (WithTerrain) {
393 		SDL_BlitSurface(MinimapTerrainSurface, NULL, MinimapSurface, NULL);
394 	}
395 	const uint32_t fogColorSDL = FogOfWar.GetFogColorSDL();
396 	if (!ReplayRevealMap) {
397 		uint32_t *const minimapFog = static_cast<uint32_t *>(MinimapFogSurface->pixels);
398 		size_t index = 0;
399 		for (uint16_t my = 0; my < H; ++my) {
400 			for (uint16_t mx = 0; mx < W; ++mx) {
401 
402 				const Vec2i tilePos(Minimap2MapX[mx], Minimap2MapY[my] / Map.Info.MapWidth);
403 				const uint8_t vis = FogOfWar.GetVisibilityForTile(tilePos);
404 
405 				const uint32_t fogAlpha = vis == 0 ? (GameSettings.RevealMap ? Settings.FogRevealedOpacity : Settings.FogUnseenOpacity)
406 											   	   : vis == 1 ? Settings.FogExploredOpacity
407 										   		   			  : Settings.FogVisibleOpacity;
408 
409 				minimapFog[index++] = fogColorSDL | (fogAlpha << ASHIFT);
410 			}
411 		}
412 		/// Alpha blending the fog of war texture to minimap
413 		/// TODO: switch to hardware rendering
414 		const SDL_Rect fogRect {0, 0, W, H};
415 		BlitSurfaceAlphaBlending_32bpp(MinimapFogSurface, &fogRect, MinimapSurface, &fogRect);
416 	}
417 	//
418 	// Draw units on map
419 	//
420 	for (CUnitManager::Iterator it = UnitManager.begin(); it != UnitManager.end(); ++it) {
421 		CUnit &unit = **it;
422 		if (unit.IsVisibleOnMinimap() && !unit.Removed && !unit.Type->BoolFlag[REVEALER_INDEX].value) {
423 			DrawUnitOn(unit, red_phase);
424 		}
425 	}
426 }
427 
428 /**
429 **  Draw the minimap events
430 */
DrawEvents()431 static void DrawEvents()
432 {
433 	const unsigned char alpha = 192;
434 
435 	for (int i = 0; i < NumMinimapEvents; ++i) {
436 		Video.DrawTransCircleClip(MinimapEvents[i].Color,
437 								  MinimapEvents[i].pos.x, MinimapEvents[i].pos.y,
438 								  MinimapEvents[i].Size, alpha);
439 		MinimapEvents[i].Size -= 1;
440 		if (MinimapEvents[i].Size < 2) {
441 			MinimapEvents[i] = MinimapEvents[--NumMinimapEvents];
442 			--i;
443 		}
444 	}
445 }
446 
447 /**
448 **  Draw the minimap on the screen
449 */
Draw() const450 void CMinimap::Draw() const
451 {
452 	SDL_Rect drect = {Sint16(X), Sint16(Y), 0, 0};
453 	SDL_BlitSurface(MinimapSurface, NULL, TheScreen, &drect);
454 
455 	DrawEvents();
456 }
457 
458 /**
459 **  Convert screen position to tile map coordinate.
460 **
461 **  @param screenPos  Screen pixel coordinate.
462 **
463 **  @return   Tile coordinate.
464 */
ScreenToTilePos(const PixelPos & screenPos) const465 Vec2i CMinimap::ScreenToTilePos(const PixelPos &screenPos) const
466 {
467 	Vec2i tilePos((((screenPos.x - X - XOffset) * MINIMAP_FAC) / MinimapScaleX),
468 				  (((screenPos.y - Y - YOffset) * MINIMAP_FAC) / MinimapScaleY));
469 
470 	Map.Clamp(tilePos);
471 	return tilePos;
472 }
473 
474 /**
475 **  Convert tile map coordinate to screen position.
476 **
477 **  @param tilePos  Tile coordinate.
478 **
479 **  @return   Screen pixel coordinate.
480 */
TilePosToScreenPos(const Vec2i & tilePos) const481 PixelPos CMinimap::TilePosToScreenPos(const Vec2i &tilePos) const
482 {
483 	const PixelPos screenPos(X + XOffset + (tilePos.x * MinimapScaleX) / MINIMAP_FAC,
484 							 Y + YOffset + (tilePos.y * MinimapScaleY) / MINIMAP_FAC);
485 	return screenPos;
486 }
487 
488 /**
489 **  Destroy mini-map.
490 */
Destroy()491 void CMinimap::Destroy()
492 {
493 	VideoPaletteListRemove(MinimapTerrainSurface);
494 	SDL_FreeSurface(MinimapTerrainSurface);
495 	MinimapTerrainSurface = NULL;
496 	if (MinimapSurface) {
497 		VideoPaletteListRemove(MinimapSurface);
498 		SDL_FreeSurface(MinimapSurface);
499 		MinimapSurface = NULL;
500 	}
501 	if (MinimapFogSurface) {
502 		SDL_FreeSurface(MinimapFogSurface);
503 		MinimapSurface = NULL;
504 	}
505 	delete[] Minimap2MapX;
506 	Minimap2MapX = NULL;
507 	delete[] Minimap2MapY;
508 	Minimap2MapY = NULL;
509 }
510 
511 /**
512 **  Draw viewport area contour.
513 */
DrawViewportArea(const CViewport & viewport) const514 void CMinimap::DrawViewportArea(const CViewport &viewport) const
515 {
516 	// Determine and save region below minimap cursor
517 	const PixelPos screenPos = TilePosToScreenPos(viewport.MapPos);
518 	int w = (viewport.MapWidth * MinimapScaleX) / MINIMAP_FAC;
519 	int h = (viewport.MapHeight * MinimapScaleY) / MINIMAP_FAC;
520 
521 	// Draw cursor as rectangle (Note: unclipped, as it is always visible)
522 	Video.DrawTransRectangle(UI.ViewportCursorColor, screenPos.x, screenPos.y, w, h, 128);
523 }
524 
525 /**
526 **  Add a minimap event
527 **
528 **  @param pos  Map tile position
529 */
AddEvent(const Vec2i & pos,Uint32 color)530 void CMinimap::AddEvent(const Vec2i &pos, Uint32 color)
531 {
532 	if (NumMinimapEvents == MAX_MINIMAP_EVENTS) {
533 		return;
534 	}
535 	MinimapEvents[NumMinimapEvents].pos = TilePosToScreenPos(pos);
536 	MinimapEvents[NumMinimapEvents].Size = (W < H) ? W / 3 : H / 3;
537 	MinimapEvents[NumMinimapEvents].Color = color;
538 	++NumMinimapEvents;
539 }
540 
Contains(const PixelPos & screenPos) const541 bool CMinimap::Contains(const PixelPos &screenPos) const
542 {
543 	return this->X <= screenPos.x && screenPos.x < this->X + this->W
544 		   && this->Y <= screenPos.y && screenPos.y < this->Y + this->H;
545 }
546 
547 //@}
548