2019-09-23 00:09:58 +03:00
|
|
|
/*
|
|
|
|
*
|
|
|
|
* 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"
|
|
|
|
|
|
|
|
// range for snipers to select a hiding spot
|
|
|
|
const float sniperHideRange = 2000.0f;
|
|
|
|
|
|
|
|
// The Idle state.
|
|
|
|
// We never stay in the Idle state - it is a "home base" for the state machine that
|
|
|
|
// does various checks to determine what we should do next.
|
|
|
|
void IdleState::OnEnter(CCSBot *me)
|
|
|
|
{
|
|
|
|
me->DestroyPath();
|
|
|
|
me->SetEnemy(nullptr);
|
|
|
|
|
|
|
|
// lurking death
|
|
|
|
if (me->IsUsingKnife() && me->IsWellPastSafe() && !me->IsHurrying())
|
|
|
|
me->Walk();
|
|
|
|
|
|
|
|
// Since Idle assigns tasks, we assume that coming back to Idle means our task is complete
|
|
|
|
me->SetTask(CCSBot::SEEK_AND_DESTROY);
|
|
|
|
me->SetDisposition(CCSBot::ENGAGE_AND_INVESTIGATE);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine what we should do next
|
|
|
|
void IdleState::OnUpdate(CCSBot *me)
|
|
|
|
{
|
|
|
|
// all other states assume GetLastKnownArea() is valid, ensure that it is
|
|
|
|
if (!me->GetLastKnownArea() && me->StayOnNavMesh() == false)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// zombies never leave the Idle state
|
|
|
|
if (cv_bot_zombie.value > 0.0f)
|
|
|
|
{
|
|
|
|
me->ResetStuckMonitor();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if we are in the early "safe" time, grab a knife or grenade
|
|
|
|
if (me->IsSafe())
|
|
|
|
{
|
|
|
|
// if we have a grenade, use it
|
|
|
|
if (!me->EquipGrenade())
|
|
|
|
{
|
|
|
|
// high-skill bots run with the knife
|
|
|
|
if (me->GetProfile()->GetSkill() > 0.33f)
|
|
|
|
{
|
|
|
|
me->EquipKnife();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if round is over, hunt
|
|
|
|
if (me->GetGameState()->IsRoundOver())
|
|
|
|
{
|
|
|
|
// if we are escorting hostages, try to get to the rescue zone
|
|
|
|
if (me->GetHostageEscortCount())
|
|
|
|
{
|
|
|
|
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone(me->GetLastKnownArea(), PathCost(me, FASTEST_ROUTE));
|
|
|
|
const Vector *zonePos = TheCSBots()->GetRandomPositionInZone(zone);
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
|
|
if (zonePos)
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
me->SetTask(CCSBot::RESCUE_HOSTAGES);
|
|
|
|
me->Run();
|
|
|
|
me->SetDisposition(CCSBot::SELF_DEFENSE);
|
|
|
|
me->MoveTo(zonePos, FASTEST_ROUTE);
|
|
|
|
me->PrintIfWatched("Trying to rescue hostages at the end of the round\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
me->Hunt();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const float defenseSniperCampChance = 75.0f;
|
|
|
|
const float offenseSniperCampChance = 10.0f;
|
|
|
|
|
|
|
|
// if we were following someone, continue following them
|
|
|
|
if (me->IsFollowing())
|
|
|
|
{
|
|
|
|
me->ContinueFollowing();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Scenario logic
|
|
|
|
switch (TheCSBots()->GetScenario())
|
|
|
|
{
|
|
|
|
case CCSBotManager::SCENARIO_DEFUSE_BOMB:
|
|
|
|
{
|
|
|
|
// if this is a bomb game and we have the bomb, go plant it
|
|
|
|
if (me->m_iTeam == TERRORIST)
|
|
|
|
{
|
|
|
|
if (me->GetGameState()->IsBombPlanted())
|
|
|
|
{
|
|
|
|
if (me->GetGameState()->GetPlantedBombsite() != CSGameState::UNKNOWN)
|
|
|
|
{
|
|
|
|
// T's always know where the bomb is - go defend it
|
|
|
|
const CCSBotManager::Zone *zone = TheCSBots()->GetZone(me->GetGameState()->GetPlantedBombsite());
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
|
|
if (zone)
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
me->SetTask(CCSBot::GUARD_TICKING_BOMB);
|
|
|
|
|
|
|
|
Place place = TheNavAreaGrid.GetPlace(&zone->m_center);
|
|
|
|
if (place != UNDEFINED_PLACE)
|
|
|
|
{
|
|
|
|
// pick a random hiding spot in this place
|
|
|
|
const Vector *spot = FindRandomHidingSpot(me, place, me->IsSniper());
|
|
|
|
if (spot)
|
|
|
|
{
|
|
|
|
me->Hide(spot);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// hide nearby
|
|
|
|
me->Hide(TheNavAreaGrid.GetNearestNavArea(&zone->m_center));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// ask our teammates where the bomb is
|
|
|
|
me->GetChatter()->RequestBombLocation();
|
|
|
|
|
|
|
|
// we dont know where the bomb is - we must search the bombsites
|
|
|
|
int zoneIndex = me->GetGameState()->GetNextBombsiteToSearch();
|
|
|
|
|
|
|
|
// move to bombsite - if we reach it, we'll update its cleared status, causing us to select another
|
|
|
|
const Vector *pos = TheCSBots()->GetRandomPositionInZone(TheCSBots()->GetZone(zoneIndex));
|
|
|
|
if (pos)
|
|
|
|
{
|
|
|
|
me->SetTask(CCSBot::FIND_TICKING_BOMB);
|
|
|
|
me->MoveTo(pos);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (me->IsCarryingBomb())
|
|
|
|
{
|
|
|
|
// if we're at a bomb site, plant the bomb
|
|
|
|
if (me->IsAtBombsite())
|
|
|
|
{
|
|
|
|
// plant it
|
|
|
|
me->SetTask(CCSBot::PLANT_BOMB);
|
|
|
|
me->PlantBomb();
|
|
|
|
|
|
|
|
// radio to the team
|
|
|
|
me->GetChatter()->PlantingTheBomb(me->GetPlace());
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if (TheCSBots()->IsTimeToPlantBomb())
|
|
|
|
{
|
|
|
|
// move to the closest bomb site
|
|
|
|
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone(me->GetLastKnownArea(), PathCost(me));
|
|
|
|
if (zone)
|
|
|
|
{
|
|
|
|
// pick a random spot within the bomb zone
|
|
|
|
const Vector *pos = TheCSBots()->GetRandomPositionInZone(zone);
|
|
|
|
if (pos)
|
|
|
|
{
|
|
|
|
// move to bombsite
|
|
|
|
me->SetTask(CCSBot::PLANT_BOMB);
|
|
|
|
me->Run();
|
|
|
|
me->MoveTo(pos);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// small chance of sniper camping on offense, if we aren't carrying the bomb
|
|
|
|
if (me->GetFriendsRemaining() && me->IsSniper() && RANDOM_FLOAT(0, 100.0f) < offenseSniperCampChance)
|
|
|
|
{
|
|
|
|
me->SetTask(CCSBot::MOVE_TO_SNIPER_SPOT);
|
|
|
|
me->Hide(me->GetLastKnownArea(), RANDOM_FLOAT(10.0f, 30.0f), sniperHideRange);
|
|
|
|
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
|
|
|
|
me->PrintIfWatched("Sniping!\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if the bomb is loose (on the ground), go get it
|
|
|
|
if (me->NoticeLooseBomb())
|
|
|
|
{
|
|
|
|
me->FetchBomb();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if bomb has been planted, and we hear it, move to a hiding spot near the bomb and guard it
|
|
|
|
if (!me->IsRogue() && me->GetGameState()->IsBombPlanted() && me->GetGameState()->GetBombPosition())
|
|
|
|
{
|
|
|
|
const Vector *bombPos = me->GetGameState()->GetBombPosition();
|
|
|
|
|
|
|
|
if (bombPos)
|
|
|
|
{
|
|
|
|
me->SetTask(CCSBot::GUARD_TICKING_BOMB);
|
|
|
|
me->Hide(TheNavAreaGrid.GetNavArea(bombPos));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// CT
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (me->GetGameState()->IsBombPlanted())
|
|
|
|
{
|
|
|
|
// if the bomb has been planted, attempt to defuse it
|
|
|
|
const Vector *bombPos = me->GetGameState()->GetBombPosition();
|
|
|
|
if (bombPos)
|
|
|
|
{
|
|
|
|
// if someone is defusing the bomb, guard them
|
|
|
|
if (TheCSBots()->GetBombDefuser())
|
|
|
|
{
|
|
|
|
if (!me->IsRogue())
|
|
|
|
{
|
|
|
|
me->SetTask(CCSBot::GUARD_BOMB_DEFUSER);
|
|
|
|
me->Hide(TheNavAreaGrid.GetNavArea(bombPos));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (me->IsDoingScenario())
|
|
|
|
{
|
|
|
|
// move to the bomb and defuse it
|
|
|
|
me->SetTask(CCSBot::DEFUSE_BOMB);
|
|
|
|
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
|
|
|
|
me->MoveTo(bombPos);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// we're not allowed to defuse, guard the bomb zone
|
|
|
|
me->SetTask(CCSBot::GUARD_BOMB_ZONE);
|
|
|
|
me->Hide(TheNavAreaGrid.GetNavArea(bombPos));
|
|
|
|
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (me->GetGameState()->GetPlantedBombsite() != CSGameState::UNKNOWN)
|
|
|
|
{
|
|
|
|
// we know which bombsite, but not exactly where the bomb is, go there
|
|
|
|
const CCSBotManager::Zone *zone = TheCSBots()->GetZone(me->GetGameState()->GetPlantedBombsite());
|
|
|
|
if (zone)
|
|
|
|
{
|
|
|
|
if (me->IsDoingScenario())
|
|
|
|
{
|
|
|
|
me->SetTask(CCSBot::DEFUSE_BOMB);
|
|
|
|
me->MoveTo(&zone->m_center);
|
|
|
|
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// we're not allowed to defuse, guard the bomb zone
|
|
|
|
me->SetTask(CCSBot::GUARD_BOMB_ZONE);
|
|
|
|
me->Hide(TheNavAreaGrid.GetNavArea(&zone->m_center));
|
|
|
|
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// we dont know where the bomb is - we must search the bombsites
|
|
|
|
// find closest un-cleared bombsite
|
|
|
|
const CCSBotManager::Zone *zone = nullptr;
|
|
|
|
float travelDistance = 9999999.9f;
|
|
|
|
|
|
|
|
for (int z = 0; z < TheCSBots()->GetZoneCount(); z++)
|
|
|
|
{
|
|
|
|
if (TheCSBots()->GetZone(z)->m_areaCount == 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// don't check bombsites that have been cleared
|
|
|
|
if (me->GetGameState()->IsBombsiteClear(z))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// just use the first overlapping nav area as a reasonable approximation
|
|
|
|
ShortestPathCost pathCost = ShortestPathCost();
|
|
|
|
real_t dist = NavAreaTravelDistance(me->GetLastKnownArea(), TheNavAreaGrid.GetNearestNavArea(&TheCSBots()->GetZone(z)->m_center), pathCost);
|
|
|
|
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
|
|
if (dist < 0.0f)
|
|
|
|
continue;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (dist < travelDistance)
|
|
|
|
{
|
|
|
|
zone = TheCSBots()->GetZone(z);
|
|
|
|
travelDistance = dist;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (zone)
|
|
|
|
{
|
|
|
|
const float farAwayRange = 2000.0f;
|
|
|
|
if (travelDistance > farAwayRange)
|
|
|
|
{
|
|
|
|
zone = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if closest bombsite is "far away", pick one at random
|
|
|
|
if (!zone)
|
|
|
|
{
|
|
|
|
int zoneIndex = me->GetGameState()->GetNextBombsiteToSearch();
|
|
|
|
zone = TheCSBots()->GetZone(zoneIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
// move to bombsite - if we reach it, we'll update its cleared status, causing us to select another
|
|
|
|
if (zone)
|
|
|
|
{
|
|
|
|
const Vector *pos = TheCSBots()->GetRandomPositionInZone(zone);
|
|
|
|
if (pos)
|
|
|
|
{
|
|
|
|
me->SetTask(CCSBot::FIND_TICKING_BOMB);
|
|
|
|
me->MoveTo(pos);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-13 23:16:27 +03:00
|
|
|
DbgAssert(!"A CT bot doesn't know what to do while the bomb is planted!\n");
|
2019-09-23 00:09:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// if we have a sniper rifle, we like to camp, whether rogue or not
|
|
|
|
if (me->IsSniper())
|
|
|
|
{
|
|
|
|
if (RANDOM_FLOAT(0, 100) <= defenseSniperCampChance)
|
|
|
|
{
|
|
|
|
CNavArea *snipingArea = nullptr;
|
|
|
|
|
|
|
|
// if the bomb is loose, snipe near it
|
|
|
|
if (me->GetGameState()->IsLooseBombLocationKnown())
|
|
|
|
{
|
|
|
|
snipingArea = TheNavAreaGrid.GetNearestNavArea(me->GetGameState()->GetBombPosition());
|
|
|
|
me->PrintIfWatched("Sniping near loose bomb\n");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// snipe bomb zone(s)
|
|
|
|
const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
|
|
|
|
if (zone)
|
|
|
|
{
|
|
|
|
snipingArea = TheCSBots()->GetRandomAreaInZone(zone);
|
|
|
|
me->PrintIfWatched("Sniping near bombsite\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (snipingArea)
|
|
|
|
{
|
|
|
|
me->SetTask(CCSBot::MOVE_TO_SNIPER_SPOT);
|
|
|
|
me->Hide(snipingArea, -1.0f, sniperHideRange);
|
|
|
|
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// rogues just hunt, unless they want to snipe
|
|
|
|
// if the whole team has decided to rush, hunt
|
|
|
|
// if we know the bomb is dropped, hunt for enemies and the loose bomb
|
|
|
|
if (me->IsRogue() || TheCSBots()->IsDefenseRushing() || me->GetGameState()->IsLooseBombLocationKnown())
|
|
|
|
{
|
|
|
|
me->Hunt();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// the lower our morale gets, the more we want to camp the bomb zone(s)
|
|
|
|
// only decide to camp at the start of the round, or if we haven't seen anything for a long time
|
|
|
|
if (me->IsSafe() || me->HasNotSeenEnemyForLongTime())
|
|
|
|
{
|
|
|
|
float guardBombsiteChance = -34.0f * me->GetMorale();
|
|
|
|
|
|
|
|
if (RANDOM_FLOAT(0.0f, 100.0f) < guardBombsiteChance)
|
|
|
|
{
|
|
|
|
float guardRange = 500.0f + 100.0f * (me->GetMorale() + 3);
|
|
|
|
|
|
|
|
// guard bomb zone(s)
|
|
|
|
const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
|
|
|
|
if (zone)
|
|
|
|
{
|
|
|
|
CNavArea *area = TheCSBots()->GetRandomAreaInZone(zone);
|
|
|
|
if (area)
|
|
|
|
{
|
|
|
|
me->PrintIfWatched("I'm guarding a bombsite\n");
|
|
|
|
me->GetChatter()->AnnouncePlan("GoingToDefendBombsite", area->GetPlace());
|
|
|
|
me->SetTask(CCSBot::GUARD_BOMB_ZONE);
|
|
|
|
me->Hide(area, -1.0, guardRange);
|
|
|
|
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case CCSBotManager::SCENARIO_ESCORT_VIP:
|
|
|
|
{
|
|
|
|
if (me->m_iTeam == TERRORIST)
|
|
|
|
{
|
|
|
|
// if we have a sniper rifle, we like to camp, whether rogue or not
|
|
|
|
if (me->IsSniper())
|
|
|
|
{
|
|
|
|
if (RANDOM_FLOAT(0, 100) <= defenseSniperCampChance)
|
|
|
|
{
|
|
|
|
// snipe escape zone(s)
|
|
|
|
const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
|
|
|
|
if (zone)
|
|
|
|
{
|
|
|
|
CNavArea *area = TheCSBots()->GetRandomAreaInZone(zone);
|
|
|
|
if (area)
|
|
|
|
{
|
|
|
|
me->SetTask(CCSBot::MOVE_TO_SNIPER_SPOT);
|
|
|
|
me->Hide(area, -1.0, sniperHideRange);
|
|
|
|
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
|
|
|
|
me->PrintIfWatched("Sniping near escape zone\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// rogues just hunt, unless they want to snipe
|
|
|
|
// if the whole team has decided to rush, hunt
|
|
|
|
if (me->IsRogue() || TheCSBots()->IsDefenseRushing())
|
|
|
|
break;
|
|
|
|
|
|
|
|
// the lower our morale gets, the more we want to camp the escape zone(s)
|
|
|
|
float guardEscapeZoneChance = -34.0f * me->GetMorale();
|
|
|
|
|
|
|
|
if (RANDOM_FLOAT(0.0f, 100.0f) < guardEscapeZoneChance)
|
|
|
|
{
|
|
|
|
// guard escape zone(s)
|
|
|
|
const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
|
|
|
|
if (zone)
|
|
|
|
{
|
|
|
|
CNavArea *area = TheCSBots()->GetRandomAreaInZone(zone);
|
|
|
|
if (area)
|
|
|
|
{
|
|
|
|
// guard the escape zone - stay closer if our morale is low
|
|
|
|
me->SetTask(CCSBot::GUARD_VIP_ESCAPE_ZONE);
|
|
|
|
me->PrintIfWatched("I'm guarding an escape zone\n");
|
|
|
|
|
|
|
|
float escapeGuardRange = 750.0f + 250.0f * (me->GetMorale() + 3);
|
|
|
|
me->Hide(area, -1.0, escapeGuardRange);
|
|
|
|
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// CT
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (me->m_bIsVIP)
|
|
|
|
{
|
|
|
|
// if early in round, pick a random zone, otherwise pick closest zone
|
|
|
|
const float earlyTime = 20.0f;
|
|
|
|
const CCSBotManager::Zone *zone = nullptr;
|
|
|
|
|
|
|
|
if (TheCSBots()->GetElapsedRoundTime() < earlyTime)
|
|
|
|
{
|
|
|
|
// pick random zone
|
|
|
|
zone = TheCSBots()->GetRandomZone();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// pick closest zone
|
|
|
|
zone = TheCSBots()->GetClosestZone(me->GetLastKnownArea(), PathCost(me));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (zone)
|
|
|
|
{
|
|
|
|
// pick a random spot within the escape zone
|
|
|
|
const Vector *pos = TheCSBots()->GetRandomPositionInZone(zone);
|
|
|
|
if (pos)
|
|
|
|
{
|
|
|
|
// move to escape zone
|
|
|
|
me->SetTask(CCSBot::VIP_ESCAPE);
|
|
|
|
me->Run();
|
|
|
|
me->MoveTo(pos);
|
|
|
|
|
|
|
|
// tell team to follow
|
|
|
|
const float repeatTime = 30.0f;
|
|
|
|
if (me->GetFriendsRemaining() && TheCSBots()->GetRadioMessageInterval(EVENT_RADIO_FOLLOW_ME, me->m_iTeam) > repeatTime)
|
|
|
|
me->SendRadioMessage(EVENT_RADIO_FOLLOW_ME);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// small chance of sniper camping on offense, if we aren't VIP
|
|
|
|
if (me->GetFriendsRemaining() && me->IsSniper() && RANDOM_FLOAT(0, 100.0f) < offenseSniperCampChance)
|
|
|
|
{
|
|
|
|
me->SetTask(CCSBot::MOVE_TO_SNIPER_SPOT);
|
|
|
|
me->Hide(me->GetLastKnownArea(), RANDOM_FLOAT(10.0f, 30.0f), sniperHideRange);
|
|
|
|
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
|
|
|
|
me->PrintIfWatched("Sniping!\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case CCSBotManager::SCENARIO_RESCUE_HOSTAGES:
|
|
|
|
{
|
|
|
|
if (me->m_iTeam == TERRORIST)
|
|
|
|
{
|
|
|
|
bool campHostages;
|
|
|
|
|
|
|
|
// if we are in early game, camp the hostages
|
|
|
|
if (me->IsSafe())
|
|
|
|
{
|
|
|
|
campHostages = true;
|
|
|
|
}
|
|
|
|
else if (me->GetGameState()->HaveSomeHostagesBeenTaken() || me->GetGameState()->AreAllHostagesBeingRescued())
|
|
|
|
{
|
|
|
|
campHostages = false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// later in the game, camp either hostages or escape zone
|
|
|
|
const float campZoneChance = 100.0f * (TheCSBots()->GetElapsedRoundTime() - me->GetSafeTime()) / 120.0f;
|
|
|
|
campHostages = (RANDOM_FLOAT(0, 100) > campZoneChance) ? true : false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if we have a sniper rifle, we like to camp, whether rogue or not
|
|
|
|
if (me->IsSniper())
|
|
|
|
{
|
|
|
|
if (RANDOM_FLOAT(0, 100) <= defenseSniperCampChance)
|
|
|
|
{
|
|
|
|
const Vector *hostagePos = me->GetGameState()->GetRandomFreeHostagePosition();
|
|
|
|
if (hostagePos && campHostages)
|
|
|
|
{
|
|
|
|
me->SetTask(CCSBot::MOVE_TO_SNIPER_SPOT);
|
|
|
|
me->PrintIfWatched("Sniping near hostages\n");
|
|
|
|
me->Hide(TheNavAreaGrid.GetNearestNavArea(hostagePos), -1.0, sniperHideRange);
|
|
|
|
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// camp the escape zone(s)
|
|
|
|
if (me->GuardRandomZone(sniperHideRange))
|
|
|
|
{
|
|
|
|
me->SetTask(CCSBot::MOVE_TO_SNIPER_SPOT);
|
|
|
|
me->PrintIfWatched("Sniping near a rescue zone\n");
|
|
|
|
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if safe time is up, and we stumble across a hostage, guard it
|
|
|
|
if (!me->IsSafe() && !me->IsRogue())
|
|
|
|
{
|
|
|
|
CBaseEntity *pHostage = me->GetGameState()->GetNearestVisibleFreeHostage();
|
|
|
|
if (pHostage)
|
|
|
|
{
|
|
|
|
// we see a free hostage, guard it
|
|
|
|
CNavArea *area = TheNavAreaGrid.GetNearestNavArea(&pHostage->pev->origin);
|
|
|
|
if (area)
|
|
|
|
{
|
|
|
|
me->SetTask(CCSBot::GUARD_HOSTAGES);
|
|
|
|
me->Hide(area);
|
|
|
|
me->PrintIfWatched("I'm guarding hostages I found\n");
|
|
|
|
// don't chatter here - he'll tell us when he's in his hiding spot
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// decide if we want to hunt, or guard
|
|
|
|
const float huntChance = 70.0f + 25.0f * me->GetMorale();
|
|
|
|
|
|
|
|
// rogues just hunt, unless they want to snipe
|
|
|
|
// if the whole team has decided to rush, hunt
|
|
|
|
if (me->GetFriendsRemaining())
|
|
|
|
{
|
|
|
|
if (me->IsRogue() || TheCSBots()->IsDefenseRushing() || RANDOM_FLOAT(0, 100) < huntChance)
|
|
|
|
{
|
|
|
|
me->Hunt();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// decide whether to camp the hostages or the escape zones
|
|
|
|
const Vector *hostagePos = me->GetGameState()->GetRandomFreeHostagePosition();
|
|
|
|
if (hostagePos && campHostages)
|
|
|
|
{
|
|
|
|
CNavArea *area = TheNavAreaGrid.GetNearestNavArea(hostagePos);
|
|
|
|
if (area)
|
|
|
|
{
|
|
|
|
// guard the hostages - stay closer to hostages if our morale is low
|
|
|
|
me->SetTask(CCSBot::GUARD_HOSTAGES);
|
|
|
|
me->PrintIfWatched("I'm guarding hostages\n");
|
|
|
|
|
|
|
|
float hostageGuardRange = 750.0f + 250.0f * (me->GetMorale() + 3);
|
|
|
|
me->Hide(area, -1.0, hostageGuardRange);
|
|
|
|
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
|
|
|
|
|
|
|
|
if (RANDOM_FLOAT(0, 100) < 50)
|
|
|
|
me->GetChatter()->GuardingHostages(area->GetPlace(), IS_PLAN);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// guard rescue zone(s)
|
|
|
|
if (me->GuardRandomZone())
|
|
|
|
{
|
|
|
|
me->SetTask(CCSBot::GUARD_HOSTAGE_RESCUE_ZONE);
|
|
|
|
me->PrintIfWatched("I'm guarding a rescue zone\n");
|
|
|
|
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
|
|
|
|
me->GetChatter()->GuardingHostageEscapeZone(IS_PLAN);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// CT
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// only decide to do something else if we aren't already rescuing hostages
|
|
|
|
if (!me->GetHostageEscortCount())
|
|
|
|
{
|
|
|
|
// small chance of sniper camping on offense
|
|
|
|
if (me->GetFriendsRemaining() && me->IsSniper() && RANDOM_FLOAT(0, 100.0f) < offenseSniperCampChance)
|
|
|
|
{
|
|
|
|
me->SetTask(CCSBot::MOVE_TO_SNIPER_SPOT);
|
|
|
|
me->Hide(me->GetLastKnownArea(), RANDOM_FLOAT(10.0f, 30.0f), sniperHideRange);
|
|
|
|
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
|
|
|
|
me->PrintIfWatched("Sniping!\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (me->GetFriendsRemaining() && !me->GetHostageEscortCount())
|
|
|
|
{
|
|
|
|
// rogues just hunt, unless all friends are dead
|
|
|
|
// if we have friends left, we might go hunting instead of hostage rescuing
|
|
|
|
const float huntChance = 33.3f;
|
|
|
|
if (me->IsRogue() || RANDOM_FLOAT(0.0f, 100.0f) < huntChance)
|
|
|
|
{
|
|
|
|
me->Hunt();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// look for free hostages - CT's have radar so they know where hostages are at all times
|
|
|
|
CHostage *pHostage = me->GetGameState()->GetNearestFreeHostage();
|
|
|
|
|
|
|
|
// if we are not allowed to do the scenario, guard the hostages to clear the area for the human(s)
|
|
|
|
if (!me->IsDoingScenario())
|
|
|
|
{
|
|
|
|
if (pHostage)
|
|
|
|
{
|
|
|
|
CNavArea *area = TheNavAreaGrid.GetNearestNavArea(&pHostage->pev->origin);
|
|
|
|
if (area)
|
|
|
|
{
|
|
|
|
me->SetTask(CCSBot::GUARD_HOSTAGES);
|
|
|
|
me->Hide(area);
|
|
|
|
me->PrintIfWatched("I'm securing the hostages for a human to rescue\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
me->Hunt();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool fetchHostages = false;
|
|
|
|
bool rescueHostages = false;
|
|
|
|
const CCSBotManager::Zone *zone = nullptr;
|
|
|
|
me->SetGoalEntity(nullptr);
|
|
|
|
|
|
|
|
// if we are escorting hostages, determine where to take them
|
|
|
|
if (me->GetHostageEscortCount())
|
|
|
|
zone = TheCSBots()->GetClosestZone(me->GetLastKnownArea(), PathCost(me, FASTEST_ROUTE));
|
|
|
|
|
|
|
|
// if we are escorting hostages and there are more hostages to rescue,
|
|
|
|
// determine whether it's faster to rescue the ones we have, or go get the remaining ones
|
|
|
|
if (pHostage)
|
|
|
|
{
|
|
|
|
if (zone)
|
|
|
|
{
|
|
|
|
PathCost pathCost(me, FASTEST_ROUTE);
|
|
|
|
float toZone = NavAreaTravelDistance(me->GetLastKnownArea(), zone->m_area[0], pathCost);
|
|
|
|
float toHostage = NavAreaTravelDistance(me->GetLastKnownArea(), TheNavAreaGrid.GetNearestNavArea(&pHostage->pev->origin), pathCost);
|
|
|
|
|
|
|
|
if (toHostage < 0.0f)
|
|
|
|
{
|
|
|
|
rescueHostages = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (toZone < toHostage)
|
|
|
|
rescueHostages = true;
|
|
|
|
else
|
|
|
|
fetchHostages = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fetchHostages = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (zone)
|
|
|
|
{
|
|
|
|
rescueHostages = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fetchHostages)
|
|
|
|
{
|
|
|
|
// go get hostages
|
|
|
|
me->SetTask(CCSBot::COLLECT_HOSTAGES);
|
|
|
|
me->Run();
|
|
|
|
me->SetGoalEntity(pHostage);
|
|
|
|
me->ResetWaitForHostagePatience();
|
|
|
|
|
|
|
|
// if we already have some hostages, move to the others by the quickest route
|
|
|
|
RouteType route = (me->GetHostageEscortCount()) ? FASTEST_ROUTE : SAFEST_ROUTE;
|
|
|
|
me->MoveTo(&pHostage->pev->origin, route);
|
|
|
|
me->PrintIfWatched("I'm collecting hostages\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rescueHostages)
|
|
|
|
{
|
|
|
|
me->SetTask(CCSBot::RESCUE_HOSTAGES);
|
|
|
|
me->Run();
|
|
|
|
me->SetDisposition(CCSBot::SELF_DEFENSE);
|
|
|
|
me->MoveTo(TheCSBots()->GetRandomPositionInZone(zone), FASTEST_ROUTE);
|
|
|
|
me->PrintIfWatched("I'm rescuing hostages\n");
|
|
|
|
me->GetChatter()->EscortingHostages();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// deathmatch
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
// sniping check
|
|
|
|
if (me->GetFriendsRemaining() && me->IsSniper() && RANDOM_FLOAT(0, 100.0f) < offenseSniperCampChance)
|
|
|
|
{
|
|
|
|
me->SetTask(CCSBot::MOVE_TO_SNIPER_SPOT);
|
|
|
|
me->Hide(me->GetLastKnownArea(), RANDOM_FLOAT(10.0f, 30.0f), sniperHideRange);
|
|
|
|
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
|
|
|
|
me->PrintIfWatched("Sniping!\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if we have nothing special to do, go hunting for enemies
|
|
|
|
me->Hunt();
|
|
|
|
}
|