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