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