/* * * 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(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(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; }