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

FREE SmoothRestarter : 3.2.0 3.2.0

Нет прав для скачивания
C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
// #define UNITY_ASSERTIONS // Uncomment this if you have any issues with the plugin, assertion log can help locate problematic code
// #define SIMULATE_OXIDE_PATCH

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;

using ConVar;

using Newtonsoft.Json;

using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;

using UnityEngine;
using UnityEngine.UI;

using Debug = UnityEngine.Debug;
using Pool = Facepunch.Pool;

namespace Oxide.Plugins
{
    [Info("SmoothRestarter", "2CHEVSKII", "3.2.0")]
    [Description("A reliable way to shutdown your server when you need it")]
    public class SmoothRestarter : CovalencePlugin
    {

        #region Permission names

        const string PERMISSION_STATUS  = "smoothrestarter.status",
                     PERMISSION_RESTART = "smoothrestarter.restart",
                     PERMISSION_CANCEL  = "smoothrestarter.cancel";

        #endregion

        #region LangAPI keys

        const string M_CHAT_PREFIX                = "Chat prefix",
                     M_NO_PERMISSION              = "No permission",
                     M_KICK_REASON                = "Kick reason",
                     M_HELP                       = "Help message",
                     M_HELP_HELP                  = "Help message: Help",
                     M_HELP_STATUS                = "Help message: Status",
                     M_HELP_RESTART               = "Help message: Restart",
                     M_HELP_CANCEL                = "Help message: Cancel",
                     M_RESTARTING_ALREADY         = "Restarting already",
                     M_NOT_RESTARTING             = "Not restarting",
                     M_CANCEL_SUCCESS             = "Cancelled successfully",
                     M_RESTART_REASON_TIMED       = "Restart reason: Timed",
                     M_RESTART_REASON_OXIDE       = "Restart reason: Oxide update",
                     M_RESTART_REASON_COMMAND     = "Restart reason: Command",
                     M_RESTART_REASON_API         = "Restart reason: API call",
                     M_ANNOUNCE_RESTART_INIT      = "Announcement: Restart initiated",
                     M_ANNOUNCE_COUNTDOWN_TICK    = "Announcement: Countdown tick",
                     M_ANNOUNCE_RESTART_CANCELLED = "Announcement: Restart cancelled",
                     M_RESTART_SUCCESS            = "Restart initiated",
                     M_STATUS_RESTARTING          = "Status: Restarting",
                     M_STATUS_RESTARTING_NATIVE   = "Status: Restarting (global.restart)",
                     M_STATUS_PLANNED             = "Status: Restart planned",
                     M_STATUS_NO_PLANNED          = "Status: No planned restarts",
                     M_UI_TITLE                   = "UI title",
                     M_UI_COUNTDOWN               = "UI countdown format";

        #endregion

#if SIMULATE_OXIDE_PATCH
        static readonly VersionNumber CurrentOxideRustVersion = new VersionNumber(0, 0, 0);
#else
        static readonly VersionNumber CurrentOxideRustVersion = Interface.Oxide.GetAllExtensions().First(
                                                                             e => e.Name == "Rust"
                                                                         ).Version;
#endif
        static SmoothRestarter Instance;

        readonly FieldInfo nativeRestartRoutine = typeof(ServerMgr).GetField(
            "restartCoroutine",
            BindingFlags.Instance | BindingFlags.NonPublic
        );

        #region LangAPI EN dictionary

