|
@@ -5,31 +5,32 @@
|
|
|
#include <tf2>
|
|
|
#include <tf2_stocks>
|
|
|
|
|
|
-#define PLUGIN_VERSION "1.3.4"
|
|
|
+#pragma newdecls required
|
|
|
|
|
|
-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"
|
|
|
+#define PLUGIN_VERSION "1.3.5"
|
|
|
+
|
|
|
+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;
|
|
|
+ConVar cvarBotQuota;
|
|
|
+ConVar cvarBotJoinAfterPlayer;
|
|
|
+ConVar cvarGameLogic;
|
|
|
+ConVar cvarSupportedMap;
|
|
|
+ConVar cvarOnTeamsOnly;
|
|
|
|
|
|
-new Handle:tf_bot_quota;
|
|
|
+ConVar tf_bot_quota;
|
|
|
|
|
|
-new Handle:joiningBots;
|
|
|
+ArrayList joiningBots; // userid
|
|
|
|
|
|
-new Handle:fwdBotAdd;
|
|
|
-new Handle:fwdBotKick;
|
|
|
+Handle fwdBotAdd, fwdBotKick;
|
|
|
|
|
|
-public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max) {
|
|
|
- decl String:game[64];
|
|
|
+public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) {
|
|
|
+ char game[64];
|
|
|
GetGameFolderName(game, sizeof(game));
|
|
|
if(!StrEqual(game, "tf")) {
|
|
|
strcopy(error, err_max, "Bot Manager only works on Team Fortress 2");
|
|
@@ -40,12 +41,17 @@ public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max)
|
|
|
return APLRes_Success;
|
|
|
}
|
|
|
|
|
|
-public OnPluginStart() {
|
|
|
+public void 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");
|
|
|
+ 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");
|
|
|
|
|
@@ -53,47 +59,49 @@ public OnPluginStart() {
|
|
|
HookEvent("player_disconnect", Event_PlayerDisconnect, EventHookMode_Pre);
|
|
|
HookEvent("player_team", Event_PlayerTeam, EventHookMode_Pre);
|
|
|
|
|
|
- joiningBots = CreateArray();
|
|
|
+ joiningBots = new ArrayList();
|
|
|
|
|
|
- fwdBotAdd = CreateGlobalForward("Bot_OnBotAdd", ET_Single, Param_CellByRef, Param_CellByRef, Param_CellByRef, Param_String);
|
|
|
+ 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);
|
|
|
+ ConVar cvar = FindConVar("tf_bot_quota_mode");
|
|
|
+ cvar.SetString("normal");
|
|
|
+ cvar.AddChangeHook(OnConVarChange);
|
|
|
+
|
|
|
+ cvar = FindConVar("tf_bot_join_after_player");
|
|
|
+ cvar.IntValue = 0;
|
|
|
+ cvar.AddChangeHook(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")) {
|
|
|
+public void OnConVarChange(ConVar convar, const char[] oldValue, const char[] newValue) {
|
|
|
+ char name[64];
|
|
|
+ convar.GetName(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")) {
|
|
|
+ } 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];
|
|
|
+public void OnConfigsExecuted() {
|
|
|
+ char 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);
|
|
|
+ if (FileExists(buffer, true) || !cvarSupportedMap.BoolValue) {
|
|
|
+ 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 void OnMapEnd() {
|
|
|
+ tf_bot_quota.IntValue = 0; // Prevents an issue that happens at mapchange
|
|
|
}
|
|
|
|
|
|
-public Action:Timer_CheckBotNum(Handle:timer) {
|
|
|
+public Action Timer_CheckBotNum(Handle timer) {
|
|
|
if (GameRules_GetProp("m_nGameType") == 4) {
|
|
|
// suspend bot checks during an active arena round
|
|
|
switch (GameRules_GetRoundState()) {
|
|
@@ -103,36 +111,37 @@ public Action:Timer_CheckBotNum(Handle:timer) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- new clients = GetValidClientCount();
|
|
|
- new actual = GetValidClientCount(false);
|
|
|
- new bots = GetBotCount();
|
|
|
- new realClients = clients - bots;
|
|
|
- if(realClients == 0 && GetConVarBool(cvarBotJoinAfterPlayer)) {
|
|
|
+ int clients = GetValidClientCount();
|
|
|
+ int actual = GetValidClientCount(false);
|
|
|
+ int bots = GetBotCount();
|
|
|
+ int realClients = clients - bots;
|
|
|
+ if (realClients == 0 && cvarBotJoinAfterPlayer.BoolValue) {
|
|
|
if(bots > 0) {
|
|
|
RemoveBot();
|
|
|
}
|
|
|
return Plugin_Continue;
|
|
|
}
|
|
|
- if(GetConVarInt(cvarBotQuota) >= MaxClients) {
|
|
|
+ if (cvarBotQuota.IntValue >= 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);
|
|
|
+ cvarBotQuota.IntValue = MaxClients - 1;
|
|
|
}
|
|
|
- if(clients < GetConVarInt(cvarBotQuota) && actual < (MaxClients - 1)) {
|
|
|
+ if (clients < cvarBotQuota.IntValue && actual < (MaxClients - 1)) {
|
|
|
AddBot();
|
|
|
- } else if(clients > GetConVarInt(cvarBotQuota) && bots > 0) {
|
|
|
+ } else if (clients > cvarBotQuota.IntValue && 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)) {
|
|
|
+int GetValidClientCount(bool excludeTeamsOnly = true) {
|
|
|
+ int count = 0;
|
|
|
+ for (int 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")) )) {
|
|
|
+ if (excludeTeamsOnly && cvarOnTeamsOnly.BoolValue && (GetClientTeam(i) <= 1
|
|
|
+ || (GameRules_GetProp("m_nGameType") == 4 && GetEntProp(i, Prop_Send, "m_bArenaSpectator")) )) {
|
|
|
continue;
|
|
|
}
|
|
|
count++;
|
|
@@ -140,30 +149,30 @@ GetValidClientCount(bool:excludeTeamsOnly = true) {
|
|
|
return count;
|
|
|
}
|
|
|
|
|
|
-GetBotCount() {
|
|
|
- new count = 0;
|
|
|
- for(new i = 1; i <= MaxClients; i++) {
|
|
|
- if(IsClientInGame(i) && !IsClientSourceTV(i) && !IsClientReplay(i) && IsFakeClient(i)) {
|
|
|
+int GetBotCount() {
|
|
|
+ int count = 0;
|
|
|
+ for (int i = 1; i <= MaxClients; i++) {
|
|
|
+ if (IsClientInGame(i) && !IsClientSourceTV(i) && !IsClientReplay(i) && IsFakeClient(i)) {
|
|
|
count++;
|
|
|
}
|
|
|
}
|
|
|
return count;
|
|
|
}
|
|
|
|
|
|
-AddBot() {
|
|
|
- new TFTeam:team = TFTeam_Unassigned;
|
|
|
+void AddBot() {
|
|
|
+ TFTeam team = TFTeam_Unassigned;
|
|
|
|
|
|
- if(!GetConVarBool(cvarGameLogic)) {
|
|
|
- if(GetTeamClientCount(2) < GetTeamClientCount(3)) {
|
|
|
+ if (!cvarGameLogic.BoolValue) {
|
|
|
+ if (GetTeamClientCount(2) < GetTeamClientCount(3)) {
|
|
|
team = TFTeam_Red;
|
|
|
} else {
|
|
|
team = TFTeam_Blue;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- new TFClassType:class = TFClass_Unknown;
|
|
|
+ TFClassType class = TFClass_Unknown;
|
|
|
|
|
|
- if(!GetConVarBool(cvarGameLogic)) {
|
|
|
+ if (!cvarGameLogic.BoolValue) {
|
|
|
int numClass[TFClassType];
|
|
|
GetClassCounts(team, numClass);
|
|
|
if(!numClass[TFClass_Medic]) {
|
|
@@ -183,8 +192,8 @@ AddBot() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- new difficulty = -1;
|
|
|
- new String:name[MAX_NAME_LENGTH];
|
|
|
+ int difficulty = -1;
|
|
|
+ char name[MAX_NAME_LENGTH];
|
|
|
|
|
|
Call_StartForward(fwdBotAdd);
|
|
|
Call_PushCellRef(class);
|
|
@@ -193,9 +202,9 @@ AddBot() {
|
|
|
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];
|
|
|
+ char strDifficulty[16], strTeam[16], strClass[16];
|
|
|
|
|
|
- switch(difficulty) {
|
|
|
+ switch (difficulty) {
|
|
|
case 0: Format(strDifficulty, sizeof(strDifficulty), "easy");
|
|
|
case 1: Format(strDifficulty, sizeof(strDifficulty), "normal");
|
|
|
case 2: Format(strDifficulty, sizeof(strDifficulty), "hard");
|
|
@@ -203,13 +212,13 @@ AddBot() {
|
|
|
default: Format(strDifficulty, sizeof(strDifficulty), "");
|
|
|
}
|
|
|
|
|
|
- switch(team) {
|
|
|
+ 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) {
|
|
|
+ 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");
|
|
@@ -232,39 +241,40 @@ AddBot() {
|
|
|
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) {
|
|
|
+void GetClassCounts(TFTeam team, int numClass[TFClassType]) {
|
|
|
+ for (int i = 1; i <= MaxClients; i++) {
|
|
|
+ if (!IsClientInGame(i) || TF2_GetClientTeam(i) != team) {
|
|
|
continue;
|
|
|
}
|
|
|
numClass[ TF2_GetPlayerClass(i) ]++;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-RemoveBot() {
|
|
|
- new teamToKick;
|
|
|
- if(GetTeamClientCount(2) > GetTeamClientCount(3)) {
|
|
|
+void RemoveBot() {
|
|
|
+ int teamToKick;
|
|
|
+ if (GetTeamClientCount(2) > GetTeamClientCount(3)) {
|
|
|
teamToKick = 2;
|
|
|
- } else if(GetTeamClientCount(2) < GetTeamClientCount(3)) {
|
|
|
+ } 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);
|
|
|
+ ArrayList bots = CreateArray();
|
|
|
+ for (int i = 1; i <= MaxClients; i++) {
|
|
|
+ if (IsClientConnected(i) && !IsClientSourceTV(i) && !IsClientReplay(i) &&
|
|
|
+ IsFakeClient(i) && GetClientTeam(i) == teamToKick) {
|
|
|
+ bots.Push(i);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if(GetArraySize(bots) == 0) {
|
|
|
- CloseHandle(bots);
|
|
|
+ if (!bots.Length) {
|
|
|
+ delete bots;
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- new bot = GetArrayCell(bots, GetRandomInt(0, GetArraySize(bots) - 1));
|
|
|
- CloseHandle(bots);
|
|
|
+ int bot = bots.Get(GetRandomInt(0, bots.Length - 1));
|
|
|
+ delete bots;
|
|
|
|
|
|
Call_StartForward(fwdBotKick);
|
|
|
Call_PushCellRef(bot);
|
|
@@ -273,58 +283,60 @@ RemoveBot() {
|
|
|
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);
|
|
|
+public Action Event_PlayerConnect(Event event, const char[] name, bool dontBroadcast) {
|
|
|
+ if (event.GetBool("bot")) {
|
|
|
+ joiningBots.Push(event.GetInt("userid"));
|
|
|
+ event.BroadcastDisabled = true;
|
|
|
|
|
|
// more arena hacks
|
|
|
- ServerCommand("namelockid %d 1", GetEventInt(event, "userid"));
|
|
|
+ ServerCommand("namelockid %d 1", event.GetInt("userid"));
|
|
|
}
|
|
|
+ return Plugin_Continue;
|
|
|
}
|
|
|
|
|
|
-public Event_PlayerDisconnect(Handle:event, const String:name[], bool:dontBroadcast) {
|
|
|
- new client = GetClientOfUserId(GetEventInt(event, "userid"));
|
|
|
- if(client == 0) {
|
|
|
- return;
|
|
|
+public Action Event_PlayerDisconnect(Event event, const char[] name, bool dontBroadcast) {
|
|
|
+ int client = GetClientOfUserId(event.GetInt("userid"));
|
|
|
+ if (!client) {
|
|
|
+ return Plugin_Continue;
|
|
|
}
|
|
|
- if(IsFakeClient(client)) {
|
|
|
- SetEventBroadcast(event, true);
|
|
|
+
|
|
|
+ if (IsFakeClient(client)) {
|
|
|
+ event.BroadcastDisabled = true;
|
|
|
PrintToChatAll("\x01BOT \x07%06X%N \x01has left the game", GetTeamColor(GetClientTeam(client)), client);
|
|
|
}
|
|
|
+ return Plugin_Continue;
|
|
|
}
|
|
|
|
|
|
-public Event_PlayerTeam(Handle:event, const String:name[], bool:dontBroadcast) {
|
|
|
- new client = GetClientOfUserId(GetEventInt(event, "userid"));
|
|
|
- if(client == 0) {
|
|
|
- return;
|
|
|
+public Action Event_PlayerTeam(Event event, const char[] name, bool dontBroadcast) {
|
|
|
+ int client = GetClientOfUserId(event.GetInt("userid"));
|
|
|
+ if (!client) {
|
|
|
+ return Plugin_Continue;
|
|
|
}
|
|
|
- if(IsFakeClient(client)) {
|
|
|
- SetEventBroadcast(event, true);
|
|
|
- new pos;
|
|
|
- if((pos = FindValueInArray(joiningBots, GetClientUserId(client))) != -1) {
|
|
|
- RemoveFromArray(joiningBots, pos);
|
|
|
+ if (IsFakeClient(client)) {
|
|
|
+ event.BroadcastDisabled = true;
|
|
|
+ int pos;
|
|
|
+ if ((pos = joiningBots.FindValue(GetClientUserId(client))) != -1) {
|
|
|
+ joiningBots.Erase(pos);
|
|
|
+
|
|
|
PrintToServer("BOT %N has joined the game", client);
|
|
|
- PrintToChatAll("\x01BOT \x07%06X%N \x01has joined the game", GetTeamColor(GetEventInt(event, "team")), client);
|
|
|
+ PrintToChatAll("\x01BOT \x07%06X%N \x01has joined the game",
|
|
|
+ GetTeamColor(event.GetInt("team")), client);
|
|
|
}
|
|
|
}
|
|
|
+ return Plugin_Continue;
|
|
|
}
|
|
|
|
|
|
-GetTeamColor(team) {
|
|
|
- new value;
|
|
|
+int GetTeamColor(int team) {
|
|
|
switch(team) {
|
|
|
case 1: {
|
|
|
- value = 0xCCCCCC;
|
|
|
+ return 0xCCCCCC;
|
|
|
}
|
|
|
case 2: {
|
|
|
- value = 0xFF4040;
|
|
|
+ return 0xFF4040;
|
|
|
}
|
|
|
case 3: {
|
|
|
- value = 0x99CCFF;
|
|
|
- }
|
|
|
- default: {
|
|
|
- value = 0x3EFF3E;
|
|
|
+ return 0x99CCFF;
|
|
|
}
|
|
|
}
|
|
|
- return value;
|
|
|
-}
|
|
|
+ return 0x3EFF3E;
|
|
|
+}
|