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

FREE BradleyOptions : 2.3.4 2.3.4

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

namespace Oxide.Plugins
{
    [Info("BradleyOptions" , "Krungh Crow" , "2.3.4" , ResourceId = 157)]
    [Description("Bradley Reworked Configurations")]

    class BradleyOptions : RustPlugin
    {
        [PluginReference] Plugin ArmoredTrain, BradleyGuards, BradleyTiers, Convoy, Paratroopers;

        #region Variables
        Dictionary<string , CooldownTimer> CoolDowns = new Dictionary<string , CooldownTimer>();

        const ulong chaticon = 76561199090290915;
        const string Admin_Perm = "bradleyoptions.Admin";
        const string prefix = "<color=yellow>[Bradley Options]</color> ";
        const string MLRS = "assets/content/vehicles/mlrs/rocket_mlrs.prefab";
        const string MLRSFX = "assets/content/vehicles/mlrs/effects/pfx_mlrs_rocket_explosion_ground.prefab";
        private bool IsBradleyGuardsB;
        private bool IsBradleyGuardsK;

        private static readonly FieldInfo TooHotUntilField = FindField(typeof(HelicopterDebris) , "tooHotUntil");

        public class CooldownTimer
        {
            public Timer timer;
            public float start;
            public float countdown;
        }
        #endregion

        #region Configuration
        void Init()
        {
            if (!LoadConfigVariables())
            {
                Puts("Config file issue detected. Please delete file, or check syntax and fix.");
                return;
            }
            permission.RegisterPermission(Admin_Perm , this);
        }

        private ConfigData configData;

        class ConfigData
        {
            [JsonProperty(PropertyName = "Bonus")]
            public BonusSettings Bonus = new BonusSettings();
            [JsonProperty(PropertyName = "Spawn settings")]
            public SettingsSpawn Spawn = new SettingsSpawn();
            [JsonProperty(PropertyName = "Cooldowns")]
            public SettingsCooldown CooldownChecks = new SettingsCooldown();
            [JsonProperty(PropertyName = "Avoid BradleyOptions override settings for other plugins")]
            public SettingsPluginchecks PluginChecks = new SettingsPluginchecks();
            [JsonProperty(PropertyName = "Bradley Settings")]
            public SettingsBradley Bradley = new SettingsBradley();
            [JsonProperty(PropertyName = "Bradley Loot")]
            public SettingsBradleyLoot BradleyLoot = new SettingsBradleyLoot();
            [JsonProperty(PropertyName = "Bradley Canon")]
            public SettingsBradleyCanon BradleyCanon = new SettingsBradleyCanon();
            [JsonProperty(PropertyName = "Bradley Machinegun")]
            public SettingsBradleyMachinegun BradleyMachineGun = new SettingsBradleyMachinegun();
        }

        class BonusSettings
        {
            [JsonProperty(PropertyName = "Realistic explosion when Bradley is killed")]
            public bool RealExplo = true;
        }

        class SettingsCooldown
        {
            [JsonProperty(PropertyName = "use cooldown between Bradley Kills")]
            public bool CooldownUse = false;
            [JsonProperty(PropertyName = "Show Gametip on bradleycooldown command")]
            public bool CooldownTipUse = true;
            [JsonProperty(PropertyName = "cooldown between Bradley kills (minutes)")]
            public int Cooldown = 120;
        }

        class SettingsSpawn
        {
            [JsonProperty(PropertyName = "Enable Launchsite respawn")]
            public bool EnableLaunchsiteRespawn = false;
            [JsonProperty(PropertyName = "Launchsite min respawn time (minutes)")]
            public float LaunchsiteMinRespawn = 5.0f;
            [JsonProperty(PropertyName = "Launchsite max respawn time (minutes)")]
            public float LaunchsiteMaxRespawn = 5.0f;
            [JsonProperty(PropertyName = "Enable Custom Map respawn")]
            public bool EnableCustomRespawn = false;
            [JsonProperty(PropertyName = "Custom Map respawn check (minutes)")]
            public float CustomRespawnTimer = 15.0f;
        }

