]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/bot/default/havocbot/havocbot.qc
Merge branch 'master' into terencehill/bot_AI_improvements
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / bot / default / havocbot / havocbot.qc
index 0047a26283ba485710c5cb3630fc4af7b16a20da..fad7dbda7b0d573a75aff2f9bdbaa63187be6ea1 100644 (file)
@@ -9,6 +9,7 @@
 #include "../waypoints.qh"
 
 #include <common/constants.qh>
+#include <common/impulses/all.qh>
 #include <common/net_linked.qh>
 #include <common/physics/player.qh>
 #include <common/state.qh>
@@ -30,13 +31,6 @@ void havocbot_ai(entity this)
        if(bot_execute_commands(this))
                return;
 
-       while(this.goalcurrent && wasfreed(this.goalcurrent))
-       {
-               navigation_poproute(this);
-               if(!this.goalcurrent)
-                       this.bot_strategytime = 0;
-       }
-
        if (bot_strategytoken == this)
        if (!bot_strategytoken_taken)
        {
@@ -186,7 +180,7 @@ void havocbot_ai(entity this)
                        // we are currently holding a weapon that's not fully loaded, reload it
                        if(skill >= 2) // bots can only reload the held weapon on purpose past this skill
                        if(this.(weaponentity).clip_load < this.(weaponentity).clip_size)
-                               this.impulse = 20; // "press" the reload button, not sure if this is done right
+                               this.impulse = IMP_weapon_reload.impulse; // not sure if this is done right
 
                        // if we're not reloading a weapon, switch to any weapon in our invnetory that's not fully loaded to reload it next
                        // the code above executes next frame, starting the reloading then
@@ -195,7 +189,10 @@ void havocbot_ai(entity this)
                        {
                                FOREACH(Weapons, it != WEP_Null, LAMBDA(
                                        if((this.weapons & (it.m_wepset)) && (it.spawnflags & WEP_FLAG_RELOADABLE) && (this.(weaponentity).weapon_load[it.m_id] < it.reloading_ammo))
+                                       {
                                                this.(weaponentity).m_switchweapon = it;
+                                               break;
+                                       }
                                ));
                        }
                }
@@ -293,7 +290,8 @@ void havocbot_bunnyhop(entity this, vector dir)
 
        maxspeed = autocvar_sv_maxspeed;
 
