X-Git-Url: http://git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fserver%2Fbot%2Fdefault%2Fnavigation.qc;h=da87445b8f31b42e6b4d245b720fe001d3fce8fa;hb=0d44cd70bafa39eabf636208941ae13e5fcb6211;hp=76ac3389cdac1e72961e48b23c64bc3ebb27d63b;hpb=af714c7aa2978049a30147cde6cf21198eab54cf;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/server/bot/default/navigation.qc b/qcsrc/server/bot/default/navigation.qc index 76ac3389c..da87445b8 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" @@ -37,48 +39,94 @@ 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 +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 tracewalk(entity e, vector start, vector m1, vector m2, vector end, float movemode) +bool navigation_checkladders(entity e, vector org, vector m1, vector m2, vector end, vector end2, int movemode) { - vector org; - vector move; - vector dir; - float dist; - float totaldist; - float stepdist; - float ignorehazards; - float swimming; + 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 +138,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; + } + + 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; + //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"); } - continue; + //succesful stepswim + + if (flatdist <= 0) + { + org = trace_endpos; + 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) + { + 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) { - move = dir * stepdist + org; + // walk tracebox(org, m1, m2, move, movemode, e); if(autocvar_bot_debug_tracewalk) @@ -174,20 +453,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", true, + // 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(org + jumpheight_vec + m1 + '-1 -1 -1', org + jumpheight_vec + m2 + '1 1 1', it.absmin, it.absmax)) - 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(end, DEBUG_NODE_SUCCESS); + { + debugnode(e, v); + debugnodestatus(v, DEBUG_NODE_SUCCESS); + } //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n"); return true; @@ -204,10 +497,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 { @@ -235,18 +529,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; } } @@ -398,23 +717,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; } } @@ -425,6 +745,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; @@ -435,33 +758,31 @@ entity navigation_findnearestwaypoint_withdist_except(entity ent, float walkfrom 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', 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(it.wpisbox) - { - 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)) + if(walkfromwp && (it.wpflags & WAYPOINTFLAG_NORELINK)) + continue; + + 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); }); } @@ -469,17 +790,14 @@ entity navigation_findnearestwaypoint_withdist_except(entity ent, float walkfrom // box check failed, try walk IL_EACH(g_waypoints, it != ent, { - if(it.wpisbox) - { - 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); - } + if(walkfromwp && (it.wpflags & WAYPOINTFLAG_NORELINK)) + continue; + v = it.origin; + if(walkfromwp) + SET_TRACEWALK_DESTCOORDS(ent, v, v2, v2_height); else - v = it.origin; - if(navigation_waypoint_will_link(v, org, ent, walkfromwp, bestdist)) + 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; @@ -502,31 +820,23 @@ 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; + vector v = '0 0 0'; + //navigation_testtracewalk = true; int c = 0; + float v_height = 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; + SET_TRACEWALK_DESTCOORDS(it, this.origin, v, v_height); + vector diff = v - 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, v, v_height, bot_navigation_movemode)) { it.wpnearestpoint = v; - it.wpcost = waypoint_getdistancecost(this.origin, v) + it.dmg; + it.wpcost = waypoint_gettravelcost(this.origin, v, this, it) + it.dmg; it.wpfire = 1; it.enemy = NULL; c = c + 1; @@ -540,13 +850,12 @@ 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 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); @@ -556,7 +865,7 @@ void navigation_markroutes_checkwaypoint(entity w, entity wp, float cost, vector if (w.wpflags & WAYPOINTFLAG_TELEPORT) cost += w.wp00mincost; // assuming teleport has exactly one destination else - cost += waypoint_getdistancecost(p, v); + cost += waypoint_gettravelcost(p, v, w, wp); if (wp.wpcost > cost) { wp.wpcost = cost; @@ -714,8 +1023,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)) { @@ -917,7 +1226,7 @@ void navigation_routerating(entity this, entity e, float f, float rangebias) if (nwp.wpcost < 10000000) { //te_wizspike(nwp.wpnearestpoint); - float cost = nwp.wpcost + waypoint_getdistancecost(nwp.wpnearestpoint, goal_org); + float cost = nwp.wpcost + waypoint_gettravelcost(nwp.wpnearestpoint, goal_org, nwp, e); 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), ")"); @@ -937,19 +1246,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 - 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; @@ -961,7 +1276,10 @@ 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)) + 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; entity nearest_wp = NULL; @@ -971,6 +1289,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... @@ -980,10 +1300,20 @@ 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.nearestwaypoint_dist < 8) e = nearest_wp.enemy; + else + { + if (this.goalentity.navigation_dynamicgoal || autocvar_g_waypointeditor) + { + SET_TRACEWALK_DESTCOORDS(e, 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 (;;) @@ -1026,22 +1356,29 @@ void navigation_poptouchedgoals(entity this) 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)) { - 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 + 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); + if(this.goalcurrent && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT) + return; + // 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(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)) { @@ -1057,6 +1394,8 @@ void navigation_poptouchedgoals(entity this) } navigation_poproute(this); + if(this.goalcurrent && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT) + return; } } } @@ -1082,7 +1421,44 @@ void navigation_poptouchedgoals(entity this) } navigation_poproute(this); + if(this.goalcurrent && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT) + return; + } +} + +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) @@ -1093,9 +1469,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) @@ -1123,11 +1500,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; @@ -1135,7 +1514,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); @@ -1173,7 +1552,10 @@ 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)) + 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) {