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

FREE PulseTickets : 2.0.0 2.0.0

Нет прав для скачивания
C#:
using System;
using System.Collections.Generic;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using UnityEngine;
namespace Oxide.Plugins
{
    [Info("PulseTickets", "Sergioklv", "2.0.0")]
    public class PulseTickets : RustPlugin
    {
        private const int MaxTickets = 60000;
        private const string TicketDataFile = "PulseTickets/Tickets";
        private const string PlaytimeDataFile = "PulseTickets/Playtime";
        private const string DiscordDataFile = "PulseTickets/Discords";

        private Dictionary<int, TicketInfo> ticketDatabase = new Dictionary<int, TicketInfo>();
        private Dictionary<string, int> playtimeMinutes = new Dictionary<string, int>();
        private Dictionary<string, string> discordBySteam = new Dictionary<string, string>();
        private Dictionary<ulong, Timer> playerTimers = new Dictionary<ulong, Timer>();

        private readonly object ticketLock = new object();

        private class TicketInfo
        {
            public string steamid;
            public string name;
            public string discord;
        }

        #region Oxide hooks

        void Init()
        {
            ticketDatabase = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<int, TicketInfo>>(TicketDataFile) ?? new Dictionary<int, TicketInfo>();
            playtimeMinutes = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<string, int>>(PlaytimeDataFile) ?? new Dictionary<string, int>();
            discordBySteam = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<string, string>>(DiscordDataFile) ?? new Dictionary<string, string>();
            Puts("PulseTickets: данные загружены. Найдено билетов: " + ticketDatabase.Count);
        }

        void OnServerInitialized()
        {
            foreach (var player in BasePlayer.activePlayerList)
            {
                if (player == null) continue;
                EnsurePlaytimeEntry(player.userID);
                StartPlayerTimer(player);
				ShowTicketUI(player);
            }
        }

        void OnPlayerConnected(BasePlayer player)
        {
            if (player == null) return;
            EnsurePlaytimeEntry(player.userID);
            StartPlayerTimer(player);
 			ShowTicketUI(player);
        }

        void OnPlayerDisconnected(BasePlayer player, string reason)
        {
            if (player == null) return;
            StopPlayerTimer(player.userID);
            SavePlaytimeFor(player.userID);
        }

        void Unload()
        {
            foreach (var t in playerTimers.Values)
                t.Destroy();
            playerTimers.Clear();

            SaveAllData();
        }

        #endregion

        #region Timers & playtime

        private void EnsurePlaytimeEntry(ulong userID)
        {
            string sid = userID.ToString();
            if (!playtimeMinutes.ContainsKey(sid))
                playtimeMinutes[sid] = 0;
        }

        private void StartPlayerTimer(BasePlayer player)
        {
            if (player == null) return;
            ulong id = player.userID;
            if (playerTimers.ContainsKey(id)) return;

            Timer t = timer.Repeat(60f, 0, () =>
            {
                try
                {
                    AddMinuteAndCheckTicket(player.userID, player);
                }
                catch (Exception ex)
                {
                    Puts("PulseTickets: ошибка в таймере: " + ex);
                }
            });

            playerTimers[id] = t;
        }

        private void StopPlayerTimer(ulong userID)
        {
            if (playerTimers.TryGetValue(userID, out Timer t))
            {
                t.Destroy();
                playerTimers.Remove(userID);
            }
        }
        private void ShowTicketUI(BasePlayer player)
        {
            if (player == null || !player.IsConnected) return;

            string sid = player.UserIDString;
            int count = 0;
            if (!string.IsNullOrEmpty(sid))
            {
                foreach (var kv in ticketDatabase)
                {
                    if (kv.Value != null && kv.Value.steamid == sid)
                        count++;
                }
            }

            int minutesLeft = 60;
            if (playtimeMinutes.ContainsKey(sid))
                minutesLeft = Math.Max(0, 60 - playtimeMinutes[sid]);

            var ui = new CuiElementContainer();

            var label = new CuiLabel
            {
                Text =
                {
                    Text =
                        $"<color=#4DFFFF>У вас всего</color> <color=#FFFF55>{count}</color> <color=#4DFFFF>билетов <color=#FFFF55>/mytickets</color></color>\n" +
                        $"<color=#FF8080>Следующий билет через:</color> <color=#FFFF55>{minutesLeft} мин</color>",
                    FontSize = 14,
                    Color = "1 1 1 1"
                },
                RectTransform =
                {
                    AnchorMin = "0.832 0.94",
                    AnchorMax = "1.0 1.0"
                }
            };

            CuiHelper.DestroyUi(player, "PulseTicketsUI");
            ui.Add(label, "Hud", "PulseTicketsUI");
            CuiHelper.AddUi(player, ui);
        }