        readonly Dictionary<string, string> defaultMessagesEn = new Dictionary<string, string> {
            [M_CHAT_PREFIX] = "<color=#d9770f>Smooth Restarter</color>:",
            [M_NO_PERMISSION] = "<color=#f04c32>You</color> have no permission to use this command",
            [M_KICK_REASON] = "Server is restarting",
            [M_HELP] =
                "/sr <color=#1a97ba>[command]</color> <color=#1aba8f>[arguments]</color>\n" +
                "Commands: " +
                "<color=#1a97ba>help</color>, <color=#1a97ba>status</color>, <color=#1a97ba>restart</color>, <color=#1a97ba>cancel</color>\n" +
                "To get information about command usage, type '/sr <color=#1a97ba>help</color> <color=#1aba8f>[command]</color>'",
            [M_HELP_HELP] =
                "/sr <color=#1a97ba>help</color> <color=#1aba8f>[command]</color> - Outputs general help message or command usage help if command is specified",
            [M_HELP_STATUS] = "/sr <color=#1a97ba>status</color> - Outputs current restart status",
            [M_HELP_RESTART] =
                "/sr <color=#1a97ba>restart</color> <color=#1aba8f>[time]</color> - Initiates new restart process\n" +
                "Time must be in one of the following formats:\n" +
                "<color=#77ba20>123</color> - delay before restart in seconds\n" +
                "<color=#77ba20>123</color><h|m|s> - delay before restart in <hours|minutes|seconds>\n" +
                "<color=#77ba20>1</color>h <color=#77ba20>2</color>m <color=#77ba20>3</color>s - delay before restart in hr+min+sec, all optional\n" +
                "<color=#77ba20>1</color>:<color=#77ba20>23</color> - schedule restart on 1:23 (24hr format)",
            [M_HELP_CANCEL] = "/sr <color=#1a97ba>cancel</color> - Cancels current restart process",
            [M_RESTARTING_ALREADY] =
                "Cannot do restart - already restarting.Use '/sr <color=#1a97ba>status</color>' to get info about current restart process, or try '/sr <color=#1a97ba>cancel</color>' to cancel current restart before starting new one",
            [M_NOT_RESTARTING] =
                "Cannot cancel restart - plugin does not perform a restart currently.",
            [M_CANCEL_SUCCESS] = "Restart was successfully cancelled",
            [M_RESTART_REASON_TIMED] = "Planned",
            [M_RESTART_REASON_OXIDE] = "New Oxide update is out",
            [M_RESTART_REASON_COMMAND] = "Command from <color=#dbc30b>{0}</color>",
            [M_RESTART_REASON_API] = "API call from <color=#dbc30b>{0}</color>",
            [M_ANNOUNCE_RESTART_INIT] =
                "Server will be restarted in <color=#a4db0b>{0:sfmt::<hr?+ hours ><min?+ minutes ><sec?+ seconds>}</color> ({1})",
            [M_ANNOUNCE_COUNTDOWN_TICK] =
                "<color=#a4db0b>{0:sfmt::<hr?+h ><min?+min ><sec?+s>}</color> left before server restart",
            [M_ANNOUNCE_RESTART_CANCELLED] = "Server restart was cancelled",
            [M_RESTART_SUCCESS] = "Restart initiated successfully",
            [M_STATUS_RESTARTING] =
                "Server is restarting, <color=#a4db0b>{1:sfmt::<min#0.0!+ min ><sec?+ seconds>}</color> left",
            [M_STATUS_RESTARTING_NATIVE] = "Server is restarting natively",
            [M_STATUS_PLANNED] =
                "Server restart planned at <color=#a4db0b>{0:hh\\:mm}</color> (<color=#a4db0b>{1:sfmt::<hr?+ hours ><min?+ minutes ><sec?+ seconds>}</color> left)",
            [M_STATUS_NO_PLANNED] = "Server is not restarting, no planned restarts found",
            [M_UI_TITLE] = "SmoothRestarter",
            [M_UI_COUNTDOWN] = "{0:sfmt::<min#2!>:<sec#2>} left"
        };

        #endregion

        readonly Regex timeParseRegex = new Regex(
            @"^\s*(?:(\d+)|(\d+\s*[HhMmSs])|(?!$)((?:(?<h>\d+)[Hh])?\s*(?:(?<m>\d+)[Mm])?\s*(?:(?<s>\d+)[Ss])?)|(\d{1,2}:\d{1,2}))\s*$",
            RegexOptions.Compiled
        );

        SmoothRestart  component;
        PluginSettings settings;
        bool           isNewOxideOut;

        bool IsRestarting => IsRestartingNative || IsRestartingComponent;
        bool IsRestartingNative => ServerMgr.Instance.Restarting;
        bool IsRestartingComponent => component.IsRestarting;

        #region Oxide hooks

        // ReSharper disable UnusedMember.Local

        void Init()
        {
            if (!settings.EnableUi)
            {
                Unsubscribe(nameof(OnUserConnected));
                Unsubscribe(nameof(OnUserDisconnected));
            }

            permission.RegisterPermission(PERMISSION_STATUS, this);
            permission.RegisterPermission(PERMISSION_RESTART, this);
            permission.RegisterPermission(PERMISSION_CANCEL, this);

            AddCovalenceCommand(settings.Commands, "CommandHandler");
        }

        void OnServerInitialized()
        {
            component = ServerMgr.Instance.gameObject.AddComponent<SmoothRestart>();

            if (settings.EnableUi)
                foreach (var player in players.Connected)
                {
                    OnUserConnected(player);
                }
        }

        void OnUserConnected(IPlayer user)
        {
            var player = (BasePlayer) user.Object;

            player.gameObject.AddComponent<SmoothRestarterUi>();
        }

        void OnUserDisconnected(IPlayer user)
        {
            var player = (BasePlayer) user.Object;

            UnityEngine.Object.Destroy(player.GetComponent<SmoothRestarterUi>());
        }

        void Unload()
        {
            if (IsRestartingComponent)
            {
                component.CancelRestart(this);
            }

            if (settings.EnableUi)
            {
                SmoothRestarterUi.Cleanup();
            }

            UnityEngine.Object.Destroy(component);
            Instance = null;
        }

