]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/cl_impulse.qc
Merge branch 'master' into terencehill/menu_hudskin_selector
[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 (this.deadflag != DEAD_NO) 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 (this.deadflag != DEAD_NO) 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 (this.deadflag != DEAD_NO) 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 (this.deadflag != DEAD_NO) return;
156         W_NextWeapon(0);
157 }
158
159 IMPULSE(weapon_prev_byid)
160 {
161         if (this.vehicle) return;
162         if (this.deadflag != DEAD_NO) return;
163         W_PreviousWeapon(0);
164 }
165
166 IMPULSE(weapon_next_bygroup)
167 {
168         if (this.vehicle) return;
169         if (this.deadflag != DEAD_NO) return;
170         W_NextWeapon(1);
171 }
172
173 IMPULSE(weapon_prev_bygroup)
174 {
175         if (this.vehicle) return;
176         if (this.deadflag != DEAD_NO) return;
177         W_PreviousWeapon(1);
178 }
179
180 IMPULSE(weapon_next_bypriority)
181 {
182         if (this.vehicle) return;
183         if (this.deadflag != DEAD_NO) return;
184         W_NextWeapon(2);
185 }
186
187 IMPULSE(weapon_prev_bypriority)
188 {
189         if (this.vehicle) return;
190         if (this.deadflag != DEAD_NO) return;
191         W_PreviousWeapon(2);
192 }
193
194 IMPULSE(weapon_last)
195 {
196         if (this.vehicle) return;
197         if (this.deadflag != DEAD_NO) return;
198         W_LastWeapon();
199 }
200
201 IMPULSE(weapon_best)
202 {
203         if (this.vehicle) return;
204         if (this.deadflag != DEAD_NO) return;
205         W_SwitchWeapon(Weapons_from(w_getbestweapon(this)));
206 }
207
208 IMPULSE(weapon_drop)
209 {
210         if (this.vehicle) return;
211         if (this.deadflag != DEAD_NO) 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 (this.deadflag != DEAD_NO) return;
219         if (forbidWeaponUse(this)) return;
220         Weapon w = Weapons_from(this.weapon);
221         w.wr_reload(w);
222 }
223
224 void ImpulseCommands(entity this)
225 {
226         if (gameover) return;
227
228         int imp = this.impulse;
229         if (!imp) return;
230         this.impulse = 0;
231
232         if (MinigameImpulse(this, imp)) return;
233
234         if (timeout_status == TIMEOUT_ACTIVE) return;  // don't allow any impulses while the game is paused
235
236         // allow only weapon change impulses when not in round time
237         if (round_handler_IsActive() && !round_handler_IsRoundStarted())
238         {
239                 #define X(id) case IMP_##id.impulse:
240                 switch (imp)
241                 {
242                         X(weapon_group_0)
243                         X(weapon_group_1)
244                         X(weapon_group_2)
245                         X(weapon_group_3)
246                         X(weapon_group_4)
247                         X(weapon_group_5)
248                         X(weapon_group_6)
249                         X(weapon_group_7)
250                         X(weapon_group_8)
251                         X(weapon_group_9)
252                         X(weapon_next_byid)
253                         X(weapon_prev_byid)
254                         X(weapon_next_bygroup)
255                         X(weapon_prev_bygroup)
256                         X(weapon_next_bypriority)
257                         X(weapon_prev_bypriority)
258                         X(weapon_last)
259                         X(weapon_best)
260                         X(weapon_reload)
261                         X(weapon_priority_0_prev)
262             X(weapon_priority_1_prev)
263             X(weapon_priority_2_prev)
264             X(weapon_priority_3_prev)
265             X(weapon_priority_4_prev)
266             X(weapon_priority_5_prev)
267             X(weapon_priority_6_prev)
268             X(weapon_priority_7_prev)
269             X(weapon_priority_8_prev)
270             X(weapon_priority_9_prev)
271             X(weapon_priority_0_next)
272                         X(weapon_priority_1_next)
273                         X(weapon_priority_2_next)
274                         X(weapon_priority_3_next)
275                         X(weapon_priority_4_next)
276                         X(weapon_priority_5_next)
277                         X(weapon_priority_6_next)
278                         X(weapon_priority_7_next)
279                         X(weapon_priority_8_next)
280                         X(weapon_priority_9_next)
281                         X(weapon_priority_0_best)
282             X(weapon_priority_1_best)
283             X(weapon_priority_2_best)
284             X(weapon_priority_3_best)
285             X(weapon_priority_4_best)
286             X(weapon_priority_5_best)
287             X(weapon_priority_6_best)
288             X(weapon_priority_7_best)
289             X(weapon_priority_8_best)
290             X(weapon_priority_9_best)
291             X(weapon_byid_0)
292             X(weapon_byid_1)
293             X(weapon_byid_2)
294             X(weapon_byid_3)
295             X(weapon_byid_4)
296             X(weapon_byid_5)
297             X(weapon_byid_6)
298             X(weapon_byid_7)
299             X(weapon_byid_8)
300             X(weapon_byid_9)
301             X(weapon_byid_10)
302             X(weapon_byid_11)
303             X(weapon_byid_12)
304             X(weapon_byid_13)
305             X(weapon_byid_14)
306             X(weapon_byid_15)
307             X(weapon_byid_16)
308             X(weapon_byid_17)
309             X(weapon_byid_18)
310             X(weapon_byid_19)
311             X(weapon_byid_20)
312             X(weapon_byid_21)
313             X(weapon_byid_22)
314             X(weapon_byid_23)
315                         break;
316                         default: return;
317                 }
318 #undef X
319         }
320
321         if (vehicle_impulse(this, imp)) return;
322
323         if (CheatImpulse(imp)) return;
324
325         FOREACH(IMPULSES, it.impulse == imp, {
326                 void(entity) f = it.impulse_handle;
327                 if (!f) continue;
328                 f(this);
329                 return;
330         });
331 }
332
333 IMPULSE(use)
334 {
335         PlayerUseKey();
336 }
337
338 IMPULSE(waypoint_personal_here)
339 {
340         entity wp = WaypointSprite_DeployPersonal(WP_Waypoint, this.origin, RADARICON_WAYPOINT);
341         if (wp) WaypointSprite_Ping(wp);
342         sprint(this, "personal waypoint spawned at location\n");
343 }
344
345 IMPULSE(waypoint_personal_crosshair)
346 {
347         WarpZone_crosshair_trace(this);
348         entity wp = WaypointSprite_DeployPersonal(WP_Waypoint, trace_endpos, RADARICON_WAYPOINT);
349         if (wp) WaypointSprite_Ping(wp);
350         sprint(this, "personal waypoint spawned at crosshair\n");
351 }
352
353 IMPULSE(waypoint_personal_death)
354 {
355         if (!this.death_origin) return;
356         entity wp = WaypointSprite_DeployPersonal(WP_Waypoint, this.death_origin, RADARICON_WAYPOINT);
357         if (wp) WaypointSprite_Ping(wp);
358         sprint(this, "personal waypoint spawned at death location\n");
359 }
360
361 IMPULSE(waypoint_here_follow)
362 {
363         if (!teamplay) return;
364         if (this.deadflag != DEAD_NO) return;
365         if (!MUTATOR_CALLHOOK(HelpMePing, this))
366         {
367                 entity wp = WaypointSprite_Attach(WP_Helpme, true, RADARICON_HELPME);
368                 if (!wp) WaypointSprite_HelpMePing(this.waypointsprite_attachedforcarrier);
369                 else WaypointSprite_Ping(wp);
370         }
371         sprint(this, "HELP ME attached\n");
372 }
373
374 IMPULSE(waypoint_here_here)
375 {
376         entity wp = WaypointSprite_DeployFixed(WP_Here, false, this.origin, RADARICON_HERE);
377         if (wp) WaypointSprite_Ping(wp);
378         sprint(this, "HERE spawned at location\n");
379 }
380
381 IMPULSE(waypoint_here_crosshair)
382 {
383         WarpZone_crosshair_trace(this);
384         entity wp = WaypointSprite_DeployFixed(WP_Here, false, trace_endpos, RADARICON_HERE);
385         if (wp) WaypointSprite_Ping(wp);
386         sprint(this, "HERE spawned at crosshair\n");
387 }
388
389 IMPULSE(waypoint_here_death)
390 {
391         if (!this.death_origin) return;
392         entity wp = WaypointSprite_DeployFixed(WP_Here, false, this.death_origin, RADARICON_HERE);
393         if (wp) WaypointSprite_Ping(wp);
394         sprint(this, "HERE spawned at death location\n");
395 }
396
397 IMPULSE(waypoint_danger_here)
398 {
399         entity wp = WaypointSprite_DeployFixed(WP_Danger, false, this.origin, RADARICON_DANGER);
400         if (wp) WaypointSprite_Ping(wp);
401         sprint(this, "DANGER spawned at location\n");
402 }
403
404 IMPULSE(waypoint_danger_crosshair)
405 {
406         WarpZone_crosshair_trace(this);
407         entity wp = WaypointSprite_DeployFixed(WP_Danger, false, trace_endpos, RADARICON_DANGER);
408         if (wp) WaypointSprite_Ping(wp);
409         sprint(this, "DANGER spawned at crosshair\n");
410 }
411
412 IMPULSE(waypoint_danger_death)
413 {
414         if (!this.death_origin) return;
415         entity wp = WaypointSprite_DeployFixed(WP_Danger, false, this.death_origin, RADARICON_DANGER);
416         if (wp) WaypointSprite_Ping(wp);
417         sprint(this, "DANGER spawned at death location\n");
418 }
419
420 IMPULSE(waypoint_clear_personal)
421 {
422         WaypointSprite_ClearPersonal();
423         if (this.personal)
424         {
425                 remove(this.personal);
426                 this.personal = NULL;
427         }
428         sprint(this, "personal waypoint cleared\n");
429 }
430
431 IMPULSE(waypoint_clear)
432 {
433         WaypointSprite_ClearOwned();
434         if (this.personal)
435         {
436                 remove(this.personal);
437                 this.personal = NULL;
438         }
439         sprint(this, "all waypoints cleared\n");
440 }
441
442 IMPULSE(navwaypoint_spawn)
443 {
444         if (!autocvar_g_waypointeditor) return;
445         waypoint_schedulerelink(waypoint_spawn(this.origin, this.origin, 0));
446         bprint(strcat("Waypoint spawned at ", vtos(this.origin), "\n"));
447 }
448
449 IMPULSE(navwaypoint_remove)
450 {
451         if (!autocvar_g_waypointeditor) return;
452         entity e = navigation_findnearestwaypoint(this, false);
453         if (!e) return;
454         if (e.wpflags & WAYPOINTFLAG_GENERATED) return;
455         bprint(strcat("Waypoint removed at ", vtos(e.origin), "\n"));
456         waypoint_remove(e);
457 }
458
459 IMPULSE(navwaypoint_relink)
460 {
461         if (!autocvar_g_waypointeditor) return;
462         waypoint_schedulerelinkall();
463 }
464
465 IMPULSE(navwaypoint_save)
466 {
467         if (!autocvar_g_waypointeditor) return;
468         waypoint_saveall();
469 }
470
471 IMPULSE(navwaypoint_unreachable)
472 {
473         if (!autocvar_g_waypointeditor) return;
474         for (entity e = findchain(classname, "waypoint"); e; e = e.chain)
475         {
476                 e.colormod = '0.5 0.5 0.5';
477                 e.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
478         }
479         entity e2 = navigation_findnearestwaypoint(this, false);
480         navigation_markroutes(e2);
481
482         int i, m;
483
484         i = 0;
485         m = 0;
486         for (entity e = findchain(classname, "waypoint"); e; e = e.chain)
487         {
488                 if (e.wpcost < 10000000) continue;
489                 LOG_INFO("unreachable: ", etos(e), " ", vtos(e.origin), "\n");
490                 e.colormod_z = 8;
491                 e.effects |= EF_NODEPTHTEST | EF_BLUE;
492                 ++i;
493                 ++m;
494         }
495         if (i) LOG_INFOF("%d waypoints cannot be reached from here in any way (marked with blue light)\n", i);
496         navigation_markroutes_inverted(e2);
497
498         i = 0;
499         for (entity e = findchain(classname, "waypoint"); e; e = e.chain)
500         {
501                 if (e.wpcost < 10000000) continue;
502                 LOG_INFO("cannot reach me: ", etos(e), " ", vtos(e.origin), "\n");
503                 e.colormod_x = 8;
504                 if (!(e.effects & EF_NODEPTHTEST))  // not already reported before
505                         ++m;
506                 e.effects |= EF_NODEPTHTEST | EF_RED;
507                 ++i;
508         }
509         if (i) LOG_INFOF("%d waypoints cannot walk to here in any way (marked with red light)\n", i);
510         if (m) LOG_INFOF("%d waypoints have been marked total\n", m);
511
512         i = 0;
513         for (entity e = findchain(classname, "info_player_deathmatch"); e; e = e.chain)
514         {
515                 vector org = e.origin;
516                 tracebox(e.origin, PL_MIN, PL_MAX, e.origin - '0 0 512', MOVE_NOMONSTERS, world);
517                 setorigin(e, trace_endpos);
518                 if (navigation_findnearestwaypoint(e, false))
519                 {
520                         setorigin(e, org);
521                         e.effects &= ~EF_NODEPTHTEST;
522                         e.model = "";
523                 }
524                 else
525                 {
526                         setorigin(e, org);
527                         LOG_INFO("spawn without waypoint: ", etos(e), " ", vtos(e.origin), "\n");
528                         e.effects |= EF_NODEPTHTEST;
529                         _setmodel(e, this.model);
530                         e.frame = this.frame;
531                         e.skin = this.skin;
532                         e.colormod = '8 0.5 8';
533                         setsize(e, '0 0 0', '0 0 0');
534                         ++i;
535                 }
536         }
537         if (i) LOG_INFOF("%d spawnpoints have no nearest waypoint (marked by player model)\n", i);
538
539         i = 0;
540         entity start = findchainflags(flags, FL_ITEM);
541         for (entity e = start; e; e = e.chain)
542         {
543                 e.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
544                 e.colormod = '0.5 0.5 0.5';
545         }
546         for (entity e = start; e; e = e.chain)
547         {
548                 if (navigation_findnearestwaypoint(e, false)) continue;
549                 LOG_INFO("item without waypoint: ", etos(e), " ", vtos(e.origin), "\n");
550                 e.effects |= EF_NODEPTHTEST | EF_RED;
551                 e.colormod_x = 8;
552                 ++i;
553         }
554         if (i) LOG_INFOF("%d items have no nearest waypoint and cannot be walked away from (marked with red light)\n", i);
555
556         i = 0;
557         for (entity e = start; e; e = e.chain)
558         {
559                 if (navigation_findnearestwaypoint(e, true)) continue;
560                 LOG_INFO("item without waypoint: ", etos(e), " ", vtos(e.origin), "\n");
561                 e.effects |= EF_NODEPTHTEST | EF_BLUE;
562                 e.colormod_z = 8;
563                 ++i;
564         }
565         if (i) LOG_INFOF("%d items have no nearest waypoint and cannot be walked to (marked with blue light)\n", i);
566 }