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

FREE SQPVEMode : 4.5.5 4.5.5

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

namespace Oxide.Plugins
{
    [Info("SQPVEMode", "BLOOD KRAKEN", "4.5.5")]
    [Description("Advanced PVE/PVP Mode System with JSON Database")]
    class SQPVEMode : RustPlugin
    {
        [PluginReference]
        private Plugin ImageLibrary;

        #region Configuration

        private ConfigData configData;
        
        private class ConfigData
        {
            [JsonProperty("Database Settings")]
            public DatabaseSettings Database = new DatabaseSettings();
            
            [JsonProperty("PVE Settings")]
            public PVEOptions PVE = new PVEOptions();
            
            [JsonProperty("Messages")]
            public Messages MSG = new Messages();
            
            [JsonProperty("UI Settings")]
            public UIOptions UI = new UIOptions();
            
            [JsonProperty("Image Settings")]
            public ImageOptions ImageSettings = new ImageOptions();
            
            [JsonProperty("Performance")]
            public PerformanceOptions Performance = new PerformanceOptions();
            
            [JsonProperty("Debug")]
            public DebugOptions Debug = new DebugOptions();
        }

        public class DatabaseSettings
        {
            [JsonProperty("Auto Save Interval (seconds)")]
            public float AutoSaveInterval = 60f;
            
            [JsonProperty("Batch Save Size")]
            public int BatchSaveSize = 20;
        }

        public class PVEOptions
        {
            [JsonProperty("Max Warnings")]
            public int MaxWarnings = 2;
            
            [JsonProperty("Protect Buildings")]
            public bool ProtectBuildings = true;
            
            [JsonProperty("Protect Players")]
            public bool ProtectPlayers = true;
            
            [JsonProperty("Allow Loot Own Corpse")]
            public bool AllowLootOwnCorpse = true;
            
            [JsonProperty("Block Build Radius")]
            public float BlockBuildRadius = 0f;
            
            [JsonProperty("Auto Convert On Team Join")]
            public bool AutoConvertOnTeamJoin = true;
            
            [JsonProperty("Protect From Fire")]
            public bool ProtectFromFire = true;
            
            [JsonProperty("Fire Prefabs")]
            public List<string> FirePrefabs = new List<string>()
            {
                "fireball_small",
                "fireball_small_arrow",
                "fireball_small_molotov",
                "fireball_small_shotgun",
                "fireball"
            };
            
            [JsonProperty("PVE Can Loot Any Corpse")]
            public bool PVECanLootAnyCorpse = true;
        }

        public class Messages
        {
            [JsonProperty("Welcome")]
            public string WelcomeText = "<size=32>Выберите режим игры</size>\n<size=18>Этот выбор определит вашу судьбу до следующего вайпа</size>";
            
            [JsonProperty("Selected PVE")]
            public string SelectedPVE = "<color=#81C784>✓ Вы выбрали PVE режим. Вы защищены!</color>";
            
            [JsonProperty("Selected PVP")]
            public string SelectedPVP = "<color=#FF7043>⚔ Вы выбрали PVP режим. Удачи!</color>";
            
            [JsonProperty("Attack Blocked")]
            public string AttackBlocked = "<color=#FF7043>⚠ Атака запрещена! Предупреждение: {0}/{1}</color>";
            
            [JsonProperty("Forced PVP")]
            public string ForcedPVP = "<color=#D32F2F>⛔ Превышен лимит! Вы переведены в PVP.</color>";
            
            [JsonProperty("Team Convert")]
            public string TeamConvert = "<color=#D32F2F>⚠ В команде PVP игрок! Автоперевод в PVP.</color>";
            
            [JsonProperty("Cannot Loot")]
            public string CannotLoot = "<color=#FF7043>⛔ Нельзя лутать чужие вещи в PVE режиме!</color>";
            
            [JsonProperty("Cannot Loot Sleeping")]
            public string CannotLootSleeping = "<color=#FF7043>⛔ Нельзя лутать спящих PVE игроков!</color>";
            
            [JsonProperty("Building Protected")]
            public string BuildingProtected = "<color=#81C784>🏠 Строение защищено (PVE)</color>";
            
            [JsonProperty("Building Blocked")]
            public string BuildingBlockedRadius = "<color=#FF7043>⛔ Нельзя строиться рядом с PVP базой!</color>";
            
            [JsonProperty("Team Warning")]
            public string TeamWarning = "<color=#FF7043>⚠ В команде есть PVP игроки!</color>";
            
            [JsonProperty("Mode PVE")]
            public string ModePVE = "<color=#81C784>🛡 PVE</color>";
            
            [JsonProperty("Mode PVP")]
            public string ModePVP = "<color=#FF7043>⚔ PVP</color>";
            
            [JsonProperty("Already Chosen")]
            public string AlreadyChosen = "<color=#FF7043>⚠ Вы уже выбрали режим! Используйте /mode для проверки.</color>";
            