        // ReSharper restore UnusedMember.Local

        #endregion

        #region Command handler

        // ReSharper disable once UnusedMember.Local
        void CommandHandler(IPlayer player, string _, string[] args)
        {
            if (args.Length == 0)
            {
                Message(player, M_HELP);
            }
            else
            {
                var command = args[0];

                switch (command)
                {
                    case "status":
                        if (CheckPermission(player, PERMISSION_STATUS))
                        {
                            DateTime dateTime;
                            //Message(player, GetStatus(out dateTime), dateTime.TimeOfDay, (dateTime - DateTime.Now).TotalSeconds);
                            MessageWithCustomFormatter(
                                player,
                                SmoothTimeFormatter.Instance,
                                GetStatus(out dateTime),
                                dateTime.TimeOfDay,
                                (dateTime - DateTime.Now).TotalSeconds
                            );
                        }

                        break;

                    case "restart":
                        if (CheckPermission(player, PERMISSION_RESTART))
                        {
                            if (IsRestarting)
                            {
                                Message(player, M_RESTARTING_ALREADY);
                            }
                            else
                            {
                                TimeSpan time;
                                bool isTod;

                                if (args.Length == 1 ||
                                    !TryParseTime(
                                        string.Join(" ", args.Skip(1)),
                                        out time,
                                        out isTod
                                    ))
                                {
                                    Message(player, M_HELP_RESTART);

                                    return;
                                }

                                DateTime restartTime;

                                if (!isTod)
                                {
                                    restartTime = DateTime.Now + time;
                                }
                                else if (time < DateTime.Now.TimeOfDay)
                                {
                                    restartTime = DateTime.Today.AddDays(1) + time;
                                }
                                else
                                {
                                    restartTime = DateTime.Today + time;
                                }

                                component.DoRestart(restartTime, RestartReason.Command, player);
                                Message(player, M_RESTART_SUCCESS);
                            }
                        }

                        break;

                    case "cancel":
                        if (CheckPermission(player, PERMISSION_CANCEL))
                        {
                            if (IsRestartingNative)
                            {
                                CancelNativeRestart();
                            }
                            else if (IsRestartingComponent)
                            {
                                component.CancelRestart(player);
                            }
                            else
                            {
                                Message(player, M_NOT_RESTARTING);

                                return;
                            }

                            Message(player, M_CANCEL_SUCCESS);
                        }

                        break;

                    case "help":
                        Message(player, GetHelp(args.Skip(1)));

                        break;

                    default:
                        Message(player, M_HELP);

                        break;
                }
            }
        }

        string GetHelp(IEnumerable<string> args)
        {
            var arg = args.FirstOrDefault();

            if (arg != null)
            {
                switch (arg.ToLower())
                {
                    case "help":
                        return M_HELP_HELP;

                    case "status":
                        return M_HELP_STATUS;

                    case "restart":
                        return M_HELP_RESTART;

                    case "cancel":
                        return M_HELP_CANCEL;
                }
            }

            return M_HELP;
        }

        string GetStatus(out DateTime restartTime)
        {
            restartTime = default(DateTime);

            if (IsRestartingNative)
            {
                return M_STATUS_RESTARTING_NATIVE;
            }

            if (IsRestartingComponent)
            {
                restartTime = component.CurrentRestartTime.Value;

                return M_STATUS_RESTARTING;
            }

            if (settings.RestartTimes.Count > 0)
            {
                double _;
                restartTime = component.FindNextRestartTime(out _);

                return M_STATUS_PLANNED;
            }

            return M_STATUS_NO_PLANNED;
        }

        bool TryParseTime(string argString, out TimeSpan time, out bool isTod)
        {
            Match match = timeParseRegex.Match(argString);
            isTod = false;

            if (match.Groups[1].Success)
            {
                var seconds = int.Parse(match.Groups[1].Value);

                time = TimeSpan.FromSeconds(seconds);
            }
            else if (match.Groups[2].Success)
            {
                var str = match.Groups[2].Value;
                var specifier = str[str.Length - 1];
                var number = int.Parse(str.Remove(str.Length - 1).TrimEnd());

                switch (specifier)
                {
                    case 'H':
                    case 'h':
                        number *= 3600;

                        break;

                    case 'M':
                    case 'm':
                        number *= 60;

                        break;
                }

                time = TimeSpan.FromSeconds(number);
            }
            else if (match.Groups[3].Success)
            {
                var h = match.Groups["h"].Success ? int.Parse(match.Groups["h"].Value) : 0;
                var m = match.Groups["m"].Success ? int.Parse(match.Groups["m"].Value) : 0;
                var s = match.Groups["s"].Success ? int.Parse(match.Groups["s"].Value) : 0;

                time = new TimeSpan(h, m, s);
            }
            else if (TimeSpan.TryParse(match.Groups[4].Value, out time))
            {
                isTod = true;
            }
            else
            {
                time = default(TimeSpan);

                return false;
            }

            if (time.TotalSeconds == 0)
            {
                return false;
            }

            return true;
        }

