]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/impulse.qc
Merge branch 'master' into develop
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / impulse.qc
1 #include "impulse.qh"
2
3 #include <common/gamemodes/_mod.qh>
4 #include <common/minigames/sv_minigames.qh>
5 #include <common/mutators/mutator/waypoints/waypointsprites.qh>
6 #include <common/state.qh>
7 #include <common/vehicles/sv_vehicles.qh>
8 #include <common/weapons/_all.qh>
9 #include <server/cheats.qh>
10 #include <server/client.qh>
11 #include <server/clientkill.qh>
12 #include <server/command/common.qh>
13 #include <server/damage.qh>
14 #include <server/mutators/_mod.qh>
15 #include <server/round_handler.qh>
16 #include <server/weapons/selection.qh>
17 #include <server/weapons/throwing.qh>
18 #include <server/weapons/tracing.qh>
19 #include <server/weapons/weaponsystem.qh>
20
21 .entity vehicle;
22
23 #define IMPULSE(id) _IMPULSE(IMP_##id)
24 #define _IMPULSE(id) \
25         void id##_handle(entity this); \
26         STATIC_INIT_LATE(id) \
27         { \
28                 id.impulse_handle = id##_handle; \
29         } \
30         void id##_handle(entity this)
31
32 /**
33  * Impulse map:
34  *
35  * 0 reserved (no input)
36  *
37  * 99: loaded
38  *
39  * 140: moving clone
40  * 141: ctf speedrun
41  * 142: fixed clone
42  * 143: emergency teleport
43  * 148: unfairly eliminate
44  *
45  * TODO:
46  * 200 to 209: prev weapon shortcuts
47  * 210 to 219: best weapon shortcuts
48  * 220 to 229: next weapon shortcuts
49  * 230 to 253: individual weapons (up to 24)
50  */
51
52 // weapon switching impulses
53
54 void weapon_group_handle(entity this, int number, int imp)
55 {
56         if (IS_DEAD(this))
57         {
58                 this.impulse = imp;
59                 return;
60         }
61         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
62         {
63                 .entity weaponentity = weaponentities[slot];
64                 W_NextWeaponOnImpulse(this, number, weaponentity);
65                 if(autocvar_g_weaponswitch_debug != 1)
66                         break;
67         }
68 }
69
70 #define X(i) \
71         IMPULSE(weapon_group_##i) \
72         { \
73                 weapon_group_handle(this, i, IMP_weapon_group_##i.impulse); \
74         }
75 X(1)
76 X(2)
77 X(3)
78 X(4)
79 X(5)
80 X(6)
81 X(7)
82 X(8)
83 X(9)
84 X(0)
85 #undef X
86
87 // custom order weapon cycling
88
89 void weapon_priority_handle(entity this, int dir, int number, int imp)
90 {
91         if (this.vehicle) return;
92         if (IS_DEAD(this))
93         {
94                 this.impulse = imp;
95                 return;
96         }
97         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
98         {
99                 .entity weaponentity = weaponentities[slot];
100                 W_CycleWeapon(this, CS_CVAR(this).cvar_cl_weaponpriorities[number], dir, weaponentity);
101                 if(autocvar_g_weaponswitch_debug != 1)
102                         break;
103         }
104 }
105
106 #define X(i, dir) \
107         IMPULSE(weapon_priority_##i##_##dir) \
108         { \
109                 noref int prev = -1; \
110                 noref int best =  0; \
111                 noref int next = +1; \
112                 weapon_priority_handle(this, dir, i, IMP_weapon_priority_##i##_##dir.impulse); \
113         }
114 X(0, prev)
115 X(1, prev)
116 X(2, prev)
117 X(3, prev)
118 X(4, prev)
119 X(5, prev)
120 X(6, prev)
121 X(7, prev)
122 X(8, prev)
123 X(9, prev)
124
125 X(0, best)
126 X(1, best)
127 X(2, best)
128 X(3, best)
129 X(4, best)
130 X(5, best)
131 X(6, best)
132 X(7, best)
133 X(8, best)
134 X(9, best)
135
136 X(0, next)
137 X(1, next)
138 X(2, next)
139 X(3, next)
140 X(4, next)
141 X(5, next)
142 X(6, next)
143 X(7, next)
144 X(8, next)
145 X(9, next)
146 #undef X
147
148 // direct weapons
149
150 void weapon_byid_handle(entity this, int number, int imp)
151 {
152         if (this.vehicle) return;
153         if (IS_DEAD(this))
154         {
155                 this.impulse = imp;
156                 return;
157         }
158         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
159         {
160                 .entity weaponentity = weaponentities[slot];
161                 W_SwitchWeapon_TryOthers(this, REGISTRY_GET(Weapons, WEP_FIRST + number), weaponentity);
162                 if(autocvar_g_weaponswitch_debug != 1)
163                         break;
164         }
165 }
166
167 #define X(i) \
168         IMPULSE(weapon_byid_##i) \
169         { \
170                 weapon_byid_handle(this, i, IMP_weapon_byid_##i.impulse); \
171         }
172 X(0)
173 X(1)
174 X(2)
175 X(3)
176 X(4)
177 X(5)
178 X(6)
179 X(7)
180 X(8)
181 X(9)
182 X(10)
183 X(11)
184 X(12)
185 X(13)
186 X(14)
187 X(15)
188 X(16)
189 X(17)
190 X(18)
191 X(19)
192 X(20)
193 X(21)
194 X(22)
195 X(23)
196 #undef X
197
198 IMPULSE(weapon_next_byid)
199 {
200         if (this.vehicle) return;
201         if (IS_DEAD(this))
202         {
203                 this.impulse = IMP_weapon_next_byid.impulse;
204                 return;
205         }
206         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
207         {
208                 .entity weaponentity = weaponentities[slot];
209                 W_NextWeapon(this, 0, weaponentity);
210
211                 if(autocvar_g_weaponswitch_debug != 1)
212                         break;
213         }
214 }
215
216 IMPULSE(weapon_prev_byid)
217 {
218         if (this.vehicle) return;
219         if (IS_DEAD(this))
220         {
221                 this.impulse = IMP_weapon_prev_byid.impulse;
222                 return;
223         }
224         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
225         {
226                 .entity weaponentity = weaponentities[slot];
227                 W_PreviousWeapon(this, 0, weaponentity);
228
229                 if(autocvar_g_weaponswitch_debug != 1)
230                         break;
231         }
232 }
233
234 IMPULSE(weapon_next_bygroup)
235 {
236         if (this.vehicle) return;
237         if (IS_DEAD(this))
238         {
239                 this.impulse = IMP_weapon_next_bygroup.impulse;
240                 return;
241         }
242         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
243         {
244                 .entity weaponentity = weaponentities[slot];
245                 W_NextWeapon(this, 1, weaponentity);
246
247                 if(autocvar_g_weaponswitch_debug != 1)
248                         break;
249         }
250 }
251
252 IMPULSE(weapon_prev_bygroup)
253 {
254         if (this.vehicle) return;
255         if (IS_DEAD(this))
256         {
257                 this.impulse = IMP_weapon_prev_bygroup.impulse;
258                 return;
259         }
260         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
261         {
262                 .entity weaponentity = weaponentities[slot];
263                 W_PreviousWeapon(this, 1, weaponentity);
264
265                 if(autocvar_g_weaponswitch_debug != 1)
266                         break;
267         }
268 }
269
270 IMPULSE(weapon_next_bypriority)
271 {
272         if (this.vehicle) return;
273         if (IS_DEAD(this))
274         {
275                 this.impulse = IMP_weapon_next_bypriority.impulse;
276                 return;
277         }
278         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
279         {
280                 .entity weaponentity = weaponentities[slot];
281                 W_NextWeapon(this, 2, weaponentity);
282
283                 if(autocvar_g_weaponswitch_debug != 1)
284                         break;
285         }
286 }
287
288 IMPULSE(weapon_prev_bypriority)
289 {
290         if (this.vehicle) return;
291         if (IS_DEAD(this))
292         {
293                 this.impulse = IMP_weapon_prev_bypriority.impulse;
294                 return;
295         }
296         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
297         {
298                 .entity weaponentity = weaponentities[slot];
299                 W_PreviousWeapon(this, 2, weaponentity);
300
301                 if(autocvar_g_weaponswitch_debug != 1)
302                         break;
303         }
304 }
305
306 IMPULSE(weapon_last)
307 {
308         if (this.vehicle) return;
309         if (IS_DEAD(this)) return;
310         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
311         {
312                 .entity weaponentity = weaponentities[slot];
313                 W_LastWeapon(this, weaponentity);
314
315                 if(autocvar_g_weaponswitch_debug != 1)
316                         break;
317         }
318 }
319
320 IMPULSE(weapon_best)
321 {
322         if (this.vehicle) return;
323         if (IS_DEAD(this)) return;
324         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
325         {
326                 .entity weaponentity = weaponentities[slot];
327                 W_SwitchWeapon(this, w_getbestweapon(this, weaponentity), weaponentity);
328
329                 if(autocvar_g_weaponswitch_debug != 1)
330                         break;
331         }
332 }
333
334 IMPULSE(weapon_drop)
335 {
336         if (this.vehicle) return;
337         if (IS_DEAD(this)) return;
338         bool is_dualwielding = W_DualWielding(this);
339         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
340         {
341                 .entity weaponentity = weaponentities[slot];
342                 vector md = this.(weaponentity).movedir;
343                 vector vecs = ((md.x > 0) ? md : '0 0 0');
344                 vector dv = v_right * -vecs.y;
345                 if(!is_dualwielding)
346                         dv = '0 0 0'; // don't override!
347                 W_ThrowWeapon(this, weaponentity, W_CalculateProjectileVelocity(this, this.velocity, v_forward * 750, false), dv, true);
348
349                 if(autocvar_g_weaponswitch_debug == 2)
350                         break; // in this mode, the off-hand weapon is selected based on the primary weapon, don't drop it twice!
351         }
352 }
353
354 IMPULSE(weapon_reload)
355 {
356         if (this.vehicle) return;
357         if (IS_DEAD(this)) return;
358         if (weaponLocked(this)) return;
359         entity actor = this;
360         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
361         {
362                 .entity weaponentity = weaponentities[slot];
363                 Weapon w = this.(weaponentity).m_weapon;
364                 w.wr_reload(w, actor, weaponentity);
365
366                 // allow reloading all active slots?
367                 //if(autocvar_g_weaponswitch_debug != 1)
368                         //break;
369         }
370 }
371
372 void ImpulseCommands(entity this)
373 {
374         if (game_stopped) return;
375
376         int imp = CS(this).impulse;
377         if (!imp) return;
378         CS(this).impulse = 0;
379
380         if (MinigameImpulse(this, imp)) return;
381
382         if (timeout_status == TIMEOUT_ACTIVE) return;  // don't allow any impulses while the game is paused
383
384         if (round_handler_IsActive() && !round_handler_IsRoundStarted())
385         {
386                 // impulses forbidden while waiting for the start of a round
387                 #define X(id) case IMP_##id.impulse:
388                 switch (imp)
389                 {
390                         X(weapon_drop)
391                         X(weapon_reload)
392                         X(use)
393                                 return;
394                 }
395 #undef X
396         }
397
398         if (vehicle_impulse(this, imp)) return;
399
400         if (CheatImpulse(this, imp)) return;
401
402         FOREACH(IMPULSES, it.impulse == imp, {
403                 void(entity) f = it.impulse_handle;
404                 if (!f) continue;
405                 f(this);
406                 return;
407         });
408 }
409
410 IMPULSE(use)
411 {
412         PlayerUseKey(this);
413 }
414
415 IMPULSE(waypoint_personal_here)
416 {
417         entity wp = WaypointSprite_DeployPersonal(WP_Waypoint, this, this.origin, RADARICON_WAYPOINT);
418         if (wp) WaypointSprite_Ping(wp);
419         sprint(this, "personal waypoint spawned at location\n");
420 }
421
422 IMPULSE(waypoint_personal_crosshair)
423 {
424         WarpZone_crosshair_trace(this);
425         entity wp = WaypointSprite_DeployPersonal(WP_Waypoint, this, trace_endpos, RADARICON_WAYPOINT);
426         if (wp) WaypointSprite_Ping(wp);
427         sprint(this, "personal waypoint spawned at crosshair\n");
428 }
429
430 IMPULSE(waypoint_personal_death)
431 {
432         if (!this.death_origin) return;
433         entity wp = WaypointSprite_DeployPersonal(WP_Waypoint, this, this.death_origin, RADARICON_WAYPOINT);
434         if (wp) WaypointSprite_Ping(wp);
435         sprint(this, "personal waypoint spawned at death location\n");
436 }
437
438 IMPULSE(waypoint_here_follow)
439 {
440         if (!teamplay) return;
441         if (IS_DEAD(this)) return;
442         if (!MUTATOR_CALLHOOK(HelpMePing, this))
443         {
444                 entity wp = WaypointSprite_Attach(WP_Helpme, this, true, RADARICON_HELPME);
445                 if (!wp) WaypointSprite_HelpMePing(this.waypointsprite_attachedforcarrier);
446                 else WaypointSprite_Ping(wp);
447         }
448         sprint(this, "HELP ME attached\n");
449 }
450
451 IMPULSE(waypoint_here_here)
452 {
453         entity wp = WaypointSprite_DeployFixed(WP_Here, false, this, this.origin, RADARICON_HERE);
454         if (wp) WaypointSprite_Ping(wp);
455         sprint(this, "HERE spawned at location\n");
456 }
457
458 IMPULSE(waypoint_here_crosshair)
459 {
460         WarpZone_crosshair_trace_plusvisibletriggers(this);
461         entity wp = WaypointSprite_DeployFixed(WP_Here, false, this, trace_endpos, RADARICON_HERE);
462         if (wp) WaypointSprite_Ping(wp);
463         sprint(this, "HERE spawned at crosshair\n");
464 }
465
466 IMPULSE(waypoint_here_death)
467 {
468         if (!this.death_origin) return;
469         entity wp = WaypointSprite_DeployFixed(WP_Here, false, this, this.death_origin, RADARICON_HERE);
470         if (wp) WaypointSprite_Ping(wp);
471         sprint(this, "HERE spawned at death location\n");
472 }
473
474 IMPULSE(waypoint_danger_here)
475 {
476         entity wp = WaypointSprite_DeployFixed(WP_Danger, false, this, this.origin, RADARICON_DANGER);
477         if (wp) WaypointSprite_Ping(wp);
478         sprint(this, "DANGER spawned at location\n");
479 }
480
481 IMPULSE(waypoint_danger_crosshair)
482 {
483         WarpZone_crosshair_trace(this);
484         entity wp = WaypointSprite_DeployFixed(WP_Danger, false, this, trace_endpos, RADARICON_DANGER);
485         if (wp) WaypointSprite_Ping(wp);
486         sprint(this, "DANGER spawned at crosshair\n");
487 }
488
489 IMPULSE(waypoint_danger_death)
490 {
491         if (!this.death_origin) return;
492         entity wp = WaypointSprite_DeployFixed(WP_Danger, false, this, this.death_origin, RADARICON_DANGER);
493         if (wp) WaypointSprite_Ping(wp);
494         sprint(this, "DANGER spawned at death location\n");
495 }
496
497 IMPULSE(waypoint_clear_personal)
498 {
499         WaypointSprite_ClearPersonal(this);
500         if (this.personal)
501         {
502                 delete(this.personal);
503                 this.personal = NULL;
504
505                 if((g_cts || g_race) && autocvar_g_allow_checkpoints)
506                         ClientKill(this);
507         }
508         sprint(this, "personal waypoint cleared\n");
509 }
510
511 IMPULSE(waypoint_clear)
512 {
513         WaypointSprite_ClearOwned(this);
514         if (this.personal)
515         {
516                 delete(this.personal);
517                 this.personal = NULL;
518                 if((g_cts || g_race) && autocvar_g_allow_checkpoints)
519                         ClientKill(this);
520         }
521         sprint(this, "all waypoints cleared\n");
522 }