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

FREE CoinFinder : 1.4.4 1.4.4

Нет прав для скачивания
C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence; // Для IPlayer
using Oxide.Core.Plugins;
using System;
using System.Collections.Generic;
using UnityEngine;
using Network;         // Для NetworkableId
using Newtonsoft.Json; // Для атрибутов конфигурации
using Oxide.Game.Rust.Cui;
using System.IO;
using System.Text;
using UnityEngine.UI;

namespace Oxide.Plugins
{
    [Info("CoinFinder", "YourName/ChatGPT", "1.4.4")]
    [Description("Find coins and exchange them directly via GameStores Web API.")]
    partial class CoinFinder : RustPlugin
    {
        #region Fields

        private static Configuration _config;
        private const string PermissionUse = "coinfinder.use";
        private const string PermissionExchange = "coinfinder.exchange";

        // HashSet для быстрой проверки префабов контейнеров
        private HashSet<string> _lootableContainerPrefabs;
        // HashSet для хранения ID уже залутанных контейнеров (используем ulong)
        private readonly HashSet<ulong> _handledContainerInstanceIds = new HashSet<ulong>();
        // Базовый URL API GameStores
        private const string GameStoresApiBaseUrl = "https://gamestores.ru/api";

        #endregion

        #region Configuration

        private class Configuration
        {
            // --- Настройки поиска монет ---
            [JsonProperty(PropertyName = "1.Шанс найти монету (0.0 - 1.0)", Order = 1)]
            public float DropChance { get; set; } = 0.1f; // 10%

            [JsonProperty(PropertyName = "2.Базовый предмет для монеты (Shortname)", Order = 2)]
            public string CoinItemShortname { get; set; } = "branch"; // Ветка

            [JsonProperty(PropertyName = "3.ID скина монеты (0 = нет скина)", Order = 3)]
            public ulong CoinSkinID { get; set; } = 0; // Установите реальный Skin ID

            [JsonProperty(PropertyName = "4.Отображаемое имя монеты в чате", Order = 4)]
            public string CustomCoinName { get; set; } = "Монета";

            [JsonProperty(PropertyName = "5.Минимальное количество монет за раз", Order = 5)]
            public int MinCoinAmount { get; set; } = 1;

            [JsonProperty(PropertyName = "6.Максимальное количество монет за раз", Order = 6)]
            public int MaxCoinAmount { get; set; } = 5;

            [JsonProperty(PropertyName = "7.Показывать сообщение о находке монет", Order = 7)]
            public bool ShowFoundMessage { get; set; } = true;

            [JsonProperty(PropertyName = "8.Список контейнеров для поиска (Shortnames)", Order = 8)]
            public List<string> LootableContainers { get; set; } = new List<string>
             {
                "crate_normal", "crate_normal_2", "crate_tools", "crate_mine", "crate_basic",
                "crate_elite", "loot_barrel_1", "loot_barrel_2", "oil_barrel", "foodbox",
                "medical_storage", "codelockedhackablecrate", "supply_drop", "heli_crate",
                "bradley_crate"
                // Добавляйте/удаляйте по необходимости
             };

            // --- Настройки обмена через GameStores API ---
            [JsonProperty(PropertyName = "9.Включить команду обмена", Order = 9)]
            public bool EnableExchangeCommand { get; set; } = true;

            [JsonProperty(PropertyName = "10.Название команды обмена в чате", Order = 10)]
            public string ExchangeCommandName { get; set; } = "exchange";

            [JsonProperty(PropertyName = "11.ID вашего магазина GameStores", Order = 11)]
            public ulong GameStoresShopId { get; set; } = 0; // <-- ВАЖНО: Установите ваш ID!

            [JsonProperty(PropertyName = "12.Секретный ключ вашего магазина GameStores", Order = 12)]
            public string GameStoresSecretKey { get; set; } = string.Empty; // <-- ВАЖНО: Установите ваш ключ!

            [JsonProperty(PropertyName = "13.Название операции для API GameStores (параметр mess)", Order = 13)]
            public string GameStoresActionName { get; set; } = "Обмен монет CoinFinder"; // Название операции для API
        }

        // Загрузка конфигурации
        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                _config = Config.ReadObject<Configuration>();
                if (_config == null) throw new Exception("Config file is empty or invalid.");
                _config.LootableContainers = _config.LootableContainers ?? new List<string>(); // Убедимся, что список не null
            }
            catch (Exception ex)
            {
                PrintError($"Failed to load config file: {ex.Message}.Loading default configuration.");
                LoadDefaultConfig(); // Загружаем дефолт, если чтение не удалось
            }

            // Проверяем обязательные поля API после загрузки (или создания дефолтного конфига)
            if (_config.EnableExchangeCommand && (_config.GameStoresShopId == 0 || string.IsNullOrEmpty(_config.GameStoresSecretKey)))
            {
                PrintWarning("Exchange command is enabled, but GameStores Shop ID or Secret Key is not set! Exchange will not work.");
            }

