Иконка ресурса

FREE CryptoTrade : 3.2.3 3.2.3

Нет прав для скачивания
C#:
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using UnityEngine;

namespace Oxide.Plugins;

[Info("CryptoTrade", "BLOOD KRAKEN BLKR", "3.2.3")]
internal class CryptoTrade : RustPlugin
{
    [PluginReference] private Plugin IQEconomic;
    [PluginReference] private Plugin ImageLibrary;
    
    private Configuration _config;
    private StoredData _data;
    private Dictionary<ulong, PendingValidation> _pendingValidations = new Dictionary<ulong, PendingValidation>();
    private Dictionary<ulong, string> _linkedWallets = new Dictionary<ulong, string>();
    private List<ulong> _toRemoveBuffer = new List<ulong>();

    private const string BUTTON_IMAGE_NAME = "CryptoTrade_Button";
    private const string PermAdmin = "cryptotrade.admin";

    private class PendingValidation
    {
        public string Wallet;
        public int Amount;
        public float StartTime;
    }

    #region Config

    private class Configuration
    {
        [JsonProperty(PropertyName = "Ключ от тг бота", ObjectCreationHandling = ObjectCreationHandling.Replace)]
        public string ApiKey = "ВАШ_ТОКЕН_ЗДЕСЬ";

        [JsonProperty(PropertyName = "Минимальное количество игровой валюты для обмена",
            ObjectCreationHandling = ObjectCreationHandling.Replace)]
        public int MinCoin = 100000;

        [JsonProperty(PropertyName = "Курс 1 коина к Solana",
            ObjectCreationHandling = ObjectCreationHandling.Replace)]
        public int Course = 1000;

        [JsonProperty(PropertyName = "TG ChatId", ObjectCreationHandling = ObjectCreationHandling.Replace)]
        public long ChatId = -4979038064;
        
        [JsonProperty(PropertyName = "Включить логирование в файл",
            ObjectCreationHandling = ObjectCreationHandling.Replace)]
        public bool EnableFileLogging = true;

        [JsonProperty(PropertyName = "Helius API Key (для проверки кошельков)",
            ObjectCreationHandling = ObjectCreationHandling.Replace)]
        public string HeliusApiKey = "ВАШ_HELIUS_API_KEY";

        [JsonProperty(PropertyName = "Включить проверку кошельков",
            ObjectCreationHandling = ObjectCreationHandling.Replace)]
        public bool EnableWalletValidation = true;

        [JsonProperty(PropertyName = "Таймаут проверки кошелька (секунды)",
            ObjectCreationHandling = ObjectCreationHandling.Replace)]
        public int ValidationTimeout = 10;

        [JsonProperty(PropertyName = "Включить кнопку запуска",
            ObjectCreationHandling = ObjectCreationHandling.Replace)]
        public bool EnableLaunchButton = true;

        [JsonProperty(PropertyName = "URL картинки кнопки (рекомендуется 512x512)",
            ObjectCreationHandling = ObjectCreationHandling.Replace)]
        public string ButtonImageUrl = "https://i.yapx.ru/c29vJ.png";

        [JsonProperty(PropertyName = "Размер кнопки (пиксели)",
            ObjectCreationHandling = ObjectCreationHandling.Replace)]
        public float ButtonSize = 80f;

        [JsonProperty(PropertyName = "Отступ от правого края (пиксели)",
            ObjectCreationHandling = ObjectCreationHandling.Replace)]
        public float ButtonRightOffset = 20f;

        [JsonProperty(PropertyName = "Отступ от верхнего края (пиксели, 0 = центр)",
            ObjectCreationHandling = ObjectCreationHandling.Replace)]
        public float ButtonTopOffset = 100f;

        [JsonProperty(PropertyName = "Включить недельный лимит вывода",
            ObjectCreationHandling = ObjectCreationHandling.Replace)]
        public bool EnableWeeklyLimit = true;

        [JsonProperty(PropertyName = "Максимальная сумма вывода за неделю (в игровой валюте)",
            ObjectCreationHandling = ObjectCreationHandling.Replace)]
        public int WeeklyLimitAmount = 5000000;

        [JsonProperty(PropertyName = "День сброса лимита (0 = Воскресенье, 1 = Понедельник...)",
            ObjectCreationHandling = ObjectCreationHandling.Replace)]
        public int ResetDay = 1;

        [JsonProperty(PropertyName = "Час сброса лимита (0-23)",
            ObjectCreationHandling = ObjectCreationHandling.Replace)]
        public int ResetHour = 0;
    }

    protected override void LoadConfig()
    {
        base.LoadConfig();
        try
        {
            _config = Config.ReadObject<Configuration>();
            if (_config == null) throw new Exception();
            SaveConfig();
        }
        catch
        {
            PrintError("Your configuration file contains an error. Using default configuration values.");
            LoadDefaultConfig();
        }
    }

    protected override void SaveConfig()
    {
        Config.WriteObject(_config);
    }

    protected override void LoadDefaultConfig()
    {
        _config = new Configuration();
    }

    #endregion

    #region Data

    private class WithdrawalData
    {
        public string PlayerName;
        public ulong PlayerId;
        public string Wallet;
        public int Amount;
        public long SolanaAmount; // ИСПРАВЛЕНО: было int, стало long
        public DateTime Timestamp;
        public string Status;
    }

    private class StoredData
    {
        public List<WithdrawalData> Withdrawals = new List<WithdrawalData>();
        public Dictionary<ulong, PlayerWeeklyLimit> PlayerLimits = new Dictionary<ulong, PlayerWeeklyLimit>();
    }

