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.15")]
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);
var text = string.Join("\n", targetText).Replace("<br>", "\n");
if (textFormatter != null) text = textFormatter(text);
container.Add(new CuiElement {
Name = name, Parent = parent, DestroyUi = destroy, Update = needUpdate, Components = {
new CuiTextComponent {
Text = Visible ? text : string.Empty, Align = Align, Font = GetFontByType(Font), FontSize = FontSize, Color = Visible ? TextColor.Get() : "0 0 0 0"
}
, customRectTransform ?? GetRectTransform()
}
}
);
break;
}
case CuiElementType.InputField : {
var targetText = GetLocalizedText(player);
var text = string.Join("\n", targetText).Replace("<br>", "\n");
if (textFormatter != null) text = textFormatter(text);
container.Add(new CuiElement {
Name = name, Parent = parent, DestroyUi = destroy, Update = needUpdate, Components = {
new CuiInputFieldComponent {
Text = Visible ? text : string.Empty, Align = Align, Font = GetFontByType(Font), FontSize = FontSize, Color = Visible ? TextColor.Get() : "0 0 0 0", HudMenuInput = true, ReadOnly = true
}
, customRectTransform ?? GetRectTransform()
}
}
);
break;
}
case CuiElementType.Panel : {
var imageElement = new CuiImageComponent {
Color = Visible ? Color.Get() : "0 0 0 0"
}
;
if (!string.IsNullOrEmpty(Sprite)) imageElement.Sprite = Sprite;
if (!string.IsNullOrEmpty(Material)) imageElement.Material = Material;
var cuiElement = new CuiElement {
Name = name, Parent = parent, DestroyUi = destroy, Update = needUpdate, Components = {
imageElement, customRectTransform ?? GetRectTransform()
}
}
;
if (CursorEnabled) cuiElement.Components.Add(new CuiNeedsCursorComponent());
if (KeyboardEnabled) cuiElement.Components.Add(new CuiNeedsKeyboardComponent());
container.Add(cuiElement);
break;
}
case CuiElementType.Button : {
var targetCommand = $"{Command}".Replace("{user}", player.UserIDString);
if (cmdFormatter != null) targetCommand = cmdFormatter(targetCommand);
var btnElement = new CuiButtonComponent {
Command = targetCommand, Color = Visible ? Color.Get() : "0 0 0 0", Close = close
}
;
if (!string.IsNullOrEmpty(Sprite)) btnElement.Sprite = Sprite;
if (!string.IsNullOrEmpty(Material)) btnElement.Material = Material;
container.Add(new CuiElement {
Name = name, Parent = parent, DestroyUi = destroy, Update = needUpdate, Components = {
btnElement, customRectTransform ?? GetRectTransform()
}
}
);
var targetText = GetLocalizedText(player);
var message = string.Join("\n", targetText)?.Replace("<br>", "\n") ?? string.Empty;
if (textFormatter != null) message = textFormatter(message);
if (!string.IsNullOrEmpty(message)) container.Add(new CuiElement {
Parent = name, Components = {
new CuiTextComponent {
Text = Visible ? message : string.Empty, Align = Align, Font = GetFontByType(Font), FontSize = FontSize, Color = Visible ? TextColor.Get() : "0 0 0 0"
}
, customRectTransform ?? new CuiRectTransformComponent()
}
}
);
break;
}
case CuiElementType.Image : {
if (string.IsNullOrEmpty(Image)) return;
ICuiComponent imageElement;
if (Image == "{player_avatar}") {
var image = Image;
if (textFormatter != null) image = textFormatter(image);
imageElement = new CuiRawImageComponent {
SteamId = image, Color = Visible ? Color.Get() : "0 0 0 0"
}
;
}
else {
if (Image.StartsWith("assets/")) {
if (Image.Contains("Linear")) imageElement = new CuiRawImageComponent {
Color = Visible ? Color.Get() : "0 0 0 0", Sprite = Image
}
;
else imageElement = new CuiImageComponent {
Color = Enabled ? Color.Get() : "0 0 0 0", Sprite = Image
}
;
}
else if (Image.IsURL()) {
imageElement = new CuiRawImageComponent {
Png = Instance?.GetImage(Image), Color = Visible ? Color.Get() : "0 0 0 0"
}
;
}
else {
var image = Image;
if (textFormatter != null) image = textFormatter(image);
imageElement = new CuiRawImageComponent {
Png = Instance?.GetImage(image), Color = Visible ? Color.Get() : "0 0 0 0"
}
;
}
}
var cuiElement = new CuiElement {
// ... [Code preview truncated due to length. Full source in attached file] ...