From e23a2de6a7e1bb0cf5ec0e5a3c0768415e88ec6b Mon Sep 17 00:00:00 2001 From: Sachin Date: Tue, 24 Dec 2024 22:41:39 +0530 Subject: [PATCH 1/4] Release 1.5.5 --- CHANGELOG.md | 4 + src-botsplugin/K4-Arenas-Bots.cs | 198 ++++++++++++++++++++ src-botsplugin/K4-Arenas-Bots.csproj | 29 +++ src-plugin/Plugin/Models/ArenaModel.cs | 7 +- src-plugin/Plugin/Models/ArenasModel.cs | 20 ++ src-plugin/Plugin/Models/GameConfigModel.cs | 13 +- src-plugin/Plugin/PluginAPI.cs | 25 +++ src-plugin/Plugin/PluginEvents.cs | 2 +- src-plugin/Plugin/PluginManifest.cs | 2 +- src-plugin/Plugin/PluginStock.cs | 16 +- src-plugin/lang/en.json | 1 + src-plugin/lang/pl.json | 1 + src-shared/K4-ArenaSharedApi.cs | 3 + src-shared/K4-ArenaSharedApi.dll | Bin 4608 -> 4608 bytes 14 files changed, 314 insertions(+), 7 deletions(-) create mode 100644 src-botsplugin/K4-Arenas-Bots.cs create mode 100644 src-botsplugin/K4-Arenas-Bots.csproj diff --git a/CHANGELOG.md b/CHANGELOG.md index c2e3167..6b3de47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +-- 2024.12.23 - 1.5.5 + +- feat: Added 3 new natives - IsAFK, FindOpponents & TerminateRoundIfPossible + -- 2024.12.20 - 1.5.4 - fix: PerformAFKAction didnt change the AFK state of player diff --git a/src-botsplugin/K4-Arenas-Bots.cs b/src-botsplugin/K4-Arenas-Bots.cs new file mode 100644 index 0000000..31a35cb --- /dev/null +++ b/src-botsplugin/K4-Arenas-Bots.cs @@ -0,0 +1,198 @@ + +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Attributes; +using CounterStrikeSharp.API.Core.Capabilities; +using CounterStrikeSharp.API.Modules.Cvars; +using K4ArenaSharedApi; +using Microsoft.Extensions.Logging; + +namespace K4ArenaBots; + +[MinimumApiVersion(284)] +public class Plugin : BasePlugin +{ + public override string ModuleName => "K4-Arenas Addon - Bots Support"; + public override string ModuleDescription => "Adds a bot in empty arena if there is no opponent."; + public override string ModuleAuthor => "Cruze"; + public override string ModuleVersion => "1.0.0"; + + public static PluginCapability Capability_SharedAPI { get; } = new("k4-arenas:sharedapi"); + public static IK4ArenaSharedApi? SharedAPI_Arena { get; private set; } = null; + + private CCSGameRules? gameRules = null; + private string botQuotaMode = "normal"; + + public override void OnAllPluginsLoaded(bool hotReload) + { + SharedAPI_Arena = Capability_SharedAPI.Get(); + } + + public override void Load(bool hotReload) + { + if(hotReload) + { + gameRules = Utilities.FindAllEntitiesByDesignerName("cs_gamerules").First().GameRules; + + SharedAPI_Arena = Capability_SharedAPI.Get(); + + var quota = ConVar.Find("bot_quota_mode"); + botQuotaMode = quota?.StringValue ?? "normal"; + } + + RegisterListener((mapName) => + { + DeleteOldGameConfig(); + AddTimer(0.1f, () => + { + gameRules = Utilities.FindAllEntitiesByDesignerName("cs_gamerules").First().GameRules; + + var quota = ConVar.Find("bot_quota_mode"); + botQuotaMode = quota?.StringValue ?? "normal"; + }); + }); + + RegisterListener(() => + { + gameRules = null; + }); + + RegisterEventHandler((EventPlayerSpawn @event, GameEventInfo info) => + { + var player = @event.Userid; + + if(player == null || !player.IsValid || player.IsBot || player.IsHLTV || SharedAPI_Arena == null || SharedAPI_Arena.IsAFK(player)) + return HookResult.Continue; + + SpawnBotInEmptyArena(player); + + return HookResult.Continue; + }); + + RegisterEventHandler((EventPlayerDisconnect @event, GameEventInfo info) => + { + var player = @event.Userid; + + if(player == null || !player.IsValid || !player.IsBot) + return HookResult.Continue; + + info.DontBroadcast = true; + return HookResult.Continue; + }, HookMode.Pre); + + RegisterEventHandler((EventRoundPrestart @event, GameEventInfo info) => + { + if(gameRules == null || gameRules.WarmupPeriod || SharedAPI_Arena == null) + { + Server.ExecuteCommand($"bot_prefix \"WARMUP\""); + return HookResult.Continue; + } + + (var players, var bots) = GetPlayers(); + + if(!bots.Any() || bots.First() == null) + { + Server.ExecuteCommand($"bot_prefix \"\""); + return HookResult.Continue; + } + + var bot = bots.First(); + var arenaS = SharedAPI_Arena?.GetArenaName(bot) + " |" ?? ""; + + Server.ExecuteCommand($"bot_prefix {arenaS}"); + return HookResult.Continue; + }, HookMode.Post); + + RegisterEventHandler((EventRoundEnd @event, GameEventInfo info) => + { + SpawnBotInEmptyArena(null, true); + return HookResult.Continue; + }); + } + + private void SpawnBotInEmptyArena(CCSPlayerController? play, bool roundEnd = false) + { + if(gameRules == null || gameRules.WarmupPeriod || SharedAPI_Arena == null) + return; + + (var players, var bots) = GetPlayers(); + + Logger.LogInformation($"Players: {players.Count()} | Bots: {bots.Count()}"); + + if(players.Count() % 2 == 0) + { + Logger.LogInformation($"Even players, no need to spawn bot."); + if(bots.Count() > 0) + Server.ExecuteCommand("bot_quota 0"); + return; + } + + if(play != null && SharedAPI_Arena.FindOpponents(play).Count() > 0) + return; + + if(!bots.Any() || bots.Count() > 1) + { + if(botQuotaMode == "fill") + Server.ExecuteCommand("bot_quota 2"); + else + Server.ExecuteCommand("bot_quota 1"); + + Logger.LogInformation($"Spawning bot in empty arena."); + } + + if(roundEnd) + return; + + AddTimer(0.1f, () => + { + players = new(); + bots = new(); + (players, bots) = GetPlayers(); + if(!bots.Any() || bots.First() == null) + return; + + SharedAPI_Arena.TerminateRoundIfPossible(); + }); + } + + private void DeleteOldGameConfig() + { + // If bot_quota 0 exists in gameconfig.cfg, unlimited round restart will be there so we need to delete it & create a fresh config without it. + // Creating of new file is handled by main plugin with the update. + + string filePath = Path.Combine(Server.GameDirectory, "csgo/addons/counterstrikesharp/plugins", "K4-Arenas", "gameconfig.cfg"); + + if(File.Exists(filePath)) + { + if(File.ReadAllText(filePath).Contains("bot_quota ")) + { + Logger.LogWarning($"Old gameconfig file found, deleting it."); + File.Delete(filePath); + } + } + } + + private (List, List) GetPlayers() + { + var players = new List(); + var bots = new List(); + + for (int i = 0; i < Server.MaxPlayers; i++) + { + var controller = Utilities.GetPlayerFromSlot(i); + + if (controller == null || !controller.IsValid || controller.IsHLTV) + continue; + + if(controller.IsBot) + { + bots.Add(controller); + continue; + } + + if(controller.Connected == PlayerConnectedState.PlayerConnected) + players.Add(controller); + } + return (players, bots); + } +} \ No newline at end of file diff --git a/src-botsplugin/K4-Arenas-Bots.csproj b/src-botsplugin/K4-Arenas-Bots.csproj new file mode 100644 index 0000000..1f6280e --- /dev/null +++ b/src-botsplugin/K4-Arenas-Bots.csproj @@ -0,0 +1,29 @@ + + + False + None + false + ./bin/K4-Arenas-Bots/plugins/K4-Arenas-Bots/ + + + net8.0 + enable + enable + + + + none + runtime + compile; build; native; contentfiles; analyzers; buildtransitive + + + + + ../src-shared/K4-ArenaSharedApi.dll + false + + + + + + \ No newline at end of file diff --git a/src-plugin/Plugin/Models/ArenaModel.cs b/src-plugin/Plugin/Models/ArenaModel.cs index d0c892a..fcbbc9c 100644 --- a/src-plugin/Plugin/Models/ArenaModel.cs +++ b/src-plugin/Plugin/Models/ArenaModel.cs @@ -178,7 +178,12 @@ public void SetPlayerDetails(List? team, List spawns, C if (ArenaID != -1) { - player.Controller.PrintToChat($" {Localizer["k4.general.prefix"]} {Localizer["k4.chat.arena_roundstart", Plugin.GetRequiredArenaName(ArenaID), Plugin.GetOpponentNames(opponents) ?? "Unknown", Localizer[RoundType.Name ?? "Missing"]]}"); + // Bots plugin sets bot_prefix at EventRoundPreStart hence some delay to print opponent names + Server.NextWorldUpdate(() => + { + if(player.Controller.IsValid) + player.Controller.PrintToChat($" {Localizer["k4.general.prefix"]} {Localizer["k4.chat.arena_roundstart", Plugin.GetRequiredArenaName(ArenaID), Plugin.GetOpponentNames(opponents) ?? "Unknown", Localizer[RoundType.Name ?? "Missing"]]}"); + }); } if (Plugin.gameRules?.WarmupPeriod == true) diff --git a/src-plugin/Plugin/Models/ArenasModel.cs b/src-plugin/Plugin/Models/ArenasModel.cs index c685a13..26f0101 100644 --- a/src-plugin/Plugin/Models/ArenasModel.cs +++ b/src-plugin/Plugin/Models/ArenasModel.cs @@ -40,6 +40,26 @@ public Arenas(Plugin plugin) return allPlayers.FirstOrDefault(p => p.Controller == player); } + public List FindOpponents(CCSPlayerController? player) + { + var arenaPlayer = FindPlayer(player); + + if (arenaPlayer is null) + return new List(); + + var arenaID = Plugin.GetPlayerArenaID(arenaPlayer); + + if(arenaID < 0) + return new List(); + + var arena = ArenaList.FirstOrDefault(a => a.ArenaID == arenaID); + if (arena == null) + return new List(); + + var opponents = arena.Team1?.Any(p => p.Controller == player) == true ? arena.Team2 : arena.Team1; + return opponents?.Select(p => p.Controller).ToList() ?? new List(); + } + public ArenaPlayer? FindPlayer(ulong steamId) { IEnumerable allPlayers = Plugin.WaitingArenaPlayers diff --git a/src-plugin/Plugin/Models/GameConfigModel.cs b/src-plugin/Plugin/Models/GameConfigModel.cs index 8f3c0c9..2211dd9 100644 --- a/src-plugin/Plugin/Models/GameConfigModel.cs +++ b/src-plugin/Plugin/Models/GameConfigModel.cs @@ -24,8 +24,15 @@ public GameConfig(Plugin plugin) public void Apply() { - if (ConfigSettings is null) - return; + string filePath = Path.Combine(Plugin.ModuleDirectory, "gameconfig.cfg"); + + if (!File.Exists(filePath) || ConfigSettings is null) + { + Load(); + + if(ConfigSettings is null) + return; + } foreach (var (key, value) in ConfigSettings) { @@ -63,7 +70,7 @@ private void Create(string path) var defaultConfigLines = new List { "// Changing these might break the gamemode", - "bot_quota 0", + "bot_quota_mode \"normal\"", "mp_autoteambalance 0", "mp_ct_default_primary \"\"", "mp_ct_default_secondary \"\"", diff --git a/src-plugin/Plugin/PluginAPI.cs b/src-plugin/Plugin/PluginAPI.cs index 1b00ed4..a57ae46 100644 --- a/src-plugin/Plugin/PluginAPI.cs +++ b/src-plugin/Plugin/PluginAPI.cs @@ -55,6 +55,31 @@ public string GetArenaName(CCSPlayerController player) return string.Empty; } + public bool IsAFK(CCSPlayerController player) + { + var arenaPlayer = plugin.Arenas?.FindPlayer(player); + if (arenaPlayer is not null) + { + return arenaPlayer.AFK; + } + return false; + } + + public List FindOpponents(CCSPlayerController player) + { + var arenaOpponents = plugin.Arenas?.FindOpponents(player); + if (arenaOpponents is not null) + { + return arenaOpponents; + } + return new List(); + } + + public void TerminateRoundIfPossible() + { + plugin.TerminateRoundIfPossible(); + } + public void PerformAFKAction(CCSPlayerController player, bool afk) { ArenaPlayer? arenaPlayer = plugin.Arenas?.FindPlayer(player!); diff --git a/src-plugin/Plugin/PluginEvents.cs b/src-plugin/Plugin/PluginEvents.cs index 0623736..e052be9 100644 --- a/src-plugin/Plugin/PluginEvents.cs +++ b/src-plugin/Plugin/PluginEvents.cs @@ -380,7 +380,7 @@ public void Initialize_Events() } return HookResult.Continue; - }); + }, HookMode.Pre); RegisterEventHandler((EventRoundStart @event, GameEventInfo info) => { diff --git a/src-plugin/Plugin/PluginManifest.cs b/src-plugin/Plugin/PluginManifest.cs index e204dc9..ed573d5 100644 --- a/src-plugin/Plugin/PluginManifest.cs +++ b/src-plugin/Plugin/PluginManifest.cs @@ -10,7 +10,7 @@ public sealed partial class Plugin : BasePlugin public override string ModuleAuthor => "K4ryuu"; - public override string ModuleVersion => "1.5.4 " + + public override string ModuleVersion => "1.5.5 " + #if RELEASE "(release)"; #else diff --git a/src-plugin/Plugin/PluginStock.cs b/src-plugin/Plugin/PluginStock.cs index 3ba77bb..4df4ba0 100644 --- a/src-plugin/Plugin/PluginStock.cs +++ b/src-plugin/Plugin/PluginStock.cs @@ -233,7 +233,21 @@ public string GetOpponentNames(List? opponents) return Localizer["k4.general.no_opponent"]; - return string.Join(", ", opponents.Where(p => p.IsValid).Select(p => p.Controller.PlayerName)); + return string.Join(", ", opponents.Where(p => p.IsValid).Select(p => p.Controller.IsBot && !string.IsNullOrEmpty(GetArenaName(p.Controller)) ? $"{Localizer["k4.general.bot"]} " + p.Controller.PlayerName.Replace(GetArenaName(p.Controller), "").Replace("|", "") : p.Controller.PlayerName)); + } + + public string GetArenaName(CCSPlayerController player) + { + var arenaPlayer = Arenas?.FindPlayer(player); + if (arenaPlayer is not null) + { + string arenaTag = arenaPlayer.ArenaTag; + if (arenaTag.EndsWith(" |")) + arenaTag = arenaTag.Substring(0, arenaTag.Length - 2); + + return arenaTag; + } + return string.Empty; } public static CsItem? FindEnumValueByEnumMemberValue(string? search) diff --git a/src-plugin/lang/en.json b/src-plugin/lang/en.json index cf467fa..fc83033 100644 --- a/src-plugin/lang/en.json +++ b/src-plugin/lang/en.json @@ -8,6 +8,7 @@ "k4.general.warmup": "WARMUP", "k4.general.challenge": "CHALLENGE", "k4.general.random": "Random", + "k4.general.bot": "BOT", "k4.general.challenge.tie": "{silver}The challenge between {lime}{0}{silver} and {lime}{1}{silver} ended in a tie.", "k4.general.challenge.winner": "{lime}{0}{silver} won the challenge against {lime}{1}{silver}.", diff --git a/src-plugin/lang/pl.json b/src-plugin/lang/pl.json index 0469f29..638406b 100644 --- a/src-plugin/lang/pl.json +++ b/src-plugin/lang/pl.json @@ -8,6 +8,7 @@ "k4.general.warmup": "ROZGRZEWKA", "k4.general.challenge": "WYZWANIE", "k4.general.random": "Losowo", + "k4.general.bot": "BOT", "k4.general.challenge.tie": "{silver}Wyzwanie pomiędzy {lime}{0}{silver} a {lime}{1}{silver} zakończyło się remisem.", "k4.general.challenge.winner": "{lime}{0}{silver} wygrał wyzwanie z {lime}{1}{silver}.", diff --git a/src-shared/K4-ArenaSharedApi.cs b/src-shared/K4-ArenaSharedApi.cs index 216c740..ec2ee2b 100644 --- a/src-shared/K4-ArenaSharedApi.cs +++ b/src-shared/K4-ArenaSharedApi.cs @@ -8,6 +8,9 @@ public interface IK4ArenaSharedApi public void RemoveSpecialRound(int id); public int GetArenaPlacement(CCSPlayerController player); public string GetArenaName(CCSPlayerController player); + public bool IsAFK(CCSPlayerController player); + public List FindOpponents(CCSPlayerController player); + public void TerminateRoundIfPossible(); public void PerformAFKAction(CCSPlayerController player, bool afk); } } diff --git a/src-shared/K4-ArenaSharedApi.dll b/src-shared/K4-ArenaSharedApi.dll index 828575648dd109d90ddf1e77ebeb781aa70882e7..ec914582a43699aae4563b69e5c351f57fd69f96 100644 GIT binary patch delta 1039 zcmZvaT}YEr7{~wTZ13Eb`I%v6G;^e-=r-p*h#*s|CFs;Fr4?;%>PTm~CEe64A_@vy zZx>xe6xl^r(MC62NH=*ET?O4n1ztrFMbt(0e@_|(J=^)6=l?wC>pAD0bIv)hT{6$+ z@?UdTYQ^>3gF_{|8i790b^%XKxw^{YV?Z&rlZjfrPB!Z!qCwo$&x!1Y3}c4vW0TMj zb^tGn0HN2}{d!nBcb=9&{aN50`;3YHU}h$n8c!RuhAn`Zef`iNBJ|4)fA3JJi~S_^ zg#JjjX024l{%hGv*aRCFqRmLd2Q0WPV6wm_4Zgs8SqcY-Sg=O8qy-*@=k2GGBl16nBEv8P9G)^=APNX$$nQ_)Of zVER-l9vTZzr_;&UWTG&o2_+k;>?stlP_KA3kfHtv=dye*Wjox6My} z=)=}Xspb%}qTB8eRTUkgqFc{f3yt2kxF_fxO}PB+{QEsFU(h?|4+Pv%mv=N6^~Kr( zF<*P!qhGZ(>5pxd6%&oB28Hv6@5t4ymzl=GC)Ht$jPnDekF&Uro-MsBZv+bEWgkS&RrCv{aI2I6 delta 924 zcmYk4O-NKx6vzMf&b)Da`7wjEP-mfzFp6d4*qe`PLNQ1&8Dp}LgnIKOW<*(wRy_rS zqM(M`s*MS5f}&&yBw8e)xC#oQO`8@iil7z|Ee!gj%-9`k+fYJxN_LN&2YR&^S^q(*hQmt~I8W$1KqUMCtxXG9+gH#Cv zqb4gP@C{aT9rV!7UYnV^GLgs@5CHAsA*`nX)b7f zfSaAvwJVx$;GX8klr$k6^zhoJQt7LLnu3p5L)s0H&nvm0x4g8}`)-7$xD zW{oDb-$<%yqrb9dEK4=-?+C)vxq9gD&4(lRUZy{;^c_j4SN;jrS-sqmETrtXl`7hH zZ?vbB$fvAQJQgqJ<95u>*@d2xW#tO_sHLRYrJ`n__UHDW?Y_oTBWAPQj8ra|OF}(2 zuUG17+B}&g3LLAAI4rJ3S~lkq<-kiwu=f<>C1zvPBKU From a901e327bc6b15f5c1b963d5a3931c1e8c86812c Mon Sep 17 00:00:00 2001 From: Sachin Date: Wed, 25 Dec 2024 14:13:27 +0530 Subject: [PATCH 2/4] fix: Cleanup --- src-botsplugin/K4-Arenas-Bots.cs | 39 ++++++++++++-------------- src-plugin/Plugin/Models/ArenaModel.cs | 4 +-- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src-botsplugin/K4-Arenas-Bots.cs b/src-botsplugin/K4-Arenas-Bots.cs index 31a35cb..99728c0 100644 --- a/src-botsplugin/K4-Arenas-Bots.cs +++ b/src-botsplugin/K4-Arenas-Bots.cs @@ -35,9 +35,6 @@ public override void Load(bool hotReload) gameRules = Utilities.FindAllEntitiesByDesignerName("cs_gamerules").First().GameRules; SharedAPI_Arena = Capability_SharedAPI.Get(); - - var quota = ConVar.Find("bot_quota_mode"); - botQuotaMode = quota?.StringValue ?? "normal"; } RegisterListener((mapName) => @@ -46,9 +43,6 @@ public override void Load(bool hotReload) AddTimer(0.1f, () => { gameRules = Utilities.FindAllEntitiesByDesignerName("cs_gamerules").First().GameRules; - - var quota = ConVar.Find("bot_quota_mode"); - botQuotaMode = quota?.StringValue ?? "normal"; }); }); @@ -61,7 +55,7 @@ public override void Load(bool hotReload) { var player = @event.Userid; - if(player == null || !player.IsValid || player.IsBot || player.IsHLTV || SharedAPI_Arena == null || SharedAPI_Arena.IsAFK(player)) + if(player == null || !player.IsValid || player.IsBot || player.IsHLTV || SharedAPI_Arena?.IsAFK(player) == true) return HookResult.Continue; SpawnBotInEmptyArena(player); @@ -73,7 +67,7 @@ public override void Load(bool hotReload) { var player = @event.Userid; - if(player == null || !player.IsValid || !player.IsBot) + if(player == null || !player.IsValid || player.IsHLTV || !player.IsBot) return HookResult.Continue; info.DontBroadcast = true; @@ -82,7 +76,7 @@ public override void Load(bool hotReload) RegisterEventHandler((EventRoundPrestart @event, GameEventInfo info) => { - if(gameRules == null || gameRules.WarmupPeriod || SharedAPI_Arena == null) + if(gameRules == null || gameRules.WarmupPeriod) { Server.ExecuteCommand($"bot_prefix \"WARMUP\""); return HookResult.Continue; @@ -96,8 +90,7 @@ public override void Load(bool hotReload) return HookResult.Continue; } - var bot = bots.First(); - var arenaS = SharedAPI_Arena?.GetArenaName(bot) + " |" ?? ""; + var arenaS = SharedAPI_Arena?.GetArenaName(bots.First()) + " |" ?? ""; Server.ExecuteCommand($"bot_prefix {arenaS}"); return HookResult.Continue; @@ -110,24 +103,26 @@ public override void Load(bool hotReload) }); } - private void SpawnBotInEmptyArena(CCSPlayerController? play, bool roundEnd = false) + private void SpawnBotInEmptyArena(CCSPlayerController? player, bool roundEnd = false) { - if(gameRules == null || gameRules.WarmupPeriod || SharedAPI_Arena == null) - return; - (var players, var bots) = GetPlayers(); - Logger.LogInformation($"Players: {players.Count()} | Bots: {bots.Count()}"); + // Logger.LogInformation($"Players: {players.Count()} | Bots: {bots.Count()}"); + + botQuotaMode = ConVar.Find("bot_quota_mode")?.StringValue ?? "none"; - if(players.Count() % 2 == 0) + if(players.Count() % 2 == 0 || botQuotaMode != "normal") { - Logger.LogInformation($"Even players, no need to spawn bot."); + Logger.LogInformation($"Even players / botQuotaMode not normal, no need to spawn bot."); if(bots.Count() > 0) + { Server.ExecuteCommand("bot_quota 0"); + Server.ExecuteCommand($"bot_prefix \"\""); + } return; } - if(play != null && SharedAPI_Arena.FindOpponents(play).Count() > 0) + if(player != null && SharedAPI_Arena?.FindOpponents(player).Count() > 0) return; if(!bots.Any() || bots.Count() > 1) @@ -147,11 +142,13 @@ private void SpawnBotInEmptyArena(CCSPlayerController? play, bool roundEnd = fal { players = new(); bots = new(); + (players, bots) = GetPlayers(); - if(!bots.Any() || bots.First() == null) + + if(!bots.Any()) return; - SharedAPI_Arena.TerminateRoundIfPossible(); + SharedAPI_Arena?.TerminateRoundIfPossible(); }); } diff --git a/src-plugin/Plugin/Models/ArenaModel.cs b/src-plugin/Plugin/Models/ArenaModel.cs index fcbbc9c..cdb2029 100644 --- a/src-plugin/Plugin/Models/ArenaModel.cs +++ b/src-plugin/Plugin/Models/ArenaModel.cs @@ -178,8 +178,8 @@ public void SetPlayerDetails(List? team, List spawns, C if (ArenaID != -1) { - // Bots plugin sets bot_prefix at EventRoundPreStart hence some delay to print opponent names - Server.NextWorldUpdate(() => + // Bots plugin sets bot_prefix at EventRoundPreStart hence some delay to print opponent names. (Frame not enough sometimes) + Plugin.AddTimer(0.001f, () => { if(player.Controller.IsValid) player.Controller.PrintToChat($" {Localizer["k4.general.prefix"]} {Localizer["k4.chat.arena_roundstart", Plugin.GetRequiredArenaName(ArenaID), Plugin.GetOpponentNames(opponents) ?? "Unknown", Localizer[RoundType.Name ?? "Missing"]]}"); From 9abe056857f3a9c26df2e38553aa62af0ac3bb64 Mon Sep 17 00:00:00 2001 From: Sachin Date: Wed, 1 Jan 2025 13:54:37 +0530 Subject: [PATCH 3/4] fix: Bot not getting kicked when a player is marked as AFK. update: When a player executes !afk, terminateroundifpossible. update: Added `allowed-weapon-prefs` to toggle showing weaponTypes in `!guns` menu. --- CHANGELOG.md | 2 ++ src-botsplugin/K4-Arenas-Bots.cs | 2 +- src-plugin/Plugin/Models/ArenaModel.cs | 2 +- src-plugin/Plugin/Models/ArenaPlayerModel.cs | 23 ++++++++++++++--- src-plugin/Plugin/PluginCommands.cs | 2 ++ src-plugin/Plugin/PluginConfig.cs | 26 +++++++++++++++++++- 6 files changed, 51 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b3de47..80a161d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ -- 2024.12.23 - 1.5.5 - feat: Added 3 new natives - IsAFK, FindOpponents & TerminateRoundIfPossible +- update: When a player executes !afk, execute TerminateRoundIfPossible. +- update: Added `allowed-weapon-prefs` to toggle showing weaponTypes in `!guns` menu. -- 2024.12.20 - 1.5.4 diff --git a/src-botsplugin/K4-Arenas-Bots.cs b/src-botsplugin/K4-Arenas-Bots.cs index 99728c0..36ee140 100644 --- a/src-botsplugin/K4-Arenas-Bots.cs +++ b/src-botsplugin/K4-Arenas-Bots.cs @@ -187,7 +187,7 @@ private void DeleteOldGameConfig() continue; } - if(controller.Connected == PlayerConnectedState.PlayerConnected) + if(controller.Connected == PlayerConnectedState.PlayerConnected && SharedAPI_Arena?.IsAFK(controller) == false) players.Add(controller); } return (players, bots); diff --git a/src-plugin/Plugin/Models/ArenaModel.cs b/src-plugin/Plugin/Models/ArenaModel.cs index cdb2029..3646f13 100644 --- a/src-plugin/Plugin/Models/ArenaModel.cs +++ b/src-plugin/Plugin/Models/ArenaModel.cs @@ -31,7 +31,7 @@ public Arena(Plugin plugin, Tuple, List> spawns) } public bool IsActive - => Team1?.Any(p => p.IsValid) == true && Team2?.Any(p => p.IsValid) == true; + => Team1?.Any(p => p.IsValid && Plugin.Arenas?.FindPlayer(p.Controller)?.AFK == false) == true && Team2?.Any(p => p.IsValid && Plugin.Arenas?.FindPlayer(p.Controller)?.AFK == false) == true; public bool HasFinished => !IsActive || Team1?.Any(p => p.IsValid && p.IsAlive) == false || Team2?.Any(p => p.IsValid && p.IsAlive) == false; diff --git a/src-plugin/Plugin/Models/ArenaPlayerModel.cs b/src-plugin/Plugin/Models/ArenaPlayerModel.cs index 6422af0..eeeb1e4 100644 --- a/src-plugin/Plugin/Models/ArenaPlayerModel.cs +++ b/src-plugin/Plugin/Models/ArenaPlayerModel.cs @@ -228,7 +228,7 @@ private void ShowChatWeaponPreferenceMenu() ChatMenu weaponPreferenceMenu = new ChatMenu(Localizer["k4.menu.weaponpref.title"]); foreach (WeaponType weaponType in Enum.GetValues(typeof(WeaponType))) { - if (weaponType == WeaponType.Unknown) + if (weaponType == WeaponType.Unknown || !IsAllowedWeaponType(weaponType)) continue; weaponPreferenceMenu.AddMenuOption(Localizer[$"k4.rounds.{weaponType.ToString().ToLower()}"], (player, option) => @@ -243,11 +243,14 @@ private void ShowChatWeaponPreferenceMenu() private void ShowCenterWeaponPreferenceMenu() { var items = new List(); + var values = new Dictionary(); + int count = 0; foreach (WeaponType weaponType in Enum.GetValues(typeof(WeaponType))) { - if (weaponType == WeaponType.Unknown) + if (weaponType == WeaponType.Unknown || !IsAllowedWeaponType(weaponType)) continue; items.Add(new MenuItem(MenuItemType.Button, [new MenuValue($"{Localizer[$"k4.rounds.{weaponType.ToString().ToLower()}"]}")])); + values.Add(count++, weaponType); } Plugin.Menu?.ShowScrollableMenu(Controller, Localizer["k4.menu.weaponpref.title"], items, (buttons, menu, selected) => @@ -255,12 +258,26 @@ private void ShowCenterWeaponPreferenceMenu() if (selected == null) return; if (buttons == MenuButtons.Select) { - WeaponType selectedWeaponType = (WeaponType)(menu.Option); + WeaponType selectedWeaponType = values[menu.Option]; ShowWeaponSubPreferenceMenu(selectedWeaponType); } }, false, Config.CommandSettings.FreezeInMenu, disableDeveloper: Config.CommandSettings.ShowMenuCredits); } + private bool IsAllowedWeaponType(WeaponType weaponType) + { + return weaponType switch + { + WeaponType.Rifle => Config.AllowedWeaponPreferences.Rifle, + WeaponType.Sniper => Config.AllowedWeaponPreferences.Sniper, + WeaponType.SMG => Config.AllowedWeaponPreferences.SMG, + WeaponType.LMG => Config.AllowedWeaponPreferences.LMG, + WeaponType.Shotgun => Config.AllowedWeaponPreferences.Shotgun, + WeaponType.Pistol => Config.AllowedWeaponPreferences.Pistol, + _ => false + }; + } + public void ShowWeaponSubPreferenceMenu(WeaponType weaponType) { if (Plugin.Config.CommandSettings.CenterMenuMode) diff --git a/src-plugin/Plugin/PluginCommands.cs b/src-plugin/Plugin/PluginCommands.cs index 57c64ac..f364124 100644 --- a/src-plugin/Plugin/PluginCommands.cs +++ b/src-plugin/Plugin/PluginCommands.cs @@ -191,6 +191,8 @@ public void Command_AFK(CCSPlayerController? player, CommandInfo info) player.Clan = arenaPlayer.ArenaTag; Utilities.SetStateChanged(player, "CCSPlayerController", "m_szClan"); } + + TerminateRoundIfPossible(); } else { diff --git a/src-plugin/Plugin/PluginConfig.cs b/src-plugin/Plugin/PluginConfig.cs index 215792e..c16cc70 100644 --- a/src-plugin/Plugin/PluginConfig.cs +++ b/src-plugin/Plugin/PluginConfig.cs @@ -127,11 +127,14 @@ public sealed class PluginConfig : BasePluginConfig [JsonPropertyName("default-weapon-settings")] public DefaultWeaponSettings DefaultWeaponSettings { get; set; } = new DefaultWeaponSettings(); + [JsonPropertyName("allowed-weapon-prefs")] + public AllowedWeaponPreferences AllowedWeaponPreferences { get; set; } = new AllowedWeaponPreferences(); + [JsonPropertyName("arena-math-overrides")] public int ArenaMathOverrides { get; set; } = 0; [JsonPropertyName("ConfigVersion")] - public override int Version { get; set; } = 5; + public override int Version { get; set; } = 6; } public sealed class CompatibilitySettings @@ -236,6 +239,27 @@ public sealed class DefaultWeaponSettings public string? DefaultPistol { get; set; } = null; } + public sealed class AllowedWeaponPreferences + { + [JsonPropertyName("rifle")] + public bool Rifle { get; set; } = true; + + [JsonPropertyName("sniper")] + public bool Sniper { get; set; } = true; + + [JsonPropertyName("smg")] + public bool SMG { get; set; } = true; + + [JsonPropertyName("lmg")] + public bool LMG { get; set; } = true; + + [JsonPropertyName("shotgun")] + public bool Shotgun { get; set; } = true; + + [JsonPropertyName("pistol")] + public bool Pistol { get; set; } = true; + } + public sealed class DatabaseSettings { [JsonPropertyName("host")] From 9db6f3cdc9c2c59a5c7b99c194b1fdbe1c1bd3fd Mon Sep 17 00:00:00 2001 From: Sachin Date: Wed, 1 Jan 2025 15:30:16 +0530 Subject: [PATCH 4/4] update: Call `TerminateRoundIfPossible` at `PlayerDisconnect` also --- src-plugin/Plugin/PluginEvents.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src-plugin/Plugin/PluginEvents.cs b/src-plugin/Plugin/PluginEvents.cs index e052be9..2322f50 100644 --- a/src-plugin/Plugin/PluginEvents.cs +++ b/src-plugin/Plugin/PluginEvents.cs @@ -98,6 +98,7 @@ public void Initialize_Events() WaitingArenaPlayers = new Queue(WaitingArenaPlayers.Where(p => p.Controller != playerController)); Arenas?.ArenaList.ForEach(arena => arena.RemovePlayer(playerController)); + TerminateRoundIfPossible(); return HookResult.Continue; });