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