            [JsonProperty("Cannot Use Explosive")]
            public string CannotUseExplosive = "<color=#FF7043>⛔ Нельзя использовать взрывчатку в PVE режиме!</color>";
            
            [JsonProperty("Cannot Use Rocket")]
            public string CannotUseRocket = "<color=#FF7043>⛔ Нельзя использовать ракеты в PVE режиме!</color>";
            
            [JsonProperty("PVE Base Nearby")]
            public string PVEBaseNearby = "<color=#FF7043>⛔ Рядом PVE база! Нельзя использовать взрывчатку!</color>";
            
            [JsonProperty("Team Mixed Modes")]
            public string TeamMixedModes = "<color=#D32F2F>[Команда] Обнаружены смешанные режимы в команде!</color>";
            
            [JsonProperty("Team Converted")]
            public string TeamConverted = "<color=#D32F2F>[Команда] {0} игрок(ов) переведено в PVP: {1}</color>";
            
            [JsonProperty("Team PVP Joined")]
            public string TeamPVPJoined = "<color=#FF7043>[Команда] {0} переведен в PVP (в команде есть PVP игроки)</color>";
        }

        public class UIOptions
        {
            [JsonProperty("Scroll Width")]
            public float ScrollWidth = 0.28f;
            
            [JsonProperty("Scroll Spacing")]
            public float ScrollSpacing = 0.04f;
            
            [JsonProperty("Max Scroll Height")]
            public float MaxScrollHeight = 0.75f;
            
            [JsonProperty("Mode Icon Offset Min")]
            public string ModeIconOffsetMin = "-440 16";
            
            [JsonProperty("Mode Icon Offset Max")]
            public string ModeIconOffsetMax = "-350 98";
        }

        public class ImageOptions
        {
            [JsonProperty("Use ImageLibrary")]
            public bool UseImageLibrary = true;
            
            [JsonProperty("PVE Scroll URL")]
            public string PVEScrollURL = "https://i.yapx.ru/c3gYD.png    ";
            
            [JsonProperty("PVP Scroll URL")]
            public string PVPScrollURL = "https://i.yapx.ru/c3gYZ.png    ";
            
            [JsonProperty("PVE Icon URL")]
            public string PVEIconURL = "https://i.yapx.ru/c3iZi.png    ";
            
            [JsonProperty("PVP Icon URL")]
            public string PVPIconURL = "https://i.yapx.ru/c3iZj.png    ";
            
            [JsonProperty("PVE Scroll Image Name")]
            public string PVEScrollImageName = "SQPVEMode_PVE_Scroll";
            
            [JsonProperty("PVP Scroll Image Name")]
            public string PVPScrollImageName = "SQPVEMode_PVP_Scroll";
            
            [JsonProperty("PVE Icon Image Name")]
            public string PVEIconImageName = "SQPVEMode_PVE_Icon";
            
            [JsonProperty("PVP Icon Image Name")]
            public string PVPIconImageName = "SQPVEMode_PVP_Icon";
            
            [JsonProperty("Image Aspect Ratio")]
            public float ImageAspectRatio = 0.56f;
        }

        public class PerformanceOptions
        {
            [JsonProperty("Cache Player Data")]
            public bool CachePlayerData = true;
            
            [JsonProperty("Save On Disconnect")]
            public bool SaveOnDisconnect = true;
            
            [JsonProperty("Max Save Queue Size")]
            public int MaxSaveQueueSize = 1000;
        }

        public class DebugOptions
        {
            [JsonProperty("Enable Debug")]
            public bool EnableDebug = false;
            
            [JsonProperty("Log Violations")]
            public bool LogViolations = true;
            
            [JsonProperty("Log Database Operations")]
            public bool LogDatabase = false;
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            configData = Config.ReadObject<ConfigData>();
            
            if (configData?.PVE?.FirePrefabs != null)
            {
                var uniquePrefabs = configData.PVE.FirePrefabs.Distinct().ToList();
                if (uniquePrefabs.Count != configData.PVE.FirePrefabs.Count)
                {
                    PrintWarning($"[SQPVEMode] Удалено {configData.PVE.FirePrefabs.Count - uniquePrefabs.Count} дубликатов из FirePrefabs");
                    configData.PVE.FirePrefabs = uniquePrefabs;
                }
            }
            
            SaveConfig();
        }

        protected override void LoadDefaultConfig() => configData = new ConfigData();
        protected override void SaveConfig() => Config.WriteObject(configData, true);

        #endregion

        #region Data Models

        public class PlayerData
        {
            public ulong SteamID { get; set; }
            public bool IsPVE { get; set; } = true;
            public bool HasChosen { get; set; } = false;
            public int Warnings { get; set; } = 0;
            public long LastUpdate { get; set; } = DateTimeOffset.Now.ToUnixTimeSeconds();
            public int Version { get; set; } = 1;
            
            [JsonIgnore]
            public bool IsDirty { get; set; } = false;
            
