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