]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/scores.qc
fix score clearing in race
[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         scores_initialized = 1;
169         if(teams >= 1)
170                 TeamScore_Spawn(COLOR_TEAM1, "Red");
171         if(teams >= 2)
172                 TeamScore_Spawn(COLOR_TEAM2, "Blue");
173         if(teams >= 3)
174                 TeamScore_Spawn(COLOR_TEAM3, "Yellow");
175         if(teams >= 4)
176                 TeamScore_Spawn(COLOR_TEAM4, "Pink");
177         ScoreInfo_Write(MSG_ALL);
178 }
179
180 /*
181  * per-player score entities
182  */
183
184 float PlayerScore_SendEntity()
185 {
186         float i;
187
188         WriteByte(MSG_ENTITY, ENT_CLIENT_SCORES);
189         WriteByte(MSG_ENTITY, num_for_edict(self.owner));
190         for(i = 0; i < MAX_SCORE; ++i)
191                 WriteShort(MSG_ENTITY, self.scores[i]);
192
193         return TRUE;
194 }
195
196 void PlayerScore_Clear(entity player)
197 {
198         entity sk;
199         float i;
200
201         if(teamscores_entities_count)
202                 return;
203         if(g_lms) return;
204         if(g_arena) return;
205         if(g_race && !g_race_qualifying) return;
206
207         sk = player.scorekeeper;
208         for(i = 0; i < MAX_SCORE; ++i)
209                 sk.(scores[i]) = 0;
210         sk.Version += 1;
211 }
212
213 void Score_ClearAll()
214 {
215         entity p, sk;
216         float i;
217         FOR_EACH_CLIENTSLOT(p)
218         {
219                 sk = p.scorekeeper;
220                 if(!sk)
221                         continue;
222                 for(i = 0; i < MAX_SCORE; ++i)
223                         sk.(scores[i]) = 0;
224                 sk.Version += 1;
225         }
226         for(i = 0; i < 16; ++i)
227         {
228                 sk = teamscorekeepers[i];
229                 if(!sk)
230                         continue;
231                 for(i = 0; i < MAX_SCORE; ++i)
232                         sk.(teamscores[i]) = 0;
233                 sk.Version += 1;
234         }
235 }
236
237 void PlayerScore_Attach(entity player)
238 {
239         entity sk;
240         if(player.scorekeeper)
241                 error("player already has a scorekeeper");
242         sk = spawn();
243         sk.owner = player;
244         sk.Version = 1;
245         sk.SendEntity = PlayerScore_SendEntity;
246         Net_LinkEntity(sk);
247         player.scorekeeper = sk;
248 }
249
250 void PlayerScore_Detach(entity player)
251 {
252         if(!player.scorekeeper)
253                 error("player has no scorekeeper");
254         remove(player.scorekeeper);
255         player.scorekeeper = world;
256 }
257
258 float PlayerScore_Add(entity player, float scorefield, float score)
259 {
260         entity s;
261         if(!scores_initialized) return 0; // FIXME remove this when everything uses this system
262         s = player.scorekeeper;
263         if(!s)
264                 error("Adding score to unknown player!");
265         if(score)
266                 s.Version += 1;
267         return (s.(scores[scorefield]) += score);
268 }
269
270 float PlayerTeamScore_Add(entity player, float pscorefield, float tscorefield, float score)
271 {
272         float r;
273         r = PlayerScore_Add(player, pscorefield, score);
274         if(teamscores_entities_count) // only for teamplay
275                 r = TeamScore_Add(player, tscorefield, score);
276         return r;
277 }
278
279 float PlayerScore_Compare(entity t1, entity t2)
280 {
281         if(!t1 || !t2) return (!t2) - !t1;
282
283         vector result;
284         float i;
285         for(i = 0; i < MAX_SCORE; ++i)
286         {
287                 var .float f;
288                 f = scores[i];
289                 result = ScoreField_Compare(t1, t2, f, scores_flags[i], result);
290         }
291         return result_x;
292 }
293
294 void WinningConditionHelper()
295 {
296         float c;
297         string s;
298         entity p;
299         s = GetGametype();
300         s = strcat(s, ":", GetPlayerScoreString(world, 2)); // make this 1 once we can
301
302         if(teamscores_entities_count)
303         {
304                 float t;
305
306                 s = strcat(s, ":", GetTeamScoreString(0, 1));
307                 for(t = 0; t < 16; ++t)
308                         if(teamscorekeepers[t])
309                                 s = strcat(s, ":", ftos(t+1), ":", GetTeamScoreString(t+1, 1));
310
311                 WinningConditionHelper_equality = 0;
312                 WinningConditionHelper_winnerteam = 0;
313                 for(t = 1; t < 16; ++t)
314                 {
315                         entity sk1, sk2;
316                         sk1 = teamscorekeepers[WinningConditionHelper_winnerteam];
317                         sk2 = teamscorekeepers[t];
318                         c = TeamScore_Compare(sk1, sk2);
319                         if(c == 0)
320                                 WinningConditionHelper_equality = 1;
321                         else if(c < 0)
322                         {
323                                 WinningConditionHelper_equality = 0;
324                                 WinningConditionHelper_winnerteam = t;
325                         }
326                 }
327
328                 WinningConditionHelper_topscore = teamscorekeepers[WinningConditionHelper_winnerteam].teamscores_primary;
329                 WinningConditionHelper_lowerisbetter = (teamscores_flags_primary & SFL_LOWER_IS_BETTER);
330                 WinningConditionHelper_zeroisworst = (teamscores_flags_primary & SFL_ZERO_IS_WORST);
331                 if(teamscores_flags_primary & SFL_TIME)
332                         WinningConditionHelper_topscore /= 10;
333
334                 WinningConditionHelper_winner = world;
335                 if(WinningConditionHelper_equality)
336                         WinningConditionHelper_winnerteam = -1;
337                 else
338                         ++WinningConditionHelper_winnerteam; // map to Nexuiz team numbers (as opposed to colors)
339         }
340         else
341         {
342                 WinningConditionHelper_equality = 0;
343                 WinningConditionHelper_winner = world;
344                 FOR_EACH_PLAYER(p)
345                 {
346                         c = PlayerScore_Compare(WinningConditionHelper_winner.scorekeeper, p.scorekeeper);
347                         if(c == 0)
348                                 WinningConditionHelper_equality = 1;
349                         else if(c < 0)
350                         {
351                                 WinningConditionHelper_equality = 0;
352                                 WinningConditionHelper_winner = p;
353                         }
354                 }
355
356                 WinningConditionHelper_topscore = WinningConditionHelper_winner.scorekeeper.scores_primary;
357                 WinningConditionHelper_lowerisbetter = (scores_flags_primary & SFL_LOWER_IS_BETTER);
358                 if(scores_flags_primary & SFL_ZERO_IS_WORST)
359                         if(WinningConditionHelper_topscore == 0)
360                         {
361                                 if(WinningConditionHelper_lowerisbetter)
362                                         WinningConditionHelper_topscore = 999999999;
363                                 else
364                                         WinningConditionHelper_topscore = -999999999;
365                         }
366                 if(teamscores_flags_primary & SFL_TIME)
367                         WinningConditionHelper_topscore /= 10;
368
369                 if(WinningConditionHelper_equality)
370                         WinningConditionHelper_winner = world;
371                 WinningConditionHelper_winnerteam = -1;
372         }
373
374         if(worldstatus)
375                 strunzone(worldstatus);
376         worldstatus = strzone(s);
377
378         FOR_EACH_CLIENT(p)
379         {
380                 /* this breaks qstat :( find a way to make qstat parse this at least as an int first
381                 s = GetPlayerScoreString(p, 1);
382                 if(clienttype(p) == CLIENTTYPE_REAL)
383                         s = strcat(s, ":human");
384                 else
385                         s = strcat(s, ":bot");
386                 if(p.classname == "player" || g_arena || g_lms)
387                         s = strcat(s, ":", ftos(p.team));
388                 else
389                         s = strcat(s, ":spectator");
390                 */
391                 if(p.classname == "player" || g_arena || g_lms)
392                         s = "-666";
393                 else
394                         s = GetPlayerScoreString(p, 2);
395
396                 if(p.clientstatus)
397                         strunzone(p.clientstatus);
398                 p.clientstatus = strzone(s);
399         }
400 }
401
402 void Score_DebugPrint()
403 {
404         entity p, sk;
405         float i, t;
406
407         print("netname");
408         for(i = 0; i < MAX_SCORE; ++i)
409                 print(":", scores_label[i]);
410         print("\n");
411         FOR_EACH_PLAYER(p)
412         {
413                 sk = p.scorekeeper;
414                 print(p.netname);
415                 for(i = 0; i < MAX_SCORE; ++i)
416                         print(":", ftos(sk.(scores[i])));
417                 print("\n");
418         }
419
420         print("teamname");
421         for(i = 0; i < MAX_TEAMSCORE; ++i)
422                 print(":", teamscores_label[i]);
423         print("\n");
424         for(t = 0; t < 16; ++t)
425         {
426                 sk = teamscorekeepers[t];
427                 if(sk)
428                 {
429                         print(ftos(t));
430                         for(i = 0; i < MAX_TEAMSCORE; ++i)
431                                 print(":", ftos(sk.(teamscores[i])));
432                         print("\n");
433                 }
434         }
435 }
436
437 string GetScoreLogLabel(string label, float fl)
438 {
439         if(fl & SFL_LOWER_IS_BETTER)
440                 label = strcat(label, "<");
441         if(fl & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
442                 label = strcat(label, "!!");
443         else if(fl & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
444                 label = strcat(label, "!");
445         return label;
446 }
447
448 string GetPlayerScoreString(entity pl, float shortString)
449 {
450         string out;
451         entity sk;
452         float i, f;
453         string l;
454
455         out = "";
456         if(!pl)
457         {
458                 // label
459                 for(i = 0; i < MAX_SCORE; ++i)
460                         if(scores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
461                         {
462                                 f = scores_flags[i];
463                                 l = scores_label[i];
464                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
465                         }
466                 if(shortString < 2)
467                 for(i = 0; i < MAX_SCORE; ++i)
468                         if(scores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
469                         {
470                                 f = scores_flags[i];
471                                 l = scores_label[i];
472                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
473                         }
474                 if(shortString < 1)
475                 for(i = 0; i < MAX_SCORE; ++i)
476                         if(scores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_PRIMARY)
477                         if(scores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_SECONDARY)
478                         {
479                                 f = scores_flags[i];
480                                 l = scores_label[i];
481                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
482                         }
483                 out = substring(out, 0, strlen(out) - 1);
484         }
485         else if((sk = pl.scorekeeper))
486         {
487                 for(i = 0; i < MAX_SCORE; ++i)
488                         if(scores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
489                                 out = strcat(out, ftos(sk.(scores[i])), ",");
490                 if(shortString < 2)
491                 for(i = 0; i < MAX_SCORE; ++i)
492                         if(scores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
493                                 out = strcat(out, ftos(sk.(scores[i])), ",");
494                 if(shortString < 1)
495                 for(i = 0; i < MAX_SCORE; ++i)
496                         if(scores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_PRIMARY)
497                         if(scores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_SECONDARY)
498                                 out = strcat(out, ftos(sk.(scores[i])), ",");
499                 out = substring(out, 0, strlen(out) - 1);
500         }
501         return out;
502 }
503
504 string GetTeamScoreString(float tm, float shortString)
505 {
506         string out;
507         entity sk;
508         float i, f;
509         string l;
510
511         out = "";
512         if(tm == 0)
513         {
514                 // label
515                 for(i = 0; i < MAX_SCORE; ++i)
516                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
517                         {
518                                 f = teamscores_flags[i];
519                                 l = teamscores_label[i];
520                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
521                         }
522                 if(shortString < 2)
523                 for(i = 0; i < MAX_SCORE; ++i)
524                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
525                         {
526                                 f = teamscores_flags[i];
527                                 l = teamscores_label[i];
528                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
529                         }
530                 if(shortString < 1)
531                 for(i = 0; i < MAX_SCORE; ++i)
532                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_PRIMARY)
533                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_SECONDARY)
534                         {
535                                 f = teamscores_flags[i];
536                                 l = teamscores_label[i];
537                                 out = strcat(out, GetScoreLogLabel(l, f), ",");
538                         }
539                 out = substring(out, 0, strlen(out) - 1);
540         }
541         else if((sk = teamscorekeepers[tm - 1]))
542         {
543                 for(i = 0; i < MAX_TEAMSCORE; ++i)
544                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_PRIMARY)
545                                 out = strcat(out, ftos(sk.(teamscores[i])), ",");
546                 if(shortString < 2)
547                 for(i = 0; i < MAX_TEAMSCORE; ++i)
548                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK == SFL_SORT_PRIO_SECONDARY)
549                                 out = strcat(out, ftos(sk.(teamscores[i])), ",");
550                 if(shortString < 1)
551                 for(i = 0; i < MAX_TEAMSCORE; ++i)
552                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_PRIMARY)
553                         if(teamscores_flags[i] & SFL_SORT_PRIO_MASK != SFL_SORT_PRIO_SECONDARY)
554                                 out = strcat(out, ftos(sk.(teamscores[i])), ",");
555                 out = substring(out, 0, strlen(out) - 1);
556         }
557         return out;
558 }
559
560 float PlayerTeamScore_Compare(entity p1, entity p2)
561 {
562         if(teamscores_entities_count)
563                 if(p1.team != p2.team)
564                 {
565                         entity t1, t2;
566                         t1 = teamscorekeepers[p1.team];
567                         t2 = teamscorekeepers[p2.team];
568                         return TeamScore_Compare(t1, t2);
569                 }
570         
571         return PlayerScore_Compare(p1.scorekeeper, p2.scorekeeper);
572 }
573
574 float PlayerScore_Sort(.float field)
575 {
576         entity p, plist, pprev, pbest, pbestprev;
577         float i;
578         plist = world;
579
580         FOR_EACH_CLIENT(p)
581                 p.field = 0;
582
583         FOR_EACH_PLAYER(p) if(p.scorekeeper)
584         {
585                 p.chain = plist;
586                 plist = p;
587         }
588         // Now plist points to the whole list.
589
590         i = 0;
591         while(plist)
592         {
593                 pprev = pbestprev = world;
594                 pbest = plist;
595                 for(p = plist; (pprev = p), (p = p.chain); )
596                 {
597                         if(PlayerTeamScore_Compare(p, pbest) > 0)
598                         {
599                                 pbest = p;
600                                 pbestprev = pprev;
601                         }
602                 }
603
604                 // remove pbest out of the chain
605                 if(pbestprev == world)
606                         plist = pbest.chain;
607                 else
608                         pbestprev.chain = pbest.chain;
609                 pbest.chain = world;
610
611                 pbest.field = ++i;
612         }
613
614         return i;
615 }