]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/scores.qc
Merge commit 'origin/fruitiex/newpanelhud'
[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, "::", GetPlayerScoreString(world, 2)); // make this 1 once we can
373
374         fullstatus = cvar("g_full_getstatus_responses");
375
376         if(teamscores_entities_count)
377         {
378                 float t;
379
380                 s = strcat(s, ":", GetTeamScoreString(0, 1));
381                 for(t = 0; t < 16; ++t)
382                         if(teamscorekeepers[t])
383                                 s = strcat(s, ":", ftos(t+1), ":", GetTeamScoreString(t+1, 1));
384
385                 WinningConditionHelper_winnerteam = -1;
386                 WinningConditionHelper_secondteam = -1;
387                 winnerscorekeeper = world;
388                 secondscorekeeper = world;
389                 for(t = 0; t < 16; ++t)
390                 {
391                         sk = teamscorekeepers[t];
392                         c = TeamScore_Compare(winnerscorekeeper, sk);
393                         if(c < 0)
394                         {
395                                 WinningConditionHelper_secondteam = WinningConditionHelper_winnerteam;
396                                 WinningConditionHelper_winnerteam = t + 1;
397                                 secondscorekeeper = winnerscorekeeper;
398                                 winnerscorekeeper = sk;
399                         }
400                         else
401                         {
402                                 c = TeamScore_Compare(secondscorekeeper, sk);
403                                 if(c < 0)
404                                 {
405                                         WinningConditionHelper_secondteam = t + 1;
406                                         secondscorekeeper = sk;
407                                 }
408                         }
409                 }
410
411                 WinningConditionHelper_equality = (TeamScore_Compare(winnerscorekeeper, secondscorekeeper) == 0);
412                 if(WinningConditionHelper_equality)
413                         WinningConditionHelper_winnerteam = WinningConditionHelper_secondteam = -1;
414
415                 WinningConditionHelper_topscore = winnerscorekeeper.teamscores_primary;
416                 WinningConditionHelper_secondscore = secondscorekeeper.teamscores_primary;
417                 WinningConditionHelper_lowerisbetter = (teamscores_flags_primary & SFL_LOWER_IS_BETTER);
418                 WinningConditionHelper_zeroisworst = (teamscores_flags_primary & SFL_ZERO_IS_WORST);
419
420                 WinningConditionHelper_winner = world; // not supported in teamplay
421                 WinningConditionHelper_second = world; // not supported in teamplay
422         }
423         else
424         {
425                 WinningConditionHelper_winner = world;
426                 WinningConditionHelper_second = world;
427                 winnerscorekeeper = world;
428                 secondscorekeeper = world;
429                 FOR_EACH_PLAYER(p)
430                 {
431                         sk = p.scorekeeper;
432                         c = PlayerScore_Compare(winnerscorekeeper, sk);
433                         if(c < 0)
434                         {
435                                 WinningConditionHelper_second = WinningConditionHelper_winner;
436                                 WinningConditionHelper_winner = p;
437                                 secondscorekeeper = winnerscorekeeper;
438                                 winnerscorekeeper = sk;
439                         }
440                         else
441                         {
442                                 c = PlayerScore_Compare(secondscorekeeper, sk);
443                                 if(c < 0)
444                                 {
445                                         WinningConditionHelper_second = p;
446                                         secondscorekeeper = sk;
447                                 }
448                         }
449                 }
450
451                 WinningConditionHelper_equality = (PlayerScore_Compare(winnerscorekeeper, secondscorekeeper) == 0);
452                 if(WinningConditionHelper_equality)
453                         WinningConditionHelper_winner = WinningConditionHelper_second = world;
454
455                 WinningConditionHelper_topscore = winnerscorekeeper.scores_primary;
456                 WinningConditionHelper_secondscore = secondscorekeeper.scores_primary;
457                 WinningConditionHelper_lowerisbetter = (scores_flags_primary & SFL_LOWER_IS_BETTER);
458                 WinningConditionHelper_zeroisworst = (scores_flags_primary & SFL_ZERO_IS_WORST);
459
460                 WinningConditionHelper_winnerteam = -1; // no teamplay
461                 WinningConditionHelper_secondteam = -1; // no teamplay
462         }
463
464         if(WinningConditionHelper_topscore == 0)
465         {
466                 if(scores_flags_primary & SFL_ZERO_IS_WORST)
467                 {
468                         if(WinningConditionHelper_lowerisbetter)
469                                 WinningConditionHelper_topscore = 999999999;
470                         else
471                                 WinningConditionHelper_topscore = -999999999;
472                 }
473                 WinningConditionHelper_equality = 0;
474         }
475
476         if(WinningConditionHelper_secondscore == 0)
477         {
478                 if(scores_flags_primary & SFL_ZERO_IS_WORST)
479                 {
480                         if(WinningConditionHelper_lowerisbetter)
481                                 WinningConditionHelper_secondscore = 999999999;
482                         else
483                                 WinningConditionHelper_secondscore = -999999999;
484                 }
485         }
486
487         if(worldstatus)
488                 strunzone(worldstatus);
489         worldstatus = strzone(s);
490
491         FOR_EACH_CLIENT(p)
492         {
493                 if(fullstatus)
494                 {
495                         s = GetPlayerScoreString(p, 1);
496                         if(clienttype(p) == CLIENTTYPE_REAL)
497                                 s = strcat(s, ":human");
498                         else
499                                 s = strcat(s, ":bot");
500                         if(p.classname != "player" && !g_arena && !g_ca && !g_lms)
501                                 s = strcat(s, ":spectator");
502                 }
503                 else
504                 {
505                         if(p.classname == "player" || g_arena || g_ca || g_lms)
506                                 s = GetPlayerScoreString(p, 2);
507                         else
508                                 s = "-666";
509                 }
510
511                 if(p.clientstatus)
512                         strunzone(p.clientstatus);
513                 p.clientstatus = strzone(s);
514         }
515 }
516
517 string GetScoreLogLabel(string label, float fl)
518 {
519         if(fl & SFL_LOWER_IS_BETTER)
520                 label = strcat(label, "<");
521         if(fl & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
522                 label = strcat(label, "!!");
523         else if(fl & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
524                 label = strcat(label, "!");
525         return label;
526 }
527
528 string GetPlayerScoreString(entity pl, float shortString)
529 {
530         string out;
531         entity sk;
532         float i, f;
533         string l;
534
535         out = "";
536         if(!pl)
537         {
538                 // label
539                 for(i = 0; i < MAX_SCORE; ++i)
540                         if(scores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
541                         {
542                                 f = scores_flags[i];
543                                 l = scores_label[i];
544                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
545                         }
546                 if(shortString < 2)
547                 for(i = 0; i < MAX_SCORE; ++i)
548                         if(scores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
549                         {
550                                 f = scores_flags[i];
551                                 l = scores_label[i];
552                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
553                         }
554                 if(shortString < 1)
555                 for(i = 0; i < MAX_SCORE; ++i)
556                         if(scores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_PRIMARY)
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                 out = substring(out, 0, strlen(out) - 1);
564         }
565         else if((sk = pl.scorekeeper))
566         {
567                 for(i = 0; i < MAX_SCORE; ++i)
568                         if(scores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
569                                 out = strcat(out, ftos(sk.(scores[i])), ",");
570                 if(shortString < 2)
571                 for(i = 0; i < MAX_SCORE; ++i)
572                         if(scores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
573                                 out = strcat(out, ftos(sk.(scores[i])), ",");
574                 if(shortString < 1)
575                 for(i = 0; i < MAX_SCORE; ++i)
576                         if(scores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_PRIMARY)
577                         if(scores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_SECONDARY)
578                                 out = strcat(out, ftos(sk.(scores[i])), ",");
579                 out = substring(out, 0, strlen(out) - 1);
580         }
581         return out;
582 }
583
584 string GetTeamScoreString(float tm, float shortString)
585 {
586         string out;
587         entity sk;
588         float i, f;
589         string l;
590
591         out = "";
592         if(tm == 0)
593         {
594                 // label
595                 for(i = 0; i < MAX_SCORE; ++i)
596                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
597                         {
598                                 f = teamscores_flags[i];
599                                 l = teamscores_label[i];
600                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
601                         }
602                 if(shortString < 2)
603                 for(i = 0; i < MAX_SCORE; ++i)
604                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
605                         {
606                                 f = teamscores_flags[i];
607                                 l = teamscores_label[i];
608                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
609                         }
610                 if(shortString < 1)
611                 for(i = 0; i < MAX_SCORE; ++i)
612                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_PRIMARY)
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                 out = substring(out, 0, strlen(out) - 1);
620         }
621         else if((sk = teamscorekeepers[tm - 1]))
622         {
623                 for(i = 0; i < MAX_TEAMSCORE; ++i)
624                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
625                                 out = strcat(out, ftos(sk.(teamscores[i])), ",");
626                 if(shortString < 2)
627                 for(i = 0; i < MAX_TEAMSCORE; ++i)
628                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
629                                 out = strcat(out, ftos(sk.(teamscores[i])), ",");
630                 if(shortString < 1)
631                 for(i = 0; i < MAX_TEAMSCORE; ++i)
632                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_PRIMARY)
633                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_SECONDARY)
634                                 out = strcat(out, ftos(sk.(teamscores[i])), ",");
635                 out = substring(out, 0, strlen(out) - 1);
636         }
637         return out;
638 }
639
640 float PlayerTeamScore_Compare(entity p1, entity p2)
641 {
642         if(teamscores_entities_count)
643                 if(p1.team != p2.team)
644                 {
645                         entity t1, t2;
646                         float r;
647                         t1 = teamscorekeepers[p1.team - 1];
648                         t2 = teamscorekeepers[p2.team - 1];
649                         r = TeamScore_Compare(t1, t2);
650                         if(r == 0) // ensure a deterministic order
651                                 r = p1.team - p2.team;
652                         return r;
653                 }
654         
655         return PlayerScore_Compare(p1.scorekeeper, p2.scorekeeper);
656 }
657
658 entity PlayerScore_Sort(.float field)
659 {
660         entity p, plist, pprev, pbest, pbestprev, pfirst, plast;
661         float i;
662
663         plist = world;
664
665         FOR_EACH_CLIENT(p)
666                 p.field = 0;
667
668         FOR_EACH_PLAYER(p) if(p.scorekeeper)
669         {
670                 p.chain = plist;
671                 plist = p;
672         }
673         // Now plist points to the whole list.
674         
675         pfirst = plast = world;
676
677         i = 0;
678         while(plist)
679         {
680                 pprev = pbestprev = world;
681                 pbest = plist;
682                 for(p = plist; (pprev = p), (p = p.chain); )
683                 {
684                         if(PlayerTeamScore_Compare(p, pbest) > 0)
685                         {
686                                 pbest = p;
687                                 pbestprev = pprev;
688                         }
689                 }
690
691                 // remove pbest out of the chain
692                 if(pbestprev == world)
693                         plist = pbest.chain;
694                 else
695                         pbestprev.chain = pbest.chain;
696                 pbest.chain = world;
697
698                 pbest.field = ++i;
699
700                 if not(pfirst)
701                         pfirst = pbest;
702                 if(plast)
703                         plast.chain = pbest;
704                 plast = pbest;
705         }
706
707         return pfirst;
708 }
709
710 float TeamScore_GetCompareValue(float t)
711 {
712         float s;
713         entity sk;
714
715         if(t <= 0 || t >= 16)
716         {
717                 if(gameover)
718                         return 0;
719                 error("Reading score of invalid team!");
720         }
721
722         sk = teamscorekeepers[t - 1];
723         if not(sk)
724                 return -999999999;
725         s = sk.teamscores_primary;
726         if(teamscores_flags_primary & SFL_ZERO_IS_WORST)
727                 if(!s)
728                         return -999999999;
729         if(teamscores_flags_primary & SFL_LOWER_IS_BETTER)
730                 s = -s;
731         return s;
732 }
733
734 #define NAMEWIDTH 22
735 #define SCORESWIDTH 58
736 // TODO put this somewhere in common?
737 string Score_NicePrint_ItemColor(float vflags)
738 {
739         if(vflags & SFL_SORT_PRIO_PRIMARY)
740                 return "^3";
741         else if(vflags & SFL_SORT_PRIO_SECONDARY)
742                 return "^5";
743         else
744                 return "^7";
745 }
746
747 void Score_NicePrint_Team(entity to, float t, float w)
748 {
749         string s, s2;
750         float i;
751         entity sk;
752         float fl, sc;
753         s = "";
754
755         sk = teamscorekeepers[t - 1];
756         if(sk)
757         {
758                 s = strcat(s, ColoredTeamName(t));
759                 for(i = 0; i < MAX_TEAMSCORE; ++i)
760                         if(teamscores_label[i] != "")
761                         {
762                                 fl = teamscores_flags[i];
763                                 sc = sk.(teamscores[i]);
764                                 s = strcat(s, " ", Score_NicePrint_ItemColor(fl), ScoreString(fl, sc));
765                         }
766         }
767         else
768                 s = "Scores:";
769
770         s = strcat(s, strpad(max(0, NAMEWIDTH - strlennocol(s)), ""));
771         
772         for(i = 0; i < MAX_SCORE; ++i)
773                 if(scores_label[i] != "")
774                 {
775                         fl = scores_flags[i];
776                         s2 = scores_label[i];
777                         s = strcat(s, " ", Score_NicePrint_ItemColor(fl), strpad(-w, substring(s2, 0, w)));
778                 }
779
780         print_to(to, s);
781 }
782
783 void Score_NicePrint_Player(entity to, entity p, float w)
784 {
785         string s;
786         float i;
787         entity sk;
788         float fl, sc;
789         s = "  ";
790
791         sk = p.scorekeeper;
792
793         s = strcat(s, p.netname);
794         for(;;)
795         {
796                 i = strlennocol(s) - NAMEWIDTH;
797                 if(i > 0)
798                         s = substring(s, 0, strlen(s) - i);
799                 else
800                 {
801                         s = strcat(s, strpad(i, ""));
802                         break;
803                 }
804         }
805         
806         for(i = 0; i < MAX_SCORE; ++i)
807                 if(scores_label[i] != "")
808                 {
809                         fl = scores_flags[i];
810                         sc = sk.(scores[i]);
811                         s = strcat(s, " ", Score_NicePrint_ItemColor(fl), strpad(-w, ScoreString(fl, sc)));
812                 }
813
814         print_to(to, s);
815 }
816
817 void Score_NicePrint_Spectators(entity to)
818 {
819         print_to(to, "Spectators:");
820 }
821
822 void Score_NicePrint_Spectator(entity to, entity p)
823 {
824         print_to(to, strcat("  ", p.netname));
825 }
826
827 .float score_dummyfield;
828 void Score_NicePrint(entity to)
829 {
830         entity p;
831         float t, i;
832         float w;
833
834         t = 0;
835         for(i = 0; i < MAX_SCORE; ++i)
836                 if(scores_label[i] != "")
837                         ++t;
838         w = bound(6, floor(SCORESWIDTH / t - 1), 9);
839
840         p = PlayerScore_Sort(score_dummyfield);
841         t = -1;
842
843         if(!teamscores_entities_count)
844                 Score_NicePrint_Team(to, t, w);
845         while(p)
846         {
847                 if(teamscores_entities_count)
848                         if(t != p.team)
849                                 Score_NicePrint_Team(to, p.team, w);
850                 Score_NicePrint_Player(to, p, w);
851                 t = p.team;
852                 p = p.chain;
853         }
854         
855         t = 0;
856         FOR_EACH_CLIENT(p)
857         if(p.classname != "player")
858         {
859                 if not(t)
860                         Score_NicePrint_Spectators(to);
861                 Score_NicePrint_Spectator(to, p);
862                 t = 1;
863         }
864 }
865