            public void Migrate()
            {
                if (Version < 1) Version = 1;
            }
        }

        public class ServerData
        {
            public long LastWipeTime { get; set; } = 0;
            public string Version { get; set; } = "4.5.0";
        }

        #endregion

        #region JSON Database

        public class JsonDatabase
        {
            private SQPVEMode plugin;
            private string dataPath;
            private string playersPath;
            private string serverFile;
            
            public JsonDatabase(SQPVEMode plugin)
            {
                this.plugin = plugin;
                dataPath = $"{Interface.Oxide.DataDirectory}/{plugin.Name}";
                playersPath = $"{dataPath}/players";
                serverFile = $"{dataPath}/server.json";
                
                EnsureDirectory(dataPath);
                EnsureDirectory(playersPath);
            }
            
            private void EnsureDirectory(string path)
            {
                if (!Directory.Exists(path))
                    Directory.CreateDirectory(path);
            }
            
            public PlayerData LoadPlayer(ulong steamId)
            {
                string path = Path.Combine(playersPath, $"{steamId}.json");
                
                if (!File.Exists(path))
                    return new PlayerData { SteamID = steamId };
                
                try
                {
                    string json = File.ReadAllText(path);
                    var playerData = JsonConvert.DeserializeObject<PlayerData>(json);
                    if (playerData != null)
                    {
                        playerData.SteamID = steamId;
                        playerData.Migrate();
                        return playerData;
                    }
                }
                catch (Exception ex)
                {
                    plugin.PrintError($"Failed to load player {steamId}: {ex.Message}");
                }
                
                return new PlayerData { SteamID = steamId };
            }
            
            public void SavePlayer(PlayerData playerData)
            {
                string path = Path.Combine(playersPath, $"{playerData.SteamID}.json");
                
                try
                {
                    playerData.LastUpdate = DateTimeOffset.Now.ToUnixTimeSeconds();
                    File.WriteAllText(path, JsonConvert.SerializeObject(playerData, Formatting.Indented));
                    playerData.IsDirty = false;
                }
                catch (Exception ex)
                {
                    plugin.PrintError($"Failed to save player {playerData.SteamID}: {ex.Message}");
                }
            }
            
            public void SaveBatch(IEnumerable<PlayerData> batch)
            {
                foreach (var playerData in batch)
                {
                    if (playerData.IsDirty)
                        SavePlayer(playerData);
                }
            }
            
            public void DeletePlayer(ulong steamId)
            {
                string path = Path.Combine(playersPath, $"{steamId}.json");
                if (File.Exists(path))
                {
                    try { File.Delete(path); } catch { }
                }
            }
            
            public void WipeAll()
            {
                try
                {
                    if (Directory.Exists(playersPath))
                    {
                        foreach (var file in Directory.GetFiles(playersPath, "*.json"))
                        {
                            try { File.Delete(file); } catch { }
                        }
                    }
                }
                catch (Exception ex)
                {
                    plugin.PrintError($"Failed to wipe data: {ex.Message}");
                }
            }
            
            public ServerData LoadServerData()
            {
                if (!File.Exists(serverFile))
                    return new ServerData();
                
                try
                {
                    string json = File.ReadAllText(serverFile);
                    return JsonConvert.DeserializeObject<ServerData>(json) ?? new ServerData();
                }
                catch
                {
                    return new ServerData();
                }
            }
            
            public void SaveServerData(ServerData serverData)
            {
                try
                {
                    File.WriteAllText(serverFile, JsonConvert.SerializeObject(serverData, Formatting.Indented));
                }
                catch (Exception ex)
                {
                    plugin.PrintError($"Failed to save server data: {ex.Message}");
                }
            }
        }

        #endregion

        #region Database Manager

        public class DatabaseManager
        {
            private SQPVEMode plugin;
            private JsonDatabase database;
            private ConcurrentDictionary<ulong, PlayerData> cache;
            private ConcurrentQueue<ulong> saveQueue;
            private HashSet<ulong> pendingSaves;
            private Timer autoSaveTimer;
            private bool isShuttingDown = false;
            private object lockObject = new object();
            
            public DatabaseManager(SQPVEMode plugin)
            {
                this.plugin = plugin;
                database = new JsonDatabase(plugin);
                cache = new ConcurrentDictionary<ulong, PlayerData>();
                saveQueue = new ConcurrentQueue<ulong>();
                pendingSaves = new HashSet<ulong>();
                
                if (plugin.configData.Database.AutoSaveInterval > 0)
                {
                    autoSaveTimer = plugin.timer.Every(plugin.configData.Database.AutoSaveInterval, ProcessSaveQueue);
                }
            }
            
            public PlayerData GetPlayer(ulong steamId)
            {
                if (cache.TryGetValue(steamId, out var cachedData))
                    return cachedData;
                
                var playerData = database.LoadPlayer(steamId);
                cache[steamId] = playerData;
                return playerData;
            }
            
