diff --git a/addons/sourcemod/plugins/optional/l4d_tank_control_eq.smx b/addons/sourcemod/plugins/optional/l4d_tank_control_eq.smx index 62c355efa..d531308bd 100644 Binary files a/addons/sourcemod/plugins/optional/l4d_tank_control_eq.smx and b/addons/sourcemod/plugins/optional/l4d_tank_control_eq.smx differ diff --git a/addons/sourcemod/scripting/include/l4d_tank_control_eq.inc b/addons/sourcemod/scripting/include/l4d_tank_control_eq.inc index 70048e600..42b2b3f3f 100644 --- a/addons/sourcemod/scripting/include/l4d_tank_control_eq.inc +++ b/addons/sourcemod/scripting/include/l4d_tank_control_eq.inc @@ -10,9 +10,104 @@ */ native int GetTankSelection(); +/** +* @brief Gets an array of steam IDs of players who have had tank. +* +* @return ArrayList containing steam IDs, must be freed by caller. +*/ +native ArrayList GetWhosHadTank(); + +/** +* @brief Gets an array of steam IDs of players who have not had tank. +* +* @return ArrayList containing steam IDs, must be freed by caller. +*/ +native ArrayList GetWhosNotHadTank(); + +/** +* @brief Clears the list of players who have had tank. +* +* @return True if successful, false otherwise. +*/ +native bool ClearWhosHadTank(); + +/** +* @brief Gets the current tank pool. +* +* @return ArrayList containing steam IDs of players in tank pool, must be freed by caller. +*/ +native ArrayList GetTankPool(); + +/** +* @brief Sets the tank to a specific player +* +* @param steamId Steam ID of the player to set as tank +* @return True if successful, false otherwise +*/ +native bool SetTank(const char[] steamId); + +/** +* @brief Gets the current tank queue +* +* @return ArrayList containing steam IDs in queue order, must be freed by caller. +*/ +native ArrayList GetTankQueue(); + +/** +* @brief Adds a player to the tank queue at specified position +* +* @param steamId Steam ID of the player to add +* @param position Position in queue (0 = end, 1 = first, etc) +* @return True if successful, false if player already in queue +*/ +native bool AddToTankQueue(const char[] steamId, int position = 0); + +/** +* @brief Removes a player from the tank queue +* +* @param steamId Steam ID of the player to remove +* @return True if successful, false if player not in queue +*/ +native bool RemoveFromTankQueue(const char[] steamId); + +/** +* @brief Called when plugin tries to offer tank to a bot +* +* @param sQueuedTank Steam ID of queued tank +*/ forward void TankControl_OnTryOfferingTankBot(char sQueuedTank[64]); + +/** +* @brief Called when a tank is selected +* +* @param sQueuedTank Steam ID of selected tank +*/ forward void TankControl_OnTankSelection(char sQueuedTank[64]); +/** +* @brief Called when tank control is reset +*/ +forward void OnTankControlReset(); + +/** +* @brief Called when choosing tank +* +* @return Plugin_Handled to override tank selection, Plugin_Continue to allow default behavior +*/ +forward Action OnChooseTank(); + +/** +* @brief Called when tank is given to a player +* +* @param steamId Steam ID of the player who received tank +*/ +forward void OnTankGiven(const char[] steamId); + +/** +* @brief Called when the tank queue is modified +*/ +forward void OnTankQueueChanged(); + public SharedPlugin __pl_l4d_tank_control_eq = { name = "l4d_tank_control_eq", @@ -28,5 +123,13 @@ public SharedPlugin __pl_l4d_tank_control_eq = public void __pl_l4d_tank_control_eq_SetNTVOptional() { MarkNativeAsOptional("GetTankSelection"); + MarkNativeAsOptional("GetWhosHadTank"); + MarkNativeAsOptional("GetWhosNotHadTank"); + MarkNativeAsOptional("GetTankQueue"); + MarkNativeAsOptional("ClearWhosHadTank"); + MarkNativeAsOptional("AddToTankQueue"); + MarkNativeAsOptional("RemoveFromTankQueue"); + MarkNativeAsOptional("GetTankPool"); + MarkNativeAsOptional("SetTank"); } #endif diff --git a/addons/sourcemod/scripting/l4d_tank_control_eq.sp b/addons/sourcemod/scripting/l4d_tank_control_eq.sp index 1119b995e..5d48900bd 100644 --- a/addons/sourcemod/scripting/l4d_tank_control_eq.sp +++ b/addons/sourcemod/scripting/l4d_tank_control_eq.sp @@ -22,10 +22,6 @@ ConVar hTankWindow, hTankDebug; -GlobalForward - hForwardOnTryOfferingTankBot, - hForwardOnTankSelection; - char queuedTankSteamId[64], tankInitiallyChosen[64]; @@ -37,12 +33,33 @@ float int dcedTankFrustration = -1; +bool g_bRoundStarted; + +Handle g_hForwardOnTryOfferingTankBot; +Handle g_hForwardOnTankSelection; +Handle g_hForwardOnQueueChanged; + public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { - CreateNative("GetTankSelection", Native_GetTankSelection); + RegPluginLibrary("l4d_tank_control_eq"); - hForwardOnTryOfferingTankBot = new GlobalForward("TankControl_OnTryOfferingTankBot", ET_Ignore, Param_String); - hForwardOnTankSelection = new GlobalForward("TankControl_OnTankSelection", ET_Ignore, Param_String); + CreateNative("GetTankSelection", Native_GetTankSelection); + CreateNative("GetWhosHadTank", Native_GetWhosHadTank); + CreateNative("GetWhosNotHadTank", Native_GetWhosNotHadTank); + CreateNative("ClearWhosHadTank", Native_ClearWhosHadTank); + CreateNative("GetTankPool", Native_GetTankPool); + CreateNative("SetTank", Native_SetTank); + CreateNative("GetTankQueue", Native_GetTankQueue); + CreateNative("AddToTankQueue", Native_AddToTankQueue); + CreateNative("RemoveFromTankQueue", Native_RemoveFromTankQueue); + + CreateGlobalForward("OnTankControlReset", ET_Ignore); + CreateGlobalForward("OnChooseTank", ET_Event, Param_String); + CreateGlobalForward("OnTankGiven", ET_Ignore, Param_String); + + g_hForwardOnTryOfferingTankBot = new GlobalForward("TankControl_OnTryOfferingTankBot", ET_Ignore, Param_String); + g_hForwardOnTankSelection = new GlobalForward("TankControl_OnTankSelection", ET_Ignore, Param_String); + g_hForwardOnQueueChanged = CreateGlobalForward("OnTankQueueChanged", ET_Ignore); return APLRes_Success; } @@ -52,9 +69,9 @@ int Native_GetTankSelection(Handle plugin, int numParams) { return getInfectedPl public Plugin myinfo = { name = "L4D2 Tank Control", - author = "arti, (Contributions by: Sheo, Sir, Altair-Sossai)", + author = "arti, (Contributions by: Sheo, Sir, Altair-Sossai) , Hana", description = "Distributes the role of the tank evenly throughout the team, allows for overrides. (Includes forwards)", - version = "0.0.26", + version = "0.0.27", url = "https://github.com/SirPlease/L4D2-Competitive-Rework" } @@ -77,6 +94,10 @@ public void OnPluginStart() // Admin commands RegAdminCmd("sm_tankshuffle", TankShuffle_Cmd, ADMFLAG_SLAY, "Re-picks at random someone to become tank."); RegAdminCmd("sm_givetank", GiveTank_Cmd, ADMFLAG_SLAY, "Gives the tank to a selected player"); + RegAdminCmd("sm_addtankpool", AddTankPool_Cmd, ADMFLAG_SLAY, "Adds selected player to tank pool."); + RegAdminCmd("sm_queuetank", AddTankPool_Cmd, ADMFLAG_SLAY, "Adds selected player to tank pool."); + RegAdminCmd("sm_removetankpool", RemoveTankPool_Cmd, ADMFLAG_SLAY, "Removes selected player from tank pool."); + RegAdminCmd("sm_dequeuetank", RemoveTankPool_Cmd, ADMFLAG_SLAY, "Removes selected player from tank pool."); // Register the boss commands RegConsoleCmd("sm_tank", Tank_Cmd, "Shows who is becoming the tank."); @@ -143,7 +164,7 @@ public Action L4D_OnTryOfferingTankBot(int tank_index, bool &enterStatis) // Allow third party plugins to override tank selection char sOverrideTank[64]; sOverrideTank[0] = '\0'; - Call_StartForward(hForwardOnTryOfferingTankBot); + Call_StartForward(g_hForwardOnTryOfferingTankBot); Call_PushStringEx(sOverrideTank, sizeof(sOverrideTank), SM_PARAM_STRING_UTF8, SM_PARAM_COPYBACK); Call_Finish(); @@ -217,6 +238,7 @@ void RoundStart_Event(Event hEvent, const char[] eName, bool dontBroadcast) dcedTankFrustration = -1; gotTankAt = 0.0; tankInitiallyChosen = ""; + g_bRoundStarted = true; } Action newGame(Handle timer) @@ -243,6 +265,7 @@ void RoundEnd_Event(Event hEvent, const char[] eName, bool dontBroadcast) { queuedTankSteamId = ""; tankInitiallyChosen = ""; + g_bRoundStarted = false; } /** @@ -269,6 +292,11 @@ void PlayerTeam_Event(Event hEvent, const char[] name, bool dontBroadcast) if (client < 1 || client > MaxClients) return; + if (oldTeam == TEAM_INFECTED || team == TEAM_INFECTED) + { + RequestFrame(CleanTankQueue); + } + if (oldTeam == TEAM_INFECTED) { /* @@ -477,6 +505,108 @@ Action GiveTank_Cmd(int client, int args) return Plugin_Handled; } +public Action AddTankPool_Cmd(int client, int args) +{ + if (args < 1) + { + ReplyToCommand(client, "[SM] Usage: sm_addtankpool "); + return Plugin_Handled; + } + + char arg[MAX_NAME_LENGTH]; + GetCmdArg(1, arg, sizeof(arg)); + + int target = FindTarget(client, arg); + if (target == -1) + return Plugin_Handled; + + char steamId[64]; + GetClientAuthId(target, AuthId_Steam2, steamId, sizeof(steamId)); + + if (h_tankQueue.FindString(steamId) != -1) + { + CPrintToChatAll("%t", "PlayerAlreadyInQueue", target); + return Plugin_Handled; + } + + if (StrEqual(queuedTankSteamId, "")) + { + strcopy(queuedTankSteamId, sizeof(queuedTankSteamId), steamId); + strcopy(tankInitiallyChosen, sizeof(tankInitiallyChosen), steamId); + outputTankToAll(0); + } + else + { + h_tankQueue.PushString(steamId); + } + + CPrintToChatAll("%t", "PlayerAddedToQueue", target); + Call_StartForward(g_hForwardOnQueueChanged); + Call_Finish(); + + return Plugin_Handled; +} + +public Action RemoveTankPool_Cmd(int client, int args) +{ + if (args < 1) + { + ReplyToCommand(client, "[SM] Usage: sm_removetankpool "); + return Plugin_Handled; + } + + char arg[MAX_NAME_LENGTH]; + GetCmdArg(1, arg, sizeof(arg)); + + int target = FindTarget(client, arg); + if (target == -1) + return Plugin_Handled; + + char steamId[64]; + GetClientAuthId(target, AuthId_Steam2, steamId, sizeof(steamId)); + + bool wasCurrentTank = StrEqual(queuedTankSteamId, steamId); + int queueIndex = h_tankQueue.FindString(steamId); + + if (queueIndex != -1) + { + h_tankQueue.Erase(queueIndex); + CPrintToChatAll("%t", "PlayerRemovedFromQueue", target); + } + + if (wasCurrentTank) + { + queuedTankSteamId = ""; + tankInitiallyChosen = ""; + + if (h_tankQueue.Length > 0) + { + char nextTankSteamId[64]; + h_tankQueue.GetString(0, nextTankSteamId, sizeof(nextTankSteamId)); + strcopy(queuedTankSteamId, sizeof(queuedTankSteamId), nextTankSteamId); + strcopy(tankInitiallyChosen, sizeof(tankInitiallyChosen), nextTankSteamId); + h_tankQueue.Erase(0); + } + else + { + chooseTank(0); + } + + outputTankToAll(0); + CPrintToChatAll("%t", "PlayerRemovedFromCurrent", target); + } + + if (queueIndex == -1 && !wasCurrentTank) + { + CPrintToChatAll("%t", "PlayerNotInQueue", target); + return Plugin_Handled; + } + + Call_StartForward(g_hForwardOnQueueChanged); + Call_Finish(); + + return Plugin_Handled; +} /*========================================================================= | Stocks | @@ -492,17 +622,36 @@ void chooseTank(any data) // Allow other plugins to override tank selection. char sOverrideTank[64]; sOverrideTank[0] = '\0'; - Call_StartForward(hForwardOnTankSelection); + Call_StartForward(g_hForwardOnTankSelection); Call_PushStringEx(sOverrideTank, sizeof(sOverrideTank), SM_PARAM_STRING_UTF8, SM_PARAM_COPYBACK); Call_Finish(); if (!StrEqual(sOverrideTank, "")) { strcopy(queuedTankSteamId, sizeof(queuedTankSteamId), sOverrideTank); + if (StrEqual(tankInitiallyChosen, "")) + strcopy(tankInitiallyChosen, sizeof(tankInitiallyChosen), sOverrideTank); return; } + if (h_tankQueue.Length > 0) + { + char steamId[64]; + h_tankQueue.GetString(0, steamId, sizeof(steamId)); + + int tankClient = getInfectedPlayerBySteamId(steamId); + if (tankClient != -1 && IS_VALID_INFECTED(tankClient)) + { + strcopy(queuedTankSteamId, sizeof(queuedTankSteamId), steamId); + if (StrEqual(tankInitiallyChosen, "")) + strcopy(tankInitiallyChosen, sizeof(tankInitiallyChosen), steamId); + h_tankQueue.Erase(0); + return; + } + } + queuedTankSteamId = ""; + tankInitiallyChosen = ""; int nextTankIndex = PeekNextTankIndexInTheQueue(); @@ -531,6 +680,11 @@ void chooseTank(any data) if (StrEqual(tankInitiallyChosen, "")) strcopy(tankInitiallyChosen, sizeof(tankInitiallyChosen), steamId); + + if (!StrEqual(queuedTankSteamId, "")) + { + TriggerQueueChanged(); + } } /** @@ -720,6 +874,43 @@ void ShuffleArray(ArrayList arrayList, int start, int end) } } +void AddTeamSteamIdsToArray(ArrayList steamIds, int team) +{ + char steamId[64]; + + for (int i = 1; i <= MaxClients; i++) + { + if (IsClientInGame(i) && !IsFakeClient(i) && GetClientTeam(i) == team) + { + GetClientAuthId(i, AuthId_Steam2, steamId, sizeof(steamId)); + steamIds.PushString(steamId); + } + } +} + +void RemoveSteamIdsFromArray(ArrayList steamIds, ArrayList steamIdsToRemove) +{ + int index; + char steamId[64]; + + for (int i = 0; i < steamIdsToRemove.Length; i++) + { + steamIdsToRemove.GetString(i, steamId, sizeof(steamId)); + index = steamIds.FindString(steamId); + + if (index != -1) + { + steamIds.Erase(index); + } + } +} + +void TriggerQueueChanged() +{ + Call_StartForward(g_hForwardOnQueueChanged); + Call_Finish(); +} + /** * Check if the translation file exists * @@ -738,4 +929,132 @@ stock void LoadTranslation(const char[] translation) SetFailState("Missing translation file %s.txt", translation); LoadTranslations(translation); -} \ No newline at end of file +} + +public int Native_GetWhosHadTank(Handle plugin, int numParams) +{ + return view_as(CloneHandle(h_whosHadTank, plugin)); +} + +public int Native_GetWhosNotHadTank(Handle plugin, int numParams) +{ + ArrayList infectedPool = new ArrayList(ByteCountToCells(64)); + AddTeamSteamIdsToArray(infectedPool, TEAM_INFECTED); + + // Remove players who've already had tank from the pool + RemoveSteamIdsFromArray(infectedPool, h_whosHadTank); + + return view_as(CloneHandle(infectedPool, plugin)); +} + +public int Native_GetTankPool(Handle plugin, int numParams) +{ + ArrayList infectedPool = new ArrayList(ByteCountToCells(64)); + AddTeamSteamIdsToArray(infectedPool, TEAM_INFECTED); + + // Remove players who've already had tank from the pool + RemoveSteamIdsFromArray(infectedPool, h_whosHadTank); + + // If the infected pool is empty, reset pool of players + if (infectedPool.Length == 0) + { + AddTeamSteamIdsToArray(infectedPool, TEAM_INFECTED); + } + + return view_as(CloneHandle(infectedPool, plugin)); +} + +public int Native_ClearWhosHadTank(Handle plugin, int numParams) +{ + h_whosHadTank.Clear(); + return 1; +} + +public int Native_SetTank(Handle plugin, int numParams) +{ + char steamId[64]; + GetNativeString(1, steamId, sizeof(steamId)); + + int client = getInfectedPlayerBySteamId(steamId); + if (client == -1 || !IS_VALID_INFECTED(client)) + return false; + + strcopy(queuedTankSteamId, sizeof(queuedTankSteamId), steamId); + + if (g_bRoundStarted) + outputTankToAll(0); + + return true; +} + +public int Native_GetTankQueue(Handle plugin, int numParams) +{ + return view_as(CloneHandle(h_tankQueue, plugin)); +} + +public int Native_AddToTankQueue(Handle plugin, int numParams) +{ + char steamId[64]; + GetNativeString(1, steamId, sizeof(steamId)); + int position = GetNativeCell(2); + + if (h_tankQueue.FindString(steamId) != -1) + return false; + + if (position <= 0) + h_tankQueue.PushString(steamId); + else + { + if (position > h_tankQueue.Length) + position = h_tankQueue.Length; + h_tankQueue.ShiftUp(position - 1); + h_tankQueue.SetString(position - 1, steamId); + } + + Call_StartForward(g_hForwardOnQueueChanged); + Call_Finish(); + return true; +} + +public int Native_RemoveFromTankQueue(Handle plugin, int numParams) +{ + char steamId[64]; + GetNativeString(1, steamId, sizeof(steamId)); + + int index = h_tankQueue.FindString(steamId); + if (index == -1) + return false; + + h_tankQueue.Erase(index); + Call_StartForward(g_hForwardOnQueueChanged); + Call_Finish(); + return true; +} + +void CleanTankQueue() +{ + ArrayList validPlayers = new ArrayList(ByteCountToCells(64)); + char steamId[64]; + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsClientInGame(i) || IsFakeClient(i) || GetClientTeam(i) != TEAM_INFECTED) + continue; + + GetClientAuthId(i, AuthId_Steam2, steamId, sizeof(steamId)); + validPlayers.PushString(steamId); + } + + for (int i = h_tankQueue.Length - 1; i >= 0; i--) + { + h_tankQueue.GetString(i, steamId, sizeof(steamId)); + if (validPlayers.FindString(steamId) == -1) + { + h_tankQueue.Erase(i); + } + } + + delete validPlayers; + + TriggerQueueChanged(); +} diff --git a/addons/sourcemod/translations/chi/l4d_tank_control_eq.phrases.txt b/addons/sourcemod/translations/chi/l4d_tank_control_eq.phrases.txt new file mode 100644 index 000000000..286383807 --- /dev/null +++ b/addons/sourcemod/translations/chi/l4d_tank_control_eq.phrases.txt @@ -0,0 +1,67 @@ +"Phrases" +{ + "TagRage" + { + "chi" "{red}<{default}Tank控制权{red}>" + } + "Refilled" + { + "chi" "{default}({green}%N{default}) 的{olive}控制权{red}重新回满" + } + "RefilledBot" + { + "chi" "{olive}控制权{red}重新回满" + } + + "TagSelection" + { + "chi" "{red}<{default}Tank轮换{red}>" + } + "YouBecomeTank" + { + "chi" "{green}你 {default}将会成为 {red}Tank{default}!" + } + "BecomeTank" + { + "chi" "{olive}%N {default}将会成为 {red}Tank!" + } + + "TagControl" + { + "chi" "{green}[{olive}Tank控制人{green}]" + } + "InvalidTarget" + { + "chi" "{default}无效的目标. 无法给予Tank" + } + "NoInfected" + { + "chi" "{olive}%N {default}不在感染者阵营, 无法给予Tank" + } + + "HintText" + { + "chi" "控制权重新回满" + } + + "PlayerAlreadyInQueue" + { + "chi" "{olive}%N {default}已经在坦克队列中了" + } + "PlayerAddedToQueue" + { + "chi" "{olive}%N {default}已被添加到坦克队列" + } + "PlayerRemovedFromQueue" + { + "chi" "{olive}%N {default}已从坦克队列中移除" + } + "PlayerRemovedFromCurrent" + { + "chi" "{olive}%N {default}已被移除当前坦克身份" + } + "PlayerNotInQueue" + { + "chi" "{olive}%N {default}不在坦克队列中" + } +} diff --git a/addons/sourcemod/translations/es/l4d_tank_control_eq.phrases.txt b/addons/sourcemod/translations/es/l4d_tank_control_eq.phrases.txt index c35fc4f13..b73b35315 100644 --- a/addons/sourcemod/translations/es/l4d_tank_control_eq.phrases.txt +++ b/addons/sourcemod/translations/es/l4d_tank_control_eq.phrases.txt @@ -43,4 +43,25 @@ { "es" "Medidor de Control Recargado" } + + "PlayerAlreadyInQueue" + { + "es" "{olive}%N {default}ya está en la cola del Tank" + } + "PlayerAddedToQueue" + { + "es" "{olive}%N {default}ha sido añadido a la cola del Tank" + } + "PlayerRemovedFromQueue" + { + "es" "{olive}%N {default}ha sido eliminado de la cola del Tank" + } + "PlayerRemovedFromCurrent" + { + "es" "{olive}%N {default}ha sido removido como el Tank actual" + } + "PlayerNotInQueue" + { + "es" "{olive}%N {default}no está en la cola del Tank" + } } diff --git a/addons/sourcemod/translations/l4d_tank_control_eq.phrases.txt b/addons/sourcemod/translations/l4d_tank_control_eq.phrases.txt index e67855022..b771de02c 100644 --- a/addons/sourcemod/translations/l4d_tank_control_eq.phrases.txt +++ b/addons/sourcemod/translations/l4d_tank_control_eq.phrases.txt @@ -43,4 +43,25 @@ { "en" "Rage Meter Refilled" } + + "PlayerAlreadyInQueue" + { + "en" "{olive}%N {default}is already in the tank queue" + } + "PlayerAddedToQueue" + { + "en" "{olive}%N {default}has been added to the tank queue" + } + "PlayerRemovedFromQueue" + { + "en" "{olive}%N {default}has been removed from the tank queue" + } + "PlayerRemovedFromCurrent" + { + "en" "{olive}%N {default}has been removed as the current tank" + } + "PlayerNotInQueue" + { + "en" "{olive}%N {default}is not in the tank queue" + } }