Fix bots waiting for a teamed item to spawn again once they got it (e.g. megas and...
[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, CS(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 = CS(this).impulse;
356         if (!imp) return;
357         CS(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_symmetryaxis_equation.x;
588                 float q = havocbot_symmetryaxis_equation.y;
589                 if (autocvar_g_waypointeditor_symmetrical == -2)
590                 {
591                         m = autocvar_g_waypointeditor_symmetrical_axis.x;
592                         q = autocvar_g_waypointeditor_symmetrical_axis.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         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)
612         {
613                 order = autocvar_g_waypointeditor_symmetrical_order;
614                 ctf_flags = order;
615         }
616
617         LABEL(add_wp);
618         e = waypoint_spawn(org, org, 0);
619         waypoint_schedulerelink(e);
620         bprint(strcat("Waypoint spawned at ", vtos(org), "\n"));
621         if(sym)
622         {
623                 org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
624                 if (vdist(org - this.origin, >, 32))
625                 {
626                         if(order > 2)
627                                 order--;
628                         else
629                                 sym = false;
630                         goto add_wp;
631                 }
632         }
633 }
634
635 IMPULSE(navwaypoint_remove)
636 {
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)
644         {
645                 order = autocvar_g_waypointeditor_symmetrical_order;
646                 ctf_flags = order;
647         }
648
649         LABEL(remove_wp);
650         if (!e) return;
651         if (e.wpflags & WAYPOINTFLAG_GENERATED) return;
652
653         if (e.wphardwired)
654         {
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");
656                 return;
657         }
658
659         entity wp_sym = NULL;
660         if (sym)
661         {
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))
665                         {
666                                 wp_sym = it;
667                                 break;
668                         }
669                 });
670         }
671         bprint(strcat("Waypoint removed at ", vtos(e.origin), "\n"));
672         waypoint_remove(e);
673         if (sym && wp_sym)
674         {
675                 e = wp_sym;
676                 if(order > 2)
677                         order--;
678                 else
679                         sym = false;
680                 goto remove_wp;
681         }
682 }
683
684 IMPULSE(navwaypoint_relink)
685 {
686         if (!autocvar_g_waypointeditor) return;
687         waypoint_schedulerelinkall();
688 }
689
690 IMPULSE(navwaypoint_save)
691 {
692         if (!autocvar_g_waypointeditor) return;
693         waypoint_saveall();
694 }
695
696 IMPULSE(navwaypoint_unreachable)
697 {
698         if (!autocvar_g_waypointeditor) return;
699         IL_EACH(g_waypoints, true,
700         {
701                 it.colormod = '0.5 0.5 0.5';
702                 it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
703         });
704         entity e2 = navigation_findnearestwaypoint(this, false);
705         navigation_markroutes(this, e2);
706
707         int j, m;
708
709         j = 0;
710         m = 0;
711         IL_EACH(g_waypoints, it.wpcost >= 10000000,
712         {
713                 LOG_INFO("unreachable: ", etos(it), " ", vtos(it.origin));
714                 it.colormod_z = 8;
715                 it.effects |= EF_NODEPTHTEST | EF_BLUE;
716                 ++j;
717                 ++m;
718         });
719         if (j) LOG_INFOF("%d waypoints cannot be reached from here in any way (marked with blue light)", j);
720         navigation_markroutes_inverted(e2);
721
722         j = 0;
723         IL_EACH(g_waypoints, it.wpcost >= 10000000,
724         {
725                 LOG_INFO("cannot reach me: ", etos(it), " ", vtos(it.origin));
726                 it.colormod_x = 8;
727                 if (!(it.effects & EF_NODEPTHTEST))  // not already reported before
728                         ++m;
729                 it.effects |= EF_NODEPTHTEST | EF_RED;
730                 ++j;
731         });
732         if (j) LOG_INFOF("%d waypoints cannot walk to here in any way (marked with red light)", j);
733         if (m) LOG_INFOF("%d waypoints have been marked total", m);
734
735         j = 0;
736         IL_EACH(g_spawnpoints, true,
737         {
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))
742                 {
743                         setorigin(it, org);
744                         it.effects &= ~EF_NODEPTHTEST;
745                         it.model = "";
746                 }
747                 else
748                 {
749                         setorigin(it, org);
750                         LOG_INFO("spawn without waypoint: ", etos(it), " ", vtos(it.origin));
751                         it.effects |= EF_NODEPTHTEST;
752                         _setmodel(it, this.model);
753                         it.frame = this.frame;
754                         it.skin = this.skin;
755                         it.colormod = '8 0.5 8';
756                         setsize(it, '0 0 0', '0 0 0');
757                         ++j;
758                 }
759         });
760         if (j) LOG_INFOF("%d spawnpoints have no nearest waypoint (marked by player model)", j);
761
762         j = 0;
763         IL_EACH(g_items, true,
764         {
765                 it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
766                 it.colormod = '0.5 0.5 0.5';
767         });
768         IL_EACH(g_items, true,
769         {
770                 if (navigation_findnearestwaypoint(it, false)) continue;
771                 LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin));
772                 it.effects |= EF_NODEPTHTEST | EF_RED;
773                 it.colormod_x = 8;
774                 ++j;
775         });
776         if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked away from (marked with red light)", j);
777
778         j = 0;
779         IL_EACH(g_items, true,
780         {
781                 if (navigation_findnearestwaypoint(it, true)) continue;
782                 LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin));
783                 it.effects |= EF_NODEPTHTEST | EF_BLUE;
784                 it.colormod_z = 8;
785                 ++j;
786         });
787         if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked to (marked with blue light)", j);
788 }