]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/scores.qc
fix crash in CTF when bots are leaving at end of match
[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         }
462
463         if(WinningConditionHelper_secondscore == 0)
464         {
465                 if(scores_flags_primary & SFL_ZERO_IS_WORST)
466                 {
467                         if(WinningConditionHelper_lowerisbetter)
468                                 WinningConditionHelper_secondscore = 999999999;
469                         else
470                                 WinningConditionHelper_secondscore = -999999999;
471                 }
472         }
473
474         if(worldstatus)
475                 strunzone(worldstatus);
476         worldstatus = strzone(s);
477
478         FOR_EACH_CLIENT(p)
479         {
480                 if(fullstatus)
481                 {
482                         s = GetPlayerScoreString(p, 1);
483                         if(clienttype(p) == CLIENTTYPE_REAL)
484                                 s = strcat(s, ":human");
485                         else
486                                 s = strcat(s, ":bot");
487                         if(p.classname != "player" && !g_arena && !g_lms)
488                                 s = strcat(s, ":spectator");
489                 }
490                 else
491                 {
492                         if(p.classname == "player" || g_arena || g_lms)
493                                 s = GetPlayerScoreString(p, 2);
494                         else
495                                 s = "-666";
496                 }
497
498                 if(p.clientstatus)
499                         strunzone(p.clientstatus);
500                 p.clientstatus = strzone(s);
501         }
502 }
503
504 string GetScoreLogLabel(string label, float fl)
505 {
506         if(fl & SFL_LOWER_IS_BETTER)
507                 label = strcat(label, "<");
508         if(fl & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
509                 label = strcat(label, "!!");
510         else if(fl & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
511                 label = strcat(label, "!");
512         return label;
513 }
514
515 string GetPlayerScoreString(entity pl, float shortString)
516 {
517         string out;
518         entity sk;
519         float i, f;
520         string l;
521
522         out = "";
523         if(!pl)
524         {
525                 // label
526                 for(i = 0; i < MAX_SCORE; ++i)
527                         if(scores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
528                         {
529                                 f = scores_flags[i];
530                                 l = scores_label[i];
531                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
532                         }
533                 if(shortString < 2)
534                 for(i = 0; i < MAX_SCORE; ++i)
535                         if(scores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
536                         {
537                                 f = scores_flags[i];
538                                 l = scores_label[i];
539                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
540                         }
541                 if(shortString < 1)
542                 for(i = 0; i < MAX_SCORE; ++i)
543                         if(scores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_PRIMARY)
544                         if(scores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_SECONDARY)
545                         {
546                                 f = scores_flags[i];
547                                 l = scores_label[i];
548                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
549                         }
550                 out = substring(out, 0, strlen(out) - 1);
551         }
552         else if((sk = pl.scorekeeper))
553         {
554                 for(i = 0; i < MAX_SCORE; ++i)
555                         if(scores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
556                                 out = strcat(out, ftos(sk.(scores[i])), ",");
557                 if(shortString < 2)
558                 for(i = 0; i < MAX_SCORE; ++i)
559                         if(scores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
560                                 out = strcat(out, ftos(sk.(scores[i])), ",");
561                 if(shortString < 1)
562                 for(i = 0; i < MAX_SCORE; ++i)
563                         if(scores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_PRIMARY)
564                         if(scores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_SECONDARY)
565                                 out = strcat(out, ftos(sk.(scores[i])), ",");
566                 out = substring(out, 0, strlen(out) - 1);
567         }
568         return out;
569 }
570
571 string GetTeamScoreString(float tm, float shortString)
572 {
573         string out;
574         entity sk;
575         float i, f;
576         string l;
577
578         out = "";
579         if(tm == 0)
580         {
581                 // label
582                 for(i = 0; i < MAX_SCORE; ++i)
583                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
584                         {
585                                 f = teamscores_flags[i];
586                                 l = teamscores_label[i];
587                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
588                         }
589                 if(shortString < 2)
590                 for(i = 0; i < MAX_SCORE; ++i)
591                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
592                         {
593                                 f = teamscores_flags[i];
594                                 l = teamscores_label[i];
595                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
596                         }
597                 if(shortString < 1)
598                 for(i = 0; i < MAX_SCORE; ++i)
599                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_PRIMARY)
600                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_SECONDARY)
601                         {
602                                 f = teamscores_flags[i];
603                                 l = teamscores_label[i];
604                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
605                         }
606                 out = substring(out, 0, strlen(out) - 1);
607         }
608         else if((sk = teamscorekeepers[tm - 1]))
609         {
610                 for(i = 0; i < MAX_TEAMSCORE; ++i)
611                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
612                                 out = strcat(out, ftos(sk.(teamscores[i])), ",");
613                 if(shortString < 2)
614                 for(i = 0; i < MAX_TEAMSCORE; ++i)
615                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
616                                 out = strcat(out, ftos(sk.(teamscores[i])), ",");
617                 if(shortString < 1)
618                 for(i = 0; i < MAX_TEAMSCORE; ++i)
619                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_PRIMARY)
620                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_SECONDARY)
621                                 out = strcat(out, ftos(sk.(teamscores[i])), ",");
622                 out = substring(out, 0, strlen(out) - 1);
623         }
624         return out;
625 }
626
627 float PlayerTeamScore_Compare(entity p1, entity p2)
628 {
629         if(teamscores_entities_count)
630                 if(p1.team != p2.team)
631                 {
632                         entity t1, t2;
633                         float r;
634                         t1 = teamscorekeepers[p1.team - 1];
635                         t2 = teamscorekeepers[p2.team - 1];
636                         r = TeamScore_Compare(t1, t2);
637                         if(r == 0) // ensure a deterministic order
638                                 r = p1.team - p2.team;
639                         return r;
640                 }
641         
642         return PlayerScore_Compare(p1.scorekeeper, p2.scorekeeper);
643 }
644
645 entity PlayerScore_Sort(.float field)
646 {
647         entity p, plist, pprev, pbest, pbestprev, pfirst, plast;
648         float i;
649
650         plist = world;
651
652         FOR_EACH_CLIENT(p)
653                 p.field = 0;
654
655         FOR_EACH_PLAYER(p) if(p.scorekeeper)
656         {
657                 p.chain = plist;
658                 plist = p;
659         }
660         // Now plist points to the whole list.
661         
662         pfirst = plast = world;
663
664         i = 0;
665         while(plist)
666         {
667                 pprev = pbestprev = world;
668                 pbest = plist;
669                 for(p = plist; (pprev = p), (p = p.chain); )
670                 {
671                         if(PlayerTeamScore_Compare(p, pbest) > 0)
672                         {
673                                 pbest = p;
674                                 pbestprev = pprev;
675                         }
676                 }
677
678                 // remove pbest out of the chain
679                 if(pbestprev == world)
680                         plist = pbest.chain;
681                 else
682                         pbestprev.chain = pbest.chain;
683                 pbest.chain = world;
684
685                 pbest.field = ++i;
686
687                 if not(pfirst)
688                         pfirst = pbest;
689                 if(plast)
690                         plast.chain = pbest;
691                 plast = pbest;
692         }
693
694         return pfirst;
695 }
696
697 float TeamScore_GetCompareValue(float t)
698 {
699         float s;
700         entity sk;
701
702         if(t <= 0 || t >= 16)
703         {
704                 if(gameover)
705                         return 0;
706                 error("Reading score of invalid team!");
707         }
708
709         sk = teamscorekeepers[t - 1];
710         if not(sk)
711                 return -999999999;
712         s = sk.teamscores_primary;
713         if(teamscores_flags_primary & SFL_ZERO_IS_WORST)
714                 if(!s)
715                         return -999999999;
716         if(teamscores_flags_primary & SFL_LOWER_IS_BETTER)
717                 s = -s;
718         return s;
719 }
720
721 #define NAMEWIDTH 22
722 #define SCORESWIDTH 58
723 // TODO put this somewhere in common?
724 string Score_NicePrint_ItemColor(float vflags)
725 {
726         if(vflags & SFL_SORT_PRIO_PRIMARY)
727                 return "^3";
728         else if(vflags & SFL_SORT_PRIO_SECONDARY)
729                 return "^5";
730         else
731                 return "^7";
732 }
733
734 void Score_NicePrint_Team(entity to, float t, float w)
735 {
736         string s, s2;
737         float i;
738         entity sk;
739         float fl, sc;
740         s = "";
741
742         sk = teamscorekeepers[t - 1];
743         if(sk)
744         {
745                 s = strcat(s, ColoredTeamName(t));
746                 for(i = 0; i < MAX_TEAMSCORE; ++i)
747                         if(teamscores_label[i] != "")
748                         {
749                                 fl = teamscores_flags[i];
750                                 sc = sk.(teamscores[i]);
751                                 s = strcat(s, " ", Score_NicePrint_ItemColor(fl), ScoreString(fl, sc));
752                         }
753         }
754         else
755                 s = "Scores:";
756
757         s = strcat(s, strpad(max(0, NAMEWIDTH - strlennocol(s)), ""));
758         
759         for(i = 0; i < MAX_SCORE; ++i)
760                 if(scores_label[i] != "")
761                 {
762                         fl = scores_flags[i];
763                         s2 = scores_label[i];
764                         s = strcat(s, " ", Score_NicePrint_ItemColor(fl), strpad(-w, substring(s2, 0, w)));
765                 }
766
767         print_to(to, s);
768 }
769
770 void Score_NicePrint_Player(entity to, entity p, float w)
771 {
772         string s;
773         float i;
774         entity sk;
775         float fl, sc;
776         s = "  ";
777
778         sk = p.scorekeeper;
779
780         s = strcat(s, p.netname);
781         for(;;)
782         {
783                 i = strlennocol(s) - NAMEWIDTH;
784                 if(i > 0)
785                         s = substring(s, 0, strlen(s) - i);
786                 else
787                 {
788                         s = strcat(s, strpad(i, ""));
789                         break;
790                 }
791         }
792         
793         for(i = 0; i < MAX_SCORE; ++i)
794                 if(scores_label[i] != "")
795                 {
796                         fl = scores_flags[i];
797                         sc = sk.(scores[i]);
798                         s = strcat(s, " ", Score_NicePrint_ItemColor(fl), strpad(-w, ScoreString(fl, sc)));
799                 }
800
801         print_to(to, s);
802 }
803
804 void Score_NicePrint_Spectators(entity to)
805 {
806         print_to(to, "Spectators:");
807 }
808
809 void Score_NicePrint_Spectator(entity to, entity p)
810 {
811         print_to(to, strcat("  ", p.netname));
812 }
813
814 .float score_dummyfield;
815 void Score_NicePrint(entity to)
816 {
817         entity p;
818         float t, i;
819         float w;
820
821         t = 0;
822         for(i = 0; i < MAX_SCORE; ++i)
823                 if(scores_label[i] != "")
824                         ++t;
825         w = bound(6, floor(SCORESWIDTH / t - 1), 9);
826
827         p = PlayerScore_Sort(score_dummyfield);
828         t = -1;
829
830         if(!teamscores_entities_count)
831                 Score_NicePrint_Team(to, t, w);
832         while(p)
833         {
834                 if(teamscores_entities_count)
835                         if(t != p.team)
836                                 Score_NicePrint_Team(to, p.team, w);
837                 Score_NicePrint_Player(to, p, w);
838                 t = p.team;
839                 p = p.chain;
840         }
841         
842         t = 0;
843         FOR_EACH_CLIENT(p)
844         if(p.classname != "player")
845         {
846                 if not(t)
847                         Score_NicePrint_Spectators(to);
848                 Score_NicePrint_Spectator(to, p);
849                 t = 1;
850         }
851 }
852