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