/* * <----- End-User License Agreement -----> * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Developer: ꜰᴀᴋᴇɴɪɴᴊᴀ🔥#0001 * * Copyright © 2023 FAKENINJA */ using Oxide.Core.Libraries.Covalence; using System.Collections.Generic; using Newtonsoft.Json; using System; namespace Oxide.Plugins { /* * CHANGELOG * * 1.0.0 - Public release of SmartWarnings Lite version * 1.1.1 - Optimized plugin, completely got rid of System.Linq and unused functions. * */ [Info("SmartWarningsLite", "FAKENINJA", "1.1.1")] [Description("Simple chat-based player warning system")] class SmartWarningsLite : CovalencePlugin { public static SmartWarningsLite Instance; #region Config internal static Cfg config; internal class Cfg { [JsonProperty(PropertyName = "System Settings")] public SystemSettings System { get; set; } public class SystemSettings { [JsonProperty(PropertyName = "Max Warnings")] public int MaxWarnings { get; set; } [JsonProperty(PropertyName = "Default Warning Expiration time (Days)")] public int DefaultWarningExpDays { get; set; } [JsonProperty(PropertyName = "Announce Warnings in Global Chat")] public bool AnnounceWarnings { get; set; } [JsonProperty(PropertyName = "Show players who issued the warning")] public bool ShowWhoIssued { get; set; } [JsonProperty(PropertyName = "Server Name")] public string ServerName { get; set; } [JsonProperty(PropertyName = "Clear all Warnings on Server Wipe")] public bool ClearOnWipe { get; set; } } } #endregion #region Player Data Dictionary Warnings = new Dictionary(); class PlayerWarnings { public string Name { get; set; } public int Count { get; set; } public int Points { get; set; } public Dictionary WarnData { get; set; } public PlayerWarnings(string Name) { this.Name = Name; this.Count = 0; this.Points = 0; WarnData = new Dictionary(); } } class WarningData { public string Reason { get; set; } public Boolean Acknowledged { get; set; } public string ExpDate { get; set; } public string WarnDate { get; set; } public string WarnedBy { get; set; } public WarningData() { Reason = ""; Acknowledged = false; ExpDate = ""; WarnDate = ""; WarnedBy = ""; } } PlayerWarnings GetPlayerData(ulong userID, string name) { PlayerWarnings userData; if (!Warnings.TryGetValue(userID, out userData)) { Warnings[userID] = userData = new PlayerWarnings(name); SaveData(ref Warnings, "SmartWarningsLite_PlayerData"); } return userData; } #endregion #region Plugin General //////////////////////////////////////// /// Plugin Related Hooks //////////////////////////////////////// void Loaded() { LoadConfig(); LoadMessages(); LoadData(ref Warnings, "SmartWarningsLite_PlayerData"); RegisterPerm("admin"); RegisterPerm("admin.canclear"); timer.Every(720f, () => {ExpireWarnings();}); } //////////////////////////////////////// /// Config & Message Loading //////////////////////////////////////// private Cfg GetDefaultCfg() { return new Cfg { System = new Cfg.SystemSettings { AnnounceWarnings = true, MaxWarnings = 5, ClearOnWipe = true, ServerName = "MyRustServer", ShowWhoIssued = true, DefaultWarningExpDays = 7 } }; } protected override void LoadDefaultConfig() => config = GetDefaultCfg(); protected override void LoadConfig() { base.LoadConfig(); config = Config.ReadObject(); Config.WriteObject(config, true); } protected override void SaveConfig() => Config.WriteObject(config, true); void LoadMessages() { lang.RegisterMessages(new Dictionary { {"NO_PERM", "You don't have permission to use this command."}, {"NO_MATCH", "Could not find a match for player name or steamid" }, {"WARNING_TITLE", "You have received a warning"}, {"WARNING_ISSUEDBY", "Warning issued by {0} at {1}" }, {"WARNING_ISSUEDAT", "Warning issued at {0}" }, {"CHAT_ACKNOWLEDGE_TEXT", "Warning #{0} Acknowledged: You're now unfrozen and free to go.\nPlease review the server rules by typing /info in chat to avoid getting warned in the future.\n\nIf you feel this was an incorrect warning please reach out to our Staff via Discord." }, {"ANNOUNCE_WARNING_TEXT","{0} has been warned!\nFurther violations will lead to disciplinary action.\nReason: {1}" }, {"ANNOUNCE_WARNING_ISSUEDBY","\n\nWarning Issued by: {0}" }, {"REASON","REASON" }, }, this); } #endregion #region Commands [Command("warn")] void cmdWarn(IPlayer player, string cmd, string[] args) { if (args.Length == 0) { if (!HasPerm(player.Id, "admin")) { player.Reply(msg("NO_PERM")); return; } player.Reply($"[{this.Name} v{this.Version} (Free) by FAKENINJA]\nAvailable commands:\n/warn pid \"reason\"\nWarn player\n/warn clan \"tag\" \"reason\"\nWarn entire clan (including offline members)\n/warn info pid\nSee player warnings\n/warn clear pid all|id\nClear all warnings or specific warning id\n\npid = Player Name or ID\n\nIf you enjoy this plugin, please consider supporting the developer by upgrading to the full version @ https://codefling.com/plugins/smart-warnings"); return; } switch (args[0].ToLower()) { case "acknowledge": switch (args.Length) { case 1: player.Reply($"You need to specify which warning to acknowledge!"); return; case 2: WarnAcknowledge(Convert.ToUInt64(player.Id), Convert.ToInt32(args[1])); return; default: player.Reply($"You are trying to acknowledge a warning with incorrect input!"); return; } case "info": if (args.Length == 1) { GetPlayerWarnings(player,player); return;} else { if (players.FindPlayer(args[1]) == null) { player.Reply($"{msg("NO_MATCH")}: '{args[1]}'"); return; } GetPlayerWarnings(players.FindPlayer(args[1]), player); return; } case "clan": player.Reply("This command is not available in the Lite version.\nIf you enjoy this plugin, please consider supporting the developer by upgrading to the full version @ https://codefling.com/plugins/smart-warnings"); return; case "clear": if (!HasPerm(player.Id, "admin.canclear")) { player.Reply(msg("NO_PERM")); return; } if (args.Length < 1 || args.Length > 2) { ClearPlayerWarnings(players.FindPlayer(args[1]), player.Name, args[2]); } else { player.Reply($"Usage: /warn clear playerNameOrId \n\nExample: Clear all warnings for JohnDoe with /warn clear JohnDoe all\nOnly clear warning id: 1 with /warn clear JohnDoe 1"); } return; default: string syntaxMsg = "Usage: /warn playerNameOrId \"reason\"\nTo see all commands type /warn, to see presets type /warn p"; if (!HasPerm(player.Id, "admin")) { player.Reply(msg("NO_PERM")); return; } switch (args.Length) { case 1: player.Reply($"You must provide a warning reason!\n{syntaxMsg}"); return; case 4: player.Reply($"Invalid syntax (too many options provided)\n{syntaxMsg}"); return; } IPlayer foundPlayer = players.FindPlayer(args[0]); if (foundPlayer == null) { player.Reply($"{msg("NO_MATCH")} {args[0]}"); return; } WarnPlayer(foundPlayer, player, args[1]); break; } return; } void WarnPlayer(IPlayer targetPlayer, IPlayer adminPlayer,string preset) { var userData = GetPlayerData(Convert.ToUInt64(targetPlayer.Id),targetPlayer.Name); if (userData == null){Warnings[Convert.ToUInt64(targetPlayer.Id)] = new PlayerWarnings(targetPlayer.Name);} var warnId = userData.Count + 1; userData.WarnData.Add(warnId, new WarningData()); userData.Points += 1; userData.WarnData[warnId].Reason = preset; userData.WarnData[warnId].Acknowledged = false; userData.WarnData[warnId].WarnDate = DateTime.Now.ToString(); userData.WarnData[warnId].ExpDate = DateTime.Now.AddDays(config.System.DefaultWarningExpDays).ToString(); userData.WarnData[warnId].WarnedBy = adminPlayer.Name; LogToFile("log", $"{targetPlayer.Name} ({targetPlayer.Id}) warned for (Specific) \"{preset}\", worth 1 warning points, expires at {DateTime.Now.AddDays(config.System.DefaultWarningExpDays).ToString()} issued by {adminPlayer.Name} ({adminPlayer.Id})", this); userData.Count = userData.WarnData.Count; if (config.System.AnnounceWarnings){ server.Broadcast(string.Format(msg("ANNOUNCE_WARNING_TEXT"), targetPlayer.Name, preset) + (config.System.ShowWhoIssued ? String.Format(msg("ANNOUNCE_WARNING_ISSUEDBY"),adminPlayer.Name) : "")); } UseChat(targetPlayer, warnId, preset, DateTime.Now.ToString(), adminPlayer.Name); SaveData(ref Warnings, "SmartWarningsLite_PlayerData"); } void WarnAcknowledge(ulong playerId, int warnId) { BasePlayer baseplayer = BasePlayer.FindByID(Convert.ToUInt64(playerId)); var userData = GetPlayerData(playerId, baseplayer.name); if (userData.WarnData.ContainsKey(warnId)) { var warning = userData.WarnData[warnId]; UnfreezePlayer(baseplayer.IPlayer, warnId); baseplayer.IPlayer.Reply(string.Format(msg("CHAT_ACKNOWLEDGE_TEXT"),warnId.ToString())); warning.Acknowledged = true; userData.WarnData[warnId] = warning; SaveData(ref Warnings, "SmartWarningsLite_PlayerData"); } else {baseplayer.IPlayer.Reply($"Warning id {warnId} does not exist and can not be acknowledged.");} } void GetPlayerWarnings(IPlayer targetPlayer, IPlayer player) { if(targetPlayer.IsServer) { player.Reply($"Usage: /warn info playerNameOrId"); return; } var userData = GetPlayerData(Convert.ToUInt64(targetPlayer.Id), targetPlayer.Name); if(userData == null){player.Reply($"{player.Name} does not have any warnings."); return; } player.Reply($"[{this.Name} v{this.Version} (Free) by FAKENINJA]"); player.Reply($"{targetPlayer.Name} has {userData.Count}/{config.System.MaxWarnings} warnings and {userData.Points} warning points."); foreach (KeyValuePair entry in userData.WarnData) { var warning = entry.Value; var warnId = entry.Key; player.Reply($"Warning ID: {warnId}\n{warning.Reason}\n\nDate Issued: {warning.WarnDate}\nExpiry Date: {warning.ExpDate}" + (config.System.ShowWhoIssued || HasPerm(player.Id,"admin") ? $"\nIssued by: {warning.WarnedBy}" : "")); } } void ClearPlayerWarnings(IPlayer player, string adminName, string warnId) { var userData = GetPlayerData(Convert.ToUInt64(player.Id), player.Name); if (userData == null) { player.Reply($"{player.Name} does not have any warnings."); return; } if (warnId == "all") { foreach(var warning in userData.WarnData) { UnfreezePlayer(player, Convert.ToInt32(warning.Key)); } userData.WarnData.Clear(); userData.Points = 0; player.Reply($"Cleared all of {player.Name} warnings and warning points."); LogToFile("log", $"{adminName} cleared all of {player.Name} warnings", this); } else if(userData.WarnData[Convert.ToInt32(warnId)] == null){player.Reply($"{player.Name} does not have warning #{warnId}");} else { userData.WarnData.Remove(Convert.ToInt32(warnId)); UnfreezePlayer(player, Convert.ToInt32(warnId)); } userData.Count = userData.WarnData.Count; SaveData(ref Warnings, "SmartWarningsLite_PlayerData"); } void DisplayUnAcknowledgedWarnings(IPlayer player) { try { var userData = GetPlayerData(Convert.ToUInt64(player.Id), player.Name); if(userData.WarnData == null) { return; } if(userData.WarnData.Count < 1) { return; } foreach (KeyValuePair entry in userData.WarnData) { var warning = entry.Value; var warnId = entry.Key; if(warning.Acknowledged == false) { UseChat(player, warnId, warning.Reason, warning.WarnDate, warning.WarnedBy); } } } catch (Exception e) { LogError($"[DisplayUnAcknowledgedWarnings] An error occurred that shouldn't happen for {player.Id} when trying to display warnings\n\n{e}"); } } void ExpireWarnings() { DateTime now = DateTime.Now; foreach (var player in Warnings.Values) { var keysToRemove = new List(); foreach (var warning in player.WarnData) { if (DateTime.Parse(warning.Value.ExpDate) <= now) { keysToRemove.Add(warning.Key); LogToFile("log", $"{player.Name} warning id: {warning.Key} expired {warning.Value.ExpDate}", this); } } foreach (var key in keysToRemove){ player.WarnData.Remove(key);} } Puts("If you enjoy this plugin, please consider supporting the developer by upgrading to the full version @ https://codefling.com/plugins/smart-warnings"); } private void OnNewSave(string str) { if (config.System.ClearOnWipe) { Puts("ClearOnWipe is enabled - All player warnings have been cleared"); LogToFile("log", "New wipe detected, cleared warnings", this); Warnings.Clear(); SaveData(ref Warnings, "SmartWarningsLite_PlayerData"); } } #endregion #region Hooks private void OnPlayerSleepEnded(BasePlayer player) { timer.Once(5f, () => { if(!player.IsFullySpawned()) { return; } if(!player.IsConnected) { return; } if(player.HasPlayerFlag(BasePlayer.PlayerFlags.ReceivingSnapshot)) { return; } DisplayUnAcknowledgedWarnings(player.IPlayer); }); } #endregion #region UseChat utils private readonly Dictionary timers = new Dictionary(); private void UnfreezePlayer(IPlayer player, int warnId) { try { timers[$"{player.Id}_{warnId}"].Destroy(); timers[$"{player.Id}_{warnId}_msg"].Destroy(); } catch (Exception e) { LogError($"[UnfreezePlayer] An error occurred that shouldn't happen when trying to unfreeze {player.Id}\n\n{e}"); } } #endregion void UseChat(IPlayer player, int warnId, string reason, string warndate, string warnedby) { // Player Freeze GenericPosition pos = player.Position(); timers[$"{player.Id}_{warnId}"] = timer.Every(0.1f, () => { if (!player.IsConnected) { timers[$"{player.Id}_{warnId}"].Destroy(); timers[$"{player.Id}_{warnId}_msg"].Destroy(); return; } player.Teleport(pos.X, pos.Y, pos.Z); }); string IssuedBy = (config.System.ShowWhoIssued ? $"{string.Format(msg("WARNING_ISSUEDBY"), warnedby, warndate)}" : $"{string.Format(msg("WARNING_ISSUEDAT"), warndate)}"); timers[$"{player.Id}_{warnId}_msg"] = timer.Every(5f, () => { player.Reply($"{config.System.ServerName} Warnings System\n" + $"{msg("WARNING_TITLE")}\n" + $"{msg("REASON")}:\n{reason}\n" + $"\n{IssuedBy}"); player.Reply($"You are frozen until you accept this warning!\nType /warn acknowledge {warnId} to accept this warning."); }); } #region General Methods #region Format Helpers public static string StripTags(string original) => Formatter.ToPlaintext(original); public static string FormatText(string original) => Instance.covalence.FormatText(original); public static void ReplySafe(IPlayer player, string message) { player.Reply(player.IsServer ? StripTags(message) : FormatText(message)); } #endregion //////////////////////////////////////// /// Data Related //////////////////////////////////////// void LoadData(ref T data, string filename = "?") => data = Core.Interface.Oxide.DataFileSystem.ReadObject(filename == "?" ? this.Title : filename); void SaveData(ref T data, string filename = "?") => Core.Interface.Oxide.DataFileSystem.WriteObject(filename == "?" ? this.Title : filename, data); //////////////////////////////////////// /// Message Related //////////////////////////////////////// string msg(string key, object userID = null) => lang.GetMessage(key, this, userID == null ? null : userID.ToString()); //////////////////////////////////////// /// Permission Related //////////////////////////////////////// void RegisterPerm(params string[] permArray) { string perm = string.Join(".", permArray); permission.RegisterPermission($"{PermissionPrefix}.{perm}", this); } bool HasPerm(object uid, params string[] permArray) { if (uid.ToString().Equals("server_console")) { return true; } string perm = string.Join(".", permArray); return permission.UserHasPermission(uid.ToString(), $"{PermissionPrefix}.{perm}"); } string PermissionPrefix { get { return this.Title.Replace(" ", "").ToLower(); } } #endregion } }