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 map_draw.cpp - The map drawing. */
12 //
13 // (c) Copyright 1999-2005 by Lutz Sammer and Jimmy Salmon
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 #include "stratagus.h"
33
34 #include "viewport.h"
35
36 #include "font.h"
37 #include "fow.h"
38 #include "map.h"
39 #include "missile.h"
40 #include "particle.h"
41 #include "pathfinder.h"
42 #include "player.h"
43 #include "unit.h"
44 #include "unittype.h"
45 #include "ui.h"
46 #include "video.h"
47
48
49 bool CViewport::ShowGrid = false;
50
51
CViewport()52 CViewport::CViewport() : MapWidth(0), MapHeight(0), Unit(NULL)
53 {
54 this->TopLeftPos.x = this->TopLeftPos.y = 0;
55 this->BottomRightPos.x = this->BottomRightPos.y = 0;
56 this->MapPos.x = this->MapPos.y = 0;
57 this->Offset.x = this->Offset.y = 0;
58 }
59
~CViewport()60 CViewport::~CViewport()
61 {
62 this->Clean();
63 }
64
Contains(const PixelPos & screenPos) const65 bool CViewport::Contains(const PixelPos &screenPos) const
66 {
67 return this->GetTopLeftPos().x <= screenPos.x && screenPos.x <= this->GetBottomRightPos().x
68 && this->GetTopLeftPos().y <= screenPos.y && screenPos.y <= this->GetBottomRightPos().y;
69 }
70
71
Restrict(int & screenPosX,int & screenPosY) const72 void CViewport::Restrict(int &screenPosX, int &screenPosY) const
73 {
74 clamp(&screenPosX, this->GetTopLeftPos().x, this->GetBottomRightPos().x - 1);
75 clamp(&screenPosY, this->GetTopLeftPos().y, this->GetBottomRightPos().y - 1);
76 }
77
GetPixelSize() const78 PixelSize CViewport::GetPixelSize() const
79 {
80 return this->BottomRightPos - this->TopLeftPos;
81 }
82
SetClipping() const83 void CViewport::SetClipping() const
84 {
85 ::SetClipping(this->TopLeftPos.x, this->TopLeftPos.y, this->BottomRightPos.x, this->BottomRightPos.y);
86 }
87
88 /**
89 ** Check if any part of an area is visible in a viewport.
90 **
91 ** @param boxmin map tile position of area in map to be checked.
92 ** @param boxmax map tile position of area in map to be checked.
93 **
94 ** @return True if any part of area is visible, false otherwise
95 */
AnyMapAreaVisibleInViewport(const Vec2i & boxmin,const Vec2i & boxmax) const96 bool CViewport::AnyMapAreaVisibleInViewport(const Vec2i &boxmin, const Vec2i &boxmax) const
97 {
98 Assert(boxmin.x <= boxmax.x && boxmin.y <= boxmax.y);
99
100 if (boxmax.x < this->MapPos.x
101 || boxmax.y < this->MapPos.y
102 || boxmin.x >= this->MapPos.x + this->MapWidth
103 || boxmin.y >= this->MapPos.y + this->MapHeight) {
104 return false;
105 }
106 return true;
107 }
108
IsInsideMapArea(const PixelPos & screenPixelPos) const109 bool CViewport::IsInsideMapArea(const PixelPos &screenPixelPos) const
110 {
111 const Vec2i tilePos = ScreenToTilePos(screenPixelPos);
112
113 return Map.Info.IsPointOnMap(tilePos);
114 }
115
116 // Convert viewport coordinates into map pixel coordinates
ScreenToMapPixelPos(const PixelPos & screenPixelPos) const117 PixelPos CViewport::ScreenToMapPixelPos(const PixelPos &screenPixelPos) const
118 {
119 const PixelDiff relPos = screenPixelPos - this->TopLeftPos + this->Offset;
120 const PixelPos mapPixelPos = relPos + Map.TilePosToMapPixelPos_TopLeft(this->MapPos);
121
122 return mapPixelPos;
123 }
124
125 // Convert map pixel coordinates into viewport coordinates
MapToScreenPixelPos(const PixelPos & mapPixelPos) const126 PixelPos CViewport::MapToScreenPixelPos(const PixelPos &mapPixelPos) const
127 {
128 const PixelDiff relPos = mapPixelPos - Map.TilePosToMapPixelPos_TopLeft(this->MapPos);
129
130 return this->TopLeftPos + relPos - this->Offset;
131 }
132
133 /// convert screen coordinate into tilepos
ScreenToTilePos(const PixelPos & screenPixelPos) const134 Vec2i CViewport::ScreenToTilePos(const PixelPos &screenPixelPos) const
135 {
136 const PixelPos mapPixelPos = ScreenToMapPixelPos(screenPixelPos);
137 const Vec2i tilePos = Map.MapPixelPosToTilePos(mapPixelPos);
138
139 return tilePos;
140 }
141
142 /// convert tilepos coordonates into screen (take the top left of the tile)
TilePosToScreen_TopLeft(const Vec2i & tilePos) const143 PixelPos CViewport::TilePosToScreen_TopLeft(const Vec2i &tilePos) const
144 {
145 const PixelPos mapPos = Map.TilePosToMapPixelPos_TopLeft(tilePos);
146
147 return MapToScreenPixelPos(mapPos);
148 }
149
150 /// convert tilepos coordonates into screen (take the center of the tile)
TilePosToScreen_Center(const Vec2i & tilePos) const151 PixelPos CViewport::TilePosToScreen_Center(const Vec2i &tilePos) const
152 {
153 const PixelPos topLeft = TilePosToScreen_TopLeft(tilePos);
154
155 return topLeft + PixelTileSize / 2;
156 }
157
158 /**
159 ** Change viewpoint of map viewport v to tilePos.
160 **
161 ** @param tilePos map tile position.
162 ** @param offset offset in tile.
163 */
Set(const PixelPos & mapPos)164 void CViewport::Set(const PixelPos &mapPos)
165 {
166 int x = mapPos.x;
167 int y = mapPos.y;
168
169 x = std::max(x, -UI.MapArea.ScrollPaddingLeft);
170 y = std::max(y, -UI.MapArea.ScrollPaddingTop);
171
172 const PixelSize pixelSize = this->GetPixelSize();
173 x = std::min(x, Map.Info.MapWidth * PixelTileSize.x - (pixelSize.x) - 1 + UI.MapArea.ScrollPaddingRight);
174 y = std::min(y, Map.Info.MapHeight * PixelTileSize.y - (pixelSize.y) - 1 + UI.MapArea.ScrollPaddingBottom);
175
176 this->MapPos.x = x / PixelTileSize.x;
177 if (x < 0 && x % PixelTileSize.x) {
178 this->MapPos.x--;
179 }
180 this->MapPos.y = y / PixelTileSize.y;
181 if (y < 0 && y % PixelTileSize.y) {
182 this->MapPos.y--;
183 }
184 this->Offset.x = x % PixelTileSize.x;
185 if (this->Offset.x < 0) {
186 this->Offset.x += PixelTileSize.x;
187 }
188 this->Offset.y = y % PixelTileSize.y;
189 if (this->Offset.y < 0) {
190 this->Offset.y += PixelTileSize.y;
191 }
192 this->MapWidth = (pixelSize.x + this->Offset.x - 1) / PixelTileSize.x + 1;
193 this->MapHeight = (pixelSize.y + this->Offset.y - 1) / PixelTileSize.y + 1;
194 }
195
196 /**
197 ** Change viewpoint of map viewport v to tilePos.
198 **
199 ** @param tilePos map tile position.
200 ** @param offset offset in tile.
201 */
Set(const Vec2i & tilePos,const PixelDiff & offset)202 void CViewport::Set(const Vec2i &tilePos, const PixelDiff &offset)
203 {
204 const PixelPos mapPixelPos = Map.TilePosToMapPixelPos_TopLeft(tilePos) + offset;
205
206 this->Set(mapPixelPos);
207 }
208
209 /**
210 ** Center map viewport v on map tile (pos).
211 **
212 ** @param mapPixelPos map pixel position.
213 */
Center(const PixelPos & mapPixelPos)214 void CViewport::Center(const PixelPos &mapPixelPos)
215 {
216 this->Set(mapPixelPos - this->GetPixelSize() / 2);
217 }
218
219 /**
220 ** Draw the map backgrounds.
221 **
222 ** StephanR: variables explained below for screen:<PRE>
223 ** *---------------------------------------*
224 ** | |
225 ** | *-----------------------* |<-TheUi.MapY,dy (in pixels)
226 ** | | | | | | | | | |
227 ** | | | | | | | | | |
228 ** | |---+---+---+---+---+---| | |
229 ** | | | | | | | | | |MapHeight (in tiles)
230 ** | | | | | | | | | |
231 ** | |---+---+---+---+---+---| | |
232 ** | | | | | | | | | |
233 ** | | | | | | | | | |
234 ** | *-----------------------* |<-ey,UI.MapEndY (in pixels)
235 ** | |
236 ** | |
237 ** *---------------------------------------*
238 ** ^ ^
239 ** dx|-----------------------|ex,UI.MapEndX (in pixels)
240 ** UI.MapX MapWidth (in tiles)
241 ** (in pixels)
242 ** </PRE>
243 */
244 /// Draw the map grid for dubug purposes
DrawMapGridInViewport() const245 void CViewport::DrawMapGridInViewport() const
246 {
247 int x0 = this->TopLeftPos.x - this->Offset.x;
248 int y0 = this->TopLeftPos.y - this->Offset.y;
249
250 for (int x = ((x0 % PixelTileSize.x != 0) ? x0 + PixelTileSize.x : x0) ; x <= this->BottomRightPos.x ; x += PixelTileSize.x) {
251 Video.DrawLineClip(ColorDarkGray, {x, this->TopLeftPos.y - this->Offset.y}, {x, this->BottomRightPos.y});
252 }
253 for(int y = ((y0 % PixelTileSize.y != 0) ? y0 + PixelTileSize.y : y0) ; y <= this->BottomRightPos.y ; y += PixelTileSize.y) {
254 Video.DrawLineClip(ColorDarkGray, {this->TopLeftPos.x - this->Offset.x, y}, {this->BottomRightPos.x, y});
255 }
256 }
257
DrawMapBackgroundInViewport() const258 void CViewport::DrawMapBackgroundInViewport() const
259 {
260 int ex = this->BottomRightPos.x;
261 int ey = this->BottomRightPos.y;
262 int sy = this->MapPos.y;
263 int dy = this->TopLeftPos.y - this->Offset.y;
264 const int map_max = Map.Info.MapWidth * Map.Info.MapHeight;
265
266 while (sy < 0) {
267 sy++;
268 dy += PixelTileSize.y;
269 }
270 sy *= Map.Info.MapWidth;
271
272 while (dy <= ey && sy < map_max) {
273 int sx = this->MapPos.x + sy;
274 int dx = this->TopLeftPos.x - this->Offset.x;
275 while (dx <= ex && (sx - sy < Map.Info.MapWidth)) {
276 if (sx - sy < 0) {
277 ++sx;
278 dx += PixelTileSize.x;
279 continue;
280 }
281 const CMapField &mf = Map.Fields[sx];
282 unsigned short int tile;
283 if (ReplayRevealMap) {
284 tile = mf.getGraphicTile();
285 } else {
286 tile = mf.playerInfo.SeenTile;
287 }
288 Map.TileGraphic->DrawFrameClip(tile, dx, dy);
289 ++sx;
290 dx += PixelTileSize.x;
291 }
292 sy += Map.Info.MapWidth;
293 dy += PixelTileSize.y;
294 }
295 if (CViewport::isGridEnabled()) {
296 DrawMapGridInViewport();
297 }
298 }
299
300 /**
301 ** Show unit's name under cursor or print the message if territory is invisible.
302 **
303 ** @param pos Mouse position.
304 ** @param unit Unit to show name.
305 ** @param hidden If true, write "Unrevealed terrain"
306 **
307 */
ShowUnitName(const CViewport & vp,PixelPos pos,CUnit * unit,bool hidden=false)308 static void ShowUnitName(const CViewport &vp, PixelPos pos, CUnit *unit, bool hidden = false)
309 {
310 CFont &font = GetSmallFont();
311 int width;
312 int height = font.Height() + 6;
313 CLabel label(font, "white", "red");
314 int x;
315 int y = std::min<int>(GameCursor->G->Height + pos.y + 10, vp.BottomRightPos.y - 1 - height);
316 const CPlayer *tplayer = ThisPlayer;
317
318 if (unit && unit->IsAliveOnMap()) {
319 int backgroundColor;
320 if (unit->Player->Index == (*tplayer).Index) {
321 backgroundColor = Video.MapRGB(TheScreen->format, 0, 0, 252);
322 } else if (unit->Player->IsAllied(*tplayer)) {
323 backgroundColor = Video.MapRGB(TheScreen->format, 0, 176, 0);
324 } else if (unit->Player->IsEnemy(*tplayer)) {
325 backgroundColor = Video.MapRGB(TheScreen->format, 252, 0, 0);
326 } else {
327 backgroundColor = Video.MapRGB(TheScreen->format, 176, 176, 176);
328 }
329 width = font.getWidth(unit->Type->Name) + 10;
330 x = std::min<int>(GameCursor->G->Width + pos.x, vp.BottomRightPos.x - 1 - width);
331 Video.FillTransRectangle(backgroundColor, x, y, width, height, 128);
332 Video.DrawRectangle(ColorWhite, x, y, width, height);
333 label.DrawCentered(x + width / 2, y + 3, unit->Type->Name);
334 } else if (hidden) {
335 const std::string str("Unrevealed terrain");
336 width = font.getWidth(str) + 10;
337 x = std::min<int>(GameCursor->G->Width + pos.x, vp.BottomRightPos.x - 1 - width);
338 Video.FillTransRectangle(ColorBlue, x, y, width, height, 128);
339 Video.DrawRectangle(ColorWhite, x, y, width, height);
340 label.DrawCentered(x + width / 2, y + 3, str);
341 }
342 }
343
344 /**
345 ** Draw a map viewport.
346 */
Draw()347 void CViewport::Draw()
348 {
349 PushClipping();
350 this->SetClipping();
351
352 /* this may take while */
353 this->DrawMapBackgroundInViewport();
354
355 Missile *clickMissile = NULL;
356 CurrentViewport = this;
357 {
358 // Now we need to sort units, missiles, particles by draw level and draw them
359 std::vector<CUnit *> unittable;
360 std::vector<Missile *> missiletable;
361 std::vector<CParticle *> particletable;
362
363 FindAndSortUnits(*this, unittable);
364 const size_t nunits = unittable.size();
365 FindAndSortMissiles(*this, missiletable);
366 const size_t nmissiles = missiletable.size();
367 ParticleManager.prepareToDraw(*this, particletable);
368 const size_t nparticles = particletable.size();
369
370 size_t i = 0;
371 size_t j = 0;
372 size_t k = 0;
373
374
375 while ((i < nunits && j < nmissiles) || (i < nunits && k < nparticles)
376 || (j < nmissiles && k < nparticles)) {
377 if (i == nunits) {
378 if (missiletable[j]->Type->DrawLevel < particletable[k]->getDrawLevel()) {
379 missiletable[j]->DrawMissile(*this);
380 if (clickMissile == NULL && missiletable[j]->Type->Ident == ClickMissile) {
381 clickMissile = missiletable[j];
382 }
383 ++j;
384 } else {
385 particletable[k]->draw();
386 ++k;
387 }
388 } else if (j == nmissiles) {
389 if (unittable[i]->Type->DrawLevel < particletable[k]->getDrawLevel()) {
390 unittable[i]->Draw(*this);
391 ++i;
392 } else {
393 particletable[k]->draw();
394 ++k;
395 }
396 } else if (k == nparticles) {
397 if (unittable[i]->Type->DrawLevel < missiletable[j]->Type->DrawLevel) {
398 unittable[i]->Draw(*this);
399 ++i;
400 } else {
401 missiletable[j]->DrawMissile(*this);
402 if (clickMissile == NULL && missiletable[j]->Type->Ident == ClickMissile) {
403 clickMissile = missiletable[j];
404 }
405 ++j;
406 }
407 } else {
408 if (unittable[i]->Type->DrawLevel <= missiletable[j]->Type->DrawLevel) {
409 if (unittable[i]->Type->DrawLevel < particletable[k]->getDrawLevel()) {
410 unittable[i]->Draw(*this);
411 ++i;
412 } else {
413 particletable[k]->draw();
414 ++k;
415 }
416 } else {
417 if (missiletable[j]->Type->DrawLevel < particletable[k]->getDrawLevel()) {
418 missiletable[j]->DrawMissile(*this);
419 if (clickMissile == NULL && missiletable[j]->Type->Ident == ClickMissile) {
420 clickMissile = missiletable[j];
421 }
422 ++j;
423 } else {
424 particletable[k]->draw();
425 ++k;
426 }
427 }
428 }
429 }
430 for (; i < nunits; ++i) {
431 unittable[i]->Draw(*this);
432 }
433 for (; j < nmissiles; ++j) {
434 missiletable[j]->DrawMissile(*this);
435 if (clickMissile == NULL && missiletable[j]->Type->Ident == ClickMissile) {
436 clickMissile = missiletable[j];
437 }
438 }
439 for (; k < nparticles; ++k) {
440 particletable[k]->draw();
441 }
442 ParticleManager.endDraw();
443 }
444
445 /// Draw Fog of War
446 this->DrawMapFogOfWar();
447
448 // If there was a click missile, draw it again here above the fog
449 if (clickMissile != NULL) {
450 Vec2i pos = Map.MapPixelPosToTilePos(clickMissile->position);
451 if (Map.Field(pos.x, pos.y)->playerInfo.TeamVisibilityState(*ThisPlayer) != 2) {
452 // if this tile is not visible, we want to draw the click on top of
453 // the fog again
454 clickMissile->DrawMissile(*this);
455 }
456 }
457
458
459 //
460 // Draw orders of selected units.
461 // Drawn here so that they are shown even when the unit is out of the screen.
462 //
463 if (!Preference.ShowOrders) {
464 } else if (Preference.ShowOrders < 0
465 || (ShowOrdersCount >= GameCycle) || (KeyModifiers & ModifierShift)) {
466 for (size_t i = 0; i != Selected.size(); ++i) {
467 ShowOrder(*Selected[i]);
468 }
469 }
470
471 //
472 // Draw unit's name popup
473 //
474 if (CursorOn == CursorOnMap && Preference.ShowNameDelay && (ShowNameDelay < GameCycle) && (GameCycle < ShowNameTime)) {
475 const Vec2i tilePos = this->ScreenToTilePos(CursorScreenPos);
476 const bool isMapFieldVisile = Map.Field(tilePos)->playerInfo.IsTeamVisible(*ThisPlayer);
477
478 if (UI.MouseViewport->IsInsideMapArea(CursorScreenPos) && UnitUnderCursor
479 && ((isMapFieldVisile && !UnitUnderCursor->Type->BoolFlag[ISNOTSELECTABLE_INDEX].value) || ReplayRevealMap)) {
480 ShowUnitName(*this, CursorScreenPos, UnitUnderCursor);
481 } else if (!isMapFieldVisile) {
482 ShowUnitName(*this, CursorScreenPos, NULL, true);
483 }
484 }
485
486 DrawBorder();
487 PopClipping();
488 }
489
490 /**
491 ** Draw border around the viewport
492 */
DrawBorder() const493 void CViewport::DrawBorder() const
494 {
495 // if we a single viewport, no need to denote the "selected" one
496 if (UI.NumViewports == 1) {
497 return;
498 }
499
500 Uint32 color = ColorBlack;
501 if (this == UI.SelectedViewport) {
502 color = ColorOrange;
503 }
504
505 const PixelSize pixelSize = this->GetPixelSize();
506 Video.DrawRectangle(color, this->TopLeftPos.x, this->TopLeftPos.y, pixelSize.x + 1, pixelSize.y + 1);
507 }
508
509 //@}
510