/* * * 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. * */ #pragma once #include "gamerules.h" // 30 times per second, just like human clients constexpr float g_flBotCommandInterval = 1.0 / 30.0; // full AI only 10 times per second constexpr float g_flBotFullThinkInterval = 1.0 / 10.0; class BotProfile; template T *CreateBot(const BotProfile *profile) { edict_t *pentBot; if (UTIL_ClientsInGame() >= gpGlobals->maxClients) { CONSOLE_ECHO("Unable to create bot: Server is full (%d/%d clients).\n", UTIL_ClientsInGame(), gpGlobals->maxClients); return nullptr; } char netname[64]; UTIL_ConstructBotNetName(netname, sizeof(netname), profile); pentBot = CREATE_FAKE_CLIENT(netname); if (FNullEnt(pentBot)) { CONSOLE_ECHO("Unable to create bot: pfnCreateFakeClient() returned null.\n"); return nullptr; } else { T *pBot = nullptr; #ifdef REGAMEDLL_FIXES auto name = pentBot->v.netname; Q_memset(&pentBot->v, 0, sizeof(pentBot->v)); // Reset entvars data pentBot->v.netname = name; pentBot->v.flags = FL_FAKECLIENT | FL_CLIENT; pentBot->v.pContainingEntity = pentBot; #endif FREE_PRIVATE(pentBot); pBot = GetClassPtr((T *)VARS(pentBot)); pBot->Initialize(profile); return pBot; } } // The base bot class from which bots for specific games are derived class CBot: public CBasePlayer { public: // constructor initializes all values to zero CBot(); virtual void Spawn(); // invoked when injured by something virtual BOOL TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) { return CBasePlayer::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); } // invoked when killed virtual void Killed(entvars_t *pevAttacker, int iGib) { CBasePlayer::Killed(pevAttacker, iGib); } virtual void Think() {}; virtual BOOL IsBot() { return TRUE; } virtual Vector GetAutoaimVector(float flDelta); // invoked when in contact with a CWeaponBox virtual void OnTouchingWeapon(CWeaponBox *box) {} // prepare bot for action virtual bool Initialize(const BotProfile *profile); virtual void SpawnBot() = 0; // lightweight maintenance, invoked frequently virtual void Upkeep() = 0; // heavyweight algorithms, invoked less often virtual void Update() = 0; virtual void Run(); virtual void Walk(); virtual void Crouch(); virtual void StandUp(); virtual void MoveForward(); virtual void MoveBackward(); virtual void StrafeLeft(); virtual void StrafeRight(); // returns true if jump was started #define MUST_JUMP true virtual bool Jump(bool mustJump = false); // zero any MoveForward(), Jump(), etc virtual void ClearMovement(); // Weapon interface virtual void UseEnvironment(); virtual void PrimaryAttack(); virtual void ClearPrimaryAttack(); virtual void TogglePrimaryAttack(); virtual void SecondaryAttack(); virtual void Reload(); // invoked when event occurs in the game (some events have NULL entities) virtual void OnEvent(GameEventType event, CBaseEntity *pEntity = nullptr, CBaseEntity *pOther = nullptr) {}; // return true if we can see the point virtual bool IsVisible(const Vector *pos, bool testFOV = false) const = 0; // return true if we can see any part of the player virtual bool IsVisible(CBasePlayer *pPlayer, bool testFOV = false, unsigned char *visParts = nullptr) const = 0; enum VisiblePartType : uint8 { NONE = 0x00, CHEST = 0x01, HEAD = 0x02, LEFT_SIDE = 0x04, // the left side of the object from our point of view (not their left side) RIGHT_SIDE = 0x08, // the right side of the object from our point of view (not their right side) FEET = 0x10 }; // if enemy is visible, return the part we see virtual bool IsEnemyPartVisible(VisiblePartType part) const = 0; // return true if player is facing towards us virtual bool IsPlayerFacingMe(CBasePlayer *pOther) const; // returns true if other player is pointing right at us virtual bool IsPlayerLookingAtMe(CBasePlayer *pOther) const; virtual void ExecuteCommand(); virtual void SetModel(const char *modelName); public: // return bot's unique ID unsigned int GetID() const { return m_id; } bool IsRunning() const { return m_isRunning; } bool IsCrouching() const { return m_isCrouching; } // push the current posture context onto the top of the stack void PushPostureContext(); // restore the posture context to the next context on the stack void PopPostureContext(); bool IsJumping(); // return time last jump began float GetJumpTimestamp() const { return m_jumpTimestamp; } // returns ratio of ammo left to max ammo (1 = full clip, 0 = empty) float GetActiveWeaponAmmoRatio() const; // return true if active weapon has any empty clip bool IsActiveWeaponClipEmpty() const; // return true if active weapon has no ammo at all bool IsActiveWeaponOutOfAmmo() const; // is the weapon in the middle of a reload bool IsActiveWeaponReloading() const; // return true if active weapon's bullet spray has become large and inaccurate bool IsActiveWeaponRecoilHigh() const; // return the weapon the bot is currently using CBasePlayerWeapon *GetActiveWeapon() const; // return true if looking thru weapon's scope bool IsUsingScope() const; // returns TRUE if given entity is our enemy bool IsEnemy(CBaseEntity *pEntity) const; // return number of enemies left alive int GetEnemiesRemaining() const; // return number of friends left alive int GetFriendsRemaining() const; // return true if local player is observing this bot bool IsLocalPlayerWatchingMe() const; // output message to console void Print(char *format,...) const; // output message to console if we are being watched by the local player void PrintIfWatched(char *format,...) const; void BotThink(); #ifdef REGAMEDLL_FIXES BOOL IsNetClient() { return FALSE; } #else // The ambiguous function because there is a virtual function in inherited classes. bool IsNetClient() const { return false; } int Save(CSave &save) const; int Restore(CRestore &restore) const; #endif // return our personality profile const BotProfile *GetProfile() const { return m_profile; } enum BotRelationshipTeam: uint8 { BOT_TEAMMATE = 0, BOT_ENEMY }; BotRelationshipTeam BotRelationship(CBasePlayer *pTarget) const; protected: #ifndef REGAMEDLL_FIXES // Do a "client command" - useful for invoking menu choices, etc. void ClientCommand(const char *cmd, const char *arg1 = nullptr, const char *arg2 = nullptr, const char *arg3 = nullptr); #endif // the "personality" profile of this bot const BotProfile *m_profile; private: void ResetCommand(); byte ThrottledMsec() const; // returns current movement speed (for walk/run) float GetMoveSpeed(); // unique bot ID unsigned int m_id; // Think mechanism variables float m_flNextBotThink; float m_flNextFullBotThink; // Command interface variables float m_flPreviousCommandTime; // run/walk mode bool m_isRunning; // true if crouching (ducking) bool m_isCrouching; float m_forwardSpeed; float m_strafeSpeed; float m_verticalSpeed; // bitfield of movement buttons unsigned short m_buttonFlags; // time when we last began a jump float m_jumpTimestamp; // the PostureContext represents the current settings of walking and crouching struct PostureContext { bool isRunning; bool isCrouching; }; enum { MAX_POSTURE_STACK = 8 }; PostureContext m_postureStack[MAX_POSTURE_STACK]; // index of top of stack int m_postureStackIndex; }; inline void CBot::SetModel(const char *modelName) { SET_CLIENT_KEY_VALUE(entindex(), GET_INFO_BUFFER(edict()), "model", (char *)modelName); } inline float CBot::GetMoveSpeed() { if (m_isRunning || m_isCrouching) return pev->maxspeed; return 0.4f * pev->maxspeed; } inline void CBot::Run() { m_isRunning = true; } inline void CBot::Walk() { m_isRunning = false; } inline CBasePlayerWeapon *CBot::GetActiveWeapon() const { return static_cast(m_pActiveItem); } inline bool CBot::IsActiveWeaponReloading() const { CBasePlayerWeapon *pCurrentWeapon = GetActiveWeapon(); if (!pCurrentWeapon) return false; return (pCurrentWeapon->m_fInReload || pCurrentWeapon->m_fInSpecialReload) != 0; } inline bool CBot::IsActiveWeaponRecoilHigh() const { CBasePlayerWeapon *pCurrentWeapon = GetActiveWeapon(); if (pCurrentWeapon) { const float highRecoil = 0.4f; return (pCurrentWeapon->m_flAccuracy > highRecoil) != 0; } return false; } inline void CBot::PushPostureContext() { if (m_postureStackIndex == MAX_POSTURE_STACK) { if (pev) { PrintIfWatched("PushPostureContext() overflow error!\n"); } return; } m_postureStack[m_postureStackIndex].isRunning = m_isRunning; m_postureStack[m_postureStackIndex].isCrouching = m_isCrouching; m_postureStackIndex++; } inline void CBot::PopPostureContext() { if (m_postureStackIndex == 0) { if (pev) { PrintIfWatched("PopPostureContext() underflow error!\n"); } m_isRunning = true; m_isCrouching = false; return; } m_postureStackIndex--; m_isRunning = m_postureStack[m_postureStackIndex].isRunning; m_isCrouching = m_postureStack[m_postureStackIndex].isCrouching; } inline bool CBot::IsPlayerFacingMe(CBasePlayer *pOther) const { Vector toOther = pOther->pev->origin - pev->origin; UTIL_MakeVectors(pOther->pev->v_angle + pOther->pev->punchangle); Vector otherDir = gpGlobals->v_forward; if (otherDir.x * toOther.x + otherDir.y * toOther.y < 0.0f) return true; return false; } inline bool CBot::IsPlayerLookingAtMe(CBasePlayer *pOther) const { Vector toOther = pOther->pev->origin - pev->origin; toOther.NormalizeInPlace(); UTIL_MakeVectors(pOther->pev->v_angle + pOther->pev->punchangle); Vector otherDir = gpGlobals->v_forward; const float lookAtCos = 0.9f; if (otherDir.x * toOther.x + otherDir.y * toOther.y < -lookAtCos) { Vector vec(pOther->EyePosition()); if (IsVisible(&vec)) return true; } return false; } inline CBot::BotRelationshipTeam CBot::BotRelationship(CBasePlayer *pTarget) const { #ifdef REGAMEDLL_ADD if (CSGameRules()->IsFreeForAll()) return BOT_ENEMY; #endif return pTarget->m_iTeam == m_iTeam ? BOT_TEAMMATE : BOT_ENEMY; } extern const char *BotArgs[4]; extern bool UseBotArgs; extern bool AreBotsAllowed();