]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/scores.qc
make weapon death messages translatable
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / scores.qc
1 .entity scorekeeper;
2 entity teamscorekeepers[16];
3 string scores_label[MAX_SCORE];
4 float scores_flags[MAX_SCORE];
5 string teamscores_label[MAX_TEAMSCORE];
6 float teamscores_flags[MAX_TEAMSCORE];
7 float teamscores_entities_count;
8 var .float scores_primary;
9 var .float teamscores_primary;
10 float scores_flags_primary;
11 float teamscores_flags_primary;
12
13 vector ScoreField_Compare(entity t1, entity t2, .float field, float fieldflags, vector previous) // returns: cmp value, best prio
14 {
15         if(!(fieldflags & SFL_SORT_PRIO_MASK)) // column does not sort
16                 return previous;
17         if(fieldflags & SFL_SORT_PRIO_MASK < previous_y)
18                 return previous;
19         if(t1.field == t2.field)
20                 return previous;
21
22         previous_y = fieldflags & SFL_SORT_PRIO_MASK;
23
24         if(fieldflags & SFL_ZERO_IS_WORST)
25         {
26                 if(t1.field == 0)
27                 {
28                         previous_x = -1;
29                         return previous;
30                 }
31                 else if(t2.field == 0)
32                 {
33                         previous_x = +1;
34                         return previous;
35                 }
36         }
37
38         if(fieldflags & SFL_LOWER_IS_BETTER)
39                 previous_x = (t2.field - t1.field);
40         else
41                 previous_x = (t1.field - t2.field);
42
43         return previous;
44 }
45
46 /*
47  * teamscore entities
48  */
49
50 float TeamScore_SendEntity(entity to, float sendflags)
51 {
52         float i, p, longflags;
53
54         WriteByte(MSG_ENTITY, ENT_CLIENT_TEAMSCORES);
55         WriteByte(MSG_ENTITY, self.team - 1);
56
57         longflags = 0;
58         for(i = 0, p = 1; i < MAX_TEAMSCORE; ++i, p *= 2)
59                 if(self.teamscores[i] > 127 || self.teamscores[i] <= -128)
60                         longflags |= p;
61
62 #if MAX_TEAMSCORE <= 8
63         WriteByte(MSG_ENTITY, sendflags);
64         WriteByte(MSG_ENTITY, longflags);
65 #else
66         WriteShort(MSG_ENTITY, sendflags);
67         WriteShort(MSG_ENTITY, longflags);
68 #endif
69         for(i = 0, p = 1; i < MAX_TEAMSCORE; ++i, p *= 2)
70                 if(sendflags & p)
71                 {
72                         if(longflags & p)
73                                 WriteInt24_t(MSG_ENTITY, self.teamscores[i]);
74                         else
75                                 WriteChar(MSG_ENTITY, self.teamscores[i]);
76                 }
77
78         return TRUE;
79 }
80
81 void TeamScore_Spawn(float t, string name)
82 {
83         entity ts;
84         ts = spawn();
85         ts.classname = "csqc_score_team";
86         ts.netname = name; // not used yet, FIXME
87         ts.team = t;
88         Net_LinkEntity(ts, FALSE, 0, TeamScore_SendEntity);
89         teamscorekeepers[t - 1] = ts;
90         ++teamscores_entities_count;
91 }
92
93 float TeamScore_AddToTeam(float t, float scorefield, float score)
94 {
95         entity s;
96
97         if(gameover)
98                 score = 0;
99
100         if(!scores_initialized) return 0; // FIXME remove this when everything uses this system
101         if(t <= 0 || t >= 16)
102         {
103                 if(gameover)
104                         return 0;
105                 error("Adding score to invalid team!");
106         }
107         s = teamscorekeepers[t - 1];
108         if(!s)
109         {
110                 if(gameover)
111                         return 0;
112                 error("Adding score to unknown team!");
113         }
114         if(score)
115                 if(teamscores_label[scorefield] != "")
116                         s.SendFlags |= pow(2, scorefield);
117         return (s.(teamscores[scorefield]) += score);
118 }
119
120 float TeamScore_Add(entity player, float scorefield, float score)
121 {
122         return TeamScore_AddToTeam(player.team, scorefield, score);
123 }
124
125 float TeamScore_Compare(entity t1, entity t2)
126 {
127         if(!t1 || !t2) return (!t2) - !t1;
128
129         vector result;
130         float i;
131         for(i = 0; i < MAX_TEAMSCORE; ++i)
132         {
133                 var .float f;
134                 f = teamscores[i];
135                 result = ScoreField_Compare(t1, t2, f, teamscores_flags[i], result);
136         }
137         return result_x;
138 }
139
140 /*
141  * the scoreinfo entity
142  */
143
144 void ScoreInfo_SetLabel_PlayerScore(float i, string label, float scoreflags)
145 {
146         scores_label[i] = label;
147         scores_flags[i] = scoreflags;
148         if(scoreflags & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
149         {
150                 scores_primary = scores[i];
151                 scores_flags_primary = scoreflags;
152         }
153         if(label != "")
154         {
155                 PlayerStats_AddEvent(strcat(PLAYERSTATS_TOTAL, label));
156                 PlayerStats_AddEvent(strcat(PLAYERSTATS_SCOREBOARD, label));
157         }
158 }
159
160 void ScoreInfo_SetLabel_TeamScore(float i, string label, float scoreflags)
161 {
162         teamscores_label[i] = label;
163         teamscores_flags[i] = scoreflags;
164         if(scoreflags & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
165         {
166                 teamscores_primary = teamscores[i];
167                 teamscores_flags_primary = scoreflags;
168         }
169 }
170
171 float ScoreInfo_SendEntity(entity to, float sf)
172 {
173         float i;
174         WriteByte(MSG_ENTITY, ENT_CLIENT_SCORES_INFO);
175         WriteByte(MSG_ENTITY, game);
176         for(i = 0; i < MAX_SCORE; ++i)
177         {
178                 WriteString(MSG_ENTITY, scores_label[i]);
179                 WriteByte(MSG_ENTITY, scores_flags[i]);
180         }
181         for(i = 0; i < MAX_TEAMSCORE; ++i)
182         {
183                 WriteString(MSG_ENTITY, teamscores_label[i]);
184                 WriteByte(MSG_ENTITY, teamscores_flags[i]);
185         }
186         return TRUE;
187 }
188
189 void ScoreInfo_Init(float teams)
190 {
191         if(scores_initialized)
192         {
193                 scores_initialized.SendFlags |= 1; // force a resend
194         }
195         else
196         {
197                 scores_initialized = spawn();
198                 scores_initialized.classname = "ent_client_scoreinfo";
199                 Net_LinkEntity(scores_initialized, FALSE, 0, ScoreInfo_SendEntity);
200         }
201         if(teams >= 1)
202                 TeamScore_Spawn(COLOR_TEAM1, "Red");
203         if(teams >= 2)
204                 TeamScore_Spawn(COLOR_TEAM2, "Blue");
205         if(teams >= 3)
206                 TeamScore_Spawn(COLOR_TEAM3, "Yellow");
207         if(teams >= 4)
208                 TeamScore_Spawn(COLOR_TEAM4, "Pink");
209 }
210
211 /*
212  * per-player score entities
213  */
214
215 float PlayerScore_SendEntity(entity to, float sendflags)
216 {
217         float i, p, longflags;
218
219         WriteByte(MSG_ENTITY, ENT_CLIENT_SCORES);
220         WriteByte(MSG_ENTITY, num_for_edict(self.owner));
221
222         longflags = 0;
223         for(i = 0, p = 1; i < MAX_SCORE; ++i, p *= 2)
224                 if(self.scores[i] > 127 || self.scores[i] <= -128)
225                         longflags |= p;
226
227 #if MAX_SCORE <= 8
228         WriteByte(MSG_ENTITY, sendflags);
229         WriteByte(MSG_ENTITY, longflags);
230 #else
231         WriteShort(MSG_ENTITY, sendflags);
232         WriteShort(MSG_ENTITY, longflags);
233 #endif
234         for(i = 0, p = 1; i < MAX_SCORE; ++i, p *= 2)
235                 if(sendflags & p)
236                 {
237                         if(longflags & p)
238                                 WriteInt24_t(MSG_ENTITY, self.scores[i]);
239                         else
240                                 WriteChar(MSG_ENTITY, self.scores[i]);
241                 }
242
243         return TRUE;
244 }
245
246 void PlayerScore_Clear(entity player)
247 {
248         entity sk;
249         float i;
250
251         if(teamscores_entities_count)
252                 return;
253
254         if(g_lms) return;
255         if(g_arena || g_ca) return;
256         if(g_race && !g_race_qualifying) return;
257
258         sk = player.scorekeeper;
259         for(i = 0; i < MAX_SCORE; ++i)
260         {
261                 if(sk.(scores[i]) != 0)
262                         if(scores_label[i] != "")
263                                 sk.SendFlags |= pow(2, i);
264                 sk.(scores[i]) = 0;
265         }
266 }
267
268 void Score_ClearAll()
269 {
270         entity p, sk;
271         float i, t;
272         FOR_EACH_CLIENTSLOT(p)
273         {
274                 sk = p.scorekeeper;
275                 if(!sk)
276                         continue;
277                 for(i = 0; i < MAX_SCORE; ++i)
278                 {
279                         if(sk.(scores[i]) != 0)
280                                 if(scores_label[i] != "")
281                                         sk.SendFlags |= pow(2, i);
282                         sk.(scores[i]) = 0;
283                 }
284         }
285         for(t = 0; t < 16; ++t)
286         {
287                 sk = teamscorekeepers[t];
288                 if(!sk)
289                         continue;
290                 for(i = 0; i < MAX_TEAMSCORE; ++i)
291                 {
292                         if(sk.(teamscores[i]) != 0)
293                                 if(teamscores_label[i] != "")
294                                         sk.SendFlags |= pow(2, i);
295                         sk.(teamscores[i]) = 0;
296                 }
297         }
298 }
299
300 void PlayerScore_Attach(entity player)
301 {
302         entity sk;
303         if(player.scorekeeper)
304                 error("player already has a scorekeeper");
305         sk = spawn();
306         sk.owner = player;
307         Net_LinkEntity(sk, FALSE, 0, PlayerScore_SendEntity);
308         player.scorekeeper = sk;
309 }
310
311 void PlayerScore_Detach(entity player)
312 {
313         if(!player.scorekeeper)
314                 error("player has no scorekeeper");
315         remove(player.scorekeeper);
316         player.scorekeeper = world;
317 }
318
319 float PlayerScore_Add(entity player, float scorefield, float score)
320 {
321         entity s;
322
323         if(gameover)
324         if not(g_lms && scorefield == SP_LMS_RANK) // allow writing to this field in intermission as it is needed for newly joining players
325                 score = 0;
326
327         if(!scores_initialized) return 0; // FIXME remove this when everything uses this system
328         s = player.scorekeeper;
329         if(!s)
330         {
331                 if(gameover)
332                         return 0;
333                 backtrace("Adding score to unknown player!");
334                 return 0;
335         }
336         if(score)
337                 if(scores_label[scorefield] != "")
338                         s.SendFlags |= pow(2, scorefield);
339         PlayerStats_Event(s.owner, strcat(PLAYERSTATS_TOTAL, scores_label[scorefield]), score);
340         s.(scores_accumulated[scorefield]) += score;
341         return (s.(scores[scorefield]) += score);
342 }
343
344 float PlayerTeamScore_Add(entity player, float pscorefield, float tscorefield, float score)
345 {
346         float r;
347         r = PlayerScore_Add(player, pscorefield, score);
348         if(teamscores_entities_count) // only for teamplay
349                 r = TeamScore_Add(player, tscorefield, score);
350         return r;
351 }
352
353 float PlayerScore_Compare(entity t1, entity t2)
354 {
355         if(!t1 || !t2) return (!t2) - !t1;
356
357         vector result;
358         float i;
359         for(i = 0; i < MAX_SCORE; ++i)
360         {
361                 var .float f;
362                 f = scores[i];
363                 result = ScoreField_Compare(t1, t2, f, scores_flags[i], result);
364         }
365         return result_x;
366 }
367
368 void WinningConditionHelper()
369 {
370         float c;
371         string s;
372         entity p;
373         float fullstatus;
374         entity winnerscorekeeper;
375         entity secondscorekeeper;
376         entity sk;
377
378         s = GetGametype();
379         s = strcat(s, ":", autocvar_g_xonoticversion);
380         s = strcat(s, ":P", ftos(cvar_purechanges_count));
381         s = strcat(s, "::", GetPlayerScoreString(world, 1)); // make this 1 once we can, note: this doesn't contain any :<letter>
382
383         fullstatus = autocvar_g_full_getstatus_responses;
384
385         if(teamscores_entities_count)
386         {
387                 float t;
388
389                 s = strcat(s, ":", GetTeamScoreString(0, 1));
390                 for(t = 0; t < 16; ++t)
391                         if(teamscorekeepers[t])
392                                 s = strcat(s, ":", ftos(t+1), ":", GetTeamScoreString(t+1, 1));
393
394                 WinningConditionHelper_winnerteam = -1;
395                 WinningConditionHelper_secondteam = -1;
396                 winnerscorekeeper = world;
397                 secondscorekeeper = world;
398                 for(t = 0; t < 16; ++t)
399                 {
400                         sk = teamscorekeepers[t];
401                         c = TeamScore_Compare(winnerscorekeeper, sk);
402                         if(c < 0)
403                         {
404                                 WinningConditionHelper_secondteam = WinningConditionHelper_winnerteam;
405                                 WinningConditionHelper_winnerteam = t + 1;
406                                 secondscorekeeper = winnerscorekeeper;
407                                 winnerscorekeeper = sk;
408                         }
409                         else
410                         {
411                                 c = TeamScore_Compare(secondscorekeeper, sk);
412                                 if(c < 0)
413                                 {
414                                         WinningConditionHelper_secondteam = t + 1;
415                                         secondscorekeeper = sk;
416                                 }
417                         }
418                 }
419
420                 WinningConditionHelper_equality = (TeamScore_Compare(winnerscorekeeper, secondscorekeeper) == 0);
421                 if(WinningConditionHelper_equality)
422                         WinningConditionHelper_winnerteam = WinningConditionHelper_secondteam = -1;
423
424                 WinningConditionHelper_topscore = winnerscorekeeper.teamscores_primary;
425                 WinningConditionHelper_secondscore = secondscorekeeper.teamscores_primary;
426                 WinningConditionHelper_lowerisbetter = (teamscores_flags_primary & SFL_LOWER_IS_BETTER);
427                 WinningConditionHelper_zeroisworst = (teamscores_flags_primary & SFL_ZERO_IS_WORST);
428
429                 WinningConditionHelper_winner = world; // not supported in teamplay
430                 WinningConditionHelper_second = world; // not supported in teamplay
431         }
432         else
433         {
434                 WinningConditionHelper_winner = world;
435                 WinningConditionHelper_second = world;
436                 winnerscorekeeper = world;
437                 secondscorekeeper = world;
438                 FOR_EACH_PLAYER(p)
439                 {
440                         sk = p.scorekeeper;
441                         c = PlayerScore_Compare(winnerscorekeeper, sk);
442                         if(c < 0)
443                         {
444                                 WinningConditionHelper_second = WinningConditionHelper_winner;
445                                 WinningConditionHelper_winner = p;
446                                 secondscorekeeper = winnerscorekeeper;
447                                 winnerscorekeeper = sk;
448                         }
449                         else
450                         {
451                                 c = PlayerScore_Compare(secondscorekeeper, sk);
452                                 if(c < 0)
453                                 {
454                                         WinningConditionHelper_second = p;
455                                         secondscorekeeper = sk;
456                                 }
457                         }
458                 }
459
460                 WinningConditionHelper_equality = (PlayerScore_Compare(winnerscorekeeper, secondscorekeeper) == 0);
461                 if(WinningConditionHelper_equality)
462                         WinningConditionHelper_winner = WinningConditionHelper_second = world;
463
464                 WinningConditionHelper_topscore = winnerscorekeeper.scores_primary;
465                 WinningConditionHelper_secondscore = secondscorekeeper.scores_primary;
466                 WinningConditionHelper_lowerisbetter = (scores_flags_primary & SFL_LOWER_IS_BETTER);
467                 WinningConditionHelper_zeroisworst = (scores_flags_primary & SFL_ZERO_IS_WORST);
468
469                 WinningConditionHelper_winnerteam = -1; // no teamplay
470                 WinningConditionHelper_secondteam = -1; // no teamplay
471         }
472
473         if(WinningConditionHelper_topscore == 0)
474         {
475                 if(scores_flags_primary & SFL_ZERO_IS_WORST)
476                 {
477                         if(WinningConditionHelper_lowerisbetter)
478                                 WinningConditionHelper_topscore = 999999999;
479                         else
480                                 WinningConditionHelper_topscore = -999999999;
481                 }
482                 WinningConditionHelper_equality = 0;
483         }
484
485         if(WinningConditionHelper_secondscore == 0)
486         {
487                 if(scores_flags_primary & SFL_ZERO_IS_WORST)
488                 {
489                         if(WinningConditionHelper_lowerisbetter)
490                                 WinningConditionHelper_secondscore = 999999999;
491                         else
492                                 WinningConditionHelper_secondscore = -999999999;
493                 }
494         }
495
496         if(worldstatus)
497                 strunzone(worldstatus);
498         worldstatus = strzone(s);
499
500         FOR_EACH_CLIENT(p)
501         {
502                 if(fullstatus)
503                 {
504                         s = GetPlayerScoreString(p, 1);
505                         if(clienttype(p) == CLIENTTYPE_REAL)
506                                 s = strcat(s, ":human");
507                         else
508                                 s = strcat(s, ":bot");
509                         if(p.classname != "player" && !g_arena && !g_ca && !g_lms)
510                                 s = strcat(s, ":spectator");
511                 }
512                 else
513                 {
514                         if(p.classname == "player" || g_arena || g_ca || g_lms)
515                                 s = GetPlayerScoreString(p, 2);
516                         else
517                                 s = "-666";
518                 }
519
520                 if(p.clientstatus)
521                         strunzone(p.clientstatus);
522                 p.clientstatus = strzone(s);
523         }
524 }
525
526 string GetScoreLogLabel(string label, float fl)
527 {
528         if(fl & SFL_LOWER_IS_BETTER)
529                 label = strcat(label, "<");
530         if(fl & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
531                 label = strcat(label, "!!");
532         else if(fl & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
533                 label = strcat(label, "!");
534         return label;
535 }
536
537 string GetPlayerScoreString(entity pl, float shortString)
538 {
539         string out;
540         entity sk;
541         float i, f;
542         string l;
543
544         out = "";
545         if(!pl)
546         {
547                 // label
548                 for(i = 0; i < MAX_SCORE; ++i)
549                         if(scores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
550                         {
551                                 f = scores_flags[i];
552                                 l = scores_label[i];
553                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
554                         }
555                 if(shortString < 2)
556                 for(i = 0; i < MAX_SCORE; ++i)
557                         if(scores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
558                         {
559                                 f = scores_flags[i];
560                                 l = scores_label[i];
561                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
562                         }
563                 if(shortString < 1)
564                 for(i = 0; i < MAX_SCORE; ++i)
565                         if(scores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_PRIMARY)
566                         if(scores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_SECONDARY)
567                         {
568                                 f = scores_flags[i];
569                                 l = scores_label[i];
570                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
571                         }
572                 out = substring(out, 0, strlen(out) - 1);
573         }
574         else if((sk = pl.scorekeeper))
575         {
576                 for(i = 0; i < MAX_SCORE; ++i)
577                         if(scores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
578                                 out = strcat(out, ftos(sk.(scores[i])), ",");
579                 if(shortString < 2)
580                 for(i = 0; i < MAX_SCORE; ++i)
581                         if(scores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
582                                 out = strcat(out, ftos(sk.(scores[i])), ",");
583                 if(shortString < 1)
584                 for(i = 0; i < MAX_SCORE; ++i)
585                         if(scores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_PRIMARY)
586                         if(scores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_SECONDARY)
587                                 out = strcat(out, ftos(sk.(scores[i])), ",");
588                 out = substring(out, 0, strlen(out) - 1);
589         }
590         return out;
591 }
592
593 string GetTeamScoreString(float tm, float shortString)
594 {
595         string out;
596         entity sk;
597         float i, f;
598         string l;
599
600         out = "";
601         if(tm == 0)
602         {
603                 // label
604                 for(i = 0; i < MAX_SCORE; ++i)
605                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
606                         {
607                                 f = teamscores_flags[i];
608                                 l = teamscores_label[i];
609                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
610                         }
611                 if(shortString < 2)
612                 for(i = 0; i < MAX_SCORE; ++i)
613                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
614                         {
615                                 f = teamscores_flags[i];
616                                 l = teamscores_label[i];
617                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
618                         }
619                 if(shortString < 1)
620                 for(i = 0; i < MAX_SCORE; ++i)
621                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_PRIMARY)
622                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_SECONDARY)
623                         {
624                                 f = teamscores_flags[i];
625                                 l = teamscores_label[i];
626                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
627                         }
628                 out = substring(out, 0, strlen(out) - 1);
629         }
630         else if((sk = teamscorekeepers[tm - 1]))
631         {
632                 for(i = 0; i < MAX_TEAMSCORE; ++i)
633                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
634                                 out = strcat(out, ftos(sk.(teamscores[i])), ",");
635                 if(shortString < 2)
636                 for(i = 0; i < MAX_TEAMSCORE; ++i)
637                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
638                                 out = strcat(out, ftos(sk.(teamscores[i])), ",");
639                 if(shortString < 1)
640                 for(i = 0; i < MAX_TEAMSCORE; ++i)
641                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_PRIMARY)
642                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_SECONDARY)
643                                 out = strcat(out, ftos(sk.(teamscores[i])), ",");
644                 out = substring(out, 0, strlen(out) - 1);
645         }
646         return out;
647 }
648
649 float PlayerTeamScore_Compare(entity p1, entity p2)
650 {
651         if(teamscores_entities_count)
652                 if(p1.team != p2.team)
653                 {
654                         entity t1, t2;
655                         float r;
656                         t1 = teamscorekeepers[p1.team - 1];
657                         t2 = teamscorekeepers[p2.team - 1];
658                         r = TeamScore_Compare(t1, t2);
659                         if(r == 0) // ensure a deterministic order
660                                 r = p1.team - p2.team;
661                         return r;
662                 }
663         
664         return PlayerScore_Compare(p1.scorekeeper, p2.scorekeeper);
665 }
666
667 entity PlayerScore_Sort(.float field)
668 {
669         entity p, plist, pprev, pbest, pbestprev, pfirst, plast;
670         float i;
671
672         plist = world;
673
674         FOR_EACH_CLIENT(p)
675                 p.field = 0;
676
677         FOR_EACH_PLAYER(p) if(p.scorekeeper)
678         {
679                 p.chain = plist;
680                 plist = p;
681         }
682         // Now plist points to the whole list.
683         
684         pfirst = plast = world;
685
686         i = 0;
687         while(plist)
688         {
689                 pprev = pbestprev = world;
690                 pbest = plist;
691                 for(p = plist; (pprev = p), (p = p.chain); )
692                 {
693                         if(PlayerTeamScore_Compare(p, pbest) > 0)
694                         {
695                                 pbest = p;
696                                 pbestprev = pprev;
697                         }
698                 }
699
700                 // remove pbest out of the chain
701                 if(pbestprev == world)
702                         plist = pbest.chain;
703                 else
704                         pbestprev.chain = pbest.chain;
705                 pbest.chain = world;
706
707                 pbest.field = ++i;
708
709                 if not(pfirst)
710                         pfirst = pbest;
711                 if(plast)
712                         plast.chain = pbest;
713                 plast = pbest;
714         }
715
716         return pfirst;
717 }
718
719 float TeamScore_GetCompareValue(float t)
720 {
721         float s;
722         entity sk;
723
724         if(t <= 0 || t >= 16)
725         {
726                 if(gameover)
727                         return 0;
728                 error("Reading score of invalid team!");
729         }
730
731         sk = teamscorekeepers[t - 1];
732         if not(sk)
733                 return -999999999;
734         s = sk.teamscores_primary;
735         if(teamscores_flags_primary & SFL_ZERO_IS_WORST)
736                 if(!s)
737                         return -999999999;
738         if(teamscores_flags_primary & SFL_LOWER_IS_BETTER)
739                 s = -s;
740         return s;
741 }
742
743 #define NAMEWIDTH 22
744 #define SCORESWIDTH 58
745 // TODO put this somewhere in common?
746 string Score_NicePrint_ItemColor(float vflags)
747 {
748         if(vflags & SFL_SORT_PRIO_PRIMARY)
749                 return "^3";
750         else if(vflags & SFL_SORT_PRIO_SECONDARY)
751                 return "^5";
752         else
753                 return "^7";
754 }
755
756 void Score_NicePrint_Team(entity to, float t, float w)
757 {
758         string s, s2;
759         float i;
760         entity sk;
761         float fl, sc;
762         s = "";
763
764         sk = teamscorekeepers[t - 1];
765         if(sk)
766         {
767                 s = strcat(s, ColoredTeamName(t));
768                 for(i = 0; i < MAX_TEAMSCORE; ++i)
769                         if(teamscores_label[i] != "")
770                         {
771                                 fl = teamscores_flags[i];
772                                 sc = sk.(teamscores[i]);
773                                 s = strcat(s, " ", Score_NicePrint_ItemColor(fl), ScoreString(fl, sc));
774                         }
775         }
776         else
777                 s = "Scores:";
778
779         s = strcat(s, strpad(max(0, NAMEWIDTH - strlennocol(s)), ""));
780         
781         for(i = 0; i < MAX_SCORE; ++i)
782                 if(scores_label[i] != "")
783                 {
784                         fl = scores_flags[i];
785                         s2 = scores_label[i];
786                         s = strcat(s, " ", Score_NicePrint_ItemColor(fl), strpad(-w, substring(s2, 0, w)));
787                 }
788
789         print_to(to, s);
790 }
791
792 void Score_NicePrint_Player(entity to, entity p, float w)
793 {
794         string s;
795         float i;
796         entity sk;
797         float fl, sc;
798         s = "  ";
799
800         sk = p.scorekeeper;
801
802         s = strcat(s, p.netname);
803         for(;;)
804         {
805                 i = strlennocol(s) - NAMEWIDTH;
806                 if(i > 0)
807                         s = substring(s, 0, strlen(s) - i);
808                 else
809                 {
810                         s = strcat(s, strpad(i, ""));
811                         break;
812                 }
813         }
814         
815         for(i = 0; i < MAX_SCORE; ++i)
816                 if(scores_label[i] != "")
817                 {
818                         fl = scores_flags[i];
819                         sc = sk.(scores[i]);
820                         s = strcat(s, " ", Score_NicePrint_ItemColor(fl), strpad(-w, ScoreString(fl, sc)));
821                 }
822
823         print_to(to, s);
824 }
825
826 void Score_NicePrint_Spectators(entity to)
827 {
828         print_to(to, "Spectators:");
829 }
830
831 void Score_NicePrint_Spectator(entity to, entity p)
832 {
833         print_to(to, strcat("  ", p.netname));
834 }
835
836 .float score_dummyfield;
837 void Score_NicePrint(entity to)
838 {
839         entity p;
840         float t, i;
841         float w;
842
843         t = 0;
844         for(i = 0; i < MAX_SCORE; ++i)
845                 if(scores_label[i] != "")
846                         ++t;
847         w = bound(6, floor(SCORESWIDTH / t - 1), 9);
848
849         p = PlayerScore_Sort(score_dummyfield);
850         t = -1;
851
852         if(!teamscores_entities_count)
853                 Score_NicePrint_Team(to, t, w);
854         while(p)
855         {
856                 if(teamscores_entities_count)
857                         if(t != p.team)
858                                 Score_NicePrint_Team(to, p.team, w);
859                 Score_NicePrint_Player(to, p, w);
860                 t = p.team;
861                 p = p.chain;
862         }
863         
864         t = 0;
865         FOR_EACH_CLIENT(p)
866         if(p.classname != "player")
867         {
868                 if not(t)
869                         Score_NicePrint_Spectators(to);
870                 Score_NicePrint_Spectator(to, p);
871                 t = 1;
872         }
873 }
874
875 void PlayerScore_PlayerStats(entity p)
876 {
877         entity s;
878         float i;
879         s = p.scorekeeper;
880
881         for(i = 0; i < MAX_SCORE; ++i)
882                 if(s.(scores[i]) != 0)
883                         if(scores_label[i] != "")
884                                 PlayerStats_Event(s.owner, strcat(PLAYERSTATS_SCOREBOARD, scores_label[i]), s.(scores[i]));
885 }