    private class PlayerWeeklyLimit
    {
        public int WithdrawnThisWeek = 0;
        public DateTime LastResetTime = DateTime.MinValue;
        public List<WeeklyWithdrawal> WeeklyHistory = new List<WeeklyWithdrawal>();
    }

    private class WeeklyWithdrawal
    {
        public DateTime Timestamp;
        public int Amount;
        public long SolanaAmount; // ИСПРАВЛЕНО: было int, стало long
        public string Wallet;
    }

    private class PlayerWalletData
    {
        public string PlayerName;
        public ulong SteamID;
        public string SolanaWallet;
        public List<WithdrawalHistory> WithdrawalHistory = new List<WithdrawalHistory>();
    }

    private class WithdrawalHistory
    {
        public DateTime Timestamp;
        public int Amount;
        public long SolanaAmount; // ИСПРАВЛЕНО: было int, стало long
    }

    private void LoadData()
    {
        try
        {
            _data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>("CryptoTrade_Data");
            if (_data == null) _data = new StoredData();
            
            CheckAndResetWeeklyLimits();
        }
        catch
        {
            _data = new StoredData();
        }
    }

    private void SaveData()
    {
        Interface.Oxide.DataFileSystem.WriteObject("CryptoTrade_Data", _data);
    }

    private void EnsureDirectoryExists()
    {
        var directory = Path.Combine(Interface.Oxide.DataDirectory, "CryptoTradeSystem");
        if (!Directory.Exists(directory))
            Directory.CreateDirectory(directory);
    }

    private void LoadLinkedWallets()
    {
        EnsureDirectoryExists();
        var directory = Path.Combine(Interface.Oxide.DataDirectory, "CryptoTradeSystem");
        
        if (!Directory.Exists(directory))
        {
            PrintWarning($"Директория {directory} не существует");
            return;
        }

        var files = Directory.GetFiles(directory, "*.json");
        PrintWarning($"Найдено {files.Length} файлов кошельков");

        foreach (var file in files)
        {
            try
            {
                var fileName = Path.GetFileNameWithoutExtension(file);
                if (ulong.TryParse(fileName, out var steamId))
                {
                    var json = File.ReadAllText(file);
                    var walletData = JsonConvert.DeserializeObject<PlayerWalletData>(json);
                    if (walletData != null && !string.IsNullOrEmpty(walletData.SolanaWallet))
                    {
                        _linkedWallets[steamId] = walletData.SolanaWallet;
                        PrintWarning($"Загружен кошелек для {steamId}: {walletData.SolanaWallet.Substring(0, 8)}...");
                    }
                    else
                    {
                        PrintWarning($"Файл {file} пустой или некорректный");
                    }
                }
                else
                {
                    PrintWarning($"Не удалось распарсить SteamID из имени файла: {fileName}");
                }
            }
            catch (Exception ex)
            {
                PrintError($"Ошибка загрузки кошелька из {file}: {ex.Message}");
            }
        }
        
        PrintWarning($"Всего загружено кошельков: {_linkedWallets.Count}");
    }

    private void SavePlayerWallet(BasePlayer player, string wallet)
    {
        EnsureDirectoryExists();
        var directory = Path.Combine(Interface.Oxide.DataDirectory, "CryptoTradeSystem");
        var filePath = Path.Combine(directory, $"{player.userID}.json");
        
        PlayerWalletData walletData;
        if (File.Exists(filePath))
        {
            try
            {
                var json = File.ReadAllText(filePath);
                walletData = JsonConvert.DeserializeObject<PlayerWalletData>(json) ?? new PlayerWalletData();
            }
            catch
            {
                walletData = new PlayerWalletData();
            }
        }
        else
        {
            walletData = new PlayerWalletData();
        }

        walletData.PlayerName = player.displayName;
        walletData.SteamID = player.userID.Get();
        walletData.SolanaWallet = wallet;

        try
        {
            File.WriteAllText(filePath, JsonConvert.SerializeObject(walletData, Formatting.Indented));
            _linkedWallets[player.userID.Get()] = wallet;
            PrintWarning($"Сохранен кошелек для {player.userID}: {wallet.Substring(0, 8)}...");
        }
        catch (Exception ex)
        {
            PrintError($"Ошибка сохранения кошелька: {ex.Message}");
        }
    }

    private void UnlinkPlayerWallet(BasePlayer player)
    {
        var directory = Path.Combine(Interface.Oxide.DataDirectory, "CryptoTradeSystem");
        var filePath = Path.Combine(directory, $"{player.userID}.json");
        
        if (File.Exists(filePath))
        {
            try
            {
                File.Delete(filePath);
                PrintWarning($"Удален файл кошелька для {player.userID}");
            }
            catch (Exception ex)
            {
                PrintError($"Ошибка удаления файла кошелька: {ex.Message}");
            }
        }
        
        _linkedWallets.Remove(player.userID.Get());
    }

