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