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

FREE GhostLabsCrateStack : 2.1.0 2.1.0

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

namespace Oxide.Plugins
{
    [Info("GhostLabsCrateStack", "Ghost", "2.1.0")]
    [Description("Safe, duplicate-resistant stacking for Rust small and large wooden storage boxes.")]
    public class GhostLabsCrateStack : RustPlugin
    {
        [PluginReference]
        private Plugin GhostLabsNotifications;

        private const string PluginVersion = "2.1.0";
        private const string PermUse = "ghostlabs.cratestack.use";
        private const string PermAdmin = "ghostlabs.cratestack.admin";
        private const string PermUnlimited = "ghostlabs.cratestack.unlimited";
        private const int MaxTraversalDepth = 64;

        private PluginConfig _config;

        private readonly Dictionary<ulong, float> _cooldowns = new Dictionary<ulong, float>();
        private readonly Collider[] _overlapBuffer = new Collider[256];

        private readonly Dictionary<string, CrateInfo> _crates = new Dictionary<string, CrateInfo>(StringComparer.OrdinalIgnoreCase)
        {
            ["box.wooden"] = new CrateInfo
            {
                DisplayName = "Small Wooden Box",
                ItemShortname = "box.wooden",
                Prefab = "assets/prefabs/deployable/woodenbox/woodbox_deployed.prefab",
                VerticalOffset = 0.55f,
                CheckExtents = new Vector3(0.28f, 0.14f, 0.28f)
            },

            ["box.wooden.large"] = new CrateInfo
            {
                DisplayName = "Large Wooden Box",
                ItemShortname = "box.wooden.large",
                Prefab = "assets/prefabs/deployable/large wood storage/box.wooden.large.prefab",
                VerticalOffset = 0.76f,
                CheckExtents = new Vector3(0.48f, 0.22f, 0.32f)
            }
        };

        private sealed class CrateInfo
        {
            public string DisplayName;
            public string ItemShortname;
            public string Prefab;
            public float VerticalOffset;
            public Vector3 CheckExtents;
            public int MaxStack;
        }

        private sealed class PluginConfig
        {
            public string ConfigVersion = PluginVersion;

            public bool RequirePermission = true;
            public bool RequireBuildingPrivilege = true;
            public bool RequireOwner = true;
            public bool AdminPermissionBypassesRestrictions = true;

            public bool AllowSmallWoodenBox = true;
            public bool AllowLargeWoodenBox = true;

            public int MaxSmallWoodenBoxStack = 5;
            public int MaxLargeWoodenBoxStack = 3;

            public float RaycastDistance = 4f;
            public float PlaceCooldown = 0.35f;

            public float HorizontalAlignmentTolerance = 0.18f;
            public float VerticalAlignmentTolerance = 0.12f;
            public float RotationToleranceDegrees = 12f;
            public float CollisionBoundsInset = 0.02f;

            public bool OnlyAllowTopCratePickup = true;
            public bool ProtectValidStacksFromGroundWatch = true;
            public bool RespectCanDeployItemHook = true;
            public bool ConsumeItemInCreativeMode = false;

            public bool DebugMode = false;

            public bool EnableChatMessages = true;
            public bool UseSharedNotifications = true;
            public string Prefix = "<color=#9efcff>[Ghost Labs]</color>";
        }

        #region Configuration

        protected override void LoadDefaultConfig()
        {
            _config = new PluginConfig();
            NormalizeConfig();
            SaveConfig();
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();

            try
            {
                PluginConfig loadedConfig = Config.ReadObject<PluginConfig>();

                if (loadedConfig == null)
                {
                    throw new JsonException("The configuration file was empty.");
                }

                _config = loadedConfig;
                NormalizeConfig();
                SaveConfig();
            }
            catch (Exception ex)
            {
                PrintWarning(string.Format(
                    CultureInfo.InvariantCulture,
                    "Unable to read the configuration. Defaults will be used. Reason: {0}",
                    ex.Message));

                LoadDefaultConfig();
            }
        }

