]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_race.qc
34196b38e336f23809aae036d2d28655912afdd8
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_race.qc
1 #include "gamemode_race.qh"
2
3 #include "gamemode.qh"
4
5 #include "../race.qh"
6
7 // legacy bot roles
8 .float race_checkpoint;
9 void havocbot_role_race()
10 {SELFPARAM();
11         if(self.deadflag != DEAD_NO)
12                 return;
13
14         entity e;
15         if (self.bot_strategytime < time)
16         {
17                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
18                 navigation_goalrating_start();
19
20                 for(e = world; (e = find(e, classname, "trigger_race_checkpoint")) != world; )
21                 {
22                         if(e.cnt == self.race_checkpoint)
23                         {
24                                 navigation_routerating(e, 1000000, 5000);
25                         }
26                         else if(self.race_checkpoint == -1)
27                         {
28                                 navigation_routerating(e, 1000000, 5000);
29                         }
30                 }
31
32                 navigation_goalrating_end();
33         }
34 }
35
36 void race_ScoreRules()
37 {
38         ScoreRules_basics(race_teams, 0, 0, false);
39         if(race_teams)
40         {
41                 ScoreInfo_SetLabel_TeamScore(  ST_RACE_LAPS,    "laps",      SFL_SORT_PRIO_PRIMARY);
42                 ScoreInfo_SetLabel_PlayerScore(SP_RACE_LAPS,    "laps",      SFL_SORT_PRIO_PRIMARY);
43                 ScoreInfo_SetLabel_PlayerScore(SP_RACE_TIME,    "time",      SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
44                 ScoreInfo_SetLabel_PlayerScore(SP_RACE_FASTEST, "fastest",   SFL_LOWER_IS_BETTER | SFL_TIME);
45         }
46         else if(g_race_qualifying)
47         {
48                 ScoreInfo_SetLabel_PlayerScore(SP_RACE_FASTEST, "fastest",   SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
49         }
50         else
51         {
52                 ScoreInfo_SetLabel_PlayerScore(SP_RACE_LAPS,    "laps",      SFL_SORT_PRIO_PRIMARY);
53                 ScoreInfo_SetLabel_PlayerScore(SP_RACE_TIME,    "time",      SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
54                 ScoreInfo_SetLabel_PlayerScore(SP_RACE_FASTEST, "fastest",   SFL_LOWER_IS_BETTER | SFL_TIME);
55         }
56         ScoreRules_basics_end();
57 }
58
59 void race_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
60 {
61         if(autocvar_sv_eventlog)
62                 GameLogEcho(strcat(":race:", mode, ":", ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
63 }
64
65 MUTATOR_HOOKFUNCTION(rc, PlayerPhysics)
66 {SELFPARAM();
67         // force kbd movement for fairness
68         float wishspeed;
69         vector wishvel;
70
71         // if record times matter
72         // ensure nothing EVIL is being done (i.e. div0_evade)
73         // this hinders joystick users though
74         // but it still gives SOME analog control
75         wishvel.x = fabs(self.movement.x);
76         wishvel.y = fabs(self.movement.y);
77         if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y)
78         {
79                 wishvel.z = 0;
80                 wishspeed = vlen(wishvel);
81                 if(wishvel.x >= 2 * wishvel.y)
82                 {
83                         // pure X motion
84                         if(self.movement.x > 0)
85                                 self.movement_x = wishspeed;
86                         else
87                                 self.movement_x = -wishspeed;
88                         self.movement_y = 0;
89                 }
90                 else if(wishvel.y >= 2 * wishvel.x)
91                 {
92                         // pure Y motion
93                         self.movement_x = 0;
94                         if(self.movement.y > 0)
95                                 self.movement_y = wishspeed;
96                         else
97                                 self.movement_y = -wishspeed;
98                 }
99                 else
100                 {
101                         // diagonal
102                         if(self.movement.x > 0)
103                                 self.movement_x = M_SQRT1_2 * wishspeed;
104                         else
105                                 self.movement_x = -M_SQRT1_2 * wishspeed;
106                         if(self.movement.y > 0)
107                                 self.movement_y = M_SQRT1_2 * wishspeed;
108                         else
109                                 self.movement_y = -M_SQRT1_2 * wishspeed;
110                 }
111         }
112
113         return false;
114 }
115
116 MUTATOR_HOOKFUNCTION(rc, reset_map_global)
117 {
118         float s;
119
120         Score_NicePrint(world);
121
122         race_ClearRecords();
123         PlayerScore_Sort(race_place, 0, 1, 0);
124
125         entity e;
126         FOR_EACH_CLIENT(e)
127         {
128                 if(e.race_place)
129                 {
130                         s = PlayerScore_Add(e, SP_RACE_FASTEST, 0);
131                         if(!s)
132                                 e.race_place = 0;
133                 }
134                 race_EventLog(ftos(e.race_place), e);
135         }
136
137         if(g_race_qualifying == 2)
138         {
139                 g_race_qualifying = 0;
140                 independent_players = 0;
141                 cvar_set("fraglimit", ftos(race_fraglimit));
142                 cvar_set("leadlimit", ftos(race_leadlimit));
143                 cvar_set("timelimit", ftos(race_timelimit));
144                 race_ScoreRules();
145         }
146
147         return false;
148 }
149
150 MUTATOR_HOOKFUNCTION(rc, PlayerPreThink)
151 {SELFPARAM();
152         if(IS_SPEC(self) || IS_OBSERVER(self))
153         if(g_race_qualifying)
154         if(msg_entity.enemy.race_laptime)
155                 race_SendNextCheckpoint(msg_entity.enemy, 1);
156
157         return false;
158 }
159
160 MUTATOR_HOOKFUNCTION(rc, ClientConnect)
161 {SELFPARAM();
162         race_PreparePlayer();
163         self.race_checkpoint = -1;
164
165         string rr = RACE_RECORD;
166
167         if(IS_REAL_CLIENT(self))
168         {
169                 msg_entity = self;
170                 race_send_recordtime(MSG_ONE);
171                 race_send_speedaward(MSG_ONE);
172
173                 speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed")));
174                 speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp")));
175                 race_send_speedaward_alltimebest(MSG_ONE);
176
177                 float i;
178                 for (i = 1; i <= RANKINGS_CNT; ++i)
179                 {
180                         race_SendRankings(i, 0, 0, MSG_ONE);
181                 }
182         }
183
184         return false;
185 }
186
187 MUTATOR_HOOKFUNCTION(rc, MakePlayerObserver)
188 {SELFPARAM();
189         if(g_race_qualifying)
190         if(PlayerScore_Add(self, SP_RACE_FASTEST, 0))
191                 self.frags = FRAGS_LMS_LOSER;
192         else
193                 self.frags = FRAGS_SPECTATOR;
194
195         race_PreparePlayer();
196         self.race_checkpoint = -1;
197
198         return false;
199 }
200
201 MUTATOR_HOOKFUNCTION(rc, PlayerSpawn)
202 {SELFPARAM();
203         if(spawn_spot.target == "")
204                 // Emergency: this wasn't a real spawnpoint. Can this ever happen?
205                 race_PreparePlayer();
206
207         // if we need to respawn, do it right
208         self.race_respawn_checkpoint = self.race_checkpoint;
209         self.race_respawn_spotref = spawn_spot;
210
211         self.race_place = 0;
212
213         return false;
214 }
215
216 MUTATOR_HOOKFUNCTION(rc, PutClientInServer)
217 {SELFPARAM();
218         if(IS_PLAYER(self))
219         if(!gameover)
220         {
221                 if(self.killcount == -666 /* initial spawn */ || g_race_qualifying) // spawn
222                         race_PreparePlayer();
223                 else // respawn
224                         race_RetractPlayer();
225
226                 race_AbandonRaceCheck(self);
227         }
228         return false;
229 }
230
231 MUTATOR_HOOKFUNCTION(rc, PlayerDies)
232 {SELFPARAM();
233         self.respawn_flags |= RESPAWN_FORCE;
234         race_AbandonRaceCheck(self);
235         return false;
236 }
237
238 MUTATOR_HOOKFUNCTION(rc, HavocBot_ChooseRole)
239 {SELFPARAM();
240         self.havocbot_role = havocbot_role_race;
241         return true;
242 }
243
244 MUTATOR_HOOKFUNCTION(rc, GetPressedKeys)
245 {SELFPARAM();
246         if(self.cvar_cl_allow_uidtracking == 1 && self.cvar_cl_allow_uid2name == 1)
247         {
248                 if (!self.stored_netname)
249                         self.stored_netname = strzone(uid2name(self.crypto_idfp));
250                 if(self.stored_netname != self.netname)
251                 {
252                         db_put(ServerProgsDB, strcat("/uid2name/", self.crypto_idfp), self.netname);
253                         strunzone(self.stored_netname);
254                         self.stored_netname = strzone(self.netname);
255                 }
256         }
257
258         return false;
259 }
260
261 MUTATOR_HOOKFUNCTION(rc, ForbidPlayerScore_Clear)
262 {
263         if(g_race_qualifying)
264                 return true; // in qualifying, you don't lose score by observing
265
266         return false;
267 }
268
269 MUTATOR_HOOKFUNCTION(rc, GetTeamCount, CBC_ORDER_EXCLUSIVE)
270 {
271         ret_float = race_teams;
272         return false;
273 }
274
275 MUTATOR_HOOKFUNCTION(rc, Scores_CountFragsRemaining)
276 {
277         // announce remaining frags if not in qualifying mode
278         if(!g_race_qualifying)
279                 return true;
280
281         return false;
282 }
283
284 MUTATOR_HOOKFUNCTION(rc, GetRecords)
285 {
286         for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
287         {
288                 if(MapInfo_Get_ByID(i))
289                 {
290                         float r = race_readTime(MapInfo_Map_bspname, 1);
291
292                         if(!r)
293                                 continue;
294
295                         string h = race_readName(MapInfo_Map_bspname, 1);
296                         ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n");
297                 }
298         }
299
300         return false;
301 }
302
303 void race_Initialize()
304 {
305         race_ScoreRules();
306         if(g_race_qualifying == 2)
307                 warmup_stage = 0;
308 }
309
310 void rc_SetLimits()
311 {
312         int fraglimit_override, leadlimit_override;
313         float timelimit_override, qualifying_override;
314
315         if(autocvar_g_race_teams)
316         {
317                 ActivateTeamplay();
318                 race_teams = bound(2, autocvar_g_race_teams, 4);
319                 have_team_spawns = -1; // request team spawns
320         }
321         else
322                 race_teams = 0;
323
324         qualifying_override = autocvar_g_race_qualifying_timelimit_override;
325         fraglimit_override = autocvar_g_race_laps_limit;
326         leadlimit_override = 0; // currently not supported by race
327         timelimit_override = -1; // use default if we don't set it below
328
329         // we need to find out the correct value for g_race_qualifying
330         float want_qualifying = ((qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit) > 0;
331
332         if(autocvar_g_campaign)
333         {
334                 g_race_qualifying = 1;
335                 independent_players = 1;
336         }
337         else if(!autocvar_g_campaign && want_qualifying)
338         {
339                 g_race_qualifying = 2;
340                 independent_players = 1;
341                 race_fraglimit = (race_fraglimit >= 0) ? fraglimit_override : autocvar_fraglimit;
342                 race_leadlimit = (race_leadlimit >= 0) ? leadlimit_override : autocvar_leadlimit;
343                 race_timelimit = (race_timelimit >= 0) ? timelimit_override : autocvar_timelimit;
344                 fraglimit_override = 0;
345                 leadlimit_override = 0;
346                 timelimit_override = autocvar_g_race_qualifying_timelimit;
347         }
348         else
349                 g_race_qualifying = 0;
350
351         SetLimits(fraglimit_override, leadlimit_override, timelimit_override, qualifying_override);
352 }
353
354 REGISTER_MUTATOR(rc, g_race)
355 {
356         rc_SetLimits();
357
358         MUTATOR_ONADD
359         {
360                 if(time > 1) // game loads at time 1
361                         error("This is a game type and it cannot be added at runtime.");
362                 race_Initialize();
363         }
364
365         MUTATOR_ONROLLBACK_OR_REMOVE
366         {
367                 // we actually cannot roll back race_Initialize here
368                 // BUT: we don't need to! If this gets called, adding always
369                 // succeeds.
370         }
371
372         MUTATOR_ONREMOVE
373         {
374                 LOG_INFO("This is a game type and it cannot be removed at runtime.");
375                 return -1;
376         }
377
378         return 0;
379 }