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