#include "havocbot.qh"
+#include <server/defs.qh>
+#include <server/miscfunctions.qh>
#include "../cvars.qh"
#include "../aim.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 && !wasfreed(this.goalstack01) && ((this.aistatus & AI_STATUS_RUNNING) == 0) &&
// 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 = IMP_weapon_reload.impulse; // 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(vlen(this.velocity - eZ * this.velocity.z) >= autocvar_sv_maxspeed) // if -really- running
+ 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(checkdistance)
{
this.aistatus &= ~AI_STATUS_RUNNING;
- // increase stop distance in case the goal is on a slope or a lower platform
+ // 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;
}
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 maxspeed;
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
PHYS_INPUT_BUTTON_HOOK(this) = true;
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
- // but with some randomness so it can try out different paths
- if(this.aistatus & AI_STATUS_OUT_JUMPPAD)
+ if(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
{
- if(fabs(this.velocity.z)<50)
+ this.aistatus |= AI_STATUS_OUT_JUMPPAD;
+ navigation_poptouchedgoals(this);
+ return;
+ }
+ else if(this.aistatus & AI_STATUS_OUT_JUMPPAD)
+ {
+ // 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)
}
}
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;
- navigation_poptouchedgoals(this);
+
+ 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(this.goalcurrent == NULL)
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);
}
else
{
// 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
- offset = (vdist(this.velocity, >, 32) ? this.velocity * 0.5 : v_forward * 32);
- vector dst_ahead = this.origin + this.view_ofs + offset;
- vector dst_down = dst_ahead - '0 0 3000';
-
- // 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))
- {
- 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
+ // 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))
{
- 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;
//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);
// if this weapon is scheduled for reloading, don't switch to it during combat
if (this.(weaponentity).weapon_load[new_weapon] < 0)
{
- FOREACH(Weapons, it != WEP_Null, LAMBDA(
+ FOREACH(Weapons, it != WEP_Null, {
if(it.wr_checkammo1(it, this, weaponentity) + it.wr_checkammo2(it, this, weaponentity))
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);