]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/scores.qc
race: let everyone complete his lap when the race is over; players who have completed...
[divverent/nexuiz.git] / data / qcsrc / server / scores.qc
1 .float scores[MAX_SCORE];
2 .float teamscores[MAX_TEAMSCORE];
3
4 .entity scorekeeper;
5 entity teamscorekeepers[16];
6 string scores_label[MAX_SCORE];
7 float scores_flags[MAX_SCORE];
8 string teamscores_label[MAX_TEAMSCORE];
9 float teamscores_flags[MAX_TEAMSCORE];
10 float teamscores_entities_count;
11 var .float scores_primary;
12 var .float teamscores_primary;
13 float scores_flags_primary;
14 float teamscores_flags_primary;
15
16 void Net_LinkEntity(entity e)
17 {
18         e.model = "net_entity";
19         e.modelindex = 1;
20         e.effects = EF_NODEPTHTEST | EF_LOWPRECISION;
21 }
22
23 vector ScoreField_Compare(entity t1, entity t2, .float field, float fieldflags, vector previous) // returns: cmp value, best prio
24 {
25         if(!(fieldflags & SFL_SORT_PRIO_MASK)) // column does not sort
26                 return previous;
27         if(fieldflags & SFL_SORT_PRIO_MASK < previous_y)
28                 return previous;
29         if(t1.field == t2.field)
30                 return previous;
31
32         previous_y = fieldflags & SFL_SORT_PRIO_MASK;
33
34         if(fieldflags & SFL_ZERO_IS_WORST)
35         {
36                 if(t1.field == 0)
37                 {
38                         previous_x = -1;
39                         return previous;
40                 }
41                 else if(t2.field == 0)
42                 {
43                         previous_x = +1;
44                         return previous;
45                 }
46         }
47
48         if(fieldflags & SFL_LOWER_IS_BETTER)
49                 previous_x = (t2.field - t1.field);
50         else
51                 previous_x = (t1.field - t2.field);
52
53         return previous;
54 }
55
56 /*
57  * teamscore entities
58  */
59
60 float TeamScore_SendEntity(entity to)
61 {
62         float i;
63
64         WriteByte(MSG_ENTITY, ENT_CLIENT_TEAMSCORES);
65         WriteByte(MSG_ENTITY, self.team - 1);
66         for(i = 0; i < MAX_TEAMSCORE; ++i)
67                 WriteShort(MSG_ENTITY, self.teamscores[i]);
68
69         return TRUE;
70 }
71
72 void TeamScore_Spawn(float t, string name)
73 {
74         entity ts;
75         ts = spawn();
76         ts.classname = "csqc_score_team";
77         ts.SendEntity = TeamScore_SendEntity;
78         ts.netname = name; // not used yet, FIXME
79         ts.Version = 1; // immediately send, so csqc knows about the team
80         ts.team = t;
81         Net_LinkEntity(ts);
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         if(!scores_initialized) return 0; // FIXME remove this when everything uses this system
90         if(t <= 0 || t >= 16)
91                 error("Adding score to invalid team!");
92         s = teamscorekeepers[t - 1];
93         if(!s)
94                 error("Adding score to unknown team!");
95         if(score)
96                 s.Version += 1;
97         return (s.(teamscores[scorefield]) += score);
98 }
99
100 float TeamScore_Add(entity player, float scorefield, float score)
101 {
102         return TeamScore_AddToTeam(player.team, scorefield, score);
103 }
104
105 float TeamScore_Compare(entity t1, entity t2)
106 {
107         if(!t1 || !t2) return (!t2) - !t1;
108
109         vector result;
110         float i;
111         for(i = 0; i < MAX_TEAMSCORE; ++i)
112         {
113                 var .float f;
114                 f = teamscores[i];
115                 result = ScoreField_Compare(t1, t2, f, teamscores_flags[i], result);
116         }
117         return result_x;
118 }
119
120 /*
121  * the scoreinfo entity
122  */
123
124 void ScoreInfo_SetLabel_PlayerScore(float i, string label, float scoreflags)
125 {
126         scores_label[i] = label;
127         scores_flags[i] = scoreflags;
128         if(scoreflags & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
129         {
130                 scores_primary = scores[i];
131                 scores_flags_primary = scoreflags;
132         }
133 }
134
135 void ScoreInfo_SetLabel_TeamScore(float i, string label, float scoreflags)
136 {
137         teamscores_label[i] = label;
138         teamscores_flags[i] = scoreflags;
139         if(scoreflags & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
140         {
141                 teamscores_primary = teamscores[i];
142                 teamscores_flags_primary = scoreflags;
143         }
144 }
145
146 void ScoreInfo_Write(float targ)
147 {
148         float i;
149         if not(scores_initialized)
150                 return;
151         WriteByte(targ, SVC_TEMPENTITY);
152         WriteByte(targ, TE_CSQC_SCORESINFO);
153         WriteByte(targ, game);
154         for(i = 0; i < MAX_SCORE; ++i)
155         {
156                 WriteString(targ, scores_label[i]);
157                 WriteByte(targ, scores_flags[i]);
158         }
159         for(i = 0; i < MAX_TEAMSCORE; ++i)
160         {
161                 WriteString(targ, teamscores_label[i]);
162                 WriteByte(targ, teamscores_flags[i]);
163         }
164 }
165
166 void ScoreInfo_Init(float teams)
167 {
168         entity pl;
169         scores_initialized = 1;
170         if(teams >= 1)
171                 TeamScore_Spawn(COLOR_TEAM1, "Red");
172         if(teams >= 2)
173                 TeamScore_Spawn(COLOR_TEAM2, "Blue");
174         if(teams >= 3)
175                 TeamScore_Spawn(COLOR_TEAM3, "Yellow");
176         if(teams >= 4)
177                 TeamScore_Spawn(COLOR_TEAM4, "Pink");
178         FOR_EACH_REALCLIENT(msg_entity) // cannot use MSG_ALL here, as that may come too early on level changes (that SUCKS)
179                 ScoreInfo_Write(MSG_ONE);
180 }
181
182 /*
183  * per-player score entities
184  */
185
186 float PlayerScore_SendEntity()
187 {
188         float i;
189
190         WriteByte(MSG_ENTITY, ENT_CLIENT_SCORES);
191         WriteByte(MSG_ENTITY, num_for_edict(self.owner));
192         for(i = 0; i < MAX_SCORE; ++i)
193                 WriteShort(MSG_ENTITY, self.scores[i]);
194
195         return TRUE;
196 }
197
198 void PlayerScore_Clear(entity player)
199 {
200         entity sk;
201         float i;
202
203         if(teamscores_entities_count)
204                 return;
205         if(g_lms) return;
206         if(g_arena) return;
207         if(g_race) return;
208         //print("clear clear clear... HAHA\n");
209
210         sk = player.scorekeeper;
211         for(i = 0; i < MAX_SCORE; ++i)
212                 sk.(scores[i]) = 0;
213         sk.Version += 1;
214 }
215
216 void Score_ClearAll()
217 {
218         entity p, sk;
219         float i;
220         FOR_EACH_CLIENTSLOT(p)
221         {
222                 sk = p.scorekeeper;
223                 if(!sk)
224                         continue;
225                 for(i = 0; i < MAX_SCORE; ++i)
226                         sk.(scores[i]) = 0;
227                 sk.Version += 1;
228         }
229         for(i = 0; i < 16; ++i)
230         {
231                 sk = teamscorekeepers[i];
232                 if(!sk)
233                         continue;
234                 for(i = 0; i < MAX_SCORE; ++i)
235                         sk.(teamscores[i]) = 0;
236                 sk.Version += 1;
237         }
238 }
239
240 void PlayerScore_Attach(entity player)
241 {
242         entity sk;
243         if(player.scorekeeper)
244                 error("player already has a scorekeeper");
245         sk = spawn();
246         sk.owner = player;
247         sk.Version = 1;
248         sk.SendEntity = PlayerScore_SendEntity;
249         Net_LinkEntity(sk);
250         player.scorekeeper = sk;
251 }
252
253 void PlayerScore_Detach(entity player)
254 {
255         if(!player.scorekeeper)
256                 error("player has no scorekeeper");
257         remove(player.scorekeeper);
258         player.scorekeeper = world;
259 }
260
261 float PlayerScore_Add(entity player, float scorefield, float score)
262 {
263         entity s;
264         if(!scores_initialized) return 0; // FIXME remove this when everything uses this system
265         s = player.scorekeeper;
266         if(!s)
267                 error("Adding score to unknown player!");
268         if(score)
269                 s.Version += 1;
270         return (s.(scores[scorefield]) += score);
271 }
272
273 float PlayerTeamScore_Add(entity player, float pscorefield, float tscorefield, float score)
274 {
275         float r;
276         r = PlayerScore_Add(player, pscorefield, score);
277         if(teamscores_entities_count) // only for teamplay
278                 r = TeamScore_Add(player, tscorefield, score);
279         return r;
280 }
281
282 float PlayerScore_Compare(entity t1, entity t2)
283 {
284         if(!t1 || !t2) return (!t2) - !t1;
285
286         vector result;
287         float i;
288         for(i = 0; i < MAX_SCORE; ++i)
289         {
290                 var .float f;
291                 f = scores[i];
292                 result = ScoreField_Compare(t1, t2, f, scores_flags[i], result);
293         }
294         return result_x;
295 }
296
297 void WinningConditionHelper()
298 {
299         float c;
300         string s;
301         entity p;
302         s = GetGametype();
303         s = strcat(s, ":", GetPlayerScoreString(world, 2)); // make this 1 once we can
304
305         if(teamscores_entities_count)
306         {
307                 float t;
308
309                 s = strcat(s, ":", GetTeamScoreString(0, 1));
310                 for(t = 0; t < 16; ++t)
311                         if(teamscorekeepers[t])
312                                 s = strcat(s, ":", ftos(t+1), ":", GetTeamScoreString(t+1, 1));
313
314                 WinningConditionHelper_equality = 1;
315                 WinningConditionHelper_winnerteam = 0;
316                 for(t = 1; t < 16; ++t)
317                 {
318                         entity sk1, sk2;
319                         sk1 = teamscorekeepers[WinningConditionHelper_winnerteam];
320                         sk2 = teamscorekeepers[t];
321                         c = TeamScore_Compare(sk1, sk2);
322                         if(c == 0)
323                                 WinningConditionHelper_equality = 1;
324                         else if(c < 0)
325                         {
326                                 WinningConditionHelper_equality = 0;
327                                 WinningConditionHelper_winnerteam = t;
328                         }
329                 }
330
331                 WinningConditionHelper_topscore = teamscorekeepers[WinningConditionHelper_winnerteam].teamscores_primary;
332                 WinningConditionHelper_lowerisbetter = (teamscores_flags_primary & SFL_LOWER_IS_BETTER);
333                 WinningConditionHelper_zeroisworst = (teamscores_flags_primary & SFL_ZERO_IS_WORST);
334                 if(teamscores_flags_primary & SFL_TIME)
335                         WinningConditionHelper_topscore /= 10;
336
337                 WinningConditionHelper_winner = world;
338                 if(WinningConditionHelper_equality)
339                         WinningConditionHelper_winnerteam = -1;
340                 else
341                         ++WinningConditionHelper_winnerteam; // map to Nexuiz team numbers (as opposed to colors)
342         }
343         else
344         {
345                 WinningConditionHelper_equality = 1;
346                 WinningConditionHelper_winner = world;
347                 FOR_EACH_PLAYER(p)
348                 {
349                         c = PlayerScore_Compare(WinningConditionHelper_winner.scorekeeper, p.scorekeeper);
350                         if(c == 0)
351                                 WinningConditionHelper_equality = 1;
352                         else if(c < 0)
353                         {
354                                 WinningConditionHelper_equality = 0;
355                                 WinningConditionHelper_winner = p;
356                         }
357                 }
358
359                 WinningConditionHelper_topscore = WinningConditionHelper_winner.scorekeeper.scores_primary;
360                 WinningConditionHelper_lowerisbetter = (scores_flags_primary & SFL_LOWER_IS_BETTER);
361                 if(scores_flags_primary & SFL_ZERO_IS_WORST)
362                         if(WinningConditionHelper_topscore == 0)
363                         {
364                                 if(WinningConditionHelper_lowerisbetter)
365                                         WinningConditionHelper_topscore = 999999999;
366                                 else
367                                         WinningConditionHelper_topscore = -999999999;
368                         }
369                 if(teamscores_flags_primary & SFL_TIME)
370                         WinningConditionHelper_topscore /= 10;
371
372                 if(WinningConditionHelper_equality)
373                         WinningConditionHelper_winner = world;
374                 WinningConditionHelper_winnerteam = -1;
375         }
376
377         if(worldstatus)
378                 strunzone(worldstatus);
379         worldstatus = strzone(s);
380
381         FOR_EACH_CLIENT(p)
382         {
383                 /* this breaks qstat :( find a way to make qstat parse this at least as an int first
384                 s = GetPlayerScoreString(p, 1);
385                 if(clienttype(p) == CLIENTTYPE_REAL)
386                         s = strcat(s, ":human");
387                 else
388                         s = strcat(s, ":bot");
389                 if(p.classname == "player" || g_arena || g_lms)
390                         s = strcat(s, ":", ftos(p.team));
391                 else
392                         s = strcat(s, ":spectator");
393                 */
394                 if(p.classname == "player" || g_arena || g_lms)
395                         s = "-666";
396                 else
397                         s = GetPlayerScoreString(p, 2);
398
399                 if(p.clientstatus)
400                         strunzone(p.clientstatus);
401                 p.clientstatus = strzone(s);
402         }
403 }
404
405 void Score_DebugPrint()
406 {
407         entity p, sk;
408         float i, t;
409
410         print("netname");
411         for(i = 0; i < MAX_SCORE; ++i)
412                 print(":", scores_label[i]);
413         print("\n");
414         FOR_EACH_PLAYER(p)
415         {
416                 sk = p.scorekeeper;
417                 print(p.netname);
418                 for(i = 0; i < MAX_SCORE; ++i)
419                         print(":", ftos(sk.(scores[i])));
420                 print("\n");
421         }
422
423         print("teamname");
424         for(i = 0; i < MAX_TEAMSCORE; ++i)
425                 print(":", teamscores_label[i]);
426         print("\n");
427         for(t = 0; t < 16; ++t)
428         {
429                 sk = teamscorekeepers[t];
430                 if(sk)
431                 {
432                         print(ftos(t));
433                         for(i = 0; i < MAX_TEAMSCORE; ++i)
434                                 print(":", ftos(sk.(teamscores[i])));
435                         print("\n");
436                 }
437         }
438 }
439
440 string GetScoreLogLabel(string label, float fl)
441 {
442         if(fl & SFL_LOWER_IS_BETTER)
443                 label = strcat(label, "<");
444         if(fl & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
445                 label = strcat(label, "!!");
446         else if(fl & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
447                 label = strcat(label, "!");
448         return label;
449 }
450
451 string GetPlayerScoreString(entity pl, float shortString)
452 {
453         string out;
454         entity sk;
455         float i, f;
456         string l;
457
458         out = "";
459         if(!pl)
460         {
461                 // label
462                 for(i = 0; i < MAX_SCORE; ++i)
463                         if(scores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
464                         {
465                                 f = scores_flags[i];
466                                 l = scores_label[i];
467                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
468                         }
469                 if(shortString < 2)
470                 for(i = 0; i < MAX_SCORE; ++i)
471                         if(scores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
472                         {
473                                 f = scores_flags[i];
474                                 l = scores_label[i];
475                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
476                         }
477                 if(shortString < 1)
478                 for(i = 0; i < MAX_SCORE; ++i)
479                         if(scores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_PRIMARY)
480                         if(scores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_SECONDARY)
481                         {
482                                 f = scores_flags[i];
483                                 l = scores_label[i];
484                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
485                         }
486                 out = substring(out, 0, strlen(out) - 1);
487         }
488         else if((sk = pl.scorekeeper))
489         {
490                 for(i = 0; i < MAX_SCORE; ++i)
491                         if(scores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
492                                 out = strcat(out, ftos(sk.(scores[i])), ",");
493                 if(shortString < 2)
494                 for(i = 0; i < MAX_SCORE; ++i)
495                         if(scores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
496                                 out = strcat(out, ftos(sk.(scores[i])), ",");
497                 if(shortString < 1)
498                 for(i = 0; i < MAX_SCORE; ++i)
499                         if(scores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_PRIMARY)
500                         if(scores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_SECONDARY)
501                                 out = strcat(out, ftos(sk.(scores[i])), ",");
502                 out = substring(out, 0, strlen(out) - 1);
503         }
504         return out;
505 }
506
507 string GetTeamScoreString(float tm, float shortString)
508 {
509         string out;
510         entity sk;
511         float i, f;
512         string l;
513
514         out = "";
515         if(tm == 0)
516         {
517                 // label
518                 for(i = 0; i < MAX_SCORE; ++i)
519                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
520                         {
521                                 f = teamscores_flags[i];
522                                 l = teamscores_label[i];
523                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
524                         }
525                 if(shortString < 2)
526                 for(i = 0; i < MAX_SCORE; ++i)
527                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
528                         {
529                                 f = teamscores_flags[i];
530                                 l = teamscores_label[i];
531                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
532                         }
533                 if(shortString < 1)
534                 for(i = 0; i < MAX_SCORE; ++i)
535                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_PRIMARY)
536                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_SECONDARY)
537                         {
538                                 f = teamscores_flags[i];
539                                 l = teamscores_label[i];
540                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
541                         }
542                 out = substring(out, 0, strlen(out) - 1);
543         }
544         else if((sk = teamscorekeepers[tm - 1]))
545         {
546                 for(i = 0; i < MAX_TEAMSCORE; ++i)
547                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
548                                 out = strcat(out, ftos(sk.(teamscores[i])), ",");
549                 if(shortString < 2)
550                 for(i = 0; i < MAX_TEAMSCORE; ++i)
551                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
552                                 out = strcat(out, ftos(sk.(teamscores[i])), ",");
553                 if(shortString < 1)
554                 for(i = 0; i < MAX_TEAMSCORE; ++i)
555                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_PRIMARY)
556                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_SECONDARY)
557                                 out = strcat(out, ftos(sk.(teamscores[i])), ",");
558                 out = substring(out, 0, strlen(out) - 1);
559         }
560         return out;
561 }
562
563 float PlayerTeamScore_Compare(entity p1, entity p2)
564 {
565         if(teamscores_entities_count)
566                 if(p1.team != p2.team)
567                 {
568                         entity t1, t2;
569                         t1 = teamscorekeepers[p1.team];
570                         t2 = teamscorekeepers[p2.team];
571                         return TeamScore_Compare(t1, t2);
572                 }
573         
574         return PlayerScore_Compare(p1.scorekeeper, p2.scorekeeper);
575 }
576
577 float PlayerScore_Sort(.float field)
578 {
579         entity p, plist, pprev, pbest, pbestprev;
580         float i;
581         plist = world;
582
583         FOR_EACH_CLIENT(p)
584                 p.field = 0;
585
586         FOR_EACH_PLAYER(p) if(p.scorekeeper)
587         {
588                 p.chain = plist;
589                 plist = p;
590         }
591         // Now plist points to the whole list.
592
593         i = 0;
594         while(plist)
595         {
596                 pprev = pbestprev = world;
597                 pbest = plist;
598                 for(p = plist; (p = p.chain); )
599                 {
600                         if(PlayerTeamScore_Compare(p, pbest) > 0)
601                         {
602                                 pbest = p;
603                                 pbestprev = pprev;
604                         }
605                         pprev = p;
606                 }
607
608                 // remove pbest out of the chain
609                 if(pbestprev == world)
610                         plist = pbest.chain;
611                 else
612                         pbestprev.chain = pbest.chain;
613                 pbest.chain = world;
614
615                 pbest.field = ++i;
616         }
617
618         return i;
619 }