diff --git a/regamedll/dlls/bot/cs_bot_init.cpp b/regamedll/dlls/bot/cs_bot_init.cpp index 4ed27ac5..a6503e90 100644 --- a/regamedll/dlls/bot/cs_bot_init.cpp +++ b/regamedll/dlls/bot/cs_bot_init.cpp @@ -62,6 +62,8 @@ cvar_t cv_bot_deathmatch = { "bot_deathmatch", "0", FCVAR_SERVER, 0. cvar_t cv_bot_quota_mode = { "bot_quota_mode", "normal", FCVAR_SERVER, 0.0f, nullptr }; cvar_t cv_bot_join_delay = { "bot_join_delay", "0", FCVAR_SERVER, 0.0f, nullptr }; cvar_t cv_bot_freeze = { "bot_freeze", "0", 0, 0.0f, nullptr }; +cvar_t cv_bot_mimic = { "bot_mimic", "0", 0, 0.0f, nullptr }; +cvar_t cv_bot_mimic_yaw_offset = { "bot_mimic_yaw_offset", "0", 0, 0.0f, nullptr }; #else // Migrated to bot_quota_mode, use "match" cvar_t cv_bot_quota_match = { "bot_quota_match", "0", FCVAR_SERVER, 0.0f, nullptr }; @@ -131,6 +133,8 @@ void Bot_RegisterCVars() CVAR_REGISTER(&cv_bot_quota_mode); CVAR_REGISTER(&cv_bot_join_delay); CVAR_REGISTER(&cv_bot_freeze); + CVAR_REGISTER(&cv_bot_mimic); + CVAR_REGISTER(&cv_bot_mimic_yaw_offset); #endif } diff --git a/regamedll/dlls/bot/cs_bot_init.h b/regamedll/dlls/bot/cs_bot_init.h index 4f62b511..8687e3f7 100644 --- a/regamedll/dlls/bot/cs_bot_init.h +++ b/regamedll/dlls/bot/cs_bot_init.h @@ -62,6 +62,8 @@ extern cvar_t cv_bot_deathmatch; extern cvar_t cv_bot_quota_mode; extern cvar_t cv_bot_join_delay; extern cvar_t cv_bot_freeze; +extern cvar_t cv_bot_mimic; +extern cvar_t cv_bot_mimic_yaw_offset; #else extern cvar_t cv_bot_quota_match; #endif diff --git a/regamedll/dlls/bot/cs_bot_manager.cpp b/regamedll/dlls/bot/cs_bot_manager.cpp index 7e923885..d8759560 100644 --- a/regamedll/dlls/bot/cs_bot_manager.cpp +++ b/regamedll/dlls/bot/cs_bot_manager.cpp @@ -758,6 +758,23 @@ void CCSBotManager::ServerCommand(const char *pcmd) BOOL CCSBotManager::ClientCommand(CBasePlayer *pPlayer, const char *pcmd) { +#ifdef REGAMEDLL_ADD + if (pPlayer->IsBot()) + return FALSE; + + if (cv_bot_mimic.value == pPlayer->entindex()) + { + // Bots mimic our client commands + ForEachPlayer([pPlayer, pcmd](CBasePlayer *bot) + { + if (pPlayer != bot && bot->IsBot()) + bot->ClientCommand(pcmd, CMD_ARGV_(1)); + + return true; + }); + } +#endif + return FALSE; } diff --git a/regamedll/dlls/bot/cs_bot_statemachine.cpp b/regamedll/dlls/bot/cs_bot_statemachine.cpp index 32525264..7bb31edd 100644 --- a/regamedll/dlls/bot/cs_bot_statemachine.cpp +++ b/regamedll/dlls/bot/cs_bot_statemachine.cpp @@ -299,6 +299,12 @@ void CCSBot::Attack(CBasePlayer *victim) if (cv_bot_zombie.value != 0.0f) return; +#ifdef REGAMEDLL_ADD + // If mimicing the player, don't attack state + if (cv_bot_mimic.value) + return; +#endif + // cannot attack if we are reloading if (IsActiveWeaponReloading()) return; diff --git a/regamedll/dlls/bot/cs_bot_vision.cpp b/regamedll/dlls/bot/cs_bot_vision.cpp index db9e2143..f7b16acc 100644 --- a/regamedll/dlls/bot/cs_bot_vision.cpp +++ b/regamedll/dlls/bot/cs_bot_vision.cpp @@ -61,6 +61,12 @@ void CCSBot::UpdateLookAngles() float stiffness; float damping; +#ifdef REGAMEDLL_ADD + // If mimicing the player, don't modify the view angles + if (cv_bot_mimic.value > 0) + return; +#endif + // springs are stiffer when attacking, so we can track and move between targets better if (IsAttacking()) { diff --git a/regamedll/dlls/player.cpp b/regamedll/dlls/player.cpp index 52eaeaea..38605eba 100644 --- a/regamedll/dlls/player.cpp +++ b/regamedll/dlls/player.cpp @@ -10717,3 +10717,12 @@ bool CBasePlayer::Kill() return true; } + +const usercmd_t *CBasePlayer::GetLastUserCommand() const +{ +#ifdef REGAMEDLL_API + return CSPlayer()->GetLastUserCommand(); +#else + return nullptr; +#endif +} diff --git a/regamedll/dlls/player.h b/regamedll/dlls/player.h index 82e81aa6..d3d37d3e 100644 --- a/regamedll/dlls/player.h +++ b/regamedll/dlls/player.h @@ -500,6 +500,7 @@ public: void SetClientUserInfoModel(char *infobuffer, char *szNewModel); void SetClientUserInfoModel_api(char *infobuffer, char *szNewModel); void SetNewPlayerModel(const char *modelName); + const usercmd_t *GetLastUserCommand() const; BOOL SwitchWeapon(CBasePlayerItem *pWeapon); void CheckPowerups(); bool CanAffordPrimary(); diff --git a/regamedll/game_shared/bot/bot.cpp b/regamedll/game_shared/bot/bot.cpp index f53266bd..0a9ae70a 100644 --- a/regamedll/game_shared/bot/bot.cpp +++ b/regamedll/game_shared/bot/bot.cpp @@ -261,6 +261,18 @@ void CBot::ExecuteCommand() // Adjust msec to command time interval adjustedMSec = ThrottledMsec(); + // 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; @@ -283,7 +295,49 @@ void CBot::ExecuteCommand() #endif // Run the command - PLAYER_RUN_MOVE(edict(), pev->v_angle, m_forwardSpeed, m_strafeSpeed, m_verticalSpeed, m_buttonFlags, 0, adjustedMSec); + 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() diff --git a/regamedll/game_shared/bot/bot.h b/regamedll/game_shared/bot/bot.h index 6ad3cda1..17cec5a9 100644 --- a/regamedll/game_shared/bot/bot.h +++ b/regamedll/game_shared/bot/bot.h @@ -259,6 +259,8 @@ private: void ResetCommand(); byte ThrottledMsec() const; + bool RunMimicCommand(usercmd_t &botCmd); + // returns current movement speed (for walk/run) float GetMoveSpeed(); diff --git a/regamedll/game_shared/bot/bot_util.h b/regamedll/game_shared/bot/bot_util.h index e7fbd71b..8dfa352d 100644 --- a/regamedll/game_shared/bot/bot_util.h +++ b/regamedll/game_shared/bot/bot_util.h @@ -141,7 +141,7 @@ inline bool IsIntersecting2D(const Vector &startA, const Vector &endA, const Vec // Iterate over all active players in the game, invoking functor on each. // If functor returns false, stop iteration and return false. template -bool ForEachPlayer(Functor &func) +bool ForEachPlayer(Functor func) { for (int i = 1; i <= gpGlobals->maxClients; i++) { diff --git a/regamedll/pm_shared/pm_shared.cpp b/regamedll/pm_shared/pm_shared.cpp index ed681976..b4049556 100644 --- a/regamedll/pm_shared/pm_shared.cpp +++ b/regamedll/pm_shared/pm_shared.cpp @@ -3300,6 +3300,11 @@ void EXT_FUNC __API_HOOK(PM_Move)(struct playermove_s *ppmove, int server) { pmove->friction = 1.0f; } + +#ifdef REGAMEDLL_API + // save the last usercmd + pmoveplayer->SetLastUserCommand(pmove->cmd); +#endif } NOXREF int PM_GetVisEntInfo(int ent) diff --git a/regamedll/public/regamedll/API/CSPlayer.h b/regamedll/public/regamedll/API/CSPlayer.h index 0b867233..f2bfe028 100644 --- a/regamedll/public/regamedll/API/CSPlayer.h +++ b/regamedll/public/regamedll/API/CSPlayer.h @@ -141,6 +141,16 @@ public: EProtectionState GetProtectionState() const; bool CheckActivityInGame(); + const usercmd_t *GetLastUserCommand() const + { + return &m_LastCmd; + } + + void SetLastUserCommand(const usercmd_t &ucmd) + { + m_LastCmd = ucmd; + } + public: char m_szModel[32]; bool m_bForceShowMenu; @@ -175,6 +185,7 @@ public: bool m_bPlayerDominated[MAX_CLIENTS]; // [0-31] array of state per other player whether player is dominating other players int m_iGibDamageThreshold; // negative health to reach to gib player + usercmd_t m_LastCmd; }; // Inlines