]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/bot/default/navigation.qc
Merge branch 'master' into terencehill/bot_waypoints
[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, 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, org, 0, bot_navigation_movemode))
733                                         return true;
734                         }
735                         else
736                         {
737                                 if (tracewalk(ent, org, PL_MIN_CONST, PL_MAX_CONST, v2, v2_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         vector pm1 = ent.origin + ent.mins;
749         vector pm2 = ent.origin + ent.maxs;
750
751         // do two scans, because box test is cheaper
752         IL_EACH(g_waypoints, it != ent && it != except,
753         {
754                 if(boxesoverlap(pm1, pm2, it.absmin, it.absmax))
755                         return it;
756         });
757
758         vector org = ent.origin;
759         if(ent.size)
760         {
761                 org += 0.5 * (ent.mins + ent.maxs);
762                 org.z = ent.origin.z + ent.mins.z - PL_MIN_CONST.z; // player height
763         }
764         // TODO possibly make other code have the same support for bboxes
765         if(ent.tag_entity)
766                 org = org + ent.tag_entity.origin;
767         if (navigation_testtracewalk)
768                 te_plasmaburn(org);
769
770         entity best = NULL;
771         vector v, v2;
772         float v2_height;
773
774         if(!autocvar_g_waypointeditor && !ent.navigation_dynamicgoal)
775         {
776                 waypoint_clearlinks(ent); // initialize wpXXmincost fields
777                 IL_EACH(g_waypoints, it != ent,
778                 {
779                         if(walkfromwp && (it.wpflags & WAYPOINTFLAG_NORELINK))
780                                 continue;
781
782                         SET_TRACEWALK_DESTCOORDS_2(it, org, v, v2, v2_height);
783                         if(navigation_waypoint_will_link(v, org, ent, v2, v2_height, walkfromwp, 1050))
784                                 navigation_item_addlink(it, ent);
785                 });
786         }
787
788         // box check failed, try walk
789         IL_EACH(g_waypoints, it != ent,
790         {
791                 if(walkfromwp && (it.wpflags & WAYPOINTFLAG_NORELINK))
792                         continue;
793                 SET_TRACEWALK_DESTCOORDS_2(it, org, v, v2, v2_height);
794                 if(navigation_waypoint_will_link(v, org, ent, 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;
818         //navigation_testtracewalk = true;
819         int c = 0;
820         float v_height;
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         if(e.wpflags & WAYPOINTFLAG_TELEPORT)
1244         {
1245                 // force teleport destination as route destination
1246                 e.wp00.enemy = e;
1247                 e = e.wp00;
1248         }
1249
1250         this.goalentity = e;
1251
1252         // put the entity on the goal stack
1253         //print("routetogoal ", etos(e), "\n");
1254         navigation_pushroute(this, e);
1255
1256         if(e.classname == "waypoint" && !(e.wpflags & WAYPOINTFLAG_PERSONAL))
1257         {
1258                 this.wp_goal_prev1 = this.wp_goal_prev0;
1259                 this.wp_goal_prev0 = e;
1260         }
1261
1262         if(g_jetpack)
1263         if(e==this.navigation_jetpack_goal)
1264                 return true;
1265
1266         // if it can reach the goal there is nothing more to do
1267         vector dest;
1268         float dest_height;
1269         SET_TRACEWALK_DESTCOORDS(e, e.origin, dest, dest_height);
1270         if (tracewalk(this, startposition, STAT(PL_MIN, this), STAT(PL_MAX, this), dest, dest_height, bot_navigation_movemode))
1271                 return true;
1272
1273         entity nearest_wp = NULL;
1274         // see if there are waypoints describing a path to the item
1275         if(e.classname != "waypoint" || (e.wpflags & WAYPOINTFLAG_PERSONAL))
1276         {
1277                 e = e.nearestwaypoint;
1278                 nearest_wp = e;
1279         }
1280         else
1281                 e = e.enemy; // we already have added it, so...
1282
1283         if(e == NULL)
1284                 return false;
1285
1286         if(nearest_wp && nearest_wp.enemy)
1287         {
1288                 // often path can be optimized by not adding the nearest waypoint
1289                 if (this.goalentity.nearestwaypoint_dist < 8)
1290                         e = nearest_wp.enemy;
1291                 else
1292                 {
1293                         if (this.goalentity.navigation_dynamicgoal)
1294                         {
1295                                 SET_TRACEWALK_DESTCOORDS(e, e.origin, dest, dest_height);
1296                                 if(tracewalk(this, nearest_wp.enemy.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), dest, dest_height, bot_navigation_movemode))
1297                                         e = nearest_wp.enemy;
1298                         }
1299                         else if(navigation_item_islinked(nearest_wp.enemy, this.goalentity))
1300                                 e = nearest_wp.enemy;
1301                 }
1302         }
1303
1304         for (;;)
1305         {
1306                 // add the spawnfunc_waypoint to the path
1307                 navigation_pushroute(this, e);
1308                 e = e.enemy;
1309
1310                 if(e==NULL)
1311                         break;
1312         }
1313
1314         return false;
1315 }
1316
1317 // removes any currently touching waypoints from the goal stack
1318 // (this is how bots detect if they reached a goal)
1319 void navigation_poptouchedgoals(entity this)
1320 {
1321         if(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
1322         {
1323                 // make sure jumppad is really hit, don't rely on distance based checks
1324                 // as they may report a touch even if it didn't really happen
1325                 if(this.lastteleporttime > 0
1326                         && time - this.lastteleporttime < ((this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL) ? 2 : 0.15))
1327                 {
1328                         if(this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
1329                         if(this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL && this.goalcurrent.owner==this)
1330                         {
1331                                 this.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
1332                                 this.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED;
1333                         }
1334                         navigation_poproute(this);
1335                 }
1336                 else
1337                         return;
1338         }
1339
1340         // If for some reason the bot is closer to the next goal, pop the current one
1341         if(this.goalstack01 && !wasfreed(this.goalstack01))
1342         if(vlen2(this.goalcurrent.origin - this.origin) > vlen2(this.goalstack01.origin - this.origin))
1343         if(checkpvs(this.origin + this.view_ofs, this.goalstack01))
1344         {
1345                 vector dest = (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5;
1346                 dest.z = this.goalstack01.absmin.z;
1347                 float dest_height = this.goalstack01.absmax.z - this.goalstack01.absmin.z;
1348                 if(tracewalk(this, this.origin, this.mins, this.maxs, dest, dest_height, bot_navigation_movemode))
1349                 {
1350                         LOG_DEBUG("path optimized for ", this.netname, ", removed a goal from the queue");
1351                         navigation_poproute(this);
1352                         if(this.goalcurrent && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
1353                                 return;
1354                         // TODO this may also be a nice idea to do "early" (e.g. by
1355                         // manipulating the vlen() comparisons) to shorten paths in
1356                         // general - this would make bots walk more "on rails" than
1357                         // "zigzagging" which they currently do with sufficiently
1358                         // random-like waypoints, and thus can make a nice bot
1359                         // personality property
1360                 }
1361         }
1362
1363         // Loose goal touching check when running
1364         if(this.aistatus & AI_STATUS_RUNNING)
1365         if(this.goalcurrent.classname=="waypoint")
1366         if(vdist(vec2(this.velocity), >=, autocvar_sv_maxspeed)) // if -really- running
1367         {
1368                 if(vdist(this.origin - this.goalcurrent.origin, <, 150))
1369                 {
1370                         traceline(this.origin + this.view_ofs , this.goalcurrent.origin, true, NULL);
1371                         if(trace_fraction==1)
1372                         {
1373                                 // Detect personal waypoints
1374                                 if(this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
1375                                 if(this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL && this.goalcurrent.owner==this)
1376                                 {
1377                                         this.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
1378                                         this.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED;
1379                                 }
1380
1381                                 navigation_poproute(this);
1382                                 if(this.goalcurrent && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
1383                                         return;
1384                         }
1385                 }
1386         }
1387
1388         while (this.goalcurrent && !IS_PLAYER(this.goalcurrent))
1389         {
1390                 vector gc_min = this.goalcurrent.absmin;
1391                 vector gc_max = this.goalcurrent.absmax;
1392                 if(this.goalcurrent.classname == "waypoint" && !this.goalcurrent.wpisbox)
1393                 {
1394                         gc_min = this.goalcurrent.origin - '1 1 1' * 12;
1395                         gc_max = this.goalcurrent.origin + '1 1 1' * 12;
1396                 }
1397                 if(!boxesoverlap(this.absmin, this.absmax, gc_min, gc_max))
1398                         break;
1399
1400                 // Detect personal waypoints
1401                 if(this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
1402                 if(this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL && this.goalcurrent.owner==this)
1403                 {
1404                         this.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
1405                         this.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED;
1406                 }
1407
1408                 navigation_poproute(this);
1409                 if(this.goalcurrent && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
1410                         return;
1411         }
1412 }
1413
1414 // begin a goal selection session (queries spawnfunc_waypoint network)
1415 void navigation_goalrating_start(entity this)
1416 {
1417         if(this.aistatus & AI_STATUS_STUCK)
1418                 return;
1419
1420         this.navigation_jetpack_goal = NULL;
1421         navigation_bestrating = -1;
1422         navigation_clearroute(this);
1423         navigation_bestgoal = NULL;
1424         navigation_markroutes(this, NULL);
1425 }
1426
1427 // ends a goal selection session (updates goal stack to the best goal)
1428 void navigation_goalrating_end(entity this)
1429 {
1430         if(this.aistatus & AI_STATUS_STUCK)
1431                 return;
1432
1433         navigation_routetogoal(this, navigation_bestgoal, this.origin);
1434         LOG_DEBUG("best goal ", this.goalcurrent.classname);
1435
1436         // If the bot got stuck then try to reach the farthest waypoint
1437         if (!this.goalentity && autocvar_bot_wander_enable)
1438         {
1439                 if (!(this.aistatus & AI_STATUS_STUCK))
1440                 {
1441                         LOG_DEBUG(this.netname, " cannot walk to any goal");
1442                         this.aistatus |= AI_STATUS_STUCK;
1443                 }
1444         }
1445 }
1446
1447 void botframe_updatedangerousobjects(float maxupdate)
1448 {
1449         vector m1, m2, v, o;
1450         float c, d, danger;
1451         c = 0;
1452         entity wp_cur;
1453         IL_EACH(g_waypoints, true,
1454         {
1455                 danger = 0;
1456                 m1 = it.absmin;
1457                 m2 = it.absmax;
1458                 wp_cur = it;
1459                 IL_EACH(g_bot_dodge, it.bot_dodge,
1460                 {
1461                         v = it.origin;
1462                         v.x = bound(m1_x, v.x, m2_x);
1463                         v.y = bound(m1_y, v.y, m2_y);
1464                         v.z = bound(m1_z, v.z, m2_z);
1465                         o = (it.absmin + it.absmax) * 0.5;
1466                         d = waypoint_getlinearcost(it.bot_dodgerating) - waypoint_gettravelcost(o, v, it, wp_cur);
1467                         if (d > 0)
1468                         {
1469                                 traceline(o, v, true, NULL);
1470                                 if (trace_fraction == 1)
1471                                         danger = danger + d;
1472                         }
1473                 });
1474                 it.dmg = danger;
1475                 c = c + 1;
1476                 if (c >= maxupdate)
1477                         break;
1478         });
1479 }
1480
1481 void navigation_unstuck(entity this)
1482 {
1483         float search_radius = 1000;
1484
1485         if (!autocvar_bot_wander_enable)
1486                 return;
1487
1488         if (!bot_waypoint_queue_owner)
1489         {
1490                 LOG_DEBUG(this.netname, " stuck, taking over the waypoints queue");
1491                 bot_waypoint_queue_owner = this;
1492                 bot_waypoint_queue_bestgoal = NULL;
1493                 bot_waypoint_queue_bestgoalrating = 0;
1494         }
1495
1496         if(bot_waypoint_queue_owner!=this)
1497                 return;
1498
1499         if (bot_waypoint_queue_goal)
1500         {
1501                 // evaluate the next goal on the queue
1502                 float d = vlen2(this.origin - bot_waypoint_queue_goal.origin);
1503                 LOG_DEBUG(this.netname, " evaluating ", bot_waypoint_queue_goal.classname, " with distance ", ftos(d));
1504                 vector dest = (bot_waypoint_queue_goal.absmin + bot_waypoint_queue_goal.absmax) * 0.5;
1505                 dest.z = bot_waypoint_queue_goal.absmin.z;
1506                 float dest_height = bot_waypoint_queue_goal.absmax.z - bot_waypoint_queue_goal.absmin.z;
1507                 if(tracewalk(bot_waypoint_queue_goal, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), dest, dest_height, bot_navigation_movemode))
1508                 {
1509                         if( d > bot_waypoint_queue_bestgoalrating)
1510                         {
1511                                 bot_waypoint_queue_bestgoalrating = d;
1512                                 bot_waypoint_queue_bestgoal = bot_waypoint_queue_goal;
1513                         }
1514                 }
1515                 bot_waypoint_queue_goal = bot_waypoint_queue_goal.bot_waypoint_queue_nextgoal;
1516
1517                 if (!bot_waypoint_queue_goal)
1518                 {
1519                         if (bot_waypoint_queue_bestgoal)
1520                         {
1521                                 LOG_DEBUG(this.netname, " stuck, reachable waypoint found, heading to it");
1522                                 navigation_routetogoal(this, bot_waypoint_queue_bestgoal, this.origin);
1523                                 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1524                                 this.aistatus &= ~AI_STATUS_STUCK;
1525                         }
1526                         else
1527                         {
1528                                 LOG_DEBUG(this.netname, " stuck, cannot walk to any waypoint at all");
1529                         }
1530
1531                         bot_waypoint_queue_owner = NULL;
1532                 }
1533         }
1534         else
1535         {
1536                 if(bot_strategytoken!=this)
1537                         return;
1538
1539                 // build a new queue
1540                 LOG_DEBUG(this.netname, " stuck, scanning reachable waypoints within ", ftos(search_radius)," qu");
1541
1542                 entity first = NULL;
1543
1544                 FOREACH_ENTITY_RADIUS(this.origin, search_radius, it.classname == "waypoint" && !(it.wpflags & WAYPOINTFLAG_GENERATED),
1545                 {
1546                         if(bot_waypoint_queue_goal)
1547                                 bot_waypoint_queue_goal.bot_waypoint_queue_nextgoal = it;
1548                         else
1549                                 first = it;
1550
1551                         bot_waypoint_queue_goal = it;
1552                         bot_waypoint_queue_goal.bot_waypoint_queue_nextgoal = NULL;
1553                 });
1554
1555                 if (first)
1556                         bot_waypoint_queue_goal = first;
1557                 else
1558                 {
1559                         LOG_DEBUG(this.netname, " stuck, cannot walk to any waypoint at all");
1560                         bot_waypoint_queue_owner = NULL;
1561                 }
1562         }
1563 }
1564
1565 // Support for debugging tracewalk visually
1566
1567 void debugresetnodes()
1568 {
1569         debuglastnode = '0 0 0';
1570 }
1571
1572 void debugnode(entity this, vector node)
1573 {
1574         if (!IS_PLAYER(this))
1575                 return;
1576
1577         if(debuglastnode=='0 0 0')
1578         {
1579                 debuglastnode = node;
1580                 return;
1581         }
1582
1583         te_lightning2(NULL, node, debuglastnode);
1584         debuglastnode = node;
1585 }
1586
1587 void debugnodestatus(vector position, float status)
1588 {
1589         vector c;
1590
1591         switch (status)
1592         {
1593                 case DEBUG_NODE_SUCCESS:
1594                         c = '0 15 0';
1595                         break;
1596                 case DEBUG_NODE_WARNING:
1597                         c = '15 15 0';
1598                         break;
1599                 case DEBUG_NODE_FAIL:
1600                         c = '15 0 0';
1601                         break;
1602                 default:
1603                         c = '15 15 15';
1604         }
1605
1606         te_customflash(position, 40,  2, c);
1607 }
1608
1609 // Support for debugging the goal stack visually
1610
1611 .float goalcounter;
1612 .vector lastposition;
1613
1614 // Debug the goal stack visually
1615 void debuggoalstack(entity this)
1616 {
1617         entity goal;
1618         vector org, go;
1619
1620         if(this.goalcounter==0)goal=this.goalcurrent;
1621         else if(this.goalcounter==1)goal=this.goalstack01;
1622         else if(this.goalcounter==2)goal=this.goalstack02;
1623         else if(this.goalcounter==3)goal=this.goalstack03;
1624         else if(this.goalcounter==4)goal=this.goalstack04;
1625         else if(this.goalcounter==5)goal=this.goalstack05;
1626         else if(this.goalcounter==6)goal=this.goalstack06;
1627         else if(this.goalcounter==7)goal=this.goalstack07;
1628         else if(this.goalcounter==8)goal=this.goalstack08;
1629         else if(this.goalcounter==9)goal=this.goalstack09;
1630         else if(this.goalcounter==10)goal=this.goalstack10;
1631         else if(this.goalcounter==11)goal=this.goalstack11;
1632         else if(this.goalcounter==12)goal=this.goalstack12;
1633         else if(this.goalcounter==13)goal=this.goalstack13;
1634         else if(this.goalcounter==14)goal=this.goalstack14;
1635         else if(this.goalcounter==15)goal=this.goalstack15;
1636         else if(this.goalcounter==16)goal=this.goalstack16;
1637         else if(this.goalcounter==17)goal=this.goalstack17;
1638         else if(this.goalcounter==18)goal=this.goalstack18;
1639         else if(this.goalcounter==19)goal=this.goalstack19;
1640         else if(this.goalcounter==20)goal=this.goalstack20;
1641         else if(this.goalcounter==21)goal=this.goalstack21;
1642         else if(this.goalcounter==22)goal=this.goalstack22;
1643         else if(this.goalcounter==23)goal=this.goalstack23;
1644         else if(this.goalcounter==24)goal=this.goalstack24;
1645         else if(this.goalcounter==25)goal=this.goalstack25;
1646         else if(this.goalcounter==26)goal=this.goalstack26;
1647         else if(this.goalcounter==27)goal=this.goalstack27;
1648         else if(this.goalcounter==28)goal=this.goalstack28;
1649         else if(this.goalcounter==29)goal=this.goalstack29;
1650         else if(this.goalcounter==30)goal=this.goalstack30;
1651         else if(this.goalcounter==31)goal=this.goalstack31;
1652         else goal=NULL;
1653
1654         if(goal==NULL)
1655         {
1656                 this.goalcounter = 0;
1657                 this.lastposition='0 0 0';
1658                 return;
1659         }
1660
1661         if(this.lastposition=='0 0 0')
1662                 org = this.origin;
1663         else
1664                 org = this.lastposition;
1665
1666
1667         go = ( goal.absmin + goal.absmax ) * 0.5;
1668         te_lightning2(NULL, org, go);
1669         this.lastposition = go;
1670
1671         this.goalcounter++;
1672 }