C#:
// #define TESTING
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using Facepunch;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Libraries;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using Oxide.Plugins.ShopExtensionMethods;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
using Debug = UnityEngine.Debug;
using Global = Rust.Global;
using Random = UnityEngine.Random;
using Time = UnityEngine.Time;
#if CARBON
using Carbon.Base;
using Carbon.Modules;
#endif
namespace Oxide.Plugins
{
[Info("Shop", "Mevent", "2.4.12")]
public class Shop : RustPlugin
{
#region Fields
[PluginReference] private Plugin
ImageLibrary = null,
ServerPanel = null,
ItemCostCalculator = null,
LangAPI = null,
Notify = null,
UINotify = null,
NoEscape = null,
Duel = null,
Duelist = null;
private static Shop Instance;
private (bool spStatus, int categoryID)
_serverPanelCategory = (false, -1); // key - use serverPanel, value - category id
#if CARBON
private ImageDatabaseModule imageDatabase;
#endif
private const bool LangRu = false;
private readonly Dictionary<int, ShopItem> _shopItems = new();
private readonly Dictionary<ulong, Coroutine> _coroutines = new();
private readonly Dictionary<ulong, float> _lastCommandTime = new();
private readonly Dictionary<string, List<(int itemID, string shortName)>> _itemsCategories = new();
private readonly HashSet<string> _images = new();
private const string
Layer = "UI.Shop",
ModalLayer = "UI.Shop.Modal",
EditingLayer = "UI.Shop.Editing",
CmdMainConsole = "UI_Shop";
private const string
PERM_ADMIN = "shop.admin",
PERM_FREE_BYPASS = "shop.free",
PERM_SET_VM = "shop.setvm",
PERM_SET_NPC = "shop.setnpc",
PERM_BYPASS_DLC = "shop.bypass.dlc";
private const int _itemsPerTick = 10;
private readonly Dictionary<ulong, NPCShop> _openedShopsNPC = new();
private readonly HashSet<ulong> _adminModeUsers = new();
private bool _isEnabledFavorites;
private (bool status, string message) _initializedStatus = (false, string.Empty);
private bool _templateMigrationInProgress;
#endregion
#region Config
private static Configuration _config;
private class Configuration
{
#region Fields
[JsonProperty(PropertyName =
LangRu
? "Разрешение для использования плагина (прим: shop.use)"
: "Permission to use plugin (ex: shop.use)")]
public string Permission = string.Empty;
[JsonProperty(PropertyName = LangRu ? "Команды" : "Commands",
ObjectCreationHandling = ObjectCreationHandling.Replace)]
public string[] Commands = {"shop", "shops"};
[JsonProperty(PropertyName =
LangRu ? "Включить переводы денег между игроками?" : "Enable money transfers between players?")]
public bool Transfer = true;
[JsonProperty(PropertyName =
LangRu ? "Разрешить переводы денег оффлайн игрокам?" : "Allow money transfers to offline players?")]
public bool TransferToOfflinePlayers = false;
[JsonProperty(PropertyName = LangRu ? "Включить продажу предметов?" : "Enable selling items?")]
public bool EnableSelling = true;
[JsonProperty(PropertyName = LangRu ? "Включить логирование в консоль?" : "Enable logging to the console?")]
public bool LogToConsole = true;
[JsonProperty(PropertyName = LangRu ? "Включить логирование в файл?" : "Enable logging to the file?")]
public bool LogToFile = true;
[JsonProperty(PropertyName =
LangRu
? "Загружать изображения при подключении к серверу?"
: "Load images when logging into the server?")]
public bool LoginImages = true;
[JsonProperty(PropertyName = LangRu ? "Работать с Notify?" : "Work with Notify?")]
public bool UseNotify = true;
[JsonProperty(PropertyName = LangRu ? "Включить режим оффлайн изображений" : "Enable Offline Image Mode")]
public bool EnableOfflineImageMode = false;
[JsonProperty(PropertyName = LangRu ? "Включить работу с LangAPI?" : "Work with LangAPI?")]
public bool UseLangAPI = true;
[JsonProperty(PropertyName =
LangRu ? "Могут ли админы редактировать предметы? (флаг)" : "Can admins edit? (by flag)")]
public bool FlagAdmin = false;
[JsonProperty(PropertyName = LangRu ? "Поддержка NoEscape" : "Block (NoEscape)")]
public bool BlockNoEscape = false;
[JsonProperty(PropertyName = LangRu ? "Включить блокировку после вайпа" : "Wipe Block")]
public bool WipeCooldown = false;
[JsonProperty(PropertyName = LangRu ? "Длительность блокировки после вайпа" : "Wipe Cooldown")]
public float WipeCooldownTimer = 3600;
[JsonProperty(PropertyName = LangRu ? "Включить блокировку после респавна" : "Respawn Block")]
public bool RespawnCooldown = true;
[JsonProperty(PropertyName = LangRu ? "Длительность блокировки после респавна" : "Respawn Cooldown")]
public float RespawnCooldownTimer = 60;
[JsonProperty(PropertyName = LangRu ? "Запрещать открытие на дуэлях?" : "Blocking the opening in duels?")]
public bool UseDuels = false;
[JsonProperty(PropertyName =
LangRu ? "Задержка между действиями (в секундах)" : "Cooldown between actions (in seconds)")]
public float CooldownBetweenActions = 0.1f;
[JsonProperty(PropertyName =
LangRu ? "Задержка между загрузкой изображений" : "Delay between loading images")]
public float ImagesDelay = 1f;
[JsonProperty(PropertyName = LangRu ? "Экономика" : "Economy")]
public EconomyEntry Economy = new()
{
Type = EconomyType.Plugin,
AddHook = "Deposit",
BalanceHook = "Balance",
RemoveHook = "Withdraw",
Plug = "Economics",
ShortName = "scrap",
DisplayName = string.Empty,
Skin = 0,
EconomyTitle = new EconomyTitle("Economics"),
EconomyBalance = new EconomyTitle("${0}"),
EconomyPrice = new EconomyTitle("${0}"),
EconomyFooterPrice = new EconomyTitle("${0}")
};
[JsonProperty(PropertyName = LangRu ? "Дополнительная экономика" : "Additional Economics",
ObjectCreationHandling = ObjectCreationHandling.Replace)]
public List<AdditionalEconomy> AdditionalEconomics = new()
{
new AdditionalEconomy
{
ID = 1,
Enabled = true,
Type = EconomyType.Plugin,
AddHook = "AddPoints",
BalanceHook = "CheckPoints",
RemoveHook = "TakePoints",
Plug = "ServerRewards",
ShortName = "scrap",
DisplayName = string.Empty,
Skin = 0,
EconomyTitle = new EconomyTitle("Server Rewards"),
EconomyBalance = new EconomyTitle("{0} RP"),
EconomyPrice = new EconomyTitle("{0} RP"),
EconomyFooterPrice = new EconomyTitle("{0} RP")
}
};
[JsonProperty(
PropertyName = LangRu
? "Магазины NPC (NPC ID - категории магазина)"
: "NPC Shops (NPC ID - shop categories)",
ObjectCreationHandling = ObjectCreationHandling.Replace)]
public Dictionary<string, NPCShop> NPCs = new()
{
["1234567"] = new NPCShop
{
Permission = string.Empty,
Categories = new List<string>
{
"Tool",
"Food"
}
},
["7654321"] = new NPCShop
{
Permission = string.Empty,
Categories = new List<string>
{
"Weapon",
"Ammunition"
}
},
["4644687478"] = new NPCShop
{
Permission = "shop.usenpc",
Categories = new List<string>
{
"*"
}
}
};
[JsonProperty(PropertyName = LangRu ? "Интерфейс" : "Interface")]
public UserInterface UI = new()
{
DisplayType = "OverlayNonScaled",
RoundDigits = 5,
Color1 = IColor.Create("#161617"),
Color2 = IColor.Create("#4B68FF"),
Color3 = IColor.Create("#0E0E10"),
Color5 = IColor.Create("#FF4B4B"),
Color7 = IColor.Create("#CD3838"),
Color8 = IColor.Create("#FFFFFF"),
Color9 = IColor.Create("#4B68FF", 33),
Color10 = IColor.Create("#4B68FF", 50),
Color11 = IColor.Create("#161617", 95),
Color12 = IColor.Create("#161617", 80),
Color13 = IColor.Create("#0E0E10", 98)
};
[JsonProperty(PropertyName = LangRu ? "Заблокированные скины для продажи" : "Blocked skins for sell",
ObjectCreationHandling = ObjectCreationHandling.Replace)]
public Dictionary<string, List<ulong>> BlockedSkins = new()
{
["short name"] = new List<ulong>
{
52,
25
},
["short name 2"] = new List<ulong>
{
52,
25
}
};
[JsonProperty(PropertyName = LangRu ? "Auto-Wipe настройки" : "Auto-Wipe Settings")]
public WipeSettings Wipe = new()
{
Cooldown = true,
Players = true,
Limits = true
};
[JsonProperty(
PropertyName = LangRu
? "Кастомные Торговые Автоматы (Entity ID - settings)"
: "Custom Vending Machines (Entity ID - settings)",
ObjectCreationHandling = ObjectCreationHandling.Replace)]
public Dictionary<ulong, CustomVendingEntry> CustomVending =
new()
{
[123343941] = new CustomVendingEntry
{
Permission = string.Empty,
Categories = new List<string>
{
"Cars", "Misc"
}
}
};
[JsonProperty(PropertyName =
LangRu
? "Настройки контейнеров для продажи товаров"
: "Settings available containers for selling item")]
public SellContainers SellContainers = new()
{
Enabled = true,
Containers = new List<string>
{
"main",
"belt"
}
};
[JsonProperty(PropertyName = LangRu ? "Настройки повторной покупки" : "Buy Again Settings")]
public BuyAgainConf BuyAgain = new()
{
Enabled = false,
Permission = string.Empty,
Image = "assets/icons/history_servers.png"
};
[JsonProperty(PropertyName = LangRu ? "Настройки форматирования" : "Formatting Settings")]
public FormattingConf Formatting = new()
{
BuyPriceFormat = "G",
SellPriceFormat = "G",
ShoppingBagCostFormat = "G",
BalanceFormat = "G"
};
[JsonProperty(PropertyName = LangRu ? "Скидка" : "Discount")]
public DiscountConf Discount = new()
{
Enabled = true,
Discount = new Dictionary<string, int>
{
["shop.default"] = 0,
["shop.vip"] = 10
}
};
[JsonProperty(PropertyName = LangRu ? "Уведомления" : "Notifications")]
public NotificationsConf Notifications = new()
{
GeneralNotifications = new Dictionary<string, NotificationsConf.BaseNotification>
{
[NoPermission] = NotificationsConf.BaseNotification.Create(1),
[ErrorSyntax] = NotificationsConf.BaseNotification.Create(1),
[VMNotFoundCategories] = NotificationsConf.BaseNotification.Create(1),
[VMNotFound] = NotificationsConf.BaseNotification.Create(1),
[VMExists] = NotificationsConf.BaseNotification.Create(1),
[VMInstalled] = NotificationsConf.BaseNotification.Create(0),
[NPCNotFound] = NotificationsConf.BaseNotification.Create(1),
[NPCInstalled] = NotificationsConf.BaseNotification.Create(0),
[BuyCooldownMessage] = NotificationsConf.BaseNotification.Create(1),
[ReceivedItems] = NotificationsConf.BaseNotification.Create(0),
[SellNotify] = NotificationsConf.BaseNotification.Create(0),
[SuccessfulTransfer] = NotificationsConf.BaseNotification.Create(0),
[MsgIsFavoriteItem] = NotificationsConf.BaseNotification.Create(1),
[MsgAddedToFavoriteItem] = NotificationsConf.BaseNotification.Create(0),
[MsgNoFavoriteItem] = NotificationsConf.BaseNotification.Create(1),
[MsgRemovedFromFavoriteItem] = NotificationsConf.BaseNotification.Create(0),
[BuyLimitReached] = NotificationsConf.BaseNotification.Create(1),
[DailyBuyLimitReached] = NotificationsConf.BaseNotification.Create(1),
[UIMsgShopInInitialization] = NotificationsConf.BaseNotification.Create(1),
[NoILError] = NotificationsConf.BaseNotification.Create(1),
[NoUseDuel] = NotificationsConf.BaseNotification.Create(1),
[UIDLCItem] = NotificationsConf.BaseNotification.Create(1)
}
};
[JsonProperty(PropertyName = LangRu ? "Настройки Discord логирования" : "Discord Logging Settings")]
public DiscordSettings DiscordConfig = new();
public VersionNumber Version;
#endregion
#region Classes
public class NotificationsConf
{
#region Fields
[JsonProperty(
PropertyName = LangRu
? "Основные уведомления (сообщение – настройки оповещения)"
: "General Notifications (message - settings notification)",
ObjectCreationHandling = ObjectCreationHandling.Replace)]
public Dictionary<string, BaseNotification> GeneralNotifications = new();
#endregion
#region Public Methods
public void ShowNotify(BasePlayer player, string key, int type, params object[] obj)
{
if (GeneralNotifications.TryGetValue(key, out var targetNotify))
targetNotify.Show(player, key, obj);
else
Instance?.SendNotify(player, key, type, obj);
}
#endregion Public Method
#region Classes
public class BaseNotification
{
#region Fields
[JsonProperty(PropertyName = LangRu ? "Тип" : "Type")]
public int Type;
[JsonProperty(PropertyName = LangRu ? "Показывать уведомление?" : "Show notify?")]
public bool ShowNotify = true;
#endregion
#region Public Methods
public void Show(BasePlayer player, string key, params object[] args)
{
if (ShowNotify)
Instance.SendNotify(player, key, Type, args);
else
Instance.Reply(player, key, args);
}
#endregion
#region Constructors
public static BaseNotification Create(int type)
{
return new BaseNotification
{
Type = type,
ShowNotify = true
};
}
#endregion
}
#endregion
}
public class DiscountConf
{
[JsonProperty(PropertyName = LangRu ? "Включено" : "Enabled")]
public bool Enabled;
[JsonProperty(PropertyName = LangRu ? "Скидка (%)" : "Discount (%)",
ObjectCreationHandling = ObjectCreationHandling.Replace)]
public Dictionary<string, int> Discount = new();
public int GetDiscount(BasePlayer player)
{
if (!Enabled || Discount == null || Discount.Count == 0) return 0;
var result = 0;
foreach (var (perm, discount) in Discount)
if (discount > result && player.HasPermission(perm))
result = discount;
return result;
}
}
public class FormattingConf
{
[JsonProperty(PropertyName = LangRu ? "Формат цены покупки" : "Buy Price Format")]
public string BuyPriceFormat;
[JsonProperty(PropertyName = LangRu ? "Формат цены продажи" : "Sell Price Format")]
public string SellPriceFormat;
[JsonProperty(PropertyName = LangRu ? "Формат стоимости в корзине" : "Shopping Bag Cost Format")]
public string ShoppingBagCostFormat;
[JsonProperty(PropertyName = LangRu ? "Формат баланса" : "Balance Format")]
public string BalanceFormat;
}
public class BuyAgainConf
{
[JsonProperty(PropertyName = LangRu ? "Включено" : "Enabled")]
public bool Enabled;
[JsonProperty(PropertyName =
LangRu ? "Разрешение (пример: shopru.buyagain)" : "Permission (ex: shop.buyagain)")]
public string Permission;
[JsonProperty(PropertyName = LangRu ? "Изображение" : "Image")]
public string Image;
public bool HasAccess(BasePlayer player)
{
return Enabled && (string.IsNullOrEmpty(Permission) || player.HasPermission(Permission));
}
}
public class DiscordSettings
{
[JsonProperty(PropertyName = LangRu ? "Настройки покупки" : "Buy Settings")]
public DiscordLogSettings Buy = new()
{
Enabled = false,
Webhook = "",
Color = 3066993, // Green
Title = "Shop Purchase",
FooterIconUrl = string.Empty,
FooterText = "{username} bought {item} x{amount} for {price}"
};
[JsonProperty(PropertyName = LangRu ? "Настройки продажи" : "Sell Settings")]
public DiscordLogSettings Sell = new()
{
Enabled = false,
Webhook = "",
Color = 15158332, // Red
Title = "Shop Sale",
FooterIconUrl = string.Empty,
FooterText = "{username} sold {item} x{amount} for {price}"
};
[JsonProperty(PropertyName = LangRu ? "Настройки переводов" : "Transfer Settings")]
public DiscordLogSettings Transfer = new()
{
Enabled = false,
Webhook = "",
Color = 3447003, // Blue
Title = "Money Transfer",
FooterIconUrl = string.Empty,
FooterText = "Transferred {amount} from {username} to {targetname}"
};
public class DiscordLogSettings
{
[JsonProperty(PropertyName = LangRu ? "Включено" : "Enabled")]
public bool Enabled;
[JsonProperty(PropertyName = LangRu ? "Discord Webhook URL" : "Discord Webhook URL")]
public string Webhook;
[JsonProperty(PropertyName = LangRu ? "Цвет (Decimal формат)" : "Color (Decimal format)")]
public int Color;
[JsonProperty(PropertyName = LangRu ? "Заголовок" : "Title")]
public string Title;
[JsonProperty(PropertyName =
LangRu
? "URL иконки футера (для Buy/Sell используется иконка предмета автоматически)"
: "Footer icon URL (for Buy/Sell item icon is used automatically)")]
public string FooterIconUrl;
[JsonProperty(PropertyName =
LangRu
? "Текст футера. Переменные Buy/Sell: {username}, {steamid}, {item}, {amount}, {price}. Transfer: {username}, {steamid}, {targetname}, {targetid}, {amount}"
: "Footer text. Variables Buy/Sell: {username}, {steamid}, {item}, {amount}, {price}. Transfer: {username}, {steamid}, {targetname}, {targetid}, {amount})")]
public string FooterText;
}
}
public enum SortType
{
None,
Name,
Amount,
PriceDecrease,
PriceIncrease
}
#endregion
}
#region Classes
public class Localization
{
#region Fields
[JsonProperty(PropertyName = LangRu ? "Включено" : "Enabled")]
public bool Enabled;
[JsonProperty(PropertyName = LangRu ? "Текст (язык - текст)" : "Text (language - text)",
ObjectCreationHandling = ObjectCreationHandling.Replace)]
public Dictionary<string, string> Messages = new();
#endregion
#region Helpers
public string GetMessage(BasePlayer player = null)
{
if (Messages.Count == 0)
throw new Exception("The use of localization is enabled, but there are no messages!");
var userLang = "en";
if (player != null) userLang = Instance.lang.GetLanguage(player.UserIDString);
if (Messages.TryGetValue(userLang, out var message))
return message;
return Messages.TryGetValue("en", out message) ? message : Messages.ElementAt(0).Value;
}
#endregion
}
public class SellContainers
{
#region Fields
[JsonProperty(PropertyName = LangRu ? "Включено" : "Enabled")]
public bool Enabled;
[JsonProperty(
PropertyName = LangRu
? "Доступные Контейнеры (main, belt, wear)"
: "Available Containers (main, belt, wear)",
ObjectCreationHandling = ObjectCreationHandling.Replace)]
public List<string> Containers = new();
#endregion
#region Helpers
public List<ItemContainer> GetContainers(BasePlayer player)
{
if (player == null || player.inventory == null)
return Pool.Get<List<ItemContainer>>();
var list = Pool.Get<List<ItemContainer>>();
foreach (var cont in Containers)
switch (cont)
{
case "main":
{
list.Add(player.inventory.containerMain);
break;
}
case "belt":
{
list.Add(player.inventory.containerBelt);
break;
}
case "wear":
{
list.Add(player.inventory.containerWear);
break;
}
}
return list;
}
public Item[] AllItems(BasePlayer player)
{
var containers = GetContainers(player);
try
{
var items = new List<Item>();
foreach (var itemContainer in containers) items.AddRange(itemContainer.itemList);
return items.ToArray();
}
finally
{
Pool.FreeUnmanaged(ref containers);
}
}
public int AllItems(BasePlayer player, List<Item> items)
{
items.Clear();
var containers = GetContainers(player);
try
{
foreach (var itemContainer in containers) items.AddRange(itemContainer.itemList);
}
finally
{
Pool.FreeUnmanaged(ref containers);
}
return items.Count;
}
#endregion
}
public class SelectCurrencyUI
{
#region Fields
[JsonProperty(PropertyName = "Use Fullscreen Background (relative to Overlay)?")]
public bool UseFullscreenBackground;
[JsonProperty(PropertyName = "Background")]
public UiElement Background;
[JsonProperty(PropertyName = "Use Vertical Layout?")]
public bool UseVerticalLayout;
[JsonProperty(PropertyName = "Title")] public UiElement Title;
[JsonProperty(PropertyName = "Economy Title")]
public UiElement EconomyTitle;
[JsonProperty(PropertyName = "Economy Panel Material")]
public string EconomyPanelMaterial = string.Empty;
[JsonProperty(PropertyName = "Economy Panel Sprite")]
public string EconomyPanelSprite = string.Empty;
[JsonProperty(PropertyName = "Selected Economy Color")]
public IColor SelectedEconomyColor = IColor.CreateWhite();
[JsonProperty(PropertyName = "Unselected Economy Color")]
public IColor UnselectedEconomyColor = IColor.CreateWhite();
[JsonProperty(PropertyName = "Economy Width")]
public float EconomyWidth;
[JsonProperty(PropertyName = "Economy Height")]
public float EconomyHeight;
[JsonProperty(PropertyName = "Economy Margin")]
public float EconomyMargin;
[JsonProperty(PropertyName = "Economy Indent")]
public float EconomyIndent;
[JsonProperty(PropertyName = "Frame Width")]
public float FrameWidth;
[JsonProperty(PropertyName = "Frame Indent")]
public float FrameIndent;
[JsonProperty(PropertyName = "Frame Header")]
public float FrameHeader;
[JsonProperty(PropertyName = "Close the menu after a currency change?")]
public bool CloseAfterChange;
#endregion
}
#region UI.Classes
public class InterfacePosition
{
#region Fields
[JsonProperty(PropertyName = "AnchorMin (X)")]
public float AnchorMinX;
[JsonProperty(PropertyName = "AnchorMin (Y)")]
public float AnchorMinY;
[JsonProperty(PropertyName = "AnchorMax (X)")]
public float AnchorMaxX = 1;
[JsonProperty(PropertyName = "AnchorMax (Y)")]
public float AnchorMaxY = 1;
[JsonProperty(PropertyName = "OffsetMin (X)")]
public float OffsetMinX;
[JsonProperty(PropertyName = "OffsetMin (Y)")]
public float OffsetMinY;
[JsonProperty(PropertyName = "OffsetMax (X)")]
public float OffsetMaxX;
[JsonProperty(PropertyName = "OffsetMax (Y)")]
public float OffsetMaxY;
#endregion Fields
#region Public Methods
public float GetAxis(bool isX)
{
if (isX) return OffsetMinX;
return -OffsetMaxY;
}
public void SetVerticalAxis(VerticalConstraint constraint)
{
switch (constraint)
{
case VerticalConstraint.Center:
AnchorMinY = AnchorMaxY = 0.5f;
break;
case VerticalConstraint.Bottom:
AnchorMinY = AnchorMaxY = 0f;
break;
case VerticalConstraint.Top:
AnchorMinY = AnchorMaxY = 1f;
break;
case VerticalConstraint.Scale:
AnchorMinY = 0f;
AnchorMaxY = 1f;
break;
}
}
public VerticalConstraint GetVerticalAxis()
{
if (Mathf.Approximately(AnchorMinY, AnchorMaxY))
{
if (Mathf.Approximately(AnchorMinY, 0.5f))
return VerticalConstraint.Center;
if (Mathf.Approximately(AnchorMinY, 0f))
return VerticalConstraint.Bottom;
if (Mathf.Approximately(AnchorMinY, 1f))
return VerticalConstraint.Top;
}
if (Mathf.Approximately(AnchorMinY, 0) && Mathf.Approximately(AnchorMaxY, 1))
return VerticalConstraint.Scale;
return VerticalConstraint.Custom;
}
public void SetHorizontalAxis(HorizontalConstraint constraint)
{
switch (constraint)
{
case HorizontalConstraint.Center:
AnchorMinX = AnchorMaxX = 0.5f;
break;
case HorizontalConstraint.Left:
AnchorMinX = AnchorMaxX = 0f;
break;
case HorizontalConstraint.Right:
AnchorMinX = AnchorMaxX = 1f;
break;
case HorizontalConstraint.Scale:
AnchorMinX = 0f;
AnchorMaxX = 1f;
break;
}
}
public HorizontalConstraint GetHorizontalAxis()
{
if (Mathf.Approximately(AnchorMinX, AnchorMaxX))
{
if (Mathf.Approximately(AnchorMinX, 0.5f))
return HorizontalConstraint.Center;
if (Mathf.Approximately(AnchorMinX, 0f))
return HorizontalConstraint.Left;
if (Mathf.Approximately(AnchorMinX, 1f))
return HorizontalConstraint.Right;
}
if (Mathf.Approximately(AnchorMinX, 0) && Mathf.Approximately(AnchorMaxX, 1))
return HorizontalConstraint.Scale;
return HorizontalConstraint.Custom;
}
public enum HorizontalConstraint
{
Left,
Center,
Right,
Scale,
Custom
}
public enum VerticalConstraint
{
Bottom,
Center,
Top,
Scale,
Custom
}
public void SetAxis(bool isX, float value)
{
if (isX)
{
var oldX = OffsetMinX;
OffsetMinX = value;
OffsetMaxX = OffsetMaxX - oldX + value;
}
else
{
var oldY = -OffsetMaxY;
OffsetMaxY = -value;
OffsetMinY = OffsetMinY + oldY - value;
}
}
public void MoveX(float value)
{
OffsetMinX += value;
OffsetMaxX += value;
}
public void MoveY(float value)
{
OffsetMinY += value;
OffsetMaxY += value;
}
public float GetPadding(int type = 0) // 0 - left, 1 - right, 2 - top, 3 - bottom
{
switch (type)
{
case 0: return OffsetMinX;
case 1: return -OffsetMaxX;
case 2: return -OffsetMaxY;
case 3: return OffsetMinY;
default: return OffsetMinX;
}
}
public void SetPadding(
float? left = null,
float? top = null,
float? right = null,
float? bottom = null)
{
if (left.HasValue) OffsetMinX = left.Value;
if (right.HasValue) OffsetMaxX = -right.Value;
if (bottom.HasValue) OffsetMinY = bottom.Value;
if (top.HasValue) OffsetMaxY = -top.Value;
}
public float GetWidth()
{
return OffsetMaxX - OffsetMinX;
}
public void SetWidth(float width)
{
if (GetHorizontalAxis() == HorizontalConstraint.Center)
{
var half = (float) Math.Round(width / 2f, 2);
OffsetMinX = -half;
OffsetMaxX = half;
return;
}
OffsetMaxX = OffsetMinX + width;
}
public float GetHeight()
{
return OffsetMaxY - OffsetMinY;
}
public void SetHeight(float height)
{
if (GetVerticalAxis() == VerticalConstraint.Center)
{
var half = (float) Math.Round(height / 2f, 2);
OffsetMinY = -half;
OffsetMaxY = half;
return;
}
OffsetMaxY = OffsetMinY + height;
}
public Rect GetRect()
{
var rect = new Rect();
ManipulateRect(GetPivot(), rectTransform => rect = rectTransform.rect);
return rect;
}
private Vector2 GetPivot()
{
return Mathf.Approximately(AnchorMinX, 0.5f) ? new Vector2(0.5f, 0.5f) : new Vector2(0f, 1f);
}
private void ManipulateRect(Vector2 pivot, Action<RectTransform> callback)
{
var rect = new GameObject().AddComponent<RectTransform>();
try
{
rect.pivot = pivot;
rect.anchorMin = new Vector2(AnchorMinX, AnchorMinY);
rect.anchorMax = new Vector2(AnchorMaxX, AnchorMaxY);
rect.offsetMin = new Vector2(OffsetMinX, OffsetMinY);
rect.offsetMax = new Vector2(OffsetMaxX, OffsetMaxY);
callback?.Invoke(rect);
AnchorMinX = rect.anchorMin.x;
AnchorMinY = rect.anchorMin.y;
AnchorMaxX = rect.anchorMax.x;
AnchorMaxY = rect.anchorMax.y;
OffsetMinX = rect.offsetMin.x;
OffsetMinY = rect.offsetMin.y;
OffsetMaxX = rect.offsetMax.x;
OffsetMaxY = rect.offsetMax.y;
}
finally
{
UnityEngine.Object.Destroy(rect.gameObject);
}
}
#region CuiRectTransformComponent
[JsonIgnore] private CuiRectTransformComponent _cachedRectTransform;
public CuiRectTransformComponent GetRectTransform()
{
if (_cachedRectTransform != null)
return _cachedRectTransform;
_cachedRectTransform = new CuiRectTransformComponent
{
AnchorMin = $"{AnchorMinX} {AnchorMinY}",
AnchorMax = $"{AnchorMaxX} {AnchorMaxY}",
OffsetMin = $"{OffsetMinX} {OffsetMinY}",
OffsetMax = $"{OffsetMaxX} {OffsetMaxY}"
};
return _cachedRectTransform;
}
public void InvalidateCache()
{
_cachedRectTransform = null;
}
#endregion
public CuiRectTransformComponent GetRectTransform(Func<float, float> formatterOffMaxX,
Func<float, float> formatterOffMaxY)
{
var oMaxX = OffsetMaxX;
if (formatterOffMaxX != null) oMaxX = formatterOffMaxX(OffsetMaxX);
var oMaxY = OffsetMaxY;
if (formatterOffMaxY != null) oMaxY = formatterOffMaxY(OffsetMaxY);
return new CuiRectTransformComponent
{
AnchorMin = $"{AnchorMinX} {AnchorMinY}",
AnchorMax = $"{AnchorMaxX} {AnchorMaxY}",
OffsetMin = $"{OffsetMinX} {OffsetMinY}",
OffsetMax = $"{oMaxX} {oMaxY}"
};
}
public override string ToString()
{
return JsonConvert.SerializeObject(GetRectTransform(), 0, new JsonSerializerSettings
{
DefaultValueHandling = DefaultValueHandling.Ignore
}).Replace("\\n", "\n");
}
#endregion
#region Constructors
public static InterfacePosition CreatePosition(float aMinX, float aMinY, float aMaxX, float aMaxY,
float oMinX, float oMinY, float oMaxX, float oMaxY)
{
return new InterfacePosition
{
AnchorMinX = aMinX,
AnchorMinY = aMinY,
AnchorMaxX = aMaxX,
AnchorMaxY = aMaxY,
OffsetMinX = oMinX,
OffsetMinY = oMinY,
OffsetMaxX = oMaxX,
OffsetMaxY = oMaxY
};
}
public static InterfacePosition CreatePosition(
string anchorMin = "0 0",
string anchorMax = "1 1",
string offsetMin = "0 0",
string offsetMax = "0 0")
{
var aMinX = float.Parse(anchorMin.Split(' ')[0]);
var aMinY = float.Parse(anchorMin.Split(' ')[1]);
var aMaxX = float.Parse(anchorMax.Split(' ')[0]);
var aMaxY = float.Parse(anchorMax.Split(' ')[1]);
var oMinX = float.Parse(offsetMin.Split(' ')[0]);
var oMinY = float.Parse(offsetMin.Split(' ')[1]);
var oMaxX = float.Parse(offsetMax.Split(' ')[0]);
var oMaxY = float.Parse(offsetMax.Split(' ')[1]);
return new InterfacePosition
{
AnchorMinX = aMinX,
AnchorMinY = aMinY,
AnchorMaxX = aMaxX,
AnchorMaxY = aMaxY,
OffsetMinX = oMinX,
OffsetMinY = oMinY,
OffsetMaxX = oMaxX,
OffsetMaxY = oMaxY
};
}
public static InterfacePosition CreatePosition(CuiRectTransform rectTransform)
{
var aMinX = float.Parse(rectTransform.AnchorMin.Split(' ')[0]);
var aMinY = float.Parse(rectTransform.AnchorMin.Split(' ')[1]);
var aMaxX = float.Parse(rectTransform.AnchorMax.Split(' ')[0]);
var aMaxY = float.Parse(rectTransform.AnchorMax.Split(' ')[1]);
var oMinX = float.Parse(rectTransform.OffsetMin.Split(' ')[0]);
var oMinY = float.Parse(rectTransform.OffsetMin.Split(' ')[1]);
var oMaxX = float.Parse(rectTransform.OffsetMax.Split(' ')[0]);
var oMaxY = float.Parse(rectTransform.OffsetMax.Split(' ')[1]);
return new InterfacePosition
{
AnchorMinX = aMinX,
AnchorMinY = aMinY,
AnchorMaxX = aMaxX,
AnchorMaxY = aMaxY,
OffsetMinX = oMinX,
OffsetMinY = oMinY,
OffsetMaxX = oMaxX,
OffsetMaxY = oMaxY
};
}
#endregion Constructors
}
public enum CuiElementType
{
Label,
Panel,
Button,
Image,
InputField
}
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
private sealed class TextEditableAttribute : Attribute
{
}
public class UiElement : InterfacePosition
{
#region Fields
[JsonProperty(PropertyName = "Enabled?")]
public bool Enabled;
[JsonProperty(PropertyName = "Visible")]
public bool Visible = true;
[JsonProperty(PropertyName = "Name")] public string Name = string.Empty;
[JsonProperty(PropertyName = "Type (Label/Panel/Button/Image)")]
[JsonConverter(typeof(StringEnumConverter))]
public CuiElementType Type;
[JsonProperty(PropertyName = "Color")] public IColor Color = new("#FFFFFF", 100);
[TextEditable]
[JsonProperty(PropertyName = "Text", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public List<string> Text = new();
[JsonProperty(PropertyName = "Font Size")]
public int FontSize;
[JsonProperty(PropertyName = "Font")] public CuiElementFont Font = CuiElementFont.RobotoCondensedBold;
[JsonProperty(PropertyName = "Align")] [JsonConverter(typeof(StringEnumConverter))]
public TextAnchor Align;
[JsonProperty(PropertyName = "Text Color")]
public IColor TextColor = new("#FFFFFF", 100);
[JsonProperty(PropertyName = "Command ({user} - user steamid)")]
public string Command = string.Empty;
[JsonProperty(PropertyName = "Image")] public string Image = string.Empty;
[JsonProperty(PropertyName = "Cursor Enabled")]
public bool CursorEnabled;
[JsonProperty(PropertyName = "Keyboard Enabled")]
public bool KeyboardEnabled;
[JsonProperty(PropertyName = "Sprite")]
public string Sprite = string.Empty;
[JsonProperty(PropertyName = "Material")]
public string Material = string.Empty;
#endregion Fields
#region Public Methods
public new void InvalidateCache()
{
base.InvalidateCache();
Color?.InvalidateCache();
TextColor?.InvalidateCache();
}
public bool TryGetImage(out string image)
{
if (Type == CuiElementType.Image)
if (!string.IsNullOrEmpty(Image))
if (Image.IsURL() || Image.StartsWith("TheMevent/"))
{
image = Image;
return true;
}
image = null;
return false;
}
public void Get(ref CuiElementContainer container, BasePlayer player,
string parent,
string name = null,
string destroy = "",
string close = "",
Func<string, string> textFormatter = null,
Func<string, string> cmdFormatter = null,
bool needUpdate = false,
CuiRectTransformComponent customRectTransform = null)
{
if (!Enabled) return;
if (string.IsNullOrEmpty(name))
name = CuiHelper.GetGuid();
if (needUpdate) destroy = string.Empty;
switch (Type)
{
case CuiElementType.Label:
{
var targetText = GetLocalizedText(player);
// ... [Code preview truncated due to length. Full source in attached file] ...