1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2
3 #include "EndGameBox.h"
4
5 #include "MouseHandler.h"
6 #include "Game/Game.h"
7 #include "Game/GlobalUnsynced.h"
8 #include "Game/SelectedUnitsHandler.h"
9 #include "Game/Players/Player.h"
10 #include "Game/Players/PlayerHandler.h"
11 #include "Rendering/Fonts/glFont.h"
12 #include "Rendering/GL/VertexArray.h"
13 #include "Sim/Misc/GlobalSynced.h"
14 #include "Sim/Misc/TeamHandler.h"
15 #include "Sim/Misc/TeamStatistics.h"
16 #include "System/Exceptions.h"
17
18 #include <cstdio>
19 #include <sstream>
20
21 using std::sprintf;
22
23
FloatToSmallString(float num,float mul=1)24 static std::string FloatToSmallString(float num, float mul = 1) {
25
26 char c[50];
27
28 if (num == 0) {
29 sprintf(c, "0");
30 } else if (math::fabs(num) < 10 * mul) {
31 sprintf(c, "%.1f", num);
32 } else if (math::fabs(num) < 10000 * mul) {
33 sprintf(c, "%.0f", num);
34 } else if (math::fabs(num) < 10000000 * mul) {
35 sprintf(c, "%.0fk", num / 1000);
36 } else {
37 sprintf(c, "%.0fM", num / 1000000);
38 }
39
40 return c;
41 }
42
43
44 bool CEndGameBox::enabled = true;
45 CEndGameBox* CEndGameBox::endGameBox = NULL;
46
CEndGameBox(const std::vector<unsigned char> & winningAllyTeams)47 CEndGameBox::CEndGameBox(const std::vector<unsigned char>& winningAllyTeams)
48 : CInputReceiver()
49 , moveBox(false)
50 , dispMode(0)
51 , stat1(1)
52 , stat2(-1)
53 , winners(winningAllyTeams)
54 , graphTex(0)
55 {
56 endGameBox = this;
57 box.x1 = 0.14f;
58 box.y1 = 0.1f;
59 box.x2 = 0.86f;
60 box.y2 = 0.8f;
61
62 exitBox.x1 = 0.31f;
63 exitBox.y1 = 0.02f;
64 exitBox.x2 = 0.41f;
65 exitBox.y2 = 0.06f;
66
67 playerBox.x1 = 0.05f;
68 playerBox.y1 = 0.62f;
69 playerBox.x2 = 0.15f;
70 playerBox.y2 = 0.65f;
71
72 sumBox.x1 = 0.16f;
73 sumBox.y1 = 0.62f;
74 sumBox.x2 = 0.26f;
75 sumBox.y2 = 0.65f;
76
77 difBox.x1 = 0.27f;
78 difBox.y1 = 0.62f;
79 difBox.x2 = 0.38f;
80 difBox.y2 = 0.65f;
81
82 if (!bm.Load("bitmaps/graphPaper.bmp")) {
83 throw content_error("Could not load bitmaps/graphPaper.bmp");
84 }
85 }
86
~CEndGameBox()87 CEndGameBox::~CEndGameBox()
88 {
89 if (graphTex) {
90 glDeleteTextures(1,&graphTex);
91 }
92 endGameBox = NULL;
93 }
94
MousePress(int x,int y,int button)95 bool CEndGameBox::MousePress(int x, int y, int button)
96 {
97 if (!enabled) {
98 return false;
99 }
100
101 float mx = MouseX(x);
102 float my = MouseY(y);
103 if (InBox(mx, my, box)) {
104 moveBox = true;
105 if (InBox(mx, my, box + exitBox)) {
106 moveBox = false;
107 }
108 if (InBox(mx, my, box + playerBox)) {
109 moveBox = false;
110 }
111 if (InBox(mx, my, box + sumBox)) {
112 moveBox = false;
113 }
114 if (InBox(mx, my, box + difBox)) {
115 moveBox = false;
116 }
117 if (dispMode>0 && mx>box.x1+0.01f && mx<box.x1+0.12f && my<box.y1+0.57f && my>box.y1+0.571f-stats.size()*0.02f) {
118 moveBox = false;
119 }
120 return true;
121 }
122
123 return false;
124 }
125
MouseMove(int x,int y,int dx,int dy,int button)126 void CEndGameBox::MouseMove(int x, int y, int dx, int dy, int button)
127 {
128 if (!enabled) {
129 return;
130 }
131
132 if (moveBox) {
133 box.x1 += MouseMoveX(dx);
134 box.x2 += MouseMoveX(dx);
135 box.y1 += MouseMoveY(dy);
136 box.y2 += MouseMoveY(dy);
137 }
138 }
139
MouseRelease(int x,int y,int button)140 void CEndGameBox::MouseRelease(int x, int y, int button)
141 {
142 if (!enabled) {
143 return;
144 }
145
146 float mx = MouseX(x);
147 float my = MouseY(y);
148
149 if (InBox(mx, my, box + exitBox)) {
150 delete this;
151 gu->globalQuit = true;
152 return;
153 }
154
155 if (InBox(mx, my, box + playerBox)) {
156 dispMode = 0;
157 }
158 if (InBox(mx, my, box + sumBox)) {
159 dispMode = 1;
160 }
161 if (InBox(mx, my, box + difBox)) {
162 dispMode = 2;
163 }
164
165 if (dispMode > 0 ) {
166 if ((mx > (box.x1 + 0.01f)) && (mx < (box.x1 + 0.12f)) &&
167 (my < (box.y1 + 0.57f)) && (my > (box.y1 + 0.571f - stats.size()*0.02f))) {
168 int sel = (int) math::floor(-(my - box.y1 - 0.57f) * 50);
169
170 if (button == 1) {
171 stat1 = sel;
172 stat2 = -1;
173 } else {
174 stat2 = sel;
175 }
176 }
177 }
178
179 }
180
IsAbove(int x,int y)181 bool CEndGameBox::IsAbove(int x, int y)
182 {
183 if (!enabled) {
184 return false;
185 }
186
187 const float mx = MouseX(x);
188 const float my = MouseY(y);
189 return (InBox(mx, my, box));
190 }
191
Draw()192 void CEndGameBox::Draw()
193 {
194 if (!graphTex) {
195 graphTex = bm.CreateTexture();
196 }
197
198 if (!enabled) {
199 return;
200 }
201
202 float mx = MouseX(mouse->lastx);
203 float my = MouseY(mouse->lasty);
204
205 glDisable(GL_TEXTURE_2D);
206 glEnable(GL_BLEND);
207 glDisable(GL_ALPHA_TEST);
208
209 // Large Box
210 glColor4f(0.2f, 0.2f, 0.2f, guiAlpha);
211 DrawBox(box);
212
213 glColor4f(0.2f, 0.2f, 0.7f, guiAlpha);
214 if (dispMode == 0) {
215 DrawBox(box + playerBox);
216 } else if (dispMode == 1) {
217 DrawBox(box + sumBox);
218 } else {
219 DrawBox(box + difBox);
220 }
221
222 if (InBox(mx, my, box+exitBox)) {
223 glColor4f(0.7f, 0.2f, 0.2f, guiAlpha);
224 DrawBox(box + exitBox);
225 }
226 if (InBox(mx,my,box+playerBox)) {
227 glColor4f(0.7f, 0.2f, 0.2f, guiAlpha);
228 DrawBox(box + playerBox);
229 }
230 if (InBox(mx,my,box+sumBox)) {
231 glColor4f(0.7f, 0.2f, 0.2f, guiAlpha);
232 DrawBox(box + sumBox);
233 }
234 if (InBox(mx,my,box+difBox)) {
235 glColor4f(0.7f, 0.2f, 0.2f, guiAlpha);
236 DrawBox(box + difBox);
237 }
238
239 glEnable(GL_TEXTURE_2D);
240 glColor4f(1, 1, 1, 0.8f);
241 font->glPrint(box.x1 + exitBox.x1 + 0.025f, box.y1 + exitBox.y1 + 0.005f, 1.0f, FONT_SCALE | FONT_NORM, "Exit");
242 font->glPrint(box.x1 + playerBox.x1 + 0.015f, box.y1 + playerBox.y1 + 0.005f, 0.7f, FONT_SCALE | FONT_NORM, "Player stats");
243 font->glPrint(box.x1 + sumBox.x1 + 0.015f, box.y1 + sumBox.y1 + 0.005f, 0.7f, FONT_SCALE | FONT_NORM, "Team stats");
244 font->glPrint(box.x1 + difBox.x1 + 0.015f, box.y1 + difBox.y1 + 0.005f, 0.7f, FONT_SCALE | FONT_NORM, "Team delta stats");
245
246 if (winners.empty()) {
247 font->glPrint(box.x1 + 0.25f, box.y1 + 0.65f, 1.0f, FONT_SCALE | FONT_NORM, "Game result was undecided");
248 } else {
249 std::stringstream winnersText;
250 std::stringstream winnersList;
251
252 // myPlayingAllyTeam is >= 0 iff we ever joined a team
253 const bool neverPlayed = (gu->myPlayingAllyTeam < 0);
254 bool playedAndWon = false;
255
256 for (unsigned int i = 0; i < winners.size(); i++) {
257 const int winnerAllyTeam = winners[i];
258
259 if (!neverPlayed && winnerAllyTeam == gu->myPlayingAllyTeam) {
260 // we actually played and won!
261 playedAndWon = true; break;
262 }
263
264 winnersList << (((i > 0)? ((i < (winners.size() - 1))? ", ": " and "): ""));
265 winnersList << winnerAllyTeam;
266 }
267
268 if (neverPlayed) {
269 winnersText << "Game Over! Ally-team(s) ";
270 winnersText << winnersList.str() << " won!";
271
272 font->glPrint(box.x1 + 0.25f, box.y1 + 0.65f, 1.0f, FONT_SCALE | FONT_NORM, (winnersText.str()).c_str());
273 } else {
274 winnersText.str("");
275 winnersText << "Game Over! Your ally-team ";
276 winnersText << (playedAndWon? "won!": "lost!");
277 font->glPrint(box.x1 + 0.25f, box.y1 + 0.65f, 1.0f, FONT_SCALE | FONT_NORM, (winnersText.str()).c_str());
278 }
279 }
280
281 if (gs->frameNum <= 0) {
282 return;
283 }
284
285 if (dispMode == 0) {
286 float xpos = 0.01f;
287
288 std::string headers[] = {"Name", "MC/m", "MP/m", "KP/m", "Cmds/m", "ACS"};
289
290 for (int a = 0; a < 6; ++a) {
291 font->glPrint(box.x1 + xpos, box.y1 + 0.55f, 0.8f, FONT_SCALE | FONT_NORM,headers[a].c_str());
292 xpos += 0.1f;
293 }
294
295 float ypos = 0.5f;
296 for (int a = 0; a < playerHandler->ActivePlayers(); ++a) {
297 const CPlayer* p = playerHandler->Player(a);
298 const PlayerStatistics& pStats = p->currentStats;
299 char values[6][100];
300
301 SNPRINTF(values[0], 100, "%s", p->name.c_str());
302 if (game->totalGameTime>0){ //prevent div zero
303 SNPRINTF(values[1], 100, "%i", int(pStats.mouseClicks * 60 / game->totalGameTime));
304 SNPRINTF(values[2], 100, "%i", int(pStats.mousePixels * 60 / game->totalGameTime));
305 SNPRINTF(values[3], 100, "%i", int(pStats.keyPresses * 60 / game->totalGameTime));
306 SNPRINTF(values[4], 100, "%i", int(pStats.numCommands * 60 / game->totalGameTime));
307 }else{
308 for(int i=1; i<5; i++)
309 SNPRINTF(values[i], 100, "%i", 0);
310 }
311 SNPRINTF(values[5], 100, "%i",
312 (pStats.numCommands != 0)?
313 (pStats.unitCommands / pStats.numCommands):
314 (0));
315
316 float xpos = 0.01f;
317 for (int a = 0; a < 6; ++a) {
318 font->glPrint(box.x1 + xpos, box.y1 + ypos, 0.8f, FONT_SCALE | FONT_NORM, values[a]);
319 xpos += 0.1f;
320 }
321
322 ypos -= 0.02f;
323 }
324 } else {
325 if (stats.empty()) {
326 FillTeamStats();
327 }
328
329 glBindTexture(GL_TEXTURE_2D, graphTex);
330 CVertexArray* va=GetVertexArray();
331 va->Initialize();
332
333 va->AddVertexT(float3(box.x1+0.15f, box.y1+0.08f, 0), 0, 0);
334 va->AddVertexT(float3(box.x1+0.69f, box.y1+0.08f, 0), 4, 0);
335 va->AddVertexT(float3(box.x1+0.69f, box.y1+0.62f, 0), 4, 4);
336 va->AddVertexT(float3(box.x1+0.15f, box.y1+0.62f, 0), 0, 4);
337
338 va->DrawArrayT(GL_QUADS);
339
340 if ((mx > box.x1 + 0.01f) && (mx < box.x1 + 0.12f) &&
341 (my < box.y1 + 0.57f) && (my > box.y1 + 0.571f - (stats.size() * 0.02f))) {
342 const int sel = (int) math::floor(50 * -(my - box.y1 - 0.57f));
343
344 glColor4f(0.7f, 0.2f, 0.2f, guiAlpha);
345 glDisable(GL_TEXTURE_2D);
346 CVertexArray* va = GetVertexArray();
347 va->Initialize();
348
349 va->AddVertex0(float3(box.x1 + 0.01f, box.y1 + 0.55f - (sel * 0.02f) , 0));
350 va->AddVertex0(float3(box.x1 + 0.01f, box.y1 + 0.55f - (sel * 0.02f) + 0.02f , 0));
351 va->AddVertex0(float3(box.x1 + 0.12f, box.y1 + 0.55f - (sel * 0.02f) + 0.02f , 0));
352 va->AddVertex0(float3(box.x1 + 0.12f, box.y1 + 0.55f - (sel * 0.02f) , 0));
353
354 va->DrawArray0(GL_QUADS);
355 glEnable(GL_TEXTURE_2D);
356 glColor4f(1, 1, 1, 0.8f);
357 }
358 float ypos = 0.55f;
359 for (size_t a = 0; a < stats.size(); ++a) {
360 font->glPrint(box.x1 + 0.01f, box.y1 + ypos, 0.8f, FONT_SCALE | FONT_NORM, stats[a].name);
361 ypos -= 0.02f;
362 }
363 float maxy = 1;
364
365 if (dispMode == 1) {
366 maxy = std::max(stats[stat1].max, (stat2 != -1) ? stats[stat2].max : 0);
367 } else {
368 maxy = std::max(stats[stat1].maxdif, (stat2 != -1) ? stats[stat2].maxdif : 0) / TeamStatistics::statsPeriod;
369 }
370
371 int numPoints = stats[0].values[0].size();
372
373 for (int a = 0; a < 5; ++a) {
374 font->glPrint(box.x1 + 0.12f, box.y1 + 0.07f + (a * 0.135f), 0.8f, FONT_SCALE | FONT_NORM,
375 FloatToSmallString(maxy * 0.25f * a));
376 font->glFormat(box.x1 + 0.135f + (a * 0.135f), box.y1 + 0.057f, 0.8f, FONT_SCALE | FONT_NORM, "%02i:%02i",
377 (int) (a * 0.25f * numPoints * TeamStatistics::statsPeriod / 60),
378 (int) (a * 0.25f * (numPoints - 1) * TeamStatistics::statsPeriod) % 60);
379 }
380
381 font->glPrint(box.x1 + 0.55f, box.y1 + 0.65f, 0.8f, FONT_SCALE | FONT_NORM, stats[stat1].name);
382 font->glPrint(box.x1 + 0.55f, box.y1 + 0.63f, 0.8f, FONT_SCALE | FONT_NORM, (stat2 != -1) ? stats[stat2].name : "");
383
384 glDisable(GL_TEXTURE_2D);
385 glBegin(GL_LINES);
386 glVertex3f(box.x1+0.50f, box.y1+0.66f, 0);
387 glVertex3f(box.x1+0.55f, box.y1+0.66f, 0);
388 glEnd();
389
390 glLineStipple(3, 0x5555);
391 glEnable(GL_LINE_STIPPLE);
392 glBegin(GL_LINES);
393 glVertex3f(box.x1 + 0.50f, box.y1 + 0.64f, 0.0f);
394 glVertex3f(box.x1 + 0.55f, box.y1 + 0.64f, 0.0f);
395 glEnd();
396 glDisable(GL_LINE_STIPPLE);
397
398 const float scalex = 0.54f / std::max(1.0f, numPoints - 1.0f);
399 const float scaley = 0.54f / maxy;
400
401 for (int team = 0; team < teamHandler->ActiveTeams(); team++) {
402 const CTeam* pteam = teamHandler->Team(team);
403
404 if (pteam->gaia) {
405 continue;
406 }
407
408 glColor4ubv(pteam->color);
409
410 glBegin(GL_LINE_STRIP);
411 for (int a = 0; a < numPoints; ++a) {
412 float value = 0.0f;
413
414 if (dispMode == 1) {
415 value = stats[stat1].values[team][a];
416 } else if (a > 0) {
417 value = (stats[stat1].values[team][a] - stats[stat1].values[team][a - 1]) / TeamStatistics::statsPeriod;
418 }
419
420 glVertex3f(box.x1 + 0.15f + a * scalex, box.y1 + 0.08f + value * scaley, 0.0f);
421 }
422 glEnd();
423
424 if (stat2 != -1) {
425 glLineStipple(3, 0x5555);
426 glEnable(GL_LINE_STIPPLE);
427
428 glBegin(GL_LINE_STRIP);
429 for (int a = 0; a < numPoints; ++a) {
430 float value = 0;
431 if (dispMode == 1) {
432 value = stats[stat2].values[team][a];
433 } else if (a > 0) {
434 value = (stats[stat2].values[team][a]-stats[stat2].values[team][a-1]) / TeamStatistics::statsPeriod;
435 }
436
437 glVertex3f(box.x1+0.15f+a*scalex, box.y1+0.08f+value*scaley, 0);
438 }
439 glEnd();
440
441 glDisable(GL_LINE_STIPPLE);
442 }
443 }
444 }
445 }
446
GetTooltip(int x,int y)447 std::string CEndGameBox::GetTooltip(int x, int y)
448 {
449 if (!enabled) {
450 return "";
451 }
452
453 const float mx = MouseX(x);
454
455 if (dispMode == 0) {
456 if ((mx > box.x1 + 0.02f) && (mx < box.x1 + 0.1f * 6)) {
457 static const std::string tips[] = {
458 "Player Name",
459 "Mouse clicks per minute",
460 "Mouse movement in pixels per minute",
461 "Keyboard presses per minute",
462 "Unit commands per minute",
463 "Average command size (units affected per command)"
464 };
465
466 return tips[int((mx - box.x1 - 0.01f) * 10)];
467 }
468 }
469
470 return "No tooltip defined";
471 }
472
FillTeamStats()473 void CEndGameBox::FillTeamStats()
474 {
475 stats.push_back(Stat(""));
476 stats.push_back(Stat("Metal used"));
477 stats.push_back(Stat("Energy used"));
478 stats.push_back(Stat("Metal produced"));
479 stats.push_back(Stat("Energy produced"));
480
481 stats.push_back(Stat("Metal excess"));
482 stats.push_back(Stat("Energy excess"));
483
484 stats.push_back(Stat("Metal received"));
485 stats.push_back(Stat("Energy received"));
486
487 stats.push_back(Stat("Metal sent"));
488 stats.push_back(Stat("Energy sent"));
489
490 stats.push_back(Stat("Metal stored"));
491 stats.push_back(Stat("Energy stored"));
492
493 stats.push_back(Stat("Active Units"));
494 stats.push_back(Stat("Units killed"));
495
496 stats.push_back(Stat("Units produced"));
497 stats.push_back(Stat("Units died"));
498
499 stats.push_back(Stat("Units received"));
500 stats.push_back(Stat("Units sent"));
501 stats.push_back(Stat("Units captured"));
502 stats.push_back(Stat("Units stolen"));
503
504 stats.push_back(Stat("Damage Dealt"));
505 stats.push_back(Stat("Damage Received"));
506
507 for (int team = 0; team < teamHandler->ActiveTeams(); team++) {
508 const CTeam* pteam = teamHandler->Team(team);
509
510 if (pteam->gaia) {
511 continue;
512 }
513
514 for (std::list<CTeam::Statistics>::const_iterator si = pteam->statHistory.begin(); si != pteam->statHistory.end(); ++si) {
515 stats[0].AddStat(team, 0);
516
517 stats[1].AddStat(team, si->metalUsed);
518 stats[2].AddStat(team, si->energyUsed);
519 stats[3].AddStat(team, si->metalProduced);
520 stats[4].AddStat(team, si->energyProduced);
521
522 stats[5].AddStat(team, si->metalExcess);
523 stats[6].AddStat(team, si->energyExcess);
524
525 stats[7].AddStat(team, si->metalReceived);
526 stats[8].AddStat(team, si->energyReceived);
527
528 stats[9].AddStat(team, si->metalSent);
529 stats[10].AddStat(team, si->energySent);
530
531 stats[11].AddStat(team, si->metalProduced+si->metalReceived - (si->metalUsed + si->metalSent+si->metalExcess) );
532 stats[12].AddStat(team, si->energyProduced+si->energyReceived - (si->energyUsed + si->energySent+si->energyExcess) );
533
534 stats[13].AddStat(team, si->unitsProduced+si->unitsReceived + si->unitsCaptured - (si->unitsDied + si->unitsSent + si->unitsOutCaptured) );
535 stats[14].AddStat(team, si->unitsKilled);
536
537 stats[15].AddStat(team, si->unitsProduced);
538 stats[16].AddStat(team, si->unitsDied);
539
540 stats[17].AddStat(team, si->unitsReceived);
541 stats[18].AddStat(team, si->unitsSent);
542 stats[19].AddStat(team, si->unitsCaptured);
543 stats[20].AddStat(team, si->unitsOutCaptured);
544
545 stats[21].AddStat(team, si->damageDealt);
546 stats[22].AddStat(team, si->damageReceived);
547 }
548 }
549 }
550