            public void MarkDirty(ulong steamId)
            {
                if (!cache.TryGetValue(steamId, out var playerData)) return;
                
                playerData.IsDirty = true;
                
                lock (lockObject)
                {
                    if (!pendingSaves.Contains(steamId) && saveQueue.Count < plugin.configData.Performance.MaxSaveQueueSize)
                    {
                        saveQueue.Enqueue(steamId);
                        pendingSaves.Add(steamId);
                    }
                }
            }
            
            public void SavePlayer(ulong steamId, bool immediate = false)
            {
                if (!cache.TryGetValue(steamId, out var playerData)) return;
                
                playerData.IsDirty = true;
                
                if (immediate)
                {
                    database.SavePlayer(playerData);
                    lock (lockObject) { pendingSaves.Remove(steamId); }
                }
                else
                {
                    MarkDirty(steamId);
                }
            }
            
            public void ProcessSaveQueue()
            {
                if (isShuttingDown) return;
                
                int batchSize = plugin.configData.Database.BatchSaveSize;
                var batch = new List<PlayerData>();
                var processed = new List<ulong>();
                
                lock (lockObject)
                {
                    while (batch.Count < batchSize && saveQueue.TryDequeue(out var steamId))
                    {
                        if (cache.TryGetValue(steamId, out var playerData) && playerData.IsDirty)
                        {
                            batch.Add(playerData);
                            processed.Add(steamId);
                        }
                        pendingSaves.Remove(steamId);
                    }
                }
                
                if (batch.Count > 0)
                {
                    try
                    {
                        database.SaveBatch(batch);
                        
                        if (plugin.configData.Debug.LogDatabase)
                            plugin.Puts($"Saved {batch.Count} players");
                    }
                    catch (Exception ex)
                    {
                        plugin.PrintError($"Batch save failed: {ex.Message}");
                        lock (lockObject)
                        {
                            foreach (var steamId in processed)
                            {
                                if (cache.TryGetValue(steamId, out var playerData) && playerData.IsDirty && !pendingSaves.Contains(steamId))
                                {
                                    saveQueue.Enqueue(steamId);
                                    pendingSaves.Add(steamId);
                                }
                            }
                        }
                    }
                }
            }
            
            public void SaveAll()
            {
                var dirtyPlayers = cache.Values.Where(p => p.IsDirty).ToList();
                
                if (dirtyPlayers.Count > 0)
                {
                    try
                    {
                        database.SaveBatch(dirtyPlayers);
                        plugin.Puts($"Saved {dirtyPlayers.Count} players on shutdown");
                    }
                    catch (Exception ex)
                    {
                        plugin.PrintError($"Failed to save all: {ex.Message}");
                    }
                }
            }
            
            public void RemoveFromCache(ulong steamId)
            {
                if (cache.TryRemove(steamId, out var playerData) && playerData.IsDirty)
                {
                    try
                    {
                        database.SavePlayer(playerData);
                    }
                    catch (Exception ex)
                    {
                        plugin.PrintError($"Failed to save on disconnect: {ex.Message}");
                    }
                }
            }
            
            public void WipeAll()
            {
                cache.Clear();
                lock (lockObject)
                {
                    saveQueue.Clear();
                    pendingSaves.Clear();
                }
                database.WipeAll();
            }
            
            public ServerData LoadServerData() => database.LoadServerData();
            
            public void SaveServerData(ServerData serverData) => database.SaveServerData(serverData);
            
            public void Shutdown()
            {
                isShuttingDown = true;
                autoSaveTimer?.Destroy();
                SaveAll();
            }
        }

        #endregion

        #region Fields

        private DatabaseManager dbManager;
        private ServerData serverData;
        private ImageManager imageManager;
        
        private const string UI_SELECTION = "SQPVEMode_SelectionUI";
        private const string UI_MODE_ICON = "SQPVEMode_ModeIcon";
        private Timer imageCheckTimer;
        private Timer teamCheckTimer;
        private Dictionary<ulong, Timer> disconnectTimers = new Dictionary<ulong, Timer>();
        private readonly object disconnectLock = new object();
        private Dictionary<ulong, RelationshipManager.PlayerTeam> playerTeamsCache = new Dictionary<ulong, RelationshipManager.PlayerTeam>();

        #endregion

        #region Image Management

        public class ImageManager
        {
            private SQPVEMode plugin;
            private Dictionary<string, string> cachedImages = new Dictionary<string, string>();
            private bool isReady = false;
            private bool isLoading = false;
            private int checkCount = 0;
            
            public bool IsReady => isReady;
            
            public ImageManager(SQPVEMode plugin)
            {
                this.plugin = plugin;
            }
            
