| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 | 
							- /**
 
-  * [TF2] AFK TFBot
 
-  * 
 
-  * Performs some dirty hacks so clients have an underlying TFBot AI that can be toggled.
 
-  */
 
- #pragma semicolon 1
 
- #include <sourcemod>
 
- #include <sdkhooks>
 
- #include <sdktools>
 
- #include <dhooks>
 
- #pragma newdecls required
 
- #include <stocksoup/color_literals>
 
- #define PLUGIN_VERSION "1.0.0"
 
- public Plugin myinfo = {
 
- 	name = "[TF2] TFAFKBot",
 
- 	author = "nosoop",
 
- 	description = "TFBots conjoined to human players.",
 
- 	version = PLUGIN_VERSION,
 
- 	url = "localhost"
 
- }
 
- Handle g_AllocatePlayerBotEntity;
 
- Handle g_SDKCallPEntityOfEntIndex, g_SDKCallNextBotFakeClient;
 
- Handle g_DHookProcessUsercmd, g_DHookPlayerIsBot, g_DHookNextBotFakeClient;
 
- static bool g_bInBotPhysicsSimulate[MAXPLAYERS + 1];
 
- static bool g_bIsIdleBot[MAXPLAYERS + 1];
 
- public void OnPluginStart() {
 
- 	Handle hGameConf = LoadGameConfigFile("tf2.afkbot");
 
- 	if (!hGameConf) {
 
- 		SetFailState("Failed to load gamedata (tf2.afkbot).");
 
- 	}
 
- 	
 
- 	StartPrepSDKCall(SDKCall_Static);
 
- 	PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "CTFBot::AllocatePlayerEntity()");
 
- 	PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain);
 
- 	PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer);
 
- 	g_AllocatePlayerBotEntity = EndPrepSDKCall();
 
- 	
 
- 	if (!g_AllocatePlayerBotEntity) {
 
- 		SetFailState("Failed to create SDKCall for function CTFBot::AllocatePlayerEntity()");
 
- 	}
 
- 	
 
- 	StartPrepSDKCall(SDKCall_Static);
 
- 	PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "EDICT_NUM()");
 
- 	PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain);
 
- 	PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain);
 
- 	g_SDKCallPEntityOfEntIndex = EndPrepSDKCall();
 
- 	
 
- 	Handle dt_ClientPutInServer = DHookCreateFromConf(hGameConf, "ClientPutInServer()");
 
- 	DHookEnableDetour(dt_ClientPutInServer, false, OnClientPutInServerPre);
 
- 	
 
- 	Handle dt_BotUpdate = DHookCreateFromConf(hGameConf, "NextBotPlayer<CTFPlayer>::Update()");
 
- 	DHookEnableDetour(dt_BotUpdate, false, OnBotUpdate);
 
- 	
 
- 	Handle dt_BotEntPhysicsSimulate = DHookCreateFromConf(hGameConf, "CTFBot::PhysicsSimulate()");
 
- 	DHookEnableDetour(dt_BotEntPhysicsSimulate, false, OnBotEntPhysicsSimulate);
 
- 	DHookEnableDetour(dt_BotEntPhysicsSimulate, true, OnBotEntPhysicsSimulatePost);
 
- 	
 
- 	g_DHookProcessUsercmd = DHookCreateFromConf(hGameConf, "CBasePlayer::ProcessUsercmds()");
 
- 	
 
- 	g_DHookPlayerIsBot = DHookCreateFromConf(hGameConf, "CBasePlayer::IsBot()");
 
- 	g_DHookNextBotFakeClient = DHookCreateFromConf(hGameConf, "NextBotPlayer<CTFPlayer>::IsFakeClient()");
 
- 	
 
- 	StartPrepSDKCall(SDKCall_Player);
 
- 	PrepSDKCall_SetFromConf(hGameConf, SDKConf_Virtual, "NextBotPlayer<CTFPlayer>::IsFakeClient()");
 
- 	PrepSDKCall_SetReturnInfo(SDKType_Bool, SDKPass_Plain);
 
