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"
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);
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;
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?
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;
* 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;
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)
{
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
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)
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;
});
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;
}
}
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)
}
// 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);
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');
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 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))
+ 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;
}
}
}
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)
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))
{
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)