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

FREE VaultDoors : 1.0.0 1.0.0

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

namespace Oxide.Plugins
{
    [Info("VaultDoors", "da7h_9", "1.0.0")]
    [Description("Vault door with workbench craft menu, scrap research unlock, code lock, explosive-only damage and rebuild blocker")]
    public class VaultDoors : RustPlugin
    {
        #region Fields

        private const string PermUse = "vaultdoors.use";
        private const string PermGive = "vaultdoors.give";
        private const string PermCraft = "vaultdoors.craft";
        private const string CodeLockPrefab = "assets/prefabs/locks/keypad/lock.code.prefab";

        private const string UiButton = ".VaultDoorButton";
        private const string UiPanel = ".VaultDoorPanel";
        private const string UiPanelBody = ".VaultDoorPanelB";

        private Configuration config;
        private SavedData data = new SavedData();

        private Dictionary<string, int> itemIds = new Dictionary<string, int>();
        private HashSet<ulong> buttonShown = new HashSet<ulong>();
        private HashSet<ulong> pickedUp = new HashSet<ulong>();

        private struct BaseTransform
        {
            public Vector3 Pos;
            public Quaternion Rot;
        }

        private Dictionary<ulong, BaseTransform> baseTransforms = new Dictionary<ulong, BaseTransform>();

        #endregion

        #region Config

        private class Configuration
        {
            [JsonProperty("Vault door prefab path")]
            public string VaultPrefab = "assets/bundled/prefabs/modding/asset_store/bankheist_package/bankheist_vol03/prefabs/door.vault.static.prefab";

            [JsonProperty("Base item given to players (door.hinged.metal = single doorway)")]
            public string BaseItemShortname = "door.hinged.metal";

            [JsonProperty("Custom item display name")]
            public string ItemName = "Vault Door";

            [JsonProperty("Skin ID for the item in inventory")]
            public ulong SkinId = 3770358915UL;

            [JsonProperty("Vault door health (spawn and max)")]
            public float Health = 2000f;

            [JsonProperty("Limit to one vault door per Tool Cupboard")]
            public bool OnePerTC = true;

            [JsonProperty("Require building privilege (TC) to place")]
            public bool RequireTC = true;

            [JsonProperty("Automatically attach a code lock on placement")]
            public bool AutoCodeLock = false;

            [JsonProperty("Allow picking the vault door back up (hold E, no lock attached)")]
            public bool AllowPickup = true;

            [JsonProperty("Destroy the vault door when its frame is destroyed")]
            public bool KillWithFrame = true;

            [JsonProperty("Explosives deal normal damage")]
            public bool ExplosivesNormal = true;

            [JsonProperty("Non-explosive hits needed to remove 1 HP")]
            public float HitsPerHealthPoint = 80f;

            [JsonProperty("Rebuild blocker - radius in meters")]
            public float BlockRadius = 1f;

            [JsonProperty("Rebuild blocker - seconds before the spot frees up")]
            public float BlockSeconds = 300f;

            [JsonProperty("Research - scrap cost to unlock")]
            public int ResearchCost = 300;

            [JsonProperty("Research - prerequisite item shortname that must be learned first")]
            public string PrerequisiteShortname = "door.double.hinged.toptier";

            [JsonProperty("Research - friendly name of the prerequisite")]
            public string PrerequisiteName = "Armored Double Door";

            [JsonProperty("Crafting workbench level. [ 0 - workbench is not required ]")]
            public int WorkBench = 3;

            [JsonProperty("List of crafting resources")]
            public Dictionary<string, int> ItemList = new Dictionary<string, int>
            {
                ["gears"] = 10,
                ["metal.fragments"] = 200,
                ["metal.refined"] = 40,
                ["metalspring"] = 5
            };

            [JsonProperty("Alignment - forward/back offset (meters)")]
            public float OffsetForward = -0.9f;

            [JsonProperty("Alignment - left/right offset (meters)")]
            public float OffsetRight = 0.54f;

            [JsonProperty("Alignment - up/down offset (meters)")]
            public float OffsetUp = 0.222f;

            [JsonProperty("Alignment - yaw rotation offset (degrees)")]
            public float YawOffset = 0f;

            [JsonProperty("Code lock - forward offset")]
            public float LockOffsetForward = -0.35f;

            [JsonProperty("Code lock - right offset")]
            public float LockOffsetRight = 0.60f;

            [JsonProperty("Code lock - up offset")]
            public float LockOffsetUp = 0f;
        }

