]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/mutator/gamemode_cts.qc
Mutators: combine headers
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / mutator / gamemode_cts.qc
1 #ifndef GAMEMODE_CTS_H
2 #define GAMEMODE_CTS_H
3
4 // scores
5 const float ST_CTS_LAPS = 1;
6 const float SP_CTS_LAPS = 4;
7 const float SP_CTS_TIME = 5;
8 const float SP_CTS_FASTEST = 6;
9 #endif
10
11 #ifdef IMPLEMENTATION
12
13 #include "../../race.qh"
14
15 float autocvar_g_cts_finish_kill_delay;
16 bool autocvar_g_cts_selfdamage;
17
18 // legacy bot roles
19 .float race_checkpoint;
20 void havocbot_role_cts()
21 {SELFPARAM();
22         if(self.deadflag != DEAD_NO)
23                 return;
24
25         entity e;
26         if (self.bot_strategytime < time)
27         {
28                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
29                 navigation_goalrating_start();
30
31                 for(e = world; (e = find(e, classname, "trigger_race_checkpoint")) != world; )
32                 {
33                         if(e.cnt == self.race_checkpoint)
34                         {
35                                 navigation_routerating(e, 1000000, 5000);
36                         }
37                         else if(self.race_checkpoint == -1)
38                         {
39                                 navigation_routerating(e, 1000000, 5000);
40                         }
41                 }
42
43                 navigation_goalrating_end();
44         }
45 }
46
47 void cts_ScoreRules()
48 {
49         ScoreRules_basics(0, 0, 0, false);
50         if(g_race_qualifying)
51         {
52                 ScoreInfo_SetLabel_PlayerScore(SP_CTS_FASTEST, "fastest",   SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
53         }
54         else
55         {
56                 ScoreInfo_SetLabel_PlayerScore(SP_CTS_LAPS,    "laps",      SFL_SORT_PRIO_PRIMARY);
57                 ScoreInfo_SetLabel_PlayerScore(SP_CTS_TIME,    "time",      SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
58                 ScoreInfo_SetLabel_PlayerScore(SP_CTS_FASTEST, "fastest",   SFL_LOWER_IS_BETTER | SFL_TIME);
59         }
60         ScoreRules_basics_end();
61 }
62
63 void cts_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
64 {
65         if(autocvar_sv_eventlog)
66                 GameLogEcho(strcat(":cts:", mode, ":", ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
67 }
68
69 void CTS_ClientKill(entity e) // silent version of ClientKill, used when player finishes a CTS run. Useful to prevent cheating by running back to the start line and starting out with more speed
70 {
71     e.killindicator = spawn();
72     e.killindicator.owner = e;
73     e.killindicator.think = KillIndicator_Think;
74     e.killindicator.nextthink = time + (e.lip) * 0.05;
75     e.killindicator.cnt = ceil(autocvar_g_cts_finish_kill_delay);
76     e.killindicator.health = 1; // this is used to indicate that it should be silent
77     e.lip = 0;
78 }
79
80 MUTATOR_HOOKFUNCTION(cts, PlayerPhysics)
81 {SELFPARAM();
82         self.race_movetime_frac += PHYS_INPUT_TIMELENGTH;
83         float f = floor(self.race_movetime_frac);
84         self.race_movetime_frac -= f;
85         self.race_movetime_count += f;
86         self.race_movetime = self.race_movetime_frac + self.race_movetime_count;
87
88 #ifdef SVQC
89         if(IS_PLAYER(self))
90         {
91                 if (self.race_penalty)
92                         if (time > self.race_penalty)
93                                 self.race_penalty = 0;
94                 if(self.race_penalty)
95                 {
96                         self.velocity = '0 0 0';
97                         self.movetype = MOVETYPE_NONE;
98                         self.disableclientprediction = 2;
99                 }
100         }
101 #endif
102
103         // force kbd movement for fairness
104         float wishspeed;
105         vector wishvel;
106
107         // if record times matter
108         // ensure nothing EVIL is being done (i.e. div0_evade)
109         // this hinders joystick users though
110         // but it still gives SOME analog control
111         wishvel.x = fabs(self.movement.x);
112         wishvel.y = fabs(self.movement.y);
113         if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y)
114         {
115                 wishvel.z = 0;
116                 wishspeed = vlen(wishvel);
117                 if(wishvel.x >= 2 * wishvel.y)
118                 {
119                         // pure X motion
120                         if(self.movement.x > 0)
121                                 self.movement_x = wishspeed;
122                         else
123                                 self.movement_x = -wishspeed;
124                         self.movement_y = 0;
125                 }
126                 else if(wishvel.y >= 2 * wishvel.x)
127                 {
128                         // pure Y motion
129                         self.movement_x = 0;
130                         if(self.movement.y > 0)
131                                 self.movement_y = wishspeed;
132                         else
133                                 self.movement_y = -wishspeed;
134                 }
135                 else
136                 {
137                         // diagonal
138                         if(self.movement.x > 0)
139                                 self.movement_x = M_SQRT1_2 * wishspeed;
140                         else
141                                 self.movement_x = -M_SQRT1_2 * wishspeed;
142                         if(self.movement.y > 0)
143                                 self.movement_y = M_SQRT1_2 * wishspeed;
144                         else
145                                 self.movement_y = -M_SQRT1_2 * wishspeed;
146                 }
147         }
148
149         return false;
150 }
151
152 MUTATOR_HOOKFUNCTION(cts, reset_map_global)
153 {
154         float s;
155
156         Score_NicePrint(world);
157
158         race_ClearRecords();
159         PlayerScore_Sort(race_place, 0, 1, 0);
160
161         entity e;
162         FOR_EACH_CLIENT(e)
163         {
164                 if(e.race_place)
165                 {
166                         s = PlayerScore_Add(e, SP_RACE_FASTEST, 0);
167                         if(!s)
168                                 e.race_place = 0;
169                 }
170                 cts_EventLog(ftos(e.race_place), e);
171         }
172
173         if(g_race_qualifying == 2)
174         {
175                 g_race_qualifying = 0;
176                 independent_players = 0;
177                 cvar_set("fraglimit", ftos(race_fraglimit));
178                 cvar_set("leadlimit", ftos(race_leadlimit));
179                 cvar_set("timelimit", ftos(race_timelimit));
180                 cts_ScoreRules();
181         }
182
183         return false;
184 }
185
186 MUTATOR_HOOKFUNCTION(cts, PlayerPreThink)
187 {SELFPARAM();
188         if(IS_SPEC(self) || IS_OBSERVER(self))
189         if(g_race_qualifying)
190         if(msg_entity.enemy.race_laptime)
191                 race_SendNextCheckpoint(msg_entity.enemy, 1);
192
193         return false;
194 }
195
196 MUTATOR_HOOKFUNCTION(cts, ClientConnect)
197 {SELFPARAM();
198         race_PreparePlayer();
199         self.race_checkpoint = -1;
200
201         if(IS_REAL_CLIENT(self))
202         {
203                 string rr = CTS_RECORD;
204
205                 msg_entity = self;
206                 race_send_recordtime(MSG_ONE);
207                 race_send_speedaward(MSG_ONE);
208
209                 speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed")));
210                 speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp")));
211                 race_send_speedaward_alltimebest(MSG_ONE);
212
213                 float i;
214                 for (i = 1; i <= RANKINGS_CNT; ++i)
215                 {
216                         race_SendRankings(i, 0, 0, MSG_ONE);
217                 }
218         }
219
220         return false;
221 }
222
223 MUTATOR_HOOKFUNCTION(cts, MakePlayerObserver)
224 {SELFPARAM();
225         if(PlayerScore_Add(self, SP_RACE_FASTEST, 0))
226                 self.frags = FRAGS_LMS_LOSER;
227         else
228                 self.frags = FRAGS_SPECTATOR;
229
230         race_PreparePlayer();
231         self.race_checkpoint = -1;
232
233         return false;
234 }
235
236 MUTATOR_HOOKFUNCTION(cts, PlayerSpawn)
237 {SELFPARAM();
238         if(spawn_spot.target == "")
239                 // Emergency: this wasn't a real spawnpoint. Can this ever happen?
240                 race_PreparePlayer();
241
242         // if we need to respawn, do it right
243         self.race_respawn_checkpoint = self.race_checkpoint;
244         self.race_respawn_spotref = spawn_spot;
245
246         self.race_place = 0;
247
248         return false;
249 }
250
251 MUTATOR_HOOKFUNCTION(cts, PutClientInServer)
252 {SELFPARAM();
253         if(IS_PLAYER(self))
254         if(!gameover)
255         {
256                 if(self.killcount == -666 /* initial spawn */ || g_race_qualifying) // spawn
257                         race_PreparePlayer();
258                 else // respawn
259                         race_RetractPlayer();
260
261                 race_AbandonRaceCheck(self);
262         }
263         return false;
264 }
265
266 MUTATOR_HOOKFUNCTION(cts, PlayerDies)
267 {SELFPARAM();
268         self.respawn_flags |= RESPAWN_FORCE;
269         race_AbandonRaceCheck(self);
270         return false;
271 }
272
273 MUTATOR_HOOKFUNCTION(cts, HavocBot_ChooseRole)
274 {SELFPARAM();
275         self.havocbot_role = havocbot_role_cts;
276         return true;
277 }
278
279 MUTATOR_HOOKFUNCTION(cts, GetPressedKeys)
280 {SELFPARAM();
281         if(self.cvar_cl_allow_uidtracking == 1 && self.cvar_cl_allow_uid2name == 1)
282         {
283                 if (!self.stored_netname)
284                         self.stored_netname = strzone(uid2name(self.crypto_idfp));
285                 if(self.stored_netname != self.netname)
286                 {
287                         db_put(ServerProgsDB, strcat("/uid2name/", self.crypto_idfp), self.netname);
288                         strunzone(self.stored_netname);
289                         self.stored_netname = strzone(self.netname);
290                 }
291         }
292
293         if (!IS_OBSERVER(self))
294         {
295                 if (vlen(self.velocity - self.velocity_z * '0 0 1') > speedaward_speed)
296                 {
297                         speedaward_speed = vlen(self.velocity - self.velocity_z * '0 0 1');
298                         speedaward_holder = self.netname;
299                         speedaward_uid = self.crypto_idfp;
300                         speedaward_lastupdate = time;
301                 }
302                 if (speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1)
303                 {
304                         string rr = CTS_RECORD;
305                         race_send_speedaward(MSG_ALL);
306                         speedaward_lastsent = speedaward_speed;
307                         if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "")
308                         {
309                                 speedaward_alltimebest = speedaward_speed;
310                                 speedaward_alltimebest_holder = speedaward_holder;
311                                 speedaward_alltimebest_uid = speedaward_uid;
312                                 db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest));
313                                 db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid);
314                                 race_send_speedaward_alltimebest(MSG_ALL);
315                         }
316                 }
317         }
318
319         return false;
320 }
321
322 MUTATOR_HOOKFUNCTION(cts, ForbidThrowCurrentWeapon)
323 {
324         // no weapon dropping in CTS
325         return true;
326 }
327
328 MUTATOR_HOOKFUNCTION(cts, FilterItem)
329 {SELFPARAM();
330         if(self.classname == "droppedweapon")
331                 return true;
332
333         return false;
334 }
335
336 MUTATOR_HOOKFUNCTION(cts, PlayerDamage_Calculate)
337 {
338         if(frag_target == frag_attacker || frag_deathtype == DEATH_FALL.m_id)
339         if(!autocvar_g_cts_selfdamage)
340                 frag_damage = 0;
341
342         return false;
343 }
344
345 MUTATOR_HOOKFUNCTION(cts, ForbidPlayerScore_Clear)
346 {
347         return true; // in CTS, you don't lose score by observing
348 }
349
350 MUTATOR_HOOKFUNCTION(cts, SetModname)
351 {
352         g_cloaked = 1; // always enable cloak in CTS
353
354         return false;
355 }
356
357 MUTATOR_HOOKFUNCTION(cts, GetRecords)
358 {
359         for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
360         {
361                 if(MapInfo_Get_ByID(i))
362                 {
363                         float r = race_readTime(MapInfo_Map_bspname, 1);
364
365                         if(!r)
366                                 continue;
367
368                         string h = race_readName(MapInfo_Map_bspname, 1);
369                         ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n");
370                 }
371         }
372
373         return false;
374 }
375
376 MUTATOR_HOOKFUNCTION(cts, ClientKill)
377 {
378         ret_float = 0;
379
380         if(self.killindicator && self.killindicator.health == 1) // self.killindicator.health == 1 means that the kill indicator was spawned by CTS_ClientKill
381         {
382                 remove(self.killindicator);
383                 self.killindicator = world;
384
385                 ClientKill_Now(); // allow instant kill in this case
386                 return;
387         }
388
389 }
390
391 MUTATOR_HOOKFUNCTION(cts, Race_FinalCheckpoint)
392 {
393         if(autocvar_g_cts_finish_kill_delay)
394                 CTS_ClientKill(self);
395
396         return false;
397 }
398
399 MUTATOR_HOOKFUNCTION(cts, FixClientCvars)
400 {
401         stuffcmd(fix_client, "cl_cmd settemp cl_movecliptokeyboard 2\n");
402         return false;
403 }
404
405 MUTATOR_HOOKFUNCTION(cts, WantWeapon)
406 {
407         ret_float = (want_weaponinfo.weapon == WEP_SHOTGUN.m_id);
408         want_mutatorblocked = true;
409         return true;
410 }
411
412 void cts_Initialize()
413 {
414         cts_ScoreRules();
415 }
416
417 REGISTER_MUTATOR(cts, g_cts)
418 {
419         g_race_qualifying = 1;
420         independent_players = 1;
421         SetLimits(0, 0, 0, -1);
422
423         MUTATOR_ONADD
424         {
425                 if(time > 1) // game loads at time 1
426                         error("This is a game type and it cannot be added at runtime.");
427                 cts_Initialize();
428         }
429
430         MUTATOR_ONROLLBACK_OR_REMOVE
431         {
432                 // we actually cannot roll back cts_Initialize here
433                 // BUT: we don't need to! If this gets called, adding always
434                 // succeeds.
435         }
436
437         MUTATOR_ONREMOVE
438         {
439                 LOG_INFO("This is a game type and it cannot be removed at runtime.");
440                 return -1;
441         }
442
443         return 0;
444 }
445 #endif