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