]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/bot/default/navigation.qc
Bot AI: immediately remove player as goal if dead and visible
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / bot / default / navigation.qc
index 64e960ff760f975a3ac36a9c7204ac6648978a82..b21670a06357527f6a44d9ccb4d0bdd62695ec89 100644 (file)
 
 .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;
@@ -755,7 +773,14 @@ 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;
@@ -1177,29 +1202,8 @@ 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);
-                       }
-                       else
+                       e.nearestwaypoint = nwp = navigation_findnearestwaypoint(e, true);
+                       if(!nwp)
                        {
                                LOG_DEBUG("FAILED to find a nearest waypoint to '", e.classname, "' #", etos(e));
 
@@ -1300,20 +1304,15 @@ 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)
-                       e = nearest_wp.enemy;
-               else
+               if (this.goalentity.navigation_dynamicgoal || autocvar_g_waypointeditor)
                {
-                       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))
+                       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 (;;)
@@ -1331,8 +1330,16 @@ 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)
 {
+       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
@@ -1347,9 +1354,10 @@ void navigation_poptouchedgoals(entity this)
                                this.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED;
                        }
                        navigation_poproute(this);
+                       ++removed_goals;
                }
                else
-                       return;
+                       return removed_goals;
        }
 
        // If for some reason the bot is closer to the next goal, pop the current one
@@ -1357,15 +1365,16 @@ void navigation_poptouchedgoals(entity this)
        if(vlen2(this.goalcurrent.origin - this.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;
+                               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
@@ -1394,8 +1403,9 @@ void navigation_poptouchedgoals(entity this)
                                }
 
                                navigation_poproute(this);
+                               ++removed_goals;
                                if(this.goalcurrent && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
-                                       return;
+                                       return removed_goals;
                        }
                }
        }
@@ -1421,9 +1431,46 @@ void navigation_poptouchedgoals(entity this)
                }
 
                navigation_poproute(this);
+               ++removed_goals;
                if(this.goalcurrent && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
-                       return;
+                       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)
@@ -1434,9 +1481,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)
@@ -1516,9 +1564,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));
-               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)
@@ -1535,7 +1583,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