        private void DestroyTicketUI(BasePlayer player)
        {
            if (player == null) return;
            CuiHelper.DestroyUi(player, "PulseTicketsUI");
        }
        private void AddMinuteAndCheckTicket(ulong userID, BasePlayer playerRef)
        {
            string sid = userID.ToString();

            if (!playtimeMinutes.ContainsKey(sid))
                playtimeMinutes[sid] = 0;

            playtimeMinutes[sid]++;

            Interface.Oxide.DataFileSystem.WriteObject(PlaytimeDataFile, playtimeMinutes);

            BasePlayer p = playerRef;
            if (p == null || !p.IsConnected)
                p = BasePlayer.FindByID(userID);

            if (p != null && p.IsConnected)
                ShowTicketUI(p);			

            if (playtimeMinutes[sid] >= 60)
            {
                playtimeMinutes[sid] -= 60;
                Interface.Oxide.DataFileSystem.WriteObject(PlaytimeDataFile, playtimeMinutes);

                BasePlayer bp = playerRef;
                if (bp == null || !bp.IsConnected)
                {
                    bp = BasePlayer.FindByID(userID);
                }
                if (bp != null && bp.IsConnected)
                {
                    GiveTicketSafe(bp);
                }
                else
                {
                    string name = bp != null ? bp.displayName : "Unknown";
                    GiveTicketOffline(userID.ToString(), name);
                }
            }
        }

        #endregion

        #region Ticket assignment (thread-safe)

        private void GiveTicketSafe(BasePlayer player)
        {
            if (player == null) return;

            int assigned = -1;
            lock (ticketLock)
            {
                assigned = GetNextAvailableTicketLocked();
                if (assigned == -1)
                {
                    Puts("PulseTickets: билеты закончились (1-" + MaxTickets + ").");
                }
                else
                {
                    var info = new TicketInfo
                    {
                        steamid = player.UserIDString,
                        name = player.displayName,
                        discord = GetSavedDiscord(player.UserIDString)
                    };
                    ticketDatabase[assigned] = info;

                    Interface.Oxide.DataFileSystem.WriteObject(TicketDataFile, ticketDatabase);
                }
            }

            if (assigned != -1)
            {
                string discord = GetSavedDiscord(player.UserIDString);
                if (string.IsNullOrEmpty(discord))
                {
                    player.ChatMessage($"<color=#00ff00>Вы получили билет № {assigned}.</color>\n" +
                        $"<color=#ffff00>Не забудьте использовать команду /discord ВашDiscord, чтобы мы могли связаться с вами в случае выигрыша.</color>");
                }
                else
                {
                    player.ChatMessage($"<color=#00ff00>Вы получили билет № {assigned}.</color>\n" +
                        $"<color=#ffff00>Мы свяжемся с вами через Discord: {discord}</color>");
                }

                Puts($"PulseTickets: выдан билет #{assigned} игроку {player.displayName} ({player.UserIDString}).");
				ShowTicketUI(player);
            }
            else
            {
                if (player != null && player.IsConnected)
                    player.ChatMessage("<color=#ff4444>Нет доступных билетов (1–30000).</color>");
            }
        }

        private void GiveTicketOffline(string steamid, string name)
        {
            int assigned = -1;
            lock (ticketLock)
            {
                assigned = GetNextAvailableTicketLocked();
                if (assigned == -1)
                {
                    Puts("PulseTickets: билеты закончились (1-" + MaxTickets + ").");
                }
                else
                {
                    var info = new TicketInfo
                    {
                        steamid = steamid,
                        name = name ?? "Unknown",
                        discord = GetSavedDiscord(steamid)
                    };
                    ticketDatabase[assigned] = info;
                    Interface.Oxide.DataFileSystem.WriteObject(TicketDataFile, ticketDatabase);
                    Puts($"PulseTickets: выдан оффлайн-билет #{assigned} для steamid {steamid} ({name}).");
                }
            }
        }

        private int GetNextAvailableTicketLocked()
        {
            for (int i = 1; i <= MaxTickets; i++)
            {
                if (!ticketDatabase.ContainsKey(i))
                    return i;
            }
            return -1;
        }

