mirror of
https://github.com/s1lentq/ReGameDLL_CS.git
synced 2024-12-28 07:35:40 +03:00
19714af6e6
minor refactor
472 lines
11 KiB
C++
472 lines
11 KiB
C++
#include "precompiled.h"
|
|
|
|
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",
|
|
nullptr,
|
|
};
|
|
|
|
// 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)
|
|
{
|
|
int index = 0;
|
|
for (auto event : GameEventName)
|
|
{
|
|
if (!Q_stricmp(event, name)) {
|
|
return static_cast<GameEventType>(index);
|
|
}
|
|
|
|
index++;
|
|
}
|
|
|
|
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;
|
|
|
|
auto iter = m_activeGrenadeList.begin();
|
|
while (iter != m_activeGrenadeList.end())
|
|
{
|
|
ActiveGrenade *ag = (*iter);
|
|
|
|
// lazy validation
|
|
ag->CheckOnEntityGone();
|
|
|
|
if (!ag->IsValid())
|
|
{
|
|
delete ag;
|
|
iter = m_activeGrenadeList.erase(iter);
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
iter++;
|
|
}
|
|
|
|
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 = UTIL_PlayerByIndex(i);
|
|
|
|
if (!UTIL_IsValidPlayer(pPlayer))
|
|
continue;
|
|
|
|
if (pPlayer->IsBot() && IsEntityValid(pPlayer))
|
|
{
|
|
CBot *pBot = static_cast<CBot *>(pPlayer);
|
|
pBot->BotThink();
|
|
}
|
|
}
|
|
|
|
ValidateActiveGrenades();
|
|
}
|
|
|
|
// Return the filename for this map's "nav map" file
|
|
const char *CBotManager::GetNavMapFilename() const
|
|
{
|
|
static char filename[256];
|
|
Q_snprintf(filename, sizeof(filename), "maps\\%s.nav", STRING(gpGlobals->mapname));
|
|
return filename;
|
|
}
|
|
|
|
LINK_HOOK_CLASS_VOID_CHAIN(CBotManager, OnEvent, (GameEventType event, CBaseEntity* pEntity, CBaseEntity* pOther), event, pEntity, pOther)
|
|
|
|
// 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::__API_HOOK(OnEvent)(GameEventType event, CBaseEntity* pEntity, CBaseEntity* pOther)
|
|
{
|
|
// propogate event to all bots
|
|
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;
|
|
|
|
// do not send self-generated event
|
|
if (pEntity == pPlayer)
|
|
continue;
|
|
|
|
CBot *bot = static_cast<CBot *>(pPlayer);
|
|
bot->OnEvent(event, pEntity, pOther);
|
|
}
|
|
|
|
if (TheTutor)
|
|
{
|
|
TheTutor->OnEvent(event, pEntity, pOther);
|
|
}
|
|
|
|
if (g_pHostages)
|
|
{
|
|
g_pHostages->OnEvent(event, pEntity, pOther);
|
|
}
|
|
}
|
|
|
|
// Add an active grenade to the bot's awareness
|
|
void CBotManager::AddGrenade(int type, CGrenade *grenade)
|
|
{
|
|
ActiveGrenade *ag = new ActiveGrenade(type, grenade);
|
|
|
|
m_activeGrenadeList.push_back(ag);
|
|
}
|
|
|
|
// The grenade entity in the world is going away
|
|
void CBotManager::RemoveGrenade(CGrenade *grenade)
|
|
{
|
|
for (auto ag : m_activeGrenadeList)
|
|
{
|
|
if (ag->IsEntity(grenade))
|
|
{
|
|
ag->OnEntityGone();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Destroy any invalid active grenades
|
|
void CBotManager::ValidateActiveGrenades()
|
|
{
|
|
auto iter = m_activeGrenadeList.begin();
|
|
while (iter != m_activeGrenadeList.end())
|
|
{
|
|
ActiveGrenade *ag = (*iter);
|
|
|
|
// lazy validation
|
|
ag->CheckOnEntityGone();
|
|
|
|
if (!ag->IsValid())
|
|
{
|
|
delete ag;
|
|
iter = m_activeGrenadeList.erase(iter);
|
|
}
|
|
else
|
|
{
|
|
iter++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CBotManager::DestroyAllGrenades()
|
|
{
|
|
for (auto grenade : m_activeGrenadeList)
|
|
delete grenade;
|
|
|
|
m_activeGrenadeList.clear();
|
|
}
|
|
|
|
// Return true if position is inside a smoke cloud
|
|
bool CBotManager::IsInsideSmokeCloud(const Vector *pos)
|
|
{
|
|
auto iter = m_activeGrenadeList.begin();
|
|
while (iter != m_activeGrenadeList.end())
|
|
{
|
|
ActiveGrenade *ag = (*iter);
|
|
|
|
// lazy validation
|
|
ag->CheckOnEntityGone();
|
|
|
|
if (!ag->IsValid())
|
|
{
|
|
delete ag;
|
|
iter = m_activeGrenadeList.erase(iter);
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
iter++;
|
|
}
|
|
|
|
if (ag->GetID() == WEAPON_SMOKEGRENADE)
|
|
{
|
|
const Vector *smokeOrigin = ag->GetDetonationPosition();
|
|
|
|
if ((*smokeOrigin - *pos).IsLengthLessThan(smokeRadius))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
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();
|
|
|
|
auto iter = m_activeGrenadeList.begin();
|
|
while (iter != m_activeGrenadeList.end())
|
|
{
|
|
ActiveGrenade *ag = (*iter);
|
|
|
|
// lazy validation
|
|
ag->CheckOnEntityGone();
|
|
|
|
if (!ag->IsValid())
|
|
{
|
|
delete ag;
|
|
iter = m_activeGrenadeList.erase(iter);
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
iter++;
|
|
}
|
|
|
|
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 = Q_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 = Q_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 * Q_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);
|
|
}
|