X-Git-Url: http://de.git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fserver%2Fbot%2Fdefault%2Fnavigation.qc;h=59bc8dba8350478f0cc5fae8dc9536a9d087dce7;hb=1f178e9e99364ebbb3398d9de8b5a13aca01e2f8;hp=9a6549331b2db98dde8e2bc0079b530b303c40d2;hpb=711a34533d7db697e8fa7987c5937dfee7ae6b4a;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/server/bot/default/navigation.qc b/qcsrc/server/bot/default/navigation.qc index 9a6549331..c9ebc0755 100644 --- a/qcsrc/server/bot/default/navigation.qc +++ b/qcsrc/server/bot/default/navigation.qc @@ -1,5 +1,7 @@ #include "navigation.qh" +#include +#include #include "cvars.qh" #include "bot.qh" @@ -15,6 +17,53 @@ .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; +} + +// use this when current goal must be discarded immediately +void navigation_goalrating_timeout_force(entity this) +{ + navigation_goalrating_timeout_expire(this, 0); +} + +// use this when current goal can be kept for a short while to increase the chance +// of bot touching a waypoint, which helps to find a new goal more efficiently +void navigation_goalrating_timeout_expire(entity this, float seconds) +{ + if (seconds <= 0) + this.bot_strategytime = 0; + else if (this.bot_strategytime > time + seconds) + this.bot_strategytime = time + seconds; +} + +bool navigation_goalrating_timeout(entity this) +{ + return this.bot_strategytime < time; +} + +bool navigation_goalrating_timeout_can_be_anticipated(entity this) +{ + if(time > this.bot_strategytime - (IS_MOVABLE(this.goalentity) ? 3 : 2)) + return true; + + if (this.goalentity.bot_pickup && time > this.bot_strategytime - 5) + { + vector gco = (this.goalentity.absmin + this.goalentity.absmax) * 0.5; + if(!havocbot_goalrating_item_pickable_check_players(this, this.origin, this.goalentity, gco)) + { + this.ignoregoal = this.goalentity; + this.ignoregoaltime = time + autocvar_bot_ai_ignoregoal_timeout; + return true; + } + } + return false; +} + void navigation_dynamicgoal_init(entity this, bool initially_static) { this.navigation_dynamicgoal = true; @@ -28,6 +77,8 @@ void navigation_dynamicgoal_init(entity this, bool initially_static) void navigation_dynamicgoal_set(entity this) { this.nearestwaypointtimeout = time; + if (this.nearestwaypoint) + this.nearestwaypointtimeout += 2; } void navigation_dynamicgoal_unset(entity this) @@ -37,48 +88,185 @@ void navigation_dynamicgoal_unset(entity this) this.nearestwaypointtimeout = -1; } -// rough simulation of walking from one point to another to test if a path -// can be traveled, used for waypoint linking and havocbot +// returns point of ent closer to org +vector get_closer_dest(entity ent, vector org) +{ + vector dest = '0 0 0'; + if ((ent.classname != "waypoint") || ent.wpisbox) + { + vector wm1 = ent.origin + ent.mins - eZ * (PL_MAX_CONST.z - 1); + vector wm2 = ent.origin + ent.maxs - eZ * (PL_MIN_CONST.z + 1); + dest.x = bound(wm1.x, org.x, wm2.x); + dest.y = bound(wm1.y, org.y, wm2.y); + dest.z = bound(wm1.z, org.z, wm2.z); + } + else + dest = ent.origin; + return dest; +} -bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float movemode) +void set_tracewalk_dest(entity ent, vector org, bool fix_player_dest) { - vector org; - vector move; - vector dir; - float dist; - float totaldist; - float stepdist; - float ignorehazards; - float swimming; + if ((ent.classname != "waypoint") || ent.wpisbox) + { + vector wm1 = ent.origin + ent.mins - eZ * (PL_MAX_CONST.z - 1); + vector wm2 = ent.origin + ent.maxs - eZ * (PL_MIN_CONST.z + 1); + if (IS_PLAYER(ent) || IS_MONSTER(ent)) + { + // move destination point out of player bbox otherwise tracebox always fails + // (if bot_navigation_ignoreplayers is false) + wm1 += vec2(PL_MIN_CONST) + '-1 -1 0'; + wm2 += vec2(PL_MAX_CONST) + '1 1 0'; + } + // set destination point to x and y coords of ent that are closer to org + // z coord is set to ent's min height + tracewalk_dest.x = bound(wm1.x, org.x, wm2.x); + tracewalk_dest.y = bound(wm1.y, org.y, wm2.y); + tracewalk_dest.z = wm1.z; + tracewalk_dest_height = wm2.z - wm1.z; // destination height + } + else + { + tracewalk_dest = ent.origin; + tracewalk_dest_height = 0; + } + if (fix_player_dest && IS_PLAYER(ent) && !IS_ONGROUND(ent)) + { + // snap player to the ground + if (org.x == tracewalk_dest.x && org.y == tracewalk_dest.y) + { + // bot is right under the player + tracebox(ent.origin, ent.mins, ent.maxs, ent.origin - '0 0 700', MOVE_NORMAL, ent); + tracewalk_dest = trace_endpos; + tracewalk_dest_height = 0; + } + else + { + tracebox(tracewalk_dest, ent.mins, ent.maxs, tracewalk_dest - '0 0 700', MOVE_NORMAL, ent); + if (!trace_startsolid && tracewalk_dest.z - trace_endpos.z > 0) + { + tracewalk_dest_height = tracewalk_dest.z - trace_endpos.z; + tracewalk_dest.z = trace_endpos.z; + } + } + } +} + +// returns point of ent closer to org +vector set_tracewalk_dest_2(entity ent, vector org) +{ + vector closer_dest = '0 0 0'; + if ((ent.classname != "waypoint") || ent.wpisbox) + { + vector wm1 = ent.origin + ent.mins - eZ * (PL_MAX_CONST.z - 1); + vector wm2 = ent.origin + ent.maxs - eZ * (PL_MIN_CONST.z + 1); + closer_dest.x = bound(wm1.x, org.x, wm2.x); + closer_dest.y = bound(wm1.y, org.y, wm2.y); + closer_dest.z = bound(wm1.z, org.z, wm2.z); + // set destination point to x and y coords of ent that are closer to org + // z coord is set to ent's min height + tracewalk_dest.x = closer_dest.x; + tracewalk_dest.y = closer_dest.y; + tracewalk_dest.z = wm1.z; + tracewalk_dest_height = wm2.z - wm1.z; // destination height + } + else + { + closer_dest = ent.origin; + tracewalk_dest = closer_dest; + tracewalk_dest_height = 0; + } + return closer_dest; +} + +bool navigation_check_submerged_state(entity ent, vector pos) +{ + bool submerged; + if(IS_PLAYER(ent)) + submerged = (ent.waterlevel == WATERLEVEL_SUBMERGED); + else if(ent.nav_submerged_state != SUBMERGED_UNDEFINED) + submerged = (ent.nav_submerged_state == SUBMERGED_YES); + else + { + submerged = SUBMERGED(pos); + // NOTE: SUBMERGED check of box waypoint origin may fail even if origin + // is actually submerged because often they are inside some solid. + // That's why submerged state is saved now that we know current pos is + // not stuck in solid (previous tracewalk call to this pos was successfully) + if(!ent.navigation_dynamicgoal) + ent.nav_submerged_state = (submerged) ? SUBMERGED_YES : SUBMERGED_NO; + } + return submerged; +} +bool navigation_checkladders(entity e, vector org, vector m1, vector m2, vector end, vector end2, int movemode) +{ + IL_EACH(g_ladders, it.classname == "func_ladder", + { + if(it.bot_pickup) + if(boxesoverlap(org + m1 + '-1 -1 -1', org + m2 + '1 1 1', it.absmin, it.absmax)) + if(boxesoverlap(end, end2, it.absmin + vec2(m1) + '-1 -1 0', it.absmax + vec2(m2) + '1 1 0')) + { + vector top = org; + top.z = it.absmax.z + (PL_MAX_CONST.z - PL_MIN_CONST.z); + tracebox(org, m1, m2, top, movemode, e); + if(trace_fraction == 1) + return true; + } + }); + return false; +} + +vector resurface_limited(vector org, float lim, vector m1) +{ + if (WETFEET(org + eZ * (lim - org.z))) + org.z = lim; + else + { + float RES_min_h = org.z; + float RES_max_h = lim; + do { + org.z = 0.5 * (RES_min_h + RES_max_h); + if(WETFEET(org)) + RES_min_h = org.z; + else + RES_max_h = org.z; + } while (RES_max_h - RES_min_h >= 1); + org.z = RES_min_h; + } + return org; +} +#define RESURFACE_LIMITED(org, lim) org = resurface_limited(org, lim, m1) + +#define NAV_WALK 0 +#define NAV_SWIM_ONWATER 1 +#define NAV_SWIM_UNDERWATER 2 + +// rough simulation of walking from one point to another to test if a path +// can be traveled, used for waypoint linking and havocbot +// if end_height is > 0 destination is any point in the vertical segment [end, end + end_height * eZ] +bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float end_height, float movemode) +{ if(autocvar_bot_debug_tracewalk) { debugresetnodes(); debugnode(e, start); } - move = end - start; - move.z = 0; - org = start; - dist = totaldist = vlen(move); - dir = normalize(move); - stepdist = 32; - ignorehazards = false; - swimming = false; + vector org = start; + vector flatdir = end - start; + flatdir.z = 0; + float flatdist = vlen(flatdir); + flatdir = normalize(flatdir); + float stepdist = 32; + bool ignorehazards = false; + int nav_action; // Analyze starting point traceline(start, start, MOVE_NORMAL, e); if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA)) ignorehazards = true; - else - { - traceline( start, start + '0 0 -65536', MOVE_NORMAL, e); - if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA)) - { - ignorehazards = true; - swimming = true; - } - } + tracebox(start, m1, m2, start, MOVE_NOMONSTERS, e); if (trace_startsolid) { @@ -90,78 +278,309 @@ bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float m return false; } + vector end2 = end; + if(end_height) + end2.z += end_height; + + vector fixed_end = end; + vector move; + + if (flatdist > 0 && WETFEET(org)) + { + if (SUBMERGED(org)) + nav_action = NAV_SWIM_UNDERWATER; + else + { + // tracebox down by player's height + // useful to know if water level is so low that bot can still walk + tracebox(org, m1, m2, org - eZ * (m2.z - m1.z), movemode, e); + if (SUBMERGED(trace_endpos)) + { + org = trace_endpos; + nav_action = NAV_SWIM_UNDERWATER; + } + else + nav_action = NAV_WALK; + } + } + else + nav_action = NAV_WALK; + // Movement loop - move = end - org; - for (;;) + while (true) { - if (boxesoverlap(end, end, org + m1 + '-1 -1 -1', org + m2 + '1 1 1')) + if (flatdist <= 0) { - // Succeeded - if(autocvar_bot_debug_tracewalk) - debugnodestatus(org, DEBUG_NODE_SUCCESS); + bool success = true; + if (org.z > end2.z + 1) + { + tracebox(org, m1, m2, end2, movemode, e); + org = trace_endpos; + if (org.z > end2.z + 1) + success = false; + } + else if (org.z < end.z - 1) + { + tracebox(org, m1, m2, org - jumpheight_vec, movemode, e); + if (SUBMERGED(trace_endpos)) + { + 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; + if (org.z < end.z - 1) + success = false; + } - //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n"); - return true; + if (success) + { + // Succeeded + if(autocvar_bot_debug_tracewalk) + { + debugnode(e, org); + debugnodestatus(org, DEBUG_NODE_SUCCESS); + } + + //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n"); + return true; + } } + if(autocvar_bot_debug_tracewalk) debugnode(e, org); - if (dist <= 0) + if (flatdist <= 0) break; - if (stepdist > dist) - stepdist = dist; - dist = dist - stepdist; - traceline(org, org, MOVE_NORMAL, e); - if (!ignorehazards) - { - if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA)) - { - // hazards blocking path - if(autocvar_bot_debug_tracewalk) - debugnodestatus(org, DEBUG_NODE_FAIL); - //print("tracewalk: ", vtos(start), " hits a hazard when trying to reach ", vtos(end), "\n"); - return false; + if (stepdist > flatdist) + stepdist = flatdist; + if(nav_action == NAV_SWIM_UNDERWATER || (nav_action == NAV_SWIM_ONWATER && org.z > end2.z)) + { + // can't use movement direction here to calculate move because of + // precision errors especially when direction has a high enough z value + //water_dir = normalize(water_end - org); + //move = org + water_dir * stepdist; + fixed_end.z = bound(end.z, org.z, end2.z); + if (stepdist == flatdist) { + move = fixed_end; + flatdist = 0; + } else { + move = org + (fixed_end - org) * (stepdist / flatdist); + flatdist = vlen(vec2(fixed_end - move)); } } - if (trace_dpstartcontents & DPCONTENTS_LIQUIDSMASK) + else // horiz. direction { - move = normalize(end - org); - tracebox(org, m1, m2, org + move * stepdist, movemode, e); + flatdist -= stepdist; + move = org + flatdir * stepdist; + } - if(autocvar_bot_debug_tracewalk) - debugnode(e, trace_endpos); + if(nav_action == NAV_SWIM_ONWATER) + { + tracebox(org, m1, m2, move, movemode, e); // swim + // hit something if (trace_fraction < 1) { - swimming = true; - org = trace_endpos + normalize(org - trace_endpos) * stepdist; - for (; org.z < end.z + e.maxs.z; org.z += stepdist) + // stepswim + tracebox(org + stepheightvec, m1, m2, move + stepheightvec, movemode, e); + + if (trace_fraction < 1 || trace_startsolid) // can't jump obstacle out of water { - if(autocvar_bot_debug_tracewalk) - debugnode(e, org); + org = trace_endpos; + if(navigation_checkladders(e, org, m1, m2, end, end2, movemode)) + { + if(autocvar_bot_debug_tracewalk) + { + debugnode(e, org); + debugnodestatus(org, DEBUG_NODE_SUCCESS); + } - if(pointcontents(org) == CONTENT_EMPTY) - break; - } + //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n"); + return true; + } - if(pointcontents(org + '0 0 1') != CONTENT_EMPTY) - { if(autocvar_bot_debug_tracewalk) debugnodestatus(org, DEBUG_NODE_FAIL); return false; - //print("tracewalk: ", vtos(start), " failed under water\n"); + //print("tracewalk: ", vtos(start), " hit something when trying to reach ", vtos(end), "\n"); + } + + //succesful stepswim + + if (flatdist <= 0) + { + org = trace_endpos; + continue; } - continue; + if (org.z <= move.z) // going horiz. + { + tracebox(trace_endpos, m1, m2, move, movemode, e); + org = trace_endpos; + nav_action = NAV_WALK; + continue; + } } - else + + if (org.z <= move.z) // going horiz. + { org = trace_endpos; + nav_action = NAV_SWIM_ONWATER; + } + else // going down + { + org = trace_endpos; + if (SUBMERGED(org)) + nav_action = NAV_SWIM_UNDERWATER; + else + nav_action = NAV_SWIM_ONWATER; + } } - else + else if(nav_action == NAV_SWIM_UNDERWATER) { - move = dir * stepdist + org; + if (move.z >= org.z) // swimming upwards or horiz. + { + tracebox(org, m1, m2, move, movemode, e); // swim + + bool stepswum = false; + + // hit something + if (trace_fraction < 1) + { + // stepswim + vector stepswim_move = move + stepheightvec; + if (flatdist > 0 && stepswim_move.z > end2.z + stepheightvec.z) // don't allow stepswim to go higher than destination + stepswim_move.z = end2.z; + + tracebox(org + stepheightvec, m1, m2, stepswim_move, movemode, e); + + // hit something + if (trace_startsolid) + { + if(autocvar_bot_debug_tracewalk) + debugnodestatus(org, DEBUG_NODE_FAIL); + + //print("tracewalk: ", vtos(start), " hit something when trying to reach ", vtos(end), "\n"); + return false; + } + + if (trace_fraction < 1) + { + float org_z_prev = org.z; + RESURFACE_LIMITED(org, end2.z); + if(org.z == org_z_prev) + { + if(autocvar_bot_debug_tracewalk) + debugnodestatus(org, DEBUG_NODE_FAIL); + + //print("tracewalk: ", vtos(start), " can't reach ", vtos(end), "\n"); + return false; + } + if(SUBMERGED(org)) + nav_action = NAV_SWIM_UNDERWATER; + else + nav_action = NAV_SWIM_ONWATER; + + // we didn't advance horiz. in this step, flatdist decrease should be reverted + // but we can't do it properly right now... apply this workaround instead + if (flatdist <= 0) + flatdist = 1; + + continue; + } + + //succesful stepswim + + if (flatdist <= 0) + { + org = trace_endpos; + continue; + } + + stepswum = true; + } + + if (!WETFEET(trace_endpos)) + { + tracebox(trace_endpos, m1, m2, trace_endpos - eZ * (stepdist + (m2.z - m1.z)), movemode, e); + // if stepswum we'll land on the obstacle, avoid the SUBMERGED check + if (!stepswum && SUBMERGED(trace_endpos)) + { + RESURFACE_LIMITED(trace_endpos, end2.z); + org = trace_endpos; + nav_action = NAV_SWIM_ONWATER; + continue; + } + + // not submerged + org = trace_endpos; + nav_action = NAV_WALK; + continue; + } + + // wetfeet + org = trace_endpos; + nav_action = NAV_SWIM_UNDERWATER; + continue; + } + else //if (move.z < org.z) // swimming downwards + { + tracebox(org, m1, m2, move, movemode, e); // swim + + // hit something + if (trace_fraction < 1) + { + // stepswim + tracebox(org + stepheightvec, m1, m2, move + stepheightvec, movemode, e); + + // hit something + if (trace_fraction < 1 || trace_startsolid) // can't jump obstacle out of water + { + if(autocvar_bot_debug_tracewalk) + debugnodestatus(move, DEBUG_NODE_FAIL); + + //print("tracewalk: ", vtos(start), " hit something when trying to reach ", vtos(end), "\n"); + return false; + } + + //succesful stepswim + + if (flatdist <= 0) + { + org = trace_endpos; + continue; + } + + if (trace_endpos.z > org.z && !SUBMERGED(trace_endpos)) + { + // stepswim caused upwards direction + tracebox(trace_endpos, m1, m2, trace_endpos - stepheightvec, movemode, e); + if (!SUBMERGED(trace_endpos)) + { + org = trace_endpos; + nav_action = NAV_WALK; + continue; + } + } + } + + org = trace_endpos; + nav_action = NAV_SWIM_UNDERWATER; + continue; + } + } + else if(nav_action == NAV_WALK) + { + // walk tracebox(org, m1, m2, move, movemode, e); if(autocvar_bot_debug_tracewalk) @@ -174,19 +593,34 @@ bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float m tracebox(org + stepheightvec, m1, m2, move + stepheightvec, movemode, e); if (trace_fraction < 1 || trace_startsolid) { - tracebox(org + jumpstepheightvec, m1, m2, move + jumpstepheightvec, movemode, e); - if (trace_fraction < 1 || trace_startsolid) + if (trace_startsolid) // hit ceiling above org { - bool ladder_found = false; - FOREACH_ENTITY_CLASS("func_ladder", boxesoverlap(trace_endpos + m1 + '-1 -1 -1', trace_endpos + m2 + '1 1 1', it.absmin, it.absmax), + // reduce stepwalk height + tracebox(org, m1, m2, org + stepheightvec, movemode, e); + tracebox(trace_endpos, m1, m2, move + eZ * (trace_endpos.z - move.z), movemode, e); + } + else //if (trace_fraction < 1) + { + tracebox(org + jumpstepheightvec, m1, m2, move + jumpstepheightvec, movemode, e); + if (trace_startsolid) // hit ceiling above org { - if(boxesoverlap(end, end, it.absmin + (m1 - eZ * m1.z - '1 1 0'), it.absmax + (m2 - eZ * m2.z + '1 1 0'))) - ladder_found = true; // can't return here ("Loop mutex held by tracewalk" error) - }); - if(ladder_found) + // reduce jumpstepwalk height + tracebox(org, m1, m2, org + jumpstepheightvec, movemode, e); + tracebox(trace_endpos, m1, m2, move + eZ * (trace_endpos.z - move.z), movemode, e); + } + } + + if (trace_fraction < 1) + { + vector v = trace_endpos; + v.z = org.z + jumpheight_vec.z; + if(navigation_checkladders(e, v, m1, m2, end, end2, movemode)) { if(autocvar_bot_debug_tracewalk) - debugnodestatus(org, DEBUG_NODE_SUCCESS); + { + debugnode(e, v); + debugnodestatus(v, DEBUG_NODE_SUCCESS); + } //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n"); return true; @@ -203,10 +637,11 @@ bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float m move = trace_endpos; while(trace_ent.classname == "door_rotating" || trace_ent.classname == "door") { - nextmove = move + (dir * stepdist); + nextmove = move + (flatdir * stepdist); traceline( move, nextmove, movemode, e); move = nextmove; } + flatdist = vlen(vec2(end - move)); } else { @@ -234,18 +669,43 @@ bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float m // (this is the same logic as the Quake walkmove function used) tracebox(move, m1, m2, move + '0 0 -65536', movemode, e); - // moved successfully - if(swimming) + org = trace_endpos; + + if (!ignorehazards) { - float c; - c = pointcontents(org + '0 0 1'); - if (!(c == CONTENT_WATER || c == CONTENT_LAVA || c == CONTENT_SLIME)) - swimming = false; - else - continue; + if (IN_LAVA(org)) + { + if(autocvar_bot_debug_tracewalk) + { + debugnode(e, trace_endpos); + debugnodestatus(org, DEBUG_NODE_FAIL); + } + + //print("tracewalk: ", vtos(start), " hits a hazard when trying to reach ", vtos(end), "\n"); + return false; + } } - org = trace_endpos; + if (flatdist <= 0) + { + if(move.z >= end2.z && org.z < end2.z) + org.z = end2.z; + continue; + } + + if(org.z > move.z - 1 || !SUBMERGED(org)) + { + nav_action = NAV_WALK; + continue; + } + + // ended up submerged while walking + if(autocvar_bot_debug_tracewalk) + debugnode(e, org); + + RESURFACE_LIMITED(org, move.z); + nav_action = NAV_SWIM_ONWATER; + continue; } } @@ -265,7 +725,11 @@ bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float m // completely empty the goal stack, used when deciding where to go void navigation_clearroute(entity this) { - //print("bot ", etos(this), " clear\n"); + this.goalcurrent_prev = this.goalcurrent; + this.goalcurrent_distance_2d = FLOAT_MAX; + this.goalcurrent_distance_z = FLOAT_MAX; + this.goalcurrent_distance_time = 0; + this.goalentity_lock_timeout = 0; this.goalentity = NULL; this.goalcurrent = NULL; this.goalstack01 = NULL; @@ -309,6 +773,10 @@ void navigation_clearroute(entity this) // steps to the goal, and then recalculate the path. void navigation_pushroute(entity this, entity e) { + this.goalcurrent_prev = this.goalcurrent; + this.goalcurrent_distance_2d = FLOAT_MAX; + this.goalcurrent_distance_z = FLOAT_MAX; + this.goalcurrent_distance_time = 0; //print("bot ", etos(this), " push ", etos(e), "\n"); if(this.goalstack31 == this.goalentity) this.goalentity = NULL; @@ -351,9 +819,16 @@ void navigation_pushroute(entity this, entity e) // (used when a spawnfunc_waypoint is reached) void navigation_poproute(entity this) { + this.goalcurrent_prev = this.goalcurrent; + this.goalcurrent_distance_2d = FLOAT_MAX; + this.goalcurrent_distance_z = FLOAT_MAX; + this.goalcurrent_distance_time = 0; //print("bot ", etos(this), " pop\n"); if(this.goalcurrent == this.goalentity) + { this.goalentity = NULL; + this.goalentity_lock_timeout = 0; + } this.goalcurrent = this.goalstack01; this.goalstack01 = this.goalstack02; this.goalstack02 = this.goalstack03; @@ -388,23 +863,24 @@ void navigation_poproute(entity this) this.goalstack31 = NULL; } -float navigation_waypoint_will_link(vector v, vector org, entity ent, float walkfromwp, float bestdist) +// 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, vector o2, float o2_height, float walkfromwp, float bestdist) { - float dist; - dist = vlen(v - org); - if (bestdist > dist) + if (vdist(v - org, <, bestdist)) { traceline(v, org, true, ent); if (trace_fraction == 1) { if (walkfromwp) { - if (tracewalk(ent, v, PL_MIN_CONST, PL_MAX_CONST, org, 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, v, bot_navigation_movemode)) + if (tracewalk(ent, org, PL_MIN_CONST, PL_MAX_CONST, o2, o2_height, bot_navigation_movemode)) return true; } } @@ -415,6 +891,9 @@ float navigation_waypoint_will_link(vector v, vector org, entity ent, float walk // 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; @@ -422,59 +901,92 @@ entity navigation_findnearestwaypoint_withdist_except(entity ent, float walkfrom 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; + vector v = '0 0 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(it.wpisbox) + if(walkfromwp && (it.wpflags & WAYPOINTFLAG_NORELINK)) + continue; + + set_tracewalk_dest(ent, it.origin, false); + if (vdist(tracewalk_dest - it.origin, <, 1050) + && tracewalk(ent, it.origin, PL_MIN_CONST, PL_MAX_CONST, + tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode)) { - vector wm1 = it.absmin; - vector wm2 = it.absmax; - v.x = bound(wm1_x, org.x, wm2_x); - v.y = bound(wm1_y, org.y, wm2_y); - v.z = bound(wm1_z, org.z, wm2_z); - } - else - v = it.origin; - if(navigation_waypoint_will_link(v, org, ent, walkfromwp, 1050)) navigation_item_addlink(it, ent); + } }); } // box check failed, try walk IL_EACH(g_waypoints, it != ent, { - if(it.wpisbox) + if(walkfromwp && (it.wpflags & WAYPOINTFLAG_NORELINK)) + continue; + v = it.origin; + + if (walkfromwp) { - vector wm1 = it.origin + it.mins; - vector wm2 = it.origin + it.maxs; - v.x = bound(wm1_x, org.x, wm2_x); - v.y = bound(wm1_y, org.y, wm2_y); - v.z = bound(wm1_z, org.z, wm2_z); + set_tracewalk_dest(ent, v, true); + if (trace_ent == ent) + { + bestdist = 0; + best = it; + break; + } } else - v = it.origin; - if(navigation_waypoint_will_link(v, org, ent, walkfromwp, bestdist)) + set_tracewalk_dest(it, org, false); + + if (navigation_waypoint_will_link(v, org, ent, + tracewalk_dest, tracewalk_dest_height, + tracewalk_dest, tracewalk_dest_height, walkfromwp, bestdist)) { - bestdist = vlen(v - org); + if (walkfromwp) + bestdist = vlen(tracewalk_dest - org); + else + bestdist = vlen(v - org); best = it; } }); + if(!best && !ent.navigation_dynamicgoal) + { + int solid_save = ent.solid; + ent.solid = SOLID_BSP; + IL_EACH(g_jumppads, true, + { + if(trigger_push_test(it, ent)) + { + best = it.nearestwaypoint; + break; + } + }); + ent.solid = solid_save; + } return best; } entity navigation_findnearestwaypoint(entity ent, float walkfromwp) @@ -492,31 +1004,22 @@ entity navigation_findnearestwaypoint(entity ent, float walkfromwp) // finds the waypoints near the bot initiating a navigation query float navigation_markroutes_nearestwaypoints(entity this, float maxdist) { - vector v, m1, m2; -// navigation_testtracewalk = true; + //navigation_testtracewalk = true; int c = 0; IL_EACH(g_waypoints, !it.wpconsidered, { - if (it.wpisbox) - { - m1 = it.origin + it.mins; - m2 = it.origin + it.maxs; - v = this.origin; - v.x = bound(m1_x, v.x, m2_x); - v.y = bound(m1_y, v.y, m2_y); - v.z = bound(m1_z, v.z, m2_z); - } - else - v = it.origin; - vector diff = v - this.origin; + set_tracewalk_dest(it, this.origin, false); + + vector diff = tracewalk_dest - this.origin; diff.z = max(0, diff.z); if(vdist(diff, <, maxdist)) { it.wpconsidered = true; - if (tracewalk(this, this.origin, this.mins, this.maxs, v, bot_navigation_movemode)) + if (tracewalk(this, this.origin, this.mins, this.maxs, + tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode)) { - it.wpnearestpoint = v; - it.wpcost = waypoint_getdistancecost(this.origin, v) + it.dmg; + it.wpnearestpoint = tracewalk_dest; + it.wpcost = waypoint_gettravelcost(this.origin, tracewalk_dest, this, it) + it.dmg; it.wpfire = 1; it.enemy = NULL; c = c + 1; @@ -528,25 +1031,27 @@ float navigation_markroutes_nearestwaypoints(entity this, float maxdist) } // updates a path link if a spawnfunc_waypoint link is better than the current one -void navigation_markroutes_checkwaypoint(entity w, entity wp, float cost2, vector p) +void navigation_markroutes_checkwaypoint(entity w, entity wp, float cost, vector p) { - vector m1; - vector m2; + vector m1, m2; vector v; if (wp.wpisbox) { - m1 = wp.absmin; - m2 = wp.absmax; + m1 = wp.origin + wp.mins; + m2 = wp.origin + wp.maxs; v.x = bound(m1_x, p.x, m2_x); v.y = bound(m1_y, p.y, m2_y); v.z = bound(m1_z, p.z, m2_z); } else v = wp.origin; - cost2 += waypoint_getdistancecost(p, v); - if (wp.wpcost > cost2) + if (w.wpflags & WAYPOINTFLAG_TELEPORT) + cost += w.wp00mincost; // assuming teleport has exactly one destination + else + cost += waypoint_gettravelcost(p, v, w, wp); + if (wp.wpcost > cost) { - wp.wpcost = cost2; + wp.wpcost = cost; wp.enemy = w; wp.wpfire = 1; wp.wpnearestpoint = v; @@ -701,8 +1206,8 @@ void navigation_routerating(entity this, entity e, float f, float rangebias) if(e.blacklisted) return; - rangebias = waypoint_getdistancecost_simple(rangebias); - f = waypoint_getdistancecost_simple(f); + rangebias = waypoint_getlinearcost(rangebias); + f = waypoint_getlinearcost(f); if (IS_PLAYER(e)) { @@ -745,7 +1250,7 @@ void navigation_routerating(entity this, entity e, float f, float rangebias) } } - vector o = (e.absmin + e.absmax) * 0.5; + vector goal_org = (e.absmin + e.absmax) * 0.5; //print("routerating ", etos(e), " = ", ftos(f), " - ", ftos(rangebias), "\n"); @@ -753,7 +1258,7 @@ void navigation_routerating(entity this, entity e, float f, float rangebias) if(g_jetpack) if(this.items & IT_JETPACK) if(autocvar_bot_ai_navigation_jetpack) - if(vdist(this.origin - o, >, autocvar_bot_ai_navigation_jetpack_mindistance)) + if(vdist(this.origin - goal_org, >, autocvar_bot_ai_navigation_jetpack_mindistance)) { vector pointa, pointb; @@ -764,7 +1269,7 @@ void navigation_routerating(entity this, entity e, float f, float rangebias) pointa = trace_endpos - '0 0 1'; // Point B - traceline(o, o + '0 0 65535', MOVE_NORMAL, e); + traceline(goal_org, goal_org + '0 0 65535', MOVE_NORMAL, e); pointb = trace_endpos - '0 0 1'; // Can I see these two points from the sky? @@ -855,29 +1360,11 @@ void navigation_routerating(entity this, entity e, float f, float rangebias) 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); - } + if(IS_BOT_CLIENT(e) && e.goalcurrent && e.goalcurrent.classname == "waypoint") + e.nearestwaypoint = nwp = e.goalcurrent; else + e.nearestwaypoint = nwp = navigation_findnearestwaypoint(e, true); + if(!nwp) { LOG_DEBUG("FAILED to find a nearest waypoint to '", e.classname, "' #", etos(e)); @@ -900,12 +1387,17 @@ void navigation_routerating(entity this, entity e, float f, float rangebias) } LOG_DEBUG("-- checking ", e.classname, " (with cost ", ftos(nwp.wpcost), ")"); - if (nwp) - if (nwp.wpcost < 10000000) + if (nwp && nwp.wpcost < 10000000) { //te_wizspike(nwp.wpnearestpoint); - LOG_DEBUG(e.classname, " ", ftos(f), "/(1+", ftos((nwp.wpcost + vlen(e.origin - nwp.wpnearestpoint))), "/", ftos(rangebias), ") = "); - f = f * rangebias / (rangebias + (nwp.wpcost + vlen(o - nwp.wpnearestpoint))); + float nwptoitem_cost = 0; + if(nwp.wpflags & WAYPOINTFLAG_TELEPORT) + nwptoitem_cost = nwp.wp00mincost; + else + nwptoitem_cost = waypoint_gettravelcost(nwp.wpnearestpoint, goal_org, nwp, e); + float cost = nwp.wpcost + nwptoitem_cost; + LOG_DEBUG(e.classname, " ", ftos(f), "/(1+", ftos(cost), "/", ftos(rangebias), ") = "); + f = f * rangebias / (rangebias + cost); LOG_DEBUG("considering ", e.classname, " (with rating ", ftos(f), ")"); if (navigation_bestrating < f) { @@ -923,12 +1415,25 @@ bool navigation_routetogoal(entity this, entity e, vector startposition) if (!e) return false; + entity teleport_goal = NULL; + this.goalentity = e; + if(e.wpflags & WAYPOINTFLAG_TELEPORT) + { + // force teleport destination as route destination + teleport_goal = e; + navigation_pushroute(this, e.wp00); + this.goalentity = e.wp00; + } + // 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; @@ -940,8 +1445,12 @@ bool navigation_routetogoal(entity this, entity e, vector startposition) return true; // if it can reach the goal there is nothing more to do - if (tracewalk(this, startposition, STAT(PL_MIN, this), STAT(PL_MAX, this), (e.absmin + e.absmax) * 0.5, bot_navigation_movemode)) + set_tracewalk_dest(e, startposition, true); + if (trace_ent == this || tracewalk(this, startposition, STAT(PL_MIN, this), STAT(PL_MAX, this), + tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode)) + { return true; + } entity nearest_wp = NULL; // see if there are waypoints describing a path to the item @@ -950,6 +1459,8 @@ bool navigation_routetogoal(entity this, entity e, vector startposition) e = e.nearestwaypoint; nearest_wp = e; } + else if(teleport_goal) + e = teleport_goal; else e = e.enemy; // we already have added it, so... @@ -959,9 +1470,17 @@ bool navigation_routetogoal(entity this, entity e, vector startposition) if(nearest_wp && nearest_wp.enemy) { // often path can be optimized by not adding the nearest waypoint - if (this.goalentity.nearestwaypoint_dist < 8 - || (!this.goalentity.navigation_dynamicgoal && navigation_item_islinked(nearest_wp.enemy, this.goalentity)) - || (this.goalentity.navigation_dynamicgoal && tracewalk(this, nearest_wp.enemy.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), (this.goalentity.absmin + this.goalentity.absmax) * 0.5, bot_navigation_movemode))) + if (this.goalentity.navigation_dynamicgoal || autocvar_g_waypointeditor) + { + set_tracewalk_dest(this.goalentity, nearest_wp.enemy.origin, true); + if (trace_ent == this || (vdist(tracewalk_dest - nearest_wp.enemy.origin, <, 1050) + && tracewalk(this, nearest_wp.enemy.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), + tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode))) + { + e = nearest_wp.enemy; + } + } + else if(navigation_item_islinked(nearest_wp.enemy, this.goalentity)) e = nearest_wp.enemy; } @@ -980,19 +1499,19 @@ bool navigation_routetogoal(entity this, entity e, vector startposition) // 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) { - vector org, m1, m2; - org = this.origin; - m1 = org + this.mins; - m2 = org + this.maxs; + int removed_goals = 0; + + if(!this.goalcurrent) + return removed_goals; if(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT) { // make sure jumppad is really hit, don't rely on distance based checks // as they may report a touch even if it didn't really happen - if(this.lastteleporttime>0) - if(time - this.lastteleporttime < ((this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL) ? 2 : 0.15)) + if(this.lastteleporttime > 0 + && time - this.lastteleporttime < ((this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL) ? 2 : 0.15)) { if(this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING) if(this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL && this.goalcurrent.owner==this) @@ -1001,31 +1520,49 @@ void navigation_poptouchedgoals(entity this) this.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED; } navigation_poproute(this); - return; + this.lastteleporttime = 0; + ++removed_goals; } + else + 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(checkpvs(this.origin + this.view_ofs, this.goalstack01)) - if(tracewalk(this, this.origin, this.mins, this.maxs, (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5, bot_navigation_movemode)) + // randomness should help to get unstuck bot on certain hard paths with climbs and tight corners + if (this.goalstack01 && !wasfreed(this.goalstack01) && random() < 0.7) { - LOG_DEBUG("path optimized for ", this.netname, ", removed a goal from the queue"); - navigation_poproute(this); - // 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 - // "zigzagging" which they currently do with sufficiently - // random-like waypoints, and thus can make a nice bot - // personality property + entity next = IS_PLAYER(this.goalentity) ? this.goalentity : this.goalstack01; + if (vlen2(this.goalcurrent.origin - next.origin) > vlen2(next.origin - this.origin) + && checkpvs(this.origin + this.view_ofs, next)) + { + set_tracewalk_dest(next, this.origin, true); + if (trace_ent == this || tracewalk(this, this.origin, this.mins, this.maxs, + tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode)) + { + LOG_DEBUG("path optimized for ", this.netname, ", removed a goal from the queue"); + do + { + // loop clears the whole route if next is a player + navigation_poproute(this); + ++removed_goals; + } + while (this.goalcurrent == next); + 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 + // "zigzagging" which they currently do with sufficiently + // random-like waypoints, and thus can make a nice bot + // personality property + } + } } // Loose goal touching check when running if(this.aistatus & AI_STATUS_RUNNING) if(this.goalcurrent.classname=="waypoint") - if(!(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)) - if(vlen(this.velocity - eZ * this.velocity.z) >= autocvar_sv_maxspeed) // if -really- running + if(vdist(vec2(this.velocity), >=, autocvar_sv_maxspeed)) // if -really- running { if(vdist(this.origin - this.goalcurrent.origin, <, 150)) { @@ -1041,6 +1578,9 @@ void navigation_poptouchedgoals(entity this) } navigation_poproute(this); + ++removed_goals; + if(this.goalcurrent && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT) + return removed_goals; } } } @@ -1054,10 +1594,7 @@ void navigation_poptouchedgoals(entity this) gc_min = this.goalcurrent.origin - '1 1 1' * 12; gc_max = this.goalcurrent.origin + '1 1 1' * 12; } - if(!boxesoverlap(m1, m2, gc_min, gc_max)) - break; - - if((this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)) + if(!boxesoverlap(this.absmin, this.absmax, gc_min, gc_max)) break; // Detect personal waypoints @@ -1069,7 +1606,47 @@ void navigation_poptouchedgoals(entity this) } 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; + + set_tracewalk_dest(wp, this.origin, false); + if (!tracewalk(this, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), + tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode)) + { + return NULL; + } + return wp; } // begin a goal selection session (queries spawnfunc_waypoint network) @@ -1080,9 +1657,10 @@ void navigation_goalrating_start(entity this) 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) @@ -1110,11 +1688,13 @@ void botframe_updatedangerousobjects(float maxupdate) vector m1, m2, v, o; float c, d, danger; c = 0; + entity wp_cur; IL_EACH(g_waypoints, true, { danger = 0; m1 = it.absmin; m2 = it.absmax; + wp_cur = it; IL_EACH(g_bot_dodge, it.bot_dodge, { v = it.origin; @@ -1122,7 +1702,7 @@ void botframe_updatedangerousobjects(float maxupdate) v.y = bound(m1_y, v.y, m2_y); v.z = bound(m1_z, v.z, m2_z); o = (it.absmin + it.absmax) * 0.5; - d = waypoint_getdistancecost_simple(it.bot_dodgerating) - waypoint_getdistancecost(o, v); + d = waypoint_getlinearcost(it.bot_dodgerating) - waypoint_gettravelcost(o, v, it, wp_cur); if (d > 0) { traceline(o, v, true, NULL); @@ -1160,7 +1740,9 @@ void navigation_unstuck(entity this) // 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)); - if(tracewalk(bot_waypoint_queue_goal, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), bot_waypoint_queue_goal.origin, bot_navigation_movemode)) + set_tracewalk_dest(bot_waypoint_queue_goal, this.origin, false); + if (tracewalk(bot_waypoint_queue_goal, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), + tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode)) { if( d > bot_waypoint_queue_bestgoalrating) { @@ -1176,7 +1758,7 @@ void navigation_unstuck(entity this) { 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