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