]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/race.qc
race: checkpoints with spawnflag FINISH are now a separate finish CP
[divverent/nexuiz.git] / data / qcsrc / server / race.qc
1 #define MAX_CHECKPOINTS 255
2
3 .float race_checkpoint; // player: next checkpoint that has to be reached
4 .float race_laptime;
5
6 .entity sprite;
7
8 float race_checkpoint_records[MAX_CHECKPOINTS];
9 string race_checkpoint_recordholders[MAX_CHECKPOINTS];
10 float race_checkpoint_lasttimes[MAX_CHECKPOINTS];
11 float race_checkpoint_lastlaps[MAX_CHECKPOINTS];
12 entity race_checkpoint_lastplayers[MAX_CHECKPOINTS];
13
14 float race_highest_checkpoint;
15 float race_timed_checkpoint;
16
17 float race_NextCheckpoint(float f)
18 {
19         if(f >= race_highest_checkpoint)
20                 return 0;
21         else
22                 return f + 1;
23 }
24
25 float race_PreviousCheckpoint(float f)
26 {
27         if(f == -1)
28                 return 0;
29         else if(f == 0)
30                 return race_highest_checkpoint;
31         else
32                 return f - 1;
33 }
34
35 // encode as:
36 //   0 = common start/finish
37 // 254 = start
38 // 255 = finish
39 float race_CheckpointNetworkID(float f)
40 {
41         if(race_timed_checkpoint)
42         {
43                 if(f == 0)
44                         return 254; // start
45                 else if(f == race_timed_checkpoint)
46                         return 255; // finish
47         }
48         return f;
49 }
50
51 void race_SendNextCheckpoint(entity e, float spec)
52 {
53         float recordtime;
54         string recordholder;
55         float cp;
56
57         if(!e.race_laptime)
58                 return;
59
60         cp = e.race_checkpoint;
61         recordtime = race_checkpoint_records[cp];
62         recordholder = race_checkpoint_recordholders[cp];
63         /*
64         recordtime = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/racerecord/", ftos(cp), "/time")));
65         recordholder = db_get(ServerProgsDB, strcat(GetMapname(), "/racerecord/", ftos(cp), "/netname"));
66         */
67         if(recordholder == e.netname)
68                 recordholder = "";
69
70         if(!spec)
71                 msg_entity = e;
72         WRITESPECTATABLE_MSG_ONE({
73                 WriteByte(MSG_ONE, SVC_TEMPENTITY);
74                 WriteByte(MSG_ONE, TE_CSQC_RACE);
75                 if(spec)
76                 {
77                         WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_NEXT_SPEC_QUALIFYING);
78                         WriteCoord(MSG_ONE, e.race_laptime);
79                 }
80                 else
81                         WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_NEXT_QUALIFYING);
82                 WriteByte(MSG_ONE, race_CheckpointNetworkID(cp)); // checkpoint the player will be at next
83                 WriteShort(MSG_ONE, recordtime);
84                 WriteString(MSG_ONE, recordholder);
85         });
86 }
87
88 void race_InitSpectator()
89 {
90         if(g_race_qualifying)
91                 if(msg_entity.enemy.race_laptime)
92                         race_SendNextCheckpoint(msg_entity.enemy, 1);
93 }
94
95 void race_SendTime(entity e, float cp, float t, float tvalid)
96 {
97         float snew, l;
98         entity p;
99
100         t = floor(0.4 + 10 * t); // make integer
101         // adding just 0.4 so it rounds down in the .5 case (matching the timer display)
102
103         if(tvalid)
104         if(cp == race_timed_checkpoint) // finish line
105         if not(e.race_completed)
106         {
107                 float s;
108                 if(g_race_qualifying)
109                 {
110                         s = PlayerScore_Add(e, SP_RACE_FASTEST, 0);
111                         if(!s || t < s)
112                                 PlayerScore_Add(e, SP_RACE_FASTEST, t - s);
113                 }
114                 else
115                 {
116                         s = PlayerScore_Add(e, SP_RACE_TIME, 0);
117                         snew = floor(0.5 + 10 * (time - game_starttime));
118                         PlayerScore_Add(e, SP_RACE_TIME, snew - s);
119                         l = PlayerTeamScore_Add(e, SP_RACE_LAPS, ST_RACE_LAPS, 1);
120
121                         if(cvar("fraglimit"))
122                                 if(l >= cvar("fraglimit"))
123                                 {
124                                         race_completing = 1;
125                                         FOR_EACH_PLAYER(p)
126                                                 if(p.deadflag != DEAD_NO)
127                                                         race_AbandonRaceCheck(p);
128                                 }
129
130                         if(race_completing)
131                         {
132                                 e.race_completed = 1;
133                                 MAKE_INDEPENDENT_PLAYER(e);
134                                 bprint(e.netname, "^7 has finished the race.\n");
135                                 ClientData_Touch(e);
136                         }
137                 }
138         }
139
140         float recordtime;
141         string recordholder;
142
143         if(g_race_qualifying)
144         {
145                 if(tvalid)
146                 {
147                         recordtime = race_checkpoint_records[cp];
148                         recordholder = strcat1(race_checkpoint_recordholders[cp]); // make a tempstring copy, as we'll possibly strunzone it!
149                         if(recordholder == e.netname)
150                                 recordholder = "";
151
152                         if(t != 0)
153                         if(t < recordtime || recordtime == 0)
154                         {
155                                 race_checkpoint_records[cp] = t;
156                                 if(race_checkpoint_recordholders[cp])
157                                         strunzone(race_checkpoint_recordholders[cp]);
158                                 race_checkpoint_recordholders[cp] = strzone(e.netname);
159                                 if(cp == race_timed_checkpoint)
160                                 {
161                                         float grecordtime;
162                                         string grecordholder;
163                                         grecordtime = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/racerecord/time")));
164                                         grecordholder = db_get(ServerProgsDB, strcat(GetMapname(), "/racerecord/netname"));
165                                         if(grecordholder == e.netname)
166                                                 grecordholder = "";
167                                         if(grecordtime == 0)
168                                         {
169                                                 bprint(e.netname, "^7 set the all-time fastest lap record with ", mmsss(t), "\n");
170                                                 db_put(ServerProgsDB, strcat(GetMapname(), "/racerecord/time"), ftos(t));
171                                                 db_put(ServerProgsDB, strcat(GetMapname(), "/racerecord/netname"), e.netname);
172                                                 write_recordmarker(e, time - t/10, t/10);
173                                         }
174                                         else if(t < grecordtime)
175                                         {
176                                                 if(grecordholder == "")
177                                                         bprint(e.netname, "^7 broke his all-time fastest lap record with ", mmsss(t), "\n");
178                                                 else
179                                                         bprint(e.netname, "^7 broke ", grecordholder, "^7's all-time fastest lap record with ", mmsss(t), "\n");
180                                                 db_put(ServerProgsDB, strcat(GetMapname(), "/racerecord/time"), ftos(t));
181                                                 db_put(ServerProgsDB, strcat(GetMapname(), "/racerecord/netname"), e.netname);
182                                                 write_recordmarker(e, time - t/10, t/10);
183                                         }
184                                         else
185                                         {
186                                                 if(grecordholder == "")
187                                                         bprint(e.netname, "^7's new fastest lap could not break his all-time fastest lap record of ", mmsss(grecordtime), "\n");
188                                                 else
189                                                         bprint(e.netname, "^7's new fastest lap could not break ", grecordholder, "^7's all-time fastest lap record of ", mmsss(grecordtime), "\n");
190                                         }
191                                 }
192
193                                 if(g_race_qualifying)
194                                 {
195                                         FOR_EACH_REALPLAYER(p)
196                                                 if(p.race_checkpoint == cp)
197                                                         race_SendNextCheckpoint(p, 0);
198                                 }
199                         }
200                 }
201                 else
202                 {
203                         // dummies
204                         t = 0;
205                         recordtime = 0;
206                         recordholder = "";
207                 }
208
209                 msg_entity = e;
210                 if(g_race_qualifying)
211                 {
212                         WRITESPECTATABLE_MSG_ONE_VARNAME(dummy1, {
213                                 WriteByte(MSG_ONE, SVC_TEMPENTITY);
214                                 WriteByte(MSG_ONE, TE_CSQC_RACE);
215                                 WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_HIT_QUALIFYING);
216                                 WriteByte(MSG_ONE, race_CheckpointNetworkID(cp)); // checkpoint the player now is at
217                                 WriteShort(MSG_ONE, t); // time to that intermediate
218                                 WriteShort(MSG_ONE, recordtime); // previously best time
219                                 WriteString(MSG_ONE, recordholder); // record holder
220                         });
221                 }
222         }
223         else // RACE! Not Qualifying
224         {
225                 float lself, lother, othtime;
226                 entity oth;
227                 oth = race_checkpoint_lastplayers[cp];
228                 if(oth)
229                 {
230                         lself = PlayerScore_Add(e, SP_RACE_LAPS, 0);
231                         lother = race_checkpoint_lastlaps[cp];
232                         othtime = race_checkpoint_lasttimes[cp];
233                 }
234                 else
235                         lself = lother = othtime = 0;
236
237                 msg_entity = e;
238                 WRITESPECTATABLE_MSG_ONE_VARNAME(dummy2, {
239                         WriteByte(MSG_ONE, SVC_TEMPENTITY);
240                         WriteByte(MSG_ONE, TE_CSQC_RACE);
241                         WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_HIT_RACE);
242                         WriteByte(MSG_ONE, race_CheckpointNetworkID(cp)); // checkpoint the player now is at
243                         if(e == oth)
244                         {
245                                 WriteShort(MSG_ONE, 0);
246                                 WriteByte(MSG_ONE, 0);
247                                 WriteString(MSG_ONE, "");
248                         }
249                         else
250                         {
251                                 WriteShort(MSG_ONE, floor(10 * (time - race_checkpoint_lasttimes[cp]) + 0.5));
252                                 WriteByte(MSG_ONE, lself - lother);
253                                 WriteString(MSG_ONE, oth.netname); // record holder
254                         }
255                 });
256
257                 race_checkpoint_lastplayers[cp] = e;
258                 race_checkpoint_lasttimes[cp] = time;
259                 race_checkpoint_lastlaps[cp] = lself;
260
261                 msg_entity = oth;
262                 WRITESPECTATABLE_MSG_ONE_VARNAME(dummy3, {
263                         WriteByte(MSG_ONE, SVC_TEMPENTITY);
264                         WriteByte(MSG_ONE, TE_CSQC_RACE);
265                         WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_HIT_RACE_BY_OPPONENT);
266                         WriteByte(MSG_ONE, race_CheckpointNetworkID(cp)); // checkpoint the player now is at
267                         if(e == oth)
268                         {
269                                 WriteShort(MSG_ONE, 0);
270                                 WriteByte(MSG_ONE, 0);
271                                 WriteString(MSG_ONE, "");
272                         }
273                         else
274                         {
275                                 WriteShort(MSG_ONE, floor(10 * (time - othtime) + 0.5));
276                                 WriteByte(MSG_ONE, lother - lself);
277                                 WriteString(MSG_ONE, e.netname); // record holder
278                         }
279                 });
280         }
281 }
282
283 void race_ClearTime(entity e)
284 {
285         e.race_checkpoint = -1;
286         e.race_laptime = 0;
287
288         msg_entity = e;
289         WRITESPECTATABLE_MSG_ONE({
290                 WriteByte(MSG_ONE, SVC_TEMPENTITY);
291                 WriteByte(MSG_ONE, TE_CSQC_RACE);
292                 WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_CLEAR); // next
293         });
294 }
295
296 void dumpsurface(entity e)
297 {
298         float n, si, ni;
299         vector norm, vec;
300         print("Surfaces of ", etos(e), ":\n");
301
302         print("TEST = ", ftos(getsurfacenearpoint(e, '0 0 0')), "\n");
303
304         for(si = 0; ; ++si)
305         {
306                 n = getsurfacenumpoints(e, si);
307                 if(n <= 0)
308                         break;
309                 print("  Surface ", ftos(si), ":\n");
310                 norm = getsurfacenormal(e, si);
311                 print("    Normal = ", vtos(norm), "\n");
312                 for(ni = 0; ni < n; ++ni)
313                 {
314                         vec = getsurfacepoint(e, si, ni);
315                         print("    Point ", ftos(ni), " = ", vtos(vec), " (", ftos(norm * vec), ")\n");
316                 }
317         }
318 }
319
320 void checkpoint_passed()
321 {
322         string oldmsg;
323
324         if(other.classname == "porto")
325         {
326                 // do not allow portalling through checkpoints
327                 trace_plane_normal = normalize(-1 * other.velocity);
328                 self = other;
329                 W_Porto_Fail(0);
330                 return;
331         }
332
333         /*
334          * Trigger targets
335          */
336         if not(self.spawnflags & 2)
337         {
338                 activator = other;
339                 oldmsg = self.message;
340                 self.message = "";
341                 SUB_UseTargets();
342                 self.message = oldmsg;
343         }
344
345         if(other.classname != "player")
346                 return;
347
348         /*
349          * Remove unauthorized equipment
350          */
351         Portal_ClearAll(other);
352
353         other.porto_forbidden = 2; // decreased by 1 each StartFrame
354
355         if(other.race_checkpoint == -1 || other.race_checkpoint == self.race_checkpoint)
356         {
357                 /*
358                  * Trigger targets
359                  */
360                 if(self.spawnflags & 2)
361                 {
362                         activator = other;
363                         oldmsg = self.message;
364                         self.message = "";
365                         SUB_UseTargets();
366                         self.message = oldmsg;
367                 }
368
369                 other.race_checkpoint = race_NextCheckpoint(self.race_checkpoint);
370
371                 race_SendTime(other, self.race_checkpoint, time - other.race_laptime, !!other.race_laptime);
372
373                 if(!self.race_checkpoint) // start line
374                         other.race_laptime = time;
375
376                 if(g_race_qualifying)
377                         race_SendNextCheckpoint(other, 0);
378         }
379         else if(other.race_checkpoint == race_NextCheckpoint(self.race_checkpoint))
380         {
381                 // ignored
382         }
383         else
384         {
385                 if(self.spawnflags & 4)
386                         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
387         }
388 }
389
390 void checkpoint_touch()
391 {
392         EXACTTRIGGER_TOUCH;
393         checkpoint_passed();
394 }
395
396 void checkpoint_use()
397 {
398         if(other.classname == "info_player_deathmatch") // a spawn, a spawn
399                 return;
400
401         other = activator;
402         checkpoint_passed();
403 }
404
405 float race_waypointsprite_visible_for_player(entity e)
406 {
407         if(e.race_checkpoint == -1)
408                 return TRUE;
409         else if(e.race_checkpoint == self.owner.race_checkpoint)
410                 return TRUE;
411         else
412                 return FALSE;
413 }
414
415 float have_verified;
416 void trigger_race_checkpoint_verify()
417 {
418         entity oldself, cp;
419         float i, p;
420         float qual;
421
422         if(have_verified)
423                 return;
424         have_verified = 1;
425         
426         qual = g_race_qualifying;
427
428         oldself = self;
429         self = spawn();
430         self.classname = "player";
431
432         for(i = 0; i <= race_highest_checkpoint; ++i)
433         {
434                 self.race_checkpoint = race_NextCheckpoint(i);
435
436                 // race only (middle of the race)
437                 g_race_qualifying = 0;
438                 self.race_place = 0;
439                 if(!Spawn_FilterOutBadSpots(findchain(classname, "info_player_deathmatch"), world, 0, FALSE))
440                         error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(self.race_place), " (used for respawning in race) - bailing out"));
441
442                 if(i == 0)
443                 {
444                         // qualifying only
445                         g_race_qualifying = 1;
446                         self.race_place = race_lowest_place_spawn;
447                         if(!Spawn_FilterOutBadSpots(findchain(classname, "info_player_deathmatch"), world, 0, FALSE))
448                                 error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(self.race_place), " (used for qualifying) - bailing out"));
449                         
450                         // race only (initial spawn)
451                         g_race_qualifying = 0;
452                         for(p = 1; p <= race_highest_place_spawn; ++p)
453                         {
454                                 self.race_place = p;
455                                 if(!Spawn_FilterOutBadSpots(findchain(classname, "info_player_deathmatch"), world, 0, FALSE))
456                                         error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(self.race_place), " (used for initially spawning in race) - bailing out"));
457                         }
458                 }
459         }
460
461         g_race_qualifying = qual;
462
463         if(race_timed_checkpoint)
464                 for(cp = world; (cp = find(cp, classname, "trigger_race_checkpoint")); )
465                         if(cp.race_checkpoint == 0)
466                                 WaypointSprite_UpdateSprites(cp.sprite, "race-start", "", "");
467                         else if(cp.race_checkpoint == race_timed_checkpoint)
468                                 WaypointSprite_UpdateSprites(cp.sprite, "race-finish", "", "");
469
470         remove(self);
471         self = oldself;
472 }
473
474 void spawnfunc_trigger_race_checkpoint()
475 {
476         vector o;
477         if(!g_race)
478         {
479                 remove(self);
480                 return;
481         }
482
483         EXACTTRIGGER_INIT;
484
485         self.use = checkpoint_use;
486         if not(self.spawnflags & 1)
487                 self.touch = checkpoint_touch;
488
489         o = (self.absmin + self.absmax) * 0.5;
490         tracebox(o, PL_MIN, PL_MAX, o - '0 0 1' * (o_z - self.absmin_z), MOVE_NORMAL, self);
491         self.nearestwaypoint = waypoint_spawn(trace_endpos, trace_endpos, WAYPOINTFLAG_GENERATED);
492         self.nearestwaypointtimeout = time + 1000000000;
493
494         if(!self.message)
495                 self.message = "went backwards";
496         if (!self.message2)
497                 self.message2 = "was pushed backwards by";
498         
499         self.race_checkpoint = self.cnt;
500
501         if(self.race_checkpoint > race_highest_checkpoint)
502         {
503                 race_highest_checkpoint = self.race_checkpoint;
504                 if(self.spawnflags & 8)
505                         race_timed_checkpoint = self.race_checkpoint;
506                 else
507                         race_timed_checkpoint = 0;
508         }
509
510         if(self.race_checkpoint)
511                 WaypointSprite_SpawnFixed("race-checkpoint", o, self, sprite);
512         else
513                 WaypointSprite_SpawnFixed("race-finish", o, self, sprite);
514
515         self.sprite.waypointsprite_visible_for_player = race_waypointsprite_visible_for_player;
516
517         InitializeEntity(self, trigger_race_checkpoint_verify, INITPRIO_FINDTARGET);
518 }
519
520 void race_AbandonRaceCheck(entity p)
521 {
522         if(race_completing && !p.race_completed)
523         {
524                 p.race_completed = 1;
525                 MAKE_INDEPENDENT_PLAYER(p);
526                 bprint(p.netname, "^7 has abandoned the race.\n");
527                 ClientData_Touch(p);
528         }
529 }
530
531 void race_PreparePlayer()
532 {
533         race_ClearTime(self);
534         self.race_place = 0;
535 }
536
537 void race_RetractPlayer()
538 {
539         if(!g_race)
540                 return;
541         self.race_checkpoint = race_PreviousCheckpoint(self.race_checkpoint);
542         if(self.race_checkpoint == 0)
543         {
544                 race_ClearTime(self);
545                 self.race_checkpoint = 0;
546         }
547 }
548
549 void race_PreDie()
550 {
551         if(!g_race)
552                 return;
553
554         race_AbandonRaceCheck(self);
555 }
556
557 void race_PreSpawn()
558 {
559         if(!g_race)
560                 return;
561         if(self.killcount == -666 || g_race_qualifying)
562                 race_PreparePlayer();
563
564         race_AbandonRaceCheck(self);
565 }
566
567 void race_PostSpawn(entity spot)
568 {
569         if(!g_race)
570                 return;
571         if(self.killcount != -666 && !g_race_qualifying)
572         {
573                 if(spot.target == "")
574                         // let the player run without timing, if he did not spawn at a targetting spawnpoint
575                         race_PreparePlayer();
576                 else
577                         race_RetractPlayer();
578         }
579
580         if(spot.target != "" && self.race_checkpoint == -1)
581                 self.race_checkpoint = 0;
582
583         self.race_place = 0;
584 }
585
586 void race_PreSpawnObserver()
587 {
588         if(!g_race)
589                 return;
590         race_PreparePlayer();
591 }
592
593 void spawnfunc_info_player_race (void)
594 {
595         if(!g_race)
596         {
597                 remove(self);
598                 return;
599         }
600         ++race_spawns;
601         spawnfunc_info_player_deathmatch();
602
603         if(self.race_place > race_highest_place_spawn)
604                 race_highest_place_spawn = self.race_place;
605         if(self.race_place < race_lowest_place_spawn)
606                 race_lowest_place_spawn = self.race_place;
607 }
608
609 void race_ClearRecords()
610 {
611         float i;
612         entity e;
613
614         for(i = 0; i < MAX_CHECKPOINTS; ++i)
615         {
616                 race_checkpoint_records[i] = 0;
617                 if(race_checkpoint_recordholders[i])
618                         strunzone(race_checkpoint_recordholders[i]);
619                 race_checkpoint_recordholders[i] = string_null;
620         }
621
622         FOR_EACH_CLIENT(e)
623                 race_ClearTime(e);
624 }
625
626 void race_ReadyRestart()
627 {
628         Score_NicePrint(world);
629
630         race_ClearRecords();
631         PlayerScore_Sort(race_place);
632
633         entity e;
634         FOR_EACH_CLIENT(e)
635                 print(e.netname, " = ", ftos(e.race_place), "\n");
636
637         if(g_race_qualifying == 2)
638         {
639                 g_race_qualifying = 0;
640                 independent_players = 0;
641                 cvar_set("fraglimit", ftos(race_fraglimit));
642                 cvar_set("leadlimit", ftos(race_leadlimit));
643                 ScoreRules_race();
644         }
645 }