        #endregion

        #region Discord command & helpers
        [ChatCommand("discord")]
        private void CmdDiscord(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;

            if (args == null || args.Length == 0)
            {
                player.ChatMessage("Использование: /discord ВашDiscord");
                return;
            }

            string discord = string.Join(" ", args).Trim();

            if (string.IsNullOrEmpty(discord))
            {
                player.ChatMessage("Ошибка: пустое имя Discord. Использование: /discord ВашDiscord");
                return;
            }

            string sid = player.UserIDString;

            if (discordBySteam.ContainsKey(sid) && !string.IsNullOrEmpty(discordBySteam[sid]))
            {
                player.ChatMessage($"Ваш Discord уже зарегистрирован: <color=#00ffff>{discordBySteam[sid]}</color>");
                return;
            }
            discordBySteam[sid] = discord;
            Interface.Oxide.DataFileSystem.WriteObject(DiscordDataFile, discordBySteam);
            bool updatedAny = false;
            lock (ticketLock)
            {
                foreach (var kv in new Dictionary<int, TicketInfo>(ticketDatabase))
                {
                    if (kv.Value != null && kv.Value.steamid == sid)
                    {
                        kv.Value.discord = discord;
                        ticketDatabase[kv.Key] = kv.Value;
                        updatedAny = true;
                    }
                }
                if (updatedAny)
                {
                    Interface.Oxide.DataFileSystem.WriteObject(TicketDataFile, ticketDatabase);
                }
            }

            player.ChatMessage($"Ваш Discord <color=#00ffff>{discord}</color> успешно сохранён.");
            if (updatedAny)
                player.ChatMessage("Ваш Discord также добавлен к уже выданным билетам.");
        }

        private string GetSavedDiscord(string steamid)
        {
            if (string.IsNullOrEmpty(steamid)) return null;
            if (discordBySteam.TryGetValue(steamid, out string d))
                return string.IsNullOrEmpty(d) ? null : d;
            return null;
        }

        private string GetSavedDiscord(ulong steamid)
        {
            return GetSavedDiscord(steamid.ToString());
        }

        #endregion

        #region Persistence helpers

        private void SavePlaytimeFor(ulong userID)
        {
            string sid = userID.ToString();
            if (playtimeMinutes.ContainsKey(sid))
            {
                Interface.Oxide.DataFileSystem.WriteObject(PlaytimeDataFile, playtimeMinutes);
            }
        }

        private void SaveAllData()
        {
            try
            {
                Interface.Oxide.DataFileSystem.WriteObject(TicketDataFile, ticketDatabase);
                Interface.Oxide.DataFileSystem.WriteObject(PlaytimeDataFile, playtimeMinutes);
                Interface.Oxide.DataFileSystem.WriteObject(DiscordDataFile, discordBySteam);
                Puts("PulseTickets: все данные сохранены.");
            }
            catch (Exception ex)
            {
                Puts("PulseTickets: ошибка при сохранении данных: " + ex);
            }
        }

        #endregion

        #region Utility admin commands (optional)

        [ConsoleCommand("validatetickets")]
        private void CmdValidateTicketsConsole(ConsoleSystem.Arg arg)
        {
            // Si lo ejecuta un jugador desde F1, comprobamos admin
            BasePlayer player = arg.Player();
            if (player != null && !player.IsAdmin)
            {
                arg.ReplyWith("Команда доступна только администраторам.");
                return;
            }

            Dictionary<int, List<TicketInfo>> grouped = new Dictionary<int, List<TicketInfo>>();

            foreach (var kv in ticketDatabase)
            {
                if (!grouped.ContainsKey(kv.Key))
                    grouped[kv.Key] = new List<TicketInfo>();

                grouped[kv.Key].Add(kv.Value);
            }

            int fixedCount = 0;

            foreach (var group in grouped)
            {
                int ticketNumber = group.Key;
                List<TicketInfo> owners = group.Value;

                if (owners.Count > 1)
                {
                    Puts($"VALIDATE: Ticket #{ticketNumber} имеет {owners.Count} владельцев. Исправление...");

                    TicketInfo keeper = owners[0];
                    ticketDatabase[ticketNumber] = keeper;

                    for (int i = 1; i < owners.Count; i++)
                    {
                        TicketInfo dup = owners[i];
                        int newTicket = -1;

                        lock (ticketLock)
                        {
                            newTicket = GetNextAvailableTicketLocked();
                            if (newTicket != -1)
                            {
                                ticketDatabase[newTicket] = new TicketInfo
                                {
                                    steamid = dup.steamid,
                                    name = dup.name,
                                    discord = dup.discord
                                };
                            }
                        }

                        if (newTicket != -1)
                        {
                            Puts($"VALIDATE: {dup.name} ({dup.steamid}) переназначен на билет #{newTicket}");
                        }
                        else
                        {
                            Puts("VALIDATE: Билеты закончились — невозможно переназначить!");
                        }

                        fixedCount++;
                    }
                }
            }

            Interface.Oxide.DataFileSystem.WriteObject(TicketDataFile, ticketDatabase);

            arg.ReplyWith($"Проверка завершена. Исправлено дубликатов: {fixedCount}.");
            Puts($"VALIDATE: Завершено. Исправлено дубликатов: {fixedCount}.");
        }