        bool CheckPermission(IPlayer player, string perm)
        {
            if (player.HasPermission(perm))
            {
                return true;
            }

            Message(player, M_NO_PERMISSION);

            return false;
        }

        #endregion

        #region Utility

        void FetchLatestOxideRustVersion(Action<Exception, VersionNumber> callback)
        {
            PluginLog("Fetching latest Oxide.Rust version...");
            webrequest.Enqueue(
                "https://umod.org/games/rust.json",
                null,
                (responseCode, json) => {
                    if (responseCode != 200)
                    {
                        callback(
                            new Exception(
                                $"Failed to fetch latest Oxide.Rust version from uMod.org API - code {responseCode}"
                            ),
                            default(VersionNumber)
                        );
                    }
                    else
                    {
                        try
                        {
                            var response =
                                JsonConvert.DeserializeObject<Dictionary<string, object>>(json);

                            if (response == null)
                            {
                                throw new Exception("Response is null");
                            }

                            var latestVersionStr = response["latest_release_version"];

                            var array = ((string) latestVersionStr).Split('.');

                            var version = new VersionNumber(
                                int.Parse(array[0]),
                                int.Parse(array[1]),
                                int.Parse(array[2])
                            );

                            callback(null, version);
                        }
                        catch (Exception e)
                        {
                            callback(
                                new Exception(
                                    $"Failed to deserialize uMod.org API response: {e.Message}"
                                ),
                                default(VersionNumber)
                            );
                        }
                    }
                },
                this
            );
        }

        void PluginLog(string format, params object[] args)
        {
            if (settings.EnableLog)
            {
                Log(format, args);
            }
        }

        void KickAll()
        {
            foreach (var player in players.Connected.ToArray())
            {
                player.Kick(GetMessage(player, M_KICK_REASON));
            }
        }

        void CancelNativeRestart()
        {
            Debug.Assert(IsRestartingNative, "Cancelling native restart while !IsRestartingNative");

            var routine = (IEnumerator) nativeRestartRoutine.GetValue(ServerMgr.Instance);
            ServerMgr.Instance.StopCoroutine(routine);

            nativeRestartRoutine.SetValue(ServerMgr.Instance, null);

            ConsoleNetwork.BroadcastToAllClients(
                "chat.add",
                new object[] {
                    2,
                    0,
                    "<color=#fff>SERVER</color> Restart interrupted!"
                }
            );

            PluginLog("Native restart was cancelled");
        }

        #endregion

        #region Plugin API

        // ReSharper disable UnusedMember.Local

        [HookMethod(nameof(IsSmoothRestarting))]
        public bool IsSmoothRestarting()
        {
            return IsRestartingComponent;
        }

        [HookMethod(nameof(GetPlannedRestarts))]
        public IReadOnlyCollection<TimeSpan> GetPlannedRestarts()
        {
            return settings.RestartTimes;
        }

        [HookMethod(nameof(GetCurrentRestartTime))]
        public DateTime? GetCurrentRestartTime()
        {
            return component.CurrentRestartTime;
        }

        [HookMethod(nameof(GetCurrentRestartReason))]
        public int? GetCurrentRestartReason()
        {
            return (int?) component.CurrentRestartReason;
        }

        [HookMethod(nameof(GetCurrentRestartInitiator))]
        public object GetCurrentRestartInitiator()
        {
            return component.CurrentRestartInitiator;
        }

        [HookMethod(nameof(InitSmoothRestart))]
        public bool InitSmoothRestart(DateTime restartTime, Plugin initiator)
        {
            if (initiator == null || !initiator.IsLoaded)
            {
                return false;
            }

            if (restartTime < DateTime.Now)
            {
                return false;
            }

            if (IsRestartingComponent)
            {
                return false;
            }

            if (IsRestartingNative)
            {
                CancelNativeRestart();
            }

            component.DoRestart(restartTime, RestartReason.ApiCall, initiator);

            return true;
        }

        [HookMethod(nameof(CancelSmoothRestart))]
        public bool CancelSmoothRestart(Plugin canceller)
        {
            if (canceller == null || !canceller.IsLoaded)
            {
                return false;
            }

            if (!IsRestartingComponent)
            {
                return false;
            }

            component.CancelRestart(canceller);

            return true;
        }

        // ReSharper restore UnusedMember.Local

        #endregion

        #region LangAPI

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(defaultMessagesEn, this, "en");
        }

        string GetMessage(IPlayer player, string langKey)
        {
            return lang.GetMessage(langKey, this, player.Id);
        }