- 	g_SDKCallNextBotFakeClient = EndPrepSDKCall();
 
- 	
 
- 	delete hGameConf;
 
- 	
 
- 	RegConsoleCmd("sm_afkbot", ToggleAFKBot);
 
- }
 
- public void OnPluginEnd() {
 
- 	// re-enable client prediction
 
- 	for (int i = 1; i <= MaxClients; i++) {
 
- 		if (IsClientInGame(i) && !IsFakeClient(i)) {
 
- 			SetClientPrediction(i, true);
 
- 		}
 
- 	}
 
- }
 
- public Action ToggleAFKBot(int client, int argc) {
 
- 	if (!IsPlayerNextBot(client)) {
 
- 		ReplyToCommand(client, "No underlying bot logic available.  Reconnecting...");
 
- 		ClientCommand(client, "retry");
 
- 		return Plugin_Handled;
 
- 	}
 
- 	
 
- 	g_bIsIdleBot[client] = !g_bIsIdleBot[client];
 
- 	
 
- 	SetClientPrediction(client, !g_bIsIdleBot[client]);
 
- 	
 
- 	PrintColoredChatEx(client, CHAT_SOURCE_SELF, COLOR_TEAM ... "Beep boop (%d).",
 
- 			g_bIsIdleBot[client]);
 
- 	
 
- 	return Plugin_Handled;
 
- }
 
- public void OnMapStart() {
 
- 	for (int i = 1; i <= MaxClients; i++) {
 
- 		if (IsClientInGame(i)) {
 
- 			OnClientPutInServer(i);
 
- 		}
 
- 	}
 
- }
 
- /**
 
-  * Intercepts the default player entity allocation step and uses the TFBot allocator instead.
 
-  */
 
- public MRESReturn OnClientPutInServerPre(Handle hParams) {
 
- 	int client = DHookGetParam(hParams, 1);
 
- 	
 
- 	char name[MAX_NAME_LENGTH];
 
- 	DHookGetParamString(hParams, 2, name, sizeof(name));
 
- 	
 
- 	AllocatePlayerBotEntity(client, name);
 
- 	
 
- 	// equivalent to CBasePlayer::SetPlayerName()
 
- 	SetEntPropString(client, Prop_Data, "m_szNetname", name);
 
- 	
 
- 	SetEntityFlags(client, GetEntityFlags(client) & ~FL_FAKECLIENT);
 
- 	
 
- 	PrintToServer("[afkbot] ocpis");
 
- 	
 
- 	return MRES_Supercede;
 
- }
 
- public void OnClientPutInServer(int client) {
 
- 	if (!IsFakeClient(client)) {
 
- 		DHookEntity(g_DHookProcessUsercmd, false, client, .callback = OnPlayerProcessUsercmds);
 
- 		
 
- 		DHookEntity(g_DHookPlayerIsBot, false, client, .callback = OnPlayerIsBot);
 
- 		DHookEntity(g_DHookNextBotFakeClient, false, client, .callback = OnPlayerIsFakeClient);
 
- 		
 
- 		g_bIsIdleBot[client] = false;
 
- 	}
 
- }
 
- /**
 
-  * Disables NextBot updates on human players that aren't configured to be AFK.
 
-  */
 
- public MRESReturn OnBotUpdate(int client) {
 
- 	if (!IsFakeClient(client) && !g_bIsIdleBot[client]) {
 
- 		return MRES_Supercede;
 
- 	}
 
- 	return MRES_Ignored;
 
- }
 
- /**
 
-  * Scoped check to determine if CBasePlayer::ProcessUsercmds() is called from within the context
 
-  * of a bot or if it is not.
 
-  * 
 
-  * Because of the filthy, filthy hacks we're doing to make this work, both bot and player
 
-  * usercmds are called, and without this check, the player gets updated twice as quickly
 
-  * (having interesting side effects in that the player moves much faster).
 
-  */
 
- public MRESReturn OnBotEntPhysicsSimulate(int client) {
 
- 	g_bInBotPhysicsSimulate[client] = true;
 
- 	return MRES_Ignored;
 
- }
 
