C#:
using System.Collections.Generic;
using System.Globalization;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using UnityEngine;
using System;
using Oxide.Core;
using System.Collections;
using UnityEngine.Networking;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using WebSocketSharp;
using System.Linq;
using Oxide.Core.Libraries;
using System.Text.RegularExpressions;
using System.Net.Mail;
namespace Oxide.Plugins {
[Info("RustPay", "[LimePlugin] Chibubrik", "1.0.12")]
public class RustPay : RustPlugin {
#region Вар
private string Layer = "RustPay_UI";
private string LogoUrl = "";
private string BackgroundUrl = "";
private static RustPay _ins;
private WS _ws;
private BannerImage banner = new BannerImage();
private List<Categories> categories = new List<Categories>();
private List<ItemsList> items = new List<ItemsList>();
private List<Discounts> discounts = new List<Discounts>();
private List<Bonus> bonusList = new List<Bonus>();
private List<PaymentMethods> paymentMethods = new List<PaymentMethods>();
private Dictionary<ulong, List<string>> DBFavorites = new Dictionary<ulong, List<string>>();
private Dictionary<ulong, int> activeCategory = new Dictionary<ulong, int>();
private Dictionary<ulong, string> promoPlayer = new Dictionary<ulong, string>();
private Dictionary<ulong, int> inputBalancePlayer = new Dictionary<ulong, int>();
private Dictionary<ulong, string> emailPlayer = new Dictionary<ulong, string>();
List<ulong> spamBlock = new List<ulong>();
List<ulong> spamBlockPayment = new List<ulong>();
private bool _serverDataReady = false;
private int _pendingImages = 0;
private HashSet<string> _cachedImages = new HashSet<string>();
private const string ImageCachePath = "RustPay/cache/images";
private List<string> ImageDownloadList = new() {
"back", "discount", "scan", "exit", "inventory", "promo_w", "promo", "promo_gift", "topup", "url", "notify", "sbp", "card", "crypto", "skin"
}
;
private string privacy_policy = "";
private string user_agreement = "";
#endregion
#region Конфиг
private Configuration config;
private class Configuration {
[JsonProperty("Настройки магазина")]
public Settings settings = new Settings();
public static Configuration GetNewCong() {
return new Configuration {
settings = new Settings() {
SecretKey = "123",
}
}
;
}
}
protected override void LoadConfig() {
base.LoadConfig();
try {
config = Config.ReadObject<Configuration>();
if (config?.settings == null) LoadDefaultConfig();
}
catch {
PrintWarning($"Что то с этим конфигом не так! 'oxide/config/{Name}', создаём новую конфигурацию!");
LoadDefaultConfig();
}
NextTick(SaveConfig);
}
protected override void LoadDefaultConfig() => config = Configuration.GetNewCong();
protected override void SaveConfig() => Config.WriteObject(config);
#endregion
#region Класс
private class Settings {
[JsonProperty("Секретный ключ магазина")]
public string SecretKey;
}
private class BannerImage {
public string Banner_1 {
get;
set;
}
public string Command_1 {
get;
set;
}
public string Banner_2 {
get;
set;
}
public string Command_2 {
get;
set;
}
}
private class Categories {
public int ID {
get;
set;
}
public string Name {
get;
set;
}
public string IconUrl {
get;
set;
}
}
public class ItemsList {
public int CategoryID {
get;
set;
}
public int ID {
get;
set;
}
public string Type {
get;
set;
}
public string DisplayName {
get;
set;
}
public string Description {
get;
set;
}
public string ShortName {
get;
set;
}
public ulong SkinID {
get;
set;
}
public string Command {
get;
set;
}
public string Image {
get;
set;
}
public float Price {
get;
set;
}
public int Amount {
get;
set;
}
public bool Blueprint {
get;
set;
}
public List<Items> contentItems = new List<Items>();
public List<Kits> kits = new List<Kits>();
}
public class Kits {
public string DisplayName {
get;
set;
}
public int Cooldown {
get;
set;
}
public List<Items> kitItems = new List<Items>();
}
public class Items {
public int ID {
get;
set;
}
public string Type {
get;
set;
}
public string DisplayName {
get;
set;
}
public string ShortName {
get;
set;
}
public ulong SkinID {
get;
set;
}
public string Command {
get;
set;
}
public string Image {
get;
set;
}
public float Price {
get;
set;
}
public int Amount {
get;
set;
}
public bool Blueprint {
get;
set;
}
}
public class InventoryResponse {
public bool success {
get;
set;
}
public List<ItemInventory> items {
get;
set;
}
}
public class ItemInventory {
public int id {
get;
set;
}
public bool from_set {
get;
set;
}
public int purchase_id {
get;
set;
}
public string name {
get;
set;
}
public string type {
get;
set;
}
public int amount {
get;
set;
}
public ulong skin_id {
get;
set;
}
public string image_url {
get;
set;
}
public string shortname {
get;
set;
}
public string description {
get;
set;
}
public string command_text {
get;
set;
}
public bool is_blueprint {
get;
set;
}
}
private class Discounts {
public int ID {
get;
set;
}
public string Type {
get;
set;
}
public int ProductID {
get;
set;
}
public int Discount {
get;
set;
}
}
private class Bonus {
public int BonusText {
get;
set;
}
public int BonusPercent {
get;
set;
}
public string ColorBonus {
get;
set;
}
}
private class PaymentMethods {
public int ID {
get;
set;
}
public string PaymentText {
get;
set;
}
public string PaymentIcon {
get;
set;
}
public string PaymentColor {
get;
set;
}
}
#endregion
#region Хуки
private void Init() {
_ins = this;
string url = $"wss://socket.rustpay.org";
_ws = new WS(url);
LoadDataBase();
}
private void OnServerInitialized() {
_ws?.connect();
foreach (BasePlayer player in BasePlayer.activePlayerList) InitPlayer(player);
foreach (string image in ImageDownloadList) {
LoadImage(image, $"https://cdn.rustpay.org/uploads/icons/{image}.png");
}
}
private void OnPlayerConnected(BasePlayer player, bool load = false) {
InitPlayer(player);
_ws?.SendWhenReady(() => _ws.PlayerConnected(player.userID, player.displayName));
}
private void InitPlayer(BasePlayer player) {
LoadSteamAvatar(player.UserIDString);
if (!DBFavorites.ContainsKey(player.userID)) DBFavorites[player.userID] = new List<string>();
}
private void SetPlayerCategory(BasePlayer player) {
if (!activeCategory.ContainsKey(player.userID)) {
int startID = categories.Skip(1).FirstOrDefault()?.ID ?? 0;
if (DBFavorites.ContainsKey(player.userID) && DBFavorites[player.userID].Count > 0) startID = 0;
activeCategory[player.userID] = startID;
}
}
private void Unload() {
foreach (var player in BasePlayer.activePlayerList) CuiHelper.DestroyUi(player, Layer);
SaveDataBase();
_ws?.close();
}
private void LoadDataBase() {
if (Interface.Oxide.DataFileSystem.ExistsDatafile($"{Name}/data/FavoritesList")) DBFavorites = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<ulong, List<string>>>($"{Name}/data/FavoritesList");
else DBFavorites = new Dictionary<ulong, List<string>>();
LoadImageCache();
}
private void SaveDataBase() {
Interface.Oxide.DataFileSystem.WriteObject($"{Name}/data/FavoritesList", DBFavorites);
SaveImageCache();
}
#endregion
#region Подключение и обработка
public class WS {
private WebSocket socket;
private bool isConnected = false;
public bool hasUnloaded = false;
public bool criticalError = false;
private IEnumerator coroutineRoutine;
private List<Action> _pendingQueue = new List<Action>();
private bool _verified = false;
public WS(string url) {
socket = new WebSocket(url);
socket.SslConfiguration.EnabledSslProtocols = System.Security.Authentication.SslProtocols.Tls12;
socket.OnMessage += OnSocketMessage;
socket.OnOpen += OnSocketOpen;
socket.OnClose += OnSocketClose;
socket.OnError += OnSocketError;
}
public bool IsConnected() {
return isConnected && socket != null && socket.ReadyState == WebSocketState.Open;
}
public void SendWhenReady(Action action) {
if (_verified && IsConnected()) action?.Invoke();
else _pendingQueue.Add(action);
}
private void OnSocketMessage(object sender, MessageEventArgs e) {
JObject obj = JObject.Parse(e.Data);
if (obj.TryGetValue("action", out var action)) {
switch (action?.ToString()) {
case "verified" : if (this.coroutineRoutine == null) {
this.coroutineRoutine = this.ping();
ServerMgr.Instance.StartCoroutine(this.coroutineRoutine);
}
_ins.PrintWarning($"[WS] Авторизация пройдена! Начинаем работу");
_verified = true;
this.GetInfo();
Dictionary<ulong, string> onlinePlayers = new Dictionary<ulong, string>();
foreach (BasePlayer p in BasePlayer.activePlayerList) onlinePlayers.Add(p.userID, p.displayName);
if (onlinePlayers.Count > 0) this.PlayersArray(onlinePlayers);
foreach (var pending in _pendingQueue) pending?.Invoke();
_pendingQueue.Clear();
break;
case "pong" : Console.WriteLine(obj["data"]);
break;
case "serverInfo" : _ins.StoreAppearance(obj);
_ins.StoreCategories(obj);
_ins.StoreItems(obj);
_ins.StoreDocuments(obj);
_ins.StoreDiscounts(obj);
_ins.StoreBonus(obj);
_ins.StorePayments(obj);
_ins._serverDataReady = true;
break;
case "playerBalance" : ulong userIDbalance = (ulong)obj["steam_id"];
float balance = (float)obj["balance"];
string email = (string)obj["email"];
if (email != null) _ins.emailPlayer[userIDbalance] = email;
_ins.UpdateBalance(userIDbalance, balance);
break;
case "update_appearance" : _ins.StoreAppearance(obj);
break;
case "update_store" : _ins.StoreCategories(obj);
_ins.StoreItems(obj);
break;
case "update_discounts" : _ins.StoreDiscounts(obj);
break;
case "update_bonus" : _ins.StoreBonus(obj);
break;
case "update_paymentMethods" : _ins.StorePayments(obj);
break;
case "created_payment" : ulong userID = (ulong)obj["data"]["steam_id"];
string iconQR = (string)obj["data"]["url_qr"];
string url = (string)obj["data"]["short_link"];
_ins.LoadImage(iconQR, report : false);
_ins.PaymentWindowUI(userID, iconQR, url);
break;
case "payment_made" : BasePlayer player = BasePlayer.FindByID((ulong)obj["data"]["steam_id"]);
if (player == null || !player.IsConnected) break;
CuiHelper.DestroyUi(player, "PaymentWindow_UI");
float amount = (float)obj["data"]["amount"];
_ins.NotificationPaymentUI(player, amount);
_ins.UpdateBalance(player.userID, (float)obj["data"]["balance"]);
_ins.SendReply(player, $"Вы успешно пополнили свой баланс: {amount}₽");
Effect x = new Effect("assets/prefabs/deployable/vendingmachine/effects/vending-machine-purchase-human.prefab", player, 0, new Vector3(), new Vector3());
EffectNetwork.Send(x, player.Connection);
break;
case "clear_cache" : _ins.ClearCacheInternal();
break;
case "update_documents" : _ins.StoreDocuments(obj);
break;
case "error" : _ins.PrintWarning($"{obj["message"]}");
this.criticalError = true;
break;
default : _ins.PrintWarning($"action неизвестен!\n{JsonConvert.SerializeObject(obj, Formatting.Indented)}");
break;
}
}
}
private void OnSocketOpen(object sender, EventArgs e) {
this.isConnected = true;
_ins.PrintWarning($"[WS] Соединение открыто. Проходим авторизацию...");
this.Send(new {
action = "verification", key = _ins.config.settings.SecretKey, timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
}
);
}
private void OnSocketClose(object sender, CloseEventArgs e) {
if (e == null) _ins.PrintError("[WS] Соединение закрыто, но аргументы события были null.");
else if (string.IsNullOrEmpty(e.Reason)) _ins.PrintWarning("[WS] Соединение закрыто.");
else _ins.PrintError($"[WS] Соединение закрыто: {e.Reason}");
isConnected = false;
_verified = false;
if (this.coroutineRoutine != null) {
ServerMgr.Instance.StopCoroutine(this.coroutineRoutine);
this.coroutineRoutine = null;
}
if (!this.hasUnloaded && !this.criticalError) {
_ins.timer.Once(20, this.connect);
}
}
private void OnSocketError(object sender, ErrorEventArgs e) {
isConnected = false;
if (e == null) {
_ins.PrintError("[WS] Ошибка WebSocket: аргументы события были null.");
return;
}
_ins.PrintError($"[WS] Ошибка WebSocket: {e.Message}");
if (e.Exception != null) {
_ins.PrintError($"[WS] Исключение: {e.Exception}");
}
}
public void connect() {
if (!this.hasUnloaded) {
_ins.PrintWarning($"[WS] Попытка подключения");
try {
socket.ConnectAsync();
}
catch (Exception ex) {
}
}
}
public void close() {
this.hasUnloaded = true;
socket?.CloseAsync();
}
private IEnumerator ping() {
while (true) {
this.Send(new {
action = "ping"
}
);
yield return CoroutineEx.waitForSeconds(10f);
}
}
public void Send(object data) {
if (!isConnected || socket == null || hasUnloaded) return;
_ins.RunInWorker(() => {
try {
if (isConnected && socket != null && socket.ReadyState == WebSocketState.Open) {
string json = JsonConvert.SerializeObject(data);
socket.SendAsync(json, (_) => {
}
);
}
}
catch (Exception ex) {
_ins.PrintError($"[WS] Ошибка отправки: {ex.Message}");
}
}
);
}
public void GetInfo() {
this.Send(new {
action = "getServerInfo"
}
);
}
public void PlayersArray(Dictionary<ulong, string> userID) {
this.Send(new {
action = "playerConnect", type = "array", players = userID
}
);
}
public void PlayerConnected(ulong userID, string displayName) {
this.Send(new {
action = "playerConnect", type = "one", steam_id = userID, name = displayName
}
);
}
public void GetPlayerBalance(ulong userID) {
this.Send(new {
action = "getPlayerBalance", steam_id = userID
}
);
}
}
public void RunInWorker(Action action) {
QueueWorkerThread(_ => action());
}
#endregion
#region Image Cache
private void LoadImageCache() {
try {
if (Interface.Oxide.DataFileSystem.ExistsDatafile($"{Name}/cache/ImageCache")) _cachedImages = Interface.Oxide.DataFileSystem.ReadObject<HashSet<string>>($"{Name}/cache/ImageCache");
else _cachedImages = new HashSet<string>();
}
catch {
PrintWarning("Кэш изображений повреждён или устарел, создаём новый");
_cachedImages = new HashSet<string>();
SaveImageCache();
}
if (_cachedImages == null) _cachedImages = new HashSet<string>();
}
private void SaveImageCache() {
Interface.Oxide.DataFileSystem.WriteObject($"{Name}/cache/ImageCache", _cachedImages);
}
private bool ShouldCache(string key) {
if (string.IsNullOrEmpty(key)) return false;
if (key.ToLower().Contains("qr") || key.Contains("payment_qr")) return false;
if (key.Length == 17 && key.StartsWith("765") && key.All(char.IsDigit)) return false;
if (key.Contains("steamcommunity") || key.Contains("akamaihd.net")) return false;
return true;
}
private string GetCachedFilePath(string key) {
string safeKey = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(key)) .Replace("/", "_") .Replace("+", "-") .Replace("=", "");
return $"{Interface.Oxide.DataDirectory}/{ImageCachePath}/{safeKey}.png";
}
private bool TryLoadFromCache(string key) {
if (!ShouldCache(key)) return false;
if (!_cachedImages.Contains(key)) return false;
string fullPath = GetCachedFilePath(key);
if (!System.IO.File.Exists(fullPath)) {
_cachedImages.Remove(key);
return false;
}
try {
byte[] data = System.IO.File.ReadAllBytes(fullPath);
uint crc = FileStorage.server.Store(data, FileStorage.Type.png, CommunityEntity.ServerInstance.net.ID);
images[key] = crc;
return true;
}
catch (Exception ex) {
PrintError($"Ошибка загрузки из кэша: {key} - {ex.Message}");
_cachedImages.Remove(key);
return false;
}
}
private void SaveToCache(string key, byte[] data) {
if (!ShouldCache(key)) return;
try {
string fullPath = GetCachedFilePath(key);
string directory = System.IO.Path.GetDirectoryName(fullPath);
if (!System.IO.Directory.Exists(directory)) System.IO.Directory.CreateDirectory(directory);
System.IO.File.WriteAllBytes(fullPath, data);
_cachedImages.Add(key);
}
catch (Exception ex) {
PrintError($"Ошибка сохранения в кэш: {key} - {ex.Message}");
}
}
#endregion
private Dictionary<string, uint?> images = new Dictionary<string, uint?>();
private void LoadImage(string key, string url = null, Action callback = null, bool report = true) {
if (url == null) url = key;
if (string.IsNullOrEmpty(url)) {
callback?.Invoke();
return;
}
if (HasImage(key)) {
callback?.Invoke();
return;
}
if (TryLoadFromCache(key)) {
callback?.Invoke();
return;
}
_pendingImages++;
ServerMgr.Instance.StartCoroutine(DownloadImage(key, url, () => {
_pendingImages--;
callback?.Invoke();
}
, report));
}
private void LoadSteamAvatar(string steamId, Action callback = null) {
string apiUrl = $"http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=B18F4869325C455B96C6801D486FB8EA&steamids={steamId}";
webrequest.Enqueue(apiUrl, null, (code, response) => {
if (code != 200) {
callback?.Invoke();
return;
}
string avatarUrl = (string)JObject.Parse(response)["response"]?["players"]?[0]?["avatarfull"];
if (string.IsNullOrEmpty(avatarUrl)) {
callback?.Invoke();
return;
}
LoadImage(steamId, avatarUrl, callback);
}
, this);
}
private IEnumerator DownloadImage(string key, string url, Action callback, bool report, int maxAttempts = 5) {
if (!url.StartsWith("http://") && !url.StartsWith("https://")) url = "https://" + url;
else url = url.Replace("http://", "https://");
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
UnityWebRequest www = null;
try {
www = UnityWebRequest.Get(url);
}
catch (Exception e) {
PrintError($"Bad URL: {url} | ex: {e.Message}");
images[key] = null;
callback?.Invoke();
yield break;
}
using (www) {
www.timeout = 10;
yield return www.SendWebRequest();
bool failed = www.isNetworkError || www.isHttpError;
if (!failed) {
if (attempt > 1 && report) Puts($"Загрузили: {key}");
byte[] data = www.downloadHandler.data;
uint crc = FileStorage.server.Store(data, FileStorage.Type.png, CommunityEntity.ServerInstance.net.ID);
images[key] = crc;
SaveToCache(key, data);
callback?.Invoke();
yield break;
}
if (report) PrintError($"Ошибка загрузки ({attempt}/{maxAttempts}): {www.error} | URL: {url}");
if (attempt < maxAttempts) yield return new WaitForSeconds(2f * attempt);
}
}
if (report) PrintError($"Не смогли загрузить: {key} / {url}");
images[key] = null;
callback?.Invoke();
}
private string GetImage(string key) {
if (string.IsNullOrEmpty(key)) return null;
return images.TryGetValue(key, out uint? crc) && crc.HasValue ? crc.Value.ToString() : null;
}
private bool HasImage(string key) => !string.IsNullOrEmpty(key) && GetImage(key) != null;
#region Методы создания и обновления логотипа, баннеров, предметов, категорий, скидок
private void StoreAppearance(JObject obj) {
var msg = obj["data"] as JObject;
if (msg == null) {
PrintWarning($"[WS] StoreAppearance: message не объект. Type={obj["data"]?.Type}");
return;
}
LogoUrl = msg.Value<string>("logo_url") ?? "";
BackgroundUrl = msg.Value<string>("background_url") ?? "";
LoadImage(LogoUrl);
LoadImage(BackgroundUrl);
var banners = msg["banners"] as JObject;
var b1 = banners?["banner_1"] as JObject;
banner.Banner_1 = b1?.Value<string>("url") ?? "";
banner.Command_1 = b1?.Value<string>("command") ?? "";
var b2 = banners?["banner_2"] as JObject;
banner.Banner_2 = b2?.Value<string>("url") ?? "";
banner.Command_2 = b2?.Value<string>("command") ?? "";
LoadImage(banner.Banner_1);
LoadImage(banner.Banner_2);
}
private void StoreCategories(JObject obj) {
var categoryList = obj["data"]["categories"];
if (categoryList is JArray category) {
categories.Clear();
categories.Add(new Categories {
ID = 0, Name = "Избранное", IconUrl = "https://cdn.rustpay.org/uploads/favorite.category.png"
}
);
foreach (JObject cat in category) {
categories.Add(new Categories {
ID = cat.Value<int>("id"), Name = cat.Value<string>("name"), IconUrl = cat.Value<string>("icon_url")
}
);
}
}
foreach (Categories check in categories) {
if (string.IsNullOrEmpty(check.IconUrl)) continue;
LoadImage(check.IconUrl);
}
}
private void StoreDocuments(JObject obj) {
var documents = obj["data"]["documents"];
privacy_policy = (string) documents["policy"];
user_agreement = (string) documents["agreement"];
}
private void StoreItems(JObject obj) {
var itemList = obj["data"]["store"];
if (itemList is JArray checkItemsList) {
items.Clear();
foreach (JObject check in checkItemsList) {
int catId = check.Value<int?>("id") ?? 0;
if (check["items"] is not JArray itemsListArray) continue;
foreach (JObject item in itemsListArray) {
List<Items> contents = new List<Items>();
if (item.Value<string>("type") == "set" && item["content"] is JArray contentsList) {
foreach (JObject content in contentsList) {
contents.Add(new Items {
ID = content.Value<int?>("id") ?? 0, Type = content.Value<string>("type"), DisplayName = content.Value<string>("name"), ShortName = content.Value<string>("shortname"), SkinID = content.Value<ulong?>("skin_id") ?? 0, Command = content.Value<string>("command"), Image = content.Value<string>("image"), Amount = content.Value<int?>("amount") ?? 1, Blueprint = content.Value<bool?>("is_blueprint") ?? false,
}
);
}
}
List<Kits> kitsInfo = new List<Kits>();
if (item.Value<string>("type") == "command" && item["kits"] is JArray kitsList) {
foreach (JObject kit in kitsList) {
List<Items> kitItemsList = new List<Items>();
if (kit["items"] is JArray itemsList) {
foreach (JObject itemKit in itemsList) {
kitItemsList.Add(new Items {
ID = itemKit.Value<int?>("id") ?? 0, DisplayName = itemKit.Value<string>("name"), ShortName = itemKit.Value<string>("shortname"), SkinID = itemKit.Value<ulong?>("skin_id") ?? 0, Image = itemKit.Value<string>("image"), Amount = itemKit.Value<int?>("amount") ?? 1
}
);
}
}
kitsInfo.Add(new Kits {
DisplayName = kit.Value<string>("name"), Cooldown = kit.Value<int>("cooldown"), kitItems = kitItemsList
}
);
}
}
items.Add(new ItemsList {
CategoryID = catId, ID = item.Value<int?>("id") ?? 0, Type = item.Value<string>("type"), DisplayName = item.Value<string>("name"), Description = item.Value<string>("description"), ShortName = item.Value<string>("shortname"), SkinID = item.Value<ulong?>("skin_id") ?? 0, Command = item.Value<string>("command"), Image = item.Value<string>("image"), Price = item.Value<float?>("price") ?? 0f, Amount = item.Value<int?>("amount") ?? 1, Blueprint = item.Value<bool?>("is_blueprint") ?? false, contentItems = contents, kits = kitsInfo
}
);
}
}
}
LoadImage("https://cdn.rustpay.org/images/rust/blueprintbase.png");
foreach (ItemsList check in items) {
if (!HasImage(check.Image) && !string.IsNullOrEmpty(check.Image)) LoadImage(check.Image);
foreach (Items content in check.contentItems) if (!HasImage(content.Image)) LoadImage(content.Image);
foreach (var kit in check.kits.SelectMany(i => i.kitItems)) if (!HasImage(kit.Image)) LoadImage(kit.Image);
}
}
private void StoreDiscounts(JObject obj) {
var discountList = obj["data"]["discounts"];
if (discountList is JArray itemDiscounts) {
discounts.Clear();
foreach (JObject check in itemDiscounts) {
discounts.Add(new Discounts {
ID = check.Value<int?>("id") ?? 0, Type = check.Value<string>("product_type"), ProductID = check.Value<int?>("product_id") ?? 0, Discount = check.Value<int>("discount"),
}
);
}
}
}
private void StoreBonus(JObject obj) {
var bonusesList = obj["data"]["bonuses"];
if (bonusesList is JArray bonus) {
bonusList.Clear();
foreach (JObject check in bonus) {
int random = UnityEngine.Random.Range(0, BonusColorList.Count());
bonusList.Add(new Bonus() {
BonusText = check.Value<int?>("min_amount") ?? 0, BonusPercent = check.Value<int?>("bonus_percent") ?? 0, ColorBonus = BonusColorList[random]
}
);
}
}
}
private void StorePayments(JObject obj) {
var payments = obj["data"]["paymentMethods"];
if (payments is JArray paymentList) {
paymentMethods.Clear();
foreach (JObject check in paymentList) {
string name = check.Value<string>("method_code");
paymentMethods.Add(new PaymentMethods() {
ID = check.Value<int?>("id") ?? 0, PaymentText = check.Value<string>("method_name"), PaymentIcon = check.Value<string>("method_code").ToLower(), PaymentColor = "979797ff"
}
);
}
}
}
#endregion
#region Команды
[ConsoleCommand("rustpay.clearcache")]
private void ClearCacheCommand(ConsoleSystem.Arg arg) {
if (!arg.IsServerside || arg.Connection != null) {
arg.ReplyWith("Команда доступна только из серверной консоли");
return;
}
ClearCacheInternal();
arg.ReplyWith("Кэш изображений очищен");
}
[ConsoleCommand("rustpay.balance")]
private void CmdBalance(ConsoleSystem.Arg arg) {
if (!arg.IsAdmin) {
return;
}
if (arg.Args == null || arg.Args.Length < 3) {
arg.ReplyWith("Пример: rustpay.balance <steamId> <amount> <credit|debit> [reason]");
return;
}
ulong steamId;
if (!ulong.TryParse(arg.Args[0], out steamId)) {
arg.ReplyWith("Неверный SteamID");
return;
}
int amount;
if (!int.TryParse(arg.Args[1], out amount) || amount <= 0) {
arg.ReplyWith("Неверный amount");
return;
}
string type = arg.Args[2].ToLower();
if (type != "credit" && type != "debit") {
arg.ReplyWith("Доступные способы 'credit' или 'debit'");
return;
}
string reason = arg.Args.Length > 3 ? string.Join(" ", arg.Args.Skip(3)) : "console";
API_ChangeBalance(steamId, amount, type, reason, success => {
arg.ReplyWith(success ? $"[RustPay] {type} {amount} for {steamId} - SUCCESS" : $"[RustPay] {type} {amount} for {steamId} - FAILED");
}
);
}
private void ClearCacheInternal() {
try {
string cachePath = $"{Interface.Oxide.DataDirectory}/{ImageCachePath}";
if (System.IO.Directory.Exists(cachePath)) System.IO.Directory.Delete(cachePath, true);
_cachedImages.Clear();
SaveImageCache();
SaveDataBase();
}
catch (Exception ex) {
PrintError($"Ошибка очистки кэша: {ex.Message}");
}
}
[ChatCommand("pay")]
private void ChatRustPay(BasePlayer player) => RustPayUI(player);
[ConsoleCommand("rustpay")]
private void ConsoleRustPay(ConsoleSystem.Arg arg) {
BasePlayer player = arg.Player();
if (player != null && arg.HasArgs(1)) {
string command = arg.Args[0];
if (command == "openpromo") {
PromocodeUI(player);
EffectBtn(player);
}
if (command == "promo") {
string promo = string.Join(" ", arg.Args.Skip(1));
promoPlayer[player.userID] = promo;
}
if (command == "email") {
string email = string.Join(" ", arg.Args.Skip(1));
if (IsValidEmail(email)) {
emailPlayer[player.userID] = email;
}
}
if (command == "back") {
StoreUI(player);
EffectBtn(player);
}
if (command == "inventory") {
StoreUI(player, "inv");
EffectBtn(player);
}
if (command == "privacypolicy") {
StoreUI(player, "privacy");
EffectBtn(player);
}
if (command == "useragreement") {
StoreUI(player, "agreement");
EffectBtn(player);
}
if (command == "send.promo") {
string promo = promoPlayer[player.userID];
PromocodeUI(player);
if (promo != "") SendPromo(player, promo);
else NotificationUI(player, "promo", "Вы не указали промокод", true);
}
if (command == "balanceup") {
BalanceReplenishmentUI(player);
EffectBtn(player);
}
if (command == "category") {
int id = int.Parse(arg.Args[1]);
if (activeCategory[player.userID] != id) {
activeCategory[player.userID] = id;
ButtonCategoriesUI(player);
EffectBtn(player);
}
}
if (command == "fav.toggle") {
string id = arg.Args[1];
string type = arg.Args[2];
List<string> list = DBFavorites[player.userID];
if (list.Contains(type + id)) {
list.Remove(type + id);
if (activeCategory[player.userID] == 0) ItemsUI(player);
else UpdateItemUI(player, int.Parse(id), type);
}
else {
list.Add(type + id);
UpdateItemUI(player, int.Parse(id), type);
}
EffectBtn(player);
}
if (command == "openitem") {
if (arg.Args[2] == "item") OpenItemUI(player, int.Parse(arg.Args[1]));
else OpenSetAndKitsUI(player, int.Parse(arg.Args[1]), arg.Args[2]);
EffectBtn(player);
}
if (command == "change") {
int change = int.Parse(arg.Args[3]);
if (change <= 0) return;
ChangeItemCount(player, int.Parse(arg.Args[1]), arg.Args[2], change);
EffectBtn(player);
}
if (command == "buy") {
EffectBtn(player);
if (spamBlock.Contains(player.userID)) {
NotificationUI(player, arg.Args[2], "Подождите, слишком быстро!", true);
return;
}
spamBlock.Add(player.userID);
UpdateBuyButtonText(player, arg.Args[2], "Обработка..", true);
SendBuy(player, int.Parse(arg.Args[1]), arg.Args[2], float.Parse(arg.Args[3]), int.Parse(arg.Args[4]));
}
if (command == "take") {
TakeItem(player, int.Parse(arg.Args[1]), arg.Args[2], int.Parse(arg.Args[3]));
EffectBtn(player, "assets/bundled/prefabs/fx/weapons/survey_charge/survey_charge_stick.prefab");
}
if (command == "bonusvalue") {
string input = arg.Args[1];
CuiHelper.DestroyUi(player, "InputValue");
CuiElementContainer container = new CuiElementContainer();
container.Add(new CuiElement {
Parent = "BalanceInput", Name = "InputValue", Components = {
new CuiInputFieldComponent {
Command = "rustpay inputbalance", Text = input, FontSize = 12, Align = TextAnchor.MiddleLeft, Font = "robotocondensed-bold.ttf", Color = HexToCuiColor("#ffffff", 60), NeedsKeyboard = true, CharsLimit = 60
}
, new CuiRectTransformComponent {
AnchorMin = "0.05 0", AnchorMax = "0.86 1"
}
}
}
);
CuiHelper.AddUi(player, container);
inputBalancePlayer[player.userID] = int.Parse(arg.Args[1]);
EffectBtn(player);
}
if (command == "inputbalance") {
inputBalancePlayer[player.userID] = int.Parse(arg.Args[1]);
}
if (command == "payment") {
if (spamBlockPayment.Contains(player.userID)) return;
spamBlockPayment.Add(player.userID);
timer.Once(0.5f, () => spamBlockPayment.Remove(player.userID));
if (emailPlayer.ContainsKey(player.userID)) RequestUpBalance(player, int.Parse(arg.Args[1]), inputBalancePlayer[player.userID]);
EffectBtn(player);
}
if (command == "updateQR") {
UpdateIconQR(player, arg.Args[1]);
}
}
}
#endregion
#region Интерфейс
private void RustPayUI(BasePlayer player) {
if (!_serverDataReady) {
SendReply(player, "Магазин загружается, подождите...");
return;
}
if (_pendingImages > 0) {
SendReply(player, $"Загрузка изображений ({_pendingImages} осталось), подождите...");
return;
}
SetPlayerCategory(player);
_ws.GetPlayerBalance(player.userID);
CuiHelper.DestroyUi(player, Layer);
CuiElementContainer 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", Layer);
container.Add(new CuiPanel {
RectTransform = {
AnchorMin = "0 0", AnchorMax = "1 1", OffsetMax = "0 0"
}
, Image = {
Sprite = "assets/content/ui/ui.background.transparent.radial.psd", Color = HexToCuiColor("#363636", 60)
}
}
, Layer);
container.Add(new CuiElement {
Parent = Layer, Components = {
new CuiRawImageComponent {
Png = GetImage(BackgroundUrl), Color = "1 1 1 1"
}
, new CuiRectTransformComponent {
AnchorMin = "0 0", AnchorMax = "1 1", OffsetMax = "0 0"
}
}
}
);
container.Add(new CuiPanel {
RectTransform = {
AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = "-640 -360", OffsetMax = "640 360"
}
, Image = {
Color = "0 0 0 0"
}
}
, Layer, "MainStore");
container.Add(new CuiButton {
RectTransform = {
AnchorMin = "0.924 0.945", AnchorMax = "0.989 0.981", OffsetMax = "0 0"
}
, Button = {
Color = HexToCuiColor("#cccccc", 5), Close = Layer, Material = "assets/content/ui/uibackgroundblur.mat"
}
}
, "MainStore"
// ... [Code preview truncated due to length. Full source in attached file] ...