From 6ae3313187027d7a8f68893ed0185f46a24b1236 Mon Sep 17 00:00:00 2001 From: terencehill Date: Mon, 10 Feb 2020 19:17:08 +0100 Subject: [PATCH] Bot AI: make bots faster by refactoring bunnyhop algorithm --- qcsrc/server/bot/default/cvars.qh | 8 +- qcsrc/server/bot/default/havocbot/havocbot.qc | 164 ++++++------------ qcsrc/server/bot/default/havocbot/havocbot.qh | 2 - qcsrc/server/bot/default/navigation.qc | 21 ++- xonotic-server.cfg | 8 +- 5 files changed, 79 insertions(+), 124 deletions(-) diff --git a/qcsrc/server/bot/default/cvars.qh b/qcsrc/server/bot/default/cvars.qh index 55bc9c8f3..682555238 100644 --- a/qcsrc/server/bot/default/cvars.qh +++ b/qcsrc/server/bot/default/cvars.qh @@ -19,10 +19,12 @@ float autocvar_bot_ai_aimskill_order_mix_3th; float autocvar_bot_ai_aimskill_order_mix_4th; float autocvar_bot_ai_aimskill_order_mix_5th; float autocvar_bot_ai_aimskill_think; -float autocvar_bot_ai_bunnyhop_firstjumpdelay; float autocvar_bot_ai_bunnyhop_skilloffset; -float autocvar_bot_ai_bunnyhop_startdistance; -float autocvar_bot_ai_bunnyhop_stopdistance; +float autocvar_bot_ai_bunnyhop_dir_deviation_max; +float autocvar_bot_ai_bunnyhop_downward_pitch_max; +float autocvar_bot_ai_bunnyhop_turn_angle_min; +float autocvar_bot_ai_bunnyhop_turn_angle_max; +float autocvar_bot_ai_bunnyhop_turn_angle_reduction; float autocvar_bot_ai_chooseweaponinterval; string autocvar_bot_ai_custom_weapon_priority_close; string autocvar_bot_ai_custom_weapon_priority_distances; diff --git a/qcsrc/server/bot/default/havocbot/havocbot.qc b/qcsrc/server/bot/default/havocbot/havocbot.qc index 4dff6733e..cc8620173 100644 --- a/qcsrc/server/bot/default/havocbot/havocbot.qc +++ b/qcsrc/server/bot/default/havocbot/havocbot.qc @@ -198,6 +198,62 @@ void havocbot_ai(entity this) } } +void havocbot_bunnyhop(entity this, vector dir) +{ + bool can_run = false; + if (!(this.aistatus & AI_STATUS_ATTACKING) && this.goalcurrent && !IS_PLAYER(this.goalcurrent) + && vdist(vec2(this.velocity), >=, autocvar_sv_maxspeed) && !(this.aistatus & AI_STATUS_DANGER_AHEAD) + && this.waterlevel <= WATERLEVEL_WETFEET && !IS_DUCKED(this) + && IS_ONGROUND(this) && !(this.goalcurrent_prev && (this.goalcurrent_prev.wpflags & WAYPOINTFLAG_JUMP))) + { + vector vel_angles = vectoangles(this.velocity); + vector deviation = vel_angles - vectoangles(dir); + while (deviation.y < -180) deviation.y = deviation.y + 360; + while (deviation.y > 180) deviation.y = deviation.y - 360; + if (fabs(deviation.y) < autocvar_bot_ai_bunnyhop_dir_deviation_max) + { + vector gco = get_closer_dest(this.goalcurrent, this.origin); + float vel = vlen(vec2(this.velocity)); + + // with the current physics, jump distance grows linearly with the speed + float jump_distance = 52.661 + 0.606 * vel; + jump_distance += this.origin.z - gco.z; // roughly take into account vertical distance too + if (vdist(vec2(gco - this.origin), >, max(0, jump_distance))) + can_run = true; + else if (!(this.goalcurrent.wpflags & WAYPOINTFLAG_JUMP) + && !(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT) + && this.goalstack01 && !wasfreed(this.goalstack01) && !(this.goalstack01.wpflags & WAYPOINTFLAG_JUMP) + && vdist(vec2(gco - this.goalstack01.origin), >, 70)) + { + vector gno = (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5; + vector ang = vectoangles(gco - this.origin); + deviation = vectoangles(gno - gco) - vel_angles; + while (deviation.y < -180) deviation.y = deviation.y + 360; + while (deviation.y > 180) deviation.y = deviation.y - 360; + + float max_turn_angle = autocvar_bot_ai_bunnyhop_turn_angle_max; + max_turn_angle -= autocvar_bot_ai_bunnyhop_turn_angle_reduction * ((vel - autocvar_sv_maxspeed) / autocvar_sv_maxspeed); + if ((ang.x < 90 || ang.x > 360 - autocvar_bot_ai_bunnyhop_downward_pitch_max) + && fabs(deviation.y) < max(autocvar_bot_ai_bunnyhop_turn_angle_min, max_turn_angle)) + { + can_run = true; + } + } + } + } + + if (can_run) + { + PHYS_INPUT_BUTTON_JUMP(this) = true; + this.aistatus |= AI_STATUS_RUNNING; + } + else + { + if (IS_ONGROUND(this) || this.waterlevel > WATERLEVEL_WETFEET) + this.aistatus &= ~AI_STATUS_RUNNING; + } +} + void havocbot_keyboard_movement(entity this, vector destorg) { if(time <= this.havocbot_keyboardtime) @@ -268,114 +324,6 @@ void havocbot_keyboard_movement(entity this, vector destorg) CS(this).movement = CS(this).movement + (keyboard - CS(this).movement) * blend; } -void havocbot_bunnyhop(entity this, vector dir) -{ - // Don't jump when attacking - if(this.aistatus & AI_STATUS_ATTACKING) - return; - - if (!this.goalcurrent || IS_PLAYER(this.goalcurrent)) - return; - - 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; - this.bot_canruntogoal = 0; - this.bot_timelastseengoal = 0; - return; - } - - if(this.waterlevel > WATERLEVEL_WETFEET || IS_DUCKED(this)) - { - this.aistatus &= ~AI_STATUS_RUNNING; - return; - } - - if(this.bot_lastseengoal != this.goalcurrent && !(this.aistatus & AI_STATUS_RUNNING)) - { - this.bot_canruntogoal = 0; - this.bot_timelastseengoal = 0; - } - - // Run only to visible goals - if(IS_ONGROUND(this)) - if(vdist(vec2(this.velocity), >=, autocvar_sv_maxspeed)) - if(checkpvs(this.origin + this.view_ofs, this.goalcurrent)) - { - this.bot_lastseengoal = this.goalcurrent; - - // seen it before - if(this.bot_timelastseengoal) - { - vector gco = get_closer_dest(this.goalcurrent, this.origin); - // for a period of time - if(time - this.bot_timelastseengoal > autocvar_bot_ai_bunnyhop_firstjumpdelay) - { - bool checkdistance = true; - - // don't run if it is too close - if(this.bot_canruntogoal==0) - { - if(vdist(this.origin - gco, >, autocvar_bot_ai_bunnyhop_startdistance)) - this.bot_canruntogoal = 1; - else - this.bot_canruntogoal = -1; - } - - if(this.bot_canruntogoal != 1) - return; - - if(this.aistatus & AI_STATUS_ROAMING) - if(this.goalcurrent.classname == "waypoint") - if (!(this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL)) - if(fabs(gco.z - this.origin.z) < this.maxs.z - this.mins.z) - if (this.goalstack01 && !wasfreed(this.goalstack01)) - if (!(this.goalstack01.wpflags & WAYPOINTFLAG_JUMP)) - { - vector gno = (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5; - vector deviation = vectoangles(gno - this.origin) - vectoangles(gco - this.origin); - while (deviation.y < -180) deviation.y = deviation.y + 360; - while (deviation.y > 180) deviation.y = deviation.y - 360; - - if(fabs(deviation.y) < 20) - if(vlen2(this.origin - gco) < vlen2(this.origin - gno)) - if(fabs(gno.z - gco.z) < this.maxs.z - this.mins.z) - { - if(vdist(gco - gno, >, autocvar_bot_ai_bunnyhop_startdistance)) - if(checkpvs(this.origin + this.view_ofs, this.goalstack01)) - { - checkdistance = false; - } - } - } - - if(checkdistance) - { - this.aistatus &= ~AI_STATUS_RUNNING; - // increase stop distance in case the goal is on a slope or a lower platform - if(vdist(this.origin - gco, >, autocvar_bot_ai_bunnyhop_stopdistance + (this.origin.z - gco.z))) - PHYS_INPUT_BUTTON_JUMP(this) = true; - } - else - { - this.aistatus |= AI_STATUS_RUNNING; - PHYS_INPUT_BUTTON_JUMP(this) = true; - } - } - } - else - { - this.bot_timelastseengoal = time; - } - } - else - { - this.bot_timelastseengoal = 0; - } -} - // return true when bot isn't getting closer to the current goal bool havocbot_checkgoaldistance(entity this, vector gco) { diff --git a/qcsrc/server/bot/default/havocbot/havocbot.qh b/qcsrc/server/bot/default/havocbot/havocbot.qh index d1a36a117..a45e072c7 100644 --- a/qcsrc/server/bot/default/havocbot/havocbot.qh +++ b/qcsrc/server/bot/default/havocbot/havocbot.qh @@ -12,8 +12,6 @@ .float havocbot_keyboardtime; .float havocbot_ducktime; -.float bot_timelastseengoal; -.float bot_canruntogoal; .float bot_chooseweapontime; .float rocketjumptime; .float nextaim; diff --git a/qcsrc/server/bot/default/navigation.qc b/qcsrc/server/bot/default/navigation.qc index fad601e35..dcf2f304c 100644 --- a/qcsrc/server/bot/default/navigation.qc +++ b/qcsrc/server/bot/default/navigation.qc @@ -1716,11 +1716,15 @@ int navigation_poptouchedgoals(entity this) } // Loose goal touching check when running - if(this.aistatus & AI_STATUS_RUNNING) - if(this.goalcurrent.classname=="waypoint") - if(vdist(vec2(this.velocity), >=, autocvar_sv_maxspeed)) // if -really- running - { - if(vdist(this.origin - this.goalcurrent.origin, <, 150)) + // check goalstack01 to make sure waypoint isn't the final goal + if(this.aistatus & AI_STATUS_RUNNING && this.goalcurrent.classname == "waypoint" && !(this.goalcurrent.wpflags & WAYPOINTFLAG_JUMP) + && this.goalstack01 && !wasfreed(this.goalstack01) && vdist(vec2(this.velocity), >=, autocvar_sv_maxspeed)) + { + vector gco = this.goalcurrent.origin; + vector gno = this.goalstack01.origin; + float min_dist = 100; + if(vdist(this.origin - gco, <, min_dist) + || (vdist(this.origin + eZ * 0.5 * min_dist - gco, <, min_dist) && vlen2(this.origin - gno) < vlen2(gco - gno))) { traceline(this.origin + this.view_ofs , this.goalcurrent.origin, true, NULL); if(trace_fraction==1) @@ -1784,7 +1788,8 @@ entity navigation_get_really_close_waypoint(entity this) wp = this.goalcurrent_prev; if(!wp) return NULL; - if(wp != this.goalcurrent_prev && vdist(wp.origin - this.origin, >, 50)) + float min_dist = ((this.aistatus & AI_STATUS_RUNNING) ? 100 : 50); + if(wp != this.goalcurrent_prev && vdist(wp.origin - this.origin, >, min_dist)) { wp = this.goalcurrent_prev; if(!wp) @@ -1796,12 +1801,12 @@ entity navigation_get_really_close_waypoint(entity this) if(!wp) return NULL; } - if(vdist(wp.origin - this.origin, >, 50)) + if(vdist(wp.origin - this.origin, >, min_dist)) { wp = NULL; IL_EACH(g_waypoints, !(it.wpflags & (WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_JUMP)), { - if(vdist(it.origin - this.origin, <, 50)) + if(vdist(it.origin - this.origin, <, min_dist)) { wp = it; break; diff --git a/xonotic-server.cfg b/xonotic-server.cfg index 87ab27076..a763b5d71 100644 --- a/xonotic-server.cfg +++ b/xonotic-server.cfg @@ -152,9 +152,11 @@ set bot_ai_weapon_combo_threshold 0.4 "Try to make a combo N seconds after the l set bot_ai_friends_aware_pickup_radius "500" "Bots will not pickup items if a team mate is this distance near the item" set bot_ai_ignoregoal_timeout 3 "Ignore goals making bots to get stuck in front of a wall for N seconds" set bot_ai_bunnyhop_skilloffset 7 "Bots with skill equal or greater than this value will perform the \"bunnyhop\" technique" -set bot_ai_bunnyhop_startdistance 200 "Run to goals located further than this distance" -set bot_ai_bunnyhop_stopdistance 300 "Stop jumping after reaching this distance to the goal" -set bot_ai_bunnyhop_firstjumpdelay 0.2 "Start running to the goal only if it was seen for more than N seconds" +set bot_ai_bunnyhop_dir_deviation_max 20 "bunnyhop if speed - direction deviation is <= this amount" +set bot_ai_bunnyhop_downward_pitch_max 30 "bunnyhop if downard pitch towards the next waypoint is <= this amount" +set bot_ai_bunnyhop_turn_angle_max 80 "bunnyhop if next turn angle is <= this amount at walk speed (sv_maxspeed)" +set bot_ai_bunnyhop_turn_angle_min 4 "bunnyhop regardless of speed if next turn angle is <= this amount" +set bot_ai_bunnyhop_turn_angle_reduction 40 "linearly reduce max turn angle by this amount when speed increases by sv_maxspeed" set bot_god 0 "god mode for bots" set bot_ai_navigation_jetpack 0 "Enable bots to navigate maps using the jetpack" set bot_ai_navigation_jetpack_mindistance 3500 "Bots will try fly to objects located farther than this distance" -- 2.39.2