        [ChatCommand("validatetickets")]
        private void CmdValidateTickets(BasePlayer player, string cmd, string[] args)
        {
            if (player == null) return;
            if (!player.IsAdmin)
            {
                player.ChatMessage("Команда доступна только администраторам.");
                return;
            }

            Dictionary<int, List<TicketInfo>> grouped = new Dictionary<int, List<TicketInfo>>();

            foreach (var kv in ticketDatabase)
            {
                if (!grouped.ContainsKey(kv.Key))
                    grouped[kv.Key] = new List<TicketInfo>();

                grouped[kv.Key].Add(kv.Value);
            }

            int fixedCount = 0;

            foreach (var group in grouped)
            {
                int ticketNumber = group.Key;
                List<TicketInfo> owners = group.Value;

                if (owners.Count > 1)
                {
                    Puts($"VALIDATE: Ticket #{ticketNumber} имеет {owners.Count} владельцев. Исправление...");

                    TicketInfo keeper = owners[0];
                    ticketDatabase[ticketNumber] = keeper;
                    for (int i = 1; i < owners.Count; i++)
                    {
                        TicketInfo dup = owners[i];

                        int newTicket;
                        lock (ticketLock)
                        {
                            newTicket = GetNextAvailableTicketLocked();
                            if (newTicket != -1)
                            {
                                ticketDatabase[newTicket] = new TicketInfo
                                {
                                    steamid = dup.steamid,
                                    name = dup.name,
                                    discord = dup.discord
                                };
                            }
                        }

                        if (newTicket != -1)
                        {
                            Puts($"VALIDATE: {dup.name} ({dup.steamid}) переназначен на билет #{newTicket}");
                        }
                        else
                        {
                            Puts("VALIDATE: Билеты закончились — невозможно переназначить!");
                        }

                        fixedCount++;
                    }
                }
            }
            Interface.Oxide.DataFileSystem.WriteObject(TicketDataFile, ticketDatabase);

            player.ChatMessage($"Проверка завершена. Исправлено дубликатов: {fixedCount}.");
            Puts($"VALIDATE: Завершено. Исправлено дубликатов: {fixedCount}.");
        }

        [ChatCommand("ticketinfo")]
        private void CmdTicketInfo(BasePlayer player, string cmd, string[] args)
        {
            if (player == null) return;
            if (!player.IsAdmin)
            {
                player.ChatMessage("Только админам.");
                return;
            }
            if (args.Length == 0 || !int.TryParse(args[0], out int tnum))
            {
                player.ChatMessage("Использование: /ticketinfo <номер>");
                return;
            }
            if (ticketDatabase.TryGetValue(tnum, out TicketInfo info))
            {
                player.ChatMessage($"Билет #{tnum}: SteamID {info.steamid}, Имя: {info.name}, Discord: {info.discord ?? "не задан"}");
            }
            else
            {
                player.ChatMessage($"Билет #{tnum} не найден (не выдан).");
            }
        }

        [ChatCommand("mytickets")]
        private void CmdMyTickets(BasePlayer player, string cmd, string[] args)
        {
            if (player == null) return;
            string sid = player.UserIDString;
            List<int> my = new List<int>();
            foreach (var kv in ticketDatabase)
            {
                if (kv.Value != null && kv.Value.steamid == sid)
                    my.Add(kv.Key);
            }
            if (my.Count == 0)
                player.ChatMessage("У вас ещё нет билетов.");
            else
                player.ChatMessage("Ваши билеты: " + string.Join(", ", my));
        }

        #endregion
    }
}

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