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