Xonotic
race.qc
Go to the documentation of this file.
1 #include "race.qh"
2 
9 #include <common/net_linked.qh>
11 #include <common/playerstats.qh>
12 #include <common/state.qh>
13 #include <common/stats.qh>
15 #include <common/weapons/_all.qh>
17 #include <lib/warpzone/common.qh>
19 #include <server/bot/api.qh>
20 #include <server/cheats.qh>
21 #include <server/client.qh>
23 #include <server/damage.qh>
24 #include <server/gamelog.qh>
25 #include <server/intermission.qh>
26 #include <server/main.qh>
27 #include <server/mutators/_mod.qh>
28 #include <server/portals.qh>
29 #include <server/scores.qh>
30 #include <server/spawnpoints.qh>
31 #include <server/weapons/common.qh>
32 #include <server/world.qh>
33 
34 .string stored_netname; // TODO: store this information independently of race-based gamemodes
35 
36 string uid2name(string myuid)
37 {
38  string s = db_get(ServerProgsDB, strcat("/uid2name/", myuid));
39 
40  // FIXME remove this later after 0.6 release
41  // convert old style broken records to correct style
42  if(s == "")
43  {
44  s = db_get(ServerProgsDB, strcat("uid2name", myuid));
45  if(s != "")
46  {
47  db_put(ServerProgsDB, strcat("/uid2name/", myuid), s);
48  db_remove(ServerProgsDB, strcat("uid2name", myuid));
49  }
50  }
51 
52  if(s == "")
53  s = "^1Unregistered Player";
54  return s;
55 }
56 
57 void write_recordmarker(entity pl, float tstart, float dt)
58 {
59  GameLogEcho(strcat(":recordset:", ftos(pl.playerid), ":", ftos(dt)));
60 
61  // also write a marker into demo files for demotc-race-record-extractor to find
62  stuffcmd(pl,
63  strcat(
64  strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", TIME_ENCODED_TOSTRING(TIME_ENCODE(dt))),
65  " ", ftos(tstart), " ", ftos(dt), "\n"));
66 }
67 
70 
72 {
74  if(msg_entity.enemy.race_laptime)
76 }
77 
78 float race_readTime(string map, float pos)
79 {
80  return stof(db_get(ServerProgsDB, strcat(map, record_type, "time", ftos(pos))));
81 }
82 
83 string race_readUID(string map, float pos)
84 {
85  return db_get(ServerProgsDB, strcat(map, record_type, "crypto_idfp", ftos(pos)));
86 }
87 
88 float race_readPos(string map, float t)
89 {
90  for(int i = 1; i <= RANKINGS_CNT; ++i)
91  {
92  int mytime = race_readTime(map, i);
93  if(!mytime || mytime > t)
94  return i;
95  }
96 
97  return 0; // pos is zero if unranked
98 }
99 
100 void race_writeTime(string map, float t, string myuid)
101 {
102  float newpos;
103  newpos = race_readPos(map, t);
104 
105  float i, prevpos = 0;
106  for(i = 1; i <= RANKINGS_CNT; ++i)
107  {
108  if(race_readUID(map, i) == myuid)
109  prevpos = i;
110  }
111  if (prevpos)
112  {
113  // player improved his existing record, only have to iterate on ranks between new and old recs
114  for (i = prevpos; i > newpos; --i)
115  {
116  db_put(ServerProgsDB, strcat(map, record_type, "time", ftos(i)), ftos(race_readTime(map, i - 1)));
117  db_put(ServerProgsDB, strcat(map, record_type, "crypto_idfp", ftos(i)), race_readUID(map, i - 1));
118  }
119  }
120  else
121  {
122  // player has no ranked record yet
123  for (i = RANKINGS_CNT; i > newpos; --i)
124  {
125  float other_time = race_readTime(map, i - 1);
126  if (other_time) {
127  db_put(ServerProgsDB, strcat(map, record_type, "time", ftos(i)), ftos(other_time));
128  db_put(ServerProgsDB, strcat(map, record_type, "crypto_idfp", ftos(i)), race_readUID(map, i - 1));
129  }
130  }
131  }
132 
133  // store new time itself
134  db_put(ServerProgsDB, strcat(map, record_type, "time", ftos(newpos)), ftos(t));
135  db_put(ServerProgsDB, strcat(map, record_type, "crypto_idfp", ftos(newpos)), myuid);
136 }
137 
138 string race_readName(string map, float pos)
139 {
140  return uid2name(db_get(ServerProgsDB, strcat(map, record_type, "crypto_idfp", ftos(pos))));
141 }
142 
144 {
145  if(CS_CVAR(player).cvar_cl_allow_uidtracking == 1 && CS_CVAR(player).cvar_cl_allow_uid2name == 1)
146  {
147  if (!player.stored_netname)
148  player.stored_netname = strzone(uid2name(player.crypto_idfp));
149  if(player.stored_netname != player.netname)
150  {
151  db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
152  strcpy(player.stored_netname, player.netname);
153  }
154  }
155 }
156 
157 
158 const float MAX_CHECKPOINTS = 255;
159 
163 .float race_checkpoint; // player: next checkpoint that has to be reached
165 
166 .entity sprite;
167 
173 
175 
178 
181 
182 float race_NextCheckpoint(float f)
183 {
184  if(f >= race_highest_checkpoint)
185  return 0;
186  else
187  return f + 1;
188 }
189 
191 {
192  if(f == -1)
193  return 0;
194  else if(f == 0)
196  else
197  return f - 1;
198 }
199 
200 // encode as:
201 // 0 = common start/finish
202 // 254 = start
203 // 255 = finish
205 {
207  {
208  if(f == 0)
209  return 254; // start
210  else if(f == race_timed_checkpoint)
211  return 255; // finish
212  }
213  return f;
214 }
215 
216 void race_SendNextCheckpoint(entity e, float spec) // qualifying only
217 {
218  if(!e.race_laptime)
219  return;
220 
221  int cp = e.race_checkpoint;
222  float recordtime = race_checkpoint_records[cp];
223  float myrecordtime = e.race_checkpoint_record[cp];
224  string recordholder = race_checkpoint_recordholders[cp];
225  if(recordholder == e.netname)
226  recordholder = "";
227 
228  if(!IS_REAL_CLIENT(e))
229  return;
230 
231  if(!spec)
232  msg_entity = e;
233  WRITESPECTATABLE_MSG_ONE(msg_entity, {
234  WriteHeader(MSG_ONE, TE_CSQC_RACE);
235  if(spec)
236  {
238  //WriteCoord(MSG_ONE, e.race_laptime - e.race_penalty_accumulator);
239  WriteCoord(MSG_ONE, time - e.race_movetime - e.race_penalty_accumulator);
240  }
241  else
243  WriteByte(MSG_ONE, race_CheckpointNetworkID(cp)); // checkpoint the player will be at next
244  WriteInt24_t(MSG_ONE, recordtime);
245  if(!spec)
246  WriteInt24_t(MSG_ONE, myrecordtime);
247  WriteString(MSG_ONE, recordholder);
248  });
249 }
250 
251 void race_send_recordtime(float msg)
252 {
253  // send the server best time
254  WriteHeader(msg, TE_CSQC_RACE);
256  WriteInt24_t(msg, race_readTime(GetMapname(), 1));
257 }
258 
259 void race_send_speedaward(float msg)
260 {
261  // send the best speed of the round
262  WriteHeader(msg, TE_CSQC_RACE);
264  WriteInt24_t(msg, floor(speedaward_speed+0.5));
266 }
267 
269 {
270  // send the best speed
271  WriteHeader(msg, TE_CSQC_RACE);
273  WriteInt24_t(msg, floor(speedaward_alltimebest+0.5));
275 }
276 
277 void race_send_rankings_cnt(float msg)
278 {
279  WriteHeader(msg, TE_CSQC_RACE);
281  int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
282  WriteByte(msg, m);
283 }
284 
285 void race_SendRanking(float pos, float prevpos, float del, float msg)
286 {
287  WriteHeader(msg, TE_CSQC_RACE);
289  WriteShort(msg, pos);
290  WriteShort(msg, prevpos);
291  WriteShort(msg, del);
292  WriteString(msg, race_readName(GetMapname(), pos));
293  WriteInt24_t(msg, race_readTime(GetMapname(), pos));
294 }
295 
297 {
298  if (IS_OBSERVER(player))
299  return;
300 
301  if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed))
302  {
303  speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1');
304  speedaward_holder = player.netname;
305  speedaward_uid = player.crypto_idfp;
307  }
309  {
313  {
320  }
321  }
322 }
323 
324 void race_SendAll(entity player, bool only_rankings)
325 {
326  if(!IS_REAL_CLIENT(player))
327  return;
328 
329  msg_entity = player;
330  if (!only_rankings)
331  {
334 
338  }
339 
340  int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
342  for (int i = 1; i <= m; ++i)
343  race_SendRanking(i, 0, 0, MSG_ONE);
344 }
345 
346 void race_SendStatus(float id, entity e)
347 {
348  if(!IS_REAL_CLIENT(e))
349  return;
350 
351  float msg;
352  if (id == 0)
353  msg = MSG_ONE;
354  else
355  msg = MSG_ALL;
356  msg_entity = e;
357  WRITESPECTATABLE_MSG_ONE(msg_entity, {
358  WriteHeader(msg, TE_CSQC_RACE);
360  WriteShort(msg, id);
361  WriteString(msg, e.netname);
362  });
363 }
364 
365 void race_setTime(string map, float t, string myuid, string mynetname, entity e, bool showmessage)
366 {
367  // netname only used TEMPORARILY for printing
368  int newpos = race_readPos(map, t);
369 
370  int player_prevpos = 0;
371  for(int i = 1; i <= RANKINGS_CNT; ++i)
372  {
373  if(race_readUID(map, i) == myuid)
374  player_prevpos = i;
375  }
376 
377  float oldrec;
378  string oldrec_holder;
379  if (player_prevpos && (player_prevpos < newpos || !newpos))
380  {
381  oldrec = race_readTime(GetMapname(), player_prevpos);
382  race_SendStatus(0, e); // "fail"
383  if(showmessage)
384  Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_RACE_FAIL_RANKED, mynetname, player_prevpos, t, oldrec);
385  return;
386  }
387  else if (!newpos)
388  {
389  // no ranking, time worse than the worst ranked
390  oldrec = race_readTime(GetMapname(), RANKINGS_CNT);
391  race_SendStatus(0, e); // "fail"
392  if(showmessage)
393  Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_RACE_FAIL_UNRANKED, mynetname, RANKINGS_CNT, t, oldrec);
394  return;
395  }
396 
397  // if we didn't hit a return yet, we have a new record!
398 
399  // if the player does not have a UID we can unfortunately not store the record, as the rankings system relies on UIDs
400  if(myuid == "")
401  {
402  if(showmessage)
403  Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_RACE_NEW_MISSING_UID, mynetname, t);
404  return;
405  }
406 
407  if(uid2name(myuid) == "^1Unregistered Player")
408  {
409  if(showmessage)
410  Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_RACE_NEW_MISSING_NAME, mynetname, t);
411  return;
412  }
413 
414  oldrec = race_readTime(GetMapname(), newpos);
415  oldrec_holder = race_readName(GetMapname(), newpos);
416 
417  // store new ranking
418  race_writeTime(GetMapname(), t, myuid);
419 
420  if (newpos == 1 && showmessage)
421  {
424  }
425 
426  race_SendRanking(newpos, player_prevpos, 0, MSG_ALL);
427  strcpy(rankings_reply, getrankings());
428 
429  if(newpos == player_prevpos)
430  {
431  if(showmessage)
432  Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_RACE_NEW_IMPROVED, mynetname, newpos, t, oldrec);
433  if(newpos == 1) { race_SendStatus(3, e); } // "new server record"
434  else { race_SendStatus(1, e); } // "new time"
435  }
436  else if(oldrec == 0)
437  {
438  if(showmessage)
439  Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_RACE_NEW_SET, mynetname, newpos, t);
440  if(newpos == 1) { race_SendStatus(3, e); } // "new server record"
441  else { race_SendStatus(2, e); } // "new rank"
442  }
443  else
444  {
445  if(showmessage)
446  Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_RACE_NEW_BROKEN, mynetname, oldrec_holder, newpos, t, oldrec);
447  if(newpos == 1) { race_SendStatus(3, e); } // "new server record"
448  else { race_SendStatus(2, e); } // "new rank"
449  }
450 }
451 
452 void race_deleteTime(string map, float pos)
453 {
454  for(int i = pos; i <= RANKINGS_CNT; ++i)
455  {
456  string therank = ftos(i);
457  if (i == RANKINGS_CNT)
458  {
459  db_remove(ServerProgsDB, strcat(map, record_type, "time", therank));
460  db_remove(ServerProgsDB, strcat(map, record_type, "crypto_idfp", therank));
461  }
462  else
463  {
464  db_put(ServerProgsDB, strcat(map, record_type, "time", therank), ftos(race_readTime(GetMapname(), i+1)));
465  db_put(ServerProgsDB, strcat(map, record_type, "crypto_idfp", therank), race_readUID(GetMapname(), i+1));
466  }
467  }
468 
469  race_SendRanking(pos, 0, 1, MSG_ALL);
470  if(pos == 1)
472 
473  strcpy(rankings_reply, getrankings());
474 }
475 
476 void race_SendTime(entity e, float cp, float t, float tvalid)
477 {
478  float snew, l;
479 
481  t += e.race_penalty_accumulator;
482 
483  t = TIME_ENCODE(t); // make integer
484 
485  if(tvalid)
486  if(cp == race_timed_checkpoint) // finish line
487  if (!CS(e).race_completed)
488  {
489  float s;
491  {
492  s = GameRules_scoring_add(e, RACE_FASTEST, 0);
493  if(!s || t < s)
494  GameRules_scoring_add(e, RACE_FASTEST, t - s);
495  }
496  else
497  {
498  s = GameRules_scoring_add(e, RACE_FASTEST, 0);
499  if(!s || t < s)
500  GameRules_scoring_add(e, RACE_FASTEST, t - s);
501 
502  s = GameRules_scoring_add(e, RACE_TIME, 0);
503  snew = TIME_ENCODE(time - game_starttime);
504  GameRules_scoring_add(e, RACE_TIME, snew - s);
505  l = GameRules_scoring_add_team(e, RACE_LAPS, 1);
506 
507  if(autocvar_fraglimit)
508  if(l >= autocvar_fraglimit)
510 
511  if(race_completing)
512  {
513  CS(e).race_completed = 1;
515  Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_RACE_FINISHED, e.netname);
516  ClientData_Touch(e);
517  }
518  }
519  }
520 
522  {
523  float recordtime;
524  string recordholder;
525 
526  if(tvalid)
527  {
528  recordtime = race_checkpoint_records[cp];
529  float myrecordtime = e.race_checkpoint_record[cp];
530  recordholder = strcat1(race_checkpoint_recordholders[cp]); // make a tempstring copy, as we'll possibly strunzone it!
531  if(recordholder == e.netname)
532  recordholder = "";
533 
534  if(t != 0)
535  {
536  if(cp == race_timed_checkpoint)
537  {
538  race_setTime(GetMapname(), t, e.crypto_idfp, e.netname, e, true);
539  MUTATOR_CALLHOOK(Race_FinalCheckpoint, e);
540  }
541  if(t < myrecordtime || myrecordtime == 0)
542  e.race_checkpoint_record[cp] = t; // resending done below
543 
544  if(t < recordtime || recordtime == 0)
545  {
546  race_checkpoint_records[cp] = t;
547  strcpy(race_checkpoint_recordholders[cp], e.netname);
549  FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && it.race_checkpoint == cp, { race_SendNextCheckpoint(it, 0); });
550  }
551 
552  }
553  }
554  else
555  {
556  // dummies
557  t = 0;
558  recordtime = 0;
559  recordholder = "";
560  }
561 
562  if(IS_REAL_CLIENT(e))
563  {
565  {
567  {
568  if(it == e || (IS_SPEC(it) && it.enemy == e))
569  {
570  msg_entity = it;
571  WriteHeader(MSG_ONE, TE_CSQC_RACE);
572  WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_HIT_QUALIFYING);
573  WriteByte(MSG_ONE, race_CheckpointNetworkID(cp)); // checkpoint the player now is at
574  WriteInt24_t(MSG_ONE, t); // time to that intermediate
575  WriteInt24_t(MSG_ONE, recordtime); // previously best time
576  WriteInt24_t(MSG_ONE, ((tvalid) ? it.race_checkpoint_record[cp] : 0)); // previously best time
577  WriteString(MSG_ONE, recordholder); // record holder
578  }
579  });
580  }
581  }
582  }
583  else // RACE! Not Qualifying
584  {
585  float mylaps, lother, othtime;
587  if(oth)
588  {
589  mylaps = GameRules_scoring_add(e, RACE_LAPS, 0);
590  lother = race_checkpoint_lastlaps[cp];
591  othtime = race_checkpoint_lasttimes[cp];
592  }
593  else
594  mylaps = lother = othtime = 0;
595 
596  if(IS_REAL_CLIENT(e))
597  {
598  msg_entity = e;
599  WRITESPECTATABLE_MSG_ONE(msg_entity, {
600  WriteHeader(MSG_ONE, TE_CSQC_RACE);
602  WriteByte(MSG_ONE, race_CheckpointNetworkID(cp)); // checkpoint the player now is at
603  if(e == oth)
604  {
605  WriteInt24_t(MSG_ONE, 0);
606  WriteByte(MSG_ONE, 0);
607  WriteByte(MSG_ONE, 0);
608  }
609  else
610  {
611  WriteInt24_t(MSG_ONE, TIME_ENCODE(time - race_checkpoint_lasttimes[cp]));
612  WriteByte(MSG_ONE, mylaps - lother);
613  WriteByte(MSG_ONE, etof(oth)); // record holder
614  }
615  });
616  }
617 
620  race_checkpoint_lastlaps[cp] = mylaps;
621 
622  if(IS_REAL_CLIENT(oth))
623  {
624  msg_entity = oth;
625  WRITESPECTATABLE_MSG_ONE(msg_entity, {
626  WriteHeader(MSG_ONE, TE_CSQC_RACE);
628  WriteByte(MSG_ONE, race_CheckpointNetworkID(cp)); // checkpoint the player now is at
629  if(e == oth)
630  {
631  WriteInt24_t(MSG_ONE, 0);
632  WriteByte(MSG_ONE, 0);
633  WriteByte(MSG_ONE, 0);
634  }
635  else
636  {
637  WriteInt24_t(MSG_ONE, TIME_ENCODE(time - othtime));
638  WriteByte(MSG_ONE, lother - mylaps);
639  WriteByte(MSG_ONE, etof(e) - 1); // record holder
640  }
641  });
642  }
643  }
644 }
645 
647 {
648  e.race_checkpoint = 0;
649  e.race_laptime = 0;
650  e.race_movetime = e.race_movetime_frac = e.race_movetime_count = 0;
651  e.race_penalty_accumulator = 0;
652  e.race_lastpenalty = NULL;
653 
654  if(!IS_REAL_CLIENT(e))
655  return;
656 
657  msg_entity = e;
658  WRITESPECTATABLE_MSG_ONE(msg_entity, {
659  WriteHeader(MSG_ONE, TE_CSQC_RACE);
661  });
662 }
663 
664 void checkpoint_passed(entity this, entity player)
665 {
666  if(IS_VEHICLE(player) && player.owner)
667  player = player.owner;
668 
669  if(player.personal && autocvar_g_allow_checkpoints)
670  return; // practice mode!
671 
672  if(player.classname == "porto")
673  {
674  // do not allow portalling through checkpoints
675  trace_plane_normal = normalize(-1 * player.velocity);
676  W_Porto_Fail(player, 0);
677  return;
678  }
679 
680  string oldmsg; // used twice
681 
682  /*
683  * Trigger targets
684  */
685  if (!((this.spawnflags & 2) && (IS_PLAYER(player))))
686  {
687  oldmsg = this.message;
688  this.message = "";
689  SUB_UseTargets(this, player, player);
690  this.message = oldmsg;
691  }
692 
693  if (!IS_PLAYER(player))
694  return;
695 
696  /*
697  * Remove unauthorized equipment
698  */
699  Portal_ClearAll(player);
700 
701  player.porto_forbidden = 2; // decreased by 1 each StartFrame
702 
703  if(defrag_ents)
704  {
705  if(this.race_checkpoint == -2)
706  {
707  this.race_checkpoint = player.race_checkpoint;
708  }
709 
710  int cp_amount = 0, largest_cp_id = 0;
711  IL_EACH(g_race_targets, it.classname == "target_checkpoint",
712  {
713  cp_amount += 1;
714  if(it.race_checkpoint > largest_cp_id) // update the finish id if someone hit a new checkpoint
715  {
716  if(!largest_cp_id)
717  {
718  IL_EACH(g_race_targets, it.classname == "target_checkpoint",
719  {
720  if(it.race_checkpoint == -2) // set defragcpexists to -1 so that the cp id file will be rewritten when someone finishes
721  defragcpexists = -1;
722  });
723  }
724 
725  largest_cp_id = it.race_checkpoint;
726  IL_EACH(g_race_targets, it.classname == "target_stopTimer",
727  {
728  it.race_checkpoint = largest_cp_id + 1; // finish line
729  });
730  race_highest_checkpoint = largest_cp_id + 1;
731  race_timed_checkpoint = largest_cp_id + 1;
732  }
733  });
734 
735  if(!cp_amount)
736  {
737  IL_EACH(g_race_targets, it.classname == "target_stopTimer",
738  {
739  it.race_checkpoint = 1;
740  });
743  }
744  }
745 
746  if((player.race_checkpoint == -1 && this.race_checkpoint == 0) || (player.race_checkpoint == this.race_checkpoint))
747  {
748  if(this.race_penalty)
749  {
750  if(player.race_lastpenalty != this)
751  {
752  player.race_lastpenalty = this;
754  }
755  }
756 
757  if(player.race_penalty)
758  return;
759 
760  /*
761  * Trigger targets
762  */
763  if(this.spawnflags & 2)
764  {
765  oldmsg = this.message;
766  this.message = "";
767  SUB_UseTargets(this, player, player); // TODO: should we be using other for the trigger here?
768  this.message = oldmsg;
769  }
770 
771  if(player.race_respawn_checkpoint != this.race_checkpoint || !player.race_started)
772  player.race_respawn_spotref = this; // this is not a spot but a CP, but spawnpoint selection will deal with that
773  player.race_respawn_checkpoint = this.race_checkpoint;
774  player.race_checkpoint = race_NextCheckpoint(this.race_checkpoint);
775  player.race_started = 1;
776 
777  race_SendTime(player, this.race_checkpoint, player.race_movetime, boolean(player.race_laptime));
778 
779  if(!this.race_checkpoint) // start line
780  {
781  player.race_laptime = time;
782  player.race_movetime = player.race_movetime_frac = player.race_movetime_count = 0;
783  player.race_penalty_accumulator = 0;
784  player.race_lastpenalty = NULL;
785  }
786 
788  race_SendNextCheckpoint(player, 0);
789 
790  if(defrag_ents && defragcpexists < 0 && this.classname == "target_stopTimer")
791  {
792  float fh;
793  defragcpexists = fh = fopen(strcat("maps/", GetMapname(), ".defragcp"), FILE_WRITE);
794  if(fh >= 0)
795  {
796  IL_EACH(g_race_targets, it.classname == "target_checkpoint",
797  {
798  fputs(fh, strcat(it.targetname, " ", ftos(it.race_checkpoint), "\n"));
799  });
800  }
801  fclose(fh);
802  }
803  }
804  else if(player.race_checkpoint == race_NextCheckpoint(this.race_checkpoint))
805  {
806  // ignored
807  }
808  else
809  {
810  if(this.spawnflags & 4)
811  Damage (player, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, player.origin, '0 0 0');
812  }
813 }
814 
815 void checkpoint_touch(entity this, entity toucher)
816 {
817  EXACTTRIGGER_TOUCH(this, toucher);
818  checkpoint_passed(this, toucher);
819 }
820 
821 void checkpoint_use(entity this, entity actor, entity trigger)
822 {
823  if(trigger.classname == "info_player_deathmatch") // a spawn, a spawn
824  return;
825 
826  checkpoint_passed(this, actor);
827 }
828 
830 {
831  entity own = this.owner;
832  if(this.realowner)
833  own = this.realowner; // target support
834 
835  if(view.race_checkpoint == -1 || own.race_checkpoint == -2)
836  return true;
837  else if(view.race_checkpoint == own.race_checkpoint)
838  return true;
839  else
840  return false;
841 }
842 
843 void defrag_waypointsprites(entity targeted, entity checkpoint)
844 {
845  // bones_was_here: spawn a waypoint for every entity with a bmodel
846  // that directly or indirectly targets this checkpoint
847  // (anything a player could touch or shoot to activate this cp)
848 
849  entity s = WP_RaceCheckpoint;
850  if (checkpoint.classname == "target_startTimer")
851  s = WP_RaceStart;
852  else if (checkpoint.classname == "target_stopTimer")
853  s = WP_RaceFinish;
854 
855  for (entity t = findchain(target, targeted.targetname); t; t = t.chain)
856  {
857  if (t.modelindex)
858  {
859  WaypointSprite_SpawnFixed(s, (t.absmin + t.absmax) * 0.5, t, sprite, RADARICON_NONE);
860  t.sprite.realowner = checkpoint;
861  t.sprite.waypointsprite_visible_for_player = race_waypointsprite_visible_for_player;
862  }
863 
864  if (t.targetname)
865  defrag_waypointsprites(t, checkpoint);
866  }
867 }
868 
870 {
871  static bool have_verified;
872  if (have_verified) return;
873  have_verified = true;
874 
875  bool qual = g_race_qualifying;
876 
877  int pl_race_checkpoint = 0;
878  int pl_race_place = 0;
879 
880  if (g_race) {
881  for (int i = 0; i <= race_highest_checkpoint; ++i) {
882  pl_race_checkpoint = race_NextCheckpoint(i);
883 
884  // race only (middle of the race)
885  g_race_qualifying = false;
886  pl_race_place = 0;
887  if (!Spawn_FilterOutBadSpots(this, findchain(classname, "info_player_deathmatch"), 0, false, true)) {
888  error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(pl_race_place), " (used for respawning in race) - bailing out"));
889  }
890 
891  if (i == 0) {
892  // qualifying only
893  g_race_qualifying = 1;
894  pl_race_place = race_lowest_place_spawn;
895  if (!Spawn_FilterOutBadSpots(this, findchain(classname, "info_player_deathmatch"), 0, false, true)) {
896  error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(pl_race_place), " (used for qualifying) - bailing out"));
897  }
898 
899  // race only (initial spawn)
900  g_race_qualifying = 0;
901  for (int p = 1; p <= race_highest_place_spawn; ++p) {
902  pl_race_place = p;
903  if (!Spawn_FilterOutBadSpots(this, findchain(classname, "info_player_deathmatch"), 0, false, true)) {
904  error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(pl_race_place), " (used for initially spawning in race) - bailing out"));
905  }
906  }
907  }
908  }
909  } else if (!defrag_ents) {
910  // qualifying only
911  pl_race_checkpoint = race_NextCheckpoint(0);
912  g_race_qualifying = 1;
913  pl_race_place = race_lowest_place_spawn;
914  if (!Spawn_FilterOutBadSpots(this, findchain(classname, "info_player_deathmatch"), 0, false, true)) {
915  error(strcat("Checkpoint 0 misses a spawnpoint with race_place==", ftos(pl_race_place), " (used for qualifying) - bailing out"));
916  }
917  } else {
918  pl_race_checkpoint = race_NextCheckpoint(0);
919  g_race_qualifying = 1;
920  pl_race_place = 0; // there's only one spawn on defrag maps
921 
922  // check if a defragcp file already exists, then read it and apply the checkpoint order
923  float fh;
924  float len;
925  string l;
926 
927  defragcpexists = fh = fopen(strcat("maps/", GetMapname(), ".defragcp"), FILE_READ);
928  if (fh >= 0) {
929  while ((l = fgets(fh))) {
930  len = tokenize_console(l);
931  if (len != 2) {
932  defragcpexists = -1; // something's wrong in the defrag cp file, set defragcpexists to -1 so that it will be rewritten when someone finishes
933  continue;
934  }
935  for (entity cp = NULL; (cp = find(cp, classname, "target_checkpoint"));) {
936  if (argv(0) == cp.targetname) {
937  cp.race_checkpoint = stof(argv(1));
938  }
939  }
940  }
941  fclose(fh);
942  }
943  }
944 
945  g_race_qualifying = qual;
946 
947  if (race_timed_checkpoint) {
948  if (defrag_ents) {
949  IL_EACH(g_race_targets, it.classname == "target_checkpoint" || it.classname == "target_startTimer" || it.classname == "target_stopTimer",
950  {
951  defrag_waypointsprites(it, it);
952 
953  if(it.classname == "target_checkpoint") {
954  if(it.race_checkpoint == -2)
955  defragcpexists = -1; // 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
956  }
957  });
958  if (defragcpexists != -1) {
959  float largest_cp_id = 0;
960  for (entity cp = NULL; (cp = find(cp, classname, "target_checkpoint"));) {
961  if (cp.race_checkpoint > largest_cp_id) {
962  largest_cp_id = cp.race_checkpoint;
963  }
964  }
965  for (entity cp = NULL; (cp = find(cp, classname, "target_stopTimer"));) {
966  cp.race_checkpoint = largest_cp_id + 1; // finish line
967  }
968  race_highest_checkpoint = largest_cp_id + 1;
969  race_timed_checkpoint = largest_cp_id + 1;
970  } else {
971  for (entity cp = NULL; (cp = find(cp, classname, "target_stopTimer"));) {
972  cp.race_checkpoint = 255; // finish line
973  }
975  race_timed_checkpoint = 255;
976  }
977  } else {
978  IL_EACH(g_racecheckpoints, it.sprite,
979  {
980  if (it.race_checkpoint == 0) {
981  WaypointSprite_UpdateSprites(it.sprite, WP_RaceStart, WP_Null, WP_Null);
982  } else if (it.race_checkpoint == race_timed_checkpoint) {
983  WaypointSprite_UpdateSprites(it.sprite, WP_RaceFinish, WP_Null, WP_Null);
984  }
985  });
986  }
987  }
988 
989  if (defrag_ents) { /* The following hack shall be removed when per-player trigger_multiple.wait is implemented for cts */
990  for (entity trigger = NULL; (trigger = find(trigger, classname, "trigger_multiple")); ) {
991  for (entity targ = NULL; (targ = find(targ, targetname, trigger.target)); ) {
992  if (targ.classname == "target_checkpoint" || targ.classname == "target_startTimer" || targ.classname == "target_stopTimer") {
993  trigger.wait = 0;
994  trigger.delay = 0;
995  targ.wait = 0;
996  targ.delay = 0;
997 
998  // These just make the game crash on some maps with oddly shaped triggers.
999  // (on the other hand they used to fix the case when two players ran through a checkpoint at once,
1000  // and often one of them just passed through without being registered. Hope it's fixed in a better way now.
1001  // (happened on item triggers too)
1002  //
1003  //targ.wait = -2;
1004  //targ.delay = 0;
1005 
1006  //setsize(targ, trigger.mins, trigger.maxs);
1007  //setorigin(targ, trigger.origin);
1008  //remove(trigger);
1009  }
1010  }
1011  }
1012  }
1013 }
1014 
1016 {
1017  if(g_race_qualifying)
1018  {
1019  // spawn at first
1020  if(this.race_checkpoint != 0)
1021  return '-1 0 0';
1022  if(spot.race_place != race_lowest_place_spawn)
1023  return '-1 0 0';
1024  }
1025  else
1026  {
1027  if(this.race_checkpoint != player.race_respawn_checkpoint)
1028  return '-1 0 0';
1029  // try reusing the previous spawn
1030  if(this == player.race_respawn_spotref || spot == player.race_respawn_spotref)
1031  current.x += SPAWN_PRIO_RACE_PREVIOUS_SPAWN;
1032  if(this.race_checkpoint == 0)
1033  {
1034  int pl = player.race_place;
1035  if(pl > race_highest_place_spawn)
1036  pl = 0;
1037  if(pl == 0 && !player.race_started)
1038  pl = race_highest_place_spawn; // use last place if he has not even touched finish yet
1039  if(spot.race_place != pl)
1040  return '-1 0 0';
1041  }
1042  }
1043  return current;
1044 }
1045 
1046 spawnfunc(trigger_race_checkpoint)
1047 {
1048  vector o;
1049  if(!g_race && !g_cts) { delete(this); return; }
1050 
1052 
1053  this.use = checkpoint_use;
1054  if (!(this.spawnflags & 1))
1055  settouch(this, checkpoint_touch);
1056 
1057  o = (this.absmin + this.absmax) * 0.5;
1058  tracebox(o, PL_MIN_CONST, PL_MAX_CONST, o - '0 0 1' * (o.z - this.absmin.z), MOVE_NORMAL, this);
1060  this.nearestwaypointtimeout = -1;
1061 
1062  if(this.message == "")
1063  this.message = "went backwards";
1064  if (this.message2 == "")
1065  this.message2 = "was pushed backwards by";
1066  if (this.race_penalty_reason == "")
1067  this.race_penalty_reason = "missing a checkpoint";
1068 
1069  this.race_checkpoint = this.cnt;
1070 
1072  {
1074  if(this.spawnflags & 8)
1076  else
1078  }
1079 
1080  if(!this.race_penalty)
1081  {
1082  if(this.race_checkpoint)
1083  WaypointSprite_SpawnFixed(WP_RaceCheckpoint, o, this, sprite, RADARICON_NONE);
1084  else
1085  WaypointSprite_SpawnFixed(WP_RaceStartFinish, o, this, sprite, RADARICON_NONE);
1086  }
1087 
1088  this.sprite.waypointsprite_visible_for_player = race_waypointsprite_visible_for_player;
1090 
1091  if (!g_racecheckpoints)
1092  g_racecheckpoints = IL_NEW();
1093  IL_PUSH(g_racecheckpoints, this);
1094 
1095  // trigger_race_checkpoint_verify checks this list too
1096  if (!g_race_targets)
1097  g_race_targets = IL_NEW();
1098 
1099  InitializeEntity(this, trigger_race_checkpoint_verify, INITPRIO_FINDTARGET);
1100 }
1101 
1103 {
1104  if(!g_race && !g_cts) { delete(this); return; }
1105  defrag_ents = 1;
1106 
1107  // if this is targeted, then it probably isn't a trigger
1108  bool is_trigger = this.targetname == "";
1109 
1110  if(is_trigger)
1112 
1113  this.use = checkpoint_use;
1114  if (is_trigger && !(this.spawnflags & 1))
1115  settouch(this, checkpoint_touch);
1116 
1117  vector org = this.origin;
1118 
1119  // bots should only pathfind to this if it is a valid touchable trigger
1120  if(is_trigger)
1121  {
1122  org = (this.absmin + this.absmax) * 0.5;
1123  tracebox(org, PL_MIN_CONST, PL_MAX_CONST, org - '0 0 1' * (org.z - this.absmin.z), MOVE_NORMAL, this);
1125  this.nearestwaypointtimeout = -1;
1126  }
1127 
1128  if(this.message == "")
1129  this.message = "went backwards";
1130  if (this.message2 == "")
1131  this.message2 = "was pushed backwards by";
1132  if (this.race_penalty_reason == "")
1133  this.race_penalty_reason = "missing a checkpoint";
1134 
1135  if(this.classname == "target_startTimer")
1136  this.race_checkpoint = 0;
1137  else
1138  this.race_checkpoint = -2;
1139 
1141 
1142  if (!g_race_targets)
1143  g_race_targets = IL_NEW();
1144  IL_PUSH(g_race_targets, this);
1145 
1146  // trigger_race_checkpoint_verify checks this list too
1147  if (!g_racecheckpoints)
1148  g_racecheckpoints = IL_NEW();
1149 
1150  InitializeEntity(this, trigger_race_checkpoint_verify, INITPRIO_FINDTARGET);
1151 }
1152 
1153 spawnfunc(target_checkpoint)
1154 {
1155  // xonotic defrag entity
1157 }
1158 
1159 // compatibility entity names
1160 spawnfunc(target_startTimer) { target_checkpoint_setup(this); }
1161 spawnfunc(target_stopTimer) { target_checkpoint_setup(this); }
1162 
1164 {
1165  if(race_completing && !CS(p).race_completed)
1166  {
1167  CS(p).race_completed = 1;
1169  Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_RACE_ABANDONED, p.netname);
1170  ClientData_Touch(p);
1171  }
1172 }
1173 
1175 {
1176  race_completing = 1;
1177  FOREACH_CLIENT(IS_PLAYER(it) && IS_DEAD(it), { race_AbandonRaceCheck(it); });
1178 }
1179 
1181 {
1182  race_ClearTime(this);
1183  this.race_place = 0;
1184  this.race_started = 0;
1185  this.race_respawn_checkpoint = 0;
1186  this.race_respawn_spotref = NULL;
1187 }
1188 
1190 {
1191  if(!g_race && !g_cts)
1192  return;
1194  race_ClearTime(this);
1196 }
1197 
1198 spawnfunc(info_player_race)
1199 {
1200  if(!g_race && !g_cts) { delete(this); return; }
1201  ++race_spawns;
1202  spawnfunc_info_player_deathmatch(this);
1203 
1208 }
1209 
1211 {
1212  for(int j = 0; j < MAX_CHECKPOINTS; ++j)
1213  {
1214  race_checkpoint_records[j] = 0;
1216  }
1217 
1218  FOREACH_CLIENT(true, {
1219  float p = it.race_place;
1220  race_PreparePlayer(it);
1221  it.race_place = p;
1222  });
1223 }
1224 
1225 void race_ImposePenaltyTime(entity pl, float penalty, string reason)
1226 {
1227  if(g_race_qualifying)
1228  {
1229  pl.race_penalty_accumulator += penalty;
1230  if(IS_REAL_CLIENT(pl))
1231  {
1232  msg_entity = pl;
1233  WRITESPECTATABLE_MSG_ONE(msg_entity, {
1234  WriteHeader(MSG_ONE, TE_CSQC_RACE);
1236  WriteShort(MSG_ONE, TIME_ENCODE(penalty));
1237  WriteString(MSG_ONE, reason);
1238  });
1239  }
1240  }
1241  else
1242  {
1243  pl.race_penalty = time + penalty;
1244  if(IS_REAL_CLIENT(pl))
1245  {
1246  msg_entity = pl;
1247  WRITESPECTATABLE_MSG_ONE(msg_entity, {
1248  WriteHeader(MSG_ONE, TE_CSQC_RACE);
1250  WriteShort(MSG_ONE, TIME_ENCODE(penalty));
1251  WriteString(MSG_ONE, reason);
1252  });
1253  }
1254  }
1255 }
1256 
1257 void penalty_touch(entity this, entity toucher)
1258 {
1259  EXACTTRIGGER_TOUCH(this, toucher);
1260  if(toucher.race_lastpenalty != this)
1261  {
1262  toucher.race_lastpenalty = this;
1264  }
1265 }
1266 
1267 void penalty_use(entity this, entity actor, entity trigger)
1268 {
1270 }
1271 
1272 spawnfunc(trigger_race_penalty)
1273 {
1274  // TODO: find out why this wasnt done:
1275  //if(!g_cts && !g_race) { remove(this); return; }
1276 
1278 
1279  this.use = penalty_use;
1280  if (!(this.spawnflags & 1))
1281  settouch(this, penalty_touch);
1282 
1283  if (this.race_penalty_reason == "")
1284  this.race_penalty_reason = "missing a checkpoint";
1285  if (!this.race_penalty)
1286  this.race_penalty = 5;
1287 }
1288 
1290 {
1291  // interesting metrics (idea by KrimZon) to maybe sort players in the
1292  // scoreboard, immediately updates when overtaking
1293  //
1294  // requires the track to be built so you never get farther away from the
1295  // next checkpoint, though, and current Xonotic race maps are not built that
1296  // way
1297  //
1298  // also, this code is slow and would need optimization (i.e. "next CP"
1299  // links on CP entities)
1300 
1301  float l;
1302  l = GameRules_scoring_add(e, RACE_LAPS, 0);
1303  if(CS(e).race_completed)
1304  return l; // not fractional
1305 
1306  vector o0, o1;
1307  float bestfraction, fraction;
1308  entity lastcp;
1309  float nextcpindex, lastcpindex;
1310 
1311  nextcpindex = max(e.race_checkpoint, 0);
1312  lastcpindex = e.race_respawn_checkpoint;
1313  lastcp = e.race_respawn_spotref;
1314 
1315  if(nextcpindex == lastcpindex)
1316  return l; // finish
1317 
1318  bestfraction = 1;
1319  IL_EACH(g_racecheckpoints, true,
1320  {
1321  if(it.race_checkpoint != lastcpindex)
1322  continue;
1323  if(lastcp)
1324  if(it != lastcp)
1325  continue;
1326  o0 = (it.absmin + it.absmax) * 0.5;
1327  IL_EACH(g_racecheckpoints, true,
1328  {
1329  if(it.race_checkpoint != nextcpindex)
1330  continue;
1331  o1 = (it.absmin + it.absmax) * 0.5;
1332  if(o0 == o1)
1333  continue;
1334  fraction = bound(0.0001, vlen(e.origin - o1) / vlen(o0 - o1), 1);
1335  if(fraction < bestfraction)
1336  bestfraction = fraction;
1337  });
1338  });
1339 
1340  // we are at CP "nextcpindex - bestfraction"
1341  // race_timed_checkpoint == 4: then nextcp==4 means 0.9999x, nextcp==0 means 0.0000x
1342  // race_timed_checkpoint == 0: then nextcp==0 means 0.9999x
1343  float c, nc;
1344  nc = race_highest_checkpoint + 1;
1345  c = ((nextcpindex - race_timed_checkpoint + nc + nc - 1) % nc) + 1 - bestfraction;
1346 
1347  return l + c / nc;
1348 }
void race_SendAll(entity player, bool only_rankings)
Definition: race.qc:324
float race_PreviousCheckpoint(float f)
Definition: race.qc:190
float nearestwaypointtimeout
Definition: api.qh:53
#define IL_EACH(this, cond, body)
void race_StartCompleting()
Definition: race.qc:1174
ERASEABLE void db_put(int db, string key, string value)
Definition: map.qh:101
void race_SendNextCheckpoint(entity e, float spec)
Definition: race.qc:216
string speedaward_uid
Definition: race.qh:59
vector trigger_race_checkpoint_spawn_evalfunc(entity this, entity player, entity spot, vector current)
Definition: race.qc:1015
float race_checkpoint_record[MAX_CHECKPOINTS]
Definition: race.qc:174
float race_place
Definition: race.qh:23
void race_ClearRecords()
Definition: race.qc:1210
#define EXACTTRIGGER_INIT
Definition: common.qh:117
string getrankings()
Definition: getreplies.qc:46
entity race_checkpoint_lastplayers[MAX_CHECKPOINTS]
Definition: race.qc:172
void race_deleteTime(string map, float pos)
Definition: race.qc:452
void race_writeTime(string map, float t, string myuid)
Definition: race.qc:100
float race_timed_checkpoint
Definition: race.qc:177
int autocvar_g_cts_send_rankings_cnt
Definition: race.qh:10
float race_readPos(string map, float t)
Definition: race.qc:88
string uid2name(string myuid)
Definition: race.qc:36
string GetMapname()
Definition: intermission.qc:18
#define EXACTTRIGGER_TOUCH(e, t)
Definition: common.qh:116
#define IL_NEW()
spawn_evalfunc_t spawn_evalfunc
Definition: spawnpoints.qh:28
const int RACE_NET_CHECKPOINT_NEXT_QUALIFYING
Definition: net_linked.qh:12
float ServerProgsDB
Definition: world.qh:131
entity() spawn
float race_spawns
Definition: race.qh:17
float speedaward_lastupdate
Definition: race.qh:15
const float MOVE_NORMAL
Definition: csprogsdefs.qc:252
ClientState CS(Client this)
Definition: state.qh:47
#define FOREACH_CLIENT(cond, body)
Definition: utils.qh:49
string speedaward_alltimebest_uid
Definition: race.qh:63
string speedaward_alltimebest_holder
Definition: race.qh:62
#define GameRules_scoring_add(client, fld, value)
Definition: sv_rules.qh:78
string race_readUID(string map, float pos)
Definition: race.qc:83
#define IS_OBSERVER(v)
Definition: utils.qh:11
#define CS_CVAR(this)
Definition: state.qh:51
void race_checkAndWriteName(entity player)
Definition: race.qc:143
const int RACE_NET_CHECKPOINT_NEXT_SPEC_QUALIFYING
Definition: net_linked.qh:15
float race_highest_checkpoint
Definition: race.qc:176
void SUB_UseTargets(entity this, entity actor, entity trigger)
Definition: triggers.qc:366
const float FILE_READ
Definition: csprogsdefs.qc:231
void defrag_waypointsprites(entity targeted, entity checkpoint)
Definition: race.qc:843
void penalty_use(entity this, entity actor, entity trigger)
Definition: race.qc:1267
void checkpoint_use(entity this, entity actor, entity trigger)
Definition: race.qc:821
limitations: NULL cannot be present elements can only be present once a maximum of IL_MAX lists can e...
origin
Definition: ent_cs.qc:114
float race_CheckpointNetworkID(float f)
Definition: race.qc:204
const int RACE_NET_SPEED_AWARD
Definition: net_linked.qh:19
#define MAKE_INDEPENDENT_PLAYER(e)
Definition: client.qh:315
string classname
Definition: csprogsdefs.qc:107
float race_completing
Definition: race.qh:27
float race_NextCheckpoint(float f)
Definition: race.qc:182
bool g_race_qualifying
Definition: race.qh:12
#define TIME_ENCODED_TOSTRING(n)
Definition: util.qh:57
float spawnflags
Definition: progsdefs.qc:191
#define DMG_NOWEP
Definition: damage.qh:126
void race_send_speedaward_alltimebest(float msg)
Definition: race.qc:268
entity owner
Definition: main.qh:73
void write_recordmarker(entity pl, float tstart, float dt)
Definition: race.qc:57
const int RACE_NET_SERVER_RECORD
Definition: net_linked.qh:18
#define TIME_DECODE(n)
Definition: util.qh:62
const int RACE_NET_CHECKPOINT_HIT_RACE_BY_OPPONENT
Definition: net_linked.qh:14
void race_ImposePenaltyTime(entity pl, float penalty, string reason)
Definition: race.qc:1225
void race_send_speedaward(float msg)
Definition: race.qc:259
#define IS_REAL_CLIENT(v)
Definition: utils.qh:17
#define strcpy(this, s)
Definition: string.qh:49
entity race_respawn_spotref
Definition: race.qh:34
void race_SendStatus(float id, entity e)
Definition: race.qc:346
vector absmax
Definition: csprogsdefs.qc:92
void race_SendTime(entity e, float cp, float t, float tvalid)
Definition: race.qc:476
const int RACE_NET_CHECKPOINT_HIT_RACE
Definition: net_linked.qh:13
string race_checkpoint_recordholders[MAX_CHECKPOINTS]
Definition: race.qc:169
#define TIME_ENCODE(t)
Definition: util.qh:61
string stored_netname
Definition: race.qc:34
entity race_lastpenalty
Definition: race.qc:164
#define IS_SPEC(v)
Definition: utils.qh:10
float race_checkpoint
Definition: race.qc:163
void penalty_touch(entity this, entity toucher)
Definition: race.qc:1257
const int RACE_NET_RANKINGS_CNT
Definition: net_linked.qh:25
void target_checkpoint_setup(entity this)
Definition: race.qc:1102
entity msg_entity
Definition: progsdefs.qc:63
float cnt
Definition: powerups.qc:24
#define g_cts
Definition: cts.qh:36
#define g_race
Definition: race.qh:46
IntrusiveList g_race_targets
Definition: race.qc:68
ERASEABLE entity IL_PUSH(IntrusiveList this, entity it)
Push to tail.
spawnfunc(trigger_race_checkpoint)
Definition: race.qc:1046
const int RACE_NET_SERVER_RANKINGS
Definition: net_linked.qh:21
void Portal_ClearAll(entity own)
Definition: portals.qc:587
float speedaward_alltimebest
Definition: race.qh:61
void waypoint_spawnforitem_force(entity e, vector org)
Definition: waypoints.qc:1978
entity sprite
Definition: race.qc:166
string record_type
Definition: world.qh:49
spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 f1 s1 strcat(_("Level %s: "), "^BG%s\3\, _("^BGPress ^F2%s^BG to enter the game"))
void checkpoint_passed(entity this, entity player)
Definition: race.qc:664
IntrusiveList g_racecheckpoints
Definition: race.qc:69
#define GameRules_scoring_add_team(client, fld, value)
Definition: sv_rules.qh:80
bool autocvar_g_allow_checkpoints
Definition: race.qh:3
void race_send_recordtime(float msg)
Definition: race.qc:251
float race_readTime(string map, float pos)
Definition: race.qc:78
void race_SpeedAwardFrame(entity player)
Definition: race.qc:296
bool race_waypointsprite_visible_for_player(entity this, entity player, entity view)
Definition: race.qc:829
void race_PreparePlayer(entity this)
Definition: race.qc:1180
string message
Definition: powerups.qc:19
#define NULL
Definition: post.qh:17
void checkpoint_touch(entity this, entity toucher)
Definition: race.qc:815
void trigger_race_checkpoint_verify(entity this)
Definition: race.qc:869
float defragcpexists
Definition: race.qc:180
const int RACE_NET_PENALTY_QUALIFYING
Definition: net_linked.qh:17
float race_checkpoint_records[MAX_CHECKPOINTS]
Definition: race.qc:168
const int RACE_NET_SPEED_AWARD_BEST
Definition: net_linked.qh:20
const int RACE_NET_SERVER_STATUS
Definition: net_linked.qh:22
vector trace_endpos
Definition: csprogsdefs.qc:37
void GameLogEcho(string s)
Definition: gamelog.qc:12
ERASEABLE string db_get(int db, string key)
Definition: map.qh:91
void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype,.entity weaponentity, vector hitloc, vector force)
Definition: damage.qc:583
#define IS_DEAD(s)
Definition: utils.qh:26
#define stuffcmd(cl,...)
Definition: progsdefs.qh:23
vector(float skel, float bonenum) _skel_get_boneabs_hidden
#define IS_VEHICLE(v)
Definition: utils.qh:22
#define tokenize_console
Definition: dpextensions.qh:24
float race_checkpoint_lasttimes[MAX_CHECKPOINTS]
Definition: race.qc:170
const float FILE_WRITE
Definition: csprogsdefs.qc:233
entity Spawn_FilterOutBadSpots(entity this, entity firstspot, float mindist, float teamcheck, bool targetcheck)
Definition: spawnpoints.qc:281
void InitializeEntity(entity e, void(entity this) func, int order)
Definition: world.qc:2146
string race_readName(string map, float pos)
Definition: race.qc:138
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition: vector.qh:8
float race_completed
Definition: race.qh:25
void ClientData_Touch(entity e)
Definition: client.qc:171
float race_started
Definition: race.qh:24
float speedaward_speed
Definition: race.qh:57
float race_respawn_checkpoint
Definition: race.qh:33
string GetGametype()
Definition: intermission.qc:13
void race_InitSpectator()
Definition: race.qc:71
const int RACE_NET_PENALTY_RACE
Definition: net_linked.qh:16
float race_highest_place_spawn
Definition: race.qh:18
string targetname
Definition: progsdefs.qc:194
entity realowner
Definition: common.qh:25
#define MUTATOR_CALLHOOK(id,...)
Definition: base.qh:140
void race_ClearTime(entity e)
Definition: race.qc:646
float race_penalty_accumulator
Definition: race.qc:161
void race_RetractPlayer(entity this)
Definition: race.qc:1189
float speedaward_lastsent
Definition: race.qh:14
vector trace_plane_normal
Definition: csprogsdefs.qc:38
bool intermission_running
Definition: intermission.qh:9
#define strfree(this)
Definition: string.qh:56
#define use
Definition: csprogsdefs.qh:50
float race_checkpoint_lastlaps[MAX_CHECKPOINTS]
Definition: race.qc:171
const float MAX_CHECKPOINTS
Definition: race.qc:158
string target
Definition: progsdefs.qc:193
vector absmin
Definition: csprogsdefs.qc:92
float race_GetFractionalLapCount(entity e)
Definition: race.qc:1289
string race_penalty_reason
Definition: race.qc:162
if(IS_DEAD(this))
Definition: impulse.qc:92
float time
Definition: csprogsdefs.qc:16
#define db_remove(db, key)
Definition: map.qh:98
void race_SendRanking(float pos, float prevpos, float del, float msg)
Definition: race.qc:285
string speedaward_holder
Definition: race.qh:58
void race_setTime(string map, float t, string myuid, string mynetname, entity e, bool showmessage)
Definition: race.qc:365
void race_send_rankings_cnt(float msg)
Definition: race.qc:277
const int RACE_NET_CHECKPOINT_CLEAR
Definition: net_linked.qh:11
const int SPAWN_PRIO_RACE_PREVIOUS_SPAWN
Definition: spawnpoints.qh:12
#define IS_PLAYER(v)
Definition: utils.qh:9
float race_lowest_place_spawn
Definition: race.qh:19
void race_AbandonRaceCheck(entity p)
Definition: race.qc:1163
float defrag_ents
Definition: race.qc:179
float race_penalty
Definition: race.qc:160