using Oxide.Core; using Oxide.Core.Libraries.Covalence; using Oxide.Game.Rust.Cui; using Oxide.Core.Plugins; using System.Collections; using System.Collections.Generic; using UnityEngine; using Newtonsoft.Json; namespace Oxide.Plugins { [Info("Underwear Selection", "Marat", "1.0.4")] [Description("Allows players to customize their underwear appearance")] class UnderwearSelection : RustPlugin { [PluginReference] private Plugin ImageLibrary; #region Field private const string InitialLayer = "UI_UnderwearLayer"; private const string permissionUse = "underwearselection.use"; #endregion #region Oxide Hooks private void OnServerInitialized() { if (ImageLibrary == null) { PrintError("[ImageLibrary] not found! Plugin is disabled!"); Interface.Oxide.UnloadPlugin(Title); return; } foreach (var list in config.Underwear) { ImageLibrary.Call("AddImage", list.UrlMale, $"{list.Title}.Male"); ImageLibrary.Call("AddImage", list.UrlFemale, $"{list.Title}.Female"); } LoadData(); AddCovalenceCommand(config.Commands, nameof(CmdChangeWear)); permission.RegisterPermission(permissionUse, this); for (int i = 0; i < BasePlayer.activePlayerList.Count; i++) { OnPlayerConnected(BasePlayer.activePlayerList[i]); } } private void Unload() { for (int i = 0; i < BasePlayer.activePlayerList.Count; i++) { var player = BasePlayer.activePlayerList[i]; CuiHelper.DestroyUi(player, InitialLayer); player.nextUnderwearValidationTime = Time.time + 0.2f; ServerMgr.Instance?.StopCoroutine(PreloadImages(player)); } SaveData(); config = null; } private void OnServerSave() => SaveData(); private void OnPlayerConnected(BasePlayer player) { if (player == null || !player.IsConnected) return; if (player.IsReceivingSnapshot) { timer.Once(1f, () => OnPlayerConnected(player)); return; } ServerMgr.Instance?.StartCoroutine(PreloadImages(player)); if (!storedData.PlayerData.TryGetValue(player.userID, out var data)) { data = new Data { id = player.lastValidUnderwearSkin, enable = false }; storedData.PlayerData.Add(player.userID, data); } if (config.SkinConnection != 0) { data.enable = true; UnderwearChange(player, config.SkinConnection); return; } if (!permission.UserHasPermission(player.UserIDString, permissionUse)) return; UnderwearChange(player, data.id); } private void OnPlayerDisconnected(BasePlayer player) { player.nextUnderwearValidationTime = Time.time + 0.2f; ServerMgr.Instance?.StopCoroutine(PreloadImages(player)); } private void OnPlayerRespawned(BasePlayer player) { if (player == null || !player.IsConnected) return; if (!storedData.PlayerData.TryGetValue(player.userID, out var data)) { data = new Data { id = player.lastValidUnderwearSkin, enable = false }; storedData.PlayerData.Add(player.userID, data); } if (config.UseRandomSkin) { var random = new System.Random(); var randomUnderwear = config.Underwear[random.Next(config.Underwear.Count)]; data.enable = true; UnderwearChange(player, randomUnderwear.Id); return; } if (!permission.UserHasPermission(player.UserIDString, permissionUse)) return; UnderwearChange(player, data.id); } #endregion #region Configuration private static PluginConfig config; private class PluginConfig { [JsonProperty("Commands")] public string[] Commands; [JsonProperty("Forced underwear skin when a player connects")] public uint SkinConnection; [JsonProperty("Random underwear skin upon respawn")] public bool UseRandomSkin; [JsonProperty("Image and description")] public List Underwear; } protected override void LoadDefaultConfig() { config = new PluginConfig { Commands = new string[] {"uw", "wear", "underwear"}, SkinConnection = 0, UseRandomSkin = false, Underwear = new List() { new("Scribble", 792014640, "https://i.ibb.co/cvFYv4B/scribble-male.png", "https://i.ibb.co/mHLj0FP/scribble-female.png"), new("Gradient", 241501709, "https://i.ibb.co/MG5GMKh/gradient-male.png", "https://i.ibb.co/b70811b/gradient-female.png"), new("PalmLeaves", 1756736103, "https://i.ibb.co/YQFnzZm/palmleaves-male.png", "https://i.ibb.co/z6dhs5R/palmleaves-female.png"), new("Bikini/Rapido", 359039573, "https://i.ibb.co/42pYF1D/rapido-male.png", "https://i.ibb.co/j3wZXF4/pink-bikini.png"), new("Coconut", 3797783720, "https://i.ibb.co/5RcSNsW/coconut-female-male.png", "https://i.ibb.co/5RcSNsW/coconut-female-male.png"), new("MummyWraps", 1154108357, "https://i.ibb.co/3SWCyj2/mummywraps-male.png", "https://i.ibb.co/p15j3cD/mummywraps-female.png"), new("Purple", 1967073602, "https://i.ibb.co/yVCBwSD/purple-male.png", "https://i.ibb.co/0QnjzKT/purple-female.png"), new("GrassSkirt", 4122325535, "https://i.ibb.co/8XvwtzS/grassskirt-male.png", "https://i.ibb.co/T4Yg0Cm/grassskirt-top-female.png") } }; } private class ItemInfo { public string Title; public uint Id; public string UrlMale; public string UrlFemale; public ItemInfo(string title, uint id, string urlMale, string urlFemale) { Title = title; Id = id; UrlMale = urlMale; UrlFemale = urlFemale; } } protected override void SaveConfig() => Config.WriteObject(config); protected override void LoadConfig() { base.LoadConfig(); try { config = Config.ReadObject(); if (config == null) LoadDefaultConfig(); } catch { PrintWarning("The config file contains an error and has been replaced with the default config."); LoadDefaultConfig(); } SaveConfig(); } #endregion #region Commands private void CmdChangeWear(IPlayer ipPlayer, string command, string[] arg) { var player = ipPlayer?.Object as BasePlayer; if (player == null) return; if (!permission.UserHasPermission(player.UserIDString, permissionUse)) { PrintToChat(player, GetMessage("Lang_NoPermissions", player)); return; } InitializeLayers(player, true); } [ConsoleCommand("setwear")] private void CmdConsoleExpPlus(ConsoleSystem.Arg arg) { if (!arg.IsAdmin) return; if (arg.Args == null || arg.Args.Length <= 0 || arg.Args.Length < 2) { SendReply(arg, "Invalid syntax. Use setwear {name/steamId} {skinId}"); return; } var player = BasePlayer.Find(arg.Args[0]); if (player == null) { SendReply(arg, "Player not found. Check the correct spelling of the player name or steamId."); return; } if (!uint.TryParse(arg.Args[1], out uint index) || config.Underwear.Find(x => x.Id == index) == null) { SendReply(arg, $"SkinId [{index}] not found. Check the correct spelling of the skin ID."); return; } if (!storedData.PlayerData.ContainsKey(player.userID)) { SendReply(arg, $"Player not found. {player} is missing from the plugin database."); return; } var data = storedData.PlayerData[player.userID]; data.id = index; data.enable = true; UnderwearChange(player, index); SendReply(arg, $"SkinId [{index}] was successfully set to player {player}."); } [ConsoleCommand("UI_WearController")] private void CmdConsoleHandler(ConsoleSystem.Arg arg) { var player = arg.Player(); if (player == null || !arg.HasArgs(1)) return; var data = storedData.PlayerData[player.userID]; switch (arg.Args[0].ToLower()) { case "choose": { if (!uint.TryParse(arg.Args[1], out uint index)) return; data.id = index; data.enable = true; UnderwearChange(player, index); InitializeLayers(player, false); EffectNetwork.Send(new Effect("assets/prefabs/npc/autoturret/effects/targetlost.prefab", player, 0, new Vector3(), new Vector3()), player.Connection); break; } case "enabled": { var enable = !data.enable; data.enable = enable; if (!data.enable) player.nextUnderwearValidationTime = Time.time + 0.2f; InitializeLayers(player, false); break; } } } #endregion #region Methods private void UnderwearChange(BasePlayer player, uint id) { if (!storedData.PlayerData[player.userID].enable) return; player.nextUnderwearValidationTime = float.PositiveInfinity; player.lastValidUnderwearSkin = id; player.SendNetworkUpdateImmediate(); } private string GetMessage(string key, BasePlayer player) { return lang.GetMessage(key, this, player.UserIDString); } #endregion #region Interfaces private void InitializeLayers(BasePlayer player, bool update) { float fade = !update ? 0f : 0.25f; var data = storedData.PlayerData[player.userID]; CuiElementContainer container = new CuiElementContainer(); container.Add(new CuiElement() { Parent = "Overlay", Name = InitialLayer, DestroyUi = InitialLayer, Components = { new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "0 0", OffsetMax = "0 0" }, new CuiImageComponent { Color = "0.235 0.227 0.2 0.9" }, new CuiNeedsCursorComponent() } }); container.Add(new CuiButton() { RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "0 0", OffsetMax = "0 0" }, Button = { Close = InitialLayer, Color = "0.141 0.137 0.096 0.98", Sprite = "assets/content/ui/ui.background.transparent.radial.psd" } }, InitialLayer); container.Add(new CuiPanel() { RectTransform = { AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = $"-440 -300", OffsetMax = $"440 300" }, Image = { Color = "0.11 0.12 0.10 0.8", Material = "assets/content/ui/uibackgroundblur.mat", FadeIn = fade } }, InitialLayer, InitialLayer + ".Main"); container.Add(new CuiPanel() { RectTransform = { AnchorMin = "0 1", AnchorMax = "1 1", OffsetMin = $"0 -50", OffsetMax = $"0 0" }, Image = { Color = "0.08 0.08 0.08 0.8", Material = "assets/content/ui/uibackgroundblur.mat", FadeIn = fade } }, InitialLayer + ".Main", InitialLayer + ".Title"); container.Add(new CuiLabel() { RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "0 0", OffsetMax = "0 0" }, Text = { Text = GetMessage("Lang_Title", player).ToUpper(), Color = "0.78 0.74 0.70 1.0", FontSize = 20, Font = "robotocondensed-bold.ttf", Align = TextAnchor.MiddleCenter, FadeIn = fade } }, InitialLayer + ".Title"); container.Add(new CuiPanel() { RectTransform = { AnchorMin = "0 0.5", AnchorMax = "0 0.5", OffsetMin = $"20 -13", OffsetMax = $"120 13" }, Image = { Color = "0.11 0.12 0.10 0.8", Material = "assets/content/ui/uibackgroundblur.mat", FadeIn = fade } }, InitialLayer + ".Title", InitialLayer + ".Toggle"); var text = GetMessage(data.enable ? "Lang_Enable" : "Lang_Disable", player); var offsetMin = data.enable ? "1 1" : "50 1"; var offsetMax = data.enable ? "-50 -1" : "-1 -1"; container.Add(new CuiButton() { RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = offsetMin, OffsetMax = offsetMax }, Button = { Command = $"UI_WearController enabled", Color = data.enable ? "0.36 0.44 0.22 1.0" : "0.71 0.22 0.15 1.0", Material = "assets/content/ui/uibackgroundblur.mat" }, Text = { Text = text.ToUpper(), Color = data.enable ? "0.78 0.74 0.70 1.0" : "0.78 0.74 0.70 0.6", FontSize = 12, Font = "robotocondensed-bold.ttf", Align = TextAnchor.MiddleCenter } }, InitialLayer + ".Toggle"); container.Add(new CuiButton() { RectTransform = { AnchorMin = "1 1", AnchorMax = "1 1", OffsetMin = "-25 -25", OffsetMax = "-5 -5" }, Button = { Close = InitialLayer, Color = "0.71 0.22 0.15 1.0", Sprite = "assets/icons/close.png" } }, InitialLayer + ".Main"); const int marginTop = 18, margin = 15, width = 200, height = 250; for (var i = 0; i < config.Underwear.Count; i++) { var offsetX = i % 4 * (width + margin) - (2 * width + 1.5 * margin); var offsetY = -i / 4 * (height + margin) - marginTop; container.Add(new CuiPanel() { RectTransform = { AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = $"{offsetX} {offsetY}", OffsetMax = $"{offsetX + width} {offsetY + height}" }, Image = { Color = "0.19 0.23 0.14 1.0", Material = "assets/content/ui/uibackgroundblur.mat", FadeIn = fade } }, InitialLayer + ".Main", InitialLayer + $".Button.{i}"); var isSelected = player.lastValidUnderwearSkin == config.Underwear[i].Id; var image = Underwear.IsFemale(player) ? $"{config.Underwear[i].Title}.Female" : $"{config.Underwear[i].Title}.Male"; container.Add(new CuiElement() { Parent = InitialLayer + $".Button.{i}", Name = InitialLayer + $".Image.{i}", DestroyUi = InitialLayer + $".Image.{i}", Components = { new CuiRectTransformComponent { AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = "-85 -72", OffsetMax = "85 98" }, new CuiRawImageComponent { Png = (string) ImageLibrary.Call("GetImage", image), Color = "1 1 1 1" } } }); if (isSelected) { container.Add(new CuiPanel() { RectTransform = { AnchorMin = "1 1", AnchorMax = "1 1", OffsetMin = "-30 -28", OffsetMax = "-10 -10" }, Image = { Color = "0.81 0.77 0.74 0.8", Sprite = "assets/icons/clothing.png" } }, InitialLayer + $".Button.{i}"); } container.Add(new CuiPanel() { RectTransform = { AnchorMin = "0 0", AnchorMax = "1 0", OffsetMin = "0 0", OffsetMax = "0 26" }, Image = { Color = "0 0 0 0.6" } }, InitialLayer + $".Button.{i}", InitialLayer + $".Title.{i}"); string[] parts = config.Underwear[i].Title.Split('/'); string genderTitle = Underwear.IsFemale(player) ? parts[0] : (parts.Length > 1 ? parts[1] : parts[0]); container.Add(new CuiLabel() { RectTransform = { AnchorMin = "0 0", AnchorMax = "0 0", OffsetMin = "10 0", OffsetMax = "125 26" }, Text = { Text = GetMessage($"{genderTitle}", player).ToUpper(), Color = "0.81 0.77 0.74 1.0", FontSize = 11, Font = "robotocondensed-bold.ttf", Align = TextAnchor.MiddleLeft } }, InitialLayer + $".Title.{i}"); var textTitle = GetMessage(isSelected ? "Lang_Installed" : "Lang_Apply", player); var colorTitle = !isSelected ? "0.59 0.84 0.18 1.0" : "0.30 0.65 0.90 1.0"; var colorButton = !isSelected ? "0.30 0.36 0.16 1.0" : "0.20 0.30 0.40 1.0"; var command = !isSelected ? $"UI_WearController choose {config.Underwear[i].Id}" : ""; container.Add(new CuiButton() { RectTransform = { AnchorMin = "1 0", AnchorMax = "1 0", OffsetMin = "-75 0", OffsetMax = "0 26" }, Button = { Command = command, Color = colorButton, Material = "assets/content/ui/uibackgroundblur.mat" }, Text = { Text = textTitle.ToUpper(), Color = colorTitle, FontSize = 11, Font = "robotocondensed-bold.ttf", Align = TextAnchor.MiddleCenter } }, InitialLayer + $".Title.{i}"); } CuiHelper.AddUi(player, container); } private IEnumerator PreloadImages(BasePlayer player) { if (player == null || !player.IsConnected) yield break; for (var i = 0; i < config.Underwear.Count; i++) { var image = $"{config.Underwear[i].Title}.{(Underwear.IsFemale(player) ? "Female" : "Male")}"; CuiElementContainer temp = new CuiElementContainer(); temp.Add(new CuiElement() { Parent = "Hud", Name = $".{i}", Components = { new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "0 0" }, new CuiRawImageComponent { Png = (string) ImageLibrary?.Call("GetImage", image) } } }); CuiHelper.AddUi(player, temp); yield return new WaitForSeconds(1.0f); CuiHelper.DestroyUi(player, $".{i}"); } } #endregion #region Language protected override void LoadDefaultMessages() { lang.RegisterMessages(new Dictionary { ["Lang_NoPermissions"] = "You don't have permission to use this command.", ["Lang_Title"] = "Underwear Selection", ["Lang_Apply"] = "Apply", ["Lang_Installed"] = "Installed", ["Lang_Enable"] = "On", ["Lang_Disable"] = "Off", ["Scribble"] = "Scribble", ["Gradient"] = "Gradient", ["PalmLeaves"] = "Palm Leaves", ["Bikini"] = "Pink Bikini", ["Rapido"] = "Rapido", ["Coconut"] = "Coconut", ["MummyWraps"] = "Mummy Wraps", ["Purple"] = "Purple", ["GrassSkirt"] = "Grass Skirt" }, this); lang.RegisterMessages(new Dictionary { ["Lang_NoPermissions"] = "У вас нет разрешения на использование этой команды.", ["Lang_Title"] = "Выбор нижнего белья", ["Lang_Apply"] = "Применить", ["Lang_Installed"] = "Установлен", ["Lang_Enable"] = "Вкл", ["Lang_Disable"] = "Выкл", ["Scribble"] = "Каракули", ["Gradient"] = "Градиент", ["PalmLeaves"] = "Пальмовые листья", ["Bikini"] = "Розовое бикини", ["Rapido"] = "Rapido", ["Coconut"] = "Кокос", ["MummyWraps"] = "Mummy Wraps", ["Purple"] = "Фиолетовое белье", ["GrassSkirt"] = "Травяная юбка" }, this, "ru"); } #endregion #region Data private StoredData storedData; private class StoredData { public Dictionary PlayerData = new Dictionary(); } private class Data { public uint id; public bool enable; } private void SaveData() { if (storedData != null) { Interface.Oxide.DataFileSystem.WriteObject($"{Name}_Data", storedData, true); } } private void LoadData() { storedData = Interface.Oxide.DataFileSystem.ReadObject($"{Name}_Data"); if (storedData == null) { storedData = new StoredData(); SaveData(); } } #endregion } }