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