|
@@ -0,0 +1,169 @@
|
|
|
|
+/**
|
|
|
|
+ * [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 <sourcemod>
|
|
|
|
+
|
|
|
|
+#include <tf2_stocks>
|
|
|
|
+#include <printvalvetranslation>
|
|
|
|
+
|
|
|
|
+#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 "<player> 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<int>(team));
|
|
|
|
+}
|