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