}
else
{
- self.havocbot_role();
+ if not(self.jumppadcount)
+ self.havocbot_role();
}
// TODO: tracewalk() should take care of this job (better path finding under water)
havocbot_chooseenemy();
if (self.bot_chooseweapontime < time )
{
- self.bot_chooseweapontime = time + cvar("bot_ai_chooseweaponinterval");
+ self.bot_chooseweapontime = time + autocvar_bot_ai_chooseweaponinterval;
havocbot_chooseweapon();
}
havocbot_aim();
if(self.weapons)
{
weapon_action(self.weapon, WR_AIM);
- if (cvar("bot_nofire") || IS_INDEPENDENT_PLAYER(self))
+ if (autocvar_bot_nofire || IS_INDEPENDENT_PLAYER(self))
{
self.BUTTON_ATCK = FALSE;
self.BUTTON_ATCK2 = FALSE;
next = self.goalstack01.origin - (self.origin + self.view_ofs);
skillblend=bound(0,(skill+self.bot_moveskill-2.5)*0.5,1); //lower skill player can't preturn
- distanceblend=bound(0,aimdistance/cvar("bot_ai_keyboard_distance"),1);
+ 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);
bot_aimdir(v, -1);
}
havocbot_movetogoal();
+
+ // if the bot is not attacking, consider reloading weapons
+ if not(self.aistatus & AI_STATUS_ATTACKING)
+ {
+ float i;
+ entity e;
+
+ // 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(self.clip_load < self.clip_size)
+ self.impulse = 20; // "press" the reload button, 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(self.clip_load >= 0) // only if we're not reloading a weapon already
+ {
+ for(i = WEP_FIRST; i <= WEP_LAST; ++i)
+ {
+ e = get_weaponinfo(i);
+ if ((e.spawnflags & WEP_FLAG_RELOADABLE) && (self.weapon_load[i] < cvar(strcat("g_balance_", e.netname, "_reload_ammo"))))
+ self.switchweapon = i;
+ }
+ }
+ }
};
void havocbot_keyboard_movement(vector destorg)
sk = skill + self.bot_moveskill;
- maxspeed = cvar("sv_maxspeed");
+ maxspeed = autocvar_sv_maxspeed;
if (time < self.havocbot_keyboardtime)
return;
local float trigger, trigger1;
blend = bound(0,sk*0.1,1);
- trigger = cvar("bot_ai_keyboard_treshold");
+ trigger = autocvar_bot_ai_keyboard_threshold;
trigger1 = 0 - trigger;
// categorize forward movement
if (self.havocbot_ducktime>time) self.BUTTON_CROUCH=TRUE;
keyboard = self.havocbot_keyboard;
- blend = bound(0,vlen(destorg-self.origin)/cvar("bot_ai_keyboard_distance"),1); // When getting close move with 360 degree
+ blend = bound(0,vlen(destorg-self.origin)/autocvar_bot_ai_keyboard_distance,1); // When getting close move with 360 degree
//dprint("movement ", vtos(self.movement), " keyboard ", vtos(keyboard), " blend ", ftos(blend), "\n");
self.movement = self.movement + (keyboard - self.movement) * blend;
};
local vector deviation;
local float maxspeed;
- if(cvar("g_midair"))
+ if(autocvar_g_midair)
return;
// Don't jump when using some weapons
+ /*
if(self.aistatus & AI_STATUS_ATTACKING)
- if(self.weapon & WEP_CAMPINGRIFLE)
+ if(self.weapon == WEP_RIFLE)
return;
if(self.goalcurrent.classname == "player")
return;
+ */
- maxspeed = cvar("sv_maxspeed");
+ maxspeed = autocvar_sv_maxspeed;
if(self.aistatus & AI_STATUS_DANGER_AHEAD)
{
if(self.bot_timelastseengoal)
{
// for a period of time
- if(time - self.bot_timelastseengoal > cvar("bot_ai_bunnyhop_firstjumpdelay"))
+ if(time - self.bot_timelastseengoal > autocvar_bot_ai_bunnyhop_firstjumpdelay)
{
local float checkdistance;
checkdistance = TRUE;
// don't run if it is too close
if(self.bot_canruntogoal==0)
{
- if(bunnyhopdistance > cvar("bot_ai_bunnyhop_startdistance"))
+ if(bunnyhopdistance > autocvar_bot_ai_bunnyhop_startdistance)
self.bot_canruntogoal = 1;
else
self.bot_canruntogoal = -1;
if(bunnyhopdistance < vlen(self.origin - self.goalstack01.origin))
if(fabs(self.goalstack01.origin_z - self.goalcurrent.origin_z) < self.maxs_z - self.mins_z)
{
- if(vlen(self.goalcurrent.origin - self.goalstack01.origin) > cvar("bot_ai_bunnyhop_startdistance"))
+ if(vlen(self.goalcurrent.origin - self.goalstack01.origin) > autocvar_bot_ai_bunnyhop_startdistance)
if(checkpvs(self.origin + self.view_ofs, self.goalstack01))
{
checkdistance = FALSE;
if(checkdistance)
{
self.aistatus &~= AI_STATUS_RUNNING;
- if(bunnyhopdistance > cvar("bot_ai_bunnyhop_stopdistance"))
+ if(bunnyhopdistance > autocvar_bot_ai_bunnyhop_stopdistance)
self.BUTTON_JUMP = TRUE;
}
else
}
// Release jump button
+ if(!cvar("sv_pogostick"))
if(self.flags & FL_ONGROUND == 0)
{
if(self.velocity_z < 0 || vlen(self.velocity)<maxspeed)
//if (self.goalentity)
// te_lightning2(self, self.origin, (self.goalentity.absmin + self.goalentity.absmax) * 0.5);
self.movement = '0 0 0';
- maxspeed = cvar("sv_maxspeed");
+ maxspeed = autocvar_sv_maxspeed;
// Jetpack navigation
if(self.goalcurrent)
if(self.goalcurrent==self.navigation_jetpack_goal)
if(self.ammo_fuel)
{
- #ifdef DEBUG_BOT_GOALSTACK
+ if(autocvar_bot_debug_goalstack)
+ {
debuggoalstack();
te_wizspike(self.navigation_jetpack_point);
- #endif
+ }
// Take off
if not(self.aistatus & AI_STATUS_JETPACK_FLYING)
dxy = self.origin - self.goalcurrent.origin; dxy_z = 0;
d = vlen(dxy);
v = vlen(self.velocity - self.velocity_z * '0 0 1');
- db = (pow(v,2) / (cvar("g_jetpack_acceleration_side") * 2)) + 100;
+ db = (pow(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)
{
// Handling of jump pads
if(self.jumppadcount)
{
- if(self.flags & FL_ONGROUND)
- {
- self.jumppadcount = FALSE;
- if(self.aistatus & AI_STATUS_OUT_JUMPPAD)
- self.aistatus &~= AI_STATUS_OUT_JUMPPAD;
- }
-
- // If got stuck on the jump pad try to reach the farther visible item
+ // If got stuck on the jump pad try to reach the farthest visible item
if(self.aistatus & AI_STATUS_OUT_JUMPPAD)
{
if(fabs(self.velocity_z)<50)
if(newgoal)
{
self.ignoregoal = self.goalcurrent;
- self.ignoregoaltime = time + cvar("bot_ai_ignoregoal_timeout");
+ self.ignoregoaltime = time + autocvar_bot_ai_ignoregoal_timeout;
navigation_clearroute();
navigation_routetogoal(newgoal, self.origin);
self.aistatus &~= AI_STATUS_OUT_JUMPPAD;
local float threshold;
threshold = maxspeed * 0.2;
if(fabs(self.velocity_x) < threshold && fabs(self.velocity_y) < threshold)
+ {
+ dprint("Warning: ", self.netname, " got stuck on a jumppad, trying to get out of it now\n");
self.aistatus |= AI_STATUS_OUT_JUMPPAD;
+ }
return;
}
+
+ // Don't chase players while using a jump pad
+ if(self.goalcurrent.classname=="player" || self.goalstack01.classname=="player")
+ return;
}
}
+ else if(self.aistatus & AI_STATUS_OUT_JUMPPAD)
+ self.aistatus &~= AI_STATUS_OUT_JUMPPAD;
// If there is a trigger_hurt right below try to use the jetpack or make a rocketjump
if(skill>6)
return;
}
- else if(self.health>cvar("g_balance_rocketlauncher_damage")*0.5)
+ else if(self.health>autocvar_g_balance_rocketlauncher_damage*0.5)
{
if(self.velocity_z < 0)
if(client_hasweapon(self, WEP_ROCKET_LAUNCHER, TRUE, FALSE))
self.switchweapon = WEP_ROCKET_LAUNCHER;
self.v_angle_x = 90;
self.BUTTON_ATCK = TRUE;
- self.rocketjumptime = time + cvar("g_balance_rocketlauncher_detonatedelay");
+ self.rocketjumptime = time + autocvar_g_balance_rocketlauncher_detonatedelay;
return;
}
}
return;
}
-#ifdef DEBUG_BOT_GOALSTACK
- debuggoalstack();
-#endif
+ if(autocvar_bot_debug_goalstack)
+ debuggoalstack();
m1 = self.goalcurrent.origin + self.goalcurrent.mins;
m2 = self.goalcurrent.origin + self.goalcurrent.maxs;
if(self.facingwalltime && time > self.facingwalltime)
{
self.ignoregoal = self.goalcurrent;
- self.ignoregoaltime = time + cvar("bot_ai_ignoregoal_timeout");
+ self.ignoregoaltime = time + autocvar_bot_ai_ignoregoal_timeout;
self.bot_strategytime = 0;
return;
}
// (only when the bot is on the ground or jumping intentionally)
self.aistatus &~= AI_STATUS_DANGER_AHEAD;
- if(trace_fraction == 1)
+ if(trace_fraction == 1 && self.jumppadcount == 0)
if(self.flags & FL_ONGROUND || self.aistatus & AI_STATUS_RUNNING || self.BUTTON_JUMP == TRUE)
{
// Look downwards
// Bunnyhop!
// if(self.aistatus & AI_STATUS_ROAMING)
if(self.goalcurrent)
- if(skill+self.bot_moveskill >= cvar("bot_ai_bunnyhop_skilloffset"))
+ if(skill+self.bot_moveskill >= autocvar_bot_ai_bunnyhop_skilloffset)
havocbot_bunnyhop(dir);
- if ((dir * v_up) >= cvar("sv_jumpvelocity")*0.5 && (self.flags & FL_ONGROUND)) self.BUTTON_JUMP=1;
+ 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 (((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);
};
local entity head, best, head2;
local float rating, bestrating, i, f;
local vector eye, v;
- if (cvar("bot_nofire") || IS_INDEPENDENT_PLAYER(self))
+ if (autocvar_bot_nofire || IS_INDEPENDENT_PLAYER(self))
{
self.enemy = world;
return;
if (self.health > 30)
{
// remain tracking him for a shot while (case he went after a small corner or pilar
- self.havocbot_chooseenemy_finished = time + cvar("bot_ai_enemydetectioninterval");
+ self.havocbot_chooseenemy_finished = time + autocvar_bot_ai_enemydetectioninterval;
return;
}
// enemy isn't visible, or is far away, or we're injured severely
}
if (time < self.havocbot_chooseenemy_finished)
return;
- self.havocbot_chooseenemy_finished = time + cvar("bot_ai_enemydetectioninterval");
+ self.havocbot_chooseenemy_finished = time + autocvar_bot_ai_enemydetectioninterval;
eye = self.origin + self.view_ofs;
best = world;
bestrating = 100000000;
{
v = (head.absmin + head.absmax) * 0.5;
rating = vlen(v - eye);
- if (rating<cvar("bot_ai_enemydetectionradius"))
+ if (rating<autocvar_bot_ai_enemydetectionradius)
if (bestrating > rating)
if (bot_shouldattack(head))
{
// 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_CAMPINGRIFLE
+ if(best || self.weapons) // || self.weapon == WEP_RIFLE
break;
if(i)
break;
self.havocbot_stickenemy = TRUE;
};
+float havocbot_chooseweapon_checkreload(float new_weapon)
+{
+ // 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(skill < 5)
+ return FALSE;
+
+ // if this weapon is scheduled for reloading, don't switch to it during combat
+ if (self.weapon_load[new_weapon] < 0)
+ {
+ local float i, other_weapon_available;
+ 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 (weapon_action(i, WR_CHECKAMMO1) + weapon_action(i, WR_CHECKAMMO2))
+ other_weapon_available = TRUE;
+ }
+ if(other_weapon_available)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
void havocbot_chooseweapon()
{
local float i;
if(i < 1)
return;
- // Workaround for rifle reloading (..)
- if(self.weapon == WEP_CAMPINGRIFLE)
- if(i < cvar("g_balance_campingrifle_reloadtime") + 1)
- return;
-
local float w;
- local float rocket ; rocket =-1000;
- local float nex ; nex =-1000;
- local float hagar ; hagar =-1000;
- local float grenade ; grenade =-1000;
- local float mine ; mine =-1000;
- local float electro ; electro =-1000;
- local float crylink ; crylink =-1000;
- local float uzi ; uzi =-1000;
- local float shotgun ; shotgun =-1000;
- local float campingrifle ; campingrifle =-1000;
- local float laser ; laser =-1000;
- local float minstanex ; minstanex =-1000;
- local float bestscore; bestscore = 0;
- local float bestweapon; bestweapon=self.switchweapon;
local float distance; distance=bound(10,vlen(self.origin-self.enemy.origin)-200,10000);
- local float maxdelaytime=0.5;
- local float spreadpenalty=10;
// Should it do a weapon combo?
local float af, ct, combo_time, combo;
af = ATTACK_FINISHED(self);
- ct = cvar("bot_ai_weapon_combo_threshold");
+ ct = autocvar_bot_ai_weapon_combo_threshold;
// Bots with no skill will be 4 times more slower than "godlike" bots when doing weapon combos
// Ideally this 4 should be calculated as longest_weapon_refire / bot_ai_weapon_combo_threshold
combo = FALSE;
- if(cvar("bot_ai_weapon_combo"))
+ if(autocvar_bot_ai_weapon_combo)
if(self.weapon == self.lastfiredweapon)
if(af > combo_time)
{
if ( distance > bot_distance_far ) {
for(i=0; i < WEP_COUNT && bot_weapons_far[i] != -1 ; ++i){
w = bot_weapons_far[i];
- if ( client_hasweapon(self, w, TRUE, FALSE) ){
- if ( self.weapon == w && combo)
+ if ( client_hasweapon(self, w, TRUE, FALSE) )
+ {
+ if ((self.weapon == w && combo) || havocbot_chooseweapon_checkreload(w))
continue;
self.switchweapon = w;
return;
if ( distance > bot_distance_close) {
for(i=0; i < WEP_COUNT && bot_weapons_mid[i] != -1 ; ++i){
w = bot_weapons_mid[i];
- if ( client_hasweapon(self, w, TRUE, FALSE) ){
- if ( self.weapon == w && combo)
+ if ( client_hasweapon(self, w, TRUE, FALSE) )
+ {
+ if ((self.weapon == w && combo) || havocbot_chooseweapon_checkreload(w))
continue;
self.switchweapon = w;
return;
// Choose weapons for close distance
for(i=0; i < WEP_COUNT && bot_weapons_close[i] != -1 ; ++i){
w = bot_weapons_close[i];
- if ( client_hasweapon(self, w, TRUE, FALSE) ){
- if ( self.weapon == w && combo)
+ if ( client_hasweapon(self, w, TRUE, FALSE) )
+ {
+ if ((self.weapon == w && combo) || havocbot_chooseweapon_checkreload(w))
continue;
self.switchweapon = w;
return;
}
}
- #ifdef DEBUG_BOT_GOALSTACK
+ if(autocvar_bot_debug_goalstack)
debuggoalstack();
- #endif
// Heading
local vector dir = self.goalcurrent.origin - (self.origin + self.view_ofs);
{
// LordHavoc: disabled because this is too expensive
return '0 0 0';
- /*
+#if 0
local entity head;
local vector dodge, v, n;
local float danger, bestdanger, vl, d;
head = head.chain;
}
return dodge;
- */
+#endif
};