        class SettingsPluginchecks
        {
            [JsonProperty(PropertyName = "Using Only BradleyOptions")]
            public bool BOCF = true;
            [JsonProperty(PropertyName = "Using BradleyGuards/ParaTroopers (Umod)")]
            public bool BGU = false;
            [JsonProperty(PropertyName = "Using BradleyGuards (Codefling)")]
            public bool BGCF = false;
        }

        class SettingsSpawnchecksVanilla
        {
            [JsonProperty(PropertyName = "Respawn Use (Launchsite Bradley ,use only when spawn checks are disabled)")]
            public bool respawnvanillalaunch = false;
            [JsonProperty(PropertyName = "Min respawn time (minutes)")]
            public float respawntimelaunchmin = 5.0f;
            [JsonProperty(PropertyName = "Max respawn time (minutes)")]
            public float respawntimelaunchmax = 5.0f;
        }

        class SettingsBradleyLoot
        {
            [JsonProperty(PropertyName = "Max crates after kill")]
            public int BradleyMaxCratesAfterKill = 4;
            [JsonProperty(PropertyName = "Remove Napalm")]
            public bool Instantfire = false;
            [JsonProperty(PropertyName = "Remove Gibs")]
            public bool KillGibs = false;
            [JsonProperty(PropertyName = "Instantly unlock crates")]
            public bool InstantUnlock = false;
            [JsonProperty(PropertyName = "Instantly unlock Gibs")]
            public bool InstantUnlockGibs = false;
        }

        class SettingsBradley
        {
            [JsonProperty(PropertyName = "Enabled (will not spawn when false)")]
            public bool BradleyEnabled = true;
            [JsonProperty(PropertyName = "Change Health (If only using BradleyOptions)")]
            public bool BradleyHealthChange = true;
            [JsonProperty(PropertyName = "Health")]
            public int BradleyHealth = 2000;
            [JsonProperty(PropertyName = "Max Fire Range")]
            public int BradleyMaxFireRange = 100;
            [JsonProperty(PropertyName = "Throttle Responce")]
            public float BradleyThrotle = 1.0f;
            [JsonProperty(PropertyName = "Hostile timer")]
            public float BradleyMemory = 20.0f;
            [JsonProperty(PropertyName = "Ignores sleepers")]
            public bool sleepersafe = true;
            [JsonProperty(PropertyName = "Ignores admins")]
            public bool adminsafe = false;
        }

        class SettingsBradleyCanon
        {
            [JsonProperty(PropertyName = "Canon Fire delay")]
            public float BradleyCanonFireDelay = 0.3f;
            [JsonProperty(PropertyName = "Canon Firerate")]
            public float BradleyCanonFireRate = 0.25f;
        }

        class SettingsBradleyMachinegun
        {
            [JsonProperty(PropertyName = "Machinegun Bullet Damage")]
            public int BradleyMachinegunBulletDamage = 15;
            [JsonProperty(PropertyName = "Machinegun Firerate")]
            public float BradleyMachinegunFirerate = 0.06667f;
            [JsonProperty(PropertyName = "Machinegun Burst Duration")]
            public int BradleyMachinegunBurstDuration = 10;
        }

        private bool LoadConfigVariables()
        {
            try
            {
                configData = Config.ReadObject<ConfigData>();
            }
            catch
            {
                return false;
            }
            SaveConf();
            return true;
        }

        protected override void LoadDefaultConfig()
        {
            Puts("Fresh install detected Creating a new config file.");
            configData = new ConfigData();
            SaveConf();
        }

        void SaveConf() => Config.WriteObject(configData , true);
        #endregion