    private void AddWithdrawalToHistory(BasePlayer player, int amount, long solanaAmount)
    {
        var directory = Path.Combine(Interface.Oxide.DataDirectory, "CryptoTradeSystem");
        var filePath = Path.Combine(directory, $"{player.userID}.json");
        
        PlayerWalletData walletData;
        
        // Загружаем существующие данные или создаём новые
        if (File.Exists(filePath))
        {
            try
            {
                var json = File.ReadAllText(filePath);
                walletData = JsonConvert.DeserializeObject<PlayerWalletData>(json);
                if (walletData == null) walletData = new PlayerWalletData();
            }
            catch (Exception ex)
            {
                PrintError($"[CryptoTrade] Ошибка чтения истории: {ex.Message}");
                walletData = new PlayerWalletData();
            }
        }
        else
        {
            walletData = new PlayerWalletData();
        }

        // Обновляем данные игрока
        walletData.PlayerName = player.displayName;
        walletData.SteamID = player.userID.Get();
        
        // Инициализируем список если null
        if (walletData.WithdrawalHistory == null)
            walletData.WithdrawalHistory = new List<WithdrawalHistory>();

        // Добавляем в историю (не перезаписываем!)
        walletData.WithdrawalHistory.Add(new WithdrawalHistory
        {
            Timestamp = DateTime.Now,
            Amount = amount,
            SolanaAmount = solanaAmount
        });

        // Сохраняем
        try
        {
            File.WriteAllText(filePath, JsonConvert.SerializeObject(walletData, Formatting.Indented));
            PrintWarning($"[CryptoTrade] История сохранена: {player.displayName} - {amount} (всего записей: {walletData.WithdrawalHistory.Count})");
        }
        catch (Exception ex)
        {
            PrintError($"[CryptoTrade] Ошибка сохранения истории: {ex.Message}");
        }
    }

    private string GetLinkedWallet(ulong steamId)
    {
        if (_linkedWallets.ContainsKey(steamId))
            return _linkedWallets[steamId];
        return "";
    }

    #endregion

    #region Weekly Limit System

    private void CheckAndResetWeeklyLimits()
    {
        if (!_config.EnableWeeklyLimit) return;

        var now = DateTime.Now;
        var lastReset = GetLastGlobalResetTime();
        
        if (now >= lastReset.AddDays(7))
        {
            var nextReset = GetNextResetTime(now.AddDays(-7));
            
            if (now >= nextReset)
            {
                PrintWarning($"[CryptoTrade] Сброс недельных лимитов. Время: {now}");
                
                foreach (var kvp in _data.PlayerLimits.ToList())
                {
                    var playerLimit = kvp.Value;
                    
                    playerLimit.WithdrawnThisWeek = 0;
                    playerLimit.LastResetTime = now;
                }
                
                SaveGlobalResetTime(now);
                SaveData();
            }
        }
    }

    private DateTime GetLastGlobalResetTime()
    {
        try
        {
            var resetFile = Path.Combine(Interface.Oxide.DataDirectory, "CryptoTradeSystem", "last_reset.txt");
            if (File.Exists(resetFile))
            {
                var text = File.ReadAllText(resetFile);
                if (DateTime.TryParse(text, out var date))
                    return date;
            }
        }
        catch { }
        
        return GetCurrentWeekStart();
    }

    private void SaveGlobalResetTime(DateTime time)
    {
        try
        {
            var resetFile = Path.Combine(Interface.Oxide.DataDirectory, "CryptoTradeSystem", "last_reset.txt");
            File.WriteAllText(resetFile, time.ToString("O"));
        }
        catch (Exception ex)
        {
            PrintError($"Ошибка сохранения времени сброса: {ex.Message}");
        }
    }

    private DateTime GetCurrentWeekStart()
    {
        var now = DateTime.Now;
        var daysSinceReset = ((int)now.DayOfWeek - _config.ResetDay + 7) % 7;
        return new DateTime(now.Year, now.Month, now.Day, _config.ResetHour, 0, 0)
            .AddDays(-daysSinceReset);
    }

    private DateTime GetNextResetTime(DateTime from)
    {
        var daysUntilReset = ((int)DayOfWeek.Sunday - (int)from.DayOfWeek + _config.ResetDay + 7) % 7;
        if (daysUntilReset == 0 && from.Hour >= _config.ResetHour)
            daysUntilReset = 7;
            
        return new DateTime(from.Year, from.Month, from.Day, _config.ResetHour, 0, 0)
            .AddDays(daysUntilReset);
    }

    private PlayerWeeklyLimit GetPlayerLimit(ulong steamId)
    {
        if (!_data.PlayerLimits.ContainsKey(steamId))
        {
            _data.PlayerLimits[steamId] = new PlayerWeeklyLimit
            {
                LastResetTime = DateTime.Now
            };
        }
        return _data.PlayerLimits[steamId];
    }

    private int GetRemainingWeeklyLimit(ulong steamId)
    {
        if (!_config.EnableWeeklyLimit) return int.MaxValue;
        
        CheckAndResetWeeklyLimits();
        
        var limit = GetPlayerLimit(steamId);
        return Math.Max(0, _config.WeeklyLimitAmount - limit.WithdrawnThisWeek);
    }

    private void AddToWeeklyLimit(ulong steamId, int amount)
    {
        if (!_config.EnableWeeklyLimit) return;
        
        var limit = GetPlayerLimit(steamId);
        limit.WithdrawnThisWeek += amount;
        
        // Используем long для расчета
        long solanaAmount = (long)amount * (long)_config.Course;
        
        limit.WeeklyHistory.Add(new WeeklyWithdrawal
        {
            Timestamp = DateTime.Now,
            Amount = amount,
            SolanaAmount = solanaAmount,
            Wallet = _linkedWallets.ContainsKey(steamId) ? _linkedWallets[steamId] : ""
        });
        
        SaveData();
        PrintWarning($"[CryptoTrade] Лимит обновлен: {steamId} +{amount} (всего: {limit.WithdrawnThisWeek}/{_config.WeeklyLimitAmount})");
    }

