C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using System;
using ConVar;
using Newtonsoft.Json;
using System.Globalization;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Core;
using Oxide.Game.Rust.Cui;
using UnityEngine;
using UnityEngine.UI;
using Oxide.Core.Libraries;
using Oxide.Plugins.AutomatedMessagesMethods;
/*2.2.1
* ArgumentException fix
*/
/*2.2.0
* Added Settings section in admin ui
* Fix for replies not sent to eligible players if trigger owner was not eligible
* Fix for broadcast to all replies not always showing same message for all players
* Added "Use trigger owner chat icon" to actions
* Added "Custom chat icon (Steam Id)" to actions
* Added reply specific custom chat icon prefix (customicon:{steamid})
* Added "Game tip color (true = blue - false = red)" to actions
* Starts fetching country earlier + webrequest exception handling
* Get grid using MapHelper.PositionToString
* Code cleanup
*/
namespace Oxide.Plugins {
[Info("AutomatedMessages", "beee", "2.2.1")]
[Description("Automated chat messages based on triggers or repeating interval.")]
class AutomatedMessages : RustPlugin {
#region Fields
private static AutomatedMessages _plugin;
private PluginConfig _config;
private List<Timer> _timers;
private const string PREFIX_SHORT = "am";
private const string PREFIX_LONG = "automatedmessages";
private const string PERM_ADMIN = $"{PREFIX_LONG}.admin";
private Dictionary<Regex, PluginConfig.ConfigAction> _autoReplyPatterns;
[PluginReference]
private Plugin PlaceholderAPI;
#endregion
#region Load/Unload Hooks
private void Init() {
UnsubscribeHooks();
}
private void OnServerInitialized() {
pData = PlayersData.Load();
ProcessConfig();
RegisterPermissions();
InitTimers();
InitAutoReply();
SubscribeHooks();
RegisterConfigCommands();
SetUnsetCountries();
RegisterCommand($"{PREFIX_SHORT}.ui.texteditor", nameof(LongInputCMD));
}
private void Loaded() {
_plugin = this;
_timers = new();
}
private void Unload() {
DestroyTimers();
SaveData();
foreach (var player in BasePlayer.activePlayerList) {
CuiHelper.DestroyUi(player, uimodal);
CuiHelper.DestroyUi(player, chatpreview);
CuiHelper.DestroyUi(player, gametippreview);
}
_plugin = null;
_timers = null;
}
#endregion
#region Functions
private void RegisterPermissions() {
permission.RegisterPermission(PERM_ADMIN, this);
}
private void InitTimers() {
DestroyTimers();
_timers = new();
int count = 0;
foreach (var action in _config.Actions.FindAll(s => s.IsEnabled() && s.Type == "Timed")) {
Lang_Action defaultLangAction = action.CachedLangActions[_config.DefaultLang];
if (defaultLangAction.Replies == null || defaultLangAction.Replies.Count == 0) continue;
if (action.Interval <= 0) continue;
var repeatTimer = _plugin.timer.Repeat(action.Interval * 60, 0, () => ProcessReply(action, defaultLangAction : defaultLangAction));
_timers.Add(repeatTimer);
count++;
}
PrintWarning($"Started {count} chat timer{(count == 1 ? "" : "s")}.");
}
private void InitAutoReply() {
_autoReplyPatterns = new();
int count = 0;
foreach (var message in _config.Actions.FindAll(s => s.IsEnabled() && s.Type == "AutoReply")) {
Lang_Action defaultLangAction = message.CachedLangActions[_config.DefaultLang];
if (defaultLangAction.Replies == null || defaultLangAction.Replies.Count == 0) continue;
if (string.IsNullOrEmpty(message.Target)) continue;
foreach (var set in message.Target.Split('|')) {
string regexPattern = "";
foreach (var keyword in set.Split(',')) regexPattern += $@"(?=.*{(keyword.StartsWith("!") ? "^!" : "")}\b{keyword.TrimStart('!').Trim()}\b)";
if (regexPattern == "") continue;
regexPattern = $"^{regexPattern}.*$";
_autoReplyPatterns.Add(new (regexPattern, RegexOptions.IgnoreCase), message);
count++;
}
}
PrintWarning($"Cached {count} auto reply keyword pattern{(count == 1 ? "" : "s")}.");
}
List<string> _registeredConfigCommands;
private void RegisterConfigCommands() {
_registeredConfigCommands = new();
RegisterCommand(_config.ToggleCommand, nameof(TipsToggleChatCMD));
_registeredConfigCommands.Add(_config.ToggleCommand);
int count = 0;
foreach (var message in _config.Actions.FindAll(s => s.IsEnabled() && s.Type == "ChatCommand")) {
Lang_Action defaultLangAction = message.CachedLangActions[_config.DefaultLang];
if (defaultLangAction.Replies == null || defaultLangAction.Replies.Count == 0) continue;
if (string.IsNullOrEmpty(message.Target)) continue;
string command = message.Target.Replace("/", "").Trim().ToLower();
foreach (string com in command.Split(',').Select(s => s.Trim())) {
if (string.IsNullOrEmpty(com)) continue;
RegisterCommand(com, nameof(CustomChatCMD));
_registeredConfigCommands.Add(com);
count++;
}
}
PrintWarning($"Registered {count} chat command{(count == 1 ? "" : "s")}.");
}
private void UnregisterCachedCommands() {
if (_registeredConfigCommands == null) return;
var library = GetLibrary<Covalence>();
foreach (var command in _registeredConfigCommands) library.UnregisterCommand(command, this);
_registeredConfigCommands.Clear();
}
private void RegisterCommand(string command, string callback, string perm = null) {
if (!string.IsNullOrEmpty(command) && !command.Equals("null", StringComparison.OrdinalIgnoreCase)) {
if (string.IsNullOrEmpty(perm)) AddCovalenceCommand(command, callback);
else AddCovalenceCommand(command, callback, perm);
}
}
private void SetUnsetCountries() {
foreach (var player in BasePlayer.activePlayerList) {
if (!pData.Players.TryGetValue(player.userID, out var pinfo)) pData.Players.Add(player.userID, pinfo = new PlayerInfo {
TipsActive = true, isNew = true
}
);
if (string.IsNullOrEmpty(pinfo.Country)) FetchConnectionCountry(player.Connection, pinfo);
}
}
private void DestroyTimers() {
foreach (var repeatTimer in _timers) repeatTimer?.Destroy();
}
private void ProcessReplies(BasePlayer player, List<string> triggersToProcess, string argument = "", bool onlySendToTeam = false) {
if (!player.userID.IsSteamId()) return;
//add player if not in data, and in that case set isNew to true to recognize for NewPlayerJoined trigger
if(!pData.Players.TryGetValue(player.userID, out var pinfo)) pData.Players.Add(player.userID, pinfo = new PlayerInfo() {
TipsActive = true, isNew = true
}
);
//loop enabled actions with trigger in triggersToProcess
foreach (var action in _config.Actions.FindAll(s => s.IsEnabled() && triggersToProcess.Contains(s.Type))) {
Lang_Action defaultLangAction = action.CachedLangActions[_config.DefaultLang];
//skip if default lang for this action does not have replies
if (defaultLangAction.Replies == null || defaultLangAction.Replies.Count == 0) continue;
bool canSend = false;
if(TriggerDefinitions.TryGetValue(action.Type, out TriggerDefinition triggerDefinition)) {
if (triggerDefinition.RequiresTarget) {
//skip action if it's trigger requires a target but target parameter is null/empty
if (string.IsNullOrEmpty(argument)) continue;
//trigger based conditions
switch (triggerDefinition.Key) {
case "ChatCommand" : if (action.Target.Replace("/", "").ToLower().Split(',').Any(x => x.Trim() == argument)) canSend = true;
break;
default : if(action.Target == argument) canSend = true;
break;
}
}
else {
//trigger based conditions
switch (triggerDefinition.Key) {
case "NewPlayerJoined" : if (pinfo.isNew) canSend = true;
break;
case "PlayerConnected" : if (pinfo.justJoined) canSend = true;
break;
case "PlayerDead" : if (pinfo.wasDead && ((argument == "" && !action.IsGlobalBroadcast) || (argument == "FromEntityDeath" && action.IsGlobalBroadcast))) {
pinfo.wasDead = false;
canSend = true;
}
break;
default : canSend = true;
break;
}
}
//skip if action is on cooldown
if (triggerDefinition.UsesGenericCooldown && action.OnCooldownAll) continue;
//skip if trigger owner is on cooldown for this action
if (triggerDefinition.UsesPlayerCooldown && action.OnCooldown != null && action.OnCooldown.Contains(player.userID)) continue;
}
if (canSend) {
if (action.IsGlobalBroadcast) //sends to all eligible players
ProcessReply(action, defaultLangAction, player, onlySendToTeam);
else //sends to trigger owner
{
//check if player is eligible or has tips enabled
if ((action.PlayerCanDisable && !pinfo.TipsActive) || !MessageIsEligible(action, player.UserIDString)) continue;
string playerLanguage = lang.GetLanguage(player.UserIDString);
//look for reply based on player language, if not use defaut server language
if (!Lang_TryGetAction(player, action.Type, action.Id, playerLanguage, out Lang_Action langAction) || langAction.Replies.Count == 0) langAction = defaultLangAction;
//double check to avoid index out of range (might be unnecessary)
if (langAction.ReplyIndex >= langAction.Replies.Count) langAction.ReplyIndex = 0;
string reply = langAction.Replies[langAction.ReplyIndex];
IncrementReplyIndex(ref langAction);
//increment reply index
//define variables/placeholders
reply = DefineVariables(player, reply, triggerDefinition);
//Log($"{action.Type} | ({player.displayName} | {player.UserIDString}) | {playerLanguage} | index({langAction.ReplyIndex}) | {reply}");
//get icon steam id - reply custom icon > action custom icon > general settings custom icon
ulong iconSteamId = GetActionIcon(action, player, _config.IconSteamId);
timer.Once(0.5f, () => SendMessage(player, reply, action.SendInChat, iconSteamId, action.SendAsGameTip, action.IsBlueGameTip));
}
//start cooldowns
switch(action.Type) {
case "AutoReply" : action.RunCooldownAll(_config.AutoReplyCooldown);
break;
case "ChatCommand" : action.RunCooldownAll(_config.ChatCommandCooldown);
break;
case "EnteredZone" : action.RunCooldownPlayer(player, _config.ZoneManagerCooldown);
break;
case "LeftZone" : action.RunCooldownPlayer(player, _config.ZoneManagerCooldown);
break;
case "EnteredMonument" : action.RunCooldownPlayer(player, _config.MonumentWatcherCooldown);
break;
case "LeftMonument" : action.RunCooldownPlayer(player, _config.MonumentWatcherCooldown);
break;
}
}
}
pinfo.isNew = false;
pinfo.justJoined = false;
}
private void ProcessReply(PluginConfig.ConfigAction action, Lang_Action defaultLangAction, BasePlayer triggerOwner = null, bool onlySendToTeam = false) {
//skip if trigger owner is admin and disables it
if (triggerOwner && triggerOwner.IsAdmin && action.DontTriggerAdmin) return;
//keep track of already incremented lang replies
Dictionary<string, string> cachedLangsReplies = new();
ulong iconSteamId = GetActionIcon(action, triggerOwner, _config.IconSteamId);
foreach (var player in BasePlayer.activePlayerList) {
if(triggerOwner && onlySendToTeam && triggerOwner.currentTeam != 0 && triggerOwner.currentTeam != player.currentTeam) continue;
if (!pData.Players.TryGetValue(player.userID, out var pinfo)) pData.Players.Add(player.userID, pinfo = new PlayerInfo {
TipsActive = true, isNew = true
}
);
if ((action.PlayerCanDisable && !pinfo.TipsActive) || !MessageIsEligible(action, player.UserIDString)) continue;
string playerLanguage = lang.GetLanguage(player.UserIDString);
if (!cachedLangsReplies.TryGetValue(playerLanguage, out string reply)) {
bool retrievedCached = false;
//look for reply based on player language, if not use default server lang action
if (!Lang_TryGetAction(player, action.Type, action.Id, playerLanguage, out Lang_Action playerlangAction) || playerlangAction.Replies.Count == 0) {
//check if default lang reply was cached
if (cachedLangsReplies.TryGetValue(_config.DefaultLang, out reply)) retrievedCached = true;
else {
playerLanguage = _config.DefaultLang;
playerlangAction = defaultLangAction;
}
}
if (!retrievedCached) {
//double check to avoid index out of range (might be unnecessary)
if (playerlangAction.ReplyIndex >= playerlangAction.Replies.Count) playerlangAction.ReplyIndex = 0;
reply = playerlangAction.Replies[playerlangAction.ReplyIndex];
//increment reply index
IncrementReplyIndex(ref playerlangAction);
cachedLangsReplies.Add(playerLanguage, reply);
}
}
//define variables/placeholders
reply = DefineVariables(triggerOwner ? triggerOwner : player, reply, TriggerDefinitions[action.Type]);
//Log($"{action.Type} | ({player.displayName} | {player.UserIDString}) | {playerLanguage} | {reply}");
timer.Once(0.5f, () => SendMessage(player, reply, action.SendInChat, iconSteamId, action.SendAsGameTip, action.IsBlueGameTip));
}
}
private ulong GetActionIcon(PluginConfig.ConfigAction action, BasePlayer triggerOwner, string defaultIconSteamId) {
ulong iconSteamId;
if (triggerOwner != null && action.UseTriggerOwnerIcon) iconSteamId = triggerOwner.userID;
else if (!ulong.TryParse(action.CustomIconSteamId, out iconSteamId) || iconSteamId <= 0) ulong.TryParse(defaultIconSteamId, out iconSteamId);
return iconSteamId;
}
private void IncrementReplyIndex(ref Lang_Action lang_Action) {
lang_Action.ReplyIndex++;
if (lang_Action.ReplyIndex >= lang_Action.Replies.Count || lang_Action.ReplyIndex < 0) lang_Action.ReplyIndex = 0;
}
readonly List<string> availableVariables = new() {
"{playername}", "{playerid}", "{playercountry}", "{wipetimeremaining}", "{online}", "{sleeping}", "{joining}"
}
;
private string DefineVariables(BasePlayer player, string message, TriggerDefinition triggerDefinition) {
StringBuilder builder = new StringBuilder(message);
builder.Replace("{playername}", player.displayName).Replace("{playerid}", player.UserIDString).Replace("{online}", $"{BasePlayer.activePlayerList.Count}").Replace("{sleeping}", $"{BasePlayer.sleepingPlayerList.Count}").Replace("{joining}", $"{ServerMgr.Instance.connectionQueue.Joining + ServerMgr.Instance.connectionQueue.Queued}");
if (message.Contains("{playercountry}")) {
var found = pData.Players.TryGetValue(player.userID, out var pinfo);
if (found && !string.IsNullOrEmpty(pinfo.Country)) builder.Replace("{playercountry}", pinfo.Country);
else builder.Replace("{playercountry}", "");
}
if (message.Contains("{wipetimeremaining}") && WipeTimer.serverinstance != null) {
DateTimeOffset nowTime = DateTimeOffset.UtcNow;
nowTime = nowTime.AddDays(WipeTimer.daysToAddTest);
nowTime = nowTime.AddHours(WipeTimer.hoursToAddTest);
DateTimeOffset wipeTime = WipeTimer.serverinstance.GetWipeTime(nowTime);
TimeSpan timeSpan = wipeTime - nowTime;
builder.Replace("{wipetimeremaining}", FormatTime(timeSpan, m : false, s : false));
}
switch (triggerDefinition.Key) {
case "CrateHacked" : if (pData.Players.TryGetValue(player.userID, out var pinfo) && !string.IsNullOrEmpty(pinfo.HackLocation)) builder.Replace("{hacklocation}", pinfo.HackLocation);
else builder.Replace("{hacklocation}", MapHelper.PositionToString(player.transform.position));
break;
}
PlaceholderAPI?.CallHook("ProcessPlaceholders", player.IPlayer, builder);
return builder.ToString();
}
private void Log(string text) {
LogToFile("debug", $"[{DateTime.Now}] {text}", this);
}
#endregion
#region Hooks
private void UnsubscribeHooks() {
Unsubscribe(nameof(OnPlayerChat));
Unsubscribe(nameof(OnPlayerDisconnected));
Unsubscribe(nameof(CanHackCrate));
Unsubscribe(nameof(OnEntityDeath));
Unsubscribe(nameof(OnPlayerSleepEnded));
Unsubscribe(nameof(OnUserPermissionGranted));
Unsubscribe(nameof(OnUserPermissionRevoked));
Unsubscribe(nameof(OnUserGroupAdded));
Unsubscribe(nameof(OnUserGroupRemoved));
Unsubscribe(nameof(OnEnterZone));
Unsubscribe(nameof(OnExitZone));
Unsubscribe(nameof(OnPlayerEnteredMonument));
Unsubscribe(nameof(OnPlayerExitedMonument));
}
private void SubscribeHooks() {
foreach (var trigger in TriggerDefinitions) {
if (string.IsNullOrEmpty(trigger.Value.Hooks)) continue;
if (_config.Actions.Any(s => s.Type == trigger.Key && s.IsEnabled())) foreach (string hook in trigger.Value.Hooks.Split(',')) Subscribe(hook);
}
}
private void OnPlayerChat(BasePlayer player, string message, Chat.ChatChannel chatchannel) {
if (chatchannel != Chat.ChatChannel.Global && chatchannel != Chat.ChatChannel.Team) return;
foreach (var keywords in _autoReplyPatterns.Where(regex => regex.Key.Match(message).Success)) NextTick(() => ProcessReplies(player, new() {
"AutoReply"
}
, keywords.Value.Target, chatchannel == Chat.ChatChannel.Team && _config.BroadcastToTeamOnly));
}
private void OnClientAuth(Network.Connection connection) {
pData.Players.TryGetValue(connection.userid, out var pinfo);
FetchConnectionCountry(connection, pinfo);
}
private void OnPlayerConnected(BasePlayer player) {
if (!player.userID.IsSteamId()) return;
if (pData.Players.TryGetValue(player.userID, out var pinfo)) pinfo.justJoined = true;
else pData.Players.Add(player.userID, pinfo = new PlayerInfo {
TipsActive = true, isNew = true, justJoined = !HasActiveNewPlayerJoinedAction
}
);
if (string.IsNullOrEmpty(pinfo.Country)) FetchConnectionCountry(player.Connection, pinfo);
}
private void OnPlayerDisconnected(BasePlayer player) {
if (player != null) ProcessReplies(player, new() {
"PlayerDisconnected"
}
);
}
private void CanHackCrate(BasePlayer player, HackableLockedCrate crate) {
if (player == null || !player.userID.IsSteamId()) return;
string Location = MapHelper.PositionToString(crate.transform.position);
if (OnCargoShip(crate)) Location = "Cargo Ship";
else {
MonumentRef monument = GetMonumentFromPosition(crate.transform.position);
if (monument != null) Location = $"{monument.DisplayName} ({Location})";
}
if (pData.Players.TryGetValue(player.userID, out var pinfo)) pinfo.HackLocation = Location;
else pData.Players.Add(player.userID, new PlayerInfo {
TipsActive = true, HackLocation = Location
}
);
ProcessReplies(player, new() {
"CrateHacked"
}
);
}
private void OnEntityDeath(BasePlayer player, HitInfo hitInfo) {
if (player == null || !player.userID.IsSteamId()) return;
if (pData.Players.TryGetValue(player.userID, out var pinfo)) {
pinfo.wasDead = true;
ProcessReplies(player, new() {
"PlayerDead"
}
, "FromEntityDeath");
}
}
private void OnPlayerSleepEnded(BasePlayer player) {
if (player.IsDead() || !player.IsConnected) return;
ProcessReplies(player, new() {
"NewPlayerJoined", "PlayerConnected", "PlayerDead"
}
);
}
private void OnUserPermissionGranted(string id, string permName) {
var player = BasePlayer.FindByID(Convert.ToUInt64(id));
if (player != null) ProcessReplies(player, new() {
"PermissionGranted"
}
, permName);
}
private void OnUserPermissionRevoked(string id, string permName) {
var player = BasePlayer.FindByID(Convert.ToUInt64(id));
if (player != null) ProcessReplies(player, new() {
"PermissionRevoked"
}
, permName);
}
private void OnUserGroupAdded(string userId, string groupName) {
var player = BasePlayer.FindByID(Convert.ToUInt64(userId));
if (player != null) ProcessReplies(player, new() {
"AddedToGroup"
}
, groupName);
}
private void OnUserGroupRemoved(string userId, string groupName) {
var player = BasePlayer.FindByID(Convert.ToUInt64(userId));
if (player != null) ProcessReplies(player, new() {
"RemovedFromGroup"
}
, groupName);
}
private void OnEnterZone(string ZoneID, BasePlayer player) {
if (player != null) ProcessReplies(player, new() {
"EnteredZone"
}
, ZoneID);
}
private void OnExitZone(string ZoneID, BasePlayer player) {
if (player != null) ProcessReplies(player, new() {
"LeftZone"
}
, ZoneID);
}
private void OnPlayerEnteredMonument(string monumentID, BasePlayer player, string type, string oldMonumentID) {
if (player != null) ProcessReplies(player, new() {
"EnteredMonument"
}
, monumentID);
}
private void OnPlayerExitedMonument(string monumentID, BasePlayer player, string type, string reason, string newMonumentID) {
if (player != null) ProcessReplies(player, new() {
"LeftMonument"
}
, monumentID);
}
#endregion
#region Commands
private void TipsToggleChatCMD(IPlayer user, string command, string[] args) {
var player = user.Object as BasePlayer;
if (player == null) return;
var isActive = pData.ToggleTipsActive(player.userID);
ulong iconSteamId;
ulong.TryParse(_config.IconSteamId, out iconSteamId);
SendMessage(player, isActive ? lang.GetMessage("toggle_enabled", this, player.UserIDString) : lang.GetMessage("toggle_disabled", this, player.UserIDString), IconSteamId : iconSteamId);
}
private void CustomChatCMD(IPlayer user, string command, string[] args) {
var player = user.Object as BasePlayer;
if (player == null) return;
ProcessReplies(player, new() {
"ChatCommand"
}
, command);
}
#endregion
#region TriggerDefinition
Dictionary<string, TriggerDefinition> TriggerDefinitions;
private class TriggerDefinition {
public string Key {
get;
set;
}
public string Hooks {
get;
set;
}
public bool RequiresTarget {
get;
set;
}
public bool UsesIsGlobalBroadcast = true;
public bool UsesDontTriggerAdmin = true;
public bool UsesGenericCooldown {
get;
set;
}
public bool UsesPlayerCooldown {
get;
set;
}
public bool HasTriggerOwner = true;
public List<string> Variables = new();
}
private void InitTriggerDefinitions() {
TriggerDefinitions = new();
TriggerDefinitions.Add("Timed", new() {
Key = "Timed", RequiresTarget = false, UsesIsGlobalBroadcast = false, UsesDontTriggerAdmin = false, HasTriggerOwner = false
}
);
TriggerDefinitions.Add("ChatCommand", new() {
Key = "ChatCommand", Hooks = "OnPlayerChat", RequiresTarget = true, UsesGenericCooldown = true
}
);
TriggerDefinitions.Add("AutoReply", new() {
Key = "AutoReply", Hooks = "OnPlayerChat", RequiresTarget = true, UsesGenericCooldown = true
}
);
TriggerDefinitions.Add("NewPlayerJoined", new() {
Key = "NewPlayerJoined", Hooks = "OnPlayerSleepEnded", RequiresTarget = false
}
);
TriggerDefinitions.Add("PlayerConnected", new() {
Key = "PlayerConnected", Hooks = "OnPlayerSleepEnded", RequiresTarget = false
}
);
TriggerDefinitions.Add("PlayerDisconnected", new() {
Key = "PlayerDisconnected", Hooks = "OnPlayerDisconnected", RequiresTarget = false
}
);
TriggerDefinitions.Add("PermissionGranted", new() {
Key = "PermissionGranted", Hooks = "OnUserPermissionGranted", RequiresTarget = true
}
);
TriggerDefinitions.Add("PermissionRevoked", new() {
Key = "PermissionRevoked", Hooks = "OnUserPermissionRevoked", RequiresTarget = true
}
);
TriggerDefinitions.Add("AddedToGroup", new() {
Key = "AddedToGroup", Hooks = "OnUserGroupAdded", RequiresTarget = true
}
);
TriggerDefinitions.Add("RemovedFromGroup", new() {
Key = "RemovedFromGroup", Hooks = "OnUserGroupRemoved", RequiresTarget = true
}
);
TriggerDefinitions.Add("PlayerDead", new() {
Key = "PlayerDead", Hooks = "OnEntityDeath,OnPlayerSleepEnded", RequiresTarget = false
}
);
TriggerDefinitions.Add("CrateHacked", new() {
Key = "CrateHacked", Hooks = "CanHackCrate", RequiresTarget = false, Variables = new() {
"{hacklocation}"
}
}
);
TriggerDefinitions.Add("EnteredZone", new() {
Key = "EnteredZone", Hooks = "OnEnterZone", RequiresTarget = true, UsesPlayerCooldown = true
}
);
TriggerDefinitions.Add("LeftZone", new() {
Key = "LeftZone", Hooks = "OnExitZone", RequiresTarget = true, UsesPlayerCooldown = true
}
);
TriggerDefinitions.Add("EnteredMonument", new() {
Key = "EnteredMonument", Hooks = "OnPlayerEnteredMonument", RequiresTarget = true, UsesPlayerCooldown = true
}
);
TriggerDefinitions.Add("LeftMonument", new() {
Key = "LeftMonument", Hooks = "OnPlayerExitedMonument", RequiresTarget = true, UsesPlayerCooldown = true
}
);
}
#endregion
#region Config
bool HasActiveNewPlayerJoinedAction;
private void ProcessConfig() {
HasActiveNewPlayerJoinedAction = _config.Actions.Any(s => s.Type == "NewPlayerJoined" && s.IsEnabled());
Lang_CacheActions();
}
private class PluginConfig {
[JsonProperty(Order = 1000)]
public VersionNumber Version;
[JsonProperty(Order = 1, PropertyName = "Chat Icon (Steam Id)")]
public string IconSteamId {
get;
set;
}
[JsonProperty(Order = 2, PropertyName = "Toggle Chat Command")]
public string ToggleCommand {
get;
set;
}
[JsonProperty(Order = 3, PropertyName = "AutoReply Cooldown (in seconds)")]
public int AutoReplyCooldown {
get;
set;
}
[JsonProperty(Order = 4, PropertyName = "ChatCommand Cooldown (in seconds)")]
public int ChatCommandCooldown {
get;
set;
}
[JsonProperty(Order = 5, PropertyName = "ZoneManager Cooldown (in seconds)")]
public int ZoneManagerCooldown {
get;
set;
}
[JsonProperty(Order = 6, PropertyName = "MonumentWatcher Cooldown (in seconds)")]
public int MonumentWatcherCooldown {
get;
set;
}
[JsonProperty(Order = 7, PropertyName = "Sample Types for Reference (Do Not Edit)")]
public string SampleTypes {
get;
set;
}
[JsonProperty(Order = 8, PropertyName = "Replies Server Languages (Creates lang file for each in data/AutomatedMessages/lang)")]
public List<string> ServerLangs {
get;
set;
}
[JsonProperty(Order = 9, PropertyName = "Default Server Language")]
public string DefaultLang {
get;
set;
}
[JsonProperty(Order = 10, PropertyName = "AutoReply `Broadcast to all` option to broadcast to team only if keywords sent from team chat")]
public bool BroadcastToTeamOnly {
get;
set;
}
[JsonProperty(Order = 11, PropertyName = "Actions")]
public List<ConfigAction> Actions {
get;
set;
}
[JsonProperty("Chat Icon (SteamId)")]
public ulong _ObsIconSteamId {
get;
set;
}
[JsonProperty("Messages")]
public List<ConfigAction> _ObsMessages;
public bool ShouldSerialize_ObsMessages() => _ObsMessages != null;
internal class ConfigAction {
[JsonProperty("Id")]
public string Id = GenerateId();
[JsonProperty(Order = 0, PropertyName = "Enabled")]
public bool Enabled {
get;
set;
}
[JsonProperty("Type (Check Sample Types above for Reference)")]
public string Type = "";
[JsonProperty("Broadcast to all?")]
public bool IsGlobalBroadcast {
get;
set;
}
[JsonProperty("Don't trigger for admins")]
public bool DontTriggerAdmin {
get;
set;
}
[JsonProperty("Send in chat")]
public bool SendInChat = true;
[JsonProperty("Send as game tip")]
public bool SendAsGameTip {
get;
set;
}
[JsonProperty("Game tip color (true = blue - false = red)")]
public bool IsBlueGameTip = true;
[JsonProperty("Interval between messages in minutes (if Type = Timed)")]
public int Interval = 0;
[JsonProperty("Target")]
public string Target = "";
[JsonProperty("Custom chat icon (Steam Id)")]
public string CustomIconSteamId = "0";
[JsonProperty("Use trigger owner chat icon")]
public bool UseTriggerOwnerIcon {
get;
set;
}
[JsonProperty("Permissions")]
public List<string> Permissions = new();
[JsonProperty("Groups")]
public List<string> Groups = new();
[JsonProperty("Blacklisted Permissions")]
public List<string> BlacklistedPerms = new();
[JsonProperty("Blacklisted Groups")]
public List<string> BlacklistedGroups = new();
[JsonProperty("Player Can Disable?")]
public bool PlayerCanDisable {
get;
set;
}
[JsonIgnore]
public bool OnCooldownAll;
[JsonIgnore]
public List<ulong> OnCooldown {
get;
set;
}
[JsonIgnore]
public Dictionary<string, Lang_Action> CachedLangActions {
get;
set;
}
[JsonProperty("Messages (Random if more than one)")]
public List<string> _ObsMessages;
[JsonProperty("Replies")]
public List<string> _ObsReplies;
public void RunCooldownAll(int cooldown) {
if (cooldown <= 0) return;
OnCooldownAll = true;
_plugin.timer.Once(cooldown, () => {
OnCooldownAll = false;
}
);
}
public void RunCooldownPlayer(BasePlayer player, int cooldown) {
OnCooldown ??= new();
if (cooldown <= 0 || OnCooldown.Contains(player.userID)) return;
OnCooldown.Add(player.userID);
_plugin.timer.Once(cooldown, () => {
OnCooldown.Remove(player.userID);
}
);
}
public ConfigAction Clone(bool newGuid = false) {
return new ConfigAction {
Id = newGuid ? GenerateId() : Id, Enabled = Enabled, Type = Type, IsGlobalBroadcast = IsGlobalBroadcast, DontTriggerAdmin = DontTriggerAdmin, SendInChat = SendInChat, SendAsGameTip = SendAsGameTip, IsBlueGameTip = IsBlueGameTip, Interval = Interval, Target = Target, UseTriggerOwnerIcon = UseTriggerOwnerIcon, CustomIconSteamId = CustomIconSteamId, Permissions = new(Permissions), Groups = new(Groups), BlacklistedPerms = new(BlacklistedPerms), BlacklistedGroups = new(BlacklistedGroups), PlayerCanDisable = PlayerCanDisable, CachedLangActions = null
}
;
}
public bool ShouldSerialize_ObsMessages() => _ObsMessages != null;
public bool ShouldSerialize_ObsReplies() => _ObsReplies != null;
private static string GenerateId() {
ulong timestamp = (ulong)DateTime.UtcNow.Ticks;
uint randomPart = (uint)UnityEngine.Random.Range(0, int.MaxValue);
return ((timestamp << 32) | randomPart).ToString();
}
public bool IsEnabled() => Enabled && (SendInChat || SendAsGameTip);
}
public bool ShouldSerialize_ObsIconSteamId() => _ObsIconSteamId != 0;
}
private PluginConfig GetDefaultConfig() {
PluginConfig result = new() {
IconSteamId = "0", Version = Version, ToggleCommand = "tips", AutoReplyCooldown = 30, ChatCommandCooldown = 30, ZoneManagerCooldown = 0, MonumentWatcherCooldown = 15, SampleTypes = string.Join(" | ", TriggerDefinitions.Keys), ServerLangs = new() {
"en", "es-ES", "it", "ru", "zh-CN"
}
, DefaultLang = GetOxideLanguage(), BroadcastToTeamOnly = true, Actions = new()
}
;
return result;
}
private void CheckForConfigUpdates() {
bool changes = false;
if (_config == null) {
PluginConfig tmpDefaultConfig = GetDefaultConfig();
_config = tmpDefaultConfig;
changes = true;
}
string sampleTypes = string.Join(" | ", TriggerDefinitions.Keys);
if(_config.SampleTypes != sampleTypes) {
_config.SampleTypes = sampleTypes;
changes = true;
}
//1.0.8 update
if (_config.Version < new VersionNumber(1, 0, 8)) {
PluginConfig tmpDefaultConfig = GetDefaultConfig();
_config.ToggleCommand = tmpDefaultConfig.ToggleCommand;
changes = true;
}
//1.0.16 update
if (_config.Version < new VersionNumber(1, 0, 16)) {
PluginConfig tmpDefaultConfig = GetDefaultConfig();
_config.AutoReplyCooldown = tmpDefaultConfig.AutoReplyCooldown;
changes = true;
}
//2.0.0 update
if (_config.Version < new VersionNumber(2, 0, 0)) {
if (_config._ObsMessages != null) _config.Actions = new(_config._ObsMessages);
else _config.Actions = new();
_config._ObsMessages = null;
foreach (var action in _config.Actions) {
if (action._ObsMessages != null) action._ObsReplies = new(action._ObsMessages);
else action._ObsReplies = new();
action._ObsMessages = null;
}
changes = true;
}
if(string.IsNullOrEmpty(_config.DefaultLang) || !AvailableLangs.ContainsKey(_config.DefaultLang)) {
if (!string.IsNullOrEmpty(_config.DefaultLang) && CorrectedLangNames.TryGetValue(_config.DefaultLang, out string changedTo)) {
PrintWarning($"Config default language {_config.DefaultLang} has been changed to '{changedTo}'");
_config.DefaultLang = changedTo;
}
else _config.DefaultLang = GetOxideLanguage();
changes = true;
}
//2.0.6 update
if (_config.Version < new VersionNumber(2, 0, 6)) {
PluginConfig tmpDefaultConfig = GetDefaultConfig();
_config.ServerLangs = _config.ServerLangs;
_config.IconSteamId = _config._ObsIconSteamId.ToString();
_config._ObsIconSteamId = 0;
_config.ChatCommandCooldown = tmpDefaultConfig.ChatCommandCooldown;
_config.ZoneManagerCooldown = tmpDefaultConfig.ZoneManagerCooldown;
_config.MonumentWatcherCooldown = tmpDefaultConfig.MonumentWatcherCooldown;
_config.ServerLangs = new() {
_config.DefaultLang
}
;
RepliesLangData = new();
foreach (string serverLang in _config.ServerLangs) {
if (!AvailableLangs.ContainsKey(serverLang)) continue;
if (RepliesLangData.ContainsKey(serverLang)) continue;
Lang_Root langRoot = new();
foreach (var triggerDefinition in TriggerDefinitions) if (!langRoot.Triggers.ContainsKey(triggerDefinition.Key)) langRoot.Triggers.Add(triggerDefinition.Key, new());
RepliesLangData.Add(serverLang, langRoot);
}
foreach (var action in _config.Actions) {
if (action._ObsReplies != null) {
foreach (var langRoot in RepliesLangData) {
if(langRoot.Value.Triggers.TryGetValue(action.Type, out Lang_Trigger langTrigger)) {
langTrigger.Actions ??= new();
if(langRoot.Key == _config.DefaultLang) langTrigger.Actions.Add(action.Id, new() {
Replies = action._ObsReplies
}
);
else langTrigger.Actions.Add(action.Id, new());
}
}
action._ObsReplies = null;
}
}
Lang_SaveRepliesData();
changes = true;
}
else {
if (_config.ServerLangs == null || _config.ServerLangs.Count == 0) {
_config.ServerLangs = new() {
GetOxideLanguage()
}
;
changes = true;
}
List<string> langsRemoved = new();
for (int i = _config.ServerLangs.Count - 1; i >= 0; i--) {
if (!AvailableLangs.ContainsKey(_config.ServerLangs[i])) {
if (CorrectedLangNames.TryGetValue(_config.ServerLangs[i], out string changedTo)) {
PrintWarning($"Config reply language {_config.ServerLangs[i]} has been changed to '{changedTo}'");
_config.ServerLangs[i] = changedTo;
changes = true;
}
else {
langsRemoved.Add(_config.ServerLangs[i]);
_config.ServerLangs.RemoveAt(i);
changes = true;
}
}
}
if (langsRemoved.Count > 0) {
PrintWarning($"Language code{(langsRemoved.Count > 1 ? "s" : "")} \"{string.Join(", ", langsRemoved)}\" {(langsRemoved.Count > 1 ? "are" : "is")} not available or not in correct code (removed from config), the following are the available options:\n{string.Join(", ", AvailableLangs.Keys)}.");
changes = true;
}
Lang_LoadRepliesData();
}
//2.0.9 update
if (_config.Version < new VersionNumber(2, 0, 9)) {
_config.BroadcastToTeamOnly = true;
changes = true;
}
if (_config.Version != Version) changes = true;
if (changes) {
_config.Version = Version;
PrintWarning("Config updated.");
SaveConfig();
}
}
protected override void LoadDefaultConfig() => _config = GetDefaultConfig();
protected override void LoadConfig() {
InitTriggerDefinitions();
base.LoadConfig();
_config = Config.ReadObject<PluginConfig>();
CheckForConfigUpdates();
}
protected override void SaveConfig() => Config.WriteObject(_config);
#endregion
#region Data
private PlayersData pData;
private void OnServerSave() => timer.Once(UnityEngine.Random.Range(5f, 10f), SaveData);
private bool wiped {
get;
set;
}
private void OnNewSave(string filename) => wiped = true;
private void SaveData() => pData?.Save();
public class PlayersData {
public Dictionary<ulong, PlayerInfo> Players = new();
public static PlayersData Load() {
var data = Interface.Oxide.DataFileSystem.ReadObject<PlayersData>($"{nameof(AutomatedMessages)}/PlayerData");
if (data == null || _plugin.wiped) {
_plugin.PrintWarning("No player data found! Creating a new data file.");
data = new();
data.Save();
}
return data;
}
public void Save() {
Interface.Oxide.DataFileSystem.WriteObject($"{nameof(AutomatedMessages)}/PlayerData", this);
}
public bool CheckIfNew(ulong playerid) {
return !Players.ContainsKey(playerid);
}
public void EnableTips(ulong play
// ... [Code preview truncated due to length. Full source in attached file] ...