        protected override void LoadDefaultConfig() => config = new Configuration();

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                config = Config.ReadObject<Configuration>();
                if (config == null) throw new JsonException();
                if (config.ItemList == null || config.ItemList.Count == 0)
                    config.ItemList = new Configuration().ItemList;
                SaveConfig();
            }
            catch
            {
                PrintWarning("Config file is corrupt, loading defaults.");
                LoadDefaultConfig();
            }
        }

        protected override void SaveConfig() => Config.WriteObject(config);

        #endregion

        #region Localization

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["NoPermission"] = "You don't have permission to do that.",
                ["Given"] = "You received a <color=#ffa500>Vault Door</color>.",
                ["GivenTo"] = "Gave a Vault Door to {0}.",
                ["NoTC"] = "Vault doors can only be placed inside Tool Cupboard range. The door was refunded.",
                ["AlreadyHasVault"] = "This base already has a vault door. Only <color=#ffa500>one per TC</color> is allowed. The door was refunded.",
                ["SpawnFailed"] = "Failed to spawn the vault door. The door was refunded.",
                ["Blocked"] = "A vault door was recently destroyed here. Blocked for <color=#ffa500>{0}</color> more seconds.",
                ["PlayerNotFound"] = "Player not found: {0}",
                ["PickupHasLock"] = "Remove the lock from the vault door before picking it up.",                ["PickupNoAuth"] = "You need building privilege to pick up this vault door.",
                ["PickedUp"] = "Picked up the <color=#ffa500>Vault Door</color>.",
                ["PickupDisabled"] = "Picking up vault doors is disabled on this server.",
                ["PickupNothingNear"] = "No vault door nearby. Stand next to one and try again.",
                ["TITLE"] = "VAULT DOOR",
                ["BUTTON_CRAFT"] = "CRAFT",
                ["BUTTON_RESEARCH"] = "RESEARCH",
                ["BUTTON_OPEN"] = "VAULT DOOR",
                ["LVL_WORKBENCH"] = "WORKBENCH LVL {0}",
                ["MSG_RESOURCE"] = "INSUFFICIENT RESOURCES FOR CRAFTING!",
                ["MSG_WORKBENCH"] = "INSUFFICIENT WORKBENCH LEVEL FOR CRAFTING!",
                ["MSG_CRAFT"] = "VAULT DOOR SUCCESSFULLY CRAFTED!",
                ["MSG_LOCKED"] = "LEARN THE {0} FIRST!",
                ["MSG_NOT_RESEARCHED"] = "RESEARCH THIS BLUEPRINT FIRST!",
                ["MSG_NO_SCRAP"] = "YOU NEED {0} SCRAP TO RESEARCH THIS!",
                ["MSG_RESEARCHED"] = "VAULT DOOR BLUEPRINT UNLOCKED!",
                ["MSG_ALREADY"] = "ALREADY RESEARCHED!",
                ["LOCKED_TITLE"] = "LOCKED - LEARN {0}",
                ["RESEARCH_TITLE"] = "RESEARCH COST: {0} SCRAP"
            }, this);
        }

        private string Msg(string key, BasePlayer player = null, params object[] args)
            => string.Format(lang.GetMessage(key, this, player?.UserIDString), args);

        #endregion

        #region Hooks

        private void Init()
        {
            permission.RegisterPermission(PermUse, this);
            permission.RegisterPermission(PermGive, this);
            permission.RegisterPermission(PermCraft, this);
            LoadData();
        }

        private void OnServerInitialized()
        {
            Puts($"Using vault prefab path: \"{config.VaultPrefab}\"");

            itemIds.Clear();
            foreach (var kv in config.ItemList)
            {
                var def = ItemManager.FindItemDefinition(kv.Key);
                if (def == null)
                {
                    PrintWarning($"Unknown item shortname in craft list: {kv.Key}");
                    continue;
                }
                itemIds[kv.Key] = def.itemid;
            }

            var scrapDef = ItemManager.FindItemDefinition("scrap");
            if (scrapDef != null) itemIds["scrap"] = scrapDef.itemid;

            if (data.SecondLocks != null && data.SecondLocks.Count > 0)
            {
                foreach (var id in data.SecondLocks.Values.ToList())
                {
                    var ent = BaseNetworkable.serverEntities.Find(new NetworkableId(id)) as BaseEntity;
                    if (ent != null && !ent.IsDestroyed) ent.Kill();
                }
                data.SecondLocks.Clear();
                SaveData();
            }

            foreach (var ent in BaseNetworkable.serverEntities.ToList())
            {
                var door = ent as Door;
                if (door == null || door.IsDestroyed || !IsVaultDoor(door)) continue;
                ApplyHealth(door);
                EnablePickup(door);
            }

            PruneBlockers();
            timer.Every(60f, PruneBlockers);
            timer.Every(1f, CheckBenchProximity);
        }

        private void Unload()
        {
            foreach (var player in BasePlayer.activePlayerList)
            {
                CuiHelper.DestroyUi(player, UiButton);
                CuiHelper.DestroyUi(player, UiPanel);
            }
            buttonShown.Clear();
            SaveData();
        }

        // The crafting window is client-side and never reports to the server, so
        // there is no hook for it opening. Closest we can do: show the button when
        // the player presses USE on a bench of the right tier, hide when they leave.
        private void OnPlayerInput(BasePlayer player, InputState input)
        {
            if (player == null || input == null) return;
            if (buttonShown.Contains(player.userID)) return;
            if (!input.WasJustPressed(BUTTON.USE)) return;
            if (config.WorkBench > 0 && player.currentCraftLevel < config.WorkBench) return;

            ShowButton(player);
            buttonShown.Add(player.userID);
        }

        // Hides the button once the player walks away from the bench
        private void CheckBenchProximity()
        {
            if (buttonShown.Count == 0) return;

            foreach (var id in buttonShown.ToList())
            {
                var player = BasePlayer.FindByID(id);
                if (player == null || !player.IsConnected)
                {
                    buttonShown.Remove(id);
                    continue;
                }

                bool atBench = config.WorkBench <= 0 || player.currentCraftLevel >= config.WorkBench;
                if (atBench) continue;

                CuiHelper.DestroyUi(player, UiButton);
                CuiHelper.DestroyUi(player, UiPanel);
                buttonShown.Remove(id);
            }
        }

        private void OnPlayerDisconnected(BasePlayer player)
        {
            if (player == null) return;
            buttonShown.Remove(player.userID);
            CuiHelper.DestroyUi(player, UiButton);
            CuiHelper.DestroyUi(player, UiPanel);
        }

        private bool IsVaultDoor(BaseEntity entity)
        {
            if (entity == null || entity.IsDestroyed) return false;
            return entity.skinID == config.SkinId && entity.PrefabName.Equals(config.VaultPrefab);
        }

        private void OnEntityBuilt(Planner plan, GameObject go)
        {
            BasePlayer player = plan?.GetOwnerPlayer();
            BaseEntity entity = go?.ToBaseEntity();
            if (player == null || entity == null) return;
            if (entity.skinID != config.SkinId) return;

            NextTick(() => ReplaceWithVault(player, entity));
        }

        private void OnEntityDeath(Door door, HitInfo info) => HandleVaultRemoved(door);
        private void OnEntityKill(Door door) => HandleVaultRemoved(door);

        private void HandleVaultRemoved(Door door)
        {
            if (!IsVaultDoor(door)) return;
            ulong id = door.net?.ID.Value ?? 0;
            if (id == 0) return;

            bool wasPickedUp = pickedUp.Remove(id);

            if (config.BlockSeconds > 0f && !wasPickedUp)
            {
                data.Blockers.Add(new Blocker
                {
                    X = door.transform.position.x,
                    Y = door.transform.position.y,
                    Z = door.transform.position.z,
                    Expires = CurrentTime() + config.BlockSeconds
                });
            }

            foreach (var key in data.TcVaults.Where(kv => kv.Value == id).Select(kv => kv.Key).ToList())
                data.TcVaults.Remove(key);

            data.DoorFrames.Remove(id);
            baseTransforms.Remove(id);
            SaveData();
        }

        private void OnEntityKill(BuildingPrivlidge priv)
        {
            ulong id = priv?.net?.ID.Value ?? 0;
            if (id != 0 && data.TcVaults.Remove(id)) SaveData();
        }

        private void OnEntityKill(BuildingBlock block)
        {
            if (!config.KillWithFrame || block?.net == null) return;
            ulong frameId = block.net.ID.Value;

            foreach (var kv in data.DoorFrames.Where(kv => kv.Value == frameId).ToList())
            {
                var door = BaseNetworkable.serverEntities.Find(new NetworkableId(kv.Key)) as BaseEntity;
                if (door != null && !door.IsDestroyed) door.Kill();
                data.DoorFrames.Remove(kv.Key);
            }
            SaveData();
        }

        private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info)
        {
            if (entity == null || info == null || !IsVaultDoor(entity)) return null;

            var major = info.damageTypes.GetMajorityDamageType();
            bool explosive = major == Rust.DamageType.Explosion || major == Rust.DamageType.Heat;

            if (explosive && config.ExplosivesNormal) return null;

            float perHit = config.HitsPerHealthPoint > 0f ? 1f / config.HitsPerHealthPoint : 0f;
            info.damageTypes.ScaleAll(0f);
            info.damageTypes.Add(Rust.DamageType.Generic, perHit);
            return null;
        }

        #endregion

        #region Research

        private bool HasLearned(BasePlayer player)
            => player != null && data.Learned.Contains(player.userID);

        private bool HasPrerequisite(BasePlayer player)
        {
            if (player?.blueprints == null) return false;
            if (string.IsNullOrEmpty(config.PrerequisiteShortname)) return true;

            var def = ItemManager.FindItemDefinition(config.PrerequisiteShortname);
            if (def == null) return true;
            return player.blueprints.IsUnlocked(def);
        }

        private bool GetItemAmount(BasePlayer player, int itemId, int need)
            => player.inventory.GetAmount(itemId) >= need;

        #endregion

        #region UI

        private void ShowButton(BasePlayer player)
        {
            CuiHelper.DestroyUi(player, UiButton);

            var container = new CuiElementContainer();

            // Bottom right, above the hotbar - no cursor component so it does
            // not interfere with the workbench UI that is already open
            container.Add(new CuiButton
            {
                RectTransform =
                {
                    AnchorMin = "1 0", AnchorMax = "1 0",
                    OffsetMin = "-180 175", OffsetMax = "-15 205"
                },
                Button = { Color = "0.35 0.45 0.25 0.95", Command = "vaultdoor.ui open" },
                Text =
                {
                    Text = Msg("BUTTON_OPEN", player),
                    Align = TextAnchor.MiddleCenter,
                    FontSize = 14,
                    Font = "robotocondensed-regular.ttf",
                    Color = "0.75 0.95 0.41 1"
                }
            }, "Overlay", UiButton);

            CuiHelper.AddUi(player, container);
        }

        private void ShowPanel(BasePlayer player)
        {
            CuiHelper.DestroyUi(player, UiPanel);

            bool prereq = HasPrerequisite(player);
            bool learned = HasLearned(player);

            var container = new CuiElementContainer();

            container.Add(new CuiElement
            {
                Parent = "Overlay",
                Name = UiPanel,
                DestroyUi = UiPanel,
                Components =
                {
                    new CuiImageComponent { Color = "0.517 0.521 0.509 0.95", Material = "assets/icons/greyout.mat" },
                    new CuiRectTransformComponent { AnchorMin = "0.5 0", AnchorMax = "0.5 0", OffsetMin = "-337 90", OffsetMax = "318 268.5" },
                    new CuiNeedsCursorComponent {}
                }
            });

            container.Add(new CuiPanel
            {
                RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "5 5", OffsetMax = "-5 -5" },
                Image = { Color = "0.217 0.221 0.209 0.95" }
            }, UiPanel, UiPanelBody);

            // Title changes with state
            string title = !prereq
                ? Msg("LOCKED_TITLE", player, config.PrerequisiteName.ToUpper())
                : (!learned ? Msg("RESEARCH_TITLE", player, config.ResearchCost) : Msg("TITLE", player));

            container.Add(new CuiLabel
            {
                RectTransform = { AnchorMin = "0 1", AnchorMax = "1 1", OffsetMin = "0 -36.75", OffsetMax = "0 0" },
                Text =
                {
                    Text = title,
                    Align = TextAnchor.MiddleCenter,
                    FontSize = 18,
                    Font = "robotocondensed-regular.ttf",
                    Color = !prereq ? "1 0.4 0.4 0.9" : "1 1 1 0.75"
                }
            }, UiPanelBody);

            // Ingredient row - real item icons, red when short
            if (!prereq)
            {
                container.Add(new CuiLabel
                {
                    RectTransform = { AnchorMin = "0 0.25", AnchorMax = "1 0.75" },
                    Text =
                    {
                        Text = Msg("MSG_LOCKED", player, config.PrerequisiteName.ToUpper()),
                        Align = TextAnchor.MiddleCenter,
                        FontSize = 16,
                        Font = "robotocondensed-regular.ttf",
                        Color = "0.8 0.8 0.8 1"
                    }
                }, UiPanelBody);
            }
            else if (!learned)
            {
                DrawScrapCost(container, player);
            }
            else
            {
                DrawIngredients(container, player);
            }

            // Divider bars
            container.Add(new CuiPanel
            {
                RectTransform = { AnchorMin = "0 1", AnchorMax = "1 1", OffsetMin = "0 -41.75", OffsetMax = "0 -36.75" },
                Image = { Color = "0.517 0.521 0.509 0.95", Material = "assets/icons/greyout.mat" }
            }, UiPanelBody);

            container.Add(new CuiPanel
            {
                RectTransform = { AnchorMin = "0 0", AnchorMax = "1 0", OffsetMin = "0 36.75", OffsetMax = "0 41.75" },
                Image = { Color = "0.517 0.521 0.509 0.95", Material = "assets/icons/greyout.mat" }
            }, UiPanelBody);

            // Close button
            container.Add(new CuiButton
            {
                RectTransform = { AnchorMin = "1 0", AnchorMax = "1 0", OffsetMin = "-31.75 5", OffsetMax = "-5 31.75" },
                Button = { Color = "1 1 1 0.75", Sprite = "assets/icons/close.png", Close = UiPanel },
                Text = { Text = "" }
            }, UiPanelBody);

            container.Add(new CuiPanel
            {
                RectTransform = { AnchorMin = "1 0", AnchorMax = "1 0", OffsetMin = "-41.75 0", OffsetMax = "-36.75 41.75" },
                Image = { Color = "0.517 0.521 0.509 0.95", Material = "assets/icons/greyout.mat" }
            }, UiPanelBody);

            // Workbench badge
            if (config.WorkBench > 0)
            {
                container.Add(new CuiPanel
                {
                    RectTransform = { AnchorMin = "1 0", AnchorMax = "1 0", OffsetMin = "-296.75 5", OffsetMax = "-151.75 31.75" },
                    Image = { Color = config.WorkBench == 1 ? "0.71 0.95 0.29 0.57" : config.WorkBench == 2 ? "0.26 0.69 1 0.57" : "1 0.58 0.27 0.57" }
                }, UiPanelBody, ".VDWorkBench");

                container.Add(new CuiLabel
                {
                    RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
                    Text =
                    {
                        Text = Msg("LVL_WORKBENCH", player, config.WorkBench),
                        Align = TextAnchor.MiddleCenter,
                        FontSize = 16,
                        Font = "robotocondensed-regular.ttf",
                        Color = "0.8 0.8 0.8 1"
                    }
                }, ".VDWorkBench");

                container.Add(new CuiPanel
                {
                    RectTransform = { AnchorMin = "1 0", AnchorMax = "1 0", OffsetMin = "-306.75 0", OffsetMax = "-301.75 41.75" },
                    Image = { Color = "0.517 0.521 0.509 0.95", Material = "assets/icons/greyout.mat" }
                }, UiPanelBody);
            }

            // Action button - research or craft
            if (prereq)
            {
                container.Add(new CuiButton
                {
                    RectTransform = { AnchorMin = "1 0", AnchorMax = "1 0", OffsetMin = "-136.75 5", OffsetMax = "-46.75 31.75" },
                    Button = { Color = "0.35 0.45 0.25 1", Command = learned ? "vaultdoor.ui craft" : "vaultdoor.ui research" },
                    Text =
                    {
                        Text = learned ? Msg("BUTTON_CRAFT", player) : Msg("BUTTON_RESEARCH", player),
                        Align = TextAnchor.MiddleCenter,
                        FontSize = 16,
                        Font = "robotocondensed-regular.ttf",
                        Color = "0.75 0.95 0.41 1"
                    }
                }, UiPanelBody);

                container.Add(new CuiPanel
                {
                    RectTransform = { AnchorMin = "1 0", AnchorMax = "1 0", OffsetMin = "-146.75 0", OffsetMax = "-141.75 41.75" },
                    Image = { Color = "0.517 0.521 0.509 0.95", Material = "assets/icons/greyout.mat" }
                }, UiPanelBody);
            }

            CuiHelper.AddUi(player, container);
        }

        private void DrawIngredients(CuiElementContainer container, BasePlayer player)
        {
            int count = config.ItemList.Count;

            foreach (var item in config.ItemList)
            {
                int itemId;
                if (!itemIds.TryGetValue(item.Key, out itemId)) continue;

                double offset = -(37.5 * count--) + -(2.5 * count--);

                container.Add(new CuiPanel
                {
                    RectTransform = { AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = $"{offset} -37.5", OffsetMax = $"{offset + 75} 37.5" },
                    Image = { Color = "0.517 0.521 0.509 0.5", Material = "assets/icons/greyout.mat" }
                }, UiPanelBody, ".VDItem");

                container.Add(new CuiElement
                {
                    Parent = ".VDItem",
                    Components =
                    {
                        new CuiImageComponent { ItemId = itemId },
                        new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "5 5", OffsetMax = "-5 -5" }
                    }
                });

                container.Add(new CuiLabel
                {
                    RectTransform = { AnchorMin = "0 0", AnchorMax = "1 0.5", OffsetMax = "-2 0" },
                    Text =
                    {
                        Text = $"x{item.Value}",
                        Align = TextAnchor.LowerRight,
                        FontSize = 12,
                        Font = "robotocondensed-regular.ttf",
                        Color = GetItemAmount(player, itemId, item.Value) ? "0.3 1 0.3 1" : "1 0.3 0.3 1"
                    }
                }, ".VDItem");
            }
        }

        private void DrawScrapCost(CuiElementContainer container, BasePlayer player)
        {
            int scrapId;
            if (!itemIds.TryGetValue("scrap", out scrapId)) return;

            container.Add(new CuiPanel
            {
                RectTransform = { AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = "-37.5 -37.5", OffsetMax = "37.5 37.5" },
                Image = { Color = "0.517 0.521 0.509 0.5", Material = "assets/icons/greyout.mat" }
            }, UiPanelBody, ".VDScrap");

            container.Add(new CuiElement
            {
                Parent = ".VDScrap",
                Components =
                {
                    new CuiImageComponent { ItemId = scrapId },
                    new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "5 5", OffsetMax = "-5 -5" }
                }
            });

            container.Add(new CuiLabel
            {
                RectTransform = { AnchorMin = "0 0", AnchorMax = "1 0.5", OffsetMax = "-2 0" },
                Text =
                {
                    Text = $"x{config.ResearchCost}",
                    Align = TextAnchor.LowerRight,
                    FontSize = 12,
                    Font = "robotocondensed-regular.ttf",
                    Color = GetItemAmount(player, scrapId, config.ResearchCost) ? "0.3 1 0.3 1" : "1 0.3 0.3 1"
                }
            }, ".VDScrap");
        }

        private void SendInfo(BasePlayer player, string message)
        {
            var container = new CuiElementContainer();

            container.Add(new CuiLabel
            {
                FadeOut = 0.5f,
                RectTransform = { AnchorMin = "0 0", AnchorMax = "0 0", OffsetMax = config.WorkBench > 0 ? "337.75 36.75" : "497.75 36.75" },
                Text =
                {
                    FadeIn = 0.5f,
                    Text = message,
                    Align = TextAnchor.MiddleCenter,
                    Font = "robotocondensed-regular.ttf",
                    FontSize = 14,
                    Color = "0.75 0.75 0.75 1"
                }
            }, UiPanelBody, ".VDInfo", ".VDInfo");

            CuiHelper.AddUi(player, container);
            player.Invoke(() => CuiHelper.DestroyUi(player, ".VDInfo"), 3);
        }

        [ConsoleCommand("vaultdoor.ui")]
        private void CmdUi(ConsoleSystem.Arg arg)
        {
            BasePlayer player = arg.Player();
            if (player == null) return;

            string action = arg.HasArgs() ? arg.GetString(0).ToLower() : "close";

            switch (action)
            {
                case "open":
                    ShowPanel(player);
                    break;

                case "close":
                    CuiHelper.DestroyUi(player, UiPanel);
                    break;

                case "research":
                    DoResearch(player);
                    break;

                case "craft":
                    DoCraft(player);
                    break;
            }
        }

        private void DoResearch(BasePlayer player)
        {
            if (HasLearned(player))
            {
                SendInfo(player, Msg("MSG_ALREADY", player));
                return;
            }

            if (!HasPrerequisite(player))
            {
                SendInfo(player, Msg("MSG_LOCKED", player, config.PrerequisiteName.ToUpper()));
                return;
            }

            if (config.WorkBench > 0 && player.currentCraftLevel < config.WorkBench)
            {
                SendInfo(player, Msg("MSG_WORKBENCH", player));
                return;
            }

            int scrapId;
            if (!itemIds.TryGetValue("scrap", out scrapId)) return;

            if (player.inventory.GetAmount(scrapId) < config.ResearchCost)
            {
                SendInfo(player, Msg("MSG_NO_SCRAP", player, config.ResearchCost));
                return;
            }

            player.inventory.Take(null, scrapId, config.ResearchCost);
            data.Learned.Add(player.userID);
            SaveData();

            ShowPanel(player);
            SendInfo(player, Msg("MSG_RESEARCHED", player));
        }

        private void DoCraft(BasePlayer player)
        {
            if (!permission.UserHasPermission(player.UserIDString, PermCraft))
            {
                SendInfo(player, Msg("NoPermission", player));
                return;
            }

            if (!HasLearned(player))
            {
                SendInfo(player, Msg("MSG_NOT_RESEARCHED", player));
                return;
            }

            if (config.WorkBench > 0 && player.currentCraftLevel < config.WorkBench)
            {
                SendInfo(player, Msg("MSG_WORKBENCH", player));
                return;
            }

            foreach (var item in config.ItemList)
            {
                int id;
                if (!itemIds.TryGetValue(item.Key, out id)) continue;
                if (player.inventory.GetAmount(id) < item.Value)
                {
                    SendInfo(player, Msg("MSG_RESOURCE", player));
                    return;
                }
            }

            foreach (var item in config.ItemList)
            {
                int id;
                if (!itemIds.TryGetValue(item.Key, out id)) continue;
                player.inventory.Take(null, id, item.Value);
            }

            Item created = CreateVaultItem();
            if (created == null) return;

            player.GiveItem(created, BaseEntity.GiveItemReason.Crafted);
            ShowPanel(player);
            SendInfo(player, Msg("MSG_CRAFT", player));
        }

        #endregion

        #region Core

        private void ReplaceWithVault(BasePlayer player, BaseEntity placed)
        {
            if (placed == null || placed.IsDestroyed) return;

            Vector3 pos = placed.transform.position;
            Quaternion rot = placed.transform.rotation;

            float remaining;
            if (IsBlocked(pos, out remaining))
            {
                placed.Kill();
                Refund(player);
                player.ChatMessage(Msg("Blocked", player, Mathf.CeilToInt(remaining)));
                return;
            }

            BuildingPrivlidge priv = player.GetBuildingPrivilege(placed.WorldSpaceBounds());
            if (config.RequireTC && priv == null)
            {
                placed.Kill();
                Refund(player);
                player.ChatMessage(Msg("NoTC", player));
                return;
            }

            if (config.OnePerTC && priv != null)
            {
                ulong tcId = priv.net.ID.Value;
                ulong existingId;
                if (data.TcVaults.TryGetValue(tcId, out existingId))
                {
                    var existing = BaseNetworkable.serverEntities.Find(new NetworkableId(existingId)) as BaseEntity;
                    if (existing != null && !existing.IsDestroyed)
                    {
                        placed.Kill();
                        Refund(player);
                        player.ChatMessage(Msg("AlreadyHasVault", player));
                        return;
                    }
                    data.TcVaults.Remove(tcId);
                }
            }

            BuildingBlock frame = FindFrame(pos);

            placed.Kill();

            Vector3 basePos = pos;
            Quaternion baseRot = rot;

            Quaternion finalRot = baseRot * Quaternion.Euler(0f, config.YawOffset, 0f);
            Vector3 finalPos = basePos
                             + finalRot * Vector3.forward * config.OffsetForward
                             + finalRot * Vector3.right * config.OffsetRight
                             + Vector3.up * config.OffsetUp;

            BaseEntity created = GameManager.server.CreateEntity(config.VaultPrefab, finalPos, finalRot);
            if (created == null)
            {
                Refund(player);
                player.ChatMessage(Msg("SpawnFailed", player));
                PrintError($"CreateEntity returned null for path: \"{config.VaultPrefab}\"");
                return;
            }

            created.skinID = config.SkinId;
            created.OwnerID = player.userID;
            created.Spawn();

            Door vault = created as Door;
            if (vault != null)
            {
                ApplyHealth(vault);
                EnablePickup(vault);
                if (config.AutoCodeLock) AttachLock(vault, player);
            }

            created.SendNetworkUpdateImmediate();

            ulong doorId = created.net.ID.Value;
            baseTransforms[doorId] = new BaseTransform { Pos = basePos, Rot = baseRot };

            if (frame?.net != null)
                data.DoorFrames[doorId] = frame.net.ID.Value;

            if (priv != null)
                data.TcVaults[priv.net.ID.Value] = doorId;

            SaveData();
        }

        // Manually placed locks land at the prefab's own anchor, which is wrong for
        // this oversized door, so apply the configured offset to those as well.
        private void OnEntitySpawned(BaseNetworkable entity)
        {
            var baseLock = entity as BaseLock;
            if (baseLock == null) return;

            NextTick(() =>
            {
                if (baseLock == null || baseLock.IsDestroyed) return;

                var parent = baseLock.GetParentEntity();
                if (!IsVaultDoor(parent)) return;

                baseLock.transform.localPosition += new Vector3(
                    config.LockOffsetRight, config.LockOffsetUp, config.LockOffsetForward);
                baseLock.SendNetworkUpdateImmediate();
            });
        }

        private object OnHammerHit(BasePlayer player, HitInfo info)
        {
            if (player == null || info == null) return null;

            var door = info.HitEntity as Door;
            if (!IsVaultDoor(door)) return null;
            if (!config.AllowPickup) return null;

            TryPickup(player, door);
            return null;
        }

        private bool TryPickup(BasePlayer player, Door door)
        {
            if (door.GetSlot(BaseEntity.Slot.Lock) != null)
            {
                player.ChatMessage(Msg("PickupHasLock", player));
                return false;
            }

            if (!player.CanBuild())
            {
                player.ChatMessage(Msg("PickupNoAuth", player));
                return false;
            }

            if (door.OwnerID != player.userID && !HasTcAuth(player, door))
            {
                player.ChatMessage(Msg("PickupNoAuth", player));
                return false;
            }

            ulong id = door.net?.ID.Value ?? 0;
            if (id != 0) pickedUp.Add(id);

            Item item = CreateVaultItem();
            if (item != null)
                player.GiveItem(item, BaseEntity.GiveItemReason.PickedUp);

            player.ChatMessage(Msg("PickedUp", player));
            door.Kill();
            return true;
        }

        private bool HasTcAuth(BasePlayer player, BaseEntity door)
        {
            var priv = player.GetBuildingPrivilege();
            return priv != null && priv.IsAuthed(player);
        }

        private void EnablePickup(Door door)
        {
            if (door == null || !config.AllowPickup) return;

            door.pickup.enabled = true;
            door.pickup.requireBuildingPrivilege = true;
            door.pickup.requireHammer = false;
        }

        // We handle pickup ourselves so the player gets the skinned vault door
        // item back rather than a plain metal door, and so picking it up does
        // not leave a rebuild blocker behind.
        private object CanPickupEntity(BasePlayer player, BaseEntity entity)
        {
            if (player == null || !IsVaultDoor(entity)) return null;

            var door = entity as Door;
            if (door == null) return null;

            if (!config.AllowPickup) return false;

            TryPickup(player, door);
            return false;
        }

        private void ApplyHealth(Door door)
        {
            SetHealth(door);
            NextTick(() => SetHealth(door));
            timer.Once(0.5f, () => SetHealth(door));
            timer.Once(2f, () => SetHealth(door));
        }

        private void SetHealth(Door door)
        {
            if (door == null || door.IsDestroyed) return;
            door.InitializeHealth(config.Health, config.Health);
            door._maxHealth = config.Health;
            door.health = config.Health;
            door.SendNetworkUpdate();
        }

        private void AttachLock(Door door, BasePlayer player)
        {
            if (door.GetSlot(BaseEntity.Slot.Lock) != null) return;

            var codeLock = GameManager.server.CreateEntity(CodeLockPrefab) as CodeLock;
            if (codeLock == null) return;

            codeLock.OwnerID = player.userID;
            codeLock.SetParent(door, door.GetSlotAnchorName(BaseEntity.Slot.Lock));
            codeLock.Spawn();
            door.SetSlot(BaseEntity.Slot.Lock, codeLock);

            codeLock.transform.localPosition += new Vector3(
                config.LockOffsetRight, config.LockOffsetUp, config.LockOffsetForward);
            codeLock.SendNetworkUpdateImmediate();
        }

        private BuildingBlock FindFrame(Vector3 pos)
        {
            var list = new List<BuildingBlock>();
            Vis.Entities(pos, 2f, list, Rust.Layers.Mask.Construction);

            BuildingBlock best = null;
            float closest = float.MaxValue;
            foreach (var b in list)
            {
                if (b == null || b.IsDestroyed) continue;
                float d = Vector3.Distance(b.transform.position, pos);
                if (d < closest) { closest = d; best = b; }
            }
            return best;
        }

        private bool IsBlocked(Vector3 pos, out float secondsLeft)
        {
            secondsLeft = 0f;
            double now = CurrentTime();

            foreach (var b in data.Blockers)
            {
                if (b.Expires <= now) continue;
                if (Vector3.Distance(new Vector3(b.X, b.Y, b.Z), pos) <= config.BlockRadius)
                {
                    secondsLeft = (float)(b.Expires - now);
                    return true;
                }
            }
            return false;
        }

        private void PruneBlockers()
        {
            double now = CurrentTime();
            int before = data.Blockers.Count;
            data.Blockers.RemoveAll(b => b.Expires <= now);
            if (data.Blockers.Count != before) SaveData();
        }

        private double CurrentTime() => DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;

        private Item CreateVaultItem()
        {
            Item item = ItemManager.CreateByName(config.BaseItemShortname, 1, config.SkinId);
            if (item != null) item.name = config.ItemName;
            return item;
        }

        private void Refund(BasePlayer player)
        {
            Item item = CreateVaultItem();
            if (item != null) player.GiveItem(item, BaseEntity.GiveItemReason.PickedUp);
        }

        #endregion

        #region Commands

        [ChatCommand("vaultpickup")]
        private void CmdVaultPickup(BasePlayer player, string command, string[] args)
        {
            if (!config.AllowPickup)
            {
                player.ChatMessage(Msg("PickupDisabled", player));
                return;
            }

            var door = FindNearestVault(player, 4f) as Door;
            if (door == null)
            {
                player.ChatMessage(Msg("PickupNothingNear", player));
                return;
            }

            TryPickup(player, door);
        }

        [ConsoleCommand("vaultdoor.debug")]
        private void CmdDebug(ConsoleSystem.Arg arg)
        {
            BasePlayer player = arg.Player();
            if (player == null || !permission.UserHasPermission(player.UserIDString, PermGive))
            {
                arg.ReplyWith(Msg("NoPermission"));
                return;
            }

            var door = FindNearestVault(player, 10f);
            if (door == null)
            {
                arg.ReplyWith("No vault door within 10m.\n" +
                              $"Config skin ID: {config.SkinId}\n" +
                              $"Config prefab: {config.VaultPrefab}");
                return;
            }

            var d = door as Door;
            arg.ReplyWith(
                $"Found: {door.ShortPrefabName}\n" +
                $"Type: {door.GetType().Name}\n" +
                $"Skin: {door.skinID} (config {config.SkinId})\n" +
                $"Prefab match: {door.PrefabName.Equals(config.VaultPrefab)}\n" +
                $"Health: {(d != null ? d.health.ToString("0") : "n/a")} / {(d != null ? d.MaxHealth().ToString("0") : "n/a")}\n" +
                $"Lock attached: {(d != null && d.GetSlot(BaseEntity.Slot.Lock) != null)}\n" +
                $"Owner: {door.OwnerID} (you: {player.userID})\n" +
                $"Pickup enabled in config: {config.AllowPickup}");
        }

        [ChatCommand("vault")]
        private void CmdVaultPanel(BasePlayer player, string command, string[] args)
        {
            ShowPanel(player);
        }

        [ChatCommand("vaultdoor")]
        private void CmdVaultDoor(BasePlayer player, string command, string[] args)
        {
            if (!permission.UserHasPermission(player.UserIDString, PermUse))
            {
                ShowPanel(player);
                return;
            }

            Item item = CreateVaultItem();
            if (item == null) return;
            player.GiveItem(item, BaseEntity.GiveItemReason.PickedUp);
            player.ChatMessage(Msg("Given", player));
        }

        [ConsoleCommand("vaultdoor.unlock")]
        private void CmdUnlock(ConsoleSystem.Arg arg)
        {
            if (arg.Player() != null && !permission.UserHasPermission(arg.Player().UserIDString, PermGive))
            {
                arg.ReplyWith(Msg("NoPermission"));
                return;
            }
            if (!arg.HasArgs())
            {
                arg.ReplyWith("Usage: vaultdoor.unlock <name or steamID>");
                return;
            }

            BasePlayer target = BasePlayer.FindAwakeOrSleeping(arg.GetString(0));
            if (target == null)
            {
                arg.ReplyWith(Msg("PlayerNotFound", null, arg.GetString(0)));
                return;
            }

            data.Learned.Add(target.userID);
            SaveData();
            arg.ReplyWith($"{target.displayName} can now craft the Vault Door.");
        }

        [ConsoleCommand("vaultdoor.give")]
        private void CmdGive(ConsoleSystem.Arg arg)
        {
            if (arg.Player() != null && !permission.UserHasPermission(arg.Player().UserIDString, PermGive))
            {
                arg.ReplyWith(Msg("NoPermission"));
                return;
            }
            if (!arg.HasArgs())
            {
                arg.ReplyWith("Usage: vaultdoor.give <name or steamID>");
                return;
            }

            BasePlayer target = BasePlayer.FindAwakeOrSleeping(arg.GetString(0));
            if (target == null)
            {
                arg.ReplyWith(Msg("PlayerNotFound", null, arg.GetString(0)));
                return;
            }

            Item item = CreateVaultItem();
            if (item == null) return;
            target.GiveItem(item, BaseEntity.GiveItemReason.PickedUp);
            arg.ReplyWith(Msg("GivenTo", null, target.displayName));
        }

        [ConsoleCommand("vaultdoor.align")]
        private void CmdAlign(ConsoleSystem.Arg arg)
        {
            BasePlayer player = arg.Player();
            if (player == null || !permission.UserHasPermission(player.UserIDString, PermGive))
            {
                arg.ReplyWith(Msg("NoPermission"));
                return;
            }

            if (arg.Args == null || arg.Args.Length < 4)
            {
                arg.ReplyWith("Usage: vaultdoor.align <forward> <right> <up> <yaw>\n" +
                              $"Current: {config.OffsetForward} {config.OffsetRight} {config.OffsetUp} {config.YawOffset}");
                return;
            }

            float fwd, right, up, yaw;
            if (!float.TryParse(arg.Args[0], out fwd) || !float.TryParse(arg.Args[1], out right) ||
                !float.TryParse(arg.Args[2], out up) || !float.TryParse(arg.Args[3], out yaw))
            {
                arg.ReplyWith("All four values must be numbers.");
                return;
            }

            BaseEntity door = FindNearestVault(player);
            if (door == null)
            {
                arg.ReplyWith("No vault door found within 10m.");
                return;
            }

            BaseTransform bt;
            if (!baseTransforms.TryGetValue(door.net.ID.Value, out bt))
            {
                bt = new BaseTransform { Pos = door.transform.position, Rot = door.transform.rotation };
                baseTransforms[door.net.ID.Value] = bt;
            }

            Quaternion newRot = bt.Rot * Quaternion.Euler(0f, yaw, 0f);
            Vector3 newPos = bt.Pos
                           + newRot * Vector3.forward * fwd
                           + newRot * Vector3.right * right
                           + Vector3.up * up;

            door.transform.position = newPos;
            door.transform.rotation = newRot;
            door.SendNetworkUpdateImmediate();

            config.OffsetForward = fwd;
            config.OffsetRight = right;
            config.OffsetUp = up;
            config.YawOffset = yaw;

            arg.ReplyWith("Applied. Run vaultdoor.align.save to keep it.");
        }

        [ConsoleCommand("vaultdoor.align.save")]
        private void CmdAlignSave(ConsoleSystem.Arg arg)
        {
            if (arg.Player() != null && !permission.UserHasPermission(arg.Player().UserIDString, PermGive))
            {
                arg.ReplyWith(Msg("NoPermission"));
                return;
            }
            SaveConfig();
            arg.ReplyWith("Alignment saved.");
        }

        [ConsoleCommand("vaultdoor.lock")]
        private void CmdLockOffset(ConsoleSystem.Arg arg)
        {
            BasePlayer player = arg.Player();
            if (player == null || !permission.UserHasPermissi
// ... [Code preview truncated due to length. Full source in attached file] ...

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