    private string GetTimeUntilReset()
    {
        var now = DateTime.Now;
        var nextReset = GetNextResetTime(now);
        var timeLeft = nextReset - now;
        
        if (timeLeft.TotalDays >= 1)
            return $"{(int)timeLeft.TotalDays}д {timeLeft.Hours}ч";
        else if (timeLeft.TotalHours >= 1)
            return $"{(int)timeLeft.TotalHours}ч {timeLeft.Minutes}м";
        else
            return $"{(int)timeLeft.TotalMinutes}м";
    }

    #endregion

    #region OxideHooks

    private void Init()
    {
        permission.RegisterPermission(PermAdmin, this);
        LoadData();
        LoadLinkedWallets();
    }

    private void OnServerInitialized()
    {
        if (IQEconomic == null)
        {
            PrintError("|-----------------------------------|");
            PrintError("|     IQEconomic НЕ НАЙДЕН!         |");
            PrintError("|  Плагин не будет работать без него|");
            PrintError("|-----------------------------------|");
            return;
        }

        PrintError("|-----------------------------------|");
        PrintWarning($"|  Plugin {Title} v{Version} is loaded  |");
        PrintWarning("|          Telegram: SNBLKR      |");
        PrintError("|-----------------------------------|");
        
        if (_config.EnableWalletValidation && _config.HeliusApiKey == "ВАШ_HELIUS_API_KEY")
        {
            PrintWarning("ВНИМАНИЕ: Helius API Key не настроен! Проверка кошельков будет отключена.");
        }

        if (ImageLibrary == null)
        {
            PrintWarning("ВНИМАНИЕ: ImageLibrary не найден! Кнопка запуска работать не будет.");
        }
        else if (_config.EnableLaunchButton && !string.IsNullOrEmpty(_config.ButtonImageUrl))
        {
            RegisterButtonImage();
        }

        LoadLinkedWallets();
        
        timer.Every(3600f, () => CheckAndResetWeeklyLimits());
    }

    private void Unload()
    {
        SaveData();
        
        foreach (var player in BasePlayer.activePlayerList)
        {
            HideLaunchButton(player);
        }
    }

    private void OnTick()
    {
        _toRemoveBuffer.Clear();
        
        foreach (var kvp in _pendingValidations)
        {
            if (Time.realtimeSinceStartup - kvp.Value.StartTime > _config.ValidationTimeout)
            {
                _toRemoveBuffer.Add(kvp.Key);
            }
        }
        
        foreach (var id in _toRemoveBuffer)
        {
            _pendingValidations.Remove(id);
        }
    }

    private void OnPlayerRespawned(BasePlayer player)
    {
        if (_config.EnableLaunchButton && ImageLibrary != null)
        {
            timer.Once(2f, () => ShowLaunchButton(player));
        }
    }

    private void OnPlayerSleepEnded(BasePlayer player)
    {
        if (_config.EnableLaunchButton && ImageLibrary != null)
        {
            timer.Once(2f, () => ShowLaunchButton(player));
        }
    }

    private void OnPlayerDisconnected(BasePlayer player, string reason)
    {
        HideLaunchButton(player);
    }

    #endregion

    #region ImageLibrary Integration

    private void RegisterButtonImage()
    {
        if (ImageLibrary == null) return;

        var hasImage = ImageLibrary.Call("HasImage", BUTTON_IMAGE_NAME, (ulong)0);
        if (hasImage is bool has && has)
        {
            PrintWarning("Картинка кнопки уже зарегистрирована в ImageLibrary");
            return;
        }

        ImageLibrary.Call("AddImage", _config.ButtonImageUrl, BUTTON_IMAGE_NAME, (ulong)0, (Action)(() =>
        {
            PrintWarning($"Картинка кнопки успешно загружена в ImageLibrary");
            
            foreach (var player in BasePlayer.activePlayerList)
            {
                ShowLaunchButton(player);
            }
        }));
        
        PrintWarning($"Регистрация картинки кнопки в ImageLibrary: {_config.ButtonImageUrl}");
    }

    private string GetButtonImage()
    {
        if (ImageLibrary == null) return null;

        var result = ImageLibrary.Call("GetImage", BUTTON_IMAGE_NAME, (ulong)0, false);
        
        if (result == null || string.IsNullOrEmpty(result.ToString()))
        {
            PrintWarning("Картинка кнопки не найдена в ImageLibrary");
            return null;
        }

        return result.ToString();
    }

    #endregion

    #region Launch Button

    private void ShowLaunchButton(BasePlayer player)
    {
        if (!_config.EnableLaunchButton) return;
        if (ImageLibrary == null) return;
        if (player == null || !player.IsConnected) return;

        HideLaunchButton(player);

        var imageId = GetButtonImage();
        if (string.IsNullOrEmpty(imageId))
        {
            RegisterButtonImage();
            return;
        }

        var container = new CuiElementContainer();

        var screenWidth = 1920f;
        var screenHeight = 1080f;
        
        var buttonSize = _config.ButtonSize;
        var rightOffset = _config.ButtonRightOffset;
        var topOffset = _config.ButtonTopOffset;

        var anchorMinX = 1f - (rightOffset + buttonSize) / screenWidth;
        var anchorMaxX = 1f - rightOffset / screenWidth;
        
        var anchorMinY = 1f - (topOffset + buttonSize) / screenHeight;
        var anchorMaxY = 1f - topOffset / screenHeight;

        if (topOffset <= 0)
        {
            var centerY = 0.5f;
            var halfHeight = (buttonSize / screenHeight) / 2f;
            anchorMinY = centerY - halfHeight;
            anchorMaxY = centerY + halfHeight;
        }

        anchorMinX = Mathf.Round(anchorMinX * 1000f) / 1000f;
        anchorMaxX = Mathf.Round(anchorMaxX * 1000f) / 1000f;
        anchorMinY = Mathf.Round(anchorMinY * 1000f) / 1000f;
        anchorMaxY = Mathf.Round(anchorMaxY * 1000f) / 1000f;

        container.Add(new CuiElement
        {
            Name = "CryptoTrade_LaunchButton",
            Parent = "Overlay",
            Components =
            {
                new CuiRawImageComponent
                {
                    Png = imageId,
                    Color = "1 1 1 1"
                },
                new CuiRectTransformComponent
                {
                    AnchorMin = $"{anchorMinX} {anchorMinY}",
                    AnchorMax = $"{anchorMaxX} {anchorMaxY}",
                    OffsetMin = "0 0",
                    OffsetMax = "0 0"
                }
            }
        });

        container.Add(new CuiButton
        {
            Button =
            {
                Color = "0 0 0 0",
                Command = "cryptolaunch"
            },
            RectTransform =
            {
                AnchorMin = $"{anchorMinX} {anchorMinY}",
                AnchorMax = $"{anchorMaxX} {anchorMaxY}",
                OffsetMin = "0 0",
                OffsetMax = "0 0"
            },
            Text =
            {
                Text = ""
            }
        }, "Overlay", "CryptoTrade_LaunchButton_Click");

        CuiHelper.AddUi(player, container);
    }