        protected override void SaveConfig()
        {
            Config.WriteObject(_config, true);
        }

        private void NormalizeConfig()
        {
            if (_config == null)
            {
                _config = new PluginConfig();
            }

            _config.ConfigVersion = PluginVersion;
            _config.MaxSmallWoodenBoxStack = Mathf.Clamp(_config.MaxSmallWoodenBoxStack, 1, 20);
            _config.MaxLargeWoodenBoxStack = Mathf.Clamp(_config.MaxLargeWoodenBoxStack, 1, 20);
            _config.RaycastDistance = Mathf.Clamp(_config.RaycastDistance, 1f, 8f);
            _config.PlaceCooldown = Mathf.Clamp(_config.PlaceCooldown, 0.1f, 5f);
            _config.HorizontalAlignmentTolerance = Mathf.Clamp(_config.HorizontalAlignmentTolerance, 0.03f, 0.30f);
            _config.VerticalAlignmentTolerance = Mathf.Clamp(_config.VerticalAlignmentTolerance, 0.03f, 0.30f);
            _config.RotationToleranceDegrees = Mathf.Clamp(_config.RotationToleranceDegrees, 0f, 45f);
            _config.CollisionBoundsInset = Mathf.Clamp(_config.CollisionBoundsInset, 0f, 0.08f);

            if (string.IsNullOrWhiteSpace(_config.Prefix))
            {
                _config.Prefix = "<color=#9efcff>[Ghost Labs]</color>";
            }
        }

        private void ApplyConfigValues()
        {
            _crates["box.wooden"].MaxStack = _config.MaxSmallWoodenBoxStack;
            _crates["box.wooden.large"].MaxStack = _config.MaxLargeWoodenBoxStack;
        }

        #endregion

        #region Lifecycle

        private void Init()
        {
            permission.RegisterPermission(PermUse, this);
            permission.RegisterPermission(PermAdmin, this);
            permission.RegisterPermission(PermUnlimited, this);

            NormalizeConfig();
            ApplyConfigValues();
        }

        private void OnServerInitialized()
        {
            Puts(string.Format(
                CultureInfo.InvariantCulture,
                "GhostLabsCrateStack v{0} loaded. Small max: {1}, large max: {2}.",
                Version,
                _crates["box.wooden"].MaxStack,
                _crates["box.wooden.large"].MaxStack));
        }

        private void Unload()
        {
            _cooldowns.Clear();
        }

        private void OnPlayerDisconnected(BasePlayer player, string reason)
        {
            if (player != null)
            {
                _cooldowns.Remove(player.userID);
            }
        }

        #endregion

