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