    private void HideLaunchButton(BasePlayer player)
    {
        if (player == null) return;
        
        CuiHelper.DestroyUi(player, "CryptoTrade_LaunchButton");
        CuiHelper.DestroyUi(player, "CryptoTrade_LaunchButton_Click");
    }

    [ConsoleCommand("cryptolaunch")]
    private void CmdCryptoLaunch(ConsoleSystem.Arg arg)
    {
        var player = arg.Player();
        if (player == null) return;

        HideLaunchButton(player);
        CmdChatcrypto(player, "crypto", new string[0]);
    }

    #endregion

    #region Function

    [ChatCommand("crypto")]
    private void CmdChatcrypto(BasePlayer player, string command, string[] args)
    {
        if (IQEconomic == null)
        {
            player.ChatMessage("<color=red>Ошибка: экономика не загружена!</color>");
            return;
        }
        
        HideLaunchButton(player);
        
        var linkedWallet = GetLinkedWallet(player.userID.Get());
        var isLinked = !string.IsNullOrEmpty(linkedWallet);
        
        PrintWarning($"Игрок {player.displayName} открыл UI. Кошелек привязан: {isLinked}");
        
        ShowMainUI(player, linkedWallet, _config.MinCoin, "", isLinked);
    }

    [ConsoleCommand("UI_CRYPTOTRADE")]
    private void CmdConsoleUI_CRYPTOTRADE(ConsoleSystem.Arg arg)
    {
        var player = arg.Player();
        if (player == null || arg.Args == null || arg.Args.Length < 1) return;

        switch (arg.Args[0])
        {
            case "CLOSE":
            {
                if (_config.EnableLaunchButton && ImageLibrary != null)
                {
                    ShowLaunchButton(player);
                }
                break;
            }
            case "ADDRESS":
            {
                if (arg.Args.Length < 3) return;
                var amount = arg.GetInt(1);
                var wallet = arg.Args[2] ?? "";
                var isLinked = !string.IsNullOrEmpty(GetLinkedWallet(player.userID.Get()));
                ShowMainUI(player, wallet, amount, "", isLinked);
                break;
            }
            case "COIN":
            {
                if (arg.Args.Length < 3) return;
                var wallet = arg.Args[1] ?? "";
                var amountStr = arg.Args[2];
                int amount;
                if (!int.TryParse(amountStr, out amount))
                {
                    amount = _config.MinCoin;
                }
                var isLinked = !string.IsNullOrEmpty(GetLinkedWallet(player.userID.Get()));
                ShowMainUI(player, wallet, amount, "", isLinked);
                break;
            }
            case "LINK_WALLET":
            {
                if (arg.Args.Length < 2) return;
                var wallet = arg.Args[1] ?? "";
                LinkWallet(player, wallet);
                break;
            }
            case "UNLINK_WALLET":
            {
                UnlinkWallet(player);
                break;
            }
            case "CONFIRM":
            {
                if (arg.Args.Length < 3) return;
                var wallet = arg.Args[1] ?? "";
                var amount = arg.GetInt(2);
                
                if (_config.EnableWeeklyLimit)
                {
                    var remainingLimit = GetRemainingWeeklyLimit(player.userID.Get());
                    if (amount > remainingLimit)
                    {
                        ShowMainUI(player, wallet, amount, $"<color=red>❌ Превышен недельный лимит! Доступно: {remainingLimit}</color>", true);
                        return;
                    }
                }
                
                if (_config.EnableWalletValidation)
                {
                    ValidateWalletAndProcess(player, wallet, amount);
                }
                else
                {
                    ProcessWithdrawal(player, wallet, amount);
                }
                break;
            }
        }
    }

