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
464 lines
11 KiB
8 years ago
|
#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;
|
||
|
}
|