mirror of
https://github.com/s1lentq/ReGameDLL_CS.git
synced 2024-12-28 07:35:40 +03:00
685 lines
15 KiB
C++
685 lines
15 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"
|
|
|
|
CSGameState::CSGameState(CCSBot *owner)
|
|
{
|
|
m_owner = owner;
|
|
m_isRoundOver = false;
|
|
m_bombState = MOVING;
|
|
|
|
m_lastSawBomber.Invalidate();
|
|
m_lastSawLooseBomb.Invalidate();
|
|
m_validateInterval.Invalidate();
|
|
m_isPlantedBombPosKnown = false;
|
|
m_plantedBombsite = UNKNOWN;
|
|
|
|
m_bombsiteCount = 0;
|
|
m_bombsiteSearchIndex = 0;
|
|
|
|
for (int i = 0; i < MAX_HOSTAGES; i++)
|
|
{
|
|
HostageInfo *info = &m_hostage[i];
|
|
|
|
info->hostage = nullptr;
|
|
info->knownPos = Vector(0, 0, 0);
|
|
info->isValid = false;
|
|
info->isAlive = false;
|
|
info->isFree = true;
|
|
}
|
|
}
|
|
|
|
// Reset at round start
|
|
void CSGameState::Reset()
|
|
{
|
|
int i;
|
|
m_isRoundOver = false;
|
|
|
|
// bomb
|
|
m_bombState = MOVING;
|
|
m_lastSawBomber.Invalidate();
|
|
m_lastSawLooseBomb.Invalidate();
|
|
m_bombsiteCount = TheCSBots()->GetZoneCount();
|
|
|
|
m_isPlantedBombPosKnown = false;
|
|
m_plantedBombsite = UNKNOWN;
|
|
|
|
for (i = 0; i < m_bombsiteCount; i++)
|
|
{
|
|
m_isBombsiteClear[i] = false;
|
|
m_bombsiteSearchOrder[i] = i;
|
|
}
|
|
|
|
// shuffle the bombsite search order
|
|
// allows T's to plant at random site, and TEAM_CT's to search in a random order
|
|
// NOTE: VS6 std::random_shuffle() doesn't work well with an array of two elements (most maps)
|
|
for (i = 0; i < m_bombsiteCount; i++)
|
|
{
|
|
int swap = m_bombsiteSearchOrder[i];
|
|
int rnd = RANDOM_LONG(i, m_bombsiteCount - 1);
|
|
|
|
m_bombsiteSearchOrder[i] = m_bombsiteSearchOrder[rnd];
|
|
m_bombsiteSearchOrder[rnd] = swap;
|
|
}
|
|
|
|
m_bombsiteSearchIndex = 0;
|
|
InitializeHostageInfo();
|
|
}
|
|
|
|
// Update game state based on events we have received
|
|
void CSGameState::OnEvent(GameEventType event, CBaseEntity *pEntity, CBaseEntity *pOther)
|
|
{
|
|
switch (event)
|
|
{
|
|
case EVENT_BOMB_PLANTED:
|
|
{
|
|
// change state - the event is announced to everyone
|
|
SetBombState(PLANTED);
|
|
|
|
// Terrorists always know where the bomb is
|
|
if (m_owner->m_iTeam == TERRORIST && pOther)
|
|
{
|
|
UpdatePlantedBomb(&pOther->pev->origin);
|
|
}
|
|
break;
|
|
}
|
|
case EVENT_BOMB_DEFUSED:
|
|
SetBombState(DEFUSED);
|
|
break;
|
|
case EVENT_BOMB_EXPLODED:
|
|
SetBombState(EXPLODED);
|
|
break;
|
|
case EVENT_ALL_HOSTAGES_RESCUED:
|
|
m_allHostagesRescued = true;
|
|
break;
|
|
case EVENT_TERRORISTS_WIN:
|
|
case EVENT_CTS_WIN:
|
|
case EVENT_ROUND_DRAW:
|
|
m_isRoundOver = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// True if round has been won or lost (but not yet reset)
|
|
bool CSGameState::IsRoundOver() const
|
|
{
|
|
return m_isRoundOver;
|
|
}
|
|
|
|
void CSGameState::SetBombState(BombState state)
|
|
{
|
|
// if state changed, reset "last seen" timestamps
|
|
if (m_bombState != state)
|
|
{
|
|
m_bombState = state;
|
|
}
|
|
}
|
|
|
|
void CSGameState::UpdateLooseBomb(const Vector *pos)
|
|
{
|
|
m_looseBombPos = *pos;
|
|
m_lastSawLooseBomb.Reset();
|
|
|
|
// we saw the loose bomb, update our state
|
|
SetBombState(LOOSE);
|
|
}
|
|
|
|
float CSGameState::TimeSinceLastSawLooseBomb() const
|
|
{
|
|
return m_lastSawLooseBomb.GetElapsedTime();
|
|
}
|
|
|
|
bool CSGameState::IsLooseBombLocationKnown() const
|
|
{
|
|
if (m_bombState != LOOSE)
|
|
return false;
|
|
|
|
return (m_lastSawLooseBomb.HasStarted()) ? true : false;
|
|
}
|
|
|
|
void CSGameState::UpdateBomber(const Vector *pos)
|
|
{
|
|
m_bomberPos = *pos;
|
|
m_lastSawBomber.Reset();
|
|
|
|
// we saw the bomber, update our state
|
|
SetBombState(MOVING);
|
|
}
|
|
|
|
float CSGameState::TimeSinceLastSawBomber() const
|
|
{
|
|
return m_lastSawBomber.GetElapsedTime();
|
|
}
|
|
|
|
bool CSGameState::IsPlantedBombLocationKnown() const
|
|
{
|
|
if (m_bombState != PLANTED)
|
|
return false;
|
|
|
|
return m_isPlantedBombPosKnown;
|
|
}
|
|
|
|
// Return the zone index of the planted bombsite, or UNKNOWN
|
|
int CSGameState::GetPlantedBombsite() const
|
|
{
|
|
if (m_bombState != PLANTED)
|
|
return UNKNOWN;
|
|
|
|
return m_plantedBombsite;
|
|
}
|
|
|
|
// Return true if we are currently in the bombsite where the bomb is planted
|
|
bool CSGameState::IsAtPlantedBombsite() const
|
|
{
|
|
if (m_bombState != PLANTED)
|
|
return false;
|
|
|
|
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone(&m_owner->pev->origin);
|
|
|
|
if (zone)
|
|
{
|
|
return (m_plantedBombsite == zone->m_index);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Return the zone index of the next bombsite to search
|
|
int CSGameState::GetNextBombsiteToSearch()
|
|
{
|
|
if (m_bombsiteCount <= 0)
|
|
return 0;
|
|
|
|
int i;
|
|
// return next non-cleared bombsite index
|
|
for (i = m_bombsiteSearchIndex; i < m_bombsiteCount; i++)
|
|
{
|
|
int z = m_bombsiteSearchOrder[i];
|
|
if (!m_isBombsiteClear[z])
|
|
{
|
|
m_bombsiteSearchIndex = i;
|
|
return z;
|
|
}
|
|
}
|
|
|
|
// all the bombsites are clear, someone must have been mistaken - start search over
|
|
for (i = 0; i < m_bombsiteCount; i++)
|
|
{
|
|
m_isBombsiteClear[i] = false;
|
|
}
|
|
|
|
m_bombsiteSearchIndex = 0;
|
|
return GetNextBombsiteToSearch();
|
|
}
|
|
|
|
// Returns position of bomb in its various states (moving, loose, planted),
|
|
// or NULL if we don't know where the bomb is
|
|
const Vector *CSGameState::GetBombPosition() const
|
|
{
|
|
switch (m_bombState)
|
|
{
|
|
case MOVING:
|
|
{
|
|
if (!m_lastSawBomber.HasStarted())
|
|
return nullptr;
|
|
|
|
return &m_bomberPos;
|
|
}
|
|
case LOOSE:
|
|
{
|
|
if (IsLooseBombLocationKnown())
|
|
return &m_looseBombPos;
|
|
|
|
return nullptr;
|
|
}
|
|
case PLANTED:
|
|
{
|
|
if (IsPlantedBombLocationKnown())
|
|
return &m_plantedBombPos;
|
|
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// We see the planted bomb at 'pos'
|
|
void CSGameState::UpdatePlantedBomb(const Vector *pos)
|
|
{
|
|
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone(pos);
|
|
|
|
if (!zone)
|
|
{
|
|
CONSOLE_ECHO("ERROR: Bomb planted outside of a zone!\n");
|
|
m_plantedBombsite = UNKNOWN;
|
|
}
|
|
else
|
|
{
|
|
m_plantedBombsite = zone->m_index;
|
|
}
|
|
|
|
m_plantedBombPos = *pos;
|
|
m_isPlantedBombPosKnown = true;
|
|
SetBombState(PLANTED);
|
|
}
|
|
|
|
// Someone told us where the bomb is planted
|
|
void CSGameState::MarkBombsiteAsPlanted(int zoneIndex)
|
|
{
|
|
m_plantedBombsite = zoneIndex;
|
|
SetBombState(PLANTED);
|
|
}
|
|
|
|
// Someone told us a bombsite is clear
|
|
void CSGameState::ClearBombsite(int zoneIndex)
|
|
{
|
|
if (zoneIndex >= 0 && zoneIndex < m_bombsiteCount)
|
|
m_isBombsiteClear[zoneIndex] = true;
|
|
}
|
|
|
|
bool CSGameState::IsBombsiteClear(int zoneIndex) const
|
|
{
|
|
if (zoneIndex >= 0 && zoneIndex < m_bombsiteCount)
|
|
return m_isBombsiteClear[zoneIndex];
|
|
|
|
return false;
|
|
}
|
|
|
|
void CSGameState::InitializeHostageInfo()
|
|
{
|
|
m_hostageCount = 0;
|
|
m_allHostagesRescued = false;
|
|
m_haveSomeHostagesBeenTaken = false;
|
|
|
|
CHostage *pHostage = nullptr;
|
|
while ((pHostage = UTIL_FindEntityByClassname(pHostage, "hostage_entity")))
|
|
{
|
|
if (m_hostageCount >= MAX_HOSTAGES)
|
|
break;
|
|
|
|
if (!pHostage->IsAlive())
|
|
continue;
|
|
|
|
m_hostage[m_hostageCount].hostage = pHostage;
|
|
m_hostage[m_hostageCount].knownPos = pHostage->pev->origin;
|
|
m_hostage[m_hostageCount].isValid = true;
|
|
m_hostage[m_hostageCount].isAlive = true;
|
|
m_hostage[m_hostageCount].isFree = true;
|
|
m_hostageCount++;
|
|
}
|
|
}
|
|
|
|
// Return the closest free and live hostage
|
|
// If we are a CT this information is perfect.
|
|
// Otherwise, this is based on our individual memory of the game state.
|
|
// If NULL is returned, we don't think there are any hostages left, or we dont know where they are.
|
|
// NOTE: a T can remember a hostage who has died. knowPos will be filled in, but NULL will be
|
|
// returned, since CHostages get deleted when they die.
|
|
CHostage *CSGameState::GetNearestFreeHostage(Vector *knowPos) const
|
|
{
|
|
if (!m_owner)
|
|
return nullptr;
|
|
|
|
CNavArea *startArea = m_owner->GetLastKnownArea();
|
|
if (!startArea)
|
|
return nullptr;
|
|
|
|
CHostage *close = nullptr;
|
|
const Vector *closePos = nullptr;
|
|
float closeDistance = 9999999999.9f;
|
|
|
|
for (int i = 0; i < m_hostageCount; i++)
|
|
{
|
|
CHostage *pHostage = m_hostage[i].hostage;
|
|
const Vector *hostagePos = nullptr;
|
|
|
|
if (m_owner->m_iTeam == CT)
|
|
{
|
|
// we know exactly where the hostages are, and if they are alive
|
|
if (!m_hostage[i].hostage || !m_hostage[i].hostage->IsAlive())
|
|
continue;
|
|
|
|
if (m_hostage[i].hostage->IsFollowingSomeone())
|
|
continue;
|
|
|
|
hostagePos = &pHostage->pev->origin;
|
|
}
|
|
else
|
|
{
|
|
// use our memory of where we think the hostages are
|
|
if (m_hostage[i].isValid == false)
|
|
continue;
|
|
|
|
hostagePos = &m_hostage[i].knownPos;
|
|
}
|
|
|
|
CNavArea *hostageArea = TheNavAreaGrid.GetNearestNavArea(hostagePos);
|
|
if (hostageArea)
|
|
{
|
|
ShortestPathCost pc;
|
|
float travelDistance = NavAreaTravelDistance(startArea, hostageArea, pc);
|
|
|
|
if (travelDistance >= 0.0f && travelDistance < closeDistance)
|
|
{
|
|
closePos = hostagePos;
|
|
closeDistance = travelDistance;
|
|
close = pHostage;
|
|
}
|
|
}
|
|
}
|
|
|
|
// return where we think the hostage is
|
|
if (knowPos && closePos)
|
|
{
|
|
knowPos = const_cast<Vector *>(closePos);
|
|
}
|
|
|
|
return close;
|
|
}
|
|
|
|
// Return the location of a "free" hostage, or NULL if we dont know of any
|
|
const Vector *CSGameState::GetRandomFreeHostagePosition()
|
|
{
|
|
// TODO: use static?
|
|
const Vector *freePos[MAX_HOSTAGES];
|
|
int freeCount = 0;
|
|
|
|
if (!m_owner)
|
|
return nullptr;
|
|
|
|
for (int i = 0; i < m_hostageCount; i++)
|
|
{
|
|
const HostageInfo *info = &m_hostage[i];
|
|
const Vector *hostagePos = nullptr;
|
|
|
|
if (m_owner->m_iTeam == CT)
|
|
{
|
|
// we know exactly where the hostages are, and if they are alive
|
|
if (!info->hostage || !info->hostage->IsAlive())
|
|
continue;
|
|
|
|
// escorted hostages are not "free"
|
|
if (info->hostage->IsFollowingSomeone())
|
|
continue;
|
|
|
|
freePos[freeCount++] = &info->hostage->pev->origin;
|
|
}
|
|
else
|
|
{
|
|
// use our memory of where we think the hostages are
|
|
if (info->isValid == false)
|
|
continue;
|
|
|
|
freePos[freeCount++] = &info->knownPos;
|
|
}
|
|
}
|
|
|
|
if (freeCount)
|
|
{
|
|
return freePos[RANDOM_LONG(0, freeCount - 1)];
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// If we can see any of the positions where we think a hostage is, validate it
|
|
// Return status of any changes (a hostage died or was moved)
|
|
CSGameState::ValidateStatusType CSGameState::ValidateHostagePositions()
|
|
{
|
|
// limit how often we validate
|
|
if (!m_validateInterval.IsElapsed())
|
|
return NO_CHANGE;
|
|
|
|
const float validateInterval = 0.5f;
|
|
m_validateInterval.Start(validateInterval);
|
|
|
|
// check the status of hostages
|
|
unsigned char status = NO_CHANGE;
|
|
|
|
int i;
|
|
int startValidCount = 0;
|
|
for (i = 0; i < m_hostageCount; i++)
|
|
{
|
|
if (m_hostage[i].isValid)
|
|
startValidCount++;
|
|
}
|
|
|
|
for (i = 0; i < m_hostageCount; i++)
|
|
{
|
|
HostageInfo *info = &m_hostage[i];
|
|
|
|
if (!info->hostage)
|
|
continue;
|
|
|
|
// if we can see a hostage, update our knowledge of it
|
|
if (m_owner->IsVisible(&info->hostage->pev->origin, CHECK_FOV))
|
|
{
|
|
if (info->hostage->IsAlive())
|
|
{
|
|
// live hostage
|
|
// if hostage is being escorted by a CT, we don't "see" it, we see the CT
|
|
if (info->hostage->IsFollowingSomeone())
|
|
{
|
|
info->isValid = false;
|
|
}
|
|
else
|
|
{
|
|
info->knownPos = info->hostage->pev->origin;
|
|
info->isValid = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// dead hostage
|
|
// if we thought it was alive, this is news to us
|
|
if (info->isAlive)
|
|
status |= HOSTAGE_DIED;
|
|
|
|
info->isAlive = false;
|
|
info->isValid = false;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
// if we dont know where this hostage is, nothing to validate
|
|
if (!info->isValid)
|
|
continue;
|
|
|
|
// can't directly see this hostage
|
|
// check line of sight to where we think this hostage is, to see if we noticed that is has moved
|
|
if (m_owner->IsVisible(&info->knownPos, CHECK_FOV))
|
|
{
|
|
// we can see where we thought the hostage was - verify it is still there and alive
|
|
if (!info->hostage->IsAlive())
|
|
{
|
|
// since we have line of sight to an invalid hostage, it must be dead
|
|
// discovered that hostage has been killed
|
|
status |= HOSTAGE_DIED;
|
|
|
|
info->isAlive = false;
|
|
info->isValid = false;
|
|
continue;
|
|
}
|
|
|
|
if (info->hostage->IsFollowingSomeone())
|
|
{
|
|
// discovered the hostage has been taken
|
|
status |= HOSTAGE_GONE;
|
|
info->isValid = false;
|
|
continue;
|
|
}
|
|
|
|
const float tolerance = 50.0f;
|
|
if ((info->hostage->pev->origin - info->knownPos).IsLengthGreaterThan(tolerance))
|
|
{
|
|
// discovered that hostage has been moved
|
|
status |= HOSTAGE_GONE;
|
|
info->isValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
int endValidCount = 0;
|
|
for (i = 0; i < m_hostageCount; i++)
|
|
{
|
|
if (m_hostage[i].isValid)
|
|
endValidCount++;
|
|
}
|
|
|
|
if (endValidCount == 0 && startValidCount > 0)
|
|
{
|
|
// we discovered all the hostages are gone
|
|
status &= ~HOSTAGE_GONE;
|
|
status |= HOSTAGES_ALL_GONE;
|
|
}
|
|
|
|
return static_cast<ValidateStatusType>(status);
|
|
}
|
|
|
|
// Return the nearest visible free hostage
|
|
// Since we can actually see any hostage we return, we know its actual position
|
|
CHostage *CSGameState::GetNearestVisibleFreeHostage() const
|
|
{
|
|
CHostage *close = nullptr;
|
|
float closeRangeSq = 999999999.9f;
|
|
float rangeSq;
|
|
|
|
Vector pos;
|
|
|
|
for (int i = 0; i < m_hostageCount; i++)
|
|
{
|
|
const HostageInfo *info = &m_hostage[i];
|
|
|
|
if (!info->hostage)
|
|
continue;
|
|
|
|
// if the hostage is dead or rescued, its not free
|
|
if (!info->hostage->IsAlive())
|
|
continue;
|
|
|
|
// if this hostage is following someone, its not free
|
|
if (info->hostage->IsFollowingSomeone())
|
|
continue;
|
|
|
|
// TODO: Use travel distance here
|
|
pos = info->hostage->pev->origin + Vector(0, 0, HumanHeight * 0.75f);
|
|
rangeSq = (pos - m_owner->pev->origin).LengthSquared();
|
|
|
|
if (rangeSq < closeRangeSq)
|
|
{
|
|
if (!m_owner->IsVisible(&pos))
|
|
continue;
|
|
|
|
close = info->hostage;
|
|
closeRangeSq = rangeSq;
|
|
}
|
|
}
|
|
|
|
return close;
|
|
}
|
|
|
|
// Return true if there are no free hostages
|
|
bool CSGameState::AreAllHostagesBeingRescued() const
|
|
{
|
|
// if the hostages have all been rescued, they are not being rescued any longer
|
|
if (m_allHostagesRescued)
|
|
return false;
|
|
|
|
bool isAllDead = true;
|
|
for (int i = 0; i < m_hostageCount; i++)
|
|
{
|
|
const HostageInfo *info = &m_hostage[i];
|
|
|
|
if (m_owner->m_iTeam == CT)
|
|
{
|
|
// CT's have perfect knowledge via their radar
|
|
if (info->hostage && info->hostage->IsAlive())
|
|
{
|
|
if (!info->hostage->IsFollowingSomeone())
|
|
return false;
|
|
|
|
isAllDead = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (info->isValid && info->isAlive)
|
|
return false;
|
|
|
|
if (info->isAlive)
|
|
isAllDead = false;
|
|
}
|
|
}
|
|
|
|
// if all of the remaining hostages are dead, they arent being rescued
|
|
if (isAllDead)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// All hostages have been rescued or are dead
|
|
bool CSGameState::AreAllHostagesGone() const
|
|
{
|
|
if (m_allHostagesRescued)
|
|
return true;
|
|
|
|
// do we know that all the hostages are dead
|
|
for (int i = 0; i < m_hostageCount; i++)
|
|
{
|
|
const HostageInfo *info = &m_hostage[i];
|
|
|
|
if (m_owner->m_iTeam == CT)
|
|
{
|
|
// CT's have perfect knowledge via their radar
|
|
if (info->hostage->IsAlive())
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (info->isValid && info->isAlive)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Someone told us all the hostages are gone
|
|
void CSGameState::AllHostagesGone()
|
|
{
|
|
for (int i = 0; i < m_hostageCount; i++)
|
|
m_hostage[i].isValid = false;
|
|
}
|