    private void LinkWallet(BasePlayer player, string wallet)
    {
        PrintWarning($"Попытка привязки кошелька {wallet.Substring(0, 8)}... для {player.displayName}");

        if (!IsValidSolanaAddress(wallet))
        {
            ShowMainUI(player, wallet, _config.MinCoin, "<color=red>Некорректный формат адреса Solana!</color>", false);
            return;
        }

        if (_config.EnableWalletValidation && _config.HeliusApiKey != "ВАШ_HELIUS_API_KEY")
        {
            ShowMainUI(player, wallet, _config.MinCoin, "<color=yellow>⏳ Проверка кошелька...</color>", false);

            var heliusUrl = $"https://mainnet.helius-rpc.com/?api-key={_config.HeliusApiKey}";
            var payload = new
            {
                jsonrpc = "2.0",
                id = 1,
                method = "getBalance",
                @params = new object[] { wallet }
            };

            var jsonPayload = JsonConvert.SerializeObject(payload);

            webrequest.Enqueue(heliusUrl, jsonPayload, (code, response) =>
            {
                if (code != 200 || string.IsNullOrEmpty(response))
                {
                    ShowMainUI(player, wallet, _config.MinCoin, "<color=red>Ошибка проверки кошелька.</color>", false);
                    return;
                }

                try
                {
                    var json = JObject.Parse(response);
                    var error = json["error"];
                    if (error != null)
                    {
                        var errorMessage = error["message"]?.ToString() ?? "";
                        if (errorMessage.Contains("could not find account") || errorMessage.Contains("Invalid param"))
                        {
                            ShowMainUI(player, wallet, _config.MinCoin, "<color=red>❌ Кошелек не найден в сети Solana!</color>", false);
                            return;
                        }
                    }

                    SavePlayerWallet(player, wallet);
                    PrintWarning($"Кошелек привязан для {player.displayName}");
                    ShowMainUI(player, wallet, _config.MinCoin, "<color=green>✅ Кошелек успешно привязан!</color>", true);
                }
                catch (Exception ex)
                {
                    PrintError($"Ошибка: {ex.Message}");
                    ShowMainUI(player, wallet, _config.MinCoin, "<color=red>Ошибка проверки.</color>", false);
                }
            }, this, Core.Libraries.RequestMethod.POST);
        }
        else
        {
            SavePlayerWallet(player, wallet);
            PrintWarning($"Кошелек привязан (без проверки) для {player.displayName}");
            ShowMainUI(player, wallet, _config.MinCoin, "<color=green>✅ Кошелек успешно привязан!</color>", true);
        }
    }

    private void UnlinkWallet(BasePlayer player)
    {
        PrintWarning($"Отвязка кошелька для {player.displayName}");
        UnlinkPlayerWallet(player);
        ShowMainUI(player, "", _config.MinCoin, "<color=yellow>Кошелек отвязан.</color>", false);
    }

    private void ValidateWalletAndProcess(BasePlayer player, string wallet, int amount)
    {
        if (!IsValidSolanaAddress(wallet))
        {
            ShowMainUI(player, wallet, amount, "<color=red>Некорректный формат адреса Solana!</color>", true);
            return;
        }

        if (amount < _config.MinCoin)
        {
            ShowMainUI(player, wallet, amount, $"<color=red>Минимальная сумма: {_config.MinCoin}</color>", true);
            return;
        }

        var currentBalance = GetBalance(player);
        if (currentBalance < amount)
        {
            ShowMainUI(player, wallet, amount, $"<color=red>Недостаточно средств! Доступно: {currentBalance}</color>", true);
            return;
        }

        if (_config.EnableWeeklyLimit)
        {
            var remainingLimit = GetRemainingWeeklyLimit(player.userID.Get());
            if (amount > remainingLimit)
            {
                ShowMainUI(player, wallet, amount, $"<color=red>❌ Превышен недельный лимит! Доступно: {remainingLimit}</color>", true);
                return;
            }
        }

        if (_config.HeliusApiKey == "ВАШ_HELIUS_API_KEY")
        {
            ProcessWithdrawal(player, wallet, amount);
            return;
        }

        ShowMainUI(player, wallet, amount, "<color=yellow>⏳ Проверка кошелька...</color>", true);

        _pendingValidations[player.userID.Get()] = new PendingValidation
        {
            Wallet = wallet,
            Amount = amount,
            StartTime = Time.realtimeSinceStartup
        };

        var heliusUrl = $"https://mainnet.helius-rpc.com/?api-key={_config.HeliusApiKey}";
        var payload = new
        {
            jsonrpc = "2.0",
            id = 1,
            method = "getBalance",
            @params = new object[] { wallet }
        };

        var jsonPayload = JsonConvert.SerializeObject(payload);

        webrequest.Enqueue(heliusUrl, jsonPayload, (code, response) =>
        {
            _pendingValidations.Remove(player.userID.Get());

            if (code != 200 || string.IsNullOrEmpty(response))
            {
                ShowMainUI(player, wallet, amount, "<color=red>Ошибка проверки кошелька.</color>", true);
                return;
            }

            try
            {
                var json = JObject.Parse(response);
                var error = json["error"];
                if (error != null)
                {
                    var errorMessage = error["message"]?.ToString() ?? "";
                    if (errorMessage.Contains("could not find account") || errorMessage.Contains("Invalid param"))
                    {
                        ShowMainUI(player, wallet, amount, "<color=red>❌ Кошелек не найден!</color>", true);
                        return;
                    }
                }

                ProcessWithdrawal(player, wallet, amount);
            }
            catch (Exception ex)
            {
                PrintError($"Ошибка: {ex.Message}");
                ShowMainUI(player, wallet, amount, "<color=red>Ошибка проверки.</color>", true);
            }
        }, this, Core.Libraries.RequestMethod.POST);
    }