            public void LoadImages()
            {
                if (isLoading) return;
                isLoading = true;
                cachedImages.Clear();
                isReady = false;
                checkCount = 0;
                
                if (plugin.ImageLibrary == null)
                {
                    plugin.PrintWarning("[SQPVEMode] ImageLibrary not found! Using fallback colors.");
                    isReady = false;
                    isLoading = false;
                    return;
                }
                
                string pveScrollUrl = plugin.configData.ImageSettings.PVEScrollURL?.Trim() ?? "";
                string pvpScrollUrl = plugin.configData.ImageSettings.PVPScrollURL?.Trim() ?? "";
                string pveIconUrl = plugin.configData.ImageSettings.PVEIconURL?.Trim() ?? "";
                string pvpIconUrl = plugin.configData.ImageSettings.PVPIconURL?.Trim() ?? "";
                
                if (string.IsNullOrEmpty(pveScrollUrl) || string.IsNullOrEmpty(pvpScrollUrl) ||
                    string.IsNullOrEmpty(pveIconUrl) || string.IsNullOrEmpty(pvpIconUrl))
                {
                    plugin.PrintWarning("[SQPVEMode] Some image URLs not configured! Using fallback colors.");
                    isReady = false;
                    isLoading = false;
                    return;
                }
                
                plugin.Puts($"[SQPVEMode] Loading images...");
                
                LoadImageWithCallback("pve", pveScrollUrl, plugin.configData.ImageSettings.PVEScrollImageName);
                LoadImageWithCallback("pvp", pvpScrollUrl, plugin.configData.ImageSettings.PVPScrollImageName);
                LoadImageWithCallback("pve_icon", pveIconUrl, plugin.configData.ImageSettings.PVEIconImageName);
                LoadImageWithCallback("pvp_icon", pvpIconUrl, plugin.configData.ImageSettings.PVPIconImageName);
                
                plugin.imageCheckTimer = plugin.timer.Repeat(2f, 40, CheckImagesReady);
            }
            
            private void LoadImageWithCallback(string key, string url, string imageName)
            {
                try
                {
                    Action callback = () =>
                    {
                        if (plugin.configData.Debug.EnableDebug)
                            plugin.Puts($"[SQPVEMode] Callback received for {key}");
                        CheckAndCacheImage(key, imageName);
                    };
                    
                    plugin.ImageLibrary.Call("AddImage", url, imageName, 0UL, callback);
                }
                catch (Exception ex)
                {
                    plugin.PrintError($"[SQPVEMode] Error loading {key}: {ex.Message}");
                }
            }
            
            private void CheckAndCacheImage(string key, string imageName)
            {
                try
                {
                    string imageId = plugin.ImageLibrary.Call<string>("GetImage", imageName, 0UL, false);
                    
                    if (!string.IsNullOrEmpty(imageId) && imageId != "0" && !imageId.StartsWith("LOADING") && !imageId.StartsWith("NONE"))
                    {
                        cachedImages[key] = imageId;
                        if (plugin.configData.Debug.EnableDebug)
                            plugin.Puts($"[SQPVEMode] {key} image cached");
                    }
                }
                catch (Exception ex)
                {
                    if (plugin.configData.Debug.EnableDebug)
                        plugin.PrintError($"[SQPVEMode] Error caching {key}: {ex.Message}");
                }
            }
            
            private void CheckImagesReady()
            {
                checkCount++;
                
                if (!cachedImages.ContainsKey("pve"))
                    CheckAndCacheImage("pve", plugin.configData.ImageSettings.PVEScrollImageName);
                
                if (!cachedImages.ContainsKey("pvp"))
                    CheckAndCacheImage("pvp", plugin.configData.ImageSettings.PVPScrollImageName);
                
                if (!cachedImages.ContainsKey("pve_icon"))
                    CheckAndCacheImage("pve_icon", plugin.configData.ImageSettings.PVEIconImageName);
                
                if (!cachedImages.ContainsKey("pvp_icon"))
                    CheckAndCacheImage("pvp_icon", plugin.configData.ImageSettings.PVPIconImageName);
                
                bool pveReady = cachedImages.ContainsKey("pve");
                bool pvpReady = cachedImages.ContainsKey("pvp");
                bool pveIconReady = cachedImages.ContainsKey("pve_icon");
                bool pvpIconReady = cachedImages.ContainsKey("pvp_icon");
                
                if (pveReady && pvpReady && pveIconReady && pvpIconReady)
                {
                    isReady = true;
                    isLoading = false;
                    
                    plugin.imageCheckTimer?.Destroy();
                    plugin.imageCheckTimer = null;
                    
                    plugin.Puts($"[SQPVEMode] Images loaded successfully!");
                    
                    foreach (var player in BasePlayer.activePlayerList)
                    {
                        var data = plugin.dbManager.GetPlayer(player.userID);
                        if (!data.HasChosen && player.IsConnected)
                        {
                            CuiHelper.DestroyUi(player, UI_SELECTION);
                            plugin.ShowSelectionUI(player);
                        }
                        else if (data.HasChosen && player.IsConnected)
                        {
                            plugin.UpdateModeIcon(player);
                        }
                    }
                }
                else if (checkCount >= 40)
                {
                    isReady = false;
                    isLoading = false;
                    
                    plugin.imageCheckTimer?.Destroy();
                    plugin.imageCheckTimer = null;
                    
                    plugin.PrintWarning($"[SQPVEMode] Image loading timeout. Using fallback colors.");
                }
            }
            
