aa18dab2ba07aba5eb4b1f686a5b04057d9c4f8b
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / impulse.qc
1 #include "impulse.qh"
2 #include "round_handler.qh"
3
4 #include "bot/api.qh"
5
6 #include "weapons/throwing.qh"
7 #include "command/common.qh"
8 #include "cheats.qh"
9 #include "weapons/selection.qh"
10 #include "weapons/tracing.qh"
11 #include "weapons/weaponsystem.qh"
12
13 #include <common/state.qh>
14
15 #include "../common/minigames/sv_minigames.qh"
16
17 #include <common/weapons/_all.qh>
18 #include "../common/vehicles/sv_vehicles.qh"
19
20 #include "../common/mutators/mutator/waypoints/waypointsprites.qh"
21
22 .entity vehicle;
23
24 #define IMPULSE(id) _IMPULSE(IMP_##id)
25 #define _IMPULSE(id) \
26         void id##_handle(entity this); \
27         STATIC_INIT_LATE(id) \
28         { \
29                 id.impulse_handle = id##_handle; \
30         } \
31         void id##_handle(entity this)
32
33 /**
34  * Impulse map:
35  *
36  * 0 reserved (no input)
37  *
38  * 99: loaded
39  *
40  * 140: moving clone
41  * 141: ctf speedrun
42  * 142: fixed clone
43  * 143: emergency teleport
44  * 148: unfairly eliminate
45  *
46  * TODO:
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)
51  */
52
53 // weapon switching impulses
54
55 #define X(slot) \
56         IMPULSE(weapon_group_##slot) \
57         { \
58                 if (IS_DEAD(this)) \
59                 { \
60                         this.impulse = IMP_weapon_group_##slot.impulse; \
61                         return; \
62                 } \
63                 for(int wepslot = 0; wepslot < MAX_WEAPONSLOTS; ++wepslot) \
64                 { \
65                         .entity weaponentity = weaponentities[wepslot]; \
66                         W_NextWeaponOnImpulse(this, slot, weaponentity); \
67                         if(wepslot == 0 && autocvar_g_weaponswitch_debug != 1) \
68                                 break; \
69                 } \
70         }
71 X(1)
72 X(2)
73 X(3)
74 X(4)
75 X(5)
76 X(6)
77 X(7)
78 X(8)
79 X(9)
80 X(0)
81 #undef X
82
83 // custom order weapon cycling
84
85 #define X(slot, dir) \
86         IMPULSE(weapon_priority_##slot##_##dir) \
87         { \
88                 if (this.vehicle) return; \
89                 if (IS_DEAD(this)) \
90                 { \
91                         this.impulse = IMP_weapon_priority_##slot##_##dir.impulse; \
92                         return; \
93                 } \
94                 noref int prev = -1; \
95                 noref int best =  0; \
96                 noref int next = +1; \
97                 for(int wepslot = 0; wepslot < MAX_WEAPONSLOTS; ++wepslot) \
98                 { \
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) \
102                                 break; \
103                 } \
104         }
105 X(0, prev)
106 X(1, prev)
107 X(2, prev)
108 X(3, prev)
109 X(4, prev)
110 X(5, prev)
111 X(6, prev)
112 X(7, prev)
113 X(8, prev)
114 X(9, prev)
115
116 X(0, best)
117 X(1, best)
118 X(2, best)
119 X(3, best)
120 X(4, best)
121 X(5, best)
122 X(6, best)
123 X(7, best)
124 X(8, best)
125 X(9, best)
126
127 X(0, next)
128 X(1, next)
129 X(2, next)
130 X(3, next)
131 X(4, next)
132 X(5, next)
133 X(6, next)
134 X(7, next)
135 X(8, next)
136 X(9, next)
137 #undef X
138
139 // direct weapons
140
141 #define X(i) \
142         IMPULSE(weapon_byid_##i) \
143         { \
144                 if (this.vehicle) return; \
145                 if (IS_DEAD(this)) \
146                 { \
147                         this.impulse = IMP_weapon_byid_##i.impulse; \
148                         return; \
149                 } \
150                 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) \
151                 { \
152                         .entity weaponentity = weaponentities[slot]; \
153                         W_SwitchWeapon(this, Weapons_from(WEP_FIRST + i), weaponentity); \
154                         if(slot == 0 && autocvar_g_weaponswitch_debug != 1) \
155                                 break; \
156                 } \
157         }
158 X(0)
159 X(1)
160 X(2)
161 X(3)
162 X(4)
163 X(5)
164 X(6)
165 X(7)
166 X(8)
167 X(9)
168 X(10)
169 X(11)
170 X(12)
171 X(13)
172 X(14)
173 X(15)
174 X(16)
175 X(17)
176 X(18)
177 X(19)
178 X(20)
179 X(21)
180 X(22)
181 X(23)
182 #undef X
183
184 IMPULSE(weapon_next_byid)
185 {
186         if (this.vehicle) return;
187         if (IS_DEAD(this))
188         {
189                 this.impulse = IMP_weapon_next_byid.impulse;
190                 return;
191         }
192         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
193         {
194                 .entity weaponentity = weaponentities[slot];
195                 W_NextWeapon(this, 0, weaponentity);
196
197                 if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
198                         break;
199         }
200 }
201
202 IMPULSE(weapon_prev_byid)
203 {
204         if (this.vehicle) return;
205         if (IS_DEAD(this))
206         {
207                 this.impulse = IMP_weapon_prev_byid.impulse;
208                 return;
209         }
210         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
211         {
212                 .entity weaponentity = weaponentities[slot];
213                 W_PreviousWeapon(this, 0, weaponentity);
214
215                 if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
216                         break;
217         }
218 }
219
220 IMPULSE(weapon_next_bygroup)
221 {
222         if (this.vehicle) return;
223         if (IS_DEAD(this))
224         {
225                 this.impulse = IMP_weapon_next_bygroup.impulse;
226                 return;
227         }
228         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
229         {
230                 .entity weaponentity = weaponentities[slot];
231                 W_NextWeapon(this, 1, weaponentity);
232
233                 if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
234                         break;
235         }
236 }
237
238 IMPULSE(weapon_prev_bygroup)
239 {
240         if (this.vehicle) return;
241         if (IS_DEAD(this))
242         {
243                 this.impulse = IMP_weapon_prev_bygroup.impulse;
244                 return;
245         }
246         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
247         {
248                 .entity weaponentity = weaponentities[slot];
249                 W_PreviousWeapon(this, 1, weaponentity);
250
251                 if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
252                         break;
253         }
254 }
255
256 IMPULSE(weapon_next_bypriority)
257 {
258         if (this.vehicle) return;
259         if (IS_DEAD(this))
260         {
261                 this.impulse = IMP_weapon_next_bypriority.impulse;
262                 return;
263         }
264         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
265         {
266                 .entity weaponentity = weaponentities[slot];
267                 W_NextWeapon(this, 2, weaponentity);
268
269                 if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
270                         break;
271         }
272 }
273
274 IMPULSE(weapon_prev_bypriority)
275 {
276         if (this.vehicle) return;
277         if (IS_DEAD(this))
278         {
279                 this.impulse = IMP_weapon_prev_bypriority.impulse;
280                 return;
281         }
282         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
283         {
284                 .entity weaponentity = weaponentities[slot];
285                 W_PreviousWeapon(this, 2, weaponentity);
286
287                 if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
288                         break;
289         }
290 }
291
292 IMPULSE(weapon_last)
293 {
294         if (this.vehicle) return;
295         if (IS_DEAD(this)) return;
296         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
297         {
298                 .entity weaponentity = weaponentities[slot];
299                 W_LastWeapon(this, weaponentity);
300
301                 if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
302                         break;
303         }
304 }
305
306 IMPULSE(weapon_best)
307 {
308         if (this.vehicle) return;
309         if (IS_DEAD(this)) return;
310         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
311         {
312                 .entity weaponentity = weaponentities[slot];
313                 W_SwitchWeapon(this, w_getbestweapon(this, weaponentity), weaponentity);
314
315                 if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
316                         break;
317         }
318 }
319
320 IMPULSE(weapon_drop)
321 {
322         if (this.vehicle) return;
323         if (IS_DEAD(this)) return;
324         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
325         {
326                 .entity weaponentity = weaponentities[slot];
327                 W_ThrowWeapon(this, weaponentity, W_CalculateProjectileVelocity(this, this.velocity, v_forward * 750, false), '0 0 0', true);
328
329                 if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
330                         break;
331         }
332 }
333
334 IMPULSE(weapon_reload)
335 {
336         if (this.vehicle) return;
337         if (IS_DEAD(this)) return;
338         if (forbidWeaponUse(this)) return;
339         entity actor = this;
340         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
341         {
342                 .entity weaponentity = weaponentities[slot];
343                 Weapon w = this.(weaponentity).m_weapon;
344                 w.wr_reload(w, actor, weaponentity);
345
346                 if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
347                         break;
348         }
349 }
350
351 void ImpulseCommands(entity this)
352 {
353         if (game_stopped) return;
354
355         int imp = this.impulse;
356         if (!imp) return;
357         this.impulse = 0;
358
359         if (MinigameImpulse(this, imp)) return;
360
361         if (timeout_status == TIMEOUT_ACTIVE) return;  // don't allow any impulses while the game is paused
362
363         // allow only weapon change impulses when not in round time
364         if (round_handler_IsActive() && !round_handler_IsRoundStarted())
365         {
366                 #define X(id) case IMP_##id.impulse:
367                 switch (imp)
368                 {
369                         X(weapon_group_0)
370                         X(weapon_group_1)
371                         X(weapon_group_2)
372                         X(weapon_group_3)
373                         X(weapon_group_4)
374                         X(weapon_group_5)
375                         X(weapon_group_6)
376                         X(weapon_group_7)
377                         X(weapon_group_8)
378                         X(weapon_group_9)
379                         X(weapon_next_byid)
380                         X(weapon_prev_byid)
381                         X(weapon_next_bygroup)
382                         X(weapon_prev_bygroup)
383                         X(weapon_next_bypriority)
384                         X(weapon_prev_bypriority)
385                         X(weapon_last)
386                         X(weapon_best)
387                         X(weapon_reload)
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)
418             X(weapon_byid_0)
419             X(weapon_byid_1)
420             X(weapon_byid_2)
421             X(weapon_byid_3)
422             X(weapon_byid_4)
423             X(weapon_byid_5)
424             X(weapon_byid_6)
425             X(weapon_byid_7)
426             X(weapon_byid_8)
427             X(weapon_byid_9)
428             X(weapon_byid_10)
429             X(weapon_byid_11)
430             X(weapon_byid_12)
431             X(weapon_byid_13)
432             X(weapon_byid_14)
433             X(weapon_byid_15)
434             X(weapon_byid_16)
435             X(weapon_byid_17)
436             X(weapon_byid_18)
437             X(weapon_byid_19)
438             X(weapon_byid_20)
439             X(weapon_byid_21)
440             X(weapon_byid_22)
441             X(weapon_byid_23)
442                         break;
443                         default: return;
444                 }
445 #undef X
446         }
447
448         if (vehicle_impulse(this, imp)) return;
449
450         if (CheatImpulse(this, imp)) return;
451
452         FOREACH(IMPULSES, it.impulse == imp, {
453                 void(entity) f = it.impulse_handle;
454                 if (!f) continue;
455                 f(this);
456                 return;
457         });
458 }
459
460 IMPULSE(use)
461 {
462         PlayerUseKey(this);
463 }
464
465 IMPULSE(waypoint_personal_here)
466 {
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");
470 }
471
472 IMPULSE(waypoint_personal_crosshair)
473 {
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");
478 }
479
480 IMPULSE(waypoint_personal_death)
481 {
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");
486 }
487
488 IMPULSE(waypoint_here_follow)
489 {
490         if (!teamplay) return;
491         if (IS_DEAD(this)) return;
492         if (!MUTATOR_CALLHOOK(HelpMePing, this))
493         {
494                 entity wp = WaypointSprite_Attach(WP_Helpme, this, true, RADARICON_HELPME);
495                 if (!wp) WaypointSprite_HelpMePing(this.waypointsprite_attachedforcarrier);
496                 else WaypointSprite_Ping(wp);
497         }
498         sprint(this, "HELP ME attached\n");
499 }
500
501 IMPULSE(waypoint_here_here)
502 {
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");
506 }
507
508 IMPULSE(waypoint_here_crosshair)
509 {
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");
514 }
515
516 IMPULSE(waypoint_here_death)
517 {
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");
522 }
523
524 IMPULSE(waypoint_danger_here)
525 {
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");
529 }
530
531 IMPULSE(waypoint_danger_crosshair)
532 {
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");
537 }
538
539 IMPULSE(waypoint_danger_death)
540 {
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");
545 }
546
547 IMPULSE(waypoint_clear_personal)
548 {
549         WaypointSprite_ClearPersonal(this);
550         if (this.personal)
551         {
552                 delete(this.personal);
553                 this.personal = NULL;
554
555                 if((g_cts || g_race) && autocvar_g_allow_checkpoints)
556                         ClientKill(this);
557         }
558         sprint(this, "personal waypoint cleared\n");
559 }
560
561 IMPULSE(waypoint_clear)
562 {
563         WaypointSprite_ClearOwned(this);
564         if (this.personal)
565         {
566                 delete(this.personal);
567                 this.personal = NULL;
568                 if((g_cts || g_race) && autocvar_g_allow_checkpoints)
569                         ClientKill(this);
570         }
571         sprint(this, "all waypoints cleared\n");
572 }
573
574 vector waypoint_getSymmetricalOrigin(vector org, int ctf_flags)
575 {
576         vector new_org = org;
577         if (fabs(autocvar_g_waypointeditor_symmetrical) == 1)
578         {
579                 vector map_center = havocbot_middlepoint;
580                 if (autocvar_g_waypointeditor_symmetrical == -1)
581                         map_center = autocvar_g_waypointeditor_symmetrical_origin;
582
583                 new_org = Rotate(org - map_center, 360 * DEG2RAD / ctf_flags) + map_center;
584         }
585         else if (fabs(autocvar_g_waypointeditor_symmetrical) == 2)
586         {
587                 float m = havocbot_symmetryaxys_equation.x;
588                 float q = havocbot_symmetryaxys_equation.y;
589                 if (autocvar_g_waypointeditor_symmetrical == -2)
590                 {
591                         m = autocvar_g_waypointeditor_symmetrical_axys.x;
592                         q = autocvar_g_waypointeditor_symmetrical_axys.y;
593                 }
594
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);
597         }
598         new_org.z = org.z;
599         return new_org;
600 }
601
602 IMPULSE(navwaypoint_spawn)
603 {
604         if (!autocvar_g_waypointeditor) return;
605         entity e;
606         vector org = this.origin;
607         bool sym = boolean(autocvar_g_waypointeditor_symmetrical);
608         int ctf_flags = havocbot_symmetryaxys_equation.z;
609         int order = ctf_flags;
610         if(autocvar_g_waypointeditor_symmetrical_order >= 2)
611         {
612                 order = autocvar_g_waypointeditor_symmetrical_order;
613                 ctf_flags = order;
614         }
615
616         LABEL(add_wp);
617         e = waypoint_spawn(org, org, 0);
618         waypoint_schedulerelink(e);
619         bprint(strcat("Waypoint spawned at ", vtos(org), "\n"));
620         if(sym)
621         {
622                 org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
623                 if (vdist(org - this.origin, >, 32))
624                 {
625                         if(order > 2)
626                                 order--;
627                         else
628                                 sym = false;
629                         goto add_wp;
630                 }
631         }
632 }
633
634 IMPULSE(navwaypoint_remove)
635 {
636         if (!autocvar_g_waypointeditor) return;
637         entity e = navigation_findnearestwaypoint(this, false);
638         bool sym = boolean(autocvar_g_waypointeditor_symmetrical);
639         int ctf_flags = havocbot_symmetryaxys_equation.z;
640         int order = ctf_flags;
641         if(autocvar_g_waypointeditor_symmetrical_order >= 2)
642         {
643                 order = autocvar_g_waypointeditor_symmetrical_order;
644                 ctf_flags = order;
645         }
646
647         LABEL(remove_wp);
648         if (!e) return;
649         if (e.wpflags & WAYPOINTFLAG_GENERATED) return;
650
651         if (e.wphardwired)
652         {
653                 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");
654                 return;
655         }
656
657         entity wp_sym = NULL;
658         if (sym)
659         {
660                 vector org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
661                 FOREACH_ENTITY_CLASS("waypoint", !(it.wpflags & WAYPOINTFLAG_GENERATED), {
662                         if(vdist(org - it.origin, <, 3))
663                         {
664                                 wp_sym = it;
665                                 break;
666                         }
667                 });
668         }
669         bprint(strcat("Waypoint removed at ", vtos(e.origin), "\n"));
670         waypoint_remove(e);
671         if (sym && wp_sym)
672         {
673                 e = wp_sym;
674                 if(order > 2)
675                         order--;
676                 else
677                         sym = false;
678                 goto remove_wp;
679         }
680 }
681
682 IMPULSE(navwaypoint_relink)
683 {
684         if (!autocvar_g_waypointeditor) return;
685         waypoint_schedulerelinkall();
686 }
687
688 IMPULSE(navwaypoint_save)
689 {
690         if (!autocvar_g_waypointeditor) return;
691         waypoint_saveall();
692 }
693
694 IMPULSE(navwaypoint_unreachable)
695 {
696         if (!autocvar_g_waypointeditor) return;
697         IL_EACH(g_waypoints, true,
698         {
699                 it.colormod = '0.5 0.5 0.5';
700                 it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
701         });
702         entity e2 = navigation_findnearestwaypoint(this, false);
703         navigation_markroutes(this, e2);
704
705         int j, m;
706
707         j = 0;
708         m = 0;
709         IL_EACH(g_waypoints, it.wpcost >= 10000000,
710         {
711                 LOG_INFO("unreachable: ", etos(it), " ", vtos(it.origin), "\n");
712                 it.colormod_z = 8;
713                 it.effects |= EF_NODEPTHTEST | EF_BLUE;
714                 ++j;
715                 ++m;
716         });
717         if (j) LOG_INFOF("%d waypoints cannot be reached from here in any way (marked with blue light)\n", j);
718         navigation_markroutes_inverted(e2);
719
720         j = 0;
721         IL_EACH(g_waypoints, it.wpcost >= 10000000,
722         {
723                 LOG_INFO("cannot reach me: ", etos(it), " ", vtos(it.origin), "\n");
724                 it.colormod_x = 8;
725                 if (!(it.effects & EF_NODEPTHTEST))  // not already reported before
726                         ++m;
727                 it.effects |= EF_NODEPTHTEST | EF_RED;
728                 ++j;
729         });
730         if (j) LOG_INFOF("%d waypoints cannot walk to here in any way (marked with red light)\n", j);
731         if (m) LOG_INFOF("%d waypoints have been marked total\n", m);
732
733         j = 0;
734         IL_EACH(g_spawnpoints, true,
735         {
736                 vector org = it.origin;
737                 tracebox(it.origin, PL_MIN_CONST, PL_MAX_CONST, it.origin - '0 0 512', MOVE_NOMONSTERS, NULL);
738                 setorigin(it, trace_endpos);
739                 if (navigation_findnearestwaypoint(it, false))
740                 {
741                         setorigin(it, org);
742                         it.effects &= ~EF_NODEPTHTEST;
743                         it.model = "";
744                 }
745                 else
746                 {
747                         setorigin(it, org);
748                         LOG_INFO("spawn without waypoint: ", etos(it), " ", vtos(it.origin), "\n");
749                         it.effects |= EF_NODEPTHTEST;
750                         _setmodel(it, this.model);
751                         it.frame = this.frame;
752                         it.skin = this.skin;
753                         it.colormod = '8 0.5 8';
754                         setsize(it, '0 0 0', '0 0 0');
755                         ++j;
756                 }
757         });
758         if (j) LOG_INFOF("%d spawnpoints have no nearest waypoint (marked by player model)\n", j);
759
760         j = 0;
761         IL_EACH(g_items, true,
762         {
763                 it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
764                 it.colormod = '0.5 0.5 0.5';
765         });
766         IL_EACH(g_items, true,
767         {
768                 if (navigation_findnearestwaypoint(it, false)) continue;
769                 LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin), "\n");
770                 it.effects |= EF_NODEPTHTEST | EF_RED;
771                 it.colormod_x = 8;
772                 ++j;
773         });
774         if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked away from (marked with red light)\n", j);
775
776         j = 0;
777         IL_EACH(g_items, true,
778         {
779                 if (navigation_findnearestwaypoint(it, true)) continue;
780                 LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin), "\n");
781                 it.effects |= EF_NODEPTHTEST | EF_BLUE;
782                 it.colormod_z = 8;
783                 ++j;
784         });
785         if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked to (marked with blue light)\n", j);
786 }