#include "havocbot.qh"
+#include <server/defs.qh>
+#include <server/miscfunctions.qh>
#include "../cvars.qh"
#include "../aim.qh"
#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>
#include <common/items/_mod.qh>
+#include <common/wepent.qh>
#include <common/triggers/teleporters.qh>
#include <common/triggers/trigger/jumppads.qh>
}
else
{
- if (!this.jumppadcount)
+ if (!this.jumppadcount && !STAT(FROZEN, this))
this.havocbot_role(this); // little too far down the rabbit hole
}
- // TODO: tracewalk() should take care of this job (better path finding under water)
// if we don't have a goal and we're under water look for a waypoint near the "shore" and push it
- if(IS_DEAD(this))
+ if(!(IS_DEAD(this) || STAT(FROZEN, this)))
if(!this.goalcurrent)
if(this.waterlevel == WATERLEVEL_SWIMMING || (this.aistatus & AI_STATUS_OUT_WATER))
{
bot_strategytoken_taken = true;
}
- if(IS_DEAD(this))
+ if(IS_DEAD(this) || STAT(FROZEN, this))
return;
havocbot_chooseenemy(this);
this.aistatus |= AI_STATUS_ROAMING;
this.aistatus &= ~AI_STATUS_ATTACKING;
- vector now,v,next;//,heading;
+ vector v, now, next;
float aimdistance,skillblend,distanceblend,blend;
- next = now = ( (this.goalcurrent.absmin + this.goalcurrent.absmax) * 0.5) - (this.origin + this.view_ofs);
+
+ SET_DESTCOORDS(this.goalcurrent, this.origin, v);
+ 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);
- //heading = this.velocity;
+
//dprint(this.goalstack01.classname,etos(this.goalstack01),"\n");
if(
- this.goalstack01 != this && this.goalstack01 != NULL && ((this.aistatus & AI_STATUS_RUNNING) == 0) &&
+ 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);
// 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
+ CS(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
if(skill >= 5) // bots can only look for unloaded weapons past this skill
if(this.(weaponentity).clip_load >= 0) // only if we're not reloading a weapon already
{
- FOREACH(Weapons, it != WEP_Null, LAMBDA(
+ FOREACH(Weapons, it != WEP_Null, {
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;
+ }
+ });
}
}
}
void havocbot_keyboard_movement(entity this, vector destorg)
{
vector keyboard;
- float blend, maxspeed;
- float sk;
-
- sk = skill + this.bot_moveskill;
- maxspeed = autocvar_sv_maxspeed;
-
- if (time < this.havocbot_keyboardtime)
- return;
-
- this.havocbot_keyboardtime =
- max(
- this.havocbot_keyboardtime
- + 0.05/max(1, sk+this.havocbot_keyboardskill)
- + random()*0.025/max(0.00025, skill+this.havocbot_keyboardskill)
- , time);
- keyboard = this.movement * (1.0 / maxspeed);
-
- float trigger, trigger1;
- blend = bound(0,sk*0.1,1);
- trigger = autocvar_bot_ai_keyboard_threshold;
- trigger1 = 0 - trigger;
-
- // categorize forward movement
- // at skill < 1.5 only forward
- // at skill < 2.5 only individual directions
- // at skill < 4.5 only individual directions, and forward diagonals
- // at skill >= 4.5, all cases allowed
- if (keyboard.x > trigger)
+ if (time > this.havocbot_keyboardtime)
{
- keyboard.x = 1;
- if (sk < 2.5)
- keyboard.y = 0;
- }
- else if (keyboard.x < trigger1 && sk > 1.5)
- {
- keyboard.x = -1;
+ float sk = skill + this.bot_moveskill;
+ this.havocbot_keyboardtime =
+ max(
+ this.havocbot_keyboardtime
+ + 0.05 / max(1, sk + this.havocbot_keyboardskill)
+ + random() * 0.025 / max(0.00025, skill + this.havocbot_keyboardskill)
+ , time);
+ keyboard = CS(this).movement / autocvar_sv_maxspeed;
+
+ float trigger = autocvar_bot_ai_keyboard_threshold;
+ float trigger1 = -trigger;
+
+ // categorize forward movement
+ // at skill < 1.5 only forward
+ // at skill < 2.5 only individual directions
+ // at skill < 4.5 only individual directions, and forward diagonals
+ // at skill >= 4.5, all cases allowed
+ if (keyboard.x > trigger)
+ {
+ keyboard.x = 1;
+ if (sk < 2.5)
+ keyboard.y = 0;
+ }
+ else if (keyboard.x < trigger1 && sk > 1.5)
+ {
+ keyboard.x = -1;
+ if (sk < 4.5)
+ keyboard.y = 0;
+ }
+ else
+ {
+ keyboard.x = 0;
+ if (sk < 1.5)
+ keyboard.y = 0;
+ }
if (sk < 4.5)
- keyboard.y = 0;
- }
- else
- {
- keyboard.x = 0;
- if (sk < 1.5)
- keyboard.y = 0;
- }
- if (sk < 4.5)
- keyboard.z = 0;
+ keyboard.z = 0;
- if (keyboard.y > trigger)
- keyboard.y = 1;
- else if (keyboard.y < trigger1)
- keyboard.y = -1;
- else
- keyboard.y = 0;
+ if (keyboard.y > trigger)
+ keyboard.y = 1;
+ else if (keyboard.y < trigger1)
+ keyboard.y = -1;
+ else
+ keyboard.y = 0;
- if (keyboard.z > trigger)
- keyboard.z = 1;
- else if (keyboard.z < trigger1)
- keyboard.z = -1;
- else
- keyboard.z = 0;
+ if (keyboard.z > trigger)
+ keyboard.z = 1;
+ else if (keyboard.z < trigger1)
+ keyboard.z = -1;
+ else
+ keyboard.z = 0;
- this.havocbot_keyboard = keyboard * maxspeed;
- if (this.havocbot_ducktime>time) PHYS_INPUT_BUTTON_CROUCH(this) = true;
+ this.havocbot_keyboard = keyboard * autocvar_sv_maxspeed;
+ if (this.havocbot_ducktime > time)
+ PHYS_INPUT_BUTTON_CROUCH(this) = true;
+ }
keyboard = this.havocbot_keyboard;
- blend = bound(0,vlen(destorg-this.origin)/autocvar_bot_ai_keyboard_distance,1); // When getting close move with 360 degree
- //dprint("movement ", vtos(this.movement), " keyboard ", vtos(keyboard), " blend ", ftos(blend), "\n");
- this.movement = this.movement + (keyboard - this.movement) * blend;
+ float blend = bound(0, vlen(destorg - this.origin) / autocvar_bot_ai_keyboard_distance, 1); // When getting close move with 360 degree
+ //dprint("movement ", vtos(CS(this).movement), " keyboard ", vtos(keyboard), " blend ", ftos(blend), "\n");
+ CS(this).movement = CS(this).movement + (keyboard - CS(this).movement) * blend;
}
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;
this.bot_timelastseengoal = 0;
}
- gco = (this.goalcurrent.absmin + this.goalcurrent.absmax) * 0.5;
+ SET_DESTCOORDS(this.goalcurrent, this.origin, gco);
bunnyhopdistance = vlen(this.origin - gco);
// Run only to visible goals
if(IS_ONGROUND(this))
- if(this.speed==maxspeed)
+ if(vdist(vec2(this.velocity), >=, autocvar_sv_maxspeed)) // if -really- running
if(checkpvs(this.origin + this.view_ofs, this.goalcurrent))
{
this.bot_lastseengoal = this.goalcurrent;
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!=NULL)
+ if(this.goalstack01 && !wasfreed(this.goalstack01))
{
gno = (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5;
deviation = vectoangles(gno - this.origin) - vectoangles(gco - this.origin);
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
while (deviation.y > 180) deviation.y = deviation.y - 360;
if(fabs(deviation.y)>10)
- this.movement_x = 0;
+ CS(this).movement_x = 0;
if(deviation.y>10)
- this.movement_y = maxspeed * -1;
+ CS(this).movement_y = maxspeed * -1;
else if(deviation.y<10)
- this.movement_y = maxspeed;
+ CS(this).movement_y = maxspeed;
}
}
#endif
}
+// return true when bot isn't getting closer to the current goal
+bool havocbot_checkgoaldistance(entity this, vector gco)
+{
+ float curr_dist = vlen(this.origin - gco);
+ if(curr_dist > this.goalcurrent_distance)
+ {
+ if(!this.goalcurrent_distance_time)
+ this.goalcurrent_distance_time = time;
+ else if (time - this.goalcurrent_distance_time > 0.5)
+ return true;
+ }
+ else
+ {
+ // reduce it a little bit so it works even with very small approaches to the goal
+ this.goalcurrent_distance = max(20, curr_dist - 15);
+ this.goalcurrent_distance_time = 0;
+ }
+ return false;
+}
+
void havocbot_movetogoal(entity this)
{
vector destorg;
vector diff;
vector dir;
vector flatdir;
- vector m1;
- vector m2;
vector evadeobstacle;
vector evadelava;
- float s;
float maxspeed;
vector gco;
//float dist;
vector dodge;
//if (this.goalentity)
// te_lightning2(this, this.origin, (this.goalentity.absmin + this.goalentity.absmax) * 0.5);
- this.movement = '0 0 0';
+ CS(this).movement = '0 0 0';
maxspeed = autocvar_sv_maxspeed;
// Jetpack navigation
dxy = this.origin - ( ( this.goalcurrent.absmin + this.goalcurrent.absmax ) * 0.5 ); dxy.z = 0;
d = vlen(dxy);
v = vlen(this.velocity - this.velocity.z * '0 0 1');
- db = (pow(v,2) / (autocvar_g_jetpack_acceleration_side * 2)) + 100;
+ db = ((v ** 2) / (autocvar_g_jetpack_acceleration_side * 2)) + 100;
// dprint("distance ", ftos(ceil(d)), " velocity ", ftos(ceil(v)), " brake at ", ftos(ceil(db)), "\n");
if(d < db || d < 500)
{
// Brake
if(fabs(this.velocity.x)>maxspeed*0.3)
{
- this.movement_x = dir * v_forward * -maxspeed;
+ CS(this).movement_x = dir * v_forward * -maxspeed;
return;
}
// Switch to normal mode
// 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;
+ CS(this).movement_x = dir * v_forward * maxspeed;
+ CS(this).movement_y = dir * v_right * maxspeed;
}
return;
}
// Handling of jump pads
if(this.jumppadcount)
{
- // If got stuck on the jump pad try to reach the farthest visible waypoint
- if(this.aistatus & AI_STATUS_OUT_JUMPPAD)
+ if(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
+ {
+ this.aistatus |= AI_STATUS_OUT_JUMPPAD;
+ navigation_poptouchedgoals(this);
+ return;
+ }
+ else if(this.aistatus & AI_STATUS_OUT_JUMPPAD)
{
- if(fabs(this.velocity.z)<50)
+ // 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.goalcurrent)
{
entity newgoal = NULL;
IL_EACH(g_waypoints, vdist(it.origin - this.origin, <=, 1000),
{
+ if(it.wpflags & WAYPOINTFLAG_TELEPORT)
+ if(it.origin.z < this.origin.z - 100 && vdist(vec2(it.origin - this.origin), <, 100))
+ continue;
+
traceline(this.origin + this.view_ofs, ((it.absmin + it.absmax) * 0.5), true, 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;
});
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;
}
}
else
- return;
+ {
+ gco = (this.goalcurrent.absmin + this.goalcurrent.absmax) * 0.5;
+ if (this.origin.z > gco.z && vdist(vec2(this.velocity), <, autocvar_sv_maxspeed))
+ this.aistatus &= ~AI_STATUS_OUT_JUMPPAD;
+ else if(havocbot_checkgoaldistance(this, gco))
+ {
+ navigation_clearroute(this);
+ this.bot_strategytime = 0;
+ }
+ else
+ return;
+ }
}
else
{
- if(this.velocity.z>0)
+ if(time - this.lastteleporttime > 0.2 && this.velocity.z > 0)
{
- float threshold;
vector velxy = this.velocity; velxy_z = 0;
- threshold = maxspeed * 0.2;
- if(vdist(velxy, <, threshold))
+ if(vdist(velxy, <, autocvar_sv_maxspeed * 0.2))
{
LOG_TRACE("Warning: ", this.netname, " got stuck on a jumppad (velocity in xy is ", vtos(velxy), "), trying to get out of it now");
this.aistatus |= AI_STATUS_OUT_JUMPPAD;
tracebox(this.origin, this.mins, this.maxs, this.origin + (dir * maxspeed * 3), MOVE_NOMONSTERS, this);
if(trace_fraction==1)
{
- this.movement_x = dir * v_forward * maxspeed;
- this.movement_y = dir * v_right * maxspeed;
+ CS(this).movement_x = dir * v_forward * maxspeed;
+ CS(this).movement_y = dir * v_right * maxspeed;
if (skill < 10)
havocbot_keyboard_movement(this, this.origin + dir * 100);
}
return;
}
- else if(this.health>WEP_CVAR(devastator, damage)*0.5)
+ else if(this.health > WEP_CVAR(devastator, damage) * 0.5 * ((this.strength_finished < time) ? autocvar_g_balance_powerup_strength_selfdamage : 1))
{
if(this.velocity.z < 0)
{
if(client_hasweapon(this, WEP_DEVASTATOR, weaponentity, true, false))
{
- this.movement_x = maxspeed;
+ CS(this).movement_x = maxspeed;
if(this.rocketjumptime)
{
{
// If there is no goal try to move forward
if(this.goalcurrent==NULL)
- this.movement_x = maxspeed;
+ CS(this).movement_x = maxspeed;
}
}
else
PHYS_INPUT_BUTTON_JUMP(this) = false;
makevectors(this.v_angle.y * '0 1 0');
- this.movement_x = dir * v_forward * maxspeed;
- this.movement_y = dir * v_right * maxspeed;
- this.movement_z = dir * v_up * maxspeed;
+ CS(this).movement_x = dir * v_forward * maxspeed;
+ CS(this).movement_y = dir * v_right * maxspeed;
+ CS(this).movement_z = dir * v_up * maxspeed;
}
// if there is nowhere to go, exit
if (this.goalcurrent == NULL)
return;
- if (this.goalcurrent)
+
+ bool locked_goal = false;
+ if(this.goalentity && wasfreed(this.goalentity))
+ {
+ navigation_clearroute(this);
+ this.bot_strategytime = 0;
+ return;
+ }
+ else if(this.goalentity.bot_pickup)
+ {
+ if(this.goalentity.bot_pickup_respawning)
+ {
+ if(this.goalentity.solid) // item respawned
+ this.goalentity.bot_pickup_respawning = false;
+ else if(time < this.goalentity.scheduledrespawntime - 10) // item already taken (by someone else)
+ {
+ this.goalentity.bot_pickup_respawning = false;
+ navigation_clearroute(this);
+ this.bot_strategytime = 0;
+ return;
+ }
+ else if(this.goalentity == this.goalcurrent)
+ locked_goal = true; // wait for item to respawn
+ }
+ else if(!this.goalentity.solid)
+ {
+ navigation_clearroute(this);
+ this.bot_strategytime = 0;
+ return;
+ }
+ }
+ if(!locked_goal)
navigation_poptouchedgoals(this);
// if ran out of goals try to use an alternative goal or get a new strategy asap
if(autocvar_bot_debug_goalstack)
debuggoalstack(this);
- m1 = this.goalcurrent.origin + this.goalcurrent.mins;
- m2 = this.goalcurrent.origin + this.goalcurrent.maxs;
- destorg = this.origin;
- destorg.x = bound(m1_x, destorg.x, m2_x);
- destorg.y = bound(m1_y, destorg.y, m2_y);
- destorg.z = bound(m1_z, destorg.z, m2_z);
+ bool bunnyhop_forbidden = false;;
+ SET_DESTCOORDS(this.goalcurrent, this.origin, destorg);
+
+ // 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))
+ {
+ bunnyhop_forbidden = true;
+ destorg = this.goalcurrent.origin;
+ if(destorg.z > this.origin.z)
+ PHYS_INPUT_BUTTON_JUMP(this) = true;
+ }
+
diff = destorg - this.origin;
//dist = vlen(diff);
dir = normalize(diff);
//if (this.bot_dodgevector_time < time)
{
- // this.bot_dodgevector_time = time + cvar("bot_ai_dodgeupdateinterval");
- // this.bot_dodgevector_jumpbutton = 1;
+ //this.bot_dodgevector_time = time + cvar("bot_ai_dodgeupdateinterval");
+ //this.bot_dodgevector_jumpbutton = 1;
evadeobstacle = '0 0 0';
evadelava = '0 0 0';
+ this.aistatus &= ~AI_STATUS_DANGER_AHEAD;
+ makevectors(this.v_angle.y * '0 1 0');
if (this.waterlevel)
{
if(this.waterlevel>WATERLEVEL_SWIMMING)
{
- // flatdir_z = 1;
- this.aistatus |= AI_STATUS_OUT_WATER;
+ if(!this.goalcurrent)
+ this.aistatus |= AI_STATUS_OUT_WATER;
+ else if(gco.z > this.origin.z)
+ PHYS_INPUT_BUTTON_JUMP(this) = true;
}
else
{
+ dir = flatdir;
if(this.velocity.z >= 0 && !(this.watertype == CONTENT_WATER && gco.z < this.origin.z) &&
( !(this.waterlevel == WATERLEVEL_WETFEET && this.watertype == CONTENT_WATER) || this.aistatus & AI_STATUS_OUT_WATER))
PHYS_INPUT_BUTTON_JUMP(this) = true;
else
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';
-
- // Look ahead
- traceline(this.origin + this.view_ofs, dst_ahead, true, NULL);
-
- // Check head-banging against walls
- if(vdist(this.origin + this.view_ofs - trace_endpos, <, 25) && !(this.aistatus & AI_STATUS_OUT_WATER))
+ // if bot for some reason doesn't get close to the current goal find another one
+ if(!this.jumppadcount && !IS_PLAYER(this.goalcurrent) && !(this.goalcurrent.bot_pickup_respawning && this.goalcurrent_distance < 50))
+ if(havocbot_checkgoaldistance(this, gco))
{
- PHYS_INPUT_BUTTON_JUMP(this) = true;
- if(this.facingwalltime && time > this.facingwalltime)
- {
- this.ignoregoal = this.goalcurrent;
- this.ignoregoaltime = time + autocvar_bot_ai_ignoregoal_timeout;
- this.bot_strategytime = 0;
- return;
- }
- else
- {
- this.facingwalltime = time + 0.05;
- }
- }
- else
- {
- this.facingwalltime = 0;
-
- if(this.ignoregoal != NULL && time > this.ignoregoaltime)
- {
- this.ignoregoal = NULL;
- this.ignoregoaltime = 0;
- }
+ navigation_clearroute(this);
+ this.bot_strategytime = 0;
+ return;
}
// Check for water/slime/lava and dangerous edges
// (only when the bot is on the ground or jumping intentionally)
- this.aistatus &= ~AI_STATUS_DANGER_AHEAD;
+ vector dst_ahead = this.origin + this.view_ofs + offset;
+ vector dst_down = dst_ahead - '0 0 3000';
+ traceline(this.origin + this.view_ofs, dst_ahead, true, NULL);
+
+ 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');
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;
}
}
}
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);
//dir = this.bot_dodgevector;
//if (this.bot_dodgevector_jumpbutton)
// PHYS_INPUT_BUTTON_JUMP(this) = true;
- this.movement_x = dir * v_forward * maxspeed;
- this.movement_y = dir * v_right * maxspeed;
- this.movement_z = dir * v_up * maxspeed;
+ CS(this).movement_x = dir * v_forward * maxspeed;
+ CS(this).movement_y = dir * v_right * maxspeed;
+ CS(this).movement_z = dir * v_up * maxspeed;
// Emulate keyboard interface
if (skill < 10)
havocbot_keyboard_movement(this, destorg);
// Bunnyhop!
-// if(this.aistatus & AI_STATUS_ROAMING)
- if(this.goalcurrent)
+ //if(this.aistatus & AI_STATUS_ROAMING)
+ if(!bunnyhop_forbidden && this.goalcurrent)
if(skill+this.bot_moveskill >= autocvar_bot_ai_bunnyhop_skilloffset)
havocbot_bunnyhop(this, dir);
void havocbot_chooseenemy(entity this)
{
- entity head, best, head2;
- float rating, bestrating, hf;
- vector eye, v;
if (autocvar_bot_nofire || IS_INDEPENDENT_PLAYER(this))
{
this.enemy = NULL;
if (time < this.havocbot_chooseenemy_finished)
return;
this.havocbot_chooseenemy_finished = time + autocvar_bot_ai_enemydetectioninterval;
- eye = this.origin + this.view_ofs;
- best = NULL;
- bestrating = 100000000;
- head = head2 = findchainfloat(bot_attack, true);
+ vector eye = this.origin + this.view_ofs;
+ entity best = NULL;
+ float bestrating = 100000000;
// Backup hit flags
- hf = this.dphitcontentsmask;
+ int hf = this.dphitcontentsmask;
// Search for enemies, if no enemy can be seen directly try to look through transparent objects
{
scan_secondary_targets = false;
LABEL(scan_targets)
- for( ; head; head = head.chain)
+ IL_EACH(g_bot_targets, it.bot_attack,
{
if(!scan_secondary_targets)
{
- if(head.classname == "misc_breakablemodel")
+ if(it.classname == "misc_breakablemodel")
{
have_secondary_targets = true;
continue;
}
}
- else
- {
- if(head.classname != "misc_breakablemodel")
- continue;
- }
+ else if(it.classname != "misc_breakablemodel")
+ continue;
- v = (head.absmin + head.absmax) * 0.5;
- rating = vlen(v - eye);
- if (rating<autocvar_bot_ai_enemydetectionradius)
+ vector v = (it.absmin + it.absmax) * 0.5;
+ float rating = vlen2(v - eye);
+ if (vdist(v - eye, <, autocvar_bot_ai_enemydetectionradius))
if (bestrating > rating)
- if (bot_shouldattack(this, head))
+ if (bot_shouldattack(this, it))
{
traceline(eye, v, true, this);
- if (trace_ent == head || trace_fraction >= 1)
+ if (trace_ent == it || trace_fraction >= 1)
{
- best = head;
+ best = it;
bestrating = rating;
}
}
- }
+ });
if(!best && have_secondary_targets && !scan_secondary_targets)
{
scan_secondary_targets = true;
// restart the loop
- head = head2;
bestrating = 100000000;
goto scan_targets;
}
// Set flags to see through transparent objects
this.dphitcontentsmask |= DPCONTENTS_OPAQUE;
- head = head2;
scan_transparent = true;
}
// 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(
+ FOREACH(Weapons, it != WEP_Null, {
if(it.wr_checkammo1(it, this, weaponentity) + it.wr_checkammo2(it, this, weaponentity))
- other_weapon_available = true;
- ));
- if(other_weapon_available)
- return true;
+ return true; // other weapon available
+ });
}
return false;
{
// If no weapon was chosen get the first available weapon
if(this.(weaponentity).m_weapon==WEP_Null)
- FOREACH(Weapons, it != WEP_Null, LAMBDA(
+ FOREACH(Weapons, it != WEP_Null, {
if(client_hasweapon(this, it, weaponentity, true, false))
{
this.(weaponentity).m_switchweapon = it;
return;
}
- ));
+ });
return;
}
this.lastcombotime = time;
}
- distance *= pow(2, this.bot_rangepreference);
+ distance *= (2 ** this.bot_rangepreference);
// Custom weapon list based on distance to the enemy
if(bot_custom_weapon){
void havocbot_aim(entity this)
{
- vector myvel, enemyvel;
-// if(this.flags & FL_INWATER)
-// return;
if (time < this.nextaim)
return;
this.nextaim = time + 0.1;
- myvel = this.velocity;
+ vector myvel = this.velocity;
if (!this.waterlevel)
myvel.z = 0;
- if (this.enemy)
+ if(MUTATOR_CALLHOOK(HavocBot_Aim, this)) { /* do nothing */ }
+ else if (this.enemy)
{
- enemyvel = this.enemy.velocity;
+ vector enemyvel = this.enemy.velocity;
if (!this.enemy.waterlevel)
enemyvel.z = 0;
- lag_additem(this, time + this.ping, 0, 0, this.enemy, this.origin, myvel, (this.enemy.absmin + this.enemy.absmax) * 0.5, enemyvel);
+ lag_additem(this, time + CS(this).ping, 0, 0, this.enemy, this.origin, myvel, (this.enemy.absmin + this.enemy.absmax) * 0.5, enemyvel);
}
else
- lag_additem(this, time + this.ping, 0, 0, NULL, this.origin, myvel, ( this.goalcurrent.absmin + this.goalcurrent.absmax ) * 0.5, '0 0 0');
+ lag_additem(this, time + CS(this).ping, 0, 0, NULL, this.origin, myvel, ( this.goalcurrent.absmin + this.goalcurrent.absmax ) * 0.5, '0 0 0');
}
bool havocbot_moveto_refresh_route(entity this)
navigation_goalrating_start(this);
navigation_routerating(this, wp, 10000, 10000);
navigation_goalrating_end(this);
- return this.navigation_hasgoals;
+ return (this.goalentity != NULL);
}
float havocbot_moveto(entity this, vector pos)
debuggoalstack(this);
// Heading
- vector dir = ( ( this.goalcurrent.absmin + this.goalcurrent.absmax ) * 0.5 ) - (this.origin + this.view_ofs);
+ vector dir;
+ SET_DESTCOORDS(this.goalcurrent, this.origin, dir);
+ dir = dir - (this.origin + this.view_ofs);
dir.z = 0;
bot_aimdir(this, dir, -1);