diff --git a/Allowlist.cs.md b/Allowlist.cs.md new file mode 100644 index 0000000..b91759d --- /dev/null +++ b/Allowlist.cs.md @@ -0,0 +1,23 @@ +# 1 Modules in Allowlist.cs + +| Description | Version | +|:-------------------------------------------------------------------|:----------| +| Block players who are not on the allowlist from joining the server | 1.0.0 | + +## Commands +| Command | Function Name | Description | Allowed Roles | Parameters | Defaults | +|:-------------|:----------------|:------------------------------------|:----------------|:------------------------------------------------|:-----------| +| allow add | void | Adds a player to the allowlist | Moderator | ['RunnerPlayer commandSource', 'ulong steamID'] | {} | +| allow remove | void | Removes a player from the allowlist | Moderator | ['RunnerPlayer commandSource', 'ulong steamID'] | {} | + +## Public Methods +| Function Name | Parameters | Defaults | +|:----------------|:-------------------------------------------------|:-----------| +| | | | +| | | | +| | | | +| Task | ['ulong steamID', 'PlayerJoiningArguments args'] | {} | +| AllowAdd | ['RunnerPlayer commandSource', 'ulong steamID'] | {} | +| AllowRemove | ['RunnerPlayer commandSource', 'ulong steamID'] | {} | +| | | | +| | | | \ No newline at end of file diff --git a/Announcements.cs b/Announcements.cs index a972c8e..27272d0 100644 --- a/Announcements.cs +++ b/Announcements.cs @@ -5,59 +5,48 @@ namespace BattleBitBaseModules; [Module("Periodically execute announcements and messages based on configurable delays and conditions", "1.0.0")] -public class Announcements : BattleBitModule -{ +public class Announcements : BattleBitModule { public AnnouncementsConfiguration Configuration { get; set; } = null!; public AnnouncementStore Store { get; set; } = null!; - public override Task OnConnected() - { + public override Task OnConnected() { Task.Run(announcements); return Task.CompletedTask; } - private async void announcements() - { - while (this.IsLoaded && this.Server.IsConnected) - { + private async void announcements() { + while (this.IsLoaded && this.Server.IsConnected) { int lastItem = this.doAnnouncement(this.Configuration.AnnounceLongDelay, this.Store.lastAnnounceLong, this.Store.lastAnnounceLongItem, this.Configuration.AnnounceLong, this.Server.AnnounceLong); - if (lastItem != -1) - { + if (lastItem != -1) { this.Store.lastAnnounceLong = DateTime.Now; this.Store.lastAnnounceLongItem = lastItem; } lastItem = this.doAnnouncement(this.Configuration.AnnounceShortDelay, this.Store.lastAnnounceShort, this.Store.lastAnnounceShortItem, this.Configuration.AnnounceShort, this.Server.AnnounceShort); - if (lastItem != -1) - { + if (lastItem != -1) { this.Store.lastAnnounceShort = DateTime.Now; this.Store.lastAnnounceShortItem = lastItem; } lastItem = this.doAnnouncement(this.Configuration.UILogOnServerDelay, this.Store.lastUILogOnServer, this.Store.lastUILogOnServerItem, this.Configuration.UILogOnServer, message => this.Server.UILogOnServer(message, this.Configuration.UILogOnserverTimeout)); - if (lastItem != -1) - { + if (lastItem != -1) { this.Store.lastUILogOnServer = DateTime.Now; this.Store.lastUILogOnServerItem = lastItem; } - lastItem = this.doAnnouncement(this.Configuration.MessageToPlayerDelay, this.Store.lastMessageToPlayer, this.Store.lastMessageToPlayerItem, this.Configuration.MessageToPlayer, message => - { - foreach (RunnerPlayer player in this.Server.AllPlayers) - { + lastItem = this.doAnnouncement(this.Configuration.MessageToPlayerDelay, this.Store.lastMessageToPlayer, this.Store.lastMessageToPlayerItem, this.Configuration.MessageToPlayer, message => { + foreach (RunnerPlayer player in this.Server.AllPlayers) { player.Message(message, this.Configuration.MessageToPlayerTimeout); } }); - if (lastItem != -1) - { + if (lastItem != -1) { this.Store.lastMessageToPlayer = DateTime.Now; this.Store.lastMessageToPlayerItem = lastItem; } lastItem = this.doAnnouncement(this.Configuration.SayToAllChatDelay, this.Store.lastSayToAllChat, this.Store.lastSayToAllChatItem, this.Configuration.SayToAllChat, this.Server.SayToAllChat); - if (lastItem != -1) - { + if (lastItem != -1) { this.Store.lastSayToAllChat = DateTime.Now; this.Store.lastSayToAllChatItem = lastItem; } @@ -68,20 +57,16 @@ private async void announcements() } } - private int doAnnouncement(int delay, DateTime lastAnnounce, int lastItem, string[] messages, Action action) - { - if (messages.Length == 0) - { + private int doAnnouncement(int delay, DateTime lastAnnounce, int lastItem, string[] messages, Action action) { + if (messages.Length == 0) { return -1; } - if (DateTime.Now.Subtract(lastAnnounce).TotalSeconds < delay) - { + if (DateTime.Now.Subtract(lastAnnounce).TotalSeconds < delay) { return -1; } - if (lastItem >= messages.Length) - { + if (lastItem >= messages.Length) { lastItem = 0; } @@ -91,8 +76,7 @@ private int doAnnouncement(int delay, DateTime lastAnnounce, int lastItem, strin } } -public class AnnouncementsConfiguration : ModuleConfiguration -{ +public class AnnouncementsConfiguration : ModuleConfiguration { public int AnnounceLongDelay { get; set; } = 600; public int AnnounceShortDelay { get; set; } = 300; public int UILogOnServerDelay { get; set; } = 60; @@ -105,6 +89,7 @@ public class AnnouncementsConfiguration : ModuleConfiguration public string[] AnnounceShort { get; set; } = Array.Empty(); public string[] UILogOnServer { get; set; } = Array.Empty(); public string[] MessageToPlayer { get; set; } = Array.Empty(); + public string[] SayToAllChat { get; set; } = new[] { "We hope you enjoy our server!", @@ -112,8 +97,7 @@ public class AnnouncementsConfiguration : ModuleConfiguration }; } -public class AnnouncementStore : ModuleConfiguration -{ +public class AnnouncementStore : ModuleConfiguration { public DateTime lastAnnounceLong { get; set; } = DateTime.MinValue; public DateTime lastAnnounceShort { get; set; } = DateTime.MinValue; public DateTime lastUILogOnServer { get; set; } = DateTime.MinValue; @@ -125,4 +109,4 @@ public class AnnouncementStore : ModuleConfiguration public int lastUILogOnServerItem { get; set; } = 0; public int lastMessageToPlayerItem { get; set; } = 0; public int lastSayToAllChatItem { get; set; } = 0; -} +} \ No newline at end of file diff --git a/Announcements.cs.md b/Announcements.cs.md new file mode 100644 index 0000000..e69de29 diff --git a/BasicProgression.cs b/BasicProgression.cs index 60c2004..04403cf 100644 --- a/BasicProgression.cs +++ b/BasicProgression.cs @@ -1,46 +1,73 @@ using BattleBitAPI.Common; using BBRAPIModules; using System; +using System.Collections.Generic; using System.IO; using System.Threading.Tasks; +using static BattleBitAPI.Common.PlayerStats; namespace BattleBitBaseModules; -[Module("Provide basic persistent progression for players", "1.1.0")] -public class BasicProgression : BattleBitModule -{ +[Module("Provide basic persistent progression for players", "1.1.1")] +public class BasicProgression : BattleBitModule { public BasicProgressionConfiguration Configuration { get; set; } = null!; + private IReadOnlyDictionary Cache { get { return _Cache; } } + private Dictionary _Cache { get; set; } = new(); + + public delegate void StatsRecievedHandler(ulong steamID, PlayerStats stats); + public event StatsRecievedHandler? OnStatsRecieved; private string dataDir => this.Configuration.PerServer ? Path.Combine(this.Configuration.DataDirectory, $"{this.Server.GameIP}:{this.Server.GamePort}") : this.Configuration.DataDirectory; - public override Task OnConnected() - { - if (!Directory.Exists(dataDir)) - { + public override Task OnConnected() { + if (!Directory.Exists(dataDir)) { Directory.CreateDirectory(dataDir); } return Task.CompletedTask; } - public override async Task OnPlayerJoiningToServer(ulong steamID, PlayerJoiningArguments args) - { - if (this.Configuration.ApplyInitialStatsOnEveryJoin) - { - args.Stats = this.Configuration.InitialStats ?? args.Stats; + + public async Task GetPlayerStats(RunnerPlayer player) => await GetPlayerStats(player.SteamID); + public async Task GetPlayerStats(ulong steamId) { + if (this.Cache.TryGetValue(steamId, out PlayerStats? stats)) { + return stats; + } + string playerFileName = getPlayerFileName(steamId); + for (int i = 0; i < 5; i++) { + try { + stats = new PlayerStats(File.ReadAllBytes(playerFileName)); + _Cache[steamId] = stats; + return stats; + } catch (Exception ex) { + this.Logger.Error($"Tried {i} times to read from file {playerFileName} but failed:{ex}"); + } + await Task.Delay(250); + } + + return this.Configuration.InitialStats?.ToPlayerStats() ?? new PlayerStats(); + } + + private void SetPlayerStats(ulong steamID, PlayerStats stats) { + _Cache[steamID] = stats; + OnStatsRecieved?.Invoke(steamID, stats); + } + private void RemovePlayerStats(ulong steamID) => _Cache.Remove(steamID); + + public override async Task OnPlayerJoiningToServer(ulong steamID, PlayerJoiningArguments args) { + if (this.Configuration.ApplyInitialStatsOnEveryJoin) { + args.Stats = this.Configuration.InitialStats?.ToPlayerStats() ?? args.Stats; + SetPlayerStats(steamID, args.Stats); return; } string playerFileName = getPlayerFileName(steamID); - for (int i = 0; i < 5; i++) - { - try - { - args.Stats = File.Exists(playerFileName) ? new PlayerStats(File.ReadAllBytes(playerFileName)) : (this.Configuration.InitialStats ?? args.Stats); + for (int i = 0; i < 5; i++) { + try { + args.Stats = File.Exists(playerFileName) ? new PlayerStats(File.ReadAllBytes(playerFileName)) : (this.Configuration.InitialStats?.ToPlayerStats() ?? args.Stats); + SetPlayerStats(steamID, args.Stats); return; - } - catch (Exception ex) - { + } catch (Exception ex) { this.Logger.Error($"Tried {i} times to read from file {playerFileName} but failed:{ex}"); } await Task.Delay(250); @@ -48,17 +75,14 @@ public override async Task OnPlayerJoiningToServer(ulong steamID, PlayerJoiningA this.Logger.Error("Giving up trying to read."); } - public override async Task OnSavePlayerStats(ulong steamID, PlayerStats stats) - { - for (int i = 0; i < 5; i++) - { - try - { + public override async Task OnSavePlayerStats(ulong steamID, PlayerStats stats) { + for (int i = 0; i < 5; i++) { + try { File.WriteAllBytes(getPlayerFileName(steamID), stats.SerializeToByteArray()); + await Task.Delay(Configuration.RemoveEntriesFromCacheDelay); + RemovePlayerStats(steamID); return; - } - catch (Exception ex) - { + } catch (Exception ex) { this.Logger.Error($"Tried {i} times to write to file {getPlayerFileName(steamID)} but failed:{ex}"); } await Task.Delay(250); @@ -66,27 +90,184 @@ public override async Task OnSavePlayerStats(ulong steamID, PlayerStats stats) this.Logger.Error("Giving up trying to save."); } - private string getPlayerFileName(ulong steamId) - { + private string getPlayerFileName(ulong steamId) { return Path.Combine(dataDir, $"{steamId}.bin"); } } -public class BasicProgressionConfiguration : ModuleConfiguration -{ +public class BasicProgressionConfiguration : ModuleConfiguration { public string DataDirectory { get; set; } = "./data/PersistentProgressionFiles"; public bool PerServer { get; set; } = false; public bool ApplyInitialStatsOnEveryJoin { get; set; } = false; - public PlayerStats? InitialStats { get; set; } = new PlayerStats() - { + public TimeSpan RemoveEntriesFromCacheDelay { get; set; } = TimeSpan.FromMinutes(1); + + public BasicPlayerStats? InitialStats { get; set; } = new BasicPlayerStats() { Achievements = new byte[0], IsBanned = false, - Progress = new PlayerStats.PlayerProgess(), + Progress = new BasicPlayerProgress(), Roles = Roles.None, Selections = new byte[0], ToolProgress = new byte[0] }; +} + +public class BasicPlayerStats { + public bool IsBanned { get; set; } + + public Roles Roles { get; set; } + + public BasicPlayerProgress Progress { get; set; } = new(); + + public byte[] ToolProgress { get; set; } = Array.Empty(); + + public byte[] Achievements { get; set; } = Array.Empty(); + + public byte[] Selections { get; set; } = Array.Empty(); + + public PlayerStats ToPlayerStats() { + return new() { + IsBanned = this.IsBanned, + Roles = this.Roles, + Progress = this.Progress.ToPlayerProgress(), + ToolProgress = this.ToolProgress, + Achievements = this.Achievements, + Selections = this.Selections + }; + } +} + +public class BasicPlayerProgress { + public uint KillCount { get; set; } + + public uint LeaderKills { get; set; } + + public uint AssaultKills { get; set; } + + public uint MedicKills { get; set; } + + public uint EngineerKills { get; set; } + + public uint SupportKills { get; set; } + + public uint ReconKills { get; set; } + + public uint DeathCount { get; set; } + + public uint WinCount { get; set; } + + public uint LoseCount { get; set; } + + public uint FriendlyShots { get; set; } + + public uint FriendlyKills { get; set; } + + public uint Revived { get; set; } + + public uint RevivedTeamMates { get; set; } + + public uint Assists { get; set; } + + public uint Prestige { get; set; } + + public uint Rank { get; set; } + + public uint EXP { get; set; } + + public uint ShotsFired { get; set; } + + public uint ShotsHit { get; set; } + + public uint Headshots { get; set; } + + public uint ObjectivesComplated { get; set; } + + public uint HealedHPs { get; set; } + + public uint RoadKills { get; set; } + + public uint Suicides { get; set; } + + public uint VehiclesDestroyed { get; set; } + + public uint VehicleHPRepaired { get; set; } + + public uint LongestKill { get; set; } + + public uint PlayTimeSeconds { get; set; } + + public uint LeaderPlayTime { get; set; } + + public uint AssaultPlayTime { get; set; } + + public uint MedicPlayTime { get; set; } + + public uint EngineerPlayTime { get; set; } + + public uint SupportPlayTime { get; set; } + + public uint ReconPlayTime { get; set; } + + public uint LeaderScore { get; set; } + + public uint AssaultScore { get; set; } + + public uint MedicScore { get; set; } + + public uint EngineerScore { get; set; } + + public uint SupportScore { get; set; } + + public uint ReconScore { get; set; } + + public uint TotalScore { get; set; } + + public PlayerProgess ToPlayerProgress() { + return new() { + KillCount = this.KillCount, + LeaderKills = this.LeaderKills, + AssaultKills = this.AssaultKills, + MedicKills = this.MedicKills, + EngineerKills = this.EngineerKills, + SupportKills = this.SupportKills, + ReconKills = this.ReconKills, + DeathCount = this.DeathCount, + WinCount = this.WinCount, + LoseCount = this.LoseCount, + FriendlyShots = this.FriendlyShots, + FriendlyKills = this.FriendlyKills, + Revived = this.Revived, + RevivedTeamMates = this.RevivedTeamMates, + Assists = this.Assists, + Prestige = this.Prestige, + Rank = this.Rank, + EXP = this.EXP, + ShotsFired = this.ShotsFired, + ShotsHit = this.ShotsHit, + Headshots = this.Headshots, + ObjectivesComplated = this.ObjectivesComplated, + HealedHPs = this.HealedHPs, + RoadKills = this.RoadKills, + Suicides = this.Suicides, + VehiclesDestroyed = this.VehiclesDestroyed, + VehicleHPRepaired = this.VehicleHPRepaired, + LongestKill = this.LongestKill, + PlayTimeSeconds = this.PlayTimeSeconds, + LeaderPlayTime = this.LeaderPlayTime, + AssaultPlayTime = this.AssaultPlayTime, + MedicPlayTime = this.MedicPlayTime, + EngineerPlayTime = this.EngineerPlayTime, + SupportPlayTime = this.SupportPlayTime, + ReconPlayTime = this.ReconPlayTime, + LeaderScore = this.LeaderScore, + AssaultScore = this.AssaultScore, + MedicScore = this.MedicScore, + EngineerScore = this.EngineerScore, + SupportScore = this.SupportScore, + ReconScore = this.ReconScore, + TotalScore = this.TotalScore + }; + } } \ No newline at end of file diff --git a/BasicProgression.cs.md b/BasicProgression.cs.md new file mode 100644 index 0000000..e69de29 diff --git a/BasicServerSettings.cs b/BasicServerSettings.cs index e97a5e3..d977f2e 100644 --- a/BasicServerSettings.cs +++ b/BasicServerSettings.cs @@ -1,32 +1,26 @@ using BattleBitAPI.Common; using BBRAPIModules; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Threading.Tasks; namespace BattleBitBaseModules; [Module("Configure basic server settings", "1.0.2")] -public class BasicServerSettings : BattleBitModule -{ +public class BasicServerSettings : BattleBitModule { public BasicServerSettingsConfiguration Configuration { get; set; } = null!; - public override Task OnConnected() - { + public override Task OnConnected() { this.applyServerSettings(); - foreach (RunnerPlayer player in this.Server.AllPlayers) - { + foreach (RunnerPlayer player in this.Server.AllPlayers) { this.applyPlayerSettings(player); } return Task.CompletedTask; } - public override Task OnGameStateChanged(GameState oldState, GameState newState) - { - if (oldState == newState) - { + public override Task OnGameStateChanged(GameState oldState, GameState newState) { + if (oldState == newState) { return Task.CompletedTask; } @@ -35,15 +29,13 @@ public override Task OnGameStateChanged(GameState oldState, GameState newState) return Task.CompletedTask; } - public override Task OnPlayerConnected(RunnerPlayer player) - { + public override Task OnPlayerConnected(RunnerPlayer player) { this.applyPlayerSettings(player); return Task.CompletedTask; } - private void applyServerSettings() - { + private void applyServerSettings() { this.Server.ServerSettings.APCSpawnDelayMultipler = this.Configuration.APCSpawnDelayMultipler ?? this.Server.ServerSettings.APCSpawnDelayMultipler; this.Server.ServerSettings.CanVoteDay = this.Configuration.CanVoteDay ?? this.Server.ServerSettings.CanVoteDay; this.Server.ServerSettings.CanVoteNight = this.Configuration.CanVoteNight ?? this.Server.ServerSettings.CanVoteNight; @@ -63,10 +55,8 @@ private void applyServerSettings() this.Server.ServerSettings.TeamlessMode = this.Configuration.TeamlessMode ?? this.Server.ServerSettings.TeamlessMode; } - private void applyRoundSettings(GameState gameState) - { - if (!this.Configuration.RoundSettings.ContainsKey(gameState)) - { + private void applyRoundSettings(GameState gameState) { + if (!this.Configuration.RoundSettings.ContainsKey(gameState)) { return; } @@ -79,8 +69,7 @@ private void applyRoundSettings(GameState gameState) this.Server.RoundSettings.TeamBTickets = roundSettings.TeamBTickets ?? this.Server.RoundSettings.TeamBTickets; } - private void applyPlayerSettings(RunnerPlayer player) - { + private void applyPlayerSettings(RunnerPlayer player) { player.Modifications.AirStrafe = this.Configuration.AirStrafe ?? player.Modifications.AirStrafe; player.Modifications.AllowedVehicles = this.Configuration.AllowedVehicles ?? player.Modifications.AllowedVehicles; player.Modifications.CanDeploy = this.Configuration.CanDeploy ?? player.Modifications.CanDeploy; @@ -113,10 +102,12 @@ private void applyPlayerSettings(RunnerPlayer player) player.Modifications.ReviveHP = this.Configuration.ReviveHP ?? player.Modifications.ReviveHP; } } -public class BasicServerSettingsConfiguration : ModuleConfiguration -{ + +public class BasicServerSettingsConfiguration : ModuleConfiguration { + // Server public float? APCSpawnDelayMultipler { get; set; } = null; + public float? HelicopterSpawnDelayMultipler { get; set; } = null; public float? SeaVehicleSpawnDelayMultipler { get; set; } = null; public float? TankSpawnDelayMultipler { get; set; } = null; @@ -136,6 +127,7 @@ public class BasicServerSettingsConfiguration : ModuleConfiguration // Player public bool? AirStrafe { get; set; } = null; + public VehicleType? AllowedVehicles { get; set; } = null; public bool? CanDeploy { get; set; } = null; public bool? CanSpectate { get; set; } = null; @@ -166,17 +158,16 @@ public class BasicServerSettingsConfiguration : ModuleConfiguration public bool? Freeze { get; set; } = null; public float? ReviveHP { get; set; } = null; - public Dictionary RoundSettings { get; set; } = new(new Dictionary() + public Dictionary RoundSettings { get; set; } = new() { { GameState.WaitingForPlayers, new() }, { GameState.CountingDown, new() }, { GameState.Playing, new() }, { GameState.EndingGame, new() } - }); + }; } -public class RoundSettingsConfiguration -{ +public class RoundSettingsConfiguration { public double? MaxTickets { get; set; } = null; public int? PlayersToStart { get; set; } = null; public int? SecondsLeft { get; set; } = null; diff --git a/BasicServerSettings.cs.md b/BasicServerSettings.cs.md new file mode 100644 index 0000000..e69de29 diff --git a/BattleBitBaseModules.csproj b/BattleBitBaseModules.csproj index 50a9d10..d1bee59 100644 --- a/BattleBitBaseModules.csproj +++ b/BattleBitBaseModules.csproj @@ -10,4 +10,8 @@ + + + + diff --git a/ChatOverwrite.cs b/ChatOverwrite.cs deleted file mode 100644 index a783827..0000000 --- a/ChatOverwrite.cs +++ /dev/null @@ -1,122 +0,0 @@ -using BattleBitAPI.Common; -using BBRAPIModules; -using Permissions; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ChatOverwrite; - -[RequireModule(typeof(GranularPermissions))] -[Module("Overwrite chat messages", "1.0.0")] -public class ChatOverwrite : BattleBitModule -{ - public ChatOverwriteConfiguration Configuration { get; set; } = null!; - - [ModuleReference] - public dynamic? CommandHandler { get; set; } - - [ModuleReference] - public GranularPermissions GranularPermissions { get; set; } = null!; - - public override Task OnPlayerTypedMessage(RunnerPlayer player, ChatChannel channel, string msg) - { - if (this.CommandHandler is not null && this.CommandHandler.IsCommand(msg)) - { - this.Logger.Debug($"Ignoring message {msg} from {player.Name} because it is a command"); - return Task.FromResult(true); - } - - if (this.GranularPermissions.HasPermission(player.SteamID, "ChatOverwrite.Bypass")) - { - this.Logger.Debug($"Ignoring message {msg} from {player.Name} because they have the ChatOverwrite.Bypass permission"); - return Task.FromResult(true); - } - - string? permission = this.Configuration.Overwrites.Keys.FirstOrDefault(k => this.GranularPermissions.HasPermission(player.SteamID, k)); - - if (String.IsNullOrEmpty(permission)) - { - this.Logger.Debug($"Ignoring message {msg} from {player.Name} because they do not have any ChatOverwrite permissions"); - return Task.FromResult(true); - } - - OverwriteMessage overwriteMessage = this.Configuration.Overwrites[permission]; - - this.Logger.Debug($"Overwriting message {msg} from {player.Name} with permission {permission}"); - - string gradientName = FormatTextWithGradient(player.Name, overwriteMessage.GradientColors ?? Array.Empty()); - - string teamName = player.Team == Team.TeamA ? "US" : "RU"; - char? squadLetter = player.InSquad ? player.SquadName.ToString()[0] : null; - - foreach (RunnerPlayer chatTarget in this.Server.AllPlayers) - { - if (channel == ChatChannel.SquadChat && (chatTarget.Team != player.Team || !chatTarget.InSquad || !player.InSquad || chatTarget.SquadName != player.SquadName)) - { - continue; - } - - if (channel == ChatChannel.TeamChat && chatTarget.Team != player.Team) - { - continue; - } - - string nameColor = player.Team == chatTarget.Team && player.InSquad && chatTarget.InSquad && player.SquadName == chatTarget.SquadName ? "green" : (player.Team == chatTarget.Team ? "blue" : "red"); - string teamAndSquadIndicator = teamName; - if (squadLetter != null) - { - teamAndSquadIndicator += $"-{squadLetter}"; - } - teamAndSquadIndicator = $"[{teamAndSquadIndicator}]"; - - string textColor = channel == ChatChannel.TeamChat ? "blue" : (channel == ChatChannel.SquadChat ? "green" : "white"); - - chatTarget.SayToChat(string.Format(overwriteMessage.Text, nameColor, player.Name, teamAndSquadIndicator, textColor, msg, gradientName)); - } - - return Task.FromResult(false); - } - - private static string FormatTextWithGradient(string text, string[] gradientColors) - { - if (string.IsNullOrEmpty(text) || gradientColors.Length == 0) - { - return text; - } - - int segmentCount = gradientColors.Length; - int segmentLength = text.Length / segmentCount; - int remainder = text.Length % segmentCount; - - StringBuilder formattedName = new StringBuilder(); - int currentIndex = 0; - - for (int i = 0; i < segmentCount; i++) - { - int currentSegmentLength = segmentLength + (i < remainder ? 1 : 0); - string currentColor = gradientColors[i]; - string segmentText = text.Substring(currentIndex, currentSegmentLength); - - formattedName.Append($"{segmentText}"); - currentIndex += currentSegmentLength; - } - - return formattedName.ToString(); - } -} - -public class ChatOverwriteConfiguration : ModuleConfiguration -{ - public Dictionary Overwrites { get; set; } = new() - { - { "ChatOverwrite.Rainbow", new("{5}{2} : {4}", new string[] { "red", "orange", "yellow", "green", "blue", "purple" }) }, - { "ChatOverwrite.Large", new("{1}{2} : {4}") }, - { "ChatOverwrite.Sus", new("{1}{2} : I am an idiot and cheat in online games. Please go to my Steam profile and report me!") }, - { "ChatOverwrite.Admin", new("{1}{2}[Server Admin] : {4}") } - }; -} - -public record OverwriteMessage(string Text, string[]? GradientColors = null); \ No newline at end of file diff --git a/CommandHandler.cs b/CommandHandler.cs index 174ce51..6d1cef9 100644 --- a/CommandHandler.cs +++ b/CommandHandler.cs @@ -11,11 +11,10 @@ namespace Commands; -[Module("Basic in-game chat command handler library", "1.1.0")] -public class CommandHandler : BattleBitModule -{ +[Module("Basic in-game chat command handler library", "1.2.0")] +public class CommandHandler : BattleBitModule { public static CommandConfiguration CommandConfiguration { get; set; } = null!; - public CommandPermissions CommandPermissions { get; set; } = null!; + public CommandSettings CommandSettings { get; set; } = null!; private Dictionary commandCallbacks = new(); @@ -28,90 +27,81 @@ public class CommandHandler : BattleBitModule [ModuleReference] public dynamic? PlayerPermissions { get; set; } - public override void OnModulesLoaded() - { - if (this.PlayerPermissions is null && this.GranularPermissions is null) - { + public override void OnModulesLoaded() { + if (this.PlayerPermissions is null && this.GranularPermissions is null) { this.Logger.Warn($"Neither PlayerPermissions nor GranularPermissions is loaded. This module will not be able to check permissions for commands."); } this.Register(this); } - public void Register(BattleBitModule module) - { - foreach (MethodInfo method in module.GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) - { + public void Register(BattleBitModule module) { + foreach (MethodInfo method in module.GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { CommandCallbackAttribute? attribute = method.GetCustomAttribute(); - if (attribute == null) - { + if (attribute == null) { continue; } - if (attribute.AllowedRoles != Roles.None) - { + if (attribute.AllowedRoles != Roles.None) { this.Logger.Warn($"Command callback method {method.Name} in module {module.GetType().Name} has the deprecated AllowedRoles property set. Use Permissions instead. If you did not make this module, report the issue to the module author."); - } - else if (attribute.Permissions.Length == 1 && attribute.Permissions[0] == "*") - { + } else if (attribute.Permissions.Length == 1 && attribute.Permissions[0] == "*") { this.Logger.Warn($"Command callback method {method.Name} in module {module.GetType().Name} has no permissions set. This is not recommended as it allows everyone to use the command. Commands should have at least one granular permission or a PlayerPermissions role."); } // Store command permissions - if (!this.CommandPermissions.Permissions.ContainsKey(attribute.Name)) - { - this.CommandPermissions.Permissions.Add(attribute.Name, null); + if (!this.CommandSettings.Settings.ContainsKey(attribute.Name)) { + this.CommandSettings.Settings.Add(attribute.Name, new()); } // Validate parameter ParameterInfo[] parameters = method.GetParameters(); - if (parameters.Length > 0 && parameters[0].ParameterType != typeof(RunnerPlayer)) - { - throw new Exception($"Command callback method {method.Name} in module {module.GetType().Name} has invalid first parameter. Must be of type RunnerPlayer."); + + if (parameters.Length == 0) { + this.Logger.Error($"Command callback method {method.Name} in module {module.GetType().Name} has no parameters. Must have at least one parameter of type Context."); + continue; + } + + if (parameters[0].ParameterType != typeof(Context)) { + this.Logger.Error($"Command callback method {method.Name} in module {module.GetType().Name} has invalid first parameter. Must be of type Context."); + continue; } string command = attribute.Name.Trim().ToLower(); // Prevent duplicate command names in different methods or modules - if (this.commandCallbacks.ContainsKey(command)) - { - if (this.commandCallbacks[command].Method == method) - { + if (this.commandCallbacks.ContainsKey(command)) { + if (this.commandCallbacks[command].Method == method) { continue; } - if (this.commandCallbacks[command].Module.GetType().Name == module.GetType().Name) - { - throw new Exception($"Command callback method {method.Name} in module {module.GetType().Name} has the same command name {command} as another command callback method {this.commandCallbacks[command].Method.Name} in the same module."); - } - else - { - throw new Exception($"Command callback method {method.Name} in module {module.GetType().Name} has the same command name {command} as command callback method {this.commandCallbacks[command].Method.Name} in module {this.commandCallbacks[command].Module.GetType().Name}."); + if (this.commandCallbacks[command].Module.GetType().Name == module.GetType().Name) { + this.Logger.Error($"Command callback method {method.Name} in module {module.GetType().Name} has the same command name {command} as another command callback method {this.commandCallbacks[command].Method.Name} in the same module."); + continue; + } else { + this.Logger.Error($"Command callback method {method.Name} in module {module.GetType().Name} has the same command name {command} as command callback method {this.commandCallbacks[command].Method.Name} in module {this.commandCallbacks[command].Module.GetType().Name}."); + continue; } } // Prevent parent commands of subcommands (!perm command does not allow !perm add and !perm remove) - foreach (string subcommand in this.commandCallbacks.Keys.Where(c => c.Contains(' '))) - { - if (!subcommand.StartsWith(command)) - { + foreach (string subcommand in this.commandCallbacks.Keys.Where(c => c.Contains(' '))) { + if (!subcommand.StartsWith(command)) { continue; } - throw new Exception($"Command callback {command} in module {module.GetType().Name} conflicts with subcommand {subcommand}."); + this.Logger.Error($"Command callback {command} in module {module.GetType().Name} conflicts with subcommand {subcommand}."); + continue; } // Prevent subcommands of existing commands (!perm add and !perm remove do not allow !perm) - if (command.Contains(' ')) - { + if (command.Contains(' ')) { string[] subcommandChain = command.Split(' '); string subcommand = ""; - for (int i = 0; i < subcommandChain.Length; i++) - { + for (int i = 0; i < subcommandChain.Length; i++) { subcommand += $"{subcommandChain[i]} "; - if (this.commandCallbacks.ContainsKey(subcommand.Trim())) - { - throw new Exception($"Command callback {command} in module {module.GetType().Name} conflicts with parent command {subcommand.Trim()}."); + if (this.commandCallbacks.ContainsKey(subcommand.Trim())) { + this.Logger.Error($"Command callback {command} in module {module.GetType().Name} conflicts with parent command {subcommand.Trim()}."); + continue; } } } @@ -119,54 +109,43 @@ public void Register(BattleBitModule module) this.commandCallbacks.Add(command, (module, method)); } - this.CommandPermissions.Save(); + this.CommandSettings.Save(); } - public override Task OnPlayerTypedMessage(RunnerPlayer player, ChatChannel channel, string message) - { - if (!IsCommand(message)) - { + public override Task OnPlayerTypedMessage(RunnerPlayer player, ChatChannel channel, string message) { + if (!IsCommand(message)) { return Task.FromResult(true); } - Task.Run(() => this.handleCommand(player, message)); + Task.Run(() => this.HandleCommand(new ChatSource(player), message)); return Task.FromResult(false); } - public override void OnConsoleCommand(string command) - { - if (!IsCommand(command)) - { + public override void OnConsoleCommand(string command) { + if (!IsCommand(command)) { return; } - Task.Run(() => this.handleCommand(null, command)); + Task.Run(() => this.HandleCommand(new ConsoleSource(), command)); } - public bool IsCommand(string message) - { + public bool IsCommand(string message) { return message.StartsWith(CommandConfiguration.CommandPrefix) && message.Length > CommandConfiguration.CommandPrefix.Length; } - public bool HasPermissionForCommand(RunnerPlayer player, CommandCallbackAttribute attribute) - { - if (attribute.AllowedRoles != Roles.None) - { + public bool HasPermissionForCommand(RunnerPlayer player, CommandCallbackAttribute attribute) { + if (attribute.AllowedRoles != Roles.None) { this.Logger.Warn($"Command {attribute.Name} has the deprecated AllowedRoles property set. Use Permissions instead. If you did not make this module, report the issue to the module author."); - if (attribute.Permissions.Length == 1 && attribute.Permissions[0] == "*") - { + if (attribute.Permissions.Length == 1 && attribute.Permissions[0] == "*") { List roles = new(); - foreach (Roles role in Enum.GetValues(typeof(Roles))) - { - if (role == Roles.None) - { + foreach (Roles role in Enum.GetValues(typeof(Roles))) { + if (role == Roles.None) { continue; } - if ((attribute.AllowedRoles & role) == role) - { + if ((attribute.AllowedRoles & role) == role) { roles.Add(role); } } @@ -176,55 +155,40 @@ public bool HasPermissionForCommand(RunnerPlayer player, CommandCallbackAttribut } } - if (attribute.Permissions.Length == 0 || attribute.Permissions[0] == "*") - { + if (attribute.Permissions.Length == 0 || attribute.Permissions[0] == "*") { return true; } - if (this.PlayerPermissions is null && this.GranularPermissions is null) - { + if (this.PlayerPermissions is null && this.GranularPermissions is null) { this.Logger.Warn($"Command {attribute.Name} requires permissions but neither PlayerPermissions nor GranularPermissions is loaded."); return false; } // Permission overwrites from configuration file string[] requiredPermissions = attribute.Permissions; - if (!this.CommandPermissions.Permissions.ContainsKey(attribute.Name)) - { - this.Logger.Error($"Command {attribute.Name} has no permissions stored in the CommandPermissions configuration file. This should not happen, report the bug."); - } - - if (this.CommandPermissions.Permissions.ContainsKey(attribute.Name) && this.CommandPermissions.Permissions[attribute.Name] is not null) - { - requiredPermissions = this.CommandPermissions.Permissions[attribute.Name]!; + if (this.CommandSettings.Settings.ContainsKey(attribute.Name) && this.CommandSettings.Settings.ContainsKey(attribute.Name) && this.CommandSettings.Settings[attribute.Name]?.Permissions is not null) { + requiredPermissions = this.CommandSettings.Settings[attribute.Name]!.Permissions!; } // PlayerPermissions module - if (this.PlayerPermissions is not null) - { - foreach (string requiredPermission in requiredPermissions) - { - if (!Enum.TryParse(requiredPermission, true, out Roles role)) - { + if (this.PlayerPermissions is not null) { + foreach (string requiredPermission in requiredPermissions) { + if (!Enum.TryParse(requiredPermission, true, out Roles role)) { this.Logger.Warn($"Command {attribute.Name} could not resolve {requiredPermission} to a Role for PlayerPermissions."); this.Logger.Info($"This warning can be ignored if you are also using the GranularPermissions module as the permission may be defined there."); continue; } - if (this.PlayerPermissions?.HasPlayerRole(player.SteamID, role)) - { + if (this.PlayerPermissions?.HasPlayerRole(player.SteamID, role)) { return true; } } } // GranularPermissions module - if (this.GranularPermissions is not null) - { - foreach (string requiredPermission in requiredPermissions) - { - if (this.GranularPermissions.HasPermission(player.SteamID, requiredPermission)) - { + if (this.GranularPermissions is not null) { + foreach (string requiredPermission in requiredPermissions) { + if (this.GranularPermissions.HasPermission(player.SteamID, requiredPermission)) { return true; } } @@ -233,26 +197,23 @@ public bool HasPermissionForCommand(RunnerPlayer player, CommandCallbackAttribut return false; } - private void handleCommand(RunnerPlayer? player, string message) - { + public void HandleCommand(Source source, string message) { string[] fullCommand = parseCommandString(message); string command = fullCommand[0].Trim().ToLower()[CommandConfiguration.CommandPrefix.Length..]; + ChatSource? chatSource = source as ChatSource; + Context errorContext = new Context(source, message, command, Array.Empty(), Array.Empty(), null, this, null); + int subCommandSkip; - for (subCommandSkip = 1; subCommandSkip < fullCommand.Length && !this.commandCallbacks.ContainsKey(command); subCommandSkip++) - { + for (subCommandSkip = 1; subCommandSkip < fullCommand.Length && !this.commandCallbacks.ContainsKey(command); subCommandSkip++) { command += $" {fullCommand[subCommandSkip]}"; } - if (!this.commandCallbacks.ContainsKey(command)) - { - if (player is null) - { - this.Logger.Error($"Command not found: {command}"); - } - else - { - player.Message("Command not found"); + if (!this.commandCallbacks.ContainsKey(command)) { + if (chatSource is not null) { + errorContext.Reply($"Command not found: {command}"); + } else { + errorContext.Reply($"Command not found: {command}"); } return; } @@ -262,121 +223,86 @@ private void handleCommand(RunnerPlayer? player, string message) (BattleBitModule module, MethodInfo method) = this.commandCallbacks[command]; CommandCallbackAttribute commandCallbackAttribute = method.GetCustomAttribute()!; - if (player is null && !commandCallbackAttribute.ConsoleCommand) - { + if (source is ConsoleSource && !commandCallbackAttribute.ConsoleCommand) { this.Logger.Error($"Command {command} is not a console command."); return; } // Permissions - if (player is not null && !this.HasPermissionForCommand(player, commandCallbackAttribute)) - { - player.Message($"You don't have permission to use this command.{Environment.NewLine}Required permission: {string.Join(" or ", commandCallbackAttribute.Permissions)}"); - return; - } + if (chatSource is not null && !this.HasPermissionForCommand(chatSource.Invoker, commandCallbackAttribute)) { + bool hideInaccessible = CommandConfiguration.HideInaccessibleCommands; + if (this.CommandSettings.Settings[command]?.HideInaccessible is not null) { + hideInaccessible = this.CommandSettings.Settings[command]!.HideInaccessible!.Value; + } - ParameterInfo[] parameters = method.GetParameters(); + if (hideInaccessible) { + errorContext.Reply($"Command not found: {command}"); + return; + } - if (parameters.Length == 0) - { - method.Invoke(module, null); + errorContext.Reply($"You don't have permission to use this command.{Environment.NewLine}Required permission: {string.Join(" or ", commandCallbackAttribute.Permissions)}"); return; } + ParameterInfo[] parameters = method.GetParameters(); + bool hasOptional = parameters.Any(p => p.IsOptional); - if (fullCommand.Length - 1 < parameters.Skip(1).Count(p => !p.IsOptional) || fullCommand.Length - 1 > parameters.Length - 1) - { - if (player is not null) - { - messagePlayerCommandUsage(player, method, $"Require {(hasOptional ? $"between {parameters.Skip(1).Count(p => !p.IsOptional)} and {parameters.Length - 1}" : $"{parameters.Length - 1}")} but got {fullCommand.Length - 1} argument{((fullCommand.Length - 1) == 1 ? "" : "s")}."); - } - else - { - this.Logger.Error($"Command {command} requires {(hasOptional ? $"between {parameters.Skip(1).Count(p => !p.IsOptional)} and {parameters.Length - 1}" : $"{parameters.Length - 1}")} but got {fullCommand.Length - 1} argument{((fullCommand.Length - 1) == 1 ? "" : "s")}."); - } + if (fullCommand.Length - 1 < parameters.Skip(1).Count(p => !p.IsOptional) || fullCommand.Length - 1 > parameters.Length - 1) { + sendCommandUsageMessage(errorContext, method, $"Require {(hasOptional ? $"between {parameters.Skip(1).Count(p => !p.IsOptional)} and {parameters.Length - 1}" : $"{parameters.Length - 1}")} but got {fullCommand.Length - 1} argument{((fullCommand.Length - 1) == 1 ? "" : "s")}."); return; } object?[] args = new object[parameters.Length]; - args[0] = player; - for (int i = 1; i < parameters.Length; i++) - { + for (int i = 1; i < parameters.Length; i++) { ParameterInfo parameter = parameters[i]; - if (parameter.IsOptional && i >= fullCommand.Length) - { + if (parameter.IsOptional && i >= fullCommand.Length) { args[i] = parameter.DefaultValue; continue; } string argument = fullCommand[i].Trim(); - if (parameter.ParameterType == typeof(string)) - { + if (parameter.ParameterType == typeof(string)) { args[i] = argument; - } - else if (parameter.ParameterType == typeof(RunnerPlayer)) - { + } else if (parameter.ParameterType == typeof(RunnerPlayer)) { RunnerPlayer? targetPlayer = null; - if (this.PlayerFinder is not null) - { - try - { + if (ulong.TryParse(argument, out ulong steamId) && this.Server.AllPlayers.FirstOrDefault(p => p.SteamID == steamId) is RunnerPlayer playerBySteamId) { + args[i] = targetPlayer; + continue; + } + + if (this.PlayerFinder is not null) { + try { targetPlayer = this.PlayerFinder.ByNamePart(argument); - } - catch (Exception ex) - { - if (player is not null) - { - player.Message($"Error while searching for player name containing {argument}.{Environment.NewLine}{ex.Message}"); - } - else - { + } catch (Exception ex) { + if (chatSource is not null) { + errorContext.Reply($"Error while searching for player name containing {argument}.{Environment.NewLine}{ex.Message}"); + } else { this.Logger.Error($"Error while searching for player name containing {argument}.{Environment.NewLine}{ex.Message}"); } return; } - if (targetPlayer == null) - { - if (player is not null) - { - player.Message($"Could not find player name containing {argument}."); - } - else - { - this.Logger.Error($"Could not find player name containing {argument}."); - } + if (targetPlayer == null) { + errorContext.Reply($"Could not find player name containing {argument}."); return; } - } - else - { + } else { targetPlayer = this.Server.AllPlayers.FirstOrDefault(p => p.Name.Equals(argument, StringComparison.OrdinalIgnoreCase)); } - if (targetPlayer == null) - { - if (player is not null) - { - player.Message($"Could not find player {argument}."); - } - else - { - this.Logger.Error($"Could not find player {argument}."); - } + if (targetPlayer == null) { + errorContext.Reply($"Could not find player {argument}."); return; } args[i] = targetPlayer; - } - else - { - if (!tryParseParameter(parameter, argument, out object? parsedValue)) - { - messagePlayerCommandUsage(player, method, $"Couldn't parse value {argument} to type {parameter.ParameterType.Name}"); + } else { + if (!tryParseParameter(parameter, argument, out object? parsedValue)) { + sendCommandUsageMessage(errorContext, method, $"Couldn't parse value {argument} to type {parameter.ParameterType.Name}"); return; } @@ -384,90 +310,70 @@ private void handleCommand(RunnerPlayer? player, string message) } } - method.Invoke(module, args); + args[0] = new Context(source, message, command, fullCommand.Skip(1).ToArray(), args.Skip(1).ToArray(), module, this, commandCallbackAttribute); + + object? result = method.Invoke(module, args); + if (result is not null) { + source.Reply((Context)args[0]!, result.ToString() ?? "No reply"); + } } - private void messagePlayerCommandUsage(RunnerPlayer? player, MethodInfo method, string? error = null) - { + private void sendCommandUsageMessage(Context context, MethodInfo method, string? error = null) { CommandCallbackAttribute commandCallbackAttribute = method.GetCustomAttribute()!; bool hasOptional = method.GetParameters().Any(p => p.IsOptional); - if (player is not null) - { - player.Message($"Invalid command usage{(error == null ? "" : $" ({error})")}.
Usage: {CommandConfiguration.CommandPrefix}{commandCallbackAttribute.Name} {string.Join(' ', method.GetParameters().Skip(1).Select(s => $"{s.Name}{(s.IsOptional ? "*" : "")}"))}{(hasOptional ? "
* Parameter is optional." : "")}"); - } - else - { - this.Logger.Error($"Invalid command usage{(error == null ? "" : $" ({error})")}.{Environment.NewLine}Usage: {CommandConfiguration.CommandPrefix}{commandCallbackAttribute.Name} {string.Join(' ', method.GetParameters().Skip(1).Select(s => $"{s.Name}{(s.IsOptional ? "*" : "")}"))}{(hasOptional ? $"{Environment.NewLine}* Parameter is optional." : "")}"); + if (context.Source is ChatSource chatSource) { + context.Reply($"Invalid command usage{(error == null ? "" : $" ({error})")}.
Usage: {CommandConfiguration.CommandPrefix}{commandCallbackAttribute.Name} {string.Join(' ', method.GetParameters().Skip(1).Select(s => $"{s.Name}{(s.IsOptional ? "*" : "")}"))}{(hasOptional ? "
* Parameter is optional." : "")}"); + } else { + context.Reply($"Invalid command usage{(error == null ? "" : $" ({error})")}.{Environment.NewLine}Usage: {CommandConfiguration.CommandPrefix}{commandCallbackAttribute.Name} {string.Join(' ', method.GetParameters().Skip(1).Select(s => $"{s.Name}{(s.IsOptional ? "*" : "")}"))}{(hasOptional ? $"{Environment.NewLine}* Parameter is optional." : "")}"); } } - private static bool tryParseParameter(ParameterInfo parameterInfo, string input, out object? parsedValue) - { + private static bool tryParseParameter(ParameterInfo parameterInfo, string input, out object? parsedValue) { parsedValue = null; - try - { - if (parameterInfo.ParameterType.IsEnum) - { + try { + if (parameterInfo.ParameterType.IsEnum) { parsedValue = Enum.Parse(parameterInfo.ParameterType, input, true); - } - else - { + } else { Type? targetType = targetType = Nullable.GetUnderlyingType(parameterInfo.ParameterType); - if (targetType is null) - { + if (targetType is null) { targetType = parameterInfo.ParameterType; } parsedValue = Convert.ChangeType(input, targetType); } return true; - } - catch - { + } catch { return false; } } - private static string[] parseCommandString(string command) - { + private static string[] parseCommandString(string command) { List parameterValues = new(); string[] tokens = command.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); bool insideQuotes = false; StringBuilder currentValue = new(); - foreach (var token in tokens) - { - if (!insideQuotes) - { - if (token.StartsWith("\"") && token.EndsWith("\"")) - { + foreach (var token in tokens) { + if (!insideQuotes) { + if (token.StartsWith("\"") && token.EndsWith("\"")) { insideQuotes = false; currentValue.Clear(); parameterValues.Add(token.Substring(1, token.Length - 2)); - } - else if (token.StartsWith("\"")) - { + } else if (token.StartsWith("\"")) { insideQuotes = true; currentValue.Append(token.Substring(1)); - } - else - { + } else { parameterValues.Add(token); } - } - else - { - if (token.EndsWith("\"")) - { + } else { + if (token.EndsWith("\"")) { insideQuotes = false; currentValue.Append(" ").Append(token.Substring(0, token.Length - 1)); parameterValues.Add(currentValue.ToString()); currentValue.Clear(); - } - else - { + } else { currentValue.Append(" ").Append(token); } } @@ -476,62 +382,80 @@ private static string[] parseCommandString(string command) return parameterValues.Select(unescapeQuotes).ToArray(); } - private static string unescapeQuotes(string input) - { + private static string unescapeQuotes(string input) { return input.Replace("\\\"", "\""); } - [CommandCallback("help", Description = "Shows this help message", Permissions = new[] { "CommandHandler.Help" })] - public void HelpCommand(RunnerPlayer player, int page = 1) - { + [CommandCallback("help", Description = "Shows this help message", Permissions = new[] { "CommandHandler.Help" }, ConsoleCommand = true)] + public string HelpCommand(Context context, int page = 1) { List helpLines = new(); - foreach (var (commandKey, (module, method)) in this.commandCallbacks) - { + foreach (var (commandKey, (module, method)) in this.commandCallbacks) { CommandCallbackAttribute commandCallbackAttribute = method.GetCustomAttribute()!; - if (!this.HasPermissionForCommand(player, commandCallbackAttribute)) - { + if (context.Source is ChatSource chatSource && !this.HasPermissionForCommand(chatSource.Invoker, commandCallbackAttribute)) { continue; } - helpLines.Add($"{CommandConfiguration.CommandPrefix}{commandCallbackAttribute.Name}{(string.IsNullOrEmpty(commandCallbackAttribute.Description) ? "" : $": {commandCallbackAttribute.Description}")}"); + if (context.Source is ChatSource) { + helpLines.Add($"{CommandConfiguration.CommandPrefix}{commandCallbackAttribute.Name}{(string.IsNullOrEmpty(commandCallbackAttribute.Description) ? "" : $": {commandCallbackAttribute.Description}")}"); + } else { + helpLines.Add($"{CommandConfiguration.CommandPrefix}{commandCallbackAttribute.Name}{(string.IsNullOrEmpty(commandCallbackAttribute.Description) ? "" : $": {commandCallbackAttribute.Description}")}"); + } } int pages = (int)Math.Ceiling((double)helpLines.Count / CommandConfiguration.CommandsPerPage); - if (page < 1 || page > pages) - { - player.Message($"Invalid page number. Must be between 1 and {pages}."); - return; + if (page < 1 || page > pages) { + if (context.Source is ChatSource) { + return $"Invalid page number. Must be between 1 and {pages}."; + } else { + return $"Invalid page number. Must be between 1 and {pages}."; + } } - player.Message($"<#FFA500>Available commands
{Environment.NewLine}{string.Join(Environment.NewLine, helpLines.Skip((page - 1) * CommandConfiguration.CommandsPerPage).Take(CommandConfiguration.CommandsPerPage))}{(pages > 1 ? $"{Environment.NewLine}Page {page} of {pages}{(page < pages ? $" - type !help {page + 1} for next page" : "")}" : "")}"); + if (context.Source is ChatSource) { + return $"<#FFA500>Available commands
{Environment.NewLine}{string.Join(Environment.NewLine, helpLines.Skip((page - 1) * CommandConfiguration.CommandsPerPage).Take(CommandConfiguration.CommandsPerPage))}{(pages > 1 ? $"{Environment.NewLine}Page {page} of {pages}{(page < pages ? $" - type !help {page + 1} for next page" : "")}" : "")}"; + } else { + return $"Available commands{Environment.NewLine}{string.Join(Environment.NewLine, helpLines.Skip((page - 1) * CommandConfiguration.CommandsPerPage).Take(CommandConfiguration.CommandsPerPage))}{(pages > 1 ? $"{Environment.NewLine}Page {page} of {pages}{(page < pages ? $" - type !help {page + 1} for next page" : "")}" : "")}"; + } } - [CommandCallback("cmdhelp", Description = "Shows help for a specific command", Permissions = new[] { "CommandHandler.CommandHelp" })] - public void CommandHelpCommand(RunnerPlayer player, string command) - { - if (!this.commandCallbacks.TryGetValue(command, out var commandCallback)) - { - player.Message($"Command {command} not found."); - return; + [CommandCallback("cmdhelp", Description = "Shows help for a specific command", Permissions = new[] { "CommandHandler.CommandHelp" }, ConsoleCommand = true)] + public string CommandHelpCommand(Context context, string command) { + if (!this.commandCallbacks.TryGetValue(command, out var commandCallback)) { + if (context.Source is ChatSource) { + return $"Command {command} not found."; + } else { + return $"Command {command} not found."; + } } CommandCallbackAttribute commandCallbackAttribute = commandCallback.Method.GetCustomAttribute()!; - if (!this.HasPermissionForCommand(player, commandCallbackAttribute)) - { - player.Message($"You don't have permission to see help about this command."); - return; + if (context.Source is ChatSource chatSource && !this.HasPermissionForCommand(chatSource.Invoker, commandCallbackAttribute)) { + bool hideInaccessible = CommandConfiguration.HideInaccessibleCommands; + if (this.CommandSettings.Settings[command]?.HideInaccessible is not null) { + hideInaccessible = this.CommandSettings.Settings[command]!.HideInaccessible!.Value; + } + + if (hideInaccessible) { + return $"Command {command} not found."; + } + + return $"You don't have permission to see help about this command."; } bool hasOptional = commandCallback.Method.GetParameters().Any(p => p.IsOptional); - player.Message($"{commandCallback.Module.GetType().Name} {commandCallbackAttribute.Name}
{commandCallbackAttribute.Description}
<#F5F5F5>{CommandConfiguration.CommandPrefix}{commandCallbackAttribute.Name} {string.Join(' ', commandCallback.Method.GetParameters().Skip(1).Select(s => $"{s.Name}{(s.IsOptional ? "*" : "")}"))}{(hasOptional ? "
* Parameter is optional." : "")}"); + + if (context.Source is ChatSource) { + return $"{commandCallback.Module.GetType().Name} {commandCallbackAttribute.Name}
{commandCallbackAttribute.Description}
<#F5F5F5>{CommandConfiguration.CommandPrefix}{commandCallbackAttribute.Name} {string.Join(' ', commandCallback.Method.GetParameters().Skip(1).Select(s => $"{s.Name}{(s.IsOptional ? "*" : "")}"))}{(hasOptional ? "
* Parameter is optional." : "")}"; + } else { + return $"{commandCallback.Module.GetType().Name} {commandCallbackAttribute.Name}{Environment.NewLine}{commandCallbackAttribute.Description}{Environment.NewLine}{CommandConfiguration.CommandPrefix}{commandCallbackAttribute.Name} {string.Join(' ', commandCallback.Method.GetParameters().Skip(1).Select(s => $"{s.Name}{(s.IsOptional ? "*" : "")}"))}{(hasOptional ? $"{Environment.NewLine}* Parameter is optional." : "")}"; + } } } -public class CommandCallbackAttribute : Attribute -{ +public class CommandCallbackAttribute : Attribute { public string Name { get; set; } public string Description { get; set; } = string.Empty; @@ -539,19 +463,88 @@ public class CommandCallbackAttribute : Attribute public string[] Permissions { get; set; } = new[] { "*" }; public bool ConsoleCommand { get; set; } = false; - public CommandCallbackAttribute(string name) - { + public CommandCallbackAttribute(string name) { this.Name = name; } } -public class CommandConfiguration : ModuleConfiguration -{ +public class CommandConfiguration : ModuleConfiguration { public string CommandPrefix { get; set; } = "!"; public int CommandsPerPage { get; set; } = 6; + public int MessageTimeout { get; set; } = 15; + public bool HideInaccessibleCommands { get; set; } = false; + public bool ReplyToChat { get; set; } = false; +} + +public class CommandSettings : ModuleConfiguration { + public Dictionary Settings { get; set; } = new(); } -public class CommandPermissions : ModuleConfiguration -{ - public Dictionary Permissions { get; set; } = new(); +public class CommandSetting { + public string[]? Permissions { get; set; } + public bool? ReplyToChat { get; set; } + public int? MessageTimeout { get; set; } + public bool? HideInaccessible { get; set; } } + +public class Context { + public Source Source { get; set; } + public string Message { get; set; } + public string Command { get; set; } + public string[] RawParameters { get; set; } + public object?[] Parameters { get; set; } + public BattleBitModule? Module { get; set; } + public CommandHandler CommandHandler { get; set; } + public CommandCallbackAttribute? CommandCallbackAttribute { get; set; } + + public Context(Source source, string message, string command, string[] rawParameters, object?[] parameters, BattleBitModule? module, CommandHandler commandHandler, CommandCallbackAttribute? commandCallbackAttribute) { + this.Source = source; + this.Message = message; + this.Command = command; + this.RawParameters = rawParameters; + this.Parameters = parameters; + this.Module = module; + this.CommandHandler = commandHandler; + this.CommandCallbackAttribute = commandCallbackAttribute; + } + + public virtual void Reply(string message) { + this.Source.Reply(this, message); + } +} + +public abstract class Source { + public abstract void Reply(Context context, string message); +} + +public class ChatSource : Source { + public ChatSource(RunnerPlayer invoker) { + this.Invoker = invoker; + } + + public RunnerPlayer Invoker { get; } + + public override void Reply(Context context, string message) { + bool replyToChat = CommandHandler.CommandConfiguration.ReplyToChat; + if (context.CommandHandler.CommandSettings.Settings[context.Command]?.ReplyToChat is not null) { + replyToChat = context.CommandHandler.CommandSettings.Settings[context.Command]!.ReplyToChat!.Value; + } + + if (replyToChat) { + this.Invoker.SayToChat(message); + } else { + int messageTimeout = CommandHandler.CommandConfiguration.MessageTimeout; + if (context.CommandHandler.CommandSettings.Settings[context.Command]?.MessageTimeout is not null) { + messageTimeout = context.CommandHandler.CommandSettings.Settings[context.Command]!.MessageTimeout!.Value; + } + + this.Invoker.Message(message, messageTimeout); + } + } +} + +public class ConsoleSource : Source { + public override void Reply(Context context, string message) { + context.CommandHandler.Logger.Info(message); + } +} \ No newline at end of file diff --git a/CommandHandler.cs.md b/CommandHandler.cs.md new file mode 100644 index 0000000..fd635cd --- /dev/null +++ b/CommandHandler.cs.md @@ -0,0 +1,34 @@ +# 1 Modules in CommandHandler.cs + +| Description | Version | +|:-------------------------------------------|:----------| +| Basic in-game chat command handler library | 1.0.0 | + +## Commands +| Command | Function Name | Description | Allowed Roles | Parameters | Defaults | +|:----------|:----------------|:----------------------------------|:----------------|:------------------------------------------|:--------------| +| help | void | Shows this help message | | ['RunnerPlayer player', 'int page = 1'] | {'page': '1'} | +| cmdhelp | void | Shows help for a specific command | | ['RunnerPlayer player', 'string command'] | {} | +| modules | void | Lists all loaded modules | Admin | ['RunnerPlayer commandSource'] | {} | + +## Public Methods +| Function Name | Parameters | Defaults | +|:-------------------|:-----------------------------------------------------------------|:--------------| +| | | | +| | | | +| | | | +| | | | +| | | | +| | | | +| | | | +| void | [''] | {} | +| Register | ['BattleBitModule module'] | {} | +| Task | ['RunnerPlayer player', 'ChatChannel channel', 'string message'] | {} | +| HelpCommand | ['RunnerPlayer player', 'int page = 1'] | {'page': '1'} | +| CommandHelpCommand | ['RunnerPlayer player', 'string command'] | {} | +| ListModules | ['RunnerPlayer commandSource'] | {} | +| | | | +| | | | +| | | | +| | | | +| | | | \ No newline at end of file diff --git a/DiscordWebhooks.cs b/DiscordWebhooks.cs index 7622fff..f502e91 100644 --- a/DiscordWebhooks.cs +++ b/DiscordWebhooks.cs @@ -11,88 +11,68 @@ namespace BattleBitDiscordWebhooks; [Module("Send some basic events to Discord and allow for other modules to send messages to Discord", "1.1.0")] -public class DiscordWebhooks : BattleBitModule -{ +public class DiscordWebhooks : BattleBitModule { private Queue discordMessageQueue = new(); private HttpClient httpClient = new HttpClient(); public WebhookConfiguration Configuration { get; set; } = null!; - public override void OnModulesLoaded() - { - if (string.IsNullOrEmpty(this.Configuration.WebhookURL)) - { + public override void OnModulesLoaded() { + if (string.IsNullOrEmpty(this.Configuration.WebhookURL)) { this.Unload(); - throw new Exception("Webhook URL is not set. Please set it in the configuration file."); + this.Logger.Error("Webhook URL is not set. Please set it in the configuration file."); + this.Logger.Error("Webhook URL is not set. Please set it in the configuration file."); } } - public override Task OnConnected() - { + public override Task OnConnected() { discordMessageQueue.Enqueue(new WarningMessage("Server connected to API")); Task.Run(() => sendChatMessagesToDiscord()); return Task.CompletedTask; } - public override Task OnDisconnected() - { + public override Task OnDisconnected() { discordMessageQueue.Enqueue(new WarningMessage("Server disconnected from API")); return base.OnDisconnected(); } - public override Task OnPlayerTypedMessage(RunnerPlayer player, ChatChannel channel, string msg) - { + public override Task OnPlayerTypedMessage(RunnerPlayer player, ChatChannel channel, string msg) { discordMessageQueue.Enqueue(new ChatMessage(player.Name, player.SteamID, channel, msg)); return Task.FromResult(true); } - public override Task OnPlayerReported(RunnerPlayer from, RunnerPlayer to, ReportReason reason, string additional) - { + public override Task OnPlayerReported(RunnerPlayer from, RunnerPlayer to, ReportReason reason, string additional) { this.discordMessageQueue.Enqueue(new WarningMessage($"{from.Name} ({from.SteamID}) reported {to.Name} ({to.SteamID}) for {reason}:{Environment.NewLine}> {additional}")); return Task.CompletedTask; } - public void SendMessage(string message, string? webhookURL = null) - { - if (webhookURL is not null) - { + public void SendMessage(string message, string? webhookURL = null) { + if (webhookURL is not null) { Task.Run(() => sendWebhookMessage(webhookURL, message)); - } - else - { + } else { this.discordMessageQueue.Enqueue(new RawTextMessage(message)); } } - private async Task sendChatMessagesToDiscord() - { - do - { + private async Task sendChatMessagesToDiscord() { + do { List messages = new(); - do - { - try - { - while (this.discordMessageQueue.TryDequeue(out DiscordMessage? message)) - { - if (message == null) - { + do { + try { + while (this.discordMessageQueue.TryDequeue(out DiscordMessage? message)) { + if (message == null) { continue; } messages.Add(message); } - - if (messages.Count > 0) - { + if (messages.Count > 0) { await sendWebhookMessage(this.Configuration.WebhookURL, string.Join(Environment.NewLine, messages.Select(message => message.ToString()))); } messages.Clear(); - } - catch (Exception ex) - { + } catch (Exception ex) { this.Logger.Error($"Failed to process message queue.", ex); await Task.Delay(500); } @@ -102,13 +82,10 @@ private async Task sendChatMessagesToDiscord() } while (this.Server?.IsConnected == true); } - private async Task sendWebhookMessage(string webhookUrl, string message) - { + private async Task sendWebhookMessage(string webhookUrl, string message) { bool success = false; - while (!success) - { - var payload = new - { + while (!success) { + var payload = new { content = message }; @@ -117,42 +94,34 @@ private async Task sendWebhookMessage(string webhookUrl, string message) var response = await this.httpClient.PostAsync(webhookUrl, content); - if (!response.IsSuccessStatusCode) - { + if (!response.IsSuccessStatusCode) { this.Logger.Error($"Error sending webhook message. Status Code: {response.StatusCode}"); } success = response.IsSuccessStatusCode; } } - } -internal class DiscordMessage -{ +internal class DiscordMessage { } -internal class RawTextMessage : DiscordMessage -{ +internal class RawTextMessage : DiscordMessage { public string Message { get; set; } - public RawTextMessage(string message) - { + public RawTextMessage(string message) { this.Message = message; } - public override string ToString() - { + public override string ToString() { return this.Message; } } -internal class ChatMessage : DiscordMessage -{ +internal class ChatMessage : DiscordMessage { public string PlayerName { get; set; } = string.Empty; - public ChatMessage(string playerName, ulong steamID, ChatChannel channel, string message) - { + public ChatMessage(string playerName, ulong steamID, ChatChannel channel, string message) { this.PlayerName = playerName; this.SteamID = steamID; this.Channel = channel; @@ -163,18 +132,15 @@ public ChatMessage(string playerName, ulong steamID, ChatChannel channel, string public ChatChannel Channel { get; set; } public string Message { get; set; } = string.Empty; - public override string ToString() - { + public override string ToString() { return $":speech_balloon: [{this.SteamID}] {this.PlayerName}: {this.Message}"; } } -internal class JoinAndLeaveMessage : DiscordMessage -{ +internal class JoinAndLeaveMessage : DiscordMessage { public int PlayerCount { get; set; } - public JoinAndLeaveMessage(int playerCount, string playerName, ulong steamID, bool joined) - { + public JoinAndLeaveMessage(int playerCount, string playerName, ulong steamID, bool joined) { this.PlayerCount = playerCount; this.PlayerName = playerName; this.SteamID = steamID; @@ -185,16 +151,14 @@ public JoinAndLeaveMessage(int playerCount, string playerName, ulong steamID, bo public ulong SteamID { get; set; } public bool Joined { get; set; } - public override string ToString() - { + public override string ToString() { return $"{(this.Joined ? ":arrow_right:" : ":arrow_left:")} [{this.SteamID}] {this.PlayerName} {(this.Joined ? "joined" : "left")} ({this.PlayerCount} players)"; } } -internal class WarningMessage : DiscordMessage -{ - public WarningMessage(string message) - { +internal class WarningMessage : DiscordMessage { + + public WarningMessage(string message) { this.Message = message; } @@ -202,13 +166,11 @@ public WarningMessage(string message) public string Message { get; set; } - public override string ToString() - { + public override string ToString() { return $":warning: {this.Message}"; } } -public class WebhookConfiguration : ModuleConfiguration -{ +public class WebhookConfiguration : ModuleConfiguration { public string WebhookURL { get; set; } = string.Empty; } \ No newline at end of file diff --git a/DiscordWebhooks.cs.md b/DiscordWebhooks.cs.md new file mode 100644 index 0000000..e69de29 diff --git a/GeoLimiter.cs b/GeoLimiter.cs index def01bb..76e6030 100644 --- a/GeoLimiter.cs +++ b/GeoLimiter.cs @@ -1,6 +1,5 @@ using BBRAPIModules; using System; -using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; @@ -8,9 +7,8 @@ namespace GeoLimiter; -[Module("Kick users based on their country of origin", "1.1.0")] -public class GeoLimiter : BattleBitModule -{ +[Module("Kick users based on their country of origin", "1.2.0")] +public class GeoLimiter : BattleBitModule { public static GeoLimiterConfiguration Configuration { get; set; } = null!; public GeoLimiterServerConfiguration ServerConfiguration { get; set; } = null!; @@ -19,17 +17,14 @@ public class GeoLimiter : BattleBitModule private static Geolocation[] database = null!; - public override void OnModulesLoaded() - { - if (!File.Exists(Path.Combine(Configuration.DataDirectory, Configuration.DatabaseFileName))) - { + public override void OnModulesLoaded() { + if (!File.Exists(Path.Combine(Configuration.DataDirectory, Configuration.DatabaseFileName))) { this.Logger.Error($"Could not find geolocation database at {Path.GetFullPath(Path.Combine(Configuration.DataDirectory, Configuration.DatabaseFileName))}"); this.Unload(); return; } - if (database != null) - { + if (database != null) { return; } @@ -42,17 +37,14 @@ public override void OnModulesLoaded() this.Logger.Info($"Loaded {database.Length} geolocation entries, took {stopwatch.ElapsedMilliseconds}ms"); } - public override Task OnPlayerConnected(RunnerPlayer player) - { - if (this.GranularPermissions is not null && ServerConfiguration.IgnoredPermissions?.Any(p => this.GranularPermissions.HasPermission(player.SteamID, p)) == true) - { + public override Task OnPlayerConnected(RunnerPlayer player) { + if (this.GranularPermissions is not null && ServerConfiguration.IgnoredPermissions?.Any(p => this.GranularPermissions.HasPermission(player.SteamID, p)) == true) { this.Logger.Info($"Player {player.SteamID} has an ignored permission, skipping..."); return Task.CompletedTask; } byte[] ipBytes = player.IP.GetAddressBytes(); - if (BitConverter.IsLittleEndian) - { + if (BitConverter.IsLittleEndian) { Array.Reverse(ipBytes); } @@ -60,23 +52,20 @@ public override Task OnPlayerConnected(RunnerPlayer player) this.Logger.Debug($"IP address {player.IP} as integer: {ipNumber}"); Geolocation? geolocation = database.FirstOrDefault(x => x.FromIP <= ipNumber && x.ToIP >= ipNumber); - if (geolocation == null) - { + if (geolocation == null) { this.Logger.Warn($"Could not find geolocation for IP {player.IP}"); return Task.CompletedTask; } this.Logger.Info($"Found geolocation for IP {player.IP}: {geolocation.CountryName} ({geolocation.CountryCode})"); - if (ServerConfiguration.AllowedCountries?.Contains(geolocation.CountryCode) == true) - { + if (this.ServerConfiguration.AllowCountryCodes && this.ServerConfiguration.CountryCodes.Contains(geolocation.CountryCode)) { this.Logger.Info($"Country {geolocation.CountryCode} is allowed, skipping..."); return Task.CompletedTask; } - if (ServerConfiguration.DisallowedCountries?.Contains(geolocation.CountryCode) == false) - { - this.Logger.Info($"Country {geolocation.CountryCode} is not disallowed, skipping..."); + if (!this.ServerConfiguration.AllowCountryCodes && !this.ServerConfiguration.CountryCodes.Contains(geolocation.CountryCode)) { + this.Logger.Info($"Country {geolocation.CountryCode} is not denied, skipping..."); return Task.CompletedTask; } @@ -87,26 +76,23 @@ public override Task OnPlayerConnected(RunnerPlayer player) } } -public class GeoLimiterConfiguration : ModuleConfiguration -{ +public class GeoLimiterConfiguration : ModuleConfiguration { public string DataDirectory { get; set; } = "./data/GeoLimiter"; public string DatabaseFileName { get; set; } = "IP2LOCATION-LITE-DB1.CSV"; } -public class GeoLimiterServerConfiguration : ModuleConfiguration -{ - public string[]? AllowedCountries { get; set; } = new string[] { "DE", "AT", "CH" }; - public string[]? DisallowedCountries { get; set; } = new string[] { "US", "RU", "CN" }; +public class GeoLimiterServerConfiguration : ModuleConfiguration { + public string[] CountryCodes { get; set; } = new string[] { "DE", "AT", "CH" }; + public bool AllowCountryCodes { get; set; } = true; public string KickMessage { get; set; } = "Your country {0} is not allowed on this server."; public string[] IgnoredPermissions { get; set; } = Array.Empty(); } -public class Geolocation -{ +public class Geolocation { public uint FromIP { get; set; } public uint ToIP { get; set; } public string CountryCode { get; set; } = null!; public string CountryName { get; set; } = null!; -} +} \ No newline at end of file diff --git a/GranularPermissions.cs b/GranularPermissions.cs index e67e864..29f6fe1 100644 --- a/GranularPermissions.cs +++ b/GranularPermissions.cs @@ -6,8 +6,7 @@ namespace Permissions; [Module("Granular permissions for players and groups.", "1.0.0")] -public class GranularPermissions : BattleBitModule -{ +public class GranularPermissions : BattleBitModule { public const string CatchAll = "*"; public const string RevokePrefix = "-"; public const string PermissionSeparator = "."; @@ -15,81 +14,67 @@ public class GranularPermissions : BattleBitModule public static PermissionsConfiguration Configuration { get; set; } = null!; public PermissionsServerConfiguration ServerConfiguration { get; set; } = null!; - public void Save() - { + public void Save() { this.Logger.Debug("Saving permissions configuration."); this.ServerConfiguration.Save(); Configuration.Save(); } - public bool HasPermission(ulong steamId, string permission) - { + public bool HasPermission(ulong steamId, string permission) { this.Logger.Debug($"Checking if player {steamId} has permission {permission}."); - if (permission == CatchAll) - { + if (permission == CatchAll) { this.Logger.Debug($"Player {steamId} has permission {permission} because it is a catch-all permission."); return true; } string[] playerGroups = this.ServerConfiguration.PlayerGroups.ContainsKey(steamId) ? this.ServerConfiguration.PlayerGroups[steamId].ToArray() : Array.Empty(); - if (Configuration.Groups.ContainsKey(CatchAll)) - { + if (Configuration.Groups.ContainsKey(CatchAll)) { playerGroups = playerGroups.Append(CatchAll).ToArray(); } bool permitted = false; // Player permissions - if (this.ServerConfiguration.PlayerPermissions.ContainsKey(steamId)) - { - if (this.ServerConfiguration.PlayerPermissions[steamId].Any(p => p.Equals(RevokePrefix + permission, StringComparison.InvariantCultureIgnoreCase))) - { + if (this.ServerConfiguration.PlayerPermissions.ContainsKey(steamId)) { + if (this.ServerConfiguration.PlayerPermissions[steamId].Any(p => p.Equals(RevokePrefix + permission, StringComparison.InvariantCultureIgnoreCase))) { this.Logger.Debug($"Player {steamId} does not have permission {permission} because they have revoked permission {permission}."); return false; } - if (this.ServerConfiguration.PlayerPermissions[steamId].Any(p => p.Equals(permission, StringComparison.InvariantCultureIgnoreCase))) - { + if (this.ServerConfiguration.PlayerPermissions[steamId].Any(p => p.Equals(permission, StringComparison.InvariantCultureIgnoreCase))) { this.Logger.Debug($"Player {steamId} has permission {permission} because they have permission {permission}."); permitted = true; } - if (this.ServerConfiguration.PlayerPermissions[steamId].Any(p => p.Equals(CatchAll, StringComparison.InvariantCultureIgnoreCase))) - { + if (this.ServerConfiguration.PlayerPermissions[steamId].Any(p => p.Equals(CatchAll, StringComparison.InvariantCultureIgnoreCase))) { this.Logger.Debug($"Player {steamId} has permission {permission} because they have catch-all permission *."); permitted = true; } } // Group permissions - foreach (string group in playerGroups) - { - if (!Configuration.Groups.ContainsKey(group)) - { + foreach (string group in playerGroups) { + if (!Configuration.Groups.ContainsKey(group)) { this.Logger.Error($"Group {group} of player {steamId} does not exist."); continue; } - if (Configuration.Groups[group].Contains("-*")) - { + if (Configuration.Groups[group].Contains("-*")) { this.Logger.Debug($"Player {steamId} does not have permission {permission} because group {group} has revoked all permissions."); return false; } - if (Configuration.Groups[group].Any(p => p.Equals(RevokePrefix + permission, StringComparison.InvariantCultureIgnoreCase))) - { + if (Configuration.Groups[group].Any(p => p.Equals(RevokePrefix + permission, StringComparison.InvariantCultureIgnoreCase))) { this.Logger.Debug($"Player {steamId} does not have permission {permission} because group {group} has revoked permission {permission}."); return false; } - if (Configuration.Groups[group].Any(p => p.Equals(permission, StringComparison.InvariantCultureIgnoreCase))) - { + if (Configuration.Groups[group].Any(p => p.Equals(permission, StringComparison.InvariantCultureIgnoreCase))) { permitted = true; } - if (Configuration.Groups[group].Any(p => p.Equals(CatchAll, StringComparison.InvariantCultureIgnoreCase))) - { + if (Configuration.Groups[group].Any(p => p.Equals(CatchAll, StringComparison.InvariantCultureIgnoreCase))) { this.Logger.Debug($"Player {steamId} has permission {permission} because group {group} has catch-all permission *."); permitted = true; } @@ -98,22 +83,18 @@ public bool HasPermission(ulong steamId, string permission) // Partial permissions with catch-all string[] permissionPath = permission.Split(PermissionSeparator); string partialPermissionPath = string.Empty; - foreach (string permissionPart in permissionPath) - { + foreach (string permissionPart in permissionPath) { partialPermissionPath += $"{permissionPart}{PermissionSeparator}"; // Partial player permissions - if (this.ServerConfiguration.PlayerPermissions.ContainsKey(steamId)) - { - if (this.ServerConfiguration.PlayerPermissions[steamId].Any(p => p.Equals(RevokePrefix + partialPermissionPath + CatchAll, StringComparison.InvariantCultureIgnoreCase))) - { + if (this.ServerConfiguration.PlayerPermissions.ContainsKey(steamId)) { + if (this.ServerConfiguration.PlayerPermissions[steamId].Any(p => p.Equals(RevokePrefix + partialPermissionPath + CatchAll, StringComparison.InvariantCultureIgnoreCase))) { this.Logger.Debug($"Player {steamId} does not have permission {permission} because they have revoked permission {partialPermissionPath + CatchAll}."); return false; } - if (this.ServerConfiguration.PlayerPermissions[steamId].Any(p => p.Equals(partialPermissionPath + CatchAll, StringComparison.InvariantCultureIgnoreCase))) - { + if (this.ServerConfiguration.PlayerPermissions[steamId].Any(p => p.Equals(partialPermissionPath + CatchAll, StringComparison.InvariantCultureIgnoreCase))) { this.Logger.Debug($"Player {steamId} has permission {permission} because they have permission {partialPermissionPath + CatchAll}."); permitted = true; } @@ -121,22 +102,18 @@ public bool HasPermission(ulong steamId, string permission) // Partial group permissions - foreach (string group in playerGroups) - { - if (!Configuration.Groups.ContainsKey(group)) - { + foreach (string group in playerGroups) { + if (!Configuration.Groups.ContainsKey(group)) { // We already logged this error above. continue; } - if (Configuration.Groups[group].Any(p => p.Equals(RevokePrefix + partialPermissionPath + CatchAll, StringComparison.InvariantCultureIgnoreCase))) - { + if (Configuration.Groups[group].Any(p => p.Equals(RevokePrefix + partialPermissionPath + CatchAll, StringComparison.InvariantCultureIgnoreCase))) { this.Logger.Debug($"Player {steamId} does not have permission {permission} because group {group} has revoked permission {partialPermissionPath + CatchAll}."); return false; } - if (Configuration.Groups[group].Any(p => p.Equals(partialPermissionPath + CatchAll, StringComparison.InvariantCultureIgnoreCase))) - { + if (Configuration.Groups[group].Any(p => p.Equals(partialPermissionPath + CatchAll, StringComparison.InvariantCultureIgnoreCase))) { this.Logger.Debug($"Player {steamId} has permission {permission} because group {group} has permission {partialPermissionPath + CatchAll}."); permitted = true; } @@ -148,12 +125,10 @@ public bool HasPermission(ulong steamId, string permission) return permitted; } - public void AddGroup(string group) - { + public void AddGroup(string group) { this.Logger.Debug($"Adding group {group}."); - if (Configuration.Groups.ContainsKey(group)) - { + if (Configuration.Groups.ContainsKey(group)) { this.Logger.Error($"Group {group} already exists."); return; } @@ -161,12 +136,10 @@ public void AddGroup(string group) Configuration.Groups.Add(group, new()); } - public void RemoveGroup(string group) - { + public void RemoveGroup(string group) { this.Logger.Debug($"Removing group {group}."); - if (!Configuration.Groups.ContainsKey(group)) - { + if (!Configuration.Groups.ContainsKey(group)) { this.Logger.Error($"Group {group} does not exist."); return; } @@ -174,21 +147,15 @@ public void RemoveGroup(string group) Configuration.Groups.Remove(group); } - public string[] GetGroups() - { + public string[] GetGroups() { return Configuration.Groups.Keys.ToArray(); } - public string[] GetPlayerGroups(ulong steamId) - { - if (!ServerConfiguration.PlayerGroups.ContainsKey(steamId)) - { - if (Configuration.Groups.ContainsKey(CatchAll)) - { + public string[] GetPlayerGroups(ulong steamId) { + if (!ServerConfiguration.PlayerGroups.ContainsKey(steamId)) { + if (Configuration.Groups.ContainsKey(CatchAll)) { return new[] { CatchAll }; - } - else - { + } else { return Array.Empty(); } } @@ -196,18 +163,15 @@ public string[] GetPlayerGroups(ulong steamId) return ServerConfiguration.PlayerGroups[steamId].ToArray(); } - public void AddGroupPermission(string group, string permission) - { + public void AddGroupPermission(string group, string permission) { this.Logger.Debug($"Adding permission {permission} to group {group}."); - if (!Configuration.Groups.ContainsKey(group)) - { + if (!Configuration.Groups.ContainsKey(group)) { this.Logger.Error($"Group {group} does not exist."); return; } - if (Configuration.Groups[group].Contains(permission)) - { + if (Configuration.Groups[group].Contains(permission)) { this.Logger.Error($"Group {group} already has permission {permission}."); return; } @@ -215,18 +179,15 @@ public void AddGroupPermission(string group, string permission) Configuration.Groups[group].Add(permission); } - public void RemoveGroupPermission(string group, string permission) - { + public void RemoveGroupPermission(string group, string permission) { this.Logger.Debug($"Removing permission {permission} from group {group}."); - if (!Configuration.Groups.ContainsKey(group)) - { + if (!Configuration.Groups.ContainsKey(group)) { this.Logger.Error($"Group {group} does not exist."); return; } - if (!Configuration.Groups[group].Contains(permission)) - { + if (!Configuration.Groups[group].Contains(permission)) { this.Logger.Error($"Group {group} does not have permission {permission}."); return; } @@ -234,24 +195,20 @@ public void RemoveGroupPermission(string group, string permission) Configuration.Groups[group].Remove(permission); } - public void AddRevokedGroupPermission(string group, string permission) - { + public void AddRevokedGroupPermission(string group, string permission) { this.Logger.Debug($"Adding revoked permission {permission} to group {group}."); this.AddGroupPermission(group, $"{RevokePrefix}{permission}"); } - public void RemoveRevokedGroupPermission(string group, string permission) - { + public void RemoveRevokedGroupPermission(string group, string permission) { this.Logger.Debug($"Removing revoked permission {permission} from group {group}."); this.RemoveGroupPermission(group, $"{RevokePrefix}{permission}"); } - public string[] GetGroupPermissions(string group) - { - if (!Configuration.Groups.ContainsKey(group)) - { + public string[] GetGroupPermissions(string group) { + if (!Configuration.Groups.ContainsKey(group)) { this.Logger.Error($"Group {group} does not exist."); return Array.Empty(); } @@ -259,18 +216,15 @@ public string[] GetGroupPermissions(string group) return Configuration.Groups[group].ToArray(); } - public void AddPlayerGroup(ulong steamId, string group) - { + public void AddPlayerGroup(ulong steamId, string group) { this.Logger.Debug($"Adding player {steamId} to group {group}."); - if (!ServerConfiguration.PlayerGroups.ContainsKey(steamId)) - { + if (!ServerConfiguration.PlayerGroups.ContainsKey(steamId)) { this.Logger.Debug($"Player {steamId} does not have any groups."); ServerConfiguration.PlayerGroups.Add(steamId, new()); } - if (ServerConfiguration.PlayerGroups[steamId].Contains(group)) - { + if (ServerConfiguration.PlayerGroups[steamId].Contains(group)) { this.Logger.Error($"Player {steamId} already has group {group}."); return; } @@ -278,18 +232,15 @@ public void AddPlayerGroup(ulong steamId, string group) ServerConfiguration.PlayerGroups[steamId].Add(group); } - public void RemovePlayerGroup(ulong steamId, string group) - { + public void RemovePlayerGroup(ulong steamId, string group) { this.Logger.Debug($"Removing player {steamId} from group {group}."); - if (!ServerConfiguration.PlayerGroups.ContainsKey(steamId)) - { + if (!ServerConfiguration.PlayerGroups.ContainsKey(steamId)) { this.Logger.Error($"Player {steamId} does not have any groups."); return; } - if (!ServerConfiguration.PlayerGroups[steamId].Contains(group)) - { + if (!ServerConfiguration.PlayerGroups[steamId].Contains(group)) { this.Logger.Error($"Player {steamId} does not have group {group}."); return; } @@ -297,18 +248,15 @@ public void RemovePlayerGroup(ulong steamId, string group) ServerConfiguration.PlayerGroups[steamId].Remove(group); } - public void AddPlayerPermission(ulong steamId, string permission) - { + public void AddPlayerPermission(ulong steamId, string permission) { this.Logger.Debug($"Adding permission {permission} to player {steamId}."); - if (!this.ServerConfiguration.PlayerPermissions.ContainsKey(steamId)) - { + if (!this.ServerConfiguration.PlayerPermissions.ContainsKey(steamId)) { this.Logger.Debug($"Player {steamId} does not have any permissions."); this.ServerConfiguration.PlayerPermissions.Add(steamId, new()); } - if (this.ServerConfiguration.PlayerPermissions[steamId].Contains(permission)) - { + if (this.ServerConfiguration.PlayerPermissions[steamId].Contains(permission)) { this.Logger.Error($"Player {steamId} already has permission {permission}."); return; } @@ -316,18 +264,15 @@ public void AddPlayerPermission(ulong steamId, string permission) this.ServerConfiguration.PlayerPermissions[steamId].Add(permission); } - public void RemovePlayerPermission(ulong steamId, string permission) - { + public void RemovePlayerPermission(ulong steamId, string permission) { this.Logger.Debug($"Removing permission {permission} from player {steamId}."); - if (!this.ServerConfiguration.PlayerPermissions.ContainsKey(steamId)) - { + if (!this.ServerConfiguration.PlayerPermissions.ContainsKey(steamId)) { this.Logger.Error($"Player {steamId} does not have any permissions."); return; } - if (!this.ServerConfiguration.PlayerPermissions[steamId].Contains(permission)) - { + if (!this.ServerConfiguration.PlayerPermissions[steamId].Contains(permission)) { this.Logger.Error($"Player {steamId} does not have permission {permission}."); return; } @@ -335,24 +280,20 @@ public void RemovePlayerPermission(ulong steamId, string permission) this.ServerConfiguration.PlayerPermissions[steamId].Remove(permission); } - public void AddRevokedPlayerPermission(ulong steamId, string permission) - { + public void AddRevokedPlayerPermission(ulong steamId, string permission) { this.Logger.Debug($"Adding revoked permission {permission} to player {steamId}."); this.AddPlayerPermission(steamId, $"{RevokePrefix}{permission}"); } - public void RemoveRevokedPlayerPermission(ulong steamId, string permission) - { + public void RemoveRevokedPlayerPermission(ulong steamId, string permission) { this.Logger.Debug($"Removing revoked permission {permission} from player {steamId}."); this.RemovePlayerPermission(steamId, $"{RevokePrefix}{permission}"); } - public string[] GetPlayerPermissions(ulong steamId) - { - if (!this.ServerConfiguration.PlayerPermissions.ContainsKey(steamId)) - { + public string[] GetPlayerPermissions(ulong steamId) { + if (!this.ServerConfiguration.PlayerPermissions.ContainsKey(steamId)) { this.Logger.Debug($"Player {steamId} does not have any player-specific permissions."); return Array.Empty(); } @@ -360,17 +301,14 @@ public string[] GetPlayerPermissions(ulong steamId) return this.ServerConfiguration.PlayerPermissions[steamId].ToArray(); } - public string[] GetAllPlayerPermissions(ulong steamId) - { + public string[] GetAllPlayerPermissions(ulong steamId) { List permissions = new(); - if (this.ServerConfiguration.PlayerPermissions.ContainsKey(steamId)) - { + if (this.ServerConfiguration.PlayerPermissions.ContainsKey(steamId)) { permissions.AddRange(this.ServerConfiguration.PlayerPermissions[steamId]); } - foreach (string group in this.GetPlayerGroups(steamId)) - { + foreach (string group in this.GetPlayerGroups(steamId)) { permissions.AddRange(this.GetGroupPermissions(group)); } @@ -378,15 +316,14 @@ public string[] GetAllPlayerPermissions(ulong steamId) } } -public class PermissionsConfiguration : ModuleConfiguration -{ +public class PermissionsConfiguration : ModuleConfiguration { + public Dictionary> Groups { get; set; } = new() { { GranularPermissions.CatchAll, new() } }; } -public class PermissionsServerConfiguration : ModuleConfiguration -{ +public class PermissionsServerConfiguration : ModuleConfiguration { public Dictionary> PlayerGroups { get; set; } = new(); public Dictionary> PlayerPermissions { get; set; } = new(); -} +} \ No newline at end of file diff --git a/GranularPermissionsCommands.cs b/GranularPermissionsCommands.cs index 7681b4b..d35dbd7 100644 --- a/GranularPermissionsCommands.cs +++ b/GranularPermissionsCommands.cs @@ -9,64 +9,73 @@ namespace PermissionsManager; [RequireModule(typeof(CommandHandler))] [RequireModule(typeof(GranularPermissions))] -[Module("Provide commands for managing GranularPermissions", "1.0.0")] -public class GranularPermissionsCommands : BattleBitModule -{ +[Module("Provide commands for managing GranularPermissions", "1.0.1")] +public class GranularPermissionsCommands : BattleBitModule { + [ModuleReference] public GranularPermissions GranularPermissions { get; set; } = null!; + [ModuleReference] public CommandHandler CommandHandler { get; set; } = null!; public GranularPermissionsCommandsConfiguration Configuration { get; set; } = null!; - public override void OnModulesLoaded() - { + public override void OnModulesLoaded() { this.CommandHandler.Register(this); } [CommandCallback("addplayerperm", Description = "Adds a permission to a player", Permissions = new[] { "GranularPermissions.AddPlayerPerm" })] - public void AddPermissionCommand(RunnerPlayer commandSource, RunnerPlayer player, string permission) - { + public string AddPermissionCommand(Context context, RunnerPlayer player, string permission) { this.GranularPermissions.AddPlayerPermission(player.SteamID, permission); - - commandSource.Message($"Added permission {permission} to {player.Name}"); - this.GranularPermissions.Save(); + + this.Logger.Debug($"Added permission {permission} to {player.Name}"); + + return $"Added permission {permission} to {player.Name}"; + + this.Logger.Debug($"Added permission {permission} to {player.Name}"); + + return $"Added permission {permission} to {player.Name}"; } [CommandCallback("removeplayerperm", Description = "Removes a permission from a player", Permissions = new[] { "GranularPermissions.RemovePlayerPerm" })] - public void RemovePermissionCommand(RunnerPlayer commandSource, RunnerPlayer player, string permission) - { + public string RemovePermissionCommand(Context context, RunnerPlayer player, string permission) { this.GranularPermissions.RemovePlayerPermission(player.SteamID, permission); + this.GranularPermissions.Save(); - commandSource.Message($"Removed permission {permission} from {player.Name}"); + this.Logger.Debug($"Removed permission {permission} from {player.Name}"); - this.GranularPermissions.Save(); + return $"Removed permission {permission} from {player.Name}"; + + this.Logger.Debug($"Removed permission {permission} from {player.Name}"); + + return $"Removed permission {permission} from {player.Name}"; } [CommandCallback("clearplayerperms", Description = "Clears all permissions and groups from a player", Permissions = new[] { "GranularPermissions.ClearPlayerPerms" })] - public void ClearPermissionCommand(RunnerPlayer commandSource, RunnerPlayer player) - { - foreach (string group in this.GranularPermissions.GetPlayerGroups(player.SteamID)) - { + public string ClearPermissionCommand(Context context, RunnerPlayer player) { + foreach (string group in this.GranularPermissions.GetPlayerGroups(player.SteamID)) { this.GranularPermissions.RemovePlayerGroup(player.SteamID, group); } - foreach (string permission in this.GranularPermissions.GetPlayerPermissions(player.SteamID)) - { + foreach (string permission in this.GranularPermissions.GetPlayerPermissions(player.SteamID)) { this.GranularPermissions.RemovePlayerPermission(player.SteamID, permission); } - commandSource.Message($"Cleared permissions from {player.Name}"); - this.GranularPermissions.Save(); + + this.Logger.Debug($"Cleared permissions from {player.Name}"); + + return $"Cleared permissions from {player.Name}"; + + this.Logger.Debug($"Cleared permissions from {player.Name}"); + + return $"Cleared permissions from {player.Name}"; } [CommandCallback("listplayerperms", Description = "Lists player permissions", Permissions = new[] { "GranularPermissions.ListPlayerPerms" })] - public void ListPermissionCommand(RunnerPlayer commandSource, RunnerPlayer targetPlayer, int page = 1) - { - if (page < 1) - { + public string ListPermissionCommand(Context context, RunnerPlayer targetPlayer, int page = 1) { + if (page < 1) { page = 1; } @@ -74,65 +83,74 @@ public void ListPermissionCommand(RunnerPlayer commandSource, RunnerPlayer targe int pageCount = (int)Math.Ceiling(permissions.Length / (double)this.Configuration.PermissionsPerPage); - commandSource.Message($"{targetPlayer.Name}:{Environment.NewLine}{string.Join("\n", permissions.Skip((page - 1) * this.Configuration.PermissionsPerPage).Take(this.Configuration.PermissionsPerPage))}{(pageCount > 1 ? $"{Environment.NewLine}Page {page} of {pageCount}{(page == pageCount ? "" : $", use listperms \"{targetPlayer.Name}\" {page + 1} to see more")}" : "")}"); + this.Logger.Debug($"Listing permissions for {targetPlayer.Name}"); + + return $"{targetPlayer.Name}:{Environment.NewLine}{string.Join("\n", permissions.Skip((page - 1) * this.Configuration.PermissionsPerPage).Take(this.Configuration.PermissionsPerPage))}{(pageCount > 1 ? $"{Environment.NewLine}Page {page} of {pageCount}{(page == pageCount ? "" : $", use listperms \"{targetPlayer.Name}\" {page + 1} to see more")}" : "")}"; + this.Logger.Debug($"Listing permissions for {targetPlayer.Name}"); + + return $"{targetPlayer.Name}:{Environment.NewLine}{string.Join("\n", permissions.Skip((page - 1) * this.Configuration.PermissionsPerPage).Take(this.Configuration.PermissionsPerPage))}{(pageCount > 1 ? $"{Environment.NewLine}Page {page} of {pageCount}{(page == pageCount ? "" : $", use listperms \"{targetPlayer.Name}\" {page + 1} to see more")}" : "")}"; } [CommandCallback("addplayergroup", Description = "Adds a group to a player", Permissions = new[] { "GranularPermissions.AddPlayerGroup" })] - public void AddGroupCommand(RunnerPlayer commandSource, RunnerPlayer player, string group) - { - if (this.GranularPermissions.GetPlayerGroups(player.SteamID).Contains(group)) - { - commandSource.Message($"{player.Name} already has group {group}"); - return; + public string AddGroupCommand(Context context, RunnerPlayer player, string group) { + if (this.GranularPermissions.GetPlayerGroups(player.SteamID).Contains(group)) { + return $"{player.Name} already has group {group}"; } - if (!this.GranularPermissions.GetGroups().Contains(group)) - { - commandSource.Message($"Group {group} does not exist"); - return; + if (!this.GranularPermissions.GetGroups().Contains(group)) { + return $"Group {group} does not exist"; } this.GranularPermissions.AddPlayerGroup(player.SteamID, group); + this.GranularPermissions.Save(); - commandSource.Message($"Added group {group} to {player.Name}"); + this.Logger.Debug($"Added group {group} to {player.Name}"); - this.GranularPermissions.Save(); + return $"Added group {group} to {player.Name}"; + + this.Logger.Debug($"Added group {group} to {player.Name}"); + + return $"Added group {group} to {player.Name}"; } [CommandCallback("removeplayergroup", Description = "Removes a group from a player", Permissions = new[] { "GranularPermissions.RemovePlayerGroup" })] - public void RemoveGroupCommand(RunnerPlayer commandSource, RunnerPlayer player, string group) - { - if (!this.GranularPermissions.GetPlayerGroups(player.SteamID).Contains(group)) - { - commandSource.Message($"{player.Name} does not have group {group}"); - return; + public string RemoveGroupCommand(Context context, RunnerPlayer player, string group) { + if (!this.GranularPermissions.GetPlayerGroups(player.SteamID).Contains(group)) { + return $"{player.Name} does not have group {group}"; } this.GranularPermissions.RemovePlayerGroup(player.SteamID, group); + this.GranularPermissions.Save(); - commandSource.Message($"Removed group {group} from {player.Name}"); + this.Logger.Debug($"Removed group {group} from {player.Name}"); - this.GranularPermissions.Save(); + return $"Removed group {group} from {player.Name}"; + + this.Logger.Debug($"Removed group {group} from {player.Name}"); + + return $"Removed group {group} from {player.Name}"; } [CommandCallback("clearplayergroups", Description = "Clears all groups from a player", Permissions = new[] { "GranularPermissions.ClearPlayerGroups" })] - public void ClearGroupCommand(RunnerPlayer commandSource, RunnerPlayer player) - { - foreach (string group in this.GranularPermissions.GetPlayerGroups(player.SteamID)) - { + public string ClearGroupCommand(Context context, RunnerPlayer player) { + foreach (string group in this.GranularPermissions.GetPlayerGroups(player.SteamID)) { this.GranularPermissions.RemovePlayerGroup(player.SteamID, group); } - commandSource.Message($"Cleared groups from {player.Name}"); - this.GranularPermissions.Save(); + + this.Logger.Debug($"Cleared groups from {player.Name}"); + + return $"Cleared groups from {player.Name}"; + + this.Logger.Debug($"Cleared groups from {player.Name}"); + + return $"Cleared groups from {player.Name}"; } [CommandCallback("listplayergroups", Description = "Lists player groups", Permissions = new[] { "GranularPermissions.ListPlayerGroups" })] - public void ListGroupCommand(RunnerPlayer commandSource, RunnerPlayer targetPlayer, int page = 1) - { - if (page < 1) - { + public string ListGroupCommand(Context context, RunnerPlayer targetPlayer, int page = 1) { + if (page < 1) { page = 1; } @@ -142,46 +160,53 @@ public void ListGroupCommand(RunnerPlayer commandSource, RunnerPlayer targetPlay int pageCount = (int)Math.Ceiling(groups.Count / (double)this.Configuration.PermissionsPerPage); - commandSource.Message($"{targetPlayer.Name}:{Environment.NewLine}{string.Join("\n", groups.Skip((page - 1) * this.Configuration.PermissionsPerPage).Take(this.Configuration.PermissionsPerPage))}{(pageCount > 1 ? $"{Environment.NewLine}Page {page} of {pageCount}{(page == pageCount ? "" : $", use listgroups \"{targetPlayer.Name}\" {page + 1} to see more")}" : "")}"); + this.Logger.Debug($"Listing groups for {targetPlayer.Name}"); + + return $"{targetPlayer.Name}:{Environment.NewLine}{string.Join("\n", groups.Skip((page - 1) * this.Configuration.PermissionsPerPage).Take(this.Configuration.PermissionsPerPage))}{(pageCount > 1 ? $"{Environment.NewLine}Page {page} of {pageCount}{(page == pageCount ? "" : $", use listgroups \"{targetPlayer.Name}\" {page + 1} to see more")}" : "")}"; + this.Logger.Debug($"Listing groups for {targetPlayer.Name}"); + + return $"{targetPlayer.Name}:{Environment.NewLine}{string.Join("\n", groups.Skip((page - 1) * this.Configuration.PermissionsPerPage).Take(this.Configuration.PermissionsPerPage))}{(pageCount > 1 ? $"{Environment.NewLine}Page {page} of {pageCount}{(page == pageCount ? "" : $", use listgroups \"{targetPlayer.Name}\" {page + 1} to see more")}" : "")}"; } [CommandCallback("addgroup", Description = "Adds a group", Permissions = new[] { "GranularPermissions.AddGroup" })] - public void AddGroupCommand(RunnerPlayer commandSource, string group) - { - if (this.GranularPermissions.GetGroups().Contains(group)) - { - commandSource.Message($"Group {group} already exists"); - return; + public string AddGroupCommand(Context context, string group) { + if (this.GranularPermissions.GetGroups().Contains(group)) { + return $"Group {group} already exists"; } this.GranularPermissions.AddGroup(group); + this.GranularPermissions.Save(); - commandSource.Message($"Added group {group}"); + this.Logger.Debug($"Added group {group}"); - this.GranularPermissions.Save(); + return $"Added group {group}"; + + this.Logger.Debug($"Added group {group}"); + + return $"Added group {group}"; } [CommandCallback("removegroup", Description = "Removes a group", Permissions = new[] { "GranularPermissions.RemoveGroup" })] - public void RemoveGroupCommand(RunnerPlayer commandSource, string group) - { - if (!this.GranularPermissions.GetGroups().Contains(group)) - { - commandSource.Message($"Group {group} does not exist"); - return; + public string RemoveGroupCommand(Context context, string group) { + if (!this.GranularPermissions.GetGroups().Contains(group)) { + return $"Group {group} does not exist"; } this.GranularPermissions.RemoveGroup(group); + this.GranularPermissions.Save(); - commandSource.Message($"Removed group {group}"); + this.Logger.Debug($"Removed group {group}"); - this.GranularPermissions.Save(); + return $"Removed group {group}"; + + this.Logger.Debug($"Removed group {group}"); + + return $"Removed group {group}"; } [CommandCallback("listgroups", Description = "Lists groups", Permissions = new[] { "GranularPermissions.ListGroups" })] - public void ListGroupCommand(RunnerPlayer commandSource, int page = 1) - { - if (page < 1) - { + public string ListGroupCommand(Context context, int page = 1) { + if (page < 1) { page = 1; } @@ -191,83 +216,86 @@ public void ListGroupCommand(RunnerPlayer commandSource, int page = 1) int pageCount = (int)Math.Ceiling(groups.Count / (double)this.Configuration.PermissionsPerPage); - commandSource.Message($"{Environment.NewLine}{string.Join("\n", groups.Skip((page - 1) * this.Configuration.PermissionsPerPage).Take(this.Configuration.PermissionsPerPage))}{(pageCount > 1 ? $"{Environment.NewLine}Page {page} of {pageCount}{(page == pageCount ? "" : $", use listgroups {page + 1} to see more")}" : "")}"); + this.Logger.Debug($"Listing groups"); + + return $"{Environment.NewLine}{string.Join("\n", groups.Skip((page - 1) * this.Configuration.PermissionsPerPage).Take(this.Configuration.PermissionsPerPage))}{(pageCount > 1 ? $"{Environment.NewLine}Page {page} of {pageCount}{(page == pageCount ? "" : $", use listgroups {page + 1} to see more")}" : "")}"; + this.Logger.Debug($"Listing groups"); + + return $"{Environment.NewLine}{string.Join("\n", groups.Skip((page - 1) * this.Configuration.PermissionsPerPage).Take(this.Configuration.PermissionsPerPage))}{(pageCount > 1 ? $"{Environment.NewLine}Page {page} of {pageCount}{(page == pageCount ? "" : $", use listgroups {page + 1} to see more")}" : "")}"; } [CommandCallback("addgroupperm", Description = "Adds a permission to a group", Permissions = new[] { "GranularPermissions.AddGroupPerm" })] - public void AddGroupPermissionCommand(RunnerPlayer commandSource, string group, string permission) - { - if (!this.GranularPermissions.GetGroups().Contains(group)) - { - commandSource.Message($"Group {group} does not exist"); - return; + public string AddGroupPermissionCommand(Context context, string group, string permission) { + if (!this.GranularPermissions.GetGroups().Contains(group)) { + return $"Group {group} does not exist"; } - if (this.GranularPermissions.GetGroupPermissions(group).Contains(permission)) - { - commandSource.Message($"{group} already has permission {permission}"); - return; + if (this.GranularPermissions.GetGroupPermissions(group).Contains(permission)) { + return $"{group} already has permission {permission}"; } this.GranularPermissions.AddGroupPermission(group, permission); + this.GranularPermissions.Save(); - commandSource.Message($"Added permission {permission} to {group}"); + this.Logger.Debug($"Added permission {permission} to {group}"); - this.GranularPermissions.Save(); + return $"Added permission {permission} to {group}"; + + this.Logger.Debug($"Added permission {permission} to {group}"); + + return $"Added permission {permission} to {group}"; } [CommandCallback("removegroupperm", Description = "Removes a permission from a group", Permissions = new[] { "GranularPermissions.RemoveGroupPerm" })] - public void RemoveGroupPermissionCommand(RunnerPlayer commandSource, string group, string permission) - { - if (!this.GranularPermissions.GetGroups().Contains(group)) - { - commandSource.Message($"Group {group} does not exist"); - return; + public string RemoveGroupPermissionCommand(Context context, string group, string permission) { + if (!this.GranularPermissions.GetGroups().Contains(group)) { + return $"Group {group} does not exist"; } - if (!this.GranularPermissions.GetGroupPermissions(group).Contains(permission)) - { - commandSource.Message($"{group} does not have permission {permission}"); - return; + if (!this.GranularPermissions.GetGroupPermissions(group).Contains(permission)) { + return $"{group} does not have permission {permission}"; } this.GranularPermissions.RemoveGroupPermission(group, permission); + this.GranularPermissions.Save(); - commandSource.Message($"Removed permission {permission} from {group}"); + this.Logger.Debug($"Removed permission {permission} from {group}"); - this.GranularPermissions.Save(); + return $"Removed permission {permission} from {group}"; + + this.Logger.Debug($"Removed permission {permission} from {group}"); + + return $"Removed permission {permission} from {group}"; } [CommandCallback("cleargroupperms", Description = "Clears all permissions from a group", Permissions = new[] { "GranularPermissions.ClearGroupPerms" })] - public void ClearGroupPermissionCommand(RunnerPlayer commandSource, string group) - { - if (!this.GranularPermissions.GetGroups().Contains(group)) - { - commandSource.Message($"Group {group} does not exist"); - return; + public string ClearGroupPermissionCommand(Context context, string group) { + if (!this.GranularPermissions.GetGroups().Contains(group)) { + return $"Group {group} does not exist"; } - foreach (string permission in this.GranularPermissions.GetGroupPermissions(group)) - { + foreach (string permission in this.GranularPermissions.GetGroupPermissions(group)) { this.GranularPermissions.RemoveGroupPermission(group, permission); } - commandSource.Message($"Cleared permissions from {group}"); - this.GranularPermissions.Save(); + + this.Logger.Debug($"Cleared permissions from {group}"); + + return $"Cleared permissions from {group}"; + + this.Logger.Debug($"Cleared permissions from {group}"); + + return $"Cleared permissions from {group}"; } [CommandCallback("listgroupperms", Description = "Lists group permissions", Permissions = new[] { "GranularPermissions.ListGroupPerms" })] - public void ListGroupPermissionCommand(RunnerPlayer commandSource, string group, int page = 1) - { - if (!this.GranularPermissions.GetGroups().Contains(group)) - { - commandSource.Message($"Group {group} does not exist"); - return; + public string ListGroupPermissionCommand(Context context, string group, int page = 1) { + if (!this.GranularPermissions.GetGroups().Contains(group)) { + return $"Group {group} does not exist"; } - if (page < 1) - { + if (page < 1) { page = 1; } @@ -277,11 +305,11 @@ public void ListGroupPermissionCommand(RunnerPlayer commandSource, string group, int pageCount = (int)Math.Ceiling(permissions.Count / (double)this.Configuration.PermissionsPerPage); - commandSource.Message($"{group}:{Environment.NewLine}{string.Join("\n", permissions.Skip((page - 1) * this.Configuration.PermissionsPerPage).Take(this.Configuration.PermissionsPerPage))}{(pageCount > 1 ? $"{Environment.NewLine}Page {page} of {pageCount}{(page == pageCount ? "" : $", use listgroupperms \"{group}\" {page + 1} to see more")}" : "")}"); + return $"{group}:{Environment.NewLine}{string.Join("\n", permissions.Skip((page - 1) * this.Configuration.PermissionsPerPage).Take(this.Configuration.PermissionsPerPage))}{(pageCount > 1 ? $"{Environment.NewLine}Page {page} of {pageCount}{(page == pageCount ? "" : $", use listgroupperms \"{group}\" {page + 1} to see more")}" : "")}"; + return $"{group}:{Environment.NewLine}{string.Join("\n", permissions.Skip((page - 1) * this.Configuration.PermissionsPerPage).Take(this.Configuration.PermissionsPerPage))}{(pageCount > 1 ? $"{Environment.NewLine}Page {page} of {pageCount}{(page == pageCount ? "" : $", use listgroupperms \"{group}\" {page + 1} to see more")}" : "")}"; } } -public class GranularPermissionsCommandsConfiguration : ModuleConfiguration -{ +public class GranularPermissionsCommandsConfiguration : ModuleConfiguration { public int PermissionsPerPage { get; set; } = 6; -} +} \ No newline at end of file diff --git a/LoadingScreenText.cs b/LoadingScreenText.cs deleted file mode 100644 index 4883982..0000000 --- a/LoadingScreenText.cs +++ /dev/null @@ -1,22 +0,0 @@ -using BBRAPIModules; -using System.Threading.Tasks; - -namespace BattleBitBaseModules; - -[Module("Configure the loading screen text of your server", "1.0.0")] -public class LoadingScreenText : BattleBitModule -{ - public LoadingScreenTextConfiguration Configuration { get; set; } = null!; - - public override Task OnConnected() - { - this.Server.LoadingScreenText = this.Configuration.LoadingScreenText; - - return Task.CompletedTask; - } -} - -public class LoadingScreenTextConfiguration : ModuleConfiguration -{ - public string LoadingScreenText { get; set; } = "This is a community server!"; -} \ No newline at end of file diff --git a/LoadingScreenText.cs.md b/LoadingScreenText.cs.md new file mode 100644 index 0000000..e69de29 diff --git a/MOTD.cs b/MOTD.cs index de63425..b53052d 100644 --- a/MOTD.cs +++ b/MOTD.cs @@ -1,6 +1,7 @@ -using BattleBitAPI.Common; +using BattleBitAPI.Common; using BBRAPIModules; using Commands; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -8,7 +9,7 @@ namespace BattleBitBaseModules; [RequireModule(typeof(CommandHandler))] -[Module("Show a message of the day to players who join the server", "1.1.0")] +[Module("Show a message of the day to players who join the server", "1.2.0")] public class MOTD : BattleBitModule { public MOTDConfiguration Configuration { get; set; } = null!; @@ -16,13 +17,24 @@ public class MOTD : BattleBitModule [ModuleReference] public CommandHandler CommandHandler { get; set; } = null!; + [ModuleReference] + public dynamic? PlaceholderLib { get; set; } + + [ModuleReference] + public dynamic? GranularPermissions { get; set; } + + private List greetedPlayers = new(); + public override void OnModulesLoaded() { + if (this.PlaceholderLib is null) + { + this.Logger.Info("PlaceholderLib not found. MOTD will only support basic numbered placeholders."); + } + this.CommandHandler.Register(this); } - private List greetedPlayers = new(); - public override Task OnGameStateChanged(GameState oldState, GameState newState) { if (newState == GameState.EndingGame) @@ -41,28 +53,55 @@ public override Task OnPlayerConnected(RunnerPlayer player) return Task.CompletedTask; } - this.ShowMOTD(player); + if (this.GranularPermissions is not null && !this.GranularPermissions.HasPermission(player.SteamID, "MOTD.View")) + { + this.Logger.Debug($"Player {player.Name} ({player.SteamID}) does not have permission to view the MOTD."); + return Task.CompletedTask; + } + + this.ShowMOTD(new Context(new ChatSource(player), string.Empty, "motd", Array.Empty(), Array.Empty(), this, this.CommandHandler, null)); return Task.CompletedTask; } - [CommandCallback("setmotd", Description = "Sets the MOTD", Permissions = new[] { "MOTD.Set" })] - public void SetMOTD(RunnerPlayer commandSource, string motd) + [CommandCallback("setmotd", Description = "Sets the MOTD", Permissions = new[] { "MOTD.Set" }, ConsoleCommand = true)] + public void SetMOTD(Context context, string motd) { this.Configuration.MOTD = motd; this.Configuration.Save(); - this.ShowMOTD(commandSource); + this.ShowMOTD(context); } - [CommandCallback("motd", Description = "Shows the MOTD")] - public void ShowMOTD(RunnerPlayer commandSource) + [CommandCallback("motd", Description = "Shows the MOTD", Permissions = new[] { "MOTD.View" })] + public string ShowMOTD(Context context) { - commandSource.Message(string.Format(this.Configuration.MOTD, commandSource.Name, commandSource.PingMs, this.Server.ServerName, this.Server.Gamemode, this.Server.Map, this.Server.DayNight, this.Server.MapSize.ToString().Trim('_'), this.Server.CurrentPlayerCount, this.Server.InQueuePlayerCount, this.Server.MaxPlayerCount), this.Configuration.MessageTimeout); + ChatSource? chatSource = context.Source as ChatSource; + + string message; + if (this.PlaceholderLib is not null) + { + message = this.PlaceholderLib.Create(this.Configuration.MOTD) + .AddParam("servername", this.Server.ServerName) + .AddParam("gamemode", this.Server.Gamemode) + .AddParam("map", this.Server.Map) + .AddParam("daynight", this.Server.DayNight) + .AddParam("mapsize", this.Server.MapSize.ToString().Trim('_')) + .AddParam("currentplayers", this.Server.CurrentPlayerCount) + .AddParam("inqueueplayers", this.Server.InQueuePlayerCount) + .AddParam("maxplayers", this.Server.MaxPlayerCount) + .AddParam("name", chatSource?.Invoker.Name ?? context.Source.GetType().Name) + .AddParam("ping", chatSource?.Invoker.PingMs ?? 0).Build(); + } + else + { + message = string.Format(this.Configuration.MOTD, chatSource?.Invoker.Name ?? context.Source.GetType().Name, chatSource?.Invoker.PingMs ?? 0, this.Server.ServerName, this.Server.Gamemode, this.Server.Map, this.Server.DayNight, this.Server.MapSize.ToString().Trim('_'), this.Server.CurrentPlayerCount, this.Server.InQueuePlayerCount, this.Server.MaxPlayerCount); + } + + return message; } } public class MOTDConfiguration : ModuleConfiguration { public string MOTD { get; set; } = "Welcome!"; - public int MessageTimeout { get; set; } = 30; } diff --git a/MOTD.cs.md b/MOTD.cs.md new file mode 100644 index 0000000..4c22197 --- /dev/null +++ b/MOTD.cs.md @@ -0,0 +1,26 @@ +# 1 Modules in MOTD.cs + +| Description | Version | +|:---------------------------------------------------------|:----------| +| Show a message of the day to players who join the server | 1.0.0 | + +## Commands +| Command | Function Name | Description | Allowed Roles | Parameters | Defaults | +|:----------|:----------------|:---------------|:----------------|:----------------------------------------------|:-----------| +| setmotd | void | Sets the MOTD | Admin | ['RunnerPlayer commandSource', 'string motd'] | {} | +| motd | void | Shows the MOTD | | ['RunnerPlayer commandSource'] | {} | + +## Public Methods +| Function Name | Parameters | Defaults | +|:----------------|:----------------------------------------------|:-----------| +| | | | +| | | | +| | | | +| void | [''] | {} | +| Task | ['GameState oldState', 'GameState newState'] | {} | +| Task | ['RunnerPlayer player'] | {} | +| SetMOTD | ['RunnerPlayer commandSource', 'string motd'] | {} | +| ShowMOTD | ['RunnerPlayer commandSource'] | {} | +| | | | +| | | | +| | | | \ No newline at end of file diff --git a/ModeratorTools.cs b/ModeratorTools.cs index ba7cefd..678af26 100644 --- a/ModeratorTools.cs +++ b/ModeratorTools.cs @@ -9,385 +9,400 @@ namespace BattleBitBaseModules; [RequireModule(typeof(CommandHandler))] -[Module("Basic moderator tools", "1.1.0")] -public class ModeratorTools : BattleBitModule -{ +[Module("Basic moderator tools", "1.1.1")] +public class ModeratorTools : BattleBitModule { + [ModuleReference] public CommandHandler CommandHandler { get; set; } = null!; - public override void OnModulesLoaded() - { + public override void OnModulesLoaded() { this.CommandHandler.Register(this); } - public override Task OnConnected() - { + public override Task OnConnected() { Task.Run(playerInspection); return Task.CompletedTask; } - private async void playerInspection() - { - while (this.IsLoaded && this.Server.IsConnected) - { - foreach (KeyValuePair inspection in this.inspectPlayers) - { - RunnerPlayer target = inspection.Value; - - StringBuilder playerInfo = new(); - playerInfo.AppendLine($"{target.Name} ({target.SteamID} - {target.Role}"); - playerInfo.AppendLine($"Net: {target.IP} - {target.PingMs}ms"); - playerInfo.AppendLine($"Game: {target.Team} - {target.SquadName} - {(target.IsConnected ? "Connected" : "Disconnected")}"); - playerInfo.AppendLine($"Health: {target.HP} - {(target.IsAlive ? "Alive" : "Dead")} - {(target.IsDown ? "Down" : "Up")} - {(target.IsBleeding ? "Bleeding" : "Not bleeding")}"); - playerInfo.AppendLine($"State: {target.StandingState} - {target.LeaningState} - {(target.InVehicle ? "In vehicle" : "Not in vehicle")}"); - playerInfo.AppendLine($"Position: {target.Position}"); - playerInfo.AppendLine($"Loadout: {target.CurrentLoadout.PrimaryWeapon.ToolName} - {target.CurrentLoadout.SecondaryWeapon.ToolName} - {target.CurrentLoadout.ThrowableName}"); - playerInfo.AppendLine($"Loadout: {target.CurrentLoadout.HeavyGadgetName} - {target.CurrentLoadout.LightGadgetName}"); - - inspection.Key.Message(playerInfo.ToString()); + private async void playerInspection() { + while (this.IsLoaded && this.Server.IsConnected) { + foreach (KeyValuePair inspection in this.inspectPlayers) { + inspection.Key.Message(this.getPlayerInspectionText(inspection.Value)); } await Task.Delay(250); } } + private string getPlayerInspectionText(RunnerPlayer target) { + StringBuilder playerInfo = new(); + playerInfo.AppendLine($"{target.Name} ({target.SteamID} - {target.Role}"); + playerInfo.AppendLine($"Net: {target.IP} - {target.PingMs}ms"); + playerInfo.AppendLine($"Game: {target.Team} - {target.SquadName} - {(target.IsConnected ? "Connected" : "Disconnected")}"); + playerInfo.AppendLine($"Health: {target.HP} - {(target.IsAlive ? "Alive" : "Dead")} - {(target.IsDown ? "Down" : "Up")} - {(target.IsBleeding ? "Bleeding" : "Not bleeding")}"); + playerInfo.AppendLine($"State: {target.StandingState} - {target.LeaningState} - {(target.InVehicle ? "In vehicle" : "Not in vehicle")}"); + playerInfo.AppendLine($"Position: {target.Position}"); + playerInfo.AppendLine($"Loadout: {target.CurrentLoadout.PrimaryWeapon.ToolName} - {target.CurrentLoadout.SecondaryWeapon.ToolName} - {target.CurrentLoadout.ThrowableName}"); + playerInfo.AppendLine($"Loadout: {target.CurrentLoadout.HeavyGadgetName} - {target.CurrentLoadout.LightGadgetName}"); + + return playerInfo.ToString(); + } + [CommandCallback("Say", Description = "Prints a message to all players", Permissions = new[] { "ModeratorTools.Say" })] - public void Say(RunnerPlayer commandSource, string message) - { + public string Say(Context context, string message) { + this.Logger.Info($"[Say] {(context.Source is ChatSource chatSource ? chatSource.Invoker.Name : context.Source.GetType().Name)}: {message}"); this.Server.SayToAllChat(message); + + return $"Message sent to all players"; } [CommandCallback("SayToPlayer", Description = "Prints a message to all players", Permissions = new[] { "ModeratorTools.SayToPlayer" })] - public void SayToPlayer(RunnerPlayer commandSource, RunnerPlayer target, string message) - { + public string SayToPlayer(Context context, RunnerPlayer target, string message) { + this.Logger.Info($"[SayToPlayer] {(context.Source is ChatSource chatSource ? chatSource.Invoker.Name : context.Source.GetType().Name)} -> {target.Name}: {message}"); this.Server.SayToChat(message, target.SteamID); + + return $"Message sent to {target.Name}"; } [CommandCallback("AnnounceShort", Description = "Prints a short announce to all players", Permissions = new[] { "ModeratorTools.AnnounceShort" })] - public void AnnounceShort(RunnerPlayer commandSource, string message) - { + public string AnnounceShort(Context context, string message) { + this.Logger.Info($"[AnnounceShort] {(context.Source is ChatSource chatSource ? chatSource.Invoker.Name : context.Source.GetType().Name)}: {message}"); this.Server.AnnounceShort(message); + + return $"Announce sent"; } [CommandCallback("AnnounceLong", Description = "Prints a long announce to all players", Permissions = new[] { "ModeratorTools.AnnounceLong" })] - public void AnnounceLong(RunnerPlayer commandSource, string message) - { + public string AnnounceLong(Context context, string message) { + this.Logger.Info($"[AnnounceLong] {(context.Source is ChatSource chatSource ? chatSource.Invoker.Name : context.Source.GetType().Name)}: {message}"); this.Server.AnnounceLong(message); + + return $"Announce sent"; } [CommandCallback("Message", Description = "Messages a specific player", Permissions = new[] { "ModeratorTools.Message" })] - public void Message(RunnerPlayer commandSource, RunnerPlayer target, string message, float? timeout = null) - { - if (timeout.HasValue) - { + public string Message(Context context, RunnerPlayer target, string message, float? timeout = null) { + this.Logger.Info($"[Message] {(context.Source is ChatSource chatSource ? chatSource.Invoker.Name : context.Source.GetType().Name)} -> {target.Name}: {message}"); + + if (timeout.HasValue) { target.Message(message, timeout.Value); - } - else - { + } else { target.Message(message); } - commandSource.Message($"Message sent to {target.Name}", 10); + return $"Message sent to {target.Name}"; } [CommandCallback("Clear", Description = "Clears the chat", Permissions = new[] { "ModeratorTools.Clear" })] - public void Clear(RunnerPlayer commandSource) - { + public string Clear(Context context) { + this.Logger.Info($"[Clear] {(context.Source is ChatSource chatSource ? chatSource.Invoker.Name : context.Source.GetType().Name)}"); this.Server.SayToAllChat("".PadLeft(30, '\n') + "Chat cleared"); + + return $"Chat cleared"; } [CommandCallback("Kick", Description = "Kicks a player", Permissions = new[] { "ModeratorTools.Kick" })] - public void Kick(RunnerPlayer commandSource, RunnerPlayer target, string? reason = null) - { + public string Kick(Context context, RunnerPlayer target, string? reason = null) { + this.Logger.Info($"[Kick] {(context.Source is ChatSource chatSource ? chatSource.Invoker.Name : context.Source.GetType().Name)} -> {target.Name}: {reason ?? string.Empty}"); target.Kick(reason ?? string.Empty); - commandSource.Message($"Player {target.Name} kicked", 10); + return $"Player {target.Name} kicked"; } [CommandCallback("Ban", Description = "Bans a player", Permissions = new[] { "ModeratorTools.Ban" })] - public void Ban(RunnerPlayer commandSource, RunnerPlayer target) - { + public string Ban(Context context, RunnerPlayer target) { + this.Logger.Info($"[Ban] {(context.Source is ChatSource chatSource ? chatSource.Invoker.Name : context.Source.GetType().Name)} -> {target.Name}"); this.Server.ExecuteCommand($"ban {target.SteamID}"); target.Kick(); - commandSource.Message($"Player {target.Name} banned", 10); + return $"Player {target.Name} banned"; } [CommandCallback("Kill", Description = "Kills a player", Permissions = new[] { "ModeratorTools.Kill" })] - public void Kill(RunnerPlayer commandSource, RunnerPlayer target, string? message = null) - { + public string Kill(Context context, RunnerPlayer target, string? message = null) { + this.Logger.Info($"[Kill] {(context.Source is ChatSource chatSource ? chatSource.Invoker.Name : context.Source.GetType().Name)} -> {target.Name}"); target.Kill(); - commandSource.Message($"Player {target.Name} killed", 10); - - if (!string.IsNullOrEmpty(message)) - { + if (!string.IsNullOrEmpty(message)) { target.Message(message); } + + return $"Player {target.Name} killed"; } [CommandCallback("Gag", Description = "Gags a player", Permissions = new[] { "ModeratorTools.Gag" })] - public void Gag(RunnerPlayer commandSource, RunnerPlayer target, string? message = null) - { - if (this.gaggedPlayers.Contains(target.SteamID)) - { - commandSource.Message($"Player {target.Name} is already gagged"); - return; + public string Gag(Context context, RunnerPlayer target, string? message = null) { + this.Logger.Info($"[Gag] {(context.Source is ChatSource chatSource ? chatSource.Invoker.Name : context.Source.GetType().Name)} -> {target.Name}"); + if (this.gaggedPlayers.Contains(target.SteamID)) { + return $"Player {target.Name} is already gagged"; } this.gaggedPlayers.Add(target.SteamID); - commandSource.Message($"Player {target.Name} gagged", 10); - - if (!string.IsNullOrEmpty(message)) - { + if (!string.IsNullOrEmpty(message)) { target.Message(message); } + + return $"Player {target.Name} gagged"; } [CommandCallback("Ungag", Description = "Ungags a player", Permissions = new[] { "ModeratorTools.Ungag" })] - public void Ungag(RunnerPlayer commandSource, RunnerPlayer target, string? message = null) - { - if (!this.gaggedPlayers.Contains(target.SteamID)) - { - commandSource.Message($"Player {target.Name} is not gagged"); - return; + public string Ungag(Context context, RunnerPlayer target, string? message = null) { + this.Logger.Info($"[Ungag] {(context.Source is ChatSource chatSource ? chatSource.Invoker.Name : context.Source.GetType().Name)} -> {target.Name}"); + if (!this.gaggedPlayers.Contains(target.SteamID)) { + return $"Player {target.Name} is not gagged"; } this.gaggedPlayers.Remove(target.SteamID); - commandSource.Message($"Player {target.Name} ungagged", 10); - - if (!string.IsNullOrEmpty(message)) - { + if (!string.IsNullOrEmpty(message)) { target.Message(message); } + + return $"Player {target.Name} ungagged"; } [CommandCallback("Mute", Description = "Mutes a player", Permissions = new[] { "ModeratorTools.Mute" })] - public void Mute(RunnerPlayer commandSource, RunnerPlayer target, string? message = null) - { - if (target.Modifications.IsVoiceChatMuted) - { - commandSource.Message($"Player {target.Name} is already muted"); - return; + public string Mute(Context context, RunnerPlayer target, string? message = null) { + this.Logger.Info($"[Mute] {(context.Source is ChatSource chatSource ? chatSource.Invoker.Name : context.Source.GetType().Name)} -> {target.Name}"); + if (target.Modifications.IsVoiceChatMuted) { + return $"Player {target.Name} is already muted"; } target.Modifications.IsVoiceChatMuted = true; - commandSource.Message($"Player {target.Name} muted", 10); - - if (!string.IsNullOrEmpty(message)) - { + if (!string.IsNullOrEmpty(message)) { target.Message(message); } + + return $"Player {target.Name} muted"; } [CommandCallback("Unmute", Description = "Unmutes a player", Permissions = new[] { "ModeratorTools.Unmute" })] - public void Unmute(RunnerPlayer commandSource, RunnerPlayer target, string? message = null) - { - if (!target.Modifications.IsVoiceChatMuted) - { - commandSource.Message($"Player {target.Name} is not muted"); - return; + public string Unmute(Context context, RunnerPlayer target, string? message = null) { + this.Logger.Info($"[Unmute] {(context.Source is ChatSource chatSource ? chatSource.Invoker.Name : context.Source.GetType().Name)} -> {target.Name}"); + if (!target.Modifications.IsVoiceChatMuted) { + return $"Player {target.Name} is not muted"; } target.Modifications.IsVoiceChatMuted = false; - commandSource.Message($"Player {target.Name} unmuted", 10); - - if (!string.IsNullOrEmpty(message)) - { + if (!string.IsNullOrEmpty(message)) { target.Message(message); } + + return $"Player {target.Name} unmuted"; } [CommandCallback("Silence", Description = "Mutes and gags a player", Permissions = new[] { "ModeratorTools.Silence" })] - public void Silence(RunnerPlayer commandSource, RunnerPlayer target, string? message = null) - { - Mute(commandSource, target); - Gag(commandSource, target); - commandSource.Message($"Player {target.Name} silenced", 10); - - if (!string.IsNullOrEmpty(message)) - { + public string Silence(Context context, RunnerPlayer target, string? message = null) { + this.Logger.Info($"[Silence] {(context.Source is ChatSource chatSource ? chatSource.Invoker.Name : context.Source.GetType().Name)} -> {target.Name}"); + Mute(context, target); + Gag(context, target); + + if (!string.IsNullOrEmpty(message)) { target.Message(message); } + + return $"Player {target.Name} silenced"; } [CommandCallback("Unsilence", Description = "Unmutes and ungags a player", Permissions = new[] { "ModeratorTools.Unsilence" })] - public void Unsilence(RunnerPlayer commandSource, RunnerPlayer target, string? message = null) - { - Unmute(commandSource, target); - Ungag(commandSource, target); - commandSource.Message($"Player {target.Name} unsilenced", 10); - - if (!string.IsNullOrEmpty(message)) - { + public string Unsilence(Context context, RunnerPlayer target, string? message = null) { + this.Logger.Info($"[Unsilence] {(context.Source is ChatSource chatSource ? chatSource.Invoker.Name : context.Source.GetType().Name)} -> {target.Name}"); + Unmute(context, target); + Ungag(context, target); + + if (!string.IsNullOrEmpty(message)) { target.Message(message); } + + return $"Player {target.Name} unsilenced"; } [CommandCallback("LockSpawn", Description = "Prevents a player or all players from spawning", Permissions = new[] { "ModeratorTools.LockSpawn" })] - public void LockSpawn(RunnerPlayer commandSource, RunnerPlayer? target = null, string? message = null) - { - if (target == null) - { + public string LockSpawn(Context context, RunnerPlayer? target = null, string? message = null) { + if (target == null) { + this.Logger.Info($"[LockSpawn] {(context.Source is ChatSource chatSource ? chatSource.Invoker.Name : context.Source.GetType().Name)} -> All"); this.globalSpawnLock = true; - foreach (RunnerPlayer player in this.Server.AllPlayers) - { + foreach (RunnerPlayer player in this.Server.AllPlayers) { player.Modifications.CanDeploy = false; } - commandSource.Message("Spawn globally locked", 10); - } - else - { - if (this.lockedSpawns.Contains(target.SteamID)) - { - commandSource.Message($"Spawn already locked for {target.Name}", 10); - return; + + return "Spawn globally locked"; + } else { + this.Logger.Info($"[LockSpawn] {(context.Source is ChatSource chatSource ? chatSource.Invoker.Name : context.Source.GetType().Name)} -> {target.Name}"); + if (this.lockedSpawns.Contains(target.SteamID)) { + return $"Spawn already locked for {target.Name}"; } target.Modifications.CanDeploy = false; this.lockedSpawns.Add(target.SteamID); - commandSource.Message($"Spawn locked for {target.Name}", 10); - if (!string.IsNullOrEmpty(message)) - { + if (!string.IsNullOrEmpty(message)) { target.Message(message); } + + return $"Spawn locked for {target.Name}"; } } [CommandCallback("UnlockSpawn", Description = "Allows a player or all players to spawn", Permissions = new[] { "ModeratorTools.UnlockSpawn" })] - public void UnlockSpawn(RunnerPlayer commandSource, RunnerPlayer? target = null, string? message = null) - { - if (target == null) - { + public string UnlockSpawn(Context context, RunnerPlayer? target = null, string? message = null) { + if (target == null) { + this.Logger.Info($"[UnlockSpawn] {(context.Source is ChatSource chatSource ? chatSource.Invoker.Name : context.Source.GetType().Name)} -> All"); this.globalSpawnLock = false; - foreach (RunnerPlayer player in this.Server.AllPlayers) - { + foreach (RunnerPlayer player in this.Server.AllPlayers) { player.Modifications.CanDeploy = true; } - commandSource.Message("Spawn globally unlocked", 10); - } - else - { - if (!this.lockedSpawns.Contains(target.SteamID)) - { - commandSource.Message($"Spawn already unlocked for {target.Name}", 10); - return; + + return "Spawn globally unlocked"; + } else { + this.Logger.Info($"[UnlockSpawn] {(context.Source is ChatSource chatSource ? chatSource.Invoker.Name : context.Source.GetType().Name)} -> {target.Name}"); + if (!this.lockedSpawns.Contains(target.SteamID)) { + return $"Spawn already unlocked for {target.Name}"; } target.Modifications.CanDeploy = true; this.lockedSpawns.Remove(target.SteamID); - commandSource.Message($"Spawn unlocked for {target.Name}", 10); - if (!string.IsNullOrEmpty(message)) - { + if (!string.IsNullOrEmpty(message)) { target.Message(message); } + + return $"Spawn unlocked for {target.Name}"; } } [CommandCallback("tp2me", Description = "Teleports a player to you", Permissions = new[] { "ModeratorTools.Teleport" })] - public void TeleportPlayerToMe(RunnerPlayer commandSource, RunnerPlayer target) - { - target.Teleport(new Vector3((int)commandSource.Position.X, (int)commandSource.Position.Y, (int)commandSource.Position.Z)); + public string TeleportPlayerToMe(Context context, RunnerPlayer target) { + if (context.Source is not ChatSource chatSource) { + return "Teleport player to me can only work from chat"; + } + + this.Logger.Info($"[TeleportPlayerToMe] {chatSource.Invoker.Name} -> {target.Name}"); + target.Teleport(new Vector3((int)chatSource.Invoker.Position.X, (int)chatSource.Invoker.Position.Y, (int)chatSource.Invoker.Position.Z)); + + return $"Player {target.Name} teleported to you"; } [CommandCallback("tpme2", Description = "Teleports you to a player", Permissions = new[] { "ModeratorTools.Teleport" })] - public void TeleportMeToPlayer(RunnerPlayer commandSource, RunnerPlayer target) - { - commandSource.Teleport(new Vector3((int)target.Position.X, (int)target.Position.Y, (int)target.Position.Z)); + public string TeleportMeToPlayer(Context context, RunnerPlayer target) { + if (context.Source is not ChatSource chatSource) { + return "Teleport me to player can only work from chat"; + } + + this.Logger.Info($"[TeleportMeToPlayer] {chatSource.Invoker.Name} -> {target.Name}"); + chatSource.Invoker.Teleport(new Vector3((int)target.Position.X, (int)target.Position.Y, (int)target.Position.Z)); + + return $"You teleported to {target.Name}"; } [CommandCallback("tp", Description = "Teleports a player to another player", Permissions = new[] { "ModeratorTools.Teleport" })] - public void TeleportPlayerToPlayer(RunnerPlayer commandSource, RunnerPlayer target, RunnerPlayer destination) - { + public string TeleportPlayerToPlayer(Context context, RunnerPlayer target, RunnerPlayer destination) { + this.Logger.Info($"[TeleportPlayerToPlayer] {(context.Source is ChatSource chatSource ? chatSource.Invoker.Name : context.Source.GetType().Name)} -> {target.Name} -> {destination.Name}"); target.Teleport(new Vector3((int)destination.Position.X, (int)destination.Position.Y, (int)destination.Position.Z)); + + return $"Player {target.Name} teleported to {destination.Name}"; } [CommandCallback("tp2pos", Description = "Teleports a player to a position", Permissions = new[] { "ModeratorTools.Teleport" })] - public void TeleportPlayerToPos(RunnerPlayer commandSource, RunnerPlayer target, int x, int y, int z) - { + public string TeleportPlayerToPos(Context context, RunnerPlayer target, int x, int y, int z) { + this.Logger.Info($"[TeleportPlayerToPos] {(context.Source is ChatSource chatSource ? chatSource.Invoker.Name : context.Source.GetType().Name)} -> {target.Name} -> {x} {y} {z}"); target.Teleport(new Vector3(x, y, z)); + + return $"Player {target.Name} teleported to {x} {y} {z}"; } [CommandCallback("tpme2pos", Description = "Teleports you to a position", Permissions = new[] { "ModeratorTools.Teleport" })] - public void TeleportMeToPos(RunnerPlayer commandSource, int x, int y, int z) - { - commandSource.Teleport(new Vector3(x, y, z)); + public string TeleportMeToPos(Context context, int x, int y, int z) { + if (context.Source is not ChatSource chatSource) { + return "Teleport me to position can only work from chat"; + } + + this.Logger.Info($"[TeleportMeToPos] {chatSource.Invoker.Name} -> {x} {y} {z}"); + chatSource.Invoker.Teleport(new Vector3(x, y, z)); + + return $"You teleported to {x} {y} {z}"; } [CommandCallback("freeze", Description = "Freezes a player", Permissions = new[] { "ModeratorTools.Freeze" })] - public void Freeze(RunnerPlayer commandSource, RunnerPlayer target, string? message = null) - { + public string Freeze(Context context, RunnerPlayer target, string? message = null) { + this.Logger.Info($"[Freeze] {(context.Source is ChatSource chatSource ? chatSource.Invoker.Name : context.Source.GetType().Name)} -> {target.Name}"); target.Modifications.Freeze = true; - commandSource.Message($"Player {target.Name} frozen", 10); - if (!string.IsNullOrEmpty(message)) - { + if (!string.IsNullOrEmpty(message)) { target.Message(message); } + + return $"Player {target.Name} frozen"; } [CommandCallback("unfreeze", Description = "Unfreezes a player", Permissions = new[] { "ModeratorTools.Unfreeze" })] - public void Unfreeze(RunnerPlayer commandSource, RunnerPlayer target, string? message = null) - { + public string Unfreeze(Context context, RunnerPlayer target, string? message = null) { + this.Logger.Info($"[Unfreeze] {(context.Source is ChatSource chatSource ? chatSource.Invoker.Name : context.Source.GetType().Name)} -> {target.Name}"); target.Modifications.Freeze = false; - commandSource.Message($"Player {target.Name} unfrozen", 10); - if (!string.IsNullOrEmpty(message)) - { + if (!string.IsNullOrEmpty(message)) { target.Message(message); } + + return $"Player {target.Name} unfrozen"; } private Dictionary inspectPlayers = new(); [CommandCallback("Inspect", Description = "Inspects a player or stops inspection", Permissions = new[] { "ModeratorTools.Inspect" })] - public void Inspect(RunnerPlayer commandSource, RunnerPlayer? target = null) - { - if (target is null) - { - this.inspectPlayers.Remove(commandSource); - commandSource.Message("Inspection stopped", 2); - return; + public string? Inspect(Context context, RunnerPlayer? target = null) { + if (context.Source is not ChatSource chatSource) { + if (target is null) { + return "A target player to inspect is required."; + } + + return this.getPlayerInspectionText(target); } - if (this.inspectPlayers.ContainsKey(commandSource)) - { - this.inspectPlayers[commandSource] = target; + if (target is null) { + this.inspectPlayers.Remove(chatSource.Invoker); + return "Inspection stopped"; } - else - { - this.inspectPlayers.Add(commandSource, target); + + if (this.inspectPlayers.ContainsKey(chatSource.Invoker)) { + this.inspectPlayers[chatSource.Invoker] = target; + } else { + this.inspectPlayers.Add(chatSource.Invoker, target); } + + return null; + } + + [CommandCallback("warn", Description = "Warns a player", Permissions = new[] { "ModeratorTools.Warn" })] + public string Warn(Context context, RunnerPlayer target, string? message = null) { + this.Logger.Info($"[Warn] {(context.Source is ChatSource chatSource ? chatSource.Invoker.Name : context.Source.GetType().Name)} -> {target.Name}: {message ?? "no reason"}"); + target.WarnPlayer(message ?? "no reason"); + target.Message($"You have been warned for\n{message ?? "no reason"}", 25); + + return $"Player {target.Name} warned"; } private List gaggedPlayers = new(); private List lockedSpawns = new(); private bool globalSpawnLock = false; - public override Task OnPlayerTypedMessage(RunnerPlayer player, ChatChannel channel, string msg) - { - if (this.gaggedPlayers.Contains(player.SteamID)) - { + public override Task OnPlayerTypedMessage(RunnerPlayer player, ChatChannel channel, string msg) { + if (this.gaggedPlayers.Contains(player.SteamID)) { return Task.FromResult(false); } return Task.FromResult(true); } - public override Task OnPlayerSpawning(RunnerPlayer player, OnPlayerSpawnArguments request) - { - if (this.globalSpawnLock || this.lockedSpawns.Contains(player.SteamID)) - { + public override Task OnPlayerSpawning(RunnerPlayer player, OnPlayerSpawnArguments request) { + if (this.globalSpawnLock || this.lockedSpawns.Contains(player.SteamID)) { return Task.FromResult(null); } return Task.FromResult(request as OnPlayerSpawnArguments?); } -} +} \ No newline at end of file diff --git a/ModeratorTools.cs.md b/ModeratorTools.cs.md new file mode 100644 index 0000000..98b329b --- /dev/null +++ b/ModeratorTools.cs.md @@ -0,0 +1,69 @@ +# 1 Modules in ModeratorTools.cs + +| Description | Version | +|:----------------------|:----------| +| Basic moderator tools | 1.0.0 | + +## Commands +| Command | Function Name | Description | Allowed Roles | Parameters | Defaults | +|:--------------|:----------------|:-----------------------------------------------|:----------------|:-------------------------------------------------------------------------------------------------|:--------------------------------------| +| Say | void | Prints a message to all players | Moderator | ['RunnerPlayer commandSource', 'string message'] | {} | +| SayToPlayer | void | Prints a message to all players | Moderator | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'string message'] | {} | +| AnnounceShort | void | Prints a short announce to all players | Moderator | ['RunnerPlayer commandSource', 'string message'] | {} | +| AnnounceLong | void | Prints a long announce to all players | Moderator | ['RunnerPlayer commandSource', 'string message'] | {} | +| Message | void | Messages a specific player | Moderator | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'string message', 'float? timeout = null'] | {'timeout': 'null'} | +| Clear | void | Clears the chat | Moderator | ['RunnerPlayer commandSource'] | {} | +| Kick | void | Kicks a player | Moderator | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'string? reason = null'] | {'reason': 'null'} | +| Ban | void | Bans a player | Moderator | ['RunnerPlayer commandSource', 'RunnerPlayer target'] | {} | +| Kill | void | Kills a player | Moderator | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'string? message = null'] | {'message': 'null'} | +| Gag | void | Gags a player | Moderator | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'string? message = null'] | {'message': 'null'} | +| Ungag | void | Ungags a player | Moderator | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'string? message = null'] | {'message': 'null'} | +| Mute | void | Mutes a player | Moderator | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'string? message = null'] | {'message': 'null'} | +| Unmute | void | Unmutes a player | Moderator | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'string? message = null'] | {'message': 'null'} | +| Silence | void | Mutes and gags a player | Moderator | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'string? message = null'] | {'message': 'null'} | +| Unsilence | void | Unmutes and ungags a player | Moderator | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'string? message = null'] | {'message': 'null'} | +| LockSpawn | void | Prevents a player or all players from spawning | Moderator | ['RunnerPlayer commandSource', 'RunnerPlayer? target = null', 'string? message = null'] | {'target': 'null', 'message': 'null'} | +| UnlockSpawn | void | Allows a player or all players to spawn | Moderator | ['RunnerPlayer commandSource', 'RunnerPlayer? target = null', 'string? message = null'] | {'target': 'null', 'message': 'null'} | +| tp2me | void | Teleports a player to you | Moderator | ['RunnerPlayer commandSource', 'RunnerPlayer target'] | {} | +| tpme2 | void | Teleports you to a player | Moderator | ['RunnerPlayer commandSource', 'RunnerPlayer target'] | {} | +| tp | void | Teleports a player to another player | Moderator | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'RunnerPlayer destination'] | {} | +| tp2pos | void | Teleports a player to a position | Moderator | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'int x', 'int y', 'int z'] | {} | +| tpme2pos | void | Teleports you to a position | Moderator | ['RunnerPlayer commandSource', 'int x', 'int y', 'int z'] | {} | +| freeze | void | Freezes a player | Moderator | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'string? message = null'] | {'message': 'null'} | +| unfreeze | void | Unfreezes a player | Moderator | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'string? message = null'] | {'message': 'null'} | +| Inspect | void | Inspects a player or stops inspection | Moderator | ['RunnerPlayer commandSource', 'RunnerPlayer? target = null'] | {'target': 'null'} | + +## Public Methods +| Function Name | Parameters | Defaults | +|:-----------------------|:-------------------------------------------------------------------------------------------------|:--------------------------------------| +| | | | +| | | | +| void | [''] | {} | +| Task | [''] | {} | +| Say | ['RunnerPlayer commandSource', 'string message'] | {} | +| SayToPlayer | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'string message'] | {} | +| AnnounceShort | ['RunnerPlayer commandSource', 'string message'] | {} | +| AnnounceLong | ['RunnerPlayer commandSource', 'string message'] | {} | +| Message | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'string message', 'float? timeout = null'] | {'timeout': 'null'} | +| Clear | ['RunnerPlayer commandSource'] | {} | +| Kick | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'string? reason = null'] | {'reason': 'null'} | +| Ban | ['RunnerPlayer commandSource', 'RunnerPlayer target'] | {} | +| Kill | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'string? message = null'] | {'message': 'null'} | +| Gag | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'string? message = null'] | {'message': 'null'} | +| Ungag | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'string? message = null'] | {'message': 'null'} | +| Mute | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'string? message = null'] | {'message': 'null'} | +| Unmute | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'string? message = null'] | {'message': 'null'} | +| Silence | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'string? message = null'] | {'message': 'null'} | +| Unsilence | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'string? message = null'] | {'message': 'null'} | +| LockSpawn | ['RunnerPlayer commandSource', 'RunnerPlayer? target = null', 'string? message = null'] | {'target': 'null', 'message': 'null'} | +| UnlockSpawn | ['RunnerPlayer commandSource', 'RunnerPlayer? target = null', 'string? message = null'] | {'target': 'null', 'message': 'null'} | +| TeleportPlayerToMe | ['RunnerPlayer commandSource', 'RunnerPlayer target'] | {} | +| TeleportMeToPlayer | ['RunnerPlayer commandSource', 'RunnerPlayer target'] | {} | +| TeleportPlayerToPlayer | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'RunnerPlayer destination'] | {} | +| TeleportPlayerToPos | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'int x', 'int y', 'int z'] | {} | +| TeleportMeToPos | ['RunnerPlayer commandSource', 'int x', 'int y', 'int z'] | {} | +| Freeze | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'string? message = null'] | {'message': 'null'} | +| Unfreeze | ['RunnerPlayer commandSource', 'RunnerPlayer target', 'string? message = null'] | {'message': 'null'} | +| Inspect | ['RunnerPlayer commandSource', 'RunnerPlayer? target = null'] | {'target': 'null'} | +| Task | ['RunnerPlayer player', 'ChatChannel channel', 'string msg'] | {} | +| Task | ['RunnerPlayer player', 'OnPlayerSpawnArguments request'] | {} | \ No newline at end of file diff --git a/ModuleUpdates.cs b/ModuleUpdates.cs index 1b95f06..23b96e3 100644 --- a/ModuleUpdates.cs +++ b/ModuleUpdates.cs @@ -11,39 +11,32 @@ namespace BattleBitBaseModules; [Module("Check for and download module updates from the module repository", "1.0.0")] -public class ModuleUpdates : BattleBitModule -{ +public class ModuleUpdates : BattleBitModule { public static ModuleUpdatesConfiguration Configuration { get; set; } = null!; - PropertyInfo nameProperty = null!; - PropertyInfo versionProperty = null!; - PropertyInfo moduleFilePathProperty = null!; + private PropertyInfo nameProperty = null!; + private PropertyInfo versionProperty = null!; + private PropertyInfo moduleFilePathProperty = null!; private static dynamic[] modulesToUpdate = Array.Empty(); private static bool running = false; - public override void OnModulesLoaded() - { - if (!running) - { + public override void OnModulesLoaded() { + if (!running) { running = true; Task.Run(versionChecker); } } - public override void OnModuleUnloading() - { + public override void OnModuleUnloading() { running = false; } private static DateTime lastChecked = DateTime.MinValue; - private async Task versionChecker() - { - while (running) - { - if (lastChecked.AddMinutes(Configuration.CheckDelay) < DateTime.Now) - { + private async Task versionChecker() { + while (running) { + if (lastChecked.AddMinutes(Configuration.CheckDelay) < DateTime.Now) { await doVersionCheck(); } @@ -51,14 +44,12 @@ private async Task versionChecker() } } - private async Task doVersionCheck() - { + private async Task doVersionCheck() { lastChecked = DateTime.Now; IReadOnlyList collection = null!; - try - { + try { Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetTypes().Any(t => t.Namespace == "BattleBitAPIRunner"))!; Type moduleType = assembly.GetTypes().FirstOrDefault(t => t.Name == "Module")!; PropertyInfo modulesProperty = moduleType.GetProperty("Modules")!; @@ -67,85 +58,67 @@ private async Task doVersionCheck() this.nameProperty = moduleType.GetProperty("Name")!; this.versionProperty = moduleType.GetProperty("Version")!; this.moduleFilePathProperty = moduleType.GetProperty("ModuleFilePath")!; - } - catch (Exception ex) - { + } catch (Exception ex) { this.Logger.Error($"Error retrieving loaded modules: {ex.Message}"); return; } List modulesToUpdate = new(); - foreach (dynamic item in collection) - { + foreach (dynamic item in collection) { string? moduleName = null; string? moduleVersion = null; - try - { + try { moduleName = this.nameProperty.GetValue(item).ToString(); moduleVersion = this.versionProperty.GetValue(item).ToString(); - } - catch (Exception ex) - { + } catch (Exception ex) { this.Logger.Error($"Error retrieving module name and version: {ex.Message}"); continue; } - if (moduleName == null || moduleVersion == null) - { + if (moduleName == null || moduleVersion == null) { continue; } string? latestVersion = null; - try - { + try { HttpClient client = new HttpClient(); client.DefaultRequestHeaders.Add("User-Agent", "BattleBitAPIRunner"); string response = await client.GetStringAsync($"{Configuration.APIEndpoint}/Modules/GetModule/{moduleName}"); - using (JsonDocument responseDocument = JsonDocument.Parse(response)) - { + using (JsonDocument responseDocument = JsonDocument.Parse(response)) { latestVersion = responseDocument.RootElement.GetProperty("versions").EnumerateArray().First().GetProperty("Version_v_number").GetString(); } - } - catch (Exception ex) - { + } catch (Exception ex) { this.Logger.Error($"Error checking for module {moduleName} updates: {ex.Message}"); continue; } - if (latestVersion == null) - { + if (latestVersion == null) { continue; } - if (moduleVersion != latestVersion) - { + if (moduleVersion != latestVersion) { this.Logger.Warn($"Module {moduleName} is out of date! Installed version: {moduleVersion}, Latest version: {latestVersion}"); modulesToUpdate.Add(item); } } - if (modulesToUpdate.Count > 0) - { + if (modulesToUpdate.Count > 0) { ModuleUpdates.modulesToUpdate = modulesToUpdate.ToArray(); this.Logger.Warn($"There are {modulesToUpdate.Count} modules out of date. Run 'updateall' to update all modules or 'update modulename' to update an individual module."); } } - private async Task doUpdate(string? module = null) - { - if (module is null) - { - if (modulesToUpdate.Length == 0) - { + private async Task doUpdate(string? module = null) { + if (module is null) { + if (modulesToUpdate.Length == 0) { this.Logger.Info("There are no modules to update. Run 'update' to fetch latest versions."); return; } - foreach (dynamic item in modulesToUpdate) - { + foreach (dynamic item in modulesToUpdate) { await doUpdate(this.nameProperty.GetValue(item).ToString()); } @@ -153,8 +126,7 @@ private async Task doUpdate(string? module = null) } dynamic? moduleToUpdate = modulesToUpdate.FirstOrDefault(m => module.Equals(this.nameProperty.GetValue(m).ToString(), StringComparison.InvariantCultureIgnoreCase)); - if (moduleToUpdate is null) - { + if (moduleToUpdate is null) { this.Logger.Error($"Module {module} is not out of date."); return; } @@ -168,28 +140,23 @@ private async Task doUpdate(string? module = null) this.Logger.Info($"Module {module} updated successfully."); } - public override void OnConsoleCommand(string command) - { - if (!command.Trim().ToLower().StartsWith("update")) - { + public override void OnConsoleCommand(string command) { + if (!command.Trim().ToLower().StartsWith("update")) { return; } - if (command.Trim().ToLower() == "updateall") - { + if (command.Trim().ToLower() == "updateall") { Task.Run(() => doUpdate()).ContinueWith(t => this.Logger.Error($"Error updating modules: {t.Exception!.Message}"), TaskContinuationOptions.OnlyOnFaulted); return; } string[] args = command.Split(' '); - if (args.Length > 2) - { + if (args.Length > 2) { this.Logger.Error("Usage: update [module name]"); return; } - if (args.Length == 1) - { + if (args.Length == 1) { doVersionCheck().ContinueWith(t => this.Logger.Error($"Error checking for module updates: {t.Exception!.Message}"), TaskContinuationOptions.OnlyOnFaulted); return; } @@ -198,8 +165,7 @@ public override void OnConsoleCommand(string command) } } -public class ModuleUpdatesConfiguration : ModuleConfiguration -{ +public class ModuleUpdatesConfiguration : ModuleConfiguration { public string APIEndpoint { get; set; } = "https://modules.battlebit.community/api"; public int CheckDelay { get; set; } = 30; -} +} \ No newline at end of file diff --git a/ModuleUpdates.cs.md b/ModuleUpdates.cs.md new file mode 100644 index 0000000..e69de29 diff --git a/PasswordReservedSlots.cs b/PasswordReservedSlots.cs new file mode 100644 index 0000000..f25cd99 --- /dev/null +++ b/PasswordReservedSlots.cs @@ -0,0 +1,92 @@ +using BBRAPIModules; +using Commands; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace BattleBitBaseModules; + +[RequireModule(typeof(CommandHandler))] +[Module("Add reserved slots by setting a password", "1.0.0")] +public class PasswordReservedSlots : BattleBitModule +{ + public PasswordReservedSlotsConfiguration Configuration { get; set; } = null!; + + [ModuleReference] + public CommandHandler CommandHandler { get; set; } = null!; + + public int getCurrentPlayerCount() + { + return Math.Max(this.Server.CurrentPlayerCount + this.Server.InQueuePlayerCount, this.Server.AllPlayers.Count()); + } + + public void handleSlots() + { + int currentPlayers = this.getCurrentPlayerCount(); + if (this.Server.MaxPlayerCount - this.Configuration.Slots > currentPlayers) + { + this.Server.SetNewPassword(""); + } + else + { + Server.SetNewPassword(this.Configuration.Password); + } + } + + public async Task slotHandler() + { + while (this.IsLoaded && this.Server.IsConnected) + { + this.handleSlots(); + await Task.Delay(this.Configuration.SlotCheckInterval); + } + } + + public override Task OnConnected() + { + _ = Task.Run(slotHandler); + + return Task.CompletedTask; + } + + public override Task OnPlayerConnected(RunnerPlayer player) + { + this.handleSlots(); + + return Task.CompletedTask; + } + + public override Task OnPlayerDisconnected(RunnerPlayer player) + { + this.handleSlots(); + + return Task.CompletedTask; + } + + [CommandCallback("setrspass", Description = "Sets the password for reserved slots", ConsoleCommand = true, Permissions = new[] { "PasswordReservedSlots.SetRSPass" })] + public string SetRSPassCommand(Context context, string password) + { + this.Configuration.Password = password; + this.Configuration.Save(); + this.handleSlots(); + + return "Password set."; + } + + [CommandCallback("setrsslots", Description = "Sets the number of reserved slots", ConsoleCommand = true, Permissions = new[] { "PasswordReservedSlots.SetRSSlots" })] + public string SetRSSlotsCommand(Context context, int slots) + { + this.Configuration.Slots = slots; + this.Configuration.Save(); + this.handleSlots(); + + return $"Slots set to {slots}."; + } +} + +public class PasswordReservedSlotsConfiguration : ModuleConfiguration +{ + public string Password { get; set; } = "changeme"; + public int Slots { get; set; } = 0; + public int SlotCheckInterval { get; set; } = 3000; +} diff --git a/PermissionsCommands.cs b/PermissionsCommands.cs index de3aa38..eb17f54 100644 --- a/PermissionsCommands.cs +++ b/PermissionsCommands.cs @@ -8,95 +8,88 @@ namespace PermissionsManager; [RequireModule(typeof(CommandHandler))] -[Module("Provide addperm and removeperm commands for PlayerPermissions", "1.1.0")] +[Module("Provide addperm and removeperm commands for PlayerPermissions", "1.1.1")] public class PermissionsCommands : BattleBitModule { [ModuleReference] public dynamic? PlayerPermissions { get; set; } + [ModuleReference] public dynamic? GranularPermissions { get; set; } + [ModuleReference] public CommandHandler CommandHandler { get; set; } = null!; public PermissionsCommandsConfiguration Configuration { get; set; } = null!; - public override void OnModulesLoaded() - { + public override void OnModulesLoaded() { this.CommandHandler.Register(this); } - [CommandCallback("addperm", Description = "Adds a permission to a player", Permissions = new[] { "Permissions.Add" })] - public void AddPermissionCommand(RunnerPlayer commandSource, RunnerPlayer player, string permission) + [CommandCallback("addperm", Description = "Adds a permission to a player", Permissions = new[] { "Permissions.Add" }, ConsoleCommand = true)] + public string AddPermissionCommand(Context context, RunnerPlayer player, string permission) { bool success = false; - if (this.PlayerPermissions is not null) - { - if (!Enum.TryParse(permission, out Roles roles)) - { + if (this.PlayerPermissions is not null) { + if (!Enum.TryParse(permission, out Roles roles)) { this.Logger.Error($"Could not parse {permission} to a role"); - } - else - { + } else { this.PlayerPermissions.AddPlayerRoles(player.SteamID, roles); success = true; } } - if (this.GranularPermissions is not null) - { + if (this.GranularPermissions is not null) { this.GranularPermissions.AddPlayerPermission(player.SteamID, permission); success = true; } if (success) { - commandSource.Message($"Added permission {permission} to {player.Name}"); + this.Logger.Info($"Added permission {permission} to {player.Name}"); + return $"Added permission {permission} to {player.Name}"; } else { this.Logger.Error($"Could not add permission {permission} to {player.Name}"); - commandSource.Message($"Could not add permission {permission} to {player.Name}"); + return $"Could not add permission {permission} to {player.Name}"; } } - [CommandCallback("removeperm", Description = "Removes a permission from a player", Permissions = new[] { "Permissions.Remove" })] - public void RemovePermissionCommand(RunnerPlayer commandSource, RunnerPlayer player, string permission) + [CommandCallback("removeperm", Description = "Removes a permission from a player", Permissions = new[] { "Permissions.Remove" }, ConsoleCommand = true)] + public string RemovePermissionCommand(Context context, RunnerPlayer player, string permission) { bool success = false; - if (this.PlayerPermissions is not null) - { - if (!Enum.TryParse(permission, out Roles roles)) - { + if (this.PlayerPermissions is not null) { + if (!Enum.TryParse(permission, out Roles roles)) { this.Logger.Error($"Colud not parse {permission} to a role"); - } - else - { + } else { this.PlayerPermissions.RemovePlayerRoles(player.SteamID, roles); success = true; } } - if (this.GranularPermissions is not null) - { + if (this.GranularPermissions is not null) { this.GranularPermissions.RemovePlayerPermission(player.SteamID, permission); success = true; } if (success) { - commandSource.Message($"Removed permission {permission} from {player.Name}"); + this.Logger.Info($"Removed permission {permission} from {player.Name}"); + return $"Removed permission {permission} from {player.Name}"; } else { this.Logger.Error($"Could not remove permission {permission} from {player.Name}"); - commandSource.Message($"Could not remove permission {permission} from {player.Name}"); + return $"Could not remove permission {permission} from {player.Name}"; } } - [CommandCallback("clearperms", Description = "Clears all permissions and groups from a player", Permissions = new[] { "Permissions.Clear" })] - public void ClearPermissionCommand(RunnerPlayer commandSource, RunnerPlayer player) + [CommandCallback("clearperms", Description = "Clears all permissions and groups from a player", Permissions = new[] { "Permissions.Clear" }, ConsoleCommand = true)] + public string ClearPermissionCommand(Context context, RunnerPlayer player) { if (this.GranularPermissions is not null) { @@ -105,50 +98,44 @@ public void ClearPermissionCommand(RunnerPlayer commandSource, RunnerPlayer play this.GranularPermissions.RemovePlayerGroup(player.SteamID, group); } - foreach (string permission in this.GranularPermissions.GetPlayerPermissions(player.SteamID)) - { + foreach (string permission in this.GranularPermissions.GetPlayerPermissions(player.SteamID)) { this.GranularPermissions.RemovePlayerPermission(player.SteamID, permission); } } - if (this.PlayerPermissions is not null) - { - foreach (Roles role in Enum.GetValues()) - { + if (this.PlayerPermissions is not null) { + foreach (Roles role in Enum.GetValues()) { this.PlayerPermissions.RemovePlayerRoles(player.SteamID, role); } } - commandSource.Message($"Cleared permissions from {player.Name}"); + this.Logger.Info($"Cleared permissions from {player.Name}"); + return $"Cleared permissions from {player.Name}"; } - [CommandCallback("listperms", Description = "Lists player permissions", Permissions = new[] { "Permissions.List" })] - public void ListPermissionCommand(RunnerPlayer commandSource, RunnerPlayer targetPlayer, int page = 1) + [CommandCallback("listperms", Description = "Lists player permissions", Permissions = new[] { "Permissions.List" }, ConsoleCommand = true)] + public string ListPermissionCommand(Context context, RunnerPlayer targetPlayer, int page = 1) { List permissions = new(); - if (this.GranularPermissions is not null) - { + if (this.GranularPermissions is not null) { permissions.AddRange(this.GranularPermissions.GetPlayerPermissions(targetPlayer.SteamID)); - foreach (string group in this.GranularPermissions.GetPlayerGroups(targetPlayer.SteamID)) - { + foreach (string group in this.GranularPermissions.GetPlayerGroups(targetPlayer.SteamID)) { permissions.AddRange(((string[])this.GranularPermissions.GetGroupPermissions(group)).Select(p => $"{p} from {group}")); } } - if (this.PlayerPermissions is not null) - { + if (this.PlayerPermissions is not null) { Roles playerRoles = PlayerPermissions.Configuration.PlayerRoles.GetValueOrDefault(targetPlayer.SteamID); permissions.AddRange(Enum.GetValues().Where(r => (r & playerRoles) > 0).Select(r => r.ToString())); } int pageCount = (int)Math.Ceiling(permissions.Count / (double)this.Configuration.PermissionsPerPage); - commandSource.Message($"{targetPlayer.Name}: {string.Join("\n", permissions.Skip(page * this.Configuration.PermissionsPerPage).Take(this.Configuration.PermissionsPerPage))}{(pageCount > 1 ? $"{Environment.NewLine}Page {page} of {pageCount}{(page == pageCount ? "" : $", use listperms \"{targetPlayer.Name}\" {page + 1} to see more")}" : "")}"); + return $"{targetPlayer.Name}: {string.Join("\n", permissions.Skip(page * this.Configuration.PermissionsPerPage).Take(this.Configuration.PermissionsPerPage))}{(pageCount > 1 ? $"{Environment.NewLine}Page {page} of {pageCount}{(page == pageCount ? "" : $", use listperms \"{targetPlayer.Name}\" {page + 1} to see more")}" : "")}"; } } -public class PermissionsCommandsConfiguration : ModuleConfiguration -{ +public class PermissionsCommandsConfiguration : ModuleConfiguration { public int PermissionsPerPage { get; set; } = 6; -} +} \ No newline at end of file diff --git a/PermissionsCommands.cs.md b/PermissionsCommands.cs.md new file mode 100644 index 0000000..288e29b --- /dev/null +++ b/PermissionsCommands.cs.md @@ -0,0 +1,25 @@ +# 1 Modules in PermissionsCommands.cs + +| Description | Version | +|:--------------------------------------------------------------|:----------| +| Provide addperm and removeperm commands for PlayerPermissions | 1.0.0 | + +## Commands +| Command | Function Name | Description | Allowed Roles | Parameters | Defaults | +|:-----------|:----------------|:-------------------------------------|:-----------------|:--------------------------------------------------------------------------|:-------------------------| +| addperm | void | Adds a permission to a player | Admin | ['RunnerPlayer commandSource', 'RunnerPlayer player', 'Roles permission'] | {} | +| removeperm | void | Removes a permission from a player | Admin | ['RunnerPlayer commandSource', 'RunnerPlayer player', 'Roles permission'] | {} | +| clearperms | void | Removes all permission from a player | Admin | ['RunnerPlayer commandSource', 'RunnerPlayer player'] | {} | +| listperms | void | Lists player permissions | Admin, Moderator | ['RunnerPlayer commandSource', 'RunnerPlayer? targetPlayer = null'] | {'targetPlayer': 'null'} | + +## Public Methods +| Function Name | Parameters | Defaults | +|:------------------------|:--------------------------------------------------------------------------|:-------------------------| +| | | | +| | | | +| | | | +| void | [''] | {} | +| AddPermissionCommand | ['RunnerPlayer commandSource', 'RunnerPlayer player', 'Roles permission'] | {} | +| RemovePermissionCommand | ['RunnerPlayer commandSource', 'RunnerPlayer player', 'Roles permission'] | {} | +| ClearPermissionCommand | ['RunnerPlayer commandSource', 'RunnerPlayer player'] | {} | +| ListPermissionCommand | ['RunnerPlayer commandSource', 'RunnerPlayer? targetPlayer = null'] | {'targetPlayer': 'null'} | \ No newline at end of file diff --git a/PlayerFinder.cs b/PlayerFinder.cs index 843b0d9..00e6ddf 100644 --- a/PlayerFinder.cs +++ b/PlayerFinder.cs @@ -5,64 +5,53 @@ namespace PlayerFinder; [Module("Library functions for finding players by partial names or SteamID", "1.0.0")] -public class PlayerFinder : BattleBitModule -{ - public RunnerPlayer? ByExactName(string exactName, bool caseSensitive) - { +public class PlayerFinder : BattleBitModule { + + public RunnerPlayer? ByExactName(string exactName, bool caseSensitive) { return this.Server.AllPlayers.FirstOrDefault(p => p.Name.Equals(exactName, caseSensitive ? StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase)); } - public RunnerPlayer? ByNamePart(string namePart) - { + public RunnerPlayer? ByNamePart(string namePart) { RunnerPlayer? exactMatch = this.ByExactName(namePart, true); - if (exactMatch != null) - { + if (exactMatch != null) { return exactMatch; } exactMatch = this.ByExactName(namePart, false); - if (exactMatch != null) - { + if (exactMatch != null) { return exactMatch; } RunnerPlayer[] playerList = this.AllByNamePart(namePart); - if (playerList.Length > 1) - { + if (playerList.Length > 1) { throw new ManyPlayersMatchException(playerList); } - if (playerList.Length == 0) - { + if (playerList.Length == 0) { return null; } return playerList[0]; } - public RunnerPlayer? BySteamId(ulong steamId) - { + public RunnerPlayer? BySteamId(ulong steamId) { return this.Server.AllPlayers.FirstOrDefault(p => p.SteamID == steamId); } - public RunnerPlayer[] AllByNamePart(string namePart) - { + public RunnerPlayer[] AllByNamePart(string namePart) { return this.Server.AllPlayers.Where(p => p.Name.ToLower().Contains(namePart.ToLower())).ToArray(); } } -public class ManyPlayersMatchException : Exception -{ +public class ManyPlayersMatchException : Exception { public RunnerPlayer[] Players { get; } - public ManyPlayersMatchException(RunnerPlayer[] players) - { + public ManyPlayersMatchException(RunnerPlayer[] players) { this.Players = players; } - public override string ToString() - { + public override string ToString() { return $"Multiple players match: {string.Join(", ", this.Players.Select(p => p.Name))}"; } } \ No newline at end of file diff --git a/PlayerFinder.cs.md b/PlayerFinder.cs.md new file mode 100644 index 0000000..e69de29 diff --git a/PlayerPermissions.cs b/PlayerPermissions.cs index 63bbd29..161a66b 100644 --- a/PlayerPermissions.cs +++ b/PlayerPermissions.cs @@ -6,30 +6,22 @@ namespace Permissions; [Module("Library for persistent server roles for players", "1.1.0")] -public class PlayerPermissions : BattleBitModule -{ +public class PlayerPermissions : BattleBitModule { public static PlayerPermissionsConfiguration Configuration { get; set; } = null!; - public override Task OnPlayerJoiningToServer(ulong steamID, PlayerJoiningArguments args) - { - if (Configuration.OverrideRoles) - { + public override Task OnPlayerJoiningToServer(ulong steamID, PlayerJoiningArguments args) { + if (Configuration.OverrideRoles) { args.Stats.Roles = this.GetPlayerRoles(steamID); - } - else - { + } else { args.Stats.Roles |= this.GetPlayerRoles(steamID); } return Task.CompletedTask; } - public override Task OnPlayerConnected(RunnerPlayer player) - { - lock (Configuration.PlayerRoles) - { - if (!Configuration.PlayerRoles.ContainsKey(player.SteamID)) - { + public override Task OnPlayerConnected(RunnerPlayer player) { + lock (Configuration.PlayerRoles) { + if (!Configuration.PlayerRoles.ContainsKey(player.SteamID)) { Configuration.PlayerRoles.Add(player.SteamID, Roles.None); } } @@ -37,17 +29,13 @@ public override Task OnPlayerConnected(RunnerPlayer player) return Task.CompletedTask; } - public bool HasPlayerRole(ulong steamID, Roles role) - { + public bool HasPlayerRole(ulong steamID, Roles role) { return (this.GetPlayerRoles(steamID) & role) == role; } - public Roles GetPlayerRoles(ulong steamID) - { - lock (Configuration.PlayerRoles) - { - if (Configuration.PlayerRoles.ContainsKey(steamID)) - { + public Roles GetPlayerRoles(ulong steamID) { + lock (Configuration.PlayerRoles) { + if (Configuration.PlayerRoles.ContainsKey(steamID)) { return Configuration.PlayerRoles[steamID]; } } @@ -55,16 +43,11 @@ public Roles GetPlayerRoles(ulong steamID) return Roles.None; } - public void SetPlayerRoles(ulong steamID, Roles roles) - { - lock (Configuration.PlayerRoles) - { - if (Configuration.PlayerRoles.ContainsKey(steamID)) - { + public void SetPlayerRoles(ulong steamID, Roles roles) { + lock (Configuration.PlayerRoles) { + if (Configuration.PlayerRoles.ContainsKey(steamID)) { Configuration.PlayerRoles[steamID] = roles; - } - else - { + } else { Configuration.PlayerRoles.Add(steamID, roles); } } @@ -72,19 +55,16 @@ public void SetPlayerRoles(ulong steamID, Roles roles) Configuration.Save(); } - public void AddPlayerRoles(ulong steamID, Roles role) - { + public void AddPlayerRoles(ulong steamID, Roles role) { this.SetPlayerRoles(steamID, this.GetPlayerRoles(steamID) | role); } - public void RemovePlayerRoles(ulong steamID, Roles role) - { + public void RemovePlayerRoles(ulong steamID, Roles role) { this.SetPlayerRoles(steamID, this.GetPlayerRoles(steamID) & ~role); } } -public class PlayerPermissionsConfiguration : ModuleConfiguration -{ +public class PlayerPermissionsConfiguration : ModuleConfiguration { public bool OverrideRoles { get; set; } = true; public Dictionary PlayerRoles { get; set; } = new(); } \ No newline at end of file diff --git a/PlayerPermissions.cs.md b/PlayerPermissions.cs.md new file mode 100644 index 0000000..e69de29 diff --git a/PlayerRolesFromGranularPermissions.cs b/PlayerRolesFromGranularPermissions.cs index f99d34a..685af95 100644 --- a/PlayerRolesFromGranularPermissions.cs +++ b/PlayerRolesFromGranularPermissions.cs @@ -9,41 +9,41 @@ namespace Permissions; [Module("Provides player roles based on granular permissions", "1.0.0")] public class PlayerRolesFromGranularPermissions : BattleBitModule { - [ModuleReference] - public GranularPermissions GranularPermissions { get; set; } = null!; + [ModuleReference] + public GranularPermissions GranularPermissions { get; set; } = null!; - public PlayerRolesConfiguration Configuration { get; set; } = null!; + public PlayerRolesConfiguration Configuration { get; set; } = null!; - public override Task OnPlayerJoiningToServer(ulong steamID, PlayerJoiningArguments args) - { - foreach (string permission in Configuration.PermissionRoles.Keys) - { - if (!this.GranularPermissions.HasPermission(steamID, permission)) - { - continue; - } - if (Configuration.AppendRoles) - { - args.Stats.Roles |= Configuration.PermissionRoles[permission]; - } - else - { - args.Stats.Roles = Configuration.PermissionRoles[permission]; - } - } + public override Task OnPlayerJoiningToServer(ulong steamID, PlayerJoiningArguments args) + { + foreach (string permission in Configuration.PermissionRoles.Keys) + { + if (!this.GranularPermissions.HasPermission(steamID, permission)) + { + continue; + } + if (Configuration.AppendRoles) + { + args.Stats.Roles |= Configuration.PermissionRoles[permission]; + } + else + { + args.Stats.Roles = Configuration.PermissionRoles[permission]; + } + } - return Task.CompletedTask; - } + return Task.CompletedTask; + } } public class PlayerRolesConfiguration : ModuleConfiguration { - public Dictionary PermissionRoles { get; set; } = new() - { - { "Role.Admin", Roles.Admin }, - { "Role.Moderator", Roles.Moderator }, - { "Role.Modmin", Roles.Admin | Roles.Moderator } - }; + public Dictionary PermissionRoles { get; set; } = new() + { + { "Role.Admin", Roles.Admin }, + { "Role.Moderator", Roles.Moderator }, + { "Role.Modmin", Roles.Admin | Roles.Moderator } + }; - public bool AppendRoles { get; set; } = false; -} + public bool AppendRoles { get; set; } = false; +} \ No newline at end of file diff --git a/ProfanityFilter.cs b/ProfanityFilter.cs index f7b6e3c..288d902 100644 --- a/ProfanityFilter.cs +++ b/ProfanityFilter.cs @@ -5,42 +5,33 @@ namespace BattleBitBaseModules; -public class DFAState -{ +public class DFAState { public Dictionary Transitions { get; set; } public bool IsEndOfWord { get; set; } - public DFAState() - { + public DFAState() { Transitions = new Dictionary(); IsEndOfWord = false; } } -public class ProfanityDFAFilter -{ +public class ProfanityDFAFilter { private DFAState root; - public ProfanityDFAFilter() - { + public ProfanityDFAFilter() { root = new DFAState(); } - public void LoadDictionary(string[] words) - { + public void LoadDictionary(string[] words) { root = BuildDFA(words); } - private DFAState BuildDFA(string[] words) - { + private DFAState BuildDFA(string[] words) { DFAState root = new DFAState(); - foreach (string word in words) - { + foreach (string word in words) { DFAState currentState = root; - foreach (char c in word) - { - if (!currentState.Transitions.ContainsKey(c)) - { + foreach (char c in word) { + if (!currentState.Transitions.ContainsKey(c)) { currentState.Transitions.Add(c, new DFAState()); } currentState = currentState.Transitions[c]; @@ -50,19 +41,15 @@ private DFAState BuildDFA(string[] words) return root; } - public bool ContainsProfanity(string text) - { + public bool ContainsProfanity(string text) { DFAState currentState = root; - foreach (char c in text) - { - if (!currentState.Transitions.ContainsKey(c)) - { + foreach (char c in text) { + if (!currentState.Transitions.ContainsKey(c)) { currentState = root; continue; } currentState = currentState.Transitions[c]; - if (currentState.IsEndOfWord) - { + if (currentState.IsEndOfWord) { return true; } } @@ -71,21 +58,17 @@ public bool ContainsProfanity(string text) } [Module("Bad word filter to remove chat messages", "1.0.0")] -public class ProfanityFilter : BattleBitModule -{ +public class ProfanityFilter : BattleBitModule { public static ProfanityFilterConfiguration Configuration { get; set; } = null!; public static ProfanityDFAFilter filter = new(); - public override void OnModulesLoaded() - { + public override void OnModulesLoaded() { // Load dictionary filter.LoadDictionary(Configuration.Profanity.ToArray()); } - public override Task OnPlayerTypedMessage(RunnerPlayer player, ChatChannel channel, string message) - { - if (filter.ContainsProfanity(message)) - { + public override Task OnPlayerTypedMessage(RunnerPlayer player, ChatChannel channel, string message) { + if (filter.ContainsProfanity(message)) { player.Message(Configuration.Message, Configuration.MessageTimeout); return Task.FromResult(false); } @@ -94,9 +77,8 @@ public override Task OnPlayerTypedMessage(RunnerPlayer player, ChatChannel } } -public class ProfanityFilterConfiguration : ModuleConfiguration -{ +public class ProfanityFilterConfiguration : ModuleConfiguration { public List Profanity { get; set; } = new(); public float MessageTimeout { get; set; } = 10f; public string Message { get; set; } = "Please do not use profanity in chat."; -} +} \ No newline at end of file diff --git a/ProfanityFilter.cs.md b/ProfanityFilter.cs.md new file mode 100644 index 0000000..e69de29 diff --git a/RichText.cs b/RichText.cs index ff60bd0..3b950a9 100644 --- a/RichText.cs +++ b/RichText.cs @@ -1,30 +1,25 @@ using BBRAPIModules; -using System; using System.Linq; using System.Reflection; namespace BattleBitBaseModules; [Module("Library for easily using Rich Text", "1.1.0")] -public class RichText : BattleBitModule -{ +public class RichText : BattleBitModule { + public string NewLine() => "
"; - public string Color(string? color = null) - { - if (color is null) - { + public string Color(string? color = null) { + if (color is null) { return Colors.White; } return $"<{color}>"; } - public string FromColorName(string colorName) - { + public string FromColorName(string colorName) { FieldInfo? color = typeof(Colors).GetFields().FirstOrDefault(x => x.Name.ToLower() == colorName.ToLower()); - if (color == null) - { + if (color == null) { this.Logger.Error($"No color found with name {colorName}"); return "<#FFFFFF>"; } @@ -32,21 +27,17 @@ public string FromColorName(string colorName) return color.GetValue(null)!.ToString()!; } - public string Align(string? alignment = null) - { - if (alignment is null) - { + public string Align(string? alignment = null) { + if (alignment is null) { return Alignments.None; } return $""; } - public string FromAlignmentName(string alignmentName) - { + public string FromAlignmentName(string alignmentName) { FieldInfo? alignment = typeof(Alignments).GetFields().FirstOrDefault(x => x.Name.ToLower() == alignmentName.ToLower()); - if (alignment == null) - { + if (alignment == null) { this.Logger.Error($"No alignment found with name {alignmentName}"); return Alignments.None; } @@ -54,10 +45,8 @@ public string FromAlignmentName(string alignmentName) return alignment.GetValue(null)!.ToString()!; } - public string Alpha(string? alpha = null) - { - if (alpha is null) - { + public string Alpha(string? alpha = null) { + if (alpha is null) { return ""; } @@ -65,22 +54,19 @@ public string Alpha(string? alpha = null) } public string CharacterSpacing(int pixels) => $""; + public string CharacterSpacing(float em) => $""; - public string Font(string? fontName) - { - if (fontName is null) - { + public string Font(string? fontName) { + if (fontName is null) { return ""; } return $""; } - public string Indent(int? percentage = null) - { - if (percentage is null) - { + public string Indent(int? percentage = null) { + if (percentage is null) { return ""; } @@ -92,14 +78,19 @@ public string Indent(int? percentage = null) public string LineIndentation(int percentage) => $""; public string Lowercase(bool lowercase) => lowercase ? "" : ""; + public string Uppercase(bool uppercase) => uppercase ? "" : ""; + public string Smallcaps(bool smallcaps) => smallcaps ? "" : ""; public string Margin(int pixels) => $""; + public string Margin(float em) => $""; public string Monospacing(int pixels) => $""; + public string Monospacing(float em) => $""; + public string Monospacing() => ""; public string Noparse(bool noparse) => noparse ? "" : ""; @@ -109,24 +100,22 @@ public string Indent(int? percentage = null) public string HorizontalPosition(int percentage) => $""; public string HorizontalSpace(int pixels) => $""; + public string HorizontalSpace(float em) => $""; public string VerticalOffset(float em) => $""; public string TextWidth(int percentage) => $""; - public string Sprite(string spriteName, string? color = null) - { + public string Sprite(string spriteName, string? color = null) { FieldInfo? sprite = typeof(Sprites).GetFields().FirstOrDefault(x => x.Name.ToLower() == spriteName.ToLower()); - if (sprite == null) - { + if (sprite == null) { this.Logger.Error($"No sprite found with name {spriteName}"); return string.Empty; } string spriteText = sprite.GetValue(null)!.ToString()!; - if (!string.IsNullOrEmpty(color)) - { + if (!string.IsNullOrEmpty(color)) { spriteText = spriteText.Replace(" superscript ? "" : ""; } -public static class Alignments -{ +public static class Alignments { public static readonly string Left = ""; public static readonly string Center = ""; public static readonly string Right = ""; public static readonly string None = ""; } -public static class Colors -{ +public static class Colors { + public static readonly string None = ""; + public static readonly string Reset = ""; public static readonly string Black = ""; public static readonly string Blue = ""; public static readonly string Brown = ""; @@ -303,8 +292,7 @@ public static class Colors public static readonly string Gainsboro = "<#DCDCDC>"; } -public static class Sprites -{ +public static class Sprites { public static readonly string Moderator = ""; public static readonly string Patreon = ""; public static readonly string Creator = ""; diff --git a/RichText.cs.md b/RichText.cs.md new file mode 100644 index 0000000..e69de29 diff --git a/Rotation.cs b/Rotation.cs index d741e03..93c13ab 100644 --- a/Rotation.cs +++ b/Rotation.cs @@ -4,12 +4,10 @@ namespace BattleBitBaseModules; [Module("Configure the map and game mode rotation of the server", "1.0.0")] -public class Rotation : BattleBitModule -{ +public class Rotation : BattleBitModule { public RotationConfiguration Configuration { get; set; } = null!; - public override Task OnConnected() - { + public override Task OnConnected() { this.Logger.Info($"Setting up game mode rotation to {string.Join(", ", this.Configuration.GameModes)}"); this.Server.GamemodeRotation.SetRotation(this.Configuration.GameModes); this.Logger.Debug($"New game mode rotation: {string.Join(", ", this.Server.GamemodeRotation.GetGamemodeRotation())}"); @@ -21,8 +19,8 @@ public override Task OnConnected() } } -public class RotationConfiguration : ModuleConfiguration -{ +public class RotationConfiguration : ModuleConfiguration { + public string[] GameModes { get; set; } = new[] { "TDM", @@ -44,6 +42,7 @@ public class RotationConfiguration : ModuleConfiguration "VoxelTrench", "CaptureTheFlag" }; + public string[] Maps { get; set; } = new[] { "Azagor", @@ -66,4 +65,4 @@ public class RotationConfiguration : ModuleConfiguration "Wakistan", "WineParadise" }; -} +} \ No newline at end of file diff --git a/Rotation.cs.md b/Rotation.cs.md new file mode 100644 index 0000000..e69de29 diff --git a/SpectateControl.cs b/SpectateControl.cs index cbd8ca5..1859f4a 100644 --- a/SpectateControl.cs +++ b/SpectateControl.cs @@ -8,8 +8,7 @@ namespace BattleBitBaseModules; [Module("Allow only specific Roles to spectate", "1.1.1")] -public class SpectateControl : BattleBitModule -{ +public class SpectateControl : BattleBitModule { public static SpectateControlConfiguration Configuration { get; set; } = null!; [ModuleReference] @@ -18,12 +17,9 @@ public class SpectateControl : BattleBitModule [ModuleReference] public dynamic? PlayerPermissions { get; set; } - public override void OnModulesLoaded() - { - if (this.GranularPermissions is null) - { - if (this.PlayerPermissions is null) - { + public override void OnModulesLoaded() { + if (this.GranularPermissions is null) { + if (this.PlayerPermissions is null) { this.Logger.Error("GranularPermissions or PlayerPermissions not found, unloading module"); this.Unload(); return; @@ -31,39 +27,28 @@ public override void OnModulesLoaded() this.Logger.Info("PlayerPermissions found, using roles"); - foreach (string permission in Configuration.SpectatorPermissions) - { - if (!Enum.TryParse(permission, out Roles role)) - { + foreach (string permission in Configuration.SpectatorPermissions) { + if (!Enum.TryParse(permission, out Roles role)) { this.Logger.Error($"Invalid role {permission}"); } } } } - public override Task OnPlayerConnected(RunnerPlayer player) - { - if (this.GranularPermissions is null) - { - foreach (string permission in Configuration.SpectatorPermissions) - { - if (!Enum.TryParse(permission, out Roles role)) - { + public override Task OnPlayerConnected(RunnerPlayer player) { + if (this.GranularPermissions is null) { + foreach (string permission in Configuration.SpectatorPermissions) { + if (!Enum.TryParse(permission, out Roles role)) { this.Logger.Error($"Invalid role {permission}"); - } - else - { - if (this.PlayerPermissions!.HasPlayerRole(player.SteamID, role)) - { + } else { + if (this.PlayerPermissions!.HasPlayerRole(player.SteamID, role)) { player.Modifications.CanSpectate = true; this.Logger.Info($"Player {player.Name} ({player.SteamID}) has role {role} and can spectate"); return Task.CompletedTask; } } } - } - else - { + } else { player.Modifications.CanSpectate = Configuration.SpectatorPermissions.Any(p => this.GranularPermissions.HasPermission(player.SteamID, p)); } @@ -71,7 +56,6 @@ public override Task OnPlayerConnected(RunnerPlayer player) } } -public class SpectateControlConfiguration : ModuleConfiguration -{ +public class SpectateControlConfiguration : ModuleConfiguration { public List SpectatorPermissions { get; set; } = new(); -} +} \ No newline at end of file diff --git a/SpectateControl.cs.md b/SpectateControl.cs.md new file mode 100644 index 0000000..e69de29 diff --git a/VacLimiter.cs b/VacLimiter.cs index 873fa89..55ee47d 100644 --- a/VacLimiter.cs +++ b/VacLimiter.cs @@ -11,8 +11,7 @@ namespace VacLimiter; [Module("Kick users with VAC bans", "1.1.0")] -public class VacLimiter : BattleBitModule -{ +public class VacLimiter : BattleBitModule { public static VacLimiterConfiguration Configuration { get; set; } = null!; public static VacLimiterCache Cache { get; set; } = null!; @@ -25,19 +24,15 @@ public class VacLimiter : BattleBitModule private static bool isLoaded = false; private static HttpClient httpClient = null!; - public override void OnModulesLoaded() - { - if (string.IsNullOrWhiteSpace(Configuration.SteamAPIKey)) - { + public override void OnModulesLoaded() { + if (string.IsNullOrWhiteSpace(Configuration.SteamAPIKey)) { this.Logger.Error("Steam API token is not set. Please set it in the configuration file."); this.Unload(); return; } - if (httpClient == null) - { - httpClient = new() - { + if (httpClient == null) { + httpClient = new() { Timeout = TimeSpan.FromSeconds(10) }; httpClient.DefaultRequestHeaders.Add("User-Agent", "BattleBit"); @@ -49,22 +44,18 @@ public override void OnModulesLoaded() isLoaded = true; } - public override void OnModuleUnloading() - { + public override void OnModuleUnloading() { isLoaded = false; } - private static async Task playerChecker() - { + private static async Task playerChecker() { ILog logger = LogManager.GetLogger(typeof(VacLimiter).Name); - while (isLoaded) - { + while (isLoaded) { CacheRequest[] playerBatch = playersToCheck.ToArray(); playersToCheck.Clear(); - if (playerBatch.Length == 0) - { + if (playerBatch.Length == 0) { await Task.Delay(Configuration.BatchDelay); continue; } @@ -74,21 +65,16 @@ private static async Task playerChecker() HttpResponseMessage? response = null; - do - { - try - { + do { + try { response = await httpClient.GetAsync("https://api.steampowered.com/ISteamUser/GetPlayerBans/v1/?key=" + Configuration.SteamAPIKey + "&steamids=" + string.Join(",", playerBatch.Select(x => x.Player.SteamID))); - if (!response.IsSuccessStatusCode) - { + if (!response.IsSuccessStatusCode) { logger.Error($"Failed to get player bans: {response.StatusCode} {response.ReasonPhrase}"); await Task.Delay(Configuration.RetryDelay); continue; } - } - catch (Exception e) - { + } catch (Exception e) { logger.Error($"Failed to get player bans: {e.Message}"); await Task.Delay(Configuration.RetryDelay); continue; @@ -96,28 +82,22 @@ private static async Task playerChecker() } while (response == null || !response.IsSuccessStatusCode); PlayerBanResponseModel? playerBanResponse = null; - try - { + try { playerBanResponse = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync()); - } - catch (Exception e) - { + } catch (Exception e) { logger.Error($"Failed to parse player bans: {e.Message}"); await Task.Delay(Configuration.RetryDelay); continue; } - if (playerBanResponse == null) - { + if (playerBanResponse == null) { logger.Error($"Failed to parse player bans."); await Task.Delay(Configuration.RetryDelay); continue; } - foreach (PlayerBansModel playerBans in playerBanResponse.players) - { - if (!ulong.TryParse(playerBans.SteamId, out ulong steamId)) - { + foreach (PlayerBansModel playerBans in playerBanResponse.players) { + if (!ulong.TryParse(playerBans.SteamId, out ulong steamId)) { logger.Error($"Failed to parse Steam ID {playerBans.SteamId}."); continue; } @@ -127,8 +107,7 @@ private static async Task playerChecker() logger.Debug($"Player {steamId} with {playerBans.NumberOfVACBans} VAC bans and {playerBans.NumberOfGameBans} game bans has been cached."); - foreach (CacheRequest request in playerBatch.Where(x => x.Player.SteamID == steamId)) - { + foreach (CacheRequest request in playerBatch.Where(x => x.Player.SteamID == steamId)) { request.VacLimiter.CheckBans(request.Player, playerBans); } } @@ -138,24 +117,18 @@ private static async Task playerChecker() } } - public override Task OnConnected() - { + public override Task OnConnected() { this.Logger.Info($"Setting up VAC limiter with age threshold of {this.ServerConfiguration.VACAgeThreshold} days which will {(this.ServerConfiguration.Kick ? "kick" : "")}{(this.ServerConfiguration.Kick && this.ServerConfiguration.Ban ? " and " : "")}{(this.ServerConfiguration.Ban ? "ban" : "")} players with VAC bans."); return Task.CompletedTask; } - public override Task OnPlayerConnected(RunnerPlayer player) - { - if (Cache.LastCached.TryGetValue(player.SteamID, out DateTime lastCached) && lastCached.AddDays(this.ServerConfiguration.CacheAge) > DateTime.UtcNow && Cache.PlayerBans.TryGetValue(player.SteamID, out PlayerBansModel? playerBans)) - { + public override Task OnPlayerConnected(RunnerPlayer player) { + if (Cache.LastCached.TryGetValue(player.SteamID, out DateTime lastCached) && lastCached.AddDays(this.ServerConfiguration.CacheAge) > DateTime.UtcNow && Cache.PlayerBans.TryGetValue(player.SteamID, out PlayerBansModel? playerBans)) { this.Logger.Debug($"Player {player.Name} ({player.SteamID}) is in cache. Last cached {lastCached}."); this.CheckBans(player, playerBans); - } - else - { - if (playersToCheck.Any(p => p.Player.SteamID == player.SteamID)) - { + } else { + if (playersToCheck.Any(p => p.Player.SteamID == player.SteamID)) { this.Logger.Debug($"Player {player.Name} ({player.SteamID}) is already queued for VAC ban check."); return Task.CompletedTask; } @@ -167,55 +140,46 @@ public override Task OnPlayerConnected(RunnerPlayer player) return Task.CompletedTask; } - private void CheckBans(RunnerPlayer player, PlayerBansModel playerBans) - { - if (!this.Server.IsConnected || !this.IsLoaded) - { + private void CheckBans(RunnerPlayer player, PlayerBansModel playerBans) { + if (!this.Server.IsConnected || !this.IsLoaded) { this.Logger?.Info($"Server is not connected or module is not loaded anymore. Skipping VAC ban check for player {player.Name} ({player.SteamID})."); return; } - if (!playerBans.VACBanned || playerBans.NumberOfVACBans == 0) - { + if (!playerBans.VACBanned || playerBans.NumberOfVACBans == 0) { this.Logger.Info($"Player {player.Name} ({player.SteamID}) has no VAC ban record."); return; } - if (this.GranularPermissions is not null && ServerConfiguration.IgnoredPermissions?.Any(p => this.GranularPermissions.HasPermission(player.SteamID, p)) == true) - { + if (this.GranularPermissions is not null && ServerConfiguration.IgnoredPermissions?.Any(p => this.GranularPermissions.HasPermission(player.SteamID, p)) == true) { this.Logger.Info($"Player {player.Name} ({player.SteamID}) has an ignored permission, skipping..."); return; } - if (playerBans.DaysSinceLastBan >= this.ServerConfiguration.VACAgeThreshold) - { + if (playerBans.DaysSinceLastBan >= this.ServerConfiguration.VACAgeThreshold) { this.Logger.Info($"Player {player.Name} ({player.SteamID}) has a VAC ban from {playerBans.DaysSinceLastBan} days ago on record, but it is older than the threshold of {this.ServerConfiguration.VACAgeThreshold} days."); return; } this.Logger.Info($"Player {player.Name} ({player.SteamID}) has a VAC ban from {playerBans.DaysSinceLastBan} days ago on record. {(this.ServerConfiguration.Kick ? "Kicking" : "")}{(this.ServerConfiguration.Kick && this.ServerConfiguration.Ban ? " and " : "")}{(this.ServerConfiguration.Ban ? "banning" : "")} player."); - if (this.ServerConfiguration.Kick) - { + if (this.ServerConfiguration.Kick) { this.Server.Kick(player, string.Format(this.ServerConfiguration.KickMessage, playerBans.DaysSinceLastBan, this.ServerConfiguration.VACAgeThreshold)); } - if (this.ServerConfiguration.Ban) - { + if (this.ServerConfiguration.Ban) { this.Server.ExecuteCommand($"ban {player.SteamID}"); } } } -public class VacLimiterConfiguration : ModuleConfiguration -{ +public class VacLimiterConfiguration : ModuleConfiguration { public string SteamAPIKey { get; set; } = string.Empty; public int RetryDelay { get; set; } = 5000; public int BatchDelay { get; set; } = 5000; } -public class VacLimiterServerConfiguration : ModuleConfiguration -{ +public class VacLimiterServerConfiguration : ModuleConfiguration { public int VACAgeThreshold { get; set; } = 365; public bool Kick { get; set; } = true; public bool Ban { get; set; } = false; @@ -224,8 +188,7 @@ public class VacLimiterServerConfiguration : ModuleConfiguration public string[] IgnoredPermissions { get; set; } = Array.Empty(); } -public class VacLimiterCache : ModuleConfiguration -{ +public class VacLimiterCache : ModuleConfiguration { public Dictionary LastCached { get; set; } = new(); public Dictionary PlayerBans { get; set; } = new(); } @@ -233,4 +196,4 @@ public class VacLimiterCache : ModuleConfiguration record CacheRequest(VacLimiter VacLimiter, RunnerPlayer Player); public record PlayerBansModel(string SteamId, bool CommunityBanned, bool VACBanned, uint NumberOfVACBans, uint DaysSinceLastBan, uint NumberOfGameBans, string EconomyBan); -public record PlayerBanResponseModel(PlayerBansModel[] players); +public record PlayerBanResponseModel(PlayerBansModel[] players); \ No newline at end of file diff --git a/Voting.cs b/Voting.cs index 068b296..7ca46d5 100644 --- a/Voting.cs +++ b/Voting.cs @@ -9,10 +9,9 @@ namespace BattleBitBaseModules; -[Module("Simple chat voting system", "1.1.0")] +[Module("Simple chat voting system", "1.1.1")] [RequireModule(typeof(CommandHandler))] -public class Voting : BattleBitModule -{ +public class Voting : BattleBitModule { private bool activeVote = false; private string voteText = ""; private string[] voteOptions = new string[0]; @@ -27,18 +26,16 @@ public class Voting : BattleBitModule [ModuleReference] public CommandHandler CommandHandler { get; set; } = null!; - public override void OnModulesLoaded() - { + public override void OnModulesLoaded() { this.CommandHandler.Register(this); } - [CommandCallback("vote", Description = "Votes for an option", Permissions = new[] { "Vote.Vote" })] - public void StartVoteCommand(RunnerPlayer commandSource, string text, string options) + [CommandCallback("vote", Description = "Votes for an option", Permissions = new[] { "Vote.Vote" }, ConsoleCommand = true)] + public string? StartVoteCommand(Context context, string text, string options) { if (this.activeVote) { - commandSource.Message("There is already an active vote."); - return; + return "There is already an active vote."; } this.activeVote = true; @@ -47,9 +44,8 @@ public void StartVoteCommand(RunnerPlayer commandSource, string text, string opt if (this.voteOptions.Length >= 10) { - commandSource.Message("You can only have up to 9 options."); this.activeVote = false; - return; + return "You can only have up to 9 options."; } this.votes.Clear(); @@ -58,51 +54,45 @@ public void StartVoteCommand(RunnerPlayer commandSource, string text, string opt this.Server.SayToAllChat($"{this.RichText?.Size(125)}A vote has been started!"); StringBuilder messageText = new($"{this.RichText?.Size(125)}{this.voteText}{this.RichText?.Size(100)}{Environment.NewLine}"); - for (int i = 0; i < this.voteOptions.Length; i++) - { + for (int i = 0; i < this.voteOptions.Length; i++) { messageText.AppendLine($"Type {i + 1} in chat for {this.RichText?.FromColorName("yellow")}{this.voteOptions[i]}{this.RichText?.Color()}"); } messageText.AppendLine($"{this.RichText?.Size(125)}You have {this.Configuration.VoteDuration} seconds to vote."); - foreach (RunnerPlayer player in this.Server.AllPlayers) - { + foreach (RunnerPlayer player in this.Server.AllPlayers) { player.Message($"{this.RichText?.Size(125)}{messageText}", this.Configuration.VoteDuration); } this.Server.SayToAllChat(messageText.ToString()); Task.Run(voteHandler); + + return null; } - private void voteHandler() - { - while (this.IsLoaded && this.Server?.IsConnected == true && this.activeVote) - { - if (DateTime.Now > this.endOfVote) - { + private void voteHandler() { + while (this.IsLoaded && this.Server?.IsConnected == true && this.activeVote) { + if (DateTime.Now > this.endOfVote) { break; } Task.Delay(1000).Wait(); } - if (!this.IsLoaded || this.Server?.IsConnected != true || !this.activeVote) - { + if (!this.IsLoaded || this.Server?.IsConnected != true || !this.activeVote) { return; } this.activeVote = false; - if (this.votes.Count == 0) - { + if (this.votes.Count == 0) { this.Server.SayToAllChat($"{this.RichText?.Size(125)}The vote has ended!{Environment.NewLine}Nobody voted."); return; } int[] voteCounts = new int[this.voteOptions.Length]; - foreach (int vote in this.votes.Values) - { + foreach (int vote in this.votes.Values) { voteCounts[vote - 1]++; } @@ -110,39 +100,33 @@ private void voteHandler() int maxVoteIndex = voteCounts.ToList().IndexOf(maxVotes); this.Server.SayToAllChat($"{this.RichText?.Size(125)}The vote has ended!{Environment.NewLine}{this.RichText?.FromColorName("yellow")}{this.voteOptions[maxVoteIndex]}{this.RichText?.Color()} won with {maxVotes} votes."); + this.Logger.Info($"The vote has ended! {this.voteOptions[maxVoteIndex]} won with {maxVotes} votes."); } - public override async Task OnPlayerTypedMessage(RunnerPlayer player, ChatChannel channel, string msg) - { - if (!this.activeVote) - { + public override async Task OnPlayerTypedMessage(RunnerPlayer player, ChatChannel channel, string msg) { + if (!this.activeVote) { return true; } msg = new string(msg.Where(c => char.IsDigit(c)).Distinct().ToArray()); - if (msg.Length == 0) - { + if (msg.Length == 0) { return true; } - if (msg.Length > 1) - { + if (msg.Length > 1) { player.SayToChat("Could not find a unique vote option in your message."); return true; } - if (!int.TryParse(msg, out int vote)) - { + if (!int.TryParse(msg, out int vote)) { return true; } - if (vote < 1 || vote > this.voteOptions.Length) - { + if (vote < 1 || vote > this.voteOptions.Length) { return true; } - if (this.votes.ContainsKey(player.SteamID)) - { + if (this.votes.ContainsKey(player.SteamID)) { this.votes.Remove(player.SteamID); } @@ -156,7 +140,6 @@ public override async Task OnPlayerTypedMessage(RunnerPlayer player, ChatC } } -public class VoteConfiguration : ModuleConfiguration -{ +public class VoteConfiguration : ModuleConfiguration { public int VoteDuration { get; set; } = 60; -} +} \ No newline at end of file diff --git a/Voting.cs.md b/Voting.cs.md new file mode 100644 index 0000000..626d7b5 --- /dev/null +++ b/Voting.cs.md @@ -0,0 +1,23 @@ +# 1 Modules in Voting.cs + +| Description | Version | +|:--------------------------|:----------| +| Simple chat voting system | 1.0.0 | + +## Commands +| Command | Function Name | Description | Allowed Roles | Parameters | Defaults | +|:----------|:----------------|:--------------------|:----------------|:----------------------------------------------------------------|:-----------| +| vote | void | Votes for an option | Moderator | ['RunnerPlayer commandSource', 'string text', 'string options'] | {} | + +## Public Methods +| Function Name | Parameters | Defaults | +|:-----------------|:----------------------------------------------------------------|:-----------| +| | | | +| | | | +| | | | +| | | | +| void | [''] | {} | +| StartVoteCommand | ['RunnerPlayer commandSource', 'string text', 'string options'] | {} | +| async | ['RunnerPlayer player', 'ChatChannel channel', 'string msg'] | {} | +| | | | +| | | | \ No newline at end of file