#include "navigation.qh"
+#include <server/defs.qh>
+#include <server/miscfunctions.qh>
#include "cvars.qh"
#include "bot.qh"
.float speed;
+void navigation_goalrating_timeout_set(entity this)
+{
+ if(IS_MOVABLE(this.goalentity))
+ this.bot_strategytime = time + autocvar_bot_ai_strategyinterval_movingtarget;
+ else
+ this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+}
+
+void navigation_goalrating_timeout_force(entity this)
+{
+ this.bot_strategytime = 0;
+}
+
+bool navigation_goalrating_timeout(entity this)
+{
+ return this.bot_strategytime < time;
+}
+
void navigation_dynamicgoal_init(entity this, bool initially_static)
{
this.navigation_dynamicgoal = true;
{
tracebox(org, m1, m2, org - jumpheight_vec, movemode, e);
if (SUBMERGED(trace_endpos))
- RESURFACE_LIMITED(trace_endpos, end.z);
+ {
+ vector v = trace_endpos;
+ tracebox(v, m1, m2, end, movemode, e);
+ if(trace_endpos.z >= end.z - 1)
+ {
+ RESURFACE_LIMITED(v, trace_endpos.z);
+ trace_endpos = v;
+ }
+ }
else if (trace_endpos.z > org.z - jumpheight_vec.z)
tracebox(trace_endpos, m1, m2, trace_endpos + jumpheight_vec, movemode, e);
org = trace_endpos;
// walking to wp (walkfromwp == false) v2 and v2_height will be used as
// waypoint destination coordinates instead of v (only useful for box waypoints)
// for normal waypoints v2 == v and v2_height == 0
-float navigation_waypoint_will_link(vector v, vector org, entity ent, vector v2, float v2_height, float walkfromwp, float bestdist)
+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)
{
if (vdist(v - org, <, bestdist))
{
{
if (walkfromwp)
{
- if (tracewalk(ent, v, PL_MIN_CONST, PL_MAX_CONST, org, 0, bot_navigation_movemode))
+ if (tracewalk(ent, v, PL_MIN_CONST, PL_MAX_CONST, v2, v2_height, bot_navigation_movemode))
return true;
}
else
{
- if (tracewalk(ent, org, PL_MIN_CONST, PL_MAX_CONST, v2, v2_height, bot_navigation_movemode))
+ if (tracewalk(ent, org, PL_MIN_CONST, PL_MAX_CONST, o2, o2_height, bot_navigation_movemode))
return true;
}
}
// find the spawnfunc_waypoint near a dynamic goal such as a dropped weapon
entity navigation_findnearestwaypoint_withdist_except(entity ent, float walkfromwp, float bestdist, entity except)
{
+ if(ent.tag_entity)
+ ent = ent.tag_entity;
+
vector pm1 = ent.origin + ent.mins;
vector pm2 = ent.origin + ent.maxs;
IL_EACH(g_waypoints, it != ent && it != except,
{
if(boxesoverlap(pm1, pm2, it.absmin, it.absmax))
+ {
+ if(!autocvar_g_waypointeditor && walkfromwp && !ent.navigation_dynamicgoal)
+ {
+ waypoint_clearlinks(ent); // initialize wpXXmincost fields
+ navigation_item_addlink(it, ent);
+ }
return it;
+ }
});
- vector org = ent.origin + 0.5 * (ent.mins + ent.maxs);
- org.z = ent.origin.z + ent.mins.z - PL_MIN_CONST.z; // player height
- // TODO possibly make other code have the same support for bboxes
- if(ent.tag_entity)
- org = org + ent.tag_entity.origin;
+ vector org = ent.origin;
if (navigation_testtracewalk)
te_plasmaburn(org);
entity best = NULL;
- vector v, v2;
- float v2_height;
+ vector v = '0 0 0', v2 = '0 0 0';
+ float v2_height = 0;
- if(!autocvar_g_waypointeditor && !ent.navigation_dynamicgoal)
+ if(ent.size && !IS_PLAYER(ent))
+ {
+ org += 0.5 * (ent.mins + ent.maxs);
+ org.z = ent.origin.z + ent.mins.z - PL_MIN_CONST.z; // player height
+ }
+
+ if(!autocvar_g_waypointeditor && walkfromwp && !ent.navigation_dynamicgoal)
{
waypoint_clearlinks(ent); // initialize wpXXmincost fields
IL_EACH(g_waypoints, it != ent,
if(walkfromwp && (it.wpflags & WAYPOINTFLAG_NORELINK))
continue;
- SET_TRACEWALK_DESTCOORDS_2(it, org, v, v2, v2_height);
- if(navigation_waypoint_will_link(v, org, ent, v2, v2_height, walkfromwp, 1050))
+ SET_TRACEWALK_DESTCOORDS(it, org, v2, v2_height);
+ if(vdist(v2 - org, <, 1050))
+ if(tracewalk(ent, org, PL_MIN_CONST, PL_MAX_CONST, v2, v2_height, bot_navigation_movemode))
navigation_item_addlink(it, ent);
});
}
{
if(walkfromwp && (it.wpflags & WAYPOINTFLAG_NORELINK))
continue;
- SET_TRACEWALK_DESTCOORDS_2(it, org, v, v2, v2_height);
- if(navigation_waypoint_will_link(v, org, ent, v2, v2_height, walkfromwp, bestdist))
+ v = it.origin;
+ if(walkfromwp)
+ SET_TRACEWALK_DESTCOORDS(ent, v, v2, v2_height);
+ else
+ SET_TRACEWALK_DESTCOORDS(it, org, v2, v2_height);
+ if(navigation_waypoint_will_link(v, org, ent, v2, v2_height, v2, v2_height, walkfromwp, bestdist))
{
bestdist = vlen(v - org);
best = it;
// finds the waypoints near the bot initiating a navigation query
float navigation_markroutes_nearestwaypoints(entity this, float maxdist)
{
- vector v;
+ vector v = '0 0 0';
//navigation_testtracewalk = true;
int c = 0;
- float v_height;
+ float v_height = 0;
IL_EACH(g_waypoints, !it.wpconsidered,
{
SET_TRACEWALK_DESTCOORDS(it, this.origin, v, v_height);
if ((!e.nearestwaypoint || e.navigation_dynamicgoal)
&& e.nearestwaypointtimeout >= 0 && time > e.nearestwaypointtimeout)
{
- nwp = navigation_findnearestwaypoint(e, true);
- if(nwp)
- {
- e.nearestwaypoint = nwp;
-
- vector m1 = nwp.absmin, m2 = nwp.absmax;
- m1.x = nwp.origin.x; m1.y = nwp.origin.y;
- m2.x = nwp.origin.x; m2.y = nwp.origin.y;
- vector ve = (e.absmin - e.absmax) * 0.5;
- ve.x = bound(m1.x, ve.x, m2.x);
- ve.y = bound(m1.y, ve.y, m2.y);
- ve.z = bound(m1.z, ve.z, m2.z);
-
- m1 = e.absmin; m2 = e.absmax;
- m1.x = e.origin.x; m1.y = e.origin.y;
- m2.x = e.origin.x; m2.y = e.origin.y;
- vector vnwp = nwp.origin;
- vnwp.x = bound(m1.x, vnwp.x, m2.x);
- vnwp.y = bound(m1.y, vnwp.y, m2.y);
- vnwp.z = bound(m1.z, vnwp.z, m2.z);
- e.nearestwaypoint_dist = vlen(ve - vnwp);
- }
- else
+ e.nearestwaypoint = nwp = navigation_findnearestwaypoint(e, true);
+ if(!nwp)
{
LOG_DEBUG("FAILED to find a nearest waypoint to '", e.classname, "' #", etos(e));
if (!e)
return false;
+ entity teleport_goal = NULL;
+
+ this.goalentity = e;
+
if(e.wpflags & WAYPOINTFLAG_TELEPORT)
{
// force teleport destination as route destination
- e.wp00.enemy = e;
- e = e.wp00;
+ teleport_goal = e;
+ navigation_pushroute(this, e.wp00);
+ this.goalentity = e.wp00;
}
- this.goalentity = e;
-
// put the entity on the goal stack
//print("routetogoal ", etos(e), "\n");
navigation_pushroute(this, e);
+ if(teleport_goal)
+ e = this.goalentity;
+
if(e.classname == "waypoint" && !(e.wpflags & WAYPOINTFLAG_PERSONAL))
{
this.wp_goal_prev1 = this.wp_goal_prev0;
return true;
// if it can reach the goal there is nothing more to do
- vector dest = (e.absmin + e.absmax) * 0.5;
- dest.z = e.absmin.z;
- float dest_height = e.absmax.z - e.absmin.z;
+ vector dest = '0 0 0';
+ float dest_height = 0;
+ SET_TRACEWALK_DESTCOORDS(e, startposition, dest, dest_height);
if (tracewalk(this, startposition, STAT(PL_MIN, this), STAT(PL_MAX, this), dest, dest_height, bot_navigation_movemode))
return true;
e = e.nearestwaypoint;
nearest_wp = e;
}
+ else if(teleport_goal)
+ e = teleport_goal;
else
e = e.enemy; // we already have added it, so...
if(nearest_wp && nearest_wp.enemy)
{
// often path can be optimized by not adding the nearest waypoint
- if (this.goalentity.nearestwaypoint_dist < 8)
- e = nearest_wp.enemy;
- else
+ if (this.goalentity.navigation_dynamicgoal || autocvar_g_waypointeditor)
{
- if (this.goalentity.navigation_dynamicgoal)
- {
- vector dest = (this.goalentity.absmin + this.goalentity.absmax) * 0.5;
- dest.z = this.goalentity.absmin.z;
- float dest_height = this.goalentity.absmax.z - this.goalentity.absmin.z;
- if(tracewalk(this, nearest_wp.enemy.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), dest, dest_height, bot_navigation_movemode))
- e = nearest_wp.enemy;
- }
- else if(navigation_item_islinked(nearest_wp.enemy, this.goalentity))
+ SET_TRACEWALK_DESTCOORDS(this.goalentity, nearest_wp.enemy.origin, dest, dest_height);
+ if(vdist(dest - nearest_wp.enemy.origin, <, 1050))
+ if(tracewalk(this, nearest_wp.enemy.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), dest, dest_height, bot_navigation_movemode))
e = nearest_wp.enemy;
}
+ else if(navigation_item_islinked(nearest_wp.enemy, this.goalentity))
+ e = nearest_wp.enemy;
}
for (;;)
// removes any currently touching waypoints from the goal stack
// (this is how bots detect if they reached a goal)
-void navigation_poptouchedgoals(entity this)
+int navigation_poptouchedgoals(entity this)
{
+ int removed_goals = 0;
+
+ if(IS_PLAYER(this.goalcurrent) && IS_DEAD(this.goalcurrent) && checkpvs(this.origin + this.view_ofs, this.goalcurrent))
+ {
+ navigation_poproute(this);
+ ++removed_goals;
+ }
+
if(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
{
// make sure jumppad is really hit, don't rely on distance based checks
this.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED;
}
navigation_poproute(this);
+ this.lastteleporttime = 0;
+ ++removed_goals;
}
else
- return;
+ return removed_goals;
}
// If for some reason the bot is closer to the next goal, pop the current one
if(this.goalstack01 && !wasfreed(this.goalstack01))
- if(vlen2(this.goalcurrent.origin - this.origin) > vlen2(this.goalstack01.origin - this.origin))
+ if(vlen2(this.goalcurrent.origin - this.origin) > vlen2(this.goalstack01.origin - this.origin)
+ || vlen2(this.goalcurrent.origin - this.goalstack01.origin) > vlen2(this.goalstack01.origin - this.origin))
if(checkpvs(this.origin + this.view_ofs, this.goalstack01))
{
- vector dest = (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5;
- dest.z = this.goalstack01.absmin.z;
- float dest_height = this.goalstack01.absmax.z - this.goalstack01.absmin.z;
+ vector dest = '0 0 0';
+ float dest_height = 0;
+ SET_TRACEWALK_DESTCOORDS(this.goalstack01, this.origin, dest, dest_height);
if(tracewalk(this, this.origin, this.mins, this.maxs, dest, dest_height, bot_navigation_movemode))
{
LOG_DEBUG("path optimized for ", this.netname, ", removed a goal from the queue");
navigation_poproute(this);
+ ++removed_goals;
+ if(this.goalcurrent && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
+ return removed_goals;
// TODO this may also be a nice idea to do "early" (e.g. by
// manipulating the vlen() comparisons) to shorten paths in
// general - this would make bots walk more "on rails" than
}
navigation_poproute(this);
+ ++removed_goals;
+ if(this.goalcurrent && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
+ return removed_goals;
}
}
}
}
navigation_poproute(this);
+ ++removed_goals;
+ if(this.goalcurrent && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
+ return removed_goals;
+ }
+ return removed_goals;
+}
+
+entity navigation_get_really_close_waypoint(entity this)
+{
+ entity wp = this.goalcurrent;
+ if(!wp || vdist(wp.origin - this.origin, >, 50))
+ wp = this.goalcurrent_prev;
+ if(!wp)
+ return NULL;
+ if(wp.classname != "waypoint")
+ {
+ wp = wp.nearestwaypoint;
+ if(!wp)
+ return NULL;
}
+ if(vdist(wp.origin - this.origin, >, 50))
+ {
+ IL_EACH(g_waypoints, !(it.wpflags & WAYPOINTFLAG_TELEPORT),
+ {
+ if(vdist(it.origin - this.origin, <, 50))
+ {
+ wp = it;
+ break;
+ }
+ });
+ }
+ if(wp.wpflags & WAYPOINTFLAG_TELEPORT)
+ return NULL;
+
+ vector dest = '0 0 0';
+ float dest_height = 0;
+ SET_TRACEWALK_DESTCOORDS(wp, this.origin, dest, dest_height);
+ if (!tracewalk(this, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), dest, dest_height, bot_navigation_movemode))
+ return NULL;
+ return wp;
}
// begin a goal selection session (queries spawnfunc_waypoint network)
this.navigation_jetpack_goal = NULL;
navigation_bestrating = -1;
+ entity wp = navigation_get_really_close_waypoint(this);
navigation_clearroute(this);
navigation_bestgoal = NULL;
- navigation_markroutes(this, NULL);
+ navigation_markroutes(this, wp);
}
// ends a goal selection session (updates goal stack to the best goal)
// evaluate the next goal on the queue
float d = vlen2(this.origin - bot_waypoint_queue_goal.origin);
LOG_DEBUG(this.netname, " evaluating ", bot_waypoint_queue_goal.classname, " with distance ", ftos(d));
- vector dest = (bot_waypoint_queue_goal.absmin + bot_waypoint_queue_goal.absmax) * 0.5;
- dest.z = bot_waypoint_queue_goal.absmin.z;
- float dest_height = bot_waypoint_queue_goal.absmax.z - bot_waypoint_queue_goal.absmin.z;
+ vector dest = '0 0 0';
+ float dest_height = 0;
+ SET_TRACEWALK_DESTCOORDS(bot_waypoint_queue_goal, this.origin, dest, dest_height);
if(tracewalk(bot_waypoint_queue_goal, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), dest, dest_height, bot_navigation_movemode))
{
if( d > bot_waypoint_queue_bestgoalrating)
{
LOG_DEBUG(this.netname, " stuck, reachable waypoint found, heading to it");
navigation_routetogoal(this, bot_waypoint_queue_bestgoal, this.origin);
- this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+ navigation_goalrating_timeout_set(this);
this.aistatus &= ~AI_STATUS_STUCK;
}
else