ReGameDLL_CS/regamedll/dlls/bot/cs_bot_update.cpp
2019-09-22 21:29:29 +07:00

805 lines
20 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"
// Lightweight maintenance, invoked frequently
void CCSBot::Upkeep()
{
if (TheCSBots()->IsLearningMap() || !IsAlive())
return;
if (m_isRapidFiring)
TogglePrimaryAttack();
// aiming must be smooth - update often
if (IsAimingAtEnemy())
{
UpdateAimOffset();
// aim at enemy, if he's still alive
if (m_enemy.IsValid())
{
float feetOffset = pev->origin.z - GetFeetZ();
if (IsEnemyVisible())
{
if (GetProfile()->GetSkill() > 0.5f)
{
const float k = 3.0f;
m_aimSpot = (m_enemy->pev->velocity - pev->velocity) * g_flBotCommandInterval * k + m_enemy->pev->origin;
}
else
m_aimSpot = m_enemy->pev->origin;
bool aimBlocked = false;
const float sharpshooter = 0.8f;
if (IsUsingAWP() || IsUsingShotgun() || IsUsingMachinegun() || GetProfile()->GetSkill() < sharpshooter
|| (IsActiveWeaponRecoilHigh() && !IsUsingPistol() && !IsUsingSniperRifle()))
{
if (IsEnemyPartVisible(CHEST))
{
// No headshots, go for the chest.
aimBlocked = true;
}
}
if (aimBlocked)
m_aimSpot.z -= feetOffset * 0.25f;
else if (!IsEnemyPartVisible(HEAD))
{
if (IsEnemyPartVisible(CHEST))
{
m_aimSpot.z -= feetOffset * 0.5f;
}
else if (IsEnemyPartVisible(LEFT_SIDE))
{
Vector2D to = (m_enemy->pev->origin - pev->origin).Make2D();
to.NormalizeInPlace();
m_aimSpot.x -= to.y * 16.0f;
m_aimSpot.y += to.x * 16.0f;
m_aimSpot.z -= feetOffset * 0.5f;
}
else if (IsEnemyPartVisible(RIGHT_SIDE))
{
Vector2D to = (m_enemy->pev->origin - pev->origin).Make2D();
to.NormalizeInPlace();
m_aimSpot.x += to.y * 16.0f;
m_aimSpot.y -= to.x * 16.0f;
m_aimSpot.z -= feetOffset * 0.5f;
}
else // FEET
{
m_aimSpot.z -= (feetOffset + feetOffset);
}
}
}
else
{
m_aimSpot = m_lastEnemyPosition;
}
// add in aim error
m_aimSpot.x += m_aimOffset.x;
m_aimSpot.y += m_aimOffset.y;
m_aimSpot.z += m_aimOffset.z;
Vector toEnemy = m_aimSpot - pev->origin;
Vector idealAngle = UTIL_VecToAngles(toEnemy);
idealAngle.x = 360.0 - idealAngle.x;
SetLookAngles(idealAngle.y, idealAngle.x);
}
}
else
{
if (m_lookAtSpotClearIfClose)
{
// dont look at spots just in front of our face - it causes erratic view rotation
const float tooCloseRange = 100.0f;
if ((m_lookAtSpot - pev->origin).IsLengthLessThan(tooCloseRange))
m_lookAtSpotState = NOT_LOOKING_AT_SPOT;
}
switch (m_lookAtSpotState)
{
case NOT_LOOKING_AT_SPOT:
{
// look ahead
SetLookAngles(m_lookAheadAngle, 0);
break;
}
case LOOK_TOWARDS_SPOT:
{
UpdateLookAt();
if (IsLookingAtPosition(&m_lookAtSpot, m_lookAtSpotAngleTolerance))
{
m_lookAtSpotState = LOOK_AT_SPOT;
m_lookAtSpotTimestamp = gpGlobals->time;
}
break;
}
case LOOK_AT_SPOT:
{
UpdateLookAt();
if (m_lookAtSpotDuration >= 0.0f && gpGlobals->time - m_lookAtSpotTimestamp > m_lookAtSpotDuration)
{
m_lookAtSpotState = NOT_LOOKING_AT_SPOT;
m_lookAtSpotDuration = 0.0f;
}
break;
}
}
float driftAmplitude = 2.0f;
// have view "drift" very slowly, so view looks "alive"
if (IsUsingSniperRifle() && IsUsingScope())
{
driftAmplitude = 0.5f;
}
m_lookYaw += driftAmplitude * BotCOS(33.0f * gpGlobals->time);
m_lookPitch += driftAmplitude * BotSIN(13.0f * gpGlobals->time);
}
#ifdef REGAMEDLL_FIXES
// Don't update view angles at frozen state
if (!(pev->flags & FL_FROZEN))
#endif
{
// view angles can change quickly
UpdateLookAngles();
}
}
// Heavyweight processing, invoked less often
void CCSBot::Update()
{
if (TheCSBots()->IsAnalysisRequested() && m_processMode == PROCESS_NORMAL)
{
TheCSBots()->AckAnalysisRequest();
StartAnalyzeAlphaProcess();
}
switch (m_processMode)
{
case PROCESS_LEARN: UpdateLearnProcess(); return;
case PROCESS_ANALYZE_ALPHA: UpdateAnalyzeAlphaProcess(); return;
case PROCESS_ANALYZE_BETA: UpdateAnalyzeBetaProcess(); return;
case PROCESS_SAVE: UpdateSaveProcess(); return;
}
// update our radio chatter
// need to allow bots to finish their chatter even if they are dead
GetChatter()->Update();
if (m_voiceFeedbackEndTimestamp != 0.0f
&& (m_voiceFeedbackEndTimestamp <= gpGlobals->time || gpGlobals->time < m_voiceFeedbackStartTimestamp))
{
EndVoiceFeedback(NO_FORCE);
}
// check if we are dead
if (!IsAlive())
{
// remember that we died
m_diedLastRound = true;
BotDeathThink();
return;
}
// show line of fire
if ((cv_bot_traceview.value == 100.0 && IsLocalPlayerWatchingMe()) || cv_bot_traceview.value == 101.0)
{
UTIL_MakeVectors(pev->punchangle + pev->v_angle);
if (!IsFriendInLineOfFire())
{
Vector vecAiming = gpGlobals->v_forward;
Vector vecSrc = GetGunPosition();
if (m_iTeam == TERRORIST)
UTIL_DrawBeamPoints(vecSrc, vecSrc + 2000.0f * vecAiming, 1, 255, 0, 0);
else
UTIL_DrawBeamPoints(vecSrc, vecSrc + 2000.0f * vecAiming, 1, 0, 50, 255);
}
}
//
// Debug beam rendering
//
// show approach points
if ((cv_bot_traceview.value == 2.0f && IsLocalPlayerWatchingMe()) || cv_bot_traceview.value == 3.0f)
DrawApproachPoints();
// show encounter spot data
if ((cv_bot_traceview.value == 4.0f && IsLocalPlayerWatchingMe()) || cv_bot_traceview.value == 5.0f)
{
if (m_spotEncounter)
{
UTIL_DrawBeamPoints(m_spotEncounter->path.from, m_spotEncounter->path.to, 3, 0, 0, 255);
Vector dir = m_spotEncounter->path.to - m_spotEncounter->path.from;
float length = dir.NormalizeInPlace();
for (auto &order : m_spotEncounter->spotList) {
UTIL_DrawBeamPoints(m_spotEncounter->path.from + order.t * length * dir, *order.spot->GetPosition(), 3, 0, 255, 255);
}
}
}
// show path navigation data
if (cv_bot_traceview.value == 1.0f && IsLocalPlayerWatchingMe())
{
Vector from = GetEyePosition();
const float size = 50.0f;
Vector arrow(size * float(Q_cos(m_lookAheadAngle * M_PI / 180.0f)), size * float(Q_sin(m_lookAheadAngle * M_PI / 180.0f)), 0.0f);
UTIL_DrawBeamPoints(from, from + arrow, 1, 0, 255, 255);
}
if (cv_bot_stop.value != 0.0f)
return;
// check if we are stuck
StuckCheck();
// if our current 'noise' was heard a long time ago, forget it
const float rememberNoiseDuration = 20.0f;
if (m_noiseTimestamp > 0.0f && gpGlobals->time - m_noiseTimestamp > rememberNoiseDuration)
{
ForgetNoise();
}
// where are we
if (!m_currentArea || !m_currentArea->Contains(&pev->origin))
{
m_currentArea = TheNavAreaGrid.GetNavArea(&pev->origin);
}
// track the last known area we were in
if (m_currentArea && m_currentArea != m_lastKnownArea)
{
m_lastKnownArea = m_currentArea;
// assume that we "clear" an area of enemies when we enter it
m_currentArea->SetClearedTimestamp(m_iTeam - 1);
}
// update approach points
const float recomputeApproachPointTolerance = 50.0f;
if ((m_approachPointViewPosition - pev->origin).IsLengthGreaterThan(recomputeApproachPointTolerance))
{
ComputeApproachPoints();
m_approachPointViewPosition = pev->origin;
}
if (cv_bot_show_nav.value > 0.0f && m_lastKnownArea)
{
m_lastKnownArea->DrawConnectedAreas();
}
// if we're blind, retreat!
if (IsBlind())
{
if (!IsAtHidingSpot())
{
switch (m_blindMoveDir)
{
case FORWARD: MoveForward(); break;
case RIGHT: StrafeRight(); break;
case BACKWARD: MoveBackward(); break;
case LEFT: StrafeLeft(); break;
default: Crouch(); break;
}
}
if (m_blindFire)
{
PrimaryAttack();
}
return;
}
// Enemy acquisition and attack initiation
// take a snapshot and update our reaction time queue
UpdateReactionQueue();
// "threat" may be the same as our current enemy
CBasePlayer *threat = GetRecognizedEnemy();
if (threat)
{
// adjust our personal "safe" time
AdjustSafeTime();
if (IsUsingGrenade())
{
ThrowGrenade(&threat->pev->origin);
}
else
{
// Decide if we should attack
bool doAttack = false;
switch (GetDisposition())
{
case IGNORE_ENEMIES:
{
// never attack
doAttack = false;
break;
}
case SELF_DEFENSE:
{
// attack if fired on
doAttack = IsPlayerLookingAtMe(threat);
// attack if enemy very close
if (!doAttack)
{
const float selfDefenseRange = 750.0f;
doAttack = (pev->origin - threat->pev->origin).IsLengthLessThan(selfDefenseRange);
}
break;
}
case ENGAGE_AND_INVESTIGATE:
case OPPORTUNITY_FIRE:
{
// normal combat range
doAttack = true;
break;
}
}
if (doAttack)
{
if (!GetEnemy() || threat != GetEnemy() || !IsAttacking())
{
if (IsUsingKnife() && IsHiding())
{
// if hiding with a knife, wait until threat is close
const float knifeAttackRange = 250.0f;
if ((pev->origin - threat->pev->origin).IsLengthLessThan(knifeAttackRange))
{
Attack(threat);
}
}
else
{
Attack(threat);
}
}
}
else
{
// dont attack, but keep track of nearby enemies
SetEnemy(threat);
m_isEnemyVisible = true;
}
}
// if we aren't attacking but we are being attacked, retaliate
if (GetDisposition() != IGNORE_ENEMIES && !IsAttacking())
{
const float recentAttackDuration = 1.0f;
if (GetTimeSinceAttacked() < recentAttackDuration)
{
// we may not be attacking our attacker, but at least we're not just taking it
// (since m_attacker isn't reaction-time delayed, we can't directly use it)
Attack(threat);
PrintIfWatched("Ouch! Retaliating!\n");
}
}
TheCSBots()->SetLastSeenEnemyTimestamp();
}
// Validate existing enemy, if any
if (m_enemy.IsValid())
{
if (IsAwareOfEnemyDeath())
{
// we have noticed that our enemy has died
m_enemy = nullptr;
m_isEnemyVisible = false;
}
else
{
const int dada = offsetof(CCSBot, m_visibleEnemyParts);
// check LOS to current enemy (chest & head), in case he's dead (GetNearestEnemy() only returns live players)
// note we're not checking FOV - once we've acquired an enemy (which does check FOV), assume we know roughly where he is
if (IsVisible(m_enemy, false, &m_visibleEnemyParts))
{
m_isEnemyVisible = true;
m_lastSawEnemyTimestamp = gpGlobals->time;
m_lastEnemyPosition = m_enemy->pev->origin;
}
else
{
m_isEnemyVisible = false;
}
// check if enemy died
if (m_enemy->IsAlive())
{
m_enemyDeathTimestamp = 0.0f;
m_isLastEnemyDead = false;
}
else if (m_enemyDeathTimestamp == 0.0f)
{
// note time of death (to allow bots to overshoot for a time)
m_enemyDeathTimestamp = gpGlobals->time;
m_isLastEnemyDead = true;
}
}
}
else
{
m_isEnemyVisible = false;
}
// if we have seen an enemy recently, keep an eye on him if we can
const float seenRecentTime = 3.0f;
if (m_enemy.IsValid() && GetTimeSinceLastSawEnemy() < seenRecentTime)
{
AimAtEnemy();
}
else
{
StopAiming();
}
// Hack to fire while retreating
// TODO: Encapsulate aiming and firing on enemies separately from current task
if (GetDisposition() == IGNORE_ENEMIES)
{
FireWeaponAtEnemy();
}
if (IsEndOfSafeTime() && IsUsingGrenade() && (IsWellPastSafe() || !IsUsingHEGrenade()) && !m_isWaitingToTossGrenade)
{
Vector target;
if (FindGrenadeTossPathTarget(&target))
{
ThrowGrenade(&target);
}
}
if (IsUsingGrenade())
{
bool doToss = (m_isWaitingToTossGrenade && (m_tossGrenadeTimer.IsElapsed() || m_lookAtSpotState == LOOK_AT_SPOT));
if (doToss)
{
ClearPrimaryAttack();
m_isWaitingToTossGrenade = false;
}
else
{
PrimaryAttack();
}
}
else
{
m_isWaitingToTossGrenade = false;
}
if (IsHunting() && IsWellPastSafe() && IsUsingGrenade())
{
EquipBestWeapon(MUST_EQUIP);
}
// check if our weapon is totally out of ammo
// or if we no longer feel "safe", equip our weapon
if (!IsSafe() && !IsUsingGrenade() && IsActiveWeaponOutOfAmmo())
{
EquipBestWeapon();
}
// TODO: This doesn't work if we are restricted to just knives and sniper rifles because we cant use the rifle at close range
if (!IsSafe() && !IsUsingGrenade() && IsUsingKnife() && !IsEscapingFromBomb())
{
EquipBestWeapon();
}
// if we haven't seen an enemy in awhile, and we switched to our pistol during combat,
// switch back to our primary weapon (if it still has ammo left)
const float safeRearmTime = 5.0f;
if (!IsActiveWeaponReloading() && IsUsingPistol() && !IsPrimaryWeaponEmpty() && GetTimeSinceLastSawEnemy() > safeRearmTime)
{
EquipBestWeapon();
}
// reload our weapon if we must
ReloadCheck();
// equip silencer
SilencerCheck();
// listen to the radio
RespondToRadioCommands();
// make way
const float avoidTime = 0.33f;
if (gpGlobals->time - m_avoidTimestamp < avoidTime && m_avoid)
{
StrafeAwayFromPosition(&m_avoid->pev->origin);
}
else
{
m_avoid = nullptr;
}
if (m_isJumpCrouching)
{
const float duration = 0.75f;
const float crouchDelayTime = 0.05f;
const float standUpTime = 0.6f;
float elapsed = gpGlobals->time - m_jumpCrouchTimestamp;
if (elapsed > crouchDelayTime && elapsed < standUpTime)
Crouch();
if (elapsed >= standUpTime)
StandUp();
if (elapsed > duration)
m_isJumpCrouching = false;
}
// if we're using a sniper rifle and are no longer attacking, stop looking thru scope
if (!IsAtHidingSpot() && !IsAttacking() && IsUsingSniperRifle() && IsUsingScope())
{
SecondaryAttack();
}
// check encounter spots
UpdatePeripheralVision();
// Update gamestate
if (m_bomber)
{
GetChatter()->SpottedBomber(GetBomber());
}
if (CanSeeLooseBomb())
{
GetChatter()->SpottedLooseBomb(TheCSBots()->GetLooseBomb());
}
// Scenario interrupts
switch (TheCSBots()->GetScenario())
{
case CCSBotManager::SCENARIO_DEFUSE_BOMB:
{
// flee if the bomb is ready to blow and we aren't defusing it or attacking and we know where the bomb is
// (aggressive players wait until its almost too late)
float gonnaBlowTime = 8.0f - (2.0f * GetProfile()->GetAggression());
// if we have a defuse kit, can wait longer
if (m_bHasDefuser)
gonnaBlowTime *= 0.66f;
if (!IsEscapingFromBomb() // we aren't already escaping the bomb
&& TheCSBots()->IsBombPlanted() // is the bomb planted
&& GetGameState()->IsPlantedBombLocationKnown() // we know where the bomb is
&& TheCSBots()->GetBombTimeLeft() < gonnaBlowTime // is the bomb about to explode
&& !IsDefusingBomb() // we aren't defusing the bomb
&& !IsAttacking()) // we aren't in the midst of a firefight
{
EscapeFromBomb();
break;
}
break;
}
case CCSBotManager::SCENARIO_RESCUE_HOSTAGES:
{
if (m_iTeam == CT)
{
UpdateHostageEscortCount();
}
else
{
// Terrorists have imperfect information on status of hostages
CSGameState::ValidateStatusType status = GetGameState()->ValidateHostagePositions();
if (status & CSGameState::HOSTAGES_ALL_GONE)
{
GetChatter()->HostagesTaken();
Idle();
}
else if (status & CSGameState::HOSTAGE_GONE)
{
GetGameState()->HostageWasTaken();
Idle();
}
}
break;
}
}
// Follow nearby humans if our co-op is high and we have nothing else to do
// If we were just following someone, don't auto-follow again for a short while to
// give us a chance to do something else.
const float earliestAutoFollowTime = 5.0f;
const float minAutoFollowTeamwork = 0.4f;
if (TheCSBots()->GetElapsedRoundTime() > earliestAutoFollowTime
&& GetProfile()->GetTeamwork() > minAutoFollowTeamwork
&& CanAutoFollow()
&& !IsBusy()
&& !IsFollowing()
&& !GetGameState()->IsAtPlantedBombsite())
{
// chance of following is proportional to teamwork attribute
if (GetProfile()->GetTeamwork() > RANDOM_FLOAT(0.0f, 1.0f))
{
CBasePlayer *pLeader = GetClosestVisibleHumanFriend();
if (pLeader && pLeader->IsAutoFollowAllowed())
{
// count how many bots are already following this player
const float maxFollowCount = 2;
if (GetBotFollowCount(pLeader) < maxFollowCount)
{
const float autoFollowRange = 300.0f;
if ((pLeader->pev->origin - pev->origin).IsLengthLessThan(autoFollowRange))
{
CNavArea *leaderArea = TheNavAreaGrid.GetNavArea(&pLeader->pev->origin);
if (leaderArea)
{
PathCost cost(this, FASTEST_ROUTE);
float travelRange = NavAreaTravelDistance(GetLastKnownArea(), leaderArea, cost);
if (/*travelRange >= 0.0f &&*/ travelRange < autoFollowRange)
{
// follow this human
Follow(pLeader);
PrintIfWatched("Auto-Following %s\n", STRING(pLeader->pev->netname));
if (CSGameRules()->IsCareer())
{
GetChatter()->Say("FollowingCommander", 10.0f);
}
else
{
GetChatter()->Say("FollowingSir", 10.0f);
}
}
}
}
}
}
}
else
{
// we decided not to follow, don't re-check for a duration
m_allowAutoFollowTime = gpGlobals->time + 15.0f + (1.0f - GetProfile()->GetTeamwork()) * 30.0f;
}
}
if (IsFollowing())
{
// if we are following someone, make sure they are still alive
CBaseEntity *pLeader = m_leader;
if (!pLeader || !pLeader->IsAlive())
{
StopFollowing();
}
// decide whether to continue following them
const float highTeamwork = 0.85f;
if (GetProfile()->GetTeamwork() < highTeamwork)
{
float minFollowDuration = 15.0f;
if (GetFollowDuration() > minFollowDuration + 40.0f * GetProfile()->GetTeamwork())
{
// we are bored of following our leader
StopFollowing();
PrintIfWatched("Stopping following - bored\n");
}
}
}
else
{
if (GetMorale() < NEUTRAL && IsSafe() && GetSafeTimeRemaining() < 2.0f && IsHunting())
{
if (GetMorale() * -40.0 > RANDOM_FLOAT(0.0f, 100.0f))
{
if (TheCSBots()->IsOnOffense(this) || !TheCSBots()->IsDefenseRushing())
{
SetDisposition(OPPORTUNITY_FIRE);
Hide(m_lastKnownArea, RANDOM_FLOAT(3.0f, 15.0f));
GetChatter()->Say("WaitingHere");
}
}
}
}
// Execute state machine
if (m_isAttacking)
{
m_attackState.OnUpdate(this);
}
else
{
m_state->OnUpdate(this);
}
if (m_isWaitingToTossGrenade)
{
ResetStuckMonitor();
ClearMovement();
}
// don't move while reloading unless we see an enemy
if (IsReloading() && !m_isEnemyVisible)
{
ResetStuckMonitor();
ClearMovement();
}
// if we get too far ahead of the hostages we are escorting, wait for them
if (!IsAttacking() && m_inhibitWaitingForHostageTimer.IsElapsed())
{
const float waitForHostageRange = 500.0f;
if (GetTask() == RESCUE_HOSTAGES && GetRangeToFarthestEscortedHostage() > waitForHostageRange)
{
if (!m_isWaitingForHostage)
{
// just started waiting
m_isWaitingForHostage = true;
m_waitForHostageTimer.Start(10.0f);
}
else
{
// we've been waiting
if (m_waitForHostageTimer.IsElapsed())
{
// give up waiting for awhile
m_isWaitingForHostage = false;
m_inhibitWaitingForHostageTimer.Start(3.0f);
}
else
{
// keep waiting
ResetStuckMonitor();
ClearMovement();
}
}
}
}
// remember our prior safe time status
m_wasSafe = IsSafe();
}