        void Message(IPlayer player, string langKey, params object[] args)
        {
            player.Message(GetMessage(player, langKey), GetMessage(player, M_CHAT_PREFIX), args);
        }

        void MessageWithCustomFormatter(
            IPlayer player,
            IFormatProvider formatProvider,
            string langKey,
            params object[] args
        )
        {
            var format = GetMessage(player, langKey);
            var message = string.Format(formatProvider, format, args);
            player.Message(message, GetMessage(player, M_CHAT_PREFIX));
        }

        void MessageRaw(IPlayer player, string message, params object[] args)
        {
            player.Message(message, GetMessage(player, M_CHAT_PREFIX), args);
        }

        void AnnounceRestartInit(float secondsLeft, RestartReason reason, object initiator = null)
        {
            foreach (IPlayer player in players.Connected)
            {
                var reasonStr = GetRestartReasonString(player, reason, initiator);

                //Message(player, M_ANNOUNCE_RESTART_INIT, secondsLeft.ToString("0"), reasonStr);
                MessageWithCustomFormatter(
                    player,
                    SmoothTimeFormatter.Instance,
                    M_ANNOUNCE_RESTART_INIT,
                    secondsLeft,
                    reasonStr
                );
            }
        }

        void Announce(string langKey, params object[] args)
        {
            foreach (IPlayer player in players.Connected)
            {
                Message(player, langKey, args);
            }
        }

        void AnnounceWithCustomFormatter(
            IFormatProvider formatProvider,
            string langKey,
            params object[] args
        )
        {
            foreach (IPlayer player in players.Connected)
            {
                MessageWithCustomFormatter(
                    player,
                    formatProvider,
                    langKey,
                    args
                );
            }
        }

        void AnnounceRaw(string message, params object[] args)
        {
            foreach (var player in players.Connected)
            {
                MessageRaw(player, message, args);
            }
        }

        string GetRestartReasonString(IPlayer player, RestartReason reason, object initiator = null)
        {
            Debug.Assert((int) reason < 2 || initiator != null);

            string reasonStr;

            switch (reason)
            {
                case RestartReason.Timed:
                    reasonStr = GetMessage(player, M_RESTART_REASON_TIMED);

                    break;

                case RestartReason.OxideUpdate:
                    reasonStr = GetMessage(player, M_RESTART_REASON_OXIDE);

                    break;

                case RestartReason.Command:
                    reasonStr = string.Format(
                        GetMessage(player, M_RESTART_REASON_COMMAND),
                        ((IPlayer) initiator).Name
                    );

                    break;

                default:
                    reasonStr = string.Format(
                        GetMessage(player, M_RESTART_REASON_API),
                        ((Plugin) initiator).Name
                    );

                    break;
            }
            
            return reasonStr;
        }

        #endregion

        #region Configuration

        protected override void LoadDefaultConfig()
        {
            settings = PluginSettings.GetDefaults();
            SaveConfig();
        }

        protected override void LoadConfig()
        {
            Instance = this;
            base.LoadConfig();

            try
            {
                settings = Config.ReadObject<PluginSettings>();

                if (settings == null)
                {
                    throw new Exception("Config is null");
                }

                if (!settings.Validate())
                {
                    LogWarning(
                        "Errors found in the configuration file, corrected values will be saved"
                    );

                    SaveConfig();
                }
            }
            catch (Exception e)
            {
                LogError("Error while loading configuration: {0}", e.Message);
                LoadDefaultConfig();
            }
        }

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

        #endregion

        #region Nested types

        enum RestartReason
        {
            Timed,
            OxideUpdate,
            Command,
            ApiCall
        }

        #region Configuration

        class PluginSettings
        {
            float[]           uiPosCached;
            HashSet<TimeSpan> restartTimesCached;

            [JsonProperty("Daily restarts")]
            public string[] DailyRestart { get; set; }
            [JsonProperty("Restart when new Oxide.Rust is out")]
            public bool OxideUpdateRestart { get; set; }
            [JsonProperty("Initiate countdown at")]
            public int RestartCountdownMax { get; set; }
            [JsonProperty("Enable UI")]
            public bool EnableUi { get; set; }
            [JsonProperty("UI position (X,Y)")]
            public string UiPosition { get; set; }
            [JsonProperty("UI scale")]
            public float UiScale { get; set; }
            [JsonProperty("Enable console logs")]
            public bool EnableLog { get; set; }
            [JsonProperty("Commands")]
            public string[] Commands { get; set; }
            [JsonProperty("Disable chat countdown notifications")]
            public bool DisableChatCountdown { get; set; }
            [JsonProperty("Custom countdown reference points")]
            public int[] CountdownRefPts { get; set; }
            [JsonProperty("Use custom countdown reference points")]
            public bool UseCustomCountdownRefPts { get; set; }
            [JsonProperty("Oxide update check interval in seconds")]
            public int OxideUpdateCheckInterval { get; set; }

