1 /* bzflag
2 * Copyright (c) 1993-2021 Tim Riker
3 *
4 * This package is free software; you can redistribute it and/or
5 * modify it under the terms of the license found in the file
6 * named COPYING that should have accompanied this file.
7 *
8 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
9 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
10 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
11 */
12
13 /* interface header */
14 #include "ScoreboardRenderer.h"
15
16 /* common implementation headers */
17 #include "Bundle.h"
18 #include "Team.h"
19 #include "FontManager.h"
20 #include "BZDBCache.h"
21 #include "OpenGLGState.h"
22 #include "TextUtils.h"
23 #include "TimeKeeper.h"
24
25 /* local implementation headers */
26 #include "LocalPlayer.h"
27 #include "World.h"
28 #include "sound.h"
29
30 // because of the 'player' crap, we can't #include "Roaming.h" easily
31 extern Player* getRoamTargetTank();
32
33 #define DEBUG_SHOWRATIOS 1
34
35 std::string ScoreboardRenderer::scoreSpacingLabel("88% 8888 888-888 [88]");
36 std::string ScoreboardRenderer::scoreLabel("Score");
37 std::string ScoreboardRenderer::killSpacingLabel("888~888 Hunt->");
38 std::string ScoreboardRenderer::killLabel(" Kills");
39 std::string ScoreboardRenderer::teamScoreSpacingLabel("88 (888-888) 88");
40 std::string ScoreboardRenderer::teamCountSpacingLabel("888");
41 std::string ScoreboardRenderer::playerLabel("Player");
42
43 // NOTE: order of sort labels must match SORT_ consts
44 const char* ScoreboardRenderer::sortLabels[] =
45 {
46 "[Score]",
47 "[Normalized Score]",
48 "[Reverse Score]",
49 "[Callsign]",
50 "[Team Kills]",
51 "[TK ratio]",
52 "[Team]",
53 "[1on1]",
54 NULL
55 };
56
57 int ScoreboardRenderer::sortMode = 0;
58
59 bool ScoreboardRenderer::alwaysShowTeamScore = 0;
60
61
ScoreboardRenderer()62 ScoreboardRenderer::ScoreboardRenderer() :
63 winX(),
64 winY(),
65 winWidth (0.0),
66 winHeight(),
67 dim(false),
68 huntPosition(0),
69 huntSelectEvent(false),
70 huntPositionEvent(0),
71 huntState(HUNT_NONE),
72 huntAddMode(false),
73 teamScoreYVal(0.0f),
74 roaming(false),
75 minorFontFace(),
76 minorFontSize(),
77 labelsFontFace(),
78 labelsFontSize(),
79 scoreLabelWidth(),
80 killsLabelWidth(),
81 teamScoreLabelWidth(),
82 teamCountLabelWidth(),
83 huntArrowWidth(),
84 huntPlusesWidth(),
85 huntedArrowWidth(),
86 tkWarnRatio(),
87 numHunted(0)
88 {
89 // initialize message color (white)
90 messageColor[0] = 1.0f;
91 messageColor[1] = 1.0f;
92 messageColor[2] = 1.0f;
93 sortMode = BZDB.getIntClamped("scoreboardSort", 0, SORT_MAXNUM);
94 alwaysShowTeamScore = (BZDB.getIntClamped("alwaysShowTeamScore", 0, 1) != 0);
95 }
96
97
98 /* Set window size and location to be used to render the scoreboard.
99 * Values are relative to .... when render() is invoked.
100 */
setWindowSize(float x,float y,float width,float height)101 void ScoreboardRenderer::setWindowSize (float x, float y, float width, float height)
102 {
103 winX = x;
104 winY = y;
105 winWidth = width;
106 winHeight = height;
107 setMinorFontSize (winHeight);
108 setLabelsFontSize (winHeight);
109 }
110
111
112
~ScoreboardRenderer()113 ScoreboardRenderer::~ScoreboardRenderer()
114 {
115 // no destruction needed
116 }
117
118
getSortLabels()119 const char **ScoreboardRenderer::getSortLabels ()
120 {
121 return sortLabels;
122 }
123
124
setSort(int _sortby)125 void ScoreboardRenderer::setSort (int _sortby)
126 {
127 sortMode = _sortby;
128 BZDB.setInt ("scoreboardSort", sortMode);
129 }
130
getSort()131 int ScoreboardRenderer::getSort ()
132 {
133 return sortMode;
134 }
135
setAlwaysTeamScore(bool _onoff)136 void ScoreboardRenderer::setAlwaysTeamScore (bool _onoff)
137 {
138 alwaysShowTeamScore = _onoff;
139 BZDB.set ("alwaysShowTeamScores", _onoff ? "1" : "0");
140 }
141
getAlwaysTeamScore()142 bool ScoreboardRenderer::getAlwaysTeamScore ()
143 {
144 return alwaysShowTeamScore;
145 }
146
setMinorFontSize(float height)147 void ScoreboardRenderer::setMinorFontSize(float height)
148 {
149 FontManager &fm = FontManager::instance();
150 minorFontFace = fm.getFaceID(BZDB.get("consoleFont"));
151
152 switch (static_cast<int>(BZDB.eval("scorefontsize")))
153 {
154 case 0: // auto
155 {
156 const float s = height / 72.0f;
157 minorFontSize = floorf(s);
158 break;
159 }
160 case 1: // tiny
161 minorFontSize = 6;
162 break;
163 case 2: // small
164 minorFontSize = 8;
165 break;
166 case 3: // medium
167 minorFontSize = 12;
168 break;
169 case 4: // big
170 minorFontSize = 16;
171 break;
172 }
173
174 huntArrowWidth = fm.getStrLength(minorFontFace, minorFontSize, "->");
175 huntPlusesWidth = fm.getStrLength(minorFontFace, minorFontSize, "@>");
176 huntedArrowWidth = fm.getStrLength(minorFontFace, minorFontSize, "Hunt->");
177 scoreLabelWidth = fm.getStrLength(minorFontFace, minorFontSize, scoreSpacingLabel);
178 killsLabelWidth = fm.getStrLength(minorFontFace, minorFontSize, killSpacingLabel);
179 teamScoreLabelWidth = fm.getStrLength(minorFontFace, minorFontSize, teamScoreSpacingLabel);
180 teamCountLabelWidth = fm.getStrLength(minorFontFace, minorFontSize, teamCountSpacingLabel);
181 const float spacing = fm.getStrLength(minorFontFace, minorFontSize, " ");
182 scoreLabelWidth += spacing;
183 killsLabelWidth += spacing;
184 }
185
186
setLabelsFontSize(float height)187 void ScoreboardRenderer::setLabelsFontSize(float height)
188 {
189 const float s = height / 96.0f;
190 FontManager &fm = FontManager::instance();
191 labelsFontFace = fm.getFaceID(BZDB.get("consoleFont"));
192 labelsFontSize = floorf(s);
193 }
194
195
setDim(bool _dim)196 void ScoreboardRenderer::setDim(bool _dim)
197 {
198 dim = _dim;
199 }
200
201
202 static const float dimFactor = 0.2f;
203
hudColor3fv(const GLfloat * c)204 void ScoreboardRenderer::hudColor3fv(const GLfloat* c)
205 {
206 if (dim)
207 glColor3f(dimFactor * c[0], dimFactor * c[1], dimFactor * c[2]);
208 else
209 glColor3fv(c);
210 }
211
212
213
214
exitSelectState(void)215 void ScoreboardRenderer::exitSelectState (void)
216 {
217 playLocalSound(SFX_HUNT_SELECT);
218 if (numHunted > 0)
219 setHuntState(HUNT_ENABLED);
220 else
221 setHuntState(HUNT_NONE);
222 }
223
224
225
226
render(bool forceDisplay)227 void ScoreboardRenderer::render(bool forceDisplay)
228 {
229 FontManager &fm = FontManager::instance();
230 if (dim)
231 fm.setOpacity(dimFactor);
232
233 if (BZDB.isTrue("displayScore") || forceDisplay)
234 {
235 OpenGLGState::resetState();
236 renderScoreboard();
237 }
238 else
239 {
240 if (getHuntState() == HUNT_SELECTING) // 'S' pressed while selecting ...
241 exitSelectState ();
242 if (BZDB.isTrue("alwaysShowTeamScores") && World::getWorld()->allowTeams())
243 {
244 OpenGLGState::resetState();
245 renderTeamScores(winWidth, winY,
246 FontManager::instance().getStrHeight(minorFontFace, minorFontSize, " "));
247 }
248 }
249
250 if (dim)
251 fm.setOpacity(1.0f);
252 }
253
254
teamScoreCompare(const void * _c,const void * _d)255 int ScoreboardRenderer::teamScoreCompare(const void* _c, const void* _d)
256 {
257 Team* c = World::getWorld()->getTeams()+*(const int*)_c;
258 Team* d = World::getWorld()->getTeams()+*(const int*)_d;
259 return (d->getWins()-d->getLosses()) - (c->getWins()-c->getLosses());
260 }
261
262 // invoked by playing.cxx when 'prev' is pressed
setHuntPrevEvent()263 void ScoreboardRenderer::setHuntPrevEvent()
264 {
265 huntPositionEvent = -1;
266 --huntPosition;
267 }
268
269 // invoked by playing.cxx when 'next' is pressed
setHuntNextEvent()270 void ScoreboardRenderer::setHuntNextEvent()
271 {
272 huntPositionEvent = 1;
273 ++huntPosition;
274 }
275
276 // invoked by playing.cxx when select (fire) is pressed
setHuntSelectEvent()277 void ScoreboardRenderer::setHuntSelectEvent ()
278 {
279 huntSelectEvent = true;
280 }
281
282 // invoked by clientCommands.cxx when '7' or 'U' is pressed
huntKeyEvent(bool isAdd)283 void ScoreboardRenderer::huntKeyEvent (bool isAdd)
284 {
285 if (getHuntState() == HUNT_ENABLED)
286 {
287 if (isAdd)
288 {
289 setHuntState(HUNT_SELECTING);
290 playLocalSound(SFX_HUNT_SELECT);
291 }
292 else
293 {
294 setHuntState(HUNT_NONE);
295 playLocalSound(SFX_HUNT);
296 }
297 huntAddMode = isAdd;
298
299 }
300 else if (getHuntState() == HUNT_SELECTING)
301 exitSelectState ();
302
303 else
304 {
305 setHuntState(HUNT_SELECTING);
306 playLocalSound(SFX_HUNT_SELECT);
307 huntAddMode = isAdd;
308 if (!BZDB.isTrue("displayScore"))
309 BZDB.set("displayScore", "1");
310 }
311 }
312
313
setHuntState(int _huntState)314 void ScoreboardRenderer::setHuntState (int _huntState)
315 {
316 if (huntState == _huntState)
317 return;
318 if (_huntState != HUNT_SELECTING)
319 huntAddMode = false;
320 if (_huntState==HUNT_NONE)
321 clearHuntedTanks();
322 else if (_huntState==HUNT_SELECTING)
323 huntPosition = 0;
324 huntState = _huntState;
325 }
326
327
getHuntState() const328 int ScoreboardRenderer::getHuntState() const
329 {
330 return huntState;
331 }
332
333
334 // invoked when joining a server
huntReset()335 void ScoreboardRenderer::huntReset()
336 {
337 huntState = HUNT_NONE;
338 numHunted = 0;
339 }
340
renderTeamScores(float x,float y,float dy)341 void ScoreboardRenderer::renderTeamScores (float x, float y, float dy)
342 {
343 // print teams sorted by score
344 int teams[NumTeams];
345 int teamCount = 0;
346 int i;
347 float xn, xl;
348 std::string label;
349
350 if (teamScoreYVal > 0.0)
351 y = teamScoreYVal;
352
353 if (World::getWorld()->allowRabbit())
354 return;
355
356 Bundle *bdl = BundleMgr::getCurrentBundle();
357 FontManager &fm = FontManager::instance();
358 hudColor3fv(messageColor);
359
360 label = bdl->getLocalString("Team Score");
361 xl = xn = x - teamScoreLabelWidth - teamCountLabelWidth;
362 fm.drawString(xl, y, 0, minorFontFace, minorFontSize, label);
363
364 for (i = RedTeam; i < NumTeams; i++)
365 {
366 if (!Team::isColorTeam(TeamColor(i))) continue;
367 const Team* team = World::getWorld()->getTeams() + i;
368 if (team->size == 0) continue;
369 teams[teamCount++] = i;
370 }
371 qsort(teams, teamCount, sizeof(int), teamScoreCompare);
372 y -= dy;
373
374 char score[44];
375 for (i = 0 ; i < teamCount; i++)
376 {
377 Team& team = World::getWorld()->getTeam(teams[i]);
378 sprintf(score, "%3d (%3d-%-3d) %3d", team.getWins() - team.getLosses(), team.getWins(), team.getLosses(), team.size);
379 hudColor3fv(Team::getTankColor((TeamColor)teams[i]));
380 fm.drawString(xn, y, 0, minorFontFace, minorFontSize, score);
381 y -= dy;
382 }
383 }
384
385
386 // not used yet - new feature coming
renderCtfFlags()387 void ScoreboardRenderer::renderCtfFlags ()
388 {
389 int i;
390 RemotePlayer* player;
391 const int curMaxPlayers = World::getWorld()->getCurMaxPlayers();
392 char flagColor[200];
393
394 FontManager &fm = FontManager::instance();
395 const float x = winX;
396 const float y = winY;
397 const float dy = fm.getStrHeight(minorFontFace, minorFontSize, " ");
398 float y0 = y - dy;
399
400 hudColor3fv(messageColor);
401 fm.drawString(x, y, 0, minorFontFace, minorFontSize, "Team Flags");
402
403 fm.setDimFactor(dimFactor);
404
405 for (i=0; i < curMaxPlayers; i++)
406 {
407 if ((player = World::getWorld()->getPlayer(i)))
408 {
409 FlagType* flagd = player->getFlag();
410 TeamColor teamIndex = player->getTeam();
411 if (flagd!=Flags::Null && flagd->flagTeam != NoTeam) // if player has team flag ...
412 {
413 std::string playerInfo = Team::getAnsiCode(flagd->flagTeam);
414 snprintf(flagColor, 200, "%-12s", flagd->flagName.c_str());
415 playerInfo += flagColor;
416 playerInfo += Team::getAnsiCode(teamIndex);
417 playerInfo += player->getCallSign();
418
419 fm.drawString(x, y0, 0, minorFontFace, minorFontSize, playerInfo);
420 y0 -= dy;
421 }
422 }
423 }
424 renderTeamScores (winWidth, y, dy);
425 }
426
427
clearHuntedTanks()428 void ScoreboardRenderer::clearHuntedTanks ()
429 {
430 World *world = World::getWorld();
431 if (!world)
432 return;
433 const int curMaxPlayers = world->getCurMaxPlayers();
434 Player *p;
435 for (int i=0; i<curMaxPlayers; i++)
436 {
437 if ((p = world->getPlayer(i)))
438 p->setHunted (false);
439 }
440 numHunted = 0;
441 }
442
443
renderScoreboard(void)444 void ScoreboardRenderer::renderScoreboard(void)
445 {
446 int i=0;
447 int numPlayers;
448 int mottoLen;
449 Player** players;
450 Player* player;
451 bool haveObs = false;
452
453 if ( (players = newSortedList (sortMode, true, &numPlayers)) == NULL)
454 return;
455
456 LocalPlayer* myTank = LocalPlayer::getMyTank();
457 Bundle *bdl = BundleMgr::getCurrentBundle();
458 FontManager &fm = FontManager::instance();
459
460 const float x1 = winX;
461 const float x2 = x1 + scoreLabelWidth;
462 const float x3 = x2 + killsLabelWidth;
463 const float y0 = winY;
464 hudColor3fv(messageColor);
465
466 std::string psLabel = bdl->getLocalString(playerLabel);
467 if (sortMode != SORT_SCORE)
468 {
469 psLabel += " ";
470 psLabel += sortLabels[sortMode];
471 }
472 fm.drawString(x1, y0, 0, minorFontFace, minorFontSize, bdl->getLocalString(scoreLabel));
473 fm.drawString(x2, y0, 0, minorFontFace, minorFontSize, bdl->getLocalString(killLabel));
474 fm.drawString(x3, y0, 0, minorFontFace, minorFontSize, psLabel);
475 const float dy = fm.getStrHeight(minorFontFace, minorFontSize, " ");
476 float y = y0 - dy;
477
478 // make room for the status marker
479 const float xs = x3 - fm.getStrLength(minorFontFace, minorFontSize, "+|");
480
481 if (huntState == HUNT_SELECTING)
482 {
483 std::string huntStr = ColorStrings[YellowColor];
484 huntStr += ColorStrings[PulsatingColor];
485 huntStr += " *SEL*";
486 fm.drawString(xs - huntedArrowWidth, y0, 0, minorFontFace, minorFontSize, huntStr);
487 }
488
489 // grab the tk warning ratio
490 tkWarnRatio = BZDB.eval("tkwarnratio");
491
492 if (huntState == HUNT_SELECTING)
493 {
494 if (numPlayers<1 || (numPlayers==1 && players[0]==myTank))
495 setHuntState (HUNT_NONE);
496 else
497 {
498 if (players[huntPosition] == myTank)
499 {
500 if (huntPositionEvent < 0)
501 --huntPosition;
502 else
503 ++huntPosition;
504 }
505 if (huntPosition>=numPlayers)
506 {
507 huntPosition = 0;
508 if (players[0] == myTank)
509 huntPosition = 1;
510 }
511 if (huntPosition<0)
512 {
513 huntPosition = numPlayers-1;
514 if (players[huntPosition] == myTank)
515 --huntPosition;
516 }
517 if (huntSelectEvent) // if 'fire' was pressed ...
518 {
519 if (!huntAddMode)
520 clearHuntedTanks ();
521 if (huntAddMode && players[huntPosition]->isHunted()) // UNselect
522 {
523 players[huntPosition]->setHunted(false);
524 if (--numHunted != 0)
525 playLocalSound(SFX_HUNT_SELECT);
526 }
527 else // else select
528 {
529 players[huntPosition]->setHunted(true);
530 if (++numHunted == 1)
531 playLocalSound(SFX_HUNT);
532 else
533 playLocalSound(SFX_HUNT_SELECT);
534 }
535 huntState = HUNT_ENABLED;
536 }
537 }
538 }
539
540 mottoLen = BZDB.getIntClamped ("mottoDispLen", 0, 128);
541 huntSelectEvent = false;
542 huntPositionEvent = 0;
543 numHunted = 0;
544
545 const int maxLines = BZDB.evalInt("maxScoreboardLines");
546 int lines = 0;
547 int hiddenLines = 0;
548 while ((player = players[i]) != NULL)
549 {
550 if ((maxLines > 0) && (lines >= maxLines))
551 hiddenLines++;
552 else
553 {
554 if (player->isHunted())
555 ++numHunted;
556 if (player->getTeam()==ObserverTeam && !haveObs)
557 {
558 y -= dy;
559 haveObs = true;
560 }
561 if (huntState==HUNT_SELECTING && i==huntPosition)
562 drawPlayerScore(player, x1, x2, x3, xs, (float)y, mottoLen, true);
563 else
564 drawPlayerScore(player, x1, x2, x3, xs, (float)y, mottoLen, false);
565 y -= dy;
566 }
567 ++i;
568 ++lines;
569 }
570
571 if (hiddenLines > 0)
572 {
573 char buf[64];
574 snprintf(buf, sizeof(buf), "...%i...", hiddenLines);
575 fm.drawString(x1, y, 0, minorFontFace, minorFontSize, buf);
576 fm.drawString(x2, y, 0, minorFontFace, minorFontSize, buf);
577 fm.drawString(x3, y, 0, minorFontFace, minorFontSize, buf);
578 }
579
580 if (huntState==HUNT_ENABLED && !numHunted)
581 {
582 huntState = HUNT_NONE; // last hunted player must have left the game
583 huntAddMode = false;
584 playLocalSound(SFX_HUNT);
585 }
586
587 delete[] players;
588
589 if (World::getWorld()->allowTeams())
590 renderTeamScores(winWidth, y0, dy);
591 }
592
593
stringAppendNormalized(std::string * s,float n)594 void ScoreboardRenderer::stringAppendNormalized (std::string *s, float n)
595 {
596 char fmtbuf[10];
597 sprintf (fmtbuf, " [%4.2f]", n);
598 *s += fmtbuf;
599 }
600
601
drawRoamTarget(float _x0,float _y0,float _x1,float UNUSED (_y1))602 void ScoreboardRenderer::drawRoamTarget(float _x0, float _y0,
603 float _x1, float UNUSED(_y1))
604 {
605 static const TimeKeeper startTime = TimeKeeper::getCurrent();
606
607 const float x0 = floorf(_x0) + 0.5f;
608 const float y0 = floorf(_y0) + 0.5f;
609 const float x1 = floorf(_x1) + 0.5f;
610 const float y1 = y0 + 1.0f;
611
612 const float black[4] =
613 {
614 0.0f * (dim ? dimFactor : 1.0f),
615 0.0f * (dim ? dimFactor : 1.0f),
616 0.0f * (dim ? dimFactor : 1.0f),
617 1.0f
618 };
619 const float white[4] =
620 {
621 1.0f * (dim ? dimFactor : 1.0f),
622 1.0f * (dim ? dimFactor : 1.0f),
623 1.0f * (dim ? dimFactor : 1.0f),
624 1.0f
625 };
626 const float* c0 = black;
627 const float* c1 = white;
628
629 const double diff = (TimeKeeper::getCurrent() - startTime);
630 if (fmod(diff, 0.5) > 0.25)
631 {
632 c0 = white;
633 c1 = black;
634 }
635
636 glPushAttrib(GL_ALL_ATTRIB_BITS);
637 glDisable(GL_BLEND);
638 glDisable(GL_LIGHTING);
639 glDisable(GL_TEXTURE_2D);
640 glBegin(GL_LINES);
641 glColor4fv(c0);
642 glVertex2f(x0, y1);
643 glVertex2f(x1, y1);
644 glColor4fv(c1);
645 glVertex2f(x0, y0);
646 glVertex2f(x1, y0);
647 glEnd();
648 glPopAttrib();
649 }
650
651
drawPlayerScore(const Player * player,float x1,float x2,float x3,float xs,float y,int mottoLen,bool huntCursor)652 void ScoreboardRenderer::drawPlayerScore(const Player* player,
653 float x1, float x2, float x3, float xs, float y,
654 int mottoLen, bool huntCursor)
655 {
656 // score
657 char score[40], kills[40];
658
659 bool highlightTKratio = false;
660 if (tkWarnRatio > 0.0)
661 {
662 if (((player->getWins() > 0) && (player->getTKRatio() > tkWarnRatio)) ||
663 ((player->getWins() == 0) && (player->getTeamKills() >= 3)))
664 highlightTKratio = true;
665 }
666
667 if (World::getWorld()->allowRabbit())
668 {
669 sprintf(score, "%2d%% %4d %3d-%-3d%s[%2d]", player->getRabbitScore(),
670 player->getScore(), player->getWins(), player->getLosses(),
671 highlightTKratio ? ColorStrings[CyanColor].c_str() : "",
672 player->getTeamKills());
673 }
674 else if (World::getWorld()->allowTeams())
675 {
676 sprintf(score, "%4d %4d-%-4d%s[%2d]", player->getScore(),
677 player->getWins(), player->getLosses(),
678 highlightTKratio ? ColorStrings[CyanColor].c_str() : "",
679 player->getTeamKills());
680 }
681 else
682 {
683 sprintf(score, "%4d %4d-%-4d%s", player->getScore(),
684 player->getWins(), player->getLosses(),
685 highlightTKratio ? ColorStrings[CyanColor].c_str() : "");
686 }
687
688 // kills
689 if (LocalPlayer::getMyTank() != player)
690 sprintf(kills, "%3d~%-3d", player->getLocalWins(), player->getLocalLosses());
691 else
692 sprintf(kills, "%4d", player->getSelfKills());
693
694
695 // team color
696 TeamColor teamIndex = player->getTeam();
697 std::string teamColor;
698 if (player->getId() < 200)
699 teamColor = Team::getAnsiCode(teamIndex);
700 else
701 {
702 teamColor = ColorStrings[CyanColor]; // replay observers
703 }
704
705 // authentication status
706 std::string statusInfo;
707 if (BZDBCache::colorful)
708 statusInfo += ColorStrings[CyanColor];
709 else
710 statusInfo += teamColor;
711 if (player->isAdmin())
712 statusInfo += '@';
713 else if (player->isVerified())
714 statusInfo += '+';
715 else if (player->isRegistered())
716 statusInfo += '-';
717 else
718 {
719 statusInfo = ""; // don't print
720 }
721
722 std::string playerInfo;
723 // team color
724 playerInfo += teamColor;
725 // Slot number only for admins (playerList perm check, in case they have
726 // hideAdmin)
727 LocalPlayer* localPlayer = LocalPlayer::getMyTank();
728 if (localPlayer->isAdmin() || localPlayer->hasPlayerList())
729 {
730 char slot[10];
731 sprintf(slot, "%3d",player->getId());
732 playerInfo += slot;
733 playerInfo += " - ";
734 }
735
736 if (roaming && BZDB.isTrue("showVelocities"))
737 {
738 float vel[3] = {0};
739 memcpy(vel,player->getVelocity(),sizeof(float)*3);
740
741 float linSpeed = sqrt(vel[0]*vel[0]+vel[1]*vel[1]);
742
743 float badFactor = 1.5f;
744 if (linSpeed > player->getMaxSpeed()*badFactor)
745 playerInfo += ColorStrings[RedColor];
746 if (linSpeed > player->getMaxSpeed())
747 playerInfo += ColorStrings[YellowColor];
748 else if (linSpeed < 0.0001f)
749 playerInfo += ColorStrings[GreyColor];
750 else
751 playerInfo += ColorStrings[WhiteColor];
752 playerInfo += TextUtils::format("%5.2f ",linSpeed);
753 playerInfo += teamColor;
754 }
755
756 // callsign
757 playerInfo += player->getCallSign();
758
759 // motto in parentheses
760 if (player->getMotto()[0] != '\0' && mottoLen>0)
761 {
762 playerInfo += " (";
763 playerInfo += TextUtils::str_trunc_continued (player->getMotto(), mottoLen);
764 playerInfo += ")";
765 }
766 // carried flag
767 bool coloredFlag = false;
768 FlagType* flagd = player->getFlag();
769 if (flagd != Flags::Null)
770 {
771 // color special flags
772 if (BZDBCache::colorful)
773 {
774 if ((flagd == Flags::ShockWave)
775 || (flagd == Flags::Genocide)
776 || (flagd == Flags::Laser)
777 || (flagd == Flags::GuidedMissile))
778 playerInfo += ColorStrings[WhiteColor];
779 else if (flagd->flagTeam != NoTeam)
780 {
781 // use team color for team flags
782 playerInfo += Team::getAnsiCode(flagd->flagTeam);
783 }
784 coloredFlag = true;
785 }
786 playerInfo += "/";
787 playerInfo += (flagd->endurance == FlagNormal ? flagd->flagName : flagd->flagAbbv);
788 // back to original color
789 if (coloredFlag)
790 playerInfo += teamColor;
791 }
792
793 // status
794 if (player->isPaused())
795 playerInfo += "[p]";
796 else if (player->isNotResponding())
797 playerInfo += "[nr]";
798 else if (player->isAutoPilot())
799 playerInfo += "[auto]";
800
801 #if DEBUG_SHOWRATIOS
802 if (player->getTeam() != ObserverTeam)
803 {
804 if (sortMode == SORT_NORMALIZED)
805 stringAppendNormalized (&playerInfo, player->getNormalizedScore());
806 else if (sortMode == SORT_MYRATIO && LocalPlayer::getMyTank() != player)
807 stringAppendNormalized (&playerInfo, player->getLocalNormalizedScore());
808 else if (sortMode == SORT_TKRATIO)
809 stringAppendNormalized (&playerInfo, player->getTKRatio());
810 }
811 #endif
812
813 FontManager &fm = FontManager::instance();
814 fm.setDimFactor(dimFactor);
815
816 if (player == getRoamTargetTank())
817 {
818 const float w = fm.getStrLength(minorFontFace, minorFontSize, playerInfo);
819 const float h = fm.getStrHeight(minorFontFace, minorFontSize, playerInfo);
820 drawRoamTarget(x3, y, x3 + w, y + h);
821 }
822
823 // draw
824 if (player->getTeam() != ObserverTeam)
825 {
826 hudColor3fv(Team::getTankColor(teamIndex));
827 fm.drawString(x1, y, 0, minorFontFace, minorFontSize, score);
828 hudColor3fv(Team::getTankColor(teamIndex));
829 fm.drawString(x2, y, 0, minorFontFace, minorFontSize, kills);
830 }
831 fm.drawString(x3, y, 0, minorFontFace, minorFontSize, playerInfo);
832 if (statusInfo.size() > 0)
833 fm.drawString(xs, y, 0, minorFontFace, minorFontSize, statusInfo);
834 if (BZDB.isTrue("debugHud"))
835 {
836 printf ("playerInfo: %s\n", playerInfo.c_str()); // FIXME
837 }
838
839 // draw huntEnabled status
840 if (player->isHunted())
841 {
842 std::string huntStr = ColorStrings[WhiteColor];
843 huntStr += "Hunt->";
844 fm.drawString(xs - huntedArrowWidth, y, 0, minorFontFace, minorFontSize,
845 huntStr.c_str());
846 }
847 else if (huntCursor && !huntAddMode)
848 {
849 std::string huntStr = ColorStrings[WhiteColor];
850 huntStr += ColorStrings[PulsatingColor];
851 huntStr += "->";
852 fm.drawString(xs - huntArrowWidth, y, 0, minorFontFace, minorFontSize,
853 huntStr.c_str());
854 }
855 if (huntCursor && huntAddMode)
856 {
857 std::string huntStr = ColorStrings[WhiteColor];
858 huntStr += ColorStrings[PulsatingColor];
859 huntStr += "@>";
860 fm.drawString(xs - huntPlusesWidth, y, 0, minorFontFace, minorFontSize,
861 huntStr.c_str());
862 }
863 }
864
865
866 // get current 'leader' (NULL if no player)
getLeader(std::string * label)867 Player* ScoreboardRenderer::getLeader(std::string *label)
868 {
869 int sortType=sortMode;
870
871 if (sortMode==SORT_CALLSIGN || sortMode==SORT_MYRATIO || sortMode==SORT_TEAM)
872 sortType = SORT_SCORE;
873 if (label != NULL)
874 {
875 if (sortMode==SORT_TKS)
876 *label = "TK Leader ";
877 else if (sortMode==SORT_TKRATIO)
878 *label = "TK Ratio Leader ";
879 else
880 *label = "Leader ";
881 }
882
883 Player** list = newSortedList (sortType, true);
884
885 Player* top;
886 if (!list)
887 top = NULL;
888 else
889 top = list[0];
890
891 delete[] list;
892
893 if (top==NULL || top->getTeam()==ObserverTeam)
894 return NULL;
895 return top;
896 }
897
898 /************************ Sort logic follows .... **************************/
899
900 struct st_playersort
901 {
902 Player *player;
903 int i1;
904 int i2;
905 const char *cp;
906 };
907 typedef struct st_playersort sortEntry;
908
909
sortCompareCp(const void * _a,const void * _b)910 int ScoreboardRenderer::sortCompareCp(const void* _a, const void* _b)
911 {
912 const sortEntry *a = (const sortEntry *)_a;
913 const sortEntry *b = (const sortEntry *)_b;
914 return strcasecmp (a->cp, b->cp);
915 }
916
917
sortCompareI2(const void * _a,const void * _b)918 int ScoreboardRenderer::sortCompareI2(const void* _a, const void* _b)
919 {
920 const sortEntry *a = (const sortEntry *)_a;
921 const sortEntry *b = (const sortEntry *)_b;
922 if (a->i1 != b->i1 )
923 return b->i1 - a->i1;
924 return b->i2 - a->i2;
925 }
926
927
928 // creates (allocates) a null-terminated array of Player*
newSortedList(int sortType,bool obsLast,int * _numPlayers)929 Player ** ScoreboardRenderer::newSortedList (int sortType, bool obsLast, int *_numPlayers)
930 {
931 LocalPlayer *myTank = LocalPlayer::getMyTank();
932 World *world = World::getWorld();
933
934 if (!myTank || !world)
935 return NULL;
936
937 const int curMaxPlayers = world->getCurMaxPlayers() +1;
938 int i,j;
939 int numPlayers=0;
940 int numObs=0;
941 Player* p;
942 sortEntry* sorter = new sortEntry [curMaxPlayers];
943
944 // fill the array with remote players
945 for (i=0; i<curMaxPlayers-1; i++)
946 {
947 if ((p = world->getPlayer(i)))
948 {
949 if (obsLast && p->getTeam()==ObserverTeam)
950 sorter[curMaxPlayers - (++numObs)].player = p;
951 else
952 sorter[numPlayers++].player = p;
953 }
954 }
955 // add my tank
956 if (obsLast && myTank->getTeam()==ObserverTeam)
957 sorter[curMaxPlayers - (++numObs)].player = myTank;
958 else
959 sorter[numPlayers++].player = myTank;
960
961 // sort players ...
962 if (numPlayers > 0)
963 {
964 for (i=0; i<numPlayers; i++)
965 {
966 p = sorter[i].player;
967 switch (sortType)
968 {
969 case SORT_TKS:
970 sorter[i].i1 = p->getTeamKills();
971 sorter[i].i2 = 0 - (int)(p->getNormalizedScore() * 100000);
972 break;
973 case SORT_TKRATIO:
974 sorter[i].i1 = (int)(p->getTKRatio() * 1000);
975 sorter[i].i2 = 0 - (int)(p->getNormalizedScore() * 100000);
976 break;
977 case SORT_TEAM:
978 sorter[i].i1 = p->getTeam();
979 sorter[i].i2 = (int)(p->getNormalizedScore() * 100000);
980 break;
981 case SORT_MYRATIO:
982 if (p == myTank)
983 sorter[i].i1 = -100001;
984 else
985 sorter[i].i1 = 0 - (int)(p->getLocalNormalizedScore() * 100000);
986 sorter[i].i2 = (int)(p->getNormalizedScore() * 100000);
987 break;
988 case SORT_NORMALIZED:
989 sorter[i].i1 = (int)(p->getNormalizedScore() * 100000);
990 sorter[i].i2 = 0;
991 break;
992 case SORT_CALLSIGN:
993 sorter[i].cp = p->getCallSign();
994 break;
995 default:
996 if (world->allowRabbit())
997 sorter[i].i1 = p->getRabbitScore();
998 else
999 sorter[i].i1 = p->getScore();
1000 sorter[i].i2 = 0;
1001 if (sortType == SORT_REVERSE)
1002 sorter[i].i1 *= -1;
1003 }
1004 }
1005 if (sortType == SORT_CALLSIGN)
1006 qsort (sorter, numPlayers, sizeof(sortEntry), sortCompareCp);
1007 else
1008 qsort (sorter, numPlayers, sizeof(sortEntry), sortCompareI2);
1009 }
1010
1011 // TODO: Sort obs here (by time joined, when that info is available)
1012
1013 Player** players = new Player *[numPlayers + numObs + 1];
1014 for (i=0; i<numPlayers; i++)
1015 players[i] = sorter[i].player;
1016 for (j=curMaxPlayers-numObs; j<curMaxPlayers; j++)
1017 players[i++] = sorter[j].player;
1018 players[i] = NULL;
1019
1020 if (_numPlayers != NULL)
1021 *_numPlayers = numPlayers;
1022
1023 delete[] sorter;
1024 return players;
1025 }
1026
1027
getPlayerList(std::vector<Player * > & players)1028 void ScoreboardRenderer::getPlayerList(std::vector<Player*>& players)
1029 {
1030 players.clear();
1031
1032 int playerCount;
1033 Player** pList = newSortedList(getSort(), true, &playerCount);
1034 if (pList == NULL)
1035 return;
1036 for (int i = 0; i < playerCount; i++)
1037 {
1038 Player* p = pList[i];
1039 if (p && (p->getTeam() != ObserverTeam))
1040 players.push_back(p);
1041 }
1042 delete[] pList;
1043 }
1044
1045
1046 // Local Variables: ***
1047 // mode: C++ ***
1048 // tab-width: 4 ***
1049 // c-basic-offset: 4 ***
1050 // indent-tabs-mode: nil ***
1051 // End: ***
1052 // ex: shiftwidth=4 tabstop=4
1053