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