            [JsonIgnore]
            public float UiX => uiPosCached[0];
            [JsonIgnore]
            public float UiY => uiPosCached[1];
            [JsonIgnore]
            public HashSet<TimeSpan> RestartTimes => restartTimesCached;

            public static PluginSettings GetDefaults()
            {
                return new PluginSettings {
                    DailyRestart = new[] {"0:00"},
                    OxideUpdateRestart = true,
                    RestartCountdownMax = 300,
                    EnableUi = true,
                    EnableLog = true,
                    UiPosition = "0.92, 0.92",
                    UiScale = 1.0f,
                    Commands = new[] {
                        "sr",
                        "srestart",
                        "smoothrestart",
                        "smoothrestarter"
                    },
                    DisableChatCountdown = false,
                    UseCustomCountdownRefPts = false,
                    CountdownRefPts = new[] {
                        60,
                        50,
                        40,
                        30,
                        25,
                        15,
                        10,
                        9,
                        8,
                        7,
                        6,
                        5,
                        4,
                        3,
                        2,
                        1
                    }
                };
            }

            #region Validation methods

            public bool Validate()
            {
                bool valid = true;

                if (DailyRestart == null)
                {
                    Instance.LogWarning("Daily restart times cannot be null");
                    DailyRestart = Array.Empty<string>();
                    valid = false;
                }

                if (!ParseRestartTimes(out restartTimesCached))
                {
                    DailyRestart = restartTimesCached.Select(rt => rt.ToString("hh\\:mm")).ToArray();

                    valid = false;
                }

                if (RestartCountdownMax < 0)
                {
                    Instance.LogWarning("Restart countdown cannot be less than zero");
                    RestartCountdownMax = 0;
                    valid = false;
                }

                if (UiScale < 0.3f)
                {
                    UiScale = 0.3f;
                    Instance.LogWarning("UI scale cannot be less than 0.3");
                    valid = false;
                }
                else if (UiScale > 3f)
                {
                    UiScale = 3f;
                    Instance.LogWarning("UI scale cannot be greater than 3");
                    valid = false;
                }

                uiPosCached = new float[2];

                if (!ParseUiPos(out uiPosCached[0], out uiPosCached[1]))
                {
                    uiPosCached[0] = 0.92f;
                    uiPosCached[1] = 0.92f;
                    UiPosition = "0.92, 0.92";
                    valid = false;
                }

                if (Commands == null || Commands.Length == 0)
                {
                    Commands = new[] {
                        "sr",
                        "srestart",
                        "smoothrestart",
                        "smoothrestarter"
                    };

                    valid = false;
                }

                if (CountdownRefPts == null)
                {
                    CountdownRefPts = new[] {
                        60,
                        50,
                        40,
                        30,
                        25,
                        15,
                        10,
                        9,
                        8,
                        7,
                        6,
                        5,
                        4,
                        3,
                        2,
                        1
                    };

                    valid = false;
                }

                if (UseCustomCountdownRefPts && CountdownRefPts.Length == 0)
                {
                    CountdownRefPts = new[] {
                        30,
                        10,
                        5
                    };

                    valid = false;
                }

                CountdownRefPts = CountdownRefPts.OrderByDescending(_ => _).ToArray();

                if (OxideUpdateCheckInterval < 600)
                {
                    Instance.LogWarning("Oxide update check interval must be greater than 10 minutes");
                    OxideUpdateCheckInterval = 600;

                    valid = false;
                }

                return valid;
            }

            bool ParseUiPos(out float fX, out float fY)
            {
                fX = fY = 0f;

                var array = UiPosition.Split(',');

                if (array.Length != 2)
                {
                    Instance.LogWarning("Invalid format of UI position: {0}", UiPosition);

                    return false;
                }

                bool valid = true;

                if (!float.TryParse(
                        array[0],
                        NumberStyles.Number,
                        CultureInfo.InvariantCulture,
                        out fX
                    ) ||
                    fX < 0f ||
                    fX > 1f)
                {
                    Instance.LogWarning(
                        "Invalid value for UI position X coordinate: {0}",
                        array[0]
                    );

                    valid = false;
                }

                if (!float.TryParse(
                        array[1],
                        NumberStyles.Number,
                        CultureInfo.InvariantCulture,
                        out fY
                    ) ||
                    fY < 0f ||
                    fY > 1f)
                {
                    Instance.LogWarning(
                        "Invalid value for UI position Y coordinate: {0}",
                        array[1]
                    );

                    valid = false;
                }

                return valid;
            }

