git-svn-id: svn://svn.code.sf.net/p/actiongame/code/trunk/ac@526 e016b1c5-ed68-44aa-a2f7-31cac88cffde
2306 lines
75 KiB
C++
2306 lines
75 KiB
C++
//
|
|
// C++ Implementation: bot_ai
|
|
//
|
|
// Description: The AI part comes here(navigation, shooting etc)
|
|
//
|
|
//
|
|
// Author: <[email protected]>
|
|
//
|
|
|
|
|
|
// Code of CBot - Start
|
|
|
|
#include "../cube.h"
|
|
|
|
extern vector<server_entity> sents;
|
|
extern vector<dynent*> monsters;
|
|
|
|
extern weaponinfo_s WeaponInfoTable[MAX_WEAPONS];
|
|
|
|
vec CBot::GetEnemyPos(dynent *d)
|
|
{
|
|
// Aim offset idea by botman
|
|
vec o = d->o, offset;
|
|
float flDist = GetDistance(d->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;
|
|
}
|
|
|
|
bool CBot::FindEnemy(void)
|
|
{
|
|
// UNDONE: Enemies are now only scored on their distance
|
|
|
|
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->team, m_pMyEnt->enemy->team))
|
|
{
|
|
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);
|
|
|
|
dynent *pNewEnemy = NULL, *d = NULL;
|
|
float flDist, flNearestDist = 99999.9f;
|
|
short EnemyVal, BestEnemyVal = -100;
|
|
|
|
// search for only for monsters when SP mode
|
|
/* no more sp as such -argh (sorry bout messy commenting)
|
|
if (m_sp)
|
|
{
|
|
loopv(monsters)
|
|
{
|
|
d = monsters[i]; // Handy shortcut
|
|
|
|
if (d->state != CS_ALIVE)
|
|
continue;
|
|
|
|
// Check if the enemy is visible
|
|
if(!IsInFOV(d) || !IsVisible(d))
|
|
continue;
|
|
|
|
flDist = GetDistance(d->o);
|
|
EnemyVal = 1;
|
|
|
|
if (flDist < flNearestDist)
|
|
{
|
|
EnemyVal+=2;
|
|
flNearestDist = flDist;
|
|
}
|
|
|
|
if (EnemyVal > BestEnemyVal)
|
|
{
|
|
pNewEnemy = d;
|
|
BestEnemyVal = EnemyVal;
|
|
}
|
|
}
|
|
} */
|
|
|
|
//else
|
|
//{
|
|
|
|
// First loop through all players
|
|
loopv(players)
|
|
{
|
|
d = players[i]; // Handy shortcut
|
|
|
|
if (!d || isteam(d->team, m_pMyEnt->team) || (d->state != CS_ALIVE))
|
|
continue;
|
|
|
|
// Check if the enemy is visible
|
|
if(!IsInFOV(d) || !IsVisible(d))
|
|
continue;
|
|
|
|
flDist = GetDistance(d->o);
|
|
EnemyVal = 1;
|
|
|
|
if (flDist < flNearestDist)
|
|
{
|
|
EnemyVal+=2;
|
|
flNearestDist = flDist;
|
|
}
|
|
|
|
if (EnemyVal > BestEnemyVal)
|
|
{
|
|
pNewEnemy = d;
|
|
BestEnemyVal = EnemyVal;
|
|
}
|
|
}
|
|
|
|
// Then loop through all bots
|
|
loopv(bots)
|
|
{
|
|
d = bots[i]; // Handy shortcut
|
|
|
|
if (d == m_pMyEnt)
|
|
continue;
|
|
|
|
if (!d || isteam(d->team, m_pMyEnt->team) || (d->state != CS_ALIVE))
|
|
continue;
|
|
|
|
// Check if the enemy is visible
|
|
if(!IsInFOV(d) || !IsVisible(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->team, m_pMyEnt->team) &&
|
|
(player1->state == CS_ALIVE))
|
|
{
|
|
// Check if the enemy is visible
|
|
if(IsInFOV(player1) && IsVisible(player1))
|
|
{
|
|
flDist = GetDistance(player1->o);
|
|
EnemyVal = 1;
|
|
|
|
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->team, m_pHuntTarget->team))
|
|
{
|
|
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;
|
|
|
|
dynent *pNewEnemy = NULL, *d = NULL;
|
|
float flDist, flNearestDist = 99999.9f, flNearestOldPosDistToEnemy = 99999.9f;
|
|
float flNearestOldPosDistToBot = 99999.9f;
|
|
short EnemyVal, BestEnemyVal = -100;
|
|
vec BestOldPos;
|
|
|
|
// search for only for monsters when SP mode
|
|
//edit -argh
|
|
/*if (m_sp)
|
|
{
|
|
loopv(monsters)
|
|
{
|
|
d = monsters[i]; // Handy shortcut
|
|
|
|
if (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<MAX_STORED_LOCATIONS;j++)
|
|
{
|
|
if (d->PrevLocations.prevloc[j]==m_vPrevHuntLocation) continue;
|
|
if (d->PrevLocations.prevloc[j]==g_vecZero) continue;
|
|
|
|
vec v = d->PrevLocations.prevloc[j];
|
|
|
|
flDist = GetDistance(d->o, v);
|
|
|
|
if ((flDist < flNearestOldPosDistToBot) && IsReachable(v))
|
|
{
|
|
flNearestOldPosDistToEnemy = flDist;
|
|
bestfromenemy = v;
|
|
}
|
|
}
|
|
|
|
// Check previous locations of bot hisself
|
|
for (int j=0;j<MAX_STORED_LOCATIONS;j++)
|
|
{
|
|
if (m_pMyEnt->PrevLocations.prevloc[j]==m_vPrevHuntLocation) continue;
|
|
if (m_pMyEnt->PrevLocations.prevloc[j]==g_vecZero) continue;
|
|
|
|
vec v = m_pMyEnt->PrevLocations.prevloc[j];
|
|
|
|
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;
|
|
}
|
|
}
|
|
} */
|
|
//else
|
|
//{
|
|
// First loop through all players
|
|
loopv(players)
|
|
{
|
|
d = players[i]; // Handy shortcut
|
|
|
|
if (!d || isteam(d->team, m_pMyEnt->team) || (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<MAX_STORED_LOCATIONS;j++)
|
|
{
|
|
if (d->PrevLocations.prevloc[j]==m_vPrevHuntLocation) continue;
|
|
if (d->PrevLocations.prevloc[j]==g_vecZero) continue;
|
|
|
|
vec v = d->PrevLocations.prevloc[j];
|
|
|
|
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<MAX_STORED_LOCATIONS;j++)
|
|
{
|
|
if (m_pMyEnt->PrevLocations.prevloc[j]==m_vPrevHuntLocation) continue;
|
|
if (m_pMyEnt->PrevLocations.prevloc[j]==g_vecZero) continue;
|
|
|
|
vec v = m_pMyEnt->PrevLocations.prevloc[j];
|
|
|
|
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 loop through all bots
|
|
loopv(bots)
|
|
{
|
|
d = bots[i]; // Handy shortcut
|
|
|
|
if (d == m_pMyEnt)
|
|
continue;
|
|
|
|
if (!d || isteam(d->team, m_pMyEnt->team) || (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<MAX_STORED_LOCATIONS;j++)
|
|
{
|
|
if (d->PrevLocations.prevloc[j]==m_vPrevHuntLocation) continue;
|
|
if (d->PrevLocations.prevloc[j]==g_vecZero) continue;
|
|
|
|
vec v = d->PrevLocations.prevloc[j];
|
|
|
|
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<MAX_STORED_LOCATIONS;j++)
|
|
{
|
|
if (m_pMyEnt->PrevLocations.prevloc[j]==m_vPrevHuntLocation) continue;
|
|
if (m_pMyEnt->PrevLocations.prevloc[j]==g_vecZero) continue;
|
|
|
|
vec v = m_pMyEnt->PrevLocations.prevloc[j];
|
|
|
|
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->team, m_pMyEnt->team) &&
|
|
(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<MAX_STORED_LOCATIONS;j++)
|
|
{
|
|
if (d->PrevLocations.prevloc[j]==m_vPrevHuntLocation) continue;
|
|
if (d->PrevLocations.prevloc[j]==g_vecZero) continue;
|
|
|
|
vec v = d->PrevLocations.prevloc[j];
|
|
|
|
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<MAX_STORED_LOCATIONS;j++)
|
|
{
|
|
if (m_pMyEnt->PrevLocations.prevloc[j]==m_vPrevHuntLocation) continue;
|
|
if (m_pMyEnt->PrevLocations.prevloc[j]==g_vecZero) continue;
|
|
|
|
vec v = m_pMyEnt->PrevLocations.prevloc[j];
|
|
|
|
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<MAX_STORED_LOCATIONS;j++)
|
|
{
|
|
vec OldPos = m_pHuntTarget->PrevLocations.prevloc[j];
|
|
|
|
if (bDone && m_vHuntLocation==OldPos)
|
|
continue;
|
|
|
|
if (OldPos==g_vecZero)
|
|
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<MAX_STORED_LOCATIONS;j++)
|
|
{
|
|
vec OldPos = m_pMyEnt->PrevLocations.prevloc[j];
|
|
|
|
if (bDone && m_vHuntLocation==OldPos)
|
|
continue;
|
|
|
|
if (OldPos==g_vecZero)
|
|
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->PrevLocations.prevloc[BestPosIndexFromEnemy];
|
|
bNew = true;
|
|
m_fPrevHuntDist = 0.0f;
|
|
}
|
|
else if (BestPosIndexFromBot > -1)
|
|
{
|
|
m_vPrevHuntLocation = m_vHuntLocation;
|
|
m_vHuntLocation = m_pMyEnt->PrevLocations.prevloc[BestPosIndexFromBot];
|
|
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::ShootEnemy()
|
|
{
|
|
if(!m_pMyEnt->enemy) return;
|
|
|
|
m_iSawEnemyTime = lastmillis;
|
|
|
|
// Aim to enemy
|
|
vec enemypos = GetEnemyPos(m_pMyEnt->enemy);
|
|
AimToVec(enemypos);
|
|
|
|
// 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
|
|
shoot(m_pMyEnt, tr.end);
|
|
|
|
// Add shoot delay
|
|
m_iShootDelay = lastmillis + GetShootDelay();
|
|
}
|
|
ChoosePreferredWeapon();
|
|
}
|
|
#ifndef RELEASE_BUILD
|
|
else
|
|
{
|
|
char sz[64];
|
|
sprintf(sz, "shootdelay: %d\n", (m_iShootDelay-lastmillis));
|
|
AddDebugText(sz);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool CBot::ChoosePreferredWeapon()
|
|
{
|
|
TMultiChoice<int> WeaponChoices;
|
|
short sWeaponScore;
|
|
float flDist = GetDistance(m_pMyEnt->enemy->o);
|
|
|
|
if ((m_iChangeWeaponDelay > lastmillis) && (m_pMyEnt->ammo[m_pMyEnt->gunselect]))
|
|
{
|
|
if ((WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType != TYPE_MELEE) || (flDist <= 3.5f))
|
|
return true;
|
|
}
|
|
|
|
// Choose a weapon
|
|
for(int i=0;i<MAX_WEAPONS;i++)
|
|
{
|
|
// If no ammo for this weapon, skip it
|
|
if (m_pMyEnt->ammo[i] == 0) continue;
|
|
|
|
sWeaponScore = 5; // Minimal score for a weapon
|
|
|
|
if ((flDist >= WeaponInfoTable[i].flMinDesiredDistance) &&
|
|
(flDist <= WeaponInfoTable[i].flMaxDesiredDistance))
|
|
{
|
|
// In desired range for this weapon
|
|
sWeaponScore += 5; // Increase score much
|
|
}
|
|
else if ((flDist < WeaponInfoTable[i].flMinFireDistance) ||
|
|
(flDist > WeaponInfoTable[i].flMaxFireDistance))
|
|
continue; // Wrong distance for this weapon
|
|
|
|
// The ideal distance would be between the Min and Max desired distance.
|
|
// Score on the difference of the avarage of the Min and Max desired distance.
|
|
float flAvarage = (WeaponInfoTable[i].flMinDesiredDistance +
|
|
WeaponInfoTable[i].flMaxDesiredDistance) / 2.0f;
|
|
float flIdealDiff = fabs(flDist - flAvarage);
|
|
|
|
if (flIdealDiff < 0.5f) // Close to ideal distance
|
|
sWeaponScore += 4;
|
|
else if (flIdealDiff <= 1.0f)
|
|
sWeaponScore += 2;
|
|
|
|
// Now rate the weapon on available ammo...
|
|
if (WeaponInfoTable[i].sMinDesiredAmmo > 0)
|
|
{
|
|
// Calculate how much percent of the min desired ammo the bot has
|
|
float flDesiredPercent = (float(m_pMyEnt->ammo[i]) /
|
|
float(WeaponInfoTable[i].sMinDesiredAmmo)) *
|
|
100.0f;
|
|
|
|
if (flDesiredPercent >= 400.0f)
|
|
sWeaponScore += 4;
|
|
else if (flDesiredPercent >= 200.0f)
|
|
sWeaponScore += 3;
|
|
else if (flDesiredPercent >= 100.0f)
|
|
sWeaponScore += 1;
|
|
}
|
|
|
|
WeaponChoices.Insert(i, sWeaponScore);
|
|
}
|
|
|
|
int WeaponSelect;
|
|
if (WeaponChoices.GetSelection(WeaponSelect))
|
|
{
|
|
m_iChangeWeaponDelay = lastmillis + RandomLong(2000, 8000);
|
|
m_bShootAtFeet = ((WeaponInfoTable[WeaponSelect].eWeaponType==TYPE_ROCKET) &&
|
|
(RandomLong(1, 100) <=
|
|
m_pBotSkill->sShootAtFeetWithRLPercent));
|
|
return SelectGun(WeaponSelect);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int CBot::GetShootDelay()
|
|
{
|
|
// UNDONE
|
|
if ((WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE) ||
|
|
(WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_AUTO))
|
|
return m_pMyEnt->gunwait;
|
|
|
|
float flMinShootDelay = m_pBotSkill->flMinAttackDelay;
|
|
float flMaxShootDelay = m_pBotSkill->flMaxAttackDelay;
|
|
return max(m_pMyEnt->gunwait, int(RandomFloat(flMinShootDelay, flMaxShootDelay) * 1000.0f));
|
|
}
|
|
|
|
void CBot::CheckReload() // reload gun if no enemies are around
|
|
{
|
|
if(m_pMyEnt->enemy) return;
|
|
SelectGun(m_pMyEnt->primary);
|
|
reload(m_pMyEnt);
|
|
return;
|
|
}
|
|
|
|
void CBot::MainAI()
|
|
{
|
|
// Default bots will run forward
|
|
m_pMyEnt->move = 1;
|
|
|
|
// Default bots won't strafe
|
|
m_pMyEnt->strafe = 0;
|
|
|
|
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
|
|
{
|
|
AddDebugText("has enemy");
|
|
|
|
// Shoot at enemy
|
|
ShootEnemy();
|
|
|
|
if (m_eCurrentBotState != STATE_ENEMY)
|
|
{
|
|
m_vGoal = g_vecZero;
|
|
ResetWaypointVars();
|
|
}
|
|
|
|
m_eCurrentBotState = STATE_ENEMY;
|
|
if (!CheckJump())
|
|
DoCombatNav();
|
|
}
|
|
else if (CheckHunt() && HuntEnemy())
|
|
{
|
|
CheckReload();
|
|
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;
|
|
}
|
|
else if (m_classicsp && DoSPStuff()) // Home to goal, find/follow friends etc.
|
|
{
|
|
|
|
AddDebugText("SP stuff");
|
|
m_eCurrentBotState = STATE_SP;
|
|
}
|
|
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_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 >= lastmillis)
|
|
return false;
|
|
|
|
if ((m_vGoal!=g_vecZero) && (GetDistance(m_vGoal) < 2.0f))
|
|
return false;
|
|
|
|
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;
|
|
|
|
// 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));
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// 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::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++)
|
|
{
|
|
if (small) break;
|
|
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) 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()
|
|
{
|
|
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::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, vec *o)
|
|
{
|
|
// Has the bot already an enemy?
|
|
if (m_pMyEnt->enemy) return;
|
|
|
|
// Is the sound not interesting?
|
|
//fixmebot
|
|
/* if ((n==S_DIE1) || (n==S_DIE2) || (n==S_DEATHR) || (n==S_DEATHE) || (n==S_DEATHS) ||
|
|
(n==S_DEATHB) || (n==S_DEATHH) || (n==S_DEATHD) || (n==S_PIGGR2))
|
|
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
|
|
dynent *pNearest = NULL;
|
|
|
|
/* edit -argh
|
|
if (m_sp)
|
|
{
|
|
// Check all monsters
|
|
loopv(monsters)
|
|
{
|
|
if (!monsters[i] || monsters[i]->state != CS_ALIVE) continue;
|
|
|
|
flDist = GetDistance(*o, monsters[i]->o);
|
|
if ((flDist < flNearestDist) && IsVisible(monsters[i]))
|
|
{
|
|
pNearest = monsters[i];
|
|
flNearestDist = flDist;
|
|
}
|
|
}
|
|
} */
|
|
//else
|
|
//{
|
|
// Check all players first
|
|
loopv(players)
|
|
{
|
|
if (!players[i] || (players[i]->state != CS_ALIVE) ||
|
|
isteam(m_pMyEnt->team, players[i]->team))
|
|
continue;
|
|
|
|
flDist = GetDistance(*o, players[i]->o);
|
|
if ((flDist < flNearestDist) && IsVisible(players[i]))
|
|
{
|
|
pNearest = players[i];
|
|
flNearestDist = flDist;
|
|
}
|
|
}
|
|
|
|
// Check all bots
|
|
loopv(bots)
|
|
{
|
|
if (!bots[i] || (bots[i]->state != CS_ALIVE) ||
|
|
isteam(m_pMyEnt->team, bots[i]->team) || (bots[i] == m_pMyEnt))
|
|
continue;
|
|
|
|
flDist = GetDistance(*o, bots[i]->o);
|
|
if ((flDist < flNearestDist) && IsVisible(bots[i]))
|
|
{
|
|
pNearest = bots[i];
|
|
flNearestDist = flDist;
|
|
}
|
|
}
|
|
|
|
// Check local player
|
|
if (player1 && (player1->state == CS_ALIVE) &&
|
|
!isteam(m_pMyEnt->team, player1->team))
|
|
{
|
|
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 v2, forward, right, up;
|
|
float flDot;
|
|
|
|
AnglesToVectors(GetViewAngles(), forward, right, up);
|
|
|
|
v2 = o;
|
|
v2.sub(m_pMyEnt->o);
|
|
v2.z = 0.0f; // Make 2D
|
|
v2 = Normalize(v2);
|
|
forward.z = 0; // Make 2D
|
|
|
|
flDot = v2.dot(forward);
|
|
|
|
return(flDot >= 0.5f); // sin2(0.5) == 60 degrees FOV
|
|
}
|
|
|
|
// Code of CBot - End
|