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