Xonotic
navigation.qc
Go to the documentation of this file.
1 #include "navigation.qh"
2 
3 #include <common/constants.qh>
4 #include <common/items/_mod.qh>
8 #include <common/net_linked.qh>
9 #include <common/stats.qh>
10 #include <common/weapons/_all.qh>
11 #include <server/bot/api.qh>
15 #include <server/items/items.qh>
16 
17 .float speed;
18 
20 {
21  if(IS_MOVABLE(this.goalentity))
23  else
25 }
26 
27 // use this when current goal must be discarded immediately
29 {
31 }
32 
33 // use this when current goal can be kept for a short while to increase the chance
34 // of bot touching a waypoint, which helps to find a new goal more efficiently
36 {
37  if (seconds <= 0)
38  this.bot_strategytime = 0;
39  else if (this.bot_strategytime > time + seconds)
40  this.bot_strategytime = time + seconds;
41 }
42 
44 {
45  return this.bot_strategytime < time;
46 }
47 
50 {
51  this.bot_strategytime = max(this.bot_strategytime, time + seconds);
52 }
53 
54 #define MAX_CHASE_DISTANCE 700
56 {
57  vector gco = (this.goalentity.absmin + this.goalentity.absmax) * 0.5;
58  if (vdist(gco - this.origin, >, autocvar_sv_maxspeed * 1.5)
59  && time > this.bot_strategytime - (IS_MOVABLE(this.goalentity) ? 3 : 2))
60  {
61  return true;
62  }
63 
64  if (this.goalentity.bot_pickup && time > this.bot_strategytime - 5)
65  {
67  {
68  this.ignoregoal = this.goalentity;
70  return true;
71  }
72  }
73  return false;
74 }
75 
76 void navigation_dynamicgoal_init(entity this, bool initially_static)
77 {
78  this.navigation_dynamicgoal = true;
80  if(initially_static)
81  this.nearestwaypointtimeout = -1;
82  else
84 }
85 
87 {
89  if (dropper && dropper.nearestwaypointtimeout && dropper.nearestwaypointtimeout < time + 2)
90  this.nearestwaypoint = dropper.nearestwaypoint;
91  if (this.nearestwaypoint)
92  this.nearestwaypointtimeout += 2;
93 }
94 
96 {
97  if(this.bot_basewaypoint)
99  this.nearestwaypointtimeout = -1;
100 }
101 
102 // returns point of ent closer to org
104 {
105  vector dest = '0 0 0';
106  if ((ent.classname != "waypoint") || ent.wpisbox)
107  {
108  vector wm1 = ent.origin + ent.mins;
109  vector wm2 = ent.origin + ent.maxs;
110  dest.x = bound(wm1.x, org.x, wm2.x);
111  dest.y = bound(wm1.y, org.y, wm2.y);
112  dest.z = bound(wm1.z, org.z, wm2.z);
113  }
114  else
115  dest = ent.origin;
116  return dest;
117 }
118 
119 void set_tracewalk_dest(entity ent, vector org, bool fix_player_dest)
120 {
121  if ((ent.classname != "waypoint") || ent.wpisbox)
122  {
123  vector wm1 = ent.origin + ent.mins;
124  vector wm2 = ent.origin + ent.maxs;
125  if (IS_PLAYER(ent) || IS_MONSTER(ent))
126  {
127  // move destination point out of player bbox otherwise tracebox always fails
128  // (if bot_navigation_ignoreplayers is false)
129  wm1 += vec2(PL_MIN_CONST) + '-1 -1 0';
130  wm2 += vec2(PL_MAX_CONST) + '1 1 0';
131  }
132  // set destination point to x and y coords of ent that are closer to org
133  // z coord is set to ent's min height
134  tracewalk_dest.x = bound(wm1.x, org.x, wm2.x);
135  tracewalk_dest.y = bound(wm1.y, org.y, wm2.y);
136  if ((IS_PLAYER(ent) || IS_MONSTER(ent))
137  && org.x == tracewalk_dest.x && org.y == tracewalk_dest.y && org.z > tracewalk_dest.z)
138  {
139  tracewalk_dest.z = wm2.z - PL_MIN_CONST.z;
141  fix_player_dest = false;
142  }
143  else
144  {
145  tracewalk_dest.z = wm1.z;
146  tracewalk_dest_height = wm2.z - wm1.z;
147  }
148  }
149  else
150  {
151  tracewalk_dest = ent.origin;
153  }
154  if (fix_player_dest && IS_PLAYER(ent) && !IS_ONGROUND(ent))
155  {
156  // snap player to the ground
157  if (org.x == tracewalk_dest.x && org.y == tracewalk_dest.y)
158  {
159  // bot is right under the player
160  tracebox(ent.origin, ent.mins, ent.maxs, ent.origin - '0 0 700', MOVE_NORMAL, ent);
163  }
164  else
165  {
166  tracebox(tracewalk_dest, ent.mins, ent.maxs, tracewalk_dest - '0 0 700', MOVE_NORMAL, ent);
167  if (!trace_startsolid && tracewalk_dest.z - trace_endpos.z > 0)
168  {
171  }
172  }
173  }
174 }
175 
176 // returns point of ent closer to org
178 {
179  vector closer_dest = '0 0 0';
180  if ((ent.classname != "waypoint") || ent.wpisbox)
181  {
182  vector wm1 = ent.origin + ent.mins;
183  vector wm2 = ent.origin + ent.maxs;
184  closer_dest.x = bound(wm1.x, org.x, wm2.x);
185  closer_dest.y = bound(wm1.y, org.y, wm2.y);
186  closer_dest.z = bound(wm1.z, org.z, wm2.z);
187  // set destination point to x and y coords of ent that are closer to org
188  // z coord is set to ent's min height
189  tracewalk_dest.x = closer_dest.x;
190  tracewalk_dest.y = closer_dest.y;
191  tracewalk_dest.z = wm1.z;
192  tracewalk_dest_height = wm2.z - wm1.z; // destination height
193  }
194  else
195  {
196  closer_dest = ent.origin;
197  tracewalk_dest = closer_dest;
199  }
200  return closer_dest;
201 }
202 
204 {
205  bool submerged;
206  if(IS_PLAYER(ent))
207  submerged = (ent.waterlevel == WATERLEVEL_SUBMERGED);
208  else if(ent.nav_submerged_state != SUBMERGED_UNDEFINED)
209  submerged = (ent.nav_submerged_state == SUBMERGED_YES);
210  else
211  {
212  submerged = SUBMERGED(pos);
213  // NOTE: SUBMERGED check of box waypoint origin may fail even if origin
214  // is actually submerged because often they are inside some solid.
215  // That's why submerged state is saved now that we know current pos is
216  // not stuck in solid (previous tracewalk call to this pos was successfully)
217  if(!ent.navigation_dynamicgoal)
218  ent.nav_submerged_state = (submerged) ? SUBMERGED_YES : SUBMERGED_NO;
219  }
220  return submerged;
221 }
222 
223 bool navigation_checkladders(entity e, vector org, vector m1, vector m2, vector end, vector end2, int movemode)
224 {
225  IL_EACH(g_ladders, true,
226  {
227  if(it.bot_pickup)
228  if(boxesoverlap(org + m1 + '-1 -1 -1', org + m2 + '1 1 1', it.absmin, it.absmax))
229  if(boxesoverlap(end, end2, it.absmin + vec2(m1) + '-1 -1 0', it.absmax + vec2(m2) + '1 1 0'))
230  {
231  vector top = org;
232  top.z = it.absmax.z + (PL_MAX_CONST.z - PL_MIN_CONST.z);
233  tracebox(org, m1, m2, top, movemode, e);
234  if(trace_fraction == 1)
235  return true;
236  }
237  });
238  return false;
239 }
240 
241 // Unfortuntely we can't use trace_inwater since it doesn't hold the fraction of the total
242 // distance that was traveled before impact as the description in the engine (collision.h) says.
243 // It would have helped to speed up tracewalk underwater
245 {
246  if (WETFEET(org + eZ * (lim - org.z)))
247  org.z = lim;
248  else
249  {
250  float RES_min_h = org.z;
251  float RES_max_h = lim;
252  do {
253  org.z = 0.5 * (RES_min_h + RES_max_h);
254  if(WETFEET(org))
255  RES_min_h = org.z;
256  else
257  RES_max_h = org.z;
258  } while (RES_max_h - RES_min_h >= 1);
259  org.z = RES_min_h;
260  }
261  return org;
262 }
263 #define RESURFACE_LIMITED(org, lim) org = resurface_limited(org, lim, m1)
264 
265 #define NAV_WALK 0
266 #define NAV_SWIM_ONWATER 1
267 #define NAV_SWIM_UNDERWATER 2
268 
269 // rough simulation of walking from one point to another to test if a path
270 // can be traveled, used for waypoint linking and havocbot
271 // if end_height is > 0 destination is any point in the vertical segment [end, end + end_height * eZ]
272 // INFO: the command sv_cmd trace walk is useful to test this function in game
273 bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float end_height, float movemode)
274 {
276  {
277  debugresetnodes();
278  debugnode(e, start);
279  }
280 
281  vector org = start;
282  vector flatdir = end - start;
283  flatdir.z = 0;
284  float flatdist = vlen(flatdir);
285  flatdir = normalize(flatdir);
286  float stepdist = 32;
287  bool ignorehazards = false;
288  int nav_action;
289 
290  // Analyze starting point
291  if (IN_LAVA(start))
292  ignorehazards = true;
293 
294  tracebox(start, m1, m2, start, MOVE_NOMONSTERS, e);
295  if (trace_startsolid)
296  {
297  // Bad start
300 
301  //print("tracewalk: ", vtos(start), " is a bad start\n");
302  return false;
303  }
304 
305  vector end2 = end;
306  if(end_height)
307  end2.z += end_height;
308 
309  vector fixed_end = end;
310  vector move;
311 
312  if (flatdist > 0 && WETFEET(org))
313  {
314  if (SUBMERGED(org))
315  nav_action = NAV_SWIM_UNDERWATER;
316  else
317  {
318  // tracebox down by player's height
319  // useful to know if water level is so low that bot can still walk
320  tracebox(org, m1, m2, org - eZ * (m2.z - m1.z), movemode, e);
321  if (SUBMERGED(trace_endpos))
322  {
323  org = trace_endpos;
324  nav_action = NAV_SWIM_UNDERWATER;
325  }
326  else
327  nav_action = NAV_WALK;
328  }
329  }
330  else
331  nav_action = NAV_WALK;
332 
333  // Movement loop
334  while (true)
335  {
336  if (flatdist <= 0)
337  {
338  bool success = true;
339  if (org.z > end2.z + 1)
340  {
341  tracebox(org, m1, m2, end2, movemode, e);
342  org = trace_endpos;
343  if (org.z > end2.z + 1)
344  success = false;
345  }
346  else if (org.z < end.z - 1)
347  {
348  tracebox(org, m1, m2, org - jumpheight_vec, movemode, e);
349  if (SUBMERGED(trace_endpos))
350  {
352  tracebox(v, m1, m2, end, movemode, e);
353  if(trace_endpos.z >= end.z - 1)
354  {
356  trace_endpos = v;
357  }
358  }
359  else if (trace_endpos.z > org.z - jumpheight_vec.z)
360  tracebox(trace_endpos, m1, m2, trace_endpos + jumpheight_vec, movemode, e);
361  org = trace_endpos;
362  if (org.z < end.z - 1)
363  success = false;
364  }
365 
366  if (success)
367  {
368  // Succeeded
370  {
371  debugnode(e, org);
373  }
374 
375  //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n");
376  return true;
377  }
378  }
379 
381  debugnode(e, org);
382 
383  if (flatdist <= 0)
384  break;
385 
386  if (stepdist > flatdist)
387  stepdist = flatdist;
388  if(nav_action == NAV_SWIM_UNDERWATER || (nav_action == NAV_SWIM_ONWATER && org.z > end2.z))
389  {
390  // can't use movement direction here to calculate move because of
391  // precision errors especially when direction has a high enough z value
392  //water_dir = normalize(water_end - org);
393  //move = org + water_dir * stepdist;
394  fixed_end.z = bound(end.z, org.z, end2.z);
395  if (stepdist == flatdist) {
396  move = fixed_end;
397  flatdist = 0;
398  } else {
399  move = org + (fixed_end - org) * (stepdist / flatdist);
400  flatdist = vlen(vec2(fixed_end - move));
401  }
402  }
403  else // horiz. direction
404  {
405  flatdist -= stepdist;
406  move = org + flatdir * stepdist;
407  }
408 
409  if(nav_action == NAV_SWIM_ONWATER)
410  {
411  tracebox(org, m1, m2, move, movemode, e); // swim
412 
413  // hit something
414  if (trace_fraction < 1)
415  {
416  // stepswim
417  tracebox(org + stepheightvec, m1, m2, move + stepheightvec, movemode, e);
418 
419  if (trace_fraction < 1 || trace_startsolid) // can't jump obstacle out of water
420  {
421  org = trace_endpos;
422  if(navigation_checkladders(e, org, m1, m2, end, end2, movemode))
423  {
425  {
426  debugnode(e, org);
428  }
429 
430  //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n");
431  return true;
432  }
433 
436 
437  return false;
438  //print("tracewalk: ", vtos(start), " hit something when trying to reach ", vtos(end), "\n");
439  }
440 
441  //succesful stepswim
442 
443  if (flatdist <= 0)
444  {
445  org = trace_endpos;
446  continue;
447  }
448 
449  if (org.z <= move.z) // going horiz.
450  {
451  tracebox(trace_endpos, m1, m2, move, movemode, e);
452  org = trace_endpos;
453  nav_action = NAV_WALK;
454  continue;
455  }
456  }
457 
458  if (org.z <= move.z) // going horiz.
459  {
460  org = trace_endpos;
461  nav_action = NAV_SWIM_ONWATER;
462  }
463  else // going down
464  {
465  org = trace_endpos;
466  if (SUBMERGED(org))
467  nav_action = NAV_SWIM_UNDERWATER;
468  else
469  nav_action = NAV_SWIM_ONWATER;
470  }
471  }
472  else if(nav_action == NAV_SWIM_UNDERWATER)
473  {
474  if (move.z >= org.z) // swimming upwards or horiz.
475  {
476  tracebox(org, m1, m2, move, movemode, e); // swim
477 
478  bool stepswum = false;
479 
480  // hit something
481  if (trace_fraction < 1)
482  {
483  // stepswim
484  vector stepswim_move = move + stepheightvec;
485  if (flatdist > 0 && stepswim_move.z > end2.z + stepheightvec.z) // don't allow stepswim to go higher than destination
486  stepswim_move.z = end2.z;
487 
488  tracebox(org + stepheightvec, m1, m2, stepswim_move, movemode, e);
489 
490  // hit something
491  if (trace_startsolid)
492  {
495 
496  //print("tracewalk: ", vtos(start), " hit something when trying to reach ", vtos(end), "\n");
497  return false;
498  }
499 
500  if (trace_fraction < 1)
501  {
502  float org_z_prev = org.z;
503  RESURFACE_LIMITED(org, end2.z);
504  if(org.z == org_z_prev)
505  {
508 
509  //print("tracewalk: ", vtos(start), " can't reach ", vtos(end), "\n");
510  return false;
511  }
512  if(SUBMERGED(org))
513  nav_action = NAV_SWIM_UNDERWATER;
514  else
515  nav_action = NAV_SWIM_ONWATER;
516 
517  // we didn't advance horiz. in this step, flatdist decrease should be reverted
518  // but we can't do it properly right now... apply this workaround instead
519  if (flatdist <= 0)
520  flatdist = 1;
521 
522  continue;
523  }
524 
525  //succesful stepswim
526 
527  if (flatdist <= 0)
528  {
529  org = trace_endpos;
530  continue;
531  }
532 
533  stepswum = true;
534  }
535 
536  if (!WETFEET(trace_endpos))
537  {
538  tracebox(trace_endpos, m1, m2, trace_endpos - eZ * (stepdist + (m2.z - m1.z)), movemode, e);
539  // if stepswum we'll land on the obstacle, avoid the SUBMERGED check
540  if (!stepswum && SUBMERGED(trace_endpos))
541  {
543  org = trace_endpos;
544  nav_action = NAV_SWIM_ONWATER;
545  continue;
546  }
547 
548  // not submerged
549  org = trace_endpos;
550  nav_action = NAV_WALK;
551  continue;
552  }
553 
554  // wetfeet
555  org = trace_endpos;
556  nav_action = NAV_SWIM_UNDERWATER;
557  continue;
558  }
559  else //if (move.z < org.z) // swimming downwards
560  {
561  tracebox(org, m1, m2, move, movemode, e); // swim
562 
563  // hit something
564  if (trace_fraction < 1)
565  {
566  // stepswim
567  tracebox(org + stepheightvec, m1, m2, move + stepheightvec, movemode, e);
568 
569  // hit something
570  if (trace_fraction < 1 || trace_startsolid) // can't jump obstacle out of water
571  {
574 
575  //print("tracewalk: ", vtos(start), " hit something when trying to reach ", vtos(end), "\n");
576  return false;
577  }
578 
579  //succesful stepswim
580 
581  if (flatdist <= 0)
582  {
583  org = trace_endpos;
584  continue;
585  }
586 
587  if (trace_endpos.z > org.z && !SUBMERGED(trace_endpos))
588  {
589  // stepswim caused upwards direction
590  tracebox(trace_endpos, m1, m2, trace_endpos - stepheightvec, movemode, e);
591  if (!SUBMERGED(trace_endpos))
592  {
593  org = trace_endpos;
594  nav_action = NAV_WALK;
595  continue;
596  }
597  }
598  }
599 
600  org = trace_endpos;
601  nav_action = NAV_SWIM_UNDERWATER;
602  continue;
603  }
604  }
605  else if(nav_action == NAV_WALK)
606  {
607  // walk
608  tracebox(org, m1, m2, move, movemode, e);
609 
612 
613  // hit something
614  if (trace_fraction < 1)
615  {
616  // check if we can walk over this obstacle, possibly by jumpstepping
617  tracebox(org + stepheightvec, m1, m2, move + stepheightvec, movemode, e);
618  if (trace_fraction < 1 || trace_startsolid)
619  {
620  if (trace_startsolid) // hit ceiling above org
621  {
622  // reduce stepwalk height
623  tracebox(org, m1, m2, org + stepheightvec, movemode, e);
624  tracebox(trace_endpos, m1, m2, move + eZ * (trace_endpos.z - move.z), movemode, e);
625  }
626  else //if (trace_fraction < 1)
627  {
628  tracebox(org + jumpstepheightvec, m1, m2, move + jumpstepheightvec, movemode, e);
629  if (trace_startsolid) // hit ceiling above org
630  {
631  // reduce jumpstepwalk height
632  tracebox(org, m1, m2, org + jumpstepheightvec, movemode, e);
633  tracebox(trace_endpos, m1, m2, move + eZ * (trace_endpos.z - move.z), movemode, e);
634  }
635  }
636 
637  if (trace_fraction < 1)
638  {
640  v.z = org.z + jumpheight_vec.z;
641  if(navigation_checkladders(e, v, m1, m2, end, end2, movemode))
642  {
644  {
645  debugnode(e, v);
647  }
648 
649  //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n");
650  return true;
651  }
652 
655 
656  traceline( org, move, movemode, e);
657 
658  if ( trace_ent.classname == "door_rotating" || trace_ent.classname == "door")
659  {
660  vector nextmove;
661  move = trace_endpos;
662  while(trace_ent.classname == "door_rotating" || trace_ent.classname == "door")
663  {
664  nextmove = move + (flatdir * stepdist);
665  traceline( move, nextmove, movemode, e);
666  move = nextmove;
667  }
668  flatdist = vlen(vec2(end - move));
669  }
670  else
671  {
674 
675  //print("tracewalk: ", vtos(start), " hit something when trying to reach ", vtos(end), "\n");
676  //te_explosion(trace_endpos);
677  //print(ftos(e.dphitcontentsmask), "\n");
678  return false; // failed
679  }
680  }
681  else
682  move = trace_endpos;
683  }
684  else
685  move = trace_endpos;
686  }
687  else
688  move = trace_endpos;
689 
690  // trace down from stepheight as far as possible and move there,
691  // if this starts in solid we try again without the stepup, and
692  // if that also fails we assume it is a wall
693  // (this is the same logic as the Quake walkmove function used)
694  tracebox(move, m1, m2, move + '0 0 -65536', movemode, e);
695 
696  org = trace_endpos;
697 
698  if (!ignorehazards)
699  {
700  if (IN_LAVA(org))
701  {
703  {
706  }
707 
708  //print("tracewalk: ", vtos(start), " hits a hazard when trying to reach ", vtos(end), "\n");
709  return false;
710  }
711  }
712 
713  if (flatdist <= 0)
714  {
715  if(move.z >= end2.z && org.z < end2.z)
716  org.z = end2.z;
717  continue;
718  }
719 
720  if(org.z > move.z - 1 || !SUBMERGED(org))
721  {
722  nav_action = NAV_WALK;
723  continue;
724  }
725 
726  // ended up submerged while walking
728  debugnode(e, org);
729 
730  RESURFACE_LIMITED(org, move.z);
731  nav_action = NAV_SWIM_ONWATER;
732  continue;
733  }
734  }
735 
736  //print("tracewalk: ", vtos(start), " did not arrive at ", vtos(end), " but at ", vtos(org), "\n");
737 
738  // moved but didn't arrive at the intended destination
741 
742  return false;
743 }
744 
746 // goal stack
748 
749 // completely empty the goal stack, used when deciding where to go
751 {
752  this.lastteleporttime = 0;
753  this.goalcurrent_prev = this.goalcurrent;
756  this.goalcurrent_distance_time = 0;
757  this.goalentity_lock_timeout = 0;
758  this.goalentity_shouldbefrozen = false;
759  this.goalentity = NULL;
760  this.goalcurrent = NULL;
761  this.goalstack01 = NULL;
762  this.goalstack02 = NULL;
763  this.goalstack03 = NULL;
764  this.goalstack04 = NULL;
765  this.goalstack05 = NULL;
766  this.goalstack06 = NULL;
767  this.goalstack07 = NULL;
768  this.goalstack08 = NULL;
769  this.goalstack09 = NULL;
770  this.goalstack10 = NULL;
771  this.goalstack11 = NULL;
772  this.goalstack12 = NULL;
773  this.goalstack13 = NULL;
774  this.goalstack14 = NULL;
775  this.goalstack15 = NULL;
776  this.goalstack16 = NULL;
777  this.goalstack17 = NULL;
778  this.goalstack18 = NULL;
779  this.goalstack19 = NULL;
780  this.goalstack20 = NULL;
781  this.goalstack21 = NULL;
782  this.goalstack22 = NULL;
783  this.goalstack23 = NULL;
784  this.goalstack24 = NULL;
785  this.goalstack25 = NULL;
786  this.goalstack26 = NULL;
787  this.goalstack27 = NULL;
788  this.goalstack28 = NULL;
789  this.goalstack29 = NULL;
790  this.goalstack30 = NULL;
791  this.goalstack31 = NULL;
792 }
793 
794 // add a new goal at the beginning of the stack
795 // (in other words: add a new prerequisite before going to the later goals)
796 // NOTE: when a waypoint is added, the WP gets pushed first, then the
797 // next-closest WP on the shortest path to the WP
798 // That means, if the stack overflows, the bot will know how to do the FIRST 32
799 // steps to the goal, and then recalculate the path.
801 {
802  this.goalcurrent_prev = this.goalcurrent;
805  this.goalcurrent_distance_time = 0;
806  //print("bot ", etos(this), " push ", etos(e), "\n");
807  if(this.goalstack31 == this.goalentity)
808  this.goalentity = NULL;
809  this.goalstack31 = this.goalstack30;
810  this.goalstack30 = this.goalstack29;
811  this.goalstack29 = this.goalstack28;
812  this.goalstack28 = this.goalstack27;
813  this.goalstack27 = this.goalstack26;
814  this.goalstack26 = this.goalstack25;
815  this.goalstack25 = this.goalstack24;
816  this.goalstack24 = this.goalstack23;
817  this.goalstack23 = this.goalstack22;
818  this.goalstack22 = this.goalstack21;
819  this.goalstack21 = this.goalstack20;
820  this.goalstack20 = this.goalstack19;
821  this.goalstack19 = this.goalstack18;
822  this.goalstack18 = this.goalstack17;
823  this.goalstack17 = this.goalstack16;
824  this.goalstack16 = this.goalstack15;
825  this.goalstack15 = this.goalstack14;
826  this.goalstack14 = this.goalstack13;
827  this.goalstack13 = this.goalstack12;
828  this.goalstack12 = this.goalstack11;
829  this.goalstack11 = this.goalstack10;
830  this.goalstack10 = this.goalstack09;
831  this.goalstack09 = this.goalstack08;
832  this.goalstack08 = this.goalstack07;
833  this.goalstack07 = this.goalstack06;
834  this.goalstack06 = this.goalstack05;
835  this.goalstack05 = this.goalstack04;
836  this.goalstack04 = this.goalstack03;
837  this.goalstack03 = this.goalstack02;
838  this.goalstack02 = this.goalstack01;
839  this.goalstack01 = this.goalcurrent;
840  this.goalcurrent = e;
841 }
842 
843 // remove first goal from stack
844 // (in other words: remove a prerequisite for reaching the later goals)
845 // (used when a spawnfunc_waypoint is reached)
847 {
848  this.goalcurrent_prev = this.goalcurrent;
851  this.goalcurrent_distance_time = 0;
852  //print("bot ", etos(this), " pop\n");
853  if(this.goalcurrent == this.goalentity)
854  {
855  this.goalentity = NULL;
856  this.goalentity_lock_timeout = 0;
857  }
858  this.goalcurrent = this.goalstack01;
859  this.goalstack01 = this.goalstack02;
860  this.goalstack02 = this.goalstack03;
861  this.goalstack03 = this.goalstack04;
862  this.goalstack04 = this.goalstack05;
863  this.goalstack05 = this.goalstack06;
864  this.goalstack06 = this.goalstack07;
865  this.goalstack07 = this.goalstack08;
866  this.goalstack08 = this.goalstack09;
867  this.goalstack09 = this.goalstack10;
868  this.goalstack10 = this.goalstack11;
869  this.goalstack11 = this.goalstack12;
870  this.goalstack12 = this.goalstack13;
871  this.goalstack13 = this.goalstack14;
872  this.goalstack14 = this.goalstack15;
873  this.goalstack15 = this.goalstack16;
874  this.goalstack16 = this.goalstack17;
875  this.goalstack17 = this.goalstack18;
876  this.goalstack18 = this.goalstack19;
877  this.goalstack19 = this.goalstack20;
878  this.goalstack20 = this.goalstack21;
879  this.goalstack21 = this.goalstack22;
880  this.goalstack22 = this.goalstack23;
881  this.goalstack23 = this.goalstack24;
882  this.goalstack24 = this.goalstack25;
883  this.goalstack25 = this.goalstack26;
884  this.goalstack26 = this.goalstack27;
885  this.goalstack27 = this.goalstack28;
886  this.goalstack28 = this.goalstack29;
887  this.goalstack29 = this.goalstack30;
888  this.goalstack30 = this.goalstack31;
889  this.goalstack31 = NULL;
890 }
891 
892 // walking to wp (walkfromwp == false) v2 and v2_height will be used as
893 // waypoint destination coordinates instead of v (only useful for box waypoints)
894 // for normal waypoints v2 == v and v2_height == 0
895 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)
896 {
897  if (vdist(v - org, <, bestdist))
898  {
899  traceline(v, org, true, ent);
900  if (trace_fraction == 1)
901  {
902  if (walkfromwp)
903  {
904  if (tracewalk(ent, v, PL_MIN_CONST, PL_MAX_CONST, v2, v2_height, bot_navigation_movemode))
905  return true;
906  }
907  else
908  {
909  if (tracewalk(ent, org, PL_MIN_CONST, PL_MAX_CONST, o2, o2_height, bot_navigation_movemode))
910  return true;
911  }
912  }
913  }
914  return false;
915 }
916 
917 // find the spawnfunc_waypoint near a dynamic goal such as a dropped weapon
918 entity navigation_findnearestwaypoint_withdist_except(entity ent, float walkfromwp, float bestdist, entity except)
919 {
920  if(ent.tag_entity)
921  ent = ent.tag_entity;
922 
923  vector pm1 = ent.origin + ent.mins;
924  vector pm2 = ent.origin + ent.maxs;
925 
927  {
928  // this code allows removing waypoints in the air and seeing jumppad/telepport waypoint links
929  // FIXME it causes a bug where a waypoint spawned really close to another one (max 16 qu)
930  // isn't detected as the nearest waypoint
931  IL_EACH(g_waypoints, it != ent && it != except,
932  {
933  if (boxesoverlap(pm1, pm2, it.absmin, it.absmax))
934  return it;
935  });
936  }
937  else
938  {
939  // do two scans, because box test is cheaper
940  IL_EACH(g_waypoints, it != ent && it != except && !(it.wpflags & (WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_JUMP)),
941  {
942  if(boxesoverlap(pm1, pm2, it.absmin, it.absmax))
943  {
944  if(walkfromwp && !ent.navigation_dynamicgoal)
945  waypoint_clearlinks(ent); // initialize wpXXmincost fields
946  return it;
947  }
948  });
949  }
950 
951  vector org = ent.origin;
953  te_plasmaburn(org);
954 
955  entity best = NULL;
956  vector v = '0 0 0';
957 
958  if(ent.size && !IS_PLAYER(ent))
959  {
960  org += 0.5 * (ent.mins + ent.maxs);
961  org.z = ent.origin.z + ent.mins.z - PL_MIN_CONST.z; // player height
962  }
963 
964  // box check failed, try walk
965  IL_EACH(g_waypoints, it != ent,
966  {
967  if (walkfromwp && (it.wpflags & WPFLAGMASK_NORELINK))
968  continue;
969  v = it.origin;
970 
971  if (walkfromwp)
972  {
973  set_tracewalk_dest(ent, v, true);
974  if (trace_ent == ent)
975  {
976  bestdist = 0;
977  best = it;
978  break;
979  }
980  }
981  else
982  set_tracewalk_dest(it, org, false);
983 
984  if (navigation_waypoint_will_link(v, org, ent,
986  tracewalk_dest, tracewalk_dest_height, walkfromwp, bestdist))
987  {
988  if (walkfromwp)
989  bestdist = vlen(tracewalk_dest - v);
990  else
991  bestdist = vlen(v - org);
992  best = it;
993  }
994  });
995  if(!best && !ent.navigation_dynamicgoal)
996  {
997  int solid_save = ent.solid;
998  ent.solid = SOLID_BSP;
999  IL_EACH(g_jumppads, true,
1000  {
1001  if(trigger_push_test(it, ent))
1002  {
1003  best = it.nearestwaypoint;
1004  break;
1005  }
1006  });
1007  ent.solid = solid_save;
1008  }
1009  return best;
1010 }
1012 {
1013  entity wp = navigation_findnearestwaypoint_withdist_except(ent, walkfromwp, 1050, NULL);
1015  {
1016  entity wp2 = navigation_findnearestwaypoint_withdist_except(ent, walkfromwp, 1050, wp);
1017  if (wp && !wp2)
1018  wp.wpflags |= WAYPOINTFLAG_PROTECTED;
1019  }
1020  return wp;
1021 }
1022 
1023 // finds the waypoints near the bot initiating a navigation query
1025 {
1026  //navigation_testtracewalk = true;
1027  int c = 0;
1028  IL_EACH(g_waypoints, !it.wpconsidered,
1029  {
1030  set_tracewalk_dest(it, this.origin, false);
1031 
1032  vector diff = tracewalk_dest - this.origin;
1033  diff.z = max(0, diff.z);
1034  if(vdist(diff, <, maxdist))
1035  {
1036  it.wpconsidered = true;
1037  if (tracewalk(this, this.origin, this.mins, this.maxs,
1038  tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode))
1039  {
1040  it.wpnearestpoint = tracewalk_dest;
1041  it.wpcost = waypoint_gettravelcost(this.origin, tracewalk_dest, this, it) + it.dmg;
1042  it.wpfire = 1;
1043  it.enemy = NULL;
1044  c = c + 1;
1045  }
1046  }
1047  });
1048  //navigation_testtracewalk = false;
1049  return c;
1050 }
1051 
1052 // updates a path link if a spawnfunc_waypoint link is better than the current one
1054 {
1055  vector m1, m2;
1056  vector v;
1057  if (wp.wpisbox)
1058  {
1059  m1 = wp.origin + wp.mins;
1060  m2 = wp.origin + wp.maxs;
1061  v.x = bound(m1_x, p.x, m2_x);
1062  v.y = bound(m1_y, p.y, m2_y);
1063  v.z = bound(m1_z, p.z, m2_z);
1064  }
1065  else
1066  v = wp.origin;
1067  if (w.wpflags & WAYPOINTFLAG_TELEPORT)
1068  cost += w.wp00mincost; // assuming teleport has exactly one destination
1069  else
1070  cost += waypoint_gettravelcost(p, v, w, wp);
1071  if (wp.wpcost > cost)
1072  {
1073  wp.wpcost = cost;
1074  wp.enemy = w;
1075  wp.wpfire = 1;
1076  wp.wpnearestpoint = v;
1077  }
1078 }
1079 
1080 // queries the entire spawnfunc_waypoint network for pathes leading away from the bot
1081 void navigation_markroutes(entity this, entity fixed_source_waypoint)
1082 {
1083  float cost, cost2;
1084  vector p;
1085 
1086  IL_EACH(g_waypoints, true,
1087  {
1088  it.wpconsidered = false;
1089  it.wpnearestpoint = '0 0 0';
1090  it.wpcost = 10000000;
1091  it.wpfire = 0;
1092  it.enemy = NULL;
1093  });
1094 
1095  if(fixed_source_waypoint)
1096  {
1097  fixed_source_waypoint.wpconsidered = true;
1098  fixed_source_waypoint.wpnearestpoint = fixed_source_waypoint.origin + 0.5 * (fixed_source_waypoint.mins + fixed_source_waypoint.maxs);
1099  fixed_source_waypoint.wpcost = fixed_source_waypoint.dmg;
1100  fixed_source_waypoint.wpfire = 1;
1101  fixed_source_waypoint.enemy = NULL;
1102  }
1103  else
1104  {
1105  // try a short range search for the nearest waypoints, and expand the search repeatedly if none are found
1106  // as this search is expensive we will use lower values if the bot is on the air
1107  float increment, maxdistance;
1108  if(IS_ONGROUND(this))
1109  {
1110  increment = 750;
1111  maxdistance = 50000;
1112  }
1113  else
1114  {
1115  increment = 500;
1116  maxdistance = 1500;
1117  }
1118 
1119  for(int j = increment; !navigation_markroutes_nearestwaypoints(this, j) && j < maxdistance; j += increment);
1120  }
1121 
1122  bool searching = true;
1123  while (searching)
1124  {
1125  searching = false;
1126  IL_EACH(g_waypoints, it.wpfire,
1127  {
1128  searching = true;
1129  it.wpfire = 0;
1130  cost = it.wpcost;
1131  p = it.wpnearestpoint;
1132  entity wp;
1133  wp = it.wp00;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp00mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1134  wp = it.wp01;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp01mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1135  wp = it.wp02;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp02mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1136  wp = it.wp03;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp03mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1137  wp = it.wp04;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp04mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1138  wp = it.wp05;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp05mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1139  wp = it.wp06;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp06mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1140  wp = it.wp07;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp07mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1141  wp = it.wp08;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp08mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1142  wp = it.wp09;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp09mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1143  wp = it.wp10;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp10mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1144  wp = it.wp11;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp11mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1145  wp = it.wp12;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp12mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1146  wp = it.wp13;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp13mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1147  wp = it.wp14;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp14mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1148  wp = it.wp15;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp15mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1149  wp = it.wp16;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp16mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1150  wp = it.wp17;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp17mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1151  wp = it.wp18;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp18mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1152  wp = it.wp19;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp19mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1153  wp = it.wp20;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp20mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1154  wp = it.wp21;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp21mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1155  wp = it.wp22;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp22mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1156  wp = it.wp23;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp23mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1157  wp = it.wp24;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp24mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1158  wp = it.wp25;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp25mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1159  wp = it.wp26;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp26mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1160  wp = it.wp27;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp27mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1161  wp = it.wp28;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp28mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1162  wp = it.wp29;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp29mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1163  wp = it.wp30;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp30mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1164  wp = it.wp31;if (wp){cost2 = cost + wp.dmg;if (wp.wpcost > cost2 + it.wp31mincost) navigation_markroutes_checkwaypoint(it, wp, cost2, p);
1165  }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
1166  });
1167  }
1168 }
1169 
1170 // queries the entire spawnfunc_waypoint network for pathes leading to the bot
1171 void navigation_markroutes_inverted(entity fixed_source_waypoint)
1172 {
1173  float cost, cost2;
1174  vector p;
1175  IL_EACH(g_waypoints, true,
1176  {
1177  it.wpconsidered = false;
1178  it.wpnearestpoint = '0 0 0';
1179  it.wpcost = 10000000;
1180  it.wpfire = 0;
1181  it.enemy = NULL;
1182  });
1183 
1184  if(fixed_source_waypoint)
1185  {
1186  fixed_source_waypoint.wpconsidered = true;
1187  fixed_source_waypoint.wpnearestpoint = fixed_source_waypoint.origin + 0.5 * (fixed_source_waypoint.mins + fixed_source_waypoint.maxs);
1188  fixed_source_waypoint.wpcost = fixed_source_waypoint.dmg; // the cost to get from X to fixed_source_waypoint
1189  fixed_source_waypoint.wpfire = 1;
1190  fixed_source_waypoint.enemy = NULL;
1191  }
1192  else
1193  {
1194  error("need to start with a waypoint\n");
1195  }
1196 
1197  bool searching = true;
1198  while (searching)
1199  {
1200  searching = false;
1201  IL_EACH(g_waypoints, it.wpfire,
1202  {
1203  searching = true;
1204  it.wpfire = 0;
1205  cost = it.wpcost; // cost to walk from it to home
1206  p = it.wpnearestpoint;
1207  entity wp = it;
1208  IL_EACH(g_waypoints, it != wp,
1209  {
1210  if(!waypoint_islinked(it, wp))
1211  continue;
1212  cost2 = cost + it.dmg;
1213  navigation_markroutes_checkwaypoint(wp, it, cost2, p);
1214  });
1215  });
1216  }
1217 }
1218 
1219 // updates the best goal according to a weighted calculation of travel cost and item value of a new proposed item
1220 void navigation_routerating(entity this, entity e, float f, float rangebias)
1221 {
1222  if (!e || e.blacklisted) { return; }
1223 
1224  rangebias = waypoint_getlinearcost(rangebias);
1225  f = waypoint_getlinearcost(f);
1226 
1227  if (IS_PLAYER(e))
1228  {
1229  bool rate_wps = false;
1230  if (e.watertype < CONTENT_WATER || (e.waterlevel > WATERLEVEL_WETFEET && !STAT(FROZEN, e))
1231  || (e.flags & FL_PARTIALGROUND))
1232  {
1233  rate_wps = true;
1234  }
1235 
1236  if(!IS_ONGROUND(e))
1237  {
1238  traceline(e.origin, e.origin + '0 0 -1500', true, NULL);
1239  int t = pointcontents(trace_endpos + '0 0 1');
1240  if(t != CONTENT_SOLID )
1241  {
1242  if(t == CONTENT_WATER || t == CONTENT_SLIME || t == CONTENT_LAVA)
1243  rate_wps = true;
1244  else if(tracebox_hits_trigger_hurt(e.origin, e.mins, e.maxs, trace_endpos))
1245  return;
1246  }
1247  }
1248 
1249  if(rate_wps)
1250  {
1251  entity theEnemy = e;
1252  entity best_wp = NULL;
1253  float best_dist = FLOAT_MAX;
1254  IL_EACH(g_waypoints, !(it.wpflags & WAYPOINTFLAG_TELEPORT)
1255  && vdist(it.origin - theEnemy.origin, <, 500)
1256  && vdist(it.origin - this.origin, >, 100)
1257  && vdist(it.origin - this.origin, <, 10000),
1258  {
1259  float dist = vlen2(it.origin - theEnemy.origin);
1260  if (dist < best_dist)
1261  {
1262  best_wp = it;
1263  best_dist = dist;
1264  }
1265  });
1266  if (!best_wp)
1267  return;
1268  e = best_wp;
1269  }
1270  }
1271 
1272  vector goal_org = (e.absmin + e.absmax) * 0.5;
1273 
1274  //print("routerating ", etos(e), " = ", ftos(f), " - ", ftos(rangebias), "\n");
1275 
1276  // Evaluate path using jetpack
1277  if(this.items & IT_JETPACK)
1280  {
1281  vector pointa, pointb;
1282 
1283  LOG_DEBUG("jetpack ai: evaluating path for ", e.classname);
1284 
1285  // Point A
1286  traceline(this.origin, this.origin + '0 0 65535', MOVE_NORMAL, this);
1287  pointa = trace_endpos - '0 0 1';
1288 
1289  // Point B
1290  traceline(goal_org, goal_org + '0 0 65535', MOVE_NORMAL, e);
1291  pointb = trace_endpos - '0 0 1';
1292 
1293  // Can I see these two points from the sky?
1294  traceline(pointa, pointb, MOVE_NORMAL, this);
1295 
1296  if(trace_fraction==1)
1297  {
1298  LOG_DEBUG("jetpack ai: can bridge these two points");
1299 
1300  // Lower the altitude of these points as much as possible
1301  float zdistance, xydistance, cost, t, fuel;
1302  vector down, npa, npb;
1303 
1304  down = '0 0 -1' * (STAT(PL_MAX, this).z - STAT(PL_MIN, this).z) * 10;
1305 
1306  do{
1307  npa = pointa + down;
1308  npb = pointb + down;
1309 
1310  if(npa.z<=this.absmax.z)
1311  break;
1312 
1313  if(npb.z<=e.absmax.z)
1314  break;
1315 
1316  traceline(npa, npb, MOVE_NORMAL, this);
1317  if(trace_fraction==1)
1318  {
1319  pointa = npa;
1320  pointb = npb;
1321  }
1322  }
1323  while(trace_fraction == 1);
1324 
1325 
1326  // Rough estimation of fuel consumption
1327  // (ignores acceleration and current xyz velocity)
1328  xydistance = vlen(pointa - pointb);
1329  zdistance = fabs(pointa.z - this.origin.z);
1330 
1331  t = zdistance / autocvar_g_jetpack_maxspeed_up;
1332  t += xydistance / autocvar_g_jetpack_maxspeed_side;
1333  fuel = t * autocvar_g_jetpack_fuel * 0.8;
1334 
1335  LOG_DEBUG("jetpack ai: required fuel ", ftos(fuel), ", have ", ftos(GetResource(this, RES_FUEL)));
1336 
1337  // enough fuel ?
1338  if(GetResource(this, RES_FUEL) > fuel || (this.items & IT_UNLIMITED_AMMO))
1339  {
1340  // Estimate cost
1341  // (as onground costs calculation is mostly based on distances, here we do the same establishing some relationship
1342  // - between air and ground speeds)
1343 
1344  cost = xydistance / (autocvar_g_jetpack_maxspeed_side/autocvar_sv_maxspeed);
1345  cost += zdistance / (autocvar_g_jetpack_maxspeed_up/autocvar_sv_maxspeed);
1346  cost *= 1.5;
1347 
1348  // Compare against other goals
1349  f = f * rangebias / (rangebias + cost);
1350 
1351  if (navigation_bestrating < f)
1352  {
1353  LOG_DEBUG("jetpack path: added goal ", e.classname, " (with rating ", ftos(f), ")");
1355  navigation_bestgoal = e;
1356  this.navigation_jetpack_goal = e;
1357  this.navigation_jetpack_point = pointb;
1358  }
1359  return;
1360  }
1361  }
1362  }
1363 
1364  entity nwp;
1365  //te_wizspike(e.origin);
1366  //bprint(etos(e));
1367  //bprint("\n");
1368  // update the cached spawnfunc_waypoint link on a dynamic item entity
1369  if(e.classname == "waypoint" && !(e.wpflags & WAYPOINTFLAG_PERSONAL))
1370  {
1371  nwp = e;
1372  }
1373  else
1374  {
1375  if(waypointeditor_enabled && e.nearestwaypointtimeout >= 0 && time > e.nearestwaypointtimeout)
1376  e.nearestwaypoint = NULL;
1377 
1378  if ((!e.nearestwaypoint || e.navigation_dynamicgoal)
1379  && e.nearestwaypointtimeout >= 0 && time > e.nearestwaypointtimeout)
1380  {
1381  if(IS_BOT_CLIENT(e) && e.goalcurrent && e.goalcurrent.classname == "waypoint")
1382  e.nearestwaypoint = nwp = e.goalcurrent;
1383  else
1384  e.nearestwaypoint = nwp = navigation_findnearestwaypoint(e, true);
1385  if(!nwp)
1386  {
1387  LOG_DEBUG("FAILED to find a nearest waypoint to '", e.classname, "' #", etos(e));
1388 
1389  if(!e.navigation_dynamicgoal)
1390  e.blacklisted = true;
1391 
1392  if(e.blacklisted)
1393  {
1394  LOG_DEBUG("The entity '", e.classname, "' is going to be excluded from path finding during this match");
1395  return;
1396  }
1397  }
1398 
1399  if(e.navigation_dynamicgoal)
1400  e.nearestwaypointtimeout = time + 2;
1401  else if(waypointeditor_enabled)
1402  e.nearestwaypointtimeout = time + 3 + random() * 2;
1403  }
1404  nwp = e.nearestwaypoint;
1405  }
1406 
1407  if (nwp && nwp.wpcost < 10000000)
1408  {
1409  //te_wizspike(nwp.wpnearestpoint);
1410  float nwptoitem_cost = 0;
1411  if(nwp.wpflags & WAYPOINTFLAG_TELEPORT)
1412  nwptoitem_cost = nwp.wp00mincost;
1413  else
1414  nwptoitem_cost = waypoint_gettravelcost(nwp.wpnearestpoint, goal_org, nwp, e);
1415  float cost = nwp.wpcost + nwptoitem_cost;
1416  LOG_DEBUG("checking ^5", e.classname, "^7 with base rating ^xf04", ftos(f), "^7 and rangebias ^xf40", ftos(rangebias));
1417  f = f * rangebias / (rangebias + cost);
1418  LOG_DEBUG(" ^5", e.classname, "^7 with cost ^6", ftos(cost), "^7 and final rating ^2", ftos(f));
1419  if (navigation_bestrating < f)
1420  {
1421  LOG_DEBUG(" ground path: ^3added goal ^5", e.classname);
1423  navigation_bestgoal = e;
1424  }
1425  }
1426 }
1427 
1428 // adds an item to the the goal stack with the path to a given item
1429 bool navigation_routetogoal(entity this, entity e, vector startposition)
1430 {
1431  // if there is no goal, just exit
1432  if (!e)
1433  return false;
1434 
1435  entity teleport_goal = NULL;
1436 
1437  this.goalentity = e;
1438 
1439  if(e.wpflags & WAYPOINTFLAG_TELEPORT)
1440  {
1441  // force teleport destination as route destination
1442  teleport_goal = e;
1443  navigation_pushroute(this, e.wp00);
1444  this.goalentity = e.wp00;
1445  }
1446 
1447  // put the entity on the goal stack
1448  //print("routetogoal ", etos(e), "\n");
1449  navigation_pushroute(this, e);
1450 
1451  if(teleport_goal)
1452  e = this.goalentity;
1453 
1454  if(e.classname == "waypoint" && !(e.wpflags & WAYPOINTFLAG_PERSONAL))
1455  {
1456  this.wp_goal_prev1 = this.wp_goal_prev0;
1457  this.wp_goal_prev0 = e;
1458  }
1459 
1460  if((this.items & IT_JETPACK) && e == this.navigation_jetpack_goal)
1461  return true;
1462 
1463  // if it can reach the goal there is nothing more to do
1464  set_tracewalk_dest(e, startposition, true);
1466  && (trace_ent == this || tracewalk(this, startposition, STAT(PL_MIN, this), STAT(PL_MAX, this),
1468  {
1469  return true;
1470  }
1471 
1472  entity nearest_wp = NULL;
1473  // see if there are waypoints describing a path to the item
1474  if(e.classname != "waypoint" || (e.wpflags & WAYPOINTFLAG_PERSONAL))
1475  {
1476  e = e.nearestwaypoint;
1477  nearest_wp = e;
1478  }
1479  else if(teleport_goal)
1480  e = teleport_goal;
1481  else
1482  e = e.enemy; // we already have added it, so...
1483 
1484  if(e == NULL)
1485  return false;
1486 
1487  if(nearest_wp && nearest_wp.enemy && !(nearest_wp.enemy.wpflags & WPFLAGMASK_NORELINK))
1488  {
1489  // often path can be optimized by not adding the nearest waypoint
1490  if (this.goalentity.navigation_dynamicgoal || waypointeditor_enabled)
1491  {
1492  if (nearest_wp.enemy.wpcost < autocvar_bot_ai_strategyinterval_movingtarget)
1493  {
1494  if (vdist(vec2(this.goalentity.origin - nearest_wp.origin), <, 32))
1495  e = nearest_wp.enemy;
1496  else
1497  {
1498  set_tracewalk_dest(this.goalentity, nearest_wp.enemy.origin, true);
1499  if (trace_ent == this || (vdist(tracewalk_dest - nearest_wp.enemy.origin, <, 1050)
1500  && vlen2(tracewalk_dest - nearest_wp.enemy.origin) < vlen2(nearest_wp.origin - nearest_wp.enemy.origin)
1501  && tracewalk(this, nearest_wp.enemy.origin, STAT(PL_MIN, this), STAT(PL_MAX, this),
1503  {
1504  e = nearest_wp.enemy;
1505  }
1506  }
1507  }
1508  }
1509  else
1510  {
1511  // NOTE unlike waypoints, items hold incoming links
1513  int link_num = navigation_item_getlinknum(this.goalentity, nearest_wp.enemy);
1514  if (link_num >= 0)
1515  {
1516  if (navigation_item_iswalkablelink(this.goalentity, link_num))
1517  e = nearest_wp.enemy;
1518  }
1519  else // untested link
1520  {
1521  entity wp = nearest_wp.enemy;
1522  entity goal = this.goalentity;
1523  bool walkable = false;
1524  if (checkpvs(wp.origin, goal))
1525  {
1526  set_tracewalk_dest(goal, wp.origin, false);
1527  if (vdist(tracewalk_dest - wp.origin, <, 1050)
1528  && tracewalk(goal, wp.origin, PL_MIN_CONST, PL_MAX_CONST,
1530  {
1531  walkable = true;
1532  e = nearest_wp.enemy;
1533  }
1534  }
1535  navigation_item_add_link(wp, goal, walkable);
1536  }
1537  }
1538  }
1539 
1540  for (;;)
1541  {
1542  // add the spawnfunc_waypoint to the path
1543  navigation_pushroute(this, e);
1544  e = e.enemy;
1545 
1546  if(e==NULL)
1547  break;
1548  }
1549 
1550  return false;
1551 }
1552 
1553 // shorten path by removing intermediate goals
1555 {
1556  if (!this.goalstack01 || wasfreed(this.goalstack01))
1557  return false;
1558  if (this.bot_tracewalk_time > time)
1559  return false;
1560  this.bot_tracewalk_time = max(time, this.bot_tracewalk_time) + 0.25;
1561 
1562  bool cut_allowed = false;
1563  entity next = this.goalentity;
1564  // evaluate whether bot can discard current route and chase directly a player, trying to
1565  // keep waypoint route as long as possible, as it is safer and faster (bot can bunnyhop)
1566  if (IS_MOVABLE(next))
1567  {
1568  set_tracewalk_dest(next, this.origin, true);
1569  if (vdist(this.origin - tracewalk_dest, <, 200))
1570  cut_allowed = true;
1571  else if (vdist(tracewalk_dest - this.origin, <, MAX_CHASE_DISTANCE)
1572  && vdist(tracewalk_dest - this.goalcurrent.origin, >, 200)
1573  && vdist(this.origin - this.goalcurrent.origin, >, 100)
1574  && checkpvs(this.origin + this.view_ofs, next))
1575  {
1576  if (vlen2(next.origin - this.origin) < vlen2(this.goalcurrent.origin - this.origin))
1577  cut_allowed = true;
1578  else
1579  {
1580  vector deviation = vectoangles(this.goalcurrent.origin - this.origin) - vectoangles(next.origin - this.origin);
1581  while (deviation.y < -180) deviation.y += 360;
1582  while (deviation.y > 180) deviation.y -= 360;
1583  if (fabs(deviation.y) > 25)
1584  cut_allowed = true;
1585  }
1586  }
1587  if (cut_allowed)
1588  {
1589  if (trace_ent == this || tracewalk(this, this.origin, this.mins, this.maxs,
1591  {
1592  LOG_DEBUG("path optimized for ", this.netname, ", route cleared");
1593  do
1594  {
1595  navigation_poproute(this);
1596  }
1597  while (this.goalcurrent != next);
1598  return true;
1599  }
1600  return false;
1601  }
1602  }
1603 
1604  next = this.goalstack01;
1605  // if for some reason the bot is closer to the next goal, pop the current one
1606  if (!IS_MOVABLE(next) && !(this.goalcurrent.wpflags & (WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_JUMP))
1607  && vlen2(this.goalcurrent.origin - next.origin) > vlen2(next.origin - this.origin)
1608  && checkpvs(this.origin + this.view_ofs, next))
1609  {
1610  set_tracewalk_dest(next, this.origin, true);
1611  cut_allowed = true;
1612  }
1613 
1614  if (cut_allowed)
1615  {
1616  if (trace_ent == this || tracewalk(this, this.origin, this.mins, this.maxs,
1618  {
1619  LOG_DEBUG("path optimized for ", this.netname, ", removed a goal from the queue");
1620  navigation_poproute(this);
1621  return true;
1622  }
1623  }
1624  return false;
1625 }
1626 
1627 // removes any currently touching waypoints from the goal stack
1628 // (this is how bots detect if they reached a goal)
1630 {
1631  int removed_goals = 0;
1632 
1633  if(!this.goalcurrent)
1634  return removed_goals;
1635 
1636  if(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
1637  {
1638  if (!this.goalcurrent.wpisbox // warpzone
1639  && vlen2(this.origin - this.goalstack01.origin) < vlen2(this.origin - this.goalcurrent.origin))
1640  {
1641  // immediately remove origin and destination waypoints
1642  navigation_poproute(this);
1643  ++removed_goals;
1644  navigation_poproute(this);
1645  ++removed_goals;
1646  this.lastteleporttime = 0;
1647  }
1648 
1649  // make sure jumppad is really hit, don't rely on distance based checks
1650  // as they may report a touch even if it didn't really happen
1651  if(this.lastteleporttime > 0 && TELEPORT_USED(this, this.goalcurrent))
1652  {
1654  if((this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL) && this.goalcurrent.owner==this)
1655  {
1656  this.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
1658  }
1659  if(this.jumppadcount)
1660  {
1661  // remove jumppad waypoint after a random delay to prevent bots getting
1662  // stuck on certain jumppads that require an extra initial horizontal speed
1663  float max_delay = 0.1;
1664  if (vdist(vec2(this.velocity), >, 2 * autocvar_sv_maxspeed))
1665  max_delay = 0.05;
1666  if (time - this.lastteleporttime < random() * max_delay)
1667  return removed_goals;
1668  }
1669  else if (this.goalcurrent.wpisbox) // teleport
1670  {
1671  // immediately remove origin and destination waypoints
1672  navigation_poproute(this);
1673  ++removed_goals;
1674  }
1675  navigation_poproute(this);
1676  this.lastteleporttime = 0;
1677  ++removed_goals;
1678  }
1679  return removed_goals;
1680  }
1681  else if (this.lastteleporttime > 0)
1682  {
1683  // sometimes bot is pushed so hard (by a jumppad or a shot) that ends up touching the next
1684  // teleport / jumppad / warpzone present in its path skipping check of one or more goals
1685  // if so immediately fix bot path by removing skipped goals
1686  entity tele_ent = NULL;
1687  if (this.goalstack01 && (this.goalstack01.wpflags & WAYPOINTFLAG_TELEPORT))
1688  tele_ent = this.goalstack01;
1689  else if (this.goalstack02 && (this.goalstack02.wpflags & WAYPOINTFLAG_TELEPORT))
1690  tele_ent = this.goalstack02;
1691  else if (this.goalstack03 && (this.goalstack03.wpflags & WAYPOINTFLAG_TELEPORT))
1692  tele_ent = this.goalstack03;
1693  if (tele_ent && TELEPORT_USED(this, tele_ent))
1694  {
1696  if ((tele_ent.wpflags & WAYPOINTFLAG_PERSONAL) && tele_ent.owner == this)
1697  {
1698  this.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
1700  }
1701  while (this.goalcurrent != tele_ent)
1702  {
1703  navigation_poproute(this);
1704  ++removed_goals;
1705  }
1706  navigation_poproute(this);
1707  this.lastteleporttime = 0;
1708  ++removed_goals;
1709  return removed_goals;
1710  }
1711  // reset of lastteleporttime can be overriden by a jumppad when it's set
1712  // in more than one frame: make sure it's reset
1713  this.lastteleporttime = 0;
1714  }
1715 
1716  // Loose goal touching check when running
1717  // check goalstack01 to make sure waypoint isn't the final goal
1718  if((this.aistatus & AI_STATUS_RUNNING) && this.goalcurrent.classname == "waypoint" && !(this.goalcurrent.wpflags & WAYPOINTFLAG_JUMP)
1719  && this.goalstack01 && !wasfreed(this.goalstack01) && vdist(vec2(this.velocity), >=, autocvar_sv_maxspeed))
1720  {
1721  vector gco = this.goalcurrent.origin;
1722  float min_dist = BOT_BUNNYHOP_WP_DETECTION_RANGE;
1723  // also detect waypoints when bot is way above them but with a narrower horizontal range
1724  // so to increase chances bot ends up in the standard range (optimizes nearest waypoint finding)
1725  if(vdist(this.origin - gco, <, min_dist)
1726  || (vdist(vec2(this.origin - gco), <, min_dist * 0.5) && vdist(this.origin - eZ * 1.5 * min_dist - gco, <, min_dist)))
1727  {
1728  traceline(this.origin + this.view_ofs , this.goalcurrent.origin, true, NULL);
1729  if(trace_fraction==1)
1730  {
1731  // Detect personal waypoints
1733  if((this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL) && this.goalcurrent.owner==this)
1734  {
1735  this.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
1737  }
1738 
1739  navigation_poproute(this);
1740  ++removed_goals;
1741  if(this.goalcurrent && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
1742  return removed_goals;
1743  }
1744  }
1745  }
1746 
1747  while (this.goalcurrent && !IS_PLAYER(this.goalcurrent))
1748  {
1749  vector gc_min = this.goalcurrent.absmin;
1750  vector gc_max = this.goalcurrent.absmax;
1751  if(this.goalcurrent.classname == "waypoint" && !this.goalcurrent.wpisbox)
1752  {
1753  gc_min = this.goalcurrent.origin - '1 1 1' * 12;
1754  gc_max = this.goalcurrent.origin + '1 1 1' * 12 + eZ * (jumpheight_vec.z + STAT(PL_MIN, this).z);
1755  }
1756  if (this.ladder_entity)
1757  {
1758  if (!boxesoverlap(this.absmin, this.absmax - eZ * STAT(PL_MAX, this).z, gc_min, gc_max))
1759  break;
1760  }
1761  else
1762  {
1763  if (!boxesoverlap(this.absmin, this.absmax, gc_min, gc_max))
1764  break;
1765  }
1766 
1767  // Detect personal waypoints
1769  if((this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL) && this.goalcurrent.owner==this)
1770  {
1771  this.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
1773  }
1774 
1775  navigation_poproute(this);
1776  ++removed_goals;
1777  if(this.goalcurrent && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
1778  return removed_goals;
1779  }
1780  return removed_goals;
1781 }
1782 
1784 {
1785  entity wp = this.goalcurrent;
1786  if(!wp)
1787  wp = this.goalcurrent_prev;
1788  if(!wp)
1789  return NULL;
1790  float min_dist = ((this.aistatus & AI_STATUS_RUNNING) ? BOT_BUNNYHOP_WP_DETECTION_RANGE : 50);
1791  if(wp != this.goalcurrent_prev && vdist(wp.origin - this.origin, >, min_dist))
1792  {
1793  wp = this.goalcurrent_prev;
1794  if(!wp)
1795  return NULL;
1796  }
1797  if(wp.classname != "waypoint")
1798  {
1799  wp = wp.nearestwaypoint;
1800  if(!wp)
1801  return NULL;
1802  }
1803  if(vdist(wp.origin - this.origin, >, min_dist))
1804  {
1805  wp = NULL;
1807  {
1808  if(vdist(it.origin - this.origin, <, min_dist))
1809  {
1810  wp = it;
1811  break;
1812  }
1813  });
1814  if(!wp)
1815  return NULL;
1816  }
1817  if(wp.wpflags & WAYPOINTFLAG_TELEPORT)
1818  return NULL;
1819 
1820  set_tracewalk_dest(wp, this.origin, false);
1821  if (!tracewalk(this, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this),
1823  {
1824  return NULL;
1825  }
1826  return wp;
1827 }
1828 
1829 // begin a goal selection session (queries spawnfunc_waypoint network)
1831 {
1832  if(this.aistatus & AI_STATUS_STUCK)
1833  return;
1834 
1836  navigation_bestrating = -1;
1838  navigation_clearroute(this);
1840  navigation_markroutes(this, wp);
1841  this.goalstack31 = wp; // temporarly save the really close waypoint
1842 }
1843 
1844 // ends a goal selection session (updates goal stack to the best goal)
1846 {
1847  if(this.aistatus & AI_STATUS_STUCK)
1848  return;
1849 
1850  entity wp = this.goalstack31; // save to wp as this.goalstack31 is set by navigation_routetogoal
1851  this.goalstack31 = NULL;
1852 
1854  LOG_DEBUG("best goal ", navigation_bestgoal.classname);
1855 
1856  if (wp && this.goalcurrent == wp)
1857  navigation_poproute(this);
1858 
1859  // If the bot got stuck then try to reach the farthest waypoint
1860  if (!this.goalentity)
1861  {
1862  if (autocvar_bot_wander_enable && !(this.aistatus & AI_STATUS_STUCK))
1863  {
1864  LOG_DEBUG(this.netname, " cannot walk to any goal");
1865  this.aistatus |= AI_STATUS_STUCK;
1866  }
1867  this.goalentity_shouldbefrozen = false;
1868  }
1869  else
1870  this.goalentity_shouldbefrozen = boolean(STAT(FROZEN, this.goalentity));
1871 }
1872 
1873 void botframe_updatedangerousobjects(float maxupdate)
1874 {
1875  vector m1, m2, v, o;
1876  float c, d, danger;
1877  c = 0;
1878  entity wp_cur;
1879  IL_EACH(g_waypoints, true,
1880  {
1881  danger = 0;
1882  m1 = it.absmin;
1883  m2 = it.absmax;
1884  wp_cur = it;
1885  IL_EACH(g_bot_dodge, it.bot_dodge,
1886  {
1887  v = it.origin;
1888  v.x = bound(m1_x, v.x, m2_x);
1889  v.y = bound(m1_y, v.y, m2_y);
1890  v.z = bound(m1_z, v.z, m2_z);
1891  o = (it.absmin + it.absmax) * 0.5;
1892  d = waypoint_getlinearcost(it.bot_dodgerating) - waypoint_gettravelcost(o, v, it, wp_cur);
1893  if (d > 0)
1894  {
1895  traceline(o, v, true, NULL);
1896  if (trace_fraction == 1)
1897  danger = danger + d;
1898  }
1899  });
1900  it.dmg = danger;
1901  c = c + 1;
1902  if (c >= maxupdate)
1903  break;
1904  });
1905 }
1906 
1908 {
1910  return;
1911 
1912  bool has_user_waypoints = false;
1913  IL_EACH(g_waypoints, !(it.wpflags & WAYPOINTFLAG_GENERATED),
1914  {
1915  has_user_waypoints = true;
1916  break;
1917  });
1918  if (!has_user_waypoints)
1919  return;
1920 
1921  float search_radius = 1000;
1922 
1924  {
1925  LOG_DEBUG(this.netname, " stuck, taking over the waypoints queue");
1926  bot_waypoint_queue_owner = this;
1929  }
1930 
1931  if(bot_waypoint_queue_owner!=this)
1932  return;
1933 
1935  {
1936  // evaluate the next goal on the queue
1937  float d = vlen2(this.origin - bot_waypoint_queue_goal.origin);
1938  LOG_DEBUG(this.netname, " evaluating ", bot_waypoint_queue_goal.classname, " with squared distance ", ftos(d));
1940  if (tracewalk(this, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this),
1942  {
1944  {
1947  }
1948  }
1949 
1950  // move to a random waypoint while bot is searching for a walkable path;
1951  // this is usually sufficient to unstuck bots from bad spots or when other
1952  // bots of the same team block all their ways
1953  if (!bot_waypoint_queue_bestgoal && (!this.goalentity || random() < 0.1))
1954  {
1955  navigation_clearroute(this);
1958  }
1959 
1960  bot_waypoint_queue_goal = bot_waypoint_queue_goal.bot_waypoint_queue_nextgoal;
1961 
1963  {
1965  {
1966  LOG_DEBUG(this.netname, " stuck, reachable waypoint found, heading to it");
1967  navigation_clearroute(this);
1970  this.aistatus &= ~AI_STATUS_STUCK;
1971  }
1972  else
1973  {
1974  LOG_DEBUG(this.netname, " stuck, cannot walk to any waypoint at all");
1975  }
1976 
1978  }
1979  }
1980  else
1981  {
1982  if(bot_strategytoken!=this)
1983  return;
1984 
1985  // build a new queue
1986  LOG_DEBUG(this.netname, " stuck, scanning reachable waypoints within ", ftos(search_radius)," qu");
1987 
1988  entity first = NULL;
1989 
1990  FOREACH_ENTITY_RADIUS(this.origin, search_radius, it.classname == "waypoint" && !(it.wpflags & WAYPOINTFLAG_GENERATED),
1991  {
1992  if(bot_waypoint_queue_goal)
1993  bot_waypoint_queue_goal.bot_waypoint_queue_nextgoal = it;
1994  else
1995  first = it;
1996 
1997  bot_waypoint_queue_goal = it;
1998  bot_waypoint_queue_goal.bot_waypoint_queue_nextgoal = NULL;
1999  });
2000 
2001  if (first)
2002  bot_waypoint_queue_goal = first;
2003  else
2004  {
2005  LOG_DEBUG(this.netname, " stuck, cannot walk to any waypoint at all");
2007  }
2008  }
2009 }
2010 
2011 // Support for debugging tracewalk visually
2012 
2014 {
2015  debuglastnode = '0 0 0';
2016 }
2017 
2018 void debugnode(entity this, vector node)
2019 {
2020  if (!IS_PLAYER(this))
2021  return;
2022 
2023  if(debuglastnode=='0 0 0')
2024  {
2025  debuglastnode = node;
2026  return;
2027  }
2028 
2029  te_lightning2(NULL, node, debuglastnode);
2030  debuglastnode = node;
2031 }
2032 
2033 void debugnodestatus(vector position, float status)
2034 {
2035  vector c;
2036 
2037  switch (status)
2038  {
2039  case DEBUG_NODE_SUCCESS:
2040  c = '0 15 0';
2041  break;
2042  case DEBUG_NODE_WARNING:
2043  c = '15 15 0';
2044  break;
2045  case DEBUG_NODE_FAIL:
2046  c = '15 0 0';
2047  break;
2048  default:
2049  c = '15 15 15';
2050  }
2051 
2052  te_customflash(position, 40, 2, c);
2053 }
2054 
2055 // Support for debugging the goal stack visually
2056 
2059 
2060 // Debug the goal stack visually
2062 {
2063  entity goal;
2064  vector org, go;
2065 
2066  if(this.goalcounter==0)goal=this.goalcurrent;
2067  else if(this.goalcounter==1)goal=this.goalstack01;
2068  else if(this.goalcounter==2)goal=this.goalstack02;
2069  else if(this.goalcounter==3)goal=this.goalstack03;
2070  else if(this.goalcounter==4)goal=this.goalstack04;
2071  else if(this.goalcounter==5)goal=this.goalstack05;
2072  else if(this.goalcounter==6)goal=this.goalstack06;
2073  else if(this.goalcounter==7)goal=this.goalstack07;
2074  else if(this.goalcounter==8)goal=this.goalstack08;
2075  else if(this.goalcounter==9)goal=this.goalstack09;
2076  else if(this.goalcounter==10)goal=this.goalstack10;
2077  else if(this.goalcounter==11)goal=this.goalstack11;
2078  else if(this.goalcounter==12)goal=this.goalstack12;
2079  else if(this.goalcounter==13)goal=this.goalstack13;
2080  else if(this.goalcounter==14)goal=this.goalstack14;
2081  else if(this.goalcounter==15)goal=this.goalstack15;
2082  else if(this.goalcounter==16)goal=this.goalstack16;
2083  else if(this.goalcounter==17)goal=this.goalstack17;
2084  else if(this.goalcounter==18)goal=this.goalstack18;
2085  else if(this.goalcounter==19)goal=this.goalstack19;
2086  else if(this.goalcounter==20)goal=this.goalstack20;
2087  else if(this.goalcounter==21)goal=this.goalstack21;
2088  else if(this.goalcounter==22)goal=this.goalstack22;
2089  else if(this.goalcounter==23)goal=this.goalstack23;
2090  else if(this.goalcounter==24)goal=this.goalstack24;
2091  else if(this.goalcounter==25)goal=this.goalstack25;
2092  else if(this.goalcounter==26)goal=this.goalstack26;
2093  else if(this.goalcounter==27)goal=this.goalstack27;
2094  else if(this.goalcounter==28)goal=this.goalstack28;
2095  else if(this.goalcounter==29)goal=this.goalstack29;
2096  else if(this.goalcounter==30)goal=this.goalstack30;
2097  else if(this.goalcounter==31)goal=this.goalstack31;
2098  else goal=NULL;
2099 
2100  if(goal==NULL)
2101  {
2102  this.goalcounter = 0;
2103  this.lastposition='0 0 0';
2104  return;
2105  }
2106 
2107  if(this.lastposition=='0 0 0')
2108  org = this.origin;
2109  else
2110  org = this.lastposition;
2111 
2112 
2113  go = ( goal.absmin + goal.absmax ) * 0.5;
2114  te_lightning2(NULL, org, go);
2115  this.lastposition = go;
2116 
2117  this.goalcounter++;
2118 }
float nearestwaypointtimeout
Definition: api.qh:53
#define IL_EACH(this, cond, body)
float ignoregoaltime
Definition: api.qh:98
entity ignoregoal
Definition: api.qh:99
const float CONTENT_LAVA
Definition: csprogsdefs.qc:240
vector dest
Definition: jumppads.qh:41
bool navigation_dynamicgoal
Definition: api.qh:107
const int WATERLEVEL_SUBMERGED
Definition: movetypes.qh:14
vector view_ofs
Definition: progsdefs.qc:151
bool autocvar_bot_debug_tracewalk
Definition: cvars.qh:58
float waypoint_gettravelcost(vector from, vector to, entity from_ent, entity to_ent)
Definition: waypoints.qc:1030
#define IN_LAVA(pos)
Definition: bot.qh:83
const int WAYPOINTFLAG_PROTECTED
Definition: api.qh:16
float autocvar_bot_ai_navigation_jetpack
Definition: cvars.qh:42
float autocvar_bot_ai_strategyinterval
Definition: cvars.qh:68
entity bot_basewaypoint
Definition: api.qh:106
int aistatus
Definition: bot.qh:20
float goalentity_lock_timeout
Definition: api.qh:97
entity() spawn
float autocvar_bot_ai_ignoregoal_timeout
Definition: cvars.qh:39
const float MOVE_NORMAL
Definition: csprogsdefs.qc:252
const int WAYPOINTFLAG_JUMP
Definition: api.qh:20
#define IS_ONGROUND(s)
Definition: movetypes.qh:16
vector maxs
Definition: csprogsdefs.qc:113
const float CONTENT_SOLID
Definition: csprogsdefs.qc:237
float checkpvs(vector viewpos, entity viewee)
string netname
Definition: powerups.qc:20
const int AI_STATUS_WAYPOINT_PERSONAL_GOING
Definition: bot.qh:13
#define IS_MONSTER(v)
Definition: utils.qh:21
const int WPFLAGMASK_NORELINK
Definition: api.qh:29
float jumppadcount
Definition: jumppads.qh:15
#define IS_MOVABLE(v)
Definition: utils.qh:25
origin
Definition: ent_cs.qc:114
float FL_PARTIALGROUND
Definition: progsdefs.qc:241
#define ERASEABLE
Definition: _all.inc:35
IntrusiveList g_bot_dodge
Definition: api.qh:150
entity trace_ent
Definition: csprogsdefs.qc:40
IntrusiveList g_jumppads
Definition: jumppads.qh:7
int autocvar_g_waypointeditor_auto
Definition: cvars.qh:63
float bot_tracewalk_time
Definition: api.qh:37
vector absmax
Definition: csprogsdefs.qc:92
bool waypointeditor_enabled
Definition: waypoints.qh:3
const float MOVE_NOMONSTERS
Definition: csprogsdefs.qc:253
vector mins
Definition: csprogsdefs.qc:113
#define vlen2(v)
Definition: vector.qh:4
const float CONTENT_SLIME
Definition: csprogsdefs.qc:239
const int WAYPOINTFLAG_TELEPORT
Definition: api.qh:13
ERASEABLE float boxesoverlap(vector m1, vector m2, vector m3, vector m4)
requires that m2>m1 in all coordinates, and that m4>m3
Definition: vector.qh:73
const float CONTENT_WATER
Definition: csprogsdefs.qc:238
float autocvar_bot_ai_strategyinterval_movingtarget
Definition: cvars.qh:69
entity goalentity
Definition: progsdefs.qc:189
entity nearestwaypoint
Definition: api.qh:54
#define NULL
Definition: post.qh:17
bool autocvar_bot_wander_enable
Definition: cvars.qh:60
float lastteleporttime
Definition: api.qh:50
vector trace_endpos
Definition: csprogsdefs.qc:37
const int AI_STATUS_WAYPOINT_PERSONAL_REACHED
Definition: bot.qh:14
#define WETFEET(pos)
Definition: bot.qh:86
const int WATERLEVEL_WETFEET
Definition: movetypes.qh:12
float waypoint_getlinearcost(float dist)
Definition: waypoints.qc:1012
IntrusiveList g_waypoints
Definition: api.qh:148
entity ladder_entity
Definition: ladder.qh:11
vector(float skel, float bonenum) _skel_get_boneabs_hidden
next
Definition: all.qh:88
#define SUBMERGED(pos)
Definition: bot.qh:85
vector v
Definition: ent_cs.qc:116
float GetResource(entity e, Resource res_type)
Returns the current amount of resource the given entity has.
Definition: cl_resources.qc:10
const float SOLID_BSP
Definition: csprogsdefs.qc:248
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition: vector.qh:8
const vector eZ
Definition: vector.qh:46
float items
Definition: progsdefs.qc:145
float bot_strategytime
Definition: bot.qh:69
#define IS_BOT_CLIENT(v)
want: (IS_CLIENT(v) && !IS_REAL_CLIENT(v))
Definition: utils.qh:15
#define vec2(...)
Definition: vector.qh:90
bool trigger_push_test(entity this, entity item)
if (item != NULL) returns true if the item can be reached by using this jumppad, false otherwise if (...
Definition: jumppads.qc:387
const int WAYPOINTFLAG_GENERATED
Definition: api.qh:11
const int WAYPOINTFLAG_PERSONAL
Definition: api.qh:15
float autocvar_bot_ai_navigation_jetpack_mindistance
Definition: cvars.qh:43
float trace_startsolid
Definition: csprogsdefs.qc:35
best
Definition: all.qh:77
const int AI_STATUS_STUCK
Definition: bot.qh:17
entity bot_strategytoken
Definition: bot.qh:76
vector absmin
Definition: csprogsdefs.qc:92
if(IS_DEAD(this))
Definition: impulse.qc:92
float time
Definition: csprogsdefs.qc:16
vector velocity
Definition: csprogsdefs.qc:103
float trace_fraction
Definition: csprogsdefs.qc:36
#define boolean(value)
Definition: bool.qh:9
const int AI_STATUS_RUNNING
Definition: bot.qh:8
#define IS_PLAYER(v)
Definition: utils.qh:9
const float FLOAT_MAX
Definition: float.qh:3
bool havocbot_goalrating_item_pickable_check_players(entity this, vector org, entity item, vector item_org)
Definition: roles.qc:60
#define LOG_DEBUG(...)
Definition: log.qh:85