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