if (navigation_goalrating_timeout(this))
{
navigation_goalrating_start(this);
- havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650);
+ havocbot_goalrating_enemyplayers(this, 10000, this.origin, 650);
havocbot_goalrating_ast_targets(this, 20000);
havocbot_goalrating_items(this, 15000, this.origin, 10000);
navigation_goalrating_end(this);
if (navigation_goalrating_timeout(this))
{
navigation_goalrating_start(this);
- havocbot_goalrating_enemyplayers(this, 20000, this.origin, 3000);
+ havocbot_goalrating_enemyplayers(this, 10000, this.origin, 3000);
havocbot_goalrating_ast_targets(this, 20000);
havocbot_goalrating_items(this, 15000, this.origin, 10000);
navigation_goalrating_end(this);
head = head.ctf_worldflagnext;
}
if (head)
+ {
+ if (head.ctf_status == FLAG_CARRY)
+ {
+ // adjust rating of our flag carrier depending on his health
+ head = head.tag_entity;
+ float f = bound(0, (head.health + head.armorvalue) / 100, 2) - 1;
+ ratingscale += ratingscale * f * 0.1;
+ }
navigation_routerating(this, head, ratingscale, 10000);
+ }
}
void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
{
+ // disabled because we always spawn waypoints for flags with waypoint_spawnforitem_force
+ /*
if (!bot_waypoints_for_items)
{
havocbot_goalrating_ctf_enemyflag(this, ratingscale);
return;
}
-
+ */
entity head;
head = havocbot_ctf_find_enemy_flag(this);
}
}
-void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
-{
- IL_EACH(g_items, it.bot_pickup,
- {
- // gather health and armor only
- if (it.solid)
- if (it.health || it.armorvalue)
- if (vdist(it.origin - org, <, sradius))
- {
- // get the value of the item
- float t = it.bot_pickupevalfunc(this, it) * 0.0001;
- if (t > 0)
- navigation_routerating(this, it, t * ratingscale, 500);
- }
- });
-}
-
void havocbot_ctf_reset_role(entity this)
{
float cdefense, cmiddle, coffense;
entity mf, ef;
- float c;
if(IS_DEAD(this))
return;
return;
}
- // if there is only me on the team switch to offense
- c = 0;
- FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), { ++c; });
+ // if there is no one else on the team switch to offense
+ int count = 0;
+ // don't check if this bot is a player since it isn't true when the bot is added to the server
+ FOREACH_CLIENT(it != this && IS_PLAYER(it) && SAME_TEAM(it, this), { ++count; });
- if(c==1)
+ if (count == 0)
{
havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
return;
}
+ else if (CS(this).jointime < time + 1)
+ {
+ // if bots spawn all at once set good default roles
+ if (count == 1)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
+ return;
+ }
+ else if (count == 2)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
+ return;
+ }
+ }
// Evaluate best position to take
// Count mates on middle position
havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
else
havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
+
+ // if bots spawn all at once assign them a more appropriated role after a while
+ if (CS(this).jointime < time + 1 && count > 2)
+ this.havocbot_role_timeout = time + 10 + random() * 10;
+}
+
+bool havocbot_ctf_is_basewaypoint(entity item)
+{
+ if (item.classname != "waypoint")
+ return false;
+
+ entity head = ctf_worldflaglist;
+ while (head)
+ {
+ if (item == head.bot_basewaypoint)
+ return true;
+ head = head.ctf_worldflagnext;
+ }
+ return false;
}
void havocbot_role_ctf_carrier(entity this)
{
navigation_goalrating_start(this);
+ // role: carrier
+ entity mf = havocbot_ctf_find_flag(this);
+ vector base_org = mf.dropped_origin;
+ float base_rating = (mf.ctf_status == FLAG_BASE) ? 10000 : (vdist(this.origin - base_org, >, 100) ? 2000 : 1000);
if(ctf_oneflag)
- havocbot_goalrating_ctf_enemybase(this, 50000);
+ havocbot_goalrating_ctf_enemybase(this, base_rating);
else
- havocbot_goalrating_ctf_ourbase(this, 50000);
+ havocbot_goalrating_ctf_ourbase(this, base_rating);
+
+ // start collecting items very close to the bot but only inside of own base radius
+ if (vdist(this.origin - base_org, <, havocbot_middlepoint_radius))
+ havocbot_goalrating_items(this, 15000, this.origin, min(500, havocbot_middlepoint_radius * 0.5));
- if(this.health<100)
- havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
+ havocbot_goalrating_items(this, 10000, base_org, havocbot_middlepoint_radius * 0.5);
navigation_goalrating_end(this);
navigation_goalrating_timeout_set(this);
- entity head = ctf_worldflaglist;
- while (head)
- {
- if (this.goalentity == head.bot_basewaypoint)
- {
- this.goalentity_lock_timeout = time + 5;
- break;
- }
- head = head.ctf_worldflagnext;
- }
+ entity goal = this.goalentity;
+ if (havocbot_ctf_is_basewaypoint(goal) && vdist(goal.origin - this.origin, <, 100))
+ this.goalentity_lock_timeout = time + ((this.bot_aimtarg) ? 2 : 3);
- if (this.goalentity)
+ if (goal)
this.havocbot_cantfindflag = time + 10;
else if (time > this.havocbot_cantfindflag)
{
this.havocbot_role_timeout = 0;
return;
}
+ if (ef.ctf_status == FLAG_DROPPED)
+ {
+ navigation_goalrating_timeout_expire(this, 1);
+ return;
+ }
// If the flag carrier reached the base switch to defense
mf = havocbot_ctf_find_flag(this);
- if(mf.ctf_status!=FLAG_BASE)
- if(vdist(ef.origin - mf.dropped_origin, <, 300))
+ if (mf.ctf_status != FLAG_BASE && vdist(ef.origin - mf.dropped_origin, <, 900))
{
havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
return;
{
navigation_goalrating_start(this);
- havocbot_goalrating_ctf_enemyflag(this, 30000);
- havocbot_goalrating_ctf_ourstolenflag(this, 40000);
- havocbot_goalrating_items(this, 10000, this.origin, 10000);
+ // role: escort
+ havocbot_goalrating_ctf_enemyflag(this, 10000);
+ havocbot_goalrating_ctf_ourstolenflag(this, 6000);
+ havocbot_goalrating_items(this, 21000, this.origin, 10000);
navigation_goalrating_end(this);
}
}
- // About to fail, switch to middlefield
- if(this.health<50)
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
- return;
- }
-
// Set the role timeout if necessary
if (!this.havocbot_role_timeout)
this.havocbot_role_timeout = time + 120;
{
navigation_goalrating_start(this);
- havocbot_goalrating_ctf_ourstolenflag(this, 50000);
- havocbot_goalrating_ctf_enemybase(this, 20000);
- havocbot_goalrating_items(this, 5000, this.origin, 1000);
- havocbot_goalrating_items(this, 1000, this.origin, 10000);
+ // role: offense
+ havocbot_goalrating_ctf_ourstolenflag(this, 10000);
+ havocbot_goalrating_ctf_enemybase(this, 10000);
+ havocbot_goalrating_items(this, 22000, this.origin, 10000);
navigation_goalrating_end(this);
if (navigation_goalrating_timeout(this))
{
- float rt_radius;
- rt_radius = 10000;
+ const float RT_RADIUS = 10000;
navigation_goalrating_start(this);
- havocbot_goalrating_ctf_ourstolenflag(this, 50000);
- havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
- havocbot_goalrating_ctf_enemybase(this, 30000);
- havocbot_goalrating_items(this, 500, this.origin, rt_radius);
+ // role: retriever
+ havocbot_goalrating_ctf_ourstolenflag(this, 10000);
+ havocbot_goalrating_ctf_droppedflags(this, 12000, this.origin, RT_RADIUS);
+ havocbot_goalrating_ctf_enemybase(this, 8000);
+ entity ef = havocbot_ctf_find_enemy_flag(this);
+ vector enemy_base_org = ef.dropped_origin;
+ // start collecting items very close to the bot but only inside of enemy base radius
+ if (vdist(this.origin - enemy_base_org, <, havocbot_middlepoint_radius))
+ havocbot_goalrating_items(this, 27000, this.origin, min(500, havocbot_middlepoint_radius * 0.5));
+ havocbot_goalrating_items(this, 18000, this.origin, havocbot_middlepoint_radius);
navigation_goalrating_end(this);
navigation_goalrating_start(this);
- havocbot_goalrating_ctf_ourstolenflag(this, 50000);
- havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
- havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_middlepoint_radius * 0.5);
- havocbot_goalrating_items(this, 5000, org, havocbot_middlepoint_radius * 0.5);
- havocbot_goalrating_items(this, 2500, this.origin, 10000);
- havocbot_goalrating_ctf_enemybase(this, 2500);
+ // role: middle
+ havocbot_goalrating_ctf_ourstolenflag(this, 8000);
+ havocbot_goalrating_ctf_droppedflags(this, 9000, this.origin, 10000);
+ havocbot_goalrating_enemyplayers(this, 25000, org, havocbot_middlepoint_radius * 0.5);
+ havocbot_goalrating_items(this, 25000, org, havocbot_middlepoint_radius * 0.5);
+ havocbot_goalrating_items(this, 18000, this.origin, 10000);
+ havocbot_goalrating_ctf_enemybase(this, 3000);
navigation_goalrating_end(this);
+ entity goal = this.goalentity;
+ if (havocbot_ctf_is_basewaypoint(goal) && vdist(goal.origin - this.origin, <, 100))
+ this.goalentity_lock_timeout = time + 2;
+
navigation_goalrating_timeout_set(this);
}
}
}
});
+ // role: defense
if(closestplayer)
if(DIFF_TEAM(closestplayer, this))
if(vdist(org - this.origin, >, 1000))
if(checkpvs(this.origin,closestplayer)||random()<0.5)
- havocbot_goalrating_ctf_ourbase(this, 30000);
+ havocbot_goalrating_ctf_ourbase(this, 10000);
- havocbot_goalrating_ctf_ourstolenflag(this, 20000);
- havocbot_goalrating_ctf_droppedflags(this, 20000, org, havocbot_middlepoint_radius);
- havocbot_goalrating_enemyplayers(this, 15000, org, havocbot_middlepoint_radius);
- havocbot_goalrating_items(this, 10000, org, havocbot_middlepoint_radius);
- havocbot_goalrating_items(this, 5000, this.origin, 10000);
+ havocbot_goalrating_ctf_ourstolenflag(this, 5000);
+ havocbot_goalrating_ctf_droppedflags(this, 6000, org, havocbot_middlepoint_radius);
+ havocbot_goalrating_enemyplayers(this, 25000, org, havocbot_middlepoint_radius);
+ havocbot_goalrating_items(this, 25000, org, havocbot_middlepoint_radius);
+ havocbot_goalrating_items(this, 18000, this.origin, 10000);
navigation_goalrating_end(this);
navigation_goalrating_start(this);
havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
havocbot_goalrating_items(this, 8000, this.origin, 8000);
- //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000);
+ //havocbot_goalrating_enemyplayers(this, 1500, this.origin, 2000);
havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
navigation_goalrating_end(this);
{
navigation_goalrating_start(this);
havocbot_goalrating_items(this, 10000, this.origin, 10000);
- havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
+ havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
havocbot_goalrating_freeplayers(this, 9000, this.origin, 10000);
havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
navigation_goalrating_end(this);
{
navigation_goalrating_start(this);
havocbot_goalrating_items(this, 8000, this.origin, 10000);
- havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
+ havocbot_goalrating_enemyplayers(this, 5000, this.origin, 10000);
havocbot_goalrating_freeplayers(this, 20000, this.origin, 10000);
havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
navigation_goalrating_end(this);
{
navigation_goalrating_start(this);
havocbot_goalrating_items(this, 10000, this.origin, 10000);
- havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
+ havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
navigation_goalrating_end(this);
{
navigation_goalrating_start(this);
havocbot_goalrating_items(this, 10000, this.origin, 10000);
- havocbot_goalrating_enemyplayers(this, 1000, this.origin, 10000);
+ havocbot_goalrating_enemyplayers(this, 500, this.origin, 10000);
havocbot_goalrating_ball(this, 20000, this.origin);
navigation_goalrating_end(this);
navigation_goalrating_start(this);
if(kh_Key_AllOwnedByWhichTeam() == this.team)
- havocbot_goalrating_kh(this, 10, 0.1, 0.1); // bring home
+ havocbot_goalrating_kh(this, 10, 0.1, 0.05); // bring home
else
- havocbot_goalrating_kh(this, 4, 4, 1); // play defensively
+ havocbot_goalrating_kh(this, 4, 4, 0.5); // play defensively
navigation_goalrating_end(this);
key_owner_team = kh_Key_AllOwnedByWhichTeam();
if(key_owner_team == this.team)
- havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend key carriers
+ havocbot_goalrating_kh(this, 10, 0.1, 0.05); // defend key carriers
else if(key_owner_team == -1)
- havocbot_goalrating_kh(this, 4, 1, 0.1); // play defensively
+ havocbot_goalrating_kh(this, 4, 1, 0.05); // play defensively
else
- havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY
+ havocbot_goalrating_kh(this, 0.1, 0.1, 5); // ATTACK ANYWAY
navigation_goalrating_end(this);
key_owner_team = kh_Key_AllOwnedByWhichTeam();
if(key_owner_team == this.team)
- havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend anyway
+ havocbot_goalrating_kh(this, 10, 0.1, 0.05); // defend anyway
else if(key_owner_team == -1)
- havocbot_goalrating_kh(this, 0.1, 1, 4); // play offensively
+ havocbot_goalrating_kh(this, 0.1, 1, 2); // play offensively
else
- havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK! EMERGENCY!
+ havocbot_goalrating_kh(this, 0.1, 0.1, 5); // ATTACK! EMERGENCY!
navigation_goalrating_end(this);
int key_owner_team = kh_Key_AllOwnedByWhichTeam();
if(key_owner_team == this.team)
- havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend anyway
+ havocbot_goalrating_kh(this, 10, 0.1, 0.05); // defend anyway
else if(key_owner_team == -1)
- havocbot_goalrating_kh(this, 1, 10, 4); // prefer dropped keys
+ havocbot_goalrating_kh(this, 1, 10, 2); // prefer dropped keys
else
- havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY
+ havocbot_goalrating_kh(this, 0.1, 0.1, 5); // ATTACK ANYWAY
navigation_goalrating_end(this);
if (navigation_goalrating_timeout(this))
{
navigation_goalrating_start(this);
- havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650);
+ havocbot_goalrating_enemyplayers(this, 10000, this.origin, 650);
if(!havocbot_goalrating_ons_generator_attack(this, 20000))
havocbot_goalrating_ons_controlpoints_attack(this, 20000);
havocbot_goalrating_ons_offenseitems(this, 10000, this.origin, 10000);
#ifdef SVQC
ATTRIB(Powerup, m_mins, vector, '-16 -16 0');
ATTRIB(Powerup, m_maxs, vector, '16 16 80');
- ATTRIB(Powerup, m_botvalue, int, 20000);
+ ATTRIB(Powerup, m_botvalue, int, 11000);
ATTRIB(Powerup, m_itemflags, int, FL_POWERUP);
ATTRIB(Powerup, m_respawntime, float(), GET(g_pickup_respawntime_powerup));
ATTRIB(Powerup, m_respawntimejitter, float(), GET(g_pickup_respawntimejitter_powerup));
float dist, delta_t, blend;
vector desiredang, diffang;
+ this.bot_aimdir_executed = true;
+
//dprint("aim ", this.netname, ": old:", vtos(this.v_angle));
// make sure v_angle is sane first
this.v_angle_y = this.v_angle.y - floor(this.v_angle.y / 360) * 360;
.vector lag5_vec3;
.vector lag5_vec4;
+.bool bot_aimdir_executed;
.float bot_badaimtime;
.float bot_aimthinktime;
.float bot_prevaimtime;
// if dead, just wait until we can respawn
if (IS_DEAD(this))
{
+ if (bot_waypoint_queue_owner == this)
+ bot_waypoint_queue_owner = NULL;
+ this.aistatus = 0;
CS(this).movement = '0 0 0';
if (this.deadflag == DEAD_DEAD)
{
}
havocbot_aim(this);
lag_update(this);
+
+ this.bot_aimdir_executed = false;
+
if (this.bot_aimtarg)
{
this.aistatus |= AI_STATUS_ATTACKING;
{
this.aistatus |= AI_STATUS_ROAMING;
this.aistatus &= ~AI_STATUS_ATTACKING;
-
- vector now, next;
- float aimdistance,skillblend,distanceblend,blend;
-
- vector v = get_closer_dest(this.goalcurrent, this.origin);
- if(this.goalcurrent.wpisbox)
- {
- // avoid a glitch when bot is teleported but teleport waypoint isn't removed yet
- if(this.goalstack02 && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT
- && this.lastteleporttime > 0 && time - this.lastteleporttime < 0.15)
- v = (this.goalstack02.absmin + this.goalstack02.absmax) * 0.5;
- // aim to teleport origin if bot is inside teleport waypoint but hasn't touched the real teleport yet
- else if(boxesoverlap(this.goalcurrent.absmin, this.goalcurrent.absmax, this.origin, this.origin))
- v = this.goalcurrent.origin;
- }
- next = now = v - (this.origin + this.view_ofs);
- aimdistance = vlen(now);
-
- //dprint(this.goalstack01.classname,etos(this.goalstack01),"\n");
- if(
- this.goalstack01 != this && this.goalstack01 && !wasfreed(this.goalstack01) && ((this.aistatus & AI_STATUS_RUNNING) == 0) &&
- !(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
- )
- next = ((this.goalstack01.absmin + this.goalstack01.absmax) * 0.5) - (this.origin + this.view_ofs);
-
- skillblend=bound(0,(skill+this.bot_moveskill-2.5)*0.5,1); //lower skill player can't preturn
- distanceblend=bound(0,aimdistance/autocvar_bot_ai_keyboard_distance,1);
- blend = skillblend * (1-distanceblend);
- //v = (now * (distanceblend) + next * (1-distanceblend)) * (skillblend) + now * (1-skillblend);
- //v = now * (distanceblend) * (skillblend) + next * (1-distanceblend) * (skillblend) + now * (1-skillblend);
- //v = now * ((1-skillblend) + (distanceblend) * (skillblend)) + next * (1-distanceblend) * (skillblend);
- v = now + blend * (next - now);
- //dprint(etos(this), " ");
- //dprint(vtos(now), ":", vtos(next), "=", vtos(v), " (blend ", ftos(blend), ")\n");
- //v = now * (distanceblend) + next * (1-distanceblend);
- if (this.waterlevel < WATERLEVEL_SWIMMING)
- v.z = 0;
- //dprint("walk at:", vtos(v), "\n");
- //te_lightning2(NULL, this.origin, this.goalcurrent.origin);
- bot_aimdir(this, v, -1);
}
+
havocbot_movetogoal(this);
+ if (!this.bot_aimdir_executed && this.goalcurrent)
+ {
+ // Heading
+ vector dir = get_closer_dest(this.goalcurrent, this.origin);
+ dir -= this.origin + this.view_ofs;
+ dir.z = 0;
+ bot_aimdir(this, dir, -1);
+ }
// if the bot is not attacking, consider reloading weapons
if (!(this.aistatus & AI_STATUS_ATTACKING))
vector flatdir;
vector evadeobstacle;
vector evadelava;
+ float dodge_enemy_factor = 1;
float maxspeed;
//float dist;
vector dodge;
if(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
{
this.aistatus |= AI_STATUS_OUT_JUMPPAD;
- navigation_poptouchedgoals(this);
- return;
+ if(navigation_poptouchedgoals(this))
+ return;
}
else if(this.aistatus & AI_STATUS_OUT_JUMPPAD)
{
if (this.goalcurrent == this.goalentity && this.goalentity_lock_timeout > time)
locked_goal = true;
- navigation_shortenpath(this);
+ if (navigation_shortenpath(this))
+ {
+ if (vdist(this.origin - this.goalcurrent_prev.origin, <, 50)
+ && navigation_goalrating_timeout_can_be_anticipated(this))
+ navigation_goalrating_timeout_force(this);
+ }
+ bool goalcurrent_can_be_removed = false;
if (IS_MOVABLE(this.goalcurrent))
{
- if (IS_DEAD(this.goalcurrent))
+ // if is movable => not frozen
+ if (IS_DEAD(this.goalcurrent) || (this.goalentity_shouldbefrozen && this.goalentity == this.goalcurrent))
{
+ goalcurrent_can_be_removed = true;
+ // don't remove if not visible
if (checkpvs(this.origin + this.view_ofs, this.goalcurrent))
{
navigation_goalrating_timeout_force(this);
{
if (this.goalcurrent)
{
- if (IS_MOVABLE(this.goalcurrent) && IS_DEAD(this.goalcurrent))
+ if (goalcurrent_can_be_removed)
{
// remove even if not visible
navigation_goalrating_timeout_force(this);
bool bunnyhop_forbidden = false;
vector destorg = get_closer_dest(this.goalcurrent, this.origin);
-
- // in case bot ends up inside the teleport waypoint without touching
- // the teleport itself, head to the teleport origin
- if(this.goalcurrent.wpisbox && boxesoverlap(this.goalcurrent.absmin, this.goalcurrent.absmax, this.origin + eZ * this.mins.z, this.origin + eZ * this.maxs.z))
+ if (this.jumppadcount && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
{
+ // if bot used the jumppad, push towards jumppad origin until jumppad waypoint gets removed
+ destorg = this.goalcurrent.origin;
+ }
+ else if (this.goalcurrent.wpisbox && boxesoverlap(this.goalcurrent.absmin, this.goalcurrent.absmax, this.origin + eZ * this.mins.z, this.origin + eZ * this.maxs.z))
+ {
+ // if bot is inside the teleport waypoint, head to teleport origin until teleport gets used
bunnyhop_forbidden = true;
destorg = this.goalcurrent.origin;
if(destorg.z > this.origin.z)
// jump if going toward an obstacle that doesn't look like stairs we
// can walk up directly
vector deviation = '0 0 0';
- if (this.velocity)
+ float current_speed = vlen(vec2(this.velocity));
+ if (current_speed < maxspeed * 0.2)
+ current_speed = maxspeed * 0.2;
+ else
{
deviation = vectoangles(diff) - vectoangles(this.velocity);
while (deviation.y < -180) deviation.y += 360;
while (deviation.y > 180) deviation.y -= 360;
}
+ float turning = false;
vector flat_diff = vec2(diff);
- offset = max(32, vlen(vec2(this.velocity)) * cos(deviation.y * DEG2RAD) * 0.2) * flatdir;
+ offset = max(32, current_speed * cos(deviation.y * DEG2RAD) * 0.3) * flatdir;
vector actual_destorg = this.origin + offset;
- if (!this.goalstack01 || this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
+ if (!this.goalstack01 || this.goalcurrent.wpflags & (WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_LADDER))
{
if (vlen2(flat_diff) < vlen2(offset))
{
{
vector next_goal_org = (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5;
vector next_dir = normalize(vec2(next_goal_org - destorg));
- float next_dist = vlen(vec2(this.origin + offset - destorg));
- actual_destorg = vec2(destorg) + next_dist * next_dir;
+ float dist = vlen(vec2(this.origin + offset - destorg));
+ // if current and next goal are close to each other make sure
+ // actual_destorg isn't set beyond next_goal_org
+ if (dist ** 2 > vlen2(vec2(next_goal_org - destorg)))
+ actual_destorg = next_goal_org;
+ else
+ actual_destorg = vec2(destorg) + dist * next_dir;
actual_destorg.z = this.origin.z;
+ turning = true;
}
- tracebox(this.origin, this.mins, this.maxs, actual_destorg, false, this);
- if (trace_fraction < 1)
- if (trace_plane_normal.z < 0.7)
+ LABEL(jump_check);
+ dir = flatdir = normalize(actual_destorg - this.origin);
+
+ if (turning || fabs(deviation.y) < 50) // don't even try to jump if deviation is too high
{
- s = trace_fraction;
- tracebox(this.origin + stepheightvec, this.mins, this.maxs, actual_destorg + stepheightvec, false, this);
- if (trace_fraction < s + 0.01)
- if (trace_plane_normal.z < 0.7)
+ tracebox(this.origin, this.mins, this.maxs, actual_destorg, false, this);
+ if (trace_fraction < 1 && trace_plane_normal.z < 0.7)
{
s = trace_fraction;
- tracebox(this.origin + jumpstepheightvec, this.mins, this.maxs, actual_destorg + jumpstepheightvec, false, this);
- if (trace_fraction > s)
- PHYS_INPUT_BUTTON_JUMP(this) = true;
+ tracebox(this.origin + stepheightvec, this.mins, this.maxs, actual_destorg + stepheightvec, false, this);
+ if (trace_fraction < s + 0.01 && trace_plane_normal.z < 0.7)
+ {
+ // found an obstacle
+ if (turning && fabs(deviation.y) > 5)
+ {
+ // check if the obstacle is still there without turning
+ actual_destorg = destorg;
+ turning = false;
+ this.bot_tracewalk_time = time + 0.25;
+ goto jump_check;
+ }
+ s = trace_fraction;
+ // don't artificially reduce max jump height in real-time
+ // (jumpstepheightvec is reduced a bit to make the jumps easy in tracewalk)
+ vector jump_height = (IS_ONGROUND(this)) ? stepheightvec + jumpheight_vec : jumpstepheightvec;
+ tracebox(this.origin + jump_height, this.mins, this.maxs, actual_destorg + jump_height, false, this);
+ if (trace_fraction > s)
+ PHYS_INPUT_BUTTON_JUMP(this) = true;
+ else
+ {
+ jump_height = stepheightvec + jumpheight_vec / 2;
+ tracebox(this.origin + jump_height, this.mins, this.maxs, actual_destorg + jump_height, false, this);
+ if (trace_fraction > s)
+ PHYS_INPUT_BUTTON_JUMP(this) = true;
+ }
+ }
}
}
if(IS_PLAYER(this.goalcurrent))
unreachable = true;
}
+
+ // slow down if bot is in the air and goal is under it
+ if (!this.goalcurrent.wphardwired
+ && vdist(flat_diff, <, 250) && this.origin.z - destorg.z > 120
+ && (!IS_ONGROUND(this) || vdist(vec2(this.velocity), >, maxspeed * 0.3)))
+ {
+ // tracebox wouldn't work when bot is still on the ledge
+ traceline(this.origin, this.origin - '0 0 200', true, this);
+ if (this.origin.z - trace_endpos.z > 120)
+ evadeobstacle = normalize(this.velocity) * -1;
+ }
+
if(unreachable)
{
navigation_clearroute(this);
}
dodge = havocbot_dodge(this);
- dodge = dodge * bound(0,0.5+(skill+this.bot_dodgeskill)*0.1,1);
+ if (dodge)
+ dodge *= bound(0, 0.5 + (skill + this.bot_dodgeskill) * 0.1, 1);
+ dodge += evadeobstacle + evadelava;
evadelava = evadelava * bound(1,3-(skill+this.bot_dodgeskill),3); //Noobs fear lava a lot and take more distance from it
- traceline(this.origin, ( ( this.enemy.absmin + this.enemy.absmax ) * 0.5 ), true, NULL);
- if(IS_PLAYER(trace_ent))
- dir = dir * bound(0,(skill+this.bot_dodgeskill)/7,1);
-
- dir = normalize(dir + dodge + evadeobstacle + evadelava);
+ if (this.enemy)
+ {
+ traceline(this.origin, (this.enemy.absmin + this.enemy.absmax) * 0.5, true, NULL);
+ if (IS_PLAYER(trace_ent))
+ dodge_enemy_factor = bound(0, (skill + this.bot_dodgeskill) / 7, 1);
+ }
// this.bot_dodgevector = dir;
// this.bot_dodgevector_jumpbutton = PHYS_INPUT_BUTTON_JUMP(this);
}
+ float ladder_zdir = 0;
if(time < this.ladder_time)
{
if(this.goalcurrent.origin.z + this.goalcurrent.mins.z > this.origin.z + this.mins.z)
{
if(this.origin.z + this.mins.z < this.ladder_entity.origin.z + this.ladder_entity.maxs.z)
- dir.z = 1;
+ ladder_zdir = 1;
}
else
{
if(this.origin.z + this.mins.z > this.ladder_entity.origin.z + this.ladder_entity.mins.z)
- dir.z = -1;
+ ladder_zdir = -1;
+ }
+ if (ladder_zdir)
+ {
+ dir.z = ladder_zdir * 1.3;
+ dir = normalize(dir);
}
}
+ if (this.goalcurrent.wpisbox
+ && boxesoverlap(this.goalcurrent.absmin, this.goalcurrent.absmax, this.origin, this.origin))
+ {
+ // bot is inside teleport waypoint but hasn't touched the real teleport yet
+ // head to teleport origin
+ dir = (this.goalcurrent.origin - this.origin);
+ dir.z = 0;
+ dir = normalize(dir);
+ }
+
+ if (!this.bot_aimdir_executed)
+ bot_aimdir(this, dir, -1);
+
+ if (!ladder_zdir)
+ {
+ dir *= dodge_enemy_factor;
+ dir = normalize(dir + dodge);
+ }
+
//dir = this.bot_dodgevector;
//if (this.bot_dodgevector_jumpbutton)
// PHYS_INPUT_BUTTON_JUMP(this) = true;
if(autocvar_bot_debug_goalstack)
debuggoalstack(this);
- // Heading
- vector dir = get_closer_dest(this.goalcurrent, this.origin);
- dir = dir - (this.origin + this.view_ofs);
- dir.z = 0;
- bot_aimdir(this, dir, -1);
// Go!
havocbot_movetogoal(this);
+ if (!this.bot_aimdir_executed && this.goalcurrent)
+ {
+ // Heading
+ vector dir = get_closer_dest(this.goalcurrent, this.origin);
+ dir -= this.origin + this.view_ofs;
+ dir.z = 0;
+ bot_aimdir(this, dir, -1);
+ }
+
if(this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_REACHED)
{
// Step 5: Waypoint reached
float enemy_distance = FLOAT_MAX;
float dist;
- FOREACH_CLIENT(IS_PLAYER(it) && it != this && !IS_DEAD(it),
+ FOREACH_CLIENT(IS_PLAYER(it) && it != this && !(IS_DEAD(it) || STAT(FROZEN, it)),
{
if (it.team == this.team)
{
{
float rating;
vector o;
- ratingscale = ratingscale * 0.0001; // items are rated around 10000 already
+ ratingscale = ratingscale * 0.0001;
IL_EACH(g_items, it.bot_pickup,
{
// NOTE: this code assumes each bot rates items in a different frame
if(it.bot_ratingscale_time == time && ratingscale < it.bot_ratingscale)
continue;
- it.bot_ratingscale_time = time;
- it.bot_ratingscale = ratingscale;
if(!it.solid)
{
if(!havocbot_goalrating_item_pickable_check_players(this, org, it, o))
continue;
+ it.bot_ratingscale_time = time;
+ it.bot_ratingscale = ratingscale;
rating = it.bot_pickupevalfunc(this, it);
if(rating > 0)
navigation_routerating(this, it, rating * ratingscale, 2000);
if(this.waterlevel>WATERLEVEL_WETFEET)
return;
- ratingscale = ratingscale * 0.00005; // enemies are rated around 20000 already
+ ratingscale = ratingscale * 0.0001;
float t;
FOREACH_CLIENT(IS_PLAYER(it) && bot_shouldattack(this, it), {
{
navigation_goalrating_start(this);
havocbot_goalrating_items(this, 10000, this.origin, 10000);
- havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
+ havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
navigation_goalrating_end(this);
int nav_action;
// Analyze starting point
- traceline(start, start, MOVE_NORMAL, e);
- if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA))
+ if (IN_LAVA(start))
ignorehazards = true;
tracebox(start, m1, m2, start, MOVE_NOMONSTERS, e);
this.goalcurrent_distance_z = FLOAT_MAX;
this.goalcurrent_distance_time = 0;
this.goalentity_lock_timeout = 0;
+ this.goalentity_shouldbefrozen = false;
this.goalentity = NULL;
this.goalcurrent = NULL;
this.goalstack01 = NULL;
nwp = e.nearestwaypoint;
}
- LOG_DEBUG("-- checking ", e.classname, " (with cost ", ftos(nwp.wpcost), ")");
if (nwp && nwp.wpcost < 10000000)
{
//te_wizspike(nwp.wpnearestpoint);
else
nwptoitem_cost = waypoint_gettravelcost(nwp.wpnearestpoint, goal_org, nwp, e);
float cost = nwp.wpcost + nwptoitem_cost;
- LOG_DEBUG(e.classname, " ", ftos(f), "/(1+", ftos(cost), "/", ftos(rangebias), ") = ");
+ LOG_DEBUG("checking ^5", e.classname, "^7 with base rating ^xf04", ftos(f), "^7 and rangebias ^xf40", ftos(rangebias));
f = f * rangebias / (rangebias + cost);
- LOG_DEBUG("considering ", e.classname, " (with rating ", ftos(f), ")");
+ LOG_DEBUG(" ^5", e.classname, "^7 with cost ^6", ftos(cost), "^7 and final rating ^2", ftos(f));
if (navigation_bestrating < f)
{
- LOG_DEBUG("ground path: added goal ", e.classname, " (with rating ", ftos(f), ")");
+ LOG_DEBUG(" ground path: ^3added goal ^5", e.classname);
navigation_bestrating = f;
navigation_bestgoal = e;
}
}
// shorten path by removing intermediate goals
-void navigation_shortenpath(entity this)
+bool navigation_shortenpath(entity this)
{
if (!this.goalstack01 || wasfreed(this.goalstack01))
- return;
+ return false;
if (this.bot_tracewalk_time > time)
- return;
+ return false;
this.bot_tracewalk_time = max(time, this.bot_tracewalk_time) + 0.25;
bool cut_allowed = false;
navigation_poproute(this);
}
while (this.goalcurrent != next);
+ return true;
}
- return;
+ return false;
}
}
{
LOG_DEBUG("path optimized for ", this.netname, ", removed a goal from the queue");
navigation_poproute(this);
+ return true;
}
}
+ return false;
}
// removes any currently touching waypoints from the goal stack
this.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
this.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED;
}
+ if(this.jumppadcount)
+ {
+ // remove jumppad waypoint after a random delay to prevent bots getting
+ // stuck on certain jumppads that require an extra initial horizontal speed
+ float max_delay = 0.1;
+ if (vdist(vec2(this.velocity), >, 2 * autocvar_sv_maxspeed))
+ max_delay = 0.05;
+ if (time - this.lastteleporttime < random() * max_delay)
+ return removed_goals;
+ }
navigation_poproute(this);
this.lastteleporttime = 0;
++removed_goals;
this.aistatus |= AI_STATUS_STUCK;
}
}
+ this.goalentity_shouldbefrozen = boolean(STAT(FROZEN, this.goalentity));
}
void botframe_updatedangerousobjects(float maxupdate)
.float goalcurrent_distance_time;
.float goalentity_lock_timeout;
+.bool goalentity_shouldbefrozen;
.entity nearestwaypoint;
.float nearestwaypointtimeout;
void navigation_markroutes(entity this, entity fixed_source_waypoint);
void navigation_markroutes_inverted(entity fixed_source_waypoint);
void navigation_routerating(entity this, entity e, float f, float rangebias);
-void navigation_shortenpath(entity this);
+bool navigation_shortenpath(entity this);
int navigation_poptouchedgoals(entity this);
void navigation_goalrating_start(entity this);
void navigation_goalrating_end(entity this);
set g_hitplots 0 "when set to 1, hitplots are stored by the server to provide a means of proving that a triggerbot was used"
set g_hitplots_individuals "" "the individuals, by IP, that should have their hitplots recorded"
+// set it to 1 to "fix bot moveto command and routing... now all bots can get to their seats" (Nexuiz repo, commit 2c9873e6)
set bot_navigation_ignoreplayers 0 // FIXME remove this once the issue is solved
set bot_sound_monopoly 0 "when enabled, only bots can make any noise"