]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/scoreboard.qc
Clean up CSQC #includes
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / scoreboard.qc
1 #include "scoreboard.qh"
2
3 float scoreboard_alpha_bg;
4 float scoreboard_alpha_fg;
5 float scoreboard_highlight;
6 float scoreboard_highlight_alpha;
7 float scoreboard_highlight_alpha_self;
8 float scoreboard_alpha_name;
9 float scoreboard_alpha_name_self;
10
11 void drawstringright(vector, string, vector, vector, float, float);
12 void drawstringcenter(vector, string, vector, vector, float, float);
13
14 const float SCOREBOARD_OFFSET = 50;
15
16 // wrapper to put all possible scores titles through gettext
17 string TranslateScoresLabel(string l)
18 {
19         switch(l)
20         {
21                 case "bckills": return CTX(_("SCO^bckills"));
22                 case "bctime": return CTX(_("SCO^bctime"));
23                 case "caps": return CTX(_("SCO^caps"));
24                 case "captime": return CTX(_("SCO^captime"));
25                 case "deaths": return CTX(_("SCO^deaths"));
26                 case "destroyed": return CTX(_("SCO^destroyed"));
27                 case "drops": return CTX(_("SCO^drops"));
28                 case "faults": return CTX(_("SCO^faults"));
29                 case "fckills": return CTX(_("SCO^fckills"));
30                 case "goals": return CTX(_("SCO^goals"));
31                 case "kckills": return CTX(_("SCO^kckills"));
32                 case "kdratio": return CTX(_("SCO^kdratio"));
33                 case "k/d": return CTX(_("SCO^k/d"));
34                 case "kd": return CTX(_("SCO^kd"));
35                 case "kdr": return CTX(_("SCO^kdr"));
36                 case "kills": return CTX(_("SCO^kills"));
37                 case "laps": return CTX(_("SCO^laps"));
38                 case "lives": return CTX(_("SCO^lives"));
39                 case "losses": return CTX(_("SCO^losses"));
40                 case "name": return CTX(_("SCO^name"));
41                 case "sum": return CTX(_("SCO^sum"));
42                 case "nick": return CTX(_("SCO^nick"));
43                 case "objectives": return CTX(_("SCO^objectives"));
44                 case "pickups": return CTX(_("SCO^pickups"));
45                 case "ping": return CTX(_("SCO^ping"));
46                 case "pl": return CTX(_("SCO^pl"));
47                 case "pushes": return CTX(_("SCO^pushes"));
48                 case "rank": return CTX(_("SCO^rank"));
49                 case "returns": return CTX(_("SCO^returns"));
50                 case "revivals": return CTX(_("SCO^revivals"));
51                 case "score": return CTX(_("SCO^score"));
52                 case "suicides": return CTX(_("SCO^suicides"));
53                 case "takes": return CTX(_("SCO^takes"));
54                 case "ticks": return CTX(_("SCO^ticks"));
55                 default: return l;
56         }
57 }
58
59 void HUD_InitScores()
60 {
61         int i, f;
62
63         ps_primary = ps_secondary = ts_primary = ts_secondary = -1;
64         for(i = 0; i < MAX_SCORE; ++i)
65         {
66                 f = (scores_flags[i] & SFL_SORT_PRIO_MASK);
67                 if(f == SFL_SORT_PRIO_PRIMARY)
68                         ps_primary = i;
69                 if(f == SFL_SORT_PRIO_SECONDARY)
70                         ps_secondary = i;
71         }
72         if(ps_secondary == -1)
73                 ps_secondary = ps_primary;
74
75         for(i = 0; i < MAX_TEAMSCORE; ++i)
76         {
77                 f = (teamscores_flags[i] & SFL_SORT_PRIO_MASK);
78                 if(f == SFL_SORT_PRIO_PRIMARY)
79                         ts_primary = i;
80                 if(f == SFL_SORT_PRIO_SECONDARY)
81                         ts_secondary = i;
82         }
83         if(ts_secondary == -1)
84                 ts_secondary = ts_primary;
85
86         Cmd_HUD_SetFields(0);
87 }
88
89 float SetTeam(entity pl, float Team);
90 //float lastpnum;
91 void HUD_UpdatePlayerTeams()
92 {
93         float Team;
94         entity pl, tmp;
95         float num;
96
97         num = 0;
98         for(pl = players.sort_next; pl; pl = pl.sort_next)
99         {
100                 num += 1;
101                 Team = GetPlayerColor(pl.sv_entnum);
102                 if(SetTeam(pl, Team))
103                 {
104                         tmp = pl.sort_prev;
105                         HUD_UpdatePlayerPos(pl);
106                         if(tmp)
107                                 pl = tmp;
108                         else
109                                 pl = players.sort_next;
110                 }
111         }
112         /*
113         if(num != lastpnum)
114                 print(strcat("PNUM: ", ftos(num), "\n"));
115         lastpnum = num;
116         */
117 }
118
119 int HUD_CompareScore(float vl, float vr, int f)
120 {
121         if(f & SFL_ZERO_IS_WORST)
122         {
123                 if(vl == 0 && vr != 0)
124                         return 1;
125                 if(vl != 0 && vr == 0)
126                         return 0;
127         }
128         if(vl > vr)
129                 return IS_INCREASING(f);
130         if(vl < vr)
131                 return IS_DECREASING(f);
132         return -1;
133 }
134
135 float HUD_ComparePlayerScores(entity left, entity right)
136 {
137         float vl, vr, r;
138         vl = GetPlayerColor(left.sv_entnum);
139         vr = GetPlayerColor(right.sv_entnum);
140
141         if(!left.gotscores)
142                 vl = NUM_SPECTATOR;
143         if(!right.gotscores)
144                 vr = NUM_SPECTATOR;
145
146         if(vl > vr)
147                 return true;
148         if(vl < vr)
149                 return false;
150
151         if(vl == NUM_SPECTATOR)
152         {
153                 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
154                 // no other sorting
155                 if(!left.gotscores && right.gotscores)
156                         return true;
157                 return false;
158         }
159
160         r = HUD_CompareScore(left.scores[ps_primary], right.scores[ps_primary], scores_flags[ps_primary]);
161         if (r >= 0)
162                 return r;
163
164         r = HUD_CompareScore(left.scores[ps_secondary], right.scores[ps_secondary], scores_flags[ps_secondary]);
165         if (r >= 0)
166                 return r;
167
168         int i;
169         for(i = 0; i < MAX_SCORE; ++i)
170         {
171                 r = HUD_CompareScore(left.scores[i], right.scores[i], scores_flags[i]);
172                 if (r >= 0)
173                         return r;
174         }
175
176         if (left.sv_entnum < right.sv_entnum)
177                 return true;
178
179         return false;
180 }
181
182 void HUD_UpdatePlayerPos(entity player)
183 {
184         for(other = player.sort_next; other && HUD_ComparePlayerScores(player, other); other = player.sort_next)
185         {
186                 SORT_SWAP(player, other);
187         }
188         for(other = player.sort_prev; other != players && HUD_ComparePlayerScores(other, player); other = player.sort_prev)
189         {
190                 SORT_SWAP(other, player);
191         }
192 }
193
194 float HUD_CompareTeamScores(entity left, entity right)
195 {
196         int i, r;
197
198         if(left.team == NUM_SPECTATOR)
199                 return 1;
200         if(right.team == NUM_SPECTATOR)
201                 return 0;
202
203         r = HUD_CompareScore(left.teamscores[ts_primary], right.teamscores[ts_primary], teamscores_flags[ts_primary]);
204         if (r >= 0)
205                 return r;
206
207         r = HUD_CompareScore(left.teamscores[ts_secondary], right.teamscores[ts_secondary], teamscores_flags[ts_secondary]);
208         if (r >= 0)
209                 return r;
210
211         for(i = 0; i < MAX_SCORE; ++i)
212         {
213                 r = HUD_CompareScore(left.teamscores[i], right.teamscores[i], teamscores_flags[i]);
214                 if (r >= 0)
215                         return r;
216         }
217
218         if (left.team < right.team)
219                 return true;
220
221         return false;
222 }
223
224 void HUD_UpdateTeamPos(entity Team)
225 {
226         for(other = Team.sort_next; other && HUD_CompareTeamScores(Team, other); other = Team.sort_next)
227         {
228                 SORT_SWAP(Team, other);
229         }
230         for(other = Team.sort_prev; other != teams && HUD_CompareTeamScores(other, Team); other = Team.sort_prev)
231         {
232                 SORT_SWAP(other, Team);
233         }
234 }
235
236 void Cmd_HUD_Help()
237 {
238         print(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
239         print(_("^3|---------------------------------------------------------------|\n"));
240         print(_("Usage:\n"));
241         print(_("^2scoreboard_columns_set default\n"));
242         print(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
243         print(_("The following field names are recognized (case insensitive):\n"));
244         print(_("You can use a ^3|^7 to start the right-aligned fields.\n\n"));
245
246         print(_("^3name^7 or ^3nick^7             Name of a player\n"));
247         print(_("^3ping^7                     Ping time\n"));
248         print(_("^3pl^7                       Packet loss\n"));
249         print(_("^3kills^7                    Number of kills\n"));
250         print(_("^3deaths^7                   Number of deaths\n"));
251         print(_("^3suicides^7                 Number of suicides\n"));
252         print(_("^3frags^7                    kills - suicides\n"));
253         print(_("^3kd^7                       The kill-death ratio\n"));
254         print(_("^3sum^7                      frags - deaths\n"));
255         print(_("^3caps^7                     How often a flag (CTF) or a key (KeyHunt) was captured\n"));
256         print(_("^3pickups^7                  How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
257         print(_("^3captime^7                  Time of fastest cap (CTF)\n"));
258         print(_("^3fckills^7                  Number of flag carrier kills\n"));
259         print(_("^3returns^7                  Number of flag returns\n"));
260         print(_("^3drops^7                    Number of flag drops\n"));
261         print(_("^3lives^7                    Number of lives (LMS)\n"));
262         print(_("^3rank^7                     Player rank\n"));
263         print(_("^3pushes^7                   Number of players pushed into void\n"));
264         print(_("^3destroyed^7                Number of keys destroyed by pushing them into void\n"));
265         print(_("^3kckills^7                  Number of keys carrier kills\n"));
266         print(_("^3losses^7                   Number of times a key was lost\n"));
267         print(_("^3laps^7                     Number of laps finished (race/cts)\n"));
268         print(_("^3time^7                     Total time raced (race/cts)\n"));
269         print(_("^3fastest^7                  Time of fastest lap (race/cts)\n"));
270         print(_("^3ticks^7                    Number of ticks (DOM)\n"));
271         print(_("^3takes^7                    Number of domination points taken (DOM)\n"));
272         print(_("^3bckills^7                  Number of ball carrier kills\n"));
273         print(_("^3bctime^7                   Total amount of time holding the ball in Keepaway\n"));
274         print(_("^3score^7                    Total score\n\n"));
275
276         print(_("Before a field you can put a + or - sign, then a comma separated list\n"
277                 "of game types, then a slash, to make the field show up only in these\n"
278                 "or in all but these game types. You can also specify 'all' as a\n"
279                 "field to show all fields available for the current game mode.\n\n"));
280
281         print(_("The special game type names 'teams' and 'noteams' can be used to\n"
282                 "include/exclude ALL teams/noteams game modes.\n\n"));
283
284         print(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
285         print(_("will display name, ping and pl aligned to the left, and the fields\n"
286                 "right of the vertical bar aligned to the right.\n"));
287         print(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
288                 "other gamemodes except DM.\n"));
289 }
290
291 #define HUD_DefaultColumnLayout() \
292 "ping pl name | " \
293 "-teams,race,lms/kills +ft,tdm/kills -teams,lms/deaths +ft,tdm/deaths -teams,lms,race,ka/suicides +ft,tdm/suicides -race,dm,tdm,ka,ft/frags " /* tdm already has this in "score" */ \
294 "+ctf/caps +ctf/pickups +ctf/fckills +ctf/returns " \
295 "+lms/lives +lms/rank " \
296 "+kh/caps +kh/pushes +kh/destroyed " \
297 "?+race/laps ?+race/time ?+race/fastest " \
298 "+as/objectives +nb/faults +nb/goals +ka/pickups +ka/bckills +ka/bctime +ft/revivals " \
299 "-lms,race,nb/score"
300
301 void Cmd_HUD_SetFields(float argc)
302 {
303         int i, j, slash;
304         string str, pattern;
305         float have_name = 0, have_primary = 0, have_secondary = 0, have_separator = 0;
306         float missing;
307
308         if(!gametype)
309         {
310                 // set up a temporary scoreboard layout
311                 // no layout can be properly set up until score_info data haven't been received
312                 argc = tokenizebyseparator("0 1 ping pl name | score", " ");
313                 ps_primary = 0;
314                 scores_label[ps_primary] = strzone("score");
315                 scores_flags[ps_primary] = SFL_ALLOW_HIDE;
316         }
317
318         // TODO: re enable with gametype dependant cvars?
319         if(argc < 3) // no arguments provided
320                 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
321
322         if(argc < 3)
323                 argc = tokenizebyseparator(strcat("0 1 ", HUD_DefaultColumnLayout()), " ");
324
325         if(argc == 3)
326         {
327                 if(argv(2) == "default")
328                         argc = tokenizebyseparator(strcat("0 1 ", HUD_DefaultColumnLayout()), " ");
329                 else if(argv(2) == "all")
330                 {
331                         string s;
332                         s = "ping pl name |";
333                         for(i = 0; i < MAX_SCORE; ++i)
334                         {
335                                 if(i != ps_primary)
336                                 if(i != ps_secondary)
337                                 if(scores_label[i] != "")
338                                         s = strcat(s, " ", scores_label[i]);
339                         }
340                         if(ps_secondary != ps_primary)
341                                 s = strcat(s, " ", scores_label[ps_secondary]);
342                         s = strcat(s, " ", scores_label[ps_primary]);
343                         argc = tokenizebyseparator(strcat("0 1 ", s), " ");
344                 }
345         }
346
347
348         hud_num_fields = 0;
349
350         hud_fontsize = HUD_GetFontsize("hud_fontsize");
351
352         draw_beginBoldFont();
353         for(i = 1; i < argc - 1; ++i)
354         {
355                 float nocomplain;
356                 str = argv(i+1);
357
358                 nocomplain = false;
359                 if(substring(str, 0, 1) == "?")
360                 {
361                         nocomplain = true;
362                         str = substring(str, 1, strlen(str) - 1);
363                 }
364
365                 slash = strstrofs(str, "/", 0);
366                 if(slash >= 0)
367                 {
368                         pattern = substring(str, 0, slash);
369                         str = substring(str, slash + 1, strlen(str) - (slash + 1));
370
371                         if (!isGametypeInFilter(gametype, teamplay, false, pattern))
372                                 continue;
373                 }
374
375                 strunzone(hud_title[hud_num_fields]);
376                 hud_title[hud_num_fields] = strzone(TranslateScoresLabel(str));
377                 hud_size[hud_num_fields] = stringwidth(hud_title[hud_num_fields], false, hud_fontsize);
378                 str = strtolower(str);
379
380                 if(str == "ping") {
381                         hud_field[hud_num_fields] = SP_PING;
382                 } else if(str == "pl") {
383                         hud_field[hud_num_fields] = SP_PL;
384                 } else if(str == "kd" || str == "kdr" || str == "kdratio" || str == "k/d") {
385                         hud_field[hud_num_fields] = SP_KDRATIO;
386                 } else if(str == "sum" || str == "diff" || str == "k-d") {
387                         hud_field[hud_num_fields] = SP_SUM;
388                 } else if(str == "name" || str == "nick") {
389                         hud_field[hud_num_fields] = SP_NAME;
390                         have_name = 1;
391                 } else if(str == "|") {
392                         hud_field[hud_num_fields] = SP_SEPARATOR;
393                         have_separator = 1;
394                 } else {
395                         for(j = 0; j < MAX_SCORE; ++j)
396                                 if(str == strtolower(scores_label[j]))
397                                         goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
398 :notfound
399                         if(str == "frags")
400                         {
401                                 j = SP_FRAGS;
402                         }
403                         else
404                         {
405                                 if (!nocomplain)
406                                         printf("^1Error:^7 Unknown score field: '%s'\n", str);
407                                 continue;
408                         }
409 :found
410                         hud_field[hud_num_fields] = j;
411                         if(j == ps_primary)
412                                 have_primary = 1;
413                         if(j == ps_secondary)
414                                 have_secondary = 1;
415                 }
416                 ++hud_num_fields;
417                 if(hud_num_fields >= MAX_HUD_FIELDS)
418                         break;
419         }
420
421         if(scores_flags[ps_primary] & SFL_ALLOW_HIDE)
422                 have_primary = 1;
423         if(scores_flags[ps_secondary] & SFL_ALLOW_HIDE)
424                 have_secondary = 1;
425         if(ps_primary == ps_secondary)
426                 have_secondary = 1;
427         missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
428
429         if(hud_num_fields+missing < MAX_HUD_FIELDS)
430         {
431                 if(!have_name)
432                 {
433                         strunzone(hud_title[hud_num_fields]);
434                         for(i = hud_num_fields; i > 0; --i)
435                         {
436                                 hud_title[i] = hud_title[i-1];
437                                 hud_size[i] = hud_size[i-1];
438                                 hud_field[i] = hud_field[i-1];
439                         }
440                         hud_title[0] = strzone(TranslateScoresLabel("name"));
441                         hud_field[0] = SP_NAME;
442                         ++hud_num_fields;
443                         print("fixed missing field 'name'\n");
444
445                         if(!have_separator)
446                         {
447                                 strunzone(hud_title[hud_num_fields]);
448                                 for(i = hud_num_fields; i > 1; --i)
449                                 {
450                                         hud_title[i] = hud_title[i-1];
451                                         hud_size[i] = hud_size[i-1];
452                                         hud_field[i] = hud_field[i-1];
453                                 }
454                                 hud_title[1] = strzone("|");
455                                 hud_field[1] = SP_SEPARATOR;
456                                 hud_size[1] = stringwidth("|", false, hud_fontsize);
457                                 ++hud_num_fields;
458                                 print("fixed missing field '|'\n");
459                         }
460                 }
461                 else if(!have_separator)
462                 {
463                         strunzone(hud_title[hud_num_fields]);
464                         hud_title[hud_num_fields] = strzone("|");
465                         hud_size[hud_num_fields] = stringwidth("|", false, hud_fontsize);
466                         hud_field[hud_num_fields] = SP_SEPARATOR;
467                         ++hud_num_fields;
468                         print("fixed missing field '|'\n");
469                 }
470                 if(!have_secondary)
471                 {
472                         strunzone(hud_title[hud_num_fields]);
473                         hud_title[hud_num_fields] = strzone(TranslateScoresLabel(scores_label[ps_secondary]));
474                         hud_size[hud_num_fields] = stringwidth(hud_title[hud_num_fields], false, hud_fontsize);
475                         hud_field[hud_num_fields] = ps_secondary;
476                         ++hud_num_fields;
477                         printf("fixed missing field '%s'\n", scores_label[ps_secondary]);
478                 }
479                 if(!have_primary)
480                 {
481                         strunzone(hud_title[hud_num_fields]);
482                         hud_title[hud_num_fields] = strzone(TranslateScoresLabel(scores_label[ps_primary]));
483                         hud_size[hud_num_fields] = stringwidth(hud_title[hud_num_fields], false, hud_fontsize);
484                         hud_field[hud_num_fields] = ps_primary;
485                         ++hud_num_fields;
486                         printf("fixed missing field '%s'\n", scores_label[ps_primary]);
487                 }
488         }
489
490         hud_field[hud_num_fields] = SP_END;
491         draw_endBoldFont();
492 }
493
494 // MOVEUP::
495 vector hud_field_rgb;
496 string hud_field_icon0;
497 string hud_field_icon1;
498 string hud_field_icon2;
499 vector hud_field_icon0_rgb;
500 vector hud_field_icon1_rgb;
501 vector hud_field_icon2_rgb;
502 float hud_field_icon0_alpha;
503 float hud_field_icon1_alpha;
504 float hud_field_icon2_alpha;
505 string HUD_GetField(entity pl, int field)
506 {
507         float tmp, num, denom, f;
508         string str;
509         hud_field_rgb = '1 1 1';
510         hud_field_icon0 = "";
511         hud_field_icon1 = "";
512         hud_field_icon2 = "";
513         hud_field_icon0_rgb = '1 1 1';
514         hud_field_icon1_rgb = '1 1 1';
515         hud_field_icon2_rgb = '1 1 1';
516         hud_field_icon0_alpha = 1;
517         hud_field_icon1_alpha = 1;
518         hud_field_icon2_alpha = 1;
519         switch(field)
520         {
521                 case SP_PING:
522                         if (!pl.gotscores)
523                                 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
524                         //str = getplayerkeyvalue(pl.sv_entnum, "ping");
525                         f = pl.ping;
526                         if(f == 0)
527                                 return _("N/A");
528                         tmp = max(0, min(220, f-80)) / 220;
529                         hud_field_rgb = '1 1 1' - '0 1 1'*tmp;
530                         return ftos(f);
531
532                 case SP_PL:
533                         if (!pl.gotscores)
534                                 return _("N/A");
535                         f = pl.ping_packetloss;
536                         tmp = pl.ping_movementloss;
537                         if(f == 0 && tmp == 0)
538                                 return "";
539                         str = ftos(ceil(f * 100));
540                         if(tmp != 0)
541                                 str = strcat(str, "~", ftos(ceil(tmp * 100)));
542                         tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
543                         hud_field_rgb = '1 0.5 0.5' - '0 0.5 0.5'*tmp;
544                         return str;
545
546                 case SP_NAME:
547                         if(ready_waiting && pl.ready)
548                         {
549                                 hud_field_icon0 = "gfx/scoreboard/player_ready";
550                         }
551                         else if(!teamplay)
552                         {
553                                 f = stof(getplayerkeyvalue(pl.sv_entnum, "colors"));
554                                 {
555                                         hud_field_icon0 = "gfx/scoreboard/playercolor_base";
556                                         hud_field_icon1 = "gfx/scoreboard/playercolor_shirt";
557                                         hud_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
558                                         hud_field_icon2 = "gfx/scoreboard/playercolor_pants";
559                                         hud_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
560                                 }
561                         }
562                         return GetPlayerName(pl.sv_entnum);
563
564                 case SP_FRAGS:
565                         f = pl.(scores[SP_KILLS]);
566                         f -= pl.(scores[SP_SUICIDES]);
567                         return ftos(f);
568
569                 case SP_KDRATIO:
570                         num = pl.(scores[SP_KILLS]);
571                         denom = pl.(scores[SP_DEATHS]);
572
573                         if(denom == 0) {
574                                 hud_field_rgb = '0 1 0';
575                                 str = sprintf("%d", num);
576                         } else if(num <= 0) {
577                                 hud_field_rgb = '1 0 0';
578                                 str = sprintf("%.1f", num/denom);
579                         } else
580                                 str = sprintf("%.1f", num/denom);
581                         return str;
582
583                 case SP_SUM:
584                         f = pl.(scores[SP_KILLS]);
585                         f -= pl.(scores[SP_DEATHS]);
586
587                         if(f > 0) {
588                                 hud_field_rgb = '0 1 0';
589                         } else if(f == 0) {
590                                 hud_field_rgb = '1 1 1';
591                         } else {
592                                 hud_field_rgb = '1 0 0';
593                         }
594                         return ftos(f);
595
596                 default:
597                         tmp = pl.(scores[field]);
598                         f = scores_flags[field];
599                         if(field == ps_primary)
600                                 hud_field_rgb = '1 1 0';
601                         else if(field == ps_secondary)
602                                 hud_field_rgb = '0 1 1';
603                         else
604                                 hud_field_rgb = '1 1 1';
605                         return ScoreString(f, tmp);
606         }
607         //return "error";
608 }
609
610 float hud_fixscoreboardcolumnwidth_len;
611 float hud_fixscoreboardcolumnwidth_iconlen;
612 float hud_fixscoreboardcolumnwidth_marginlen;
613
614 string HUD_FixScoreboardColumnWidth(int i, string str)
615 {
616         float field, f;
617         vector sz;
618         field = hud_field[i];
619
620         hud_fixscoreboardcolumnwidth_iconlen = 0;
621
622         if(hud_field_icon0 != "")
623         {
624                 sz = draw_getimagesize(hud_field_icon0);
625                 f = sz.x / sz.y;
626                 if(hud_fixscoreboardcolumnwidth_iconlen < f)
627                         hud_fixscoreboardcolumnwidth_iconlen = f;
628         }
629
630         if(hud_field_icon1 != "")
631         {
632                 sz = draw_getimagesize(hud_field_icon1);
633                 f = sz.x / sz.y;
634                 if(hud_fixscoreboardcolumnwidth_iconlen < f)
635                         hud_fixscoreboardcolumnwidth_iconlen = f;
636         }
637
638         if(hud_field_icon2 != "")
639         {
640                 sz = draw_getimagesize(hud_field_icon2);
641                 f = sz.x / sz.y;
642                 if(hud_fixscoreboardcolumnwidth_iconlen < f)
643                         hud_fixscoreboardcolumnwidth_iconlen = f;
644         }
645
646         hud_fixscoreboardcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
647
648         if(hud_fixscoreboardcolumnwidth_iconlen != 0)
649                 hud_fixscoreboardcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
650         else
651                 hud_fixscoreboardcolumnwidth_marginlen = 0;
652
653         if(field == SP_NAME) // name gets all remaining space
654         {
655                 int j;
656                 float namesize;
657                 namesize = sbwidth;// / hud_fontsize_x;
658                 for(j = 0; j < hud_num_fields; ++j)
659                         if(j != i)
660                                 if (hud_field[i] != SP_SEPARATOR)
661                                         namesize -= hud_size[j] + hud_fontsize.x;
662                 namesize += hud_fontsize.x;
663                 hud_size[i] = namesize;
664
665                 if (hud_fixscoreboardcolumnwidth_iconlen != 0)
666                         namesize -= hud_fixscoreboardcolumnwidth_marginlen + hud_fixscoreboardcolumnwidth_iconlen;
667                 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
668                 hud_fixscoreboardcolumnwidth_len = stringwidth(str, true, hud_fontsize);
669         }
670         else
671                 hud_fixscoreboardcolumnwidth_len = stringwidth(str, false, hud_fontsize);
672
673         f = hud_fixscoreboardcolumnwidth_len + hud_fixscoreboardcolumnwidth_marginlen + hud_fixscoreboardcolumnwidth_iconlen;
674         if(hud_size[i] < f)
675                 hud_size[i] = f;
676
677         return str;
678 }
679
680 void HUD_PrintScoreboardItem(vector pos, vector item_size, entity pl, float is_self, int pl_number)
681 {
682         vector tmp, rgb;
683         rgb = Team_ColorRGB(pl.team);
684         string str;
685         float field;
686         float is_spec;
687         is_spec = (GetPlayerColor(pl.sv_entnum) == NUM_SPECTATOR);
688
689         if((rgb == '1 1 1') && (!is_spec)) {
690                 rgb_x = autocvar_scoreboard_color_bg_r + 0.5;
691                 rgb_y = autocvar_scoreboard_color_bg_g + 0.5;
692                 rgb_z = autocvar_scoreboard_color_bg_b + 0.5; }
693
694         vector h_pos = pos - '1 1 0';
695         vector h_size = item_size + '2 0 0';
696         // alternated rows highlighting
697         if(is_self)
698                 drawfill(h_pos, h_size, rgb, scoreboard_highlight_alpha_self, DRAWFLAG_NORMAL);
699         else if((scoreboard_highlight) && (!(pl_number % 2)))
700                 drawfill(h_pos, h_size, rgb, scoreboard_highlight_alpha, DRAWFLAG_NORMAL);
701
702         tmp_x = item_size.x;
703         tmp_y = 0;
704         tmp_z = 0;
705         int i;
706         for(i = 0; i < hud_num_fields; ++i)
707         {
708                 field = hud_field[i];
709                 if(field == SP_SEPARATOR)
710                         break;
711
712                 if(is_spec && field != SP_NAME && field != SP_PING) {
713                         pos.x += hud_size[i] + hud_fontsize.x;
714                         continue;
715                 }
716                 str = HUD_GetField(pl, field);
717                 str = HUD_FixScoreboardColumnWidth(i, str);
718
719                 pos.x += hud_size[i] + hud_fontsize.x;
720
721                 if(field == SP_NAME) {
722                         tmp_x = hud_size[i] - hud_fontsize.x*hud_fixscoreboardcolumnwidth_iconlen - hud_fixscoreboardcolumnwidth_marginlen + hud_fontsize.x;
723                         if (is_self)
724                                 drawcolorcodedstring(pos - tmp, str, hud_fontsize, scoreboard_alpha_name_self, DRAWFLAG_NORMAL);
725                         else
726                                 drawcolorcodedstring(pos - tmp, str, hud_fontsize, scoreboard_alpha_name, DRAWFLAG_NORMAL);
727                 } else {
728                         tmp_x = hud_fixscoreboardcolumnwidth_len + hud_fontsize.x;
729                         if (is_self)
730                                 drawstring(pos - tmp, str, hud_fontsize, hud_field_rgb, scoreboard_alpha_name_self, DRAWFLAG_NORMAL);
731                         else
732                                 drawstring(pos - tmp, str, hud_fontsize, hud_field_rgb, scoreboard_alpha_name, DRAWFLAG_NORMAL);
733                 }
734
735                 tmp_x = hud_size[i] + hud_fontsize.x;
736                 if(hud_field_icon0 != "")
737                         if (is_self)
738                                 drawpic(pos - tmp, hud_field_icon0, '0 1 0' * hud_fontsize.y + '1 0 0' * hud_fontsize.x * hud_fixscoreboardcolumnwidth_iconlen, hud_field_icon1_rgb, hud_field_icon0_alpha * scoreboard_alpha_name_self, DRAWFLAG_NORMAL);
739                         else
740                                 drawpic(pos - tmp, hud_field_icon0, '0 1 0' * hud_fontsize.y + '1 0 0' * hud_fontsize.x * hud_fixscoreboardcolumnwidth_iconlen, hud_field_icon1_rgb, hud_field_icon0_alpha * scoreboard_alpha_name, DRAWFLAG_NORMAL);
741                 if(hud_field_icon1 != "")
742                         if (is_self)
743                                 drawpic(pos - tmp, hud_field_icon1, '0 1 0' * hud_fontsize.y + '1 0 0' * hud_fontsize.x * hud_fixscoreboardcolumnwidth_iconlen, hud_field_icon1_rgb, hud_field_icon1_alpha * scoreboard_alpha_name_self, DRAWFLAG_NORMAL);
744                         else
745                                 drawpic(pos - tmp, hud_field_icon1, '0 1 0' * hud_fontsize.y + '1 0 0' * hud_fontsize.x * hud_fixscoreboardcolumnwidth_iconlen, hud_field_icon1_rgb, hud_field_icon1_alpha * scoreboard_alpha_name, DRAWFLAG_NORMAL);
746                 if(hud_field_icon2 != "")
747                         if (is_self)
748                                 drawpic(pos - tmp, hud_field_icon2, '0 1 0' * hud_fontsize.y + '1 0 0' * hud_fontsize.x * hud_fixscoreboardcolumnwidth_iconlen, hud_field_icon2_rgb, hud_field_icon2_alpha * scoreboard_alpha_name_self, DRAWFLAG_NORMAL);
749                         else
750                                 drawpic(pos - tmp, hud_field_icon2, '0 1 0' * hud_fontsize.y + '1 0 0' * hud_fontsize.x * hud_fixscoreboardcolumnwidth_iconlen, hud_field_icon2_rgb, hud_field_icon2_alpha * scoreboard_alpha_name, DRAWFLAG_NORMAL);
751         }
752
753         if(hud_field[i] == SP_SEPARATOR)
754         {
755                 pos_x = xmax;
756                 for(i = hud_num_fields-1; i > 0; --i)
757                 {
758                         field = hud_field[i];
759                         if(field == SP_SEPARATOR)
760                                 break;
761
762                         if(is_spec && field != SP_NAME && field != SP_PING) {
763                                 pos.x -= hud_size[i] + hud_fontsize.x;
764                                 continue;
765                         }
766
767                         str = HUD_GetField(pl, field);
768                         str = HUD_FixScoreboardColumnWidth(i, str);
769
770                         if(field == SP_NAME) {
771                                 tmp_x = hud_fixscoreboardcolumnwidth_len; // left or right aligned? let's put it right...
772                                 if(is_self)
773                                         drawcolorcodedstring(pos - tmp, str, hud_fontsize, scoreboard_alpha_name_self, DRAWFLAG_NORMAL);
774                                 else
775                                         drawcolorcodedstring(pos - tmp, str, hud_fontsize, scoreboard_alpha_name, DRAWFLAG_NORMAL);
776                         } else {
777                                 tmp_x = hud_fixscoreboardcolumnwidth_len;
778                                 if(is_self)
779                                         drawstring(pos - tmp, str, hud_fontsize, hud_field_rgb, scoreboard_alpha_name_self, DRAWFLAG_NORMAL);
780                                 else
781                                         drawstring(pos - tmp, str, hud_fontsize, hud_field_rgb, scoreboard_alpha_name, DRAWFLAG_NORMAL);
782                         }
783
784                         tmp_x = hud_size[i];
785                         if(hud_field_icon0 != "")
786                                 if (is_self)
787                                         drawpic(pos - tmp, hud_field_icon0, '0 1 0' * hud_fontsize.y + '1 0 0' * hud_fontsize.x * hud_fixscoreboardcolumnwidth_iconlen, hud_field_icon1_rgb, hud_field_icon0_alpha * scoreboard_alpha_name_self, DRAWFLAG_NORMAL);
788                                 else
789                                         drawpic(pos - tmp, hud_field_icon0, '0 1 0' * hud_fontsize.y + '1 0 0' * hud_fontsize.x * hud_fixscoreboardcolumnwidth_iconlen, hud_field_icon1_rgb, hud_field_icon0_alpha * scoreboard_alpha_name, DRAWFLAG_NORMAL);
790                         if(hud_field_icon1 != "")
791                                 if (is_self)
792                                         drawpic(pos - tmp, hud_field_icon1, '0 1 0' * hud_fontsize.y + '1 0 0' * hud_fontsize.x * hud_fixscoreboardcolumnwidth_iconlen, hud_field_icon1_rgb, hud_field_icon1_alpha * scoreboard_alpha_name_self, DRAWFLAG_NORMAL);
793                                 else
794                                         drawpic(pos - tmp, hud_field_icon1, '0 1 0' * hud_fontsize.y + '1 0 0' * hud_fontsize.x * hud_fixscoreboardcolumnwidth_iconlen, hud_field_icon1_rgb, hud_field_icon1_alpha * scoreboard_alpha_name, DRAWFLAG_NORMAL);
795                         if(hud_field_icon2 != "")
796                                 if (is_self)
797                                         drawpic(pos - tmp, hud_field_icon2, '0 1 0' * hud_fontsize.y + '1 0 0' * hud_fontsize.x * hud_fixscoreboardcolumnwidth_iconlen, hud_field_icon2_rgb, hud_field_icon2_alpha * scoreboard_alpha_name_self, DRAWFLAG_NORMAL);
798                                 else
799                                         drawpic(pos - tmp, hud_field_icon2, '0 1 0' * hud_fontsize.y + '1 0 0' * hud_fontsize.x * hud_fixscoreboardcolumnwidth_iconlen, hud_field_icon2_rgb, hud_field_icon2_alpha * scoreboard_alpha_name, DRAWFLAG_NORMAL);
800                         pos.x -= hud_size[i] + hud_fontsize.x;
801                 }
802         }
803
804         if(pl.eliminated)
805                 drawfill(h_pos, h_size, '0 0 0', 0.5, DRAWFLAG_NORMAL);
806 }
807
808 /*
809  * HUD_Scoreboard_MakeTable
810  *
811  * Makes a table for a team (for all playing players in DM) and fills it
812  */
813
814 vector HUD_Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
815 {
816         float body_table_height;
817         vector tmp = '0 0 0', column_dim = '0 0 0';
818         entity pl;
819
820         body_table_height = 1.25 * hud_fontsize.y * max(1, tm.team_size); // no player? show 1 empty line
821
822         pos.y += autocvar_scoreboard_border_thickness;
823         pos -= '1 1 0';
824
825         tmp_x = sbwidth + 2;
826         tmp_y = 1.25 * hud_fontsize.y;
827
828         // rounded header
829         if (teamplay)
830                 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, (rgb * autocvar_scoreboard_color_bg_team) + '0.5 0.5 0.5', scoreboard_alpha_bg, DRAWFLAG_NORMAL);
831         else
832                 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', scoreboard_alpha_bg, DRAWFLAG_NORMAL);
833
834         // table border
835         tmp.y += autocvar_scoreboard_border_thickness;
836         tmp.y += body_table_height;
837         drawborderlines(autocvar_scoreboard_border_thickness, pos, tmp, '0 0 0', scoreboard_alpha_bg, DRAWFLAG_NORMAL); // more transparency for the scoreboard
838
839         // separator header/table
840         pos.y += 1.25 * hud_fontsize.y;
841         tmp_y = autocvar_scoreboard_border_thickness;
842         drawfill(pos, tmp, '0 0 0', scoreboard_alpha_bg, DRAWFLAG_NORMAL);
843
844         pos.y += autocvar_scoreboard_border_thickness;
845
846         // table background
847         tmp_y = body_table_height;
848         if (teamplay)
849                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb * autocvar_scoreboard_color_bg_team, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
850         else
851                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
852
853         // anyway, apply some color
854         //drawfill(pos, tmp + '2 0 0', rgb, 0.1, DRAWFLAG_NORMAL);
855
856         // go back to the top to make alternated columns highlighting and to print the strings
857         pos.y -= 1.25 * hud_fontsize.y;
858         pos.y -= autocvar_scoreboard_border_thickness;
859
860         pos += '1 1 0';
861
862         if (scoreboard_highlight)
863         {
864                 column_dim_y = 1.25 * hud_fontsize.y; // header
865                 column_dim.y += autocvar_scoreboard_border_thickness;
866                 column_dim.y += body_table_height;
867         }
868
869         // print the strings of the columns headers and draw the columns
870         draw_beginBoldFont();
871         int i;
872         for(i = 0; i < hud_num_fields; ++i)
873         {
874                 if(hud_field[i] == SP_SEPARATOR)
875                         break;
876                 column_dim_x = hud_size[i] + hud_fontsize.x;
877                 if (scoreboard_highlight)
878                 {
879                         if (i % 2)
880                                 drawfill(pos - '0 1 0' - hud_fontsize.x / 2 * '1 0 0', column_dim, '0 0 0', scoreboard_alpha_bg * 0.2, DRAWFLAG_NORMAL);
881                 }
882                 drawstring(pos, hud_title[i], hud_fontsize, rgb * 1.5, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
883                 pos.x += column_dim.x;
884         }
885         if(hud_field[i] == SP_SEPARATOR)
886         {
887                 pos_x = xmax;
888                 tmp_y = 0;
889                 for(i = hud_num_fields-1; i > 0; --i)
890                 {
891                         if(hud_field[i] == SP_SEPARATOR)
892                                 break;
893
894                         pos.x -= hud_size[i];
895
896                         if (scoreboard_highlight)
897                         {
898                                 if (!(i % 2))
899                                 {
900                                         if (i == hud_num_fields-1)
901                                                 column_dim_x = hud_size[i] + hud_fontsize.x / 2 + 1;
902                                         else
903                                                 column_dim_x = hud_size[i] + hud_fontsize.x;
904                                         drawfill(pos - '0 1 0' - hud_fontsize.x / 2 * '1 0 0', column_dim, '0 0 0', scoreboard_alpha_bg * 0.2, DRAWFLAG_NORMAL);
905                                 }
906                         }
907
908                         tmp_x = stringwidth(hud_title[i], false, hud_fontsize);
909                         tmp_x = (hud_size[i] - tmp.x);
910                         drawstring(pos + tmp, hud_title[i], hud_fontsize, rgb * 1.5, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
911                         pos.x -= hud_fontsize.x;
912                 }
913         }
914         draw_endBoldFont();
915
916         pos_x = xmin;
917         pos.y += 1.25 * hud_fontsize.y; // skip the header
918         pos.y += autocvar_scoreboard_border_thickness;
919
920         // item size
921         tmp_x = sbwidth;
922         tmp_y = hud_fontsize.y * 1.25;
923
924         // fill the table and draw the rows
925         i = 0;
926         if (teamplay)
927                 for(pl = players.sort_next; pl; pl = pl.sort_next)
928                 {
929                         if(pl.team != tm.team)
930                                 continue;
931                         HUD_PrintScoreboardItem(pos, tmp, pl, (pl.sv_entnum == player_localnum), i);
932                         pos.y += 1.25 * hud_fontsize.y;
933                         ++i;
934                 }
935         else
936                 for(pl = players.sort_next; pl; pl = pl.sort_next)
937                 {
938                         if(pl.team == NUM_SPECTATOR)
939                                 continue;
940                         HUD_PrintScoreboardItem(pos, tmp, pl, (pl.sv_entnum == player_localnum), i);
941                         pos.y += 1.25 * hud_fontsize.y;
942                         ++i;
943                 }
944
945         if (i == 0)
946                 pos.y += 1.25 * hud_fontsize.y; // move to the end of the table
947         pos.y += 1.25 * hud_fontsize.y; // move empty row (out of the table)
948
949         return pos;
950 }
951
952 float HUD_WouldDrawScoreboard() {
953         if (autocvar__hud_configure)
954                 return 0;
955         else if (scoreboard_showscores)
956                 return 1;
957         else if (intermission == 1)
958                 return 1;
959         else if (intermission == 2)
960                 return 0;
961         else if (spectatee_status != -1 && getstati(STAT_HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS)
962                 return 1;
963         else if (scoreboard_showscores_force)
964                 return 1;
965         return 0;
966 }
967
968 float average_accuracy;
969 vector HUD_DrawScoreboardAccuracyStats(vector pos, vector rgb, vector bg_size)
970 {
971         int i;
972         float weapon_cnt = WEP_COUNT - 3; // either vaporizer/vortex are hidden, no port-o-launch, no tuba
973         float rows;
974         if(autocvar_scoreboard_accuracy_doublerows)
975                 rows = 2;
976         else
977                 rows = 1;
978         float height = 40;
979         float fontsize = height * 1/3;
980         float weapon_height = height * 2/3;
981         float weapon_width = sbwidth / weapon_cnt;
982         float g_instagib = 0;
983
984         drawstring(pos, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
985         pos.y += 1.25 * hud_fontsize.y + autocvar_scoreboard_border_thickness;
986         vector tmp = '0 0 0';
987         tmp_x = sbwidth;
988         tmp_y = height * rows;
989
990         if (teamplay)
991                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb * autocvar_scoreboard_color_bg_team, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
992         else
993                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
994         drawborderlines(autocvar_scoreboard_border_thickness, pos, tmp, '0 0 0', scoreboard_alpha_bg * 0.75, DRAWFLAG_NORMAL);
995
996         // column highlighting
997         for(i = 0; i < weapon_cnt/rows; ++i)
998         {
999                 if(!(i % 2))
1000                         drawfill(pos + '1 0 0' * weapon_width * rows * i, '0 1 0' * height * rows + '1 0 0' * weapon_width * rows, '0 0 0', scoreboard_alpha_bg * 0.2, DRAWFLAG_NORMAL);
1001         }
1002
1003         // row highlighting
1004         for(i = 0; i < rows; ++i)
1005         {
1006                 drawfill(pos + '0 1 0' * weapon_height + '0 1 0' * height * i, '1 0 0' * sbwidth + '0 1 0' * fontsize, '1 1 1', scoreboard_highlight_alpha, DRAWFLAG_NORMAL);
1007         }
1008
1009         average_accuracy = 0;
1010         float weapons_with_stats;
1011         weapons_with_stats = 0;
1012         if(rows == 2)
1013                 pos.x += weapon_width / 2;
1014
1015         if(switchweapon == WEP_VAPORIZER)
1016                 g_instagib = 1; // TODO: real detection for instagib?
1017
1018         float weapon_stats;
1019         if(autocvar_scoreboard_accuracy_nocolors)
1020                 rgb = '1 1 1';
1021         else
1022                 Accuracy_LoadColors();
1023
1024         for(i = WEP_FIRST; i <= WEP_LAST; ++i)
1025         {
1026                 self = get_weaponinfo(i);
1027                 if (!self.weapon)
1028                         continue;
1029                 if ((i == WEP_VORTEX && g_instagib) || i == WEP_PORTO || (i == WEP_VAPORIZER && !g_instagib) || i == WEP_TUBA) // skip port-o-launch, vortex || vaporizer and tuba
1030                         continue;
1031                 weapon_stats = weapon_accuracy[i-WEP_FIRST];
1032
1033                 float weapon_alpha;
1034                 if(weapon_stats >= 0)
1035                         weapon_alpha = scoreboard_alpha_fg;
1036                 else
1037                         weapon_alpha = 0.2 * scoreboard_alpha_fg;
1038
1039                 // weapon icon
1040                 drawpic_aspect_skin(pos, self.model2, '1 0 0' * weapon_width + '0 1 0' * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1041                 // the accuracy
1042                 if(weapon_stats >= 0) {
1043                         weapons_with_stats += 1;
1044                         average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1045
1046                         string s;
1047                         s = sprintf("%d%%", weapon_stats*100);
1048
1049                         float padding;
1050                         padding = (weapon_width - stringwidth(s, false, '1 0 0' * fontsize)) / 2; // center the accuracy value
1051
1052                         if(!autocvar_scoreboard_accuracy_nocolors)
1053                                 rgb = Accuracy_GetColor(weapon_stats);
1054
1055                         drawstring(pos + '1 0 0' * padding + '0 1 0' * weapon_height, s, '1 1 0' * fontsize, rgb, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1056                 }
1057                 pos.x += weapon_width * rows;
1058                 if(rows == 2 && i == 6) {
1059                         pos.x -= sbwidth;
1060                         pos.y += height;
1061                 }
1062         }
1063
1064         if(weapons_with_stats)
1065                 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1066
1067         if(rows == 2)
1068                 pos.x -= weapon_width / 2;
1069         pos.x -= sbwidth;
1070         pos.y += height;
1071
1072         pos.y +=  1.25 * hud_fontsize.y;
1073         return pos;
1074 }
1075
1076 vector HUD_DrawKeyValue(vector pos, string key, string value) {
1077         float px = pos.x;
1078         pos.x += hud_fontsize.x * 0.25;
1079         drawstring(pos, key, hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1080         pos_x = xmax - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1081         drawstring(pos, value, hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1082         pos_x = px;
1083         pos.y+= hud_fontsize.y;
1084
1085         return pos;
1086 }
1087
1088 vector HUD_DrawMapStats(vector pos, vector rgb, vector bg_size) {
1089         float stat_secrets_found, stat_secrets_total;
1090         float stat_monsters_killed, stat_monsters_total;
1091         float rows = 0;
1092         string val;
1093
1094         // get monster stats
1095         stat_monsters_killed = getstatf(STAT_MONSTERS_KILLED);
1096         stat_monsters_total = getstatf(STAT_MONSTERS_TOTAL);
1097
1098         // get secrets stats
1099         stat_secrets_found = getstatf(STAT_SECRETS_FOUND);
1100         stat_secrets_total = getstatf(STAT_SECRETS_TOTAL);
1101
1102         // get number of rows
1103         if(stat_secrets_total)
1104                 rows += 1;
1105         if(stat_monsters_total)
1106                 rows += 1;
1107
1108         // if no rows, return
1109         if (!rows)
1110                 return pos;
1111
1112         //  draw table header
1113         drawstring(pos, _("Map stats:"), hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1114         pos.y += 1.25 * hud_fontsize.y + autocvar_scoreboard_border_thickness;
1115
1116         // draw table
1117         vector tmp = '0 0 0';
1118         tmp_x = sbwidth;
1119         tmp_y = hud_fontsize.y * rows;
1120
1121         if (teamplay)
1122                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb * autocvar_scoreboard_color_bg_team, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
1123         else
1124                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
1125         drawborderlines(autocvar_scoreboard_border_thickness, pos, tmp, '0 0 0', scoreboard_alpha_bg * 0.75, DRAWFLAG_NORMAL);
1126
1127         // draw monsters
1128         if(stat_monsters_total)
1129         {
1130                 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1131                 pos = HUD_DrawKeyValue(pos, _("Monsters killed:"), val);
1132         }
1133
1134         // draw secrets
1135         if(stat_secrets_total)
1136         {
1137                 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1138                 pos = HUD_DrawKeyValue(pos, _("Secrets found:"), val);
1139         }
1140
1141         // update position
1142         pos.y += 1.25 * hud_fontsize.y;
1143         return pos;
1144 }
1145
1146
1147 vector HUD_DrawScoreboardRankings(vector pos, entity pl,  vector rgb, vector bg_size)
1148 {
1149         int i;
1150         RANKINGS_RECEIVED_CNT = 0;
1151         for (i=RANKINGS_CNT-1; i>=0; --i)
1152                 if (grecordtime[i])
1153                         ++RANKINGS_RECEIVED_CNT;
1154
1155         if (RANKINGS_RECEIVED_CNT == 0)
1156                 return pos;
1157
1158         float is_spec;
1159         is_spec = (GetPlayerColor(pl.sv_entnum) == NUM_SPECTATOR);
1160         vector hl_rgb;
1161         hl_rgb_x = autocvar_scoreboard_color_bg_r + 0.5;
1162         hl_rgb_y = autocvar_scoreboard_color_bg_g + 0.5;
1163         hl_rgb_z = autocvar_scoreboard_color_bg_b + 0.5;
1164
1165         pos.y += hud_fontsize.y;
1166         drawstring(pos, _("Rankings"), hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1167         pos.y += hud_fontsize.y + autocvar_scoreboard_border_thickness;
1168         vector tmp = '0 0 0';
1169         tmp_x = sbwidth;
1170         tmp_y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1171
1172         if (teamplay)
1173                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb * autocvar_scoreboard_color_bg_team, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
1174         else
1175                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
1176         drawborderlines(autocvar_scoreboard_border_thickness, pos, tmp, '0 0 0', scoreboard_alpha_bg * 0.75, DRAWFLAG_NORMAL);
1177
1178         // row highlighting
1179         for(i = 0; i<RANKINGS_RECEIVED_CNT; ++i)
1180         {
1181                 string n, p;
1182                 float t;
1183                 t = grecordtime[i];
1184                 if (t == 0)
1185                         continue;
1186                 n = grecordholder[i];
1187                 p = count_ordinal(i+1);
1188                 if(grecordholder[i] == GetPlayerName(player_localnum))
1189                         drawfill(pos, '1 0 0' * sbwidth + '0 1.25 0' * hud_fontsize.y, hl_rgb, scoreboard_highlight_alpha_self, DRAWFLAG_NORMAL);
1190                 else if(!(i % 2) && scoreboard_highlight)
1191                         drawfill(pos, '1 0 0' * sbwidth + '0 1.25 0' * hud_fontsize.y, hl_rgb, scoreboard_highlight_alpha, DRAWFLAG_NORMAL);
1192                 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1193                 drawstring(pos + '3 0 0' * hud_fontsize.y, TIME_ENCODED_TOSTRING(t), '1 1 0' * hud_fontsize.y, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1194                 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1195                 pos.y += 1.25 * hud_fontsize.y;
1196         }
1197         pos.y += autocvar_scoreboard_border_thickness;
1198
1199         return pos;
1200 }
1201
1202 float hud_woulddrawscoreboard_prev;
1203 float hud_woulddrawscoreboard_change; // "time" at which HUD_WouldDrawScoreboard() changed
1204 void HUD_DrawScoreboard()
1205 {
1206         float hud_woulddrawscoreboard;
1207         hud_woulddrawscoreboard = scoreboard_active;
1208         if(hud_woulddrawscoreboard != hud_woulddrawscoreboard_prev) {
1209                 hud_woulddrawscoreboard_change = time;
1210                 hud_woulddrawscoreboard_prev = hud_woulddrawscoreboard;
1211         }
1212
1213         if(hud_woulddrawscoreboard) {
1214                 float scoreboard_fadeinspeed = autocvar_scoreboard_fadeinspeed;
1215                 if (scoreboard_fadeinspeed)
1216                         scoreboard_fade_alpha = bound (0, (time - hud_woulddrawscoreboard_change) * scoreboard_fadeinspeed, 1);
1217                 else
1218                         scoreboard_fade_alpha = 1;
1219         }
1220         else {
1221                 float scoreboard_fadeoutspeed = autocvar_scoreboard_fadeoutspeed;
1222                 if (scoreboard_fadeoutspeed)
1223                         scoreboard_fade_alpha = bound (0, (1/scoreboard_fadeoutspeed - (time - hud_woulddrawscoreboard_change)) * scoreboard_fadeoutspeed, 1);
1224                 else
1225                         scoreboard_fade_alpha = 0;
1226         }
1227
1228         if (!scoreboard_fade_alpha)
1229                 return;
1230
1231         HUD_UpdatePlayerTeams();
1232
1233         scoreboard_alpha_bg = autocvar_scoreboard_alpha_bg * scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1234         scoreboard_alpha_fg = autocvar_scoreboard_alpha_fg * scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1235         scoreboard_highlight = autocvar_scoreboard_highlight;
1236         scoreboard_highlight_alpha = autocvar_scoreboard_highlight_alpha * scoreboard_alpha_fg;
1237         scoreboard_highlight_alpha_self = autocvar_scoreboard_highlight_alpha_self * scoreboard_alpha_fg;
1238         scoreboard_alpha_name = autocvar_scoreboard_alpha_name * scoreboard_alpha_fg;
1239         scoreboard_alpha_name_self = autocvar_scoreboard_alpha_name_self * scoreboard_alpha_fg;
1240
1241         vector rgb, pos, tmp;
1242         entity pl, tm;
1243         string str;
1244
1245         xmin = (autocvar_scoreboard_offset_left * vid_conwidth);
1246         ymin = max((autocvar_con_notify * autocvar_con_notifysize), (autocvar_scoreboard_offset_vertical * vid_conwidth));
1247
1248         xmax = ((1 - autocvar_scoreboard_offset_right) * vid_conwidth);
1249         ymax = (vid_conheight - ymin);
1250
1251         sbwidth = xmax - xmin;
1252
1253         // Initializes position
1254         pos_x = xmin;
1255         pos_y = ymin;
1256         pos_z = 0;
1257
1258         // Heading
1259         vector sb_heading_fontsize;
1260         sb_heading_fontsize = hud_fontsize * 2;
1261         draw_beginBoldFont();
1262         drawstring(pos, _("Scoreboard"), sb_heading_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1263         draw_endBoldFont();
1264
1265         pos.y += sb_heading_fontsize.y + hud_fontsize.y * 0.25;
1266
1267         // Draw the scoreboard
1268         vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * autocvar_scoreboard_bg_scale;
1269
1270         if(teamplay)
1271         {
1272                 vector team_score_baseoffset;
1273                 team_score_baseoffset = eY * (2 * autocvar_scoreboard_border_thickness + hud_fontsize.y) - eX * (autocvar_scoreboard_border_thickness + hud_fontsize.x * 0.25);
1274                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1275                 {
1276                         if(tm.team == NUM_SPECTATOR)
1277                                 continue;
1278
1279                         draw_beginBoldFont();
1280                         rgb = Team_ColorRGB(tm.team);
1281                         str = ftos(tm.(teamscores[ts_primary]));
1282                         drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1283
1284                         if(ts_primary != ts_secondary)
1285                         {
1286                                 str = ftos(tm.(teamscores[ts_secondary]));
1287                                 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize) + eY * hud_fontsize.y * 1.5, str, hud_fontsize, rgb, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1288                         }
1289                         draw_endBoldFont();
1290
1291                         pos = HUD_Scoreboard_MakeTable(pos, tm, rgb, bg_size);
1292                 }
1293                 rgb_x = autocvar_scoreboard_color_bg_r;
1294                 rgb_y = autocvar_scoreboard_color_bg_g;
1295                 rgb_z = autocvar_scoreboard_color_bg_b;
1296         }
1297         else
1298         {
1299                 rgb_x = autocvar_scoreboard_color_bg_r;
1300                 rgb_y = autocvar_scoreboard_color_bg_g;
1301                 rgb_z = autocvar_scoreboard_color_bg_b;
1302
1303                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1304                 {
1305                         if(tm.team == NUM_SPECTATOR)
1306                                 continue;
1307
1308                         pos = HUD_Scoreboard_MakeTable(pos, tm, rgb, bg_size);
1309                 }
1310         }
1311
1312         if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1313                 if(race_speedaward) {
1314                         drawcolorcodedstring(pos, sprintf(_("Speed award: %d ^7(%s^7)"), race_speedaward, race_speedaward_holder), hud_fontsize, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1315                         pos.y += 1.25 * hud_fontsize.y;
1316                 }
1317                 if(race_speedaward_alltimebest) {
1318                         drawcolorcodedstring(pos, sprintf(_("All-time fastest: %d ^7(%s^7)"), race_speedaward_alltimebest, race_speedaward_alltimebest_holder), hud_fontsize, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1319                         pos.y += 1.25 * hud_fontsize.y;
1320                 }
1321                 pos = HUD_DrawScoreboardRankings(pos, playerslots[player_localnum], rgb, bg_size);
1322         }
1323         else if(autocvar_scoreboard_accuracy && spectatee_status == 0 && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL) {
1324                 if(teamplay)
1325                         pos = HUD_DrawScoreboardAccuracyStats(pos, Team_ColorRGB(myteam), bg_size);
1326                 else
1327                         pos = HUD_DrawScoreboardAccuracyStats(pos, rgb, bg_size);
1328         }
1329
1330
1331         if(teamplay)
1332                 pos = HUD_DrawMapStats(pos, Team_ColorRGB(myteam), bg_size);
1333         else
1334                 pos = HUD_DrawMapStats(pos, rgb, bg_size);
1335
1336         // List spectators
1337         float specs;
1338         specs = 0;
1339         tmp = pos;
1340         vector item_size;
1341         item_size_x = sbwidth;
1342         item_size_y = hud_fontsize.y * 1.25;
1343         item_size_z = 0;
1344         for(pl = players.sort_next; pl; pl = pl.sort_next)
1345         {
1346                 if(pl.team != NUM_SPECTATOR)
1347                         continue;
1348                 pos.y += 1.25 * hud_fontsize.y;
1349                 HUD_PrintScoreboardItem(pos, item_size, pl, (pl.sv_entnum == player_localnum), specs);
1350                 ++specs;
1351         }
1352
1353         if(specs)
1354         {
1355                 draw_beginBoldFont();
1356                 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1357                 draw_endBoldFont();
1358                 pos.y += 1.25 * hud_fontsize.y;
1359         }
1360
1361         // Print info string
1362         float tl, fl, ll;
1363         str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1364         tl = getstatf(STAT_TIMELIMIT);
1365         fl = getstatf(STAT_FRAGLIMIT);
1366         ll = getstatf(STAT_LEADLIMIT);
1367         if(gametype == MAPINFO_TYPE_LMS)
1368         {
1369                 if(tl > 0)
1370                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1371         }
1372         else
1373         {
1374                 if(tl > 0)
1375                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1376                 if(fl > 0)
1377                 {
1378                         if(tl > 0)
1379                                 str = strcat(str, _(" or"));
1380                         if(teamplay)
1381                         {
1382                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], fl),
1383                                         (teamscores_label[ts_primary] == "score")   ? CTX(_("SCO^points")) :
1384                                         (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1385                                         TranslateScoresLabel(teamscores_label[ts_primary])));
1386                         }
1387                         else
1388                         {
1389                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags[ps_primary], fl),
1390                                         (scores_label[ps_primary] == "score")   ? CTX(_("SCO^points")) :
1391                                         (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1392                                         TranslateScoresLabel(scores_label[ps_primary])));
1393                         }
1394                 }
1395                 if(ll > 0)
1396                 {
1397                         if(tl > 0 || fl > 0)
1398                                 str = strcat(str, _(" or"));
1399                         if(teamplay)
1400                         {
1401                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], ll),
1402                                         (teamscores_label[ts_primary] == "score")   ? CTX(_("SCO^points")) :
1403                                         (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1404                                         TranslateScoresLabel(teamscores_label[ts_primary])));
1405                         }
1406                         else
1407                         {
1408                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags[ps_primary], ll),
1409                                         (scores_label[ps_primary] == "score")   ? CTX(_("SCO^points")) :
1410                                         (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1411                                         TranslateScoresLabel(scores_label[ps_primary])));
1412                         }
1413                 }
1414         }
1415
1416         pos.y += 1.2 * hud_fontsize.y;
1417         drawcolorcodedstring(pos + '0.5 0 0' * (sbwidth - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1418
1419         // print information about respawn status
1420         float respawn_time = getstatf(STAT_RESPAWN_TIME);
1421         if(!intermission)
1422         if(respawn_time)
1423         {
1424                 if(respawn_time < 0)
1425                 {
1426                         // a negative number means we are awaiting respawn, time value is still the same
1427                         respawn_time *= -1; // remove mark now that we checked it
1428                         respawn_time = max(time, respawn_time); // don't show a negative value while the server is respawning the player (lag)
1429
1430                         str = sprintf(_("^1Respawning in ^3%s^1..."),
1431                                 (autocvar_scoreboard_respawntime_decimals ?
1432                                         count_seconds_decs(respawn_time - time, autocvar_scoreboard_respawntime_decimals)
1433                                         :
1434                                         count_seconds(respawn_time - time)
1435                                 )
1436                         );
1437                 }
1438                 else if(time < respawn_time)
1439                 {
1440                         str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1441                                 (autocvar_scoreboard_respawntime_decimals ?
1442                                         count_seconds_decs(respawn_time - time, autocvar_scoreboard_respawntime_decimals)
1443                                         :
1444                                         count_seconds(respawn_time - time)
1445                                 )
1446                         );
1447                 }
1448                 else if(time >= respawn_time)
1449                         str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1450
1451                 pos.y += 1.2 * hud_fontsize.y;
1452                 drawcolorcodedstring(pos + '0.5 0 0' * (sbwidth - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1453         }
1454
1455         scoreboard_bottom = pos.y + 2 * hud_fontsize.y;
1456 }