]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/race.qc
fixing some false alarms by fteqcc
[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         entity cp;
347
348         if(other.classname == "porto")
349         {
350                 // do not allow portalling through checkpoints
351                 trace_plane_normal = normalize(-1 * other.velocity);
352                 self = other;
353                 W_Porto_Fail(0);
354                 return;
355         }
356
357         /*
358          * Trigger targets
359          */
360         if not((self.spawnflags & 2) && (other.classname == "player"))
361         {
362                 activator = other;
363                 oldmsg = self.message;
364                 self.message = "";
365                 SUB_UseTargets();
366                 self.message = oldmsg;
367         }
368
369         if(other.classname != "player")
370                 return;
371
372         /*
373          * Remove unauthorized equipment
374          */
375         Portal_ClearAll(other);
376
377         other.porto_forbidden = 2; // decreased by 1 each StartFrame
378
379         if(defrag_ents) {
380                 if(self.race_checkpoint == -2) 
381                 {
382                         self.race_checkpoint = other.race_checkpoint;
383                 }
384
385                 float largest_cp_id;
386                 float cp_amount;
387                 for(cp = world; (cp = find(cp, classname, "target_checkpoint"));) {
388                         cp_amount += 1;
389                         if(cp.race_checkpoint > largest_cp_id) // update the finish id if someone hit a new checkpoint
390                         {
391                                 largest_cp_id = cp.race_checkpoint;
392                                 for(cp = world; (cp = find(cp, classname, "target_stopTimer"));)
393                                         cp.race_checkpoint = largest_cp_id + 1; // finish line
394                                 race_highest_checkpoint = largest_cp_id + 1;
395                                 race_timed_checkpoint = largest_cp_id + 1;
396
397                                 for(cp = world; (cp = find(cp, classname, "target_checkpoint"));) {
398                                         if(cp.race_checkpoint == -2) // set defragcpexists to -1 so that the cp id file will be rewritten when someone finishes
399                                                 defragcpexists = -1;
400                                 }       
401                         }
402                 }
403                 if(cp_amount == 0) {
404                         for(cp = world; (cp = find(cp, classname, "target_stopTimer"));)
405                                 cp.race_checkpoint = 1;
406                         race_highest_checkpoint = 1;
407                         race_timed_checkpoint = 1;
408                 }
409         }
410
411         if(other.race_checkpoint == -1 || other.race_checkpoint == self.race_checkpoint)
412         {
413                 if(self.race_penalty)
414                 {
415                         if(other.race_lastpenalty != self)
416                         {
417                                 other.race_lastpenalty = self;
418                                 race_ImposePenaltyTime(other, self.race_penalty, self.race_penalty_reason);
419                         }
420                 }
421
422                 if(other.race_penalty)
423                         return;
424
425                 /*
426                  * Trigger targets
427                  */
428                 if(self.spawnflags & 2)
429                 {
430                         activator = other;
431                         oldmsg = self.message;
432                         self.message = "";
433                         SUB_UseTargets();
434                         self.message = oldmsg;
435                 }
436
437                 other.race_checkpoint = race_NextCheckpoint(self.race_checkpoint);
438
439                 race_SendTime(other, self.race_checkpoint, other.race_movetime, !!other.race_laptime);
440
441                 if(!self.race_checkpoint) // start line
442                 {
443                         other.race_laptime = time;
444                         other.race_movetime = other.race_movetime_frac = other.race_movetime_count = 0;
445                         other.race_penalty_accumulator = 0;
446                         other.race_lastpenalty = world;
447                 }
448
449                 if(g_race_qualifying)
450                         race_SendNextCheckpoint(other, 0);
451
452                 if(defrag_ents && defragcpexists < 0 && self.classname == "target_stopTimer")
453                 {
454                         float fh;
455                         defragcpexists = fh = fopen(strcat("maps/", GetMapname(), ".defragcp"), FILE_WRITE);
456                         if(fh >= 0)
457                         {
458                                 for(cp = world; (cp = find(cp, classname, "target_checkpoint"));)
459                                 fputs(fh, strcat(cp.targetname, " ", ftos(cp.race_checkpoint), "\n"));
460                         }
461                         fclose(fh);
462                 }
463         }
464         else if(other.race_checkpoint == race_NextCheckpoint(self.race_checkpoint))
465         {
466                 // ignored
467         }
468         else
469         {
470                 if(self.spawnflags & 4)
471                         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
472         }
473 }
474
475 void checkpoint_touch()
476 {
477         EXACTTRIGGER_TOUCH;
478         checkpoint_passed();
479 }
480
481 void checkpoint_use()
482 {
483         if(other.classname == "info_player_deathmatch") // a spawn, a spawn
484                 return;
485
486         other = activator;
487         checkpoint_passed();
488 }
489
490 float race_waypointsprite_visible_for_player(entity e)
491 {
492         if(e.race_checkpoint == -1 || e.race_checkpoint == -2)
493                 return TRUE;
494         else if(e.race_checkpoint == self.owner.race_checkpoint)
495                 return TRUE;
496         else
497                 return FALSE;
498 }
499
500 float have_verified;
501 void trigger_race_checkpoint_verify()
502 {
503         entity oldself, cp;
504         float i, p;
505         float qual;
506
507         if(have_verified)
508                 return;
509         have_verified = 1;
510         
511         qual = g_race_qualifying;
512
513         oldself = self;
514         self = spawn();
515         self.classname = "player";
516
517         if(g_race)
518         {
519                 for(i = 0; i <= race_highest_checkpoint; ++i)
520                 {
521                         self.race_checkpoint = race_NextCheckpoint(i);
522
523                         // race only (middle of the race)
524                         g_race_qualifying = 0;
525                         self.race_place = 0;
526                         if(!Spawn_FilterOutBadSpots(findchain(classname, "info_player_deathmatch"), world, 0, FALSE))
527                                 error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(self.race_place), " (used for respawning in race) - bailing out"));
528
529                         if(i == 0)
530                         {
531                                 // qualifying only
532                                 g_race_qualifying = 1;
533                                 self.race_place = race_lowest_place_spawn;
534                                 if(!Spawn_FilterOutBadSpots(findchain(classname, "info_player_deathmatch"), world, 0, FALSE))
535                                         error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(self.race_place), " (used for qualifying) - bailing out"));
536                                 
537                                 // race only (initial spawn)
538                                 g_race_qualifying = 0;
539                                 for(p = 1; p <= race_highest_place_spawn; ++p)
540                                 {
541                                         self.race_place = p;
542                                         if(!Spawn_FilterOutBadSpots(findchain(classname, "info_player_deathmatch"), world, 0, FALSE))
543                                                 error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(self.race_place), " (used for initially spawning in race) - bailing out"));
544                                 }
545                         }
546                 }
547         }
548         else if(!defrag_ents)
549         {
550                 // qualifying only
551                 self.race_checkpoint = race_NextCheckpoint(0);
552                 g_race_qualifying = 1;
553                 self.race_place = race_lowest_place_spawn;
554                 if(!Spawn_FilterOutBadSpots(findchain(classname, "info_player_deathmatch"), world, 0, FALSE))
555                         error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(self.race_place), " (used for qualifying) - bailing out"));
556         }
557         else
558         {
559                 self.race_checkpoint = race_NextCheckpoint(0);
560                 g_race_qualifying = 1;
561                 self.race_place = 0; // there's only one spawn on defrag maps
562  
563                 // check if a defragcp file already exists, then read it and apply the checkpoint order
564                 float fh;
565                 float len;
566                 string l;
567
568                 defragcpexists = fh = fopen(strcat("maps/", GetMapname(), ".defragcp"), FILE_READ);
569                 if(fh >= 0)
570                 {
571                         while((l = fgets(fh)))
572                         {
573                                 len = tokenize_console(l);
574                                 if(len != 2) {
575                                         defragcpexists = -1; // something's wrong in the defrag cp file, set defragcpexists to -1 so that it will be rewritten when someone finishes
576                                         continue;
577                                 }
578                                 for(cp = world; (cp = find(cp, classname, "target_checkpoint"));)
579                                         if(argv(0) == cp.targetname)
580                                                 cp.race_checkpoint = stof(argv(1));
581                         }
582                         fclose(fh);
583                 }
584         }
585
586         g_race_qualifying = qual;
587
588         if(race_timed_checkpoint) {
589                 if(defrag_ents) {
590                         for(cp = world; (cp = find(cp, classname, "target_startTimer"));)
591                                 WaypointSprite_UpdateSprites(cp.sprite, "race-start", "", "");
592                         for(cp = world; (cp = find(cp, classname, "target_stopTimer"));)
593                                 WaypointSprite_UpdateSprites(cp.sprite, "race-finish", "", "");
594
595                         for(cp = world; (cp = find(cp, classname, "target_checkpoint"));) {
596                                 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
597                                         defragcpexists = -1;
598                         }
599
600                         if(defragcpexists != -1){
601                                 float largest_cp_id;
602                                 for(cp = world; (cp = find(cp, classname, "target_checkpoint"));)
603                                         if(cp.race_checkpoint > largest_cp_id)
604                                                 largest_cp_id = cp.race_checkpoint;
605                                 for(cp = world; (cp = find(cp, classname, "target_stopTimer"));)
606                                         cp.race_checkpoint = largest_cp_id + 1; // finish line
607                                 race_highest_checkpoint = largest_cp_id + 1;
608                                 race_timed_checkpoint = largest_cp_id + 1;
609                         } else {
610                                 for(cp = world; (cp = find(cp, classname, "target_stopTimer"));)
611                                         cp.race_checkpoint = 255; // finish line
612                                 race_highest_checkpoint = 255;
613                                 race_timed_checkpoint = 255;
614                         }
615                 }
616                 else {
617                         for(cp = world; (cp = find(cp, classname, "trigger_race_checkpoint")); )
618                                 if(cp.sprite)
619                                 {
620                                         if(cp.race_checkpoint == 0)
621                                                 WaypointSprite_UpdateSprites(cp.sprite, "race-start", "", "");
622                                         else if(cp.race_checkpoint == race_timed_checkpoint)
623                                                 WaypointSprite_UpdateSprites(cp.sprite, "race-finish", "", "");
624                                 }
625                 }
626         }
627
628         remove(self);
629         self = oldself;
630 }
631
632 void spawnfunc_trigger_race_checkpoint()
633 {
634         vector o;
635         if(!g_race && !g_cts)
636         {
637                 remove(self);
638                 return;
639         }
640
641         EXACTTRIGGER_INIT;
642
643         self.use = checkpoint_use;
644         if not(self.spawnflags & 1)
645                 self.touch = checkpoint_touch;
646
647         o = (self.absmin + self.absmax) * 0.5;
648         tracebox(o, PL_MIN, PL_MAX, o - '0 0 1' * (o_z - self.absmin_z), MOVE_NORMAL, self);
649         waypoint_spawnforitem_force(self, trace_endpos);
650         self.nearestwaypointtimeout = time + 1000000000;
651
652         if(!self.message)
653                 self.message = "went backwards";
654         if (!self.message2)
655                 self.message2 = "was pushed backwards by";
656         if (!self.race_penalty_reason)
657                 self.race_penalty_reason = "missing a checkpoint";
658         
659         self.race_checkpoint = self.cnt;
660
661         if(self.race_checkpoint > race_highest_checkpoint)
662         {
663                 race_highest_checkpoint = self.race_checkpoint;
664                 if(self.spawnflags & 8)
665                         race_timed_checkpoint = self.race_checkpoint;
666                 else
667                         race_timed_checkpoint = 0;
668         }
669
670         if(!self.race_penalty)
671         {
672                 if(self.race_checkpoint)
673                         WaypointSprite_SpawnFixed("race-checkpoint", o, self, sprite);
674                 else
675                         WaypointSprite_SpawnFixed("race-finish", o, self, sprite);
676         }
677
678         self.sprite.waypointsprite_visible_for_player = race_waypointsprite_visible_for_player;
679
680         InitializeEntity(self, trigger_race_checkpoint_verify, INITPRIO_FINDTARGET);
681 }
682
683 void spawnfunc_target_checkpoint() // defrag entity
684 {
685         vector o;
686         if(!g_race && !g_cts)
687         {
688                 remove(self);
689                 return;
690         }
691         defrag_ents = 1;
692
693
694         EXACTTRIGGER_INIT;
695
696         self.use = checkpoint_use;
697         //if not(self.spawnflags & 1)
698         //      self.touch = checkpoint_touch;
699
700         o = (self.absmin + self.absmax) * 0.5;
701         tracebox(o, PL_MIN, PL_MAX, o - '0 0 1' * (o_z - self.absmin_z), MOVE_NORMAL, self);
702         waypoint_spawnforitem_force(self, trace_endpos);
703         self.nearestwaypointtimeout = time + 1000000000;
704
705         if(!self.message)
706                 self.message = "went backwards";
707         if (!self.message2)
708                 self.message2 = "was pushed backwards by";
709         if (!self.race_penalty_reason)
710                 self.race_penalty_reason = "missing a checkpoint";
711
712         if(self.classname == "target_startTimer")
713                 self.race_checkpoint = 0;
714         else
715                 self.race_checkpoint = -2;
716
717         race_timed_checkpoint = 1;
718
719         if(self.race_checkpoint == 0)
720                 WaypointSprite_SpawnFixed("race-start", o, self, sprite);
721         else
722                 WaypointSprite_SpawnFixed("race-checkpoint", o, self, sprite);
723
724         self.sprite.waypointsprite_visible_for_player = race_waypointsprite_visible_for_player;
725
726         InitializeEntity(self, trigger_race_checkpoint_verify, INITPRIO_FINDTARGET);
727 }
728
729 void spawnfunc_target_startTimer() { spawnfunc_target_checkpoint(); }
730 void spawnfunc_target_stopTimer() { spawnfunc_target_checkpoint(); }
731
732 void race_AbandonRaceCheck(entity p)
733 {
734         if(race_completing && !p.race_completed)
735         {
736                 p.race_completed = 1;
737                 MAKE_INDEPENDENT_PLAYER(p);
738                 bprint(p.netname, "^7 has abandoned the race.\n");
739                 ClientData_Touch(p);
740         }
741 }
742
743 void race_StartCompleting()
744 {
745         entity p;
746         race_completing = 1;
747         FOR_EACH_PLAYER(p)
748                 if(p.deadflag != DEAD_NO)
749                         race_AbandonRaceCheck(p);
750 }
751
752 void race_PreparePlayer()
753 {
754         race_ClearTime(self);
755         self.race_place = 0;
756 }
757
758 void race_RetractPlayer()
759 {
760         if(!g_race && !g_cts)
761                 return;
762         self.race_checkpoint = race_PreviousCheckpoint(self.race_checkpoint);
763         if(self.race_checkpoint == 0)
764         {
765                 race_ClearTime(self);
766                 self.race_checkpoint = 0;
767         }
768 }
769
770 void race_PreDie()
771 {
772         if(!g_race && !g_cts)
773                 return;
774
775         race_AbandonRaceCheck(self);
776 }
777
778 void race_PreSpawn()
779 {
780         if(!g_race && !g_cts)
781                 return;
782         if(self.killcount == -666 || g_race_qualifying)
783                 race_PreparePlayer();
784
785         race_AbandonRaceCheck(self);
786 }
787
788 void race_PostSpawn(entity spot)
789 {
790         if(!g_race && !g_cts)
791                 return;
792         if(self.killcount != -666 && !g_race_qualifying)
793         {
794                 if(spot.target == "")
795                         // let the player run without timing, if he did not spawn at a targetting spawnpoint
796                         race_PreparePlayer();
797                 else
798                         race_RetractPlayer();
799         }
800
801         if(spot.target != "" && self.race_checkpoint == -1)
802                 self.race_checkpoint = 0;
803
804         self.race_place = 0;
805 }
806
807 void race_PreSpawnObserver()
808 {
809         if(!g_race && !g_cts)
810                 return;
811         race_PreparePlayer();
812 }
813
814 void spawnfunc_info_player_race (void)
815 {
816         if(!g_race && !g_cts)
817         {
818                 remove(self);
819                 return;
820         }
821         ++race_spawns;
822         spawnfunc_info_player_deathmatch();
823
824         if(self.race_place > race_highest_place_spawn)
825                 race_highest_place_spawn = self.race_place;
826         if(self.race_place < race_lowest_place_spawn)
827                 race_lowest_place_spawn = self.race_place;
828 }
829
830 void race_ClearRecords()
831 {
832         float i;
833         entity e;
834
835         for(i = 0; i < MAX_CHECKPOINTS; ++i)
836         {
837                 race_checkpoint_records[i] = 0;
838                 if(race_checkpoint_recordholders[i])
839                         strunzone(race_checkpoint_recordholders[i]);
840                 race_checkpoint_recordholders[i] = string_null;
841         }
842
843         FOR_EACH_CLIENT(e)
844                 race_ClearTime(e);
845 }
846
847 void race_ReadyRestart()
848 {
849         float s;
850
851         Score_NicePrint(world);
852
853         race_ClearRecords();
854         PlayerScore_Sort(race_place);
855
856         entity e;
857         FOR_EACH_CLIENT(e)
858         {
859                 if(e.race_place)
860                 {
861                         s = PlayerScore_Add(e, SP_RACE_FASTEST, 0);
862                         if(!s)
863                                 e.race_place = 0;
864                 }
865                 print(e.netname, " = ", ftos(e.race_place), "\n");
866         }
867
868         if(g_race_qualifying == 2)
869         {
870                 g_race_qualifying = 0;
871                 independent_players = 0;
872                 cvar_set("fraglimit", ftos(race_fraglimit));
873                 cvar_set("leadlimit", ftos(race_leadlimit));
874                 cvar_set("timelimit", ftos(race_timelimit));
875                 ScoreRules_race();
876         }
877 }
878
879 void race_ImposePenaltyTime(entity pl, float penalty, string reason)
880 {
881         if(g_race_qualifying)
882         {
883                 pl.race_penalty_accumulator += penalty;
884                 msg_entity = pl;
885                 WRITESPECTATABLE_MSG_ONE({
886                         WriteByte(MSG_ONE, SVC_TEMPENTITY);
887                         WriteByte(MSG_ONE, TE_CSQC_RACE);
888                         WriteByte(MSG_ONE, RACE_NET_PENALTY_QUALIFYING);
889                         WriteShort(MSG_ONE, TIME_ENCODE(penalty));
890                         WriteString(MSG_ONE, reason);
891                 });
892         }
893         else
894         {
895                 pl.race_penalty = time + penalty;
896                 msg_entity = pl;
897                 WRITESPECTATABLE_MSG_ONE_VARNAME(dummy, {
898                         WriteByte(MSG_ONE, SVC_TEMPENTITY);
899                         WriteByte(MSG_ONE, TE_CSQC_RACE);
900                         WriteByte(MSG_ONE, RACE_NET_PENALTY_RACE);
901                         WriteShort(MSG_ONE, TIME_ENCODE(penalty));
902                         WriteString(MSG_ONE, reason);
903                 });
904         }
905 }
906
907 void penalty_touch()
908 {
909         EXACTTRIGGER_TOUCH;
910         if(other.race_lastpenalty != self)
911         {
912                 other.race_lastpenalty = self;
913                 race_ImposePenaltyTime(other, self.race_penalty, self.race_penalty_reason);
914         }
915 }
916
917 void penalty_use()
918 {
919         race_ImposePenaltyTime(activator, self.race_penalty, self.race_penalty_reason);
920 }
921
922 void spawnfunc_trigger_race_penalty()
923 {
924         EXACTTRIGGER_INIT;
925
926         self.use = penalty_use;
927         if not(self.spawnflags & 1)
928                 self.touch = penalty_touch;
929
930         if (!self.race_penalty_reason)
931                 self.race_penalty_reason = "missing a checkpoint";
932         if (!self.race_penalty)
933                 self.race_penalty = 5;
934 }