X-Git-Url: https://de.git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fserver%2Fbot%2Fdefault%2Fnavigation.qc;h=9c3bae8e7992f37c5ce9157bfdd7d688808346a8;hb=862e4b2e6a3e1a771b406e634a1de880b8d3159c;hp=c9ebc07550408ea28704823bdbc27304fd9ec09a;hpb=1f178e9e99364ebbb3398d9de8b5a13aca01e2f8;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/server/bot/default/navigation.qc b/qcsrc/server/bot/default/navigation.qc index c9ebc0755..9c3bae8e7 100644 --- a/qcsrc/server/bot/default/navigation.qc +++ b/qcsrc/server/bot/default/navigation.qc @@ -13,7 +13,7 @@ #include #include -#include +#include .float speed; @@ -46,6 +46,7 @@ bool navigation_goalrating_timeout(entity this) return this.bot_strategytime < time; } +#define MAX_CHASE_DISTANCE 700 bool navigation_goalrating_timeout_can_be_anticipated(entity this) { if(time > this.bot_strategytime - (IS_MOVABLE(this.goalentity) ? 3 : 2)) @@ -94,8 +95,8 @@ 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); + vector wm1 = ent.origin + ent.mins; + vector wm2 = ent.origin + ent.maxs; 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); @@ -109,8 +110,8 @@ void set_tracewalk_dest(entity ent, vector org, bool fix_player_dest) { 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); + vector wm1 = ent.origin + ent.mins; + vector wm2 = ent.origin + ent.maxs; if (IS_PLAYER(ent) || IS_MONSTER(ent)) { // move destination point out of player bbox otherwise tracebox always fails @@ -158,8 +159,8 @@ 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); + vector wm1 = ent.origin + ent.mins; + vector wm2 = ent.origin + ent.maxs; 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); @@ -245,6 +246,7 @@ vector resurface_limited(vector org, float lim, vector m1) // 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] +// INFO: the command sv_cmd trace walk is useful to test this function in game bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float end_height, float movemode) { if(autocvar_bot_debug_tracewalk) @@ -967,7 +969,7 @@ entity navigation_findnearestwaypoint_withdist_except(entity ent, float walkfrom tracewalk_dest, tracewalk_dest_height, walkfromwp, bestdist)) { if (walkfromwp) - bestdist = vlen(tracewalk_dest - org); + bestdist = vlen(tracewalk_dest - v); else bestdist = vlen(v - org); best = it; @@ -1255,7 +1257,6 @@ void navigation_routerating(entity this, entity e, float f, float rangebias) //print("routerating ", etos(e), " = ", ftos(f), " - ", ftos(rangebias), "\n"); // Evaluate path using jetpack - if(g_jetpack) if(this.items & IT_JETPACK) if(autocvar_bot_ai_navigation_jetpack) if(vdist(this.origin - goal_org, >, autocvar_bot_ai_navigation_jetpack_mindistance)) @@ -1317,7 +1318,7 @@ void navigation_routerating(entity this, entity e, float f, float rangebias) LOG_DEBUG("jetpack ai: required fuel ", ftos(fuel), " this.ammo_fuel ", ftos(this.ammo_fuel)); // enough fuel ? - if(this.ammo_fuel>fuel) + if(this.ammo_fuel>fuel || (this.items & IT_UNLIMITED_WEAPON_AMMO)) { // Estimate cost // (as onground costs calculation is mostly based on distances, here we do the same establishing some relationship @@ -1446,8 +1447,9 @@ bool navigation_routetogoal(entity this, entity e, vector startposition) // if it can reach the goal there is nothing more to do 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)) + if ((!IS_MOVABLE(this.goalcurrent) || vdist(tracewalk_dest - this.origin, <, MAX_CHASE_DISTANCE)) + && (trace_ent == this || tracewalk(this, startposition, STAT(PL_MIN, this), STAT(PL_MAX, this), + tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode))) { return true; } @@ -1472,12 +1474,21 @@ bool navigation_routetogoal(entity this, entity e, vector startposition) // often path can be optimized by not adding the nearest waypoint 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))) + if (nearest_wp.enemy.wpcost < autocvar_bot_ai_strategyinterval_movingtarget) { - e = nearest_wp.enemy; + if (vdist(vec2(this.goalentity.origin - nearest_wp.origin), <, 32)) + e = nearest_wp.enemy; + else + { + set_tracewalk_dest(this.goalentity, nearest_wp.enemy.origin, true); + if (trace_ent == this || (vdist(tracewalk_dest - nearest_wp.enemy.origin, <, 1050) + && vlen2(tracewalk_dest - nearest_wp.enemy.origin) < vlen2(nearest_wp.origin - nearest_wp.enemy.origin) + && 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)) @@ -1497,6 +1508,77 @@ bool navigation_routetogoal(entity this, entity e, vector startposition) return false; } +// shorten path by removing intermediate goals +void navigation_shortenpath(entity this) +{ + if (!this.goalstack01 || wasfreed(this.goalstack01)) + return; + if (this.bot_tracewalk_time > time) + return; + this.bot_tracewalk_time = max(time, this.bot_tracewalk_time) + 0.25; + + bool cut_allowed = false; + entity next = this.goalentity; + // evaluate whether bot can discard current route and chase directly a player, trying to + // keep waypoint route as long as possible, as it is safer and faster (bot can bunnyhop) + if (IS_MOVABLE(next)) + { + set_tracewalk_dest(next, this.origin, true); + if (vdist(this.origin - tracewalk_dest, <, 200)) + cut_allowed = true; + else if (vdist(tracewalk_dest - this.origin, <, MAX_CHASE_DISTANCE) + && vdist(tracewalk_dest - this.goalcurrent.origin, >, 200) + && vdist(this.origin - this.goalcurrent.origin, >, 100) + && checkpvs(this.origin + this.view_ofs, next)) + { + if (vlen2(next.origin - this.origin) < vlen2(this.goalcurrent.origin - this.origin)) + cut_allowed = true; + else + { + vector deviation = vectoangles(this.goalcurrent.origin - this.origin) - vectoangles(next.origin - this.origin); + while (deviation.y < -180) deviation.y += 360; + while (deviation.y > 180) deviation.y -= 360; + if (fabs(deviation.y) > 25) + cut_allowed = true; + } + } + if (cut_allowed) + { + 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, ", route cleared"); + do + { + navigation_poproute(this); + } + while (this.goalcurrent != next); + } + return; + } + } + + next = this.goalstack01; + // if for some reason the bot is closer to the next goal, pop the current one + if (!IS_MOVABLE(next) // already checked in the previous case + && 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); + cut_allowed = true; + } + + if (cut_allowed) + { + 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"); + navigation_poproute(this); + } + } +} + // removes any currently touching waypoints from the goal stack // (this is how bots detect if they reached a goal) int navigation_poptouchedgoals(entity this) @@ -1510,8 +1592,7 @@ int navigation_poptouchedgoals(entity this) { // 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 - && time - this.lastteleporttime < ((this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL) ? 2 : 0.15)) + if(this.lastteleporttime > 0 && TELEPORT_USED(this, this.goalcurrent)) { if(this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING) if(this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL && this.goalcurrent.owner==this) @@ -1526,36 +1607,35 @@ int navigation_poptouchedgoals(entity this) else return removed_goals; } - - // If for some reason the bot is closer to the next goal, pop the current one - // 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) + else if (this.lastteleporttime > 0) { - 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)) + // sometimes bot is pushed so hard (by a jumppad or a shot) that ends up touching the next + // teleport / jumppad / warpzone present in its path skipping check of one or more goals + // if so immediately fix bot path by removing skipped goals + entity tele_ent = NULL; + if (this.goalstack01 && (this.goalstack01.wpflags & WAYPOINTFLAG_TELEPORT)) + tele_ent = this.goalstack01; + else if (this.goalstack02 && (this.goalstack02.wpflags & WAYPOINTFLAG_TELEPORT)) + tele_ent = this.goalstack02; + else if (this.goalstack03 && (this.goalstack03.wpflags & WAYPOINTFLAG_TELEPORT)) + tele_ent = this.goalstack03; + if (tele_ent && TELEPORT_USED(this, tele_ent)) { - 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)) + if (this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING) + if (tele_ent.wpflags & WAYPOINTFLAG_PERSONAL && tele_ent.owner == this) { - 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 + this.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING; + this.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED; } + while (this.goalcurrent != tele_ent) + { + navigation_poproute(this); + ++removed_goals; + } + navigation_poproute(this); + this.lastteleporttime = 0; + ++removed_goals; + return removed_goals; } } @@ -1616,10 +1696,16 @@ int navigation_poptouchedgoals(entity this) entity navigation_get_really_close_waypoint(entity this) { entity wp = this.goalcurrent; - if(!wp || vdist(wp.origin - this.origin, >, 50)) + if(!wp) wp = this.goalcurrent_prev; if(!wp) return NULL; + if(wp != this.goalcurrent_prev && vdist(wp.origin - this.origin, >, 50)) + { + wp = this.goalcurrent_prev; + if(!wp) + return NULL; + } if(wp.classname != "waypoint") { wp = wp.nearestwaypoint; @@ -1628,6 +1714,7 @@ entity navigation_get_really_close_waypoint(entity this) } if(vdist(wp.origin - this.origin, >, 50)) { + wp = NULL; IL_EACH(g_waypoints, !(it.wpflags & WAYPOINTFLAG_TELEPORT), { if(vdist(it.origin - this.origin, <, 50)) @@ -1636,6 +1723,8 @@ entity navigation_get_really_close_waypoint(entity this) break; } }); + if(!wp) + return NULL; } if(wp.wpflags & WAYPOINTFLAG_TELEPORT) return NULL; @@ -1719,11 +1808,20 @@ void botframe_updatedangerousobjects(float maxupdate) void navigation_unstuck(entity this) { - float search_radius = 1000; - if (!autocvar_bot_wander_enable) return; + bool has_user_waypoints = false; + IL_EACH(g_waypoints, !(it.wpflags & WAYPOINTFLAG_GENERATED), + { + has_user_waypoints = true; + break; + }); + if (!has_user_waypoints) + return; + + float search_radius = 1000; + if (!bot_waypoint_queue_owner) { LOG_DEBUG(this.netname, " stuck, taking over the waypoints queue"); @@ -1741,7 +1839,7 @@ void navigation_unstuck(entity this) float d = vlen2(this.origin - bot_waypoint_queue_goal.origin); LOG_DEBUG(this.netname, " evaluating ", bot_waypoint_queue_goal.classname, " with distance ", ftos(d)); 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), + if (tracewalk(this, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode)) { if( d > bot_waypoint_queue_bestgoalrating)