diff --git a/PERMISSIONS.md b/PERMISSIONS.md index 71e158c6..680dc6b4 100644 --- a/PERMISSIONS.md +++ b/PERMISSIONS.md @@ -7,11 +7,13 @@ 1. __\*__ The `Default (allowed/denied)` values are based on the assumption that you use the default permissions file included with the menu, and you've granted yourself no special permissions or added yourself to any of the admin/moderator groups. If you **DON'T** use the default permissions file, then every option will be **DENIED** by default. 2. __\*\*__ These options are only allowed by default for the "Moderators" / "Admins" groups in the provided permissions file with this resource. 3. __\*\*\*__ When spawning a car using the `Spawn By Name` button, it will always check to see if you have permission for that specific vehicle's class. eg: If you don't have permission to spawn cars from the `Super` class, trying to spawn an `adder` using the `Spawn By Name` button won't work. +4. __\*\*\*\*__ Only admins are allowed to use this by default. |Permission|Description|Default[\*](#global-permissions)| |:-|:-|:-| |`vMenu.Everything`|Grants access to everything, not recommended to give this out.|Denied| |`vMenu.DontKickMe`|Prevents this player from being kicked.|Denied| +|`vMenu.DontBanMe`|Prevents this player from being banned.|Denied| |`vMenu.NoClip`|Allows the user to use the NoClip feature.|Allowed| ## Online Players @@ -23,9 +25,11 @@ |`vMenu.OnlinePlayers.Teleport`|Allows you to teleport to another player.|Allowed| |`vMenu.OnlinePlayers.Waypoint`|Allows you to set a waypoint to another player.|Allowed| |`vMenu.OnlinePlayers.Spectate`|Allows you to spectate another player.|Allowed| -|`vMenu.OnlinePlayers.Summon`|Allows you to summon/teleport another player to you.|Denied| -|`vMenu.OnlinePlayers.Kill`|Allows you to kill another player by pressing a button. Dam, you're very cruel.|Denied| -|`vMenu.OnlinePlayers.Kick`|Allows you to kick another player from the server.|Denied| +|`vMenu.OnlinePlayers.Summon`|Allows you to summon/teleport another player to you. (Default: moderators only)|Denied| +|`vMenu.OnlinePlayers.Kill`|Allows you to kill another player by pressing a button. Dam, you're very cruel. (Default: moderators only)|Denied| +|`vMenu.OnlinePlayers.Kick`|Allows you to kick another player from the server. (Default: moderators only)|Denied| +|`vMenu.OnlinePlayers.TempBan`|Allows you to ban the player from the server for a custom amount of time, max 1 month. (Default: moderators only)|Denied\*| +|`vMenu.OnlinePlayers.PermBan`|Allows you to ban the player from the server forever. (Default: admin only)|Denied\*\*\*\*| ## Player Options diff --git a/vMenu/CommonFunctions.cs b/vMenu/CommonFunctions.cs index 414371de..a4873ddb 100644 --- a/vMenu/CommonFunctions.cs +++ b/vMenu/CommonFunctions.cs @@ -373,6 +373,48 @@ public async void KickPlayer(Player player, bool askUserForReason, string provid } #endregion + #region (Temp) Ban Player + /// + /// Bans the specified player. + /// + /// Player to ban. + /// Ban forever or ban temporarily. + public async void BanPlayer(Player player, bool forever) + { + string banReason = await GetUserInput("Enter Ban Reason", "Banned by staff", 200); + if (banReason != "" && banReason != null && banReason.Length > 1) + { + if (forever) + { + TriggerServerEvent("vMenu.PermBanPlayer", player.ServerId, banReason); + } + else + { + string banDurationHours = await GetUserInput("Ban Duration (in hours) Max: 720 (1 month)", "1.5", 10); + if (double.TryParse(banDurationHours, out double banHours)) + { + TriggerServerEvent("vMenu.TempBanPlayer", player.ServerId, banHours, banReason); + } + else + { + if (int.TryParse(banDurationHours, out int banHoursInt)) + { + TriggerServerEvent("vMenu.TempBanPlayer", player.ServerId, (double)banHoursInt, banReason); + } + else + { + Notify.Error(CommonErrors.InvalidInput); + } + } + } + } + else + { + Notify.Error(CommonErrors.InvalidInput); + } + } + #endregion + #region Kill Player /// /// Kill player diff --git a/vMenu/PermissionsManager.cs b/vMenu/PermissionsManager.cs index 9bbfead0..ca6763e8 100644 --- a/vMenu/PermissionsManager.cs +++ b/vMenu/PermissionsManager.cs @@ -14,7 +14,7 @@ public enum Permission DontKickMe, NoClip, - // Onlie Players + // Online Players OPMenu, OPAll, OPTeleport, @@ -23,6 +23,8 @@ public enum Permission OPSummon, OPKill, OPKick, + OPPermBan, + OPTempBan, // Player Options POMenu, @@ -143,7 +145,7 @@ public enum Permission MSDeathNotifs, MSNightVision, MSThermalVision, - + // Voice Chat diff --git a/vMenu/menus/OnlinePlayers.cs b/vMenu/menus/OnlinePlayers.cs index 646e4f7c..9ac2359c 100644 --- a/vMenu/menus/OnlinePlayers.cs +++ b/vMenu/menus/OnlinePlayers.cs @@ -67,7 +67,8 @@ public void UpdatePlayerlist() Player player = new Player(int.Parse(item.Description.Substring(1, 2).ToString())); // Create the menu for the player & set the width offset. - UIMenu PlayerMenu = new UIMenu(player.Name, "[" + (player.Handle < 10 ? "0" : "") + player.Handle + "] " + player.Name + " (Server ID: " + player.ServerId + ")", true) + UIMenu PlayerMenu = new UIMenu(player.Name, "[" + (player.Handle < 10 ? "0" : "") + player.Handle + "] " + player.Name + + " (Server ID: " + player.ServerId + ")", true) { ScaleWithSafezone = false, MouseControlsEnabled = false, @@ -88,9 +89,14 @@ public void UpdatePlayerlist() summonBtn.SetRightBadge(UIMenuItem.BadgeStyle.Alert); UIMenuItem killBtn = new UIMenuItem("Kill Player", "Kill the selected player! Why are you so cruel :("); killBtn.SetRightBadge(UIMenuItem.BadgeStyle.Alert); - UIMenuItem kickPlayerBtn = new UIMenuItem("~r~Kick Player", "~r~Kick~s~ this player from the server, you need to specify a reason otherwise the kick will be cancelled."); + UIMenuItem kickPlayerBtn = new UIMenuItem("~r~Kick Player", "~r~Kick~s~ this player from the server, you need to specify a reason " + + "otherwise the kick will be cancelled."); kickPlayerBtn.SetRightBadge(UIMenuItem.BadgeStyle.Alert); - + UIMenuItem permBanBtn = new UIMenuItem("~r~Ban Player", "Ban the player from the server forever."); + permBanBtn.SetRightBadge(UIMenuItem.BadgeStyle.Alert); + UIMenuItem tempBanBtn = new UIMenuItem("~r~Tempban Player", "Ban the player from the server for the specified amount of hours. " + + "The player will be able to rejoin after the ban expires."); + tempBanBtn.SetRightBadge(UIMenuItem.BadgeStyle.Alert); // Add all buttons to the player options submenu. Keeping permissions in mind. if (cf.IsAllowed(Permission.OPTeleport)) @@ -118,6 +124,14 @@ public void UpdatePlayerlist() { PlayerMenu.AddItem(kickPlayerBtn); } + if (cf.IsAllowed(Permission.OPTempBan)) + { + PlayerMenu.AddItem(tempBanBtn); + } + if (cf.IsAllowed(Permission.OPPermBan)) + { + PlayerMenu.AddItem(permBanBtn); + } // Add the player menu to the menu pool. @@ -196,6 +210,14 @@ public void UpdatePlayerlist() menu.RefreshIndex(); menu.UpdateScaleform(); } + else if (item2 == tempBanBtn) + { + cf.BanPlayer(player: player, forever: false); + } + else if (item2 == permBanBtn) + { + cf.BanPlayer(player: player, forever: true); + } }; // Reopen the playerlist menu when a player specific menu is closed. diff --git a/vMenuServer/BanManager.cs b/vMenuServer/BanManager.cs new file mode 100644 index 00000000..d3b12f1b --- /dev/null +++ b/vMenuServer/BanManager.cs @@ -0,0 +1,343 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; +using CitizenFX.Core; +using static CitizenFX.Core.Native.API; + +namespace vMenuServer +{ + public class BanManager : BaseScript + { + /// + /// Struct used to store bans. + /// + struct BanRecord + { + public string playerName; + public List identifiers; + public DateTime bannedUntil; + public string banReason; + public string bannedBy; + } + + /// + /// List of ban records. + /// + private List BannedPlayersList = new List(); + + /// + /// Constructor. + /// + public BanManager() + { + EventHandlers.Add("vMenu.TempBanPlayer", new Action(BanPlayer)); + EventHandlers.Add("vMenu.PermBanPlayer", new Action(BanPlayer)); + EventHandlers.Add("playerConnecting", new Action(CheckForBans)); + BannedPlayersList = GetBanList(); + } + + /// + /// Gets the ban list from the bans.json file. + /// + /// + private List GetBanList() + { + var banList = new List(); + string bansJson = LoadResourceFile(GetCurrentResourceName(), "bans.json"); + if (bansJson != null && bansJson != "") + { + dynamic banRecords = JsonConvert.DeserializeObject(bansJson); + if (banRecords != null) + { + foreach (dynamic br in banRecords) + { + var newBr = new BanRecord(); + foreach (Newtonsoft.Json.Linq.JProperty brValue in br) + { + string key = brValue.Name.ToString(); + var value = brValue.Value; + if (key == "playerName") + { + newBr.playerName = value.ToString(); + } + else if (key == "identifiers") + { + var tmpList = new List(); + foreach (string identifier in value) + { + tmpList.Add(identifier); + } + newBr.identifiers = tmpList; + } + else if (key == "bannedUntil") + { + newBr.bannedUntil = DateTime.Parse(value.ToString()); + } + else if (key == "banReason") + { + newBr.banReason = value.ToString(); + } + else if (key == "bannedBy") + { + newBr.bannedBy = value.ToString(); + } + } + banList.Add(newBr); + } + } + } + return banList; + } + + /// + /// Checks if the player is banned and if so how long the ban will remain active. + /// If the ban expired in the past, then the ban will be removed and the player will be allowed to join again. + /// If the ban is not expired yet, then the player will be kicked with a message displaying how long their ban will remain active. + /// + /// + /// + /// + private void CheckForBans([FromSource]Player source, string playerName, CallbackDelegate kickCallback) + { + foreach (BanRecord ban in BannedPlayersList) + { + foreach (string identifier in source.Identifiers.ToList()) + { + if (ban.identifiers.Contains(identifier)) + { + var timeRemaining = ban.bannedUntil.Subtract(DateTime.Now); + if (timeRemaining.TotalSeconds > 0) + { + if (ban.bannedUntil.Year == new DateTime(3000, 1, 1).Year) + { + kickCallback($"You have been permanently banned from this server. " + + $"Banned by: {ban.bannedBy}. Ban reason: {ban.banReason}"); + } + else + { + string timeRemainingMessage = GetRemainingTimeMessage(ban.bannedUntil.Subtract(DateTime.Now)); + kickCallback($"You are banned from this server. Ban time remaining: {timeRemainingMessage}" + + $". Banned by: {ban.bannedBy}. Ban reason: {ban.banReason}"); + } + if (MainServer.debug) + Debug.Write($"Player is still banned for {Math.Round(timeRemaining.TotalHours, 2)} hours.\n"); + CancelEvent(); + } + else + { + if (RemoveBan(ban)) + { + if (MainServer.debug) + Debug.WriteLine("Ban time expired, player has been removed from the ban list."); + } + else + { + if (MainServer.debug) + Debug.WriteLine("Ban time expired, but an unknown error occurred while removing the player from the banlist!" + + " They have been allowed to join the server, but please remove them from the ban list manually!"); + } + + } + break; + } + } + } + } + + /// + /// Bans the specified player from the server. + /// + /// The player who triggered the event. + /// The player that needs to be banned. + /// The reason why the player is getting banned. + private void BanPlayer([FromSource] Player source, int targetPlayer, string banReason) + { + if (IsPlayerAceAllowed(source.Handle, "vMenu.OnlinePlayers.PermBan")) + { + Player target = new PlayerList()[targetPlayer]; + if (!IsPlayerAceAllowed(target.Handle, "vMenu.DontBanMe")) + { + BanRecord ban = new BanRecord() + { + bannedBy = source.Name, + bannedUntil = new DateTime(3000, 1, 1), + banReason = banReason, + identifiers = target.Identifiers.ToList(), + playerName = target.Name + }; + if (AddBan(ban)) + { + if (MainServer.debug) + Debug.WriteLine("Ban successfull."); + } + else + { + if (MainServer.debug) + Debug.Write("Ban not successfull."); + } + BannedPlayersList = GetBanList(); + target.Drop($"You have been permanently banned from this server. " + + $"Banned by: {ban.bannedBy}. Ban reason: {ban.banReason}"); + } + } + else + { + BanCheater(source); + } + } + + /// + /// Bans the specified player for a the specified amount of hours. + /// + /// Player who triggered the event. + /// Player who needs to be banned. + /// Ban duration in hours. + /// Reason for the ban. + private void BanPlayer([FromSource] Player source, int targetPlayer, double banDurationHours, string banReason) + { + if (IsPlayerAceAllowed(source.Handle, "vMenu.OnlinePlayers.TempBan")) + { + Player target = new PlayerList()[targetPlayer]; + if (!IsPlayerAceAllowed(target.Handle, "vMenu.DontBanMe")) + { + BanRecord ban = new BanRecord() + { + bannedBy = source.Name, + bannedUntil = DateTime.Now.AddHours(banDurationHours <= 720.0 ? banDurationHours : 720.0), + banReason = banReason, + identifiers = target.Identifiers.ToList(), + playerName = target.Name + }; + if (AddBan(ban)) + { + if (MainServer.debug) + Debug.WriteLine("Ban successfull."); + } + else + { + if (MainServer.debug) + Debug.Write("Ban not successfull."); + } + BannedPlayersList = GetBanList(); + string timeRemaining = GetRemainingTimeMessage(ban.bannedUntil.Subtract(DateTime.Now)); + target.Drop($"You are banned from this server. Ban time remaining: {timeRemaining}" + + $". Banned by: {ban.bannedBy}. Ban reason: {ban.banReason}"); + } + } + else + { + BanCheater(source); + } + } + + /// + /// Returns a formatted string displaying exactly how many days, hours and/or minutes a ban remains active. + /// + /// + /// + private string GetRemainingTimeMessage(TimeSpan remainingTime) + { + var message = ""; + if (remainingTime.Days > 0) + { + message += $"{remainingTime.Days} day{(remainingTime.Days > 1 ? "s" : "")} "; + } + if (remainingTime.Hours > 0) + { + message += $"{remainingTime.Hours} hour{(remainingTime.Hours > 1 ? "s" : "")} "; + } + if (remainingTime.Minutes > 0) + { + message += $"{remainingTime.Minutes} minute{(remainingTime.Minutes > 1 ? "s" : "")}"; + } + if (remainingTime.Days < 1 && remainingTime.Hours < 1 && remainingTime.Minutes < 1) + { + message = "Less than 1 minute"; + } + return message; + } + + /// + /// Adds a ban manually. + /// + /// + /// + private bool AddBan(BanRecord ban) + { + BannedPlayersList = GetBanList(); + var found = false; + foreach (BanRecord b in BannedPlayersList) + { + b.identifiers.ForEach(i => + { + if (ban.identifiers.Contains(i)) + { + found = true; + } + }); + if (found) + { + BannedPlayersList.Remove(b); + break; + } + } + BannedPlayersList.Add(ban); + + var output = JsonConvert.SerializeObject(BannedPlayersList); + return SaveResourceFile(GetCurrentResourceName(), "bans.json", output, output.Length); + } + + /// + /// Removes a ban record from the banned players list. + /// + /// + /// + private bool RemoveBan(BanRecord record) + { + BannedPlayersList = GetBanList(); + List itemsToRemove = new List(); + foreach (BanRecord ban in BannedPlayersList) + { + if (!itemsToRemove.Contains(BannedPlayersList.IndexOf(ban))) + { + foreach (string s in ban.identifiers) + { + if (record.identifiers.Contains(s)) + { + itemsToRemove.Add(BannedPlayersList.IndexOf(ban)); + } + } + } + } + for (var i = BannedPlayersList.Count; i > 0; i--) + { + if (itemsToRemove.Contains(i - 1) && i - 1 >= 0 && i - 1 < BannedPlayersList.Count) + { + BannedPlayersList.RemoveAt(i - 1); + } + } + var output = JsonConvert.SerializeObject(BannedPlayersList); + return SaveResourceFile(GetCurrentResourceName(), "bans.json", output, output.Length); + } + + /// + /// Someone trying to trigger fake server events? Well, goodbye idiots. + /// + /// + public void BanCheater(Player source) + { + AddBan(new BanRecord() + { + bannedBy = "Yourself, idiot.", + bannedUntil = new DateTime(3000, 1, 1), + banReason = "You know exactly what you did wrong, you're a fucking idiot, but nobody needs to tell you that.", + identifiers = source.Identifiers.ToList(), + playerName = source.Name + }); + source.Drop("Enjoy, idiot."); + } + } +} diff --git a/vMenuServer/EventManager.cs b/vMenuServer/MainServer.cs similarity index 98% rename from vMenuServer/EventManager.cs rename to vMenuServer/MainServer.cs index 70c40926..6d34254c 100644 --- a/vMenuServer/EventManager.cs +++ b/vMenuServer/MainServer.cs @@ -11,10 +11,10 @@ namespace vMenuServer { - public class EventManager : BaseScript + public class MainServer : BaseScript { // Debug shows more information when doing certain things. Leave it off to improve performance! - private bool debug = GetResourceMetadata(GetCurrentResourceName(), "server_debug_mode", 0) == "true" ? true : false; + public static bool debug = GetResourceMetadata(GetCurrentResourceName(), "server_debug_mode", 0) == "true" ? true : false; private int currentHours = 9; private int currentMinutes = 0; @@ -64,6 +64,8 @@ public class EventManager : BaseScript "OPSummon", "OPKill", "OPKick", + "OPPermBan", + "OPTempBan", // Player Options "POMenu", @@ -200,7 +202,7 @@ public class EventManager : BaseScript /// /// Constructor. /// - public EventManager() + public MainServer() { if (GetCurrentResourceName() != "vMenu") { diff --git a/vMenuServer/config/permissions.cfg b/vMenuServer/config/permissions.cfg index 36490f88..07873018 100644 --- a/vMenuServer/config/permissions.cfg +++ b/vMenuServer/config/permissions.cfg @@ -28,8 +28,9 @@ add_principal identifier.license:4510587c13e0b645eb8d24bc104601792277ab98 group. ## Setup Permissions # Global Permissions #add_ace builtin.everyone "vMenu.Everything" allow # (Don't uncomment this, or bad things will happen if you don't know what you're doing.) -# Admins can't be kicked from the server, everyone else can be kicked though. +# Admins can't be kicked or banned from the server, everyone else can be kicked/banned without mercy though. add_ace group.admin "vMenu.DontKickMe" allow +add_ace group.admin "vMenu.vMenu.DontBanMe" allow # Allow everyone to use noclip by default. add_ace builtin.everyone "vMenu.NoClip" allow @@ -43,6 +44,10 @@ add_ace builtin.everyone "vMenu.OnlinePlayers.Spectate" allow add_ace group.moderator "vMenu.OnlinePlayers.Summon" allow add_ace group.moderator "vMenu.OnlinePlayers.Kill" allow add_ace group.moderator "vMenu.OnlinePlayers.Kick" allow +# moderators can only ban for max 1 month. +add_ace group.admin "vMenu.OnlinePlayers.TempBan" allow +# allow permanent bans for admins only. +add_ace group.admin "vMenu.OnlinePlayers.PermBan" allow # Player Options add_ace builtin.everyone "vMenu.PlayerOptions.Menu" allow diff --git a/vMenuServer/vMenuServer.csproj b/vMenuServer/vMenuServer.csproj index ae17fabf..e75e9720 100644 --- a/vMenuServer/vMenuServer.csproj +++ b/vMenuServer/vMenuServer.csproj @@ -51,7 +51,8 @@ - + +