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