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