            public string GetImage(string key)
            {
                if (cachedImages.TryGetValue(key, out var img) && !string.IsNullOrEmpty(img))
                    return img;
                
                if (plugin.ImageLibrary != null)
                {
                    string name = key switch
                    {
                        "pve" => plugin.configData.ImageSettings.PVEScrollImageName,
                        "pvp" => plugin.configData.ImageSettings.PVPScrollImageName,
                        "pve_icon" => plugin.configData.ImageSettings.PVEIconImageName,
                        "pvp_icon" => plugin.configData.ImageSettings.PVPIconImageName,
                        _ => null
                    };
                    
                    if (name != null)
                    {
                        try
                        {
                            string imageId = plugin.ImageLibrary.Call<string>("GetImage", name, 0UL, false);
                            if (!string.IsNullOrEmpty(imageId) && imageId != "0" && !imageId.StartsWith("LOADING") && !imageId.StartsWith("NONE"))
                            {
                                cachedImages[key] = imageId;
                                return imageId;
                            }
                        }
                        catch { }
                    }
                }
                return null;
            }
            
            public void ForceReload()
            {
                cachedImages.Clear();
                isReady = false;
                isLoading = false;
                checkCount = 0;
                LoadImages();
            }
        }

        #endregion

        #region Oxide Hooks

        private void Init()
        {
            imageCheckTimer?.Destroy();
            imageCheckTimer = null;
            
            dbManager = new DatabaseManager(this);
            imageManager = new ImageManager(this);
            serverData = dbManager.LoadServerData();
            
            teamCheckTimer = timer.Every(5f, CheckAllTeamsForMixedModes);
        }

        private void OnServerInitialized()
        {
            imageManager.LoadImages();
            
            timer.Once(6f, () =>
            {
                foreach (var player in BasePlayer.activePlayerList)
                    OnPlayerConnected(player);
            });
        }

        private void OnPlayerConnected(BasePlayer player)
        {
            if (player == null || player.IsNpc) return;
            
            lock (disconnectLock)
            {
                if (disconnectTimers.TryGetValue(player.userID, out var existingTimer))
                {
                    existingTimer?.Destroy();
                    disconnectTimers.Remove(player.userID);
                }
            }
            
            var playerData = dbManager.GetPlayer(player.userID);
            
            if (!playerData.HasChosen)
            {
                float delay = imageManager.IsReady ? 0.5f : 10f;
                timer.Once(delay, () =>
                {
                    if (player?.IsConnected == true)
                    {
                        CuiHelper.DestroyUi(player, UI_SELECTION);
                        ShowSelectionUI(player);
                    }
                });
            }
            else
            {
                timer.Once(1f, () => UpdateModeIcon(player));
            }
        }

        private void OnPlayerDisconnected(BasePlayer player)
        {
            if (player == null) return;
            
            ClearPlayerTeamCache(player.userID);
            
            CuiHelper.DestroyUi(player, UI_MODE_ICON);
            
            if (configData.Performance.SaveOnDisconnect)
                dbManager.SavePlayer(player.userID, true);
            
            if (configData.Performance.CachePlayerData)
            {
                lock (disconnectLock)
                {
                    if (disconnectTimers.TryGetValue(player.userID, out var oldTimer))
                    {
                        oldTimer?.Destroy();
                    }

                    disconnectTimers[player.userID] = timer.Once(30f, () =>
                    {
                        dbManager.RemoveFromCache(player.userID);
                        lock (disconnectLock) 
                        { 
                            disconnectTimers.Remove(player.userID); 
                        }
                    });
                }
            }
        }

        private void OnNewSave()
        {
            Puts("New save detected! Resetting all player data...");
            playerTeamsCache.Clear();
            dbManager.WipeAll();
            serverData.LastWipeTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
            dbManager.SaveServerData(serverData);
        }

        private void Unload()
        {
            imageCheckTimer?.Destroy();
            teamCheckTimer?.Destroy();
            
            lock (disconnectLock)
            {
                foreach (var t in disconnectTimers.Values.ToList())
                    t?.Destroy();
                disconnectTimers.Clear();
            }
            
            foreach (var player in BasePlayer.activePlayerList.ToList())
            {
                CuiHelper.DestroyUi(player, UI_SELECTION);
                CuiHelper.DestroyUi(player, UI_MODE_ICON);
            }
            
            dbManager?.Shutdown();
        }

        #endregion

        #region UI System