    private void ProcessWithdrawal(BasePlayer player, string wallet, int amount)
    {
        // ПРОВЕРКА ЛИМИТА В НАЧАЛЕ
        if (_config.EnableWeeklyLimit)
        {
            var remainingLimit = GetRemainingWeeklyLimit(player.userID.Get());
            if (amount > remainingLimit)
            {
                ShowMainUI(player, wallet, amount, $"<color=red>❌ Превышен недельный лимит! Доступно: {remainingLimit}</color>", true);
                player.ChatMessage($"<color=red>❌ Ошибка: превышен недельный лимит вывода!</color>");
                PrintWarning($"[CryptoTrade] Блокировка: {player.displayName} пытался вывести {amount}, доступно {remainingLimit}");
                return;
            }
        }

        var oldBalance = GetBalance(player);
        
        try
        {
            IQEconomic.Call("API_REMOVE_BALANCE", player.userID.Get(), amount);
        }
        catch (Exception ex)
        {
            PrintError($"Ошибка при списании: {ex.Message}");
            ShowMainUI(player, wallet, amount, "<color=red>Ошибка при списании.</color>", true);
            return;
        }

        var newBalance = GetBalance(player);
        
        if (newBalance >= oldBalance)
        {
            ShowMainUI(player, wallet, amount, "<color=red>Не удалось списать средства.</color>", true);
            return;
        }

        // РАСЧЁТ В LONG ЧТОБЫ ИЗБЕЖАТЬ ПЕРЕПОЛНЕНИЯ
        long solanaAmount = (long)amount * (long)_config.Course;
        PrintWarning($"[CryptoTrade] Расчёт: {amount} * {_config.Course} = {solanaAmount}");

        AddToWeeklyLimit(player.userID.Get(), amount);

        var withdrawal = new WithdrawalData
        {
            PlayerName = player.displayName,
            PlayerId = player.userID.Get(),
            Wallet = wallet,
            Amount = amount,
            SolanaAmount = solanaAmount,
            Timestamp = DateTime.Now,
            Status = "Pending"
        };
        _data.Withdrawals.Add(withdrawal);
        SaveData();

        AddWithdrawalToHistory(player, amount, solanaAmount);

        var message = $"💰 *Новый запрос на вывод*\n\n" +
                      $"👤 Игрок: `{player.displayName}`\n" +
                      $"🆔 SteamID: `{player.userID}`\n" +
                      $"💵 Сумма: `{amount}` игровой валюты\n" +
                      $"🪙 Solana: `{solanaAmount}`\n" +
                      $"📫 Кошелек: `{wallet}`\n" +
                      $"✅ Проверка: Пройдена\n" +
                      $"🕐 Время: `{DateTime.Now:dd.MM.yyyy HH:mm:ss}`";
        
        SendTgMessage(message);
        
        player.ChatMessage($"<color=green>✅ Запрос на вывод {solanaAmount} Solana успешно создан!</color>");

        ShowMainUI(player, wallet, _config.MinCoin, "", true);
    }

    private bool IsValidSolanaAddress(string address)
    {
        if (string.IsNullOrEmpty(address)) return false;
        
        var base58Pattern = "^[1-9A-HJ-NP-Za-km-z]+$";
        if (!Regex.IsMatch(address, base58Pattern)) return false;
        if (address.Length < 32 || address.Length > 44) return false;
        
        return true;
    }

    public void SendTgMessage(string message)
    {
        if (string.IsNullOrEmpty(_config.ApiKey) || _config.ApiKey == "ВАШ_ТОКЕН_ЗДЕСЬ")
        {
            PrintError("API ключ Telegram не настроен!");
            return;
        }

        var url = $"https://api.telegram.org/bot{_config.ApiKey}/sendMessage" +
                  $"?chat_id={_config.ChatId}" +
                  $"&text={Uri.EscapeDataString(message)}" +
                  $"&parse_mode=Markdown";

        webrequest.Enqueue(url, "", (code, response) =>
        {
            if (code != 200)
            {
                PrintError($"Ошибка отправки в Telegram. Код: {code}");
            }
            else
            {
                if (_config.EnableFileLogging)
                {
                    LogToFile("telegram", $"[{DateTime.Now}] Сообщение отправлено", this);
                }
            }
        }, this);
    }

    #endregion

    #region UI

