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