- public MRESReturn OnBotEntPhysicsSimulatePost(int client) {
 
- 	g_bInBotPhysicsSimulate[client] = false;
 
- 	return MRES_Ignored;
 
- }
 
- /** 
 
-  * Only perform the usercommand processing for bots or not bots, depending on if the player is
 
-  * being controlled by a bot or not.
 
-  * 
 
-  * Dead players always have their usercommands processed so freezecams and respawns work.
 
-  * 
 
-  * Precondition:  OnClientPutInServer() only hooks this on human players.
 
-  */
 
- public MRESReturn OnPlayerProcessUsercmds(int client, Handle hParams) {
 
- 	/**
 
- 	 * drop inputs that occur inside / outside of CTFBot::PhysicsSimulate()
 
- 	 * dependent on whether the player is idle or not
 
- 	 */
 
- 	return (g_bInBotPhysicsSimulate[client] == g_bIsIdleBot[client]) || !IsPlayerAlive(client)?
 
- 			MRES_Ignored : MRES_Supercede;
 
- }
 
- static bool s_bGetTrueFakeClientResult;
 
- stock bool IsPlayerNextBot(int client) {
 
- 	s_bGetTrueFakeClientResult = true;
 
- 	bool result = SDKCall(g_SDKCallNextBotFakeClient, client);
 
- 	s_bGetTrueFakeClientResult = false;
 
- 	return result;
 
- }
 
- /**
 
-  * Overrides the result of NextBotPlayer<CTFPlayer>::IsFakeClient().  This controls the result
 
-  * of `tf_bot_count` and seemingly deals with loadouts.
 
-  * 
 
-  * NextBot logic will only run when this returns `true`.
 
-  * Loadout caching (?) will only occur when this returns `false`, though we can set it to true
 
-  * afterwards without any known consequences.
 
-  * 
 
-  * Precondition:  OnClientPutInServer() only hooks this on human players.
 
-  */
 
- public MRESReturn OnPlayerIsFakeClient(int client, Handle hReturn) {
 
- 	if (s_bGetTrueFakeClientResult) {
 
- 		return MRES_Ignored;
 
- 	}
 
- 	
 
- 	DHookSetReturn(hReturn, g_bIsIdleBot[client]);
 
- 	return MRES_Supercede;
 
- }
 
- /**
 
-  * Overrides the result of CBasePlayer::IsBot().
 
-  * This is checked when updating m_iPing during `CPlayerResource::UpdatePlayerData`.
 
-  */
 
- public MRESReturn OnPlayerIsBot(int client, Handle hReturn) {
 
- 	DHookSetReturn(hReturn, false);
 
- 	return MRES_Supercede;
 
- }
 
- /**
 
-  * Allocates a bot to the player entity.
 
-  * 
 
-  * Uses a semi-filthy hack to get the edict_t* from an entity index, as sdktools/vdecoder.cpp
 
-  * won't let us act on a freed edict (so we pass the edict_t pointer as POD instead).
 
-  * A proper version would actually SDKCall on `server->PEntityOfEntIndex()` or reverse-engineer
 
-  * the function, but ain't nobody got time for that.
 
-  */
 
- void AllocatePlayerBotEntity(int client, const char[] name) {
 
- 	Address pEdict = SDKCall(g_SDKCallPEntityOfEntIndex, client);
 
- 	SDKCall(g_AllocatePlayerBotEntity, pEdict, name);
 
- }
 
- /**
 
-  * Sets client-side prediction on a client.
 
-  */
 
- void SetClientPrediction(int client, bool bPrediction) {
 
- 	// https://github.com/Pelipoika/TF2_Idlebot/blob/master/idlebot.sp
 
- 	FindConVar("sv_client_predict").ReplicateToClient(client, bPrediction? "-1" : "0");
 
- 	SetEntProp(client, Prop_Data, "m_bLagCompensation", bPrediction);
 
- 	SetEntProp(client, Prop_Data, "m_bPredictWeapons", bPrediction);
 
- }
 
 
  |