Properly support team field on trigger_multiple
[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                 // allow reloading all active slots?
347                 //if(slot == 0 && autocvar_g_weaponswitch_debug != 1)
348                         //break;
349         }
350 }
351
352 void ImpulseCommands(entity this)
353 {
354         if (game_stopped) return;
355
356         int imp = CS(this).impulse;
357         if (!imp) return;
358         CS(this).impulse = 0;
359
360         if (MinigameImpulse(this, imp)) return;
361
362         if (timeout_status == TIMEOUT_ACTIVE) return;  // don't allow any impulses while the game is paused
363
364         // allow only weapon change impulses when not in round time
365         if (round_handler_IsActive() && !round_handler_IsRoundStarted())
366         {
367                 #define X(id) case IMP_##id.impulse:
368                 switch (imp)
369                 {
370                         X(weapon_group_0)
371                         X(weapon_group_1)
372                         X(weapon_group_2)
373                         X(weapon_group_3)
374                         X(weapon_group_4)
375                         X(weapon_group_5)
376                         X(weapon_group_6)
377                         X(weapon_group_7)
378                         X(weapon_group_8)
379                         X(weapon_group_9)
380                         X(weapon_next_byid)
381                         X(weapon_prev_byid)
382                         X(weapon_next_bygroup)
383                         X(weapon_prev_bygroup)
384                         X(weapon_next_bypriority)
385                         X(weapon_prev_bypriority)
386                         X(weapon_last)
387                         X(weapon_best)
388                         X(weapon_reload)
389                         X(weapon_priority_0_prev)
390             X(weapon_priority_1_prev)
391             X(weapon_priority_2_prev)
392             X(weapon_priority_3_prev)
393             X(weapon_priority_4_prev)
394             X(weapon_priority_5_prev)
395             X(weapon_priority_6_prev)
396             X(weapon_priority_7_prev)
397             X(weapon_priority_8_prev)
398             X(weapon_priority_9_prev)
399             X(weapon_priority_0_next)
400                         X(weapon_priority_1_next)
401                         X(weapon_priority_2_next)
402                         X(weapon_priority_3_next)
403                         X(weapon_priority_4_next)
404                         X(weapon_priority_5_next)
405                         X(weapon_priority_6_next)
406                         X(weapon_priority_7_next)
407                         X(weapon_priority_8_next)
408                         X(weapon_priority_9_next)
409                         X(weapon_priority_0_best)
410             X(weapon_priority_1_best)
411             X(weapon_priority_2_best)
412             X(weapon_priority_3_best)
413             X(weapon_priority_4_best)
414             X(weapon_priority_5_best)
415             X(weapon_priority_6_best)
416             X(weapon_priority_7_best)
417             X(weapon_priority_8_best)
418             X(weapon_priority_9_best)
419             X(weapon_byid_0)
420             X(weapon_byid_1)
421             X(weapon_byid_2)
422             X(weapon_byid_3)
423             X(weapon_byid_4)
424             X(weapon_byid_5)
425             X(weapon_byid_6)
426             X(weapon_byid_7)
427             X(weapon_byid_8)
428             X(weapon_byid_9)
429             X(weapon_byid_10)
430             X(weapon_byid_11)
431             X(weapon_byid_12)
432             X(weapon_byid_13)
433             X(weapon_byid_14)
434             X(weapon_byid_15)
435             X(weapon_byid_16)
436             X(weapon_byid_17)
437             X(weapon_byid_18)
438             X(weapon_byid_19)
439             X(weapon_byid_20)
440             X(weapon_byid_21)
441             X(weapon_byid_22)
442             X(weapon_byid_23)
443                         break;
444                         default: return;
445                 }
446 #undef X
447         }
448
449         if (vehicle_impulse(this, imp)) return;
450
451         if (CheatImpulse(this, imp)) return;
452
453         FOREACH(IMPULSES, it.impulse == imp, {
454                 void(entity) f = it.impulse_handle;
455                 if (!f) continue;
456                 f(this);
457                 return;
458         });
459 }
460
461 IMPULSE(use)
462 {
463         PlayerUseKey(this);
464 }
465
466 IMPULSE(waypoint_personal_here)
467 {
468         entity wp = WaypointSprite_DeployPersonal(WP_Waypoint, this, this.origin, RADARICON_WAYPOINT);
469         if (wp) WaypointSprite_Ping(wp);
470         sprint(this, "personal waypoint spawned at location\n");
471 }
472
473 IMPULSE(waypoint_personal_crosshair)
474 {
475         WarpZone_crosshair_trace(this);
476         entity wp = WaypointSprite_DeployPersonal(WP_Waypoint, this, trace_endpos, RADARICON_WAYPOINT);
477         if (wp) WaypointSprite_Ping(wp);
478         sprint(this, "personal waypoint spawned at crosshair\n");
479 }
480
481 IMPULSE(waypoint_personal_death)
482 {
483         if (!this.death_origin) return;
484         entity wp = WaypointSprite_DeployPersonal(WP_Waypoint, this, this.death_origin, RADARICON_WAYPOINT);
485         if (wp) WaypointSprite_Ping(wp);
486         sprint(this, "personal waypoint spawned at death location\n");
487 }
488
489 IMPULSE(waypoint_here_follow)
490 {
491         if (!teamplay) return;
492         if (IS_DEAD(this)) return;
493         if (!MUTATOR_CALLHOOK(HelpMePing, this))
494         {
495                 entity wp = WaypointSprite_Attach(WP_Helpme, this, true, RADARICON_HELPME);
496                 if (!wp) WaypointSprite_HelpMePing(this.waypointsprite_attachedforcarrier);
497                 else WaypointSprite_Ping(wp);
498         }
499         sprint(this, "HELP ME attached\n");
500 }
501
502 IMPULSE(waypoint_here_here)
503 {
504         entity wp = WaypointSprite_DeployFixed(WP_Here, false, this, this.origin, RADARICON_HERE);
505         if (wp) WaypointSprite_Ping(wp);
506         sprint(this, "HERE spawned at location\n");
507 }
508
509 IMPULSE(waypoint_here_crosshair)
510 {
511         WarpZone_crosshair_trace(this);
512         entity wp = WaypointSprite_DeployFixed(WP_Here, false, this, trace_endpos, RADARICON_HERE);
513         if (wp) WaypointSprite_Ping(wp);
514         sprint(this, "HERE spawned at crosshair\n");
515 }
516
517 IMPULSE(waypoint_here_death)
518 {
519         if (!this.death_origin) return;
520         entity wp = WaypointSprite_DeployFixed(WP_Here, false, this, this.death_origin, RADARICON_HERE);
521         if (wp) WaypointSprite_Ping(wp);
522         sprint(this, "HERE spawned at death location\n");
523 }
524
525 IMPULSE(waypoint_danger_here)
526 {
527         entity wp = WaypointSprite_DeployFixed(WP_Danger, false, this, this.origin, RADARICON_DANGER);
528         if (wp) WaypointSprite_Ping(wp);
529         sprint(this, "DANGER spawned at location\n");
530 }
531
532 IMPULSE(waypoint_danger_crosshair)
533 {
534         WarpZone_crosshair_trace(this);
535         entity wp = WaypointSprite_DeployFixed(WP_Danger, false, this, trace_endpos, RADARICON_DANGER);
536         if (wp) WaypointSprite_Ping(wp);
537         sprint(this, "DANGER spawned at crosshair\n");
538 }
539
540 IMPULSE(waypoint_danger_death)
541 {
542         if (!this.death_origin) return;
543         entity wp = WaypointSprite_DeployFixed(WP_Danger, false, this, this.death_origin, RADARICON_DANGER);
544         if (wp) WaypointSprite_Ping(wp);
545         sprint(this, "DANGER spawned at death location\n");
546 }
547
548 IMPULSE(waypoint_clear_personal)
549 {
550         WaypointSprite_ClearPersonal(this);
551         if (this.personal)
552         {
553                 delete(this.personal);
554                 this.personal = NULL;
555
556                 if((g_cts || g_race) && autocvar_g_allow_checkpoints)
557                         ClientKill(this);
558         }
559         sprint(this, "personal waypoint cleared\n");
560 }
561
562 IMPULSE(waypoint_clear)
563 {
564         WaypointSprite_ClearOwned(this);
565         if (this.personal)
566         {
567                 delete(this.personal);
568                 this.personal = NULL;
569                 if((g_cts || g_race) && autocvar_g_allow_checkpoints)
570                         ClientKill(this);
571         }
572         sprint(this, "all waypoints cleared\n");
573 }
574
575 vector waypoint_getSymmetricalOrigin(vector org, int ctf_flags)
576 {
577         vector new_org = org;
578         if (fabs(autocvar_g_waypointeditor_symmetrical) == 1)
579         {
580                 vector map_center = havocbot_middlepoint;
581                 if (autocvar_g_waypointeditor_symmetrical == -1)
582                         map_center = autocvar_g_waypointeditor_symmetrical_origin;
583
584                 new_org = Rotate(org - map_center, 360 * DEG2RAD / ctf_flags) + map_center;
585         }
586         else if (fabs(autocvar_g_waypointeditor_symmetrical) == 2)
587         {
588                 float m = havocbot_symmetryaxis_equation.x;
589                 float q = havocbot_symmetryaxis_equation.y;
590                 if (autocvar_g_waypointeditor_symmetrical == -2)
591                 {
592                         m = autocvar_g_waypointeditor_symmetrical_axis.x;
593                         q = autocvar_g_waypointeditor_symmetrical_axis.y;
594                 }
595
596                 new_org.x = (1 / (1 + m*m)) * ((1 - m*m) * org.x + 2 * m * org.y - 2 * m * q);
597                 new_org.y = (1 / (1 + m*m)) * (2 * m * org.x + (m*m - 1) * org.y + 2 * q);
598         }
599         new_org.z = org.z;
600         return new_org;
601 }
602
603 IMPULSE(navwaypoint_spawn)
604 {
605         if (!autocvar_g_waypointeditor) return;
606         entity e;
607         vector org = this.origin;
608         int ctf_flags = havocbot_symmetryaxis_equation.z;
609         bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2)
610                    || (autocvar_g_waypointeditor_symmetrical < 0));
611         int order = ctf_flags;
612         if(autocvar_g_waypointeditor_symmetrical_order >= 2)
613         {
614                 order = autocvar_g_waypointeditor_symmetrical_order;
615                 ctf_flags = order;
616         }
617
618         LABEL(add_wp);
619         e = waypoint_spawn(org, org, 0);
620         waypoint_schedulerelink(e);
621         bprint(strcat("Waypoint spawned at ", vtos(org), "\n"));
622         if(sym)
623         {
624                 org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
625                 if (vdist(org - this.origin, >, 32))
626                 {
627                         if(order > 2)
628                                 order--;
629                         else
630                                 sym = false;
631                         goto add_wp;
632                 }
633         }
634 }
635
636 IMPULSE(navwaypoint_remove)
637 {
638         if (!autocvar_g_waypointeditor) return;
639         entity e = navigation_findnearestwaypoint(this, false);
640         int ctf_flags = havocbot_symmetryaxis_equation.z;
641         bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2)
642                    || (autocvar_g_waypointeditor_symmetrical < 0));
643         int order = ctf_flags;
644         if(autocvar_g_waypointeditor_symmetrical_order >= 2)
645         {
646                 order = autocvar_g_waypointeditor_symmetrical_order;
647                 ctf_flags = order;
648         }
649
650         LABEL(remove_wp);
651         if (!e) return;
652         if (e.wpflags & WAYPOINTFLAG_GENERATED) return;
653
654         if (e.wphardwired)
655         {
656                 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");
657                 return;
658         }
659
660         entity wp_sym = NULL;
661         if (sym)
662         {
663                 vector org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
664                 IL_EACH(g_waypoints, !(it.wpflags & WAYPOINTFLAG_GENERATED), {
665                         if(vdist(org - it.origin, <, 3))
666                         {
667                                 wp_sym = it;
668                                 break;
669                         }
670                 });
671         }
672         bprint(strcat("Waypoint removed at ", vtos(e.origin), "\n"));
673         waypoint_remove(e);
674         if (sym && wp_sym)
675         {
676                 e = wp_sym;
677                 if(order > 2)
678                         order--;
679                 else
680                         sym = false;
681                 goto remove_wp;
682         }
683 }
684
685 IMPULSE(navwaypoint_relink)
686 {
687         if (!autocvar_g_waypointeditor) return;
688         waypoint_schedulerelinkall();
689 }
690
691 IMPULSE(navwaypoint_save)
692 {
693         if (!autocvar_g_waypointeditor) return;
694         waypoint_saveall();
695 }
696
697 IMPULSE(navwaypoint_unreachable)
698 {
699         if (!autocvar_g_waypointeditor) return;
700         IL_EACH(g_waypoints, true,
701         {
702                 it.colormod = '0.5 0.5 0.5';
703                 it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
704         });
705         entity e2 = navigation_findnearestwaypoint(this, false);
706         navigation_markroutes(this, e2);
707
708         int j, m;
709
710         j = 0;
711         m = 0;
712         IL_EACH(g_waypoints, it.wpcost >= 10000000,
713         {
714                 LOG_INFO("unreachable: ", etos(it), " ", vtos(it.origin));
715                 it.colormod_z = 8;
716                 it.effects |= EF_NODEPTHTEST | EF_BLUE;
717                 ++j;
718                 ++m;
719         });
720         if (j) LOG_INFOF("%d waypoints cannot be reached from here in any way (marked with blue light)", j);
721         navigation_markroutes_inverted(e2);
722
723         j = 0;
724         IL_EACH(g_waypoints, it.wpcost >= 10000000,
725         {
726                 LOG_INFO("cannot reach me: ", etos(it), " ", vtos(it.origin));
727                 it.colormod_x = 8;
728                 if (!(it.effects & EF_NODEPTHTEST))  // not already reported before
729                         ++m;
730                 it.effects |= EF_NODEPTHTEST | EF_RED;
731                 ++j;
732         });
733         if (j) LOG_INFOF("%d waypoints cannot walk to here in any way (marked with red light)", j);
734         if (m) LOG_INFOF("%d waypoints have been marked total", m);
735
736         j = 0;
737         IL_EACH(g_spawnpoints, true,
738         {
739                 vector org = it.origin;
740                 tracebox(it.origin, PL_MIN_CONST, PL_MAX_CONST, it.origin - '0 0 512', MOVE_NOMONSTERS, NULL);
741                 setorigin(it, trace_endpos);
742                 if (navigation_findnearestwaypoint(it, false))
743                 {
744                         setorigin(it, org);
745                         it.effects &= ~EF_NODEPTHTEST;
746                         it.model = "";
747                 }
748                 else
749                 {
750                         setorigin(it, org);
751                         LOG_INFO("spawn without waypoint: ", etos(it), " ", vtos(it.origin));
752                         it.effects |= EF_NODEPTHTEST;
753                         _setmodel(it, this.model);
754                         it.frame = this.frame;
755                         it.skin = this.skin;
756                         it.colormod = '8 0.5 8';
757                         setsize(it, '0 0 0', '0 0 0');
758                         ++j;
759                 }
760         });
761         if (j) LOG_INFOF("%d spawnpoints have no nearest waypoint (marked by player model)", j);
762
763         j = 0;
764         IL_EACH(g_items, true,
765         {
766                 it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
767                 it.colormod = '0.5 0.5 0.5';
768         });
769         IL_EACH(g_items, true,
770         {
771                 if (navigation_findnearestwaypoint(it, false)) continue;
772                 LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin));
773                 it.effects |= EF_NODEPTHTEST | EF_RED;
774                 it.colormod_x = 8;
775                 ++j;
776         });
777         if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked away from (marked with red light)", j);
778
779         j = 0;
780         IL_EACH(g_items, true,
781         {
782                 if (navigation_findnearestwaypoint(it, true)) continue;
783                 LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin));
784                 it.effects |= EF_NODEPTHTEST | EF_BLUE;
785                 it.colormod_z = 8;
786                 ++j;
787         });
788         if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked to (marked with blue light)", j);
789 }