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