Portable Half-Life SDK. GoldSource and Xash3D. Crossplatform.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

464 lines
11 KiB

#include "bot/bot_common.h"
/*
* Globals initialization
*/
const char *GameEventName[NUM_GAME_EVENTS + 1] =
{
"EVENT_INVALID",
"EVENT_WEAPON_FIRED",
"EVENT_WEAPON_FIRED_ON_EMPTY",
"EVENT_WEAPON_RELOADED",
"EVENT_HE_GRENADE_EXPLODED",
"EVENT_FLASHBANG_GRENADE_EXPLODED",
"EVENT_SMOKE_GRENADE_EXPLODED",
"EVENT_GRENADE_BOUNCED",
"EVENT_BEING_SHOT_AT",
"EVENT_PLAYER_BLINDED_BY_FLASHBANG",
"EVENT_PLAYER_FOOTSTEP",
"EVENT_PLAYER_JUMPED",
"EVENT_PLAYER_DIED",
"EVENT_PLAYER_LANDED_FROM_HEIGHT",
"EVENT_PLAYER_TOOK_DAMAGE",
"EVENT_HOSTAGE_DAMAGED",
"EVENT_HOSTAGE_KILLED",
"EVENT_DOOR",
"EVENT_BREAK_GLASS",
"EVENT_BREAK_WOOD",
"EVENT_BREAK_METAL",
"EVENT_BREAK_FLESH",
"EVENT_BREAK_CONCRETE",
"EVENT_BOMB_PLANTED",
"EVENT_BOMB_DROPPED",
"EVENT_BOMB_PICKED_UP",
"EVENT_BOMB_BEEP",
"EVENT_BOMB_DEFUSING",
"EVENT_BOMB_DEFUSE_ABORTED",
"EVENT_BOMB_DEFUSED",
"EVENT_BOMB_EXPLODED",
"EVENT_HOSTAGE_USED",
"EVENT_HOSTAGE_RESCUED",
"EVENT_ALL_HOSTAGES_RESCUED",
"EVENT_VIP_ESCAPED",
"EVENT_VIP_ASSASSINATED",
"EVENT_TERRORISTS_WIN",
"EVENT_CTS_WIN",
"EVENT_ROUND_DRAW",
"EVENT_ROUND_WIN",
"EVENT_ROUND_LOSS",
"EVENT_ROUND_START",
"EVENT_PLAYER_SPAWNED",
"EVENT_CLIENT_CORPSE_SPAWNED",
"EVENT_BUY_TIME_START",
"EVENT_PLAYER_LEFT_BUY_ZONE",
"EVENT_DEATH_CAMERA_START",
"EVENT_KILL_ALL",
"EVENT_ROUND_TIME",
"EVENT_DIE",
"EVENT_KILL",
"EVENT_HEADSHOT",
"EVENT_KILL_FLASHBANGED",
"EVENT_TUTOR_BUY_MENU_OPENNED",
"EVENT_TUTOR_AUTOBUY",
"EVENT_PLAYER_BOUGHT_SOMETHING",
"EVENT_TUTOR_NOT_BUYING_ANYTHING",
"EVENT_TUTOR_NEED_TO_BUY_PRIMARY_WEAPON",
"EVENT_TUTOR_NEED_TO_BUY_PRIMARY_AMMO",
"EVENT_TUTOR_NEED_TO_BUY_SECONDARY_AMMO",
"EVENT_TUTOR_NEED_TO_BUY_ARMOR",
"EVENT_TUTOR_NEED_TO_BUY_DEFUSE_KIT",
"EVENT_TUTOR_NEED_TO_BUY_GRENADE",
"EVENT_CAREER_TASK_DONE",
"EVENT_START_RADIO_1",
"EVENT_RADIO_COVER_ME",
"EVENT_RADIO_YOU_TAKE_THE_POINT",
"EVENT_RADIO_HOLD_THIS_POSITION",
"EVENT_RADIO_REGROUP_TEAM",
"EVENT_RADIO_FOLLOW_ME",
"EVENT_RADIO_TAKING_FIRE",
"EVENT_START_RADIO_2",
"EVENT_RADIO_GO_GO_GO",
"EVENT_RADIO_TEAM_FALL_BACK",
"EVENT_RADIO_STICK_TOGETHER_TEAM",
"EVENT_RADIO_GET_IN_POSITION_AND_WAIT",
"EVENT_RADIO_STORM_THE_FRONT",
"EVENT_RADIO_REPORT_IN_TEAM",
"EVENT_START_RADIO_3",
"EVENT_RADIO_AFFIRMATIVE",
"EVENT_RADIO_ENEMY_SPOTTED",
"EVENT_RADIO_NEED_BACKUP",
"EVENT_RADIO_SECTOR_CLEAR",
"EVENT_RADIO_IN_POSITION",
"EVENT_RADIO_REPORTING_IN",
"EVENT_RADIO_GET_OUT_OF_THERE",
"EVENT_RADIO_NEGATIVE",
"EVENT_RADIO_ENEMY_DOWN",
"EVENT_END_RADIO",
"EVENT_NEW_MATCH",
"EVENT_PLAYER_CHANGED_TEAM",
"EVENT_BULLET_IMPACT",
"EVENT_GAME_COMMENCE",
"EVENT_WEAPON_ZOOMED",
"EVENT_HOSTAGE_CALLED_FOR_HELP",
NULL,
};
// STL uses exceptions, but we are not compiling with them - ignore warning
#pragma warning(disable : 4530)
const float smokeRadius = 115.0f; // for smoke grenades
// Convert name to GameEventType
// TODO: Find more appropriate place for this function
GameEventType NameToGameEvent(const char *name)
{
for (int i = 0; GameEventName[i] != NULL; ++i)
{
if (!Q_stricmp(GameEventName[i], name))
return static_cast<GameEventType>(i);
}
return EVENT_INVALID;
}
CBotManager::CBotManager()
{
InitBotTrig();
}
// Invoked when the round is restarting
void CBotManager::RestartRound()
{
DestroyAllGrenades();
}
// Invoked at the start of each frame
void CBotManager::StartFrame()
{
// debug smoke grenade visualization
if (cv_bot_debug.value == 5)
{
Vector edge, lastEdge;
int it = m_activeGrenadeList.Head ();
while (it != m_activeGrenadeList.InvalidIndex ())
{
ActiveGrenade *ag = m_activeGrenadeList[it];
int current = it;
it = m_activeGrenadeList.Next (it);
// lazy validation
if (!ag->IsValid ())
{
m_activeGrenadeList.Remove (current);
delete ag;
continue;
}
const Vector *pos = ag->GetDetonationPosition();
UTIL_DrawBeamPoints(*pos, *pos + Vector(0, 0, 50), 1, 255, 100, 0);
lastEdge = Vector(smokeRadius + pos->x, pos->y, pos->z);
float angle;
for (angle = 0.0f; angle <= 180.0f; angle += 22.5f)
{
edge.x = smokeRadius * BotCOS(angle) + pos->x;
edge.y = pos->y;
edge.z = smokeRadius * BotSIN(angle) + pos->z;
UTIL_DrawBeamPoints(edge, lastEdge, 1, 255, 50, 0);
lastEdge = edge;
}
lastEdge = Vector(pos->x, smokeRadius + pos->y, pos->z);
for (angle = 0.0f; angle <= 180.0f; angle += 22.5f)
{
edge.x = pos->x;
edge.y = smokeRadius * BotCOS(angle) + pos->y;
edge.z = smokeRadius * BotSIN(angle) + pos->z;
UTIL_DrawBeamPoints(edge, lastEdge, 1, 255, 50, 0);
lastEdge = edge;
}
}
}
// Process each active bot
for (int i = 1; i <= gpGlobals->maxClients; ++i)
{
CBasePlayer *pPlayer = static_cast<CBasePlayer *>(UTIL_PlayerByIndex(i));
if (!pPlayer)
continue;
if (pPlayer->IsBot() && IsEntityValid(pPlayer))
{
CBot *pBot = static_cast<CBot *>(pPlayer);
pBot->BotThink();
}
}
}
// Return the filename for this map's "nav map" file
const char *CBotManager::GetNavMapFilename() const
{
static char filename[256];
Q_sprintf(filename, "maps\\%s.nav", STRING(gpGlobals->mapname));
return filename;
}
// Invoked when given player does given event (some events have NULL player).
// Events are propogated to all bots.
// TODO: This has become the game-wide event dispatcher. We should restructure this.
void CBotManager::OnEvent(GameEventType event, CBaseEntity *entity, CBaseEntity *other)
{
// propogate event to all bots
for (int i = 1; i <= gpGlobals->maxClients; ++i)
{
CBasePlayer *player = static_cast<CBasePlayer *>(UTIL_PlayerByIndex(i));
if (player == NULL)
continue;
if (FNullEnt(player->pev))
continue;
if (FStrEq(STRING(player->pev->netname), ""))
continue;
if (!player->IsBot())
continue;
// do not send self-generated event
if (entity == player)
continue;
CBot *bot = static_cast<CBot *>(player);
bot->OnEvent(event, entity, other);
}
}
// Add an active grenade to the bot's awareness
void CBotManager::AddGrenade(int type, CGrenade *grenade)
{
m_activeGrenadeList.AddToTail(new ActiveGrenade (type, grenade));
}
// The grenade entity in the world is going away
void CBotManager::RemoveGrenade(CGrenade *grenade)
{
FOR_EACH_LL (m_activeGrenadeList, it)
{
ActiveGrenade *ag = m_activeGrenadeList[it];
if (ag->IsEntity (grenade))
{
ag->OnEntityGone ();
return;
}
}
}
// Destroy any invalid active grenades
NOXREF void CBotManager::ValidateActiveGrenades()
{
int it = m_activeGrenadeList.Head ();
while (it != m_activeGrenadeList.InvalidIndex ())
{
ActiveGrenade *ag = m_activeGrenadeList[it];
int current = it;
it = m_activeGrenadeList.Next (it);
// lazy validation
if (!ag->IsValid ())
{
m_activeGrenadeList.Remove (current);
delete ag;
continue;
}
}
}
void CBotManager::DestroyAllGrenades()
{
m_activeGrenadeList.PurgeAndDeleteElements ();
}
// Return true if position is inside a smoke cloud
bool CBotManager::IsInsideSmokeCloud(const Vector *pos)
{
#if 0
int it = m_activeGrenadeList.Head ();
while (it != m_activeGrenadeList.InvalidIndex ())
{
ActiveGrenade *ag = m_activeGrenadeList[it];
int current = it;
it = m_activeGrenadeList.Next (it);
// lazy validation
if (!ag->IsValid())
{
m_activeGrenadeList.Remove (current);
delete ag;
continue;
}
if (ag->GetID() == WEAPON_SMOKEGRENADE)
{
const Vector *smokeOrigin = ag->GetDetonationPosition();
if ((*smokeOrigin - *pos).IsLengthLessThan(smokeRadius))
return true;
}
}
#endif
return false;
}
// Return true if line intersects smoke volume
// Determine the length of the line of sight covered by each smoke cloud,
// and sum them (overlap is additive for obstruction).
// If the overlap exceeds the threshold, the bot can't see through.
bool CBotManager::IsLineBlockedBySmoke(const Vector *from, const Vector *to)
{
#if 0
const float smokeRadiusSq = smokeRadius * smokeRadius;
// distance along line of sight covered by smoke
float totalSmokedLength = 0.0f;
// compute unit vector and length of line of sight segment
Vector sightDir = *to - *from;
float sightLength = sightDir.NormalizeInPlace();
int it = m_activeGrenadeList.Head ();
while (it != m_activeGrenadeList.InvalidIndex ())
{
ActiveGrenade *ag = m_activeGrenadeList[it];
int current = it;
it = m_activeGrenadeList.Next (it);
// lazy validation
if (!ag->IsValid ())
{
m_activeGrenadeList.Remove (current);
delete ag;
continue;
}
if (ag->GetID() == WEAPON_SMOKEGRENADE)
{
const Vector *smokeOrigin = ag->GetDetonationPosition();
Vector toGrenade = *smokeOrigin - *from;
float alongDist = DotProduct(toGrenade, sightDir);
// compute closest point to grenade along line of sight ray
Vector close;
// constrain closest point to line segment
if (alongDist < 0.0f)
close = *from;
else if (alongDist >= sightLength)
close = *to;
else
close = *from + sightDir * alongDist;
// if closest point is within smoke radius, the line overlaps the smoke cloud
Vector toClose = close - *smokeOrigin;
float lengthSq = toClose.LengthSquared();
if (lengthSq < smokeRadiusSq)
{
// some portion of the ray intersects the cloud
float fromSq = toGrenade.LengthSquared();
float toSq = (*smokeOrigin - *to).LengthSquared();
if (fromSq < smokeRadiusSq)
{
if (toSq < smokeRadiusSq)
{
// both 'from' and 'to' lie within the cloud
// entire length is smoked
totalSmokedLength += (*to - *from).Length();
}
else
{
// 'from' is inside the cloud, 'to' is outside
// compute half of total smoked length as if ray crosses entire cloud chord
float halfSmokedLength = sqrt(smokeRadiusSq - lengthSq);
if (alongDist > 0.0f)
{
// ray goes thru 'close'
totalSmokedLength += halfSmokedLength + (close - *from).Length();
}
else
{
// ray starts after 'close'
totalSmokedLength += halfSmokedLength - (close - *from).Length();
}
}
}
else if (toSq < smokeRadiusSq)
{
// 'from' is outside the cloud, 'to' is inside
// compute half of total smoked length as if ray crosses entire cloud chord
float halfSmokedLength = sqrt(smokeRadiusSq - lengthSq);
Vector v = *to - *smokeOrigin;
if (DotProduct(v, sightDir) > 0.0f)
{
// ray goes thru 'close'
totalSmokedLength += halfSmokedLength + (close - *to).Length();
}
else
{
// ray ends before 'close'
totalSmokedLength += halfSmokedLength - (close - *to).Length();
}
}
else
{
// 'from' and 'to' lie outside of the cloud - the line of sight completely crosses it
// determine the length of the chord that crosses the cloud
float smokedLength = 2.0f * sqrt(smokeRadiusSq - lengthSq);
totalSmokedLength += smokedLength;
}
}
}
}
// define how much smoke a bot can see thru
const float maxSmokedLength = 0.7f * smokeRadius;
// return true if the total length of smoke-covered line-of-sight is too much
return (totalSmokedLength > maxSmokedLength);
#endif
return false;
}