            // Сохраняем конфиг (для применения JsonProperty или добавления новых полей)
            SaveConfig();
            // Инициализируем HashSet для быстрой проверки контейнеров
            _lootableContainerPrefabs = new HashSet<string>(_config.LootableContainers);
        }

        // Загрузка конфигурации по умолчанию
        protected override void LoadDefaultConfig()
        {
            _config = new Configuration(); // Создаем новый объект с дефолтными значениями
            Puts("Creating default configuration file...");
        }

        // Сохранение конфигурации
        protected override void SaveConfig() => Config.WriteObject(_config, true); // true для красивого JSON

        #endregion

        #region Language API

        // Загрузка стандартных сообщений
        protected override void LoadDefaultMessages()
        {
            // Русский язык
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["CoinFound"] = "Вы нашли <color=#ffd700>{0}</color> x <color=#add8e6>{1}</color>!",
                ["ExchangeUsage"] = "Используйте: <color=#ffa500>/{0}</color>",
                ["NoPermission"] = "<color=red>Ошибка:</color> У вас нет разрешения на использование этой команды.",
                ["ExchangeDisabled"] = "<color=orange>Информация:</color> Команда обмена отключена в конфигурации.",
                ["APINotConfigured"] = "<color=red>Ошибка:</color> Обмен временно недоступен.Администратор не настроил API магазина.",
                ["NoCoinsToExchange"] = "<color=orange>Информация:</color> У вас нет <color=#add8e6>{0}</color> для обмена.",
                ["ExchangeSuccess"] = "<color=lime>Успех:</color> Вы обменяли <color=#ffd700>{0}</color> x <color=#add8e6>{1}</color> на <color=#90ee90>{0}</color> кредитов! Баланс на сайте обновлен.",
                ["ExchangeWebApiError"] = "<color=red>Ошибка:</color> Не удалось связаться с API магазина или получен неверный ответ ({0}).Монеты не были списаны.",
                ["ExchangeApiRejected"] = "<color=red>Ошибка:</color> Магазин отклонил операцию (возможно, вы не авторизованы?).Монеты не были списаны.",
                ["InvalidCoinItem"] = "<color=red>Ошибка:</color> Предмет-монета ({0}) не найден в игре.Проверьте конфигурацию плагина."
            }, this, "ru");

            // Английский язык
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["CoinFound"] = "You found <color=#ffd700>{0}</color> x <color=#add8e6>{1}</color>!",
                ["ExchangeUsage"] = "Usage: <color=#ffa500>/{0}</color>",
                ["NoPermission"] = "<color=red>Error:</color> You do not have permission to use this command.",
                ["ExchangeDisabled"] = "<color=orange>Info:</color> The exchange command is disabled in the configuration.",
                ["APINotConfigured"] = "<color=red>Error:</color> Exchange is temporarily unavailable.The store API is not configured.",
                ["NoCoinsToExchange"] = "<color=orange>Info:</color> You do not have any <color=#add8e6>{0}</color> to exchange.",
                ["ExchangeSuccess"] = "<color=lime>Success:</color> You exchanged <color=#ffd700>{0}</color> x <color=#add8e6>{1}</color> for <color=#90ee90>{0}</color> credits! The website balance has been updated.",
                ["ExchangeWebApiError"] = "<color=red>Error:</color> Could not contact the store API or received an invalid response ({0}).Your coins were not removed.",
                ["ExchangeApiRejected"] = "<color=red>Error:</color> The store rejected the transaction (maybe you are not logged in?).Your coins were not removed.",
                ["InvalidCoinItem"] = "<color=red>Error:</color> The coin item ({0}) was not found in the game.Please check the plugin configuration."
            }, this, "en");
        }

        // Получение локализованного сообщения
        private string GetLang(string key, string userId = null, params object[] args) => string.Format(lang.GetMessage(key, this, userId), args);

        #endregion

        #region Hooks & Init

        // Инициализация плагина
        private void Init()
        {
            // Регистрация разрешений
            permission.RegisterPermission(PermissionUse, this);
            permission.RegisterPermission(PermissionExchange, this);

            // Проверка базового предмета монеты при запуске
            ItemDefinition coinDef = ItemManager.FindItemDefinition(_config.CoinItemShortname);
            if (coinDef == null) PrintError($"Config Error: Coin base item shortname '{_config.CoinItemShortname}' is invalid!");
            else Puts($"Using base item '{coinDef.displayName.english}' ({_config.CoinItemShortname}) for coins.");

            // Предупреждение о нулевом Skin ID
            if (_config.CoinSkinID == 0) PrintWarning($"Config Warning: CoinSkinID is 0.Coins will be given without a custom skin.");
            else Puts($"Using Skin ID {_config.CoinSkinID} for coin items.");

            // Предупреждение о пустом списке контейнеров
            if (_lootableContainerPrefabs == null || _lootableContainerPrefabs.Count == 0) PrintWarning("Config Warning: The list of LootableContainers is empty.Coins will not drop from any containers.");

            // Регистрация команды чата, если включено
            if (_config.EnableExchangeCommand)
            {
                // Проверка настроек API при включенной команде
                if (_config.GameStoresShopId == 0 || string.IsNullOrEmpty(_config.GameStoresSecretKey))
                {
                    PrintWarning("Exchange command is enabled, but GameStores Shop ID or Secret Key is not configured in CoinFinder.json! The command '/" + _config.ExchangeCommandName + "' will not function correctly.");
                }
                AddCovalenceCommand(_config.ExchangeCommandName, nameof(CmdExchange));
                Puts($"Exchange command '/{_config.ExchangeCommandName}' enabled.");
            }
            else
            {
                Puts("Exchange command disabled.");
            }

            // Подписка на хуки для сброса статуса залутанных контейнеров
            Subscribe(nameof(OnEntityKill));
            Subscribe(nameof(OnContainerPopulate));
        }

        private void GUIARCADIA(Connection connection)
        {
            CUI.Root root = new CUI.Root("Overlay");
            {
                CUI.Element KfUMeF = root.AddPanel(
                    material: "assets/content/ui/uibackgroundblur.mat",
                    color: "0.2830189 0.2830189 0.2830189 0.3529412",
                    imageType: UnityEngine.UI.Image.Type.Simple,
                    anchorMin: "0.5 0.5",
                    anchorMax: "0.5 0.5",
                    offsetMin: "-640 -360",
                    offsetMax: "640 360",
                    name: "GUIARCADIA");
                KfUMeF.AddButton(
                    command: null,
                    close: "GUIARCADIA",
                    color: "1 1 1 0.5686275",
                    sprite: "assets/icons/exit.png",
                    material: "assets/content/ui/binocular_overlay.mat",
                    imageType: UnityEngine.UI.Image.Type.Simple,
                    anchorMin: "0.5 0.5",
                    anchorMax: "0.5 0.5",
                    offsetMin: "580 300",
                    offsetMax: "620 340"
                    /* name: "Button" */);
                {
                    CUI.Element UBcOal = KfUMeF.AddPanel(
                        material: "assets/content/ui/uibackgroundblur-notice.mat",
                        imageType: UnityEngine.UI.Image.Type.Simple,
                        anchorMin: "0.5 0.5",
                        anchorMax: "0.5 0.5",
                        offsetMin: "-500 -275",
                        offsetMax: "-300 335"
                        /* name: "Panel" */);
                    {
                        CUI.Element cJPiUB = UBcOal.AddButton(
                            command: null,
                            color: "1 1 1 0.3176471",
                            material: "assets/content/ui/binocular_overlay.mat",
                            imageType: UnityEngine.UI.Image.Type.Simple,
                            anchorMin: "0.5 0.5",
                            anchorMax: "0.5 0.5",
                            offsetMin: "-75 225",
                            offsetMax: "75 275"
                            /* name: "Button" */);
                        cJPiUB.AddText(
                            text: "О СЕРВЕРЕ",
                            color: "0 0 0 1",
                            font: CUI.Font.PressStart2PRegular,
                            fontSize: 13,
                            align: TextAnchor.UpperLeft,
                            overflow: VerticalWrapMode.Overflow,
                            anchorMin: "0 0",
                            anchorMax: "0 0",
                            offsetMin: "15 -70",
                            offsetMax: "145 30"
                            /* name: "Text" */);
                    }
                    {
                        CUI.Element OZEedb = UBcOal.AddButton(
                            command: null,
                            imageType: UnityEngine.UI.Image.Type.Simple,
                            anchorMin: "0.5 0.5",
                            anchorMax: "0.5 0.5",
                            offsetMin: "-75 165",
                            offsetMax: "75 215"
                            /* name: "Button (2)" */);
                        OZEedb.AddText(
                            text: "Бинды",
                            color: "0 0 0 1",
                            font: CUI.Font.RobotoCondensedRegular,
                            align: TextAnchor.UpperLeft,
                            overflow: VerticalWrapMode.Overflow,
                            anchorMin: "0 0",
                            anchorMax: "0 0",
                            offsetMin: "50 -65",
                            offsetMax: "150 35"
                            /* name: "Text" */);
                    }
                    {
                        CUI.Element KIPgNf = UBcOal.AddButton(
                            command: null,
                            imageType: UnityEngine.UI.Image.Type.Simple,
                            anchorMin: "0.5 0.5",
                            anchorMax: "0.5 0.5",
                            offsetMin: "-75 105",
                            offsetMax: "75 155"
                            /* name: "Button (3)" */);
                        KIPgNf.AddText(
                            text: "Text",
                            font: CUI.Font.RobotoCondensedRegular,
                            align: TextAnchor.UpperLeft,
                            overflow: VerticalWrapMode.Overflow,
                            anchorMin: "0 0",
                            anchorMax: "0 0",
                            offsetMin: "-50 -50",
                            offsetMax: "50 50"
                            /* name: "Text" */);
                    }
                    {
                        CUI.Element LPnPWo = UBcOal.AddButton(
                            command: null,
                            imageType: UnityEngine.UI.Image.Type.Simple,
                            anchorMin: "0.5 0.5",
                            anchorMax: "0.5 0.5",
                            offsetMin: "-75 45",
                            offsetMax: "75 95"
                            /* name: "Button (4)" */);
                        LPnPWo.AddText(
                            text: "Text",
                            font: CUI.Font.RobotoCondensedRegular,
                            align: TextAnchor.UpperLeft,
                            overflow: VerticalWrapMode.Overflow,
                            anchorMin: "0 0",
                            anchorMax: "0 0",
                            offsetMin: "-50 -50",
                            offsetMax: "50 50"
                            /* name: "Text" */);
                    }
                    {
                        CUI.Element zOhwte = UBcOal.AddButton(
                            command: null,
                            imageType: UnityEngine.UI.Image.Type.Simple,
                            anchorMin: "0.5 0.5",
                            anchorMax: "0.5 0.5",
                            offsetMin: "-75 -15",
                            offsetMax: "75 35"
                            /* name: "Button (5)" */);
                        zOhwte.AddText(
                            text: "Text",
                            font: CUI.Font.RobotoCondensedRegular,
                            align: TextAnchor.UpperLeft,
                            overflow: VerticalWrapMode.Overflow,
                            anchorMin: "0 0",
                            anchorMax: "0 0",
                            offsetMin: "-50 -50",
                            offsetMax: "50 50"
                            /* name: "Text" */);
                    }
                    {
                        CUI.Element VPOvds = UBcOal.AddButton(
                            command: null,
                            imageType: UnityEngine.UI.Image.Type.Simple,
                            anchorMin: "0.5 0.5",
                            anchorMax: "0.5 0.5",
                            offsetMin: "-75 -75",
                            offsetMax: "75 -25"
                            /* name: "Button (6)" */);
                        VPOvds.AddText(
                            text: "Text",
                            font: CUI.Font.RobotoCondensedRegular,
                            align: TextAnchor.UpperLeft,
                            overflow: VerticalWrapMode.Overflow,
                            anchorMin: "0 0",
                            anchorMax: "0 0",
                            offsetMin: "-50 -50",
                            offsetMax: "50 50"
                            /* name: "Text" */);
                    }
                    {
                        CUI.Element nojFnG = UBcOal.AddButton(
                            command: null,
                            imageType: UnityEngine.UI.Image.Type.Simple,
                            anchorMin: "0.5 0.5",
                            anchorMax: "0.5 0.5",
                            offsetMin: "-75 -135",
                            offsetMax: "75 -85"
                            /* name: "Button (7)" */);
                        nojFnG.AddText(
                            text: "Text",
                            font: CUI.Font.RobotoCondensedRegular,
                            align: TextAnchor.UpperLeft,
                            overflow: VerticalWrapMode.Overflow,
                            anchorMin: "0 0",
                            anchorMax: "0 0",
                            offsetMin: "-50 -50",
                            offsetMax: "50 50"
                            /* name: "Text" */);
                    }
                    {
                        CUI.Element yduuuu = UBcOal.AddButton(
                            command: null,
                            imageType: UnityEngine.UI.Image.Type.Simple,
                            anchorMin: "0.5 0.5",
                            anchorMax: "0.5 0.5",
                            offsetMin: "-75 -195",
                            offsetMax: "75 -145"
                            /* name: "Button (8)" */);
                        yduuuu.AddText(
                            text: "Text",
                            font: CUI.Font.RobotoCondensedRegular,
                            align: TextAnchor.UpperLeft,
                            overflow: VerticalWrapMode.Overflow,
                            anchorMin: "0 0",
                            anchorMax: "0 0",
                            offsetMin: "-50 -50",
                            offsetMax: "50 50"
                            /* name: "Text" */);
                    }
                    {
                        CUI.Element zdohTW = UBcOal.AddButton(
                            command: null,
                            imageType: UnityEngine.UI.Image.Type.Simple,
                            anchorMin: "0.5 0.5",
                            anchorMax: "0.5 0.5",
                            offsetMin: "-75 -255",
                            offsetMax: "75 -205"
                            /* name: "Button (9)" */);
                        zdohTW.AddText(
                            text: "Text",
                            font: CUI.Font.RobotoCondensedRegular,
                            align: TextAnchor.UpperLeft,
                            overflow: VerticalWrapMode.Overflow,
                            anchorMin: "0 0",
                            anchorMax: "0 0",
                            offsetMin: "-50 -50",
                            offsetMax: "50 50"
                            /* name: "Text" */);
                    }
                }
                KfUMeF.AddPanel(
                    color: "0.2196079 0.2196079 0.2196079 1",
                    imageType: UnityEngine.UI.Image.Type.Simple,
                    anchorMin: "0.5 0.5",
                    anchorMax: "0.5 0.5",
                    offsetMin: "-300 -275",
                    offsetMax: "420 335"
                    /* name: "Panel" */);
            }
            root.Render(connection);
        }

        private void OnLootEntity(BasePlayer player, BaseEntity entity)
        {
            if (player == null || entity == null || !permission.UserHasPermission(player.UserIDString, PermissionUse)) return;

            StorageContainer container = entity as StorageContainer;
            if (container == null || container.net == null || !container.net.ID.IsValid || container.ShortPrefabName == null) return;

            if (!_lootableContainerPrefabs.Contains(container.ShortPrefabName)) return;

            ulong containerIdValue = container.net.ID.Value;

            // --- ИЗМЕНЕНИЕ ЛОГИКИ ---
            // 1.Проверяем, обрабатывали ли мы уже этот контейнер
            if (_handledContainerInstanceIds.Contains(containerIdValue))
            {
                // Если да, то больше ничего с ним не делаем в этом цикле жизни
                return;
            }

            // 2.Если не обрабатывали, СРАЗУ помечаем его как обработанный
            _handledContainerInstanceIds.Add(containerIdValue);
            // --- КОНЕЦ ИЗМЕНЕНИЯ ЛОГИКИ ---

            // 3.Теперь проверяем шанс (только для этой первой попытки)
            if (UnityEngine.Random.Range(0f, 1f) <= _config.DropChance)
            {
                // Если шанс сработал, выдаем монеты
                GiveCoins(player, containerIdValue);
                // Дополнительно помечать не нужно, он уже помечен как обработанный (_handledContainerInstanceIds.Add(containerIdValue) было выше)
            }
            // Если шанс не сработал, ничего не делаем, но контейнер уже помечен и больше проверяться не будет
        }

        private void OnEntityKill(BaseNetworkable entity)
        {
            // Проверяем, что сущность и ее сетевой ID существуют и валидны
            if (entity?.net != null && entity.net.ID.IsValid)
            {
                // Удаляем ID из списка обработанных контейнеров
                // Убедитесь, что здесь используется имя '_handledContainerInstanceIds'
                _handledContainerInstanceIds.Remove(entity.net.ID.Value);
            }
        }

        // Хук: Контейнер заполнен лутом (респавн)
        private void OnContainerPopulate(StorageContainer container)
        {
            // Удаляем ID из списка обработанных
            if (container?.net != null && container.net.ID.IsValid)
            {
                _handledContainerInstanceIds.Remove(container.net.ID.Value);
            }
        }

        // При выгрузке плагина очищаем список
        private void Unload()
        {
            _handledContainerInstanceIds.Clear();
            Puts("CoinFinder unloaded, cleared handled container list.");
        }

        #endregion

        #region Chat Command

        // Обработка команды обмена
        private void CmdExchange(IPlayer iplayer, string command, string[] args)
        {
            // 1.Проверка, включена ли команда
            if (!_config.EnableExchangeCommand)
            {
                iplayer.Reply(GetLang("ExchangeDisabled", iplayer.Id));
                return;
            }

            // 2.Проверка разрешения игрока
            if (!iplayer.HasPermission(PermissionExchange))
            {
                iplayer.Reply(GetLang("NoPermission", iplayer.Id));
                return;
            }

            // 3.Проверка конфигурации API
            if (_config.GameStoresShopId == 0 || string.IsNullOrEmpty(_config.GameStoresSecretKey))
            {
                iplayer.Reply(GetLang("APINotConfigured", iplayer.Id));
                // Дополнительно выводим ошибку в консоль для администратора
                PrintError("Exchange command failed: GameStores Shop ID or Secret Key is missing in the configuration.");
                return;
            }

            // 4.Получение объекта BasePlayer
            BasePlayer player = iplayer.Object as BasePlayer;
            if (player == null) return; // Не должно происходить для команды из чата

            // 5.Проверка валидности предмета-монеты
            ItemDefinition coinItemDefinition = ItemManager.FindItemDefinition(_config.CoinItemShortname);
            if (coinItemDefinition == null)
            {
                iplayer.Reply(GetLang("InvalidCoinItem", iplayer.Id, _config.CoinItemShortname));
                PrintError($"Exchange command failed: Coin item definition '{_config.CoinItemShortname}' not found in ItemManager.");
                return;
            }

            // 6.Подсчет монет и сбор предметов для удаления
            int totalCoinsFound = 0;
            List<Item> coinsToRemove = new List<Item>(); // Собираем здесь, удаляем ПОСЛЕ успеха API

            // Ищем только в основном инвентаре
            foreach (Item item in player.inventory.containerMain.itemList)
            {
                // Проверяем shortname И skin ID
                if (item.info.shortname == _config.CoinItemShortname && item.skin == _config.CoinSkinID)
                {
                    totalCoinsFound += item.amount;
                    coinsToRemove.Add(item); // Добавляем сам объект Item в список
                }
            }

            // 7.Проверка, есть ли монеты для обмена
            if (totalCoinsFound <= 0)
            {
                // Используем настроенное имя монеты для сообщения
                string coinNameForMsg = !string.IsNullOrEmpty(_config.CustomCoinName) ? _config.CustomCoinName : coinItemDefinition.displayName.english;
                iplayer.Reply(GetLang("NoCoinsToExchange", iplayer.Id, coinNameForMsg));
                return;
            }

            // 8.Формирование URL и отправка запроса к API
            string steamId = player.UserIDString; // SteamID64
            int amountToDeposit = totalCoinsFound; // Сумма для API
            // Экранируем текст для параметра 'mess' на случай спецсимволов
            string encodedActionName = Uri.EscapeDataString(_config.GameStoresActionName ?? string.Empty);
            string url = $"{GameStoresApiBaseUrl}?shop_id={_config.GameStoresShopId}&secret={_config.GameStoresSecretKey}&action=moneys&type=plus&steam_id={steamId}&amount={amountToDeposit}&mess={encodedActionName}";

            // Логирование запроса (можно закомментировать в продакшене)
            Puts($"DEBUG: Sending WebRequest to GameStores API for {player.displayName} ({steamId}), Amount: {amountToDeposit}.URL: {url}");

            webrequest.Enqueue(url, null, (code, response) =>
            {
                // --- Начало обработки ответа (асинхронно) ---

                // Проверяем, существует ли еще игрок
                if (player == null || !player.IsConnected)
                {
                    Puts($"DEBUG: Player {steamId} disconnected before GameStores API response arrived.");
                    return; // Игрок вышел
                }

                // Логирование ответа (можно закомментировать в продакшене)
                Puts($"DEBUG: GameStores API Response for {steamId}: Code={code}, Response='{response}'");

                // Проверяем успешность ответа
                if (code == 200 && response != null && response.Contains("success"))
                {
                    // --- УСПЕХ API ---
                    Puts($"GameStores API reported SUCCESS for {player.displayName} ({steamId}), Amount: {amountToDeposit}.Attempting to remove coins...");

                    // Удаляем монеты из инвентаря
                    int totalAmountRemoved = 0;
                    // Используем копию списка или итерируем осторожно, т.к.удаление меняет коллекцию
                    List<Item> itemsActuallyRemoved = new List<Item>(); // Для отладки, если нужно

                    for (int i = coinsToRemove.Count - 1; i >= 0; i--) // Итерация с конца для безопасного удаления
                    {
                        Item item = coinsToRemove[i];
                        // Проверяем, что предмет все еще валиден и у игрока
                        if (item != null && !item.isBroken && item.GetRootContainer() == player.inventory.containerMain && item.amount > 0)
                        {
                            int amountInStack = item.amount;
                            int amountNeeded = amountToDeposit - totalAmountRemoved;
                            int amountToRemoveFromStack = Math.Min(amountInStack, amountNeeded); // Берем сколько нужно, но не больше чем есть в стаке

                            if (amountToRemoveFromStack <= 0) continue; // Уже все забрали

                            item.UseItem(amountToRemoveFromStack); // Уменьшаем стак или удаляем предмет
                            totalAmountRemoved += amountToRemoveFromStack;
                            itemsActuallyRemoved.Add(item); // Для дебага

                            if (totalAmountRemoved >= amountToDeposit) break; // Забрали нужное количество
                        }
                    }

                    // Проверка совпадения удаленного и найденного количества
                    if (totalAmountRemoved != amountToDeposit)
                    {
                        PrintWarning($"Coin removal mismatch for {player.displayName}.Expected: {amountToDeposit}, Removed: {totalAmountRemoved}.Possible inventory change during API call or item removal issue.");
                    }
                    else
                    {
                        Puts($"Successfully removed {totalAmountRemoved} coins for {player.displayName}.");
                    }

                    // Сообщение игроку об успехе
                    string finalCoinName = !string.IsNullOrEmpty(_config.CustomCoinName) ? _config.CustomCoinName : coinItemDefinition.displayName.english;
                    iplayer.Reply(GetLang("ExchangeSuccess", iplayer.Id, totalAmountRemoved, finalCoinName)); // Сообщаем фактически удаленное кол-во
                }
                else
                {
                    // --- ОШИБКА API ---
                    // Монеты НЕ удаляем
                    string errorDetails;
                    string langKey;

                    if (code != 200) { errorDetails = $"HTTP Status {code}"; langKey = "ExchangeWebApiError"; }
                    else if (response == null) { errorDetails = "Empty Response"; langKey = "ExchangeWebApiError"; }
                    else { errorDetails = "API Rejected"; langKey = "ExchangeApiRejected"; } // Предполагаем отказ

                    // Сообщение игроку об ошибке
                    iplayer.Reply(GetLang(langKey, iplayer.Id, errorDetails));
                    // Лог ошибки на сервер
                    PrintError($"GameStores API exchange FAILED for {player.displayName} ({steamId}).Reason: {errorDetails}.Response: {response}.Coins were NOT removed.");
                }
                // --- Конец обработки ответа ---

            }, this); // Важно передать 'this'

            // Команда завершается здесь, но обработка ответа произойдет позже в callback'е
        }

        [ChatCommand("coinfinderui")]
        private void CmdOpenCoinFinderUI(BasePlayer player, string command, string[] args)
        {
            if (!permission.UserHasPermission(player.UserIDString, PermissionUse))
            {
                Player.Message(player, GetLang("NoPermission", player.UserIDString));
                return;
            }

            GUIARCADIA(player.Connection);
            Puts($"CoinFinder UI shown to {player.displayName}");
        }
        #endregion

        #region Logic (GiveCoins)

        // Логика выдачи монет игроку
        private void GiveCoins(BasePlayer player, ulong containerNetIdValue) // Принимаем ID для возможного логирования
        {
            // Находим определение предмета монеты
            ItemDefinition coinItemDefinition = ItemManager.FindItemDefinition(_config.CoinItemShortname);
            // Проверка на валидность уже была в Init, но на всякий случай
            if (coinItemDefinition == null)
            {
                PrintError($"INTERNAL ERROR: Cannot find ItemDefinition for '{_config.CoinItemShortname}' in GiveCoins.Check Init logs.");
                return;
            }

            // Определяем случайное количество монет в заданном диапазоне
            int amount = UnityEngine.Random.Range(_config.MinCoinAmount, _config.MaxCoinAmount + 1); // +1 т.к.верхняя граница исключается
            if (amount <= 0) return; // Не выдаем 0 или меньше

            // Создаем предмет с указанным Skin ID
            Item coinItem = ItemManager.Create(coinItemDefinition, amount, _config.CoinSkinID);

            if (coinItem != null)
            {
                // Выдаем предмет в основной инвентарь игрока
                // GiveItem обработает переполнение (выбросит предмет рядом)
                player.inventory.GiveItem(coinItem, player.inventory.containerMain);

                // Отправляем сообщение игроку, если включено
                if (_config.ShowFoundMessage)
                {
                    // Используем CustomCoinName или стандартное имя
                    string displayName = !string.IsNullOrEmpty(_config.CustomCoinName)
                       ? _config.CustomCoinName
                       : (coinItem.info.displayName?.translated ?? coinItem.info.displayName?.english ?? _config.CoinItemShortname); // Запасной вариант - shortname

                    Player.Message(player, GetLang("CoinFound", player.UserIDString, amount, displayName));
                }

                // Опциональный лог успешной выдачи (раскомментировать для отладки)
                // Puts($"DEBUG: Gave {amount}x {_config.CoinItemShortname} (Skin:{_config.CoinSkinID}) to {player.displayName} from container {containerNetIdValue}");
            }
            else
            {
                // Ошибка создания предмета (маловероятно, но возможно)
                PrintError($"Failed to create coin item '{_config.CoinItemShortname}' with Skin ID {_config.CoinSkinID}.Check ItemDefinition and SkinID validity.");
            }
        }
        #endregion


    }
    #region 0xF UI Library 2.3.2
    partial class CoinFinder
    {
        public class CUI
        {
            public enum Font
            {
                RobotoCondensedBold,
                RobotoCondensedRegular,
                RobotoMonoRegular,
                DroidSansMono,
                PermanentMarker,
                PressStart2PRegular,
                LSD,
                NotoSansArabicBold,
                NotoSansArabicRegular,
                NotoSansHebrewBold,
            }

            private static readonly Dictionary<Font, string> FontToString = new Dictionary<Font, string>
            {
                { Font.RobotoCondensedBold, "RobotoCondensed-Bold.ttf" },
                { Font.RobotoCondensedRegular, "RobotoCondensed-Regular.ttf" },
                { Font.RobotoMonoRegular, "RobotoMono-Regular.ttf" },
                { Font.DroidSansMono, "DroidSansMono.ttf" },
                { Font.PermanentMarker, "PermanentMarker.ttf" },
                { Font.PressStart2PRegular, "PressStart2P-Regular.ttf" },
                { Font.LSD, "lcd.ttf" },
                { Font.NotoSansArabicBold, "_nonenglish/arabic/notosansarabic-bold.ttf" },
                { Font.NotoSansArabicRegular, "_nonenglish/arabic/notosansarabic-regular.ttf" },
                { Font.NotoSansHebrewBold, "_nonenglish/notosanshebrew-bold.ttf" },
            };

            public enum InputType
            {
                None,
                Default,
                HudMenuInput
            }

            private static readonly Dictionary<TextAnchor, string> TextAnchorToString = new Dictionary<TextAnchor, string>
            {
                { TextAnchor.UpperLeft, TextAnchor.UpperLeft.ToString() },
                { TextAnchor.UpperCenter, TextAnchor.UpperCenter.ToString() },
                { TextAnchor.UpperRight, TextAnchor.UpperRight.ToString() },
                { TextAnchor.MiddleLeft, TextAnchor.MiddleLeft.ToString() },
                { TextAnchor.MiddleCenter, TextAnchor.MiddleCenter.ToString() },
                { TextAnchor.MiddleRight, TextAnchor.MiddleRight.ToString() },
                { TextAnchor.LowerLeft, TextAnchor.LowerLeft.ToString() },
                { TextAnchor.LowerCenter, TextAnchor.LowerCenter.ToString() },
                { TextAnchor.LowerRight, TextAnchor.LowerRight.ToString() }
            };

            private static readonly Dictionary<VerticalWrapMode, string> VWMToString = new Dictionary<VerticalWrapMode, string>
            {
                { VerticalWrapMode.Truncate, VerticalWrapMode.Truncate.ToString() },
                { VerticalWrapMode.Overflow, VerticalWrapMode.Overflow.ToString() },
            };

            private static readonly Dictionary<Image.Type, string> ImageTypeToString = new Dictionary<Image.Type, string>
            {
                { Image.Type.Simple, Image.Type.Simple.ToString() },
                { Image.Type.Sliced, Image.Type.Sliced.ToString() },
                { Image.Type.Tiled, Image.Type.Tiled.ToString() },
                { Image.Type.Filled, Image.Type.Filled.ToString() },
            };

            private static readonly Dictionary<InputField.LineType, string> LineTypeToString = new Dictionary<InputField.LineType, string>
            {
                { InputField.LineType.MultiLineNewline, InputField.LineType.MultiLineNewline.ToString() },
                { InputField.LineType.MultiLineSubmit, InputField.LineType.MultiLineSubmit.ToString() },
                { InputField.LineType.SingleLine, InputField.LineType.SingleLine.ToString() },
            };

            private static readonly Dictionary<ScrollRect.MovementType, string> MovementTypeToString = new Dictionary<ScrollRect.MovementType, string>
            {
                { ScrollRect.MovementType.Unrestricted, ScrollRect.MovementType.Unrestricted.ToString() },
                { ScrollRect.MovementType.Elastic, ScrollRect.MovementType.Elastic.ToString() },
                { ScrollRect.MovementType.Clamped, ScrollRect.MovementType.Clamped.ToString() },
            };


            private static readonly Dictionary<TimerFormat, string> TimerFormatToString = new Dictionary<TimerFormat, string>
            {
                { TimerFormat.None, TimerFormat.None.ToString() },
                { TimerFormat.SecondsHundreth, TimerFormat.SecondsHundreth.ToString() },
                { TimerFormat.MinutesSeconds, TimerFormat.MinutesSeconds.ToString() },
                { TimerFormat.MinutesSecondsHundreth, TimerFormat.MinutesSecondsHundreth.ToString() },
                { TimerFormat.HoursMinutes, TimerFormat.HoursMinutes.ToString() },
                { TimerFormat.HoursMinutesSeconds, TimerFormat.HoursMinutesSeconds.ToString() },
                { TimerFormat.HoursMinutesSecondsMilliseconds, TimerFormat.HoursMinutesSecondsMilliseconds.ToString() },
                { TimerFormat.HoursMinutesSecondsTenths, TimerFormat.HoursMinutesSecondsTenths.ToString() },
                { TimerFormat.DaysHoursMinutes, TimerFormat.DaysHoursMinutes.ToString() },
                { TimerFormat.DaysHoursMinutesSeconds, TimerFormat.DaysHoursMinutesSeconds.ToString() },
                { TimerFormat.Custom, TimerFormat.Custom.ToString() },
            };

            public static class Defaults
            {
                public const string VectorZero = "0 0";
                public const string VectorOne = "1 1";
                public const string Color = "1 1 1 1";
                public const string OutlineColor = "0 0 0 1";
                public const string Sprite = "assets/content/ui/ui.background.tile.psd";
                public const string Material = "assets/content/ui/namefontmaterial.mat";
                public const string IconMaterial = "assets/icons/iconmaterial.mat";
                public const Image.Type ImageType = Image.Type.Simple;
                public const CUI.Font Font = CUI.Font.RobotoCondensedRegular;
                public const int FontSize = 14;
                public const TextAnchor Align = TextAnchor.UpperLeft;
                public const VerticalWrapMode VerticalOverflow = VerticalWrapMode.Overflow;
                public const InputField.LineType LineType = InputField.LineType.SingleLine;
            }

            public static Color GetColor(string colorStr)
            {
                return ColorEx.Parse(colorStr);
            }

            public static string GetColorString(Color color)
            {
                return string.Format("{0} {1} {2} {3}", color.r, color.g, color.b, color.a);
            }

            public static void AddUI(Connection connection, string json)
            {
                CommunityEntity.ServerInstance.ClientRPCEx<string>(new SendInfo
                {
                    connection = connection
                }, null, "AddUI", json);
            }

            private static void SerializeType(ICuiComponent component, JsonWriter jsonWriter)
            {
                jsonWriter.WritePropertyName("type");
                jsonWriter.WriteValue(component.Type);
            }

            private static void SerializeField(string key, object value, object defaultValue, JsonWriter jsonWriter)
            {
                if (value != null && !value.Equals(defaultValue))
                {
                    if (value is string && defaultValue != null && string.IsNullOrEmpty(value as string))
                        return;

                    jsonWriter.WritePropertyName(key);

                    if (value is ICuiComponent)
                        SerializeComponent(value as ICuiComponent, jsonWriter);
                    else
                        jsonWriter.WriteValue(value ?? defaultValue);
                }
            }


            private static void SerializeField(string key, CuiScrollbar scrollbar, JsonWriter jsonWriter)
            {
                const string defaultHandleSprite = "assets/content/ui/ui.rounded.tga";
                const string defaultHandleColor = "0.15 0.15 0.15 1";
                const string defaultHighlightColor = "0.17 0.17 0.17 1";
                const string defaultPressedColor = "0.2 0.2 0.2 1";
                const string defaultTrackSprite = "assets/content/ui/ui.background.tile.psd";
                const string defaultTrackColor = "0.09 0.09 0.09 1";

                if (scrollbar == null)
                    return;

                jsonWriter.WritePropertyName(key);
                jsonWriter.WriteStartObject();
                SerializeField("invert", scrollbar.Invert, false, jsonWriter);
                SerializeField("autoHide", scrollbar.AutoHide, false, jsonWriter);
                SerializeField("handleSprite", scrollbar.HandleSprite, defaultHandleSprite, jsonWriter);
                SerializeField("size", scrollbar.Size, 20f, jsonWriter);
                SerializeField("handleColor", scrollbar.HandleColor, defaultHandleColor, jsonWriter);
                SerializeField("highlightColor", scrollbar.HighlightColor, defaultHighlightColor, jsonWriter);
                SerializeField("pressedColor", scrollbar.PressedColor, defaultPressedColor, jsonWriter);
                SerializeField("trackSprite", scrollbar.TrackSprite, defaultTrackSprite, jsonWriter);
                SerializeField("trackColor", scrollbar.TrackColor, defaultTrackColor, jsonWriter);
                jsonWriter.WriteEndObject();
            }

            private static void SerializeComponent(ICuiComponent IComponent, JsonWriter jsonWriter)
            {
                const string vector2zero = "0 0";
                const string vector2one = "1 1";
                const string colorWhite = "1 1 1 1";
                const string backgroundTile = "assets/content/ui/ui.background.tile.psd";
                const string iconMaterial = "assets/icons/iconmaterial.mat";
                const string fontBold = "RobotoCondensed-Bold.ttf";
                const string defaultOutlineDistance = "1.0 -1.0";

                void SerializeType() => CUI.SerializeType(IComponent, jsonWriter);
                void SerializeField(string key, object value, object defaultValue) => CUI.SerializeField(key, value, defaultValue, jsonWriter);
                void SerializeScrollbar(string key, CuiScrollbar value) => CUI.SerializeField(key, value, jsonWriter);

                switch (IComponent.Type)
                {
                    case "RectTransform":
                        {
                            CuiRectTransformComponent component = IComponent as CuiRectTransformComponent;
                            jsonWriter.WriteStartObject();
                            SerializeType();
                            SerializeField("anchormin", component.AnchorMin, vector2zero);
                            SerializeField("anchormax", component.AnchorMax, vector2one);
                            SerializeField("offsetmin", component.OffsetMin, vector2zero);
                            SerializeField("offsetmax", component.OffsetMax, vector2one);
                            jsonWriter.WriteEndObject();
                            break;
                        }
                    case "UnityEngine.UI.Image":
                        {
   
// ... [Code preview truncated due to length. Full source in attached file] ...

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