123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330 |
- #pragma semicolon 1
- #include <sourcemod>
- #include <sdktools>
- #include <tf2>
- #include <tf2_stocks>
- #define PLUGIN_VERSION "1.3.4"
- public Plugin:myinfo = {
- name = "[TF2] Bot Manager",
- author = "nosoop (forked from Dr. McKay)",
- description = "Allows for customization of TFBots",
- version = PLUGIN_VERSION,
- url = "http://csrd.science"
- };
- new Handle:cvarBotQuota;
- new Handle:cvarBotJoinAfterPlayer;
- new Handle:cvarGameLogic;
- new Handle:cvarSupportedMap;
- new Handle:cvarOnTeamsOnly;
- new Handle:tf_bot_quota;
- new Handle:joiningBots;
- new Handle:fwdBotAdd;
- new Handle:fwdBotKick;
- public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max) {
- decl String:game[64];
- GetGameFolderName(game, sizeof(game));
- if(!StrEqual(game, "tf")) {
- strcopy(error, err_max, "Bot Manager only works on Team Fortress 2");
- return APLRes_Failure;
- }
-
- RegPluginLibrary("botmanager");
- return APLRes_Success;
- }
- public OnPluginStart() {
- cvarBotQuota = CreateConVar("sm_bot_quota", "0", "Number of players to keep in the server");
- cvarBotJoinAfterPlayer = CreateConVar("sm_bot_join_after_player", "1", "If nonzero, bots wait until a player joins before entering the game.");
- cvarGameLogic = CreateConVar("sm_bot_game_logic", "1", "0 = use plugin logic when assigning bots, 1 = use game logic");
- cvarSupportedMap = CreateConVar("sm_bot_supported_map", "1", "If nonzero, bots will only be added on maps that have nav files");
- cvarOnTeamsOnly = CreateConVar("sm_bot_on_team_only", "1", "If nonzero, players will only be considered \"in-game\" if they're on a team for purposes of determining the bot count");
-
- tf_bot_quota = FindConVar("tf_bot_quota");
-
- HookEvent("player_connect_client", Event_PlayerConnect, EventHookMode_Pre);
- HookEvent("player_disconnect", Event_PlayerDisconnect, EventHookMode_Pre);
- HookEvent("player_team", Event_PlayerTeam, EventHookMode_Pre);
-
- joiningBots = CreateArray();
-
- fwdBotAdd = CreateGlobalForward("Bot_OnBotAdd", ET_Single, Param_CellByRef, Param_CellByRef, Param_CellByRef, Param_String);
- fwdBotKick = CreateGlobalForward("Bot_OnBotKick", ET_Single, Param_CellByRef);
-
- new Handle:buffer = FindConVar("tf_bot_quota_mode");
- SetConVarString(buffer, "normal");
- HookConVarChange(buffer, OnConVarChange);
- buffer = FindConVar("tf_bot_join_after_player");
- SetConVarInt(buffer, 0);
- HookConVarChange(buffer, OnConVarChange);
- }
- public OnConVarChange(Handle:convar, const String:oldValue[], const String:newValue[]) {
- decl String:name[64];
- GetConVarName(convar, name, sizeof(name));
- if(StrEqual(name, "tf_bot_quota_mode")) {
- LogMessage("tf_bot_quota_mode cannot be changed while Bot Manager is running. tf_bot_quota_mode set to \"normal\".");
- SetConVarString(convar, "normal");
- } else if(StrEqual(name, "tf_bot_join_after_player")) {
- LogMessage("tf_bot_join_after_player cannot be changed while Bot Manager is running. tf_bot_join_after_player set to \"0\". Use sm_bot_join_after_player for similar functionality.");
- SetConVarInt(convar, 0);
- }
- }
- public OnConfigsExecuted() {
- decl String:buffer[64];
- GetCurrentMap(buffer, sizeof(buffer));
- Format(buffer, sizeof(buffer), "maps/%s.nav", buffer);
- if(FileExists(buffer, true) || !GetConVarBool(cvarSupportedMap)) {
- CreateTimer(0.1, Timer_CheckBotNum, _, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE);
- } else {
- LogMessage("Bots are not supported on this map. Bot Manager disabled.");
- }
- }
- public OnMapEnd() {
- SetConVarInt(tf_bot_quota, 0); // Prevents an issue that happens at mapchange
- }
- public Action:Timer_CheckBotNum(Handle:timer) {
- if (GameRules_GetProp("m_nGameType") == 4) {
- // suspend bot checks during an active arena round
- switch (GameRules_GetRoundState()) {
- case RoundState_Stalemate, RoundState_TeamWin: {
- return Plugin_Continue;
- }
- }
- }
-
- new clients = GetValidClientCount();
- new actual = GetValidClientCount(false);
- new bots = GetBotCount();
- new realClients = clients - bots;
- if(realClients == 0 && GetConVarBool(cvarBotJoinAfterPlayer)) {
- if(bots > 0) {
- RemoveBot();
- }
- return Plugin_Continue;
- }
- if(GetConVarInt(cvarBotQuota) >= MaxClients) {
- LogMessage("sm_bot_quota cannot be greater than or equal to maxplayers. Setting sm_bot_quota to \"%d\".", MaxClients - 1);
- SetConVarInt(cvarBotQuota, MaxClients - 1);
- }
- if(clients < GetConVarInt(cvarBotQuota) && actual < (MaxClients - 1)) {
- AddBot();
- } else if(clients > GetConVarInt(cvarBotQuota) && bots > 0) {
- RemoveBot();
- }
- return Plugin_Continue;
- }
- GetValidClientCount(bool:excludeTeamsOnly = true) {
- new count = 0;
- for(new i = 1; i <= MaxClients; i++) {
- if(!IsClientInGame(i) || IsClientSourceTV(i) || IsClientReplay(i)) {
- continue;
- }
- // arena mode hacks
- if(excludeTeamsOnly && GetConVarBool(cvarOnTeamsOnly) && (GetClientTeam(i) <= 1 || (GameRules_GetProp("m_nGameType") == 4 && GetEntProp(i, Prop_Send, "m_bArenaSpectator")) )) {
- continue;
- }
- count++;
- }
- return count;
- }
- GetBotCount() {
- new count = 0;
- for(new i = 1; i <= MaxClients; i++) {
- if(IsClientInGame(i) && !IsClientSourceTV(i) && !IsClientReplay(i) && IsFakeClient(i)) {
- count++;
- }
- }
- return count;
- }
- AddBot() {
- new TFTeam:team = TFTeam_Unassigned;
-
- if(!GetConVarBool(cvarGameLogic)) {
- if(GetTeamClientCount(2) < GetTeamClientCount(3)) {
- team = TFTeam_Red;
- } else {
- team = TFTeam_Blue;
- }
- }
-
- new TFClassType:class = TFClass_Unknown;
-
- if(!GetConVarBool(cvarGameLogic)) {
- int numClass[TFClassType];
- GetClassCounts(team, numClass);
- if(!numClass[TFClass_Medic]) {
- class = TFClass_Medic;
- } else {
- static TFClassType iter[] = { TFClass_Scout, TFClass_Soldier, TFClass_Pyro,
- TFClass_DemoMan, TFClass_Heavy, TFClass_Engineer, TFClass_Sniper,
- TFClass_Spy };
-
- TFClassType lowest = TFClass_Scout;
- for (int i = 1; i < sizeof(iter); i++) {
- if (numClass[ iter[i] ] < numClass[lowest]) {
- lowest = iter[i];
- }
- }
- class = lowest;
- }
- }
-
- new difficulty = -1;
- new String:name[MAX_NAME_LENGTH];
-
- Call_StartForward(fwdBotAdd);
- Call_PushCellRef(class);
- Call_PushCellRef(team);
- Call_PushCellRef(difficulty);
- Call_PushStringEx(name, sizeof(name), SM_PARAM_STRING_UTF8|SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK);
- Call_Finish();
-
- decl String:strDifficulty[16], String:strTeam[16], String:strClass[16];
-
- switch(difficulty) {
- case 0: Format(strDifficulty, sizeof(strDifficulty), "easy");
- case 1: Format(strDifficulty, sizeof(strDifficulty), "normal");
- case 2: Format(strDifficulty, sizeof(strDifficulty), "hard");
- case 3: Format(strDifficulty, sizeof(strDifficulty), "expert");
- default: Format(strDifficulty, sizeof(strDifficulty), "");
- }
-
- switch(team) {
- case TFTeam_Red: Format(strTeam, sizeof(strTeam), "red");
- case TFTeam_Blue: Format(strTeam, sizeof(strTeam), "blue");
- default: Format(strTeam, sizeof(strTeam), "");
- }
-
- switch(class) {
- case TFClass_Scout: Format(strClass, sizeof(strClass), "Scout");
- case TFClass_Soldier: Format(strClass, sizeof(strClass), "Soldier");
- case TFClass_Pyro: Format(strClass, sizeof(strClass), "Pyro");
- case TFClass_DemoMan: Format(strClass, sizeof(strClass), "Demoman");
- case TFClass_Heavy: Format(strClass, sizeof(strClass), "HeavyWeapons");
- case TFClass_Engineer: Format(strClass, sizeof(strClass), "Engineer");
- case TFClass_Medic: Format(strClass, sizeof(strClass), "Medic");
- case TFClass_Sniper: Format(strClass, sizeof(strClass), "Sniper");
- case TFClass_Spy: Format(strClass, sizeof(strClass), "Spy");
- default: Format(strClass, sizeof(strClass), "");
- }
-
- char quotedName[MAX_NAME_LENGTH + 2];
-
- ReplaceString(name, sizeof(name), "\"", "");
- if (strlen(name)) {
- Format(quotedName, sizeof(quotedName), "\"%s\"", name);
- }
-
- ServerCommand("tf_bot_add %s %s %s %s", strDifficulty, strTeam, strClass, quotedName); // count class team difficulty name (any order)
- }
- GetClassCounts(TFTeam:team, int numClass[TFClassType]) {
- for(new i = 1; i <= MaxClients; i++) {
- if(!IsClientInGame(i) || TFTeam:GetClientTeam(i) != team) {
- continue;
- }
- numClass[ TF2_GetPlayerClass(i) ]++;
- }
- }
- RemoveBot() {
- new teamToKick;
- if(GetTeamClientCount(2) > GetTeamClientCount(3)) {
- teamToKick = 2;
- } else if(GetTeamClientCount(2) < GetTeamClientCount(3)) {
- teamToKick = 3;
- } else {
- teamToKick = GetRandomInt(2, 3);
- }
-
- new Handle:bots = CreateArray();
- for(new i = 1; i <= MaxClients; i++) {
- if(IsClientConnected(i) && !IsClientSourceTV(i) && !IsClientReplay(i) && IsFakeClient(i) && GetClientTeam(i) == teamToKick) {
- PushArrayCell(bots, i);
- }
- }
-
- if(GetArraySize(bots) == 0) {
- CloseHandle(bots);
- return;
- }
-
- new bot = GetArrayCell(bots, GetRandomInt(0, GetArraySize(bots) - 1));
- CloseHandle(bots);
-
- Call_StartForward(fwdBotKick);
- Call_PushCellRef(bot);
- Call_Finish();
-
- ServerCommand("tf_bot_kick \"%N\"", bot);
- }
- public Event_PlayerConnect(Handle:event, const String:name[], bool:dontBroadcast) {
- if(GetEventBool(event, "bot")) {
- PushArrayCell(joiningBots, GetEventInt(event, "userid"));
- SetEventBroadcast(event, true);
-
- // more arena hacks
- ServerCommand("namelockid %d 1", GetEventInt(event, "userid"));
- }
- }
- public Event_PlayerDisconnect(Handle:event, const String:name[], bool:dontBroadcast) {
- new client = GetClientOfUserId(GetEventInt(event, "userid"));
- if(client == 0) {
- return;
- }
- if(IsFakeClient(client)) {
- SetEventBroadcast(event, true);
- PrintToChatAll("\x01BOT \x07%06X%N \x01has left the game", GetTeamColor(GetClientTeam(client)), client);
- }
- }
- public Event_PlayerTeam(Handle:event, const String:name[], bool:dontBroadcast) {
- new client = GetClientOfUserId(GetEventInt(event, "userid"));
- if(client == 0) {
- return;
- }
- if(IsFakeClient(client)) {
- SetEventBroadcast(event, true);
- new pos;
- if((pos = FindValueInArray(joiningBots, GetClientUserId(client))) != -1) {
- RemoveFromArray(joiningBots, pos);
- PrintToServer("BOT %N has joined the game", client);
- PrintToChatAll("\x01BOT \x07%06X%N \x01has joined the game", GetTeamColor(GetEventInt(event, "team")), client);
- }
- }
- }
- GetTeamColor(team) {
- new value;
- switch(team) {
- case 1: {
- value = 0xCCCCCC;
- }
- case 2: {
- value = 0xFF4040;
- }
- case 3: {
- value = 0x99CCFF;
- }
- default: {
- value = 0x3EFF3E;
- }
- }
- return value;
- }
|