        private void ShowSelectionUI(BasePlayer player)
        {
            if (player == null || !player.IsConnected) return;

            var data = dbManager.GetPlayer(player.userID);
            if (data.HasChosen) return;

            CuiHelper.DestroyUi(player, UI_SELECTION);

            var container = new CuiElementContainer();
            
            container.Add(new CuiPanel
            {
                CursorEnabled = true,
                Image = { 
                    Color = "0.1 0.1 0.1 0.95",
                    Material = "assets/content/ui/uibackgroundblur-ingamemenu.mat"
                },
                RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" }
            }, "Overlay", UI_SELECTION);

            container.Add(new CuiLabel
            {
                Text = { 
                    Text = configData.MSG.WelcomeText,
                    FontSize = 28,
                    Align = TextAnchor.MiddleCenter,
                    Color = "1 1 1 1",
                    Font = "robotocondensed-bold.ttf"
                },
                RectTransform = { AnchorMin = "0 0.85", AnchorMax = "1 0.98" }
            }, UI_SELECTION);

            float width = configData.UI.ScrollWidth;
            float spacing = configData.UI.ScrollSpacing;
            float startX = (1f - (width * 2 + spacing)) / 2;

            CreateImageButton(container, startX, "sqpve_choose pve", "pve");
            CreateImageButton(container, startX + width + spacing, "sqpve_choose pvp", "pvp");

            CuiHelper.AddUi(player, container);
        }

        private void CreateImageButton(CuiElementContainer container, float xPos,
            string command, string imageKey)
        {
            string img = imageManager.GetImage(imageKey);
            
            float aspect = configData.ImageSettings.ImageAspectRatio > 0 
                ? configData.ImageSettings.ImageAspectRatio 
                : 0.56f;

            float maxW = configData.UI.ScrollWidth;
            float maxH = configData.UI.MaxScrollHeight;
            float h = maxW / aspect;
            float w = maxW;
            
            if (h > maxH)
            {
                h = maxH;
                w = h * aspect;
            }
            
            float yMin = (1f - h) / 2;
            float xMin = xPos;

            string mainPanelId = $"{UI_SELECTION}_{imageKey}_main";
            string imagePanelId = $"{UI_SELECTION}_{imageKey}_img";
            string buttonId = $"{UI_SELECTION}_{imageKey}_btn";

            container.Add(new CuiPanel
            {
                RectTransform = { 
                    AnchorMin = $"{xMin} {yMin}", 
                    AnchorMax = $"{xMin + w} {yMin + h}" 
                },
                Image = { Color = "0 0 0 0" }
            }, UI_SELECTION, mainPanelId);

            if (!string.IsNullOrEmpty(img))
            {
                container.Add(new CuiElement
                {
                    Parent = mainPanelId,
                    Name = imagePanelId,
                    Components =
                    {
                        new CuiRawImageComponent { 
                            Png = img, 
                            Color = "1 1 1 1",
                            FadeIn = 0.5f
                        },
                        new CuiRectTransformComponent { 
                            AnchorMin = "0 0", 
                            AnchorMax = "1 1" 
                        }
                    }
                });
            }
            else
            {
                container.Add(new CuiPanel
                {
                    RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
                    Image = { Color = imageKey == "pve" ? "0.2 0.6 0.2 0.9" : "0.6 0.2 0.2 0.9" }
                }, mainPanelId, imagePanelId);

                container.Add(new CuiLabel
                {
                    Text = { 
                        Text = imageKey == "pve" ? "<size=40><b>PVE</b></size>\n<size=20>РЕЖИМ</size>" : "<size=40><b>PVP</b></size>\n<size=20>РЕЖИМ</size>",
                        FontSize = 32,
                        Align = TextAnchor.MiddleCenter,
                        Color = "1 1 1 1",
                        Font = "robotocondensed-bold.ttf"
                    },
                    RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" }
                }, imagePanelId);
            }

            container.Add(new CuiButton
            {
                RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
                Button = { 
                    Color = "0 0 0 0",
                    Command = command
                },
                Text = { Text = "" }
            }, mainPanelId, buttonId);
        }

        public void UpdateModeIcon(BasePlayer player)
        {
            if (player == null || !player.IsConnected) return;
            
            var data = dbManager.GetPlayer(player.userID);
            if (!data.HasChosen) return;
            
            CuiHelper.DestroyUi(player, UI_MODE_ICON);
            
            string imageKey = data.IsPVE ? "pve_icon" : "pvp_icon";
            string img = imageManager.GetImage(imageKey);
            
            var container = new CuiElementContainer();
            
            string offsetMin = configData.UI.ModeIconOffsetMin;
            string offsetMax = configData.UI.ModeIconOffsetMax;
            
            if (!string.IsNullOrEmpty(img))
            {
                container.Add(new CuiElement
                {
                    Name = UI_MODE_ICON,
                    Parent = "Overlay",
                    Components =
                    {
                        new CuiRawImageComponent
                        {
                            Png = img,
                            Color = "1 1 1 1",
                            FadeIn = 0.3f
                        },
                        new CuiRectTransformComponent
                        {
                            AnchorMin = "1 0",
                            AnchorMax = "1 0",
                            OffsetMin = offsetMin,
                            OffsetMax = offsetMax
                        }
                    }
                });
            }
            else
            {
                container.Add(new CuiPanel
                {
                    RectTransform = 
                    {
                        AnchorMin = "1 0",
                        AnchorMax = "1 0",
                        OffsetMin = offsetMin,
                        OffsetMax = offsetMax
                    },
                    Image = 
                    { 
                        Color = data.IsPVE ? "0.2 0.8 0.2 0.8" : "0.8 0.2 0.2 0.8",
                        FadeIn = 0.3f
                    }
                }, "Overlay", UI_MODE_ICON);
                
                container.Add(new CuiLabel
                {
                    Text = 
                    {
                        Text = data.IsPVE ? "PVE" : "PVP",
                        FontSize = 14,
                        Align = TextAnchor.MiddleCenter,
                        Color = "1 1 1 1",
                        Font = "robotocondensed-bold.ttf"
                    },
                    RectTransform = 
                    {
                        AnchorMin = "0 0",
                        AnchorMax = "1 1"
                    }
                }, UI_MODE_ICON);
            }
            
            CuiHelper.AddUi(player, container);
        }