        #region Localization

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["NoCommandPermission"] = "You do not have permission to use this command.",
                ["CommandHelp"] = "Ghost Labs Crate Stack v{0}\n/cratestack info\n/cratestack debug\n/cratestack reload",
                ["CommandInfo"] = "Version: {0}\nSmall box maximum: {1}\nLarge box maximum: {2}\nTop-only pickup: {3}\nSafe ground support: {4}\nExternal placement hooks: {5}\nDebug mode: {6}",
                ["UnknownCommand"] = "Unknown command. Use /cratestack info, /cratestack debug, or /cratestack reload.",
                ["DebugChanged"] = "Debug mode is now {0}.",
                ["ConfigReloaded"] = "Configuration reloaded.",
                ["TypeDisabled"] = "Stacking this crate type is disabled.",
                ["NoUsePermission"] = "You do not have permission to stack crates.",
                ["NeedBuildingPrivilege"] = "You need building privilege at the new crate position.",
                ["NotOwner"] = "You can only stack crates in a stack you own.",
                ["StackLimit"] = "Stack limit reached: {0}/{1}.",
                ["StackTooLarge"] = "This crate stack is too large to validate safely.",
                ["PlacementTooFar"] = "Move closer to the top of the crate stack.",
                ["Blocked"] = "Something is blocking the next crate position: {0}.",
                ["DeploymentUnavailable"] = "The held crate could not be deployed safely. Re-equip it and try again.",
                ["CreationFailed"] = "The crate could not be placed.",
                ["Stacked"] = "Stacked {0}. Height: {1}/{2}.",
                ["StackedUnlimited"] = "Stacked {0}. Height: {1} (limit bypass).",
                ["PickupTopFirst"] = "Pick up the top crate first."
            }, this);
        }

        private string Message(string key, BasePlayer player, params object[] args)
        {
            string value = lang.GetMessage(key, this, player == null ? null : player.UserIDString);

            return args == null || args.Length == 0
                ? value
                : string.Format(CultureInfo.InvariantCulture, value, args);
        }

        #endregion

        #region Commands

        [ChatCommand("cratestack")]
        private void CmdCrateStack(BasePlayer player, string command, string[] args)
        {
            if (player == null)
            {
                return;
            }

            if (!CanUseAdminCommand(player))
            {
                Notify(player, Message("NoCommandPermission", player), "error");
                return;
            }

            if (args == null || args.Length == 0)
            {
                Notify(player, Message("CommandHelp", player, Version), "info");
                return;
            }

            string subCommand = args[0] ?? string.Empty;

            if (string.Equals(subCommand, "info", StringComparison.OrdinalIgnoreCase))
            {
                Notify(player, Message(
                    "CommandInfo",
                    player,
                    Version,
                    _crates["box.wooden"].MaxStack,
                    _crates["box.wooden.large"].MaxStack,
                    _config.OnlyAllowTopCratePickup,
                    _config.ProtectValidStacksFromGroundWatch,
                    _config.RespectCanDeployItemHook,
                    _config.DebugMode), "info");
                return;
            }

            if (string.Equals(subCommand, "debug", StringComparison.OrdinalIgnoreCase))
            {
                _config.DebugMode = !_config.DebugMode;
                SaveConfig();
                Notify(player, Message("DebugChanged", player, _config.DebugMode ? "enabled" : "disabled"), "success");
                return;
            }

            if (string.Equals(subCommand, "reload", StringComparison.OrdinalIgnoreCase))
            {
                LoadConfig();
                ApplyConfigValues();
                Notify(player, Message("ConfigReloaded", player), "success");
                return;
            }

            Notify(player, Message("UnknownCommand", player), "warning");
        }

        private bool CanUseAdminCommand(BasePlayer player)
        {
            return player != null &&
                   (player.IsAdmin || permission.UserHasPermission(player.UserIDString, PermAdmin));
        }

        private bool HasRestrictionBypass(BasePlayer player)
        {
            if (player == null)
            {
                return false;
            }

            if (player.IsAdmin)
            {
                return true;
            }

            return _config.AdminPermissionBypassesRestrictions &&
                   permission.UserHasPermission(player.UserIDString, PermAdmin);
        }

        #endregion

        #region Rust Hooks

        private object OnPlayerInput(BasePlayer player, InputState input)
        {
            if (player == null || input == null || !input.WasJustPressed(BUTTON.FIRE_PRIMARY))
            {
                return null;
            }

            return TryHandleStackInput(player) ? (object)false : null;
        }

        private bool? CanPickupEntity(BasePlayer player, BaseCombatEntity entity)
        {
            if (player == null || entity == null || !_config.OnlyAllowTopCratePickup)
            {
                return null;
            }

            CrateInfo crate;
            if (!TryGetCrateInfo(entity, out crate))
            {
                return null;
            }

            if (FindAdjacentCrate(entity, crate, 1, null) == null)
            {
                return null;
            }

            Notify(player, Message("PickupTopFirst", player), "warning");
            return false;
        }

        private object OnEntityGroundMissing(BaseEntity entity)
        {
            if (entity == null || !_config.ProtectValidStacksFromGroundWatch)
            {
                return null;
            }

            CrateInfo crate;
            if (!TryGetCrateInfo(entity, out crate))
            {
                return null;
            }

            BaseEntity support = FindAdjacentCrate(entity, crate, -1, null);
            if (support == null)
            {
                return null;
            }

            if (_config.DebugMode)
            {
                Puts(string.Format(
                    CultureInfo.InvariantCulture,
                    "[Debug] Preserved stacked crate {0} because valid support {1} is directly below it.",
                    GetNetworkId(entity),
                    GetNetworkId(support)));
            }

            return true;
        }

        #endregion

        #region Placement

        private bool TryHandleStackInput(BasePlayer player)
        {
            if (player == null || player.IsDead() || player.IsSleeping())
            {
                return false;
            }

            Item heldItem = player.GetActiveItem();
            if (heldItem == null || heldItem.info == null || heldItem.amount <= 0)
            {
                return false;
            }

            CrateInfo crate;
            if (!_crates.TryGetValue(heldItem.info.shortname, out crate))
            {
                return false;
            }

            BaseEntity target = GetLookEntity(player);
            if (target == null || !IsSameCrate(target, crate))
            {
                return false;
            }

            if (IsOnCooldown(player))
            {
                return true;
            }

            if (!IsCrateTypeEnabled(crate))
            {
                RejectPlacement(player, "TypeDisabled", "warning");
                return true;
            }

            bool bypassRestrictions = HasRestrictionBypass(player);

            if (_config.RequirePermission &&
                !bypassRestrictions &&
                !permission.UserHasPermission(player.UserIDString, PermUse))
            {
                RejectPlacement(player, "NoUsePermission", "error");
                return true;
            }

            bool traversalLimitReached;
            List<BaseEntity> stack = GetConnectedStack(target, crate, out traversalLimitReached);

            if (traversalLimitReached || stack.Count == 0)
            {
                RejectPlacement(player, "StackTooLarge", "warning");
                return true;
            }

            if (_config.RequireOwner && !bypassRestrictions && !PlayerOwnsStack(player, stack))
            {
                RejectPlacement(player, "NotOwner", "warning");
                return true;
            }

            bool unlimited = bypassRestrictions || permission.UserHasPermission(player.UserIDString, PermUnlimited);
            int height = stack.Count;

            if (!unlimited && height >= crate.MaxStack)
            {
                SetCooldown(player);
                Notify(player, Message("StackLimit", player, height, crate.MaxStack), "warning");
                return true;
            }

            BaseEntity top = stack[stack.Count - 1];
            Vector3 position = top.transform.position + Vector3.up * crate.VerticalOffset;
            Quaternion rotation = top.transform.rotation;

            if (!IsPlacementWithinReach(player, position, crate))
            {
                RejectPlacement(player, "PlacementTooFar", "warning");
                return true;
            }

            Bounds placementBounds = GetPlacementBounds(crate);

            if (_config.RequireBuildingPrivilege &&
                !bypassRestrictions &&
                player.IsBuildingBlocked(position, rotation, placementBounds))
            {
                RejectPlacement(player, "NeedBuildingPrivilege", "warning");
                return true;
            }

            Deployer deployer;
            ItemModDeployable modDeployable;

            if (!TryGetDeploymentLifecycle(player, heldItem, out deployer, out modDeployable))
            {
                RejectPlacement(player, "DeploymentUnavailable", "error");
                return true;
            }

            if (_config.RespectCanDeployItemHook &&
                deployer != null &&
                IsBlockedByCanDeployItem(player, deployer, target))
            {
                SetCooldown(player);

                if (_config.DebugMode)
                {
                    Puts(string.Format(
                        CultureInfo.InvariantCulture,
                        "[Debug] An external CanDeployItem hook blocked {0} for {1} ({2}).",
                        crate.ItemShortname,
                        player.displayName,
                        player.userID));
                }

                return true;
            }

            BaseEntity blocker;
            string blockerName;

            if (IsBlocked(position, rotation, crate, top, out blocker, out blockerName))
            {
                SetCooldown(player);
                Notify(player, Message("Blocked", player, blockerName), "warning");

                if (_config.DebugMode)
                {
                    DebugBlocker(player, blocker, blockerName, position);
                }

                return true;
            }

            if (!TryDeployCrate(player, heldItem, deployer, modDeployable, crate, top, position, rotation, height + 1))
            {
                RejectPlacement(player, "CreationFailed", "error");
                return true;
            }

            SetCooldown(player);

            Notify(
                player,
                unlimited
                    ? Message("StackedUnlimited", player, crate.DisplayName, height + 1)
                    : Message("Stacked", player, crate.DisplayName, height + 1, crate.MaxStack),
                "success");

            return true;
        }

        private bool IsCrateTypeEnabled(CrateInfo crate)
        {
            if (crate == null)
            {
                return false;
            }

            if (string.Equals(crate.ItemShortname, "box.wooden", StringComparison.OrdinalIgnoreCase))
            {
                return _config.AllowSmallWoodenBox;
            }

            if (string.Equals(crate.ItemShortname, "box.wooden.large", StringComparison.OrdinalIgnoreCase))
            {
                return _config.AllowLargeWoodenBox;
            }

            return false;
        }

        private bool PlayerOwnsStack(BasePlayer player, List<BaseEntity> stack)
        {
            foreach (BaseEntity existing in stack)
            {
                if (existing == null || existing.IsDestroyed)
                {
                    continue;
                }

                if (existing.OwnerID != 0UL && existing.OwnerID != player.userID)
                {
                    return false;
                }
            }

            return true;
        }

        private bool IsPlacementWithinReach(BasePlayer player, Vector3 position, CrateInfo crate)
        {
            if (player == null || player.eyes == null || crate == null)
            {
                return false;
            }

            float maximumDistance = _config.RaycastDistance + crate.VerticalOffset + 0.15f;
            return Vector3.Distance(player.eyes.position, position) <= maximumDistance;
        }

        private Bounds GetPlacementBounds(CrateInfo crate)
        {
            Vector3 size = crate.CheckExtents * 2f;
            return new Bounds(Vector3.zero, size);
        }

        private bool TryGetDeploymentLifecycle(
            BasePlayer player,
            Item heldItem,
            out Deployer deployer,
            out ItemModDeployable modDeployable)
        {
            deployer = player == null ? null : player.GetHeldEntity() as Deployer;
            modDeployable = heldItem == null || heldItem.info == null
                ? null
                : heldItem.info.GetComponent<ItemModDeployable>();

            if (modDeployable == null)
            {
                return false;
            }

            if (deployer == null && _config.DebugMode && player != null)
            {
                Puts(string.Format(
                    CultureInfo.InvariantCulture,
                    "[Debug] No Deployer instance was exposed for {0} ({1}); native deploy metadata is valid, so stacking will continue without Deployer-only hooks.",
                    player.displayName,
                    player.userID));
            }

            return true;
        }

        private bool IsBlockedByCanDeployItem(BasePlayer player, Deployer deployer, BaseEntity target)
        {
            if (player == null || deployer == null || target == null || target.net == null)
            {
                return true;
            }

            object hookResult = Interface.CallHook("CanDeployItem", player, deployer, target.net.ID);
            return hookResult != null;
        }

        private bool TryDeployCrate(
            BasePlayer player,
            Item heldItem,
            Deployer deployer,
            ItemModDeployable modDeployable,
            CrateInfo crate,
            BaseEntity support,
            Vector3 position,
            Quaternion rotation,
            int newHeight)
        {
            BaseEntity placedEntity = GameManager.server.CreateEntity(crate.Prefab, position, rotation, true);

            if (placedEntity == null)
            {
                return false;
            }

            try
            {
                placedEntity.skinID = heldItem.skin;
                placedEntity.SendMessage("SetDeployedBy", player, SendMessageOptions.DontRequireReceiver);
                placedEntity.OwnerID = player.userID;
                placedEntity.Spawn();

                if (support != null && support.HasParent())
                {
                    placedEntity.SetParent(support.GetParentEntity(), true, true);
                }

                modDeployable.OnDeployed(placedEntity, player);
                if (deployer != null)
                {
                    Interface.CallHook("OnItemDeployed", deployer, modDeployable, placedEntity);
                }

                if (_config.ConsumeItemInCreativeMode || !player.IsInCreativeMode || !ConVar.Creative.freeBuild)
                {
                    heldItem.UseItem(1);
                }

                Interface.CallHook("OnGhostLabsCrateStacked", player, placedEntity, support, newHeight);
                return true;
            }
            catch (Exception ex)
            {
                PrintError(string.Format(
                    CultureInfo.InvariantCulture,
                    "Failed to deploy {0} for {1} ({2}): {3}",
                    crate.ItemShortname,
                    player.displayName,
                    player.userID,
                    ex));

                if (placedEntity != null && !placedEntity.IsDestroyed)
                {
                    placedEntity.Kill();
                }
                return false;
            }
        }

        private void RejectPlacement(BasePlayer player, string messageKey, string notificationType)
        {
            SetCooldown(player);
            Notify(player, Message(messageKey, player), notificationType);
        }

        #endregion

        #region Connected Stack Detection

        private List<BaseEntity> GetConnectedStack(BaseEntity anchor, CrateInfo crate, out bool traversalLimitReached)
        {
            List<BaseEntity> lower = new List<BaseEntity>();
            List<BaseEntity> upper = new List<BaseEntity>();
            HashSet<BaseEntity> visited = new HashSet<BaseEntity>();
            traversalLimitReached = false;

            if (anchor == null || crate == null)
            {
                return upper;
            }

            visited.Add(anchor);

            BaseEntity cursor = anchor;
            for (int i = 0; i < MaxTraversalDepth; i++)
            {
                BaseEntity next = FindAdjacentCrate(cursor, crate, -1, visited);
                if (next == null)
                {
                    break;
                }

                lower.Add(next);
                visited.Add(next);
                cursor = next;

                if (i == MaxTraversalDepth - 1)
                {
                    traversalLimitReached = true;
                }
            }

            lower.Reverse();
            upper.AddRange(lower);
            upper.Add(anchor);

            cursor = anchor;
            for (int i = 0; i < MaxTraversalDepth; i++)
            {
                BaseEntity next = FindAdjacentCrate(cursor, crate, 1, visited);
                if (next == null)
                {
                    break;
                }

                upper.Add(next);
                visited.Add(next);
                cursor = next;

                if (i == MaxTraversalDepth - 1)
                {
                    traversalLimitReached = true;
                }
            }

            return upper;
        }

        private BaseEntity FindAdjacentCrate(
            BaseEntity origin,
            CrateInfo crate,
            int direction,
            HashSet<BaseEntity> excluded)
        {
            if (origin == null || origin.IsDestroyed || crate == null || direction == 0)
            {
                return null;
            }

            direction = direction > 0 ? 1 : -1;

            Vector3 expectedPosition = origin.transform.position + Vector3.up * crate.VerticalOffset * direction;
            float searchRadius = Mathf.Sqrt(
                _config.HorizontalAlignmentTolerance * _config.HorizontalAlignmentTolerance +
                _config.VerticalAlignmentTolerance * _config.VerticalAlignmentTolerance) + 0.05f;

            int hitCount = Physics.OverlapSphereNonAlloc(
                expectedPosition,
                searchRadius,
                _overlapBuffer,
                Physics.AllLayers,
                QueryTriggerInteraction.Ignore);

            if (hitCount >= _overlapBuffer.Length)
            {
                ClearOverlapBuffer(hitCount);
                return null;
            }

            BaseEntity best = null;
            float bestScore = float.MaxValue;

            for (int index = 0; index < hitCount; index++)
            {
                Collider collider = _overlapBuffer[index];
                if (collider == null)
                {
                    continue;
                }

                BaseEntity candidate = collider.GetComponentInParent<BaseEntity>();

                if (candidate == null || candidate.IsDestroyed || candidate == origin)
                {
                    continue;
                }

                if (excluded != null && excluded.Contains(candidate))
                {
                    continue;
                }

                if (!IsSameCrate(candidate, crate))
                {
                    continue;
                }

                Vector3 delta = candidate.transform.position - origin.transform.position;
                float signedVerticalDistance = delta.y * direction;

                if (signedVerticalDistance <= 0f)
                {
                    continue;
                }

                float verticalError = Mathf.Abs(signedVerticalDistance - crate.VerticalOffset);
                if (verticalError > _config.VerticalAlignmentTolerance)
                {
                    continue;
                }

                float horizontalDistance = Vector2.Distance(
                    new Vector2(origin.transform.position.x, origin.transform.position.z),
                    new Vector2(candidate.transform.position.x, candidate.transform.position.z));

                if (horizontalDistance > _config.HorizontalAlignmentTolerance)
                {
                    continue;
                }

                if (Quaternion.Angle(origin.transform.rotation, candidate.transform.rotation) >
                    _config.RotationToleranceDegrees)
                {
                    continue;
                }

                float score = horizontalDistance * horizontalDistance + verticalError * verticalError;
                if (score >= bestScore)
                {
                    continue;
                }

                best = candidate;
                bestScore = score;
            }

            ClearOverlapBuffer(hitCount);
            return best;
        }

        #endregion

        #region Collision and Entity Helpers

        private bool IsBlocked(
            Vector3 position,
            Quaternion rotation,
            CrateInfo crate,
            BaseEntity support,
            out BaseEntity blocker,
            out string blockerName)
        {
            blocker = null;
            blockerName = "object";

            Vector3 extents = crate.CheckExtents;
            extents.x = Mathf.Max(0.05f, extents.x - _config.CollisionBoundsInset);
            extents.y = Mathf.Max(0.05f, extents.y - _config.CollisionBoundsInset);
            extents.z = Mathf.Max(0.05f, extents.z - _config.CollisionBoundsInset);

            int hitCount = Physics.OverlapBoxNonAlloc(
                position,
                extents,
                _overlapBuffer,
                rotation,
                Physics.AllLayers,
                QueryTriggerInteraction.Ignore);

            if (hitCount >= _overlapBuffer.Length)
            {
                blockerName = "crowded area";
                ClearOverlapBuffer(hitCount);
                return true;
            }

            for (int index = 0; index < hitCount; index++)
            {
                Collider collider = _overlapBuffer[index];
                if (collider == null)
                {
                    continue;
                }

                BaseEntity entity = collider.GetComponentInParent<BaseEntity>();

                if (entity == support)
                {
                    continue;
                }

                if (entity != null && entity.IsDestroyed)
                {
                    continue;
                }

                blocker = entity;
                blockerName = entity == null
                    ? GetColliderName(collider)
                    : GetEntityName(entity);
                ClearOverlapBuffer(hitCount);
                return true;
            }

            ClearOverlapBuffer(hitCount);
            return false;
        }

        private void ClearOverlapBuffer(int count)
        {
            int maximum = Mathf.Min(count, _overlapBuffer.Length);
            for (int index = 0; index < maximum; index++)
                _overlapBuffer[index] = null;
        }

        private BaseEntity GetLookEntity(BasePlayer player)
        {
            if (player == null || player.eyes == null)
            {
                return null;
            }

            RaycastHit hit;

            if (!Physics.Raycast(
                    player.eyes.HeadRay(),
                    out hit,
                    _config.RaycastDistance,
                    Physics.AllLayers,
                    QueryTriggerInteraction.Ignore))
            {
                return null;
            }

            return hit.GetEntity();
        }

        private bool TryGetCrateInfo(BaseEntity entity, out CrateInfo crateInfo)
        {
            crateInfo = null;

            if (entity == null || entity.IsDestroyed || string.IsNullOrEmpty(entity.PrefabName))
            {
                return false;
            }

            foreach (CrateInfo crate in _crates.Values)
            {
                if (!entity.PrefabName.Equals(crate.Prefab, StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                crateInfo = crate;
                return true;
            }

            return false;
        }

        private bool IsSameCrate(BaseEntity entity, CrateInfo crate)
        {
            return entity != null &&
                   !entity.IsDestroyed &&
                   crate != null &&
                   !string.IsNullOrEmpty(entity.PrefabName) &&
                   entity.PrefabName.Equals(crate.Prefab, StringComparison.OrdinalIgnoreCase);
        }

        private string GetEntityName(BaseEntity entity)
        {
            if (entity == null)
            {
                return "object";
            }

            if (!string.IsNullOrEmpty(entity.ShortPrefabName))
            {
                return entity.ShortPrefabName;
            }

            if (!string.IsNullOrEmpty(entity.PrefabName))
            {
                return entity.PrefabName;
            }

            return entity.GetType().Name;
        }

        private string GetColliderName(Collider collider)
        {
            if (collider == null)
            {
                return "world geometry";
            }

            if (collider.gameObject != null && !string.IsNullOrEmpty(collider.gameObject.name))
            {
                return collider.gameObject.name;
            }

            return collider.GetType().Name;
        }

        private string GetNetworkId(BaseEntity entity)
        {
            return entity == null || entity.net == null
                ? "unknown"
                : entity.net.ID.ToString();
        }

        private void DebugBlocker(BasePlayer player, BaseEntity blocker, string blockerName, Vector3 attemptedPosition)
        {
            string prefab = blocker == null ? "world geometry" : blocker.PrefabName;
            string type = blocker == null ? "Collider" : blocker.GetType().Name;

            string message = string.Format(
                CultureInfo.InvariantCulture,
                "Debug blocker\nName: {0}\nType: {1}\nPrefab: {2}\nAttempted position: {3}",
                blockerName,
                type,
                prefab,
                attemptedPosition);

            Notify(player, message, "info");

            Puts(string.Format(
                CultureInfo.InvariantCulture,
                "[Debug] Blocked crate stack for {0} ({1}): {2} | {3} | {4} | Position: {5}",
                player.displayName,
                player.userID,
                blockerName,
                type,
                prefab,
                attemptedPosition));
        }

        #endregion

        #region Cooldown and Notifications

        private bool IsOnCooldown(BasePlayer player)
        {
            float nextAllowedTime;

            return player != null &&
                   _cooldowns.TryGetValue(player.userID, out nextAllowedTime) &&
                   Time.realtimeSinceStartup < nextAllowedTime;
        }

        private void SetCooldown(BasePlayer player)
        {
            if (player != null)
            {
                _cooldowns[player.userID] = Time.realtimeSinceStartup + _config.PlaceCooldown;
            }
        }

        private void Notify(BasePlayer player, string message, string type)
        {
            if (player == null || string.IsNullOrEmpty(message))
            {
                return;
            }

            if (_config.UseSharedNotifications)
            {
                Plugin notifications = GhostLabsNotifications ??
                                       plugins.Find("GhostLabs.Notifications") ??
                                       plugins.Find("GhostLabsNotifications");

                if (notifications != null)
                {
                    object result = notifications.Call(
                        "SendSimpleNotification",
                        player.userID,
                        message,
                        string.IsNullOrEmpty(type) ? "info" : type);

                    if (result is bool && (bool)result)
                    {
                        return;
                    }
                }
            }

            if (_config.EnableChatMessages)
            {
                player.ChatMessage(string.Format(
                    CultureInfo.InvariantCulture,
                    "{0} {1}",
                    _config.Prefix,
                    message));
            }
        }

        #endregion
    }
}

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