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