C#:
// #define TESTING
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using Steamworks;
using UnityEngine;
using UnityEngine.Networking;
#if CARBON
using Carbon.Base;
using Carbon.Modules;
#endif
namespace Oxide.Plugins
{
[Info("GameStoresRUST", "HOUGAN & Sstine & rostov114 & Mevent # GAMESTORES", "1.0.23")]
public class GameStoresRUST : RustPlugin
{
#region Fields
[PluginReference] private Plugin
GameStoresWipeBlock = null,
ImageLibrary = null,
NoEscape = null;
private static GameStoresRUST Instance;
#if CARBON
private ImageDatabaseModule imageDatabase = BaseModule.GetModule<ImageDatabaseModule>();
#endif
private bool initialization, Initialized, maintenance;
private string ShopURL = "UNDEFINED";
private int StartBalance;
private Dictionary<int, Dictionary<ulong, int>> ListTimeOutCommand = new();
private HashSet<ulong> ListBannedCommandUserID = new();
private Dictionary<ulong, PlayerBasket> _playerBaskets = new();
private Timer _checkInstant;
#region Images
private string NoImageID = string.Empty, LoadingImageID = string.Empty;
private const string ImageBucket = "GameStoresRUSTBucket";
private Coroutine _loadingCoroutine;
#endregion
#region Web Requests
private readonly List<Coroutine> _activeCoroutines = new();
private readonly List<UnityWebRequest> _activeRequests = new();
private bool SecureConnection = true;
private Dictionary<string, string> reqHeaders = new()
{
["User-Agent"] = "GameStores Plugin"
};
private const string
MainApiLink = "https://apip.gamestores.app/v1/",
CFApiLink = "https://api.gamestores.app/v1/",
PayApiLink = "https://pay.gamestores.app/api/";
private string BestApiLink = string.Empty;
private int errorsReq;
private const int MaxErrorsReq = 5;
#endregion Web Requests
#endregion
#region Config
private Configuration _config;
private class Configuration
{
public class APIConfiguration
{
[JsonProperty("Store ID")] public string ShopID = "UNDEFINED";
[JsonProperty("Server ID")] public string ServerID = "UNDEFINED";
[JsonProperty("Secret key (don't spread it)")]
public string SecretKey = "UNDEFINED";
}
public class InterfaceConfiguration
{
[JsonProperty("Enable cart image")] public bool BucketEnable = true;
[JsonProperty("Enable display of item names")]
public bool TextShow = true;
[JsonProperty("Link to cart image (BUCKET - default image)")]
public string BucketURL = "BUCKET";
[JsonProperty("Number of items per line")]
public int ItemOnString = 7;
[JsonProperty("Number of lines in the interface")]
public int StringAmount = 3;
[JsonProperty("Side of one item")] public int ItemSide = 150;
[JsonProperty("Space between objects")]
public int ItemMargin = 5;
[JsonProperty("Position settings cart image")]
public InterfacePosition BucketPosition = new()
{
AnchorMin = "0 1",
AnchorMax = "0 1",
OffsetMin = "8 -40",
OffsetMax = "43 -6"
};
[JsonProperty("Background settings")] public BackgroundSettings Background = new()
{
AnchorMin = "0 0",
AnchorMax = "1 1",
OffsetMin = "0 0",
OffsetMax = "0 0",
Material = "assets/content/ui/uibackgroundblur.mat",
Sprite = string.Empty,
Image = string.Empty,
Color = IColor.Create("#000000", 80),
DisplayType = "Overlay"
};
[JsonProperty("Basket title settings")]
public ButtonSettings BasketTitle = new()
{
AnchorMin = "0.3 0.9",
AnchorMax = "0.7 1",
OffsetMin = "0 0",
OffsetMax = "0 0",
FontSize = 32,
IsBold = false,
Align = TextAnchor.MiddleCenter,
Color = IColor.Create("#FFFFFF", 60),
ButtonColor = IColor.Create("#000000", 0),
Sprite = string.Empty,
Material = string.Empty,
Image = string.Empty,
ImageColor = IColor.Create("#FFFFFF")
};
[JsonProperty("Basket description settings")]
public TextSettings BasketDescription = new()
{
AnchorMin = "0 0",
AnchorMax = "1 0",
OffsetMin = "0 -0",
OffsetMax = "0 20",
FontSize = 14,
IsBold = false,
Align = TextAnchor.UpperCenter,
Color = IColor.Create("#FFFFFF", 40)
};
[JsonProperty("Help button settings")] public ButtonSettings HelpButton = new()
{
AnchorMin = "0.065 0.9",
AnchorMax = "0.2 1",
OffsetMin = "0 0",
OffsetMax = "0 0",
FontSize = 28,
IsBold = false,
Align = TextAnchor.MiddleLeft,
Color = IColor.Create("#FFFFFF"),
ButtonColor = IColor.Create("#000000", 0),
Sprite = string.Empty,
Material = string.Empty,
Image = string.Empty,
ImageColor = IColor.Create("#FFFFFF")
};
[JsonProperty(PropertyName = "Block panel settings")]
public ImageSettings BlockPanel = new()
{
AnchorMin = "0 0",
AnchorMax = "1 0.9",
OffsetMin = "0 0",
OffsetMax = "0 0",
Color = IColor.Create("#000000", 0),
Sprite = string.Empty,
Material = string.Empty,
Image = string.Empty
};
[JsonProperty(PropertyName = "Request processing title settings")]
public TextSettings RequestProcessingTitle = new()
{
AnchorMin = "0 0",
AnchorMax = "1 1.1",
OffsetMin = "0 0",
OffsetMax = "0 0",
Align = TextAnchor.MiddleCenter,
Color = IColor.Create("#FFFFFF"),
IsBold = false,
FontSize = 34
};
[JsonProperty(PropertyName = "Store exit button settings")]
public ButtonSettings StoreExitButton = new()
{
AnchorMin = "0.8 0.9", AnchorMax = "0.935 1", OffsetMin = "0 0", OffsetMax = "0 0",
ButtonColor = IColor.Create("#000000", 0),
Material = string.Empty,
Image = string.Empty,
Sprite = string.Empty,
Color = IColor.Create("#FFFFFF"),
Align = TextAnchor.MiddleRight,
FontSize = 28,
IsBold = false,
ImageColor = IColor.Create("#FFFFFF")
};
[JsonProperty(PropertyName = "Item background color")]
public IColor ItemBackgroundColor = IColor.Create("#FFFFFF", 20);
#region UI.Components
public class BackgroundSettings : ImageSettings
{
[JsonProperty(PropertyName = "Display type (Overlay/Hud)")]
public string DisplayType;
public CuiElement Get(string name, string destroyUI, bool needCursor)
{
var image = GetImage(DisplayType, name, destroyUI);
if (needCursor)
image.Components.Add(new CuiNeedsCursorComponent());
return image;
}
}
public class ImageSettings : InterfacePosition
{
[JsonProperty(PropertyName = "Sprite")]
public string Sprite;
[JsonProperty(PropertyName = "Material")]
public string Material;
[JsonProperty(PropertyName = "Image")] public string Image;
[JsonProperty(PropertyName = "Color")] public IColor Color;
private ICuiComponent GetImage()
{
if (!string.IsNullOrEmpty(Image))
{
var rawImage = new CuiRawImageComponent
{
Png = Instance?.GetImage(Image),
Color = Color.Get
};
if (!string.IsNullOrEmpty(Sprite))
rawImage.Sprite = Sprite;
if (!string.IsNullOrEmpty(Material))
rawImage.Material = Material;
return rawImage;
}
var image = new CuiImageComponent
{
Color = Color.Get
};
if (!string.IsNullOrEmpty(Sprite))
image.Sprite = Sprite;
if (!string.IsNullOrEmpty(Material))
image.Material = Material;
return image;
}
public CuiElement GetImage(string parent,
string name = null,
string destroyUI = null)
{
if (string.IsNullOrEmpty(name))
name = CuiHelper.GetGuid();
return new CuiElement
{
Name = name,
Parent = parent,
DestroyUi = destroyUI,
Components =
{
GetImage(),
GetPosition()
}
};
}
}
public class ButtonSettings : TextSettings
{
[JsonProperty(PropertyName = "Button Color")]
public IColor ButtonColor;
[JsonProperty(PropertyName = "Sprite")]
public string Sprite;
[JsonProperty(PropertyName = "Material")]
public string Material;
[JsonProperty(PropertyName = "Image")] public string Image;
[JsonProperty(PropertyName = "Image Color")]
public IColor ImageColor;
public List<CuiElement> Get(
string msg,
string cmd,
string parent,
string name = null,
string destroyUI = null,
string close = null)
{
if (string.IsNullOrEmpty(name))
name = CuiHelper.GetGuid();
var list = new List<CuiElement>();
var btn = new CuiButtonComponent
{
Color = ButtonColor.Get
};
if (!string.IsNullOrEmpty(cmd))
btn.Command = cmd;
if (!string.IsNullOrEmpty(close))
btn.Close = close;
if (!string.IsNullOrEmpty(Sprite))
btn.Sprite = Sprite;
if (!string.IsNullOrEmpty(Material))
btn.Material = Material;
if (!string.IsNullOrEmpty(Image))
{
list.Add(new CuiElement
{
Name = name,
Parent = parent,
DestroyUi = destroyUI,
Components =
{
new CuiRawImageComponent
{
Png = Instance?.GetImage(Image),
Color = ImageColor.Get
},
GetPosition()
}
});
list.Add(new CuiElement
{
Parent = name,
Components =
{
btn,
new CuiRectTransformComponent()
}
});
}
else
{
list.Add(new CuiElement
{
Name = name,
Parent = parent,
DestroyUi = destroyUI,
Components =
{
btn,
GetPosition()
}
});
}
if (!string.IsNullOrEmpty(msg))
list.Add(new CuiElement
{
Parent = name,
Components =
{
GetTextComponent(msg),
new CuiRectTransformComponent()
}
});
return list;
}
}
public class TextSettings : InterfacePosition
{
[JsonProperty(PropertyName = "Font Size")]
public int FontSize;
[JsonProperty(PropertyName = "Is Bold?")]
public bool IsBold;
[JsonProperty(PropertyName = "Align")] [JsonConverter(typeof(StringEnumConverter))]
public TextAnchor Align;
[JsonProperty(PropertyName = "Color")] public IColor Color;
protected CuiTextComponent GetTextComponent(string msg)
{
return new CuiTextComponent
{
Text = msg,
FontSize = FontSize,
Font = IsBold ? "robotocondensed-bold.ttf" : "robotocondensed-regular.ttf",
Align = Align,
Color = Color.Get
};
}
public CuiElement GetText(string msg, string parent, string name = null, string destroyUI = null)
{
if (string.IsNullOrEmpty(name))
name = CuiHelper.GetGuid();
return new CuiElement
{
Name = name,
Parent = parent,
DestroyUi = destroyUI,
Components =
{
GetTextComponent(msg),
GetPosition()
}
};
}
}
public class InterfacePosition
{
public string AnchorMin;
public string AnchorMax;
public string OffsetMin;
public string OffsetMax;
protected CuiRectTransformComponent GetPosition()
{
return new CuiRectTransformComponent
{
AnchorMin = AnchorMin,
AnchorMax = AnchorMax,
OffsetMin = OffsetMin,
OffsetMax = OffsetMax
};
}
}
public class IColor
{
[JsonProperty(PropertyName = "Opacity (0 - 100)",
NullValueHandling = NullValueHandling.Include)]
public float Alpha;
[JsonProperty(PropertyName = "HEX", NullValueHandling = NullValueHandling.Include)]
public string Hex;
public static IColor Create(string hex, float alpha = 100)
{
return new IColor
{
Hex = hex,
Alpha = alpha
};
}
[JsonIgnore] private string _color;
[JsonIgnore]
public string Get
{
get
{
if (string.IsNullOrEmpty(_color))
UpdateColor();
return _color;
}
}
public void UpdateColor()
{
_color = GetColor();
}
private string GetColor()
{
if (string.IsNullOrEmpty(Hex)) Hex = "#FFFFFF";
var str = Hex.Trim('#');
if (str.Length != 6) throw new Exception(Hex);
var r = byte.Parse(str.Substring(0, 2), NumberStyles.HexNumber);
var g = byte.Parse(str.Substring(2, 2), NumberStyles.HexNumber);
var b = byte.Parse(str.Substring(4, 2), NumberStyles.HexNumber);
return $"{(double) r / 255} {(double) g / 255} {(double) b / 255} {Alpha / 100}";
}
}
#endregion
}
public class PluginConfiguration
{
[JsonProperty("Commands to open the store", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public string[] Commands = {"store", "bucket"};
[JsonProperty("Enable show joining players in server tags")]
public bool ShowJoiningPlayers = false;
[JsonProperty("Load default images over client")]
public bool LoadSpriteImages = true;
[JsonProperty("Use Building Blocked?")]
public bool UseBuildingBlocked = false;
[JsonProperty("Use Combat Blocked?")] public bool UseCombatBlocked = false;
[JsonProperty("Use Raid Blocked?")] public bool UseRaidBlocked = false;
[JsonProperty("Execute instant commands if the player was not on the server?")]
public bool ExecuteInstantCommandsWhenNotInServer = false;
}
[JsonProperty("API configuration")] public APIConfiguration API = new();
[JsonProperty("Plugin Configuration")] public PluginConfiguration Plugin = new();
[JsonProperty("UI Configuration")] public InterfaceConfiguration UI = new();
public VersionNumber ConfigurationVersion = new(2, 0, 0);
}
#region Old Config
private void UpdateConfig(ConfigurationOld _old)
{
_config.API.ServerID = _old.API.ServerID;
_config.API.ShopID = _old.API.ShopID;
_config.API.SecretKey = _old.API.SecretKey;
_config.UI.BucketEnable = _old.Interface.BucketEnable;
_config.UI.TextShow = _old.Interface.TextShow;
_config.UI.BucketURL = _old.Interface.BucketURL;
_config.UI.ItemOnString = _old.Interface.ItemOnString;
_config.UI.StringAmount = _old.Interface.StringAmount;
_config.UI.ItemSide = _old.Interface.ItemSide;
_config.UI.ItemMargin = _old.Interface.ItemMargin;
_config.UI.BucketPosition.AnchorMin = _old.Interface.BucketPosition.AnchorMin;
_config.UI.BucketPosition.AnchorMax = _old.Interface.BucketPosition.AnchorMax;
_config.UI.BucketPosition.OffsetMin = _old.Interface.BucketPosition.OffsetMin;
_config.UI.BucketPosition.OffsetMax = _old.Interface.BucketPosition.OffsetMax;
}
private class ConfigurationOld
{
public class APIConfiguration
{
[JsonProperty("ИД магазина в сервисе")]
public string ShopID = "UNDEFINED";
[JsonProperty("ИД сервера в сервисе")] public string ServerID = "UNDEFINED";
[JsonProperty("Секретный ключ (не распространяйте его)")]
public string SecretKey = "UNDEFINED";
}
public class InterfaceConfiguration
{
[JsonProperty("Включить изображение корзины")]
public bool BucketEnable = true;
[JsonProperty("Включить отображение названий предметов")]
public bool TextShow = true;
[JsonProperty("Ссылка на изображение корзины (BUCKET - стандартное изображение)")]
public string BucketURL = "BUCKET";
[JsonProperty("Количество предметов на строке")]
public int ItemOnString = 7;
[JsonProperty("Количество строк в интерфейсе")]
public int StringAmount = 3;
[JsonProperty("Сторона одного предмета")]
public int ItemSide = 150;
[JsonProperty("Отступ между предметами")]
public int ItemMargin = 5;
[JsonProperty("Настройки позиции изображение корзины")]
public Position BucketPosition = new();
public class Position
{
public string AnchorMin = "0 1";
public string AnchorMax = "0 1";
public string OffsetMin = "8 -40";
public string OffsetMax = "43 -6";
}
}
[JsonProperty("Настройки API плагина")]
public APIConfiguration API = new();
[JsonProperty("Настройки интерфейса плагина")]
public InterfaceConfiguration Interface = new();
}
#endregion
protected override void LoadConfig()
{
base.LoadConfig();
try
{
_config = Config.ReadObject<Configuration>();
if (_config?.API == null || _config.API.SecretKey == "UNDEFINED")
{
PrintWarning("Old config?");
var oldConfig = Config.ReadObject<ConfigurationOld>();
if (oldConfig?.API != null && oldConfig.API.SecretKey != "UNDEFINED")
{
PrintError("Old config! Update configuration!");
UpdateConfig(oldConfig);
NextTick(SaveConfig);
return;
}
PrintError("Your configuration is invalid! Create new!");
LoadDefaultConfig();
}
}
catch (Exception e)
{
LogAction(null, "Error reading config, creating one new config!", true, true);
LoadDefaultConfig();
Debug.LogException(e);
}
NextTick(SaveConfig);
}
protected override void LoadDefaultConfig()
{
_config = new Configuration();
}
protected override void SaveConfig()
{
Config.WriteObject(_config);
}
#endregion
#region Hooks
#region Init / Unload
private void Init()
{
BestApiLink = MainApiLink;
Instance = this;
}
private void OnServerInitialized()
{
#if !CARBON
if (ImageLibrary == null)
{
if (TryImageCounter < maxImageCheck)
{
LogAction(null, "ImageLibrary is not initialized, try again in 1 sec.", true);
TryImageCounter++;
timer.In(1, OnServerInitialized);
return;
}
LogAction(null, "Starting without ImageLibrary", true);
}
else
#endif
{
InitializeImages();
if (_config.UI.BucketURL.Contains("http")
#if !CARBON
&& ImageLibrary != null
#endif
)
InitializeBucketImage();
else
InitializePlayers();
}
LoadHeaderParams();
if (!InitializeServerSettings())
return;
RegisterCommands();
timer.Once(2, FetchShopUrl);
InitializeTimeoutCommands();
}
private void Unload()
{
try
{
DestroyInstantTimer();
if (_loadingCoroutine != null) ServerMgr.Instance.StopCoroutine(_loadingCoroutine);
foreach (var player in BasePlayer.activePlayerList)
{
CuiHelper.DestroyUi(player, IconLayer);
CuiHelper.DestroyUi(player, StoreLayer);
}
DestroyRequestCoroutines();
DisposeActiveRequests();
}
finally
{
Instance = null;
}
}
#endregion
#region Player
private void OnPlayerConnected(BasePlayer player)
{
if (player == null) return;
if (_config.UI.BucketEnable)
InitializeIcon(player);
}
private void OnPlayerDisconnected(BasePlayer player)
{
if (player == null) return;
DequeueBasketRequest(player.userID);
}
#endregion
#region Server
private void OnServerInformationUpdated()
{
if (!_config.Plugin.ShowJoiningPlayers)
{
Unsubscribe(nameof(OnServerInformationUpdated));
return;
}
NextTick(() =>
{
if (SteamServer.GameTags.Length > 122)
{
LogAction(null, "-----------------------------", true, true);
LogAction(null, "Perhaps you specified it incorrectly 'server.tags'!", true, true);
LogAction(null, "The tag of connecting players WILL NOT be added!", true, true);
LogAction(null, "-----------------------------", true, true);
Unsubscribe(nameof(OnServerInformationUpdated));
return;
}
var newTags = new List<string>();
foreach (var tag in SteamServer.GameTags.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries))
{
newTags.Add(tag);
if (tag.Substring(0, 2) == "qp")
newTags.Add($"jp{SingletonComponent<ServerMgr>.Instance.connectionQueue.Joining}");
}
try
{
SteamServer.GameTags = string.Join(",", newTags);
}
catch (Exception ex)
{
Unsubscribe(nameof(OnServerInformationUpdated));
}
});
}
#endregion
#endregion
#region Commands
private void CmdChatStore(IPlayer cov, string command, string[] args)
{
var player = cov?.Object as BasePlayer;
if (player == null || player.Connection == null || player.IsSleeping())
return;
if (maintenance)
{
Reply(player, Maintenance);
return;
}
if (!Initialized)
{
HandleInitializationError(player);
return;
}
if (args.Length == 1 && args[0].ToLower() == "hide")
{
CuiHelper.DestroyUi(player, IconLayer);
return;
}
if (CanRequestBasket(player))
InitializeStore(player, 0);
}
[ConsoleCommand("UI_GameStoresRUST")]
private void CmdConsoleCommand(ConsoleSystem.Arg args)
{
var player = args.Player();
if (player == null) return;
if (HandleCommandBan(player, args)) return;
switch (args.Args[0].ToLower())
{
case "page":
{
var page = args.GetInt(1);
InitializeStore(player, page, false);
break;
}
case "help":
{
ShowHelpUI(player);
break;
}
case "take":
{
if (!args.HasArgs(3)) return;
if (!int.TryParse(args.Args[1], out var index) ||
!int.TryParse(args.Args[2], out var basketID)) return;
ProcessTakeCommand(player, index, basketID);
break;
}
}
}
[ConsoleCommand("closemenu")]
private void CmdCloseMenu(ConsoleSystem.Arg args)
{
var player = args.Player();
if (player == null) return;
DequeueBasketRequest(player.userID);
}
#endregion
#region Interface
private const string
StoreLayer = "UI_GameStoresRUST_Store",
IconLayer = "UI_GameStoresRUST_Bucket",
HelpLayer = StoreLayer + ".Help";
private void InitializeStore(BasePlayer player, int page, bool first = true)
{
var container = new CuiElementContainer();
if (first)
CreateStoreUiContainer(ref container, player);
CreateBlockPanel(ref container, player);
if (first)
CreateStoreExitButtonUI(player, ref container);
CuiHelper.AddUi(player, container);
if (first)
RequestBasket(player, page);
else
ShowBasketItemsUI(player, page);
}
private void ShowNotify(BasePlayer player, string text, float destroyTime = 0f)
{
CuiHelper.AddUi(player, new CuiElementContainer
{
{
new CuiLabel
{
RectTransform =
{
AnchorMin = "0 0.80", AnchorMax = "1 0.90", OffsetMax = "0 0"
},
Text =
{
FadeIn = 1f, Text = text, Align = TextAnchor.UpperCenter,
Font = "robotocondensed-regular.ttf",
FontSize = 16
}
},
StoreLayer, StoreLayer + ".Notify", StoreLayer + ".Notify"
}
});
if (destroyTime > 0f)
timer.In(destroyTime, () => CuiHelper.DestroyUi(player, StoreLayer + ".Notify"));
}
private void ShowHelpUI(BasePlayer player)
{
CuiHelper.DestroyUi(player, StoreLayer);
var container = new CuiElementContainer();
container.Add(new CuiPanel
{
CursorEnabled = true,
RectTransform = {AnchorMin = "0 0", AnchorMax = "1 1", OffsetMax = "0 0"},
Image = {Color = "0 0 0 0.9", Material = "assets/content/ui/uibackgroundblur.mat"}
}, "Overlay", HelpLayer, HelpLayer);
container.Add(new CuiLabel
{
RectTransform = {AnchorMin = "0 0.6", AnchorMax = "1 0.78", OffsetMax = "0 0"},
Text =
{
Text = Msg(player, MsgUserManual), Align = TextAnchor.MiddleCenter,
Font = "robotocondensed-regular.ttf", FontSize = 34
}
}, HelpLayer);
container.Add(new CuiLabel
{
RectTransform = {AnchorMin = "0 0.3", AnchorMax = "1 0.72", OffsetMax = "0 0"},
Text =
{
Text = Msg(player, MsgUserManualDescription),
Font = "robotocondensed-regular.ttf", FontSize = 22, Align = TextAnchor.MiddleCenter
}
}, HelpLayer);
var addText = StartBalance > 0 ? Msg(player, MsgUserManualBalance, StartBalance) : string.Empty;
container.Add(new CuiLabel
{
RectTransform = {AnchorMin = "0 0.1", AnchorMax = "1 0.48", OffsetMax = "0 0"},
Text =
{
Text = $"{addText}\n{ShopURL.ToUpper()}", Align = TextAnchor.MiddleCenter,
Font = "robotocondensed-regular.ttf", FontSize = 32
}
}, HelpLayer);
container.Add(new CuiButton
{
RectTransform = {AnchorMin = "0 0", AnchorMax = "1 1", OffsetMax = "0 0"},
Button = {Color = "0 0 0 0", Close = HelpLayer, Command = "chat.say /store"},
Text = {Text = string.Empty}
}, HelpLayer);
CuiHelper.AddUi(player, container);
}
#region UI.Components
private int basketItemsPerPage => _config.UI.ItemOnString * _config.UI.StringAmount;
private void ShowBasketItemsUI(BasePlayer player,
int page)
{
if (!_playerBaskets.TryGetValue(player.userID, out var playerBasket))
return;
var wItems = playerBasket.GetItems(page, basketItemsPerPage);
var secondContainer = new CuiElementContainer();
secondContainer.Add(new CuiLabel
{
RectTransform = {AnchorMin = "0 0", AnchorMax = "1 0.14", OffsetMax = "0 0"},
Text = {Text = (page + 1).ToString(), Align = TextAnchor.MiddleCenter, FontSize = 34}
}, StoreLayer + ".BlockPanel");
secondContainer.Add(new CuiButton
{
RectTransform =
{AnchorMin = "0.4 0.14", AnchorMax = "0.4 0.14", OffsetMin = "-40 -125", OffsetMax = "125 40"},
Button = {Color = "0 0 0 0", Command = page > 0 ? $"UI_GameStoresRUST page {page - 1}" : string.Empty},
Text =
{
Text = "<", Color = page > 0 ? "1 1 1 1" : "1 1 1 0.2", Align = TextAnchor.MiddleCenter,
Font = "robotocondensed-regular.ttf", FontSize = 80
}
}, StoreLayer + ".BlockPanel");
secondContainer.Add(new CuiButton
{
RectTransform =
{AnchorMin = "0.6 0.14", AnchorMax = "0.6 0.14", OffsetMin = "-125 -125", OffsetMax = "40 40"},
Button =
{
Color = "0 0 0 0",
Command = (page + 1) * basketItemsPerPage < playerBasket.GetCount()
? $"UI_GameStoresRUST page {page + 1}"
: string.Empty
},
Text =
{
Text = ">",
Color = (page + 1) * basketItemsPerPage < playerBasket.GetCount() ? "1 1 1 1" : "1 1 1 0.2",
Align = TextAnchor.MiddleCenter, Font = "robotocondensed-regular.ttf", FontSize = 80
}
}, StoreLayer + ".BlockPanel");
for (var i = 0; i < basketItemsPerPage; i++)
{
UI_RecountPosition(out var xSwitch, out var ySwitch, i, basketItemsPerPage);
secondContainer.Add(new CuiButton
{
RectTransform =
{
AnchorMin = "0.495 0.55",
AnchorMax = "0.495 0.55",
OffsetMin = $"{xSwitch} {ySwitch - _config.UI.ItemSide}",
OffsetMax = $"{xSwitch + _config.UI.ItemSide} {ySwitch}"
},
Button = {Color = _config.UI.ItemBackgroundColor.Get, Command = string.Empty},
Text = {Text = string.Empty}
}, StoreLayer + ".BlockPanel", StoreLayer + $".BlockPanel.{i}");
}
for (var index = 0; index < wItems.Count; index++)
{
var item = wItems[index];
ShowBasketItemUI(player, item, ref secondContainer, index);
}
CuiHelper.AddUi(player, secondContainer);
CuiHelper.DestroyUi(player, StoreLayer + ".BlockPanel.Text");
}
private void ShowBasketItemUI(BasePlayer player, WItem item, ref CuiElementContainer secondContainer,
int index)
{
if (item.IsBlueprint) ItemBlueprintImageUI(ref secondContainer, index);
if (_config.Plugin.LoadSpriteImages && item.IsValid && item.itemDefinition != null)
{
secondContainer.Add(new CuiElement
{
Parent = StoreLayer + $".BlockPanel.{index}",
Components =
{
new CuiImageComponent {ItemId = item.itemDefinition.itemid},
new CuiRectTransformComponent
{AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "5 5", OffsetMax = "-5 -5"}
}
});
}
else
{
if (!item.ImageUrl.Contains("http"))
secondContainer.Add(new CuiElement
{
Parent = StoreLayer + $".BlockPanel.{index}",
Components =
{
new CuiRawImageComponent {Png = item.ImageUrl},
new CuiRectTransformComponent
{AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "5 5", OffsetMax = "-5 -5"}
}
});
else
secondContainer.Add(new CuiElement
{
Parent = StoreLayer + $".BlockPanel.{index}",
Components =
{
new CuiRawImageComponent {Url = item.ImageUrl},
new CuiRectTransformComponent
{AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "5 5", OffsetMax = "-5 -5"}
}
});
}
if (item.Amount > 1)
secondContainer.Add(new CuiLabel
{
RectTransform =
{AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "0 5", OffsetMax = "-5 0"},
Text =
{
Text = "x" + item.Amount, Align = TextAnchor.LowerRight,
Font = "robotocondensed-regular.ttf", FontSize = 24
}
}, StoreLayer + $".BlockPanel.{index}");
secondContainer.Add(new CuiButton
{
RectTransform = {AnchorMin = "0 0", AnchorMax = "1 1", OffsetMax = "0 0"},
Button =
{
Color = "0 0 0 0", Close = StoreLayer + $".BlockPanel.{index}.Open",
Command =
$"UI_GameStoresRUST take {index} {item.basketId}"
},
Text = {Text = string.Empty}
}, StoreLayer + $".BlockPanel.{index}", StoreLayer + $".BlockPanel.{index}.Open");
if (_config.UI.TextShow)
secondContainer.Add(new CuiLabel
{
RectTransform =
{AnchorMin = "0 0", AnchorMax = "1 0", OffsetMin = "0 -22.5", OffsetMax = "0 0"},
Text =
{
Text = item.Name, Align = TextAnchor.MiddleCenter,
Font = "robotocondensed-regular.ttf", FontSize = 16, Color = "1 1 1 0.8"
}
}, StoreLayer + $".BlockPanel.{index}");
if (!CanRequestGetItem(Convert.ToInt32(item.basketId)))
{
ShowNotify(player, Msg(player, MsgTakeRequestProcessing));
secondContainer.Add(new CuiButton
{
RectTransform = {AnchorMin = "0 0", AnchorMax = "1 1", OffsetMax = "0 0"},
Button =
{
FadeIn = 1f, Color = "1 1 1 0.2",
Close = StoreLayer + ".BlockPanel." + index + ".Open",
Command = $"UI_GameStoresRUST take {index} {item.basketId}"
},
Text =
{
Text = Msg(player, MsgTakeWait), Align = TextAnchor.MiddleCenter,
Font = "robotocondensed-bold.ttf", Color = "1 1 1 0.4", FontSize = 24
}
}, StoreLayer + ".BlockPanel." + index, StoreLayer + ".BlockPanel." + index + ".Open");
}
}
private void ItemBlueprintImageUI(ref CuiElementContainer secondContainer, int index)
{
if (_config.Plugin.LoadSpriteImages)
{
secondContainer.Add(new CuiElement
{
Parent = StoreLayer + $".BlockPanel.{index}",
Components =
{
new CuiImageComponent {ItemId = ItemManager.blueprintBaseDef.itemid},
new CuiRectTransformComponent
{AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "5 5", OffsetMax = "-5 -5"}
}
});
}
else
{
#if CARBON
var blueprintImageComponent = new CuiRawImageComponent {Png = GetImage("blueprintbase")};
#else
var blueprintImageComponent = ImageLibrary?.IsLoaded == true
? new CuiRawImageComponent {Png = GetImage("blueprintbase")}
: new CuiRawImageComponent {Url = "https://gamestores.app/img/games/rust/blueprintbase.png"};
#endif
secondContainer.Add(new CuiElement
{
Parent = StoreLayer + $".BlockPanel.{index}",
Components =
{
blueprintImageComponent,
new CuiRectTransformComponent
{AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "5 5", OffsetMax = "-5 -5"}
}
});
}
}
private void CreateStoreExitButtonUI(BasePlayer player, ref CuiElementContainer container)
{
container.AddRange(_config.UI.StoreExitButton.Get(Msg(player, MsgExit), "closemenu", StoreLayer,
close: StoreLayer));
}
private void CreateStoreUiContainer(ref CuiElementContainer container, BasePlayer player)
{
container.Add(_config.UI.Background.Get(StoreLayer, StoreLayer, true));
container.AddRange(_config.UI.BasketTitle.Get(Msg(player, MsgBasket), string.Empty, StoreLayer));
container.Add(_config.UI.BasketDescription.GetText(Msg(player, MsgBasketDescription), StoreLayer));
container.AddRange(_config.UI.HelpButton.Get(Msg(player, MsgHelp), "UI_GameStoresRUST help",
StoreLayer));
}
private void CreateBlockPanel(ref CuiElementContainer container, BasePlayer player)
{
container.Add(_config.UI.BlockPanel.GetImage(StoreLayer, StoreLayer + ".BlockPanel",
StoreLayer + ".BlockPanel"));
container.Add(_config.UI.RequestProcessingTitle.GetText(Msg(player, MsgRequestProcessing),
StoreLayer + ".BlockPanel", StoreLayer + ".BlockPanel.Text"));
}
private void InitializeIcon(BasePlayer player)
{
UpdateUI(player, container =>
{
if (_config.UI.BucketURL.Contains("http"))
CreateBucketIconWithImageLibrary(ref container);
else
CreateBucketIconWithoutImageLibrary(ref container);
});
}
private void CreateBucketIconWithImageLibrary(ref CuiElementContainer container)
{
#if CARBON
var bucketImageComponent = new CuiRawImageComponent {Png = GetImage(ImageBucket)};
#else
var bucketImageComponent = ImageLibrary is {IsLoaded: true}
? new CuiRawImageComponent {Png = GetImage(ImageBucket)}
: new CuiRawImageComponent {Url = _config.UI.BucketURL};
#endif
container.Add(new CuiElement
{
Parent = "Overlay",
Name = IconLayer,
DestroyUi = IconLayer,
Components =
{
bucketImageComponent,
new CuiRectTransformComponent
{
AnchorMin = _config.UI.BucketPosition.AnchorMin,
AnchorMax = _config.UI.BucketPosition.AnchorMax,
OffsetMin = _config.UI.BucketPosition.OffsetMin,
OffsetMax = _config.UI.BucketPosition.OffsetMax
}
}
});
container.Add(new CuiButton
{
RectTransform = {AnchorMin = "0 0", AnchorMax = "1 1", OffsetMax = "0 0"},
Button = {Color = "0 0 0 0", Command = "chat.say /store"},
Text = {Text = string.Empty}
}, IconLayer);
}
private void CreateBucketIconWithoutImageLibrary(ref CuiElementContainer container)
{
container.Add(new CuiButton
{
RectTransform =
{
AnchorMin = _config.UI.BucketPosition.AnchorMin,
AnchorMax = _config.UI.BucketPosition.AnchorMax,
OffsetMin = _config.UI.BucketPosition.OffsetMin,
OffsetMax = _config.UI.BucketPosition.OffsetMax
},
Button = {Color = "1 1 1 0.6", Sprite = "assets/icons/open.png", Command = "chat.say /store"},
Text = {Text = string.Empty}
}, "Overlay", IconLayer, IconLayer);
}
private void UI_RecountPosition(out double xSwitch, out double ySwitch, int count, int max)
{
var stringAmount = Math.Ceiling((double) max / _config.UI.ItemOnString);
var currentString = Math.Floor((double) count / _config.UI.ItemOnString);
var currentPosition = count % _config.UI.ItemOnString;
var topYPosition = 0 + (float) stringAmount / 2 * _config.UI.ItemSide +
((float) stringAmount / 2 - 1) * _config.UI.ItemMargin;
var topXPosition = 0 - (float) _config.UI.ItemOnString / 2 * _config.UI.ItemSide -
((float) _config.UI.ItemOnString / 2 - 1) * _config.UI.ItemMargin;
var curYPosition = topYPosition - currentString * _config.UI.ItemSide - currentString *
(_config.UI.ItemMargin + (_config.UI.TextShow ? 20 : 0));
var curXPosition = topXPosition + currentPosition * _config.UI.ItemSide +
currentPosition * _config.UI.ItemMargin;
xSwitch = curXPosition;
ySwitch = curYPosition;
}
#endregion
#region UI.Helpers
private static void UpdateUI(BasePlayer player, Action<CuiElementContainer> callback)
{
if (player == null) return;
var container = new CuiElementContainer();
callback?.Invoke(container);
CuiHelper.AddUi(player, container);
}
#endregion
#endregion
#region Utils
#region Player Basket
#region Classes
private class PlayerBasket
{
public Dictionary<int, WItem> BasketItems = new();
public PlayerBasket(List<object> data)
{
foreach (Dictionary<string, object> product in data)
{
if (!product.TryGetValue("basketId", out var basketIdObj) ||
!int.TryParse(basketIdObj.ToString(), out var basketId))
continue;
var item = new WItem(product);
BasketItems[basketId] = item;
var leftTime = Instance?.WB_GetLeftTime(item.productId, item.ItemID, item.IsBlueprint) ?? 0;
if (leftTime > 0)
{
item.IsBlocked = true;
item.LeftTime = leftTime;
}
}
}
public void Clear()
{
BasketItems.Clear();
}
public List<WItem> GetItems(int page, int count)
{
return new List<WItem>(BasketItems.Values.Skip(page * count).Take(count));
}
public int GetCount()
{
return BasketItems.Count;
}
}
#endregion
#endregion
#region Blocks
private bool NE_IsCombatBlocked(BasePlayer player)
{
return Convert.ToBoolean(NoEscape?.Call("IsCombatBlocked", player));
}
private bool NE_IsRaidBlocked(BasePlayer player)
{
return Convert.ToBoolean(NoEscape?.Call("IsRaidBlocked", player));
}
private double WB_GetLeftTime(string productId, int itemID, bool isBlueprint)
{
return Convert.ToDouble(GameStoresWipeBlock?.Call("GetLeftTime", productId, itemID, isBlueprint));
}
#endregion
#region Plugin Loading
private void LoadHeaderParams()
{
reqHeaders.Add("X-Plugin-Version", Version.ToString());
reqHeaders.Add("storeId", _config.API.ShopID);
reqHeaders.Add("secretKey", _config.API.SecretKey);
reqHeaders.Add("serverId", _config.API.ServerID);
}
private void RegisterCommands()
{
AddCovalenceCommand(_config.Plugin.Commands, nameof(CmdChatStore));
}
private bool InitializeServerSettings()
{
if (_config.API.ServerID == "UNDEFINED")
{
LogAction(null, "Incorrect ServerID, automatic command issuance is disabled");
PrintError("Incorrect ServerID, receiving auto commands (Products) disabled");
return false;
}
if (_config.API.ServerID != "0")
StartInstantTimer();
return true;
}
private void InitializeTimeoutCommands()
{
ListTimeOutCommand[0] = new Dictionary<ulong, int>();
ListTimeOutCommand[1] = new Dictionary<ulong, int>();
ListTimeOutCommand[2] = new Dictionary<ulong, int>();
var totalTick = 0;
var tick = 0;
timer.Repeat(2, 0, () =>
{
if (totalTick == 30)
{
totalTick = 0;
ListBannedCommandUserID.Clear();
}
ListTimeOutCommand[0].Clear();
if (tick == 3 || tick == 6) ListTimeOutCommand[1].Clear();
if (tick == 6)
{
tick = 0;
ListTimeOutCommand[2].Clear();
}
tick++;
totalTick++;
});
}
private void InitializePlayers()
{
for (var i = 0; i < BasePlayer.activePlayerList.Count; i++)
OnPlayerConnected(BasePlayer.activePlayerList[i]);
}
private IEnumerator WaitForLoad()
{
while (Instance?.HasImage(ImageBucket) != true)
{
PrintError("Image of bucket is loading!");
yield return new WaitForSeconds(1);
}
PrintWarning("Image of bucket loaded correctly!");
InitializePlayers();
yield return 0;
}
#endregion
#region Image Loading
#region Image Helpers
private string GetImage(string name)
{
#if CARBON
return imageDatabase.GetImageString(name);
#else
return Convert.ToString(ImageLibrary?.Call("GetImage", name));
#endif
}
private void AddImage(string url, string fileName, ulong imageId = 0)
{
#if CARBON
imageDatabase.Queue(true, new Dictionary<string, string>
{
[fileName] = url
});
#else
ImageLibrary?.Call("AddImage", url, fileName, imageId);
#endif
}
private bool HasImage(string name)
{
#if CARBON
return Convert.ToBoolean(imageDatabase.HasImage(name));
#else
return Convert.ToBoolean(ImageLibrary?.Call("HasImage", name));
#endif
}
#endregion
private const int maxImageCheck = 3;
private int TryImageCounter;
private void InitializeImages()
{
#if CARBON
imageDatabase.Queue(true, new Dictionary<string, string>
{
["NONE"] = "https://gspics.org/images/2024/03/29/0NOj7D.png",
["LOADING"] = "https://gspics.org/images/2024/03/29/0NOxFI.png"
});
#else
if (ImageLibrary?.IsLoaded == true)
{
NoImageID = GetImage("NONE");
LoadingImageID = GetImage("LOADING");
}
#endif
}
private void InitializeBucketImage()
{
AddImage(_config.UI.BucketURL, ImageBucket);
_loadingCoroutine = ServerMgr.Instance.StartCoroutine(WaitForLoad());
}
#endregion
#region Take Item
private void ProcessTake(BasePlayer player, Dictionary<string, object> obj)
{
LogAction(player, "Начало обработки товара");
var itemInfo = new WItem(obj);
try
{
if (!itemInfo.IsValid)
{
LogAction(player, $"Ошибка получения предмета: {itemInfo.Name}");
return;
}
if (itemInfo.IsItem)
ProcessItem(player, itemInfo);
if (itemInfo.IsCommand)
ProcessCommand(player, itemInfo);
if (itemInfo.IsBlueprint)
ProcessBlueprint(player, itemInfo);
}
catch (Exception e)
{
HandleItemProcessingError(player, itemInfo, e);
}
}
private void ProcessItem(BasePlayer player, WItem itemInfo)
{
LogAction(player, $"Попытка получения предмета: {itemInfo.itemDefinition.shortname} [{itemInfo.Amount}]");
var item = ItemManager.Create(itemInfo.itemDefinition, itemInfo.Amount);
if (!player.inventory.GiveItem(item))
{
HandleItemGiveError(player, item, itemInfo);
}
else
{
LogAction(player, "Предмет выдан игроку в инвентарь");
ShowNotify(player, Msg(player, MsgTakeGiveInventory, itemInfo.itemDefinition.displayName.english));
}
}
private void ProcessInstantCommand(WItem itemInfo)
{
ExecuteCommands(null, itemInfo.commands, itemInfo.SteamID.ToString());
}
private void ProcessCommand(BasePlayer player, WItem itemInfo)
{
if (player == null)
{
ExecuteCommands(null, itemInfo.commands, itemInfo.SteamID.ToString());
return;
}
LogAction(player, "Attempting to execute commands");
ExecuteCommands(player, itemInfo.commands, player.UserIDString, player.displayName);
ShowNotify(player, Msg(player, MsgTakeGiveCommand, itemInfo.Name));
}
private void ExecuteCommands(BasePlayer player, List<object> commands, string steamId, string username = null)
{
foreach (string command in commands)
{
var selfCommand = ReplacePlaceholders(command, steamId, username);
ExecuteCommandSequence(selfCommand, player);
}
}
private string ReplacePlaceholders(string command, string steamId, string username = null)
{
return command.Replace("%steamid%", steamId, StringComparison.OrdinalIgnoreCase).Replace("%username%", username ?? string.Empty, StringComparison.OrdinalIgnoreCase);
}
private void ProcessBlueprint(BasePlayer player, WItem itemInfo)
{
LogAction(player, $"Попытка получения рецепта {itemInfo.itemDefinition.shortname}");
var create = ItemManager.Create(ItemManager.blueprintBaseDef);
create.blueprintTarget = itemInfo.itemDefinition.itemid;
if (!player.inventory.GiveItem(create))
{
HandleItemGiveError(player, create, itemInfo);
}
else
{
LogAction(player, "Рецепт выдан игроку в инвентарь");
ShowNotify(player,
Msg(player, MsgTakeGiveBlueprintInventory, itemInfo.itemDefinition.displayName.english));
}
}
private void ExecuteCommandSequence(string command, BasePlayer player = null)
{
LogAction(player, $"Исполнение команды: {command}");
Server.Command(command);
}
#endregion
#region Instant
private void StartInstantTimer()
{
_checkInstant = timer.Every(60, HandleInstantCommands);
}
private void DestroyInstantTimer()
{
_checkInstant?.Destroy();
}
private void TakeInstant(int basketID)
{
LogAction(null, "---------------------------------");
LogAction(null, $"[AutoCommands] Запрос на получение товара [{basketID}]");
RequestGetItem(basketID, true);
}
#region Handle
private void HandleInstantCommands()
{
Request("baskets.instantCommands", null, (code, response) =>
{
switch (code)
{
case 200:
HandleInstantRequestSuccess(response);
break;
default:
LogAction(null, "Ошибка выполнения запроса для выдачи автоматических команд");
break;
}
});
}
private void HandleInstantRequestSuccess(string response)
{
var firstInfo =
JsonConvert.DeserializeObject<Dictionary<string, object>>(response, new KeyValuesConverter());
if (firstInfo.ContainsKey("result"))
{
if (firstInfo["result"].ToString() == "success")
{
var data = firstInfo["data"] as List<object>;
if (data == null)
{
LogAction(null, $"Ошибка получения данных от сервера: {response}");
return;
}
var i = 1;
foreach (Dictionary<string, object> product in data)
{
if (!product.TryGetValue("basketId", out var basketId)) continue;
var basketID = Convert.ToInt32(basketId);
if (!_config.Plugin.ExecuteInstantCommandsWhenNotInServer)
if (product.TryGetValue("steamId", out var value))
{
var steamId = Convert.ToString(value);
if (!string.IsNullOrEmpty(steamId) && steamId.IsSteamId() &&
covalence.Players.FindPlayerById(steamId) == null)
continue;
}
timer.In(i, () => TakeInstant(basketID));
i++;
}
return;
}
if (firstInfo["result"].ToString() == "fail")
if (firstInfo.TryGetValue("message", out var value))
{
var message = Convert.ToString(value);
if (!string.IsNullOrEmpty(message))
{
LogAction(null, $"Ошибка получения данных от сервера: {message}", true, true);
return;
}
}
}
LogAction(null, "Ошибка получения данных от сервера");
}
private void HandleTakeInstantResponse(int code, string response, int basketID)
{
switch (code)
{
case 0:
LogAction(null, $"[AutoCommands] API не ответило на запрос: {basketID}");
PrintError("Api does not responded to a request");
break;
case 200:
HandleTakeInstantSuccess(response, basketID);
break;
case 404:
LogAction(null, $"[AutoCommands] Api не ответило на запрос: {basketID}");
LogAction(null, "Response code: 404, please check your configurations", true);
break;
}
}
private void HandleTakeInstantSuccess(string response, int basketID)
{
var responseData =
JsonConvert.DeserializeObject<Dictionary<string, object>>(response, new KeyValuesConverter());
if (responseData == null)
{
LogAction(null, $"[AutoCommands] Ошибка получения данных от сервера: {response}");
return;
}
if (!responseData.TryGetValue("data", out var value))
{
LogAction(null, $"[AutoCommands] Ошибка получения товара, отсутствует Data [{basketID}]");
return;
}
if (value is not Dictionary<string, object> data) return;
var itemInfo = new WItem(data);
if (!itemInfo.IsValid)
{
LogAction(null,
$"[AutoCommands] Ошибка получения товара [{basketID}], отсутствует информация о товаре [{itemInfo.Name}]");
return;
}
if (covalence?.Players?.FindPlayerById(itemInfo.SteamID.ToString()) == null)
{
LogAction(null,
$"[AutoCommands] Ошибка получения товара [{basketID}], отсутствует игрок [{itemInfo.SteamID}]");
return;
}
var leftTime = WB_GetLeftTime(itemInfo.productId,
itemInfo.ItemID, itemInfo.IsBlueprint);
if (leftTime > 0)
{
LogAction(nul
// ... [Code preview truncated due to length. Full source in attached file] ...