//
// C++ Implementation: bot_ai
//
// Description: The AI part comes here(navigation, shooting etc)
//
//
// Author: <rickhelmus@gmail.com>
//
// Code of CBot - Start
#include "cube.h"
#include "bot.h"
extern weaponinfo_s WeaponInfoTable[NUMGUNS];
vec CBot::GetEnemyPos(playerent *d)
{
// Aim offset idea by botman
vec o = m_pMyEnt->weaponsel->type == GUN_SNIPER && d->head.x >= 0 ? d->head : d->o, offset;
float flDist = GetDistance(o), flScale;
if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_ROCKET)
{
// Bot is using a rocket launcher, aim at enemy feet?
if (m_bShootAtFeet && !OUTBORD(d->o.x, d->o.y))
{
// Only do this when enemy is fairly close to the ground
vec end = o;
end.z -= 900.0f;
traceresult_s tr;
TraceLine(o, end, NULL, false, &tr);
if ((o.z - tr.end.z) < 8.0f)
{
end = o;
end.z = tr.end.z;
if (IsVisible(end))
{
// Target at ground
o.z = tr.end.z;
}
}
}
if (m_pBotSkill->bCanPredict)
{
// How higher the skill, how further the bot predicts
float flPredictTime = RandomFloat(1.25f, 1.7f) / (m_sSkillNr+1);
o = PredictPos(o, d->vel, flPredictTime);
}
}
else
{
if (m_pBotSkill->bCanPredict)
{
// How higher the skill, how 'more' the bot predicts
float flPredictTime = RandomFloat(0.8f, 1.2f) / (m_sSkillNr+1);
o = PredictPos(o, d->vel, flPredictTime);
}
}
if (flDist > 60.0f)
flScale = 1.0f;
else if (flDist > 6.0f)
flScale = flDist / 60.0f;
else
flScale = 0.1f;
switch (m_sSkillNr)
{
case 0:
// no offset
offset.x = 0;
offset.y = 0;
offset.z = 0;
break;
case 1:
// GOOD, offset a little for x, y, and z
offset.x = RandomFloat(-3, 3) * flScale;
offset.y = RandomFloat(-3, 3) * flScale;
offset.z = RandomFloat(-6, 6) * flScale;
break;
case 2:
// FAIR, offset somewhat for x, y, and z
offset.x = RandomFloat(-8, 8) * flScale;
offset.y = RandomFloat(-8, 8) * flScale;
offset.z = RandomFloat(-12, 12) * flScale;
break;
case 3:
// POOR, offset for x, y, and z
offset.x = RandomFloat(-15, 15) * flScale;
offset.y = RandomFloat(-15, 15) * flScale;
offset.z = RandomFloat(-25, 25) * flScale;
break;
case 4:
// BAD, offset lots for x, y, and z
offset.x = RandomFloat(-20, 20) * flScale;
offset.y = RandomFloat(-20, 20) * flScale;
offset.z = RandomFloat(-35, 35) * flScale;
break;
}
o.add(offset);
return o;
}
// WIP
bool CBot::BotsAgainstHumans()
{
return false;
}
bool CBot::DetectEnemy(playerent *p)
{
return (IsInFOV(p) || (m_pBotSkill->flAlwaysDetectDistance > m_pMyEnt->o.dist(p->o)))
&& IsVisible(p);
}
bool CBot::FindEnemy(void)
{
// UNDONE: Enemies are now only scored on their distance
if(BotsAgainstHumans())
{
m_pMyEnt->enemy = NULL;
if(player1->state == CS_ALIVE)
{
m_pMyEnt->enemy = player1;
}
return m_pMyEnt->enemy != NULL;
}
if (m_pMyEnt->enemy) // Bot already has an enemy
{
// Check if the enemy is still in game
bool found = IsInGame(m_pMyEnt->enemy);
// Check if the enemy is still ingame, still alive, not joined my team and is visible
if (found && !isteam(m_pMyEnt, m_pMyEnt->enemy))
{
if ((m_pMyEnt->enemy->state == CS_ALIVE) && (IsVisible(m_pMyEnt->enemy)))
return true;
else
m_pPrevEnemy = m_pMyEnt->enemy;
}
else
m_pMyEnt->enemy = NULL;
}
if (m_iEnemySearchDelay > lastmillis) return (m_pMyEnt->enemy!=NULL);
m_pMyEnt->enemy = NULL;
// Add enemy searchy delay
float MinDelay = m_pBotSkill->flMinEnemySearchDelay;
float MaxDelay = m_pBotSkill->flMaxEnemySearchDelay;
m_iEnemySearchDelay = lastmillis + int(RandomFloat(MinDelay, MaxDelay) * 1000.0f);
playerent *pNewEnemy = NULL, *d = NULL;
float flDist, flNearestDist = 99999.9f;
short EnemyVal, BestEnemyVal = -100;
// First loop through all players
loopv(players)
{
d = players[i]; // Handy shortcut
if (d == m_pMyEnt || !d || isteam(d, m_pMyEnt) || (d->state != CS_ALIVE))
continue;
// Check if the enemy is visible
if(!DetectEnemy(d))
continue;
flDist = GetDistance(d->o);
EnemyVal = 1;
if (flDist < flNearestDist)
{
EnemyVal+=2;
flNearestDist = flDist;
}
if (EnemyVal > BestEnemyVal)
{
pNewEnemy = d;
BestEnemyVal = EnemyVal;
}
}
// Then examine the local player
if (player1 && !isteam(player1, m_pMyEnt) &&
(player1->state == CS_ALIVE))
{
// Check if the enemy is visible
if(DetectEnemy(player1))
{
flDist = GetDistance(player1->o);
EnemyVal = 2;
if (flDist < flNearestDist)
{
EnemyVal+=2;
flNearestDist = flDist;
}
if (EnemyVal > BestEnemyVal)
{
pNewEnemy = player1;
BestEnemyVal = EnemyVal;
}
}
}
//}
if (pNewEnemy)
{
if (!m_pMyEnt->enemy) // Add shoot delay if new enemy is found
{
float flMinShootDelay = m_pBotSkill->flMinAttackDelay;
float flMaxShootDelay = m_pBotSkill->flMaxAttackDelay;
m_iShootDelay = lastmillis + int(RandomFloat(flMinShootDelay,
flMaxShootDelay) * 1000.0f);
}
if ((m_pMyEnt->enemy != pNewEnemy) && m_pMyEnt->enemy)
m_pPrevEnemy = m_pMyEnt->enemy;
m_pMyEnt->enemy = pNewEnemy;
return true;
}
return false;
}
bool CBot::CheckHunt(void)
{
if (!BotManager.BotsShoot()) return false;
if (m_pHuntTarget) // Bot already has an enemy to hunt
{
// Check if the enemy is still in game
bool found = IsInGame(m_pHuntTarget);
// Check if the enemy is still ingame, still alive, not joined my team and is visible
if (found && !isteam(m_pMyEnt, m_pHuntTarget))
{
if ((m_pHuntTarget->state == CS_ALIVE) && IsReachable(m_vHuntLocation))
return true;
}
else
m_pHuntTarget = NULL;
}
if (m_iHuntDelay > lastmillis) return (m_pHuntTarget!=NULL);
if (m_vHuntLocation!=g_vecZero)
m_vPrevHuntLocation = m_vHuntLocation;
m_pHuntTarget = NULL;
m_vHuntLocation = g_vecZero;
// Add enemy hunt search delay
m_iHuntDelay = lastmillis + 1500;
playerent *pNewEnemy = NULL, *d = NULL;
float flDist, flNearestDist = 99999.9f, flNearestOldPosDistToEnemy = 99999.9f;
float flNearestOldPosDistToBot = 99999.9f;
short EnemyVal, BestEnemyVal = -100;
vec BestOldPos;
// First loop through all players
loopv(players)
{
d = players[i]; // Handy shortcut
if (d == m_pMyEnt || !d || isteam(d, m_pMyEnt) || (d->state != CS_ALIVE))
continue;
flDist = GetDistance(d->o);
if (flDist > 250.0f) continue;
EnemyVal = 1;
if (flDist < flNearestDist)
{
EnemyVal+=2;
flNearestDist = flDist;
}
if (d == m_pPrevEnemy)
EnemyVal+=2;
if (EnemyVal < BestEnemyVal) continue;
vec bestfromenemy = g_vecZero, bestfrombot = g_vecZero;
flNearestOldPosDistToEnemy = flNearestOldPosDistToBot = 9999.9f;
// Check previous locations of enemy
for (int j=0;j<d->history.size();j++)
{
const vec &v = d->history.getpos(j);
if (v==m_vPrevHuntLocation) continue;
flDist = GetDistance(d->o, v);
if ((flDist < flNearestOldPosDistToEnemy) && IsReachable(v))
{
flNearestOldPosDistToEnemy = flDist;
bestfromenemy = v;
}
}
// Check previous locations of bot hisself
for (int j=0;j<m_pMyEnt->history.size();j++)
{
const vec &v = m_pMyEnt->history.getpos(j);
if (v==m_vPrevHuntLocation) continue;
flDist = GetDistance(v);
if ((flDist < flNearestOldPosDistToBot) && ::IsVisible(d->o, v) &&
IsReachable(v))
{
flNearestOldPosDistToBot = flDist;
bestfrombot = v;
}
}
if (bestfromenemy!=g_vecZero)
{
pNewEnemy = d;
BestEnemyVal = EnemyVal;
BestOldPos = bestfromenemy;
}
else if (bestfrombot!=g_vecZero)
{
pNewEnemy = d;
BestEnemyVal = EnemyVal;
BestOldPos = bestfrombot;
}
// Then examine the local player
if (player1 && !isteam(player1, m_pMyEnt) &&
(player1->state == CS_ALIVE) && ((flDist = GetDistance(player1->o)) <= 250.0f))
{
d = player1;
EnemyVal = 1;
if (flDist < flNearestDist)
{
EnemyVal+=2;
flNearestDist = flDist;
}
if (d == m_pPrevEnemy)
EnemyVal+=2;
if (EnemyVal >= BestEnemyVal)
{
BestEnemyVal = EnemyVal;
vec bestfromenemy = g_vecZero, bestfrombot = g_vecZero;
flNearestOldPosDistToEnemy = flNearestOldPosDistToBot = 9999.9f;
// Check previous locations of enemy
for (int j=0;j<d->history.size();j++)
{
const vec &v = d->history.getpos(j);
if (v==m_vPrevHuntLocation) continue;
flDist = GetDistance(d->o, v);
if ((flDist < flNearestOldPosDistToEnemy) && IsReachable(v))
{
flNearestOldPosDistToEnemy = flDist;
bestfromenemy = v;
}
}
// Check previous locations of bot hisself
for (int j=0;j<m_pMyEnt->history.size();j++)
{
const vec &v = m_pMyEnt->history.getpos(j);
if (v==m_vPrevHuntLocation) continue;
flDist = GetDistance(v);
if ((flDist < flNearestOldPosDistToBot) && ::IsVisible(d->o, v) &&
IsReachable(v))
{
flNearestOldPosDistToBot = flDist;
bestfrombot = v;
}
}
if (bestfromenemy!=g_vecZero)
{
pNewEnemy = d;
BestEnemyVal = EnemyVal;
BestOldPos = bestfromenemy;
}
else if (bestfrombot!=g_vecZero)
{
pNewEnemy = d;
BestEnemyVal = EnemyVal;
BestOldPos = bestfrombot;
}
}
}
}
if (pNewEnemy)
{
if (!m_pHuntTarget) // Add shoot delay if new enemy is found
{
float flMinShootDelay = m_pBotSkill->flMinAttackDelay;
float flMaxShootDelay = m_pBotSkill->flMaxAttackDelay;
m_iShootDelay = lastmillis + int(RandomFloat(flMinShootDelay,
flMaxShootDelay) * 1000.0f);
}
if (m_vHuntLocation!=g_vecZero)
m_vPrevHuntLocation = m_vHuntLocation;
m_pHuntTarget = pNewEnemy;
m_vHuntLocation = BestOldPos;
m_fPrevHuntDist = 0.0f;
return true;
}
return false;
}
bool CBot::HuntEnemy(void)
{
if (m_iCombatNavTime > lastmillis)
{
SetMoveDir(m_iMoveDir, false);
return true;
}
m_iCombatNavTime = m_iMoveDir = 0;
bool bDone = false, bNew = false;
float flDist = GetDistance(m_vHuntLocation);
if (flDist <= 3.0f)
bDone = true;
if ((m_fPrevHuntDist > 0.0) && (flDist > m_fPrevHuntDist))
bDone = true;
m_fPrevHuntDist = flDist;
if ((m_iHuntPlayerUpdateTime < lastmillis) || bDone)
{
m_iHuntPlayerUpdateTime = lastmillis + 1250;
short BestPosIndexFromEnemy = -1, BestPosIndexFromBot = -1, j;
float NearestDistToEnemy = 9999.9f, NearestDistToBot = 9999.9f;
// Check previous locations of enemy
for (j=0;j<m_pHuntTarget->history.size();j++)
{
const vec &OldPos = m_pHuntTarget->history.getpos(j);
if (bDone && m_vHuntLocation==OldPos)
continue;
if (GetDistance(OldPos) > 250.0f)
continue;
flDist = GetDistance(m_pHuntTarget->o, OldPos);
if ((flDist < NearestDistToEnemy) && (IsReachable(OldPos)))
{
NearestDistToEnemy = flDist;
BestPosIndexFromEnemy = j;
break;
}
}
// Check previous locations of bot
for (j=0;j<m_pMyEnt->history.size();j++)
{
const vec &OldPos = m_pMyEnt->history.getpos(j);
if (bDone && m_vHuntLocation==OldPos)
continue;
if (GetDistance(OldPos) > 25.0f)
continue;
flDist = GetDistance(OldPos);
if ((flDist < NearestDistToBot) && ::IsVisible(m_pHuntTarget->o, OldPos) &&
(IsReachable(OldPos)))
{
NearestDistToBot = flDist;
BestPosIndexFromBot = j;
break;
}
}
if (BestPosIndexFromEnemy > -1)
{
m_vPrevHuntLocation = m_vHuntLocation;
m_vHuntLocation = m_pHuntTarget->history.getpos(BestPosIndexFromEnemy);
bNew = true;
m_fPrevHuntDist = 0.0f;
}
else if (BestPosIndexFromBot > -1)
{
m_vPrevHuntLocation = m_vHuntLocation;
m_vHuntLocation = m_pMyEnt->history.getpos(BestPosIndexFromEnemy);
bNew = true;
m_fPrevHuntDist = 0.0f;
}
}
if (!bNew) // Check if current location is still reachable
{
if (bDone || !IsReachable(m_vHuntLocation))
{
m_pHuntTarget = NULL;
m_vPrevHuntLocation = m_vHuntLocation;
m_vHuntLocation = g_vecZero;
m_fPrevHuntDist = 0.0f;
m_iHuntDelay = lastmillis + 3500;
return false;
}
}
else
condebug("New hunt pos");
// Aim to position
//AimToVec(m_vHuntLocation);
int iMoveDir = GetDirection(GetViewAngles(), m_pMyEnt->o, m_vHuntLocation);
if (iMoveDir != DIR_NONE)
{
m_iMoveDir = iMoveDir;
m_iCombatNavTime = lastmillis + 125;
}
bool aimtopos = true;
if ((lastmillis - m_iSawEnemyTime) > 1500)
{
if (m_iLookAroundDelay < lastmillis)
{
if (m_iLookAroundTime > lastmillis)
{
if (m_iLookAroundUpdateTime < lastmillis)
{
float flAddAngle;
if (m_bLookLeft) flAddAngle = RandomFloat(-110, -80);
else flAddAngle = RandomFloat(80, 110);
m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->targetyaw + flAddAngle);
m_iLookAroundUpdateTime = lastmillis + RandomLong(400, 800);
}
aimtopos = false;
}
else if (m_iLookAroundTime > 0)
{
m_iLookAroundTime = 0;
m_iLookAroundDelay = lastmillis + RandomLong(750, 1000);
}
else
m_iLookAroundTime = lastmillis + RandomLong(2200, 3200);
}
}
if (aimtopos)
AimToVec(m_vHuntLocation);
debugbeam(m_pMyEnt->o, m_vHuntLocation);
if (m_fYawToTurn <= 25.0f)
m_iHuntLastTurnLessTime = lastmillis;
// Bot had to turn much for a while?
if ((m_iHuntLastTurnLessTime > 0) && (m_iHuntLastTurnLessTime < (lastmillis - 1000)))
{
m_iHuntPauseTime = lastmillis + 200;
}
if (m_iHuntPauseTime >= lastmillis)
{
m_pMyEnt->move = 0;
m_fPrevHuntDist = 0.0f;
}
else
{
// Check if bot has to jump over a wall...
if (CheckJump())
m_pMyEnt->jumpnext = true;
else // Check if bot has to jump to reach this location
{
float flHeightDiff = m_vHuntLocation.z - m_pMyEnt->o.z;
bool bToHigh = false;
if (Get2DDistance(m_vHuntLocation) <= 2.0f)
{
if (flHeightDiff >= 1.5f)
{
if (flHeightDiff <= JUMP_HEIGHT)
{
#ifndef RELEASE_BUILD
char sz[64];
sprintf(sz, "OldPos z diff: %f", m_vHuntLocation.z-m_pMyEnt->o.z);
condebug(sz);
#endif
// Jump if close to pos and the pos is high
m_pMyEnt->jumpnext = true;
}
else
bToHigh = true;
}
}
if (bToHigh)
{
m_pHuntTarget = NULL;
m_vPrevHuntLocation = m_vHuntLocation;
m_vHuntLocation = g_vecZero;
m_fPrevHuntDist = 0.0f;
m_iHuntDelay = lastmillis + 3500;
return false;
}
}
}
return true;
}
void CBot::CheckWeaponSwitch()
{
if(m_pMyEnt->nextweaponsel == NULL) m_pMyEnt->weaponchanging = 0;
if(!m_pMyEnt->weaponchanging) return;
int timeprogress = lastmillis-m_pMyEnt->weaponchanging;
if (timeprogress>SWITCHTIME(m_pMyEnt->perk1 == PERK_TIME))
{
m_pMyEnt->prevweaponsel = m_pMyEnt->weaponsel;
m_pMyEnt->weaponsel = m_pMyEnt->nextweaponsel;
m_pMyEnt->weaponchanging = 0;
m_iChangeWeaponDelay = 0;
if(!m_pMyEnt->weaponsel->mag)
{
tryreload(m_pMyEnt);
}
}
}
bool CBot::CheckFire(const vec &o)
{
vec target, dir, forward, right, up;
float flDot;
AnglesToVectors(GetViewAngles(), forward, right, up);
// direction the bot is aiming at
dir = forward;
// ideal direction
target = o;
target.sub(m_pMyEnt->o);
// angle between these two directions
flDot = target.dot(dir);
if(flDot/float(target.magnitude() * dir.magnitude()) > .999f) return true;
return false;
}
void CBot::ShootEnemy()
{
if(!m_pMyEnt->enemy) return;
if(!IsVisible(m_pMyEnt->enemy)) return;
m_iSawEnemyTime = lastmillis;
// Aim to enemy
vec enemypos = GetEnemyPos(m_pMyEnt->enemy);
AimToVec(enemypos);
playerent *d = player1;
if (d && isteam(d, m_pMyEnt) && (d->state == CS_ALIVE)
&& IsVisible(d) && CheckFire(d->o)) return;
loopv(players)
{
d = players[i];
if(d && isteam(d, m_pMyEnt) && (d->state == CS_ALIVE)
&& IsVisible(d) && CheckFire(d->o)) return;
}
// Time to shoot?
if (m_iShootDelay < lastmillis)
//if ((lastmillis-m_pMyEnt->lastaction) >= m_pMyEnt->gunwait)
{
if (m_pMyEnt->mag[m_pMyEnt->gunselect])
{
// If the bot is using a sniper only shoot if crosshair is near the enemy
if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_SNIPER)
{
float yawtoturn = fabs(WrapYZAngle(m_pMyEnt->yaw - m_pMyEnt->targetyaw));
float pitchtoturn = fabs(WrapYZAngle(m_pMyEnt->pitch - m_pMyEnt->targetpitch));
if ((yawtoturn > 5) || (pitchtoturn > 15)) // UNDONE: Should be skill based
return;
}
float flDist = GetDistance(enemypos);
// Check if bot is in fire range
if ((flDist < WeaponInfoTable[m_pMyEnt->gunselect].flMinFireDistance) ||
(flDist > WeaponInfoTable[m_pMyEnt->gunselect].flMaxFireDistance))
return;
// Now shoot!
m_pMyEnt->attacking = true;
// Get the position the bot is aiming at
vec forward, right, up, dest;
traceresult_s tr;
AnglesToVectors(GetViewAngles(), forward, right, up);
dest = m_pMyEnt->o;
forward.mul(1000);
dest.add(forward);
TraceLine(m_pMyEnt->o, dest, m_pMyEnt, false, &tr);
debugbeam(m_pMyEnt->o, tr.end);
shoot(m_pMyEnt, tr.end);
// Add shoot delay
m_iShootDelay = lastmillis + GetShootDelay();
}
}
#ifndef RELEASE_BUILD
else
{
char sz[64];
sprintf(sz, "shootdelay: %d\n", (m_iShootDelay-lastmillis));
AddDebugText(sz);
}
#endif
}
bool CBot::ChoosePreferredWeapon()
{
return true;
}
int CBot::GetShootDelay()
{
// UNDONE
return m_pMyEnt->gunwait[m_pMyEnt->gunselect];
/*
if ((WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE) ||
(WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_AUTO))
return m_pMyEnt->gunwait[m_pMyEnt->gunselect];
float flMinShootDelay = m_pBotSkill->flMinAttackDelay;
float flMaxShootDelay = m_pBotSkill->flMaxAttackDelay;
return max(m_pMyEnt->gunwait[m_pMyEnt->gunselect], int(RandomFloat(flMinShootDelay, flMaxShootDelay) * 1000.0f));
*/
}
void CBot::CheckReload() // reload gun if no enemies are around
{
if(m_pMyEnt->mag[m_pMyEnt->weaponsel->type] >= WeaponInfoTable[m_pMyEnt->weaponsel->type].sMinDesiredAmmo) return; // do not reload if mindesiredammo is satisfied
if(m_pMyEnt->enemy && m_pMyEnt->mag[m_pMyEnt->weaponsel->type])
{
return; // ignore the enemy, if no ammo in mag.
}
tryreload(m_pMyEnt);
return;
}
void CBot::CheckScope()
{
#define MINSCOPEDIST 10
#define MINSCOPETIME 1000
// no throwing knife
if (!ads_gun(m_pMyEnt->weaponsel->type) || !ads_classic_allowed(m_pMyEnt->weaponsel->type)) return;
if(m_pMyEnt->enemy && m_pMyEnt->o.dist(m_pMyEnt->enemy->o) > MINSCOPEDIST)
{
m_pMyEnt->scoping = true;
}
else if (m_pMyEnt->scoping && m_pMyEnt->zoomed == 1) // lastmillis - sniper->scoped_since < MINSCOPETIME)
{
m_pMyEnt->scoping = false;
}
}
void CBot::MainAI()
{
// Default bots will run forward
m_pMyEnt->move = 1;
// Default bots won't strafe
m_pMyEnt->strafe = 0;
// Whatever the bot is doing, check for needed crouch
if(CheckCrouch()) m_pMyEnt->trycrouch = true;
else m_pMyEnt->trycrouch = false;
if (!BotManager.BotsShoot() && m_pMyEnt->enemy)
m_pMyEnt->enemy = NULL; // Clear enemy when bots may not shoot
if (m_bGoToDebugGoal) // For debugging the waypoint navigation
{
if (!HeadToGoal())
{
ResetWaypointVars();
m_vGoal = g_vecZero;
}
else
AddDebugText("Heading to debug goal...");
}
if (BotManager.BotsShoot() && FindEnemy()) // Combat
{
CheckReload();
CheckScope();
AddDebugText("has enemy");
// Use best weapon
ChoosePreferredWeapon();
// Shoot at enemy
ShootEnemy();
if (m_eCurrentBotState != STATE_ENEMY)
{
m_vGoal = g_vecZero;
ResetWaypointVars();
}
m_eCurrentBotState = STATE_ENEMY;
if (!CheckJump())
DoCombatNav();
}
// Taking care of the flags
else if (CheckFlags())
{
CheckReload();
AddDebugText("has flag");
m_eCurrentBotState = STATE_FLAG;
}
else if (CheckHunt() && HuntEnemy())
{
CheckReload();
CheckScope();
AddDebugText("Hunting to %s", m_pHuntTarget->name);
m_eCurrentBotState = STATE_HUNT;
}
// Heading to an interesting entity(ammo, armour etc)
else if (CheckItems())
{
CheckReload();
AddDebugText("has ent");
m_eCurrentBotState = STATE_ENT;
}
#if m_classicsp
else if (DoSPStuff()) // Home to goal, find/follow friends etc.
{
AddDebugText("SP stuff");
m_eCurrentBotState = STATE_SP;
}
#endif
else // Normal navigation
{
CheckReload();
if (m_eCurrentBotState != STATE_NORMAL)
{
m_vGoal = g_vecZero;
ResetWaypointVars();
}
m_eCurrentBotState = STATE_NORMAL;
bool bDoNormalNav = true;
AddDebugText("normal nav");
// Make sure the bot looks straight forward and not up or down
m_pMyEnt->pitch = 0;
// if it is time to look for a waypoint AND if there are waypoints in this
// level...
if (WaypointClass.m_iWaypointCount >= 1)
{
// check if we need to find a waypoint...
if (CurrentWPIsValid() == false)
{
if (m_iLookForWaypointTime <= lastmillis)
{
// find the nearest reachable waypoint
waypoint_s *pWP = GetNearestWaypoint(10.0f);
if (pWP && (pWP != m_pCurrentWaypoint))
{
SetCurrentWaypoint(pWP);
condebug("New nav wp");
bDoNormalNav = !HeadToWaypoint();
if (bDoNormalNav)
ResetWaypointVars();
}
else
ResetWaypointVars();
m_iLookForWaypointTime = lastmillis + 250;
}
}
else
{
bDoNormalNav = !HeadToWaypoint();
if (bDoNormalNav)
ResetWaypointVars();
AddDebugText("Using wps for nav");
}
}
// If nothing special, do regular (waypointless) navigation
if(bDoNormalNav)
{
// Is the bot underwater?
if (UnderWater(m_pMyEnt->o) && WaterNav())
{
// Bot is under water, navigation happens in WaterNav
}
// Time to check the environment?
else if (m_iCheckEnvDelay < lastmillis)
{
if (m_vWaterGoal!=g_vecZero) m_vWaterGoal = g_vecZero;
// Check for stuck and strafe
if (UnderWater(m_pMyEnt->o) || !CheckStuck())
{
// Only do this when the bot is underwater or when the bot isn't stuck
// Check field of view (FOV)
CheckFOV();
}
}
// Check if the bot has to strafe
CheckStrafe();
m_pMyEnt->move = 1;
}
}
}
void CBot::DoCombatNav()
{
if (m_iCombatNavTime > lastmillis)
{
// If bot has a lower skill and has to turn much, wait
if ((m_sSkillNr > 2) && (m_fYawToTurn > 90.0f))
{
ResetMoveSpeed();
}
else
{
SetMoveDir(m_iMoveDir, false);
}
return;
}
if (m_bCombatJump)
{
m_pMyEnt->jumpnext = true;
m_bCombatJump = false;
m_iCombatJumpDelay = lastmillis + RandomLong(1500, 2800);
return;
}
m_iMoveDir = DIR_NONE;
// Check if bot is on top of his enemy
float r = m_pMyEnt->radius+m_pMyEnt->enemy->radius;
if ((fabs(m_pMyEnt->enemy->o.x-m_pMyEnt->o.x)<r &&
fabs(m_pMyEnt->enemy->o.y-m_pMyEnt->o.y)<r) &&
((m_pMyEnt->enemy->o.z+m_pMyEnt->enemy->aboveeye) < (m_pMyEnt->o.z + m_pMyEnt->aboveeye)))
{
// Try to get off him!
condebug("On enemy!");
TMultiChoice<int> AwayDirChoices;
if (IsVisible(LEFT, 4.0f, false))
AwayDirChoices.Insert(LEFT);
if (IsVisible(RIGHT, 4.0f, false))
AwayDirChoices.Insert(RIGHT);
if (IsVisible(FORWARD, 4.0f, false))
AwayDirChoices.Insert(FORWARD);
if (IsVisible(BACKWARD, 4.0f, false))
AwayDirChoices.Insert(BACKWARD);
int iDir;
if (AwayDirChoices.GetSelection(iDir))
{
m_iMoveDir = iDir;
m_iCombatNavTime = lastmillis + 500;
}
}
float flDist = GetDistance(m_pMyEnt->enemy->o);
// Check for nearby items?
if (((m_iCheckEntsDelay < lastmillis) || m_pTargetEnt) &&
m_pBotSkill->bCanSearchItemsInCombat && !(m_zombie(gamemode) && m_pMyEnt->team == TEAM_CLA))
{
m_iCheckEntsDelay = lastmillis + 125;
bool bSearchItems = false;
if (m_pTargetEnt)
{
// Bot has already found an entity, still valid?
vec v(m_pTargetEnt->x, m_pTargetEnt->y,
S(m_pTargetEnt->x, m_pTargetEnt->y)->floor+m_pMyEnt->eyeheight);
if ((GetDistance(v) > 25.0f) || !IsVisible(m_pTargetEnt))
m_pTargetEnt = NULL;
}
if (!m_pTargetEnt && (m_iCheckEntsDelay <= lastmillis))
{
if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE)
bSearchItems = (flDist >= 8.0f);
else
bSearchItems = (m_pMyEnt->ammo[m_pMyEnt->gunselect] <=
WeaponInfoTable[m_pMyEnt->gunselect].sMinDesiredAmmo);
if (bSearchItems)
m_pTargetEnt = SearchForEnts(false, 25.0f, 1.0f);
}
if (m_pTargetEnt)
{
condebug("Combat ent");
vec v(m_pTargetEnt->x, m_pTargetEnt->y,
S(m_pTargetEnt->x, m_pTargetEnt->y)->floor+m_pMyEnt->eyeheight);
debugbeam(m_pMyEnt->o, v);
float flHeightDiff = v.z - m_pMyEnt->o.z;
bool bToHigh = false;
// Check he height for this ent
if (Get2DDistance(v) <= 2.0f)
{
if (flHeightDiff >= 1.5f)
{
if (flHeightDiff <= JUMP_HEIGHT)
{
#ifndef RELEASE_BUILD
char sz[64];
sprintf(sz, "Ent z diff: %f", v.z-m_pMyEnt->o.z);
condebug(sz);
#endif
m_pMyEnt->jumpnext = true; // Jump if close to ent and the ent is high
}
else
bToHigh = true;
}
}
if (!bToHigh)
{
int iMoveDir = GetDirection(GetViewAngles(), m_pMyEnt->o, v);
if (iMoveDir != DIR_NONE)
{
m_iMoveDir = iMoveDir;
m_iCombatNavTime = lastmillis + RandomLong(125, 250);
}
// Check if bot needs to jump over something
vec from = m_pMyEnt->o;
from.z -= 1.0f;
if (!IsVisible(from, iMoveDir, 3.0f, false))
m_pMyEnt->jumpnext = true;
return;
}
}
}
// High skill and enemy is close?
if ((m_sSkillNr <= 1) && (m_fYawToTurn < 80.0f) && (flDist <= 20.0f) &&
(m_iCombatJumpDelay < lastmillis))
{
// Randomly jump a bit, to avoid some basic firepower ;)
// How lower the distance to the enemy, how higher the chance for a jump
short sJumpPercent = (100 - ((short)flDist * 8));
if (RandomLong(1, 100) <= sJumpPercent)
{
// Choose a nice direction to jump to
// Is the enemy close?
if ((GetDistance(m_pMyEnt->enemy->o) <= 4.0f) ||
(WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE))
{
m_iMoveDir = FORWARD; // Jump forward
SetMoveDir(FORWARD, false);
m_bCombatJump = true;
}
else if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType != TYPE_MELEE)// else jump to a random direction
{
/*
Directions to choose:
- Forward-right
- Right
- Backward-right
- Backward
- Backward-left
- Left
- Forward-left
*/
TMultiChoice<int> JumpDirChoices;
short sForwardScore = ((flDist > 8.0f) || (flDist < 4.0f)) ? 20 : 10;
short sBackwardScore = (flDist <= 6.0f) ? 20 : 10;
short sStrafeScore = (flDist < 6.0f) ? 20 : 10;
if (IsVisible((FORWARD | LEFT), 4.0f, false))
JumpDirChoices.Insert((FORWARD | LEFT), sForwardScore);
if (IsVisible((FORWARD | RIGHT), 4.0f, false))
JumpDirChoices.Insert((FORWARD | RIGHT), sForwardScore);
if (IsVisible(BACKWARD, 4.0f, false))
JumpDirChoices.Insert(BACKWARD, sBackwardScore);
if (IsVisible((BACKWARD | LEFT), 4.0f, false))
JumpDirChoices.Insert((BACKWARD | LEFT), sBackwardScore);
if (IsVisible((BACKWARD | RIGHT), 4.0f, false))
JumpDirChoices.Insert((BACKWARD | RIGHT), sBackwardScore);
if (IsVisible(LEFT, 4.0f, false))
JumpDirChoices.Insert(LEFT, sStrafeScore);
if (IsVisible(RIGHT, 4.0f, false))
JumpDirChoices.Insert(RIGHT, sStrafeScore);
int JumpDir;
if (JumpDirChoices.GetSelection(JumpDir))
{
m_iMoveDir = JumpDir;
SetMoveDir(JumpDir, false);
m_bCombatJump = true;
}
}
if (m_bCombatJump)
{
m_iCombatNavTime = lastmillis + RandomLong(125, 250);
return;
}
}
}
if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE)
return; // Simply walk towards enemy if using a melee type
flDist = Get2DDistance(m_pMyEnt->enemy->o);
// Out of desired range for current weapon?
if ((flDist <= WeaponInfoTable[m_pMyEnt->gunselect].flMinDesiredDistance) ||
(flDist >= WeaponInfoTable[m_pMyEnt->gunselect].flMaxDesiredDistance))
{
if (flDist >= WeaponInfoTable[m_pMyEnt->gunselect].flMaxDesiredDistance)
{
m_iMoveDir = FORWARD;
}
else
{
m_iMoveDir = BACKWARD;
}
vec src, forward, right, up, dest, MyAngles = GetViewAngles(), o = m_pMyEnt->o;
traceresult_s tr;
// Is it furthest or farthest? bleh
float flFurthestDist = 0;
int bestdir = -1, dir = 0;
bool moveback = (m_pMyEnt->move == -1);
for(int j=-45;j<=45;j+=45)
{
src = MyAngles;
src.y = WrapYZAngle(src.y + j);
src.x = 0.0f;
// If we're moving backwards, trace backwards
if (moveback)
src.y = WrapYZAngle(src.y + 180);
AnglesToVectors(src, forward, right, up);
dest = o;
forward.mul(40);
dest.add(forward);
TraceLine(o, dest, m_pMyEnt, false, &tr);
//debugbeam(origin, end);
flDist = GetDistance(tr.end);
if (flFurthestDist < flDist)
{
flFurthestDist = flDist;
bestdir = dir;
}
dir++;
}
switch(bestdir)
{
case 0:
if (moveback)
m_iMoveDir |= RIGHT; // Strafe right
else
m_iMoveDir |= LEFT; // Strafe left
break;
case 2:
if (moveback)
m_iMoveDir |= LEFT; // Strafe left
else
m_iMoveDir |= RIGHT; // Strafe right
break;
}
if (m_iMoveDir != DIR_NONE)
{
SetMoveDir(m_iMoveDir, false);
m_iCombatNavTime = lastmillis + 500;
}
}
else if (m_pBotSkill->bCircleStrafe) // Circle strafe when in desired range...
{
traceresult_s tr;
vec angles, end, forward, right, up;
TMultiChoice<int> StrafeDirChoices;
// Check the left side...
angles = GetViewAngles();
angles.y = WrapYZAngle(angles.y - 75.0f); // Not 90 degrees because the bot
// doesn't strafe in a straight line
// (aims still to enemy).
AnglesToVectors(angles, forward, right, up);
end = m_pMyEnt->o;
forward.mul(15.0f);
end.add(forward);
TraceLine(m_pMyEnt->o, end, m_pMyEnt, true, &tr);
StrafeDirChoices.Insert(LEFT, (int)GetDistance(m_pMyEnt->o, tr.end));
// Check the right side...
angles = GetViewAngles();
angles.y = WrapYZAngle(angles.y + 75.0f); // Not 90 degrees because the bot
// doesn't strafe in a straight line
// (aims still to enemy).
AnglesToVectors(angles, forward, right, up);
end = m_pMyEnt->o;
forward.mul(15.0f);
end.add(forward);
TraceLine(m_pMyEnt->o, end, m_pMyEnt, true, &tr);
StrafeDirChoices.Insert(RIGHT, (int)GetDistance(m_pMyEnt->o, tr.end));
int StrafeDir;
if (StrafeDirChoices.GetSelection(StrafeDir))
{
m_iMoveDir = StrafeDir;
SetMoveDir(StrafeDir, false);
m_iCombatNavTime = lastmillis + RandomLong(1500, 3000);
}
}
else // Bot can't circle strafe(low skill), just stand still
ResetMoveSpeed();
}
bool CBot::CheckStuck()
{
if (m_iStuckCheckDelay + (CheckCrouch() ? 2000 : 0) >= lastmillis)
return false;
if ((m_vGoal!=g_vecZero) && (GetDistance(m_vGoal) < 2.0f))
return false;
if(OUTBORD(m_pMyEnt->o.x, m_pMyEnt->o.y))
{
StuckLastResort();
return true;
}
bool IsStuck = false;
vec CurPos = m_pMyEnt->o, PrevPos = m_vPrevOrigin;
CurPos.z = PrevPos.z = 0;
// Did the bot hardly move the last frame?
if (GetDistance(CurPos, PrevPos) <= 0.1f)
{
if (m_bStuck)
{
if (m_iStuckTime < lastmillis)
IsStuck = true;
}
else
{
m_bStuck = true;
m_iStuckTime = lastmillis + 1000;
}
}
else
{
m_bStuck = false;
m_iStuckTime = 0;
}
if (IsStuck)
{
#ifndef RELEASE_BUILD
char msg[64];
sprintf(msg, "stuck (%f)", GetDistance(m_vPrevOrigin));
condebug(msg);
#endif
m_bStuck = false;
m_iStuckTime = 0;
// stuck in geometry?
if(!collide(m_pMyEnt))
{
StuckLastResort();
return true;
}
// Crap bot is stuck, lets just try some random things
// Check if the bot can turn around
vec src = GetViewAngles();
src.x = 0;
vec forward, right, up, dir, dest;
traceresult_s tr;
AnglesToVectors(src, forward, right, up);
// Check the left side...
dir = right;
dest = m_pMyEnt->o;
dir.mul(3);
dest.sub(dir);
TraceLine(m_pMyEnt->o, dest, m_pMyEnt, false, &tr);
//debugbeam(m_pMyEnt->o, end);
if (!tr.collided)
{
// Bot can turn left, do so
m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw - 90);
m_iStuckCheckDelay = m_iCheckEnvDelay = lastmillis + 500;
return true;
}
// Check the right side...
dir = right;
dest = m_pMyEnt->o;
dir.mul(3);
dest.add(dir);
TraceLine(m_pMyEnt->o, dest, m_pMyEnt, true, &tr);
//debugbeam(m_pMyEnt->o, end);
if (!tr.collided)
{
// Bot can turn right, do so
m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + 90);
m_iStuckCheckDelay = m_iCheckEnvDelay = lastmillis + 500;
return true;
}
// Check if bot can turn 180 degrees
dir = forward;
dest = m_pMyEnt->o;
dir.mul(3);
dest.add(dir);
TraceLine(m_pMyEnt->o, dest, m_pMyEnt, true, &tr);
//debugbeam(m_pMyEnt->o, end);
if (!tr.collided)
{
// Bot can turn around, do so
m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + 180);
m_iStuckCheckDelay = m_iCheckEnvDelay = lastmillis + 500;
return true;
}
// Bleh bot couldn't turn, lets just randomly jump :|
condebug("Randomly avoiding stuck...");
if (RandomLong(0, 2) == 0)
m_pMyEnt->jumpnext = true;
else
m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + RandomLong(60, 160));
StuckLastResort();
return true;
}
return false;
}
void CBot::StuckLastResort()
{
// respawn the bot to fix its being stuck
addmsg(SV_SUICIDE, "ri", m_pMyEnt->clientnum);
}
// Check if a near wall is blocking and we can jump over it
bool CBot::CheckJump()
{
bool bHasGoal = m_vGoal!=g_vecZero;
float flGoalDist = (bHasGoal) ? GetDistance(m_vGoal) : 0.0f;
// if ((bHasGoal) && (flGoalDist < 2.0f))
// return false; UNDONE?
vec start = m_pMyEnt->o;
float flTraceDist = 3.0f;
if (bHasGoal && (flGoalDist < flTraceDist))
flTraceDist = flGoalDist;
// Something blocks at eye hight?
if (!IsVisible(start, FORWARD, flTraceDist, false))
{
// Check if the bot can jump over it
start.z += (JUMP_HEIGHT - 1.0f);
if (IsVisible(start) && !IsVisible(start, FORWARD, flTraceDist, false))
{
// Jump
debugnav("High wall");
m_pMyEnt->jumpnext = true;
return true;
}
}
else
{
// Check if something is blocking at feet height, so the bot can jump over it
start.z -= 1.7f;
// Trace was blocked?
if (!IsVisible(start, FORWARD, flTraceDist, false))
{
//debugbeam(start, end);
// Jump
debugnav("Low wall");
m_pMyEnt->jumpnext = true;
return true;
}
}
return false; // Bot didn't had to jump(or couldn't)
}
bool CBot::CheckCrouch()
{
bool bHasGoal = m_vGoal!=g_vecZero;
float flGoalDist = (bHasGoal) ? GetDistance(m_vGoal) : 0.0f;
vec start = m_pMyEnt->o;
vec crouch = vec(0, 0, 2.0f);
float flTraceDist = 3.0f;
if (bHasGoal && (flGoalDist < flTraceDist))
flTraceDist = flGoalDist;
if (!IsVisible(vec(start).add(crouch), FORWARD, flTraceDist, false) && IsVisible(vec(start).sub(crouch), FORWARD, flTraceDist, false)) return true;
return false;
}
bool CBot::CheckStrafe()
{
if (m_iStrafeTime >= lastmillis)
{
SetMoveDir(m_iMoveDir, true);
return true;
}
if (m_iStrafeCheckDelay >= lastmillis)
return false;
// Check for near walls
traceresult_s tr;
vec from = m_pMyEnt->o, to, forward, right, up, dir;
float flLeftDist = -1.0f, flRightDist = -1.0f;
bool bStrafe = false;
int iStrafeDir = DIR_NONE;
AnglesToVectors(GetViewAngles(), forward, right, up);
// Check for a near left wall
to = from;
dir = right;
dir.mul(3.0f);
to.sub(dir);
TraceLine(from, to, m_pMyEnt, false, &tr);
if (tr.collided)
flLeftDist = GetDistance(from, tr.end);
//debugbeam(m_pMyEnt->o, to);
// Check for a near right wall
to = from;
dir = right;
dir.mul(3.0f);
to.add(dir);
TraceLine(from, to, m_pMyEnt, false, &tr);
if (tr.collided)
flRightDist = GetDistance(from, tr.end);
//debugbeam(m_pMyEnt->o, to);
if ((flLeftDist == -1.0f) && (flRightDist == -1.0f))
{
dir = right;
dir.mul(m_pMyEnt->radius);
// Check left
from = m_pMyEnt->o;
from.sub(dir);
if (IsVisible(from, FORWARD, 3.0f, false, &flLeftDist))
flLeftDist = -1.0f;
// Check right
from = m_pMyEnt->o;
from.add(dir);
if (IsVisible(from, FORWARD, 3.0f, false, &flRightDist))
flRightDist = -1.0f;
}
if ((flLeftDist != -1.0f) && (flRightDist != -1.0f))
{
if (flLeftDist < flRightDist)
{
// Strafe right
bStrafe = true;
iStrafeDir = RIGHT;
}
else if (flRightDist < flLeftDist)
{
// Strafe left
bStrafe = true;
iStrafeDir = LEFT;
}
else
{
// Randomly choose a strafe direction
bStrafe = true;
if (RandomLong(0, 1))
iStrafeDir = LEFT;
else
iStrafeDir = RIGHT;
}
}
else if (flLeftDist != -1.0f)
{
// Strafe right
bStrafe = true;
iStrafeDir = RIGHT;
}
else if (flRightDist != -1.0f)
{
// Strafe left
bStrafe = true;
iStrafeDir = LEFT;
}
if (bStrafe)
{
SetMoveDir(iStrafeDir, true);
m_iMoveDir = iStrafeDir;
m_iStrafeTime = lastmillis + RandomLong(75, 150);
}
return bStrafe;
}
void CBot::CheckFOV()
{
m_iCheckEnvDelay = lastmillis + RandomLong(125, 250);
vec MyAngles = GetViewAngles();
vec src, forward, right, up, dest, best(0, 0, 0);
vec origin = m_pMyEnt->o;
float flDist, flFurthestDist = 0;
bool WallLeft = false;
traceresult_s tr;
//origin.z -= 1.5; // Slightly under eye level
// Scan 90 degrees FOV
for(int angle=-45;angle<=45;angle+=5)
{
src = MyAngles;
src.y = WrapYZAngle(src.y + angle);
AnglesToVectors(src, forward, right, up);
dest = origin;
forward.mul(40);
dest.add(forward);
TraceLine(origin, dest, m_pMyEnt, false, &tr);
//debugbeam(origin, end);
flDist = GetDistance(tr.end);
if (flFurthestDist < flDist)
{
flFurthestDist = flDist;
best = tr.end;
}
}
if (best.x && best.y && best.z)
{
AimToVec(best);
// Update MyAngles, since their (going to be) change(d)
MyAngles.x = m_pMyEnt->targetpitch;
MyAngles.y = m_pMyEnt->targetyaw;
}
float flNearestHitDist = GetDistance(best);
if (!UnderWater(m_pMyEnt->o) && m_pMyEnt->onfloor)
{
// Check if a near wall is blocking and we can jump over it
if (flNearestHitDist < 4)
{
// Check if the bot can jump over it
src = MyAngles;
src.x = 0;
AnglesToVectors(src, forward, right, up);
vec start = origin;
start.z += 2.0f;
dest = start;
forward.mul(6);
dest.add(forward);
TraceLine(start, dest, m_pMyEnt, false, &tr);
//debugbeam(start, end);
if (!tr.collided)
{
// Jump
debugnav("High wall");
m_pMyEnt->jumpnext = true;
m_iStrafeCheckDelay = lastmillis + RandomLong(250, 500);
return;
}
}
else
{
// Check if something is blocking below us, so the bot can jump over it
src = MyAngles;
src.x = 0;
AnglesToVectors(src, forward, right, up);
vec start = origin;
start.z -= 1.7f;
dest = start;
forward.mul(4);
dest.add(forward);
TraceLine(start, dest, m_pMyEnt, false, &tr);
// Trace was blocked?
if (tr.collided)
{
//debugbeam(start, end);
// Jump
debugnav("Low wall");
m_pMyEnt->jumpnext = true;
m_iStrafeCheckDelay = lastmillis + RandomLong(250, 500);
return;
}
}
// Check if the bot is going to fall...
src = MyAngles;
src.x = 0.0f;
AnglesToVectors(src, forward, right, up);
dest = origin;
forward.mul(3.0f);
dest.add(forward);
TraceLine(origin, dest, m_pMyEnt, false, &tr);
int cx = int(tr.end.x), cy = int(tr.end.y);
short CubesInWater=0;
for(int x=cx-1;x<=(cx+1);x++)
{
for(int y=cy-1;y<=(cy+1);y++)
{
if (OUTBORD(x, y)) continue;
//sqr *s = S(fast_f2nat(x), fast_f2nat(y));
//if (!SOLID(s))
{
vec from(x, y, m_pMyEnt->o.z);
dest = from;
dest.z -= 6.0f;
TraceLine(from, dest, m_pMyEnt, false, &tr);
bool turn = false;
if (UnderWater(tr.end)) CubesInWater++;
if (CubesInWater > 2) turn = true; // Always avoid water
if (!tr.collided && RandomLong(0, 1))
turn = true; // Randomly avoid a fall
if (turn)
{
m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + 180);
m_iCheckEnvDelay = m_iStrafeCheckDelay = lastmillis + RandomLong(750, 1500);
debugnav("Water or a fall in front");
return;
}
}
}
}
}
// Is the bot about to head a corner?
if (flNearestHitDist <= 4.0f)
{
src = MyAngles;
src.y = WrapYZAngle(src.y - 45.0f);
AnglesToVectors(src, forward, right, up);
dest = origin;
forward.mul(4.0f);
dest.add(forward);
TraceLine(origin, dest, m_pMyEnt, false, &tr);
WallLeft = (tr.collided);
src = MyAngles;
src.y += WrapYZAngle(src.y + 45.0f);
AnglesToVectors(src, forward, right, up);
dest = origin;
forward.mul(4.0f);
dest.add(forward);
TraceLine(origin, dest, m_pMyEnt, false, &tr);
if (WallLeft && tr.collided)
{
// We're about to hit a corner, turn away
debugnav("Corner");
m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + RandomFloat(160.0f, 200.0f));
m_iCheckEnvDelay = m_iStrafeCheckDelay = lastmillis + RandomLong(750, 1500);
return;
}
}
}
// Called when bot is underwater
bool CBot::WaterNav()
{
const int iSearchRange = 4;
if (m_vWaterGoal==g_vecZero)
{
AddDebugText("WaterNav");
// Find the nearest and reachable cube which isn't underwater
int cx = int(m_pMyEnt->o.x);
int cy = int(m_pMyEnt->o.y);
float flNearestDist = 9999.0f, flDist;
if (OUTBORD(cx, cy)) return false;
// Check all cubes in range...
for (int x=(cx-iSearchRange);x<=(cx+iSearchRange);x++)
{
for (int y=(cy-iSearchRange);y<=(cy+iSearchRange);y++)
{
sqr *s = S(x, y);
if (SOLID(s)) continue;
if ((x==cx) && (y==cy)) continue;
vec v(x, y, GetCubeFloor(x, y));
if (UnderWater(v)) continue; // Skip, cube is underwater
if (hdr.waterlevel < (v.z - 2.0f)) continue; // Cube is too high
// Check if the bot 'can fit' on the cube(no near obstacles)
bool small_ = false;
for (int a=(x-2);a<=(x+2);a++)
{
for (int b=(y-2);b<=(y+2);b++)
{
if ((x==a) && (y==b)) continue;
vec v2(a, b, GetCubeFloor(a, b));
if (v.z < (v2.z-1-JUMP_HEIGHT))
{
small_=true;
break;
}
if ((a >= (x-1)) && (a <= (x+1)) && (b >= (y-1)) && (b <= (y+1)))
{
if ((v2.z) < (v.z-2.0f))
{
small_ = true;
break;
}
}
traceresult_s tr;
TraceLine(v, v2, NULL, false, &tr);
if (tr.collided)
{
small_=true;
break;
}
}
}
if (small_)
{
debugbeam(m_pMyEnt->o, v);
continue;
}
// Okay, cube is valid.
flDist = GetDistance(v);
if (flDist < flNearestDist)
{
flNearestDist = flDist;
m_vWaterGoal = v;
}
}
}
}
if (m_vWaterGoal!=g_vecZero)
{
AddDebugText("WaterNav");
//debugbeam(m_pMyEnt->o, m_vWaterGoal);
vec aim = m_vWaterGoal;
aim.z += 1.5f; // Aim a bit further up
AimToVec(aim);
if ((RandomLong(1, 100) <= 15) && (Get2DDistance(m_vWaterGoal) <= 7.0f))
m_pMyEnt->jumpnext = true;
return true;
}
return false;
}
bool CBot::CheckItems()
{
// zombies + pickups = no
if (m_zombie(gamemode) && m_pMyEnt->team == TEAM_CLA)
return false;
if (!m_pCurrentGoalWaypoint && !CheckJump() && CheckStuck())
{
// Don't check for ents a while when stuck
m_iCheckEntsDelay = lastmillis + RandomLong(1000, 2000);
return false;
}
if (m_vGoal==g_vecZero)
m_pTargetEnt = NULL;
if (!m_pTargetEnt)
{
if (m_iCheckEntsDelay > lastmillis)
return false;
else
{
m_pTargetEnt = SearchForEnts(!m_classicsp);
m_iCheckEntsDelay = lastmillis + RandomLong(2500, 5000);
}
}
if (m_pTargetEnt)
{
if (HeadToTargetEnt())
return true;
}
if (m_eCurrentBotState == STATE_ENT)
{
ResetWaypointVars();
m_vGoal = g_vecZero;
m_pTargetEnt = NULL;
}
return false;
}
bool CBot::CheckFlags()
{
if (!m_flags(gamemode)) return false;
if (!m_pCurrentGoalWaypoint && !CheckJump() && CheckStuck())
{
// Don't check for flags for a while when stuck
m_iCheckFlagsDelay = lastmillis + RandomLong(500, 1500);
return false;
}
if (m_vGoal==g_vecZero)
m_pTargetFlag = NULL;
if (!m_pTargetFlag || m_iCheckFlagsDelay <= lastmillis)
{
if (m_iCheckFlagsDelay > lastmillis)
return false;
else
{
m_pTargetFlag = SearchForFlags(true);
m_iCheckFlagsDelay = lastmillis + RandomLong(3000, 5500);
}
}
if (m_pTargetFlag)
{
if (HeadToTargetFlag())
return true;
}
if (m_eCurrentBotState == STATE_FLAG)
{
ResetWaypointVars();
m_vGoal = g_vecZero;
m_pTargetFlag = NULL;
}
return false;
}
bool CBot::InUnreachableList(entity *e)
{
TLinkedList<unreachable_ent_s *>::node_s *p = m_UnreachableEnts.GetFirst();
while(p)
{
if (p->Entry->ent == e) return true;
p = p->next;
}
return false;
}
bool CBot::IsReachable(vec to, float flMaxHeight)
{
vec from = m_pMyEnt->o;
traceresult_s tr;
float curr_height, last_height;
float distance = GetDistance(from, to);
// is the destination close enough?
//if (distance < REACHABLE_RANGE)
{
if (IsVisible(to))
{
// Look if bot can 'fit trough'
vec src = from, forward, right, up;
AnglesToVectors(GetViewAngles(), forward, right, up);
// Trace from 1 cube to the left
vec temp = right;
temp.mul(1.0f);
src.sub(temp);
if (!::IsVisible(src, to)) return false;
// Trace from 1 cube to the right
src.add(temp);
if (!::IsVisible(src, to)) return false;
if (UnderWater(from) && UnderWater(to))
{
// No need to worry about heights in water
return true;
}
/*
if (to.z > (from.z + JUMP_HEIGHT))
{
vec v_new_src = to;
vec v_new_dest = to;
v_new_dest.z = v_new_dest.z - (JUMP_HEIGHT + 1.0f);
// check if we didn't hit anything, if so then it's in mid-air
if (::IsVisible(v_new_src, v_new_dest, NULL))
{
condebug("to is in midair");
debugbeam(from, to);
return false; // can't reach this one
}
}
*/
// check if distance to ground increases more than jump height
// at points between from and to...
vec v_temp = to;
v_temp.sub(from);
vec v_direction = Normalize(v_temp); // 1 unit long
vec v_check = from;
vec v_down = from;
v_down.z = v_down.z - 100.0f; // straight down
TraceLine(v_check, v_down, NULL, false, &tr);
// height from ground
last_height = GetDistance(v_check, tr.end);
distance = GetDistance(to, v_check); // distance from goal
while (distance > 2.0f)
{
// move 2 units closer to the goal
v_temp = v_direction;
v_temp.mul(2.0f);
v_check.add(v_temp);
v_down = v_check;
v_down.z = v_down.z - 100.0f;
TraceLine(v_check, v_down, NULL, false, &tr);
curr_height = GetDistance(v_check, tr.end);
// is the difference in the last height and the current height
// higher that the jump height?
if ((last_height - curr_height) >= flMaxHeight)
{
// can't get there from here...
//condebug("traces failed to to");
debugbeam(from, to);
return false;
}
last_height = curr_height;
distance = GetDistance(to, v_check); // distance from goal
}
return true;
}
}
return false;
}
void CBot::HearSound(int n, const vec *o)
{
// Has the bot already an enemy?
if (m_pMyEnt->enemy) return;
//fixmebot
// Is the sound not interesting?
if(n == S_DIE1 || n == S_DIE2) return;
int soundvol = m_pBotSkill->iMaxHearVolume -
(int)(GetDistance(*o)*3*m_pBotSkill->iMaxHearVolume/255);
if (soundvol == 0) return;
// Look who made the sound(check for the nearest enemy)
float flDist, flNearestDist = 3.0f; // Range of 3 units
playerent *pNearest = NULL;
// Check all players first
loopv(players)
{
playerent *d = players[i];
if (d == m_pMyEnt || !d || (d->state != CS_ALIVE) ||
isteam(m_pMyEnt, d))
continue;
flDist = GetDistance(*o, d->o);
if ((flDist < flNearestDist) && IsVisible(d))
{
pNearest = d;
flNearestDist = flDist;
}
}
// Check local player
if (player1 && (player1->state == CS_ALIVE) &&
!isteam(m_pMyEnt, player1))
{
flDist = GetDistance(*o, player1->o);
if ((flDist < flNearestDist) && IsVisible(player1))
{
pNearest = player1;
flNearestDist = flDist;
}
}
if (pNearest)
{
if (m_pMyEnt->enemy != pNearest)
m_iShootDelay = lastmillis + GetShootDelay(); // Add shoot delay when new enemy found
m_pMyEnt->enemy = pNearest;
}
}
bool CBot::IsInFOV(const vec &o)
{
vec target, dir, forward, right, up;
float flDot, flAngle;
AnglesToVectors(GetViewAngles(), forward, right, up);
// direction the bot is aiming at
dir = forward;
dir.z = 0; // Make 2D
// ideal direction
target = o;
target.sub(m_pMyEnt->o);
target.z = 0.0f; // Make 2D
// angle between these two directions
flDot = target.dot(dir);
flAngle = acos(flDot/(target.magnitude() * dir.magnitude()));
return m_pBotSkill->iFov/2.0f >= flAngle/RAD;
}
// Code of CBot - End
Advertisement
153
pages
Bot ai.cpp
Advertisement