mirror of
https://github.com/s1lentq/ReGameDLL_CS.git
synced 2024-12-27 23:25:41 +03:00
19714af6e6
minor refactor
565 lines
11 KiB
C++
565 lines
11 KiB
C++
#include "precompiled.h"
|
|
|
|
// Nasty Hack. See client.cpp/ClientCommand()
|
|
const char *BotArgs[4] = {};
|
|
bool UseBotArgs = false;
|
|
|
|
CBot::CBot()
|
|
{
|
|
// the profile will be attached after this instance is constructed
|
|
m_profile = nullptr;
|
|
|
|
// assign this bot a unique ID
|
|
static unsigned int nextID = 1;
|
|
|
|
// wraparound (highly unlikely)
|
|
if (nextID == 0)
|
|
++nextID;
|
|
|
|
m_id = nextID++;
|
|
m_postureStackIndex = 0;
|
|
}
|
|
|
|
// Prepare bot for action
|
|
bool CBot::Initialize(const BotProfile *profile)
|
|
{
|
|
m_profile = profile;
|
|
return true;
|
|
}
|
|
|
|
void CBot::Spawn()
|
|
{
|
|
// Let CBasePlayer set some things up
|
|
CBasePlayer::Spawn();
|
|
|
|
// Make sure everyone knows we are a bot
|
|
pev->flags |= (FL_CLIENT | FL_FAKECLIENT);
|
|
|
|
// Bots use their own thinking mechanism
|
|
SetThink(nullptr);
|
|
pev->nextthink = -1;
|
|
|
|
m_flNextBotThink = gpGlobals->time + g_flBotCommandInterval;
|
|
m_flNextFullBotThink = gpGlobals->time + g_flBotFullThinkInterval;
|
|
m_flPreviousCommandTime = gpGlobals->time;
|
|
|
|
m_isRunning = true;
|
|
m_isCrouching = false;
|
|
m_postureStackIndex = 0;
|
|
|
|
m_jumpTimestamp = 0.0f;
|
|
|
|
// Command interface variable initialization
|
|
ResetCommand();
|
|
|
|
// Allow derived classes to setup at spawn time
|
|
SpawnBot();
|
|
}
|
|
|
|
Vector CBot::GetAutoaimVector(float flDelta)
|
|
{
|
|
UTIL_MakeVectors(pev->v_angle + pev->punchangle);
|
|
return gpGlobals->v_forward;
|
|
}
|
|
|
|
void CBot::BotThink()
|
|
{
|
|
if (gpGlobals->time >= m_flNextBotThink)
|
|
{
|
|
m_flNextBotThink = gpGlobals->time + g_flBotCommandInterval;
|
|
|
|
Upkeep();
|
|
|
|
if (gpGlobals->time >= m_flNextFullBotThink)
|
|
{
|
|
m_flNextFullBotThink = gpGlobals->time + g_flBotFullThinkInterval;
|
|
|
|
ResetCommand();
|
|
Update();
|
|
}
|
|
ExecuteCommand();
|
|
}
|
|
}
|
|
|
|
void CBot::MoveForward()
|
|
{
|
|
m_forwardSpeed = GetMoveSpeed();
|
|
m_buttonFlags |= IN_FORWARD;
|
|
|
|
// make mutually exclusive
|
|
m_buttonFlags &= ~IN_BACK;
|
|
}
|
|
|
|
void CBot::MoveBackward()
|
|
{
|
|
m_forwardSpeed = -GetMoveSpeed();
|
|
m_buttonFlags |= IN_BACK;
|
|
|
|
// make mutually exclusive
|
|
m_buttonFlags &= ~IN_FORWARD;
|
|
}
|
|
|
|
void CBot::StrafeLeft()
|
|
{
|
|
m_strafeSpeed = -GetMoveSpeed();
|
|
m_buttonFlags |= IN_MOVELEFT;
|
|
|
|
// make mutually exclusive
|
|
m_buttonFlags &= ~IN_MOVERIGHT;
|
|
}
|
|
|
|
void CBot::StrafeRight()
|
|
{
|
|
m_strafeSpeed = GetMoveSpeed();
|
|
m_buttonFlags |= IN_MOVERIGHT;
|
|
|
|
// make mutually exclusive
|
|
m_buttonFlags &= ~IN_MOVELEFT;
|
|
}
|
|
|
|
bool CBot::Jump(bool mustJump)
|
|
{
|
|
if (IsJumping() || IsCrouching())
|
|
return false;
|
|
|
|
if (!mustJump)
|
|
{
|
|
const float minJumpInterval = 0.9f; // 1.5f;
|
|
if (gpGlobals->time - m_jumpTimestamp < minJumpInterval)
|
|
return false;
|
|
}
|
|
|
|
// still need sanity check for jumping frequency
|
|
const float sanityInterval = 0.3f;
|
|
if (gpGlobals->time - m_jumpTimestamp < sanityInterval)
|
|
return false;
|
|
|
|
// jump
|
|
m_buttonFlags |= IN_JUMP;
|
|
m_jumpTimestamp = gpGlobals->time;
|
|
return true;
|
|
}
|
|
|
|
// Zero any MoveForward(), Jump(), etc
|
|
void CBot::ClearMovement()
|
|
{
|
|
ResetCommand();
|
|
}
|
|
|
|
// Returns true if we are in the midst of a jump
|
|
bool CBot::IsJumping()
|
|
{
|
|
// if long time after last jump, we can't be jumping
|
|
if (gpGlobals->time - m_jumpTimestamp > 3.0f)
|
|
return false;
|
|
|
|
// if we just jumped, we're still jumping
|
|
if (gpGlobals->time - m_jumpTimestamp < 1.0f)
|
|
return true;
|
|
|
|
// a little after our jump, we're jumping until we hit the ground
|
|
if (pev->flags & FL_ONGROUND)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void CBot::Crouch()
|
|
{
|
|
m_isCrouching = true;
|
|
}
|
|
|
|
void CBot::StandUp()
|
|
{
|
|
m_isCrouching = false;
|
|
}
|
|
|
|
void CBot::UseEnvironment()
|
|
{
|
|
m_buttonFlags |= IN_USE;
|
|
}
|
|
|
|
void CBot::PrimaryAttack()
|
|
{
|
|
m_buttonFlags |= IN_ATTACK;
|
|
}
|
|
|
|
void CBot::ClearPrimaryAttack()
|
|
{
|
|
m_buttonFlags &= ~IN_ATTACK;
|
|
}
|
|
|
|
void CBot::TogglePrimaryAttack()
|
|
{
|
|
m_buttonFlags ^= IN_ATTACK;
|
|
}
|
|
|
|
void CBot::SecondaryAttack()
|
|
{
|
|
m_buttonFlags |= IN_ATTACK2;
|
|
}
|
|
|
|
void CBot::Reload()
|
|
{
|
|
m_buttonFlags |= IN_RELOAD;
|
|
}
|
|
|
|
// Returns ratio of ammo left to max ammo (1 = full clip, 0 = empty)
|
|
float CBot::GetActiveWeaponAmmoRatio() const
|
|
{
|
|
CBasePlayerWeapon *pCurrentWeapon = GetActiveWeapon();
|
|
if (!pCurrentWeapon)
|
|
return 0.0f;
|
|
|
|
// Weapons with no ammo are always full
|
|
if (pCurrentWeapon->m_iClip < 0)
|
|
return 1.0f;
|
|
|
|
return float(pCurrentWeapon->m_iClip) / float(pCurrentWeapon->iMaxClip());
|
|
}
|
|
|
|
// Return true if active weapon has an empty clip
|
|
bool CBot::IsActiveWeaponClipEmpty() const
|
|
{
|
|
CBasePlayerWeapon *pCurrentWeapon = GetActiveWeapon();
|
|
if (pCurrentWeapon && pCurrentWeapon->m_iClip == 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// Return true if active weapon has no ammo at all
|
|
bool CBot::IsActiveWeaponOutOfAmmo() const
|
|
{
|
|
CBasePlayerWeapon *pCurrentWeapon = GetActiveWeapon();
|
|
if (!pCurrentWeapon)
|
|
return true;
|
|
|
|
if (pCurrentWeapon->m_iClip < 0)
|
|
return false;
|
|
|
|
if (pCurrentWeapon->m_iClip == 0 && m_rgAmmo[pCurrentWeapon->m_iPrimaryAmmoType] <= 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// Return true if looking thru weapon's scope
|
|
bool CBot::IsUsingScope() const
|
|
{
|
|
// if our field of view is less than 90, we're looking thru a scope (maybe only true for CS...)
|
|
if (m_iFOV < 90.0f)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void CBot::ExecuteCommand()
|
|
{
|
|
byte adjustedMSec;
|
|
|
|
// Adjust msec to command time interval
|
|
adjustedMSec = ThrottledMsec();
|
|
|
|
if (IsCrouching())
|
|
{
|
|
m_buttonFlags |= IN_DUCK;
|
|
}
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
// don't move if frozen state present
|
|
if (pev->flags & FL_FROZEN)
|
|
{
|
|
adjustedMSec = 0;
|
|
ResetCommand();
|
|
}
|
|
#endif
|
|
|
|
// Run mimic command
|
|
usercmd_t botCmd;
|
|
if (!RunMimicCommand(botCmd))
|
|
{
|
|
botCmd.forwardmove = m_forwardSpeed;
|
|
botCmd.sidemove = m_strafeSpeed;
|
|
botCmd.upmove = m_verticalSpeed;
|
|
botCmd.buttons = m_buttonFlags;
|
|
botCmd.impulse = 0;
|
|
botCmd.viewangles = pev->v_angle;
|
|
}
|
|
|
|
// player model is "munged"
|
|
pev->angles = pev->v_angle;
|
|
pev->angles.x /= -3.0f;
|
|
|
|
// save the command time
|
|
m_flPreviousCommandTime = gpGlobals->time;
|
|
|
|
// Run the command
|
|
PLAYER_RUN_MOVE(edict(), botCmd.viewangles, botCmd.forwardmove, botCmd.sidemove, botCmd.upmove, botCmd.buttons, 0, adjustedMSec);
|
|
}
|
|
|
|
bool CBot::RunMimicCommand(usercmd_t &botCmd)
|
|
{
|
|
#ifdef REGAMEDLL_ADD
|
|
if (cv_bot_mimic.value <= 0)
|
|
return false;
|
|
|
|
if (cv_bot_mimic.value > gpGlobals->maxClients)
|
|
return false;
|
|
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex(cv_bot_mimic.value);
|
|
if (!pPlayer)
|
|
return false;
|
|
|
|
if (!UTIL_IsValidPlayer(pPlayer))
|
|
return false;
|
|
|
|
if (!pPlayer->IsAlive())
|
|
return false;
|
|
|
|
if (pPlayer->IsBot())
|
|
return false;
|
|
|
|
const usercmd_t *ucmd = pPlayer->GetLastUserCommand();
|
|
if (!ucmd)
|
|
return false;
|
|
|
|
botCmd = *ucmd;
|
|
botCmd.viewangles[YAW] += cv_bot_mimic_yaw_offset.value;
|
|
|
|
float mult = 8.0f;
|
|
botCmd.forwardmove *= mult;
|
|
botCmd.sidemove *= mult;
|
|
botCmd.upmove *= mult;
|
|
|
|
pev->fixangle = 0;
|
|
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void CBot::ResetCommand()
|
|
{
|
|
m_forwardSpeed = 0.0f;
|
|
m_strafeSpeed = 0.0f;
|
|
m_verticalSpeed = 0.0f;
|
|
m_buttonFlags = 0;
|
|
}
|
|
|
|
byte CBot::ThrottledMsec() const
|
|
{
|
|
int iNewMsec;
|
|
|
|
// Estimate Msec to use for this command based on time passed from the previous command
|
|
iNewMsec = int((gpGlobals->time - m_flPreviousCommandTime) * 1000);
|
|
|
|
// Doh, bots are going to be slower than they should if this happens.
|
|
// Upgrade that CPU or use less bots!
|
|
if (iNewMsec > 255)
|
|
iNewMsec = 255;
|
|
|
|
return byte(iNewMsec);
|
|
}
|
|
|
|
#ifndef REGAMEDLL_FIXES
|
|
// Do a "client command" - useful for invoking menu choices, etc.
|
|
void CBot::ClientCommand(const char *cmd, const char *arg1, const char *arg2, const char *arg3)
|
|
{
|
|
BotArgs[0] = cmd;
|
|
BotArgs[1] = arg1;
|
|
BotArgs[2] = arg2;
|
|
BotArgs[3] = arg3;
|
|
|
|
UseBotArgs = true;
|
|
::ClientCommand_(ENT(pev));
|
|
UseBotArgs = false;
|
|
}
|
|
#endif
|
|
|
|
// Returns TRUE if given entity is our enemy
|
|
bool CBot::IsEnemy(CBaseEntity *pEntity) const
|
|
{
|
|
// only Players (real and AI) can be enemies
|
|
if (!pEntity->IsPlayer())
|
|
return false;
|
|
|
|
// corpses are no threat
|
|
if (!pEntity->IsAlive())
|
|
return false;
|
|
|
|
CBasePlayer *pPlayer = static_cast<CBasePlayer *>(pEntity);
|
|
|
|
// if they are on our team, they are our friends
|
|
if (BotRelationship(pPlayer) == BOT_TEAMMATE)
|
|
return false;
|
|
|
|
// yep, we hate 'em
|
|
return true;
|
|
}
|
|
|
|
// Return number of enemies left alive
|
|
int CBot::GetEnemiesRemaining() const
|
|
{
|
|
int count = 0;
|
|
for (int i = 1; i <= gpGlobals->maxClients; i++)
|
|
{
|
|
CBaseEntity *pPlayer = UTIL_PlayerByIndex(i);
|
|
|
|
if (!UTIL_IsValidPlayer(pPlayer))
|
|
continue;
|
|
|
|
if (FStrEq(STRING(pPlayer->pev->netname), ""))
|
|
continue;
|
|
|
|
if (!IsEnemy(pPlayer))
|
|
continue;
|
|
|
|
if (!pPlayer->IsAlive())
|
|
continue;
|
|
|
|
count++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
// Return number of friends left alive
|
|
int CBot::GetFriendsRemaining() const
|
|
{
|
|
int count = 0;
|
|
for (int i = 1; i <= gpGlobals->maxClients; i++)
|
|
{
|
|
CBaseEntity *pPlayer = UTIL_PlayerByIndex(i);
|
|
|
|
if (!UTIL_IsValidPlayer(pPlayer))
|
|
continue;
|
|
|
|
if (FStrEq(STRING(pPlayer->pev->netname), ""))
|
|
continue;
|
|
|
|
if (IsEnemy(pPlayer))
|
|
continue;
|
|
|
|
if (!pPlayer->IsAlive())
|
|
continue;
|
|
|
|
if (pPlayer == static_cast<CBaseEntity *>(const_cast<CBot *>(this)))
|
|
continue;
|
|
|
|
count++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
bool CBot::IsLocalPlayerWatchingMe() const
|
|
{
|
|
// avoid crash during spawn
|
|
if (!pev)
|
|
return false;
|
|
|
|
int myIndex = const_cast<CBot *>(this)->entindex();
|
|
|
|
CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
|
|
if (!pPlayer)
|
|
return false;
|
|
|
|
if (((pPlayer->pev->flags & FL_SPECTATOR) || pPlayer->m_iTeam == SPECTATOR) && pPlayer->pev->iuser2 == myIndex)
|
|
{
|
|
switch (pPlayer->pev->iuser1)
|
|
{
|
|
case OBS_CHASE_LOCKED:
|
|
case OBS_CHASE_FREE:
|
|
case OBS_IN_EYE:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
NOXREF void CBot::Print(char *format, ...) const
|
|
{
|
|
va_list varg;
|
|
char buffer[1024];
|
|
|
|
// prefix the message with the bot's name
|
|
Q_snprintf(buffer, sizeof(buffer), "%s: ", STRING(pev->netname));
|
|
SERVER_PRINT(buffer);
|
|
|
|
va_start(varg, format);
|
|
Q_vsnprintf(buffer, sizeof(buffer), format, varg);
|
|
va_end(varg);
|
|
|
|
SERVER_PRINT(buffer);
|
|
}
|
|
|
|
void CBot::PrintIfWatched(char *format, ...) const
|
|
{
|
|
if (!cv_bot_debug.value)
|
|
return;
|
|
|
|
if ((IsLocalPlayerWatchingMe() && (cv_bot_debug.value == 1 || cv_bot_debug.value == 3))
|
|
|| (cv_bot_debug.value == 2 || cv_bot_debug.value == 4))
|
|
{
|
|
va_list varg;
|
|
char buffer[1024];
|
|
|
|
// prefix the message with the bot's name (this can be NULL if bot was just added)
|
|
const char *name = pev ? STRING(pev->netname) : "(NULL pev)";
|
|
Q_snprintf(buffer, sizeof(buffer), "%s: ", name ? name : "(NULL netname)");
|
|
|
|
SERVER_PRINT(buffer);
|
|
|
|
va_start(varg, format);
|
|
Q_vsnprintf(buffer, sizeof(buffer), format, varg);
|
|
va_end(varg);
|
|
|
|
SERVER_PRINT(buffer);
|
|
}
|
|
}
|
|
|
|
ActiveGrenade::ActiveGrenade(int weaponID, CGrenade *grenadeEntity)
|
|
{
|
|
m_id = weaponID;
|
|
m_entity = grenadeEntity;
|
|
m_detonationPosition = grenadeEntity->pev->origin;
|
|
m_dieTimestamp = 0;
|
|
}
|
|
|
|
void ActiveGrenade::OnEntityGone()
|
|
{
|
|
if (m_id == WEAPON_SMOKEGRENADE)
|
|
{
|
|
// smoke lingers after grenade is gone
|
|
const float smokeLingerTime = 4.0f;
|
|
m_dieTimestamp = gpGlobals->time + smokeLingerTime;
|
|
}
|
|
|
|
m_entity = nullptr;
|
|
}
|
|
|
|
void ActiveGrenade::CheckOnEntityGone()
|
|
{
|
|
if (m_dieTimestamp == 0 && !m_entity.IsValid())
|
|
OnEntityGone();
|
|
}
|
|
|
|
bool ActiveGrenade::IsValid() const
|
|
{
|
|
if (!m_entity.IsValid())
|
|
{
|
|
if (gpGlobals->time > m_dieTimestamp)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
const Vector *ActiveGrenade::GetPosition()
|
|
{
|
|
return &m_entity->pev->origin;
|
|
}
|