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