            bool ParseRestartTimes(out HashSet<TimeSpan> restartTimes)
            {
                bool valid = true;
                restartTimes = new HashSet<TimeSpan>();

                foreach (var rt in DailyRestart)
                {
                    TimeSpan result;

                    if (!TimeSpan.TryParse(rt, out result))
                    {
                        valid = false;
                        Instance.LogWarning("Invalid time specifier: {0}", rt);
                    }

                    if (!restartTimes.Add(result))
                    {
                        valid = false;
                        Instance.LogWarning("Restart time duplication: {0}", rt);
                    }
                }

                return valid;
            }

            #endregion

        }

        #endregion

        #region SmoothRestarterUi

        class SmoothRestarterUi : MonoBehaviour
        {
            const string UI_BACKGROUND   = "smoothrestarter.ui::background",
                         UI_TITLE        = "smoothrestarter.ui::title",
                         UI_PROGRESS_BAR = "smoothrestarter.ui::progress_bar",
                         UI_SECONDS      = "smoothrestarter.ui::seconds_left";

            static HashSet<SmoothRestarterUi> AllComponents;
            static Dictionary<string, string> UiSecondsCache;
            static Dictionary<string, string> UiProgressbarCache;

            string     mainPanel;
            BasePlayer player;
            bool       isVisible;
            string     locale;

            bool IsVisible
            {
                set
                {
                    Debug.Assert(value != isVisible, "Double set isVisible to " + value);

                    SetVisible(value);
                }
            }

            public static void Cleanup()
            {
                if (AllComponents == null)
                {
                    return;
                }

                foreach (var component in AllComponents.ToArray())
                {
                    DestroyImmediate(component);
                }

                UiSecondsCache = null;
                UiProgressbarCache = null;
                AllComponents = null;
            }

            #region Unity methods

            void Awake()
            {
                if (AllComponents == null)
                {
                    AllComponents = new HashSet<SmoothRestarterUi>();
                }

                //if (mainPanel == null)
                //{

                //}

                player = GetComponent<BasePlayer>();
                locale = Instance.lang.GetLanguage(player.UserIDString);

                var uiTitle = Instance.GetMessage(player.IPlayer, M_UI_TITLE);

                var container = new CuiElementContainer {
                    new CuiElement {
                        FadeOut = 0.2f,
                        Name = UI_BACKGROUND,
                        Parent = "Hud",
                        Components = {
                            new CuiImageComponent {
                                Color = "0.2 0.2 0.2 0.3",
                                FadeIn = 0.2f,
                                ImageType = Image.Type.Simple,
                                Material = "assets/content/ui/uibackgroundblur.mat"
                            },
                            new CuiRectTransformComponent {
                                AnchorMin =
                                    $"{Instance.settings.UiX - 0.08f * Instance.settings.UiScale} " +
                                    $"{Instance.settings.UiY - 0.05f * Instance.settings.UiScale}",
                                AnchorMax =
                                    $"{Instance.settings.UiX + 0.08f * Instance.settings.UiScale} " +
                                    $"{Instance.settings.UiY + 0.05f * Instance.settings.UiScale}"
                            }
                        }
                    },
                    new CuiElement {
                        FadeOut = 0.2f,
                        Name = UI_TITLE,
                        Parent = UI_BACKGROUND,
                        Components = {
                            new CuiTextComponent {
                                Color = "0.8 0.8 0.8 0.95",
                                FadeIn = 0.2f,
                                Align = TextAnchor.MiddleCenter,
                                Font = "robotocondensed-bold.ttf",
                                FontSize = 16,
                                Text = uiTitle /*nameof(SmoothRestarter)*/
                            },
                            new CuiRectTransformComponent {
                                AnchorMin = "0 0.6",
                                AnchorMax = "1 1"
                            }
                        }
                    }
                };

                mainPanel = SerializeUi(container);

                if (UiSecondsCache == null)
                {
                    UiSecondsCache = new Dictionary<string, string>();
                }

                if (UiProgressbarCache == null)
                {
                    UiProgressbarCache = new Dictionary<string, string>();
                }

                AllComponents.Add(this);
            }

            void Start()
            {
                InvokeRepeating(nameof(UiTick), 1f, 1f);
            }

            void OnDestroy()
            {
                if (isVisible)
                {
                    IsVisible = false;
                }

                AllComponents?.Remove(this);
            }

            #endregion

            void SetVisible(bool visible)
            {
                Debug.Assert(player && player.IsConnected, "Player is null or disconnected");

                if (visible)
                {
                    CuiHelper.AddUi(player, mainPanel);
                }
                else
                {
                    CuiHelper.DestroyUi(player, UI_SECONDS);
                    CuiHelper.DestroyUi(player, UI_PROGRESS_BAR);
                    CuiHelper.DestroyUi(player, UI_TITLE);
                    CuiHelper.DestroyUi(player, UI_BACKGROUND);
                }

                isVisible = visible;
            }