-       if(this.aistatus & AI_STATUS_DANGER_AHEAD)
+       if(this.aistatus & AI_STATUS_RUNNING && vdist(this.velocity, <, autocvar_sv_maxspeed * 0.75)
+               || this.aistatus & AI_STATUS_DANGER_AHEAD)
        {
                this.aistatus &= ~AI_STATUS_RUNNING;
                PHYS_INPUT_BUTTON_JUMP(this) = false;
@@ -319,7 +317,7 @@ void havocbot_bunnyhop(entity this, vector dir)
 
        // Run only to visible goals
        if(IS_ONGROUND(this))
-       if(this.speed==maxspeed)
+       if(vlen(this.velocity - eZ * this.velocity.z) >= autocvar_sv_maxspeed) // if -really- running
        if(checkpvs(this.origin + this.view_ofs, this.goalcurrent))
        {
                        this.bot_lastseengoal = this.goalcurrent;
@@ -371,7 +369,8 @@ void havocbot_bunnyhop(entity this, vector dir)
                                        if(checkdistance)
                                        {
                                                this.aistatus &= ~AI_STATUS_RUNNING;
-                                               if(bunnyhopdistance > autocvar_bot_ai_bunnyhop_stopdistance)
+                                               // increase stop distance in case the goal is on a slope or a lower platform 
+                                               if(bunnyhopdistance > autocvar_bot_ai_bunnyhop_stopdistance + (this.origin.z - gco.z))
                                                        PHYS_INPUT_BUTTON_JUMP(this) = true;
                                        }
                                        else
@@ -430,7 +429,6 @@ void havocbot_movetogoal(entity this)
        vector m2;
        vector evadeobstacle;
        vector evadelava;
-       float s;
        float maxspeed;
        vector gco;
        //float dist;
@@ -501,7 +499,7 @@ void havocbot_movetogoal(entity this)
 
                // Flying
                PHYS_INPUT_BUTTON_HOOK(this) = true;
-               if(this.navigation_jetpack_point.z - STAT(PL_MAX, NULL).z + STAT(PL_MIN, NULL).z < this.origin.z)
+               if(this.navigation_jetpack_point.z - STAT(PL_MAX, this).z + STAT(PL_MIN, this).z < this.origin.z)
                {
                        this.movement_x = dir * v_forward * maxspeed;
                        this.movement_y = dir * v_right * maxspeed;
@@ -513,6 +511,7 @@ void havocbot_movetogoal(entity this)
        if(this.jumppadcount)
        {
                // If got stuck on the jump pad try to reach the farthest visible waypoint
+               // but with some randomness so it can try out different paths
                if(this.aistatus & AI_STATUS_OUT_JUMPPAD)
                {
                        if(fabs(this.velocity.z)<50)
@@ -525,7 +524,7 @@ void havocbot_movetogoal(entity this)
                                        if(trace_fraction < 1)
                                                continue;
 
-                                       if(!newgoal || vlen2(it.origin - this.origin) > vlen2(newgoal.origin - this.origin))
+                                       if(!newgoal || ((random() < 0.8) && vlen2(it.origin - this.origin) > vlen2(newgoal.origin - this.origin)))
                                                newgoal = it;
                                });
 
@@ -535,6 +534,8 @@ void havocbot_movetogoal(entity this)
                                        this.ignoregoaltime = time + autocvar_bot_ai_ignoregoal_timeout;
                                        navigation_clearroute(this);
                                        navigation_routetogoal(this, newgoal, this.origin);
+                                       if(autocvar_bot_debug_goalstack)
+                                               debuggoalstack(this);
                                        this.aistatus &= ~AI_STATUS_OUT_JUMPPAD;
                                }
                        }
@@ -673,8 +674,7 @@ void havocbot_movetogoal(entity this)
        if (this.goalcurrent == NULL)
                return;
 
-       if (this.goalcurrent)
-               navigation_poptouchedgoals(this);
+       navigation_poptouchedgoals(this);
 
        // if ran out of goals try to use an alternative goal or get a new strategy asap
        if(this.goalcurrent == NULL)
@@ -707,6 +707,7 @@ void havocbot_movetogoal(entity this)
                evadeobstacle = '0 0 0';
                evadelava = '0 0 0';
 
+               makevectors(this.v_angle.y * '0 1 0');
                if (this.waterlevel)
                {
                        if(this.waterlevel>WATERLEVEL_SWIMMING)
@@ -723,36 +724,37 @@ void havocbot_movetogoal(entity this)
                                        PHYS_INPUT_BUTTON_JUMP(this) = false;
                        }
                        dir = normalize(flatdir);
-                       makevectors(this.v_angle.y * '0 1 0');
                }
                else
                {
+                       float s;
+                       vector offset;
                        if(this.aistatus & AI_STATUS_OUT_WATER)
                                this.aistatus &= ~AI_STATUS_OUT_WATER;
 
                        // jump if going toward an obstacle that doesn't look like stairs we
                        // can walk up directly
-                       tracebox(this.origin, this.mins, this.maxs, this.origin + this.velocity * 0.2, false, this);
+                       offset = (vdist(this.velocity, >, 32) ? this.velocity * 0.2 : v_forward * 32);
+                       tracebox(this.origin, this.mins, this.maxs, this.origin + offset, false, this);
                        if (trace_fraction < 1)
                        if (trace_plane_normal.z < 0.7)
                        {
                                s = trace_fraction;
-                               tracebox(this.origin + stepheightvec, this.mins, this.maxs, this.origin + this.velocity * 0.2 + stepheightvec, false, this);
+                               tracebox(this.origin + stepheightvec, this.mins, this.maxs, this.origin + offset + stepheightvec, false, this);
                                if (trace_fraction < s + 0.01)
                                if (trace_plane_normal.z < 0.7)
                                {
                                        s = trace_fraction;
-                                       tracebox(this.origin + jumpstepheightvec, this.mins, this.maxs, this.origin + this.velocity * 0.2 + jumpstepheightvec, false, this);
+                                       tracebox(this.origin + jumpstepheightvec, this.mins, this.maxs, this.origin + offset + jumpstepheightvec, false, this);
                                        if (trace_fraction > s)
                                                PHYS_INPUT_BUTTON_JUMP(this) = true;
                                }
                        }
 
                        // avoiding dangers and obstacles
-                       vector dst_ahead, dst_down;
-                       makevectors(this.v_angle.y * '0 1 0');
-                       dst_ahead = this.origin + this.view_ofs + (this.velocity * 0.4) + (v_forward * 32 * 3);
-                       dst_down = dst_ahead - '0 0 1500';
+                       offset = (vdist(this.velocity, >, 32) ? this.velocity * 0.5 : v_forward * 32);
+                       vector dst_ahead = this.origin + this.view_ofs + offset;
+                       vector dst_down = dst_ahead - '0 0 3000';
 
                        // Look ahead
                        traceline(this.origin + this.view_ofs, dst_ahead, true, NULL);
@@ -788,13 +790,15 @@ void havocbot_movetogoal(entity this)
                        // (only when the bot is on the ground or jumping intentionally)
                        this.aistatus &= ~AI_STATUS_DANGER_AHEAD;
 
+                       bool unreachable = false;
+                       s = CONTENT_SOLID;
                        if(trace_fraction == 1 && this.jumppadcount == 0 && !this.goalcurrent.wphardwired )
-                       if((IS_ONGROUND(this)) || (this.aistatus & AI_STATUS_RUNNING) || PHYS_INPUT_BUTTON_JUMP(this))
+                       if((IS_ONGROUND(this)) || (this.aistatus & AI_STATUS_RUNNING) || (this.aistatus & AI_STATUS_ROAMING) || PHYS_INPUT_BUTTON_JUMP(this))
                        {
                                // Look downwards
                                traceline(dst_ahead , dst_down, true, NULL);
-                       //      te_lightning2(NULL, this.origin, dst_ahead);    // Draw "ahead" look
-                       //      te_lightning2(NULL, dst_ahead, dst_down);               // Draw "downwards" look
+                               //te_lightning2(NULL, this.origin + this.view_ofs, dst_ahead); // Draw "ahead" look
+                               //te_lightning2(NULL, dst_ahead, dst_down); // Draw "downwards" look
                                if(trace_endpos.z < this.origin.z + this.mins.z)
                                {
                                        s = pointcontents(trace_endpos + '0 0 1');
@@ -803,17 +807,20 @@ void havocbot_movetogoal(entity this)
                                                evadelava = normalize(this.velocity) * -1;
                                        else if (s == CONTENT_SKY)
                                                evadeobstacle = normalize(this.velocity) * -1;
-                                       else if (!boxesoverlap(dst_ahead - this.view_ofs + this.mins, dst_ahead - this.view_ofs + this.maxs,
-                                                               this.goalcurrent.absmin, this.goalcurrent.absmax))
+                                       else if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
                                        {
-                                               // if ain't a safe goal with "holes" (like the jumpad on soylent)
-                                               // and there is a trigger_hurt below
-                                               if(tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
+                                               // the traceline check isn't enough but is good as optimization,
+                                               // when not true (most of the time) this tracebox call is avoided
+                                               tracebox(dst_ahead, this.mins, this.maxs, dst_down, true, this);
+                                               if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
                                                {
-                                                       // Remove dangerous dynamic goals from stack
-                                                       LOG_TRACE("bot ", this.netname, " avoided the goal ", this.goalcurrent.classname, " ", etos(this.goalcurrent), " because it led to a dangerous path; goal stack cleared");
-                                                       navigation_clearroute(this);
-                                                       return;
+                                                       if (gco.z > this.origin.z + jumpstepheightvec.z)
+                                                       {
+                                                               // the goal is probably on an upper platform, assume bot can't get there
+                                                               unreachable = true;
+                                                       }
+                                                       else
+                                                               evadelava = normalize(this.velocity) * -1;
                                                }
                                        }
                                }
@@ -824,8 +831,17 @@ void havocbot_movetogoal(entity this)
                        evadelava.z = 0;
                        makevectors(this.v_angle.y * '0 1 0');
 
-                       if(evadeobstacle!='0 0 0'||evadelava!='0 0 0')
+                       if(evadeobstacle || evadelava || (s == CONTENT_WATER))
+                       {
                                this.aistatus |= AI_STATUS_DANGER_AHEAD;
+                               if(IS_PLAYER(this.goalcurrent))
+                                       unreachable = true;
+                       }
+                       if(unreachable)
+                       {
+                               navigation_clearroute(this);
+                               this.bot_strategytime = 0;
+                       }
                }
 
                dodge = havocbot_dodge(this);
@@ -1023,13 +1039,10 @@ float havocbot_chooseweapon_checkreload(entity this, .entity weaponentity, int n
        // if this weapon is scheduled for reloading, don't switch to it during combat
        if (this.(weaponentity).weapon_load[new_weapon] < 0)
        {
-               bool other_weapon_available = false;
                FOREACH(Weapons, it != WEP_Null, LAMBDA(
                        if(it.wr_checkammo1(it, this, weaponentity) + it.wr_checkammo2(it, this, weaponentity))
-                               other_weapon_available = true;
+                               return true; // other weapon available
                ));
-               if(other_weapon_available)
-                       return true;
        }
 
        return false;