diff --git a/CHANGELOG b/CHANGELOG index d19f38d..49598c2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,14 @@ +-- 2023.05.09 - V4.2.0 + +- feat: Toplist placement for ranks can now be displayed on scoreboard with new settings +- feat: Auto team balancer by rank points (experimental) +- fix: Removed debug messages lefover +- fix: Commands show up even if the module disabled +- fix: Compile warning from CSS updates +- fix: Headshot is now only counted if it was a kill +- fix: Points by playtime not counts anymore after 2-3 ticks +- fix: Game win and lose stats not counted properly + -- 2023.04.16 - V4.1.9 - fix: Collection was modified diff --git a/K4-System/src/Models/PlayerModel.cs b/K4-System/src/Models/PlayerModel.cs index e715391..12d871b 100644 --- a/K4-System/src/Models/PlayerModel.cs +++ b/K4-System/src/Models/PlayerModel.cs @@ -10,9 +10,6 @@ namespace K4System.Models; public class K4Player { - //** ? Main */ - private readonly Plugin Plugin; - //** ? Player */ public readonly CCSPlayerController Controller; public readonly ulong SteamID; @@ -25,10 +22,8 @@ public class K4Player public TimeData? timeData { get; set; } public (int killStreak, DateTime lastKillTime) KillStreak = (0, DateTime.MinValue); - public K4Player(Plugin plugin, CCSPlayerController playerController) + public K4Player(CCSPlayerController playerController) { - Plugin = plugin; - Controller = playerController; SteamID = playerController.SteamID; PlayerName = playerController.PlayerName; diff --git a/K4-System/src/Module/ModuleRank.cs b/K4-System/src/Module/ModuleRank.cs index 8ebb4f5..63e317e 100644 --- a/K4-System/src/Module/ModuleRank.cs +++ b/K4-System/src/Module/ModuleRank.cs @@ -7,6 +7,7 @@ namespace K4System using CounterStrikeSharp.API.Modules.Timers; using CounterStrikeSharp.API.Modules.Utils; using K4System.Models; + using Dapper; public partial class ModuleRank : IModuleRank { @@ -16,12 +17,16 @@ public ModuleRank(ILogger logger, IPluginContext pluginContext) this.pluginContext = pluginContext; } + public Timer? reservePlayTimeTimer = null; + public Timer? reservePlacementTimer = null; + public void Initialize(bool hotReload) { this.plugin = (pluginContext.Plugin as Plugin)!; this.Config = plugin.Config; - this.Logger.LogInformation("Initializing '{0}'", this.GetType().Name); + if (Config.GeneralSettings.LoadMessages) + this.Logger.LogInformation("Initializing '{0}'", this.GetType().Name); //** ? Register Module Parts */ @@ -32,22 +37,72 @@ public void Initialize(bool hotReload) //** ? Register Timers */ - plugin.AddTimer(Config.PointSettings.PlaytimeMinutes * 60, () => + if (Config.PointSettings.PlaytimeMinutes > 0) + { + reservePlayTimeTimer = plugin.AddTimer(Config.PointSettings.PlaytimeMinutes * 60, () => + { + foreach (K4Player k4player in plugin.K4Players) + { + if (!k4player.IsValid || !k4player.IsPlayer) + continue; + + if (k4player.Controller.Team == CsTeam.Terrorist) + ModifyPlayerPoints(k4player, Config.PointSettings.PlaytimePoints, "k4.phrases.playtime"); + } + }, TimerFlags.REPEAT); + } + + if (Config.RankSettings.DisplayToplistPlacement) { - foreach (K4Player k4player in plugin.K4Players) + reservePlacementTimer = plugin.AddTimer(5, () => { - if (!k4player.IsValid || !k4player.IsPlayer) - continue; - if (k4player.Controller.Team == CsTeam.Terrorist) - ModifyPlayerPoints(k4player, Config.PointSettings.PlaytimePoints, "k4.phrases.playtime"); - } - }, TimerFlags.REPEAT); + string query = $@"SELECT steam_id, + (SELECT COUNT(*) FROM `{Config.DatabaseSettings.TablePrefix}k4ranks` + WHERE `points` > (SELECT `points` FROM `{Config.DatabaseSettings.TablePrefix}k4ranks` WHERE `steam_id` = t.steam_id)) AS playerPlace + FROM `{Config.DatabaseSettings.TablePrefix}k4ranks` t + WHERE steam_id IN @SteamIds"; + + var steamIds = plugin.K4Players.Where(p => p.IsValid && p.IsPlayer && p.rankData != null) + .Select(p => p.SteamID) + .ToArray(); + + Task.Run(async () => + { + try + { + using (var connection = plugin.CreateConnection(Config)) + { + await connection.OpenAsync(); + var result = await connection.QueryAsync(query, new { SteamIds = steamIds }); + + foreach (var row in result) + { + string steamId = row.steam_id; + int playerPlace = (int)row.playerPlace + 1; + + K4Player? k4player = plugin.K4Players.FirstOrDefault(p => p.SteamID == ulong.Parse(steamId)); + if (k4player != null && k4player.rankData != null) + { + k4player.rankData.TopPlacement = playerPlace; + } + } + } + } + catch (Exception ex) + { + Server.NextFrame(() => Logger.LogError($"A problem occurred while fetching player placements: {ex.Message}")); + } + }); + }, TimerFlags.REPEAT); + } + } public void Release(bool hotReload) { - this.Logger.LogInformation("Releasing '{0}'", this.GetType().Name); + if (Config.GeneralSettings.LoadMessages) + this.Logger.LogInformation("Releasing '{0}'", this.GetType().Name); } } } \ No newline at end of file diff --git a/K4-System/src/Module/ModuleStat.cs b/K4-System/src/Module/ModuleStat.cs index 1238019..600258a 100644 --- a/K4-System/src/Module/ModuleStat.cs +++ b/K4-System/src/Module/ModuleStat.cs @@ -17,7 +17,8 @@ public void Initialize(bool hotReload) this.plugin = (pluginContext.Plugin as Plugin)!; this.Config = plugin.Config; - this.Logger.LogInformation("Initializing '{0}'", this.GetType().Name); + if (Config.GeneralSettings.LoadMessages) + this.Logger.LogInformation("Initializing '{0}'", this.GetType().Name); //** ? Register Module Parts */ @@ -27,7 +28,8 @@ public void Initialize(bool hotReload) public void Release(bool hotReload) { - this.Logger.LogInformation("Releasing '{0}'", this.GetType().Name); + if (Config.GeneralSettings.LoadMessages) + this.Logger.LogInformation("Releasing '{0}'", this.GetType().Name); } } } \ No newline at end of file diff --git a/K4-System/src/Module/ModuleTime.cs b/K4-System/src/Module/ModuleTime.cs index 4e9e47d..31a68f4 100644 --- a/K4-System/src/Module/ModuleTime.cs +++ b/K4-System/src/Module/ModuleTime.cs @@ -17,7 +17,8 @@ public void Initialize(bool hotReload) this.plugin = (pluginContext.Plugin as Plugin)!; this.Config = plugin.Config; - this.Logger.LogInformation("Initializing '{0}'", this.GetType().Name); + if (Config.GeneralSettings.LoadMessages) + this.Logger.LogInformation("Initializing '{0}'", this.GetType().Name); //** ? Register Module Parts */ @@ -27,7 +28,8 @@ public void Initialize(bool hotReload) public void Release(bool hotReload) { - this.Logger.LogInformation("Releasing '{0}'", this.GetType().Name); + if (Config.GeneralSettings.LoadMessages) + this.Logger.LogInformation("Releasing '{0}'", this.GetType().Name); } } } \ No newline at end of file diff --git a/K4-System/src/Module/ModuleUtils.cs b/K4-System/src/Module/ModuleUtils.cs index 0c1050e..73f74b1 100644 --- a/K4-System/src/Module/ModuleUtils.cs +++ b/K4-System/src/Module/ModuleUtils.cs @@ -17,7 +17,8 @@ public void Initialize(bool hotReload) this.plugin = (pluginContext.Plugin as Plugin)!; this.Config = plugin.Config; - this.Logger.LogInformation("Initializing '{0}'", this.GetType().Name); + if (Config.GeneralSettings.LoadMessages) + this.Logger.LogInformation("Initializing '{0}'", this.GetType().Name); //** ? Register Module Parts */ @@ -26,7 +27,8 @@ public void Initialize(bool hotReload) public void Release(bool hotReload) { - this.Logger.LogInformation("Releasing '{0}'", this.GetType().Name); + if (Config.GeneralSettings.LoadMessages) + this.Logger.LogInformation("Releasing '{0}'", this.GetType().Name); } } } \ No newline at end of file diff --git a/K4-System/src/Module/Rank/RankCommands.cs b/K4-System/src/Module/Rank/RankCommands.cs index 203c3e3..9200ab1 100644 --- a/K4-System/src/Module/Rank/RankCommands.cs +++ b/K4-System/src/Module/Rank/RankCommands.cs @@ -200,8 +200,6 @@ public void OnCommandTop(CCSPlayerController? player, CommandInfo info) printCount = Math.Clamp(parsedInt, 1, 25); } - Logger.LogInformation($"Player {k4player.PlayerName} requested top {printCount} players."); - Task.Run(async () => { Console.WriteLine("Saving all players data"); diff --git a/K4-System/src/Module/Rank/RankEvents.cs b/K4-System/src/Module/Rank/RankEvents.cs index 2e62948..191dc98 100644 --- a/K4-System/src/Module/Rank/RankEvents.cs +++ b/K4-System/src/Module/Rank/RankEvents.cs @@ -150,6 +150,49 @@ public void Initialize_Events() return HookResult.Continue; }, HookMode.Post); + plugin.RegisterEventHandler((EventRoundPrestart @event, GameEventInfo info) => + { + if (Config.RankSettings.RankBasedTeamBalance) + { + int team1Size = plugin.K4Players.Count(p => p.Controller.Team == CsTeam.Terrorist); + int team2Size = plugin.K4Players.Count(p => p.Controller.Team == CsTeam.CounterTerrorist); + + if (Math.Abs(team1Size - team2Size) > 1) + { + var team1Players = plugin.K4Players.Where(p => p.Controller.Team == CsTeam.Terrorist).ToList(); + var team2Players = plugin.K4Players.Where(p => p.Controller.Team == CsTeam.CounterTerrorist).ToList(); + + var team1RankPoints = team1Players.Select(p => p.rankData?.Points ?? 0).Sum(); + var team2RankPoints = team2Players.Select(p => p.rankData?.Points ?? 0).Sum(); + + while (Math.Abs(team1RankPoints - team2RankPoints) > Config.RankSettings.RankBasedTeamBalanceMaxDifference) + { + if (team1RankPoints > team2RankPoints) + { + var playerToSwitch = team1Players.OrderByDescending(p => p.rankData?.Points ?? 0).First(); + team1Players.Remove(playerToSwitch); + team2Players.Add(playerToSwitch); + playerToSwitch.Controller.ChangeTeam(CsTeam.CounterTerrorist); + } + else + { + var playerToSwitch = team2Players.OrderByDescending(p => p.rankData?.Points ?? 0).First(); + team2Players.Remove(playerToSwitch); + team1Players.Add(playerToSwitch); + playerToSwitch.Controller.ChangeTeam(CsTeam.Terrorist); + } + + team1RankPoints = team1Players.Select(p => p.rankData?.Points ?? 0).Sum(); + team2RankPoints = team2Players.Select(p => p.rankData?.Points ?? 0).Sum(); + } + + Server.PrintToChatAll($" {plugin.Localizer["k4.general.prefix"]} {plugin.Localizer["k4.ranks.rank.teamsbalanced"]}"); + } + } + + return HookResult.Continue; + }, HookMode.Post); + plugin.RegisterEventHandler((EventBombPlanted @event, GameEventInfo info) => { ModifyPlayerPointsConnector(@event.Userid, Config.PointSettings.BombPlant, "k4.phrases.bombplanted"); diff --git a/K4-System/src/Module/Rank/RankFunctions.cs b/K4-System/src/Module/Rank/RankFunctions.cs index 014ba75..de72d42 100644 --- a/K4-System/src/Module/Rank/RankFunctions.cs +++ b/K4-System/src/Module/Rank/RankFunctions.cs @@ -85,7 +85,7 @@ public Rank GetPlayerRank(int points) return rankDictionary.LastOrDefault(kv => points >= kv.Value.Point).Value ?? noneRank!; } - public void ModifyPlayerPointsConnector(CCSPlayerController player, int amount, string reason, string? extraInfo = null) + public void ModifyPlayerPointsConnector(CCSPlayerController? player, int amount, string reason, string? extraInfo = null) { K4Player? k4player = plugin.GetK4Player(player); if (k4player is null) @@ -264,6 +264,14 @@ public void SetPlayerClanTag(K4Player k4player) } } + if (Config.RankSettings.DisplayToplistPlacement && k4player.rankData != null) + { + if (k4player.rankData.TopPlacement != 0 && k4player.rankData.TopPlacement <= Config.RankSettings.DisplayToplistPlacementMax) + { + tag = $"{Config.RankSettings.DisplayToplistPlacementTag.Replace("{placement}", k4player.rankData.TopPlacement.ToString())} {tag}"; + } + } + if (Config.RankSettings.CountryTagEnabled) { string countryTag = GetPlayerCountryCode(k4player.Controller); diff --git a/K4-System/src/Module/Rank/RankGlobals.cs b/K4-System/src/Module/Rank/RankGlobals.cs index 2d47444..03d1b0c 100644 --- a/K4-System/src/Module/Rank/RankGlobals.cs +++ b/K4-System/src/Module/Rank/RankGlobals.cs @@ -30,6 +30,7 @@ public class RankData public required int RoundPoints { get; set; } public required bool HideAdminTag { get; set; } public required bool MuteMessages { get; set; } + public int TopPlacement { get; set; } } public required Plugin plugin; diff --git a/K4-System/src/Module/Stat/StatEvents.cs b/K4-System/src/Module/Stat/StatEvents.cs index f301b93..04b1ffa 100644 --- a/K4-System/src/Module/Stat/StatEvents.cs +++ b/K4-System/src/Module/Stat/StatEvents.cs @@ -5,6 +5,7 @@ namespace K4System using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Utils; using K4System.Models; + using Microsoft.Extensions.Logging; public partial class ModuleStat : IModuleStat { @@ -60,11 +61,11 @@ public void Initialize_Events() if (@event.Assistedflash) ModifyPlayerStats(k4attacker, "assist_flash", 1); + if (@event.Headshot) + ModifyPlayerStats(k4attacker, "headshots", 1); + switch (@event.Hitgroup) { - case (int)HitGroup_t.HITGROUP_HEAD: - ModifyPlayerStats(k4attacker, "headshots", 1); - break; case (int)HitGroup_t.HITGROUP_CHEST: ModifyPlayerStats(k4attacker, "chest_hits", 1); break; @@ -138,11 +139,6 @@ public void Initialize_Events() if (k4attacker != null && k4attacker.IsValid && k4attacker.IsPlayer) { ModifyPlayerStats(k4attacker, "hits_given", 1); - - if (@event.Hitgroup == 1) - { - ModifyPlayerStats(k4attacker, "headshots", 1); - } } return HookResult.Continue; @@ -277,10 +273,14 @@ public void Initialize_Events() { k4players.Where(p => p.Controller.Team > CsTeam.Spectator) .ToList() - .ForEach(p => ModifyPlayerStats(p, p.Controller.Team == winnerTeam ? "game_win" : "game_lose", 1)); + .ForEach(p => + { + ModifyPlayerStats(p, p.Controller.Team == winnerTeam ? "game_win" : "game_lose", 1); + }); } } + Task.Run(plugin.SaveAllPlayersDataAsync); return HookResult.Continue; }); } diff --git a/K4-System/src/Module/Stat/StatFunctions.cs b/K4-System/src/Module/Stat/StatFunctions.cs index 6667dbf..8f58e76 100644 --- a/K4-System/src/Module/Stat/StatFunctions.cs +++ b/K4-System/src/Module/Stat/StatFunctions.cs @@ -4,6 +4,7 @@ namespace K4System using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Utils; using K4System.Models; + using Microsoft.Extensions.Logging; public partial class ModuleStat : IModuleStat { diff --git a/K4-System/src/Plugin/PluginBasics.cs b/K4-System/src/Plugin/PluginBasics.cs index d059f8d..3bc887e 100644 --- a/K4-System/src/Plugin/PluginBasics.cs +++ b/K4-System/src/Plugin/PluginBasics.cs @@ -9,6 +9,7 @@ namespace K4System using CounterStrikeSharp.API.Modules.Commands.Targeting; using CounterStrikeSharp.API.Modules.Utils; using K4System.Models; + using Microsoft.Extensions.Logging; using static K4System.ModuleRank; using static K4System.ModuleStat; using static K4System.ModuleTime; @@ -29,23 +30,41 @@ public void Initialize_Commands() CommandSettings commands = Config.CommandSettings; - string rankLocale = Localizer["k4.general.availablecommands.rank"]; - string otherLocale = Localizer["k4.general.availablecommands.other"]; + Dictionary> commandCategories = new Dictionary>(); + + if (Config.GeneralSettings.ModuleRanks) + { + string rankLocale = Localizer["k4.general.availablecommands.rank"]; + + commandCategories[rankLocale] = new List(); + commandCategories[rankLocale].AddRange(commands.RankCommands); + commandCategories[rankLocale].AddRange(commands.TopCommands); + commandCategories[rankLocale].AddRange(commands.RanksCommands); + } + + if (Config.GeneralSettings.ModuleStats) + { + string statLocale = Localizer["k4.general.availablecommands.stat"]; - Dictionary> commandCategories = new Dictionary> + commandCategories[statLocale] = new List(); + commandCategories[statLocale].AddRange(commands.StatCommands); + } + + if (Config.GeneralSettings.ModuleTimes) { - { rankLocale, new List() }, - { otherLocale, new List() }, - { Localizer["k4.general.availablecommands.stat"], commands.StatCommands }, - { Localizer["k4.general.availablecommands.time"], commands.TimeCommands }, - }; + string timeLocale = Localizer["k4.general.availablecommands.time"]; - commandCategories[rankLocale].AddRange(commands.RankCommands); - commandCategories[rankLocale].AddRange(commands.TopCommands); - commandCategories[rankLocale].AddRange(commands.ResetMyCommands); - commandCategories[rankLocale].AddRange(commands.RanksCommands); + commandCategories[timeLocale] = new List(); + commandCategories[timeLocale].AddRange(commands.TimeCommands); + } - commandCategories[otherLocale].AddRange(commands.AdminListCommands); + string otherLocale = Localizer["k4.general.availablecommands.other"]; + + commandCategories[otherLocale] = new List(); + commandCategories[otherLocale].AddRange(commands.ResetMyCommands); + + if (Config.GeneralSettings.ModuleUtils) + commandCategories[otherLocale].AddRange(commands.AdminListCommands); StringBuilder messageBuilder = new StringBuilder(); @@ -92,7 +111,7 @@ public void Initialize_Events() { RegisterEventHandler((EventPlayerActivate @event, GameEventInfo info) => { - CCSPlayerController player = @event.Userid; + CCSPlayerController? player = @event.Userid; if (player is null || !player.IsValid || !player.PlayerPawn.IsValid || player.IsHLTV) return HookResult.Continue; @@ -102,7 +121,7 @@ public void Initialize_Events() if (K4Players.Any(p => p.Controller == player)) return HookResult.Continue; - K4Player k4player = new K4Player(this, player); + K4Player k4player = new K4Player(player); if (player.IsBot) { @@ -180,7 +199,12 @@ public void Initialize_Events() RegisterListener(() => { GameRules = null; - Task.Run(PurgeTableRowsAsync); + Task.Run(async () => + { + await SaveAllPlayersDataAsync(); + await PurgeTableRowsAsync(); + Logger.LogCritical("Map ended, all player data saved and table rows purged"); + }); }); } diff --git a/K4-System/src/Plugin/PluginConfig.cs b/K4-System/src/Plugin/PluginConfig.cs index 29e202e..8c85119 100644 --- a/K4-System/src/Plugin/PluginConfig.cs +++ b/K4-System/src/Plugin/PluginConfig.cs @@ -19,6 +19,9 @@ public sealed class AdminSettingsEntry public sealed class GeneralSettings { + [JsonPropertyName("load-messages")] + public bool LoadMessages { get; set; } = true; + [JsonPropertyName("spawn-message")] public bool SpawnMessage { get; set; } = true; @@ -162,6 +165,21 @@ public sealed class StatisticSettings public sealed class RankSettings { + [JsonPropertyName("rank-based-team-balance")] + public bool RankBasedTeamBalance { get; set; } = false; + + [JsonPropertyName("rank-based-team-balance-max-difference")] + public int RankBasedTeamBalanceMaxDifference { get; set; } = 200; + + [JsonPropertyName("display-toplist-placement")] + public bool DisplayToplistPlacement { get; set; } = true; + + [JsonPropertyName("display-toplist-placement-max")] + public int DisplayToplistPlacementMax { get; set; } = 10; + + [JsonPropertyName("display-toplist-placement-tag")] + public string DisplayToplistPlacementTag { get; set; } = "Top{placement} |"; + [JsonPropertyName("killstreak-reset-on-round-end")] public bool KillstreakResetOnRoundEnd { get; set; } = false; @@ -364,6 +382,6 @@ public sealed class PluginConfig : BasePluginConfig public PointSettings PointSettings { get; set; } = new PointSettings(); [JsonPropertyName("ConfigVersion")] - public override int Version { get; set; } = 11; + public override int Version { get; set; } = 12; } } \ No newline at end of file diff --git a/K4-System/src/Plugin/PluginDatabase.cs b/K4-System/src/Plugin/PluginDatabase.cs index e3a7a3d..116ecc3 100644 --- a/K4-System/src/Plugin/PluginDatabase.cs +++ b/K4-System/src/Plugin/PluginDatabase.cs @@ -389,7 +389,7 @@ private void LoadAllPlayersCache() foreach (var player in players) { - K4Player k4player = new K4Player(this, player); + K4Player k4player = new K4Player(player); K4Players.Add(k4player); } @@ -494,7 +494,8 @@ public void LoadPlayerRowToCache(K4Player k4player, dynamic row, bool all) PlayedRound = false, RoundPoints = 0, HideAdminTag = false, - MuteMessages = false + MuteMessages = false, + TopPlacement = 0 }; } diff --git a/K4-System/src/Plugin/PluginManifest.cs b/K4-System/src/Plugin/PluginManifest.cs index ab11ecc..b4e28d2 100644 --- a/K4-System/src/Plugin/PluginManifest.cs +++ b/K4-System/src/Plugin/PluginManifest.cs @@ -10,7 +10,7 @@ public sealed partial class Plugin : BasePlugin public override string ModuleAuthor => "K4ryuu"; - public override string ModuleVersion => "4.1.9 " + + public override string ModuleVersion => "4.2.0 " + #if RELEASE "(release)"; #else diff --git a/K4-System/src/Plugin/PluginStock.cs b/K4-System/src/Plugin/PluginStock.cs index 4eef77e..bc8b689 100644 --- a/K4-System/src/Plugin/PluginStock.cs +++ b/K4-System/src/Plugin/PluginStock.cs @@ -77,7 +77,7 @@ public bool CommandHelper(CCSPlayerController? player, CommandInfo info, Command return K4Players.FirstOrDefault(player => player.SteamID == steamID); } - public K4Player? GetK4Player(CCSPlayerController playerController) + public K4Player? GetK4Player(CCSPlayerController? playerController) { return K4Players.FirstOrDefault(player => player.Controller == playerController); } diff --git a/K4-System/src/lang/en.json b/K4-System/src/lang/en.json index cedc461..2493071 100644 --- a/K4-System/src/lang/en.json +++ b/K4-System/src/lang/en.json @@ -22,6 +22,8 @@ "k4.general.state.enabled": "{green}ENABLED", "k4.general.state.disabled": "{lightred}DISABLED", + "k4.ranks.rank.teamsbalanced": "{orange}Teams are now automatically balanced based on rank points.", + "k4.ranks.rank.title": "{lime}{0}{silver}'s Rank", "k4.ranks.rank.line1": "--- {silver}You have {lime}{0} {silver}points and are currently {1}{2} {silver}({3} out of {4})", "k4.ranks.rank.line2": "--- {silver}Next rank: {0}{1}", diff --git a/K4-System/src/lang/ro.json b/K4-System/src/lang/ro.json.outdated similarity index 100% rename from K4-System/src/lang/ro.json rename to K4-System/src/lang/ro.json.outdated