]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_cts.qc
Merge branch 'master' into TimePath/deathtypes
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_cts.qc
1 #include "gamemode_cts.qh"
2
3 #include "gamemode.qh"
4
5 #include "../race.qh"
6
7 // legacy bot roles
8 .float race_checkpoint;
9 void havocbot_role_cts()
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 cts_ScoreRules()
37 {
38         ScoreRules_basics(0, 0, 0, false);
39         if(g_race_qualifying)
40         {
41                 ScoreInfo_SetLabel_PlayerScore(SP_CTS_FASTEST, "fastest",   SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
42         }
43         else
44         {
45                 ScoreInfo_SetLabel_PlayerScore(SP_CTS_LAPS,    "laps",      SFL_SORT_PRIO_PRIMARY);
46                 ScoreInfo_SetLabel_PlayerScore(SP_CTS_TIME,    "time",      SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
47                 ScoreInfo_SetLabel_PlayerScore(SP_CTS_FASTEST, "fastest",   SFL_LOWER_IS_BETTER | SFL_TIME);
48         }
49         ScoreRules_basics_end();
50 }
51
52 void cts_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
53 {
54         if(autocvar_sv_eventlog)
55                 GameLogEcho(strcat(":cts:", mode, ":", ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
56 }
57
58 MUTATOR_HOOKFUNCTION(cts_PlayerPhysics)
59 {SELFPARAM();
60         // force kbd movement for fairness
61         float wishspeed;
62         vector wishvel;
63
64         // if record times matter
65         // ensure nothing EVIL is being done (i.e. div0_evade)
66         // this hinders joystick users though
67         // but it still gives SOME analog control
68         wishvel.x = fabs(self.movement.x);
69         wishvel.y = fabs(self.movement.y);
70         if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y)
71         {
72                 wishvel.z = 0;
73                 wishspeed = vlen(wishvel);
74                 if(wishvel.x >= 2 * wishvel.y)
75                 {
76                         // pure X motion
77                         if(self.movement.x > 0)
78                                 self.movement_x = wishspeed;
79                         else
80                                 self.movement_x = -wishspeed;
81                         self.movement_y = 0;
82                 }
83                 else if(wishvel.y >= 2 * wishvel.x)
84                 {
85                         // pure Y motion
86                         self.movement_x = 0;
87                         if(self.movement.y > 0)
88                                 self.movement_y = wishspeed;
89                         else
90                                 self.movement_y = -wishspeed;
91                 }
92                 else
93                 {
94                         // diagonal
95                         if(self.movement.x > 0)
96                                 self.movement_x = M_SQRT1_2 * wishspeed;
97                         else
98                                 self.movement_x = -M_SQRT1_2 * wishspeed;
99                         if(self.movement.y > 0)
100                                 self.movement_y = M_SQRT1_2 * wishspeed;
101                         else
102                                 self.movement_y = -M_SQRT1_2 * wishspeed;
103                 }
104         }
105
106         return false;
107 }
108
109 MUTATOR_HOOKFUNCTION(cts_ResetMap)
110 {
111         float s;
112
113         Score_NicePrint(world);
114
115         race_ClearRecords();
116         PlayerScore_Sort(race_place, 0, 1, 0);
117
118         entity e;
119         FOR_EACH_CLIENT(e)
120         {
121                 if(e.race_place)
122                 {
123                         s = PlayerScore_Add(e, SP_RACE_FASTEST, 0);
124                         if(!s)
125                                 e.race_place = 0;
126                 }
127                 cts_EventLog(ftos(e.race_place), e);
128         }
129
130         if(g_race_qualifying == 2)
131         {
132                 g_race_qualifying = 0;
133                 independent_players = 0;
134                 cvar_set("fraglimit", ftos(race_fraglimit));
135                 cvar_set("leadlimit", ftos(race_leadlimit));
136                 cvar_set("timelimit", ftos(race_timelimit));
137                 cts_ScoreRules();
138         }
139
140         return false;
141 }
142
143 MUTATOR_HOOKFUNCTION(cts_PlayerPreThink)
144 {SELFPARAM();
145         if(IS_SPEC(self) || IS_OBSERVER(self))
146         if(g_race_qualifying)
147         if(msg_entity.enemy.race_laptime)
148                 race_SendNextCheckpoint(msg_entity.enemy, 1);
149
150         return false;
151 }
152
153 MUTATOR_HOOKFUNCTION(cts_ClientConnect)
154 {SELFPARAM();
155         race_PreparePlayer();
156         self.race_checkpoint = -1;
157
158         if(IS_REAL_CLIENT(self))
159         {
160                 string rr = CTS_RECORD;
161
162                 msg_entity = self;
163                 race_send_recordtime(MSG_ONE);
164                 race_send_speedaward(MSG_ONE);
165
166                 speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed")));
167                 speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp")));
168                 race_send_speedaward_alltimebest(MSG_ONE);
169
170                 float i;
171                 for (i = 1; i <= RANKINGS_CNT; ++i)
172                 {
173                         race_SendRankings(i, 0, 0, MSG_ONE);
174                 }
175         }
176
177         return false;
178 }
179
180 MUTATOR_HOOKFUNCTION(cts_MakePlayerObserver)
181 {SELFPARAM();
182         if(PlayerScore_Add(self, SP_RACE_FASTEST, 0))
183                 self.frags = FRAGS_LMS_LOSER;
184         else
185                 self.frags = FRAGS_SPECTATOR;
186
187         race_PreparePlayer();
188         self.race_checkpoint = -1;
189
190         return false;
191 }
192
193 MUTATOR_HOOKFUNCTION(cts_PlayerSpawn)
194 {SELFPARAM();
195         if(spawn_spot.target == "")
196                 // Emergency: this wasn't a real spawnpoint. Can this ever happen?
197                 race_PreparePlayer();
198
199         // if we need to respawn, do it right
200         self.race_respawn_checkpoint = self.race_checkpoint;
201         self.race_respawn_spotref = spawn_spot;
202
203         self.race_place = 0;
204
205         return false;
206 }
207
208 MUTATOR_HOOKFUNCTION(cts_PutClientInServer)
209 {SELFPARAM();
210         if(IS_PLAYER(self))
211         if(!gameover)
212         {
213                 if(self.killcount == -666 /* initial spawn */ || g_race_qualifying) // spawn
214                         race_PreparePlayer();
215                 else // respawn
216                         race_RetractPlayer();
217
218                 race_AbandonRaceCheck(self);
219         }
220         return false;
221 }
222
223 MUTATOR_HOOKFUNCTION(cts_PlayerDies)
224 {SELFPARAM();
225         self.respawn_flags |= RESPAWN_FORCE;
226         race_AbandonRaceCheck(self);
227         return false;
228 }
229
230 MUTATOR_HOOKFUNCTION(cts_BotRoles)
231 {SELFPARAM();
232         self.havocbot_role = havocbot_role_cts;
233         return true;
234 }
235
236 MUTATOR_HOOKFUNCTION(cts_PlayerPostThink)
237 {SELFPARAM();
238         if(self.cvar_cl_allow_uidtracking == 1 && self.cvar_cl_allow_uid2name == 1)
239         {
240                 if (!self.stored_netname)
241                         self.stored_netname = strzone(uid2name(self.crypto_idfp));
242                 if(self.stored_netname != self.netname)
243                 {
244                         db_put(ServerProgsDB, strcat("/uid2name/", self.crypto_idfp), self.netname);
245                         strunzone(self.stored_netname);
246                         self.stored_netname = strzone(self.netname);
247                 }
248         }
249
250         return false;
251 }
252
253 MUTATOR_HOOKFUNCTION(cts_ForbidThrowing)
254 {
255         // no weapon dropping in CTS
256         return true;
257 }
258
259 MUTATOR_HOOKFUNCTION(cts_FilterItem)
260 {SELFPARAM();
261         if(self.classname == "droppedweapon")
262                 return true;
263
264         return false;
265 }
266
267 MUTATOR_HOOKFUNCTION(cts_PlayerDamage)
268 {
269         if(frag_target == frag_attacker || frag_deathtype == DEATH_FALL.m_id)
270         if(!autocvar_g_cts_selfdamage)
271                 frag_damage = 0;
272
273         return false;
274 }
275
276 MUTATOR_HOOKFUNCTION(cts_ForbidClearPlayerScore)
277 {
278         return true; // in CTS, you don't lose score by observing
279 }
280
281 MUTATOR_HOOKFUNCTION(cts_SetMods)
282 {
283         g_cloaked = 1; // always enable cloak in CTS
284
285         return false;
286 }
287
288 void cts_Initialize()
289 {
290         cts_ScoreRules();
291 }
292
293 MUTATOR_DEFINITION(gamemode_cts)
294 {
295         MUTATOR_HOOK(PlayerPhysics, cts_PlayerPhysics, CBC_ORDER_ANY);
296         MUTATOR_HOOK(reset_map_global, cts_ResetMap, CBC_ORDER_ANY);
297         MUTATOR_HOOK(PlayerPreThink, cts_PlayerPreThink, CBC_ORDER_ANY);
298         MUTATOR_HOOK(ClientConnect, cts_ClientConnect, CBC_ORDER_ANY);
299         MUTATOR_HOOK(MakePlayerObserver, cts_MakePlayerObserver, CBC_ORDER_ANY);
300         MUTATOR_HOOK(PlayerSpawn, cts_PlayerSpawn, CBC_ORDER_ANY);
301         MUTATOR_HOOK(PutClientInServer, cts_PutClientInServer, CBC_ORDER_ANY);
302         MUTATOR_HOOK(PlayerDies, cts_PlayerDies, CBC_ORDER_ANY);
303         MUTATOR_HOOK(HavocBot_ChooseRole, cts_BotRoles, CBC_ORDER_ANY);
304         MUTATOR_HOOK(GetPressedKeys, cts_PlayerPostThink, CBC_ORDER_ANY);
305         MUTATOR_HOOK(ForbidThrowCurrentWeapon, cts_ForbidThrowing, CBC_ORDER_ANY);
306         MUTATOR_HOOK(FilterItem, cts_FilterItem, CBC_ORDER_ANY);
307         MUTATOR_HOOK(PlayerDamage_Calculate, cts_PlayerDamage, CBC_ORDER_ANY);
308         MUTATOR_HOOK(ForbidPlayerScore_Clear, cts_ForbidClearPlayerScore, CBC_ORDER_ANY);
309         MUTATOR_HOOK(SetModname, cts_SetMods, CBC_ORDER_ANY);
310
311         MUTATOR_ONADD
312         {
313                 if(time > 1) // game loads at time 1
314                         error("This is a game type and it cannot be added at runtime.");
315                 cts_Initialize();
316         }
317
318         MUTATOR_ONROLLBACK_OR_REMOVE
319         {
320                 // we actually cannot roll back cts_Initialize here
321                 // BUT: we don't need to! If this gets called, adding always
322                 // succeeds.
323         }
324
325         MUTATOR_ONREMOVE
326         {
327                 LOG_INFO("This is a game type and it cannot be removed at runtime.");
328                 return -1;
329         }
330
331         return 0;
332 }