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