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