Impulses: migration pathway
[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, n) _IMPULSE(IMP_##id, n)
23 #define _IMPULSE(id, n) \
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, imp) \
54         IMPULSE(weapon_group_##slot, imp) \
55         { \
56                 if (this.deadflag != DEAD_NO) return; \
57                 W_NextWeaponOnImpulse(slot); \
58         }
59 X(1, 1)
60 X(2, 2)
61 X(3, 3)
62 X(4, 4)
63 X(5, 5)
64 X(6, 6)
65 X(7, 7)
66 X(8, 8)
67 X(9, 9)
68 X(0, 14)
69 #undef X
70
71 IMPULSE(weapon_next_byid, 10)
72 {
73         if (this.vehicle) return;
74         if (this.deadflag != DEAD_NO) return;
75         W_NextWeapon(0);
76 }
77
78 IMPULSE(weapon_prev_byid, 12)
79 {
80         if (this.vehicle) return;
81         if (this.deadflag != DEAD_NO) return;
82         W_PreviousWeapon(0);
83 }
84
85 IMPULSE(weapon_next_bygroup, 18)
86 {
87         if (this.vehicle) return;
88         if (this.deadflag != DEAD_NO) return;
89         W_NextWeapon(1);
90 }
91
92 IMPULSE(weapon_prev_bygroup, 19)
93 {
94         if (this.vehicle) return;
95         if (this.deadflag != DEAD_NO) return;
96         W_PreviousWeapon(1);
97 }
98
99 IMPULSE(weapon_next_bypriority, 15)
100 {
101         if (this.vehicle) return;
102         if (this.deadflag != DEAD_NO) return;
103         W_NextWeapon(2);
104 }
105
106 IMPULSE(weapon_prev_bypriority, 16)
107 {
108         if (this.vehicle) return;
109         if (this.deadflag != DEAD_NO) return;
110         W_PreviousWeapon(2);
111 }
112
113 IMPULSE(weapon_last, 11)
114 {
115         if (this.vehicle) return;
116         if (this.deadflag != DEAD_NO) return;
117         W_LastWeapon();
118 }
119
120 IMPULSE(weapon_best, 13)
121 {
122         if (this.vehicle) return;
123         if (this.deadflag != DEAD_NO) return;
124         W_SwitchWeapon(w_getbestweapon(this));
125 }
126
127 IMPULSE(weapon_drop, 17)
128 {
129         if (this.vehicle) return;
130         if (this.deadflag != DEAD_NO) return;
131         W_ThrowWeapon(W_CalculateProjectileVelocity(this.velocity, v_forward * 750, false), '0 0 0', true);
132 }
133
134 IMPULSE(weapon_reload, 20)
135 {
136         if (this.vehicle) return;
137         if (this.deadflag != DEAD_NO) return;
138         if (forbidWeaponUse(this)) return;
139         Weapon w = Weapons_from(this.weapon);
140         w.wr_reload(w);
141 }
142
143 void ImpulseCommands(entity this)
144 {
145         if (gameover) return;
146
147         int imp = this.impulse;
148         if (!imp) return;
149         this.impulse = 0;
150
151         if (MinigameImpulse(this, imp)) return;
152
153         if (timeout_status == TIMEOUT_ACTIVE) return;  // don't allow any impulses while the game is paused
154
155         // allow only weapon change impulses when not in round time
156         if (round_handler_IsActive() && !round_handler_IsRoundStarted())
157                 if (imp == 17 || (imp >= 20 && imp < 200) || imp > 253) return;
158
159         if (vehicle_impulse(this, imp)) return;
160
161         if (CheatImpulse(imp)) return;
162
163         FOREACH(IMPULSES, it.impulse == imp, {
164                 void(entity) f = it.impulse_handle;
165                 if (!f) continue;
166                 f(this);
167                 return;
168         });
169
170         if (imp >= 200 && imp <= 229)
171         {
172                 if (!this.vehicle && this.deadflag == DEAD_NO)
173                 {
174                         // custom order weapon cycling
175                         int i = imp % 10;
176                         int m = (imp - (210 + i));  // <0 for prev, =0 for best, >0 for next
177                         W_CycleWeapon(this.(cvar_cl_weaponpriorities[i]), m);
178                 }
179                 // else // don't retry, as this can break weaplast bind
180                 // this.impulse = imp; // retry in next frame
181         }
182         else if (imp >= WEP_IMPULSE_BEGIN && imp <= WEP_IMPULSE_END)
183         {
184                 if (!this.vehicle && this.deadflag == DEAD_NO) W_SwitchWeapon(imp - WEP_IMPULSE_BEGIN + WEP_FIRST);
185                 // else // don't retry, as this can break weaplast bind
186                 // this.impulse = imp; // retry in next frame
187         }
188 }
189
190 IMPULSE(use, 21)
191 {
192         PlayerUseKey();
193 }
194
195 IMPULSE(waypoint_personal_here, 30)
196 {
197         entity wp = WaypointSprite_DeployPersonal(WP_Waypoint, this.origin, RADARICON_WAYPOINT);
198         if (wp) WaypointSprite_Ping(wp);
199         sprint(this, "personal waypoint spawned at location\n");
200 }
201
202 IMPULSE(waypoint_personal_crosshair, 31)
203 {
204         WarpZone_crosshair_trace(this);
205         entity wp = WaypointSprite_DeployPersonal(WP_Waypoint, trace_endpos, RADARICON_WAYPOINT);
206         if (wp) WaypointSprite_Ping(wp);
207         sprint(this, "personal waypoint spawned at crosshair\n");
208 }
209
210 IMPULSE(waypoint_personal_death, 32)
211 {
212         if (!this.death_origin) return;
213         entity wp = WaypointSprite_DeployPersonal(WP_Waypoint, this.death_origin, RADARICON_WAYPOINT);
214         if (wp) WaypointSprite_Ping(wp);
215         sprint(this, "personal waypoint spawned at death location\n");
216 }
217
218 IMPULSE(waypoint_here_follow, 33)
219 {
220         if (!teamplay) return;
221         if (this.deadflag != DEAD_NO) return;
222         if (!MUTATOR_CALLHOOK(HelpMePing, this))
223         {
224                 entity wp = WaypointSprite_Attach(WP_Helpme, true, RADARICON_HELPME);
225                 if (!wp) WaypointSprite_HelpMePing(this.waypointsprite_attachedforcarrier);
226                 else WaypointSprite_Ping(wp);
227         }
228         sprint(this, "HELP ME attached\n");
229 }
230
231 IMPULSE(waypoint_here_here, 34)
232 {
233         entity wp = WaypointSprite_DeployFixed(WP_Here, false, this.origin, RADARICON_HERE);
234         if (wp) WaypointSprite_Ping(wp);
235         sprint(this, "HERE spawned at location\n");
236 }
237
238 IMPULSE(waypoint_here_crosshair, 35)
239 {
240         WarpZone_crosshair_trace(this);
241         entity wp = WaypointSprite_DeployFixed(WP_Here, false, trace_endpos, RADARICON_HERE);
242         if (wp) WaypointSprite_Ping(wp);
243         sprint(this, "HERE spawned at crosshair\n");
244 }
245
246 IMPULSE(waypoint_here_death, 36)
247 {
248         if (!this.death_origin) return;
249         entity wp = WaypointSprite_DeployFixed(WP_Here, false, this.death_origin, RADARICON_HERE);
250         if (wp) WaypointSprite_Ping(wp);
251         sprint(this, "HERE spawned at death location\n");
252 }
253
254 IMPULSE(waypoint_danger_here, 37)
255 {
256         entity wp = WaypointSprite_DeployFixed(WP_Danger, false, this.origin, RADARICON_DANGER);
257         if (wp) WaypointSprite_Ping(wp);
258         sprint(this, "DANGER spawned at location\n");
259 }
260
261 IMPULSE(waypoint_danger_crosshair, 38)
262 {
263         WarpZone_crosshair_trace(this);
264         entity wp = WaypointSprite_DeployFixed(WP_Danger, false, trace_endpos, RADARICON_DANGER);
265         if (wp) WaypointSprite_Ping(wp);
266         sprint(this, "DANGER spawned at crosshair\n");
267 }
268
269 IMPULSE(waypoint_danger_death, 39)
270 {
271         if (!this.death_origin) return;
272         entity wp = WaypointSprite_DeployFixed(WP_Danger, false, this.death_origin, RADARICON_DANGER);
273         if (wp) WaypointSprite_Ping(wp);
274         sprint(this, "DANGER spawned at death location\n");
275 }
276
277 IMPULSE(waypoint_clear_personal, 47)
278 {
279         WaypointSprite_ClearPersonal();
280         if (this.personal)
281         {
282                 remove(this.personal);
283                 this.personal = NULL;
284         }
285         sprint(this, "personal waypoint cleared\n");
286 }
287
288 IMPULSE(waypoint_clear, 48)
289 {
290         WaypointSprite_ClearOwned();
291         if (this.personal)
292         {
293                 remove(this.personal);
294                 this.personal = NULL;
295         }
296         sprint(this, "all waypoints cleared\n");
297 }
298
299 IMPULSE(navwaypoint_spawn, 103)
300 {
301         if (!autocvar_g_waypointeditor) return;
302         waypoint_schedulerelink(waypoint_spawn(this.origin, this.origin, 0));
303         bprint(strcat("Waypoint spawned at ", vtos(this.origin), "\n"));
304 }
305
306 IMPULSE(navwaypoint_remove, 104)
307 {
308         if (!autocvar_g_waypointeditor) return;
309         entity e = navigation_findnearestwaypoint(this, false);
310         if (!e) return;
311         if (e.wpflags & WAYPOINTFLAG_GENERATED) return;
312         bprint(strcat("Waypoint removed at ", vtos(e.origin), "\n"));
313         waypoint_remove(e);
314 }
315
316 IMPULSE(navwaypoint_relink, 105)
317 {
318         if (!autocvar_g_waypointeditor) return;
319         waypoint_schedulerelinkall();
320 }
321
322 IMPULSE(navwaypoint_save, 106)
323 {
324         if (!autocvar_g_waypointeditor) return;
325         waypoint_saveall();
326 }
327
328 IMPULSE(navwaypoint_unreachable, 107)
329 {
330         if (!autocvar_g_waypointeditor) return;
331         for (entity e = findchain(classname, "waypoint"); e; e = e.chain)
332         {
333                 e.colormod = '0.5 0.5 0.5';
334                 e.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
335         }
336         entity e2 = navigation_findnearestwaypoint(this, false);
337         navigation_markroutes(e2);
338
339         int i, m;
340
341         i = 0;
342         m = 0;
343         for (entity e = findchain(classname, "waypoint"); e; e = e.chain)
344         {
345                 if (e.wpcost < 10000000) continue;
346                 LOG_INFO("unreachable: ", etos(e), " ", vtos(e.origin), "\n");
347                 e.colormod_z = 8;
348                 e.effects |= EF_NODEPTHTEST | EF_BLUE;
349                 ++i;
350                 ++m;
351         }
352         if (i) LOG_INFOF("%d waypoints cannot be reached from here in any way (marked with blue light)\n", i);
353         navigation_markroutes_inverted(e2);
354
355         i = 0;
356         for (entity e = findchain(classname, "waypoint"); e; e = e.chain)
357         {
358                 if (e.wpcost < 10000000) continue;
359                 LOG_INFO("cannot reach me: ", etos(e), " ", vtos(e.origin), "\n");
360                 e.colormod_x = 8;
361                 if (!(e.effects & EF_NODEPTHTEST))  // not already reported before
362                         ++m;
363                 e.effects |= EF_NODEPTHTEST | EF_RED;
364                 ++i;
365         }
366         if (i) LOG_INFOF("%d waypoints cannot walk to here in any way (marked with red light)\n", i);
367         if (m) LOG_INFOF("%d waypoints have been marked total\n", m);
368
369         i = 0;
370         for (entity e = findchain(classname, "info_player_deathmatch"); e; e = e.chain)
371         {
372                 vector org = e.origin;
373                 tracebox(e.origin, PL_MIN, PL_MAX, e.origin - '0 0 512', MOVE_NOMONSTERS, world);
374                 setorigin(e, trace_endpos);
375                 if (navigation_findnearestwaypoint(e, false))
376                 {
377                         setorigin(e, org);
378                         e.effects &= ~EF_NODEPTHTEST;
379                         e.model = "";
380                 }
381                 else
382                 {
383                         setorigin(e, org);
384                         LOG_INFO("spawn without waypoint: ", etos(e), " ", vtos(e.origin), "\n");
385                         e.effects |= EF_NODEPTHTEST;
386                         _setmodel(e, this.model);
387                         e.frame = this.frame;
388                         e.skin = this.skin;
389                         e.colormod = '8 0.5 8';
390                         setsize(e, '0 0 0', '0 0 0');
391                         ++i;
392                 }
393         }
394         if (i) LOG_INFOF("%d spawnpoints have no nearest waypoint (marked by player model)\n", i);
395
396         i = 0;
397         entity start = findchainflags(flags, FL_ITEM);
398         for (entity e = start; e; e = e.chain)
399         {
400                 e.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
401                 e.colormod = '0.5 0.5 0.5';
402         }
403         for (entity e = start; e; e = e.chain)
404         {
405                 if (navigation_findnearestwaypoint(e, false)) continue;
406                 LOG_INFO("item without waypoint: ", etos(e), " ", vtos(e.origin), "\n");
407                 e.effects |= EF_NODEPTHTEST | EF_RED;
408                 e.colormod_x = 8;
409                 ++i;
410         }
411         if (i) LOG_INFOF("%d items have no nearest waypoint and cannot be walked away from (marked with red light)\n", i);
412
413         i = 0;
414         for (entity e = start; e; e = e.chain)
415         {
416                 if (navigation_findnearestwaypoint(e, true)) continue;
417                 LOG_INFO("item without waypoint: ", etos(e), " ", vtos(e.origin), "\n");
418                 e.effects |= EF_NODEPTHTEST | EF_BLUE;
419                 e.colormod_z = 8;
420                 ++i;
421         }
422         if (i) LOG_INFOF("%d items have no nearest waypoint and cannot be walked to (marked with blue light)\n", i);
423 }