]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/bot/default/navigation.qc
Merge branch 'master' into Lyberta/KillSound
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / bot / default / navigation.qc
1 #include "navigation.qh"
2
3 #include <server/defs.qh>
4 #include <server/miscfunctions.qh>
5 #include "cvars.qh"
6
7 #include "bot.qh"
8 #include "waypoints.qh"
9
10 #include <common/t_items.qh>
11
12 #include <common/items/_mod.qh>
13
14 #include <common/constants.qh>
15 #include <common/net_linked.qh>
16 #include <common/triggers/trigger/jumppads.qh>
17
18 .float speed;
19
20 void navigation_dynamicgoal_init(entity this, bool initially_static)
21 {
22         this.navigation_dynamicgoal = true;
23         this.bot_basewaypoint = this.nearestwaypoint;
24         if(initially_static)
25                 this.nearestwaypointtimeout = -1;
26         else
27                 this.nearestwaypointtimeout = time;
28 }
29
30 void navigation_dynamicgoal_set(entity this)
31 {
32         this.nearestwaypointtimeout = time;
33 }
34
35 void navigation_dynamicgoal_unset(entity this)
36 {
37         if(this.bot_basewaypoint)
38                 this.nearestwaypoint = this.bot_basewaypoint;
39         this.nearestwaypointtimeout = -1;
40 }
41
42 // rough simulation of walking from one point to another to test if a path
43 // can be traveled, used for waypoint linking and havocbot
44
45 bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float movemode)
46 {
47         vector org;
48         vector move;
49         vector dir;
50         float dist;
51         float totaldist;
52         float stepdist;
53         float ignorehazards;
54         float swimming;
55         entity tw_ladder = NULL;
56
57         if(autocvar_bot_debug_tracewalk)
58         {
59                 debugresetnodes();
60                 debugnode(e, start);
61         }
62
63         move = end - start;
64         move.z = 0;
65         org = start;
66         dist = totaldist = vlen(move);
67         dir = normalize(move);
68         stepdist = 32;
69         ignorehazards = false;
70         swimming = false;
71
72         // Analyze starting point
73         traceline(start, start, MOVE_NORMAL, e);
74         if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA))
75                 ignorehazards = true;
76         else
77         {
78                 traceline( start, start + '0 0 -65536', MOVE_NORMAL, e);
79                 if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA))
80                 {
81                         ignorehazards = true;
82                         swimming = true;
83                 }
84         }
85         tracebox(start, m1, m2, start, MOVE_NOMONSTERS, e);
86         if (trace_startsolid)
87         {
88                 // Bad start
89                 if(autocvar_bot_debug_tracewalk)
90                         debugnodestatus(start, DEBUG_NODE_FAIL);
91
92                 //print("tracewalk: ", vtos(start), " is a bad start\n");
93                 return false;
94         }
95
96         // Movement loop
97         move = end - org;
98         for (;;)
99         {
100                 if (boxesoverlap(end, end, org + m1 + '-1 -1 -1', org + m2 + '1 1 1'))
101                 {
102                         // Succeeded
103                         if(autocvar_bot_debug_tracewalk)
104                                 debugnodestatus(org, DEBUG_NODE_SUCCESS);
105
106                         //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n");
107                         return true;
108                 }
109                 if(autocvar_bot_debug_tracewalk)
110                         debugnode(e, org);
111
112                 if (dist <= 0)
113                         break;
114                 if (stepdist > dist)
115                         stepdist = dist;
116                 dist = dist - stepdist;
117                 traceline(org, org, MOVE_NORMAL, e);
118                 if (!ignorehazards)
119                 {
120                         if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA))
121                         {
122                                 // hazards blocking path
123                                 if(autocvar_bot_debug_tracewalk)
124                                         debugnodestatus(org, DEBUG_NODE_FAIL);
125
126                                 //print("tracewalk: ", vtos(start), " hits a hazard when trying to reach ", vtos(end), "\n");
127                                 return false;
128                         }
129                 }
130                 if (trace_dpstartcontents & DPCONTENTS_LIQUIDSMASK)
131                 {
132                         move = normalize(end - org);
133                         tracebox(org, m1, m2, org + move * stepdist, movemode, e);
134
135                         if(autocvar_bot_debug_tracewalk)
136                                 debugnode(e, trace_endpos);
137
138                         if (trace_fraction < 1)
139                         {
140                                 swimming = true;
141                                 org = trace_endpos + normalize(org - trace_endpos) * stepdist;
142                                 for (; org.z < end.z + e.maxs.z; org.z += stepdist)
143                                 {
144                                         if(autocvar_bot_debug_tracewalk)
145                                                 debugnode(e, org);
146
147                                         if(pointcontents(org) == CONTENT_EMPTY)
148                                                 break;
149                                 }
150
151                                 if(pointcontents(org + '0 0 1') != CONTENT_EMPTY)
152                                 {
153                                         if(autocvar_bot_debug_tracewalk)
154                                                 debugnodestatus(org, DEBUG_NODE_FAIL);
155
156                                         return false;
157                                         //print("tracewalk: ", vtos(start), " failed under water\n");
158                                 }
159                                 continue;
160
161                         }
162                         else
163                                 org = trace_endpos;
164                 }
165                 else
166                 {
167                         move = dir * stepdist + org;
168                         tracebox(org, m1, m2, move, movemode, e);
169
170                         if(autocvar_bot_debug_tracewalk)
171                                 debugnode(e, trace_endpos);
172
173                         // hit something
174                         if (trace_fraction < 1)
175                         {
176                                 // check if we can walk over this obstacle, possibly by jumpstepping
177                                 tracebox(org + stepheightvec, m1, m2, move + stepheightvec, movemode, e);
178                                 if (trace_fraction < 1 || trace_startsolid)
179                                 {
180                                         tracebox(org + jumpstepheightvec, m1, m2, move + jumpstepheightvec, movemode, e);
181                                         if (trace_fraction < 1 || trace_startsolid)
182                                         {
183                                                 if(autocvar_bot_debug_tracewalk)
184                                                         debugnodestatus(trace_endpos, DEBUG_NODE_WARNING);
185
186                                                 IL_EACH(g_ladders, it.classname == "func_ladder",
187                                                         { it.solid = SOLID_BSP; });
188
189                                                 traceline( org, move, movemode, e);
190
191                                                 IL_EACH(g_ladders, it.classname == "func_ladder",
192                                                         { it.solid = SOLID_TRIGGER; });
193
194                                                 if ( trace_ent.classname == "door_rotating" || trace_ent.classname == "door")
195                                                 {
196                                                         vector nextmove;
197                                                         move = trace_endpos;
198                                                         while(trace_ent.classname == "door_rotating" || trace_ent.classname == "door")
199                                                         {
200                                                                 nextmove = move + (dir * stepdist);
201                                                                 traceline( move, nextmove, movemode, e);
202                                                                 move = nextmove;
203                                                         }
204                                                 }
205                                                 else if (trace_ent.classname == "func_ladder")
206                                                 {
207                                                         tw_ladder = trace_ent;
208                                                         vector ladder_bottom = trace_endpos - dir * m2.x;
209                                                         vector ladder_top = ladder_bottom;
210                                                         ladder_top.z = trace_ent.absmax.z + (-m1.z + 1);
211                                                         tracebox(ladder_bottom, m1, m2, ladder_top, movemode, e);
212                                                         if (trace_fraction < 1 || trace_startsolid)
213                                                         {
214                                                                 if(autocvar_bot_debug_tracewalk)
215                                                                         debugnodestatus(trace_endpos, DEBUG_NODE_FAIL);
216
217                                                                 return false; // failed
218                                                         }
219                                                         org = ladder_top + dir * m2.x;
220                                                         move = org + dir * stepdist;
221                                                         continue;
222                                                 }
223                                                 else
224                                                 {
225                                                         if(autocvar_bot_debug_tracewalk)
226                                                                 debugnodestatus(trace_endpos, DEBUG_NODE_FAIL);
227
228                                                         //print("tracewalk: ", vtos(start), " hit something when trying to reach ", vtos(end), "\n");
229                                                         //te_explosion(trace_endpos);
230                                                         //print(ftos(e.dphitcontentsmask), "\n");
231                                                         return false; // failed
232                                                 }
233                                         }
234                                         else
235                                                 move = trace_endpos;
236                                 }
237                                 else
238                                         move = trace_endpos;
239                         }
240                         else
241                                 move = trace_endpos;
242
243                         // trace down from stepheight as far as possible and move there,
244                         // if this starts in solid we try again without the stepup, and
245                         // if that also fails we assume it is a wall
246                         // (this is the same logic as the Quake walkmove function used)
247                         tracebox(move, m1, m2, move + '0 0 -65536', movemode, e);
248
249                         // moved successfully
250                         if(swimming)
251                         {
252                                 float c;
253                                 c = pointcontents(org + '0 0 1');
254                                 if (!(c == CONTENT_WATER || c == CONTENT_LAVA || c == CONTENT_SLIME))
255                                         swimming = false;
256                                 else
257                                         continue;
258                         }
259
260                         org = trace_endpos;
261                 }
262
263                 if(tw_ladder && org.z < tw_ladder.absmax.z)
264                 {
265                         // stop tracewalk if destination height is lower than the top of the ladder
266                         // otherwise bot can't easily figure out climbing direction
267                         if(autocvar_bot_debug_tracewalk)
268                                 debugnodestatus(org, DEBUG_NODE_FAIL);
269
270                         return false;
271                 }
272         }
273
274         //print("tracewalk: ", vtos(start), " did not arrive at ", vtos(end), " but at ", vtos(org), "\n");
275
276         // moved but didn't arrive at the intended destination
277         if(autocvar_bot_debug_tracewalk)
278                 debugnodestatus(org, DEBUG_NODE_FAIL);
279
280         return false;
281 }
282
283 /////////////////////////////////////////////////////////////////////////////
284 // goal stack
285 /////////////////////////////////////////////////////////////////////////////
286
287 // completely empty the goal stack, used when deciding where to go
288 void navigation_clearroute(entity this)
289 {
290         //print("bot ", etos(this), " clear\n");
291         this.goalentity = NULL;
292         this.goalcurrent = NULL;
293         this.goalstack01 = NULL;
294         this.goalstack02 = NULL;
295         this.goalstack03 = NULL;
296         this.goalstack04 = NULL;
297         this.goalstack05 = NULL;
298         this.goalstack06 = NULL;
299         this.goalstack07 = NULL;
300         this.goalstack08 = NULL;
301         this.goalstack09 = NULL;
302         this.goalstack10 = NULL;
303         this.goalstack11 = NULL;
304         this.goalstack12 = NULL;
305         this.goalstack13 = NULL;
306         this.goalstack14 = NULL;
307         this.goalstack15 = NULL;
308         this.goalstack16 = NULL;
309         this.goalstack17 = NULL;
310         this.goalstack18 = NULL;
311         this.goalstack19 = NULL;
312         this.goalstack20 = NULL;
313         this.goalstack21 = NULL;
314         this.goalstack22 = NULL;
315         this.goalstack23 = NULL;
316         this.goalstack24 = NULL;
317         this.goalstack25 = NULL;
318         this.goalstack26 = NULL;
319         this.goalstack27 = NULL;
320         this.goalstack28 = NULL;
321         this.goalstack29 = NULL;
322         this.goalstack30 = NULL;
323         this.goalstack31 = NULL;
324 }
325
326 // add a new goal at the beginning of the stack
327 // (in other words: add a new prerequisite before going to the later goals)
328 // NOTE: when a waypoint is added, the WP gets pushed first, then the
329 // next-closest WP on the shortest path to the WP
330 // That means, if the stack overflows, the bot will know how to do the FIRST 32
331 // steps to the goal, and then recalculate the path.
332 void navigation_pushroute(entity this, entity e)
333 {
334         //print("bot ", etos(this), " push ", etos(e), "\n");
335         if(this.goalstack31 == this.goalentity)
336                 this.goalentity = NULL;
337         this.goalstack31 = this.goalstack30;
338         this.goalstack30 = this.goalstack29;
339         this.goalstack29 = this.goalstack28;
340         this.goalstack28 = this.goalstack27;
341         this.goalstack27 = this.goalstack26;
342         this.goalstack26 = this.goalstack25;
343         this.goalstack25 = this.goalstack24;
344         this.goalstack24 = this.goalstack23;
345         this.goalstack23 = this.goalstack22;
346         this.goalstack22 = this.goalstack21;
347         this.goalstack21 = this.goalstack20;
348         this.goalstack20 = this.goalstack19;
349         this.goalstack19 = this.goalstack18;
350         this.goalstack18 = this.goalstack17;
351         this.goalstack17 = this.goalstack16;
352         this.goalstack16 = this.goalstack15;
353         this.goalstack15 = this.goalstack14;
354         this.goalstack14 = this.goalstack13;
355         this.goalstack13 = this.goalstack12;
356         this.goalstack12 = this.goalstack11;
357         this.goalstack11 = this.goalstack10;
358         this.goalstack10 = this.goalstack09;
359         this.goalstack09 = this.goalstack08;
360         this.goalstack08 = this.goalstack07;
361         this.goalstack07 = this.goalstack06;
362         this.goalstack06 = this.goalstack05;
363         this.goalstack05 = this.goalstack04;
364         this.goalstack04 = this.goalstack03;
365         this.goalstack03 = this.goalstack02;
366         this.goalstack02 = this.goalstack01;
367         this.goalstack01 = this.goalcurrent;
368         this.goalcurrent = e;
369 }
370
371 // remove first goal from stack
372 // (in other words: remove a prerequisite for reaching the later goals)
373 // (used when a spawnfunc_waypoint is reached)
374 void navigation_poproute(entity this)
375 {
376         //print("bot ", etos(this), " pop\n");
377         if(this.goalcurrent == this.goalentity)
378                 this.goalentity = NULL;
379         this.goalcurrent = this.goalstack01;
380         this.goalstack01 = this.goalstack02;
381         this.goalstack02 = this.goalstack03;
382         this.goalstack03 = this.goalstack04;
383         this.goalstack04 = this.goalstack05;
384         this.goalstack05 = this.goalstack06;
385         this.goalstack06 = this.goalstack07;
386         this.goalstack07 = this.goalstack08;
387         this.goalstack08 = this.goalstack09;
388         this.goalstack09 = this.goalstack10;
389         this.goalstack10 = this.goalstack11;
390         this.goalstack11 = this.goalstack12;
391         this.goalstack12 = this.goalstack13;
392         this.goalstack13 = this.goalstack14;
393         this.goalstack14 = this.goalstack15;
394         this.goalstack15 = this.goalstack16;
395         this.goalstack16 = this.goalstack17;
396         this.goalstack17 = this.goalstack18;
397         this.goalstack18 = this.goalstack19;
398         this.goalstack19 = this.goalstack20;
399         this.goalstack20 = this.goalstack21;
400         this.goalstack21 = this.goalstack22;
401         this.goalstack22 = this.goalstack23;
402         this.goalstack23 = this.goalstack24;
403         this.goalstack24 = this.goalstack25;
404         this.goalstack25 = this.goalstack26;
405         this.goalstack26 = this.goalstack27;
406         this.goalstack27 = this.goalstack28;
407         this.goalstack28 = this.goalstack29;
408         this.goalstack29 = this.goalstack30;
409         this.goalstack30 = this.goalstack31;
410         this.goalstack31 = NULL;
411 }
412
413 float navigation_waypoint_will_link(vector v, vector org, entity ent, float walkfromwp, float bestdist)
414 {
415         float dist;
416         dist = vlen(v - org);
417         if (bestdist > dist)
418         {
419                 traceline(v, org, true, ent);
420                 if (trace_fraction == 1)
421                 {
422                         if (walkfromwp)
423                         {
424                                 if (tracewalk(ent, v, PL_MIN_CONST, PL_MAX_CONST, org, bot_navigation_movemode))
425                                         return true;
426                         }
427                         else
428                         {
429                                 if (tracewalk(ent, org, PL_MIN_CONST, PL_MAX_CONST, v, bot_navigation_movemode))
430                                         return true;
431                         }
432                 }
433         }
434         return false;
435 }
436
437 // find the spawnfunc_waypoint near a dynamic goal such as a dropped weapon
438 entity navigation_findnearestwaypoint_withdist_except(entity ent, float walkfromwp, float bestdist, entity except)
439 {
440         vector pm1 = ent.origin + ent.mins;
441         vector pm2 = ent.origin + ent.maxs;
442
443         // do two scans, because box test is cheaper
444         IL_EACH(g_waypoints, it != ent && it != except,
445         {
446                 if(boxesoverlap(pm1, pm2, it.absmin, it.absmax))
447                         return it;
448         });
449
450         vector org = ent.origin + 0.5 * (ent.mins + ent.maxs);
451         org.z = ent.origin.z + ent.mins.z - PL_MIN_CONST.z; // player height
452         // TODO possibly make other code have the same support for bboxes
453         if(ent.tag_entity)
454                 org = org + ent.tag_entity.origin;
455         if (navigation_testtracewalk)
456                 te_plasmaburn(org);
457
458         entity best = NULL;
459         vector v;
460
461         // box check failed, try walk
462         IL_EACH(g_waypoints, it != ent,
463         {
464                 if(it.wpisbox)
465                 {
466                         vector wm1 = it.origin + it.mins;
467                         vector wm2 = it.origin + it.maxs;
468                         v.x = bound(wm1_x, org.x, wm2_x);
469                         v.y = bound(wm1_y, org.y, wm2_y);
470                         v.z = bound(wm1_z, org.z, wm2_z);
471                 }
472                 else
473                         v = it.origin;
474                 if(navigation_waypoint_will_link(v, org, ent, walkfromwp, bestdist))
475                 {
476                         bestdist = vlen(v - org);
477                         best = it;
478                 }
479         });
480         return best;
481 }
482 entity navigation_findnearestwaypoint(entity ent, float walkfromwp)
483 {
484         entity wp = navigation_findnearestwaypoint_withdist_except(ent, walkfromwp, 1050, NULL);
485         if (autocvar_g_waypointeditor_auto)
486         {
487                 entity wp2 = navigation_findnearestwaypoint_withdist_except(ent, walkfromwp, 1050, wp);
488                 if (wp && !wp2)
489                         wp.wpflags |= WAYPOINTFLAG_PROTECTED;
490         }
491         return wp;
492 }
493
494 // finds the waypoints near the bot initiating a navigation query
495 float navigation_markroutes_nearestwaypoints(entity this, float maxdist)
496 {
497         vector v, m1, m2;
498 //      navigation_testtracewalk = true;
499         int c = 0;
500         IL_EACH(g_waypoints, !it.wpconsidered,
501         {
502                 if (it.wpisbox)
503                 {
504                         m1 = it.origin + it.mins;
505                         m2 = it.origin + it.maxs;
506                         v = this.origin;
507                         v.x = bound(m1_x, v.x, m2_x);
508                         v.y = bound(m1_y, v.y, m2_y);
509                         v.z = bound(m1_z, v.z, m2_z);
510                 }
511                 else
512                         v = it.origin;
513                 vector diff = v - this.origin;
514                 diff.z = max(0, diff.z);
515                 if(vdist(diff, <, maxdist))
516                 {
517                         it.wpconsidered = true;
518                         if (tracewalk(this, this.origin, this.mins, this.maxs, v, bot_navigation_movemode))
519                         {
520                                 it.wpnearestpoint = v;
521                                 it.wpcost = vlen(v - this.origin) + it.dmg;
522                                 it.wpfire = 1;
523                                 it.enemy = NULL;
524                                 c = c + 1;
525                         }
526                 }
527         });
528         //navigation_testtracewalk = false;
529         return c;
530 }
531
532 // updates a path link if a spawnfunc_waypoint link is better than the current one
533 void navigation_markroutes_checkwaypoint(entity w, entity wp, float cost2, vector p)
534 {
535         vector m1;
536         vector m2;
537         vector v;
538         if (wp.wpisbox)
539         {
540                 m1 = wp.absmin;
541                 m2 = wp.absmax;
542                 v.x = bound(m1_x, p.x, m2_x);
543                 v.y = bound(m1_y, p.y, m2_y);
544                 v.z = bound(m1_z, p.z, m2_z);
545         }
546         else
547                 v = wp.origin;
548         cost2 = cost2 + vlen(v - p);
549         if (wp.wpcost > cost2)
550         {
551                 wp.wpcost = cost2;
552                 wp.enemy = w;
553                 wp.wpfire = 1;
554                 wp.wpnearestpoint = v;
555         }
556 }
557
558 // queries the entire spawnfunc_waypoint network for pathes leading away from the bot
559 void navigation_markroutes(entity this, entity fixed_source_waypoint)
560 {
561         float cost, cost2;
562         vector p;
563
564         IL_EACH(g_waypoints, true,
565         {
566                 it.wpconsidered = false;
567                 it.wpnearestpoint = '0 0 0';
568                 it.wpcost = 10000000;
569                 it.wpfire = 0;
570                 it.enemy = NULL;
571         });
572
573         if(fixed_source_waypoint)
574         {
575                 fixed_source_waypoint.wpconsidered = true;
576                 fixed_source_waypoint.wpnearestpoint = fixed_source_waypoint.origin + 0.5 * (fixed_source_waypoint.mins + fixed_source_waypoint.maxs);
577                 fixed_source_waypoint.wpcost = fixed_source_waypoint.dmg;
578                 fixed_source_waypoint.wpfire = 1;
579                 fixed_source_waypoint.enemy = NULL;
580         }
581         else
582         {
583                 // try a short range search for the nearest waypoints, and expand the search repeatedly if none are found
584                 // as this search is expensive we will use lower values if the bot is on the air
585                 float increment, maxdistance;
586                 if(IS_ONGROUND(this))
587                 {
588                         increment = 750;
589                         maxdistance = 50000;
590                 }
591                 else
592                 {
593                         increment = 500;
594                         maxdistance = 1500;
595                 }
596
597                 for(int j = increment; !navigation_markroutes_nearestwaypoints(this, j) && j < maxdistance; j += increment);
598         }
599
600         bool searching = true;
601         while (searching)
602         {
603                 searching = false;
604                 IL_EACH(g_waypoints, it.wpfire,
605                 {
606                         searching = true;
607                         it.wpfire = 0;
608                         cost = it.wpcost;
609                         p = it.wpnearestpoint;
610                         entity wp;
611                         wp = it.wp00;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp00mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
612                         wp = it.wp01;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp01mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
613                         wp = it.wp02;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp02mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
614                         wp = it.wp03;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp03mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
615                         wp = it.wp04;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp04mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
616                         wp = it.wp05;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp05mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
617                         wp = it.wp06;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp06mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
618                         wp = it.wp07;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp07mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
619                         wp = it.wp08;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp08mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
620                         wp = it.wp09;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp09mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
621                         wp = it.wp10;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp10mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
622                         wp = it.wp11;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp11mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
623                         wp = it.wp12;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp12mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
624                         wp = it.wp13;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp13mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
625                         wp = it.wp14;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp14mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
626                         wp = it.wp15;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp15mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
627                         wp = it.wp16;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp16mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
628                         wp = it.wp17;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp17mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
629                         wp = it.wp18;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp18mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
630                         wp = it.wp19;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp19mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
631                         wp = it.wp20;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp20mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
632                         wp = it.wp21;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp21mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
633                         wp = it.wp22;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp22mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
634                         wp = it.wp23;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp23mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
635                         wp = it.wp24;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp24mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
636                         wp = it.wp25;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp25mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
637                         wp = it.wp26;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp26mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
638                         wp = it.wp27;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp27mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
639                         wp = it.wp28;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp28mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
640                         wp = it.wp29;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp29mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
641                         wp = it.wp30;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp30mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
642                         wp = it.wp31;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp31mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
643                         }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
644                 });
645         }
646 }
647
648 // queries the entire spawnfunc_waypoint network for pathes leading to the bot
649 void navigation_markroutes_inverted(entity fixed_source_waypoint)
650 {
651         float cost, cost2;
652         vector p;
653         IL_EACH(g_waypoints, true,
654         {
655                 it.wpconsidered = false;
656                 it.wpnearestpoint = '0 0 0';
657                 it.wpcost = 10000000;
658                 it.wpfire = 0;
659                 it.enemy = NULL;
660         });
661
662         if(fixed_source_waypoint)
663         {
664                 fixed_source_waypoint.wpconsidered = true;
665                 fixed_source_waypoint.wpnearestpoint = fixed_source_waypoint.origin + 0.5 * (fixed_source_waypoint.mins + fixed_source_waypoint.maxs);
666                 fixed_source_waypoint.wpcost = fixed_source_waypoint.dmg; // the cost to get from X to fixed_source_waypoint
667                 fixed_source_waypoint.wpfire = 1;
668                 fixed_source_waypoint.enemy = NULL;
669         }
670         else
671         {
672                 error("need to start with a waypoint\n");
673         }
674
675         bool searching = true;
676         while (searching)
677         {
678                 searching = false;
679                 IL_EACH(g_waypoints, it.wpfire,
680                 {
681                         searching = true;
682                         it.wpfire = 0;
683                         cost = it.wpcost; // cost to walk from it to home
684                         p = it.wpnearestpoint;
685                         entity wp = it;
686                         IL_EACH(g_waypoints, true,
687                         {
688                                 if(wp != it.wp00) if(wp != it.wp01) if(wp != it.wp02) if(wp != it.wp03)
689                                 if(wp != it.wp04) if(wp != it.wp05) if(wp != it.wp06) if(wp != it.wp07)
690                                 if(wp != it.wp08) if(wp != it.wp09) if(wp != it.wp10) if(wp != it.wp11)
691                                 if(wp != it.wp12) if(wp != it.wp13) if(wp != it.wp14) if(wp != it.wp15)
692                                 if(wp != it.wp16) if(wp != it.wp17) if(wp != it.wp18) if(wp != it.wp19)
693                                 if(wp != it.wp20) if(wp != it.wp21) if(wp != it.wp22) if(wp != it.wp23)
694                                 if(wp != it.wp24) if(wp != it.wp25) if(wp != it.wp26) if(wp != it.wp27)
695                                 if(wp != it.wp28) if(wp != it.wp29) if(wp != it.wp30) if(wp != it.wp31)
696                                         continue;
697                                 cost2 = cost + it.dmg;
698                                 navigation_markroutes_checkwaypoint(wp, it, cost2, p);
699                         });
700                 });
701         }
702 }
703
704 // updates the best goal according to a weighted calculation of travel cost and item value of a new proposed item
705 void navigation_routerating(entity this, entity e, float f, float rangebias)
706 {
707         if (!e)
708                 return;
709
710         if(e.blacklisted)
711                 return;
712
713         if (IS_PLAYER(e))
714         {
715                 bool rate_wps = false;
716                 if((e.flags & FL_INWATER) || (e.flags & FL_PARTIALGROUND))
717                         rate_wps = true;
718
719                 if(!IS_ONGROUND(e))
720                 {
721                         traceline(e.origin, e.origin + '0 0 -1500', true, NULL);
722                         int t = pointcontents(trace_endpos + '0 0 1');
723                         if(t != CONTENT_SOLID )
724                         {
725                                 if(t == CONTENT_WATER || t == CONTENT_SLIME || t == CONTENT_LAVA)
726                                         rate_wps = true;
727                                 else if(tracebox_hits_trigger_hurt(e.origin, e.mins, e.maxs, trace_endpos))
728                                         return;
729                         }
730                 }
731
732                 if(rate_wps)
733                 {
734                         entity theEnemy = e;
735                         entity best_wp = NULL;
736                         float best_dist = 10000;
737                         IL_EACH(g_waypoints, vdist(it.origin - theEnemy.origin, <, 500)
738                                 && vdist(it.origin - this.origin, >, 100)
739                                 && !(it.wpflags & WAYPOINTFLAG_TELEPORT),
740                         {
741                                 float dist = vlen(it.origin - theEnemy.origin);
742                                 if (dist < best_dist)
743                                 {
744                                         best_wp = it;
745                                         best_dist = dist;
746                                 }
747                         });
748                         if (!best_wp)
749                                 return;
750                         e = best_wp;
751                 }
752         }
753
754         vector o = (e.absmin + e.absmax) * 0.5;
755
756         //print("routerating ", etos(e), " = ", ftos(f), " - ", ftos(rangebias), "\n");
757
758         // Evaluate path using jetpack
759         if(g_jetpack)
760         if(this.items & IT_JETPACK)
761         if(autocvar_bot_ai_navigation_jetpack)
762         if(vdist(this.origin - o, >, autocvar_bot_ai_navigation_jetpack_mindistance))
763         {
764                 vector pointa, pointb;
765
766                 LOG_DEBUG("jetpack ai: evaluating path for ", e.classname);
767
768                 // Point A
769                 traceline(this.origin, this.origin + '0 0 65535', MOVE_NORMAL, this);
770                 pointa = trace_endpos - '0 0 1';
771
772                 // Point B
773                 traceline(o, o + '0 0 65535', MOVE_NORMAL, e);
774                 pointb = trace_endpos - '0 0 1';
775
776                 // Can I see these two points from the sky?
777                 traceline(pointa, pointb, MOVE_NORMAL, this);
778
779                 if(trace_fraction==1)
780                 {
781                         LOG_DEBUG("jetpack ai: can bridge these two points");
782
783                         // Lower the altitude of these points as much as possible
784                         float zdistance, xydistance, cost, t, fuel;
785                         vector down, npa, npb;
786
787                         down = '0 0 -1' * (STAT(PL_MAX, this).z - STAT(PL_MIN, this).z) * 10;
788
789                         do{
790                                 npa = pointa + down;
791                                 npb = pointb + down;
792
793                                 if(npa.z<=this.absmax.z)
794                                         break;
795
796                                 if(npb.z<=e.absmax.z)
797                                         break;
798
799                                 traceline(npa, npb, MOVE_NORMAL, this);
800                                 if(trace_fraction==1)
801                                 {
802                                         pointa = npa;
803                                         pointb = npb;
804                                 }
805                         }
806                         while(trace_fraction == 1);
807
808
809                         // Rough estimation of fuel consumption
810                         // (ignores acceleration and current xyz velocity)
811                         xydistance = vlen(pointa - pointb);
812                         zdistance = fabs(pointa.z - this.origin.z);
813
814                         t = zdistance / autocvar_g_jetpack_maxspeed_up;
815                         t += xydistance / autocvar_g_jetpack_maxspeed_side;
816                         fuel = t * autocvar_g_jetpack_fuel * 0.8;
817
818                         LOG_DEBUG("jetpack ai: required fuel ", ftos(fuel), " this.ammo_fuel ", ftos(this.ammo_fuel));
819
820                         // enough fuel ?
821                         if(this.ammo_fuel>fuel)
822                         {
823                                 // Estimate cost
824                                 // (as onground costs calculation is mostly based on distances, here we do the same establishing some relationship
825                                 //  - between air and ground speeds)
826
827                                 cost = xydistance / (autocvar_g_jetpack_maxspeed_side/autocvar_sv_maxspeed);
828                                 cost += zdistance / (autocvar_g_jetpack_maxspeed_up/autocvar_sv_maxspeed);
829                                 cost *= 1.5;
830
831                                 // Compare against other goals
832                                 f = f * rangebias / (rangebias + cost);
833
834                                 if (navigation_bestrating < f)
835                                 {
836                                         LOG_DEBUG("jetpack path: added goal ", e.classname, " (with rating ", ftos(f), ")");
837                                         navigation_bestrating = f;
838                                         navigation_bestgoal = e;
839                                         this.navigation_jetpack_goal = e;
840                                         this.navigation_jetpack_point = pointb;
841                                 }
842                                 return;
843                         }
844                 }
845         }
846
847         entity nwp;
848         //te_wizspike(e.origin);
849         //bprint(etos(e));
850         //bprint("\n");
851         // update the cached spawnfunc_waypoint link on a dynamic item entity
852         if(e.classname == "waypoint" && !(e.wpflags & WAYPOINTFLAG_PERSONAL))
853         {
854                 nwp = e;
855         }
856         else
857         {
858                 if(autocvar_g_waypointeditor && e.nearestwaypointtimeout >= 0 && time > e.nearestwaypointtimeout)
859                         e.nearestwaypoint = NULL;
860
861                 if ((!e.nearestwaypoint || e.navigation_dynamicgoal)
862                         && e.nearestwaypointtimeout >= 0 && time > e.nearestwaypointtimeout)
863                 {
864                         nwp = navigation_findnearestwaypoint(e, true);
865                         if(nwp)
866                                 e.nearestwaypoint = nwp;
867                         else
868                         {
869                                 LOG_DEBUG("FAILED to find a nearest waypoint to '", e.classname, "' #", etos(e));
870
871                                 if(!e.navigation_dynamicgoal)
872                                         e.blacklisted = true;
873
874                                 if(e.blacklisted)
875                                 {
876                                         LOG_DEBUG("The entity '", e.classname, "' is going to be excluded from path finding during this match");
877                                         return;
878                                 }
879                         }
880
881                         if(e.navigation_dynamicgoal)
882                                 e.nearestwaypointtimeout = time + 2;
883                         else if(autocvar_g_waypointeditor)
884                                 e.nearestwaypointtimeout = time + 3 + random() * 2;
885                 }
886                 nwp = e.nearestwaypoint;
887         }
888
889         LOG_DEBUG("-- checking ", e.classname, " (with cost ", ftos(nwp.wpcost), ")");
890         if (nwp)
891         if (nwp.wpcost < 10000000)
892         {
893                 //te_wizspike(nwp.wpnearestpoint);
894                 LOG_DEBUG(e.classname, " ", ftos(f), "/(1+", ftos((nwp.wpcost + vlen(e.origin - nwp.wpnearestpoint))), "/", ftos(rangebias), ") = ");
895                 f = f * rangebias / (rangebias + (nwp.wpcost + vlen(o - nwp.wpnearestpoint)));
896                 LOG_DEBUG("considering ", e.classname, " (with rating ", ftos(f), ")");
897                 if (navigation_bestrating < f)
898                 {
899                         LOG_DEBUG("ground path: added goal ", e.classname, " (with rating ", ftos(f), ")");
900                         navigation_bestrating = f;
901                         navigation_bestgoal = e;
902                 }
903         }
904 }
905
906 // adds an item to the the goal stack with the path to a given item
907 bool navigation_routetogoal(entity this, entity e, vector startposition)
908 {
909         // if there is no goal, just exit
910         if (!e)
911                 return false;
912
913         this.goalentity = e;
914
915         // put the entity on the goal stack
916         //print("routetogoal ", etos(e), "\n");
917         navigation_pushroute(this, e);
918
919         if(e.classname == "waypoint" && !(e.wpflags & WAYPOINTFLAG_PERSONAL))
920         {
921                 this.wp_goal_prev1 = this.wp_goal_prev0;
922                 this.wp_goal_prev0 = e;
923         }
924
925         if(g_jetpack)
926         if(e==this.navigation_jetpack_goal)
927                 return true;
928
929         // if it can reach the goal there is nothing more to do
930         if (tracewalk(this, startposition, STAT(PL_MIN, this), STAT(PL_MAX, this), (e.absmin + e.absmax) * 0.5, bot_navigation_movemode))
931                 return true;
932
933         entity nearest_wp = NULL;
934         // see if there are waypoints describing a path to the item
935         if(e.classname != "waypoint" || (e.wpflags & WAYPOINTFLAG_PERSONAL))
936         {
937                 e = e.nearestwaypoint;
938                 nearest_wp = e;
939         }
940         else
941                 e = e.enemy; // we already have added it, so...
942
943         if(e == NULL)
944                 return false;
945
946         if(nearest_wp && nearest_wp.enemy)
947         {
948                 // often path can be optimized by not adding the nearest waypoint
949                 if(tracewalk(this, nearest_wp.enemy.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), (this.goalentity.absmin + this.goalentity.absmax) * 0.5, bot_navigation_movemode))
950                         e = nearest_wp.enemy;
951         }
952
953         for (;;)
954         {
955                 // add the spawnfunc_waypoint to the path
956                 navigation_pushroute(this, e);
957                 e = e.enemy;
958
959                 if(e==NULL)
960                         break;
961         }
962
963         return false;
964 }
965
966 // removes any currently touching waypoints from the goal stack
967 // (this is how bots detect if they reached a goal)
968 void navigation_poptouchedgoals(entity this)
969 {
970         vector org, m1, m2;
971         org = this.origin;
972         m1 = org + this.mins;
973         m2 = org + this.maxs;
974
975         if(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
976         {
977                 // make sure jumppad is really hit, don't rely on distance based checks
978                 // as they may report a touch even if it didn't really happen
979                 if(this.lastteleporttime>0)
980                 if(time - this.lastteleporttime < ((this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL) ? 2 : 0.15))
981                 {
982                         if(this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
983                         if(this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL && this.goalcurrent.owner==this)
984                         {
985                                 this.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
986                                 this.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED;
987                         }
988                         navigation_poproute(this);
989                         return;
990                 }
991         }
992
993         // If for some reason the bot is closer to the next goal, pop the current one
994         if(this.goalstack01 && !wasfreed(this.goalstack01))
995         if(vlen2(this.goalcurrent.origin - this.origin) > vlen2(this.goalstack01.origin - this.origin))
996         if(checkpvs(this.origin + this.view_ofs, this.goalstack01))
997         if(tracewalk(this, this.origin, this.mins, this.maxs, (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5, bot_navigation_movemode))
998         {
999                 LOG_DEBUG("path optimized for ", this.netname, ", removed a goal from the queue");
1000                 navigation_poproute(this);
1001                 // TODO this may also be a nice idea to do "early" (e.g. by
1002                 // manipulating the vlen() comparisons) to shorten paths in
1003                 // general - this would make bots walk more "on rails" than
1004                 // "zigzagging" which they currently do with sufficiently
1005                 // random-like waypoints, and thus can make a nice bot
1006                 // personality property
1007         }
1008
1009         // Loose goal touching check when running
1010         if(this.aistatus & AI_STATUS_RUNNING)
1011         if(this.goalcurrent.classname=="waypoint")
1012         if(!(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT))
1013         if(vlen(this.velocity - eZ * this.velocity.z) >= autocvar_sv_maxspeed) // if -really- running
1014         {
1015                 if(vdist(this.origin - this.goalcurrent.origin, <, 150))
1016                 {
1017                         traceline(this.origin + this.view_ofs , this.goalcurrent.origin, true, NULL);
1018                         if(trace_fraction==1)
1019                         {
1020                                 // Detect personal waypoints
1021                                 if(this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
1022                                 if(this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL && this.goalcurrent.owner==this)
1023                                 {
1024                                         this.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
1025                                         this.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED;
1026                                 }
1027
1028                                 navigation_poproute(this);
1029                         }
1030                 }
1031         }
1032
1033         while (this.goalcurrent && !IS_PLAYER(this.goalcurrent))
1034         {
1035                 vector gc_min = this.goalcurrent.absmin;
1036                 vector gc_max = this.goalcurrent.absmax;
1037                 if(this.goalcurrent.classname == "waypoint" && !this.goalcurrent.wpisbox)
1038                 {
1039                         gc_min = this.goalcurrent.origin - '1 1 1' * 12;
1040                         gc_max = this.goalcurrent.origin + '1 1 1' * 12;
1041                 }
1042                 if(!boxesoverlap(m1, m2, gc_min, gc_max))
1043                         break;
1044
1045                 if((this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT))
1046                         break;
1047
1048                 // Detect personal waypoints
1049                 if(this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
1050                 if(this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL && this.goalcurrent.owner==this)
1051                 {
1052                         this.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
1053                         this.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED;
1054                 }
1055
1056                 navigation_poproute(this);
1057         }
1058 }
1059
1060 // begin a goal selection session (queries spawnfunc_waypoint network)
1061 void navigation_goalrating_start(entity this)
1062 {
1063         if(this.aistatus & AI_STATUS_STUCK)
1064                 return;
1065
1066         this.navigation_jetpack_goal = NULL;
1067         navigation_bestrating = -1;
1068         navigation_clearroute(this);
1069         navigation_bestgoal = NULL;
1070         navigation_markroutes(this, NULL);
1071 }
1072
1073 // ends a goal selection session (updates goal stack to the best goal)
1074 void navigation_goalrating_end(entity this)
1075 {
1076         if(this.aistatus & AI_STATUS_STUCK)
1077                 return;
1078
1079         navigation_routetogoal(this, navigation_bestgoal, this.origin);
1080         LOG_DEBUG("best goal ", this.goalcurrent.classname);
1081
1082         // If the bot got stuck then try to reach the farthest waypoint
1083         if (!this.goalentity && autocvar_bot_wander_enable)
1084         {
1085                 if (!(this.aistatus & AI_STATUS_STUCK))
1086                 {
1087                         LOG_DEBUG(this.netname, " cannot walk to any goal");
1088                         this.aistatus |= AI_STATUS_STUCK;
1089                 }
1090         }
1091 }
1092
1093 void botframe_updatedangerousobjects(float maxupdate)
1094 {
1095         vector m1, m2, v, o;
1096         float c, d, danger;
1097         c = 0;
1098         IL_EACH(g_waypoints, true,
1099         {
1100                 danger = 0;
1101                 m1 = it.mins;
1102                 m2 = it.maxs;
1103                 IL_EACH(g_bot_dodge, it.bot_dodge,
1104                 {
1105                         v = it.origin;
1106                         v.x = bound(m1_x, v.x, m2_x);
1107                         v.y = bound(m1_y, v.y, m2_y);
1108                         v.z = bound(m1_z, v.z, m2_z);
1109                         o = (it.absmin + it.absmax) * 0.5;
1110                         d = it.bot_dodgerating - vlen(o - v);
1111                         if (d > 0)
1112                         {
1113                                 traceline(o, v, true, NULL);
1114                                 if (trace_fraction == 1)
1115                                         danger = danger + d;
1116                         }
1117                 });
1118                 it.dmg = danger;
1119                 c = c + 1;
1120                 if (c >= maxupdate)
1121                         break;
1122         });
1123 }
1124
1125 void navigation_unstuck(entity this)
1126 {
1127         float search_radius = 1000;
1128
1129         if (!autocvar_bot_wander_enable)
1130                 return;
1131
1132         if (!bot_waypoint_queue_owner)
1133         {
1134                 LOG_DEBUG(this.netname, " stuck, taking over the waypoints queue");
1135                 bot_waypoint_queue_owner = this;
1136                 bot_waypoint_queue_bestgoal = NULL;
1137                 bot_waypoint_queue_bestgoalrating = 0;
1138         }
1139
1140         if(bot_waypoint_queue_owner!=this)
1141                 return;
1142
1143         if (bot_waypoint_queue_goal)
1144         {
1145                 // evaluate the next goal on the queue
1146                 float d = vlen2(this.origin - bot_waypoint_queue_goal.origin);
1147                 LOG_DEBUG(this.netname, " evaluating ", bot_waypoint_queue_goal.classname, " with distance ", ftos(d));
1148                 if(tracewalk(bot_waypoint_queue_goal, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), bot_waypoint_queue_goal.origin, bot_navigation_movemode))
1149                 {
1150                         if( d > bot_waypoint_queue_bestgoalrating)
1151                         {
1152                                 bot_waypoint_queue_bestgoalrating = d;
1153                                 bot_waypoint_queue_bestgoal = bot_waypoint_queue_goal;
1154                         }
1155                 }
1156                 bot_waypoint_queue_goal = bot_waypoint_queue_goal.bot_waypoint_queue_nextgoal;
1157
1158                 if (!bot_waypoint_queue_goal)
1159                 {
1160                         if (bot_waypoint_queue_bestgoal)
1161                         {
1162                                 LOG_DEBUG(this.netname, " stuck, reachable waypoint found, heading to it");
1163                                 navigation_routetogoal(this, bot_waypoint_queue_bestgoal, this.origin);
1164                                 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1165                                 this.aistatus &= ~AI_STATUS_STUCK;
1166                         }
1167                         else
1168                         {
1169                                 LOG_DEBUG(this.netname, " stuck, cannot walk to any waypoint at all");
1170                         }
1171
1172                         bot_waypoint_queue_owner = NULL;
1173                 }
1174         }
1175         else
1176         {
1177                 if(bot_strategytoken!=this)
1178                         return;
1179
1180                 // build a new queue
1181                 LOG_DEBUG(this.netname, " stuck, scanning reachable waypoints within ", ftos(search_radius)," qu");
1182
1183                 entity first = NULL;
1184
1185                 FOREACH_ENTITY_RADIUS(this.origin, search_radius, it.classname == "waypoint" && !(it.wpflags & WAYPOINTFLAG_GENERATED),
1186                 {
1187                         if(bot_waypoint_queue_goal)
1188                                 bot_waypoint_queue_goal.bot_waypoint_queue_nextgoal = it;
1189                         else
1190                                 first = it;
1191
1192                         bot_waypoint_queue_goal = it;
1193                         bot_waypoint_queue_goal.bot_waypoint_queue_nextgoal = NULL;
1194                 });
1195
1196                 if (first)
1197                         bot_waypoint_queue_goal = first;
1198                 else
1199                 {
1200                         LOG_DEBUG(this.netname, " stuck, cannot walk to any waypoint at all");
1201                         bot_waypoint_queue_owner = NULL;
1202                 }
1203         }
1204 }
1205
1206 // Support for debugging tracewalk visually
1207
1208 void debugresetnodes()
1209 {
1210         debuglastnode = '0 0 0';
1211 }
1212
1213 void debugnode(entity this, vector node)
1214 {
1215         if (!IS_PLAYER(this))
1216                 return;
1217
1218         if(debuglastnode=='0 0 0')
1219         {
1220                 debuglastnode = node;
1221                 return;
1222         }
1223
1224         te_lightning2(NULL, node, debuglastnode);
1225         debuglastnode = node;
1226 }
1227
1228 void debugnodestatus(vector position, float status)
1229 {
1230         vector c;
1231
1232         switch (status)
1233         {
1234                 case DEBUG_NODE_SUCCESS:
1235                         c = '0 15 0';
1236                         break;
1237                 case DEBUG_NODE_WARNING:
1238                         c = '15 15 0';
1239                         break;
1240                 case DEBUG_NODE_FAIL:
1241                         c = '15 0 0';
1242                         break;
1243                 default:
1244                         c = '15 15 15';
1245         }
1246
1247         te_customflash(position, 40,  2, c);
1248 }
1249
1250 // Support for debugging the goal stack visually
1251
1252 .float goalcounter;
1253 .vector lastposition;
1254
1255 // Debug the goal stack visually
1256 void debuggoalstack(entity this)
1257 {
1258         entity goal;
1259         vector org, go;
1260
1261         if(this.goalcounter==0)goal=this.goalcurrent;
1262         else if(this.goalcounter==1)goal=this.goalstack01;
1263         else if(this.goalcounter==2)goal=this.goalstack02;
1264         else if(this.goalcounter==3)goal=this.goalstack03;
1265         else if(this.goalcounter==4)goal=this.goalstack04;
1266         else if(this.goalcounter==5)goal=this.goalstack05;
1267         else if(this.goalcounter==6)goal=this.goalstack06;
1268         else if(this.goalcounter==7)goal=this.goalstack07;
1269         else if(this.goalcounter==8)goal=this.goalstack08;
1270         else if(this.goalcounter==9)goal=this.goalstack09;
1271         else if(this.goalcounter==10)goal=this.goalstack10;
1272         else if(this.goalcounter==11)goal=this.goalstack11;
1273         else if(this.goalcounter==12)goal=this.goalstack12;
1274         else if(this.goalcounter==13)goal=this.goalstack13;
1275         else if(this.goalcounter==14)goal=this.goalstack14;
1276         else if(this.goalcounter==15)goal=this.goalstack15;
1277         else if(this.goalcounter==16)goal=this.goalstack16;
1278         else if(this.goalcounter==17)goal=this.goalstack17;
1279         else if(this.goalcounter==18)goal=this.goalstack18;
1280         else if(this.goalcounter==19)goal=this.goalstack19;
1281         else if(this.goalcounter==20)goal=this.goalstack20;
1282         else if(this.goalcounter==21)goal=this.goalstack21;
1283         else if(this.goalcounter==22)goal=this.goalstack22;
1284         else if(this.goalcounter==23)goal=this.goalstack23;
1285         else if(this.goalcounter==24)goal=this.goalstack24;
1286         else if(this.goalcounter==25)goal=this.goalstack25;
1287         else if(this.goalcounter==26)goal=this.goalstack26;
1288         else if(this.goalcounter==27)goal=this.goalstack27;
1289         else if(this.goalcounter==28)goal=this.goalstack28;
1290         else if(this.goalcounter==29)goal=this.goalstack29;
1291         else if(this.goalcounter==30)goal=this.goalstack30;
1292         else if(this.goalcounter==31)goal=this.goalstack31;
1293         else goal=NULL;
1294
1295         if(goal==NULL)
1296         {
1297                 this.goalcounter = 0;
1298                 this.lastposition='0 0 0';
1299                 return;
1300         }
1301
1302         if(this.lastposition=='0 0 0')
1303                 org = this.origin;
1304         else
1305                 org = this.lastposition;
1306
1307
1308         go = ( goal.absmin + goal.absmax ) * 0.5;
1309         te_lightning2(NULL, org, go);
1310         this.lastposition = go;
1311
1312         this.goalcounter++;
1313 }