2 #include "round_handler.qh"
6 #include "weapons/throwing.qh"
7 #include "command/common.qh"
9 #include "weapons/selection.qh"
10 #include "weapons/tracing.qh"
11 #include "weapons/weaponsystem.qh"
13 #include <common/state.qh>
15 #include "../common/minigames/sv_minigames.qh"
17 #include <common/weapons/_all.qh>
18 #include "../common/vehicles/sv_vehicles.qh"
20 #include "../common/mutators/mutator/waypoints/waypointsprites.qh"
24 #define IMPULSE(id) _IMPULSE(IMP_##id)
25 #define _IMPULSE(id) \
26 void id##_handle(entity this); \
27 STATIC_INIT_LATE(id) \
29 id.impulse_handle = id##_handle; \
31 void id##_handle(entity this)
36 * 0 reserved (no input)
43 * 143: emergency teleport
44 * 148: unfairly eliminate
47 * 200 to 209: prev weapon shortcuts
48 * 210 to 219: best weapon shortcuts
49 * 220 to 229: next weapon shortcuts
50 * 230 to 253: individual weapons (up to 24)
53 // weapon switching impulses
56 IMPULSE(weapon_group_##slot) \
60 this.impulse = IMP_weapon_group_##slot.impulse; \
63 for(int wepslot = 0; wepslot < MAX_WEAPONSLOTS; ++wepslot) \
65 .entity weaponentity = weaponentities[wepslot]; \
66 W_NextWeaponOnImpulse(this, slot, weaponentity); \
67 if(wepslot == 0 && autocvar_g_weaponswitch_debug != 1) \
83 // custom order weapon cycling
85 #define X(slot, dir) \
86 IMPULSE(weapon_priority_##slot##_##dir) \
88 if (this.vehicle) return; \
91 this.impulse = IMP_weapon_priority_##slot##_##dir.impulse; \
94 noref int prev = -1; \
96 noref int next = +1; \
97 for(int wepslot = 0; wepslot < MAX_WEAPONSLOTS; ++wepslot) \
99 .entity weaponentity = weaponentities[wepslot]; \
100 W_CycleWeapon(this, this.cvar_cl_weaponpriorities[slot], dir, weaponentity); \
101 if(wepslot == 0 && autocvar_g_weaponswitch_debug != 1) \
142 IMPULSE(weapon_byid_##i) \
144 if (this.vehicle) return; \
147 this.impulse = IMP_weapon_byid_##i.impulse; \
150 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) \
152 .entity weaponentity = weaponentities[slot]; \
153 W_SwitchWeapon(this, Weapons_from(WEP_FIRST + i), weaponentity); \
154 if(slot == 0 && autocvar_g_weaponswitch_debug != 1) \
184 IMPULSE(weapon_next_byid)
186 if (this.vehicle) return;
189 this.impulse = IMP_weapon_next_byid.impulse;
192 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
194 .entity weaponentity = weaponentities[slot];
195 W_NextWeapon(this, 0, weaponentity);
197 if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
202 IMPULSE(weapon_prev_byid)
204 if (this.vehicle) return;
207 this.impulse = IMP_weapon_prev_byid.impulse;
210 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
212 .entity weaponentity = weaponentities[slot];
213 W_PreviousWeapon(this, 0, weaponentity);
215 if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
220 IMPULSE(weapon_next_bygroup)
222 if (this.vehicle) return;
225 this.impulse = IMP_weapon_next_bygroup.impulse;
228 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
230 .entity weaponentity = weaponentities[slot];
231 W_NextWeapon(this, 1, weaponentity);
233 if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
238 IMPULSE(weapon_prev_bygroup)
240 if (this.vehicle) return;
243 this.impulse = IMP_weapon_prev_bygroup.impulse;
246 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
248 .entity weaponentity = weaponentities[slot];
249 W_PreviousWeapon(this, 1, weaponentity);
251 if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
256 IMPULSE(weapon_next_bypriority)
258 if (this.vehicle) return;
261 this.impulse = IMP_weapon_next_bypriority.impulse;
264 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
266 .entity weaponentity = weaponentities[slot];
267 W_NextWeapon(this, 2, weaponentity);
269 if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
274 IMPULSE(weapon_prev_bypriority)
276 if (this.vehicle) return;
279 this.impulse = IMP_weapon_prev_bypriority.impulse;
282 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
284 .entity weaponentity = weaponentities[slot];
285 W_PreviousWeapon(this, 2, weaponentity);
287 if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
294 if (this.vehicle) return;
295 if (IS_DEAD(this)) return;
296 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
298 .entity weaponentity = weaponentities[slot];
299 W_LastWeapon(this, weaponentity);
301 if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
308 if (this.vehicle) return;
309 if (IS_DEAD(this)) return;
310 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
312 .entity weaponentity = weaponentities[slot];
313 W_SwitchWeapon(this, w_getbestweapon(this, weaponentity), weaponentity);
315 if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
322 if (this.vehicle) return;
323 if (IS_DEAD(this)) return;
324 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
326 .entity weaponentity = weaponentities[slot];
327 W_ThrowWeapon(this, weaponentity, W_CalculateProjectileVelocity(this, this.velocity, v_forward * 750, false), '0 0 0', true);
329 if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
334 IMPULSE(weapon_reload)
336 if (this.vehicle) return;
337 if (IS_DEAD(this)) return;
338 if (forbidWeaponUse(this)) return;
340 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
342 .entity weaponentity = weaponentities[slot];
343 Weapon w = this.(weaponentity).m_weapon;
344 w.wr_reload(w, actor, weaponentity);
346 if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
351 void ImpulseCommands(entity this)
353 if (game_stopped) return;
355 int imp = this.impulse;
359 if (MinigameImpulse(this, imp)) return;
361 if (timeout_status == TIMEOUT_ACTIVE) return; // don't allow any impulses while the game is paused
363 // allow only weapon change impulses when not in round time
364 if (round_handler_IsActive() && !round_handler_IsRoundStarted())
366 #define X(id) case IMP_##id.impulse:
381 X(weapon_next_bygroup)
382 X(weapon_prev_bygroup)
383 X(weapon_next_bypriority)
384 X(weapon_prev_bypriority)
388 X(weapon_priority_0_prev)
389 X(weapon_priority_1_prev)
390 X(weapon_priority_2_prev)
391 X(weapon_priority_3_prev)
392 X(weapon_priority_4_prev)
393 X(weapon_priority_5_prev)
394 X(weapon_priority_6_prev)
395 X(weapon_priority_7_prev)
396 X(weapon_priority_8_prev)
397 X(weapon_priority_9_prev)
398 X(weapon_priority_0_next)
399 X(weapon_priority_1_next)
400 X(weapon_priority_2_next)
401 X(weapon_priority_3_next)
402 X(weapon_priority_4_next)
403 X(weapon_priority_5_next)
404 X(weapon_priority_6_next)
405 X(weapon_priority_7_next)
406 X(weapon_priority_8_next)
407 X(weapon_priority_9_next)
408 X(weapon_priority_0_best)
409 X(weapon_priority_1_best)
410 X(weapon_priority_2_best)
411 X(weapon_priority_3_best)
412 X(weapon_priority_4_best)
413 X(weapon_priority_5_best)
414 X(weapon_priority_6_best)
415 X(weapon_priority_7_best)
416 X(weapon_priority_8_best)
417 X(weapon_priority_9_best)
448 if (vehicle_impulse(this, imp)) return;
450 if (CheatImpulse(this, imp)) return;
452 FOREACH(IMPULSES, it.impulse == imp, {
453 void(entity) f = it.impulse_handle;
465 IMPULSE(waypoint_personal_here)
467 entity wp = WaypointSprite_DeployPersonal(WP_Waypoint, this, this.origin, RADARICON_WAYPOINT);
468 if (wp) WaypointSprite_Ping(wp);
469 sprint(this, "personal waypoint spawned at location\n");
472 IMPULSE(waypoint_personal_crosshair)
474 WarpZone_crosshair_trace(this);
475 entity wp = WaypointSprite_DeployPersonal(WP_Waypoint, this, trace_endpos, RADARICON_WAYPOINT);
476 if (wp) WaypointSprite_Ping(wp);
477 sprint(this, "personal waypoint spawned at crosshair\n");
480 IMPULSE(waypoint_personal_death)
482 if (!this.death_origin) return;
483 entity wp = WaypointSprite_DeployPersonal(WP_Waypoint, this, this.death_origin, RADARICON_WAYPOINT);
484 if (wp) WaypointSprite_Ping(wp);
485 sprint(this, "personal waypoint spawned at death location\n");
488 IMPULSE(waypoint_here_follow)
490 if (!teamplay) return;
491 if (IS_DEAD(this)) return;
492 if (!MUTATOR_CALLHOOK(HelpMePing, this))
494 entity wp = WaypointSprite_Attach(WP_Helpme, this, true, RADARICON_HELPME);
495 if (!wp) WaypointSprite_HelpMePing(this.waypointsprite_attachedforcarrier);
496 else WaypointSprite_Ping(wp);
498 sprint(this, "HELP ME attached\n");
501 IMPULSE(waypoint_here_here)
503 entity wp = WaypointSprite_DeployFixed(WP_Here, false, this, this.origin, RADARICON_HERE);
504 if (wp) WaypointSprite_Ping(wp);
505 sprint(this, "HERE spawned at location\n");
508 IMPULSE(waypoint_here_crosshair)
510 WarpZone_crosshair_trace(this);
511 entity wp = WaypointSprite_DeployFixed(WP_Here, false, this, trace_endpos, RADARICON_HERE);
512 if (wp) WaypointSprite_Ping(wp);
513 sprint(this, "HERE spawned at crosshair\n");
516 IMPULSE(waypoint_here_death)
518 if (!this.death_origin) return;
519 entity wp = WaypointSprite_DeployFixed(WP_Here, false, this, this.death_origin, RADARICON_HERE);
520 if (wp) WaypointSprite_Ping(wp);
521 sprint(this, "HERE spawned at death location\n");
524 IMPULSE(waypoint_danger_here)
526 entity wp = WaypointSprite_DeployFixed(WP_Danger, false, this, this.origin, RADARICON_DANGER);
527 if (wp) WaypointSprite_Ping(wp);
528 sprint(this, "DANGER spawned at location\n");
531 IMPULSE(waypoint_danger_crosshair)
533 WarpZone_crosshair_trace(this);
534 entity wp = WaypointSprite_DeployFixed(WP_Danger, false, this, trace_endpos, RADARICON_DANGER);
535 if (wp) WaypointSprite_Ping(wp);
536 sprint(this, "DANGER spawned at crosshair\n");
539 IMPULSE(waypoint_danger_death)
541 if (!this.death_origin) return;
542 entity wp = WaypointSprite_DeployFixed(WP_Danger, false, this, this.death_origin, RADARICON_DANGER);
543 if (wp) WaypointSprite_Ping(wp);
544 sprint(this, "DANGER spawned at death location\n");
547 IMPULSE(waypoint_clear_personal)
549 WaypointSprite_ClearPersonal(this);
552 delete(this.personal);
553 this.personal = NULL;
555 if((g_cts || g_race) && autocvar_g_allow_checkpoints)
558 sprint(this, "personal waypoint cleared\n");
561 IMPULSE(waypoint_clear)
563 WaypointSprite_ClearOwned(this);
566 delete(this.personal);
567 this.personal = NULL;
568 if((g_cts || g_race) && autocvar_g_allow_checkpoints)
571 sprint(this, "all waypoints cleared\n");
574 vector waypoint_getSymmetricalOrigin(vector org, int ctf_flags)
576 vector new_org = org;
577 if (fabs(autocvar_g_waypointeditor_symmetrical) == 1)
579 vector map_center = havocbot_middlepoint;
580 if (autocvar_g_waypointeditor_symmetrical == -1)
581 map_center = autocvar_g_waypointeditor_symmetrical_origin;
583 new_org = Rotate(org - map_center, 360 * DEG2RAD / ctf_flags) + map_center;
585 else if (fabs(autocvar_g_waypointeditor_symmetrical) == 2)
587 float m = havocbot_symmetryaxis_equation.x;
588 float q = havocbot_symmetryaxis_equation.y;
589 if (autocvar_g_waypointeditor_symmetrical == -2)
591 m = autocvar_g_waypointeditor_symmetrical_axis.x;
592 q = autocvar_g_waypointeditor_symmetrical_axis.y;
595 new_org.x = (1 / (1 + m*m)) * ((1 - m*m) * org.x + 2 * m * org.y - 2 * m * q);
596 new_org.y = (1 / (1 + m*m)) * (2 * m * org.x + (m*m - 1) * org.y + 2 * q);
602 IMPULSE(navwaypoint_spawn)
604 if (!autocvar_g_waypointeditor) return;
606 vector org = this.origin;
607 int ctf_flags = havocbot_symmetryaxis_equation.z;
608 bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2)
609 || (autocvar_g_waypointeditor_symmetrical < 0));
610 int order = ctf_flags;
611 if(autocvar_g_waypointeditor_symmetrical_order >= 2)
613 order = autocvar_g_waypointeditor_symmetrical_order;
618 e = waypoint_spawn(org, org, 0);
619 waypoint_schedulerelink(e);
620 bprint(strcat("Waypoint spawned at ", vtos(org), "\n"));
623 org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
624 if (vdist(org - this.origin, >, 32))
635 IMPULSE(navwaypoint_remove)
637 if (!autocvar_g_waypointeditor) return;
638 entity e = navigation_findnearestwaypoint(this, false);
639 int ctf_flags = havocbot_symmetryaxis_equation.z;
640 bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2)
641 || (autocvar_g_waypointeditor_symmetrical < 0));
642 int order = ctf_flags;
643 if(autocvar_g_waypointeditor_symmetrical_order >= 2)
645 order = autocvar_g_waypointeditor_symmetrical_order;
651 if (e.wpflags & WAYPOINTFLAG_GENERATED) return;
655 LOG_INFO("^1Warning: ^7Removal of hardwired waypoints is not allowed in the editor. Please remove links from/to this waypoint (", vtos(e.origin), ") by hand from maps/", mapname, ".waypoints.hardwired\n");
659 entity wp_sym = NULL;
662 vector org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
663 IL_EACH(g_waypoints, !(it.wpflags & WAYPOINTFLAG_GENERATED), {
664 if(vdist(org - it.origin, <, 3))
671 bprint(strcat("Waypoint removed at ", vtos(e.origin), "\n"));
684 IMPULSE(navwaypoint_relink)
686 if (!autocvar_g_waypointeditor) return;
687 waypoint_schedulerelinkall();
690 IMPULSE(navwaypoint_save)
692 if (!autocvar_g_waypointeditor) return;
696 IMPULSE(navwaypoint_unreachable)
698 if (!autocvar_g_waypointeditor) return;
699 IL_EACH(g_waypoints, true,
701 it.colormod = '0.5 0.5 0.5';
702 it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
704 entity e2 = navigation_findnearestwaypoint(this, false);
705 navigation_markroutes(this, e2);
711 IL_EACH(g_waypoints, it.wpcost >= 10000000,
713 LOG_INFO("unreachable: ", etos(it), " ", vtos(it.origin), "\n");
715 it.effects |= EF_NODEPTHTEST | EF_BLUE;
719 if (j) LOG_INFOF("%d waypoints cannot be reached from here in any way (marked with blue light)\n", j);
720 navigation_markroutes_inverted(e2);
723 IL_EACH(g_waypoints, it.wpcost >= 10000000,
725 LOG_INFO("cannot reach me: ", etos(it), " ", vtos(it.origin), "\n");
727 if (!(it.effects & EF_NODEPTHTEST)) // not already reported before
729 it.effects |= EF_NODEPTHTEST | EF_RED;
732 if (j) LOG_INFOF("%d waypoints cannot walk to here in any way (marked with red light)\n", j);
733 if (m) LOG_INFOF("%d waypoints have been marked total\n", m);
736 IL_EACH(g_spawnpoints, true,
738 vector org = it.origin;
739 tracebox(it.origin, PL_MIN_CONST, PL_MAX_CONST, it.origin - '0 0 512', MOVE_NOMONSTERS, NULL);
740 setorigin(it, trace_endpos);
741 if (navigation_findnearestwaypoint(it, false))
744 it.effects &= ~EF_NODEPTHTEST;
750 LOG_INFO("spawn without waypoint: ", etos(it), " ", vtos(it.origin), "\n");
751 it.effects |= EF_NODEPTHTEST;
752 _setmodel(it, this.model);
753 it.frame = this.frame;
755 it.colormod = '8 0.5 8';
756 setsize(it, '0 0 0', '0 0 0');
760 if (j) LOG_INFOF("%d spawnpoints have no nearest waypoint (marked by player model)\n", j);
763 IL_EACH(g_items, true,
765 it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
766 it.colormod = '0.5 0.5 0.5';
768 IL_EACH(g_items, true,
770 if (navigation_findnearestwaypoint(it, false)) continue;
771 LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin), "\n");
772 it.effects |= EF_NODEPTHTEST | EF_RED;
776 if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked away from (marked with red light)\n", j);
779 IL_EACH(g_items, true,
781 if (navigation_findnearestwaypoint(it, true)) continue;
782 LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin), "\n");
783 it.effects |= EF_NODEPTHTEST | EF_BLUE;
787 if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked to (marked with blue light)\n", j);