mirror of
https://github.com/s1lentq/ReGameDLL_CS.git
synced 2024-12-28 07:35:40 +03:00
7372573c89
Ignore dormant players Minor refactoring
903 lines
21 KiB
C++
903 lines
21 KiB
C++
/*
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at
|
|
* your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
* In addition, as a special exception, the author gives permission to
|
|
* link the code of this program with the Half-Life Game Engine ("HL
|
|
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
|
|
* L.L.C ("Valve"). You must obey the GNU General Public License in all
|
|
* respects for all of the code used other than the HL Engine and MODs
|
|
* from Valve. If you modify this file, you may extend this exception
|
|
* to your version of the file, but you are not obligated to do so. If
|
|
* you do not wish to do so, delete this exception statement from your
|
|
* version.
|
|
*
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
|
|
#ifdef REGAMEDLL_ADD
|
|
// Give 3rd-party to get the virtual table of the object.
|
|
// Example: AMXModX module: Hamsandwich
|
|
// RegisterHam(Ham_Spawn, "bot", "CCSBot__Spawn", 1);
|
|
LINK_ENTITY_TO_CLASS(bot, CCSBot, CAPI_CSBot)
|
|
#endif
|
|
|
|
// Return the number of bots following the given player
|
|
int GetBotFollowCount(CBasePlayer *pLeader)
|
|
{
|
|
int count = 0;
|
|
for (int i = 1; i <= gpGlobals->maxClients; i++)
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex(i);
|
|
|
|
if (!UTIL_IsValidPlayer(pPlayer))
|
|
continue;
|
|
|
|
if (FStrEq(STRING(pPlayer->pev->netname), ""))
|
|
continue;
|
|
|
|
if (!pPlayer->IsBot())
|
|
continue;
|
|
|
|
if (!pPlayer->IsAlive())
|
|
continue;
|
|
|
|
CCSBot *pBot = static_cast<CCSBot *>(pPlayer);
|
|
if (pBot->IsBot() && pBot->GetFollowLeader() == pLeader)
|
|
count++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
// Change movement speed to walking
|
|
void CCSBot::Walk()
|
|
{
|
|
if (m_mustRunTimer.IsElapsed())
|
|
{
|
|
CBot::Walk();
|
|
return;
|
|
}
|
|
|
|
// must run
|
|
Run();
|
|
}
|
|
|
|
// Return true if jump was started.
|
|
// This is extended from the base jump to disallow jumping when in a crouch area.
|
|
bool CCSBot::Jump(bool mustJump)
|
|
{
|
|
// prevent jumping if we're crouched, unless we're in a crouchjump area - jump wins
|
|
bool inCrouchJumpArea = (m_lastKnownArea &&
|
|
(m_lastKnownArea->GetAttributes() & NAV_CROUCH) &&
|
|
!(m_lastKnownArea->GetAttributes() & NAV_JUMP));
|
|
|
|
if (inCrouchJumpArea)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return CBot::Jump(mustJump);
|
|
}
|
|
|
|
// Invoked when injured by something
|
|
// NOTE: We dont want to directly call Attack() here, or the bots will have super-human reaction times when injured
|
|
BOOL CCSBot::TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType)
|
|
{
|
|
CBaseEntity *pAttacker = GetClassPtr<CCSEntity>((CBaseEntity *)pevInflictor);
|
|
|
|
// if we were attacked by a teammate, rebuke
|
|
if (pAttacker->IsPlayer())
|
|
{
|
|
CBasePlayer *pPlayer = static_cast<CBasePlayer *>(pAttacker);
|
|
if (BotRelationship(pPlayer) == BOT_TEAMMATE && !pPlayer->IsBot())
|
|
{
|
|
GetChatter()->FriendlyFire();
|
|
}
|
|
|
|
if (IsEnemy(pPlayer))
|
|
{
|
|
// Track previous attacker so we don't try to panic multiple times for a shotgun blast
|
|
CBasePlayer *lastAttacker = m_attacker;
|
|
float lastAttackedTimestamp = m_attackedTimestamp;
|
|
|
|
// keep track of our last attacker
|
|
m_attacker = pPlayer;
|
|
m_attackedTimestamp = gpGlobals->time;
|
|
|
|
// no longer safe
|
|
AdjustSafeTime();
|
|
|
|
if (!IsSurprised() && (m_attacker != lastAttacker || m_attackedTimestamp != lastAttackedTimestamp))
|
|
{
|
|
// being hurt by an enemy we can't see causes panic
|
|
if (!IsVisible(pPlayer, CHECK_FOV))
|
|
{
|
|
bool bPanic = false;
|
|
|
|
// if not attacking anything, look around to try to find attacker
|
|
if (!IsAttacking())
|
|
{
|
|
bPanic = true;
|
|
}
|
|
else
|
|
{
|
|
// we are attacking
|
|
if (!IsEnemyVisible())
|
|
{
|
|
// can't see our current enemy, panic to acquire new attacker
|
|
bPanic = true;
|
|
}
|
|
}
|
|
|
|
if (!bPanic)
|
|
{
|
|
float invSkill = 1.0f - GetProfile()->GetSkill();
|
|
float panicChance = invSkill * invSkill * 50.0f;
|
|
|
|
if (panicChance > RANDOM_FLOAT(0, 100))
|
|
{
|
|
bPanic = true;
|
|
}
|
|
}
|
|
|
|
if (bPanic)
|
|
{
|
|
// can't see our current enemy, panic to acquire new attacker
|
|
Panic(m_attacker);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// extend
|
|
return CBasePlayer::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType);
|
|
}
|
|
|
|
// Invoked when killed
|
|
void CCSBot::Killed(entvars_t *pevAttacker, int iGib)
|
|
{
|
|
PrintIfWatched("Killed( attacker = %s )\n", STRING(pevAttacker->netname));
|
|
|
|
GetChatter()->OnDeath();
|
|
|
|
// increase the danger where we died
|
|
const float deathDanger = 1.0f;
|
|
const float deathDangerRadius = 500.0f;
|
|
IncreaseDangerNearby(m_iTeam - 1, deathDanger, m_lastKnownArea, &pev->origin, deathDangerRadius);
|
|
|
|
// end voice feedback
|
|
EndVoiceFeedback();
|
|
|
|
// extend
|
|
CBasePlayer::Killed(pevAttacker, iGib);
|
|
}
|
|
|
|
// Return true if line segment intersects rectangular volume
|
|
bool IsIntersectingBox(const Vector &start, const Vector &end, const Vector &boxMin, const Vector &boxMax)
|
|
{
|
|
constexpr auto HI_X = BIT(1);
|
|
constexpr auto LO_X = BIT(2);
|
|
|
|
constexpr auto HI_Y = BIT(3);
|
|
constexpr auto LO_Y = BIT(4);
|
|
|
|
constexpr auto HI_Z = BIT(5);
|
|
constexpr auto LO_Z = BIT(6);
|
|
|
|
unsigned char startFlags = 0;
|
|
unsigned char endFlags = 0;
|
|
|
|
// classify start point
|
|
if (start.x < boxMin.x)
|
|
startFlags |= LO_X;
|
|
else if (start.x > boxMax.x)
|
|
startFlags |= HI_X;
|
|
|
|
if (start.y < boxMin.y)
|
|
startFlags |= LO_Y;
|
|
else if (start.y > boxMax.y)
|
|
startFlags |= HI_Y;
|
|
|
|
if (start.z < boxMin.z)
|
|
startFlags |= LO_Z;
|
|
else if (start.z > boxMax.z)
|
|
startFlags |= HI_Z;
|
|
|
|
// classify end point
|
|
if (end.x < boxMin.x)
|
|
endFlags |= LO_X;
|
|
else if (end.x > boxMax.x)
|
|
endFlags |= HI_X;
|
|
|
|
if (end.y < boxMin.y)
|
|
endFlags |= LO_Y;
|
|
else if (end.y > boxMax.y)
|
|
endFlags |= HI_Y;
|
|
|
|
if (end.z < boxMin.z)
|
|
endFlags |= LO_Z;
|
|
else if (end.z > boxMax.z)
|
|
endFlags |= HI_Z;
|
|
|
|
// trivial reject
|
|
if (startFlags & endFlags)
|
|
return false;
|
|
|
|
// TODO: Do exact line/box intersection check
|
|
return true;
|
|
}
|
|
|
|
// When bot is touched by another entity.
|
|
void CCSBot::BotTouch(CBaseEntity *pOther)
|
|
{
|
|
// if we have touched a higher-priority player, make way
|
|
// TODO: Need to account for reaction time, etc.
|
|
if (pOther->IsPlayer())
|
|
{
|
|
// if we are defusing a bomb, don't move
|
|
if (IsDefusingBomb())
|
|
return;
|
|
|
|
CBasePlayer *pPlayer = static_cast<CBasePlayer *>(pOther);
|
|
|
|
// get priority of other player
|
|
unsigned int otherPri = TheCSBots()->GetPlayerPriority(pPlayer);
|
|
|
|
// get our priority
|
|
unsigned int myPri = TheCSBots()->GetPlayerPriority(this);
|
|
|
|
// if our priority is better, don't budge
|
|
if (myPri < otherPri)
|
|
return;
|
|
|
|
// they are higher priority - make way, unless we're already making way for someone more important
|
|
if (m_avoid)
|
|
{
|
|
unsigned int avoidPri = TheCSBots()->GetPlayerPriority(m_avoid);
|
|
if (avoidPri < otherPri)
|
|
{
|
|
// ignore 'pOther' because we're already avoiding someone better
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_avoid = static_cast<CBasePlayer *>(pOther);
|
|
m_avoidTimestamp = gpGlobals->time;
|
|
|
|
return;
|
|
}
|
|
|
|
// If we won't be able to break it, don't try
|
|
if (pOther->pev->takedamage != DAMAGE_YES)
|
|
return;
|
|
|
|
if (IsAttacking())
|
|
return;
|
|
|
|
// See if it's breakable
|
|
if (FClassnameIs(pOther->pev, "func_breakable"))
|
|
{
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
CBreakable *pBreak = static_cast<CBreakable *>(pOther);
|
|
|
|
// Material is "UnbreakableGlass"
|
|
if (!pBreak->IsBreakable())
|
|
return;
|
|
#endif
|
|
|
|
Vector center = (pOther->pev->absmax + pOther->pev->absmin) / 2.0f;
|
|
bool breakIt = true;
|
|
|
|
if (m_pathLength)
|
|
{
|
|
Vector goal = m_goalPosition + Vector(0, 0, HalfHumanHeight);
|
|
breakIt = IsIntersectingBox(pev->origin, goal, pOther->pev->absmin, pOther->pev->absmax);
|
|
}
|
|
|
|
if (breakIt)
|
|
{
|
|
// it's breakable - try to shoot it.
|
|
SetLookAt("Breakable", ¢er, PRIORITY_HIGH, 0.2, 0, 5.0);
|
|
|
|
if (IsUsingGrenade())
|
|
{
|
|
EquipBestWeapon();
|
|
return;
|
|
}
|
|
|
|
PrimaryAttack();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CCSBot::IsBusy() const
|
|
{
|
|
if (IsAttacking() ||
|
|
IsBuying() ||
|
|
IsDefusingBomb() ||
|
|
GetTask() == PLANT_BOMB ||
|
|
GetTask() == RESCUE_HOSTAGES ||
|
|
IsSniping())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CCSBot::BotDeathThink()
|
|
{
|
|
;
|
|
}
|
|
|
|
CBasePlayer *CCSBot::FindNearbyPlayer()
|
|
{
|
|
CBasePlayer *pPlayer = nullptr;
|
|
Vector vecSrc = pev->origin;
|
|
const float flRadius = 800.0f;
|
|
|
|
while ((pPlayer = UTIL_FindEntityInSphere(pPlayer, vecSrc, flRadius)))
|
|
{
|
|
if (!pPlayer->IsPlayer())
|
|
continue;
|
|
|
|
if (!(pPlayer->pev->flags & FL_FAKECLIENT))
|
|
continue;
|
|
|
|
return pPlayer;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// Assign given player as our current enemy to attack
|
|
void CCSBot::SetEnemy(CBasePlayer *pEnemy)
|
|
{
|
|
if (m_enemy != pEnemy)
|
|
{
|
|
m_enemy = pEnemy;
|
|
m_currentEnemyAcquireTimestamp = gpGlobals->time;
|
|
}
|
|
}
|
|
|
|
// If we are not on the navigation mesh (m_currentArea == nullptr),
|
|
// move towards last known area.
|
|
// Return false if off mesh.
|
|
bool CCSBot::StayOnNavMesh()
|
|
{
|
|
if (m_currentArea)
|
|
return true;
|
|
|
|
// move back onto the area map
|
|
|
|
// if we have no lastKnownArea, we probably started off
|
|
// of the nav mesh - find the closest nav area and use it
|
|
CNavArea *goalArea;
|
|
if (!m_currentArea && !m_lastKnownArea)
|
|
{
|
|
goalArea = TheNavAreaGrid.GetNearestNavArea(&pev->origin);
|
|
PrintIfWatched("Started off the nav mesh - moving to closest nav area...\n");
|
|
}
|
|
else
|
|
{
|
|
goalArea = m_lastKnownArea;
|
|
PrintIfWatched("Getting out of NULL area...\n");
|
|
}
|
|
|
|
if (goalArea)
|
|
{
|
|
Vector pos;
|
|
goalArea->GetClosestPointOnArea(&pev->origin, &pos);
|
|
|
|
// move point into area
|
|
Vector to = pos - pev->origin;
|
|
to.NormalizeInPlace();
|
|
|
|
// how far to "step into" an area - must be less than min area size
|
|
const float stepInDist = 5.0f;
|
|
pos = pos + (stepInDist * to);
|
|
|
|
MoveTowardsPosition(&pos);
|
|
}
|
|
|
|
// if we're stuck, try to get un-stuck
|
|
// do stuck movements last, so they override normal movement
|
|
if (m_isStuck)
|
|
{
|
|
Wiggle();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
void CCSBot::Kill()
|
|
{
|
|
m_LastHitGroup = HITGROUP_GENERIC;
|
|
|
|
// have the player kill himself
|
|
pev->health = 0.0f;
|
|
Killed(VARS(eoNullEntity), GIB_NEVER);
|
|
|
|
if (CSGameRules()->m_pVIP == this)
|
|
CSGameRules()->m_iConsecutiveVIP = 10;
|
|
}
|
|
#endif
|
|
|
|
void CCSBot::Panic(CBasePlayer *pEnemy)
|
|
{
|
|
if (IsSurprised())
|
|
return;
|
|
|
|
Vector2D dir(BotCOS(pev->v_angle.y), BotSIN(pev->v_angle.y));
|
|
Vector2D perp(-dir.y, dir.x);
|
|
Vector spot;
|
|
|
|
if (GetProfile()->GetSkill() >= 0.5f)
|
|
{
|
|
Vector2D toEnemy = (pEnemy->pev->origin - pev->origin).Make2D();
|
|
toEnemy.NormalizeInPlace();
|
|
|
|
float along = DotProduct(toEnemy, dir);
|
|
|
|
float c45 = 0.7071f;
|
|
float size = 100.0f;
|
|
|
|
real_t shift = RANDOM_FLOAT(-75.0, 75.0);
|
|
|
|
if (along > c45)
|
|
{
|
|
spot.x = pev->origin.x + dir.x * size + perp.x * shift;
|
|
spot.y = pev->origin.y + dir.y * size + perp.y * shift;
|
|
}
|
|
else if (along < -c45)
|
|
{
|
|
spot.x = pev->origin.x - dir.x * size + perp.x * shift;
|
|
spot.y = pev->origin.y - dir.y * size + perp.y * shift;
|
|
}
|
|
else if (DotProduct(toEnemy, perp) > 0.0)
|
|
{
|
|
spot.x = pev->origin.x + perp.x * size + dir.x * shift;
|
|
spot.y = pev->origin.y + perp.y * size + dir.y * shift;
|
|
}
|
|
else
|
|
{
|
|
spot.x = pev->origin.x - perp.x * size + dir.x * shift;
|
|
spot.y = pev->origin.y - perp.y * size + dir.y * shift;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const float offset = 200.0f;
|
|
real_t side = RANDOM_FLOAT(-offset, offset) * 2.0f;
|
|
|
|
spot.x = pev->origin.x - dir.x * offset + perp.x * side;
|
|
spot.y = pev->origin.y - dir.y * offset + perp.y * side;
|
|
}
|
|
|
|
spot.z = pev->origin.z + RANDOM_FLOAT(-50.0, 50.0);
|
|
|
|
// we are stunned for a moment
|
|
m_surpriseDelay = RANDOM_FLOAT(0.1, 0.2);
|
|
m_surpriseTimestamp = gpGlobals->time;
|
|
|
|
SetLookAt("Panic", &spot, PRIORITY_HIGH, 0, 0, 5.0);
|
|
PrintIfWatched("Aaaah!\n");
|
|
}
|
|
|
|
bool CCSBot::IsDoingScenario() const
|
|
{
|
|
if (cv_bot_defer_to_human.value <= 0.0f)
|
|
return true;
|
|
|
|
return !UTIL_HumansOnTeam(m_iTeam, IS_ALIVE);
|
|
}
|
|
|
|
// Return true if we noticed the bomb on the ground or on the radar (for T's only)
|
|
bool CCSBot::NoticeLooseBomb() const
|
|
{
|
|
if (TheCSBots()->GetScenario() != CCSBotManager::SCENARIO_DEFUSE_BOMB)
|
|
return false;
|
|
|
|
CBaseEntity *bomb = TheCSBots()->GetLooseBomb();
|
|
if (bomb)
|
|
{
|
|
// T's can always see bomb on their radar
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Return true if can see the bomb lying on the ground
|
|
bool CCSBot::CanSeeLooseBomb() const
|
|
{
|
|
if (TheCSBots()->GetScenario() != CCSBotManager::SCENARIO_DEFUSE_BOMB)
|
|
return false;
|
|
|
|
CBaseEntity *bomb = TheCSBots()->GetLooseBomb();
|
|
if (bomb)
|
|
{
|
|
if (IsVisible(&bomb->pev->origin, CHECK_FOV))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Return true if can see the planted bomb
|
|
bool CCSBot::CanSeePlantedBomb() const
|
|
{
|
|
if (TheCSBots()->GetScenario() != CCSBotManager::SCENARIO_DEFUSE_BOMB)
|
|
return false;
|
|
|
|
if (!GetGameState()->IsBombPlanted())
|
|
return false;
|
|
|
|
const Vector *bombPos = GetGameState()->GetBombPosition();
|
|
if (bombPos && IsVisible(bombPos, CHECK_FOV))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// Return last enemy that hurt us
|
|
CBasePlayer *CCSBot::GetAttacker() const
|
|
{
|
|
if (m_attacker && m_attacker->IsAlive())
|
|
return m_attacker;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// Immediately jump off of our ladder, if we're on one
|
|
void CCSBot::GetOffLadder()
|
|
{
|
|
if (IsUsingLadder())
|
|
{
|
|
Jump(MUST_JUMP);
|
|
DestroyPath();
|
|
}
|
|
}
|
|
|
|
// Return time when given spot was last checked
|
|
float CCSBot::GetHidingSpotCheckTimestamp(HidingSpot *spot) const
|
|
{
|
|
for (int i = 0; i < m_checkedHidingSpotCount; i++)
|
|
{
|
|
if (m_checkedHidingSpot[i].spot->GetID() == spot->GetID())
|
|
return m_checkedHidingSpot[i].timestamp;
|
|
}
|
|
|
|
return -999999.9f;
|
|
}
|
|
|
|
// Set the timestamp of the given spot to now.
|
|
// If the spot is not in the set, overwrite the least recently checked spot.
|
|
void CCSBot::SetHidingSpotCheckTimestamp(HidingSpot *spot)
|
|
{
|
|
int leastRecent = 0;
|
|
float leastRecentTime = gpGlobals->time + 1.0f;
|
|
|
|
for (int i = 0; i < m_checkedHidingSpotCount; i++)
|
|
{
|
|
// if spot is in the set, just update its timestamp
|
|
if (m_checkedHidingSpot[i].spot->GetID() == spot->GetID())
|
|
{
|
|
m_checkedHidingSpot[i].timestamp = gpGlobals->time;
|
|
return;
|
|
}
|
|
|
|
// keep track of least recent spot
|
|
if (m_checkedHidingSpot[i].timestamp < leastRecentTime)
|
|
{
|
|
leastRecentTime = m_checkedHidingSpot[i].timestamp;
|
|
leastRecent = i;
|
|
}
|
|
}
|
|
|
|
// if there is room for more spots, append this one
|
|
if (m_checkedHidingSpotCount < MAX_CHECKED_SPOTS)
|
|
{
|
|
m_checkedHidingSpot[m_checkedHidingSpotCount].spot = spot;
|
|
m_checkedHidingSpot[m_checkedHidingSpotCount].timestamp = gpGlobals->time;
|
|
m_checkedHidingSpotCount++;
|
|
}
|
|
else
|
|
{
|
|
// replace the least recent spot
|
|
m_checkedHidingSpot[leastRecent].spot = spot;
|
|
m_checkedHidingSpot[leastRecent].timestamp = gpGlobals->time;
|
|
}
|
|
}
|
|
|
|
// Periodic check of hostage count in case we lost some
|
|
void CCSBot::UpdateHostageEscortCount()
|
|
{
|
|
const float updateInterval = 1.0f;
|
|
if (m_hostageEscortCount == 0 || gpGlobals->time - m_hostageEscortCountTimestamp < updateInterval)
|
|
return;
|
|
|
|
m_hostageEscortCountTimestamp = gpGlobals->time;
|
|
|
|
// recount the hostages in case we lost some
|
|
m_hostageEscortCount = 0;
|
|
|
|
CHostage *pHostage = nullptr;
|
|
while ((pHostage = UTIL_FindEntityByClassname(pHostage, "hostage_entity")))
|
|
{
|
|
if (FNullEnt(pHostage->edict()))
|
|
break;
|
|
|
|
// skip dead or rescued hostages
|
|
if (!pHostage->IsAlive())
|
|
continue;
|
|
|
|
// check if hostage has targeted us, and is following
|
|
if (pHostage->IsFollowing(this))
|
|
m_hostageEscortCount++;
|
|
}
|
|
}
|
|
|
|
// Return true if we are outnumbered by enemies
|
|
bool CCSBot::IsOutnumbered() const
|
|
{
|
|
return (GetNearbyFriendCount() < GetNearbyEnemyCount() - 1) ? true : false;
|
|
}
|
|
|
|
// Return number of enemies we are outnumbered by
|
|
int CCSBot::OutnumberedCount() const
|
|
{
|
|
if (IsOutnumbered())
|
|
{
|
|
return (GetNearbyEnemyCount() - 1) - GetNearbyFriendCount();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Return the closest "important" enemy for the given scenario (bomb carrier, VIP, hostage escorter)
|
|
CBasePlayer *CCSBot::GetImportantEnemy(bool checkVisibility) const
|
|
{
|
|
CBasePlayer *nearEnemy = nullptr;
|
|
float nearDist = 999999999.9f;
|
|
|
|
for (int i = 1; i <= gpGlobals->maxClients; i++)
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex(i);
|
|
|
|
if (!UTIL_IsValidPlayer(pPlayer))
|
|
continue;
|
|
|
|
if (FStrEq(STRING(pPlayer->pev->netname), ""))
|
|
continue;
|
|
|
|
// is it a player?
|
|
if (!pPlayer->IsPlayer())
|
|
continue;
|
|
|
|
// is it alive?
|
|
if (!pPlayer->IsAlive())
|
|
continue;
|
|
|
|
// skip friends
|
|
if (BotRelationship(pPlayer) == BOT_TEAMMATE)
|
|
continue;
|
|
|
|
// is it "important"
|
|
if (!TheCSBots()->IsImportantPlayer(pPlayer))
|
|
continue;
|
|
|
|
// is it closest?
|
|
Vector d = pev->origin - pPlayer->pev->origin;
|
|
|
|
float distSq = d.x * d.x + d.y * d.y + d.z * d.z;
|
|
if (distSq < nearDist)
|
|
{
|
|
if (checkVisibility && !IsVisible(pPlayer, CHECK_FOV))
|
|
continue;
|
|
|
|
nearEnemy = pPlayer;
|
|
nearDist = distSq;
|
|
}
|
|
}
|
|
|
|
return nearEnemy;
|
|
}
|
|
|
|
// Sets our current disposition
|
|
void CCSBot::SetDisposition(DispositionType disposition)
|
|
{
|
|
m_disposition = disposition;
|
|
|
|
if (m_disposition != IGNORE_ENEMIES)
|
|
{
|
|
m_ignoreEnemiesTimer.Invalidate();
|
|
}
|
|
}
|
|
|
|
// Return our current disposition
|
|
CCSBot::DispositionType CCSBot::GetDisposition() const
|
|
{
|
|
if (!m_ignoreEnemiesTimer.IsElapsed())
|
|
return IGNORE_ENEMIES;
|
|
|
|
return m_disposition;
|
|
}
|
|
|
|
// Ignore enemies for a short durationy
|
|
void CCSBot::IgnoreEnemies(float duration)
|
|
{
|
|
m_ignoreEnemiesTimer.Start(duration);
|
|
}
|
|
|
|
// Increase morale one step
|
|
void CCSBot::IncreaseMorale()
|
|
{
|
|
if (m_morale < EXCELLENT)
|
|
{
|
|
m_morale = static_cast<MoraleType>(m_morale + 1);
|
|
}
|
|
}
|
|
|
|
// Decrease morale one step
|
|
void CCSBot::DecreaseMorale()
|
|
{
|
|
if (m_morale > TERRIBLE)
|
|
{
|
|
m_morale = static_cast<MoraleType>(m_morale - 1);
|
|
}
|
|
}
|
|
|
|
// Return true if we are acting like a rogue (not listening to teammates, not doing scenario goals)
|
|
// TODO: Account for morale
|
|
bool CCSBot::IsRogue() const
|
|
{
|
|
if (!TheCSBots()->AllowRogues())
|
|
return false;
|
|
|
|
// periodically re-evaluate our rogue status
|
|
if (m_rogueTimer.IsElapsed())
|
|
{
|
|
m_rogueTimer.Start(RANDOM_FLOAT(10, 30));
|
|
|
|
// our chance of going rogue is inversely proportional to our teamwork attribute
|
|
const float rogueChance = 100.0f * (1.0f - GetProfile()->GetTeamwork());
|
|
|
|
m_isRogue = (RANDOM_FLOAT(0, 100) < rogueChance);
|
|
}
|
|
|
|
return m_isRogue;
|
|
}
|
|
|
|
// Return true if we are in a hurry
|
|
bool CCSBot::IsHurrying() const
|
|
{
|
|
if (!m_hurryTimer.IsElapsed())
|
|
return true;
|
|
|
|
// if the bomb has been planted, we are in a hurry, CT or T (they could be defusing it!)
|
|
if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB && TheCSBots()->IsBombPlanted())
|
|
return true;
|
|
|
|
// if we are a T and hostages are being rescued, we are in a hurry
|
|
if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_RESCUE_HOSTAGES
|
|
&& m_iTeam == TERRORIST
|
|
&& GetGameState()->AreAllHostagesBeingRescued())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// Return true if it is the early, "safe", part of the round
|
|
bool CCSBot::IsSafe() const
|
|
{
|
|
if (TheCSBots()->GetElapsedRoundTime() < m_safeTime)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// Return true if it is well past the early, "safe", part of the round
|
|
bool CCSBot::IsWellPastSafe() const
|
|
{
|
|
if (TheCSBots()->GetElapsedRoundTime() > 1.25f * m_safeTime)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// Return true if we were in the safe time last update, but not now
|
|
bool CCSBot::IsEndOfSafeTime() const
|
|
{
|
|
return m_wasSafe && !IsSafe();
|
|
}
|
|
|
|
// Return the amount of "safe time" we have left
|
|
float CCSBot::GetSafeTimeRemaining() const
|
|
{
|
|
return m_safeTime - TheCSBots()->GetElapsedRoundTime();
|
|
}
|
|
|
|
// Called when enemy seen to adjust safe time for this round
|
|
void CCSBot::AdjustSafeTime()
|
|
{
|
|
// if we spotted an enemy sooner than we thought possible, adjust our notion of "safe" time
|
|
if (m_safeTime > TheCSBots()->GetElapsedRoundTime())
|
|
{
|
|
// since right now is not safe, adjust safe time to be a few seconds ago
|
|
m_safeTime = TheCSBots()->GetElapsedRoundTime() - 2.0f;
|
|
}
|
|
}
|
|
|
|
// Return true if we haven't seen an enemy for "a long time"
|
|
bool CCSBot::HasNotSeenEnemyForLongTime() const
|
|
{
|
|
const float longTime = 30.0f;
|
|
return (GetTimeSinceLastSawEnemy() > longTime);
|
|
}
|
|
|
|
// Pick a random zone and hide near it
|
|
bool CCSBot::GuardRandomZone(float range)
|
|
{
|
|
const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
|
|
if (zone)
|
|
{
|
|
CNavArea *rescueArea = TheCSBots()->GetRandomAreaInZone(zone);
|
|
if (rescueArea)
|
|
{
|
|
Hide(rescueArea, -1.0f, range);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Do a breadth-first search to find a good retreat spot.
|
|
// Don't pick a spot that a Player is currently occupying.
|
|
const Vector *FindNearbyRetreatSpot(CCSBot *me, float maxRange)
|
|
{
|
|
CNavArea *area = me->GetLastKnownArea();
|
|
if (!area)
|
|
return nullptr;
|
|
|
|
// collect spots that enemies cannot see
|
|
CollectRetreatSpotsFunctor collector(me, maxRange);
|
|
SearchSurroundingAreas(area, &me->pev->origin, collector, maxRange);
|
|
|
|
if (collector.m_count == 0)
|
|
return nullptr;
|
|
|
|
// select a hiding spot at random
|
|
int which = RANDOM_LONG(0, collector.m_count - 1);
|
|
return collector.m_spot[which];
|
|
}
|
|
|
|
// Return euclidean distance to farthest escorted hostage.
|
|
// Return -1 if no hostage is following us.
|
|
float CCSBot::GetRangeToFarthestEscortedHostage() const
|
|
{
|
|
FarthestHostage away(this);
|
|
|
|
g_pHostages->ForEachHostage(away);
|
|
|
|
return away.m_farRange;
|
|
}
|