            void UiTick()
            {
                if (Instance.IsRestartingComponent)
                {
                    if (!isVisible)
                    {
                        IsVisible = true;
                    }

                    var secondsLeft = (Instance.component.CurrentRestartTime.Value - DateTime.Now).TotalSeconds;

                    UpdateSeconds(secondsLeft);
                    UpdateProgressBar(GetFraction(secondsLeft));
                }
                else if (isVisible)
                {
                    IsVisible = false;
                }
            }

            #region Ui update methods

            void UpdateSeconds(double secondsLeft)
            {
                CuiHelper.DestroyUi(player, UI_SECONDS);
                CuiHelper.AddUi(
                    player,
                    LookupUiDictionary(
                        locale,
                        (int) secondsLeft,
                        UiSecondsCache,
                        CreateSeconds
                    )
                );
            }

            void UpdateProgressBar(float fraction)
            {
                Debug.Assert(
                    fraction >= 0f && fraction <= 1,
                    "Progress bar fraction is outside of 0..1 range: " + fraction
                );

                CuiHelper.DestroyUi(player, UI_PROGRESS_BAR);
                CuiHelper.AddUi(
                    player,
                    LookupUiDictionary(
                        "any",
                        fraction,
                        UiProgressbarCache,
                        CreateProgressBar
                    )
                );
            }

            string CreateSeconds(int secondsLeft)
            {
                return SerializeUi(
                    new CuiElementContainer {
                        new CuiElement {
                            FadeOut = 0.2f,
                            Name = UI_SECONDS,
                            Parent = UI_BACKGROUND,
                            Components = {
                                new CuiTextComponent {
                                    Color = "0.8 0.8 0.8 0.95",
                                    FadeIn = 0.2f,
                                    Align = TextAnchor.MiddleCenter,
                                    Font = "robotocondensed-bold.ttf",
                                    FontSize = 16,
                                    Text = string.Format(
                                        SmoothTimeFormatter.Instance,
                                        Instance.GetMessage(player.IPlayer, M_UI_COUNTDOWN),
                                        secondsLeft
                                    )
                                },
                                new CuiRectTransformComponent {
                                    AnchorMin = "0 0",
                                    AnchorMax = "1 0.45"
                                }
                            }
                        }
                    }
                );
            }

            string CreateProgressBar(float fraction)
            {
                return SerializeUi(
                    new CuiElementContainer {
                        new CuiElement {
                            FadeOut = 0.2f,
                            Name = UI_PROGRESS_BAR,
                            Parent = UI_BACKGROUND,
                            Components = {
                                new CuiImageComponent {
                                    Color = GetFractionColor(fraction),
                                    FadeIn = 0f,
                                    ImageType = Image.Type.Simple,
                                    Material = "assets/content/ui/namefontmaterial.mat"
                                },
                                new CuiRectTransformComponent {
                                    AnchorMin = $"{0.5f - fraction * 0.45f} 0.45",
                                    AnchorMax = $"{0.5f + fraction * 0.45f} 0.6"
                                }
                            }
                        }
                    }
                );
            }

            #endregion

            #region Utility

            float GetFraction(double secondsLeft)
            {
                var norm = Instance.settings.RestartCountdownMax;

                if (secondsLeft > norm)
                {
                    return 1f;
                }

                return (float) (secondsLeft / norm);
            }

            string GetFractionColor(float fraction)
            {
                Debug.Assert(
                    fraction <= 1 && fraction >= 0,
                    "GetFractionColor: fraction is not clamped"
                );

                float r;
                float g;

                if (fraction > 0.5f)
                {
                    r = Mathf.Lerp(.85f,.15f, fraction / 2);
                    g = 0.85f;
                }
                else
                {
                    r = 0.85f;
                    g = Mathf.Lerp(.15f,.85f, fraction * 2);
                }

                return $"{r} {g} 0.2 1";
            }

            static string LookupUiDictionary<T>(
                string locale,
                T value,
                Dictionary<string, string> dict,
                Func<T, string> createFunction
            )
            {
                string key = $"{locale}->{value}";
                string returnVal;

                if (!dict.TryGetValue(key, out returnVal))
                {
                    returnVal = createFunction(value);
                    dict[key] = returnVal;
                }

                return returnVal;
            }

            string SerializeUi(CuiElementContainer container)
            {
                return JsonConvert.SerializeObject(
                                      container,
                                      Formatting.None,
                                      new JsonSerializerSettings {
                                          DefaultValueHandling = DefaultValueHandling.Ignore
                                      }
                                  ).Replace("\\n", "\n");
            }

            #endregion

        }

        #endregion

        #region SmoothRestart

        clas
// ... [Code preview truncated due to length. Full source in attached file] ...

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