]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/bot/default/waypoints.qc
4077a70a36c782dd0b9eebfbe9f8bc6b25f248e8
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / bot / default / waypoints.qc
1 #include "waypoints.qh"
2
3 #include "cvars.qh"
4
5 #include "bot.qh"
6 #include "navigation.qh"
7
8 #include <common/state.qh>
9
10 #include "../../antilag.qh"
11
12 #include <common/constants.qh>
13 #include <common/net_linked.qh>
14
15 #include <lib/warpzone/common.qh>
16 #include <lib/warpzone/util_server.qh>
17
18 void waypoint_unreachable(entity pl)
19 {
20         IL_EACH(g_waypoints, true,
21         {
22                 it.colormod = '0.5 0.5 0.5';
23                 it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
24         });
25         entity e2 = navigation_findnearestwaypoint(pl, false);
26         navigation_markroutes(pl, e2);
27
28         int j = 0;
29         int m = 0;
30         IL_EACH(g_waypoints, it.wpcost >= 10000000,
31         {
32                 LOG_INFO("unreachable: ", etos(it), " ", vtos(it.origin), "\n");
33                 it.colormod_z = 8;
34                 it.effects |= EF_NODEPTHTEST | EF_BLUE;
35                 j++;
36                 m++;
37         });
38         if (j) LOG_INFOF("%d waypoints cannot be reached from here in any way (marked with blue light)\n", j);
39         navigation_markroutes_inverted(e2);
40
41         j = 0;
42         IL_EACH(g_waypoints, it.wpcost >= 10000000,
43         {
44                 LOG_INFO("cannot reach me: ", etos(it), " ", vtos(it.origin), "\n");
45                 it.colormod_x = 8;
46                 if (!(it.effects & EF_NODEPTHTEST))  // not already reported before
47                         m++;
48                 it.effects |= EF_NODEPTHTEST | EF_RED;
49                 j++;
50         });
51         if (j) LOG_INFOF("%d waypoints cannot walk to here in any way (marked with red light)\n", j);
52         if (m) LOG_INFOF("%d waypoints have been marked total\n", m);
53
54         j = 0;
55         IL_EACH(g_spawnpoints, true,
56         {
57                 vector org = it.origin;
58                 tracebox(it.origin, PL_MIN_CONST, PL_MAX_CONST, it.origin - '0 0 512', MOVE_NOMONSTERS, NULL);
59                 setorigin(it, trace_endpos);
60                 if (navigation_findnearestwaypoint(it, false))
61                 {
62                         setorigin(it, org);
63                         it.effects &= ~EF_NODEPTHTEST;
64                         it.model = "";
65                 }
66                 else
67                 {
68                         setorigin(it, org);
69                         LOG_INFO("spawn without waypoint: ", etos(it), " ", vtos(it.origin), "\n");
70                         it.effects |= EF_NODEPTHTEST;
71                         _setmodel(it, pl.model);
72                         it.frame = pl.frame;
73                         it.skin = pl.skin;
74                         it.colormod = '8 0.5 8';
75                         setsize(it, '0 0 0', '0 0 0');
76                         j++;
77                 }
78         });
79         if (j) LOG_INFOF("%d spawnpoints have no nearest waypoint (marked by player model)\n", j);
80
81         j = 0;
82         IL_EACH(g_items, true,
83         {
84                 it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
85                 it.colormod = '0.5 0.5 0.5';
86         });
87         IL_EACH(g_items, true,
88         {
89                 if (navigation_findnearestwaypoint(it, false))
90                         continue;
91                 LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin), "\n");
92                 it.effects |= EF_NODEPTHTEST | EF_RED;
93                 it.colormod_x = 8;
94                 j++;
95         });
96         if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked away from (marked with red light)\n", j);
97
98         j = 0;
99         IL_EACH(g_items, true,
100         {
101                 if (navigation_findnearestwaypoint(it, true))
102                         continue;
103                 LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin), "\n");
104                 it.effects |= EF_NODEPTHTEST | EF_BLUE;
105                 it.colormod_z = 8;
106                 j++;
107         });
108         if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked to (marked with blue light)\n", j);
109 }
110
111 vector waypoint_getSymmetricalOrigin(vector org, int ctf_flags)
112 {
113         vector new_org = org;
114         if (fabs(autocvar_g_waypointeditor_symmetrical) == 1)
115         {
116                 vector map_center = havocbot_middlepoint;
117                 if (autocvar_g_waypointeditor_symmetrical == -1)
118                         map_center = autocvar_g_waypointeditor_symmetrical_origin;
119
120                 new_org = Rotate(org - map_center, 360 * DEG2RAD / ctf_flags) + map_center;
121         }
122         else if (fabs(autocvar_g_waypointeditor_symmetrical) == 2)
123         {
124                 float m = havocbot_symmetryaxis_equation.x;
125                 float q = havocbot_symmetryaxis_equation.y;
126                 if (autocvar_g_waypointeditor_symmetrical == -2)
127                 {
128                         m = autocvar_g_waypointeditor_symmetrical_axis.x;
129                         q = autocvar_g_waypointeditor_symmetrical_axis.y;
130                 }
131
132                 new_org.x = (1 / (1 + m*m)) * ((1 - m*m) * org.x + 2 * m * org.y - 2 * m * q);
133                 new_org.y = (1 / (1 + m*m)) * (2 * m * org.x + (m*m - 1) * org.y + 2 * q);
134         }
135         new_org.z = org.z;
136         return new_org;
137 }
138
139 void waypoint_setupmodel(entity wp)
140 {
141         if (autocvar_g_waypointeditor)
142         {
143                 // TODO: add some sort of visible box in edit mode for box waypoints
144                 vector m1 = wp.mins;
145                 vector m2 = wp.maxs;
146                 setmodel(wp, MDL_WAYPOINT);
147                 setsize(wp, m1, m2);
148                 wp.effects = EF_LOWPRECISION;
149                 if (wp.wpflags & WAYPOINTFLAG_ITEM)
150                         wp.colormod = '1 0 0';
151                 else if (wp.wpflags & WAYPOINTFLAG_GENERATED)
152                         wp.colormod = '1 1 0';
153                 else
154                         wp.colormod = '1 1 1';
155         }
156         else
157                 wp.model = "";
158 }
159
160 // create a new spawnfunc_waypoint and automatically link it to other waypoints, and link
161 // them back to it as well
162 // (suitable for spawnfunc_waypoint editor)
163 entity waypoint_spawn(vector m1, vector m2, float f)
164 {
165         if(!(f & WAYPOINTFLAG_PERSONAL))
166         {
167                 IL_EACH(g_waypoints, boxesoverlap(m1, m2, it.absmin, it.absmax),
168                 {
169                         return it;
170                 });
171         }
172
173         entity w = new(waypoint);
174         IL_PUSH(g_waypoints, w);
175         w.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
176         w.wpflags = f;
177         w.solid = SOLID_TRIGGER;
178         setorigin(w, (m1 + m2) * 0.5);
179         setsize(w, m1 - w.origin, m2 - w.origin);
180         if (w.size)
181                 w.wpisbox = true;
182
183         if(!w.wpisbox)
184         {
185                 setsize(w, PL_MIN_CONST - '1 1 0', PL_MAX_CONST + '1 1 0');
186                 if(!move_out_of_solid(w))
187                 {
188                         if(!(f & WAYPOINTFLAG_GENERATED))
189                         {
190                                 LOG_TRACE("Killed a waypoint that was stuck in solid at ", vtos(w.origin));
191                                 delete(w);
192                                 return NULL;
193                         }
194                         else
195                         {
196                                 if(autocvar_developer)
197                                 {
198                                         LOG_INFO("A generated waypoint is stuck in solid at ", vtos(w.origin), "\n");
199                                         backtrace("Waypoint stuck");
200                                 }
201                         }
202                 }
203                 setsize(w, '0 0 0', '0 0 0');
204         }
205
206         waypoint_clearlinks(w);
207         //waypoint_schedulerelink(w);
208
209         waypoint_setupmodel(w);
210
211         return w;
212 }
213
214 void waypoint_spawn_fromeditor(entity pl)
215 {
216         entity e;
217         vector org = pl.origin;
218         int ctf_flags = havocbot_symmetryaxis_equation.z;
219         bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2)
220                    || (autocvar_g_waypointeditor_symmetrical < 0));
221         int order = ctf_flags;
222         if(autocvar_g_waypointeditor_symmetrical_order >= 2)
223         {
224                 order = autocvar_g_waypointeditor_symmetrical_order;
225                 ctf_flags = order;
226         }
227
228         LABEL(add_wp);
229         e = waypoint_spawn(org, org, 0);
230         waypoint_schedulerelink(e);
231         bprint(strcat("Waypoint spawned at ", vtos(org), "\n"));
232         if(sym)
233         {
234                 org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
235                 if (vdist(org - pl.origin, >, 32))
236                 {
237                         if(order > 2)
238                                 order--;
239                         else
240                                 sym = false;
241                         goto add_wp;
242                 }
243         }
244 }
245
246 void waypoint_remove(entity wp)
247 {
248         // tell all waypoints linked to wp that they need to relink
249         IL_EACH(g_waypoints, it != wp,
250         {
251                 if (waypoint_islinked(it, wp))
252                         waypoint_removelink(it, wp);
253         });
254         delete(wp);
255 }
256
257 void waypoint_remove_fromeditor(entity pl)
258 {
259         entity e = navigation_findnearestwaypoint(pl, false);
260
261         int ctf_flags = havocbot_symmetryaxis_equation.z;
262         bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2)
263                    || (autocvar_g_waypointeditor_symmetrical < 0));
264         int order = ctf_flags;
265         if(autocvar_g_waypointeditor_symmetrical_order >= 2)
266         {
267                 order = autocvar_g_waypointeditor_symmetrical_order;
268                 ctf_flags = order;
269         }
270
271         LABEL(remove_wp);
272         if (!e) return;
273         if (e.wpflags & WAYPOINTFLAG_GENERATED) return;
274
275         if (e.wphardwired)
276         {
277                 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");
278                 return;
279         }
280
281         entity wp_sym = NULL;
282         if (sym)
283         {
284                 vector org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
285                 FOREACH_ENTITY_CLASS("waypoint", !(it.wpflags & WAYPOINTFLAG_GENERATED), {
286                         if(vdist(org - it.origin, <, 3))
287                         {
288                                 wp_sym = it;
289                                 break;
290                         }
291                 });
292         }
293
294         bprint(strcat("Waypoint removed at ", vtos(e.origin), "\n"));
295         waypoint_remove(e);
296
297         if (sym && wp_sym)
298         {
299                 e = wp_sym;
300                 if(order > 2)
301                         order--;
302                 else
303                         sym = false;
304                 goto remove_wp;
305         }
306 }
307
308 void waypoint_removelink(entity from, entity to)
309 {
310         if (from == to || (from.wpflags & WAYPOINTFLAG_NORELINK))
311                 return;
312
313         bool found = false;
314         if (!found && from.wp00 == to) found = true; if (found) {from.wp00 = from.wp01; from.wp00mincost = from.wp01mincost;}
315         if (!found && from.wp01 == to) found = true; if (found) {from.wp01 = from.wp02; from.wp01mincost = from.wp02mincost;}
316         if (!found && from.wp02 == to) found = true; if (found) {from.wp02 = from.wp03; from.wp02mincost = from.wp03mincost;}
317         if (!found && from.wp03 == to) found = true; if (found) {from.wp03 = from.wp04; from.wp03mincost = from.wp04mincost;}
318         if (!found && from.wp04 == to) found = true; if (found) {from.wp04 = from.wp05; from.wp04mincost = from.wp05mincost;}
319         if (!found && from.wp05 == to) found = true; if (found) {from.wp05 = from.wp06; from.wp05mincost = from.wp06mincost;}
320         if (!found && from.wp06 == to) found = true; if (found) {from.wp06 = from.wp07; from.wp06mincost = from.wp07mincost;}
321         if (!found && from.wp07 == to) found = true; if (found) {from.wp07 = from.wp08; from.wp07mincost = from.wp08mincost;}
322         if (!found && from.wp08 == to) found = true; if (found) {from.wp08 = from.wp09; from.wp08mincost = from.wp09mincost;}
323         if (!found && from.wp09 == to) found = true; if (found) {from.wp09 = from.wp10; from.wp09mincost = from.wp10mincost;}
324         if (!found && from.wp10 == to) found = true; if (found) {from.wp10 = from.wp11; from.wp10mincost = from.wp11mincost;}
325         if (!found && from.wp11 == to) found = true; if (found) {from.wp11 = from.wp12; from.wp11mincost = from.wp12mincost;}
326         if (!found && from.wp12 == to) found = true; if (found) {from.wp12 = from.wp13; from.wp12mincost = from.wp13mincost;}
327         if (!found && from.wp13 == to) found = true; if (found) {from.wp13 = from.wp14; from.wp13mincost = from.wp14mincost;}
328         if (!found && from.wp14 == to) found = true; if (found) {from.wp14 = from.wp15; from.wp14mincost = from.wp15mincost;}
329         if (!found && from.wp15 == to) found = true; if (found) {from.wp15 = from.wp16; from.wp15mincost = from.wp16mincost;}
330         if (!found && from.wp16 == to) found = true; if (found) {from.wp16 = from.wp17; from.wp16mincost = from.wp17mincost;}
331         if (!found && from.wp17 == to) found = true; if (found) {from.wp17 = from.wp18; from.wp17mincost = from.wp18mincost;}
332         if (!found && from.wp18 == to) found = true; if (found) {from.wp18 = from.wp19; from.wp18mincost = from.wp19mincost;}
333         if (!found && from.wp19 == to) found = true; if (found) {from.wp19 = from.wp20; from.wp19mincost = from.wp20mincost;}
334         if (!found && from.wp20 == to) found = true; if (found) {from.wp20 = from.wp21; from.wp20mincost = from.wp21mincost;}
335         if (!found && from.wp21 == to) found = true; if (found) {from.wp21 = from.wp22; from.wp21mincost = from.wp22mincost;}
336         if (!found && from.wp22 == to) found = true; if (found) {from.wp22 = from.wp23; from.wp22mincost = from.wp23mincost;}
337         if (!found && from.wp23 == to) found = true; if (found) {from.wp23 = from.wp24; from.wp23mincost = from.wp24mincost;}
338         if (!found && from.wp24 == to) found = true; if (found) {from.wp24 = from.wp25; from.wp24mincost = from.wp25mincost;}
339         if (!found && from.wp25 == to) found = true; if (found) {from.wp25 = from.wp26; from.wp25mincost = from.wp26mincost;}
340         if (!found && from.wp26 == to) found = true; if (found) {from.wp26 = from.wp27; from.wp26mincost = from.wp27mincost;}
341         if (!found && from.wp27 == to) found = true; if (found) {from.wp27 = from.wp28; from.wp27mincost = from.wp28mincost;}
342         if (!found && from.wp28 == to) found = true; if (found) {from.wp28 = from.wp29; from.wp28mincost = from.wp29mincost;}
343         if (!found && from.wp29 == to) found = true; if (found) {from.wp29 = from.wp30; from.wp29mincost = from.wp30mincost;}
344         if (!found && from.wp30 == to) found = true; if (found) {from.wp30 = from.wp31; from.wp30mincost = from.wp31mincost;}
345         if (found) {from.wp31 = NULL; from.wp31mincost = 10000000;}
346 }
347
348 bool waypoint_islinked(entity from, entity to)
349 {
350         if (from.wp00 == to) return true;if (from.wp01 == to) return true;if (from.wp02 == to) return true;if (from.wp03 == to) return true;
351         if (from.wp04 == to) return true;if (from.wp05 == to) return true;if (from.wp06 == to) return true;if (from.wp07 == to) return true;
352         if (from.wp08 == to) return true;if (from.wp09 == to) return true;if (from.wp10 == to) return true;if (from.wp11 == to) return true;
353         if (from.wp12 == to) return true;if (from.wp13 == to) return true;if (from.wp14 == to) return true;if (from.wp15 == to) return true;
354         if (from.wp16 == to) return true;if (from.wp17 == to) return true;if (from.wp18 == to) return true;if (from.wp19 == to) return true;
355         if (from.wp20 == to) return true;if (from.wp21 == to) return true;if (from.wp22 == to) return true;if (from.wp23 == to) return true;
356         if (from.wp24 == to) return true;if (from.wp25 == to) return true;if (from.wp26 == to) return true;if (from.wp27 == to) return true;
357         if (from.wp28 == to) return true;if (from.wp29 == to) return true;if (from.wp30 == to) return true;if (from.wp31 == to) return true;
358         return false;
359 }
360
361 // add a new link to the spawnfunc_waypoint, replacing the furthest link it already has
362 void waypoint_addlink(entity from, entity to)
363 {
364         float c;
365
366         if (from == to)
367                 return;
368         if (from.wpflags & WAYPOINTFLAG_NORELINK)
369                 return;
370
371         if (waypoint_islinked(from, to))
372                 return;
373
374         if (to.wpisbox || from.wpisbox)
375         {
376                 // if either is a box we have to find the nearest points on them to
377                 // calculate the distance properly
378                 vector v1, v2, m1, m2;
379                 v1 = from.origin;
380                 m1 = to.absmin;
381                 m2 = to.absmax;
382                 v1_x = bound(m1_x, v1_x, m2_x);
383                 v1_y = bound(m1_y, v1_y, m2_y);
384                 v1_z = bound(m1_z, v1_z, m2_z);
385                 v2 = to.origin;
386                 m1 = from.absmin;
387                 m2 = from.absmax;
388                 v2_x = bound(m1_x, v2_x, m2_x);
389                 v2_y = bound(m1_y, v2_y, m2_y);
390                 v2_z = bound(m1_z, v2_z, m2_z);
391                 v2 = to.origin;
392                 c = vlen(v2 - v1);
393         }
394         else
395                 c = vlen(to.origin - from.origin);
396
397         if (from.wp31mincost < c) return;
398         if (from.wp30mincost < c) {from.wp31 = to;from.wp31mincost = c;return;} from.wp31 = from.wp30;from.wp31mincost = from.wp30mincost;
399         if (from.wp29mincost < c) {from.wp30 = to;from.wp30mincost = c;return;} from.wp30 = from.wp29;from.wp30mincost = from.wp29mincost;
400         if (from.wp28mincost < c) {from.wp29 = to;from.wp29mincost = c;return;} from.wp29 = from.wp28;from.wp29mincost = from.wp28mincost;
401         if (from.wp27mincost < c) {from.wp28 = to;from.wp28mincost = c;return;} from.wp28 = from.wp27;from.wp28mincost = from.wp27mincost;
402         if (from.wp26mincost < c) {from.wp27 = to;from.wp27mincost = c;return;} from.wp27 = from.wp26;from.wp27mincost = from.wp26mincost;
403         if (from.wp25mincost < c) {from.wp26 = to;from.wp26mincost = c;return;} from.wp26 = from.wp25;from.wp26mincost = from.wp25mincost;
404         if (from.wp24mincost < c) {from.wp25 = to;from.wp25mincost = c;return;} from.wp25 = from.wp24;from.wp25mincost = from.wp24mincost;
405         if (from.wp23mincost < c) {from.wp24 = to;from.wp24mincost = c;return;} from.wp24 = from.wp23;from.wp24mincost = from.wp23mincost;
406         if (from.wp22mincost < c) {from.wp23 = to;from.wp23mincost = c;return;} from.wp23 = from.wp22;from.wp23mincost = from.wp22mincost;
407         if (from.wp21mincost < c) {from.wp22 = to;from.wp22mincost = c;return;} from.wp22 = from.wp21;from.wp22mincost = from.wp21mincost;
408         if (from.wp20mincost < c) {from.wp21 = to;from.wp21mincost = c;return;} from.wp21 = from.wp20;from.wp21mincost = from.wp20mincost;
409         if (from.wp19mincost < c) {from.wp20 = to;from.wp20mincost = c;return;} from.wp20 = from.wp19;from.wp20mincost = from.wp19mincost;
410         if (from.wp18mincost < c) {from.wp19 = to;from.wp19mincost = c;return;} from.wp19 = from.wp18;from.wp19mincost = from.wp18mincost;
411         if (from.wp17mincost < c) {from.wp18 = to;from.wp18mincost = c;return;} from.wp18 = from.wp17;from.wp18mincost = from.wp17mincost;
412         if (from.wp16mincost < c) {from.wp17 = to;from.wp17mincost = c;return;} from.wp17 = from.wp16;from.wp17mincost = from.wp16mincost;
413         if (from.wp15mincost < c) {from.wp16 = to;from.wp16mincost = c;return;} from.wp16 = from.wp15;from.wp16mincost = from.wp15mincost;
414         if (from.wp14mincost < c) {from.wp15 = to;from.wp15mincost = c;return;} from.wp15 = from.wp14;from.wp15mincost = from.wp14mincost;
415         if (from.wp13mincost < c) {from.wp14 = to;from.wp14mincost = c;return;} from.wp14 = from.wp13;from.wp14mincost = from.wp13mincost;
416         if (from.wp12mincost < c) {from.wp13 = to;from.wp13mincost = c;return;} from.wp13 = from.wp12;from.wp13mincost = from.wp12mincost;
417         if (from.wp11mincost < c) {from.wp12 = to;from.wp12mincost = c;return;} from.wp12 = from.wp11;from.wp12mincost = from.wp11mincost;
418         if (from.wp10mincost < c) {from.wp11 = to;from.wp11mincost = c;return;} from.wp11 = from.wp10;from.wp11mincost = from.wp10mincost;
419         if (from.wp09mincost < c) {from.wp10 = to;from.wp10mincost = c;return;} from.wp10 = from.wp09;from.wp10mincost = from.wp09mincost;
420         if (from.wp08mincost < c) {from.wp09 = to;from.wp09mincost = c;return;} from.wp09 = from.wp08;from.wp09mincost = from.wp08mincost;
421         if (from.wp07mincost < c) {from.wp08 = to;from.wp08mincost = c;return;} from.wp08 = from.wp07;from.wp08mincost = from.wp07mincost;
422         if (from.wp06mincost < c) {from.wp07 = to;from.wp07mincost = c;return;} from.wp07 = from.wp06;from.wp07mincost = from.wp06mincost;
423         if (from.wp05mincost < c) {from.wp06 = to;from.wp06mincost = c;return;} from.wp06 = from.wp05;from.wp06mincost = from.wp05mincost;
424         if (from.wp04mincost < c) {from.wp05 = to;from.wp05mincost = c;return;} from.wp05 = from.wp04;from.wp05mincost = from.wp04mincost;
425         if (from.wp03mincost < c) {from.wp04 = to;from.wp04mincost = c;return;} from.wp04 = from.wp03;from.wp04mincost = from.wp03mincost;
426         if (from.wp02mincost < c) {from.wp03 = to;from.wp03mincost = c;return;} from.wp03 = from.wp02;from.wp03mincost = from.wp02mincost;
427         if (from.wp01mincost < c) {from.wp02 = to;from.wp02mincost = c;return;} from.wp02 = from.wp01;from.wp02mincost = from.wp01mincost;
428         if (from.wp00mincost < c) {from.wp01 = to;from.wp01mincost = c;return;} from.wp01 = from.wp00;from.wp01mincost = from.wp00mincost;
429         from.wp00 = to;from.wp00mincost = c;return;
430 }
431
432 // relink this spawnfunc_waypoint
433 // (precompile a list of all reachable waypoints from this spawnfunc_waypoint)
434 // (SLOW!)
435 void waypoint_think(entity this)
436 {
437         vector sv, sm1, sm2, ev, em1, em2, dv;
438
439         bot_calculate_stepheightvec();
440
441         bot_navigation_movemode = ((autocvar_bot_navigation_ignoreplayers) ? MOVE_NOMONSTERS : MOVE_NORMAL);
442
443         //dprint("waypoint_think wpisbox = ", ftos(this.wpisbox), "\n");
444         sm1 = this.origin + this.mins;
445         sm2 = this.origin + this.maxs;
446         IL_EACH(g_waypoints, this != it,
447         {
448                 if (boxesoverlap(this.absmin, this.absmax, it.absmin, it.absmax))
449                 {
450                         waypoint_addlink(this, it);
451                         waypoint_addlink(it, this);
452                 }
453                 else
454                 {
455                         ++relink_total;
456                         if(!checkpvs(this.origin, it))
457                         {
458                                 ++relink_pvsculled;
459                                 continue;
460                         }
461                         sv = it.origin;
462                         sv.x = bound(sm1_x, sv.x, sm2_x);
463                         sv.y = bound(sm1_y, sv.y, sm2_y);
464                         sv.z = bound(sm1_z, sv.z, sm2_z);
465                         ev = this.origin;
466                         em1 = it.origin + it.mins;
467                         em2 = it.origin + it.maxs;
468                         ev.x = bound(em1_x, ev.x, em2_x);
469                         ev.y = bound(em1_y, ev.y, em2_y);
470                         ev.z = bound(em1_z, ev.z, em2_z);
471                         dv = ev - sv;
472                         dv.z = 0;
473                         if(vdist(dv, >=, 1050)) // max search distance in XY
474                         {
475                                 ++relink_lengthculled;
476                                 continue;
477                         }
478                         navigation_testtracewalk = 0;
479                         if (!this.wpisbox)
480                         {
481                                 tracebox(sv - PL_MIN_CONST.z * '0 0 1', PL_MIN_CONST, PL_MAX_CONST, sv, false, this);
482                                 if (!trace_startsolid)
483                                 {
484                                         //dprint("sv deviation", vtos(trace_endpos - sv), "\n");
485                                         sv = trace_endpos + '0 0 1';
486                                 }
487                         }
488                         if (!it.wpisbox)
489                         {
490                                 tracebox(ev - PL_MIN_CONST.z * '0 0 1', PL_MIN_CONST, PL_MAX_CONST, ev, false, it);
491                                 if (!trace_startsolid)
492                                 {
493                                         //dprint("ev deviation", vtos(trace_endpos - ev), "\n");
494                                         ev = trace_endpos + '0 0 1';
495                                 }
496                         }
497                         //traceline(this.origin, it.origin, false, NULL);
498                         //if (trace_fraction == 1)
499                         if (!this.wpisbox && tracewalk(this, sv, PL_MIN_CONST, PL_MAX_CONST, ev, MOVE_NOMONSTERS))
500                                 waypoint_addlink(this, it);
501                         else
502                                 relink_walkculled += 0.5;
503                         if (!it.wpisbox && tracewalk(it, ev, PL_MIN_CONST, PL_MAX_CONST, sv, MOVE_NOMONSTERS))
504                                 waypoint_addlink(it, this);
505                         else
506                                 relink_walkculled += 0.5;
507                 }
508         });
509         navigation_testtracewalk = 0;
510         this.wplinked = true;
511 }
512
513 void waypoint_clearlinks(entity wp)
514 {
515         // clear links to other waypoints
516         float f = 10000000;
517         wp.wp00 = wp.wp01 = wp.wp02 = wp.wp03 = wp.wp04 = wp.wp05 = wp.wp06 = wp.wp07 = NULL;
518         wp.wp08 = wp.wp09 = wp.wp10 = wp.wp11 = wp.wp12 = wp.wp13 = wp.wp14 = wp.wp15 = NULL;
519         wp.wp16 = wp.wp17 = wp.wp18 = wp.wp19 = wp.wp20 = wp.wp21 = wp.wp22 = wp.wp23 = NULL;
520         wp.wp24 = wp.wp25 = wp.wp26 = wp.wp27 = wp.wp28 = wp.wp29 = wp.wp30 = wp.wp31 = NULL;
521
522         wp.wp00mincost = wp.wp01mincost = wp.wp02mincost = wp.wp03mincost = wp.wp04mincost = wp.wp05mincost = wp.wp06mincost = wp.wp07mincost = f;
523         wp.wp08mincost = wp.wp09mincost = wp.wp10mincost = wp.wp11mincost = wp.wp12mincost = wp.wp13mincost = wp.wp14mincost = wp.wp15mincost = f;
524         wp.wp16mincost = wp.wp17mincost = wp.wp18mincost = wp.wp19mincost = wp.wp20mincost = wp.wp21mincost = wp.wp22mincost = wp.wp23mincost = f;
525         wp.wp24mincost = wp.wp25mincost = wp.wp26mincost = wp.wp27mincost = wp.wp28mincost = wp.wp29mincost = wp.wp30mincost = wp.wp31mincost = f;
526
527         wp.wplinked = false;
528 }
529
530 // tell a spawnfunc_waypoint to relink
531 void waypoint_schedulerelink(entity wp)
532 {
533         if (wp == NULL)
534                 return;
535
536         waypoint_setupmodel(wp);
537         wp.wpisbox = vdist(wp.size, >, 0);
538         wp.enemy = NULL;
539         if (!(wp.wpflags & WAYPOINTFLAG_PERSONAL))
540                 wp.owner = NULL;
541         if (!(wp.wpflags & WAYPOINTFLAG_NORELINK))
542                 waypoint_clearlinks(wp);
543         // schedule an actual relink on next frame
544         setthink(wp, waypoint_think);
545         wp.nextthink = time;
546         wp.effects = EF_LOWPRECISION;
547 }
548
549 // spawnfunc_waypoint map entity
550 spawnfunc(waypoint)
551 {
552         IL_PUSH(g_waypoints, this);
553
554         setorigin(this, this.origin);
555         // schedule a relink after other waypoints have had a chance to spawn
556         waypoint_clearlinks(this);
557         //waypoint_schedulerelink(this);
558 }
559
560 // tell all waypoints to relink
561 // actually this is useful only to update relink_* stats
562 void waypoint_schedulerelinkall()
563 {
564         relink_total = relink_walkculled = relink_pvsculled = relink_lengthculled = 0;
565         IL_EACH(g_waypoints, true,
566         {
567                 waypoint_schedulerelink(it);
568         });
569         waypoint_load_links_hardwired();
570 }
571
572 // Load waypoint links from file
573 float waypoint_load_links()
574 {
575         string filename, s;
576         float file, tokens, c = 0, found;
577         entity wp_from = NULL, wp_to;
578         vector wp_to_pos, wp_from_pos;
579         filename = strcat("maps/", mapname);
580         filename = strcat(filename, ".waypoints.cache");
581         file = fopen(filename, FILE_READ);
582
583         if (file < 0)
584         {
585                 LOG_TRACE("waypoint links load from ");
586                 LOG_TRACE(filename);
587                 LOG_TRACE(" failed");
588                 return false;
589         }
590
591         while ((s = fgets(file)))
592         {
593                 tokens = tokenizebyseparator(s, "*");
594
595                 if (tokens!=2)
596                 {
597                         // bad file format
598                         fclose(file);
599                         return false;
600                 }
601
602                 wp_from_pos     = stov(argv(0));
603                 wp_to_pos       = stov(argv(1));
604
605                 // Search "from" waypoint
606                 if(!wp_from || wp_from.origin!=wp_from_pos)
607                 {
608                         wp_from = findradius(wp_from_pos, 1);
609                         found = false;
610                         while(wp_from)
611                         {
612                                 if(vdist(wp_from.origin - wp_from_pos, <, 1))
613                                 if(wp_from.classname == "waypoint")
614                                 {
615                                         found = true;
616                                         break;
617                                 }
618                                 wp_from = wp_from.chain;
619                         }
620
621                         if(!found)
622                         {
623                                 LOG_TRACE("waypoint_load_links: couldn't find 'from' waypoint at ", vtos(wp_from_pos));
624                                 continue;
625                         }
626
627                 }
628
629                 // Search "to" waypoint
630                 wp_to = findradius(wp_to_pos, 1);
631                 found = false;
632                 while(wp_to)
633                 {
634                         if(vdist(wp_to.origin - wp_to_pos, <, 1))
635                         if(wp_to.classname == "waypoint")
636                         {
637                                 found = true;
638                                 break;
639                         }
640                         wp_to = wp_to.chain;
641                 }
642
643                 if(!found)
644                 {
645                         LOG_TRACE("waypoint_load_links: couldn't find 'to' waypoint at ", vtos(wp_to_pos));
646                         continue;
647                 }
648
649                 ++c;
650                 waypoint_addlink(wp_from, wp_to);
651         }
652
653         fclose(file);
654
655         LOG_TRACE("loaded ", ftos(c), " waypoint links from maps/", mapname, ".waypoints.cache");
656
657         botframe_cachedwaypointlinks = true;
658         return true;
659 }
660
661 void waypoint_load_or_remove_links_hardwired(bool removal_mode)
662 {
663         string filename, s;
664         float file, tokens, c = 0, found;
665         entity wp_from = NULL, wp_to;
666         vector wp_to_pos, wp_from_pos;
667         filename = strcat("maps/", mapname);
668         filename = strcat(filename, ".waypoints.hardwired");
669         file = fopen(filename, FILE_READ);
670
671         botframe_loadedforcedlinks = true;
672
673         if (file < 0)
674         {
675                 if(!removal_mode)
676                         LOG_TRACE("waypoint links load from ", filename, " failed");
677                 return;
678         }
679
680         while ((s = fgets(file)))
681         {
682                 if(substring(s, 0, 2)=="//")
683                         continue;
684
685                 if(substring(s, 0, 1)=="#")
686                         continue;
687
688                 tokens = tokenizebyseparator(s, "*");
689
690                 if (tokens!=2)
691                         continue;
692
693                 wp_from_pos     = stov(argv(0));
694                 wp_to_pos       = stov(argv(1));
695
696                 // Search "from" waypoint
697                 if(!wp_from || wp_from.origin!=wp_from_pos)
698                 {
699                         wp_from = findradius(wp_from_pos, 5);
700                         found = false;
701                         while(wp_from)
702                         {
703                                 if(vdist(wp_from.origin - wp_from_pos, <, 5))
704                                 if(wp_from.classname == "waypoint")
705                                 {
706                                         found = true;
707                                         break;
708                                 }
709                                 wp_from = wp_from.chain;
710                         }
711
712                         if(!found)
713                         {
714                                 if(!removal_mode)
715                                         LOG_INFO(strcat("NOTICE: Can not find waypoint at ", vtos(wp_from_pos), ". Path skipped\n"));
716                                 continue;
717                         }
718                 }
719
720                 // Search "to" waypoint
721                 wp_to = findradius(wp_to_pos, 5);
722                 found = false;
723                 while(wp_to)
724                 {
725                         if(vdist(wp_to.origin - wp_to_pos, <, 5))
726                         if(wp_to.classname == "waypoint")
727                         {
728                                 found = true;
729                                 break;
730                         }
731                         wp_to = wp_to.chain;
732                 }
733
734                 if(!found)
735                 {
736                         if(!removal_mode)
737                                 LOG_INFO(strcat("NOTICE: Can not find waypoint at ", vtos(wp_to_pos), ". Path skipped\n"));
738                         continue;
739                 }
740
741                 ++c;
742                 if(removal_mode)
743                 {
744                         waypoint_removelink(wp_from, wp_to);
745                         continue;
746                 }
747
748                 waypoint_addlink(wp_from, wp_to);
749                 wp_from.wphardwired = true;
750                 wp_to.wphardwired = true;
751         }
752
753         fclose(file);
754
755         if(!removal_mode)
756                 LOG_TRACE("loaded ", ftos(c), " waypoint links from maps/", mapname, ".waypoints.hardwired");
757 }
758
759 void waypoint_load_links_hardwired() { waypoint_load_or_remove_links_hardwired(false); }
760 void waypoint_remove_links_hardwired() { waypoint_load_or_remove_links_hardwired(true); }
761
762 entity waypoint_get_link(entity w, float i)
763 {
764         switch(i)
765         {
766                 case  0:return w.wp00;
767                 case  1:return w.wp01;
768                 case  2:return w.wp02;
769                 case  3:return w.wp03;
770                 case  4:return w.wp04;
771                 case  5:return w.wp05;
772                 case  6:return w.wp06;
773                 case  7:return w.wp07;
774                 case  8:return w.wp08;
775                 case  9:return w.wp09;
776                 case 10:return w.wp10;
777                 case 11:return w.wp11;
778                 case 12:return w.wp12;
779                 case 13:return w.wp13;
780                 case 14:return w.wp14;
781                 case 15:return w.wp15;
782                 case 16:return w.wp16;
783                 case 17:return w.wp17;
784                 case 18:return w.wp18;
785                 case 19:return w.wp19;
786                 case 20:return w.wp20;
787                 case 21:return w.wp21;
788                 case 22:return w.wp22;
789                 case 23:return w.wp23;
790                 case 24:return w.wp24;
791                 case 25:return w.wp25;
792                 case 26:return w.wp26;
793                 case 27:return w.wp27;
794                 case 28:return w.wp28;
795                 case 29:return w.wp29;
796                 case 30:return w.wp30;
797                 case 31:return w.wp31;
798                 default:return NULL;
799         }
800 }
801
802 // Save all waypoint links to a file
803 void waypoint_save_links()
804 {
805         // temporarily remove hardwired links so they don't get saved among normal links
806         waypoint_remove_links_hardwired();
807
808         string filename = sprintf("maps/%s.waypoints.cache", mapname);
809         int file = fopen(filename, FILE_WRITE);
810         if (file < 0)
811         {
812                 LOG_INFOF("waypoint link save to %s failed\n", filename);
813                 return;
814         }
815
816         int c = 0;
817         IL_EACH(g_waypoints, true,
818         {
819                 for(int j = 0; j < 32; ++j)
820                 {
821                         entity link = waypoint_get_link(it, j);
822                         if(link)
823                         {
824                                 string s = strcat(vtos(it.origin), "*", vtos(link.origin), "\n");
825                                 fputs(file, s);
826                                 ++c;
827                         }
828                 }
829         });
830         fclose(file);
831         botframe_cachedwaypointlinks = true;
832
833         LOG_INFOF("saved %d waypoint links to maps/%s.waypoints.cache\n", c, mapname);
834
835         waypoint_load_links_hardwired();
836 }
837
838 // save waypoints to gamedir/data/maps/mapname.waypoints
839 void waypoint_saveall()
840 {
841         string filename = sprintf("maps/%s.waypoints", mapname);
842         int file = fopen(filename, FILE_WRITE);
843         if (file < 0)
844         {
845                 waypoint_save_links(); // save anyway?
846                 botframe_loadedforcedlinks = false;
847
848                 LOG_INFOF("waypoint links: save to %s failed\n", filename);
849                 return;
850         }
851
852         int c = 0;
853         IL_EACH(g_waypoints, true,
854         {
855                 if(it.wpflags & WAYPOINTFLAG_GENERATED)
856                         continue;
857
858                 string s;
859                 s = strcat(vtos(it.origin + it.mins), "\n");
860                 s = strcat(s, vtos(it.origin + it.maxs));
861                 s = strcat(s, "\n");
862                 s = strcat(s, ftos(it.wpflags));
863                 s = strcat(s, "\n");
864                 fputs(file, s);
865                 c++;
866         });
867         fclose(file);
868         waypoint_save_links();
869         botframe_loadedforcedlinks = false;
870
871         LOG_INFOF("saved %d waypoints to maps/%s.waypoints\n", c, mapname);
872 }
873
874 // load waypoints from file
875 float waypoint_loadall()
876 {
877         string filename, s;
878         float file, cwp, cwb, fl;
879         vector m1, m2;
880         cwp = 0;
881         cwb = 0;
882         filename = strcat("maps/", mapname);
883         filename = strcat(filename, ".waypoints");
884         file = fopen(filename, FILE_READ);
885         if (file >= 0)
886         {
887                 while ((s = fgets(file)))
888                 {
889                         m1 = stov(s);
890                         s = fgets(file);
891                         if (!s)
892                                 break;
893                         m2 = stov(s);
894                         s = fgets(file);
895                         if (!s)
896                                 break;
897                         fl = stof(s);
898                         waypoint_spawn(m1, m2, fl);
899                         if (m1 == m2)
900                                 cwp = cwp + 1;
901                         else
902                                 cwb = cwb + 1;
903                 }
904                 fclose(file);
905                 LOG_TRACE("loaded ", ftos(cwp), " waypoints and ", ftos(cwb), " wayboxes from maps/", mapname, ".waypoints");
906         }
907         else
908         {
909                 LOG_TRACE("waypoint load from ", filename, " failed");
910         }
911         return cwp + cwb;
912 }
913
914 vector waypoint_fixorigin(vector position)
915 {
916         tracebox(position + '0 0 1' * (1 - PL_MIN_CONST.z), PL_MIN_CONST, PL_MAX_CONST, position + '0 0 -512', MOVE_NOMONSTERS, NULL);
917         if(trace_fraction < 1)
918                 position = trace_endpos;
919         //traceline(position, position + '0 0 -512', MOVE_NOMONSTERS, NULL);
920         //print("position is ", ftos(trace_endpos_z - position_z), " above solid\n");
921         return position;
922 }
923
924 void waypoint_spawnforitem_force(entity e, vector org)
925 {
926         // Fix the waypoint altitude if necessary
927         org = waypoint_fixorigin(org);
928
929         // don't spawn an item spawnfunc_waypoint if it already exists
930         IL_EACH(g_waypoints, true,
931         {
932                 if(it.wpisbox)
933                 {
934                         if(boxesoverlap(org, org, it.absmin, it.absmax))
935                         {
936                                 e.nearestwaypoint = it;
937                                 return;
938                         }
939                 }
940                 else
941                 {
942                         if(vdist(it.origin - org, <, 16))
943                         {
944                                 e.nearestwaypoint = it;
945                                 return;
946                         }
947                 }
948         });
949
950         e.nearestwaypoint = waypoint_spawn(org, org, WAYPOINTFLAG_GENERATED | WAYPOINTFLAG_ITEM);
951 }
952
953 void waypoint_spawnforitem(entity e)
954 {
955         if(!bot_waypoints_for_items)
956                 return;
957
958         waypoint_spawnforitem_force(e, e.origin);
959 }
960
961 void waypoint_spawnforteleporter_boxes(entity e, vector org1, vector org2, vector destination1, vector destination2, float timetaken)
962 {
963         entity w;
964         entity dw;
965         w = waypoint_spawn(org1, org2, WAYPOINTFLAG_GENERATED | WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_NORELINK);
966         dw = waypoint_spawn(destination1, destination2, WAYPOINTFLAG_GENERATED);
967         // one way link to the destination
968         w.wp00 = dw;
969         w.wp00mincost = timetaken; // this is just for jump pads
970         // the teleporter's nearest spawnfunc_waypoint is this one
971         // (teleporters are not goals, so this is probably useless)
972         e.nearestwaypoint = w;
973         e.nearestwaypointtimeout = -1;
974 }
975
976 void waypoint_spawnforteleporter_v(entity e, vector org, vector destination, float timetaken)
977 {
978         org = waypoint_fixorigin(org);
979         destination = waypoint_fixorigin(destination);
980         waypoint_spawnforteleporter_boxes(e, org, org, destination, destination, timetaken);
981 }
982
983 void waypoint_spawnforteleporter(entity e, vector destination, float timetaken)
984 {
985         destination = waypoint_fixorigin(destination);
986         waypoint_spawnforteleporter_boxes(e, e.absmin, e.absmax, destination, destination, timetaken);
987 }
988
989 entity waypoint_spawnpersonal(entity this, vector position)
990 {
991         entity w;
992
993         // drop the waypoint to a proper location:
994         //   first move it up by a player height
995         //   then move it down to hit the floor with player bbox size
996         position = waypoint_fixorigin(position);
997
998         w = waypoint_spawn(position, position, WAYPOINTFLAG_GENERATED | WAYPOINTFLAG_PERSONAL);
999         w.nearestwaypoint = NULL;
1000         w.nearestwaypointtimeout = 0;
1001         w.owner = this;
1002
1003         waypoint_schedulerelink(w);
1004
1005         return w;
1006 }
1007
1008 void waypoint_showlink(entity wp1, entity wp2, int display_type)
1009 {
1010         if (!(wp1 && wp2))
1011                 return;
1012
1013         if (wp1.wphardwired && wp2.wphardwired)
1014                 te_beam(NULL, wp1.origin, wp2.origin);
1015         else if (display_type == 1)
1016                 te_lightning2(NULL, wp1.origin, wp2.origin);
1017 }
1018
1019 void botframe_showwaypointlinks()
1020 {
1021         if (time < botframe_waypointeditorlightningtime)
1022                 return;
1023         botframe_waypointeditorlightningtime = time + 0.5;
1024         FOREACH_CLIENT(IS_PLAYER(it) && !it.isbot,
1025         {
1026                 int display_type = 0;
1027                 entity head = navigation_findnearestwaypoint(it, false);
1028                 if (IS_ONGROUND(it) || it.waterlevel > WATERLEVEL_NONE)
1029                         display_type = 1; // default
1030                 else if(head && (head.wphardwired))
1031                         display_type = 2; // only hardwired
1032
1033                 if (display_type)
1034                 {
1035                         //navigation_testtracewalk = true;
1036                         //print("currently selected WP is ", etos(head), "\n");
1037                         //navigation_testtracewalk = false;
1038                         if (head)
1039                         {
1040                                 te_lightning2(NULL, head.origin, it.origin);
1041                                 waypoint_showlink(head.wp00, head, display_type);
1042                                 waypoint_showlink(head.wp01, head, display_type);
1043                                 waypoint_showlink(head.wp02, head, display_type);
1044                                 waypoint_showlink(head.wp03, head, display_type);
1045                                 waypoint_showlink(head.wp04, head, display_type);
1046                                 waypoint_showlink(head.wp05, head, display_type);
1047                                 waypoint_showlink(head.wp06, head, display_type);
1048                                 waypoint_showlink(head.wp07, head, display_type);
1049                                 waypoint_showlink(head.wp08, head, display_type);
1050                                 waypoint_showlink(head.wp09, head, display_type);
1051                                 waypoint_showlink(head.wp10, head, display_type);
1052                                 waypoint_showlink(head.wp11, head, display_type);
1053                                 waypoint_showlink(head.wp12, head, display_type);
1054                                 waypoint_showlink(head.wp13, head, display_type);
1055                                 waypoint_showlink(head.wp14, head, display_type);
1056                                 waypoint_showlink(head.wp15, head, display_type);
1057                                 waypoint_showlink(head.wp16, head, display_type);
1058                                 waypoint_showlink(head.wp17, head, display_type);
1059                                 waypoint_showlink(head.wp18, head, display_type);
1060                                 waypoint_showlink(head.wp19, head, display_type);
1061                                 waypoint_showlink(head.wp20, head, display_type);
1062                                 waypoint_showlink(head.wp21, head, display_type);
1063                                 waypoint_showlink(head.wp22, head, display_type);
1064                                 waypoint_showlink(head.wp23, head, display_type);
1065                                 waypoint_showlink(head.wp24, head, display_type);
1066                                 waypoint_showlink(head.wp25, head, display_type);
1067                                 waypoint_showlink(head.wp26, head, display_type);
1068                                 waypoint_showlink(head.wp27, head, display_type);
1069                                 waypoint_showlink(head.wp28, head, display_type);
1070                                 waypoint_showlink(head.wp29, head, display_type);
1071                                 waypoint_showlink(head.wp30, head, display_type);
1072                                 waypoint_showlink(head.wp31, head, display_type);
1073                         }
1074                 }
1075         });
1076 }
1077
1078 float botframe_autowaypoints_fixdown(vector v)
1079 {
1080         tracebox(v, PL_MIN_CONST, PL_MAX_CONST, v + '0 0 -64', MOVE_NOMONSTERS, NULL);
1081         if(trace_fraction >= 1)
1082                 return 0;
1083         return 1;
1084 }
1085
1086 float botframe_autowaypoints_createwp(vector v, entity p, .entity fld, float f)
1087 {
1088         IL_EACH(g_waypoints, boxesoverlap(v - '32 32 32', v + '32 32 32', it.absmin, it.absmax),
1089         {
1090                 // if a matching spawnfunc_waypoint already exists, don't add a duplicate
1091                 return 0;
1092         });
1093
1094         waypoint_schedulerelink(p.(fld) = waypoint_spawn(v, v, f));
1095         return 1;
1096 }
1097
1098 // return value:
1099 //    1 = WP created
1100 //    0 = no action needed
1101 //   -1 = temp fail, try from world too
1102 //   -2 = permanent fail, do not retry
1103 float botframe_autowaypoints_fix_from(entity p, float walkfromwp, entity wp, .entity fld)
1104 {
1105         // make it possible to go from p to wp, if we can
1106         // if wp is NULL, nearest is chosen
1107
1108         entity w;
1109         vector porg;
1110         float t, tmin, tmax;
1111         vector o;
1112         vector save;
1113
1114         if(!botframe_autowaypoints_fixdown(p.origin))
1115                 return -2;
1116         porg = trace_endpos;
1117
1118         if(wp)
1119         {
1120                 // if any WP w fulfills wp -> w -> porg and w is closer than wp, then switch from wp to w
1121
1122                 // if wp -> porg, then OK
1123                 float maxdist;
1124                 if(navigation_waypoint_will_link(wp.origin, porg, p, walkfromwp, 1050))
1125                 {
1126                         // we may find a better one
1127                         maxdist = vlen(wp.origin - porg);
1128                 }
1129                 else
1130                 {
1131                         // accept any "good"
1132                         maxdist = 2100;
1133                 }
1134
1135                 float bestdist = maxdist;
1136                 IL_EACH(g_waypoints, it != wp && !(it.wpflags & WAYPOINTFLAG_NORELINK),
1137                 {
1138                         float d = vlen(wp.origin - it.origin) + vlen(it.origin - porg);
1139                         if(d < bestdist)
1140                         if(navigation_waypoint_will_link(wp.origin, it.origin, p, walkfromwp, 1050))
1141                         if(navigation_waypoint_will_link(it.origin, porg, p, walkfromwp, 1050))
1142                         {
1143                                 bestdist = d;
1144                                 p.(fld) = it;
1145                         }
1146                 });
1147                 if(bestdist < maxdist)
1148                 {
1149                         LOG_INFO("update chain to new nearest WP ", etos(p.(fld)), "\n");
1150                         return 0;
1151                 }
1152
1153                 if(bestdist < 2100)
1154                 {
1155                         // we know maxdist < 2100
1156                         // so wp -> porg is still valid
1157                         // all is good
1158                         p.(fld) = wp;
1159                         return 0;
1160                 }
1161
1162                 // otherwise, no existing WP can fix our issues
1163         }
1164         else
1165         {
1166                 save = p.origin;
1167                 setorigin(p, porg);
1168                 w = navigation_findnearestwaypoint(p, walkfromwp);
1169                 setorigin(p, save);
1170                 if(w)
1171                 {
1172                         p.(fld) = w;
1173                         return 0;
1174                 }
1175         }
1176
1177         tmin = 0;
1178         tmax = 1;
1179         for (;;)
1180         {
1181                 if(tmax - tmin < 0.001)
1182                 {
1183                         // did not get a good candidate
1184                         return -1;
1185                 }
1186
1187                 t = (tmin + tmax) * 0.5;
1188                 o = antilag_takebackorigin(p, CS(p), time - t);
1189                 if(!botframe_autowaypoints_fixdown(o))
1190                         return -2;
1191                 o = trace_endpos;
1192
1193                 if(wp)
1194                 {
1195                         if(!navigation_waypoint_will_link(wp.origin, o, p, walkfromwp, 1050))
1196                         {
1197                                 // we cannot walk from wp.origin to o
1198                                 // get closer to tmax
1199                                 tmin = t;
1200                                 continue;
1201                         }
1202                 }
1203                 else
1204                 {
1205                         save = p.origin;
1206                         setorigin(p, o);
1207                         w = navigation_findnearestwaypoint(p, walkfromwp);
1208                         setorigin(p, save);
1209                         if(!w)
1210                         {
1211                                 // we cannot walk from any WP to o
1212                                 // get closer to tmax
1213                                 tmin = t;
1214                                 continue;
1215                         }
1216                 }
1217
1218                 // if we get here, o is valid regarding waypoints
1219                 // check if o is connected right to the player
1220                 // we break if it succeeds, as that means o is a good waypoint location
1221                 if(navigation_waypoint_will_link(o, porg, p, walkfromwp, 1050))
1222                         break;
1223
1224                 // o is no good, we need to get closer to the player
1225                 tmax = t;
1226         }
1227
1228         LOG_INFO("spawning a waypoint for connecting to ", etos(wp), "\n");
1229         botframe_autowaypoints_createwp(o, p, fld, 0);
1230         return 1;
1231 }
1232
1233 // automatically create missing waypoints
1234 .entity botframe_autowaypoints_lastwp0, botframe_autowaypoints_lastwp1;
1235 void botframe_autowaypoints_fix(entity p, float walkfromwp, .entity fld)
1236 {
1237         float r = botframe_autowaypoints_fix_from(p, walkfromwp, p.(fld), fld);
1238         if(r != -1)
1239                 return;
1240         r = botframe_autowaypoints_fix_from(p, walkfromwp, NULL, fld);
1241         if(r != -1)
1242                 return;
1243
1244         LOG_INFO("emergency: got no good nearby WP to build a link from, starting a new chain\n");
1245         if(!botframe_autowaypoints_fixdown(p.origin))
1246                 return; // shouldn't happen, caught above
1247         botframe_autowaypoints_createwp(trace_endpos, p, fld, WAYPOINTFLAG_PROTECTED);
1248 }
1249
1250 void botframe_deleteuselesswaypoints()
1251 {
1252         IL_EACH(g_items, it.bot_pickup,
1253         {
1254                 // NOTE: this protects waypoints if they're the ONLY nearest
1255                 // waypoint. That's the intention.
1256                 navigation_findnearestwaypoint(it, false);  // Walk TO item.
1257                 navigation_findnearestwaypoint(it, true);  // Walk FROM item.
1258         });
1259         IL_EACH(g_waypoints, true,
1260         {
1261                 it.wpflags |= WAYPOINTFLAG_DEAD_END;
1262                 it.wpflags &= ~WAYPOINTFLAG_USEFUL;
1263                 // WP is useful if:
1264                 if (it.wpflags & WAYPOINTFLAG_ITEM)
1265                         it.wpflags |= WAYPOINTFLAG_USEFUL;
1266                 if (it.wpflags & WAYPOINTFLAG_TELEPORT)
1267                         it.wpflags |= WAYPOINTFLAG_USEFUL;
1268                 if (it.wpflags & WAYPOINTFLAG_PROTECTED)
1269                         it.wpflags |= WAYPOINTFLAG_USEFUL;
1270                 // b) WP is closest WP for an item/spawnpoint/other entity
1271                 //    This has been done above by protecting these WPs.
1272         });
1273         // c) There are w1, w, w2 so that w1 -> w, w -> w2 and not w1 -> w2.
1274         IL_EACH(g_waypoints, !(it.wpflags & WAYPOINTFLAG_PERSONAL),
1275         {
1276                 for (int m = 0; m < 32; ++m)
1277                 {
1278                         entity w = waypoint_get_link(it, m);
1279                         if (!w)
1280                                 break;
1281                         if (w.wpflags & WAYPOINTFLAG_PERSONAL)
1282                                 continue;
1283                         if (w.wpflags & WAYPOINTFLAG_USEFUL)
1284                                 continue;
1285                         for (int j = 0; j < 32; ++j)
1286                         {
1287                                 entity w2 = waypoint_get_link(w, j);
1288                                 if (!w2)
1289                                         break;
1290                                 if (it == w2)
1291                                         continue;
1292                                 if (w2.wpflags & WAYPOINTFLAG_PERSONAL)
1293                                         continue;
1294                                 // If we got here, it != w2 exist with it -> w
1295                                 // and w -> w2. That means the waypoint is not
1296                                 // a dead end.
1297                                 w.wpflags &= ~WAYPOINTFLAG_DEAD_END;
1298                                 for (int k = 0; k < 32; ++k)
1299                                 {
1300                                         if (waypoint_get_link(it, k) == w2)
1301                                                 continue;
1302                                         // IF WE GET HERE, w is proven useful
1303                                         // to get from it to w2!
1304                                         w.wpflags |= WAYPOINTFLAG_USEFUL;
1305                                         goto next;
1306                                 }
1307                         }
1308 LABEL(next)
1309                 }
1310         });
1311         // d) The waypoint is a dead end. Dead end waypoints must be kept as
1312         //     they are needed to complete routes while autowaypointing.
1313
1314         IL_EACH(g_waypoints, !(it.wpflags & (WAYPOINTFLAG_USEFUL | WAYPOINTFLAG_DEAD_END)),
1315         {
1316                 LOG_INFOF("Removed a waypoint at %v. Try again for more!\n", it.origin);
1317                 te_explosion(it.origin);
1318                 waypoint_remove(it);
1319                 break;
1320         });
1321
1322         IL_EACH(g_waypoints, true,
1323         {
1324                 it.wpflags &= ~(WAYPOINTFLAG_USEFUL | WAYPOINTFLAG_DEAD_END); // temp flag
1325         });
1326 }
1327
1328 void botframe_autowaypoints()
1329 {
1330         FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && !IS_DEAD(it), LAMBDA(
1331                 // going back is broken, so only fix waypoints to walk TO the player
1332                 //botframe_autowaypoints_fix(p, false, botframe_autowaypoints_lastwp0);
1333                 botframe_autowaypoints_fix(it, true, botframe_autowaypoints_lastwp1);
1334                 //te_explosion(p.botframe_autowaypoints_lastwp0.origin);
1335         ));
1336
1337         if (autocvar_g_waypointeditor_auto >= 2) {
1338                 botframe_deleteuselesswaypoints();
1339         }
1340 }
1341