}
else
{
- if (!this.jumppadcount && !STAT(FROZEN, this))
+ if (!this.jumppadcount && !STAT(FROZEN, this)
+ && !(this.goalcurrent_prev && (this.goalcurrent_prev.wpflags & WAYPOINTFLAG_JUMP) && !IS_ONGROUND(this)))
+ {
+ // find a new goal
this.havocbot_role(this); // little too far down the rabbit hole
+ }
}
// if we don't have a goal and we're under water look for a waypoint near the "shore" and push it
{
if (this.goalcurrent)
navigation_clearroute(this);
+ this.enemy = NULL;
+ this.bot_aimtarg = NULL;
return;
}
if (can_run)
{
PHYS_INPUT_BUTTON_JUMP(this) = true;
+ this.bot_jump_time = time;
this.aistatus |= AI_STATUS_RUNNING;
}
else
if (skill > 6 && !(IS_ONGROUND(this)))
{
#define ROCKETJUMP_DAMAGE() WEP_CVAR(devastator, damage) * 0.8 \
- * ((this.strength_finished > time) ? autocvar_g_balance_powerup_strength_selfdamage : 1) \
- * ((this.invincible_finished > time) ? autocvar_g_balance_powerup_invincible_takedamage : 1)
+ * ((STAT(STRENGTH_FINISHED, this) > time) ? autocvar_g_balance_powerup_strength_selfdamage : 1) \
+ * ((STAT(INVINCIBLE_FINISHED, this) > time) ? autocvar_g_balance_powerup_invincible_takedamage : 1)
+
+ // save some CPU cycles by checking trigger_hurt after checking
+ // that something can be done to evade it (cheaper checks)
+ int action_for_trigger_hurt = 0;
+ if (this.items & IT_JETPACK)
+ action_for_trigger_hurt = 1;
+ else if (!this.jumppadcount && !waypoint_is_hardwiredlink(this.goalcurrent_prev, this.goalcurrent)
+ && !(this.goalcurrent_prev && this.goalcurrent_prev.wpflags & WAYPOINTFLAG_JUMP)
+ && GetResource(this, RES_HEALTH) + GetResource(this, RES_ARMOR) > ROCKETJUMP_DAMAGE())
+ {
+ action_for_trigger_hurt = 2;
+ }
+ else if (!this.goalcurrent)
+ action_for_trigger_hurt = 3;
- tracebox(this.origin, this.mins, this.maxs, this.origin + '0 0 -65536', MOVE_NOMONSTERS, this);
- if(tracebox_hits_trigger_hurt(this.origin, this.mins, this.maxs, trace_endpos ))
- if(this.items & IT_JETPACK)
+ if (action_for_trigger_hurt)
+ {
+ tracebox(this.origin, this.mins, this.maxs, this.origin + '0 0 -65536', MOVE_NOMONSTERS, this);
+ if(!tracebox_hits_trigger_hurt(this.origin, this.mins, this.maxs, trace_endpos))
+ action_for_trigger_hurt = 0;
+ }
+
+ if(action_for_trigger_hurt == 1) // jetpack
{
tracebox(this.origin, this.mins, this.maxs, this.origin + '0 0 65536', MOVE_NOMONSTERS, this);
if(tracebox_hits_trigger_hurt(this.origin, this.mins, this.maxs, trace_endpos + '0 0 1' ))
return;
}
- else if(!this.jumppadcount && !waypoint_is_hardwiredlink(this.goalcurrent_prev, this.goalcurrent)
- && !(this.goalcurrent_prev && this.goalcurrent_prev.wpflags & WAYPOINTFLAG_JUMP)
- && GetResource(this, RES_HEALTH) + GetResource(this, RES_ARMOR) > ROCKETJUMP_DAMAGE())
+ else if(action_for_trigger_hurt == 2) // rocketjump
{
if(this.velocity.z < 0)
{
}
}
}
- else
+ else if(action_for_trigger_hurt == 3) // no goal
{
// If there is no goal try to move forward
- if(this.goalcurrent==NULL)
- CS(this).movement_x = maxspeed;
+ CS(this).movement_x = maxspeed;
}
}
dir.z = 1;
else if(this.velocity.z >= 0 && !(this.waterlevel == WATERLEVEL_WETFEET && this.watertype == CONTENT_WATER))
PHYS_INPUT_BUTTON_JUMP(this) = true;
- else
- PHYS_INPUT_BUTTON_JUMP(this) = false;
makevectors(this.v_angle.y * '0 1 0');
vector v = dir * maxspeed;
CS(this).movement.x = v * v_forward;
dir = normalize(diff);
flatdir = (diff.z == 0) ? dir : normalize(vec2(diff));
- vector evadedanger = '0 0 0';
+ bool danger_detected = false;
+ vector do_break = '0 0 0';
//if (this.bot_dodgevector_time < time)
{
}
else
{
- PHYS_INPUT_BUTTON_JUMP(this) = false;
if (destorg.z > this.origin.z)
dir = flatdir;
}
)
{
PHYS_INPUT_BUTTON_JUMP(this) = true;
- // avoid changing route while bot is jumping a gap
- navigation_goalrating_timeout_extend_if_needed(this, 1.5);
+ this.bot_jump_time = time;
}
}
else if (!this.goalstack01 || (this.goalcurrent.wpflags & (WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_LADDER)))
{
if (vlen2(flat_diff) < vlen2(offset))
{
- if (this.goalcurrent.wpflags & WAYPOINTFLAG_JUMP && this.goalstack01)
+ if ((this.goalcurrent.wpflags & WAYPOINTFLAG_JUMP) && this.goalstack01)
{
// oblique warpzones need a jump otherwise bots gets stuck
PHYS_INPUT_BUTTON_JUMP(this) = true;
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;
+ this.bot_jump_time = time;
+ }
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;
+ this.bot_jump_time = time;
+ }
}
}
}
bool unreachable = false;
s = CONTENT_SOLID;
- bool danger_detected = false;
if (trace_fraction == 1 && !this.jumppadcount
&& !waypoint_is_hardwiredlink(this.goalcurrent_prev, this.goalcurrent)
&& !(this.goalcurrent_prev && (this.goalcurrent_prev.wpflags & WAYPOINTFLAG_JUMP)))
//te_lightning2(NULL, dst_ahead, trace_endpos); // Draw "downwards" look
if(trace_endpos.z < this.origin.z + this.mins.z)
{
- s = pointcontents(trace_endpos + '0 0 1');
- if (s != CONTENT_SOLID)
- if (s == CONTENT_LAVA || s == CONTENT_SLIME)
+ if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
danger_detected = true;
- else if (s == CONTENT_SKY)
+ else if (trace_endpos.z < min(this.origin.z + this.mins.z, this.goalcurrent.origin.z) - 100)
danger_detected = true;
- else if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
+ else
{
- // 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))
+ s = pointcontents(trace_endpos + '0 0 1');
+ if (s != CONTENT_SOLID)
{
- if (destorg.z > this.origin.z + jumpstepheightvec.z)
+ if (s == CONTENT_LAVA || s == CONTENT_SLIME)
+ danger_detected = true;
+ else if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
{
- // the goal is probably on an upper platform, assume bot can't get there
- unreachable = true;
+ // 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))
+ {
+ if (destorg.z > this.origin.z + jumpstepheightvec.z)
+ {
+ // the goal is probably on an upper platform, assume bot can't get there
+ unreachable = true;
+ }
+ else
+ danger_detected = true;
+ }
}
- else
- danger_detected = true;
}
}
}
}
- if (danger_detected && fabs(deviation.y) < 80
- && (fabs(deviation.y) > 5 || vdist(vec2(this.velocity), >, maxspeed * 1.5)))
- {
- evadedanger = normalize(this.velocity) * -1;
- evadedanger.z = 0;
- }
dir = flatdir;
makevectors(this.v_angle.y * '0 1 0');
// 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)
- evadedanger = normalize(this.velocity) * -1;
+ do_break = normalize(this.velocity) * -1;
}
if(unreachable)
dodge = havocbot_dodge(this);
if (dodge)
dodge *= bound(0, 0.5 + (skill + this.bot_dodgeskill) * 0.1, 1);
- evadedanger *= bound(1, 3 - (skill + this.bot_dodgeskill), 3); // Noobs fear dangers a lot and take more distance from them
if (this.enemy)
{
traceline(this.origin, (this.enemy.absmin + this.enemy.absmax) * 0.5, true, NULL);
}
float ladder_zdir = 0;
- if(time < this.ladder_time)
+ if(this.ladder_entity)
{
if(this.goalcurrent.origin.z + this.goalcurrent.mins.z > this.origin.z + this.mins.z)
{
bot_aimdir(this, dir, 0);
}
+ vector evadedanger = '0 0 0';
if (!ladder_zdir)
{
dir *= dodge_enemy_factor;
- dir = normalize(dir + dodge + evadedanger);
+ if (danger_detected && vdist(this.velocity, >, maxspeed * 0.8) && this.goalcurrent_prev
+ && this.goalcurrent.classname == "waypoint")
+ {
+ vector p = this.origin + this.velocity * 0.2;
+ vector evadedanger = point_line_vec(p, vec2(this.goalcurrent_prev.origin) + eZ * p.z,
+ vec2(destorg - this.goalcurrent_prev.origin));
+ if (vdist(evadedanger, >, 20))
+ {
+ if (vdist(evadedanger, >, 40))
+ do_break = normalize(this.velocity) * -1;
+ evadedanger = normalize(evadedanger);
+ evadedanger *= bound(1, 3 - (skill + this.bot_dodgeskill), 3); // Noobs fear dangers a lot and take more distance from them
+ }
+ else
+ evadedanger = '0 0 0';
+ }
+ dir = normalize(dir + dodge + do_break + evadedanger);
}
makevectors(this.v_angle);
havocbot_keyboard_movement(this, destorg);
// Bunnyhop!
- if (!bunnyhop_forbidden && skill + this.bot_moveskill >= autocvar_bot_ai_bunnyhop_skilloffset)
+ if (!bunnyhop_forbidden && !evadedanger && !do_break && skill + this.bot_moveskill >= autocvar_bot_ai_bunnyhop_skilloffset)
havocbot_bunnyhop(this, dir);
if (dir * v_up >= autocvar_sv_jumpvelocity * 0.5 && IS_ONGROUND(this))
this.enemy = NULL;
return;
}
+
if (this.enemy)
{
if (!bot_shouldattack(this, this.enemy))
this.enemy = NULL;
this.havocbot_chooseenemy_finished = time;
}
- else if (this.havocbot_stickenemy)
+ else if (this.havocbot_stickenemy_time && time < this.havocbot_stickenemy_time)
{
// tracking last chosen enemy
- // if enemy is visible
- // and not really really far away
- // and we're not severely injured
- // then keep tracking for a half second into the future
- traceline(this.origin+this.view_ofs, ( this.enemy.absmin + this.enemy.absmax ) * 0.5,false,NULL);
+ vector targ_pos = (this.enemy.absmin + this.enemy.absmax) * 0.5;
+ traceline(this.origin + this.view_ofs, targ_pos, false, NULL);
if (trace_ent == this.enemy || trace_fraction == 1)
- if (vdist(((this.enemy.absmin + this.enemy.absmax) * 0.5) - this.origin, <, 1000))
- if (GetResource(this, RES_HEALTH) > 30)
+ if (vdist(targ_pos - this.origin, <, 1000))
{
// remain tracking him for a shot while (case he went after a small corner or pilar
this.havocbot_chooseenemy_finished = time + 0.5;
return;
}
- // enemy isn't visible, or is far away, or we're injured severely
- // so stop preferring this enemy
- // (it will still take a half second until a new one is chosen)
- this.havocbot_stickenemy = 0;
+
+ // stop preferring this enemy
+ this.havocbot_stickenemy_time = 0;
}
}
if (time < this.havocbot_chooseenemy_finished)
this.havocbot_chooseenemy_finished = time + autocvar_bot_ai_enemydetectioninterval;
vector eye = this.origin + this.view_ofs;
entity best = NULL;
- float bestrating = 100000000;
+ float bestrating = autocvar_bot_ai_enemydetectionradius ** 2;
// Backup hit flags
int hf = this.dphitcontentsmask;
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, it))
+ if (rating < bestrating && bot_shouldattack(this, it))
{
traceline(eye, v, true, this);
if (trace_ent == it || trace_fraction >= 1)
{
scan_secondary_targets = true;
// restart the loop
- bestrating = 100000000;
+ bestrating = autocvar_bot_ai_enemydetectionradius ** 2;
goto scan_targets;
}
this.dphitcontentsmask = hf;
this.enemy = best;
- this.havocbot_stickenemy = true;
+ this.havocbot_stickenemy_time = time + autocvar_bot_ai_enemydetectioninterval_stickingtoenemy;
if(best && best.classname == "misc_breakablemodel")
- this.havocbot_stickenemy = false;
+ this.havocbot_stickenemy_time = 0;
}
float havocbot_chooseweapon_checkreload(entity this, .entity weaponentity, int new_weapon)
// Choose weapons for far distance
if ( distance > bot_distance_far ) {
- for(i=0; i < Weapons_COUNT && bot_weapons_far[i] != -1 ; ++i){
+ for(i=0; i < REGISTRY_COUNT(Weapons) && bot_weapons_far[i] != -1 ; ++i){
w = bot_weapons_far[i];
- if ( client_hasweapon(this, Weapons_from(w), weaponentity, true, false) )
+ if ( client_hasweapon(this, REGISTRY_GET(Weapons, w), weaponentity, true, false) )
{
if ((this.(weaponentity).m_weapon.m_id == w && combo) || havocbot_chooseweapon_checkreload(this, weaponentity, w))
continue;
- this.(weaponentity).m_switchweapon = Weapons_from(w);
+ this.(weaponentity).m_switchweapon = REGISTRY_GET(Weapons, w);
return;
}
}
// Choose weapons for mid distance
if ( distance > bot_distance_close) {
- for(i=0; i < Weapons_COUNT && bot_weapons_mid[i] != -1 ; ++i){
+ for(i=0; i < REGISTRY_COUNT(Weapons) && bot_weapons_mid[i] != -1 ; ++i){
w = bot_weapons_mid[i];
- if ( client_hasweapon(this, Weapons_from(w), weaponentity, true, false) )
+ if ( client_hasweapon(this, REGISTRY_GET(Weapons, w), weaponentity, true, false) )
{
if ((this.(weaponentity).m_weapon.m_id == w && combo) || havocbot_chooseweapon_checkreload(this, weaponentity, w))
continue;
- this.(weaponentity).m_switchweapon = Weapons_from(w);
+ this.(weaponentity).m_switchweapon = REGISTRY_GET(Weapons, w);
return;
}
}
}
// Choose weapons for close distance
- for(i=0; i < Weapons_COUNT && bot_weapons_close[i] != -1 ; ++i){
+ for(i=0; i < REGISTRY_COUNT(Weapons) && bot_weapons_close[i] != -1 ; ++i){
w = bot_weapons_close[i];
- if ( client_hasweapon(this, Weapons_from(w), weaponentity, true, false) )
+ if ( client_hasweapon(this, REGISTRY_GET(Weapons, w), weaponentity, true, false) )
{
if ((this.(weaponentity).m_weapon.m_id == w && combo) || havocbot_chooseweapon_checkreload(this, weaponentity, w))
continue;
- this.(weaponentity).m_switchweapon = Weapons_from(w);
+ this.(weaponentity).m_switchweapon = REGISTRY_GET(Weapons, w);
return;
}
}