//#define DEBUG using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using Newtonsoft.Json; using Oxide.Core; using Oxide.Game.Rust.Cui; using UnityEngine; using UnityEngine.Networking; using UnityEngine.UI; namespace Oxide.Plugins { [Info("UpdateChecker", "tofurahie", "4.1.13")] [Description("Update checker for all of your plugins")] internal class UpdateChecker : RustPlugin { #region DEBUG #if DEBUG private BasePlayer DEV; #endif #endregion #region Static private const string perm = "updatechecker.setup"; private bool isChecking; #region Classes private class Configuration { [JsonIgnore] public string JSON; [JsonProperty("Auto reload [If you change the config and save the file the plugin will reload itself]")] public bool AutoReload = true; [JsonProperty("Command to open UI")] public string Command = "ucsetup"; [JsonProperty("Command to send the test message to your discord")] public string CommandDiscord = "uctest"; [JsonProperty("Discord WebHook")] public string DiscordWebHook = ""; [JsonProperty("Discord message ID")] public string MessageID = ""; [JsonProperty("Difference between UTC and your time [in minutes]")] public int UTC = 60; [JsonProperty("Use 24 time format")] public bool TimeFormat = true; [JsonProperty("Check Interval(In minutes)")] public int CheckMinutes = 60; [JsonProperty("Ignore 'All plugins have the latest version' discord message")] public bool IgnoreAllPluginsUpToDateMessage = false; [JsonProperty("Ignore not found plugins")] public bool IgnoreNotFound = false; [JsonProperty("Add a link to the plugin to be updated")] public bool UseURL = true; [JsonProperty("Enable auto search")] public Dictionary AutoSearch = new Dictionary { ["uMod"] = true, ["Codefling"] = true, ["Lone.Design"] = true, ["Chaos"] = true, ["RustWorkshop"] = true, ["Github"] = true, ["ModPulse"] = true, ["RustPlugins"] = true, ["ServerArmour"] = true, ["ImperialPlugins"] = true, ["MyVector"] = true, ["SkyPlugins"] = true }; [JsonProperty("List of plugins", ObjectCreationHandling = ObjectCreationHandling.Replace)] public List ListOfPlugins = new List(); } private class PluginInfo { [JsonIgnore] public bool IsFounded; [JsonProperty("Name")] public string Name; [JsonProperty("Author")] public string Author; [JsonProperty("Plugin version")] public string Version; [JsonProperty("Link to plugin")] public string Url; [JsonProperty("Marketplace")] public string Marketplace; [JsonProperty("Ignore")] public bool Ignore; public PluginInfo(string author, string name, string version, string url = "", string marketplace = "", bool ignore = false) { Name = name; Author = author; Version = version; Url = url; Marketplace = marketplace; Ignore = ignore; } } private class PluginData { public string name; public string manualName; public string author; public string latestVersion; public string url; public string slug; public string marketplace; public string tags; } private class AboutPlugin { public string Name; public string Author; public string Version; public AboutPlugin(string name, string author, string version) { Name = name; Author = author; Version = version; } } #endregion #endregion #region OxideHooks private void OnServerInitialized() { if (_config.AutoReload) ServerMgr.Instance.StartCoroutine(ConfigCheck()); cmd.AddChatCommand(_config.Command, this, nameof(cmdChatSetup)); cmd.AddChatCommand(_config.CommandDiscord, this, nameof(cmdDiscordTest)); permission.RegisterPermission(perm, this); CheckPlugins(); //ServerMgr.Instance.StartCoroutine(CheckSite()); timer.Every(60 * _config.CheckMinutes, CheckPlugins); #if DEBUG DEV = BasePlayer.FindAwakeOrSleeping("76561198206621603"); ShowUIBG(DEV); #endif } private void Unload() { ServerMgr.Instance.StopCoroutine(CheckNewVersion()); ServerMgr.Instance.StopCoroutine(ConfigCheck()); UI.DestroyToAll(".bg"); } #endregion #region Commands private void cmdChatSetup(BasePlayer player, string command, string[] args) { if (!player.IsAdmin && !permission.UserHasPermission(player.UserIDString, perm)) { SendReply(player, "You don't have permissions to use this command"); return; } ShowUIBG(player); } private void cmdDiscordTest(BasePlayer player, string command, string[] args) { if (!player.IsAdmin && !permission.UserHasPermission(player.UserIDString, perm)) { SendReply(player, "You don't have permissions to use this command"); return; } var updateList = _config.AutoSearch.ToDictionary(check => check.Key, check => (new List(), new List { "Your discord webhook is working" })); ServerMgr.Instance.StartCoroutine(SendMessageDiscord(updateList)); } [ConsoleCommand("checkupdates")] private void cmdConsolecheckupdates(ConsoleSystem.Arg arg) { if (arg == null || arg.Player() != null) return; if (isChecking) { PrintWarning("The plugin already scans other plugins for updates"); return; } CheckPlugins(); } [ConsoleCommand("UI_UC")] private void cmdConsoleUI_UC(ConsoleSystem.Arg arg) { if (!arg.HasArgs()) return; var player = arg.Player(); switch (arg.GetString(0)) { case "SEARCH": ShowUIPlugins(player, string.Join(" ", arg.Args.Skip(1)).Replace(" ", "")); break; case "PAGE": ShowUIPlugins(player, string.Join(" ", arg.Args.Skip(2)),arg.GetInt(1)); break; case "CHECK": ShowUICurrentPluginInfo(player, _config.ListOfPlugins.FirstOrDefault(x => string.Equals(x.Name, string.Join(" ", arg.Args.Skip(1)), StringComparison.CurrentCultureIgnoreCase))); break; case "CHANGEURL": _config.ListOfPlugins.FirstOrDefault(x => string.Equals(x.Name, arg.GetString(1))).Url = arg.GetString(2); SaveConfig(); break; case "CHANGEIGNORE": _config.ListOfPlugins.FirstOrDefault(x => string.Equals(x.Name, arg.GetString(1))).Ignore = !_config.ListOfPlugins.FirstOrDefault(x => string.Equals(x.Name, arg.GetString(1))).Ignore; ShowUICurrentPluginInfo(player, _config.ListOfPlugins.FirstOrDefault(x => string.Equals(x.Name, string.Join(" ", arg.Args.Skip(1)), StringComparison.CurrentCultureIgnoreCase))); SaveConfig(); break; } } #endregion #region Functions private IEnumerator CheckSite() { var request = UnityWebRequest.Get("https://serverarmour.com/api/v1/marketplace/search?author=AhigaO&plugin=Hud"); yield return request.SendWebRequest(); } private IEnumerator SendMessageDiscord(Dictionary, List)> infoList) { if (!string.IsNullOrEmpty(_config.DiscordWebHook)) { var field = new WWWForm(); var updateList = infoList.Aggregate("", (current1, checks) => checks.Value.Item2.Aggregate(current1, (current, check) => current + check)); var space = ""; if (updateList.Length > 1999) { foreach (var check in infoList) { foreach (var url in check.Value.Item2) { space += url; if ((space + url).Length <= 1999) continue; var fielI = new WWWForm(); fielI.AddField("content", space); using (var request = UnityWebRequest.Post(_config.DiscordWebHook, fielI)) yield return request.SendWebRequest(); space = ""; yield return new WaitForSeconds(1.5f); } } field.AddField("content", space); using (var request = UnityWebRequest.Post(_config.DiscordWebHook, field)) yield return request.SendWebRequest(); } else if (string.IsNullOrEmpty(_config.MessageID)) { field.AddField("content", updateList); using var request = UnityWebRequest.Post(_config.DiscordWebHook, field); yield return request.SendWebRequest(); } else { using var request = new UnityWebRequest($"{_config.DiscordWebHook}/messages/{_config.MessageID}", "PATCH"); field.AddField("content", updateList); request.uploadHandler = new UploadHandlerRaw(field.data); request.uploadHandler.contentType = "application/x-www-form-urlencoded"; yield return request.SendWebRequest(); } } isChecking = false; } private void CheckPlugins() { UpdatePluginList(); isChecking = true; ServerMgr.Instance.StartCoroutine(CheckNewVersion()); } private List GetAllPlugins() { var allPlugins = new List(); foreach (var check in Directory.GetFiles(Interface.Oxide.PluginDirectory)) { var regex = Regex.Match(File.ReadAllText(check), @"Info\(\""(.*?)\"", \""(.*?)\"", \""(.*?)\"""); if (string.IsNullOrEmpty(regex.Groups[1].Value)) continue; allPlugins.Add(new AboutPlugin(regex.Groups[1].Value.Replace(" ", ""), regex.Groups[2].Value, regex.Groups[3].Value)); } return allPlugins; } private void UpdatePluginList() { if (covalence.Server.Address.ToString() != "91.245.38.233") ClearPluginList(); foreach (var entry in GetAllPlugins()) { var pluginInfo = _config.ListOfPlugins.FirstOrDefault(x => string.Equals(x.Name,entry.Name, StringComparison.CurrentCultureIgnoreCase)); if (pluginInfo != null) { pluginInfo.Author = entry.Author; pluginInfo.Name = entry.Name; pluginInfo.Version = entry.Version; if (pluginInfo.Name == "LightsOn") pluginInfo.Url = "https://codefling.com/plugins/lights-on"; } else _config.ListOfPlugins.Add(new PluginInfo(entry.Author, entry.Name, entry.Version)); } SaveConfig(); } private void ClearPluginList() { var allPlugins = GetAllPlugins(); _config.ListOfPlugins.RemoveAll(cfgPlugin => allPlugins.All(serverPlugin => cfgPlugin.Name != serverPlugin.Name)); } private IEnumerator CheckNewVersion() { var notFoundList = string.Empty; var updateList = _config.AutoSearch.ToDictionary(check => check.Key, check => (new List(), new List())); updateList["uMod"] = (new List {$"`{DateTime.Now.AddMinutes(_config.UTC).ToString(_config.TimeFormat ? "d MMM yyyy HH:mm" : "d MMM yyyy hh:mm tt")}`"}, new List {$"`{DateTime.Now.AddMinutes(_config.UTC).ToString(_config.TimeFormat ? "d MMM yyyy HH:mm" : "d MMM yyyy hh:mm tt")}`"}); updateList.Add("unknown", (new List(), new List())); for (var index = 0; index < _config.ListOfPlugins.Count; index++) { if (!IsLoaded) yield break; var check = _config.ListOfPlugins[index]; if (check.Ignore) continue; var request = UnityWebRequest.Get($"https://serverarmour.com/api/v1/marketplace/search?plugin={(string.IsNullOrEmpty(check.Url) ? check.Name : check.Url)}"); yield return request.SendWebRequest(); if (request.isNetworkError || request.isHttpError) { if (!request.error.Contains("Too Many Requests")) { request.Dispose(); continue; } index--; if (request.error.Contains("Too Many Requests")) { PrintError("Waiting 30 seconds for rate limit"); request.Dispose(); yield return new WaitForSeconds(30f); continue; } request.Dispose(); continue; } var foundedPlugins = JsonConvert.DeserializeObject>(request.downloadHandler.text); if (foundedPlugins.Count == 0) { if (string.IsNullOrEmpty(check.Url)) { check.IsFounded = false; notFoundList += $"\n{check.Name} - not found"; continue; } request = UnityWebRequest.Get($"https://serverarmour.com/api/v1/marketplace/search?plugin={check.Url}"); yield return request.SendWebRequest(); if (request.isNetworkError || request.isHttpError) { if (!request.error.Contains("Too Many Requests")) { request.Dispose(); continue; } index--; if (request.error.Contains("Too Many Requests")) { PrintError("Waiting 30 seconds for rate limit"); request.Dispose(); yield return new WaitForSeconds(30f); continue; } request.Dispose(); continue; } foundedPlugins = JsonConvert.DeserializeObject>(request.downloadHandler.text); if (foundedPlugins.Count == 0) { check.IsFounded = false; notFoundList += $"\n{check.Name} - not found"; continue; } } var orderedFoundedPlugins = foundedPlugins.OrderBy(x => Array.IndexOf(_config.AutoSearch.Keys.ToArray(), x.marketplace))?.ToList(); foreach (var pluginData in orderedFoundedPlugins) { if (!string.IsNullOrEmpty(check.Url) && !check.Url.Contains(pluginData.url) && !pluginData.url.Contains(check.Url)) continue; PluginData newPluginData = null; if (string.IsNullOrEmpty(check.Url)) { if (_config.AutoSearch.ContainsKey(pluginData.marketplace) && !_config.AutoSearch[pluginData.marketplace]) continue; foreach (var subPluginData in orderedFoundedPlugins) if (subPluginData.author.ToLower().Contains(check.Author.ToLower()) && (subPluginData.name.ToLower().Replace(" ", "").Contains(check.Name.ToLower()) || !string.IsNullOrEmpty(subPluginData.manualName) && subPluginData.manualName.ToLower().Contains(check.Name.ToLower()) || !string.IsNullOrEmpty(subPluginData.slug) && subPluginData.slug.ToLower().Replace("-", "").Contains(check.Name.ToLower()))) { newPluginData = subPluginData; break; } if (newPluginData == null) foreach (var subPluginData in orderedFoundedPlugins) if (string.Equals(subPluginData.name.Replace(" ", ""), check.Name, StringComparison.CurrentCultureIgnoreCase) || !string.IsNullOrEmpty(subPluginData.manualName) && subPluginData.manualName.ToLower().Contains(check.Name.ToLower()) || !string.IsNullOrEmpty(subPluginData.slug) && subPluginData.slug.ToLower().Replace("-", "").Contains(check.Name.ToLower())) { newPluginData = subPluginData; break; } if (newPluginData == null) foreach (var subPluginData in orderedFoundedPlugins) if (subPluginData.tags != null && subPluginData.tags.Contains(pluginData.name.ToLower())) { newPluginData = subPluginData; break; } if (newPluginData == null) continue; } else newPluginData = pluginData; check.IsFounded = true; check.Url = newPluginData.url; check.Marketplace = newPluginData.marketplace; if (string.IsNullOrEmpty(newPluginData.latestVersion) || ParseVersion(newPluginData.latestVersion) <= ParseVersion(check.Version)) break; if (updateList.ContainsKey(check.Marketplace)) { updateList[check.Marketplace].Item1.Add($"\n[{check.Marketplace}] {check.Name} {check.Version} -> {newPluginData.latestVersion} {(_config.UseURL ? $"<{check.Url}>" : "")}"); updateList[check.Marketplace].Item2.Add($"\n[{check.Marketplace}] {check.Name} {check.Version} -> [{newPluginData.latestVersion}]({(_config.UseURL ? $"<{check.Url}>" : "")})"); } else { updateList["unknown"].Item1.Add($"\n[{check.Marketplace}] {check.Name} {check.Version} -> {newPluginData.latestVersion} {(_config.UseURL ? $"<{check.Url}>" : "")}"); updateList["unknown"].Item2.Add($"\n[{check.Marketplace}] {check.Name} {check.Version} -> [{newPluginData.latestVersion}]({(_config.UseURL ? $"<{check.Url}>" : "")})"); } break; } if (!check.IsFounded) notFoundList += $"\n{check.Name} - not found"; } foreach (var check in updateList) { check.Value.Item1.OrderByDescending(x => x[x.IndexOf(" ") + 1]); check.Value.Item2.OrderByDescending(x => x[x.IndexOf(" ") + 1]); } if (!_config.IgnoreNotFound) updateList["unknown"].Item2.Add(notFoundList); var infoList = (updateList.Aggregate("", (current1, checks) => checks.Value.Item1.Aggregate(current1, (current, check) => current + check)) + (_config.IgnoreNotFound ? "" : notFoundList)).Length != 0 || (notFoundList.Length != 0 && !_config.IgnoreNotFound) ? updateList.Aggregate("", (current1, checks) => checks.Value.Item2.Aggregate(current1, (current, check) => current + check)) + (_config.IgnoreNotFound ? "" : notFoundList) : $"All plugins have the latest version"; if (infoList.Length != 0 || (notFoundList.Length != 0 && !_config.IgnoreNotFound)) { PrintWarning(infoList.Replace("(<", " ").Replace(">)", "")); if (!infoList.Contains("All plugins have the latest version") || (infoList.Contains("All plugins have the latest version") && !_config.IgnoreAllPluginsUpToDateMessage)) ServerMgr.Instance.StartCoroutine(SendMessageDiscord(updateList)); } SaveConfig(); } private VersionNumber ParseVersion(string v) { if (v == string.Empty) return new VersionNumber(1, 0, 0); var parts = v.Split('.'); int major; if (!int.TryParse(parts[0], out major)) { var majorPart = parts[0]; var majorString = string.Empty; for (int i = majorPart.Length - 1; i >= 0; i--) { if (!char.IsDigit(majorPart[i])) break; majorString = majorPart[i] + majorString; } if (string.IsNullOrEmpty(majorString)) return new VersionNumber(1, 0, 0); major = int.Parse(majorString); } if (parts.Length < 2) return new VersionNumber(major, 0, 0); int minor; if (!int.TryParse(parts[1], out minor)) minor = 0; if (parts.Length < 3) return new VersionNumber(major, minor, 0); int patch; if (!int.TryParse(parts[2], out patch)) { var patchPart = parts[2]; var patchString = string.Empty; for (int i = 0; i < patchPart.Length; i++) { if (!char.IsDigit(patchPart[i])) break; patchString += patchPart[i]; } if (string.IsNullOrEmpty(patchString)) return new VersionNumber(major, minor, 0); patch = int.Parse(patchString); } return new VersionNumber(major, minor, patch); } #endregion #region UI private void ShowUICurrentPluginInfo(BasePlayer player, PluginInfo pluginInfo) { var container = new CuiElementContainer(); UI.Input(ref container, ".current.plugin.name", ".current.plugin.name.input", ".current.plugin.name.input", oMin:"5 0", oMax:"-5 0", text:$"{pluginInfo.Name}", fontSize:18, color:"1 1 1 1", align:TextAnchor.MiddleLeft, readOnly:true); UI.Input(ref container, ".current.plugin.author", ".current.plugin.author.input", ".current.plugin.author.input", oMin:"5 0", oMax:"-5 0", text:$"{pluginInfo.Author}", fontSize:18, color:"1 1 1 1", align:TextAnchor.MiddleLeft, readOnly:true); UI.Input(ref container, ".current.plugin.version", ".current.plugin.version.input", ".current.plugin.version.input", oMin:"5 0", oMax:"-5 0", text:$"{pluginInfo.Version}", fontSize:18, color:"1 1 1 1", align:TextAnchor.MiddleLeft, readOnly:true); UI.Input(ref container, ".current.plugin.url", ".current.plugin.url.input", ".current.plugin.url.input", oMin:"5 0", oMax:"-5 0", text:$"{pluginInfo.Url}", fontSize:18, color:"1 1 1 1", align:TextAnchor.MiddleLeft, command:$"UI_UC CHANGEURL {pluginInfo.Name}", limit:200); UI.Input(ref container, ".current.plugin.marketplace", ".current.plugin.marketplace.input", ".current.plugin.marketplace.input", oMin:"5 0", oMax:"-5 0", text:$"{pluginInfo.Marketplace}", fontSize:18, color:"1 1 1 1", align:TextAnchor.MiddleLeft, readOnly:true); UI.Button(ref container, ".current.plugin.bg", ".current.plugin.ignore", ".current.plugin.ignore", aMin:"0 1", aMax:"1 1", oMin:"5 -175", oMax:"-5 -155",command:$"UI_UC CHANGEIGNORE {pluginInfo.Name}", text:$"Ignore the plugin {pluginInfo.Ignore}",align:TextAnchor.MiddleLeft); UI.Create(player, container); } private void ShowUIPlugins(BasePlayer player, string search = "", int page = 0) { var container = new CuiElementContainer(); UI.Panel(ref container, ".plugins.bg", ".plugins", ".plugins", bgColor:"0 0 0 0"); var searchSort = (!string.IsNullOrEmpty(search) ? _config.ListOfPlugins.Where(x => x.Name.ToLower().Contains(search.ToLower()) || x.Author.ToLower().Contains(search.ToLower())) : _config.ListOfPlugins).OrderByDescending(x => x.IsFounded).ThenByDescending(x => x.Ignore); var posX = 8; var posY = -70; foreach (var check in searchSort.Skip(page * 63).Take(63)) { UI.Button(ref container, ".plugins", ".plugin" + posX + posY, ".plugin" + posX + posY, "0 1", "0 1", $"{posX} {posY}", $"{posX + 108} {posY + 45}", $"{check.Name}\n[{check.Author}]", 12, command:$"UI_UC CHECK {check.Name}"); posX += 113; if (posX < 690) continue; posX = 8; posY -= 45; } if (page > 0) UI.Button(ref container, ".plugins", ".previous", ".previous", "0.5 0", "0.5 0", "-25 5", "-5 25", "<", command:$"UI_UC PAGE {page - 1} {search}"); UI.Label(ref container, ".plugins", ".page", ".page", "0.5 0", "0.5 0", "-5 5", "5 25", $"{page + 1}"); if (page + 1 < _config.ListOfPlugins.Count / 63f) UI.Button(ref container, ".plugins", ".next", ".next","0.5 0", "0.5 0", "5 5", "25 25", ">", command:$"UI_UC PAGE {page + 1} {search}"); UI.Create(player, container); } private void ShowUIBG(BasePlayer player) { var container = new CuiElementContainer(); UI.MainParent(ref container); UI.Button(ref container, ".bg", ".bg", oMin:"-640 -360", oMax:"640 360", material:"assets/content/ui/uibackgroundblur-ingamemenu.mat", bgColor:"0.13 0.13 0.13 0.85"); UI.Panel(ref container, ".bg", ".plugins.bg", oMin:"-400 -325", oMax:"400 125", material:"assets/content/ui/binocular_overlay.mat", bgColor:"0.25 0.25 0.25 0.95"); UI.Panel(ref container, ".bg", ".search.bg", oMin:"-250 105", oMax:"250 145", material:"assets/content/ui/binocular_overlay.mat", bgColor:"0.15 0.15 0.15 0.95"); UI.Label(ref container, ".search.bg", oMin:"10 0", text:"Search:", color:"0.85 0.85 0.85 0.95", align:TextAnchor.MiddleLeft); UI.Panel(ref container, ".search.bg", ".search.input", oMin:"65 5", oMax:"-10 -5", material:"assets/content/ui/uibackgroundblur-notice.mat", bgColor:"0.75 0.75 0.75 0.6"); UI.Input(ref container, ".search.input", oMin:"5 0", oMax:"-5 0", text:"", fontSize:18, color:"1 1 1 1", autoFocus:true, command:"UI_UC SEARCH"); UI.Panel(ref container, ".bg", ".current.plugin.bg", oMin:"-250 160", oMax:"250 339", material:"assets/content/ui/binocular_overlay.mat", bgColor:"0.25 0.25 0.25 0.95"); UI.Label(ref container, ".current.plugin.bg", aMin:"0 1", aMax:"1 1", oMin:"5 -30", oMax:"0 -5", text:"Name:",align:TextAnchor.MiddleLeft); UI.Panel(ref container, ".current.plugin.bg", ".current.plugin.name", aMin:"0 1", aMax:"1 1", oMin:"95 -30", oMax:"-5 -5", bgColor:"0.85 0.85 0.85 0.25"); UI.Label(ref container, ".current.plugin.bg", aMin:"0 1", aMax:"1 1", oMin:"5 -60", oMax:"0 -35", text:"Author:",align:TextAnchor.MiddleLeft); UI.Panel(ref container, ".current.plugin.bg", ".current.plugin.author", aMin:"0 1", aMax:"1 1", oMin:"95 -60", oMax:"-5 -35", bgColor:"0.85 0.85 0.85 0.25"); UI.Label(ref container, ".current.plugin.bg", aMin:"0 1", aMax:"1 1", oMin:"5 -90", oMax:"0 -65", text:"Version:",align:TextAnchor.MiddleLeft); UI.Panel(ref container, ".current.plugin.bg", ".current.plugin.version", aMin:"0 1", aMax:"1 1", oMin:"95 -90", oMax:"-5 -65", bgColor:"0.85 0.85 0.85 0.25"); UI.Label(ref container, ".current.plugin.bg", aMin:"0 1", aMax:"1 1", oMin:"5 -120", oMax:"0 -95", text:"URL*:",align:TextAnchor.MiddleLeft); UI.Panel(ref container, ".current.plugin.bg", ".current.plugin.url", aMin:"0 1", aMax:"1 1", oMin:"95 -120", oMax:"-5 -95", bgColor:"0.85 0.85 0.85 0.65"); UI.Label(ref container, ".current.plugin.bg", aMin:"0 1", aMax:"1 1", oMin:"5 -150", oMax:"0 -125", text:"Marketplace:",align:TextAnchor.MiddleLeft); UI.Panel(ref container, ".current.plugin.bg", ".current.plugin.marketplace", aMin:"0 1", aMax:"1 1", oMin:"95 -150", oMax:"-5 -125", bgColor:"0.85 0.85 0.85 0.25"); UI.Create(player, container); ShowUIPlugins(player); ShowUICurrentPluginInfo(player, _config.ListOfPlugins.OrderByDescending(x => x.IsFounded).ThenByDescending(x => x.Ignore).First()); } #endregion #region Language private void SendMessage(BasePlayer player, string msg, params object[] args) => Player.Message(player, GetMsg(player.UserIDString, msg, args)); private string GetMsg(string player, string msg, params object[] args) => string.Format(lang.GetMessage(msg, this, player), args); protected override void LoadDefaultMessages() { lang.RegisterMessages(new Dictionary { [""] = "" }, this); } #endregion #region Config private Configuration _config; protected override void LoadConfig() { base.LoadConfig(); try { _config = Config.ReadObject(); if (_config == null) throw new Exception(); SaveConfig(); } catch { PrintError("Your configuration file contains an error. Using default configuration values."); LoadDefaultConfig(); } } protected override void SaveConfig() { if (_config.AutoReload) _config.JSON = JsonConvert.SerializeObject(_config); Config.WriteObject(_config); } protected override void LoadDefaultConfig() => _config = new Configuration(); private IEnumerator ConfigCheck() { var jError = string.Empty; while (true) { if (!IsLoaded) yield break; try { var checkConfig = Config.ReadObject(); if (checkConfig == null || JsonConvert.SerializeObject(Config.ReadObject()) == _config.JSON) throw new Exception(); Interface.Oxide.ReloadPlugin(Name); jError = string.Empty; } catch { if (!string.IsNullOrEmpty(jError) && jError != File.ReadAllText(Path.Combine(Manager.ConfigPath, Name + ".json"))) { jError = File.ReadAllText(Path.Combine(Manager.ConfigPath, Name + ".json")); PrintError("Your configuration file contains an error. Using default configuration values."); } } yield return new WaitForSeconds(2f); } } #endregion #region GUI private class UI { private const string Layer = "UI_UpdateChecker"; public static void MainParent(ref CuiElementContainer container, string name = null, string aMin = "0.5 0.5", string aMax = "0.5 0.5", bool overAll = true, bool keyboardEnabled = true, bool cursorEnabled = true) => container.Add(new CuiPanel { KeyboardEnabled = keyboardEnabled, CursorEnabled = cursorEnabled, RectTransform = { AnchorMin = aMin, AnchorMax = aMax }, Image = { Color = "0 0 0 0" } }, overAll ? "Overlay" : "Hud", Layer + ".bg" + name, Layer + ".bg" + name); public static void Panel(ref CuiElementContainer container, string parent, string name = null, string destroy = null, string aMin = "0 0", string aMax = "1 1", string oMin = "0 0", string oMax = "0 0", string bgColor = "0.33 0.33 0.33 1", string material = null, string sprite = null, int itemID = 0, ulong skinID = 0) => container.Add(new CuiElement { Parent = Layer + parent, Name = name != null && name.Contains(".other.") ? name.Replace(".other.", "") : Layer + name, DestroyUi = destroy == null ? null : Layer + destroy, Components = { new CuiRectTransformComponent { AnchorMin = aMin, AnchorMax = aMax, OffsetMin = oMin, OffsetMax = oMax }, new CuiImageComponent { Color = HexToRustFormat(bgColor), Material = material, Sprite = sprite, ItemId = itemID, SkinId = skinID }, }, }); public static void Icon(ref CuiElementContainer container, string parent, string name = null, string destroy = null, string aMin = "0 0", string aMax = "1 1", string oMin = "0 0", string oMax = "0 0", int itemID = 0, ulong skinID = 0) => container.Add(new CuiElement { Parent = Layer + parent, Name = Layer + name, DestroyUi = destroy == null ? null : Layer + destroy, Components = { new CuiRectTransformComponent { AnchorMin = aMin, AnchorMax = aMax, OffsetMin = oMin, OffsetMax = oMax }, new CuiImageComponent { ItemId = itemID, SkinId = skinID }, }, }); public static void Image(ref CuiElementContainer container, string parent, string name = null, string destroy = null, string aMin = "0 0", string aMax = "1 1", string oMin = "0 0", string oMax = "0 0", string image = "", string color = "1 1 1 1") => container.Add(new CuiElement { Parent = Layer + parent, Name = Layer + name, DestroyUi = destroy == null ? null : Layer + destroy, Components = { new CuiRectTransformComponent { AnchorMin = aMin, AnchorMax = aMax, OffsetMin = oMin, OffsetMax = oMax }, new CuiRawImageComponent { Png = !image.StartsWith("http") && !image.StartsWith("www") ? image : null, Url = image.StartsWith("http") || image.StartsWith("www") ? image : null, Color = HexToRustFormat(color), Sprite = "assets/content/textures/generic/fulltransparent.tga" }, }, }); public static void Label(ref CuiElementContainer container, string parent, string name = null, string destroy = null, string aMin = "0 0", string aMax = "1 1", string oMin = "0 0", string oMax = "0 0", string text = null, int fontSize = 16, string color = "1 1 1 1", TextAnchor align = TextAnchor.MiddleCenter, string outlineDistance = null, string outlineColor = "0 0 0 1", VerticalWrapMode wrapMode = VerticalWrapMode.Truncate, string font = "robotocondensed-regular.ttf") => container.Add(new CuiElement { Parent = Layer + parent, Name = Layer + name, DestroyUi = destroy == null ? null : Layer + destroy, Components = { new CuiRectTransformComponent { AnchorMin = aMin, AnchorMax = aMax, OffsetMin = oMin, OffsetMax = oMax }, new CuiTextComponent { Text = text, FontSize = fontSize, Color = HexToRustFormat(color), Align = align, Font = font, VerticalOverflow = wrapMode }, outlineDistance == null ? new CuiOutlineComponent { Distance = "0 0", Color = "0 0 0 0" } : new CuiOutlineComponent { Distance = outlineDistance, Color = HexToRustFormat(outlineColor) }, }, }); public static void Button(ref CuiElementContainer container, string parent, string name = null, string destroy = null, string aMin = "0 0", string aMax = "1 1", string oMin = "0 0", string oMax = "0 0", string text = null, int fontSize = 16, string color = "1 1 1 1", string command = null, string bgColor = "0 0 0 0", VerticalWrapMode wrapMode = VerticalWrapMode.Truncate, TextAnchor align = TextAnchor.MiddleCenter, string font = "robotocondensed-regular.ttf", string material = null, string sprite = null) => container.Add(new CuiButton { RectTransform = { AnchorMin = aMin, AnchorMax = aMax, OffsetMin = oMin, OffsetMax = oMax }, Text = { Text = text, FontSize = fontSize, Color = HexToRustFormat(color), Align = align, Font = font, VerticalOverflow = wrapMode }, Button = { Command = command, Close = command == null ? Layer + name : null, Color = HexToRustFormat(bgColor), Material = material, Sprite = sprite } }, Layer + parent, command == null ? null : Layer + name, destroy == null ? null : Layer + destroy); public static void Input(ref CuiElementContainer container, string parent, string name = null, string destroy = null, string aMin = "0 0", string aMax = "1 1", string oMin = "0 0", string oMax = "0 0", string text = null, int limit = 40, int fontSize = 16, string color = "1 1 1 1", string command = null, TextAnchor align = TextAnchor.MiddleCenter, bool autoFocus = false, bool hudMenuInput = false, bool readOnly = false, bool isPassword = false, bool needsKeyboard = false, bool singleLine = true, string font = "robotocondensed-regular.ttf") => container.Add(new CuiElement { Parent = Layer + parent, Name = Layer + name, DestroyUi = destroy == null ? null : Layer + destroy, Components = { new CuiRectTransformComponent { AnchorMin = aMin, AnchorMax = aMax, OffsetMin = oMin, OffsetMax = oMax }, new CuiInputFieldComponent { Text = text, Command = command, CharsLimit = limit, FontSize = fontSize, Color = HexToRustFormat(color), Align = align, Font = font, Autofocus = autoFocus, IsPassword = isPassword, ReadOnly = readOnly, HudMenuInput = hudMenuInput, NeedsKeyboard = needsKeyboard, LineType = singleLine ? InputField.LineType.SingleLine : InputField.LineType.MultiLineNewline }, } }); public static void Outline(ref CuiElementContainer container, string layer, string size = "1 1 1 1", string color = "0 0 0 1", bool external = false) { var borders = size.Split(' '); if (borders[0] != "0") Panel(ref container, layer, aMin: "0 1", aMax: "1 1", oMin: $"-{borders[0]} {(external ? "0" : "-" + borders[0])}", oMax: $"{borders[0]} {(external ? borders[0] : "0")}", bgColor: color); if (borders[1] != "0") Panel(ref container, layer, aMin: "1 0", aMax: "1 1", oMin: $"{(external ? "0" : "-" + borders[1])} -{borders[1]}", oMax: $"{(external ? borders[1] : "0")} {borders[1]}", bgColor: color); if (borders[2] != "0") Panel(ref container, layer, aMin: "0 0", aMax: "1 0", oMin: $"-{borders[2]} {(external ? "-" + borders[2] : "0")}", oMax: $"{borders[2]} {(external ? "0" : borders[2])}", bgColor: color); if (borders[3] != "0") Panel(ref container, layer, aMin: "0 0", aMax: "0 1", oMin: $"{(external ? "-" + borders[3] : "0")} -{borders[3]}", oMax: $"{(external ? "0" : borders[3])} {borders[3]}", bgColor: color); } public static string HexToRustFormat(string hex) { if (string.IsNullOrEmpty(hex)) return hex; Color color; if (hex.Contains(":")) return ColorUtility.TryParseHtmlString(hex.Substring(0, hex.IndexOf(":")), out color) ? $"{color.r:F2} {color.g:F2} {color.b:F2} {hex.Substring(hex.IndexOf(":") + 1, hex.Length - hex.IndexOf(":") - 1)}" : hex; return ColorUtility.TryParseHtmlString(hex, out color) ? $"{color.r:F2} {color.g:F2} {color.b:F2} {color.a:F2}" : hex; } public static void Create(BasePlayer player, CuiElementContainer container) { CuiHelper.AddUi(player, container); } public static void CreateToAll(CuiElementContainer container, string layer) { foreach (var player in BasePlayer.activePlayerList) CuiHelper.AddUi(player, container); } public static void Destroy(BasePlayer player, string layer) => CuiHelper.DestroyUi(player, Layer + layer); public static void DestroyToAll(string layer) { foreach (var player in BasePlayer.activePlayerList) Destroy(player, layer); } } #endregion } }