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