Commit 19eeff94 authored by Mario's avatar Mario

Merge branch 'terencehill/bot_fix' into 'master'

Bot fixes

See merge request !398
parents 514e52ef e9139b87
Pipeline #5605119 passed with stages
in 17 minutes and 57 seconds
......@@ -30,7 +30,7 @@ test_sv_game:
- wget -O data/maps/g-23.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/g-23.waypoints.cache
- wget -O data/maps/g-23.waypoints.hardwired https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/g-23.waypoints.hardwired
- make
- EXPECT=b58f9c7587f1a14e5c52176d4e62a9fb
- EXPECT=37be9dc5489925451eb497390fdf58b9
- HASH=$(${ENGINE} -noconfig -nohome +exec serverbench.cfg
| tee /dev/stderr
| grep '^:'
......
......@@ -382,7 +382,7 @@ set bot_ai_friends_aware_pickup_radius "500" "Bots will not pickup items if a te
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 200 "Stop jumping after reaching this distance to the goal"
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_god 0 "god mode for bots"
set bot_ai_navigation_jetpack 0 "Enable bots to navigate maps using the jetpack"
......
......@@ -68,7 +68,7 @@ REGISTER_ITEM(ArmorBig, Armor) {
this.m_color = '0 1 0';
this.m_waypoint = _("Big armor");
#ifdef SVQC
this.m_botvalue = 20000; // FIXME: higher than BOT_PICKUP_RATING_HIGH?
this.m_botvalue = BOT_PICKUP_RATING_HIGH;
this.m_itemid = IT_ARMOR;
this.m_respawntime = GET(g_pickup_respawntime_long);
this.m_respawntimejitter = GET(g_pickup_respawntimejitter_long);
......
......@@ -924,17 +924,20 @@ float generic_pickupevalfunc(entity player, entity item) {return item.bot_pickup
float weapon_pickupevalfunc(entity player, entity item)
{
float c;
int rating = item.bot_pickupbasevalue;
// See if I have it already
if(item.weapons & ~player.weapons)
if(player.weapons & item.weapons)
{
// If I can pick it up
if(!item.spawnshieldtime)
c = 0;
else if(player.ammo_cells || player.ammo_shells || player.ammo_plasma || player.ammo_nails || player.ammo_rockets)
{
if (rating > 0)
rating = BOT_PICKUP_RATING_LOW * 0.5 * (1 + rating / BOT_PICKUP_RATING_HIGH);
// Skilled bots will grab more
c = bound(0, skill / 10, 1) * 0.5;
c = 1 + bound(0, skill / 10, 1) * 0.5;
}
else
c = 0;
......@@ -942,58 +945,74 @@ float weapon_pickupevalfunc(entity player, entity item)
else
c = 1;
if (c <= 0)
return 0;
// If custom weapon priorities for bots is enabled rate most wanted weapons higher
if( bot_custom_weapon && c )
{
// Find the highest position on any range
int position = -1;
for (int j = 0; j < WEP_LAST ; ++j){
if(
bot_weapons_far[j] == item.weapon ||
bot_weapons_mid[j] == item.weapon ||
bot_weapons_close[j] == item.weapon
)
if(bot_custom_weapon)
{
int best_ratio = 0;
int missing = 0;
// evaluate weapon usefulness in all ranges
for(int list = 0; list < 3; list++)
{
int position = -1;
int wep_count = 0;
int wpn = item.weapon;
for (int j = 0; j < WEP_LAST; ++j)
{
position = j;
break;
int list_wpn = 0;
if (list == 0) list_wpn = bot_weapons_far[j];
else if (list == 1) list_wpn = bot_weapons_mid[j];
else list_wpn = bot_weapons_close[j];
if (weaponsInMap & Weapons_from(list_wpn).m_wepset) // only if available
{
if (list_wpn > 0)
wep_count++;
if (position == -1 && list_wpn == wpn)
position = wep_count;
}
}
if (position == -1)
{
missing++;
position = wep_count; // if missing assume last
}
if (wep_count)
{
if (!best_ratio || position / wep_count < best_ratio)
best_ratio = position / wep_count;
}
}
// Rate it
if (position >= 0 )
{
position = WEP_LAST - position;
// item.bot_pickupbasevalue is overwritten here
return (BOT_PICKUP_RATING_LOW + ( (BOT_PICKUP_RATING_HIGH - BOT_PICKUP_RATING_LOW) * (position / WEP_LAST ))) * c;
}
if (missing < 3 && best_ratio)
c = c - best_ratio * 0.3;
}
return item.bot_pickupbasevalue * c;
return rating * c;
}
float commodity_pickupevalfunc(entity player, entity item)
{
float c;
float need_shells = false, need_nails = false, need_rockets = false, need_cells = false, need_plasma = false, need_fuel = false;
c = 0;
bool need_shells = false, need_nails = false, need_rockets = false, need_cells = false, need_plasma = false, need_fuel = false;
float c = 0;
// Detect needed ammo
FOREACH(Weapons, it != WEP_Null, {
if(!(player.weapons & (it.m_wepset)))
continue;
if(it.items & ITEM_Shells.m_itemid)
need_shells = true;
else if(it.items & ITEM_Bullets.m_itemid)
need_nails = true;
else if(it.items & ITEM_Rockets.m_itemid)
need_rockets = true;
else if(it.items & ITEM_Cells.m_itemid)
need_cells = true;
else if(it.items & ITEM_Plasma.m_itemid)
need_plasma = true;
else if(it.items & ITEM_JetpackFuel.m_itemid)
need_fuel = true;
switch(it.ammo_field)
{
case ammo_shells: need_shells = true; break;
case ammo_nails: need_nails = true; break;
case ammo_rockets: need_rockets = true; break;
case ammo_cells: need_cells = true; break;
case ammo_plasma: need_plasma = true; break;
case ammo_fuel: need_fuel = true; break;
}
});
// TODO: figure out if the player even has the weapon this ammo is for?
......
......@@ -64,6 +64,8 @@ void bot_think(entity this)
this.flags |= FL_GODMODE;
this.bot_nextthink = this.bot_nextthink + autocvar_bot_ai_thinkinterval * pow(0.5, this.bot_aiskill);
if(this.bot_nextthink < time)
this.bot_nextthink = time + autocvar_bot_ai_thinkinterval * pow(0.5, this.bot_aiskill);
//if (this.bot_painintensity > 0)
// this.bot_painintensity = this.bot_painintensity - (skill + 1) * 40 * frametime;
......
......@@ -3,18 +3,18 @@
* Globals and Fields
*/
const int AI_STATUS_ROAMING = BIT(0); // Bot is just crawling the map. No enemies at sight
const int AI_STATUS_ATTACKING = BIT(1); // There are enemies at sight
const int AI_STATUS_RUNNING = BIT(2); // Bot is bunny hopping
const int AI_STATUS_DANGER_AHEAD = BIT(3); // There is lava/slime/trigger_hurt ahead
const int AI_STATUS_OUT_JUMPPAD = BIT(4); // Trying to get out of a "vertical" jump pad
const int AI_STATUS_OUT_WATER = BIT(5); // Trying to get out of water
const int AI_STATUS_WAYPOINT_PERSONAL_LINKING = BIT(6); // Waiting for the personal waypoint to be linked
const int AI_STATUS_WAYPOINT_PERSONAL_GOING = BIT(7); // Going to a personal waypoint
const int AI_STATUS_WAYPOINT_PERSONAL_REACHED = BIT(8); // Personal waypoint reached
const int AI_STATUS_JETPACK_FLYING = BIT(9);
const int AI_STATUS_JETPACK_LANDING = BIT(10);
const int AI_STATUS_STUCK = BIT(11); // Cannot reach any goal
const int AI_STATUS_ROAMING = BIT(0); // Bot is just crawling the map. No enemies at sight
const int AI_STATUS_ATTACKING = BIT(1); // There are enemies at sight
const int AI_STATUS_RUNNING = BIT(2); // Bot is bunny hopping
const int AI_STATUS_DANGER_AHEAD = BIT(3); // There is lava/slime/trigger_hurt ahead
const int AI_STATUS_OUT_JUMPPAD = BIT(4); // Trying to get out of a "vertical" jump pad
const int AI_STATUS_OUT_WATER = BIT(5); // Trying to get out of water
const int AI_STATUS_WAYPOINT_PERSONAL_LINKING = BIT(6); // Waiting for the personal waypoint to be linked
const int AI_STATUS_WAYPOINT_PERSONAL_GOING = BIT(7); // Going to a personal waypoint
const int AI_STATUS_WAYPOINT_PERSONAL_REACHED = BIT(8); // Personal waypoint reached
const int AI_STATUS_JETPACK_FLYING = BIT(9);
const int AI_STATUS_JETPACK_LANDING = BIT(10);
const int AI_STATUS_STUCK = BIT(11); // Cannot reach any goal
.float isbot; // true if this client is actually a bot
.int aistatus;
......
......@@ -29,13 +29,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)
{
......@@ -350,7 +343,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
......@@ -492,6 +486,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)
......@@ -504,7 +499,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;
});
......@@ -514,6 +509,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;
}
}
......@@ -642,8 +639,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)
......@@ -718,10 +714,8 @@ void havocbot_movetogoal(entity this)
}
// 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';
vector dst_ahead = this.origin + this.view_ofs + this.velocity * 0.5;
vector dst_down = dst_ahead - '0 0 3000';
// Look ahead
traceline(this.origin + this.view_ofs, dst_ahead, true, NULL);
......@@ -758,12 +752,12 @@ void havocbot_movetogoal(entity this)
this.aistatus &= ~AI_STATUS_DANGER_AHEAD;
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');
......@@ -772,17 +766,22 @@ 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
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);
this.bot_strategytime = 0;
}
else
evadelava = normalize(this.velocity) * -1;
}
}
}
......
......@@ -34,7 +34,7 @@ void havocbot_goalrating_items(entity this, float ratingscale, vector org, float
traceline(o, o + '0 0 -1500', true, NULL);
d = pointcontents(trace_endpos + '0 0 1');
if(d & CONTENT_WATER || d & CONTENT_SLIME || d & CONTENT_LAVA)
if(d == CONTENT_WATER || d == CONTENT_SLIME || d == CONTENT_LAVA)
continue;
if(tracebox_hits_trigger_hurt(it.origin, it.mins, it.maxs, trace_endpos))
continue;
......@@ -145,12 +145,12 @@ void havocbot_goalrating_enemyplayers(entity this, float ratingscale, vector org
continue;
// not falling
if((IS_ONGROUND(it)) == 0)
if(!IS_ONGROUND(it))
{
traceline(it.origin, it.origin + '0 0 -1500', true, NULL);
t = pointcontents(trace_endpos + '0 0 1');
if(t != CONTENT_SOLID )
if(t & CONTENT_WATER || t & CONTENT_SLIME || t & CONTENT_LAVA)
if(t == CONTENT_WATER || t == CONTENT_SLIME || t == CONTENT_LAVA)
continue;
if(tracebox_hits_trigger_hurt(it.origin, it.mins, it.maxs, trace_endpos))
continue;
......
......@@ -876,10 +876,15 @@ void navigation_poptouchedgoals(entity this)
m1 = org + this.mins;
m2 = org + this.maxs;
while(this.goalcurrent && wasfreed(this.goalcurrent))
navigation_poproute(this);
if(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
{
// 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)
if(time-this.lastteleporttime<(this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL)?2:0.15)
if(time - this.lastteleporttime < ((this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL) ? 2 : 0.15))
{
if(this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
if(this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL && this.goalcurrent.owner==this)
......@@ -913,14 +918,11 @@ void navigation_poptouchedgoals(entity this)
if(IS_PLAYER(this.goalcurrent))
navigation_poproute(this);
// aid for detecting jump pads better (distance based check fails sometimes)
if(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT && this.jumppadcount > 0 )
navigation_poproute(this);
// Loose goal touching check when running
if(this.aistatus & AI_STATUS_RUNNING)
if(this.speed >= autocvar_sv_maxspeed) // if -really- running
if(this.goalcurrent.classname=="waypoint")
if(!(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT))
{
if(vdist(this.origin - this.goalcurrent.origin, <, 150))
{
......@@ -942,6 +944,9 @@ void navigation_poptouchedgoals(entity this)
while (this.goalcurrent && boxesoverlap(m1, m2, this.goalcurrent.absmin, this.goalcurrent.absmax))
{
if((this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT))
break;
// Detect personal waypoints
if(this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
if(this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL && this.goalcurrent.owner==this)
......
......@@ -19,7 +19,10 @@ void thrown_wep_think(entity this)
{
this.SendFlags |= ISF_LOCATION;
this.oldorigin = this.origin;
this.bot_pickup = false;
}
else
this.bot_pickup = true;
this.owner = NULL;
float timeleft = this.savenextthink - time;
if(timeleft > 1)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment