]> de.git.xonotic.org Git - voretournament/voretournament.git/blob - data/qcsrc/server/race.qc
Include gmqcc binaries for Windows and Linux
[voretournament/voretournament.git] / data / qcsrc / server / race.qc
1 #define MAX_CHECKPOINTS 255\r
2 \r
3 void spawnfunc_target_checkpoint();\r
4 \r
5 .float race_penalty;\r
6 .float race_penalty_accumulator;\r
7 .string race_penalty_reason;\r
8 .float race_checkpoint; // player: next checkpoint that has to be reached\r
9 .float race_laptime;\r
10 .entity race_lastpenalty;\r
11 \r
12 .entity sprite;\r
13 \r
14 float race_checkpoint_records[MAX_CHECKPOINTS];\r
15 string race_checkpoint_recordholders[MAX_CHECKPOINTS];\r
16 float race_checkpoint_lasttimes[MAX_CHECKPOINTS];\r
17 float race_checkpoint_lastlaps[MAX_CHECKPOINTS];\r
18 entity race_checkpoint_lastplayers[MAX_CHECKPOINTS];\r
19 \r
20 float race_highest_checkpoint;\r
21 float race_timed_checkpoint;\r
22 \r
23 float defrag_ents;\r
24 float defragcpexists;\r
25 \r
26 float race_NextCheckpoint(float f)\r
27 {\r
28         if(f >= race_highest_checkpoint)\r
29                 return 0;\r
30         else\r
31                 return f + 1;\r
32 }\r
33 \r
34 float race_PreviousCheckpoint(float f)\r
35 {\r
36         if(f == -1)\r
37                 return 0;\r
38         else if(f == 0)\r
39                 return race_highest_checkpoint;\r
40         else\r
41                 return f - 1;\r
42 }\r
43 \r
44 // encode as:\r
45 //   0 = common start/finish\r
46 // 254 = start\r
47 // 255 = finish\r
48 float race_CheckpointNetworkID(float f)\r
49 {\r
50         if(race_timed_checkpoint)\r
51         {\r
52                 if(f == 0)\r
53                         return 254; // start\r
54                 else if(f == race_timed_checkpoint)\r
55                         return 255; // finish\r
56         }\r
57         return f;\r
58 }\r
59 \r
60 void race_SendNextCheckpoint(entity e, float spec) // qualifying only\r
61 {\r
62         float recordtime;\r
63         string recordholder;\r
64         float cp;\r
65 \r
66         if(!e.race_laptime)\r
67                 return;\r
68 \r
69         cp = e.race_checkpoint;\r
70         recordtime = race_checkpoint_records[cp];\r
71         recordholder = race_checkpoint_recordholders[cp];\r
72         if(recordholder == e.netname)\r
73                 recordholder = "";\r
74 \r
75         if(!spec)\r
76                 msg_entity = e;\r
77         WRITESPECTATABLE_MSG_ONE({\r
78                 WriteByte(MSG_ONE, SVC_TEMPENTITY);\r
79                 WriteByte(MSG_ONE, TE_CSQC_RACE);\r
80                 if(spec)\r
81                 {\r
82                         WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_NEXT_SPEC_QUALIFYING);\r
83                         //WriteCoord(MSG_ONE, e.race_laptime - e.race_penalty_accumulator);\r
84                         WriteCoord(MSG_ONE, time - e.race_movetime - e.race_penalty_accumulator);\r
85                 }\r
86                 else\r
87                         WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_NEXT_QUALIFYING);\r
88                 WriteByte(MSG_ONE, race_CheckpointNetworkID(cp)); // checkpoint the player will be at next\r
89                 WriteInt24_t(MSG_ONE, recordtime);\r
90                 WriteString(MSG_ONE, recordholder);\r
91         });\r
92 }\r
93 \r
94 void race_InitSpectator()\r
95 {\r
96         if(g_race_qualifying)\r
97                 if(msg_entity.enemy.race_laptime)\r
98                         race_SendNextCheckpoint(msg_entity.enemy, 1);\r
99 }\r
100 \r
101 string rr;\r
102 float grecordtime[RANKINGS_CNT];\r
103 string grecordholder[RANKINGS_CNT];\r
104 #ifdef UID\r
105 string grecorduid[RANKINGS_CNT];\r
106 #endif\r
107 float worst_time; // last ranked time\r
108 float have_recs; // have we already read the records from the database before?\r
109 float race_GetTime(float pos) {\r
110         if(g_cts)\r
111                 rr = CTS_RECORD;\r
112         else\r
113                 rr = RACE_RECORD;\r
114 \r
115         if (!have_recs) { // I guess this is better than checking if the first array item is empty, rumor has it that arrays are slow\r
116                 float i;\r
117                 for(i=0;i<RANKINGS_CNT;++i) {\r
118                         grecordtime[i] = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "time", ftos(i))));\r
119                         grecordholder[i] = strzone(db_get(ServerProgsDB, strcat(GetMapname(), rr, "netname", ftos(i))));\r
120 #ifdef UID\r
121                         grecorduid[i] = strzone(db_get(ServerProgsDB, strcat(GetMapname(), rr, "uid", ftos(i))));\r
122 #endif\r
123                 }\r
124                 grecordtime[0] = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "time")));\r
125                 grecordholder[0] = strzone(db_get(ServerProgsDB, strcat(GetMapname(), rr, "netname")));\r
126 #ifdef UID\r
127                 grecorduid[0] = strzone(db_get(ServerProgsDB, strcat(GetMapname(), rr, "uid")));\r
128 #endif\r
129                 worst_time = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, strcat("time", ftos(RANKINGS_CNT-1)))));\r
130                 have_recs = 1;\r
131         }\r
132 \r
133         return grecordtime[pos-1];\r
134 }\r
135 \r
136 string race_GetName(float pos) { // these other functions assume that race_GetTime has been called >= once before\r
137         return grecordholder[pos-1];\r
138 }\r
139 \r
140 #ifdef UID\r
141 float race_CheckUID(string myuid, string net_name) { // return existing UID or player name ranking pos, else 0\r
142         float i;\r
143         for (i=RANKINGS_CNT-1;i>=0;--i)\r
144                 if(grecorduid[i] == myuid)\r
145                         return i+1;\r
146         for (i=RANKINGS_CNT-1;i>=0;--i)\r
147                 if(grecordholder[i] == net_name)\r
148                         return i+1;\r
149         return 0;\r
150 }\r
151 #endif\r
152 \r
153 float race_CheckName(string net_name) { // Does the name already exist in rankings? In that case, where? (otherwise 0)\r
154         float i;\r
155         for (i=RANKINGS_CNT-1;i>=0;--i)\r
156                 if(grecordholder[i] == net_name)\r
157                         return i+1;\r
158         return 0;\r
159 }\r
160 \r
161 float race_GetPos(float t) {\r
162         float i;\r
163 \r
164         if(worst_time == 0)\r
165         for (i=0;i<RANKINGS_CNT;++i)\r
166                 if (grecordtime[i] == 0 || grecordtime[i] > t)\r
167                         return i+1;\r
168 \r
169         for (i=0;i<RANKINGS_CNT;++i)\r
170                 if (grecordtime[i] > t)\r
171                         return i+1;\r
172         return 0;\r
173 }\r
174 \r
175 void race_send_recordtime(float msg)\r
176 {\r
177         // send the server best time\r
178         WriteByte(msg, SVC_TEMPENTITY);\r
179         WriteByte(msg, TE_CSQC_RACE);\r
180         WriteByte(msg, RACE_NET_SERVER_RECORD);\r
181         WriteInt24_t(msg, race_GetTime(1));\r
182 }\r
183 \r
184 void race_SendRankings(float pos, float prevpos, float del, float msg)\r
185 {\r
186         WriteByte(msg, SVC_TEMPENTITY);\r
187         WriteByte(msg, TE_CSQC_RACE);\r
188         WriteByte(msg, RACE_NET_SERVER_RANKINGS);\r
189         WriteShort(msg, pos);\r
190         WriteShort(msg, prevpos);\r
191         WriteShort(msg, del);\r
192         WriteString(msg, race_GetName(pos));\r
193         WriteInt24_t(msg, race_GetTime(pos));\r
194 }\r
195 \r
196 void race_SendStatus(float id, entity e)\r
197 {\r
198         float msg;\r
199         if (id == 0)\r
200                 msg = MSG_ONE;\r
201         else\r
202                 msg = MSG_ALL;\r
203         msg_entity = e;\r
204         WRITESPECTATABLE_MSG_ONE_VARNAME(dummy3, {\r
205                 WriteByte(msg, SVC_TEMPENTITY);\r
206                 WriteByte(msg, TE_CSQC_RACE);\r
207                 WriteByte(msg, RACE_NET_SERVER_STATUS);\r
208                 WriteShort(msg, id);\r
209                 WriteString(msg, e.netname);\r
210         });\r
211 }\r
212 \r
213 string race_PlaceName(float pos) {\r
214         if(pos == 1)\r
215                 return "1st";\r
216         else if(pos == 2)\r
217                 return "2nd";\r
218         else if(pos == 3)\r
219                 return "3rd";\r
220         else\r
221                 return strcat(ftos(pos), "th");\r
222 }\r
223 \r
224 void race_SetTime(entity e, float t, float match_rec) {\r
225         float pos, prevpos;\r
226         pos = race_GetPos(t);\r
227 #ifdef UID\r
228         prevpos = race_CheckUID(e.uid, e.netname);\r
229 #else\r
230         prevpos = race_CheckName(e.netname);\r
231 #endif\r
232 \r
233         float oldrec;\r
234         string recorddifference;\r
235         if (prevpos && (prevpos < pos || !pos))\r
236         {\r
237                 oldrec = race_GetTime(prevpos);\r
238                 recorddifference = strcat(" ^1[+", TIME_ENCODED_TOSTRING(t - oldrec), "]");\r
239                 bprint(e.netname, "^7 couldn't break their ", race_PlaceName(prevpos), " place record of ", TIME_ENCODED_TOSTRING(oldrec), recorddifference, "\n");\r
240                 race_SendStatus(0, e); // "fail"\r
241                 return;\r
242         } else if (!pos) { // no ranking, time worse than the worst ranked\r
243                 recorddifference = strcat(" ^1[+", TIME_ENCODED_TOSTRING(t - grecordtime[RANKINGS_CNT-1]), "]");\r
244                 bprint(e.netname, "^7 couldn't break the ", race_PlaceName(RANKINGS_CNT), " place record of ", TIME_ENCODED_TOSTRING(grecordtime[RANKINGS_CNT-1]), recorddifference, "\n");\r
245                 race_SendStatus(0, e); // "fail"\r
246                 return;\r
247         }\r
248 \r
249         oldrec = grecordtime[pos-1];\r
250 \r
251         // move other rankings out of the way\r
252         float i;\r
253         if (prevpos) { // player improved his existing record\r
254                 for (i=prevpos-1;i>pos-1;--i) {\r
255                         db_put(ServerProgsDB, strcat(GetMapname(), rr, "time", ftos(i)), ftos(grecordtime[i-1]));\r
256                         db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname", ftos(i)), grecordholder[i-1]);\r
257 #ifdef UID\r
258                         db_put(ServerProgsDB, strcat(GetMapname(), rr, "uid", ftos(i)), grecorduid[i-1]);\r
259 #endif\r
260                         grecordtime[i] = grecordtime[i-1];\r
261 \r
262                         if (grecordholder[i])\r
263                                 strunzone(grecordholder[i]);\r
264                         grecordholder[i] = strzone(grecordholder[i-1]);\r
265 #ifdef UID\r
266                         if (grecorduid[i])\r
267                                 strunzone(grecorduid[i]);\r
268                         grecorduid[i] = strzone(grecorduid[i-1]);\r
269 #endif\r
270                 }\r
271         } else { // player has no ranked record yet\r
272                 for (i=RANKINGS_CNT-1;i>pos-1;--i) {\r
273                         db_put(ServerProgsDB, strcat(GetMapname(), rr, "time", ftos(i)), ftos(grecordtime[i-1]));\r
274                         db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname", ftos(i)), grecordholder[i-1]);\r
275 #ifdef UID\r
276                         db_put(ServerProgsDB, strcat(GetMapname(), rr, "uid", ftos(i)), grecorduid[i-1]);\r
277 #endif\r
278                         grecordtime[i] = grecordtime[i-1];\r
279 \r
280                         if (grecordholder[i])\r
281                                 strunzone(grecordholder[i]);\r
282                         grecordholder[i] = strzone(grecordholder[i-1]);\r
283 #ifdef UID\r
284                         if (grecorduid[i])\r
285                                 strunzone(grecorduid[i]);\r
286                         grecorduid[i] = strzone(grecorduid[i-1]);\r
287 #endif\r
288                 }\r
289         }\r
290         \r
291         // store new ranking\r
292         if (pos == 1) {\r
293                 db_put(ServerProgsDB, strcat(GetMapname(), rr, "time"), ftos(t));\r
294                 db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname"), e.netname);\r
295 #ifdef UID\r
296                 db_put(ServerProgsDB, strcat(GetMapname(), rr, "uid"), e.uid);\r
297 #endif\r
298 \r
299                 grecordtime[0] = t;\r
300 \r
301                 if (grecordholder[0])\r
302                         strunzone(grecordholder[0]);\r
303                 grecordholder[0] = strzone(e.netname);\r
304 #ifdef UID\r
305                 if (grecorduid[0])\r
306                         strunzone(grecorduid[0]);\r
307                 grecorduid[0] = strzone(e.uid);\r
308 #endif\r
309                 write_recordmarker(e, time - TIME_DECODE(t), TIME_DECODE(t));\r
310                 race_send_recordtime(MSG_ALL);\r
311         } else {\r
312                 db_put(ServerProgsDB, strcat(GetMapname(), rr, "time", ftos(pos-1)), ftos(t));\r
313                 db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname", ftos(pos-1)), e.netname);\r
314 #ifdef UID\r
315                 db_put(ServerProgsDB, strcat(GetMapname(), rr, "uid", ftos(pos-1)), e.uid);\r
316 #endif\r
317 \r
318                 grecordtime[pos-1] = t;\r
319 \r
320                 if (grecordholder[pos-1])\r
321                         strunzone(grecordholder[pos-1]);\r
322                 grecordholder[pos-1] = strzone(e.netname);\r
323 #ifdef UID\r
324                 if (grecorduid[pos-1])\r
325                         strunzone(grecorduid[pos-1]);\r
326                 grecorduid[pos-1] = strzone(e.uid);\r
327 #endif\r
328         }\r
329 \r
330         if (pos == RANKINGS_CNT)\r
331                 worst_time = t;\r
332 \r
333         race_SendRankings(pos, prevpos, 0, MSG_ALL);\r
334         if(rankings_reply)\r
335                 strunzone(rankings_reply);\r
336         rankings_reply = strzone(getrankings());\r
337         if(pos == 1) {\r
338                 if(pos == prevpos) {\r
339                         recorddifference = strcat(" ^2[-", TIME_ENCODED_TOSTRING(oldrec - t), "]");\r
340                         bprint(e.netname, "^1 improved their 1st place record with ", TIME_ENCODED_TOSTRING(t), recorddifference, "\n");\r
341                 } else if (oldrec == 0) {\r
342                         bprint(e.netname, "^1 set the 1st place record with ", TIME_ENCODED_TOSTRING(t), "\n");\r
343                 } else {\r
344                         recorddifference = strcat(" ^2[-", TIME_ENCODED_TOSTRING(oldrec - t), "]");\r
345                         bprint(e.netname, "^1 broke ", grecordholder[pos], "^1's 1st place record with ", strcat(TIME_ENCODED_TOSTRING(t), recorddifference, "\n"));\r
346                 }\r
347                 race_SendStatus(3, e); // "new server record"\r
348         } else {\r
349                 if(pos == prevpos) {\r
350                         recorddifference = strcat(" ^2[-", TIME_ENCODED_TOSTRING(oldrec - t), "]");\r
351                         bprint(e.netname, "^5 improved their ", race_PlaceName(pos), " ^5place record with ", TIME_ENCODED_TOSTRING(t), recorddifference, "\n");\r
352                         race_SendStatus(1, e); // "new time"\r
353                 } else if (oldrec == 0) {\r
354                         bprint(e.netname, "^2 set the ", race_PlaceName(pos), " ^2place record with ", TIME_ENCODED_TOSTRING(t), "\n");\r
355                         race_SendStatus(2, e); // "new rank"\r
356                 } else {\r
357                         recorddifference = strcat(" ^2[-", TIME_ENCODED_TOSTRING(oldrec - t), "]");\r
358                         bprint(e.netname, "^2 broke ", grecordholder[pos], "^2's ", race_PlaceName(pos), " ^2place record with ", strcat(TIME_ENCODED_TOSTRING(t), recorddifference, "\n"));\r
359                         race_SendStatus(2, e); // "new rank"\r
360                 }\r
361         }\r
362 }\r
363 \r
364 void race_DeleteTime(float pos) {\r
365         float i;\r
366 \r
367         for (i = pos-1; i <= RANKINGS_CNT-1; ++i) {\r
368                 if (i == 0) {\r
369                         db_put(ServerProgsDB, strcat(GetMapname(), rr, "time"), ftos(grecordtime[1]));\r
370                         db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname"), grecordholder[1]);\r
371 #ifdef UID\r
372                         db_put(ServerProgsDB, strcat(GetMapname(), rr, "uid"), grecorduid[1]);\r
373 #endif\r
374                         grecordtime[0] = grecordtime[1];\r
375                         if (grecordholder[i])\r
376                                 strunzone(grecordholder[0]);\r
377                         grecordholder[0] = strzone(grecordholder[1]);\r
378 \r
379 #ifdef UID\r
380                         if (grecorduid[i])\r
381                                 strunzone(grecorduid[0]);\r
382                         grecorduid[0] = strzone(grecorduid[1]);\r
383 #endif\r
384                 }\r
385                 else if (i == RANKINGS_CNT-1) {\r
386                         db_put(ServerProgsDB, strcat(GetMapname(), rr, "time", ftos(i)), string_null);\r
387                         db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname", ftos(i)), string_null);\r
388 #ifdef UID\r
389                         db_put(ServerProgsDB, strcat(GetMapname(), rr, "uid", ftos(i)), string_null);\r
390 #endif\r
391                         grecordtime[i] = 0;\r
392                         if (grecordholder[i])\r
393                                 strunzone(grecordholder[i]);\r
394                         grecordholder[i] = string_null;\r
395 \r
396 #ifdef UID\r
397                         if (grecorduid[i])\r
398                                 strunzone(grecorduid[i]);\r
399                         grecorduid[i] = string_null;\r
400 #endif\r
401                 }\r
402                 else {\r
403                         db_put(ServerProgsDB, strcat(GetMapname(), rr, "time", ftos(i)), ftos(grecordtime[i+1]));\r
404                         db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname", ftos(i)), grecordholder[i+1]);\r
405 #ifdef UID\r
406                         db_put(ServerProgsDB, strcat(GetMapname(), rr, "uid", ftos(i)), grecorduid[i+1]);\r
407 #endif\r
408                         grecordtime[i] = grecordtime[i+1];\r
409                         if (grecordholder[i])\r
410                                 strunzone(grecordholder[i]);\r
411                         grecordholder[i] = strzone(grecordholder[i+1]);\r
412 \r
413 #ifdef UID\r
414                         if (grecorduid[i])\r
415                                 strunzone(grecorduid[i]);\r
416                         grecorduid[i] = strzone(grecorduid[i+1]);\r
417 #endif\r
418                 }\r
419         }\r
420 \r
421         race_SendRankings(pos, 0, 1, MSG_ALL);\r
422         if(pos == 1)\r
423                 race_send_recordtime(MSG_ALL);\r
424 \r
425         if(rankings_reply)\r
426                 strunzone(rankings_reply);\r
427         rankings_reply = strzone(getrankings());\r
428 \r
429         worst_time = 0;\r
430 }\r
431 \r
432 void race_SendTime(entity e, float cp, float t, float tvalid)\r
433 {\r
434         float snew, l;\r
435         entity p;\r
436 \r
437         if(g_race_qualifying)\r
438                 t += e.race_penalty_accumulator;\r
439 \r
440         t = TIME_ENCODE(t); // make integer\r
441         // adding just 0.4 so it rounds down in the .5 case (matching the timer display)\r
442 \r
443         if(tvalid)\r
444         if(cp == race_timed_checkpoint) // finish line\r
445         if not(e.race_completed)\r
446         {\r
447                 float s;\r
448                 if(g_race_qualifying)\r
449                 {\r
450                         s = PlayerScore_Add(e, SP_RACE_FASTEST, 0);\r
451                         if(!s || t < s)\r
452                                 PlayerScore_Add(e, SP_RACE_FASTEST, t - s);\r
453                 }\r
454                 else\r
455                 {\r
456                         s = PlayerScore_Add(e, SP_RACE_TIME, 0);\r
457                         snew = TIME_ENCODE(time - game_starttime);\r
458                         PlayerScore_Add(e, SP_RACE_TIME, snew - s);\r
459                         l = PlayerTeamScore_Add(e, SP_RACE_LAPS, ST_RACE_LAPS, 1);\r
460 \r
461                         if(cvar("fraglimit"))\r
462                                 if(l >= cvar("fraglimit"))\r
463                                         race_StartCompleting();\r
464 \r
465                         if(race_completing)\r
466                         {\r
467                                 e.race_completed = 1;\r
468                                 MAKE_INDEPENDENT_PLAYER(e);\r
469                                 bprint(e.netname, "^7 has finished the race.\n");\r
470                                 ClientData_Touch(e);\r
471                         }\r
472                 }\r
473         }\r
474 \r
475         float recordtime;\r
476         string recordholder;\r
477         if(g_race_qualifying)\r
478         {\r
479                 if(tvalid)\r
480                 {\r
481                         recordtime = race_checkpoint_records[cp];\r
482                         recordholder = strcat1(race_checkpoint_recordholders[cp]); // make a tempstring copy, as we'll possibly strunzone it!\r
483                         if(recordholder == e.netname)\r
484                                 recordholder = "";\r
485 \r
486                         if(t != 0) {\r
487                                 if(cp == race_timed_checkpoint)\r
488                                 {\r
489                                         race_SetTime(e, t, recordtime);\r
490                                         if(g_cts && cvar("g_cts_finish_kill_delay"))\r
491                                         {\r
492                                                 CTS_ClientKill(e);\r
493                                         }\r
494                                 }\r
495 \r
496                                 if(t < recordtime || recordtime == 0)\r
497                                 {\r
498                                         race_checkpoint_records[cp] = t;\r
499                                         if(race_checkpoint_recordholders[cp])\r
500                                                 strunzone(race_checkpoint_recordholders[cp]);\r
501                                         race_checkpoint_recordholders[cp] = strzone(e.netname);\r
502                                         if(g_race_qualifying)\r
503                                         {\r
504                                                 FOR_EACH_REALPLAYER(p)\r
505                                                         if(p.race_checkpoint == cp)\r
506                                                                 race_SendNextCheckpoint(p, 0);\r
507                                         }\r
508                                 }\r
509                         }\r
510                 }\r
511                 else\r
512                 {\r
513                         // dummies\r
514                         t = 0;\r
515                         recordtime = 0;\r
516                         recordholder = "";\r
517                 }\r
518 \r
519                 msg_entity = e;\r
520                 if(g_race_qualifying)\r
521                 {\r
522                         WRITESPECTATABLE_MSG_ONE_VARNAME(dummy1, {\r
523                                 WriteByte(MSG_ONE, SVC_TEMPENTITY);\r
524                                 WriteByte(MSG_ONE, TE_CSQC_RACE);\r
525                                 WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_HIT_QUALIFYING);\r
526                                 WriteByte(MSG_ONE, race_CheckpointNetworkID(cp)); // checkpoint the player now is at\r
527                                 WriteInt24_t(MSG_ONE, t); // time to that intermediate\r
528                                 WriteInt24_t(MSG_ONE, recordtime); // previously best time\r
529                                 WriteString(MSG_ONE, recordholder); // record holder\r
530                         });\r
531                 }\r
532         }\r
533         else // RACE! Not Qualifying\r
534         {\r
535                 float lself, lother, othtime;\r
536                 entity oth;\r
537                 oth = race_checkpoint_lastplayers[cp];\r
538                 if(oth)\r
539                 {\r
540                         lself = PlayerScore_Add(e, SP_RACE_LAPS, 0);\r
541                         lother = race_checkpoint_lastlaps[cp];\r
542                         othtime = race_checkpoint_lasttimes[cp];\r
543                 }\r
544                 else\r
545                         lself = lother = othtime = 0;\r
546 \r
547                 msg_entity = e;\r
548                 WRITESPECTATABLE_MSG_ONE_VARNAME(dummy2, {\r
549                         WriteByte(MSG_ONE, SVC_TEMPENTITY);\r
550                         WriteByte(MSG_ONE, TE_CSQC_RACE);\r
551                         WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_HIT_RACE);\r
552                         WriteByte(MSG_ONE, race_CheckpointNetworkID(cp)); // checkpoint the player now is at\r
553                         if(e == oth)\r
554                         {\r
555                                 WriteInt24_t(MSG_ONE, 0);\r
556                                 WriteByte(MSG_ONE, 0);\r
557                                 WriteString(MSG_ONE, "");\r
558                         }\r
559                         else\r
560                         {\r
561                                 WriteInt24_t(MSG_ONE, TIME_ENCODE(time - race_checkpoint_lasttimes[cp]));\r
562                                 WriteByte(MSG_ONE, lself - lother);\r
563                                 WriteString(MSG_ONE, oth.netname); // record holder\r
564                         }\r
565                 });\r
566 \r
567                 race_checkpoint_lastplayers[cp] = e;\r
568                 race_checkpoint_lasttimes[cp] = time;\r
569                 race_checkpoint_lastlaps[cp] = lself;\r
570 \r
571                 msg_entity = oth;\r
572                 WRITESPECTATABLE_MSG_ONE_VARNAME(dummy3, {\r
573                         WriteByte(MSG_ONE, SVC_TEMPENTITY);\r
574                         WriteByte(MSG_ONE, TE_CSQC_RACE);\r
575                         WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_HIT_RACE_BY_OPPONENT);\r
576                         WriteByte(MSG_ONE, race_CheckpointNetworkID(cp)); // checkpoint the player now is at\r
577                         if(e == oth)\r
578                         {\r
579                                 WriteInt24_t(MSG_ONE, 0);\r
580                                 WriteByte(MSG_ONE, 0);\r
581                                 WriteString(MSG_ONE, "");\r
582                         }\r
583                         else\r
584                         {\r
585                                 WriteInt24_t(MSG_ONE, TIME_ENCODE(time - othtime));\r
586                                 WriteByte(MSG_ONE, lother - lself);\r
587                                 WriteString(MSG_ONE, e.netname); // record holder\r
588                         }\r
589                 });\r
590         }\r
591 }\r
592 \r
593 void race_ClearTime(entity e)\r
594 {\r
595         e.race_checkpoint = 0;\r
596         e.race_laptime = 0;\r
597         e.race_movetime = e.race_movetime_frac = e.race_movetime_count = 0;\r
598         e.race_penalty_accumulator = 0;\r
599         e.race_lastpenalty = world;\r
600 \r
601         msg_entity = e;\r
602         WRITESPECTATABLE_MSG_ONE({\r
603                 WriteByte(MSG_ONE, SVC_TEMPENTITY);\r
604                 WriteByte(MSG_ONE, TE_CSQC_RACE);\r
605                 WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_CLEAR); // next\r
606         });\r
607 }\r
608 \r
609 void dumpsurface(entity e)\r
610 {\r
611         float n, si, ni;\r
612         vector norm, vec;\r
613         print("Surfaces of ", etos(e), ":\n");\r
614 \r
615         print("TEST = ", ftos(getsurfacenearpoint(e, '0 0 0')), "\n");\r
616 \r
617         for(si = 0; ; ++si)\r
618         {\r
619                 n = getsurfacenumpoints(e, si);\r
620                 if(n <= 0)\r
621                         break;\r
622                 print("  Surface ", ftos(si), ":\n");\r
623                 norm = getsurfacenormal(e, si);\r
624                 print("    Normal = ", vtos(norm), "\n");\r
625                 for(ni = 0; ni < n; ++ni)\r
626                 {\r
627                         vec = getsurfacepoint(e, si, ni);\r
628                         print("    Point ", ftos(ni), " = ", vtos(vec), " (", ftos(norm * vec), ")\n");\r
629                 }\r
630         }\r
631 }\r
632 \r
633 void checkpoint_passed()\r
634 {\r
635         string oldmsg;\r
636         entity cp;\r
637 \r
638         /*\r
639          * Trigger targets\r
640          */\r
641         if not((self.spawnflags & 2) && (other.classname == "player"))\r
642         {\r
643                 activator = other;\r
644                 oldmsg = self.message;\r
645                 self.message = "";\r
646                 SUB_UseTargets();\r
647                 self.message = oldmsg;\r
648         }\r
649 \r
650         if(other.classname != "player")\r
651                 return;\r
652 \r
653         /*\r
654          * Remove unauthorized equipment\r
655          */\r
656 \r
657         other.porto_forbidden = 2; // decreased by 1 each StartFrame\r
658 \r
659         if(defrag_ents) {\r
660                 if(self.race_checkpoint == -2) \r
661                 {\r
662                         self.race_checkpoint = other.race_checkpoint;\r
663                 }\r
664 \r
665                 float largest_cp_id;\r
666                 float cp_amount;\r
667                 for(cp = world; (cp = find(cp, classname, "target_checkpoint"));) {\r
668                         cp_amount += 1;\r
669                         if(cp.race_checkpoint > largest_cp_id) // update the finish id if someone hit a new checkpoint\r
670                         {\r
671                                 largest_cp_id = cp.race_checkpoint;\r
672                                 for(cp = world; (cp = find(cp, classname, "target_stopTimer"));)\r
673                                         cp.race_checkpoint = largest_cp_id + 1; // finish line\r
674                                 race_highest_checkpoint = largest_cp_id + 1;\r
675                                 race_timed_checkpoint = largest_cp_id + 1;\r
676 \r
677                                 for(cp = world; (cp = find(cp, classname, "target_checkpoint"));) {\r
678                                         if(cp.race_checkpoint == -2) // set defragcpexists to -1 so that the cp id file will be rewritten when someone finishes\r
679                                                 defragcpexists = -1;\r
680                                 }       \r
681                         }\r
682                 }\r
683                 if(cp_amount == 0) {\r
684                         for(cp = world; (cp = find(cp, classname, "target_stopTimer"));)\r
685                                 cp.race_checkpoint = 1;\r
686                         race_highest_checkpoint = 1;\r
687                         race_timed_checkpoint = 1;\r
688                 }\r
689         }\r
690 \r
691         if((other.race_checkpoint == -1 && self.race_checkpoint == 0) || (other.race_checkpoint == self.race_checkpoint))\r
692         {\r
693                 if(self.race_penalty)\r
694                 {\r
695                         if(other.race_lastpenalty != self)\r
696                         {\r
697                                 other.race_lastpenalty = self;\r
698                                 race_ImposePenaltyTime(other, self.race_penalty, self.race_penalty_reason);\r
699                         }\r
700                 }\r
701 \r
702                 if(other.race_penalty)\r
703                         return;\r
704 \r
705                 /*\r
706                  * Trigger targets\r
707                  */\r
708                 if(self.spawnflags & 2)\r
709                 {\r
710                         activator = other;\r
711                         oldmsg = self.message;\r
712                         self.message = "";\r
713                         SUB_UseTargets();\r
714                         self.message = oldmsg;\r
715                 }\r
716 \r
717                 if(other.race_respawn_checkpoint != self.race_checkpoint || !other.race_started)\r
718                         other.race_respawn_spotref = self; // this is not a spot but a CP, but spawnpoint selection will deal with that\r
719                 other.race_respawn_checkpoint = self.race_checkpoint;\r
720                 other.race_checkpoint = race_NextCheckpoint(self.race_checkpoint);\r
721                 other.race_started = 1;\r
722 \r
723                 race_SendTime(other, self.race_checkpoint, other.race_movetime, !!other.race_laptime);\r
724 \r
725                 if(!self.race_checkpoint) // start line\r
726                 {\r
727                         other.race_laptime = time;\r
728                         other.race_movetime = other.race_movetime_frac = other.race_movetime_count = 0;\r
729                         other.race_penalty_accumulator = 0;\r
730                         other.race_lastpenalty = world;\r
731                 }\r
732 \r
733                 if(g_race_qualifying)\r
734                         race_SendNextCheckpoint(other, 0);\r
735 \r
736                 if(defrag_ents && defragcpexists < 0 && self.classname == "target_stopTimer")\r
737                 {\r
738                         float fh;\r
739                         defragcpexists = fh = fopen(strcat("maps/", GetMapname(), ".defragcp"), FILE_WRITE);\r
740                         if(fh >= 0)\r
741                         {\r
742                                 for(cp = world; (cp = find(cp, classname, "target_checkpoint"));)\r
743                                 fputs(fh, strcat(cp.targetname, " ", ftos(cp.race_checkpoint), "\n"));\r
744                         }\r
745                         fclose(fh);\r
746                 }\r
747         }\r
748         else if(other.race_checkpoint == race_NextCheckpoint(self.race_checkpoint))\r
749         {\r
750                 // ignored\r
751         }\r
752         else\r
753         {\r
754                 if(self.spawnflags & 4)\r
755                         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');\r
756         }\r
757 }\r
758 \r
759 void checkpoint_touch()\r
760 {\r
761         EXACTTRIGGER_TOUCH;\r
762         checkpoint_passed();\r
763 }\r
764 \r
765 void checkpoint_use()\r
766 {\r
767         if(other.classname == "info_player_deathmatch") // a spawn, a spawn\r
768                 return;\r
769 \r
770         other = activator;\r
771         checkpoint_passed();\r
772 }\r
773 \r
774 float race_waypointsprite_visible_for_player(entity e)\r
775 {\r
776         if(e.race_checkpoint == -1 || self.owner.race_checkpoint == -2)\r
777                 return TRUE;\r
778         else if(e.race_checkpoint == self.owner.race_checkpoint)\r
779                 return TRUE;\r
780         else\r
781                 return FALSE;\r
782 }\r
783 \r
784 float have_verified;\r
785 void trigger_race_checkpoint_verify()\r
786 {\r
787         entity oldself, cp;\r
788         float i, p;\r
789         float qual;\r
790 \r
791         if(have_verified)\r
792                 return;\r
793         have_verified = 1;\r
794         \r
795         qual = g_race_qualifying;\r
796 \r
797         oldself = self;\r
798         self = spawn();\r
799         self.classname = "player";\r
800 \r
801         if(g_race)\r
802         {\r
803                 for(i = 0; i <= race_highest_checkpoint; ++i)\r
804                 {\r
805                         self.race_checkpoint = race_NextCheckpoint(i);\r
806 \r
807                         // race only (middle of the race)\r
808                         g_race_qualifying = 0;\r
809                         self.race_place = 0;\r
810                         if(!Spawn_FilterOutBadSpots(findchain(classname, "info_player_deathmatch"), world, 0, FALSE, FALSE))\r
811                                 error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(self.race_place), " (used for respawning in race) - bailing out"));\r
812 \r
813                         if(i == 0)\r
814                         {\r
815                                 // qualifying only\r
816                                 g_race_qualifying = 1;\r
817                                 self.race_place = race_lowest_place_spawn;\r
818                                 if(!Spawn_FilterOutBadSpots(findchain(classname, "info_player_deathmatch"), world, 0, FALSE, FALSE))\r
819                                         error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(self.race_place), " (used for qualifying) - bailing out"));\r
820                                 \r
821                                 // race only (initial spawn)\r
822                                 g_race_qualifying = 0;\r
823                                 for(p = 1; p <= race_highest_place_spawn; ++p)\r
824                                 {\r
825                                         self.race_place = p;\r
826                                         if(!Spawn_FilterOutBadSpots(findchain(classname, "info_player_deathmatch"), world, 0, FALSE, FALSE))\r
827                                                 error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(self.race_place), " (used for initially spawning in race) - bailing out"));\r
828                                 }\r
829                         }\r
830                 }\r
831         }\r
832         else if(!defrag_ents)\r
833         {\r
834                 // qualifying only\r
835                 self.race_checkpoint = race_NextCheckpoint(0);\r
836                 g_race_qualifying = 1;\r
837                 self.race_place = race_lowest_place_spawn;\r
838                 if(!Spawn_FilterOutBadSpots(findchain(classname, "info_player_deathmatch"), world, 0, FALSE, FALSE))\r
839                         error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(self.race_place), " (used for qualifying) - bailing out"));\r
840         }\r
841         else\r
842         {\r
843                 self.race_checkpoint = race_NextCheckpoint(0);\r
844                 g_race_qualifying = 1;\r
845                 self.race_place = 0; // there's only one spawn on defrag maps\r
846  \r
847                 // check if a defragcp file already exists, then read it and apply the checkpoint order\r
848                 float fh;\r
849                 float len;\r
850                 string l;\r
851 \r
852                 defragcpexists = fh = fopen(strcat("maps/", GetMapname(), ".defragcp"), FILE_READ);\r
853                 if(fh >= 0)\r
854                 {\r
855                         while((l = fgets(fh)))\r
856                         {\r
857                                 len = tokenize_console(l);\r
858                                 if(len != 2) {\r
859                                         defragcpexists = -1; // something's wrong in the defrag cp file, set defragcpexists to -1 so that it will be rewritten when someone finishes\r
860                                         continue;\r
861                                 }\r
862                                 for(cp = world; (cp = find(cp, classname, "target_checkpoint"));)\r
863                                         if(argv(0) == cp.targetname)\r
864                                                 cp.race_checkpoint = stof(argv(1));\r
865                         }\r
866                         fclose(fh);\r
867                 }\r
868         }\r
869 \r
870         g_race_qualifying = qual;\r
871 \r
872         if(race_timed_checkpoint) {\r
873                 if(defrag_ents) {\r
874                         for(cp = world; (cp = find(cp, classname, "target_startTimer"));)\r
875                                 WaypointSprite_UpdateSprites(cp.sprite, "race-start", "", "");\r
876                         for(cp = world; (cp = find(cp, classname, "target_stopTimer"));)\r
877                                 WaypointSprite_UpdateSprites(cp.sprite, "race-finish", "", "");\r
878 \r
879                         for(cp = world; (cp = find(cp, classname, "target_checkpoint"));) {\r
880                                 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\r
881                                         defragcpexists = -1;\r
882                         }\r
883 \r
884                         if(defragcpexists != -1){\r
885                                 float largest_cp_id;\r
886                                 for(cp = world; (cp = find(cp, classname, "target_checkpoint"));)\r
887                                         if(cp.race_checkpoint > largest_cp_id)\r
888                                                 largest_cp_id = cp.race_checkpoint;\r
889                                 for(cp = world; (cp = find(cp, classname, "target_stopTimer"));)\r
890                                         cp.race_checkpoint = largest_cp_id + 1; // finish line\r
891                                 race_highest_checkpoint = largest_cp_id + 1;\r
892                                 race_timed_checkpoint = largest_cp_id + 1;\r
893                         } else {\r
894                                 for(cp = world; (cp = find(cp, classname, "target_stopTimer"));)\r
895                                         cp.race_checkpoint = 255; // finish line\r
896                                 race_highest_checkpoint = 255;\r
897                                 race_timed_checkpoint = 255;\r
898                         }\r
899                 }\r
900                 else {\r
901                         for(cp = world; (cp = find(cp, classname, "trigger_race_checkpoint")); )\r
902                                 if(cp.sprite)\r
903                                 {\r
904                                         if(cp.race_checkpoint == 0)\r
905                                                 WaypointSprite_UpdateSprites(cp.sprite, "race-start", "", "");\r
906                                         else if(cp.race_checkpoint == race_timed_checkpoint)\r
907                                                 WaypointSprite_UpdateSprites(cp.sprite, "race-finish", "", "");\r
908                                 }\r
909                 }\r
910         }\r
911 \r
912         if(defrag_ents) {\r
913                 entity trigger, targ;\r
914                 for(trigger = world; (trigger = find(trigger, classname, "trigger_multiple")); )\r
915                         for(targ = world; (targ = find(targ, targetname, trigger.target)); )\r
916                                 if (targ.classname == "target_checkpoint" || targ.classname == "target_startTimer" || targ.classname == "target_stopTimer") {\r
917                                         targ.wait = -2;\r
918                                         targ.delay = 0;\r
919 \r
920                                         setsize(targ, trigger.mins, trigger.maxs);\r
921                                         setorigin(targ, trigger.origin);\r
922                                         //remove(trigger);\r
923                                 }\r
924         }\r
925         remove(self);\r
926         self = oldself;\r
927 }\r
928 \r
929 void spawnfunc_trigger_race_checkpoint()\r
930 {\r
931         vector o;\r
932         if(!g_race && !g_cts)\r
933         {\r
934                 remove(self);\r
935                 return;\r
936         }\r
937 \r
938         EXACTTRIGGER_INIT;\r
939 \r
940         self.use = checkpoint_use;\r
941         if not(self.spawnflags & 1)\r
942                 self.touch = checkpoint_touch;\r
943 \r
944         o = (self.absmin + self.absmax) * 0.5;\r
945         tracebox(o, PL_MIN, PL_MAX, o - '0 0 1' * (o_z - self.absmin_z), MOVE_NORMAL, self);\r
946         waypoint_spawnforitem_force(self, trace_endpos);\r
947         self.nearestwaypointtimeout = time + 1000000000;\r
948 \r
949         if(!self.message)\r
950                 self.message = "went backwards";\r
951         if (!self.message2)\r
952                 self.message2 = "was pushed backwards by";\r
953         if (!self.race_penalty_reason)\r
954                 self.race_penalty_reason = "missing a checkpoint";\r
955         \r
956         self.race_checkpoint = self.cnt;\r
957 \r
958         if(self.race_checkpoint > race_highest_checkpoint)\r
959         {\r
960                 race_highest_checkpoint = self.race_checkpoint;\r
961                 if(self.spawnflags & 8)\r
962                         race_timed_checkpoint = self.race_checkpoint;\r
963                 else\r
964                         race_timed_checkpoint = 0;\r
965         }\r
966 \r
967         if(!self.race_penalty)\r
968         {\r
969                 if(self.race_checkpoint)\r
970                         WaypointSprite_SpawnFixed("race-checkpoint", o, self, sprite);\r
971                 else\r
972                         WaypointSprite_SpawnFixed("race-finish", o, self, sprite);\r
973         }\r
974 \r
975         self.sprite.waypointsprite_visible_for_player = race_waypointsprite_visible_for_player;\r
976 \r
977         InitializeEntity(self, trigger_race_checkpoint_verify, INITPRIO_FINDTARGET);\r
978 }\r
979 \r
980 void spawnfunc_target_checkpoint() // defrag entity\r
981 {\r
982         vector o;\r
983         if(!g_race && !g_cts)\r
984         {\r
985                 remove(self);\r
986                 return;\r
987         }\r
988         defrag_ents = 1;\r
989 \r
990         EXACTTRIGGER_INIT;\r
991 \r
992         self.use = checkpoint_use;\r
993         if not(self.spawnflags & 1)\r
994                 self.touch = checkpoint_touch;\r
995 \r
996         o = (self.absmin + self.absmax) * 0.5;\r
997         tracebox(o, PL_MIN, PL_MAX, o - '0 0 1' * (o_z - self.absmin_z), MOVE_NORMAL, self);\r
998         waypoint_spawnforitem_force(self, trace_endpos);\r
999         self.nearestwaypointtimeout = time + 1000000000;\r
1000 \r
1001         if(!self.message)\r
1002                 self.message = "went backwards";\r
1003         if (!self.message2)\r
1004                 self.message2 = "was pushed backwards by";\r
1005         if (!self.race_penalty_reason)\r
1006                 self.race_penalty_reason = "missing a checkpoint";\r
1007 \r
1008         if(self.classname == "target_startTimer")\r
1009                 self.race_checkpoint = 0;\r
1010         else\r
1011                 self.race_checkpoint = -2;\r
1012 \r
1013         race_timed_checkpoint = 1;\r
1014 \r
1015         if(self.race_checkpoint == 0)\r
1016                 WaypointSprite_SpawnFixed("race-start", o, self, sprite);\r
1017         else\r
1018                 WaypointSprite_SpawnFixed("race-checkpoint", o, self, sprite);\r
1019 \r
1020         self.sprite.waypointsprite_visible_for_player = race_waypointsprite_visible_for_player;\r
1021 \r
1022         InitializeEntity(self, trigger_race_checkpoint_verify, INITPRIO_FINDTARGET);\r
1023 }\r
1024 \r
1025 void spawnfunc_target_startTimer() { spawnfunc_target_checkpoint(); }\r
1026 void spawnfunc_target_stopTimer() { spawnfunc_target_checkpoint(); }\r
1027 \r
1028 void race_AbandonRaceCheck(entity p)\r
1029 {\r
1030         if(race_completing && !p.race_completed)\r
1031         {\r
1032                 p.race_completed = 1;\r
1033                 MAKE_INDEPENDENT_PLAYER(p);\r
1034                 bprint(p.netname, "^7 has abandoned the race.\n");\r
1035                 ClientData_Touch(p);\r
1036         }\r
1037 }\r
1038 \r
1039 void race_StartCompleting()\r
1040 {\r
1041         entity p;\r
1042         race_completing = 1;\r
1043         FOR_EACH_PLAYER(p)\r
1044                 if(p.deadflag != DEAD_NO)\r
1045                         race_AbandonRaceCheck(p);\r
1046 }\r
1047 \r
1048 void race_PreparePlayer()\r
1049 {\r
1050         race_ClearTime(self);\r
1051         self.race_place = 0;\r
1052         self.race_started = 0;\r
1053         self.race_respawn_checkpoint = 0;\r
1054         self.race_respawn_spotref = world;\r
1055 }\r
1056 \r
1057 void race_RetractPlayer()\r
1058 {\r
1059         if(!g_race && !g_cts)\r
1060                 return;\r
1061         if(self.race_respawn_checkpoint == 0 || self.race_respawn_checkpoint == race_timed_checkpoint)\r
1062                 race_ClearTime(self);\r
1063         self.race_checkpoint = self.race_respawn_checkpoint;\r
1064 }\r
1065 \r
1066 void race_PreDie()\r
1067 {\r
1068         if(!g_race && !g_cts)\r
1069                 return;\r
1070 \r
1071         race_AbandonRaceCheck(self);\r
1072 }\r
1073 \r
1074 void race_PreSpawn()\r
1075 {\r
1076         if(!g_race && !g_cts)\r
1077                 return;\r
1078         if(self.killcount == -666 /* initial spawn */ || g_race_qualifying) // spawn\r
1079                 race_PreparePlayer();\r
1080         else // respawn\r
1081                 race_RetractPlayer();\r
1082 \r
1083         race_AbandonRaceCheck(self);\r
1084 }\r
1085 \r
1086 void race_PostSpawn(entity spot)\r
1087 {\r
1088         if(!g_race && !g_cts)\r
1089                 return;\r
1090 \r
1091         if(spot.target == "")\r
1092                 // Emergency: this wasn't a real spawnpoint. Can this ever happen?\r
1093                 race_PreparePlayer();\r
1094 \r
1095         // if we need to respawn, do it right\r
1096         self.race_respawn_checkpoint = self.race_checkpoint;\r
1097         self.race_respawn_spotref = spot;\r
1098 \r
1099         self.race_place = 0;\r
1100 }\r
1101 \r
1102 void race_PreSpawnObserver()\r
1103 {\r
1104         if(!g_race && !g_cts)\r
1105                 return;\r
1106         race_PreparePlayer();\r
1107         self.race_checkpoint = -1;\r
1108 }\r
1109 \r
1110 void spawnfunc_info_player_race (void)\r
1111 {\r
1112         if(!g_race && !g_cts)\r
1113         {\r
1114                 remove(self);\r
1115                 return;\r
1116         }\r
1117         ++race_spawns;\r
1118         spawnfunc_info_player_deathmatch();\r
1119 \r
1120         if(self.race_place > race_highest_place_spawn)\r
1121                 race_highest_place_spawn = self.race_place;\r
1122         if(self.race_place < race_lowest_place_spawn)\r
1123                 race_lowest_place_spawn = self.race_place;\r
1124 }\r
1125 \r
1126 void race_ClearRecords()\r
1127 {\r
1128         float i;\r
1129         entity e;\r
1130 \r
1131         for(i = 0; i < MAX_CHECKPOINTS; ++i)\r
1132         {\r
1133                 race_checkpoint_records[i] = 0;\r
1134                 if(race_checkpoint_recordholders[i])\r
1135                         strunzone(race_checkpoint_recordholders[i]);\r
1136                 race_checkpoint_recordholders[i] = string_null;\r
1137         }\r
1138 \r
1139         e = self;\r
1140         FOR_EACH_CLIENT(self)\r
1141         {\r
1142                 float p;\r
1143                 p = self.race_place;\r
1144                 race_PreparePlayer();\r
1145                 self.race_place = p;\r
1146         }\r
1147         self = e;\r
1148 }\r
1149 \r
1150 void race_ReadyRestart()\r
1151 {\r
1152         float s;\r
1153 \r
1154         Score_NicePrint(world);\r
1155 \r
1156         race_ClearRecords();\r
1157         PlayerScore_Sort(race_place);\r
1158 \r
1159         entity e;\r
1160         FOR_EACH_CLIENT(e)\r
1161         {\r
1162                 if(e.race_place)\r
1163                 {\r
1164                         s = PlayerScore_Add(e, SP_RACE_FASTEST, 0);\r
1165                         if(!s)\r
1166                                 e.race_place = 0;\r
1167                 }\r
1168                 print(e.netname, " = ", ftos(e.race_place), "\n");\r
1169         }\r
1170 \r
1171         if(g_race_qualifying == 2)\r
1172         {\r
1173                 g_race_qualifying = 0;\r
1174                 independent_players = 0;\r
1175                 cvar_set("fraglimit", ftos(race_fraglimit));\r
1176                 cvar_set("leadlimit", ftos(race_leadlimit));\r
1177                 cvar_set("timelimit", ftos(race_timelimit));\r
1178                 ScoreRules_race();\r
1179         }\r
1180 }\r
1181 \r
1182 void race_ImposePenaltyTime(entity pl, float penalty, string reason)\r
1183 {\r
1184         if(g_race_qualifying)\r
1185         {\r
1186                 pl.race_penalty_accumulator += penalty;\r
1187                 msg_entity = pl;\r
1188                 WRITESPECTATABLE_MSG_ONE({\r
1189                         WriteByte(MSG_ONE, SVC_TEMPENTITY);\r
1190                         WriteByte(MSG_ONE, TE_CSQC_RACE);\r
1191                         WriteByte(MSG_ONE, RACE_NET_PENALTY_QUALIFYING);\r
1192                         WriteShort(MSG_ONE, TIME_ENCODE(penalty));\r
1193                         WriteString(MSG_ONE, reason);\r
1194                 });\r
1195         }\r
1196         else\r
1197         {\r
1198                 pl.race_penalty = time + penalty;\r
1199                 msg_entity = pl;\r
1200                 WRITESPECTATABLE_MSG_ONE_VARNAME(dummy, {\r
1201                         WriteByte(MSG_ONE, SVC_TEMPENTITY);\r
1202                         WriteByte(MSG_ONE, TE_CSQC_RACE);\r
1203                         WriteByte(MSG_ONE, RACE_NET_PENALTY_RACE);\r
1204                         WriteShort(MSG_ONE, TIME_ENCODE(penalty));\r
1205                         WriteString(MSG_ONE, reason);\r
1206                 });\r
1207         }\r
1208 }\r
1209 \r
1210 void penalty_touch()\r
1211 {\r
1212         EXACTTRIGGER_TOUCH;\r
1213         if(other.race_lastpenalty != self)\r
1214         {\r
1215                 other.race_lastpenalty = self;\r
1216                 race_ImposePenaltyTime(other, self.race_penalty, self.race_penalty_reason);\r
1217         }\r
1218 }\r
1219 \r
1220 void penalty_use()\r
1221 {\r
1222         race_ImposePenaltyTime(activator, self.race_penalty, self.race_penalty_reason);\r
1223 }\r
1224 \r
1225 void spawnfunc_trigger_race_penalty()\r
1226 {\r
1227         EXACTTRIGGER_INIT;\r
1228 \r
1229         self.use = penalty_use;\r
1230         if not(self.spawnflags & 1)\r
1231                 self.touch = penalty_touch;\r
1232 \r
1233         if (!self.race_penalty_reason)\r
1234                 self.race_penalty_reason = "missing a checkpoint";\r
1235         if (!self.race_penalty)\r
1236                 self.race_penalty = 5;\r
1237 }\r
1238 \r
1239 float race_GetFractionalLapCount(entity e)\r
1240 {\r
1241         // interesting metrics (idea by KrimZon) to maybe sort players in the\r
1242         // scoreboard, immediately updates when overtaking\r
1243         //\r
1244         // requires the track to be built so you never get farther away from the\r
1245         // next checkpoint, though, and current Voretournament race maps are not built that\r
1246         // way\r
1247         //\r
1248         // also, this code is slow and would need optimization (i.e. "next CP"\r
1249         // links on CP entities)\r
1250 \r
1251         float l;\r
1252         l = PlayerScore_Add(e, SP_RACE_LAPS, 0);\r
1253         if(e.race_completed)\r
1254                 return l; // not fractional\r
1255         \r
1256         vector o0, o1;\r
1257         float bestfraction, fraction;\r
1258         entity lastcp, cp0, cp1;\r
1259         float nextcpindex, lastcpindex;\r
1260 \r
1261         nextcpindex = max(e.race_checkpoint, 0);\r
1262         lastcpindex = e.race_respawn_checkpoint;\r
1263         lastcp = e.race_respawn_spotref;\r
1264 \r
1265         if(nextcpindex == lastcpindex)\r
1266                 return l; // finish\r
1267         \r
1268         bestfraction = 1;\r
1269         for(cp0 = world; (cp0 = find(cp0, classname, "trigger_race_checkpoint")); )\r
1270         {\r
1271                 if(cp0.race_checkpoint != lastcpindex)\r
1272                         continue;\r
1273                 if(lastcp)\r
1274                         if(cp0 != lastcp)\r
1275                                 continue;\r
1276                 o0 = (cp0.absmin + cp0.absmax) * 0.5;\r
1277                 for(cp1 = world; (cp1 = find(cp1, classname, "trigger_race_checkpoint")); )\r
1278                 {\r
1279                         if(cp1.race_checkpoint != nextcpindex)\r
1280                                 continue;\r
1281                         o1 = (cp1.absmin + cp1.absmax) * 0.5;\r
1282                         if(o0 == o1)\r
1283                                 continue;\r
1284                         fraction = bound(0.0001, vlen(e.origin - o1) / vlen(o0 - o1), 1);\r
1285                         if(fraction < bestfraction)\r
1286                                 bestfraction = fraction;\r
1287                 }\r
1288         }\r
1289 \r
1290         // we are at CP "nextcpindex - bestfraction"\r
1291         // race_timed_checkpoint == 4: then nextcp==4 means 0.9999x, nextcp==0 means 0.0000x\r
1292         // race_timed_checkpoint == 0: then nextcp==0 means 0.9999x\r
1293         float c, nc;\r
1294         nc = race_highest_checkpoint + 1;\r
1295         c = (mod(nextcpindex - race_timed_checkpoint + nc + nc - 1, nc) + 1) - bestfraction;\r
1296 \r
1297         return l + c / nc;\r
1298 }\r