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