#include "havocbot.qh"
-#include "../../_all.qh"
#include "../aim.qh"
#include "../bot.qh"
#include "../scripting.qh"
#include "../waypoints.qh"
-#include "../../../common/constants.qh"
+#include <common/constants.qh>
+#include <common/physics/player.qh>
+#include <common/state.qh>
+#include <common/items/all.qh>
-#include "../../../common/triggers/trigger/jumppads.qh"
+#include <common/triggers/trigger/jumppads.qh>
-#include "../../../warpzonelib/common.qh"
+#include <lib/warpzone/common.qh>
+
+.float speed;
void havocbot_ai()
-{
+{SELFPARAM();
if(self.draggedby)
return;
// 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(self.deadflag != DEAD_NO)
+ if(IS_DEAD(self))
if(self.goalcurrent==world)
if(self.waterlevel==WATERLEVEL_SWIMMING || (self.aistatus & AI_STATUS_OUT_WATER))
{
bot_strategytoken_taken = true;
}
- if(self.deadflag != DEAD_NO)
+ if(IS_DEAD(self))
return;
havocbot_chooseenemy();
if(self.weapons)
{
- WEP_ACTION(self.weapon, WR_AIM);
+ Weapon w = PS(self).m_weapon;
+ w.wr_aim(w);
if (autocvar_bot_nofire || IS_INDEPENDENT_PLAYER(self))
{
- self.BUTTON_ATCK = false;
- self.BUTTON_ATCK2 = false;
+ PHYS_INPUT_BUTTON_ATCK(self) = false;
+ PHYS_INPUT_BUTTON_ATCK2(self) = false;
}
else
{
- if(self.BUTTON_ATCK||self.BUTTON_ATCK2)
- self.lastfiredweapon = self.weapon;
+ if(PHYS_INPUT_BUTTON_ATCK(self) || PHYS_INPUT_BUTTON_ATCK2(self))
+ self.lastfiredweapon = PS(self).m_weapon.m_id;
}
}
else
if(skill >= 5) // bots can only look for unloaded weapons past this skill
if(self.clip_load >= 0) // only if we're not reloading a weapon already
{
- for (int i = WEP_FIRST; i <= WEP_LAST; ++i)
- {
- entity e = get_weaponinfo(i);
- if ((self.weapons & WepSet_FromWeapon(i)) && (e.spawnflags & WEP_FLAG_RELOADABLE) && (self.weapon_load[i] < e.reloading_ammo))
- self.switchweapon = i;
- }
+ FOREACH(Weapons, it != WEP_Null, LAMBDA(
+ if((self.weapons & (it.m_wepset)) && (it.spawnflags & WEP_FLAG_RELOADABLE) && (self.weapon_load[it.m_id] < it.reloading_ammo))
+ PS(self).m_switchweapon = it;
+ ));
}
}
}
void havocbot_keyboard_movement(vector destorg)
-{
+{SELFPARAM();
vector keyboard;
float blend, maxspeed;
float sk;
keyboard.z = 0;
self.havocbot_keyboard = keyboard * maxspeed;
- if (self.havocbot_ducktime>time) self.BUTTON_CROUCH=true;
+ if (self.havocbot_ducktime>time) PHYS_INPUT_BUTTON_CROUCH(self) = true;
keyboard = self.havocbot_keyboard;
blend = bound(0,vlen(destorg-self.origin)/autocvar_bot_ai_keyboard_distance,1); // When getting close move with 360 degree
}
void havocbot_bunnyhop(vector dir)
-{
+{SELFPARAM();
float bunnyhopdistance;
vector deviation;
float maxspeed;
if(self.aistatus & AI_STATUS_DANGER_AHEAD)
{
self.aistatus &= ~AI_STATUS_RUNNING;
- self.BUTTON_JUMP = false;
+ PHYS_INPUT_BUTTON_JUMP(self) = false;
self.bot_canruntogoal = 0;
self.bot_timelastseengoal = 0;
return;
bunnyhopdistance = vlen(self.origin - gco);
// Run only to visible goals
- if(self.flags & FL_ONGROUND)
+ if(IS_ONGROUND(self))
if(self.speed==maxspeed)
if(checkpvs(self.origin + self.view_ofs, self.goalcurrent))
{
{
self.aistatus &= ~AI_STATUS_RUNNING;
if(bunnyhopdistance > autocvar_bot_ai_bunnyhop_stopdistance)
- self.BUTTON_JUMP = true;
+ PHYS_INPUT_BUTTON_JUMP(self) = true;
}
else
{
self.aistatus |= AI_STATUS_RUNNING;
- self.BUTTON_JUMP = true;
+ PHYS_INPUT_BUTTON_JUMP(self) = true;
}
}
}
#if 0
// Release jump button
if(!cvar("sv_pogostick"))
- if((self.flags & FL_ONGROUND) == 0)
+ if((IS_ONGROUND(self)) == 0)
{
if(self.velocity.z < 0 || vlen(self.velocity)<maxspeed)
- self.BUTTON_JUMP = false;
+ PHYS_INPUT_BUTTON_JUMP(self) = false;
// Strafe
if(self.aistatus & AI_STATUS_RUNNING)
}
void havocbot_movetogoal()
-{
+{SELFPARAM();
vector destorg;
vector diff;
vector dir;
}
// Flying
- self.BUTTON_HOOK = true;
- if(self.navigation_jetpack_point.z - PL_MAX.z + PL_MIN.z < self.origin.z)
+ PHYS_INPUT_BUTTON_HOOK(self) = true;
+ if(self.navigation_jetpack_point.z - STAT(PL_MAX, NULL).z + STAT(PL_MIN, NULL).z < self.origin.z)
{
self.movement_x = dir * v_forward * maxspeed;
self.movement_y = dir * v_right * maxspeed;
threshold = maxspeed * 0.2;
if(sxy < threshold)
{
- dprint("Warning: ", self.netname, " got stuck on a jumppad (velocity in xy is ", ftos(sxy), "), trying to get out of it now\n");
+ LOG_TRACE("Warning: ", self.netname, " got stuck on a jumppad (velocity in xy is ", ftos(sxy), "), trying to get out of it now\n");
self.aistatus |= AI_STATUS_OUT_JUMPPAD;
}
return;
// If there is a trigger_hurt right below try to use the jetpack or make a rocketjump
if(skill>6)
- if (!(self.flags & FL_ONGROUND))
+ if (!(IS_ONGROUND(self)))
{
tracebox(self.origin, self.mins, self.maxs, self.origin + '0 0 -65536', MOVE_NOMONSTERS, self);
if(tracebox_hits_trigger_hurt(self.origin, self.mins, self.maxs, trace_endpos ))
{
if(self.velocity.z<0)
{
- self.BUTTON_HOOK = true;
+ PHYS_INPUT_BUTTON_HOOK(self) = true;
}
}
else
- self.BUTTON_HOOK = true;
+ PHYS_INPUT_BUTTON_HOOK(self) = true;
// If there is no goal try to move forward
else if(self.health>WEP_CVAR(devastator, damage)*0.5)
{
if(self.velocity.z < 0)
- if(client_hasweapon(self, WEP_DEVASTATOR.m_id, true, false))
+ if(client_hasweapon(self, WEP_DEVASTATOR, true, false))
{
self.movement_x = maxspeed;
{
if(time > self.rocketjumptime)
{
- self.BUTTON_ATCK2 = true;
+ PHYS_INPUT_BUTTON_ATCK2(self) = true;
self.rocketjumptime = 0;
}
return;
}
- self.switchweapon = WEP_DEVASTATOR.m_id;
+ PS(self).m_switchweapon = WEP_DEVASTATOR;
self.v_angle_x = 90;
- self.BUTTON_ATCK = true;
+ PHYS_INPUT_BUTTON_ATCK(self) = true;
self.rocketjumptime = time + WEP_CVAR(devastator, detonatedelay);
return;
}
if(self.waterlevel>WATERLEVEL_SWIMMING)
dir.z = 1;
else if(self.velocity.z >= 0 && !(self.waterlevel == WATERLEVEL_WETFEET && self.watertype == CONTENT_WATER))
- self.BUTTON_JUMP = true;
+ PHYS_INPUT_BUTTON_JUMP(self) = true;
else
- self.BUTTON_JUMP = false;
+ PHYS_INPUT_BUTTON_JUMP(self) = false;
makevectors(self.v_angle.y * '0 1 0');
self.movement_x = dir * v_forward * maxspeed;
self.movement_y = dir * v_right * maxspeed;
{
if(self.velocity.z >= 0 && !(self.watertype == CONTENT_WATER && gco.z < self.origin.z) &&
( !(self.waterlevel == WATERLEVEL_WETFEET && self.watertype == CONTENT_WATER) || self.aistatus & AI_STATUS_OUT_WATER))
- self.BUTTON_JUMP = true;
+ PHYS_INPUT_BUTTON_JUMP(self) = true;
else
- self.BUTTON_JUMP = false;
+ PHYS_INPUT_BUTTON_JUMP(self) = false;
}
dir = normalize(flatdir);
makevectors(self.v_angle.y * '0 1 0');
s = trace_fraction;
tracebox(self.origin + jumpstepheightvec, self.mins, self.maxs, self.origin + self.velocity * 0.2 + jumpstepheightvec, false, self);
if (trace_fraction > s)
- self.BUTTON_JUMP = 1;
+ PHYS_INPUT_BUTTON_JUMP(self) = true;
}
}
// Check head-banging against walls
if(vlen(self.origin + self.view_ofs - trace_endpos) < 25 && !(self.aistatus & AI_STATUS_OUT_WATER))
{
- self.BUTTON_JUMP = true;
+ PHYS_INPUT_BUTTON_JUMP(self) = true;
if(self.facingwalltime && time > self.facingwalltime)
{
self.ignoregoal = self.goalcurrent;
self.aistatus &= ~AI_STATUS_DANGER_AHEAD;
if(trace_fraction == 1 && self.jumppadcount == 0 && !self.goalcurrent.wphardwired )
- if((self.flags & FL_ONGROUND) || (self.aistatus & AI_STATUS_RUNNING) || self.BUTTON_JUMP == true)
+ if((IS_ONGROUND(self)) || (self.aistatus & AI_STATUS_RUNNING) || PHYS_INPUT_BUTTON_JUMP(self))
{
// Look downwards
traceline(dst_ahead , dst_down, true, world);
if(tracebox_hits_trigger_hurt(dst_ahead, self.mins, self.maxs, trace_endpos))
{
// Remove dangerous dynamic goals from stack
- dprint("bot ", self.netname, " avoided the goal ", self.goalcurrent.classname, " ", etos(self.goalcurrent), " because it led to a dangerous path; goal stack cleared\n");
+ LOG_TRACE("bot ", self.netname, " avoided the goal ", self.goalcurrent.classname, " ", etos(self.goalcurrent), " because it led to a dangerous path; goal stack cleared\n");
navigation_clearroute();
return;
}
dir = normalize(dir + dodge + evadeobstacle + evadelava);
// self.bot_dodgevector = dir;
- // self.bot_dodgevector_jumpbutton = self.BUTTON_JUMP;
+ // self.bot_dodgevector_jumpbutton = PHYS_INPUT_BUTTON_JUMP(self);
}
if(time < self.ladder_time)
//dir = self.bot_dodgevector;
//if (self.bot_dodgevector_jumpbutton)
- // self.BUTTON_JUMP = 1;
+ // PHYS_INPUT_BUTTON_JUMP(self) = true;
self.movement_x = dir * v_forward * maxspeed;
self.movement_y = dir * v_right * maxspeed;
self.movement_z = dir * v_up * maxspeed;
if(skill+self.bot_moveskill >= autocvar_bot_ai_bunnyhop_skilloffset)
havocbot_bunnyhop(dir);
- if ((dir * v_up) >= autocvar_sv_jumpvelocity*0.5 && (self.flags & FL_ONGROUND)) self.BUTTON_JUMP=1;
- if (((dodge * v_up) > 0) && random()*frametime >= 0.2*bound(0,(10-skill-self.bot_dodgeskill)*0.1,1)) self.BUTTON_JUMP=true;
+ if ((dir * v_up) >= autocvar_sv_jumpvelocity*0.5 && (IS_ONGROUND(self))) PHYS_INPUT_BUTTON_JUMP(self) = true;
+ if (((dodge * v_up) > 0) && random()*frametime >= 0.2*bound(0,(10-skill-self.bot_dodgeskill)*0.1,1)) PHYS_INPUT_BUTTON_JUMP(self) = true;
if (((dodge * v_up) < 0) && random()*frametime >= 0.5*bound(0,(10-skill-self.bot_dodgeskill)*0.1,1)) self.havocbot_ducktime=time+0.3/bound(0.1,skill+self.bot_dodgeskill,10);
}
void havocbot_chooseenemy()
-{
+{SELFPARAM();
entity head, best, head2;
- float rating, bestrating, i, hf;
+ float rating, bestrating, hf;
vector eye, v;
if (autocvar_bot_nofire || IS_INDEPENDENT_PLAYER(self))
{
self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
- for(i = 0; ; ++i)
+ bool scan_transparent = false;
+ bool scan_secondary_targets = false;
+ bool have_secondary_targets = false;
+ while(true)
{
- while (head)
+ scan_secondary_targets = false;
+ :scan_targets
+ for( ; head; head = head.chain)
{
+ if(!scan_secondary_targets)
+ {
+ if(head.classname == "misc_breakablemodel")
+ {
+ have_secondary_targets = true;
+ continue;
+ }
+ }
+ else
+ {
+ if(head.classname != "misc_breakablemodel")
+ continue;
+ }
+
v = (head.absmin + head.absmax) * 0.5;
rating = vlen(v - eye);
if (rating<autocvar_bot_ai_enemydetectionradius)
bestrating = rating;
}
}
- head = head.chain;
+ }
+
+ if(!best && have_secondary_targets && !scan_secondary_targets)
+ {
+ scan_secondary_targets = true;
+ // restart the loop
+ head = head2;
+ bestrating = 100000000;
+ goto scan_targets;
}
// I want to do a second scan if no enemy was found or I don't have weapons
// TODO: Perform the scan when using the rifle (requires changes on the rifle code)
if(best || self.weapons) // || self.weapon == WEP_RIFLE.m_id
break;
- if(i)
+ if(scan_transparent)
break;
// Set flags to see through transparent objects
self.dphitcontentsmask |= DPCONTENTS_OPAQUE;
head = head2;
+ scan_transparent = true;
}
// Restore hit flags
self.enemy = best;
self.havocbot_stickenemy = true;
+ if(best && best.classname == "misc_breakablemodel")
+ self.havocbot_stickenemy = false;
}
float havocbot_chooseweapon_checkreload(int new_weapon)
-{
+{SELFPARAM();
// bots under this skill cannot find unloaded weapons to reload idly when not in combat,
// so skip this for them, or they'll never get to reload their weapons at all.
// this also allows bots under this skill to be more stupid, and reload more often during combat :)
// if this weapon is scheduled for reloading, don't switch to it during combat
if (self.weapon_load[new_weapon] < 0)
{
- float i, other_weapon_available = false;
- for(i = WEP_FIRST; i <= WEP_LAST; ++i)
- {
- // if we are out of ammo for all other weapons, it's an emergency to switch to anything else
- if (WEP_ACTION(i, WR_CHECKAMMO1) + WEP_ACTION(i, WR_CHECKAMMO2))
+ bool other_weapon_available = false;
+ FOREACH(Weapons, it != WEP_Null, LAMBDA(
+ if(it.wr_checkammo1(it) + it.wr_checkammo2(it))
other_weapon_available = true;
- }
+ ));
if(other_weapon_available)
return true;
}
}
void havocbot_chooseweapon()
-{
+{SELFPARAM();
int i;
// ;)
- if(g_weaponarena_weapons == WEPSET_TUBA)
+ if(g_weaponarena_weapons == WEPSET(TUBA))
{
- self.switchweapon = WEP_TUBA.m_id;
+ PS(self).m_switchweapon = WEP_TUBA;
return;
}
if(self.enemy==world)
{
// If no weapon was chosen get the first available weapon
- if(self.weapon==0)
- for(i = WEP_FIRST; i <= WEP_LAST; ++i) if(i != WEP_BLASTER.m_id)
- {
- if(client_hasweapon(self, i, true, false))
+ if(PS(self).m_weapon==WEP_Null)
+ FOREACH(Weapons, it != WEP_Null, LAMBDA(
+ if(client_hasweapon(self, it, true, false))
{
- self.switchweapon = i;
+ PS(self).m_switchweapon = it;
return;
}
- }
+ ));
return;
}
// Should it do a weapon combo?
float af, ct, combo_time, combo;
- af = ATTACK_FINISHED(self);
+ af = ATTACK_FINISHED(self, 0);
ct = autocvar_bot_ai_weapon_combo_threshold;
// Bots with no skill will be 4 times more slower than "godlike" bots when doing weapon combos
combo = false;
if(autocvar_bot_ai_weapon_combo)
- if(self.weapon == self.lastfiredweapon)
+ if(PS(self).m_weapon.m_id == self.lastfiredweapon)
if(af > combo_time)
{
combo = true;
// Choose weapons for far distance
if ( distance > bot_distance_far ) {
- for(i=0; i < WEP_COUNT && bot_weapons_far[i] != -1 ; ++i){
+ for(i=0; i < Weapons_COUNT && bot_weapons_far[i] != -1 ; ++i){
w = bot_weapons_far[i];
- if ( client_hasweapon(self, w, true, false) )
+ if ( client_hasweapon(self, Weapons_from(w), true, false) )
{
- if ((self.weapon == w && combo) || havocbot_chooseweapon_checkreload(w))
+ if ((PS(self).m_weapon.m_id == w && combo) || havocbot_chooseweapon_checkreload(w))
continue;
- self.switchweapon = w;
+ PS(self).m_switchweapon = Weapons_from(w);
return;
}
}
// Choose weapons for mid distance
if ( distance > bot_distance_close) {
- for(i=0; i < WEP_COUNT && bot_weapons_mid[i] != -1 ; ++i){
+ for(i=0; i < Weapons_COUNT && bot_weapons_mid[i] != -1 ; ++i){
w = bot_weapons_mid[i];
- if ( client_hasweapon(self, w, true, false) )
+ if ( client_hasweapon(self, Weapons_from(w), true, false) )
{
- if ((self.weapon == w && combo) || havocbot_chooseweapon_checkreload(w))
+ if ((PS(self).m_weapon.m_id == w && combo) || havocbot_chooseweapon_checkreload(w))
continue;
- self.switchweapon = w;
+ PS(self).m_switchweapon = Weapons_from(w);
return;
}
}
}
// Choose weapons for close distance
- for(i=0; i < WEP_COUNT && bot_weapons_close[i] != -1 ; ++i){
+ for(i=0; i < Weapons_COUNT && bot_weapons_close[i] != -1 ; ++i){
w = bot_weapons_close[i];
- if ( client_hasweapon(self, w, true, false) )
+ if ( client_hasweapon(self, Weapons_from(w), true, false) )
{
- if ((self.weapon == w && combo) || havocbot_chooseweapon_checkreload(w))
+ if ((PS(self).m_weapon.m_id == w && combo) || havocbot_chooseweapon_checkreload(w))
continue;
- self.switchweapon = w;
+ PS(self).m_switchweapon = Weapons_from(w);
return;
}
}
}
void havocbot_aim()
-{
+{SELFPARAM();
vector selfvel, enemyvel;
// if(self.flags & FL_INWATER)
// return;
}
float havocbot_moveto_refresh_route()
-{
+{SELFPARAM();
// Refresh path to goal if necessary
entity wp;
wp = self.havocbot_personal_waypoint;
}
float havocbot_moveto(vector pos)
-{
+{SELFPARAM();
entity wp;
if(self.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
// Step 4: Move to waypoint
if(self.havocbot_personal_waypoint==world)
{
- dprint("Error: ", self.netname, " trying to walk to a non existent personal waypoint\n");
+ LOG_TRACE("Error: ", self.netname, " trying to walk to a non existent personal waypoint\n");
self.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
return CMD_STATUS_ERROR;
}
bot_strategytoken_taken = true;
if(havocbot_moveto_refresh_route())
{
- dprint(self.netname, " walking to its personal waypoint (after ", ftos(self.havocbot_personal_waypoint_failcounter), " failed attempts)\n");
+ LOG_TRACE(self.netname, " walking to its personal waypoint (after ", ftos(self.havocbot_personal_waypoint_failcounter), " failed attempts)\n");
self.havocbot_personal_waypoint_searchtime = time + 10;
self.havocbot_personal_waypoint_failcounter = 0;
}
self.havocbot_personal_waypoint_searchtime = time + 2;
if(self.havocbot_personal_waypoint_failcounter >= 30)
{
- dprint("Warning: can't walk to the personal waypoint located at ", vtos(self.havocbot_personal_waypoint.origin),"\n");
+ LOG_TRACE("Warning: can't walk to the personal waypoint located at ", vtos(self.havocbot_personal_waypoint.origin),"\n");
self.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_LINKING;
remove(self.havocbot_personal_waypoint);
return CMD_STATUS_ERROR;
}
else
- dprint(self.netname, " can't walk to its personal waypoint (after ", ftos(self.havocbot_personal_waypoint_failcounter), " failed attempts), trying later\n");
+ LOG_TRACE(self.netname, " can't walk to its personal waypoint (after ", ftos(self.havocbot_personal_waypoint_failcounter), " failed attempts), trying later\n");
}
}
if(self.aistatus & AI_STATUS_WAYPOINT_PERSONAL_REACHED)
{
// Step 5: Waypoint reached
- dprint(self.netname, "'s personal waypoint reached\n");
+ LOG_TRACE(self.netname, "'s personal waypoint reached\n");
remove(self.havocbot_personal_waypoint);
self.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_REACHED;
return CMD_STATUS_FINISHED;
// Wait until it is linked
if(!self.havocbot_personal_waypoint.wplinked)
{
- dprint(self.netname, " waiting for personal waypoint to be linked\n");
+ LOG_TRACE(self.netname, " waiting for personal waypoint to be linked\n");
return CMD_STATUS_EXECUTING;
}
self.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_GOING;
// Step 3: Route to waypoint
- dprint(self.netname, " walking to its personal waypoint\n");
+ LOG_TRACE(self.netname, " walking to its personal waypoint\n");
return CMD_STATUS_EXECUTING;
}
wp = waypoint_spawnpersonal(pos);
if(wp==world)
{
- dprint("Error: Can't spawn personal waypoint at ",vtos(pos),"\n");
+ LOG_TRACE("Error: Can't spawn personal waypoint at ",vtos(pos),"\n");
return CMD_STATUS_ERROR;
}
}
void havocbot_setupbot()
-{
+{SELFPARAM();
self.bot_ai = havocbot_ai;
self.cmd_moveto = havocbot_moveto;
self.cmd_resetgoal = havocbot_resetgoal;
}
vector havocbot_dodge()
-{
+{SELFPARAM();
// LordHavoc: disabled because this is too expensive
return '0 0 0';
#if 0