csrd_bot_swap.sp 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. /**
  2. * [CSRD] Bot Team Trading
  3. *
  4. * Allows players to switch between balanced teams if a bot is able to switch in their place.
  5. * Removes the need to switch to Spectators before switching to the desired team.
  6. *
  7. * Works with the built-in manager and Doctor McKay's Bot Manager.
  8. * Intended for use with "fill" quota mode.
  9. */
  10. #pragma semicolon 1
  11. #include <sourcemod>
  12. #include <tf2_stocks>
  13. #include <printvalvetranslation>
  14. #pragma newdecls required
  15. #define PLUGIN_VERSION "0.0.1"
  16. public Plugin myinfo = {
  17. name = "[CSRD] Cross-Team Bot Swap",
  18. author = "nosoop",
  19. description = "Allow switching on balanced teams if a bot can be swapped too.",
  20. version = PLUGIN_VERSION,
  21. url = "https://git.csrd.science/"
  22. }
  23. /**
  24. * TODO: make it so only replicate disabled balance check if other team has bots?
  25. */
  26. public void OnPluginStart() {
  27. // TODO maybe call on post-round team switch??
  28. HookEvent("player_team", OnTeamStateChange, EventHookMode_PostNoCopy);
  29. AddCommandListener(OnTeamChangeRequested, "jointeam");
  30. }
  31. public void OnPluginEnd() {
  32. ConVar unbalanceLimit = FindConVar("mp_teams_unbalance_limit");
  33. for (int i = 1; i <= MaxClients; i++) {
  34. if (IsClientConnected(i) && IsClientInGame(i) && !IsFakeClient(i)) {
  35. unbalanceLimit.ReplicateToClient(i, "1");
  36. }
  37. }
  38. }
  39. public void OnConfigsExecuted() {
  40. OnTeamStateChangePost(true);
  41. }
  42. public void OnTeamStateChange(Event event, const char[] name, bool dontBroadcast) {
  43. // frame delay to ensure players are in correct team
  44. RequestFrame(OnTeamStateChangePost);
  45. }
  46. /**
  47. * Called when the number of players on a team change.
  48. *
  49. * Updates the `mp_teams_unbalance_limit` value tramsmitted to clients, so they can use the team
  50. * switch UI and freely switch to the other team if a bot is available for swapping.
  51. */
  52. public void OnTeamStateChangePost(any data) {
  53. ConVar unbalanceLimit = FindConVar("mp_teams_unbalance_limit");
  54. // if no unbalance limit, don't bother
  55. if (!unbalanceLimit.BoolValue) {
  56. return;
  57. }
  58. // First pass: set unbalance limit to 1 on all human clients, count bots on teams.
  59. int nRedBots, nBlueBots;
  60. for (int i = 1; i <= MaxClients; i++) {
  61. if (IsClientConnected(i) && IsClientInGame(i)) {
  62. if (IsFakeClient(i)) {
  63. switch (TF2_GetClientTeam(i)) {
  64. case TFTeam_Red: {
  65. nRedBots++;
  66. }
  67. case TFTeam_Blue: {
  68. nBlueBots++;
  69. }
  70. }
  71. } else {
  72. unbalanceLimit.ReplicateToClient(i, "1");
  73. }
  74. }
  75. }
  76. // Second pass: If teams are even, allow players to switch if opposite team has bots.
  77. if (TF2_GetTeamClientCount(TFTeam_Red) == TF2_GetTeamClientCount(TFTeam_Blue)) {
  78. for (int i = 1; i <= MaxClients; i++) {
  79. if (IsClientConnected(i) && IsClientInGame(i) && !IsFakeClient(i)) {
  80. TFTeam team = TF2_GetClientTeam(i);
  81. if (team == TFTeam_Red && nBlueBots) {
  82. unbalanceLimit.ReplicateToClient(i, "2");
  83. } else if (team == TFTeam_Blue && nRedBots) {
  84. unbalanceLimit.ReplicateToClient(i, "2");
  85. }
  86. }
  87. }
  88. }
  89. }
  90. /**
  91. * Called when a player has requested to switch teams, before the game's logic happens.
  92. *
  93. * If the player is on a playing team and wants to switch to the other one, moves a bot from
  94. * their desired team to their current one. The game then processes the team change command and
  95. * moves them to their desired team.
  96. *
  97. * This won't quite work the way it should if some other plugin blocks the team change further
  98. * down in their event hook. A detour on CTFPlayer::HandleCommand_JoinTeam() would do its work
  99. * after most plugins process the command, but this hasn't been an issue on my setup.
  100. *
  101. */
  102. public Action OnTeamChangeRequested(int client, const char[] name, int argc) {
  103. if (TF2_GetTeamClientCount(TFTeam_Red) == TF2_GetTeamClientCount(TFTeam_Blue)) {
  104. TFTeam team = TF2_GetClientTeam(client);
  105. if (team != TFTeam_Red && team != TFTeam_Blue) {
  106. return Plugin_Continue;
  107. }
  108. char argstring[64];
  109. GetCmdArgString(argstring, sizeof(argstring));
  110. if (StrEqual(argstring, "red")) {
  111. SwapFakeClient(TFTeam_Red);
  112. } else if (StrEqual(argstring, "blue")) {
  113. SwapFakeClient(TFTeam_Blue);
  114. }
  115. // if switching to spec, let bot manager deal with filling in a player
  116. }
  117. // team change command will succeed now that a bot was swapped
  118. return Plugin_Continue;
  119. }
  120. /**
  121. * Moves a random bot from one playing team to the other.
  122. */
  123. void SwapFakeClient(TFTeam sourceTeam) {
  124. if (sourceTeam != TFTeam_Red && sourceTeam != TFTeam_Blue) {
  125. ThrowError("Attempting to swap fake client off of a non-playing team.");
  126. }
  127. int[] bots = new int[TF2_GetTeamClientCount(sourceTeam)];
  128. int nBots;
  129. for (int i = 1; i <= MaxClients; i++) {
  130. if (IsClientInGame(i) && IsFakeClient(i) && TF2_GetClientTeam(i) == sourceTeam) {
  131. bots[nBots++] = i;
  132. }
  133. }
  134. if (nBots) {
  135. int bot = bots[GetRandomInt(0, nBots - 1)];
  136. TFTeam newTeam = sourceTeam == TFTeam_Red? TFTeam_Blue : TFTeam_Red;
  137. TF2_ChangeClientTeam(bot, newTeam);
  138. TF2_RespawnPlayer(bot);
  139. // delay message so it gets displayed after the player switches
  140. RequestFrame(BotSwapMessage, GetClientUserId(bot));
  141. }
  142. }
  143. /**
  144. * Displays a "<player> was moved to the other team for game balance" message.
  145. */
  146. public void BotSwapMessage(int userid) {
  147. int client = GetClientOfUserId(userid);
  148. if (client) {
  149. char name[MAX_NAME_LENGTH];
  150. GetClientName(client, name, sizeof(name));
  151. PrintValveTranslationToAll(Destination_Chat, "#game_player_was_team_balanced", name);
  152. }
  153. }
  154. //
  155. stock int TF2_GetTeamClientCount(TFTeam team) {
  156. return GetTeamClientCount(view_as<int>(team));
  157. }