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