        #region LanguageAPI
        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string , string>
            {
                ["CooldownMsg"] = "You have <color=green>{0}</color> minute(s) cooldown left and have to wait to take on the Bradley again!!" ,
                ["CooldownTip"] = "You have {0} minute(s) left to take on the Bradley again!!" ,
                ["CooldownEnded"] = "Your Cooldown ended you can take on the Bradley again!!" ,
                ["InvalidInput"] = "<color=red>Please enter a valid command!</color>" ,
                ["Prefix"] = "<color=yellow>[Bradley Options]</color> " ,
                ["Version"] = "\nVersion : V" ,
                ["Info"] = "\n<color=green>Available Commands</color>\n<color=green>/bradley info</color> : Shows info on version/author and commands" ,
                ["InfoRespawn"] = "\n<color=green>/bradley quickrespawn</color> : Respawns the Launchsite bradley." ,
                ["InfoRespawnCustom"] = "\n<color=green>/bradley custom</color> : Respawns custom bradleys <color=purple>[RustEdit]</color>." ,
                ["RespawnAll"] = "<color=green>Respawned all Bradleys on custom locations</color> [RustEdit]" ,
                ["RespawnLaunch"] = "<color=green>Respawned The Bradley on Launchsite" ,
                ["NoPermission"] = "<color=green>You do not have permission to use that command!</color>" ,
            } , this);
        }

        #endregion

        #region Commands

        [ChatCommand("bradleycooldown")]
        private void cmdBradleycooldown(BasePlayer player , string command , string[] args)
        {
            string prefix = lang.GetMessage("Prefix" , this);
            {
                if (CoolDowns.ContainsKey(player.UserIDString))
                {
                    string timesince = Math.Floor((CoolDowns[player.UserIDString].start + CoolDowns[player.UserIDString].countdown - Time.realtimeSinceStartup) / 60).ToString();
                    if (configData.CooldownChecks.CooldownTipUse == false)
                    {
                        Player.Message(player , prefix + string.Format(msg($"CooldownMsg" , player.UserIDString) , timesince) , chaticon);
                        return;
                    }
                    else
                    {
                        TIP(player , msg($"You have {timesince} minute(s) left to take on the Bradley again!!") , 20f);
                        return;
                    }
                    return;
                }
                else
                {
                    if (configData.CooldownChecks.CooldownTipUse == false)
                    {
                        Player.Message(player , prefix + string.Format(msg("CooldownEnded" , player.UserIDString)) , chaticon);
                        return;
                    }
                    else
                    {
                        TIP(player , msg("CooldownEnded") , 20f);
                        return;
                    }

                    return;
                }
            }
        }
        [ChatCommand("bradley")]
        private void cmdBradley(BasePlayer player , string command , string[] args)
        {
            if (!permission.UserHasPermission(player.UserIDString , Admin_Perm))
            {
                Player.Message(player , prefix + string.Format(msg("NoPermission" , player.UserIDString)) , chaticon);
                return;
            }

            if (args.Length == 0)
            {
                Player.Message(player , prefix + string.Format(msg("InvalidInput" , player.UserIDString)) , chaticon);
            }
            else
            {
                if (args[0].ToLower() == "info")
                {
                    Player.Message(player , prefix + string.Format(msg("Version" , player.UserIDString)) + this.Version.ToString() + " By : " + this.Author.ToString()
                    + msg("Info")
                    + msg("InfoRespawn")
                    + msg("InfoRespawnCustom")
                    + msg("\n\n<color=green>BradleyAPC's</color> : <color=purple>") + BaseNetworkable.serverEntities.OfType<BradleyAPC>().Count().ToString() + msg("</color> ")
                    , chaticon);
                }
                if (args[0].ToLower() == "custom")
                {
                    string prefix = lang.GetMessage("Prefix" , this);
                    if (permission.UserHasPermission(player.UserIDString , Admin_Perm))
                    {
                        this.Server.Command("rustedit.apc.respawn");
                        {
                            Player.Message(player , prefix + string.Format(msg("RespawnAll" , player.UserIDString))
                            + msg(" <color=green>BradleyAPC's in Total </color> : <color=purple>") + BaseNetworkable.serverEntities.OfType<BradleyAPC>().Count().ToString() + msg("</color> ")
                            , chaticon);
                        }
                    }
                    else
                    {
                        Player.Message(player , prefix + string.Format(msg("NoPermission" , player.UserIDString)) , chaticon);
                    }
                }
                if (args[0].ToLower() == "quickrespawn")
                {
                    string prefix = lang.GetMessage("Prefix" , this);
                    if (permission.UserHasPermission(player.UserIDString , Admin_Perm))
                    {
                        BradleySpawner bradleySpawner = BradleySpawner.singleton;
                        if (bradleySpawner == null)
                        {
                            Debug.LogWarning("No Spawner");
                            return;
                        }
                        if (bradleySpawner.spawned) return;
                        {
                            Puts("A bradley spawned succesfully at launchsite!");
                        }

                        bradleySpawner.spawned = null;
                        bradleySpawner.DoRespawn();
                        Player.Message(player , prefix + string.Format(msg("RespawnLaunch" , player.UserIDString))
                        + msg(" <color=green>BradleyAPC's in Total </color> : <color=purple>") + BaseNetworkable.serverEntities.OfType<BradleyAPC>().Count().ToString() + msg("</color> ")
                        , chaticon);
                    }
                    else
                    {
                        Player.Message(player , prefix + string.Format(msg("NoPermission" , player.UserIDString)) , chaticon);
                    }
                }
            }
        }
        #endregion

        #region Oxide Hooks

        void OnServerInitialized()
        {
            IsBradleyGuardsB = BradleyGuards != null && BradleyGuards.Author.Contains("Bazz3l");
            IsBradleyGuardsK = BradleyGuards != null && BradleyGuards.Author.Contains("Krungh");

            timer.Once(3f , () =>
            {
                if (BradleyTiers)
                {
                    Puts("BradleyTiers installed skipping HP and Crate amount settings!");
                }
            });

            if (configData.Spawn.EnableLaunchsiteRespawn)
            {
                BradleySpawner.singleton.minRespawnTimeMinutes = configData.Spawn.LaunchsiteMinRespawn;
                BradleySpawner.singleton.maxRespawnTimeMinutes = configData.Spawn.LaunchsiteMaxRespawn;
            }

            if (configData.Spawn.EnableCustomRespawn)
            {
                timer.Every(configData.Spawn.CustomRespawnTimer * 60 , () =>
                {
                    this.Server.Command("rustedit.apc.respawn");
                    Puts("A bradley spawn check for custom locations (rustedit) !");
                });
            }
        }

        private void OnBradleyApcInitialize(BradleyAPC bradley)
        {
            if (bradley == null) return;
            if (Interface.CallHook("IsTrainBradley" , bradley) != null) return;
            if (Interface.CallHook("IsConvoyVehicle" , bradley) != null) return;
            if (Interface.CallHook("CanBradleyTiersEdit" , bradley) != null) return;
            if (Interface.CallHook("CanBradleyOptionsEdit" , bradley) != null) return;

            if (configData.PluginChecks.BOCF)
            {
                if (configData.Bradley.BradleyHealthChange && (!IsBradleyGuardsK || !IsBradleyGuardsB || !BradleyTiers))
                {
                    bradley._maxHealth = configData.Bradley.BradleyHealth;
                    bradley.health = bradley._maxHealth;
                }
                bradley.throttle = configData.Bradley.BradleyThrotle;
                bradley.leftThrottle = bradley.throttle;
                bradley.rightThrottle = bradley.throttle;

                if (!BradleyTiers || !IsBradleyGuardsK || !IsBradleyGuardsK || !Convoy || !ArmoredTrain)
                {
                    bradley.maxCratesToSpawn = configData.BradleyLoot.BradleyMaxCratesAfterKill;
                }

                bradley.viewDistance = configData.Bradley.BradleyMaxFireRange;
                bradley.searchRange = configData.Bradley.BradleyMaxFireRange;
                bradley.bulletDamage = configData.BradleyMachineGun.BradleyMachinegunBulletDamage;
                bradley.coaxFireRate = configData.BradleyMachineGun.BradleyMachinegunFirerate;
                bradley.coaxBurstLength = configData.BradleyMachineGun.BradleyMachinegunBurstDuration;
                bradley.topTurretFireRate = configData.BradleyCanon.BradleyCanonFireRate;
                bradley.nextTopTurretTime = configData.BradleyCanon.BradleyCanonFireDelay;
                bradley.memoryDuration = configData.Bradley.BradleyMemory;
                bradley.SendNetworkUpdate();

            }

            if (IsBradleyGuardsK && configData.PluginChecks.BGCF)
            {
                bradley.memoryDuration = configData.Bradley.BradleyMemory;
                bradley.coaxBurstLength = configData.BradleyMachineGun.BradleyMachinegunBurstDuration;
            }

            if (configData.PluginChecks.BGU && (!IsBradleyGuardsB || !Paratroopers))
            {
                bradley.throttle = configData.Bradley.BradleyThrotle;
                bradley.leftThrottle = bradley.throttle;
                bradley.rightThrottle = bradley.throttle;
                bradley.viewDistance = configData.Bradley.BradleyMaxFireRange;
                bradley.searchRange = configData.Bradley.BradleyMaxFireRange;
                bradley.bulletDamage = configData.BradleyMachineGun.BradleyMachinegunBulletDamage;
                bradley.coaxFireRate = configData.BradleyMachineGun.BradleyMachinegunFirerate;
                bradley.coaxBurstLength = configData.BradleyMachineGun.BradleyMachinegunBurstDuration;
                bradley.topTurretFireRate = configData.BradleyCanon.BradleyCanonFireRate;
                bradley.nextTopTurretTime = configData.BradleyCanon.BradleyCanonFireDelay;
                bradley.memoryDuration = configData.Bradley.BradleyMemory;
            }

            if (bradley.health <= 100)
            {
                bradley._maxHealth = configData.Bradley.BradleyHealth;
                bradley.health = bradley._maxHealth;
                bradley.SendNetworkUpdate();
                Puts($"Updated the HP to {bradley.health}/{bradley.health}");
            }
            bradley.SendNetworkUpdate();

            if (!configData.Bradley.BradleyEnabled)
            {
                Puts($"Spawning disabled in cfg destroyed the APC!");
                bradley.Kill();
                return;
            }
        }

        private object CanBradleyApcTarget(BradleyAPC bradley , BaseEntity target)
        {
            if (bradley == null || target == null)
                return null;

            BasePlayer player = target as BasePlayer;

            if (target is NPCPlayer || target is global::HumanNPC)
                return false;

            if (player == null || !player.userID.IsSteamId())
                return false;

            object result = Interface.CallHook("BradleyOptions_CanTarget" , bradley , target);
            if (result != null)
                return result;

            if (Interface.CallHook("IsTrainBradley" , bradley) != null) return null;
            if (Interface.CallHook("IsConvoyVehicle" , bradley) != null) return null;
            if (Interface.CallHook("CanBradleyTiersEdit" , bradley) != null) return null;

            Vector3 turretPos = bradley.mainTurret?.transform?.position ?? bradley.transform.position;

            if (!(player.IsVisible(turretPos , bradley.CenterPoint()) ||
                  player.IsVisible(turretPos , player.eyes.position) ||
                  player.IsVisible(turretPos , player.transform.position)))
            {
                return false;
            }

            if (configData.Bradley.sleepersafe && player.IsSleeping())
                return false;

            if (configData.Bradley.adminsafe && player.IsAdmin)
                return false;

            return null;
        }

        void OnEntityTakeDamage(BradleyAPC bradley , HitInfo info)
        {
            try
            {
                BasePlayer player = info.InitiatorPlayer;
                if (Interface.CallHook("IsTrainBradley" , bradley) != null) return;
                if (Interface.CallHook("IsConvoyVehicle" , bradley) != null) return;
                if (Interface.CallHook("CanBradleyTiersEdit" , bradley) != null) return;

                if (configData.CooldownChecks.CooldownUse == true)
                {
                    if (player != null)
                    {
                        if (CoolDowns.ContainsKey(player.UserIDString))
                        {
                            string prefix = lang.GetMessage("Prefix" , this);
                            string timesince = Math.Floor((CoolDowns[player.UserIDString].start + CoolDowns[player.UserIDString].countdown - Time.realtimeSinceStartup) / 60).ToString();
                            Player.Message(player , prefix + string.Format(msg($"CooldownMsg" , player.UserIDString) , timesince) , chaticon);
                            info.damageTypes.ScaleAll(0);
                            return;
                        }
                    }
                    return;
                }
            }
            catch { }
            return;
        }

        private void OnEntitySpawned(FireBall fireball)
        {
            List<BaseEntity> gibs = new List<BaseEntity>();
            Vis.Entities<BaseEntity>(fireball.transform.position , 1f , gibs);
            if (fireball is MolotovCocktail) return;
            foreach (var gib in gibs)
            {
                if (gib == null || gib.name == null)
                    continue;
                if (gib.name.Contains("servergibs_bradley"))
                {
                    NextTick(() =>
                    {
                        try
                        {
                            if (!fireball.IsDestroyed && configData.BradleyLoot.Instantfire)
                                fireball.Kill();
                        }
                        catch
                        {
                        }
                    });
                }
            }
        }

        private void OnEntitySpawned(HelicopterDebris debris)
        {
            if (debris.name.Contains("servergibs_bradley") && configData.BradleyLoot.InstantUnlockGibs == true)
            {
                if (debris == null || debris.name == null) return;

                NextTick(() =>
                {
                    SetDebrisCool(debris);
                    debris.SendNetworkUpdate();
                    if (configData.BradleyLoot.KillGibs)
                    {
                        debris.Kill();
                    }
                    return;
                });
                return;
            }
            return;
        }

        private void OnEntityDeath(BradleyAPC apc , HitInfo info)
        {
            if (Interface.CallHook("IsTrainBradley" , apc) != null) return;
            if (Interface.CallHook("IsConvoyVehicle" , apc) != null) return;
            if (Interface.CallHook("CanBradleyTiersEdit" , apc) != null) return;
            var pos = apc.transform.position;

            if (configData.Bonus.RealExplo)
            {
                timer.Once(0.5f , () =>
                {
                    BaseEntity RealisticExplosion = GameManager.server.CreateEntity(MLRS , pos + new Vector3(0 , 1 , 0) , new Quaternion() , true);
                    RealisticExplosion.Spawn();
                });
            }

            BasePlayer player = info.InitiatorPlayer;
            if (!player)
            {
                return;
            }

            if (!CoolDowns.ContainsKey(player.UserIDString))
            {
                string id = player.UserIDString;
                var Cooldown = new CooldownTimer();
                Cooldown.timer = timer.Once((float)configData.CooldownChecks.Cooldown * 60 , () =>
                {
                    CoolDowns.Remove(id);
                    string prefix = lang.GetMessage("Prefix" , this);
                    if (player != null) Player.Message(player , prefix + string.Format(msg("CooldownEnded" , player.UserIDString)) , chaticon);

                });
                Cooldown.start = Time.realtimeSinceStartup;
                Cooldown.countdown = configData.CooldownChecks.Cooldown * 60;

                CoolDowns.Add(id , Cooldown);
            }
            if (configData.CooldownChecks.CooldownUse == true)
            {
                string prefix = lang.GetMessage("Prefix" , this);
                Player.Message(player , prefix + string.Format(msg("You now have <color=red>" , player.UserIDString)) + configData.CooldownChecks.Cooldown + msg("</color> minutes Cooldown till you can attack again!!") , chaticon);
            }

            if (configData.BradleyLoot.InstantUnlock)
            {
                NextTick(() => UnlockTheCrates(pos));
            }
            return;
        }

        #endregion

        #region helpers

        private string msg(string key , string id = null) => lang.GetMessage(key , this , id);

        private static FieldInfo FindField(Type type , string fieldName)
        {
            while (type != null)
            {
                FieldInfo field = type.GetField(fieldName ,
                    BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);

                if (field != null)
                    return field;

                type = type.BaseType;
            }

            return null;
        }

        private void SetDebrisCool(HelicopterDebris debris)
        {
            if (debris == null || debris.IsDestroyed)
                return;

            if (TooHotUntilField == null)
            {
                PrintWarning("HelicopterDebris.tooHotUntil could not be found; instant gib cooling is unavailable on this Rust build.");
                return;
            }

            TooHotUntilField.SetValue(debris , -1f);
        }

        private void UnlockTheCrates(Vector3 pos)
        {
            List<LockedByEntCrate> crates = new List<LockedByEntCrate>();

            Vis.Entities(pos , 30f , crates);

            foreach (var crate in crates)
            {
                crate.SetLocked(false);
            }
        }

        void TIP(BasePlayer player , string message , float dur)
        {
            if (player == null) return;
            player.SendConsoleCommand("gametip.showgametip" , message);
            timer.Once(dur , () => player?.SendConsoleCommand("gametip.hidegametip"));
        }

        #endregion

        #region API

        object CanBradleyOptionsEdit(BradleyAPC bradley)
        {
            return null;
        }

        #endregion
    }
}

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