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