C#:
/*
* <----- 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<ulong, PlayerWarnings> Warnings = new Dictionary<ulong, PlayerWarnings>();
class PlayerWarnings {
public string Name {
get;
set;
}
public int Count {
get;
set;
}
public int Points {
get;
set;
}
public Dictionary<int, WarningData> WarnData {
get;
set;
}
public PlayerWarnings(string Name) {
this.Name = Name;
this.Count = 0;
this.Points = 0;
WarnData = new Dictionary<int, WarningData>();
}
}
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<Cfg>();
Config.WriteObject(config, true);
}
protected override void SaveConfig() => Config.WriteObject(config, true);
void LoadMessages() {
lang.RegisterMessages(new Dictionary<string, string> {
{
"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", "<color=#FFFFFF>Warning issued by {0} at {1}</color>"
}
, {
"WARNING_ISSUEDAT", "<color=#FFFFFF>Warning issued at {0}</color>"
}
, {
"CHAT_ACKNOWLEDGE_TEXT", "<color=#00FF00><size=12>Warning #{0} Acknowledged: You're now unfrozen and free to go.\n</size></color><size=9>Please review the server rules by typing /info in chat to avoid getting warned in the future.</color>\n\nIf you feel this was an incorrect warning please reach out to our Staff via Discord.</size>"
}
, {
"ANNOUNCE_WARNING_TEXT", "<color=#DC143C>{0} has been warned!\nFurther violations will lead to disciplinary action.</color>\n<color=#A9A9A9>Reason: {1}"
}
, {
"ANNOUNCE_WARNING_ISSUEDBY", "\n\n<size=10>Warning Issued by: {0}</size></color>"
}
, {
"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($"[<color=#C4FF00><size=16>{this.Name} v{this.Version} (Free) by FAKENINJA</size></color>]\nAvailable commands:\n/warn pid \"reason\"\n<size=9>Warn player</size>\n/warn clan \"tag\" \"reason\"\n<size=9>Warn entire clan (including offline members)</size>\n/warn info pid\n<size=9>See player warnings</size>\n/warn clear pid all|id\n<size=9>Clear all warnings or specific warning id\n\npid = Player Name or ID\n\n<size=9>If you enjoy this plugin, please consider supporting the developer by upgrading to the full version @ <color=#006fc9>https://codefling.com/plugins/smart-warnings</color></size>");
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.\n<size=9>If you enjoy this plugin, please consider supporting the developer by upgrading to the full version @ <color=#006fc9>https://codefling.com/plugins/smart-warnings</color></size>");
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 <warnid/all>\n\n<size=10>Example: Clear all warnings for JohnDoe with /warn clear JohnDoe all\nOnly clear warning id: 1 with /warn clear JohnDoe 1</size>");
}
return;
default : string syntaxMsg = "Usage: /warn playerNameOrId \"reason\"\n<size=9>To see all commands type /warn, to see presets type /warn p</size>";
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($"[<color=#C4FF00><size=16>{this.Name} v{this.Version} (Free) by FAKENINJA</size></color>]");
player.Reply($"<size=14>{targetPlayer.Name}</size> has <size=16><color=#DC143C>{userData.Count}/{config.System.MaxWarnings}</color></size> warnings and <size=16><color=#DC143C>{userData.Points}</color></size> warning points.");
foreach (KeyValuePair<int, WarningData> entry in userData.WarnData) {
var warning = entry.Value;
var warnId = entry.Key;
player.Reply($"<color=#DC143C>Warning ID: {warnId}</color>\n<size=12>{warning.Reason}\n\n<color=#A9A9A9>Date Issued: {warning.WarnDate}\nExpiry Date: {warning.ExpDate}</color></size>" + (config.System.ShowWhoIssued || HasPerm(player.Id, "admin") ? $"\n<color=#A9A9A9><size=12>Issued by: {warning.WarnedBy}</color></size>" : ""));
}
}
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<int, WarningData> 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<int>();
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<string, Timer> timers = new Dictionary<string, Timer>();
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($"<size=9>{config.System.ServerName} Warnings System</size>\n" + $"<color=#FF0000><size=16>{msg("WARNING_TITLE")}</size></color>\n" + $"<color=#FF0000>{msg("REASON")}:\n<size=12>{reason}</size></color>\n" + $"\n<size=8>{IssuedBy}</size>");
player.Reply($"<color=#FF0000><size=8>You are frozen until you accept this warning!</size></color>\n<size=12>Type <color=#00FF00>/warn acknowledge {warnId}</color> to accept this warning.</size>");
}
);
}
#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<T>(ref T data, string filename = "?") => data = Core.Interface.Oxide.DataFileSystem.ReadObject<T>(filename == "?" ? this.Title : filename);
void SaveData<T>(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
}
}