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

FREE Restart : 1.2.6 1.2.6

Нет прав для скачивания
C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using System;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System.Linq;
using System.Text;
using Oxide.Core;
using Oxide.Core.Libraries;
using Oxide.Core.Configuration;
using UnityEngine;
namespace Oxide.Plugins {
    [Info("Restart", "A0001", "1.2.6", ResourceId = 1826)]
    public class Restart : RustPlugin {
        bool Changed;
        JsonSerializerSettings jsonsettings;
        Dictionary<BasePlayer, Timer> timers = new Dictionary<BasePlayer, Timer>();
        Dictionary<string, string> userAgent;
        Timer _blogTimer = null;
        Timer _oxideTimer = null;
        int serverOxideVersion;
        bool initCheckDevblog;
        bool initCheckOxideBuild;
        bool simulationActive;
        bool patchRestartRunning;
        int lastMinute;
        int lastSecond;
        int currentCountDown;
        bool countDownActive;
        bool secondsActive;
        bool timerActivated;
        bool newDevblogDetected;
        bool newOxideBuildDetected;
        UIColor deathNoticeShadowColor = new UIColor(0.1, 0.1, 0.1, 0.8);
        UIColor deathNoticeColor = new UIColor(0.85, 0.85, 0.85, 0.1);
        Dictionary<string, object> rebootTimes = new Dictionary<string, object>();
        List<object> countDownMinutes = new List<object>();
        List<object> countDownSeconds = new List<object>();
        bool useTimers;
        int currentDevblog;
        int currentOxideBuild;
        int checkIntervalMinutes;
        bool enableAutoChecks;
        bool enableAutoReboot;
        int autoRebootCountDown;
        bool notifyOnlineAdmins;
        bool SimpleUI_Enable;
        int SimpleUI_FontSize;
        float SimpleUI_Top;
        float SimpleUI_Left;
        float SimpleUI_MaxWidth;
        float SimpleUI_MaxHeight;
        float SimpleUI_HideTimer;
        static Dictionary<string, object> defaultRebootTimes() {
            var dp = new Dictionary<string, object>();
            dp.Add("23:30", "30");
            dp.Add("11:15", "45");
            return dp;
        }
        static List<object> defaultCountDownMinutes() {
            var dp = new List<object>();
            dp.Add(60);
            dp.Add(45);
            dp.Add(30);
            dp.Add(15);
            dp.Add(10);
            dp.Add(5);
            dp.Add(4);
            dp.Add(3);
            dp.Add(2);
            dp.Add(1);
            return dp;
        }
        static List<object> defaultCountDownSeconds() {
            var dp = new List<object>();
            dp.Add(50);
            dp.Add(40);
            dp.Add(30);
            dp.Add(20);
            dp.Add(10);
            dp.Add(5);
            dp.Add(4);
            dp.Add(3);
            dp.Add(2);
            dp.Add(1);
            return dp;
        }
        List<Regex> regexTags = new List<Regex> {
            new Regex(@"<color=.+?>", RegexOptions.Compiled), new Regex(@"<size=.+?>", RegexOptions.Compiled)
        }
        ;
        List<string> tags = new List<string> {
            "</color>", "</size>", "<i>", "</i>", "<b>", "</b>"
        }
        ;
        object GetConfig(string menu, string datavalue, object defaultValue) {
            var data = Config[menu] as Dictionary<string, object>;
            if (data == null) {
                data = new Dictionary<string, object>();
                Config[menu] = data;
                Changed = true;
            }
            object value;
            if (!data.TryGetValue(datavalue, out value)) {
                value = defaultValue;
                data[datavalue] = value;
                Changed = true;
            }
            return value;
        }
        void LoadVariables() {
            rebootTimes = (Dictionary<string, object>)GetConfig("Таймер", "Время перезагрузки", defaultRebootTimes());
            countDownMinutes = (List<object>)GetConfig("Настройки", "Показывать минуты", defaultCountDownMinutes());
            countDownSeconds = (List<object>)GetConfig("Настройки", "Показывать секунды", defaultCountDownSeconds());
            useTimers = Convert.ToBoolean(GetConfig("Таймер", "Включить таймер", false));
            currentDevblog = Convert.ToInt32(GetConfig("Проверки", "Текущий Devblog", 0));
            currentOxideBuild = Convert.ToInt32(GetConfig("Проверки", "Текущий OxideBuild", 0));
            checkIntervalMinutes = Convert.ToInt32(GetConfig("Проверки", "Проверка интервала минут", 5));
            enableAutoChecks = Convert.ToBoolean(GetConfig("Проверки", "Включить автоматическую проверку", true));
            enableAutoReboot = Convert.ToBoolean(GetConfig("Проверки", "Включить автоматическую перезагрузку", false));
            autoRebootCountDown = Convert.ToInt32(GetConfig("Проверки", "Автоматичекое появление", 3));
            notifyOnlineAdmins = Convert.ToBoolean(GetConfig("Проверки", "Уведомлять онлайн админов", true));
            SimpleUI_Enable = Convert.ToBoolean(GetConfig("UI панель", "Включить UI панель", true));
            SimpleUI_FontSize = Convert.ToInt32(GetConfig("UI панель", "Размер текста UI панели", 30));
            SimpleUI_Top = Convert.ToSingle(GetConfig("UI панель", "Перемещение UI вверх/вниз", 0.1));
            SimpleUI_Left = Convert.ToSingle(GetConfig("UI панель", "Перемещение UI вправо/влево", 0.1));
            SimpleUI_MaxWidth = Convert.ToSingle(GetConfig("UI панель", "Макс Ширина UI панели", 0.8));
            SimpleUI_MaxHeight = Convert.ToSingle(GetConfig("UI панель", "Макс высота UI панели", 0.05));
            SimpleUI_HideTimer = Convert.ToSingle(GetConfig("UI панель", "UI таймер", 10));
            if (!Changed) return;
            SaveConfig();
            Changed = false;
        }
        protected override void LoadDefaultConfig() {
            Config.Clear();
            LoadVariables();
        }
        protected override void LoadDefaultMessages() {
            lang.RegisterMessages(new Dictionary<string, string> {
                {
                    "RestartInit", "<color=lime>Рестарт сервера через</color> <color=red>{0}</color> <color=lime>минут(ы)!</color>"
                }
                , {
                    "RestartInitPatch", "<color=red>Вышло обновление OxideBuild</color> <color=lime>рестарт через</color> <color=red>{0}</color> <color=lime>минут(ы)!</color>"
                }
                , {
                    "RestartInitSec", "<color=lime>Рестарт сервера через</color> <color=red>{0}</color> <color=lime>секунд(ы/у)!</color>"
                }
                , {
                    "RestartCancel", "<b><color=orange>Рестарт сервера остановлен!</color></b>"
                }
                , {
                    "ManualInit", "<color=orange>Отсчет таймера начнется со следующей полной минуты</color>"
                }
                , {
                    "ManualCancel", "<color=orange>Таймер был отменён</color>"
                }
                , {
                    "DevblogDetected", "<color=orange>Вышел новый DevBlog</color> <color=green>{0}</color>"
                }
                , {
                    "OxideBuildDetected", "<color=orange>Вышло обновление OxideBuild</color> <color=green>{0}</color>"
                }
                ,
            }
            , this);
        }
        void Init() {
            LoadVariables();
            LoadDefaultMessages();
            lastMinute = DateTime.Now.Minute;
            lastSecond = DateTime.Now.Second;
            currentCountDown = 0;
            jsonsettings = new JsonSerializerSettings();
            jsonsettings.Converters.Add(new KeyValuePairConverter());
            newDevblogDetected = false;
            newOxideBuildDetected = false;
            userAgent = new Dictionary<string, string>();
            userAgent.Add("User-Agent", "OxideMod");
            serverOxideVersion = Convert.ToInt32(Manager.GetPlugin("RustCore").Version.Patch);
            initCheckDevblog = true;
            initCheckOxideBuild = true;
            simulationActive = false;
            patchRestartRunning = false;
        }
        void OnServerInitialized() {
            if (!permission.PermissionExists("restart.admin")) permission.RegisterPermission("restart.admin", this);
            CheckDevBlog(4);
            CheckOxideCommits();
            if (enableAutoChecks) _blogTimer = timer.Every(checkIntervalMinutes*60, () => CheckDevBlog(4));
        }
        void OnTick() {
            if ((!secondsActive && lastMinute == DateTime.Now.Minute)) return;
            if (secondsActive && lastSecond == DateTime.Now.Second) return;
            if (secondsActive) {
                lastSecond = DateTime.Now.Second;
                if (currentCountDown == 0) {
                    if (!simulationActive) {
                        ConVar.Global.quit(null);
                        return;
                    }
                    else {
                        secondsActive = false;
                        timerActivated = false;
                        simulationActive = false;
                        enableAutoReboot = (bool)Config["Проверки", "enableAutoReboot"];
                        Puts("Patch simulation finished");
                        return;
                    }
                }
                if (countDownSeconds.Contains(currentCountDown)) DoSmoothRestart(currentCountDown, false);
                currentCountDown--;
                return;
                // breaks here on times under a minute
            }
            lastMinute = DateTime.Now.Minute;
            if (countDownActive || (!countDownActive && timerActivated && currentCountDown == 1)) {
                if (countDownMinutes.Contains(currentCountDown)) DoSmoothRestart(currentCountDown);
                currentCountDown--;
                if (currentCountDown == 0) {
                    secondsActive = true;
                    lastSecond = DateTime.Now.Second;
                    currentCountDown = 59;
                    return;
                }
                return;
            }
            if ((useTimers && !countDownActive && rebootTimes.ContainsKey(DateTime.Now.ToString("HH:mm"))) || timerActivated) {
                if (!timerActivated) currentCountDown = Convert.ToInt32(rebootTimes[DateTime.Now.ToString("HH:mm")]);
                countDownActive = true;
                DoSmoothRestart(currentCountDown);
                currentCountDown--;
            }
        }
        void CheckDevBlog(int countNews) {
            var url = $"http://api.steampowered.com/ISteamNews/GetNewsForApp/v0002/?appid=252490&count={countNews}&maxlength=1&format=json";
            try {
                webrequest.Enqueue(url, null, (code, response) => APIResponse(code, response, "devblog", countNews), this, RequestMethod.GET);
            }
            catch {
                timer.Once(60f, () => CheckDevBlog(4));
            }
        }
        void CheckOxideCommits() {
            var url = $"https://api.github.com/repos/oxidemod/oxide.rust/releases/latest";
            Dictionary<string, string> userAgent = new Dictionary<string, string>();
            userAgent.Add("User-Agent", "OxideMod");
            try {
                webrequest.Enqueue(url, null, (code, response) => APIResponse(code, response, "oxide", 0), this, RequestMethod.GET, userAgent);
            }
            catch {
                timer.Once(60f, CheckOxideCommits);
            }
        }
        void APIResponse(int code, string response, string apiType, int numberCheck) {
            if (!(response == null || code != 200) && apiType == "devblog") {
                var news = JsonConvert.DeserializeObject<AppNewsClass>(response);
                foreach (var item in news.appnews.newsitems) {
                    if (item.title.Contains("Devblog")) {
                        if (currentDevblog == 0 || initCheckDevblog) {
                            var resultBlog = Convert.ToInt32(item.title.Replace(" ", "").Replace("Devblog", ""));
                            if (resultBlog != currentDevblog) {
                                currentDevblog = resultBlog;
                                Config["Проверки", "currentDevblog"] = currentDevblog;
                                Config.Save();
                            }
                            if (initCheckDevblog) initCheckDevblog = false;
                            break;
                        }
                        var checkedDevblog = Convert.ToInt32(item.title.Replace(" ", "").Replace("Devblog", ""));
                        bool blogChanged = false;
                        if (currentDevblog != checkedDevblog) {
                            blogChanged = true;
                            currentDevblog = checkedDevblog;
                            Config["Проверки", "currentDevblog"] = currentDevblog;
                            Config.Save();
                        }
                        if (blogChanged) {
                            Puts(StripTags(string.Format(lang.GetMessage("DevblogDetected", this), currentDevblog)));
                            if (notifyOnlineAdmins) foreach (var player in BasePlayer.activePlayerList.Where(p => p.IsAdmin).ToList()) SendReply(player, string.Format(lang.GetMessage("DevblogDetected", this), currentDevblog));
                            newDevblogDetected = true;
                            if (_blogTimer == null || _blogTimer.Destroyed) return;
                            _blogTimer.Destroy();
                            _blogTimer = null;
                            _oxideTimer = timer.Every(60f, CheckOxideCommits);
                        }
                        break;
                    }
                }
            }
            else if (!(response == null || code != 200) && apiType == "oxide") {
                var jsonresponse2 = JsonConvert.DeserializeObject<Dictionary<string, object>>(response, jsonsettings);
                if (!(jsonresponse2 is Dictionary<string, object>) || jsonresponse2.Count == 0 || !jsonresponse2.ContainsKey("name")) return;
                string message = (string)jsonresponse2["name"];
                if (message == "") message = Oxide.Core.OxideMod.Version.ToString();
                var buildNum = Convert.ToInt32(message.Substring(4));
                if (currentOxideBuild == 0 || initCheckOxideBuild) {
                    if (buildNum != currentOxideBuild) {
                        currentOxideBuild = buildNum;
                        Config["Проверки", "currentOxideBuild"] = currentOxideBuild;
                        Config.Save();
                    }
                    if (initCheckOxideBuild) {
                        if (currentOxideBuild != serverOxideVersion) Puts($"Серверный оксид новой сборки '{currentOxideBuild}'");
                        else Puts($"Серверный оксид обновлён");
                    }
                    if (initCheckOxideBuild) initCheckOxideBuild = false;
                    return;
                }
                bool buildChanged = false;
                if (currentOxideBuild != buildNum) {
                    buildChanged = true;
                    currentOxideBuild = buildNum;
                    Config["Проверки", "currentOxideBuild"] = currentOxideBuild;
                    Config.Save();
                }
                if (buildChanged) {
                    Puts(StripTags(string.Format(lang.GetMessage("OxideBuildDetected", this), currentOxideBuild)));
                    if (notifyOnlineAdmins) foreach (var player in BasePlayer.activePlayerList.Where(p => p.IsAdmin).ToList()) SendReply(player, string.Format(lang.GetMessage("OxideBuildDetected", this), currentOxideBuild));
                    newOxideBuildDetected = true;
                    if (_oxideTimer == null || _oxideTimer.Destroyed) return;
                    _oxideTimer.Destroy();
                    _oxideTimer = null;
                    CheckAutoRestart();
                }
            }
        }
        void CheckAutoRestart() {
            if (!enableAutoReboot) return;
            if (!newDevblogDetected || !newOxideBuildDetected) return;
            patchRestartRunning = true;
            timerActivated = true;
            currentCountDown = autoRebootCountDown;
        }
        [ConsoleCommand("r.simulatepatch")]
        void checkOxideCommits(ConsoleSystem.Arg arg) {
            if(arg.Connection != null && arg.Connection.authLevel < 2) return;
            currentDevblog--;
            currentOxideBuild--;
            SendReply(arg, "Изменены текущие номера Devblog и OxideBuild для имитации успешных проверок");
            simulationActive = true;
            enableAutoReboot = true;
        }
        [ConsoleCommand("r.restart")]
        void smoothRestartConsoleCommand(ConsoleSystem.Arg arg) {
            if(arg.Connection != null && arg.Connection.authLevel < 2) return;
            if (arg.Args != null && arg.Args.Length == 1) {
                if (arg.Args[0].ToLower() == "stop") {
                    if (countDownActive) {
                        timerActivated = false;
                        secondsActive = false;
                        patchRestartRunning = false;
                        StopRestart();
                        return;
                    }
                    if (timerActivated) {
                        timerActivated = false;
                        secondsActive = false;
                        SendReply(arg, StripTags(lang.GetMessage("ManualCancel", this)));
                    }
                }
                else {
                    int minutes;
                    if (int.TryParse(arg.Args[0], out minutes)) {
                        currentCountDown = Convert.ToInt32(minutes);
                        lastMinute = DateTime.Now.Minute;
                        timerActivated = true;
                        SendReply(arg, StripTags(lang.GetMessage("ManualInit", this)));
                    }
                    else SendReply(arg, "Неверный формат <minutes> ! Должно быть число!");
                }
            }
            else SendReply(arg, "Доступные команды:\nr.restart <минуты> - запустить ручной рестарт\nПример: r.restart 5 (рестарт сервере через 5 минут)\nr.restart stop - отменить ручной рестарт");
        }
        [ChatCommand("restart")]
        void smoothRestartCommand(BasePlayer player, string command, string[] args) {
            if (permission.UserHasPermission(player.UserIDString, "restart.admin") || player.net.connection.authLevel > 1) {
                if (args != null && args.Length == 1) {
                    if (args[0].ToLower() == "stop") {
                        if (countDownActive) {
                            timerActivated = false;
                            secondsActive = false;
                            patchRestartRunning = false;
                            StopRestart();
                            return;
                        }
                        if (timerActivated) {
                            timerActivated = false;
                            secondsActive = false;
                            SendReply(player, lang.GetMessage("ManualCancel", this));
                        }
                    }
                    else {
                        int minutes;
                        if (int.TryParse(args[0], out minutes)) {
                            currentCountDown = Convert.ToInt32(minutes);
                            lastMinute = DateTime.Now.Minute;
                            timerActivated = true;
                            SendReply(player, lang.GetMessage("ManualInit", this));
                        }
                        else SendReply(player, "Неверный формат <minutes> ! Должно быть число!");
                    }
                }
                else SendReply(player, "<size=16><color=orange>Доступные команды:</color></size>\n<color=orange>/restart <минуты> </color> - запустить ручной рестарт\nПример: <color=orange>/restart 5</color> (рестарт сервера через 5 минут)\n<color=orange>/restart stop</color> - отменить ручной рестарт");
            }
        }
        void StopRestart() {
            if (!countDownActive) return;
            countDownActive = false;
            currentCountDown = 0;
            string msg = string.Format(lang.GetMessage("RestartCancel", this));
            Puts(StripTags(msg));
            if(SimpleUI_Enable) foreach (BasePlayer player in BasePlayer.activePlayerList) {
                UIMessage(player, msg);
            }
            else Server.Broadcast(msg);
        }
        void DoSmoothRestart(int timer, bool unitMinutes = true) {
            string msg = "";
            if (unitMinutes) {
                if (patchRestartRunning) msg = string.Format(lang.GetMessage("RestartInitPatch", this), timer);
                else msg = string.Format(lang.GetMessage("RestartInit", this), timer);
            }
            else msg = string.Format(lang.GetMessage("RestartInitSec", this), timer);
            Puts(StripTags(msg));
            if(SimpleUI_Enable) foreach (BasePlayer player in BasePlayer.activePlayerList) {
                UIMessage(player, msg);
            }
            else Server.Broadcast(msg);
        }
        class UIColor {
            string color;
            public UIColor(double red, double green, double blue, double alpha) {
                color = $"{red} {green} {blue} {alpha}";
            }
            public override string ToString() => color;
        }
        class UIObject {
            List<object> ui = new List<object>();
            List<string> objectList = new List<string>();
            public UIObject() {
            }
            string RandomString() {
                const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
                List<char> charList = chars.ToList();
                string random = "";
                for (int i = 0; i <= UnityEngine.Random.Range(5, 10);
                i++) random = random + charList[UnityEngine.Random.Range(0, charList.Count - 1)];
                return random;
            }
            public void Draw(BasePlayer player) {
                CommunityEntity.ServerInstance.ClientRPCEx(new Network.SendInfo() {
                    connection = player.net.connection
                }
                , null, "AddUI", JsonConvert.SerializeObject(ui).Replace("{NEWLINE}", Environment.NewLine));
            }
            public void Destroy(BasePlayer player) {
                foreach (string uiName in objectList) CommunityEntity.ServerInstance.ClientRPCEx(new Network.SendInfo() {
                    connection = player.net.connection
                }
                , null, "DestroyUI", uiName);
            }
            public string AddText(string name, double left, double top, double width, double height, UIColor color, string text, int textsize = 15, string parent = "Hud", int alignmode = 0, float fadeIn = 0f, float fadeOut = 0f) {
                //name = name + RandomString();
                text = text.Replace("\n", "{NEWLINE}");
                string align = "";
                switch (alignmode) {
                    case 0 : {
                        align = "LowerCenter";
                        break;
                    }
                    ;
                    case 1 : {
                        align = "LowerLeft";
                        break;
                    }
                    ;
                    case 2 : {
                        align = "LowerRight";
                        break;
                    }
                    ;
                    case 3 : {
                        align = "MiddleCenter";
                        break;
                    }
                    ;
                    case 4 : {
                        align = "MiddleLeft";
                        break;
                    }
                    ;
                    case 5 : {
                        align = "MiddleRight";
                        break;
                    }
                    ;
                    case 6 : {
                        align = "UpperCenter";
                        break;
                    }
                    ;
                    case 7 : {
                        align = "UpperLeft";
                        break;
                    }
                    ;
                    case 8 : {
                        align = "UpperRight";
                        break;
                    }
                    ;
                }
                ui.Add(new Dictionary<string, object> {
                    {
                        "name", name
                    }
                    , {
                        "parent", parent
                    }
                    , {
                        "fadeOut", fadeOut.ToString()
                    }
                    , {
                        "components", new List<object> {
                            new Dictionary<string, string> {
                                {
                                    "type", "UnityEngine.UI.Text"
                                }
                                , {
                                    "text", text
                                }
                                , {
                                    "fontSize", textsize.ToString()
                                }
                                , {
                                    "color", color.ToString()
                                }
                                , {
                                    "align", align
                                }
                                , {
                                    "fadeIn", fadeIn.ToString()
                                }
                            }
                            , new Dictionary<string, string> {
                                {
                                    "type", "RectTransform"
                                }
                                , {
                                    "anchormin", $"{left} {((1 - top) - height)}"
                                }
                                , {
                                    "anchormax", $"{(left + width)} {(1 - top)}"
                                }
                            }
                        }
                    }
                }
                );
                objectList.Add(name);
                return name;
            }
        }
        void UIMessage(BasePlayer player, string message) {
            bool replaced = false;
            float fadeIn = 0.2f;
            Timer playerTimer;
            timers.TryGetValue(player, out playerTimer);
            if (playerTimer != null && !playerTimer.Destroyed) {
                playerTimer.Destroy();
                fadeIn = 0.1f;
                replaced = true;
            }
            UIObject ui = new UIObject();
            ui.AddText("DeathNotice_DropShadow", SimpleUI_Left + 0.001, SimpleUI_Top + 0.001, SimpleUI_MaxWidth, SimpleUI_MaxHeight, deathNoticeShadowColor, StripTags(message), SimpleUI_FontSize, "Hud", 3, fadeIn, 0.2f);
            ui.AddText("DeathNotice", SimpleUI_Left, SimpleUI_Top, SimpleUI_MaxWidth, SimpleUI_MaxHeight, deathNoticeColor, message, SimpleUI_FontSize, "Hud", 3, fadeIn, 0.2f);
            ui.Destroy(player);
            if(replaced) {
                timer.Once(0.1f, () => {
                    ui.Draw(player);
                    timers[player] = timer.Once(SimpleUI_HideTimer, () => ui.Destroy(player));
                }
                );
            }
            else {
                ui.Draw(player);
                timers[player] = timer.Once(SimpleUI_HideTimer, () => ui.Destroy(player));
            }
        }
        string StripTags(string original) {
            foreach (string tag in tags) original = original.Replace(tag, "");
            foreach (Regex regexTag in regexTags) original = regexTag.Replace(original, "");
            return original;
        }
        public class AppNewsClass {
            public Appnews appnews {
                get;
                set;
            }
            public class Appnews {
                public int appid {
                    get;
                    set;
                }
                public List<Newsitem> newsitems {
                    get;
                    set;
                }
                public int count {
                    get;
                    set;
                }
                public class Newsitem {
                    public string gid {
                        get;
                        set;
                    }
                    public string title {
                        get;
                        set;
                    }
                    public string url {
                        get;
                        set;
                    }
                    public bool is_external_url {
                        get;
                        set;
                    }
                    public string author {
                        get;
                        set;
                    }
                    public string contents {
                        get;
                        set;
                    }
                    public string feedlabel {
                        get;
                        set;
                    }
                    public int date {
                        get;
                        set;
                    }
                    public string feedname {
                        get;
                        set;
                    }
                    public int feed_type {
                        get;
                        set;
                    }
                    public int appid {
                        get;
                        set;
                    }
                }
            }
        }
    }
}

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