        public void DestroyModeIcon(BasePlayer player)
        {
            if (player != null)
                CuiHelper.DestroyUi(player, UI_MODE_ICON);
        }

        #endregion

        #region Commands

        [ConsoleCommand("sqpve_choose")]
        private void CmdChoose(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();
            if (player == null) return;
            
            if (!arg.HasArgs(1)) return;
            
            string choice = arg.GetString(0).ToLower();
            bool isPVE = choice == "pve";
            
            CuiHelper.DestroyUi(player, UI_SELECTION);
            
            SetMode(player, isPVE);
        }

        [ChatCommand("mode")]
        private void CmdMode(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;
            
            var playerData = dbManager.GetPlayer(player.userID);
            
            string mode;
            if (!playerData.HasChosen)
                mode = "<color=#FFD700>❓ НЕ ВЫБРАН</color>";
            else if (playerData.IsPVE)
                mode = configData.MSG.ModePVE;
            else
                mode = configData.MSG.ModePVP;
            
            string status = playerData.HasChosen ? "Выбран" : "Не выбран";
            
            SendReply(player, $"{mode} <color=#C9C9C9>({status})</color>");
            
            if (!playerData.HasChosen)
                timer.Once(0.5f, () => ShowSelectionUI(player));
        }

        [ChatCommand("setmode")]
        private void CmdSetMode(BasePlayer player, string command, string[] args)
        {
            if (!player.IsAdmin || args.Length < 2)
            {
                SendReply(player, "Usage: /setmode <name/steamid> <pve/pvp>");
                return;
            }

            BasePlayer target = ulong.TryParse(args[0], out var sid) 
                ? BasePlayer.FindByID(sid) 
                : BasePlayer.Find(args[0]);

            if (target == null)
            {
                SendReply(player, "Player not found");
                return;
            }

            bool pve = args[1].ToLower() == "pve";
            SetMode(target, pve, true);
            
            SendReply(player, $"Set {target.displayName} to {(pve ? "PVE" : "PVP")}");
        }

        [ChatCommand("warnings")]
        private void CmdWarnings(BasePlayer player, string command, string[] args)
        {
            if (!player.IsAdmin)
            {
                var playerData = dbManager.GetPlayer(player.userID);
                SendReply(player, $"Warnings: {playerData.Warnings}/{configData.PVE.MaxWarnings}");
                return;
            }

            if (args.Length == 0)
            {
                SendReply(player, "Usage: /warnings <name/steamid>");
                return;
            }

            BasePlayer target = ulong.TryParse(args[0], out var sid)
                ? BasePlayer.FindByID(sid)
                : BasePlayer.Find(args[0]);

            if (target == null)
            {
                SendReply(player, "Player not found");
                return;
            }

            var targetData = dbManager.GetPlayer(target.userID);
            string mode = targetData.HasChosen ? (targetData.IsPVE ? "PVE" : "PVP") : "NOT CHOSEN";
            SendReply(player, $"{target.displayName}: {mode}, Warnings: {targetData.Warnings}/{configData.PVE.MaxWarnings}");
        }

        [ChatCommand("resetwarn")]
        private void CmdResetWarn(BasePlayer player, string command, string[] args)
        {
            if (!player.IsAdmin) return;

            BasePlayer target = args.Length == 0 
                ? player 
                : (ulong.TryParse(args[0], out var sid) ? BasePlayer.FindByID(sid) : BasePlayer.Find(args[0]));

            if (target == null)
            {
                SendReply(player, "Player not found");
                return;
            }

            var targetData = dbManager.GetPlayer(target.userID);
            targetData.Warnings = 0;
            dbManager.MarkDirty(target.userID);
            dbManager.SavePlayer(target.userID, true);

            string msg = target == player ? "Your warnings reset" : $"{target.displayName}'s warnings reset";
            SendRe
// ... [Code preview truncated due to length. Full source in attached file] ...

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