1 #define MAX_CHECKPOINTS 255
3 void spawnfunc_target_checkpoint();
6 .float race_penalty_accumulator;
7 .string race_penalty_reason;
8 .float race_checkpoint; // player: next checkpoint that has to be reached
10 .entity race_lastpenalty;
14 float race_checkpoint_records[MAX_CHECKPOINTS];
15 string race_checkpoint_recordholders[MAX_CHECKPOINTS];
16 float race_checkpoint_lasttimes[MAX_CHECKPOINTS];
17 float race_checkpoint_lastlaps[MAX_CHECKPOINTS];
18 entity race_checkpoint_lastplayers[MAX_CHECKPOINTS];
20 float race_highest_checkpoint;
21 float race_timed_checkpoint;
26 float race_NextCheckpoint(float f)
28 if(f >= race_highest_checkpoint)
34 float race_PreviousCheckpoint(float f)
39 return race_highest_checkpoint;
45 // 0 = common start/finish
48 float race_CheckpointNetworkID(float f)
50 if(race_timed_checkpoint)
54 else if(f == race_timed_checkpoint)
60 void race_SendNextCheckpoint(entity e, float spec) // qualifying only
69 cp = e.race_checkpoint;
70 recordtime = race_checkpoint_records[cp];
71 recordholder = race_checkpoint_recordholders[cp];
72 if(recordholder == e.netname)
77 WRITESPECTATABLE_MSG_ONE({
78 WriteByte(MSG_ONE, SVC_TEMPENTITY);
79 WriteByte(MSG_ONE, TE_CSQC_RACE);
82 WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_NEXT_SPEC_QUALIFYING);
83 //WriteCoord(MSG_ONE, e.race_laptime - e.race_penalty_accumulator);
84 WriteCoord(MSG_ONE, time - e.race_movetime - e.race_penalty_accumulator);
87 WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_NEXT_QUALIFYING);
88 WriteByte(MSG_ONE, race_CheckpointNetworkID(cp)); // checkpoint the player will be at next
89 WriteInt24_t(MSG_ONE, recordtime);
90 WriteString(MSG_ONE, recordholder);
94 void race_InitSpectator()
97 if(msg_entity.enemy.race_laptime)
98 race_SendNextCheckpoint(msg_entity.enemy, 1);
102 float grecordtime[RANKINGS_CNT];
103 string grecordholder[RANKINGS_CNT];
104 float worst_time; // last ranked time
105 float have_recs; // have we already read the records from the database before?
106 float race_GetTime(float pos) {
112 if (!have_recs) { // I guess this is better than checking if the first array item is empty, rumor has it that arrays are slow
114 for(i=0;i<RANKINGS_CNT;++i) {
115 grecordtime[i] = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "time", ftos(i))));
116 grecordholder[i] = strzone(db_get(ServerProgsDB, strcat(GetMapname(), rr, "netname", ftos(i))));
118 grecordtime[0] = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "time")));
119 grecordholder[0] = strzone(db_get(ServerProgsDB, strcat(GetMapname(), rr, "netname")));
120 worst_time = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, strcat("time", ftos(RANKINGS_CNT-1)))));
124 return grecordtime[pos-1];
127 string race_GetName(float pos) { // these other functions assume that race_GetTime has been called >= once before
128 return grecordholder[pos-1];
131 float race_CheckName(string netname) { // Does the name already exist in rankings? In that case, where? (otherwise 0)
133 for (i=RANKINGS_CNT-1;i>=0;--i)
134 if(grecordholder[i] == netname)
139 float race_GetPos(float t) {
143 for (i=0;i<RANKINGS_CNT;++i)
144 if (grecordtime[i] == 0 || grecordtime[i] > t)
147 for (i=0;i<RANKINGS_CNT;++i)
148 if (grecordtime[i] > t)
153 void race_send_recordtime(float msg)
155 // send the server best time
156 WriteByte(msg, SVC_TEMPENTITY);
157 WriteByte(msg, TE_CSQC_RACE);
158 WriteByte(msg, RACE_NET_SERVER_RECORD);
159 WriteInt24_t(msg, race_GetTime(1));
162 void race_SendRankings(float pos, float prevpos, float del, float msg)
164 WriteByte(msg, SVC_TEMPENTITY);
165 WriteByte(msg, TE_CSQC_RACE);
166 WriteByte(msg, RACE_NET_SERVER_RANKINGS);
167 WriteShort(msg, pos);
168 WriteShort(msg, prevpos);
169 WriteShort(msg, del);
170 WriteString(msg, race_GetName(pos));
171 WriteInt24_t(msg, race_GetTime(pos));
174 string race_PlaceName(float pos) {
182 return strcat(ftos(pos), "th");
185 void race_SetTime(entity e, float t) {
186 if (worst_time && t > worst_time)
192 pos = race_GetPos(t);
194 float prevpos = race_CheckName(e.netname);
195 if (prevpos && (prevpos < pos || !pos))
197 oldrec = race_GetTime(prevpos);
198 string recorddifference = strcat(" ^1[+", TIME_ENCODED_TOSTRING(t - oldrec), "]");
199 bprint(e.netname, "^1 couldn't break their ", race_PlaceName(prevpos), " place record of ", TIME_ENCODED_TOSTRING(oldrec), recorddifference, "\n");
202 } else if (!pos) { // no ranking, time worse than the worst ranked
203 string recorddifference = strcat(" ^1[+", TIME_ENCODED_TOSTRING(t - grecordtime[RANKINGS_CNT-1]), "]");
204 bprint(e.netname, "^1 couldn't break the ", race_PlaceName(RANKINGS_CNT), " place record of ", TIME_ENCODED_TOSTRING(grecordtime[RANKINGS_CNT-1]), recorddifference, "\n");
209 // move other rankings out of the way
211 if (prevpos) { // player improved his existing record
213 oldrec = grecordtime[pos-1];
215 for (i=prevpos-1;i>pos-1;--i) {
216 db_put(ServerProgsDB, strcat(GetMapname(), rr, "time", ftos(i)), ftos(grecordtime[i-1]));
217 db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname", ftos(i)), grecordholder[i-1]);
219 oldrec = grecordtime[i];
220 oldname = grecordholder[i];
222 grecordtime[i] = grecordtime[i-1];
223 if (grecordholder[i])
224 strunzone(grecordholder[i]);
225 grecordholder[i] = strzone(grecordholder[i-1]);
227 } else { // player has no ranked record yet
228 for (i=RANKINGS_CNT-1;i>pos-1;--i) {
229 db_put(ServerProgsDB, strcat(GetMapname(), rr, "time", ftos(i)), ftos(grecordtime[i-1]));
230 db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname", ftos(i)), grecordholder[i-1]);
231 grecordtime[i] = grecordtime[i-1];
232 if (grecordholder[i])
233 strunzone(grecordholder[i]);
234 grecordholder[i] = strzone(grecordholder[i-1]);
240 db_put(ServerProgsDB, strcat(GetMapname(), rr, "time"), ftos(t));
241 db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname"), e.netname);
243 if (grecordholder[0])
244 strunzone(grecordholder[0]);
245 grecordholder[0] = strzone(e.netname);
246 write_recordmarker(e, time - TIME_DECODE(t), TIME_DECODE(t));
247 race_send_recordtime(MSG_ALL);
249 db_put(ServerProgsDB, strcat(GetMapname(), rr, "time", ftos(pos-1)), ftos(t));
250 db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname", ftos(pos-1)), e.netname);
251 grecordtime[pos-1] = t;
252 if (grecordholder[pos-1])
253 strunzone(grecordholder[pos-1]);
254 grecordholder[pos-1] = strzone(e.netname);
257 if (pos == RANKINGS_CNT)
260 race_SendRankings(pos, prevpos, 0, MSG_ALL);
262 strunzone(rankings_reply);
263 rankings_reply = strzone(getrankings());
265 string recorddifference = strcat(" ^3[-", TIME_ENCODED_TOSTRING(oldrec - t), "]");
266 bprint(e.netname, "^3 improved their ", race_PlaceName(pos), " ^3place record with ", TIME_ENCODED_TOSTRING(t), recorddifference, "\n");
268 else if(oldname == "")
269 bprint(e.netname, "^2 set the ", race_PlaceName(pos), " ^2place record with ", TIME_ENCODED_TOSTRING(t), "\n");
271 string recorddifference = strcat(" ^2[-", TIME_ENCODED_TOSTRING(oldrec - t), "]");
272 bprint(e.netname, "^2 broke ", oldname, "^2's ", race_PlaceName(pos), " ^2place record ", strcat(TIME_ENCODED_TOSTRING(oldrec), " ^2with ", TIME_ENCODED_TOSTRING(t), recorddifference, "\n"));
276 void race_DeleteTime(float pos) {
279 for (i = pos-1; i <= RANKINGS_CNT-1; ++i) {
281 db_put(ServerProgsDB, strcat(GetMapname(), rr, "time"), ftos(grecordtime[1]));
282 db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname"), grecordholder[1]);
283 grecordtime[0] = grecordtime[1];
284 if (grecordholder[i])
285 strunzone(grecordholder[0]);
286 grecordholder[0] = strzone(grecordholder[1]);
288 else if (i == RANKINGS_CNT-1) {
289 db_put(ServerProgsDB, strcat(GetMapname(), rr, "time", ftos(i)), string_null);
290 db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname", ftos(i)), string_null);
292 if (grecordholder[i])
293 strunzone(grecordholder[i]);
294 grecordholder[i] = string_null;
297 db_put(ServerProgsDB, strcat(GetMapname(), rr, "time", ftos(i)), ftos(grecordtime[i+1]));
298 db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname", ftos(i)), grecordholder[i+1]);
299 grecordtime[i] = grecordtime[i+1];
300 if (grecordholder[i])
301 strunzone(grecordholder[i]);
302 grecordholder[i] = strzone(grecordholder[i+1]);
306 race_SendRankings(pos, 0, 1, MSG_ALL);
308 race_send_recordtime(MSG_ALL);
311 strunzone(rankings_reply);
312 rankings_reply = strzone(getrankings());
317 void race_SendTime(entity e, float cp, float t, float tvalid)
322 if(g_race_qualifying)
323 t += e.race_penalty_accumulator;
325 t = TIME_ENCODE(t); // make integer
326 // adding just 0.4 so it rounds down in the .5 case (matching the timer display)
329 if(cp == race_timed_checkpoint) // finish line
330 if not(e.race_completed)
333 if(g_race_qualifying)
335 s = PlayerScore_Add(e, SP_RACE_FASTEST, 0);
337 PlayerScore_Add(e, SP_RACE_FASTEST, t - s);
341 s = PlayerScore_Add(e, SP_RACE_TIME, 0);
342 snew = TIME_ENCODE(time - game_starttime);
343 PlayerScore_Add(e, SP_RACE_TIME, snew - s);
344 l = PlayerTeamScore_Add(e, SP_RACE_LAPS, ST_RACE_LAPS, 1);
346 if(cvar("fraglimit"))
347 if(l >= cvar("fraglimit"))
348 race_StartCompleting();
352 e.race_completed = 1;
353 MAKE_INDEPENDENT_PLAYER(e);
354 bprint(e.netname, "^7 has finished the race.\n");
362 if(g_race_qualifying)
366 recordtime = race_checkpoint_records[cp];
367 recordholder = strcat1(race_checkpoint_recordholders[cp]); // make a tempstring copy, as we'll possibly strunzone it!
368 if(recordholder == e.netname)
372 if(t < worst_time || worst_time == 0)
374 if(cp == race_timed_checkpoint)
379 if(t < recordtime || recordtime == 0)
381 race_checkpoint_records[cp] = t;
382 if(race_checkpoint_recordholders[cp])
383 strunzone(race_checkpoint_recordholders[cp]);
384 race_checkpoint_recordholders[cp] = strzone(e.netname);
385 if(g_race_qualifying)
387 FOR_EACH_REALPLAYER(p)
388 if(p.race_checkpoint == cp)
389 race_SendNextCheckpoint(p, 0);
403 if(g_race_qualifying)
405 WRITESPECTATABLE_MSG_ONE_VARNAME(dummy1, {
406 WriteByte(MSG_ONE, SVC_TEMPENTITY);
407 WriteByte(MSG_ONE, TE_CSQC_RACE);
408 WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_HIT_QUALIFYING);
409 WriteByte(MSG_ONE, race_CheckpointNetworkID(cp)); // checkpoint the player now is at
410 WriteInt24_t(MSG_ONE, t); // time to that intermediate
411 WriteInt24_t(MSG_ONE, recordtime); // previously best time
412 WriteString(MSG_ONE, recordholder); // record holder
416 else // RACE! Not Qualifying
418 float lself, lother, othtime;
420 oth = race_checkpoint_lastplayers[cp];
423 lself = PlayerScore_Add(e, SP_RACE_LAPS, 0);
424 lother = race_checkpoint_lastlaps[cp];
425 othtime = race_checkpoint_lasttimes[cp];
428 lself = lother = othtime = 0;
431 WRITESPECTATABLE_MSG_ONE_VARNAME(dummy2, {
432 WriteByte(MSG_ONE, SVC_TEMPENTITY);
433 WriteByte(MSG_ONE, TE_CSQC_RACE);
434 WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_HIT_RACE);
435 WriteByte(MSG_ONE, race_CheckpointNetworkID(cp)); // checkpoint the player now is at
438 WriteInt24_t(MSG_ONE, 0);
439 WriteByte(MSG_ONE, 0);
440 WriteString(MSG_ONE, "");
444 WriteInt24_t(MSG_ONE, TIME_ENCODE(time - race_checkpoint_lasttimes[cp]));
445 WriteByte(MSG_ONE, lself - lother);
446 WriteString(MSG_ONE, oth.netname); // record holder
450 race_checkpoint_lastplayers[cp] = e;
451 race_checkpoint_lasttimes[cp] = time;
452 race_checkpoint_lastlaps[cp] = lself;
455 WRITESPECTATABLE_MSG_ONE_VARNAME(dummy3, {
456 WriteByte(MSG_ONE, SVC_TEMPENTITY);
457 WriteByte(MSG_ONE, TE_CSQC_RACE);
458 WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_HIT_RACE_BY_OPPONENT);
459 WriteByte(MSG_ONE, race_CheckpointNetworkID(cp)); // checkpoint the player now is at
462 WriteInt24_t(MSG_ONE, 0);
463 WriteByte(MSG_ONE, 0);
464 WriteString(MSG_ONE, "");
468 WriteInt24_t(MSG_ONE, TIME_ENCODE(time - othtime));
469 WriteByte(MSG_ONE, lother - lself);
470 WriteString(MSG_ONE, e.netname); // record holder
476 void race_ClearTime(entity e)
478 e.race_checkpoint = 0;
480 e.race_movetime = e.race_movetime_frac = e.race_movetime_count = 0;
481 e.race_penalty_accumulator = 0;
482 e.race_lastpenalty = world;
485 WRITESPECTATABLE_MSG_ONE({
486 WriteByte(MSG_ONE, SVC_TEMPENTITY);
487 WriteByte(MSG_ONE, TE_CSQC_RACE);
488 WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_CLEAR); // next
492 void dumpsurface(entity e)
496 print("Surfaces of ", etos(e), ":\n");
498 print("TEST = ", ftos(getsurfacenearpoint(e, '0 0 0')), "\n");
502 n = getsurfacenumpoints(e, si);
505 print(" Surface ", ftos(si), ":\n");
506 norm = getsurfacenormal(e, si);
507 print(" Normal = ", vtos(norm), "\n");
508 for(ni = 0; ni < n; ++ni)
510 vec = getsurfacepoint(e, si, ni);
511 print(" Point ", ftos(ni), " = ", vtos(vec), " (", ftos(norm * vec), ")\n");
516 void checkpoint_passed()
521 if(other.classname == "porto")
523 // do not allow portalling through checkpoints
524 trace_plane_normal = normalize(-1 * other.velocity);
533 if not((self.spawnflags & 2) && (other.classname == "player"))
536 oldmsg = self.message;
539 self.message = oldmsg;
542 if(other.classname != "player")
546 * Remove unauthorized equipment
548 Portal_ClearAll(other);
550 other.porto_forbidden = 2; // decreased by 1 each StartFrame
553 if(self.race_checkpoint == -2)
555 self.race_checkpoint = other.race_checkpoint;
560 for(cp = world; (cp = find(cp, classname, "target_checkpoint"));) {
562 if(cp.race_checkpoint > largest_cp_id) // update the finish id if someone hit a new checkpoint
564 largest_cp_id = cp.race_checkpoint;
565 for(cp = world; (cp = find(cp, classname, "target_stopTimer"));)
566 cp.race_checkpoint = largest_cp_id + 1; // finish line
567 race_highest_checkpoint = largest_cp_id + 1;
568 race_timed_checkpoint = largest_cp_id + 1;
570 for(cp = world; (cp = find(cp, classname, "target_checkpoint"));) {
571 if(cp.race_checkpoint == -2) // set defragcpexists to -1 so that the cp id file will be rewritten when someone finishes
577 for(cp = world; (cp = find(cp, classname, "target_stopTimer"));)
578 cp.race_checkpoint = 1;
579 race_highest_checkpoint = 1;
580 race_timed_checkpoint = 1;
584 if((other.race_checkpoint == -1 && self.race_checkpoint == 0) || (other.race_checkpoint == self.race_checkpoint))
586 if(self.race_penalty)
588 if(other.race_lastpenalty != self)
590 other.race_lastpenalty = self;
591 race_ImposePenaltyTime(other, self.race_penalty, self.race_penalty_reason);
595 if(other.race_penalty)
601 if(self.spawnflags & 2)
604 oldmsg = self.message;
607 self.message = oldmsg;
610 if(other.race_respawn_checkpoint != self.race_checkpoint || !other.race_started)
611 other.race_respawn_spotref = self; // this is not a spot but a CP, but spawnpoint selection will deal with that
612 other.race_respawn_checkpoint = self.race_checkpoint;
613 other.race_checkpoint = race_NextCheckpoint(self.race_checkpoint);
614 other.race_started = 1;
616 race_SendTime(other, self.race_checkpoint, other.race_movetime, !!other.race_laptime);
618 if(!self.race_checkpoint) // start line
620 other.race_laptime = time;
621 other.race_movetime = other.race_movetime_frac = other.race_movetime_count = 0;
622 other.race_penalty_accumulator = 0;
623 other.race_lastpenalty = world;
626 if(g_race_qualifying)
627 race_SendNextCheckpoint(other, 0);
629 if(defrag_ents && defragcpexists < 0 && self.classname == "target_stopTimer")
632 defragcpexists = fh = fopen(strcat("maps/", GetMapname(), ".defragcp"), FILE_WRITE);
635 for(cp = world; (cp = find(cp, classname, "target_checkpoint"));)
636 fputs(fh, strcat(cp.targetname, " ", ftos(cp.race_checkpoint), "\n"));
641 else if(other.race_checkpoint == race_NextCheckpoint(self.race_checkpoint))
647 if(self.spawnflags & 4)
648 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
652 void checkpoint_touch()
658 void checkpoint_use()
660 if(other.classname == "info_player_deathmatch") // a spawn, a spawn
667 float race_waypointsprite_visible_for_player(entity e)
669 if(e.race_checkpoint == -1 || self.owner.race_checkpoint == -2)
671 else if(e.race_checkpoint == self.owner.race_checkpoint)
678 void trigger_race_checkpoint_verify()
688 qual = g_race_qualifying;
692 self.classname = "player";
696 for(i = 0; i <= race_highest_checkpoint; ++i)
698 self.race_checkpoint = race_NextCheckpoint(i);
700 // race only (middle of the race)
701 g_race_qualifying = 0;
703 if(!Spawn_FilterOutBadSpots(findchain(classname, "info_player_deathmatch"), world, 0, FALSE, FALSE))
704 error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(self.race_place), " (used for respawning in race) - bailing out"));
709 g_race_qualifying = 1;
710 self.race_place = race_lowest_place_spawn;
711 if(!Spawn_FilterOutBadSpots(findchain(classname, "info_player_deathmatch"), world, 0, FALSE, FALSE))
712 error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(self.race_place), " (used for qualifying) - bailing out"));
714 // race only (initial spawn)
715 g_race_qualifying = 0;
716 for(p = 1; p <= race_highest_place_spawn; ++p)
719 if(!Spawn_FilterOutBadSpots(findchain(classname, "info_player_deathmatch"), world, 0, FALSE, FALSE))
720 error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(self.race_place), " (used for initially spawning in race) - bailing out"));
725 else if(!defrag_ents)
728 self.race_checkpoint = race_NextCheckpoint(0);
729 g_race_qualifying = 1;
730 self.race_place = race_lowest_place_spawn;
731 if(!Spawn_FilterOutBadSpots(findchain(classname, "info_player_deathmatch"), world, 0, FALSE, FALSE))
732 error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(self.race_place), " (used for qualifying) - bailing out"));
736 self.race_checkpoint = race_NextCheckpoint(0);
737 g_race_qualifying = 1;
738 self.race_place = 0; // there's only one spawn on defrag maps
740 // check if a defragcp file already exists, then read it and apply the checkpoint order
745 defragcpexists = fh = fopen(strcat("maps/", GetMapname(), ".defragcp"), FILE_READ);
748 while((l = fgets(fh)))
750 len = tokenize_console(l);
752 defragcpexists = -1; // something's wrong in the defrag cp file, set defragcpexists to -1 so that it will be rewritten when someone finishes
755 for(cp = world; (cp = find(cp, classname, "target_checkpoint"));)
756 if(argv(0) == cp.targetname)
757 cp.race_checkpoint = stof(argv(1));
763 g_race_qualifying = qual;
765 if(race_timed_checkpoint) {
767 for(cp = world; (cp = find(cp, classname, "target_startTimer"));)
768 WaypointSprite_UpdateSprites(cp.sprite, "race-start", "", "");
769 for(cp = world; (cp = find(cp, classname, "target_stopTimer"));)
770 WaypointSprite_UpdateSprites(cp.sprite, "race-finish", "", "");
772 for(cp = world; (cp = find(cp, classname, "target_checkpoint"));) {
773 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
777 if(defragcpexists != -1){
779 for(cp = world; (cp = find(cp, classname, "target_checkpoint"));)
780 if(cp.race_checkpoint > largest_cp_id)
781 largest_cp_id = cp.race_checkpoint;
782 for(cp = world; (cp = find(cp, classname, "target_stopTimer"));)
783 cp.race_checkpoint = largest_cp_id + 1; // finish line
784 race_highest_checkpoint = largest_cp_id + 1;
785 race_timed_checkpoint = largest_cp_id + 1;
787 for(cp = world; (cp = find(cp, classname, "target_stopTimer"));)
788 cp.race_checkpoint = 255; // finish line
789 race_highest_checkpoint = 255;
790 race_timed_checkpoint = 255;
794 for(cp = world; (cp = find(cp, classname, "trigger_race_checkpoint")); )
797 if(cp.race_checkpoint == 0)
798 WaypointSprite_UpdateSprites(cp.sprite, "race-start", "", "");
799 else if(cp.race_checkpoint == race_timed_checkpoint)
800 WaypointSprite_UpdateSprites(cp.sprite, "race-finish", "", "");
806 entity trigger, targ;
807 for(trigger = world; (trigger = find(trigger, classname, "trigger_multiple")); )
808 for(targ = world; (targ = find(targ, targetname, trigger.target)); )
809 if (targ.classname == "target_checkpoint" || targ.classname == "target_startTimer" || targ.classname == "target_stopTimer") {
813 setsize(targ, trigger.mins, trigger.maxs);
814 setorigin(targ, trigger.origin);
822 void spawnfunc_trigger_race_checkpoint()
825 if(!g_race && !g_cts)
833 self.use = checkpoint_use;
834 if not(self.spawnflags & 1)
835 self.touch = checkpoint_touch;
837 o = (self.absmin + self.absmax) * 0.5;
838 tracebox(o, PL_MIN, PL_MAX, o - '0 0 1' * (o_z - self.absmin_z), MOVE_NORMAL, self);
839 waypoint_spawnforitem_force(self, trace_endpos);
840 self.nearestwaypointtimeout = time + 1000000000;
843 self.message = "went backwards";
845 self.message2 = "was pushed backwards by";
846 if (!self.race_penalty_reason)
847 self.race_penalty_reason = "missing a checkpoint";
849 self.race_checkpoint = self.cnt;
851 if(self.race_checkpoint > race_highest_checkpoint)
853 race_highest_checkpoint = self.race_checkpoint;
854 if(self.spawnflags & 8)
855 race_timed_checkpoint = self.race_checkpoint;
857 race_timed_checkpoint = 0;
860 if(!self.race_penalty)
862 if(self.race_checkpoint)
863 WaypointSprite_SpawnFixed("race-checkpoint", o, self, sprite);
865 WaypointSprite_SpawnFixed("race-finish", o, self, sprite);
868 self.sprite.waypointsprite_visible_for_player = race_waypointsprite_visible_for_player;
870 InitializeEntity(self, trigger_race_checkpoint_verify, INITPRIO_FINDTARGET);
873 void spawnfunc_target_checkpoint() // defrag entity
876 if(!g_race && !g_cts)
885 self.use = checkpoint_use;
886 if not(self.spawnflags & 1)
887 self.touch = checkpoint_touch;
889 o = (self.absmin + self.absmax) * 0.5;
890 tracebox(o, PL_MIN, PL_MAX, o - '0 0 1' * (o_z - self.absmin_z), MOVE_NORMAL, self);
891 waypoint_spawnforitem_force(self, trace_endpos);
892 self.nearestwaypointtimeout = time + 1000000000;
895 self.message = "went backwards";
897 self.message2 = "was pushed backwards by";
898 if (!self.race_penalty_reason)
899 self.race_penalty_reason = "missing a checkpoint";
901 if(self.classname == "target_startTimer")
902 self.race_checkpoint = 0;
904 self.race_checkpoint = -2;
906 race_timed_checkpoint = 1;
908 if(self.race_checkpoint == 0)
909 WaypointSprite_SpawnFixed("race-start", o, self, sprite);
911 WaypointSprite_SpawnFixed("race-checkpoint", o, self, sprite);
913 self.sprite.waypointsprite_visible_for_player = race_waypointsprite_visible_for_player;
915 InitializeEntity(self, trigger_race_checkpoint_verify, INITPRIO_FINDTARGET);
918 void spawnfunc_target_startTimer() { spawnfunc_target_checkpoint(); }
919 void spawnfunc_target_stopTimer() { spawnfunc_target_checkpoint(); }
921 void race_AbandonRaceCheck(entity p)
923 if(race_completing && !p.race_completed)
925 p.race_completed = 1;
926 MAKE_INDEPENDENT_PLAYER(p);
927 bprint(p.netname, "^7 has abandoned the race.\n");
932 void race_StartCompleting()
937 if(p.deadflag != DEAD_NO)
938 race_AbandonRaceCheck(p);
941 void race_PreparePlayer()
943 race_ClearTime(self);
945 self.race_started = 0;
946 self.race_respawn_checkpoint = 0;
947 self.race_respawn_spotref = world;
950 void race_RetractPlayer()
952 if(!g_race && !g_cts)
954 if(self.race_respawn_checkpoint == 0 || self.race_respawn_checkpoint == race_timed_checkpoint)
955 race_ClearTime(self);
956 self.race_checkpoint = self.race_respawn_checkpoint;
961 if(!g_race && !g_cts)
964 race_AbandonRaceCheck(self);
969 if(!g_race && !g_cts)
971 if(self.killcount == -666 /* initial spawn */ || g_race_qualifying) // spawn
972 race_PreparePlayer();
974 race_RetractPlayer();
976 race_AbandonRaceCheck(self);
979 void race_PostSpawn(entity spot)
981 if(!g_race && !g_cts)
984 if(spot.target == "")
985 // Emergency: this wasn't a real spawnpoint. Can this ever happen?
986 race_PreparePlayer();
988 // if we need to respawn, do it right
989 self.race_respawn_checkpoint = self.race_checkpoint;
990 self.race_respawn_spotref = spot;
995 void race_PreSpawnObserver()
997 if(!g_race && !g_cts)
999 race_PreparePlayer();
1000 self.race_checkpoint = -1;
1003 void spawnfunc_info_player_race (void)
1005 if(!g_race && !g_cts)
1011 spawnfunc_info_player_deathmatch();
1013 if(self.race_place > race_highest_place_spawn)
1014 race_highest_place_spawn = self.race_place;
1015 if(self.race_place < race_lowest_place_spawn)
1016 race_lowest_place_spawn = self.race_place;
1019 void race_ClearRecords()
1024 for(i = 0; i < MAX_CHECKPOINTS; ++i)
1026 race_checkpoint_records[i] = 0;
1027 if(race_checkpoint_recordholders[i])
1028 strunzone(race_checkpoint_recordholders[i]);
1029 race_checkpoint_recordholders[i] = string_null;
1033 FOR_EACH_CLIENT(self)
1036 p = self.race_place;
1037 race_PreparePlayer();
1038 self.race_place = p;
1043 void race_ReadyRestart()
1047 Score_NicePrint(world);
1049 race_ClearRecords();
1050 PlayerScore_Sort(race_place);
1057 s = PlayerScore_Add(e, SP_RACE_FASTEST, 0);
1061 print(e.netname, " = ", ftos(e.race_place), "\n");
1064 if(g_race_qualifying == 2)
1066 g_race_qualifying = 0;
1067 independent_players = 0;
1068 cvar_set("fraglimit", ftos(race_fraglimit));
1069 cvar_set("leadlimit", ftos(race_leadlimit));
1070 cvar_set("timelimit", ftos(race_timelimit));
1075 void race_ImposePenaltyTime(entity pl, float penalty, string reason)
1077 if(g_race_qualifying)
1079 pl.race_penalty_accumulator += penalty;
1081 WRITESPECTATABLE_MSG_ONE({
1082 WriteByte(MSG_ONE, SVC_TEMPENTITY);
1083 WriteByte(MSG_ONE, TE_CSQC_RACE);
1084 WriteByte(MSG_ONE, RACE_NET_PENALTY_QUALIFYING);
1085 WriteShort(MSG_ONE, TIME_ENCODE(penalty));
1086 WriteString(MSG_ONE, reason);
1091 pl.race_penalty = time + penalty;
1093 WRITESPECTATABLE_MSG_ONE_VARNAME(dummy, {
1094 WriteByte(MSG_ONE, SVC_TEMPENTITY);
1095 WriteByte(MSG_ONE, TE_CSQC_RACE);
1096 WriteByte(MSG_ONE, RACE_NET_PENALTY_RACE);
1097 WriteShort(MSG_ONE, TIME_ENCODE(penalty));
1098 WriteString(MSG_ONE, reason);
1103 void penalty_touch()
1106 if(other.race_lastpenalty != self)
1108 other.race_lastpenalty = self;
1109 race_ImposePenaltyTime(other, self.race_penalty, self.race_penalty_reason);
1115 race_ImposePenaltyTime(activator, self.race_penalty, self.race_penalty_reason);
1118 void spawnfunc_trigger_race_penalty()
1122 self.use = penalty_use;
1123 if not(self.spawnflags & 1)
1124 self.touch = penalty_touch;
1126 if (!self.race_penalty_reason)
1127 self.race_penalty_reason = "missing a checkpoint";
1128 if (!self.race_penalty)
1129 self.race_penalty = 5;