]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_race.qc
Merge branch 'TimePath/cleanup'
[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(race_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(race_ResetMap)
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(race_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(race_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(race_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(race_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(race_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(race_PlayerDies)
232 {SELFPARAM();
233         self.respawn_flags |= RESPAWN_FORCE;
234         race_AbandonRaceCheck(self);
235         return false;
236 }
237
238 MUTATOR_HOOKFUNCTION(race_BotRoles)
239 {SELFPARAM();
240         self.havocbot_role = havocbot_role_race;
241         return true;
242 }
243
244 MUTATOR_HOOKFUNCTION(race_PlayerPostThink)
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(race_ForbidClearPlayerScore)
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(race_GetTeamCount)
270 {
271         ret_float = race_teams;
272         return false;
273 }
274
275 MUTATOR_HOOKFUNCTION(race_CountFrags)
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 void race_Initialize()
285 {
286         race_ScoreRules();
287         if(g_race_qualifying == 2)
288                 warmup_stage = 0;
289 }
290
291 MUTATOR_DEFINITION(gamemode_race)
292 {
293         MUTATOR_HOOK(PlayerPhysics, race_PlayerPhysics, CBC_ORDER_ANY);
294         MUTATOR_HOOK(reset_map_global, race_ResetMap, CBC_ORDER_ANY);
295         MUTATOR_HOOK(PlayerPreThink, race_PlayerPreThink, CBC_ORDER_ANY);
296         MUTATOR_HOOK(ClientConnect, race_ClientConnect, CBC_ORDER_ANY);
297         MUTATOR_HOOK(MakePlayerObserver, race_MakePlayerObserver, CBC_ORDER_ANY);
298         MUTATOR_HOOK(PlayerSpawn, race_PlayerSpawn, CBC_ORDER_ANY);
299         MUTATOR_HOOK(PutClientInServer, race_PutClientInServer, CBC_ORDER_ANY);
300         MUTATOR_HOOK(PlayerDies, race_PlayerDies, CBC_ORDER_ANY);
301         MUTATOR_HOOK(HavocBot_ChooseRole, race_BotRoles, CBC_ORDER_ANY);
302         MUTATOR_HOOK(GetPressedKeys, race_PlayerPostThink, CBC_ORDER_ANY);
303         MUTATOR_HOOK(ForbidPlayerScore_Clear, race_ForbidClearPlayerScore, CBC_ORDER_ANY);
304         MUTATOR_HOOK(GetTeamCount, race_GetTeamCount, CBC_ORDER_ANY);
305         MUTATOR_HOOK(Scores_CountFragsRemaining, race_CountFrags, CBC_ORDER_ANY);
306
307         MUTATOR_ONADD
308         {
309                 if(time > 1) // game loads at time 1
310                         error("This is a game type and it cannot be added at runtime.");
311                 race_Initialize();
312         }
313
314         MUTATOR_ONROLLBACK_OR_REMOVE
315         {
316                 // we actually cannot roll back race_Initialize here
317                 // BUT: we don't need to! If this gets called, adding always
318                 // succeeds.
319         }
320
321         MUTATOR_ONREMOVE
322         {
323                 LOG_INFO("This is a game type and it cannot be removed at runtime.");
324                 return -1;
325         }
326
327         return 0;
328 }