]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/gamemodes/gamemode/race/sv_race.qc
Further cleanup of defs.qh
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / gamemodes / gamemode / race / sv_race.qc
1 #include "sv_race.qh"
2
3 #include <server/client.qh>
4 #include <server/g_world.qh>
5 #include <server/gamelog.qh>
6 #include <server/race.qh>
7 #include <common/ent_cs.qh>
8 #include <common/mapobjects/triggers.qh>
9
10 #define autocvar_g_race_laps_limit cvar("g_race_laps_limit")
11 float autocvar_g_race_qualifying_timelimit;
12 float autocvar_g_race_qualifying_timelimit_override;
13 int autocvar_g_race_teams;
14
15 // legacy bot roles
16 .float race_checkpoint;
17 void havocbot_role_race(entity this)
18 {
19         if(IS_DEAD(this))
20                 return;
21
22         if (navigation_goalrating_timeout(this))
23         {
24                 navigation_goalrating_start(this);
25
26                 bool raw_touch_check = true;
27                 int cp = this.race_checkpoint;
28
29                 LABEL(search_racecheckpoints)
30                 IL_EACH(g_racecheckpoints, true,
31                 {
32                         if(it.cnt == cp || cp == -1)
33                         {
34                                 // redirect bot to next goal if it touched the waypoint of an untouchable checkpoint
35                                 // e.g. checkpoint in front of Stormkeep's warpzone
36                                 // the same workaround is applied in CTS game mode
37                                 if (raw_touch_check && vdist(this.origin - it.nearestwaypoint.origin, <, 30))
38                                 {
39                                         cp = race_NextCheckpoint(cp);
40                                         raw_touch_check = false;
41                                         goto search_racecheckpoints;
42                                 }
43                                 navigation_routerating(this, it, 1000000, 5000);
44                         }
45                 });
46
47                 navigation_goalrating_end(this);
48
49                 navigation_goalrating_timeout_set(this);
50         }
51 }
52
53 void race_ScoreRules()
54 {
55     GameRules_score_enabled(false);
56         GameRules_scoring(race_teams, 0, 0, {
57         if (race_teams) {
58             field_team(ST_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
59             field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
60             field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
61             field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
62         } else if (g_race_qualifying) {
63             field(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
64         } else {
65             field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
66             field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
67             field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
68         }
69         });
70 }
71
72 void race_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
73 {
74         if(autocvar_sv_eventlog)
75                 GameLogEcho(strcat(":race:", mode, ":", ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
76 }
77
78 float WinningCondition_Race(float fraglimit)
79 {
80         float wc;
81         float n, c;
82
83         n = 0;
84         c = 0;
85         FOREACH_CLIENT(IS_PLAYER(it), {
86                 ++n;
87                 if(CS(it).race_completed)
88                         ++c;
89         });
90         if(n && (n == c))
91                 return WINNING_YES;
92         wc = WinningCondition_Scores(fraglimit, 0);
93
94         // ALWAYS initiate overtime, unless EVERYONE has finished the race!
95         if(wc == WINNING_YES || wc == WINNING_STARTSUDDENDEATHOVERTIME)
96         // do NOT support equality when the laps are all raced!
97                 return WINNING_STARTSUDDENDEATHOVERTIME;
98         else
99                 return WINNING_NEVER;
100 }
101
102 float WinningCondition_QualifyingThenRace(float limit)
103 {
104         float wc;
105         wc = WinningCondition_Scores(limit, 0);
106
107         // NEVER initiate overtime
108         if(wc == WINNING_YES || wc == WINNING_STARTSUDDENDEATHOVERTIME)
109         {
110                 return WINNING_YES;
111         }
112
113         return wc;
114 }
115
116 MUTATOR_HOOKFUNCTION(rc, ClientKill)
117 {
118         if(g_race_qualifying)
119                 M_ARGV(1, float) = 0; // killtime
120 }
121
122 MUTATOR_HOOKFUNCTION(rc, AbortSpeedrun)
123 {
124         entity player = M_ARGV(0, entity);
125
126         if(autocvar_g_allow_checkpoints)
127                 race_PreparePlayer(player); // nice try
128 }
129
130 MUTATOR_HOOKFUNCTION(rc, PlayerPhysics)
131 {
132         entity player = M_ARGV(0, entity);
133         float dt = M_ARGV(1, float);
134
135         player.race_movetime_frac += dt;
136         float f = floor(player.race_movetime_frac);
137         player.race_movetime_frac -= f;
138         player.race_movetime_count += f;
139         player.race_movetime = player.race_movetime_frac + player.race_movetime_count;
140
141 #ifdef SVQC
142         if(IS_PLAYER(player))
143         {
144                 if (player.race_penalty)
145                         if (time > player.race_penalty)
146                                 player.race_penalty = 0;
147                 if(player.race_penalty)
148                 {
149                         player.velocity = '0 0 0';
150                         set_movetype(player, MOVETYPE_NONE);
151                         player.disableclientprediction = 2;
152                 }
153         }
154 #endif
155
156         // force kbd movement for fairness
157         float wishspeed;
158         vector wishvel;
159
160         // if record times matter
161         // ensure nothing EVIL is being done (i.e. div0_evade)
162         // this hinders joystick users though
163         // but it still gives SOME analog control
164         wishvel.x = fabs(CS(player).movement.x);
165         wishvel.y = fabs(CS(player).movement.y);
166         if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y)
167         {
168                 wishvel.z = 0;
169                 wishspeed = vlen(wishvel);
170                 if(wishvel.x >= 2 * wishvel.y)
171                 {
172                         // pure X motion
173                         if(CS(player).movement.x > 0)
174                                 CS(player).movement_x = wishspeed;
175                         else
176                                 CS(player).movement_x = -wishspeed;
177                         CS(player).movement_y = 0;
178                 }
179                 else if(wishvel.y >= 2 * wishvel.x)
180                 {
181                         // pure Y motion
182                         CS(player).movement_x = 0;
183                         if(CS(player).movement.y > 0)
184                                 CS(player).movement_y = wishspeed;
185                         else
186                                 CS(player).movement_y = -wishspeed;
187                 }
188                 else
189                 {
190                         // diagonal
191                         if(CS(player).movement.x > 0)
192                                 CS(player).movement_x = M_SQRT1_2 * wishspeed;
193                         else
194                                 CS(player).movement_x = -M_SQRT1_2 * wishspeed;
195                         if(CS(player).movement.y > 0)
196                                 CS(player).movement_y = M_SQRT1_2 * wishspeed;
197                         else
198                                 CS(player).movement_y = -M_SQRT1_2 * wishspeed;
199                 }
200         }
201 }
202
203 MUTATOR_HOOKFUNCTION(rc, reset_map_global)
204 {
205         float s;
206
207         Score_NicePrint(NULL);
208
209         race_ClearRecords();
210         PlayerScore_Sort(race_place, 0, 1, 0);
211
212         FOREACH_CLIENT(true, {
213                 if(it.race_place)
214                 {
215                         s = GameRules_scoring_add(it, RACE_FASTEST, 0);
216                         if(!s)
217                                 it.race_place = 0;
218                 }
219                 race_EventLog(ftos(it.race_place), it);
220         });
221
222         if(g_race_qualifying == 2)
223         {
224                 g_race_qualifying = 0;
225                 independent_players = 0;
226                 cvar_set("fraglimit", ftos(race_fraglimit));
227                 cvar_set("leadlimit", ftos(race_leadlimit));
228                 cvar_set("timelimit", ftos(race_timelimit));
229                 race_ScoreRules();
230         }
231 }
232
233 MUTATOR_HOOKFUNCTION(rc, ClientConnect)
234 {
235         entity player = M_ARGV(0, entity);
236
237         race_PreparePlayer(player);
238         player.race_checkpoint = -1;
239
240         string rr = RACE_RECORD;
241
242         if(IS_REAL_CLIENT(player))
243         {
244                 msg_entity = player;
245                 race_send_recordtime(MSG_ONE);
246                 race_send_speedaward(MSG_ONE);
247
248                 speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed")));
249                 speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp")));
250                 race_send_speedaward_alltimebest(MSG_ONE);
251
252                 float i;
253                 int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
254                 race_send_rankings_cnt(MSG_ONE);
255                 for (i = 1; i <= m; ++i)
256                 {
257                         race_SendRankings(i, 0, 0, MSG_ONE);
258                 }
259         }
260 }
261
262 MUTATOR_HOOKFUNCTION(rc, MakePlayerObserver)
263 {
264         entity player = M_ARGV(0, entity);
265
266         if(g_race_qualifying)
267         {
268                 if(GameRules_scoring_add(player, RACE_FASTEST, 0))
269                         player.frags = FRAGS_PLAYER_OUT_OF_GAME;
270                 else
271                         player.frags = FRAGS_SPECTATOR;
272         }
273
274         race_PreparePlayer(player);
275         player.race_checkpoint = -1;
276 }
277
278 MUTATOR_HOOKFUNCTION(rc, PlayerSpawn)
279 {
280         entity player = M_ARGV(0, entity);
281         entity spawn_spot = M_ARGV(1, entity);
282
283         if(spawn_spot.target == "")
284                 // Emergency: this wasn't a real spawnpoint. Can this ever happen?
285                 race_PreparePlayer(player);
286
287         // if we need to respawn, do it right
288         player.race_respawn_checkpoint = player.race_checkpoint;
289         player.race_respawn_spotref = spawn_spot;
290
291         player.race_place = 0;
292 }
293
294 MUTATOR_HOOKFUNCTION(rc, PutClientInServer)
295 {
296         entity player = M_ARGV(0, entity);
297
298         if(IS_PLAYER(player))
299         if(!game_stopped)
300         {
301                 if(CS(player).killcount == FRAGS_SPECTATOR /* initial spawn */ || g_race_qualifying) // spawn
302                         race_PreparePlayer(player);
303                 else // respawn
304                         race_RetractPlayer(player);
305
306                 race_AbandonRaceCheck(player);
307         }
308 }
309
310 MUTATOR_HOOKFUNCTION(rc, PlayerDies)
311 {
312         entity frag_target = M_ARGV(2, entity);
313
314         frag_target.respawn_flags |= RESPAWN_FORCE;
315         race_AbandonRaceCheck(frag_target);
316 }
317
318 MUTATOR_HOOKFUNCTION(rc, HavocBot_ChooseRole)
319 {
320         entity bot = M_ARGV(0, entity);
321
322         bot.havocbot_role = havocbot_role_race;
323         return true;
324 }
325
326 MUTATOR_HOOKFUNCTION(rc, GetPressedKeys)
327 {
328         entity player = M_ARGV(0, entity);
329
330         if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
331         {
332                 if (!player.stored_netname)
333                         player.stored_netname = strzone(uid2name(player.crypto_idfp));
334                 if(player.stored_netname != player.netname)
335                 {
336                         db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
337                         strcpy(player.stored_netname, player.netname);
338                 }
339         }
340
341         if (!IS_OBSERVER(player))
342         {
343                 if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed))
344                 {
345                         speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1');
346                         speedaward_holder = player.netname;
347                         speedaward_uid = player.crypto_idfp;
348                         speedaward_lastupdate = time;
349                 }
350                 if (speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1)
351                 {
352                         string rr = RACE_RECORD;
353                         race_send_speedaward(MSG_ALL);
354                         speedaward_lastsent = speedaward_speed;
355                         if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "")
356                         {
357                                 speedaward_alltimebest = speedaward_speed;
358                                 speedaward_alltimebest_holder = speedaward_holder;
359                                 speedaward_alltimebest_uid = speedaward_uid;
360                                 db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest));
361                                 db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid);
362                                 race_send_speedaward_alltimebest(MSG_ALL);
363                         }
364                 }
365         }
366 }
367
368 MUTATOR_HOOKFUNCTION(rc, ForbidPlayerScore_Clear)
369 {
370         if(g_race_qualifying)
371                 return true; // in qualifying, you don't lose score by observing
372 }
373
374 MUTATOR_HOOKFUNCTION(rc, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
375 {
376         M_ARGV(0, float) = race_teams;
377         return true;
378 }
379
380 MUTATOR_HOOKFUNCTION(rc, Scores_CountFragsRemaining)
381 {
382         // announce remaining frags if not in qualifying mode
383         if(!g_race_qualifying)
384                 return true;
385 }
386
387 MUTATOR_HOOKFUNCTION(rc, GetRecords)
388 {
389         int record_page = M_ARGV(0, int);
390         string ret_string = M_ARGV(1, string);
391
392         for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
393         {
394                 if(MapInfo_Get_ByID(i))
395                 {
396                         float r = race_readTime(MapInfo_Map_bspname, 1);
397
398                         if(!r)
399                                 continue;
400
401                         string h = race_readName(MapInfo_Map_bspname, 1);
402                         ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n");
403                 }
404         }
405
406         M_ARGV(1, string) = ret_string;
407 }
408
409 MUTATOR_HOOKFUNCTION(rc, HideTeamNagger)
410 {
411         return true; // doesn't work so well
412 }
413
414 MUTATOR_HOOKFUNCTION(rc, FixClientCvars)
415 {
416         entity player = M_ARGV(0, entity);
417
418         stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n");
419 }
420
421 MUTATOR_HOOKFUNCTION(rc, CheckRules_World)
422 {
423         float checkrules_timelimit = M_ARGV(1, float);
424         float checkrules_fraglimit = M_ARGV(2, float);
425
426         if(checkrules_timelimit >= 0)
427         {
428                 if(!g_race_qualifying)
429                 {
430                         M_ARGV(0, float) = WinningCondition_Race(checkrules_fraglimit);
431                         return true;
432                 }
433                 else if(g_race_qualifying == 2)
434                 {
435                         M_ARGV(0, float) = WinningCondition_QualifyingThenRace(checkrules_fraglimit);
436                         return true;
437                 }
438         }
439 }
440
441 MUTATOR_HOOKFUNCTION(rc, ReadLevelCvars)
442 {
443         if(g_race_qualifying == 2)
444                 warmup_stage = 0;
445 }
446
447 void race_Initialize()
448 {
449         race_ScoreRules();
450         if(g_race_qualifying == 2)
451                 warmup_stage = 0;
452         radar_showenemies = true;
453 }
454
455 void rc_SetLimits()
456 {
457         int fraglimit_override, leadlimit_override;
458         float timelimit_override, qualifying_override;
459
460         if(autocvar_g_race_teams)
461         {
462                 GameRules_teams(true);
463                 race_teams = BITS(bound(2, autocvar_g_race_teams, 4));
464         }
465         else
466                 race_teams = 0;
467
468         qualifying_override = autocvar_g_race_qualifying_timelimit_override;
469         fraglimit_override = autocvar_g_race_laps_limit;
470         leadlimit_override = 0; // currently not supported by race
471         timelimit_override = autocvar_timelimit_override;
472
473         float want_qualifying = ((qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit) > 0;
474
475         if(autocvar_g_campaign)
476         {
477                 g_race_qualifying = 1;
478                 independent_players = 1;
479         }
480         else if(want_qualifying)
481         {
482                 g_race_qualifying = 2;
483                 independent_players = 1;
484                 race_fraglimit = (fraglimit_override >= 0) ? fraglimit_override : autocvar_fraglimit;
485                 race_leadlimit = (leadlimit_override >= 0) ? leadlimit_override : autocvar_leadlimit;
486                 race_timelimit = (timelimit_override >= 0) ? timelimit_override : autocvar_timelimit;
487                 qualifying_override = (qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit;
488                 fraglimit_override = 0;
489                 leadlimit_override = 0;
490                 timelimit_override = qualifying_override;
491         }
492         else
493                 g_race_qualifying = 0;
494     GameRules_limit_score(fraglimit_override);
495     GameRules_limit_lead(leadlimit_override);
496     GameRules_limit_time(timelimit_override);
497     GameRules_limit_time_qualifying(qualifying_override);
498 }