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