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