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 mainscr.cpp - The main screen. */
12 //
13 //      (c) Copyright 1998-2015 by Lutz Sammer, Valery Shchedrin,
14 //      Jimmy Salmon and Andrettin
15 //
16 //      This program is free software; you can redistribute it and/or modify
17 //      it under the terms of the GNU General Public License as published by
18 //      the Free Software Foundation; only version 2 of the License.
19 //
20 //      This program is distributed in the hope that it will be useful,
21 //      but WITHOUT ANY WARRANTY; without even the implied warranty of
22 //      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23 //      GNU General Public License for more details.
24 //
25 //      You should have received a copy of the GNU General Public License
26 //      along with this program; if not, write to the Free Software
27 //      Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
28 //      02111-1307, USA.
29 //
30 
31 //@{
32 
33 /*----------------------------------------------------------------------------
34 --  Includes
35 ----------------------------------------------------------------------------*/
36 
37 #include "stratagus.h"
38 
39 #include "action/action_built.h"
40 #include "action/action_research.h"
41 #include "action/action_train.h"
42 #include "action/action_upgradeto.h"
43 #include "font.h"
44 #include "icons.h"
45 #include "interface.h"
46 #include "map.h"
47 #include "menus.h"
48 #include "network.h"
49 #include "player.h"
50 #include "settings.h"
51 #include "sound.h"
52 #include "spells.h"
53 #include "translate.h"
54 #include "trigger.h"
55 #include "ui/contenttype.h"
56 #include "ui.h"
57 #include "unit.h"
58 #include "unitsound.h"
59 #include "unittype.h"
60 #include "upgrade.h"
61 #include "video.h"
62 
63 #ifdef DEBUG
64 #include "../ai/ai_local.h"
65 #endif
66 
67 #include <sstream>
68 
69 /*----------------------------------------------------------------------------
70 --  UI BUTTONS
71 ----------------------------------------------------------------------------*/
72 
DrawMenuButtonArea_noNetwork()73 static void DrawMenuButtonArea_noNetwork()
74 {
75 	if (UI.MenuButton.X != -1) {
76 		DrawUIButton(UI.MenuButton.Style,
77 					 (ButtonAreaUnderCursor == ButtonAreaMenu
78 					  && ButtonUnderCursor == ButtonUnderMenu ? MI_FLAGS_ACTIVE : 0) |
79 					 (GameMenuButtonClicked ? MI_FLAGS_CLICKED : 0),
80 					 // (UI.MenuButton.Clicked ? MI_FLAGS_CLICKED : 0),
81 					 UI.MenuButton.X, UI.MenuButton.Y,
82 					 UI.MenuButton.Text);
83 	}
84 }
85 
DrawMenuButtonArea_Network()86 static void DrawMenuButtonArea_Network()
87 {
88 	if (UI.NetworkMenuButton.X != -1) {
89 		DrawUIButton(UI.NetworkMenuButton.Style,
90 					 (ButtonAreaUnderCursor == ButtonAreaMenu
91 					  && ButtonUnderCursor == ButtonUnderNetworkMenu ? MI_FLAGS_ACTIVE : 0) |
92 					 (GameMenuButtonClicked ? MI_FLAGS_CLICKED : 0),
93 					 // (UI.NetworkMenuButton.Clicked ? MI_FLAGS_CLICKED : 0),
94 					 UI.NetworkMenuButton.X, UI.NetworkMenuButton.Y,
95 					 UI.NetworkMenuButton.Text);
96 	}
97 	if (UI.NetworkDiplomacyButton.X != -1) {
98 		DrawUIButton(UI.NetworkDiplomacyButton.Style,
99 					 (ButtonAreaUnderCursor == ButtonAreaMenu
100 					  && ButtonUnderCursor == ButtonUnderNetworkDiplomacy ? MI_FLAGS_ACTIVE : 0) |
101 					 (GameDiplomacyButtonClicked ? MI_FLAGS_CLICKED : 0),
102 					 // (UI.NetworkDiplomacyButton.Clicked ? MI_FLAGS_CLICKED : 0),
103 					 UI.NetworkDiplomacyButton.X, UI.NetworkDiplomacyButton.Y,
104 					 UI.NetworkDiplomacyButton.Text);
105 	}
106 }
107 
108 /**
109 **  Draw menu button area.
110 */
DrawMenuButtonArea()111 void DrawMenuButtonArea()
112 {
113 	if (!IsNetworkGame()) {
114 		DrawMenuButtonArea_noNetwork();
115 	} else {
116 		DrawMenuButtonArea_Network();
117 	}
118 }
119 
DrawUserDefinedButtons()120 void DrawUserDefinedButtons()
121 {
122 	for (size_t i = 0; i < UI.UserButtons.size(); ++i) {
123 		const CUIUserButton &button = UI.UserButtons[i];
124 
125 		if (button.Button.X != -1) {
126 			DrawUIButton(button.Button.Style,
127 						 (ButtonAreaUnderCursor == ButtonAreaUser
128 						  && size_t(ButtonUnderCursor) == i ? MI_FLAGS_ACTIVE : 0) |
129 						 (button.Clicked ? MI_FLAGS_CLICKED : 0),
130 						 button.Button.X, button.Button.Y,
131 						 button.Button.Text);
132 		}
133 	}
134 }
135 
136 /*----------------------------------------------------------------------------
137 --  Icons
138 ----------------------------------------------------------------------------*/
139 
140 /**
141 **  Draw life bar of a unit at x,y.
142 **  Placed under icons on top-panel.
143 **
144 **  @param unit  Pointer to unit.
145 **  @param x     Screen X position of icon
146 **  @param y     Screen Y position of icon
147 */
UiDrawLifeBar(const CUnit & unit,int x,int y)148 static void UiDrawLifeBar(const CUnit &unit, int x, int y)
149 {
150 	// FIXME: add icon borders
151 	int hBar, hAll;
152 	if (Preference.IconsShift) {
153 		hBar = 6;
154 		hAll = 10;
155 		if (Preference.IconFrameG && Preference.PressedIconFrameG) {
156 			y -= hBar;
157 		}
158 	} else {
159 		hBar = 5;
160 		hAll = 7;
161 	}
162 	y += unit.Type->Icon.Icon->G->Height;
163 	y += UI.LifeBarYOffset;
164 	if (UI.LifeBarBorder) {
165 		Video.FillRectangleClip(ColorBlack, x - 4, y + 2,
166 								unit.Type->Icon.Icon->G->Width + 8, hAll);
167 	}
168 
169 	if (unit.Variable[HP_INDEX].Value) {
170 		Uint32 color;
171 		int f = (100 * unit.Variable[HP_INDEX].Value) / unit.Variable[HP_INDEX].Max;
172 
173 		// get to right color
174 		int i = 0;
175 		while (f < UI.LifeBarPercents[i]) {
176 			i++;
177 		}
178 		color = UI.LifeBarColorsInt[i];
179 
180 		f = (f * (unit.Type->Icon.Icon->G->Width + 6)) / 100;
181 		Video.FillRectangleClip(color, x - 2 + UI.LifeBarPadding, y + 4 + UI.LifeBarPadding,
182 			std::max(f - 2 - UI.LifeBarPadding * 2, 0), hBar - UI.LifeBarPadding);
183 	}
184 }
185 
186 /**
187 **  Draw mana bar of a unit at x,y.
188 **  Placed under icons on top-panel.
189 **
190 **  @param unit  Pointer to unit.
191 **  @param x     Screen X position of icon
192 **  @param y     Screen Y position of icon
193 */
UiDrawManaBar(const CUnit & unit,int x,int y)194 static void UiDrawManaBar(const CUnit &unit, int x, int y)
195 {
196 	// FIXME: add icon borders
197 	y += unit.Type->Icon.Icon->G->Height;
198 	Video.FillRectangleClip(ColorBlack, x, y + 3, unit.Type->Icon.Icon->G->Width, 4);
199 
200 	if (unit.Stats->Variables[MANA_INDEX].Max) {
201 		int f = (100 * unit.Variable[MANA_INDEX].Value) / unit.Variable[MANA_INDEX].Max;
202 		f = (f * (unit.Type->Icon.Icon->G->Width)) / 100;
203 		Video.FillRectangleClip(ColorBlue, x + 1, y + 3 + 1, f, 2);
204 	}
205 }
206 
207 /**
208 **  Tell if we can show the content.
209 **  verify each sub condition for that.
210 **
211 **  @param condition   condition to verify.
212 **  @param unit        unit that certain condition can refer.
213 **
214 **  @return            0 if we can't show the content, else 1.
215 */
CanShowContent(const ConditionPanel * condition,const CUnit & unit)216 static bool CanShowContent(const ConditionPanel *condition, const CUnit &unit)
217 {
218 	if (!condition) {
219 		return true;
220 	}
221 	if ((condition->ShowOnlySelected && !unit.Selected)
222 		|| (unit.Player->Type == PlayerNeutral && condition->HideNeutral)
223 		|| (ThisPlayer->IsEnemy(unit) && !condition->ShowOpponent)
224 		|| ((ThisPlayer->IsAllied(unit) || unit.Player == ThisPlayer) && condition->HideAllied)) {
225 		return false;
226 	}
227 	if (condition->BoolFlags && !unit.Type->CheckUserBoolFlags(condition->BoolFlags)) {
228 		return false;
229 	}
230 	if (condition->Variables) {
231 		for (unsigned int i = 0; i < UnitTypeVar.GetNumberVariable(); ++i) {
232 			if (condition->Variables[i] != CONDITION_TRUE) {
233 				if ((condition->Variables[i] == CONDITION_ONLY) ^ unit.Variable[i].Enable) {
234 					return false;
235 				}
236 			}
237 		}
238 	}
239 	return true;
240 }
241 
242 enum UStrIntType {
243 	USTRINT_STR, USTRINT_INT
244 };
245 struct UStrInt {
246 	union {const char *s; int i;};
247 	UStrIntType type;
248 };
249 
250 /**
251 **  Return the value corresponding.
252 **
253 **  @param unit   Unit.
254 **  @param index  Index of the variable.
255 **  @param e      Component of the variable.
256 **  @param t      Which var use (0:unit, 1:Type, 2:Stats)
257 **
258 **  @return       Value corresponding
259 */
GetComponent(const CUnit & unit,int index,EnumVariable e,int t)260 UStrInt GetComponent(const CUnit &unit, int index, EnumVariable e, int t)
261 {
262 	UStrInt val;
263 	CVariable *var;
264 
265 	Assert((unsigned int) index < UnitTypeVar.GetNumberVariable());
266 
267 	switch (t) {
268 		case 0: // Unit:
269 			var = &unit.Variable[index];
270 			break;
271 		case 1: // Type:
272 			var = &unit.Type->MapDefaultStat.Variables[index];
273 			break;
274 		case 2: // Stats:
275 			var = &unit.Stats->Variables[index];
276 			break;
277 		default:
278 			DebugPrint("Bad value for GetComponent: t = %d" _C_ t);
279 			var = &unit.Variable[index];
280 			break;
281 	}
282 
283 	switch (e) {
284 		case VariableValue:
285 			val.type = USTRINT_INT;
286 			val.i = var->Value;
287 			break;
288 		case VariableMax:
289 			val.type = USTRINT_INT;
290 			val.i = var->Max;
291 			break;
292 		case VariableIncrease:
293 			val.type = USTRINT_INT;
294 			val.i = var->Increase;
295 			break;
296 		case VariableDiff:
297 			val.type = USTRINT_INT;
298 			val.i = var->Max - var->Value;
299 			break;
300 		case VariablePercent:
301 			Assert(unit.Variable[index].Max != 0);
302 			val.type = USTRINT_INT;
303 			val.i = 100 * var->Value / var->Max;
304 			break;
305 		case VariableName:
306 			if (index == GIVERESOURCE_INDEX) {
307 				val.type = USTRINT_STR;
308 				val.i = unit.Type->GivesResource;
309 				val.s = DefaultResourceNames[unit.Type->GivesResource].c_str();
310 			} else if (index == CARRYRESOURCE_INDEX) {
311 				val.type = USTRINT_STR;
312 				val.i = unit.CurrentResource;
313 				val.s = DefaultResourceNames[unit.CurrentResource].c_str();
314 			} else {
315 				val.type = USTRINT_STR;
316 				val.i = index;
317 				val.s = UnitTypeVar.VariableNameLookup[index];
318 			}
319 			break;
320 	}
321 	return val;
322 }
323 
GetComponent(const CUnitType & type,int index,EnumVariable e,int t)324 UStrInt GetComponent(const CUnitType &type, int index, EnumVariable e, int t)
325 {
326 	UStrInt val;
327 	CVariable *var;
328 
329 	Assert((unsigned int) index < UnitTypeVar.GetNumberVariable());
330 
331 	switch (t) {
332 		case 0: // Unit:
333 			var = &type.Stats[ThisPlayer->Index].Variables[index];;
334 			break;
335 		case 1: // Type:
336 			var = &type.MapDefaultStat.Variables[index];
337 			break;
338 		case 2: // Stats:
339 			var = &type.Stats[ThisPlayer->Index].Variables[index];
340 			break;
341 		default:
342 			DebugPrint("Bad value for GetComponent: t = %d" _C_ t);
343 			var = &type.Stats[ThisPlayer->Index].Variables[index];
344 			break;
345 	}
346 	switch (e) {
347 		case VariableValue:
348 			val.type = USTRINT_INT;
349 			val.i = var->Value;
350 			break;
351 		case VariableMax:
352 			val.type = USTRINT_INT;
353 			val.i = var->Max;
354 			break;
355 		case VariableIncrease:
356 			val.type = USTRINT_INT;
357 			val.i = var->Increase;
358 			break;
359 		case VariableDiff:
360 			val.type = USTRINT_INT;
361 			val.i = var->Max - var->Value;
362 			break;
363 		case VariablePercent:
364 			Assert(type.Stats[ThisPlayer->Index].Variables[index].Max != 0);
365 			val.type = USTRINT_INT;
366 			val.i = 100 * var->Value / var->Max;
367 			break;
368 		case VariableName:
369 			if (index == GIVERESOURCE_INDEX) {
370 				val.type = USTRINT_STR;
371 				val.i = type.GivesResource;
372 				val.s = DefaultResourceNames[type.GivesResource].c_str();
373 			} else {
374 				val.type = USTRINT_STR;
375 				val.i = index;
376 				val.s = UnitTypeVar.VariableNameLookup[index];
377 			}
378 			break;
379 	}
380 	return val;
381 }
382 
DrawUnitInfo_Training(const CUnit & unit)383 static void DrawUnitInfo_Training(const CUnit &unit)
384 {
385 	if (unit.Orders.size() == 1 || unit.Orders[1]->Action != UnitActionTrain) {
386 		if (!UI.SingleTrainingText.empty()) {
387 			CLabel label(*UI.SingleTrainingFont);
388 			label.Draw(UI.SingleTrainingTextX, UI.SingleTrainingTextY, UI.SingleTrainingText);
389 		}
390 		if (UI.SingleTrainingButton) {
391 			const COrder_Train &order = *static_cast<COrder_Train *>(unit.CurrentOrder());
392 			CIcon &icon = *order.GetUnitType().Icon.Icon;
393 			const unsigned int flags = (ButtonAreaUnderCursor == ButtonAreaTraining && ButtonUnderCursor == 0) ?
394 									   (IconActive | (MouseButtons & LeftButton)) : 0;
395 			const PixelPos pos(UI.SingleTrainingButton->X, UI.SingleTrainingButton->Y);
396 			icon.DrawUnitIcon(*UI.SingleTrainingButton->Style, flags, pos, "", unit.RescuedFrom
397 				? GameSettings.Presets[unit.RescuedFrom->Index].PlayerColor
398 				: GameSettings.Presets[unit.Player->Index].PlayerColor);
399 		}
400 	} else {
401 		if (!UI.TrainingText.empty()) {
402 			CLabel label(*UI.TrainingFont);
403 			label.Draw(UI.TrainingTextX, UI.TrainingTextY, UI.TrainingText);
404 		}
405 		if (!UI.TrainingButtons.empty()) {
406 			for (size_t i = 0; i < unit.Orders.size()
407 				 && i < UI.TrainingButtons.size(); ++i) {
408 				if (unit.Orders[i]->Action == UnitActionTrain) {
409 					const COrder_Train &order = *static_cast<COrder_Train *>(unit.Orders[i]);
410 					CIcon &icon = *order.GetUnitType().Icon.Icon;
411 					const int flag = (ButtonAreaUnderCursor == ButtonAreaTraining
412 									  && static_cast<size_t>(ButtonUnderCursor) == i) ?
413 									 (IconActive | (MouseButtons & LeftButton)) : 0;
414 					const PixelPos pos(UI.TrainingButtons[i].X, UI.TrainingButtons[i].Y);
415 					icon.DrawUnitIcon(*UI.TrainingButtons[i].Style, flag, pos, "", unit.RescuedFrom
416 						? GameSettings.Presets[unit.RescuedFrom->Index].PlayerColor
417 						: GameSettings.Presets[unit.Player->Index].PlayerColor);
418 				}
419 			}
420 		}
421 	}
422 }
423 
DrawUnitInfo_portrait(const CUnit & unit)424 static void DrawUnitInfo_portrait(const CUnit &unit)
425 {
426 	const CUnitType &type = *unit.Type;
427 #ifdef USE_MNG
428 	if (type.Portrait.Num) {
429 		type.Portrait.Mngs[type.Portrait.CurrMng]->Draw(
430 			UI.SingleSelectedButton->X, UI.SingleSelectedButton->Y);
431 		if (type.Portrait.Mngs[type.Portrait.CurrMng]->iteration == type.Portrait.NumIterations) {
432 			type.Portrait.Mngs[type.Portrait.CurrMng]->Reset();
433 			// FIXME: should be configurable
434 			if (type.Portrait.CurrMng == 0) {
435 				type.Portrait.CurrMng = (MyRand() % (type.Portrait.Num - 1)) + 1;
436 				type.Portrait.NumIterations = 1;
437 			} else {
438 				type.Portrait.CurrMng = 0;
439 				type.Portrait.NumIterations = MyRand() % 16 + 1;
440 			}
441 		}
442 		return;
443 	}
444 #endif
445 	if (UI.SingleSelectedButton) {
446 		const PixelPos pos(UI.SingleSelectedButton->X, UI.SingleSelectedButton->Y);
447 		const int flag = (ButtonAreaUnderCursor == ButtonAreaSelected && ButtonUnderCursor == 0) ?
448 						 (IconActive | (MouseButtons & LeftButton)) : 0;
449 
450 		type.Icon.Icon->DrawUnitIcon(*UI.SingleSelectedButton->Style, flag, pos, "", unit.RescuedFrom
451 			? GameSettings.Presets[unit.RescuedFrom->Index].PlayerColor
452 			: GameSettings.Presets[unit.Player->Index].PlayerColor);
453 	}
454 }
455 
DrawUnitInfo_single_selection(const CUnit & unit)456 static bool DrawUnitInfo_single_selection(const CUnit &unit)
457 {
458 	switch (unit.CurrentAction()) {
459 		case UnitActionTrain: { //  Building training units.
460 			DrawUnitInfo_Training(unit);
461 			return true;
462 		}
463 		case UnitActionUpgradeTo: { //  Building upgrading to better type.
464 			if (UI.UpgradingButton) {
465 				const COrder_UpgradeTo &order = *static_cast<COrder_UpgradeTo *>(unit.CurrentOrder());
466 				CIcon &icon = *order.GetUnitType().Icon.Icon;
467 				unsigned int flag = (ButtonAreaUnderCursor == ButtonAreaUpgrading
468 									 && ButtonUnderCursor == 0) ?
469 									(IconActive | (MouseButtons & LeftButton)) : 0;
470 				const PixelPos pos(UI.UpgradingButton->X, UI.UpgradingButton->Y);
471 				icon.DrawUnitIcon(*UI.UpgradingButton->Style, flag, pos, "", unit.RescuedFrom
472 					? GameSettings.Presets[unit.RescuedFrom->Index].PlayerColor
473 					: GameSettings.Presets[unit.Player->Index].PlayerColor);
474 			}
475 			return true;
476 		}
477 		case UnitActionResearch: { //  Building research new technology.
478 			if (UI.ResearchingButton) {
479 				COrder_Research &order = *static_cast<COrder_Research *>(unit.CurrentOrder());
480 				CIcon &icon = *order.GetUpgrade().Icon;
481 				int flag = (ButtonAreaUnderCursor == ButtonAreaResearching
482 							&& ButtonUnderCursor == 0) ?
483 						   (IconActive | (MouseButtons & LeftButton)) : 0;
484 				PixelPos pos(UI.ResearchingButton->X, UI.ResearchingButton->Y);
485 				icon.DrawUnitIcon(*UI.ResearchingButton->Style, flag, pos, "", unit.RescuedFrom
486 					? GameSettings.Presets[unit.RescuedFrom->Index].PlayerColor
487 					: GameSettings.Presets[unit.Player->Index].PlayerColor);
488 			}
489 			return true;
490 		}
491 		default:
492 			return false;
493 	}
494 }
495 
DrawUnitInfo_transporter(CUnit & unit)496 static void DrawUnitInfo_transporter(CUnit &unit)
497 {
498 	CUnit *uins = unit.UnitInside;
499 	size_t j = 0;
500 
501 	for (int i = 0; i < unit.InsideCount; ++i, uins = uins->NextContained) {
502 		if (!uins->Boarded || j >= UI.TransportingButtons.size()) {
503 			continue;
504 		}
505 		CIcon &icon = *uins->Type->Icon.Icon;
506 		int flag = (ButtonAreaUnderCursor == ButtonAreaTransporting && static_cast<size_t>(ButtonUnderCursor) == j) ?
507 				   (IconActive | (MouseButtons & LeftButton)) : 0;
508 		const PixelPos pos(UI.TransportingButtons[j].X, UI.TransportingButtons[j].Y);
509 		icon.DrawUnitIcon(*UI.TransportingButtons[j].Style, flag, pos, "", uins->RescuedFrom
510 			? GameSettings.Presets[uins->RescuedFrom->Index].PlayerColor
511 			: GameSettings.Presets[uins->Player->Index].PlayerColor);
512 		UiDrawLifeBar(*uins, pos.x, pos.y);
513 		if (uins->Type->CanCastSpell && uins->Variable[MANA_INDEX].Max) {
514 			UiDrawManaBar(*uins, pos.x, pos.y);
515 		}
516 		if (ButtonAreaUnderCursor == ButtonAreaTransporting
517 			&& static_cast<size_t>(ButtonUnderCursor) == j) {
518 			UI.StatusLine.Set(uins->Type->Name);
519 		}
520 		++j;
521 	}
522 }
523 
524 /**
525 **  Draw the unit info into top-panel.
526 **
527 **  @param unit  Pointer to unit.
528 */
DrawUnitInfo(CUnit & unit)529 static void DrawUnitInfo(CUnit &unit)
530 {
531 	UpdateUnitVariables(unit);
532 	for (size_t i = 0; i != UI.InfoPanelContents.size(); ++i) {
533 		if (CanShowContent(UI.InfoPanelContents[i]->Condition, unit)) {
534 			for (std::vector<CContentType *>::const_iterator content = UI.InfoPanelContents[i]->Contents.begin();
535 				 content != UI.InfoPanelContents[i]->Contents.end(); ++content) {
536 				if (CanShowContent((*content)->Condition, unit)) {
537 					(*content)->Draw(unit, UI.InfoPanelContents[i]->DefaultFont);
538 				}
539 			}
540 		}
541 	}
542 
543 	const CUnitType &type = *unit.Type;
544 	Assert(&type);
545 
546 	// Draw IconUnit
547 	DrawUnitInfo_portrait(unit);
548 
549 	if (unit.Player != ThisPlayer && !ThisPlayer->IsAllied(*unit.Player)) {
550 		return;
551 	}
552 
553 	//  Show progress if they are selected.
554 	if (IsOnlySelected(unit)) {
555 		if (DrawUnitInfo_single_selection(unit)) {
556 			return;
557 		}
558 	}
559 
560 	//  Transporting units.
561 	if (type.CanTransport() && unit.BoardCount && CurrentButtonLevel == unit.Type->ButtonLevelForTransporter) {
562 		DrawUnitInfo_transporter(unit);
563 		return;
564 	}
565 }
566 
567 /*----------------------------------------------------------------------------
568 --  RESOURCES
569 ----------------------------------------------------------------------------*/
570 
571 /**
572 **  Draw the player resource in top line.
573 **
574 **  @todo FIXME : make DrawResources more configurable (format, font).
575 */
DrawResources()576 void DrawResources()
577 {
578 	CLabel label(GetGameFont());
579 
580 	// Draw all icons of resource.
581 	for (int i = 0; i <= FreeWorkersCount; ++i) {
582 		if (UI.Resources[i].G) {
583 			UI.Resources[i].G->DrawFrameClip(UI.Resources[i].IconFrame,
584 											 UI.Resources[i].IconX, UI.Resources[i].IconY);
585 		}
586 	}
587 	for (int i = 0; i < MaxCosts; ++i) {
588 		if (UI.Resources[i].TextX != -1) {
589 			const int resourceAmount = ThisPlayer->Resources[i];
590 
591 			if (ThisPlayer->MaxResources[i] != -1) {
592 				const int resAmount = ThisPlayer->StoredResources[i] + ThisPlayer->Resources[i];
593 				char tmp[256];
594 				snprintf(tmp, sizeof(tmp), "%d (%d)", resAmount, ThisPlayer->MaxResources[i] - ThisPlayer->StoredResources[i]);
595 				label.SetFont(GetSmallFont());
596 
597 				label.Draw(UI.Resources[i].TextX, UI.Resources[i].TextY + 3, tmp);
598 			} else {
599 				label.SetFont(resourceAmount > 99999 ? GetSmallFont() : GetGameFont());
600 
601 				label.Draw(UI.Resources[i].TextX, UI.Resources[i].TextY + (resourceAmount > 99999) * 3, resourceAmount);
602 			}
603 		}
604 	}
605 	if (UI.Resources[FoodCost].TextX != -1) {
606 		char tmp[256];
607 		snprintf(tmp, sizeof(tmp), "%d/%d", ThisPlayer->Demand, ThisPlayer->Supply);
608 		label.SetFont(GetGameFont());
609 		if (ThisPlayer->Supply < ThisPlayer->Demand) {
610 			label.DrawReverse(UI.Resources[FoodCost].TextX, UI.Resources[FoodCost].TextY, tmp);
611 		} else {
612 			label.Draw(UI.Resources[FoodCost].TextX, UI.Resources[FoodCost].TextY, tmp);
613 		}
614 	}
615 	if (UI.Resources[ScoreCost].TextX != -1) {
616 		const int score = ThisPlayer->Score;
617 
618 		label.SetFont(score > 99999 ? GetSmallFont() : GetGameFont());
619 		label.Draw(UI.Resources[ScoreCost].TextX, UI.Resources[ScoreCost].TextY + (score > 99999) * 3, score);
620 	}
621 	if (UI.Resources[FreeWorkersCount].TextX != -1) {
622 		const int workers = ThisPlayer->FreeWorkers.size();
623 
624 		label.SetFont(GetGameFont());
625 		label.Draw(UI.Resources[FreeWorkersCount].TextX, UI.Resources[FreeWorkersCount].TextY, workers);
626 	}
627 }
628 
629 /*----------------------------------------------------------------------------
630 --  MESSAGE
631 ----------------------------------------------------------------------------*/
632 
633 #define MESSAGES_MAX  10                       /// How many can be displayed
634 
635 static char MessagesEvent[MESSAGES_MAX][256];  /// Array of event messages
636 static Vec2i MessagesEventPos[MESSAGES_MAX];   /// coordinate of event
637 static int MessagesEventCount;                 /// Number of event messages
638 static int MessagesEventIndex;                 /// FIXME: docu
639 
640 class MessagesDisplay
641 {
642 public:
MessagesDisplay()643 	MessagesDisplay() : show(true)
644 	{
645 #ifdef DEBUG
646 		showBuilList = false;
647 #endif
648 		CleanMessages();
649 	}
650 
651 	void UpdateMessages();
652 	void AddUniqueMessage(const char *s);
653 	void DrawMessages();
654 	void CleanMessages();
ToggleShowMessages()655 	void ToggleShowMessages() { show = !show; }
656 #ifdef DEBUG
ToggleShowBuilListMessages()657 	void ToggleShowBuilListMessages() { showBuilList = !showBuilList; }
658 #endif
659 
660 protected:
661 	void ShiftMessages();
662 	void AddMessage(const char *msg);
663 	bool CheckRepeatMessage(const char *msg);
664 
665 private:
666 	char Messages[MESSAGES_MAX][256];         /// Array of messages
667 	int  MessagesCount;                       /// Number of messages
668 	int  MessagesSameCount;                   /// Counts same message repeats
669 	int  MessagesScrollY;
670 	unsigned long MessagesFrameTimeout;       /// Frame to expire message
671 	bool show;
672 #ifdef DEBUG
673 	bool showBuilList;
674 #endif
675 };
676 
677 /**
678 **  Shift messages array by one.
679 */
ShiftMessages()680 void MessagesDisplay::ShiftMessages()
681 {
682 	if (MessagesCount) {
683 		--MessagesCount;
684 		for (int z = 0; z < MessagesCount; ++z) {
685 			strcpy_s(Messages[z], sizeof(Messages[z]), Messages[z + 1]);
686 		}
687 	}
688 }
689 
690 /**
691 **  Update messages
692 **
693 **  @todo FIXME: make scroll speed configurable.
694 */
UpdateMessages()695 void MessagesDisplay::UpdateMessages()
696 {
697 	if (!MessagesCount) {
698 		return;
699 	}
700 
701 	// Scroll/remove old message line
702 	const unsigned long ticks = GetTicks();
703 	if (MessagesFrameTimeout < ticks) {
704 		++MessagesScrollY;
705 		if (MessagesScrollY == UI.MessageFont->Height() + 1) {
706 			MessagesFrameTimeout = ticks + UI.MessageScrollSpeed * 1000;
707 			MessagesScrollY = 0;
708 			ShiftMessages();
709 		}
710 	}
711 }
712 
713 /**
714 **  Draw message(s).
715 **
716 **  @todo FIXME: make message font configurable.
717 */
DrawMessages()718 void MessagesDisplay::DrawMessages()
719 {
720 	if (show && Preference.ShowMessages) {
721 		CLabel label(*UI.MessageFont);
722 #ifdef DEBUG
723 		if (showBuilList && ThisPlayer->Ai) {
724 			char buffer[256];
725 			int count = ThisPlayer->Ai->UnitTypeBuilt.size();
726 			// Draw message line(s)
727 			for (int z = 0; z < count; ++z) {
728 				if (z == 0) {
729 					PushClipping();
730 					SetClipping(UI.MapArea.X + 8, UI.MapArea.Y + 8,
731 								Video.Width - 1, Video.Height - 1);
732 				}
733 
734 				snprintf(buffer, 256, "%s (%d/%d) Wait %lu [%d,%d]",
735 						 ThisPlayer->Ai->UnitTypeBuilt[z].Type->Name.c_str(),
736 						 ThisPlayer->Ai->UnitTypeBuilt[z].Made,
737 						 ThisPlayer->Ai->UnitTypeBuilt[z].Want,
738 						 ThisPlayer->Ai->UnitTypeBuilt[z].Wait,
739 						 ThisPlayer->Ai->UnitTypeBuilt[z].Pos.x,
740 						 ThisPlayer->Ai->UnitTypeBuilt[z].Pos.y);
741 
742 				label.DrawClip(UI.MapArea.X + 8,
743 							   UI.MapArea.Y + 8 + z * (UI.MessageFont->Height() + 1),
744 							   buffer);
745 
746 				if (z == 0) {
747 					PopClipping();
748 				}
749 			}
750 		} else {
751 #endif
752 			// background so the text is easier to read
753 			/*
754 			if (MessagesCount) {
755 				int textHeight = MessagesCount * (UI.MessageFont->Height() + 1);
756 				Uint32 color = Video.MapRGB(TheScreen->format, 38, 38, 78);
757 				Video.FillTransRectangleClip(color, UI.MapArea.X + 7, UI.MapArea.Y + 7,
758 											 UI.MapArea.EndX - UI.MapArea.X - 16,
759 											 textHeight - MessagesScrollY + 1, 0x80);
760 
761 				Video.DrawRectangle(color, UI.MapArea.X + 6, UI.MapArea.Y + 6,
762 									UI.MapArea.EndX - UI.MapArea.X - 15,
763 									textHeight - MessagesScrollY + 2);
764 			}
765 			*/
766 
767 			// Draw message line(s)
768 			for (int z = 0; z < MessagesCount; ++z) {
769 				if (z == 0) {
770 					PushClipping();
771 					SetClipping(UI.MapArea.X + 8, UI.MapArea.Y + 8, Video.Width - 1,
772 								Video.Height - 1);
773 				}
774 				/*
775 				 * Due parallel drawing we have to force message copy due temp
776 				 * std::string(Messages[z]) creation because
777 				 * char * pointer may change during text drawing.
778 				 */
779 				label.DrawClip(UI.MapArea.X + 8,
780 							   UI.MapArea.Y + 8 +
781 							   z * (UI.MessageFont->Height() + 1) - MessagesScrollY,
782 							   std::string(Messages[z]));
783 				if (z == 0) {
784 					PopClipping();
785 				}
786 			}
787 			if (MessagesCount < 1) {
788 				MessagesSameCount = 0;
789 			}
790 #ifdef DEBUG
791 		}
792 #endif
793 
794 	}
795 }
796 
797 /**
798 **  Adds message to the stack
799 **
800 **  @param msg  Message to add.
801 */
AddMessage(const char * msg)802 void MessagesDisplay::AddMessage(const char *msg)
803 {
804 	unsigned long ticks = GetTicks();
805 
806 	if (!MessagesCount) {
807 		MessagesFrameTimeout = ticks + UI.MessageScrollSpeed * 1000;
808 	}
809 
810 	if (MessagesCount == MESSAGES_MAX) {
811 		// Out of space to store messages, can't scroll smoothly
812 		ShiftMessages();
813 		MessagesFrameTimeout = ticks + UI.MessageScrollSpeed * 1000;
814 		MessagesScrollY = 0;
815 	}
816 	char *ptr;
817 	char *next;
818 	char *message = Messages[MessagesCount];
819 	// Split long message into lines
820 	if (strlen(msg) >= sizeof(Messages[0])) {
821 		strncpy(message, msg, sizeof(Messages[0]) - 1);
822 		ptr = message + sizeof(Messages[0]) - 1;
823 		*ptr-- = '\0';
824 		next = ptr + 1;
825 		while (ptr >= message) {
826 			if (*ptr == ' ') {
827 				*ptr = '\0';
828 				next = ptr + 1;
829 				break;
830 			}
831 			--ptr;
832 		}
833 		if (ptr < message) {
834 			ptr = next - 1;
835 		}
836 	} else {
837 		strcpy_s(message, sizeof(Messages[MessagesCount]), msg);
838 		next = ptr = message + strlen(message);
839 	}
840 
841 	while (UI.MessageFont->Width(message) + 8 >= UI.MapArea.EndX - UI.MapArea.X) {
842 		while (1) {
843 			--ptr;
844 			if (*ptr == ' ') {
845 				*ptr = '\0';
846 				next = ptr + 1;
847 				break;
848 			} else if (ptr == message) {
849 				break;
850 			}
851 		}
852 		// No space found, wrap in the middle of a word
853 		if (ptr == message) {
854 			ptr = next - 1;
855 			while (UI.MessageFont->Width(message) + 8 >= UI.MapArea.EndX - UI.MapArea.X) {
856 				*--ptr = '\0';
857 			}
858 			next = ptr + 1;
859 			break;
860 		}
861 	}
862 
863 	++MessagesCount;
864 
865 	if (strlen(msg) != (size_t)(ptr - message)) {
866 		AddMessage(msg + (next - message));
867 	}
868 }
869 
870 /**
871 **  Check if this message repeats
872 **
873 **  @param msg  Message to check.
874 **
875 **  @return     true to skip this message
876 */
CheckRepeatMessage(const char * msg)877 bool MessagesDisplay::CheckRepeatMessage(const char *msg)
878 {
879 	if (MessagesCount < 1) {
880 		return false;
881 	}
882 	if (!strcmp(msg, Messages[MessagesCount - 1])) {
883 		++MessagesSameCount;
884 		return true;
885 	}
886 	if (MessagesSameCount > 0) {
887 		char temp[256];
888 		int n = MessagesSameCount;
889 
890 		MessagesSameCount = 0;
891 		// NOTE: vladi: yep it's a tricky one, but should work fine prbably :)
892 		snprintf(temp, sizeof(temp), _("Last message repeated ~<%d~> times"), n + 1);
893 		AddMessage(temp);
894 	}
895 	return false;
896 }
897 
898 /**
899 **  Add a new message to display only if it differs from the preceding one.
900 */
AddUniqueMessage(const char * s)901 void MessagesDisplay::AddUniqueMessage(const char *s)
902 {
903 	if (!CheckRepeatMessage(s)) {
904 		AddMessage(s);
905 	}
906 }
907 
908 /**
909 **  Clean up messages.
910 */
CleanMessages()911 void MessagesDisplay::CleanMessages()
912 {
913 	MessagesCount = 0;
914 	MessagesSameCount = 0;
915 	MessagesScrollY = 0;
916 	MessagesFrameTimeout = 0;
917 
918 	MessagesEventCount = 0;
919 	MessagesEventIndex = 0;
920 }
921 
922 static MessagesDisplay allmessages;
923 
924 /**
925 **  Update messages
926 */
UpdateMessages()927 void UpdateMessages()
928 {
929 	allmessages.UpdateMessages();
930 }
931 
932 /**
933 **  Clean messages
934 */
CleanMessages()935 void CleanMessages()
936 {
937 	allmessages.CleanMessages();
938 }
939 
940 /**
941 **  Draw messages
942 */
DrawMessages()943 void DrawMessages()
944 {
945 	allmessages.DrawMessages();
946 }
947 
948 /**
949 **  Set message to display.
950 **
951 **  @param fmt  To be displayed in text overlay.
952 */
SetMessage(const char * fmt,...)953 void SetMessage(const char *fmt, ...)
954 {
955 	char temp[512];
956 	va_list va;
957 
958 	va_start(va, fmt);
959 	vsnprintf(temp, sizeof(temp) - 1, fmt, va);
960 	temp[sizeof(temp) - 1] = '\0';
961 	va_end(va);
962 	allmessages.AddUniqueMessage(temp);
963 }
964 
965 /**
966 **  Shift messages events array by one.
967 */
ShiftMessagesEvent()968 void ShiftMessagesEvent()
969 {
970 	if (MessagesEventCount) {
971 		--MessagesEventCount;
972 		for (int z = 0; z < MessagesEventCount; ++z) {
973 			MessagesEventPos[z] = MessagesEventPos[z + 1];
974 			strcpy_s(MessagesEvent[z], sizeof(MessagesEvent[z]), MessagesEvent[z + 1]);
975 		}
976 	}
977 }
978 
979 /**
980 **  Set message to display.
981 **
982 **  @param pos    Message pos map origin.
983 **  @param fmt  To be displayed in text overlay.
984 **
985 **  @note FIXME: vladi: I know this can be just separated func w/o msg but
986 **               it is handy to stick all in one call, someone?
987 */
SetMessageEvent(const Vec2i & pos,const char * fmt,...)988 void SetMessageEvent(const Vec2i &pos, const char *fmt, ...)
989 {
990 	Assert(Map.Info.IsPointOnMap(pos));
991 
992 	char temp[256];
993 	va_list va;
994 
995 	va_start(va, fmt);
996 	vsnprintf(temp, sizeof(temp) - 1, fmt, va);
997 	temp[sizeof(temp) - 1] = '\0';
998 	va_end(va);
999 	allmessages.AddUniqueMessage(temp);
1000 
1001 	if (MessagesEventCount == MESSAGES_MAX) {
1002 		ShiftMessagesEvent();
1003 	}
1004 
1005 	strcpy_s(MessagesEvent[MessagesEventCount], sizeof(MessagesEvent[MessagesEventCount]), temp);
1006 	MessagesEventPos[MessagesEventCount] = pos;
1007 	MessagesEventIndex = MessagesEventCount;
1008 	++MessagesEventCount;
1009 }
1010 
1011 /**
1012 **  Goto message origin.
1013 */
CenterOnMessage()1014 void CenterOnMessage()
1015 {
1016 	if (MessagesEventIndex >= MessagesEventCount) {
1017 		MessagesEventIndex = 0;
1018 	}
1019 	if (MessagesEventCount == 0) {
1020 		return;
1021 	}
1022 	const Vec2i &pos(MessagesEventPos[MessagesEventIndex]);
1023 	UI.SelectedViewport->Center(Map.TilePosToMapPixelPos_Center(pos));
1024 	SetMessage(_("~<Event: %s~>"), MessagesEvent[MessagesEventIndex]);
1025 	++MessagesEventIndex;
1026 }
1027 
ToggleShowMessages()1028 void ToggleShowMessages()
1029 {
1030 	allmessages.ToggleShowMessages();
1031 }
1032 
1033 #ifdef DEBUG
ToggleShowBuilListMessages()1034 void ToggleShowBuilListMessages()
1035 {
1036 	allmessages.ToggleShowBuilListMessages();
1037 }
1038 #endif
1039 
1040 /*----------------------------------------------------------------------------
1041 --  INFO PANEL
1042 ----------------------------------------------------------------------------*/
1043 
1044 /**
1045 **  Draw info panel background.
1046 **
1047 **  @param frame  frame nr. of the info panel background.
1048 */
DrawInfoPanelBackground(unsigned frame)1049 static void DrawInfoPanelBackground(unsigned frame)
1050 {
1051 	if (UI.InfoPanel.G) {
1052 		UI.InfoPanel.G->DrawFrame(frame, UI.InfoPanel.X, UI.InfoPanel.Y);
1053 	}
1054 }
1055 
InfoPanel_draw_no_selection()1056 static void InfoPanel_draw_no_selection()
1057 {
1058 	DrawInfoPanelBackground(0);
1059 	if (UnitUnderCursor && UnitUnderCursor->IsVisible(*ThisPlayer)
1060 		&& !UnitUnderCursor->Type->BoolFlag[ISNOTSELECTABLE_INDEX].value) {
1061 		// FIXME: not correct for enemies units
1062 		DrawUnitInfo(*UnitUnderCursor);
1063 	} else if (Preference.ShowNoSelectionStats) {
1064 		// FIXME: need some cool ideas for this.
1065 		int x = UI.InfoPanel.X + 16;
1066 		int y = UI.InfoPanel.Y + 8;
1067 
1068 		CLabel label(GetGameFont());
1069 		label.Draw(x, y, "Stratagus");
1070 		y += 16;
1071 		label.Draw(x, y,  _("Cycle:"));
1072 		label.Draw(x + 48, y, GameCycle);
1073 		label.Draw(x + 110, y, CYCLES_PER_SECOND * VideoSyncSpeed / 100);
1074 		y += 20;
1075 
1076 		std::string nc;
1077 		std::string rc;
1078 
1079 		GetDefaultTextColors(nc, rc);
1080 		for (int i = 0; i < PlayerMax - 1; ++i) {
1081 			if (Players[i].Type != PlayerNobody) {
1082 				if (ThisPlayer->IsAllied(Players[i])) {
1083 					label.SetNormalColor(FontGreen);
1084 				} else if (ThisPlayer->IsEnemy(Players[i])) {
1085 					label.SetNormalColor(FontRed);
1086 				} else {
1087 					label.SetNormalColor(nc);
1088 				}
1089 				label.Draw(x + 15, y, i);
1090 
1091 				Video.DrawRectangleClip(ColorWhite, x, y, 12, 12);
1092 				Video.FillRectangleClip(PlayerColors[GameSettings.Presets[i].PlayerColor][0], x + 1, y + 1, 10, 10);
1093 
1094 				label.Draw(x + 27, y, Players[i].Name);
1095 				label.Draw(x + 117, y, Players[i].Score);
1096 				y += 14;
1097 			}
1098 		}
1099 	}
1100 }
1101 
InfoPanel_draw_single_selection(CUnit * selUnit)1102 static void InfoPanel_draw_single_selection(CUnit *selUnit)
1103 {
1104 	CUnit &unit = (selUnit ? *selUnit : *Selected[0]);
1105 	int panelIndex;
1106 
1107 	// FIXME: not correct for enemy's units
1108 	if (unit.Player == ThisPlayer
1109 		|| ThisPlayer->IsTeamed(unit)
1110 		|| ThisPlayer->IsAllied(unit)
1111 		|| ReplayRevealMap) {
1112 		if (unit.Orders[0]->Action == UnitActionBuilt
1113 			|| unit.Orders[0]->Action == UnitActionResearch
1114 			|| unit.Orders[0]->Action == UnitActionUpgradeTo
1115 			|| unit.Orders[0]->Action == UnitActionTrain) {
1116 			panelIndex = 3;
1117 		} else if (unit.Variable[MANA_INDEX].Max) {
1118 			panelIndex = 2;
1119 		} else {
1120 			panelIndex = 1;
1121 		}
1122 	} else {
1123 		panelIndex = 0;
1124 	}
1125 	DrawInfoPanelBackground(panelIndex);
1126 	DrawUnitInfo(unit);
1127 	if (ButtonAreaUnderCursor == ButtonAreaSelected && ButtonUnderCursor == 0) {
1128 		UI.StatusLine.Set(unit.Type->Name);
1129 	}
1130 }
1131 
InfoPanel_draw_multiple_selection()1132 static void InfoPanel_draw_multiple_selection()
1133 {
1134 	//  If there are more units selected draw their pictures and a health bar
1135 	DrawInfoPanelBackground(0);
1136 	for (size_t i = 0; i != std::min(Selected.size(), UI.SelectedButtons.size()); ++i) {
1137 		const CIcon &icon = *Selected[i]->Type->Icon.Icon;
1138 		const PixelPos pos(UI.SelectedButtons[i].X, UI.SelectedButtons[i].Y);
1139 		icon.DrawUnitIcon(*UI.SelectedButtons[i].Style,
1140 						  (ButtonAreaUnderCursor == ButtonAreaSelected && ButtonUnderCursor == (int)i) ?
1141 						  (IconActive | (MouseButtons & LeftButton)) : 0,
1142 						  pos, "", Selected[i]->RescuedFrom
1143 						  ? GameSettings.Presets[Selected[i]->RescuedFrom->Index].PlayerColor
1144 						  : GameSettings.Presets[Selected[i]->Player->Index].PlayerColor);
1145 		UiDrawLifeBar(*Selected[i], UI.SelectedButtons[i].X, UI.SelectedButtons[i].Y);
1146 
1147 		if (ButtonAreaUnderCursor == ButtonAreaSelected && ButtonUnderCursor == (int) i) {
1148 			UI.StatusLine.Set(Selected[i]->Type->Name);
1149 		}
1150 	}
1151 	if (Selected.size() > UI.SelectedButtons.size()) {
1152 
1153 		const std::string number_str = "+" + std::to_string(Selected.size() - UI.SelectedButtons.size());
1154 		CLabel(*UI.MaxSelectedFont).Draw(UI.MaxSelectedTextX, UI.MaxSelectedTextY, number_str.c_str());
1155 	}
1156 }
1157 
1158 /**
1159 **  Draw info panel.
1160 **
1161 **  Panel:
1162 **    neutral      - neutral or opponent
1163 **    normal       - not 1,3,4
1164 **    magic unit   - magic units
1165 **    construction - under construction
1166 */
Draw()1167 void CInfoPanel::Draw()
1168 {
1169 	if (UnitUnderCursor && Selected.empty() && !UnitUnderCursor->Type->BoolFlag[ISNOTSELECTABLE_INDEX].value
1170 		&& (ReplayRevealMap || UnitUnderCursor->IsVisible(*ThisPlayer))) {
1171 			InfoPanel_draw_single_selection(UnitUnderCursor);
1172 	} else {
1173 		switch (Selected.size()) {
1174 			 case 0: { InfoPanel_draw_no_selection(); break; }
1175 			 case 1: { InfoPanel_draw_single_selection(NULL); break; }
1176 			 default: { InfoPanel_draw_multiple_selection(); break; }
1177 		}
1178 	}
1179 }
1180 
1181 /*----------------------------------------------------------------------------
1182 --  TIMER
1183 ----------------------------------------------------------------------------*/
1184 
1185 /**
1186 **  Draw the timer
1187 **
1188 **  @todo FIXME : make DrawTimer more configurable (Pos, format).
1189 */
DrawTimer()1190 void DrawTimer()
1191 {
1192 	if (!GameTimer.Init) {
1193 		return;
1194 	}
1195 
1196 	int sec = GameTimer.Cycles / CYCLES_PER_SECOND;
1197 	UI.Timer.Draw(sec);
1198 }
1199 
1200 /**
1201 **  Update the timer
1202 */
UpdateTimer()1203 void UpdateTimer()
1204 {
1205 	if (GameTimer.Running) {
1206 		if (GameTimer.Increasing) {
1207 			GameTimer.Cycles += GameCycle - GameTimer.LastUpdate;
1208 		} else {
1209 			GameTimer.Cycles -= GameCycle - GameTimer.LastUpdate;
1210 			GameTimer.Cycles = std::max(GameTimer.Cycles, 0l);
1211 		}
1212 		GameTimer.LastUpdate = GameCycle;
1213 	}
1214 }
1215 
1216 //@}
1217