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