ReGameDLL_CS/regamedll/dlls/bot/states/cs_bot_attack.cpp
2019-09-23 04:09:58 +07:00

585 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"
// Begin attacking
void AttackState::OnEnter(CCSBot *me)
{
CBasePlayer *pEnemy = me->GetEnemy();
// store our posture when the attack began
me->PushPostureContext();
me->DestroyPath();
// if we are using a knife, try to sneak up on the enemy
if (pEnemy && me->IsUsingKnife() && !me->IsPlayerFacingMe(pEnemy))
me->Walk();
else
me->Run();
me->GetOffLadder();
me->ResetStuckMonitor();
m_repathTimer.Invalidate();
m_haveSeenEnemy = me->IsEnemyVisible();
m_nextDodgeStateTimestamp = 0.0f;
m_firstDodge = true;
m_isEnemyHidden = false;
m_reacquireTimestamp = 0.0f;
m_pinnedDownTimestamp = gpGlobals->time + RANDOM_FLOAT(7.0f, 10.0f);
m_shieldToggleTimestamp = gpGlobals->time + RANDOM_FLOAT(2.0f, 10.0f);
m_shieldForceOpen = false;
// if we encountered someone while escaping, grab our weapon and fight!
if (me->IsEscapingFromBomb())
me->EquipBestWeapon();
if (me->IsUsingKnife())
{
// can't crouch and hold with a knife
m_crouchAndHold = false;
me->StandUp();
}
else
{
// decide whether to crouch where we are, or run and gun (if we havent already - see CCSBot::Attack())
if (!m_crouchAndHold)
{
if (pEnemy)
{
const float crouchFarRange = 750.0f;
float crouchChance;
// more likely to crouch if using sniper rifle or if enemy is far away
if (me->IsUsingSniperRifle())
crouchChance = 50.0f;
else if ((me->pev->origin - pEnemy->pev->origin).IsLengthGreaterThan(crouchFarRange))
crouchChance = 50.0f;
else
crouchChance = 20.0f * (1.0f - me->GetProfile()->GetAggression());
if (RANDOM_FLOAT(0.0f, 100.0f) < crouchChance)
{
// make sure we can still see if we crouch
TraceResult result;
Vector origin = me->pev->origin;
if (!me->IsCrouching())
{
// we are standing, adjust for lower crouch origin
origin.z -= 20.0f;
}
UTIL_TraceLine(origin, pEnemy->EyePosition(), ignore_monsters, ignore_glass, ENT(me->pev), &result);
if (result.flFraction == 1.0f)
{
m_crouchAndHold = true;
}
}
}
}
if (m_crouchAndHold)
{
me->Crouch();
me->PrintIfWatched("Crouch and hold attack!\n");
}
}
m_scopeTimestamp = 0;
m_didAmbushCheck = false;
float skill = me->GetProfile()->GetSkill();
// tendency to dodge is proportional to skill
float dodgeChance = 80.0f * skill;
if (me->IsUsingKnife())
{
dodgeChance *= 2.0f;
}
// high skill bots always dodge if outnumbered, or they see a sniper
if (skill > 0.5f && me->IsOutnumbered())
{
dodgeChance = 100.0f;
}
m_dodge = (RANDOM_FLOAT(0.0f, 100.0f) < dodgeChance) != 0;
// decide whether we might bail out of this fight
m_isCoward = (RANDOM_FLOAT(0.0f, 100.0f) > 100.0f * me->GetProfile()->GetAggression());
}
void AttackState::StopAttacking(CCSBot *me)
{
if (me->m_task == CCSBot::SNIPING)
{
// stay in our hiding spot
me->Hide(me->GetLastKnownArea(), -1.0f, 50.0f);
}
else
{
me->StopAttacking();
}
}
// Perform attack behavior
void AttackState::OnUpdate(CCSBot *me)
{
// can't be stuck while attacking
me->ResetStuckMonitor();
me->StopRapidFire();
CBasePlayerWeapon *pWeapon = me->GetActiveWeapon();
if (pWeapon)
{
if (pWeapon->m_iId == WEAPON_C4 ||
pWeapon->m_iId == WEAPON_HEGRENADE ||
pWeapon->m_iId == WEAPON_FLASHBANG ||
pWeapon->m_iId == WEAPON_SMOKEGRENADE)
{
me->EquipBestWeapon();
}
}
CBasePlayer *pEnemy = me->GetEnemy();
if (!pEnemy)
{
StopAttacking(me);
return;
}
// keep track of whether we have seen our enemy at least once yet
if (!m_haveSeenEnemy)
m_haveSeenEnemy = me->IsEnemyVisible();
// Retreat check
// Do not retreat if the enemy is too close
if (m_retreatTimer.IsElapsed())
{
// If we've been fighting this battle for awhile, we're "pinned down" and
// need to do something else.
// If we are outnumbered, retreat.
bool isPinnedDown = (gpGlobals->time > m_pinnedDownTimestamp);
if (isPinnedDown ||
(me->IsOutnumbered() && m_isCoward) ||
(me->OutnumberedCount() >= 2 && me->GetProfile()->GetAggression() < 1.0f))
{
// tell our teammates our plight
if (isPinnedDown)
me->GetChatter()->PinnedDown();
else
me->GetChatter()->Scared();
m_retreatTimer.Start(RANDOM_FLOAT(3.0f, 15.0f));
// try to retreat
if (me->TryToRetreat())
{
// if we are a sniper, equip our pistol so we can fire while retreating
if (me->IsUsingSniperRifle())
{
me->EquipPistol();
}
}
else
{
me->PrintIfWatched("I want to retreat, but no safe spots nearby!\n");
}
}
}
// Knife fighting
// We need to pathfind right to the enemy to cut him
if (me->IsUsingKnife())
{
// can't crouch and hold with a knife
m_crouchAndHold = false;
me->StandUp();
// if we are using a knife and our prey is looking towards us, run at him
if (me->IsPlayerFacingMe(pEnemy))
{
me->ForceRun(5.0f);
me->Hurry(10.0f);
}
else
{
me->Walk();
}
// slash our victim
me->FireWeaponAtEnemy();
// if our victim has moved, repath
bool repath = false;
if (me->HasPath())
{
const float repathRange = 100.0f;
if ((me->GetPathEndpoint() - pEnemy->pev->origin).IsLengthGreaterThan(repathRange))
{
repath = true;
}
}
else
{
repath = true;
}
if (repath && m_repathTimer.IsElapsed())
{
me->ComputePath(TheNavAreaGrid.GetNearestNavArea(&pEnemy->pev->origin), &pEnemy->pev->origin, FASTEST_ROUTE);
const float repathInterval = 0.5f;
m_repathTimer.Start(repathInterval);
}
// move towards victim
if (me->UpdatePathMovement(NO_SPEED_CHANGE) != CCSBot::PROGRESSING)
{
me->DestroyPath();
}
return;
}
// Simple shield usage
if (me->HasShield())
{
if (me->IsEnemyVisible() && !m_shieldForceOpen)
{
if (!me->IsRecognizedEnemyReloading() && !me->IsReloading() && me->IsPlayerLookingAtMe(pEnemy))
{
// close up - enemy is pointing his gun at us
if (!me->IsProtectedByShield())
me->SecondaryAttack();
}
else
{
// enemy looking away or reloading his weapon - open up and shoot him
if (me->IsProtectedByShield())
me->SecondaryAttack();
}
}
else
{
// can't see enemy, open up
if (me->IsProtectedByShield())
me->SecondaryAttack();
}
if (gpGlobals->time > m_shieldToggleTimestamp)
{
m_shieldToggleTimestamp = gpGlobals->time + RANDOM_FLOAT(0.5, 2.0f);
// toggle shield force open
m_shieldForceOpen = !m_shieldForceOpen;
}
}
// check if our weapon range is bad and we should switch to pistol
if (me->IsUsingSniperRifle())
{
// if we have a sniper rifle and our enemy is too close, switch to pistol
// NOTE: Must be larger than NO_ZOOM range in AdjustZoom()
const float sniperMinRange = 310.0f;
if ((pEnemy->pev->origin - me->pev->origin).IsLengthLessThan(sniperMinRange))
me->EquipPistol();
}
else if (me->IsUsingShotgun())
{
// if we have a shotgun equipped and enemy is too far away, switch to pistol
const float shotgunMaxRange = 1000.0f;
if ((pEnemy->pev->origin - me->pev->origin).IsLengthGreaterThan(shotgunMaxRange))
me->EquipPistol();
}
// if we're sniping, look through the scope - need to do this here in case a reload resets our scope
if (me->IsUsingSniperRifle())
{
// for Scouts and AWPs, we need to wait for zoom to resume
if (me->m_bResumeZoom)
{
m_scopeTimestamp = gpGlobals->time;
return;
}
Vector toAimSpot3D = me->m_aimSpot - me->pev->origin;
float targetRange = toAimSpot3D.Length();
// dont adjust zoom level if we're already zoomed in - just fire
if (me->GetZoomLevel() == CCSBot::NO_ZOOM && me->AdjustZoom(targetRange))
m_scopeTimestamp = gpGlobals->time;
const float waitScopeTime = 0.2f + me->GetProfile()->GetReactionTime();
if (gpGlobals->time - m_scopeTimestamp < waitScopeTime)
{
// force us to wait until zoomed in before firing
return;
}
}
// see if we "notice" that our prey is dead
if (me->IsAwareOfEnemyDeath())
{
// let team know if we killed the last enemy
if (me->GetLastVictimID() == pEnemy->entindex() && me->GetNearbyEnemyCount() <= 1)
{
me->GetChatter()->KilledMyEnemy(pEnemy->entindex());
}
StopAttacking(me);
return;
}
float notSeenEnemyTime = gpGlobals->time - me->GetLastSawEnemyTimestamp();
// if we haven't seen our enemy for a moment, continue on if we dont want to fight, or decide to ambush if we do
if (!me->IsEnemyVisible())
{
// attend to nearby enemy gunfire
if (notSeenEnemyTime > 0.5f && me->CanHearNearbyEnemyGunfire())
{
// give up the attack, since we didn't want it in the first place
StopAttacking(me);
me->SetLookAt("Nearby enemy gunfire", me->GetNoisePosition(), PRIORITY_HIGH, 0.0f);
me->PrintIfWatched("Checking nearby threatening enemy gunfire!\n");
return;
}
// check if we have lost track of our enemy during combat
if (notSeenEnemyTime > 0.25f)
{
m_isEnemyHidden = true;
}
if (notSeenEnemyTime > 0.1f)
{
if (me->GetDisposition() == CCSBot::ENGAGE_AND_INVESTIGATE)
{
// decide whether we should hide and "ambush" our enemy
if (m_haveSeenEnemy && !m_didAmbushCheck)
{
const float hideChance = 33.3f;
if (RANDOM_FLOAT(0.0, 100.0f) < hideChance)
{
float ambushTime = RANDOM_FLOAT(3.0f, 15.0f);
// hide in ambush nearby
// TODO: look towards where we know enemy is
const Vector *spot = FindNearbyRetreatSpot(me, 200.0f);
if (spot)
{
me->IgnoreEnemies(1.0f);
me->Run();
me->StandUp();
me->Hide(spot, ambushTime, true);
return;
}
}
// don't check again
m_didAmbushCheck = true;
}
}
else
{
// give up the attack, since we didn't want it in the first place
StopAttacking(me);
return;
}
}
}
else
{
// we can see the enemy again - reset our ambush check
m_didAmbushCheck = false;
// if the enemy is coming out of hiding, we need time to react
if (m_isEnemyHidden)
{
m_reacquireTimestamp = gpGlobals->time + me->GetProfile()->GetReactionTime();
m_isEnemyHidden = false;
}
}
// if we haven't seen our enemy for a long time, chase after them
float chaseTime = 2.0f + 2.0f * (1.0f - me->GetProfile()->GetAggression());
// if we are sniping, be very patient
if (me->IsUsingSniperRifle())
chaseTime += 3.0f;
// if we are crouching, be a little patient
else if (me->IsCrouching())
chaseTime += 1.0f;
// if we can't see the enemy, and have either seen him but currently lost sight of him,
// or haven't yet seen him, chase after him (unless we are a sniper)
if (!me->IsEnemyVisible() && (notSeenEnemyTime > chaseTime || !m_haveSeenEnemy))
{
// snipers don't chase their prey - they wait for their prey to come to them
if (me->GetTask() == CCSBot::SNIPING)
{
StopAttacking(me);
return;
}
else
{
// move to last known position of enemy
me->SetTask(CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION, pEnemy);
me->MoveTo(&me->GetLastKnownEnemyPosition());
return;
}
}
// if we can't see our enemy at the moment, and were shot by
// a different visible enemy, engage them instead
const float hurtRecentlyTime = 3.0f;
if (!me->IsEnemyVisible() &&
me->GetTimeSinceAttacked() < hurtRecentlyTime &&
me->GetAttacker() &&
me->GetAttacker() != me->GetEnemy())
{
// if we can see them, attack, otherwise panic
if (me->IsVisible(me->GetAttacker(), CHECK_FOV))
{
me->Attack(me->GetAttacker());
me->PrintIfWatched("Switching targets to retaliate against new attacker!\n");
}
return;
}
if (gpGlobals->time > m_reacquireTimestamp)
me->FireWeaponAtEnemy();
// do dodge behavior
// If sniping or crouching, stand still.
if (m_dodge && !me->IsUsingSniperRifle() && !m_crouchAndHold)
{
Vector toEnemy = pEnemy->pev->origin - me->pev->origin;
float range = toEnemy.Length2D();
const float hysterisRange = 125.0f; // (+/-) m_combatRange
float minRange = me->GetCombatRange() - hysterisRange;
float maxRange = me->GetCombatRange() + hysterisRange;
// move towards (or away from) enemy if we are using a knife, behind a corner, or we aren't very skilled
if (me->GetProfile()->GetSkill() < 0.66f || !me->IsEnemyVisible())
{
if (range > maxRange)
me->MoveForward();
else if (range < minRange)
me->MoveBackward();
}
// don't dodge if enemy is facing away
const float dodgeRange = 2000.0f;
if (range > dodgeRange || !me->IsPlayerFacingMe(pEnemy))
{
m_dodgeState = STEADY_ON;
m_nextDodgeStateTimestamp = 0.0f;
}
else if (gpGlobals->time >= m_nextDodgeStateTimestamp)
{
int next;
// select next dodge state that is different that our current one
do
{
// high-skill bots may jump when first engaging the enemy (if they are moving)
const float jumpChance = 33.3f;
if (m_firstDodge && me->GetProfile()->GetSkill() > 0.5f && RANDOM_FLOAT(0, 100) < jumpChance && !me->IsNotMoving())
next = RANDOM_LONG(0, NUM_ATTACK_STATES - 1);
else
next = RANDOM_LONG(0, NUM_ATTACK_STATES - 2);
}
while (!m_firstDodge && next == m_dodgeState);
m_dodgeState = (DodgeStateType)next;
m_nextDodgeStateTimestamp = gpGlobals->time + RANDOM_FLOAT(0.3f, 1.0f);
m_firstDodge = false;
}
switch (m_dodgeState)
{
case STEADY_ON:
{
break;
}
case SLIDE_LEFT:
{
me->StrafeLeft();
break;
}
case SLIDE_RIGHT:
{
me->StrafeRight();
break;
}
case JUMP:
{
if (me->m_isEnemyVisible)
{
me->Jump();
}
break;
}
}
}
}
// Finish attack
void AttackState::OnExit(CCSBot *me)
{
me->PrintIfWatched("AttackState:OnExit()\n");
m_crouchAndHold = false;
// clear any noises we heard during battle
me->ForgetNoise();
me->ResetStuckMonitor();
// resume our original posture
me->PopPostureContext();
// put shield away
if (me->IsProtectedByShield())
me->SecondaryAttack();
me->StopRapidFire();
me->ClearSurpriseDelay();
}