]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/race.qc
add a cvar that can disable wearing off on powerups
[divverent/nexuiz.git] / data / qcsrc / server / race.qc
1 #define MAX_CHECKPOINTS 255
2
3 .float race_penalty;
4 .float race_penalty_accumulator;
5 .string race_penalty_reason;
6 .float race_checkpoint; // player: next checkpoint that has to be reached
7 .float race_laptime;
8 .entity race_lastpenalty;
9
10 .entity sprite;
11
12 float race_checkpoint_records[MAX_CHECKPOINTS];
13 string race_checkpoint_recordholders[MAX_CHECKPOINTS];
14 float race_checkpoint_lasttimes[MAX_CHECKPOINTS];
15 float race_checkpoint_lastlaps[MAX_CHECKPOINTS];
16 entity race_checkpoint_lastplayers[MAX_CHECKPOINTS];
17
18 float race_highest_checkpoint;
19 float race_timed_checkpoint;
20
21 float dfcp_cnt;
22 float defrag_ents;
23 float defragcpexists;
24
25 float race_NextCheckpoint(float f)
26 {
27         if(f >= race_highest_checkpoint)
28                 return 0;
29         else
30                 return f + 1;
31 }
32
33 float race_PreviousCheckpoint(float f)
34 {
35         if(f == -1)
36                 return 0;
37         else if(f == 0)
38                 return race_highest_checkpoint;
39         else
40                 return f - 1;
41 }
42
43 // encode as:
44 //   0 = common start/finish
45 // 254 = start
46 // 255 = finish
47 float race_CheckpointNetworkID(float f)
48 {
49         if(race_timed_checkpoint)
50         {
51                 if(f == 0)
52                         return 254; // start
53                 else if(f == race_timed_checkpoint)
54                         return 255; // finish
55         }
56         return f;
57 }
58
59 void race_SendNextCheckpoint(entity e, float spec) // qualifying only
60 {
61         float recordtime;
62         string recordholder;
63         float cp;
64
65         if(!e.race_laptime)
66                 return;
67
68         cp = e.race_checkpoint;
69         recordtime = race_checkpoint_records[cp];
70         recordholder = race_checkpoint_recordholders[cp];
71         if(recordholder == e.netname)
72                 recordholder = "";
73
74         if(!spec)
75                 msg_entity = e;
76         WRITESPECTATABLE_MSG_ONE({
77                 WriteByte(MSG_ONE, SVC_TEMPENTITY);
78                 WriteByte(MSG_ONE, TE_CSQC_RACE);
79                 if(spec)
80                 {
81                         WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_NEXT_SPEC_QUALIFYING);
82                         //WriteCoord(MSG_ONE, e.race_laptime - e.race_penalty_accumulator);
83                         WriteCoord(MSG_ONE, time - e.race_movetime - e.race_penalty_accumulator);
84                 }
85                 else
86                         WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_NEXT_QUALIFYING);
87                 WriteByte(MSG_ONE, race_CheckpointNetworkID(cp)); // checkpoint the player will be at next
88                 WriteInt24_t(MSG_ONE, recordtime);
89                 WriteString(MSG_ONE, recordholder);
90         });
91 }
92
93 void race_InitSpectator()
94 {
95         if(g_race_qualifying)
96                 if(msg_entity.enemy.race_laptime)
97                         race_SendNextCheckpoint(msg_entity.enemy, 1);
98 }
99
100 void race_send_recordtime(float t, float msg)
101 {
102         // send the server best time
103         WriteByte(msg, SVC_TEMPENTITY);
104         WriteByte(msg, TE_CSQC_RACE);
105         WriteByte(msg, RACE_NET_SERVER_RECORD);
106         WriteInt24_t(msg, t);
107 }
108
109 void race_SendTime(entity e, float cp, float t, float tvalid)
110 {
111         float snew, l;
112         entity p;
113
114         if(g_race_qualifying)
115                 t += e.race_penalty_accumulator;
116
117         t = TIME_ENCODE(t); // make integer
118         // adding just 0.4 so it rounds down in the .5 case (matching the timer display)
119
120         if(tvalid)
121         if(cp == race_timed_checkpoint) // finish line
122         if not(e.race_completed)
123         {
124                 float s;
125                 if(g_race_qualifying)
126                 {
127                         s = PlayerScore_Add(e, SP_RACE_FASTEST, 0);
128                         if(!s || t < s)
129                                 PlayerScore_Add(e, SP_RACE_FASTEST, t - s);
130                 }
131                 else
132                 {
133                         s = PlayerScore_Add(e, SP_RACE_TIME, 0);
134                         snew = TIME_ENCODE(time - game_starttime);
135                         PlayerScore_Add(e, SP_RACE_TIME, snew - s);
136                         l = PlayerTeamScore_Add(e, SP_RACE_LAPS, ST_RACE_LAPS, 1);
137
138                         if(cvar("fraglimit"))
139                                 if(l >= cvar("fraglimit"))
140                                         race_StartCompleting();
141
142                         if(race_completing)
143                         {
144                                 e.race_completed = 1;
145                                 MAKE_INDEPENDENT_PLAYER(e);
146                                 bprint(e.netname, "^7 has finished the race.\n");
147                                 ClientData_Touch(e);
148                         }
149                 }
150         }
151
152         float recordtime;
153         string recordholder;
154         if(g_race_qualifying)
155         {
156                 if(tvalid)
157                 {
158                         recordtime = race_checkpoint_records[cp];
159                         recordholder = strcat1(race_checkpoint_recordholders[cp]); // make a tempstring copy, as we'll possibly strunzone it!
160                         if(recordholder == e.netname)
161                                 recordholder = "";
162
163                         if(t != 0)
164                         if(t < recordtime || recordtime == 0)
165                         {
166                                 race_checkpoint_records[cp] = t;
167                                 if(race_checkpoint_recordholders[cp])
168                                         strunzone(race_checkpoint_recordholders[cp]);
169                                 race_checkpoint_recordholders[cp] = strzone(e.netname);
170                                 if(cp == race_timed_checkpoint)
171                                 {
172                                         float grecordtime;
173                                         string grecordholder, recorddifference;
174                                         string rr;
175                                         if(g_cts)
176                                                 rr = CTS_RECORD;
177                                         else
178                                                 rr = RACE_RECORD;
179                                         grecordtime = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "time")));
180                                         grecordholder = db_get(ServerProgsDB, strcat(GetMapname(), rr, "netname"));
181                                         if(grecordholder == e.netname)
182                                                 grecordholder = "";
183                                         if(grecordtime == 0)
184                                         {
185                                                 bprint(e.netname, "^7 set the all-time fastest lap record with ", TIME_ENCODED_TOSTRING(t), "\n");
186                                                 db_put(ServerProgsDB, strcat(GetMapname(), rr, "time"), ftos(t));
187                                                 db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname"), e.netname);
188                                                 write_recordmarker(e, time - TIME_DECODE(t), TIME_DECODE(t));
189                                                 race_send_recordtime(t, MSG_ALL);
190                                         }
191                                         else if(t < grecordtime)
192                                         {
193                                                 recorddifference = strcat("^2", " [-", TIME_ENCODED_TOSTRING(grecordtime-t), "]");
194                                                 if(grecordholder == "")
195                                                         bprint(e.netname, strcat("^7 broke their all-time fastest lap record ", TIME_ENCODED_TOSTRING(grecordtime), " with ", TIME_ENCODED_TOSTRING(t), recorddifference), "\n");
196                                                 else
197                                                         bprint(e.netname, strcat("^7 broke ", grecordholder, "^7's all-time fastest lap record ", TIME_ENCODED_TOSTRING(grecordtime), " with ", TIME_ENCODED_TOSTRING(t), recorddifference), "\n");
198                                                 db_put(ServerProgsDB, strcat(GetMapname(), rr, "time"), ftos(t));
199                                                 db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname"), e.netname);
200                                                 write_recordmarker(e, time - TIME_DECODE(t), TIME_DECODE(t));
201                                                 race_send_recordtime(t, MSG_ALL);
202                                         }
203                                         else
204                                         {
205                                                 recorddifference = strcat("^1", " [+", TIME_ENCODED_TOSTRING(t-grecordtime), "]");
206                                                 if(grecordholder == "")
207                                                         bprint(e.netname, strcat("^7's new fastest lap ", TIME_ENCODED_TOSTRING(t), " could not break their all-time fastest lap record of ", TIME_ENCODED_TOSTRING(grecordtime), recorddifference), "\n");
208                                                 else
209                                                         bprint(e.netname, strcat("^7's new fastest lap ", TIME_ENCODED_TOSTRING(t), " could not break ", grecordholder, "^7's all-time fastest lap record of ", TIME_ENCODED_TOSTRING(grecordtime), recorddifference), "\n");
210                                         }
211                                 }
212
213                                 if(g_race_qualifying)
214                                 {
215                                         FOR_EACH_REALPLAYER(p)
216                                                 if(p.race_checkpoint == cp)
217                                                         race_SendNextCheckpoint(p, 0);
218                                 }
219                         }
220                 }
221                 else
222                 {
223                         // dummies
224                         t = 0;
225                         recordtime = 0;
226                         recordholder = "";
227                 }
228
229                 msg_entity = e;
230                 if(g_race_qualifying)
231                 {
232                         WRITESPECTATABLE_MSG_ONE_VARNAME(dummy1, {
233                                 WriteByte(MSG_ONE, SVC_TEMPENTITY);
234                                 WriteByte(MSG_ONE, TE_CSQC_RACE);
235                                 WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_HIT_QUALIFYING);
236                                 WriteByte(MSG_ONE, race_CheckpointNetworkID(cp)); // checkpoint the player now is at
237                                 WriteInt24_t(MSG_ONE, t); // time to that intermediate
238                                 WriteInt24_t(MSG_ONE, recordtime); // previously best time
239                                 WriteString(MSG_ONE, recordholder); // record holder
240                         });
241                 }
242         }
243         else // RACE! Not Qualifying
244         {
245                 float lself, lother, othtime;
246                 entity oth;
247                 oth = race_checkpoint_lastplayers[cp];
248                 if(oth)
249                 {
250                         lself = PlayerScore_Add(e, SP_RACE_LAPS, 0);
251                         lother = race_checkpoint_lastlaps[cp];
252                         othtime = race_checkpoint_lasttimes[cp];
253                 }
254                 else
255                         lself = lother = othtime = 0;
256
257                 msg_entity = e;
258                 WRITESPECTATABLE_MSG_ONE_VARNAME(dummy2, {
259                         WriteByte(MSG_ONE, SVC_TEMPENTITY);
260                         WriteByte(MSG_ONE, TE_CSQC_RACE);
261                         WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_HIT_RACE);
262                         WriteByte(MSG_ONE, race_CheckpointNetworkID(cp)); // checkpoint the player now is at
263                         if(e == oth)
264                         {
265                                 WriteInt24_t(MSG_ONE, 0);
266                                 WriteByte(MSG_ONE, 0);
267                                 WriteString(MSG_ONE, "");
268                         }
269                         else
270                         {
271                                 WriteInt24_t(MSG_ONE, TIME_ENCODE(time - race_checkpoint_lasttimes[cp]));
272                                 WriteByte(MSG_ONE, lself - lother);
273                                 WriteString(MSG_ONE, oth.netname); // record holder
274                         }
275                 });
276
277                 race_checkpoint_lastplayers[cp] = e;
278                 race_checkpoint_lasttimes[cp] = time;
279                 race_checkpoint_lastlaps[cp] = lself;
280
281                 msg_entity = oth;
282                 WRITESPECTATABLE_MSG_ONE_VARNAME(dummy3, {
283                         WriteByte(MSG_ONE, SVC_TEMPENTITY);
284                         WriteByte(MSG_ONE, TE_CSQC_RACE);
285                         WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_HIT_RACE_BY_OPPONENT);
286                         WriteByte(MSG_ONE, race_CheckpointNetworkID(cp)); // checkpoint the player now is at
287                         if(e == oth)
288                         {
289                                 WriteInt24_t(MSG_ONE, 0);
290                                 WriteByte(MSG_ONE, 0);
291                                 WriteString(MSG_ONE, "");
292                         }
293                         else
294                         {
295                                 WriteInt24_t(MSG_ONE, TIME_ENCODE(time - othtime));
296                                 WriteByte(MSG_ONE, lother - lself);
297                                 WriteString(MSG_ONE, e.netname); // record holder
298                         }
299                 });
300         }
301 }
302
303 void race_ClearTime(entity e)
304 {
305         e.race_checkpoint = -1;
306         e.race_laptime = 0;
307         e.race_movetime = e.race_movetime_frac = e.race_movetime_count = 0;
308         e.race_penalty_accumulator = 0;
309         e.race_lastpenalty = world;
310
311         msg_entity = e;
312         WRITESPECTATABLE_MSG_ONE({
313                 WriteByte(MSG_ONE, SVC_TEMPENTITY);
314                 WriteByte(MSG_ONE, TE_CSQC_RACE);
315                 WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_CLEAR); // next
316         });
317 }
318
319 void dumpsurface(entity e)
320 {
321         float n, si, ni;
322         vector norm, vec;
323         print("Surfaces of ", etos(e), ":\n");
324
325         print("TEST = ", ftos(getsurfacenearpoint(e, '0 0 0')), "\n");
326
327         for(si = 0; ; ++si)
328         {
329                 n = getsurfacenumpoints(e, si);
330                 if(n <= 0)
331                         break;
332                 print("  Surface ", ftos(si), ":\n");
333                 norm = getsurfacenormal(e, si);
334                 print("    Normal = ", vtos(norm), "\n");
335                 for(ni = 0; ni < n; ++ni)
336                 {
337                         vec = getsurfacepoint(e, si, ni);
338                         print("    Point ", ftos(ni), " = ", vtos(vec), " (", ftos(norm * vec), ")\n");
339                 }
340         }
341 }
342
343 void checkpoint_passed()
344 {
345         string oldmsg;
346
347         if(other.classname == "porto")
348         {
349                 // do not allow portalling through checkpoints
350                 trace_plane_normal = normalize(-1 * other.velocity);
351                 self = other;
352                 W_Porto_Fail(0);
353                 return;
354         }
355
356         /*
357          * Trigger targets
358          */
359         if not((self.spawnflags & 2) && (other.classname == "player"))
360         {
361                 activator = other;
362                 oldmsg = self.message;
363                 self.message = "";
364                 SUB_UseTargets();
365                 self.message = oldmsg;
366         }
367
368         if(other.classname != "player")
369                 return;
370
371         /*
372          * Remove unauthorized equipment
373          */
374         Portal_ClearAll(other);
375
376         other.porto_forbidden = 2; // decreased by 1 each StartFrame
377
378         if(self.race_checkpoint == -2) 
379         {
380                 self.race_checkpoint = other.race_checkpoint;
381         }
382
383         entity cp;
384         float largest_cp_id;
385         for(cp = world; (cp = find(cp, classname, "target_checkpoint"));)
386                 if(cp.race_checkpoint > largest_cp_id) // update the finish id if someone hit a new checkpoint
387                 {
388                         largest_cp_id = cp.race_checkpoint;
389                         for(cp = world; (cp = find(cp, classname, "target_stopTimer"));)
390                                 cp.race_checkpoint = largest_cp_id + 1; // finish line
391                         race_highest_checkpoint = largest_cp_id + 1;
392                         race_timed_checkpoint = largest_cp_id + 1;
393
394                         for(cp = world; (cp = find(cp, classname, "target_checkpoint"));) {
395                                 if(cp.race_checkpoint == -2) // set defragcpexists to -1 so that the cp id file will be rewritten when someone finishes
396                                         defragcpexists = -1;
397                         }       
398                 }
399
400         if(other.race_checkpoint == -1 || other.race_checkpoint == self.race_checkpoint)
401         {
402                 if(self.race_penalty)
403                 {
404                         if(other.race_lastpenalty != self)
405                         {
406                                 other.race_lastpenalty = self;
407                                 race_ImposePenaltyTime(other, self.race_penalty, self.race_penalty_reason);
408                         }
409                 }
410
411                 if(other.race_penalty)
412                         return;
413
414                 /*
415                  * Trigger targets
416                  */
417                 if(self.spawnflags & 2)
418                 {
419                         activator = other;
420                         oldmsg = self.message;
421                         self.message = "";
422                         SUB_UseTargets();
423                         self.message = oldmsg;
424                 }
425
426                 other.race_checkpoint = race_NextCheckpoint(self.race_checkpoint);
427
428                 race_SendTime(other, self.race_checkpoint, other.race_movetime, !!other.race_laptime);
429
430                 if(!self.race_checkpoint) // start line
431                 {
432                         other.race_laptime = time;
433                         other.race_movetime = other.race_movetime_frac = other.race_movetime_count = 0;
434                         other.race_penalty_accumulator = 0;
435                         other.race_lastpenalty = world;
436                 }
437
438                 if(g_race_qualifying)
439                         race_SendNextCheckpoint(other, 0);
440
441                 if(defrag_ents && defragcpexists < 0 && self.classname == "target_stopTimer")
442                 {
443                         entity cp;
444                         float fh;
445                         defragcpexists = fh = fopen(strcat("maps/", GetMapname(), ".defragcp"), FILE_WRITE);
446                         if(fh >= 0)
447                         {
448                                 for(cp = world; (cp = find(cp, classname, "target_checkpoint"));)
449                                 fputs(fh, strcat(cp.targetname, " ", ftos(cp.race_checkpoint), "\n"));
450                         }
451                         fclose(fh);
452                 }
453         }
454         else if(other.race_checkpoint == race_NextCheckpoint(self.race_checkpoint))
455         {
456                 // ignored
457         }
458         else
459         {
460                 if(self.spawnflags & 4)
461                         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
462         }
463 }
464
465 void checkpoint_touch()
466 {
467         EXACTTRIGGER_TOUCH;
468         checkpoint_passed();
469 }
470
471 void checkpoint_use()
472 {
473         if(other.classname == "info_player_deathmatch") // a spawn, a spawn
474                 return;
475
476         other = activator;
477         checkpoint_passed();
478 }
479
480 float race_waypointsprite_visible_for_player(entity e)
481 {
482         if(e.race_checkpoint == -1 || e.race_checkpoint == -2)
483                 return TRUE;
484         else if(e.race_checkpoint == self.owner.race_checkpoint)
485                 return TRUE;
486         else
487                 return FALSE;
488 }
489
490 float have_verified;
491 void trigger_race_checkpoint_verify()
492 {
493         entity oldself, cp;
494         float i, p;
495         float qual;
496
497         if(have_verified)
498                 return;
499         have_verified = 1;
500         
501         qual = g_race_qualifying;
502
503         oldself = self;
504         self = spawn();
505         self.classname = "player";
506
507         if(g_race)
508         {
509                 for(i = 0; i <= race_highest_checkpoint; ++i)
510                 {
511                         self.race_checkpoint = race_NextCheckpoint(i);
512
513                         // race only (middle of the race)
514                         g_race_qualifying = 0;
515                         self.race_place = 0;
516                         if(!Spawn_FilterOutBadSpots(findchain(classname, "info_player_deathmatch"), world, 0, FALSE))
517                                 error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(self.race_place), " (used for respawning in race) - bailing out"));
518
519                         if(i == 0)
520                         {
521                                 // qualifying only
522                                 g_race_qualifying = 1;
523                                 self.race_place = race_lowest_place_spawn;
524                                 if(!Spawn_FilterOutBadSpots(findchain(classname, "info_player_deathmatch"), world, 0, FALSE))
525                                         error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(self.race_place), " (used for qualifying) - bailing out"));
526                                 
527                                 // race only (initial spawn)
528                                 g_race_qualifying = 0;
529                                 for(p = 1; p <= race_highest_place_spawn; ++p)
530                                 {
531                                         self.race_place = p;
532                                         if(!Spawn_FilterOutBadSpots(findchain(classname, "info_player_deathmatch"), world, 0, FALSE))
533                                                 error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(self.race_place), " (used for initially spawning in race) - bailing out"));
534                                 }
535                         }
536                 }
537         }
538         else if(!defrag_ents)
539         {
540                 // qualifying only
541                 self.race_checkpoint = race_NextCheckpoint(0);
542                 g_race_qualifying = 1;
543                 self.race_place = race_lowest_place_spawn;
544                 if(!Spawn_FilterOutBadSpots(findchain(classname, "info_player_deathmatch"), world, 0, FALSE))
545                         error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(self.race_place), " (used for qualifying) - bailing out"));
546         }
547         else
548         {
549                 self.race_checkpoint = race_NextCheckpoint(0);
550                 g_race_qualifying = 1;
551                 self.race_place = 0; // there's only one spawn on defrag maps
552  
553                 // check if a defragcp file already exists, then read it and apply the checkpoint order
554                 float fh;
555                 float len;
556                 string l;
557
558                 defragcpexists = fh = fopen(strcat("maps/", GetMapname(), ".defragcp"), FILE_READ);
559                 if(fh >= 0)
560                 {
561                         while(l = fgets(fh))
562                         {
563                                 len = tokenize_console(l);
564                                 if(len != 2) {
565                                         defragcpexists = -1; // something's wrong in the defrag cp file, set defragcpexists to -1 so that it will be rewritten when someone finishes
566                                         continue;
567                                 }
568                                 for(cp = world; (cp = find(cp, classname, "target_checkpoint"));)
569                                         if(argv(0) == cp.targetname)
570                                                 cp.race_checkpoint = stof(argv(1));
571                         }
572                         fclose(fh);
573                 }
574         }
575
576         g_race_qualifying = qual;
577
578         if(race_timed_checkpoint) {
579                 if(defrag_ents) {
580                         for(cp = world; (cp = find(cp, classname, "target_startTimer"));)
581                                 WaypointSprite_UpdateSprites(cp.sprite, "race-start", "", "");
582                         for(cp = world; (cp = find(cp, classname, "target_stopTimer"));)
583                                 WaypointSprite_UpdateSprites(cp.sprite, "race-finish", "", "");
584
585                         for(cp = world; (cp = find(cp, classname, "target_checkpoint"));) {
586                                 if(cp.race_checkpoint == -2) // something's wrong with the defrag cp file or it has not been written yet, set defragcpexists to -1 so that it will be rewritten when someone finishes
587                                         defragcpexists = -1;
588                         }
589
590                         if(defragcpexists != -1){
591                                 float largest_cp_id;
592                                 for(cp = world; (cp = find(cp, classname, "target_checkpoint"));)
593                                         if(cp.race_checkpoint > largest_cp_id)
594                                                 largest_cp_id = cp.race_checkpoint;
595                                 for(cp = world; (cp = find(cp, classname, "target_stopTimer"));)
596                                         cp.race_checkpoint = largest_cp_id + 1; // finish line
597                                 race_highest_checkpoint = largest_cp_id + 1;
598                                 race_timed_checkpoint = largest_cp_id + 1;
599                         } else {
600                                 for(cp = world; (cp = find(cp, classname, "target_stopTimer"));)
601                                         cp.race_checkpoint = 255; // finish line
602                                 race_highest_checkpoint = 255;
603                                 race_timed_checkpoint = 255;
604                         }
605                 }
606                 else {
607                         for(cp = world; (cp = find(cp, classname, "trigger_race_checkpoint")); )
608                                 if(cp.sprite)
609                                 {
610                                         if(cp.race_checkpoint == 0)
611                                                 WaypointSprite_UpdateSprites(cp.sprite, "race-start", "", "");
612                                         else if(cp.race_checkpoint == race_timed_checkpoint)
613                                                 WaypointSprite_UpdateSprites(cp.sprite, "race-finish", "", "");
614                                 }
615                 }
616         }
617
618         remove(self);
619         self = oldself;
620 }
621
622 void spawnfunc_trigger_race_checkpoint()
623 {
624         vector o;
625         if(!g_race && !g_cts)
626         {
627                 remove(self);
628                 return;
629         }
630
631         EXACTTRIGGER_INIT;
632
633         self.use = checkpoint_use;
634         if not(self.spawnflags & 1)
635                 self.touch = checkpoint_touch;
636
637         o = (self.absmin + self.absmax) * 0.5;
638         tracebox(o, PL_MIN, PL_MAX, o - '0 0 1' * (o_z - self.absmin_z), MOVE_NORMAL, self);
639         waypoint_spawnforitem_force(self, trace_endpos);
640         self.nearestwaypointtimeout = time + 1000000000;
641
642         if(!self.message)
643                 self.message = "went backwards";
644         if (!self.message2)
645                 self.message2 = "was pushed backwards by";
646         if (!self.race_penalty_reason)
647                 self.race_penalty_reason = "missing a checkpoint";
648         
649         self.race_checkpoint = self.cnt;
650
651         if(self.race_checkpoint > race_highest_checkpoint)
652         {
653                 race_highest_checkpoint = self.race_checkpoint;
654                 if(self.spawnflags & 8)
655                         race_timed_checkpoint = self.race_checkpoint;
656                 else
657                         race_timed_checkpoint = 0;
658         }
659
660         if(!self.race_penalty)
661         {
662                 if(self.race_checkpoint)
663                         WaypointSprite_SpawnFixed("race-checkpoint", o, self, sprite);
664                 else
665                         WaypointSprite_SpawnFixed("race-finish", o, self, sprite);
666         }
667
668         self.sprite.waypointsprite_visible_for_player = race_waypointsprite_visible_for_player;
669
670         InitializeEntity(self, trigger_race_checkpoint_verify, INITPRIO_FINDTARGET);
671 }
672
673 void spawnfunc_target_checkpoint() // defrag entity
674 {
675         vector o;
676         if(!g_race && !g_cts)
677         {
678                 remove(self);
679                 return;
680         }
681         defrag_ents = 1;
682
683
684         EXACTTRIGGER_INIT;
685
686         self.use = checkpoint_use;
687         //if not(self.spawnflags & 1)
688         //      self.touch = checkpoint_touch;
689
690         o = (self.absmin + self.absmax) * 0.5;
691         tracebox(o, PL_MIN, PL_MAX, o - '0 0 1' * (o_z - self.absmin_z), MOVE_NORMAL, self);
692         waypoint_spawnforitem_force(self, trace_endpos);
693         self.nearestwaypointtimeout = time + 1000000000;
694
695         if(!self.message)
696                 self.message = "went backwards";
697         if (!self.message2)
698                 self.message2 = "was pushed backwards by";
699         if (!self.race_penalty_reason)
700                 self.race_penalty_reason = "missing a checkpoint";
701
702         if(self.classname == "target_startTimer")
703                 self.race_checkpoint = 0;
704         else
705                 self.race_checkpoint = -2;
706
707         race_timed_checkpoint = 1;
708
709         if(self.race_checkpoint == 0)
710                 WaypointSprite_SpawnFixed("race-start", o, self, sprite);
711         else
712                 WaypointSprite_SpawnFixed("race-checkpoint", o, self, sprite);
713
714         self.sprite.waypointsprite_visible_for_player = race_waypointsprite_visible_for_player;
715
716         InitializeEntity(self, trigger_race_checkpoint_verify, INITPRIO_FINDTARGET);
717 }
718
719 void spawnfunc_target_startTimer() { spawnfunc_target_checkpoint(); }
720 void spawnfunc_target_stopTimer() { spawnfunc_target_checkpoint(); }
721
722 void race_AbandonRaceCheck(entity p)
723 {
724         if(race_completing && !p.race_completed)
725         {
726                 p.race_completed = 1;
727                 MAKE_INDEPENDENT_PLAYER(p);
728                 bprint(p.netname, "^7 has abandoned the race.\n");
729                 ClientData_Touch(p);
730         }
731 }
732
733 void race_StartCompleting()
734 {
735         entity p;
736         race_completing = 1;
737         FOR_EACH_PLAYER(p)
738                 if(p.deadflag != DEAD_NO)
739                         race_AbandonRaceCheck(p);
740 }
741
742 void race_PreparePlayer()
743 {
744         race_ClearTime(self);
745         self.race_place = 0;
746 }
747
748 void race_RetractPlayer()
749 {
750         if(!g_race && !g_cts)
751                 return;
752         self.race_checkpoint = race_PreviousCheckpoint(self.race_checkpoint);
753         if(self.race_checkpoint == 0)
754         {
755                 race_ClearTime(self);
756                 self.race_checkpoint = 0;
757         }
758 }
759
760 void race_PreDie()
761 {
762         if(!g_race && !g_cts)
763                 return;
764
765         race_AbandonRaceCheck(self);
766 }
767
768 void race_PreSpawn()
769 {
770         if(!g_race && !g_cts)
771                 return;
772         if(self.killcount == -666 || g_race_qualifying)
773                 race_PreparePlayer();
774
775         race_AbandonRaceCheck(self);
776 }
777
778 void race_PostSpawn(entity spot)
779 {
780         if(!g_race && !g_cts)
781                 return;
782         if(self.killcount != -666 && !g_race_qualifying)
783         {
784                 if(spot.target == "")
785                         // let the player run without timing, if he did not spawn at a targetting spawnpoint
786                         race_PreparePlayer();
787                 else
788                         race_RetractPlayer();
789         }
790
791         if(spot.target != "" && self.race_checkpoint == -1)
792                 self.race_checkpoint = 0;
793
794         self.race_place = 0;
795 }
796
797 void race_PreSpawnObserver()
798 {
799         if(!g_race && !g_cts)
800                 return;
801         race_PreparePlayer();
802 }
803
804 void spawnfunc_info_player_race (void)
805 {
806         if(!g_race && !g_cts)
807         {
808                 remove(self);
809                 return;
810         }
811         ++race_spawns;
812         spawnfunc_info_player_deathmatch();
813
814         if(self.race_place > race_highest_place_spawn)
815                 race_highest_place_spawn = self.race_place;
816         if(self.race_place < race_lowest_place_spawn)
817                 race_lowest_place_spawn = self.race_place;
818 }
819
820 void race_ClearRecords()
821 {
822         float i;
823         entity e;
824
825         for(i = 0; i < MAX_CHECKPOINTS; ++i)
826         {
827                 race_checkpoint_records[i] = 0;
828                 if(race_checkpoint_recordholders[i])
829                         strunzone(race_checkpoint_recordholders[i]);
830                 race_checkpoint_recordholders[i] = string_null;
831         }
832
833         FOR_EACH_CLIENT(e)
834                 race_ClearTime(e);
835 }
836
837 void race_ReadyRestart()
838 {
839         float s;
840
841         Score_NicePrint(world);
842
843         race_ClearRecords();
844         PlayerScore_Sort(race_place);
845
846         entity e;
847         FOR_EACH_CLIENT(e)
848         {
849                 if(e.race_place)
850                 {
851                         s = PlayerScore_Add(e, SP_RACE_FASTEST, 0);
852                         if(!s)
853                                 e.race_place = 0;
854                 }
855                 print(e.netname, " = ", ftos(e.race_place), "\n");
856         }
857
858         if(g_race_qualifying == 2)
859         {
860                 g_race_qualifying = 0;
861                 independent_players = 0;
862                 cvar_set("fraglimit", ftos(race_fraglimit));
863                 cvar_set("leadlimit", ftos(race_leadlimit));
864                 cvar_set("timelimit", ftos(race_timelimit));
865                 ScoreRules_race();
866         }
867 }
868
869 void race_ImposePenaltyTime(entity pl, float penalty, string reason)
870 {
871         if(g_race_qualifying)
872         {
873                 pl.race_penalty_accumulator += penalty;
874                 msg_entity = pl;
875                 WRITESPECTATABLE_MSG_ONE({
876                         WriteByte(MSG_ONE, SVC_TEMPENTITY);
877                         WriteByte(MSG_ONE, TE_CSQC_RACE);
878                         WriteByte(MSG_ONE, RACE_NET_PENALTY_QUALIFYING);
879                         WriteShort(MSG_ONE, TIME_ENCODE(penalty));
880                         WriteString(MSG_ONE, reason);
881                 });
882         }
883         else
884         {
885                 pl.race_penalty = time + penalty;
886                 msg_entity = pl;
887                 WRITESPECTATABLE_MSG_ONE_VARNAME(dummy, {
888                         WriteByte(MSG_ONE, SVC_TEMPENTITY);
889                         WriteByte(MSG_ONE, TE_CSQC_RACE);
890                         WriteByte(MSG_ONE, RACE_NET_PENALTY_RACE);
891                         WriteShort(MSG_ONE, TIME_ENCODE(penalty));
892                         WriteString(MSG_ONE, reason);
893                 });
894         }
895 }
896
897 void penalty_touch()
898 {
899         EXACTTRIGGER_TOUCH;
900         if(other.race_lastpenalty != self)
901         {
902                 other.race_lastpenalty = self;
903                 race_ImposePenaltyTime(other, self.race_penalty, self.race_penalty_reason);
904         }
905 }
906
907 void penalty_use()
908 {
909         race_ImposePenaltyTime(activator, self.race_penalty, self.race_penalty_reason);
910 }
911
912 void spawnfunc_trigger_race_penalty()
913 {
914         EXACTTRIGGER_INIT;
915
916         self.use = penalty_use;
917         if not(self.spawnflags & 1)
918                 self.touch = penalty_touch;
919
920         if (!self.race_penalty_reason)
921                 self.race_penalty_reason = "missing a checkpoint";
922         if (!self.race_penalty)
923                 self.race_penalty = 5;
924 }