/** * [CSRD] Bot Team Trading * * Allows players to switch between balanced teams if a bot is able to switch in their place. * Removes the need to switch to Spectators before switching to the desired team. * * Works with the built-in manager and Doctor McKay's Bot Manager. * Intended for use with "fill" quota mode. */ #pragma semicolon 1 #include #include #include #pragma newdecls required #define PLUGIN_VERSION "0.0.1" public Plugin myinfo = { name = "[CSRD] Cross-Team Bot Swap", author = "nosoop", description = "Allow switching on balanced teams if a bot can be swapped too.", version = PLUGIN_VERSION, url = "https://git.csrd.science/" } /** * TODO: make it so only replicate disabled balance check if other team has bots? */ public void OnPluginStart() { // TODO maybe call on post-round team switch?? HookEvent("player_team", OnTeamStateChange, EventHookMode_PostNoCopy); AddCommandListener(OnTeamChangeRequested, "jointeam"); } public void OnPluginEnd() { ConVar unbalanceLimit = FindConVar("mp_teams_unbalance_limit"); for (int i = 1; i <= MaxClients; i++) { if (IsClientConnected(i) && IsClientInGame(i) && !IsFakeClient(i)) { unbalanceLimit.ReplicateToClient(i, "1"); } } } public void OnConfigsExecuted() { OnTeamStateChangePost(true); } public void OnTeamStateChange(Event event, const char[] name, bool dontBroadcast) { // frame delay to ensure players are in correct team RequestFrame(OnTeamStateChangePost); } /** * Called when the number of players on a team change. */ public void OnTeamStateChangePost(any data) { ConVar unbalanceLimit = FindConVar("mp_teams_unbalance_limit"); // if no unbalance limit, don't bother if (!unbalanceLimit.BoolValue) { return; } // First pass: set unbalance limit to 1 on all human clients, count bots on teams. int nRedBots, nBlueBots; for (int i = 1; i <= MaxClients; i++) { if (IsClientConnected(i) && IsClientInGame(i)) { if (IsFakeClient(i)) { switch (TF2_GetClientTeam(i)) { case TFTeam_Red: { nRedBots++; } case TFTeam_Blue: { nBlueBots++; } } } else { unbalanceLimit.ReplicateToClient(i, "1"); } } } // Second pass: If teams are even, allow players to switch if opposite team has bots. if (TF2_GetTeamClientCount(TFTeam_Red) == TF2_GetTeamClientCount(TFTeam_Blue)) { for (int i = 1; i <= MaxClients; i++) { if (IsClientConnected(i) && IsClientInGame(i) && !IsFakeClient(i)) { TFTeam team = TF2_GetClientTeam(i); if (team == TFTeam_Red && nBlueBots) { unbalanceLimit.ReplicateToClient(i, "2"); } else if (team == TFTeam_Blue && nRedBots) { unbalanceLimit.ReplicateToClient(i, "2"); } } } } } public Action OnTeamChangeRequested(int client, const char[] name, int argc) { if (TF2_GetTeamClientCount(TFTeam_Red) == TF2_GetTeamClientCount(TFTeam_Blue)) { TFTeam team = TF2_GetClientTeam(client); if (team != TFTeam_Red && team != TFTeam_Blue) { return Plugin_Continue; } char argstring[64]; GetCmdArgString(argstring, sizeof(argstring)); if (StrEqual(argstring, "red")) { SwapFakeClient(TFTeam_Red); } else if (StrEqual(argstring, "blue")) { SwapFakeClient(TFTeam_Blue); } // if switching to spec, let bot manager deal with filling in a player } // team change command will succeed now that a bot was swapped return Plugin_Continue; } /** * Moves a bot from one playing team to the other. */ void SwapFakeClient(TFTeam sourceTeam) { if (sourceTeam != TFTeam_Red && sourceTeam != TFTeam_Blue) { ThrowError("Attempting to swap fake client off of a non-playing team."); } int[] bots = new int[TF2_GetTeamClientCount(sourceTeam)]; int nBots; for (int i = 1; i <= MaxClients; i++) { if (IsClientInGame(i) && IsFakeClient(i) && TF2_GetClientTeam(i) == sourceTeam) { bots[nBots++] = i; } } if (nBots) { int bot = bots[GetRandomInt(0, nBots - 1)]; TFTeam newTeam = sourceTeam == TFTeam_Red? TFTeam_Blue : TFTeam_Red; TF2_ChangeClientTeam(bot, newTeam); TF2_RespawnPlayer(bot); // delay message so it gets displayed after the player switches RequestFrame(BotSwapMessage, GetClientUserId(bot)); } } /** * Displays a " was moved to the other team for game balance" message. */ public void BotSwapMessage(int userid) { int client = GetClientOfUserId(userid); if (client) { char name[MAX_NAME_LENGTH]; GetClientName(client, name, sizeof(name)); PrintValveTranslationToAll(Destination_Chat, "#game_player_was_team_balanced", name); } } // stock int TF2_GetTeamClientCount(TFTeam team) { return GetTeamClientCount(view_as(team)); }