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