    private void ShowMainUI(BasePlayer player, string wallet, int amount, string message, bool isLinked)
    {
        var container = new CuiElementContainer();
        
        container.Add(new CuiPanel
        {
            CursorEnabled = true,
            KeyboardEnabled = true,
            Image = { Color = "0 0 0 0.9113" },
            RectTransform =
            {
                AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", 
                OffsetMin = "-293.139 -149.992", OffsetMax = "293.139 150.008"
            }
        }, "Overlay", "Panel_305");

        container.Add(new CuiButton()
        {
            RectTransform = { AnchorMin = "-100 -100", AnchorMax = "100 100", OffsetMax = "0 0" },
            Button = { 
                Color = "0 0 0 0.8", 
                Sprite = "assets/content/ui/uibackgroundblur-ingamemenu.mat", 
                Close = "Panel_305",
                Command = "UI_CRYPTOTRADE CLOSE"
            },
            Text = { Text = "" }
        }, "Panel_305");

        container.Add(new CuiElement
        {
            Name = "Label_Title",
            Parent = "Panel_305",
            Components =
            {
                new CuiTextComponent
                {
                    Text = "ОБМЕН ИГРОВОЙ ВАЛЮТЫ", 
                    Font = "robotocondensed-bold.ttf", 
                    FontSize = 25,
                    Align = TextAnchor.MiddleCenter, 
                    Color = "1 1 1 1"
                },
                new CuiRectTransformComponent
                {
                    AnchorMin = "0.5 1", AnchorMax = "0.5 1", 
                    OffsetMin = "-188.658 -47.178", OffsetMax = "188.658 0"
                }
            }
        });

        if (!string.IsNullOrEmpty(message))
        {
            container.Add(new CuiElement
            {
                Name = "MessageLabel",
                Parent = "Panel_305",
                Components =
                {
                    new CuiTextComponent
                    {
                        Text = message, 
                        Font = "robotocondensed-bold.ttf", 
                        FontSize = 14,
                        Align = TextAnchor.MiddleCenter, 
                        Color = "1 1 1 1"
                    },
                    new CuiRectTransformComponent
                    {
                        AnchorMin = "0.5 1", AnchorMax = "0.5 1", 
                        OffsetMin = "-250 -70", OffsetMax = "250 -48"
                    }
                }
            });
        }

        container.Add(new CuiPanel
        {
            CursorEnabled = true,
            Image = { Color = "0.3490566 0.3490566 0.3490566 1" },
            RectTransform =
            {
                AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", 
                OffsetMin = "-281.867 59.258", OffsetMax = "107.267 80.742"
            }
        }, "Panel_305", "Panel_WalletInput");

        container.Add(new CuiElement
        {
            Name = "Label_WalletTitle",
            Parent = "Panel_WalletInput",
            Components =
            {
                new CuiTextComponent
                {
                    Text = isLinked ? "Привязанный кошелек Solana" : "Введите адрес кошелька Solana", 
                    Font = "robotocondensed-regular.ttf", 
                    FontSize = 10,
                    Align = TextAnchor.MiddleLeft, 
                    Color = "1 1 1 1"
                },
                new CuiRectTransformComponent
                {
                    AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", 
                    OffsetMin = "-191.034 13.845", OffsetMax = "83.234 30.154"
                }
            }
        });

        container.Add(new CuiElement
        {
            Name = "Input_Wallet",
            Parent = "Panel_WalletInput",
            Components =
            {
                new CuiInputFieldComponent
                {
                    Color = "1 1 1 1",
                    Text = wallet,
                    Font = "robotocondensed-regular.ttf", 
                    FontSize = 14,
                    Align = TextAnchor.MiddleLeft,
                    CharsLimit = 44,
                    IsPassword = false, 
                    Command = $"UI_CRYPTOTRADE ADDRESS {amount}"
                },
                new CuiRectTransformComponent
                {
                    AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", 
                    OffsetMin = "-188.067 -10.742", OffsetMax = "194.568 10.741"
                }
            }
        });

        if (isLinked)
        {
            container.Add(new CuiButton
            {
                Button = { 
                    Color = "0.8 0.2 0.2 1",
                    Command = ""
                },
                Text =
                {
                    Text = "✓ КОШЕЛЕК ПРИВЯЗАН", 
                    Font = "robotocondensed-bold.ttf", 
                    FontSize = 12,
                    Align = TextAnchor.MiddleCenter, 
                    Color = "1 1 1 1"
                },
                RectTransform =
                {
                    AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", 
                    OffsetMin = "207.388 -10.742", OffsetMax = "365.012 10.742"
                }
            }, "Panel_WalletInput", "Button_WalletStatus");
        }
        else
        {
            container.Add(new CuiButton
            {
                Button = { 
                    Color = "0.2847544 0.745283 0.3062837 1",
                    Command = $"UI_CRYPTOTRADE LINK_WALLET {wallet}"
                },
                Text =
                {
                    Text = "ПОДТВЕРДИТЬ КОШЕЛЕК", 
                    Font = "robotocondensed-bold.ttf", 
                    FontSize = 12,
                    Align = TextAnchor.MiddleCenter, 
                    Color = "1 1 1 1"
                },
                RectTransform =
                {
                    AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", 
                    OffsetMin = "207.388 -10.742", OffsetMax = "365.012 10.742"
                }
            }, "Panel_WalletInput", "Button_WalletStatus");
        }

        if (isLinked)
        {
            container.Add(new CuiButton
            {
                Button = { 
                    Color = "0.2847544 0.745283 0.3062837 1",
                    Command = "UI_CRYPTOTRADE UNLINK_WALLET"
                },
                Text =
                {
                    Text = "ОТВЯЗАТЬ КОШЕЛЕК", 
                    Font = "robotocondensed-bold.ttf", 
                    FontSize = 12,
                    Align = TextAnchor.MiddleCenter, 
                    Color = "1 1 1 1"
                },
                RectTransform =
                {
                    AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", 
                    OffsetMin = "119.888 34.516", OffsetMax = "278.012 56"
                }
            }, "Panel_305", "Button_Unlink");
        }

        container.Add(new CuiPanel
        {
            CursorEnabled = true,
            Image = { Color = "0.3490566 0.3490566 0.3490566 1" },
            RectTransform =
            {
                AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", 
                OffsetMin = "-281.867 -66.842", OffsetMax = "107.267 -45.358"
            }
        }, "Panel_305", "Panel_AmountInput");

        var balance = GetBalance(player);
        container.Add(new CuiElement
        {
            Name = "Label_AmountTitle",
            Parent = "Panel_AmountInput",
            Components =
            {
                new CuiTextComponent
                {
                    Text = $"Укажите количество валюты (доступно: {balance})", 
                    Font = "robotocondensed-regular.ttf", 
                    FontSize = 10, 
                    Align = TextAnchor.MiddleLeft, 
                    Color = "1 1 1 1"
                },
                new CuiRectTransformComponent
                {
                    AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", 
                    OffsetMin = "-191.034 13.845", OffsetMax = "140.857 30.154"
                }
            }
        });

        container.Add(new CuiElement
        {
            Name = "Input_Amount",
            Parent = "Panel_AmountInput",
            Components =
   
// ... [Code preview truncated due to length. Full source in attached file] ...

Автор
15FPS
Скачиваний
0
Просмотры
1
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок
Сверху