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