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