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