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

FREE SimplePVESphere : 1.1.0 1.1.0

Нет прав для скачивания
C#:
// -----------------------------------------------------------------------
// Copyright (c) Yac Vaguer. All rights reserved.
// Author: Yac Vaguer
// This is proprietary software. Unauthorized copying, distribution, or modification is strictly prohibited.
// Licensed for use only on servers owned by the purchaser.
// -----------------------------------------------------------------------
using Oxide.Core.Plugins;
using Oxide.Core;
using System.Collections.Generic;
using System.Globalization;
using System;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("SimplePVESphere", "Yac Vaguer", "1.1.0")]
    [Description("Spawns a visible sphere and a trigger collider to allow or disallow players for certain areas to be use by events.")]
    public class SimplePVESphere : RustPlugin
    {
        private Dictionary<string, PveSphere> spheres = new Dictionary<string, PveSphere>();
        public bool ignoreAdmins = false;
        public bool debugLogging = false;

        #region Configuration
        protected override void LoadDefaultConfig()
        {
            Config["IgnoreAdmins"] = ignoreAdmins = false;
            Config["DebugLogging"] = debugLogging = false;
            SaveConfig();
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            ignoreAdmins = Convert.ToBoolean(Config["IgnoreAdmins"]);

            if (Config["DebugLogging"] == null)
            {
                Config["DebugLogging"] = false;
                SaveConfig();
            }
            debugLogging = Convert.ToBoolean(Config["DebugLogging"]);
        }
        #endregion

        #region Diagnostics

        public void Log(string line)
        {
            if (debugLogging) Interface.Oxide.LogInfo(line);
        }

        #endregion

        #region Hooks

        private void Init()
        {
            UpdateHookSubscriptions();
        }

        private void OnEntityDeath(BaseCombatEntity victim, HitInfo info)
        {
            if (victim == null || info == null)
            {
                return;
            }

            BasePlayer killer = info.InitiatorPlayer;
            if (killer == null || !killer.userID.Get().IsSteamId())
            {
                return;
            }

            if (!(victim is HumanNPC || victim is ScientistNPC))
            {
                return;
            }

            foreach (var sphereEntry in spheres)
            {
                PveSphere pveSphere = sphereEntry.Value;

                if (pveSphere.Owner != null)
                {
                    continue;
                }

                float distance = Vector3.Distance(victim.transform.position, pveSphere.Center);

                if (distance <= pveSphere.Radius)
                {
                    pveSphere.Owner = killer;
                    Interface.Oxide.CallHook("OnSimplePveOwnerAssigned", pveSphere.EventName, killer);
                    break;
                }
            }
        }

        private object CanEntityTakeDamage(BaseCombatEntity entity, HitInfo info)
        {
            if (info == null) return null;

            BasePlayer victim = entity as BasePlayer;
            if (victim == null || !victim.userID.Get().IsSteamId()) return null;

            BasePlayer attacker = info.InitiatorPlayer;
            if (attacker == null || !attacker.userID.Get().IsSteamId()) return null;
            if (attacker == victim) return null;

            foreach (var kvp in spheres)
            {
                PveSphere sphere = kvp.Value;
                if (!sphere.IsPlayerInside(victim)) continue;

                bool? verdict = SphereAccess.CanDamage(true, sphere.IsAuthorized(attacker), sphere.AllowPvP);
                sphere.LogDamage(attacker, victim, verdict);

                return verdict;
            }

            return null;
        }

        private void Unload()
        {
            foreach (var pair in spheres)
            {
                pair.Value.Destroy();
                Log(SphereAccess.DescribeDestroyed(pair.Key));
            }
            spheres.Clear();
            UpdateHookSubscriptions();
        }

        private void UpdateHookSubscriptions()
        {
            if (spheres.Count > 0)
            {
                Subscribe(nameof(OnEntityDeath));
                Subscribe(nameof(CanEntityTakeDamage));
            }
            else
            {
                Unsubscribe(nameof(OnEntityDeath));
                Unsubscribe(nameof(CanEntityTakeDamage));
            }
        }

        #endregion

        #region API

        [HookMethod("CreateSphere")]
        public void CreateSphere(string eventName, Vector3 position, float radius, int opacity, BasePlayer owner = null, bool eventDebugMode = false)
        {
            if (spheres.ContainsKey(eventName))
            {
                return;
            }

            var newSphere = new PveSphere(eventName, position, radius, opacity, owner, this, eventDebugMode);
            newSphere.SpawnVisualSphere();
            newSphere.SpawnTriggerCollider();
            spheres.Add(eventName, newSphere);

            Log(SphereAccess.DescribeCreated(eventName, radius, position.x, position.y, position.z,
                owner?.displayName, owner == null ? 0UL : (ulong)owner.userID));

            UpdateHookSubscriptions();
        }

        [HookMethod("RemoveSphere")]
        public void RemoveSphere(string eventName)
        {
            if (!spheres.ContainsKey(eventName))
            {
                return;
            }

            spheres[eventName].Destroy();
            spheres.Remove(eventName);

            Log(SphereAccess.DescribeDestroyed(eventName));

            UpdateHookSubscriptions();
        }

        [HookMethod("SetAllowPvP")]
        public void SetAllowPvP(string eventName, bool allowPvP)
        {
            if (!spheres.ContainsKey(eventName)) return;

            spheres[eventName].AllowPvP = allowPvP;
        }

        #endregion

        #region Test commands

        private const string TestSphereName = "sphere-test";
        private const int TestSphereOpacity = 5;

        [ConsoleCommand("simplepvesphere.test")]
        private void TestSphereCommand(ConsoleSystem.Arg arg)
        {
            if (!arg.IsAdmin) return;

            if (spheres.ContainsKey(TestSphereName))
            {
                arg.ReplyWith("A test sphere already exists. Remove it with simplepvesphere.remove first.");
                return;
            }

            string ownerArg = arg.GetString(1, "me");
            BasePlayer owner = null;

            if (ownerArg != "none")
            {
                owner = ownerArg == "me" ? arg.Player() : BasePlayer.FindAwakeOrSleeping(ownerArg);
                if (owner == null)
                {
                    arg.ReplyWith($"No player found for '{ownerArg}'. Use 'me', 'none' or a steam id.");
                    return;
                }
            }

            BasePlayer at = arg.Player() ?? owner;
            if (at == null)
            {
                arg.ReplyWith("Run this in game, or name a connected owner so the sphere has a position.");
                return;
            }

            float radius = arg.GetFloat(0, 25f);
            bool sphereDebugMode = arg.GetBool(2, false);

            CreateSphere(TestSphereName, at.transform.position, radius, TestSphereOpacity, owner, sphereDebugMode);

            arg.ReplyWith($"Test sphere created: radius {radius}, owner {(owner == null ? "none" : owner.displayName)}, debug mode {sphereDebugMode}.");
        }

        [ConsoleCommand("simplepvesphere.remove")]
        private void RemoveTestSphereCommand(ConsoleSystem.Arg arg)
        {
            if (!arg.IsAdmin) return;

            bool existed = spheres.ContainsKey(TestSphereName);
            RemoveSphere(TestSphereName);

            arg.ReplyWith(existed ? "Test sphere removed." : "There is no test sphere to remove.");
        }

        [ConsoleCommand("simplepvesphere.pvp")]
        private void TestSpherePvPCommand(ConsoleSystem.Arg arg)
        {
            if (!arg.IsAdmin) return;

            if (!spheres.ContainsKey(TestSphereName))
            {
                arg.ReplyWith("There is no test sphere. Create one with simplepvesphere.test.");
                return;
            }

            bool allowPvP = arg.GetBool(0, true);
            SetAllowPvP(TestSphereName, allowPvP);

            arg.ReplyWith($"Test sphere PvP {(allowPvP ? "enabled" : "disabled")}.");
        }

        #endregion
    }

    public class PveSphere
    {
        private readonly SimplePVESphere plugin;
        private readonly bool isEventDebugModeActive;
        private readonly int opacity;

        public string EventName { get; private set; }
        public Vector3 Center { get; private set; }
        public float Radius { get; private set; }
        public BasePlayer Owner { get; set; }
        public bool AllowPvP { get; set; }

        private List<SphereEntity> visualSpheres = new List<SphereEntity>();

        private GameObject triggerObject;
        private SphereCollider triggerCollider;
        private SphereTrigger trigger;

        public PveSphere(string eventName, Vector3 position, float radius, int opacity, BasePlayer owner, SimplePVESphere pluginInstance, bool eventDebugMode)
        {
            this.EventName = eventName;
            this.Center = position;
            this.Radius = radius;
            this.opacity = opacity;
            this.Owner = owner;
            this.plugin = pluginInstance;
            this.isEventDebugModeActive = eventDebugMode;
        }

        #region Public

        public void SpawnVisualSphere()
        {
            for (int i = 0; i < opacity; i++)
            {
                var sphere = GameManager.server.CreateEntity("assets/prefabs/visualization/sphere.prefab", Center) as SphereEntity;
                if (sphere == null)
                {
                    continue;
                }
                sphere.enableSaving = false;
                sphere.currentRadius = Radius * 2;
                sphere.lerpRadius = Radius * 2;
                sphere.Spawn();

                visualSpheres.Add(sphere);
            }
        }

        public void SpawnTriggerCollider()
        {
            triggerObject = new GameObject($"SphereTrigger_{Center}");
            UnityEngine.Object.DontDestroyOnLoad(triggerObject);

            triggerObject.transform.position = Center;
            triggerObject.layer = 3;

            triggerCollider = triggerObject.AddComponent<SphereCollider>();
            triggerCollider.isTrigger = true;
            triggerCollider.radius = Radius;

            trigger = triggerObject.AddComponent<SphereTrigger>();
            trigger.Initialize(this);
        }

        public void Destroy()
        {
            foreach (var sphere in visualSpheres)
            {
                if (sphere != null && !sphere.IsDestroyed)
                    sphere.Kill();
            }
            visualSpheres.Clear();

            trigger = null;

            if (triggerObject != null)
            {
                UnityEngine.Object.Destroy(triggerObject);
            }
        }

        public bool IsPlayerInside(BasePlayer player) => trigger != null && trigger.IsInside(player);

        public bool IsAuthorized(BasePlayer player)
        {
            if (player == null)
            {
                return false;
            }

            SphereAccess.AccessReason reason = SphereAccess.Authorize(
                player.userID,
                Owner == null ? 0UL : (ulong)Owner.userID,
                Owner?.Team?.members,
                player.IsAdmin,
                plugin.ignoreAdmins,
                isEventDebugModeActive);

            if (plugin.debugLogging)
            {
                LogOnChange(AccessLog, player.userID,
                    SphereAccess.DescribeAccess(EventName, player.displayName, player.userID, reason));
            }

            return SphereAccess.IsAuthorized(reason);
        }

        #endregion

        #region Diagnostics

        private const string AccessLog = "access";
        private const string DamageLog = "damage";
        private const string EjectionLog = "ejection";

        private readonly Dictionary<string, string> lastLine = new Dictionary<string, string>();

        public void LogBoundary(BasePlayer player, bool entered)
        {
            if (!entered) ForgetPlayer(player.userID);

            plugin.Log(SphereAccess.DescribeBoundary(EventName, player.displayName, player.userID, entered));
        }

        public void ForgetPlayer(ulong playerId)
        {
            lastLine.Remove(AccessLog + playerId);
            lastLine.Remove(DamageLog + playerId);
            lastLine.Remove(EjectionLog + playerId);
        }

        public void LogDamage(BasePlayer attacker, BasePlayer victim, bool? verdict)
        {
            if (!plugin.debugLogging) return;

            LogOnChange(DamageLog, attacker.userID, SphereAccess.DescribeDamage(
                EventName, attacker.displayName, attacker.userID, victim.displayName, victim.userID, verdict));
        }

        public void LogEjection(BasePlayer player)
        {
            if (!plugin.debugLogging) return;

            LogOnChange(EjectionLog, player.userID,
                SphereAccess.DescribeEjection(EventName, player.displayName, player.userID));
        }

        private void LogOnChange(string category, ulong playerId, string line)
        {
            string key = category + playerId;

            string previous;
            if (lastLine.TryGetValue(key, out previous) && previous == line) return;

            lastLine[key] = line;
            plugin.Log(line);
        }

        #endregion
    }

    public class SphereTrigger : FacepunchBehaviour
    {
        private PveSphere pveSphere;
        private const float EJECTION_COOLDOWN = 1.0f;

        private readonly HashSet<BasePlayer> insidePlayers = new HashSet<BasePlayer>();
        private readonly List<BasePlayer> evalBuffer = new List<BasePlayer>();

        private readonly Dictionary<ulong, float> lastEjectionTime = new Dictionary<ulong, float>();

        public void Initialize(PveSphere pveSphere)
        {
            this.pveSphere = pveSphere;
        }

        public bool IsInside(BasePlayer player) => player != null && insidePlayers.Contains(player);

        private void OnTriggerEnter(Collider other)
        {
            var player = other.GetComponentInParent<BasePlayer>();
            if (player == null)
            {
                return;
            }

            insidePlayers.Add(player);
            pveSphere.LogBoundary(player, true);

            if (!player.IsAlive())
            {
                return;
            }

            if (pveSphere.IsAuthorized(player))
            {
                return;
            }
            TryEjectPlayer(player);
        }

        private void OnTriggerExit(Collider other)
        {
            var player = other.GetComponentInParent<BasePlayer>();
            if (player == null)
                return;

            insidePlayers.Remove(player);
            pveSphere.LogBoundary(player, false);
        }

        private void FixedUpdate()
        {
            if (insidePlayers.Count == 0)
                return;

            evalBuffer.Clear();
            evalBuffer.AddRange(insidePlayers);

            for (int i = 0; i < evalBuffer.Count; i++)
            {
                var player = evalBuffer[i];
                if (player == null || player.IsDestroyed || !player.IsAlive())
                {
                    insidePlayers.Remove(player);
                    if (player != null) pveSphere.ForgetPlayer(player.userID);
                    continue;
                }

                if (pveSphere.IsAuthorized(player))
                    continue;

                TryEjectPlayer(player);
            }
        }

        private void TryEjectPlayer(BasePlayer player)
        {
            if (player == null) return;

            float currentTime = Time.realtimeSinceStartup;
            if (lastEjectionTime.TryGetValue(player.userID, out float lastEject))
            {
                if (currentTime - lastEject < EJECTION_COOLDOWN)
                    return;
            }

            lastEjectionTime[player.userID] = currentTime;
            pveSphere.LogEjection(player);
            EjectPlayerOrVehicle(player);
        }

        private void EjectPlayerOrVehicle(BasePlayer player)
        {
            if (player == null) return;

            try
            {
                var mountable = player.GetMounted();
                if (mountable != null)
                {
                    var vehicle = mountable.VehicleParent();
                    if (vehicle != null)
                    {
                        EjectVehicle(vehicle);
                        return;
                    }
                    else
                    {
                        player.EnsureDismounted();
                    }
                }

                EjectPlayerCharacter(player);
            }
            catch (Exception ex)
            {
                Interface.Oxide.LogError($"[SimplePVESphere] Error ejecting player/vehicle for {player.displayName}: {ex.Message}\n{ex.StackTrace}");
            }
        }

        private void EjectVehicle(BaseVehicle vehicle)
        {
            if (vehicle == null) return;

            Vector3 sphereCenter = transform.position;
            float sphereRadius = pveSphere.Radius;

            Vector3 directionFromCenter = (vehicle.transform.position.XZ3D() - sphereCenter.XZ3D()).normalized;
            if (directionFromCenter == Vector3.zero) directionFromCenter = Vector3.forward;

            Vector3 targetHorizontalPos = sphereCenter.XZ3D() + directionFromCenter * (sphereRadius + 10f);

            float groundY = GetGroundPosition(targetHorizontalPos).y;
            Vector3 newVehiclePosition = new Vector3(targetHorizontalPos.x, groundY + 0.5f, targetHorizontalPos.z);

            vehicle.transform.position = newVehiclePosition;
            vehicle.transform.rotation = Quaternion.LookRotation(-directionFromCenter, Vector3.up);
            if (vehicle.rigidBody != null) vehicle.rigidBody.linearVelocity *= -0.5f;
            vehicle.SendNetworkUpdateImmediate();
        }

        private void EjectPlayerCharacter(BasePlayer player)
        {
            if (player == null) return;

            Vector3 sphereCenter = transform.position;
            float sphereRadius = pveSphere.Radius;

            Vector3 directionFromCenter = (player.transform.position.XZ3D() - sphereCenter.XZ3D()).normalized;
            if (directionFromCenter == Vector3.zero) directionFromCenter = player.transform.forward;
            if (directionFromCenter == Vector3.zero) directionFromCenter = Vector3.forward;

            Vector3 targetHorizontalPos = sphereCenter.XZ3D() + directionFromCenter * (sphereRadius + 5f);

            Vector3 newPosition = GetGroundPosition(targetHorizontalPos);

            player.MovePosition(newPosition);
            player.ClientRPC(RpcTarget.Player("ForcePositionTo", player), newPosition);
            player.SendNetworkUpdateImmediate();
        }

        private Vector3 GetGroundPosition(Vector3 horizontalPosition)
        {
            Vector3 raycastStart = new Vector3(horizontalPosition.x, 500f, horizontalPosition.z);
            RaycastHit hitInfo;

            int groundCheckLayers = LayerMask.GetMask("Terrain", "World", "Construction", "Deployed", "Default");

            if (Physics.Raycast(raycastStart, Vector3.down, out hitInfo, 700f, groundCheckLayers, QueryTriggerInteraction.Ignore))
            {
                return hitInfo.point;
            }
            else
            {
                float terrainHeight = TerrainMeta.HeightMap.GetHeight(horizontalPosition);
                return new Vector3(horizontalPosition.x, terrainHeight, horizontalPosition.z);
            }
        }
    }

    internal static class SphereAccess
    {
        public enum AccessReason
        {
            AdminBypass,
            NoOwner,
            IsOwner,
            IsTeammate,
            Denied
        }

        public static AccessReason Authorize(
            ulong playerId,
            ulong ownerId,
            IEnumerable<ulong> ownerTeamMemberIds,
            bool isAdmin,
            bool ignoreAdmins,
            bool debugMode)
        {
            if (isAdmin && ignoreAdmins && !debugMode) return AccessReason.AdminBypass;
            if (ownerId == 0UL) return AccessReason.NoOwner;
            if (playerId == ownerId) return AccessReason.IsOwner;

            if (ownerTeamMemberIds != null)
            {
                foreach (ulong memberId in ownerTeamMemberIds)
                {
                    if (memberId == playerId) return AccessReason.IsTeammate;
                }
            }

            return AccessReason.Denied;
        }

        public static bool IsAuthorized(AccessReason reason) => reason != AccessReason.Denied;

        public static bool? CanDamage(bool victimIsProtectedPlayer, bool attackerIsAuthorized, bool allowPvP)
        {
            if (!victimIsProtectedPlayer) return null;

            return allowPvP || attackerIsAuthorized;
        }

        #region Log rendering

        public const string LogPrefix = "[SimplePVESphere]";

        public static string DescribeCreated(string sphereName, float radius, float x, float y, float z, string ownerName, ulong ownerId)
            => Line(sphereName, "created: radius " + N(radius) + " at " + N(x) + " " + N(y) + " " + N(z) + ", owner " + Who(ownerName, ownerId));

        public static string DescribeDestroyed(string sphereName)
            => Line(sphereName, "destroyed");

        public static string DescribeBoundary(string sphereName, string playerName, ulong playerId, bool entered)
            => Line(sphereName, (entered ? "entered" : "left") + " by " + Who(playerName, playerId));

        public static string DescribeAccess(string sphereName, string playerName, ulong playerId, AccessReason reason)
            => Line(sphereName, "access " + (IsAuthorized(reason) ? "granted" : "denied") + " to " + Who(playerName, playerId) + ": " + Explain(reason));

        public static string DescribeDamage(string sphereName, string attackerName, ulong attackerId, string victimName, ulong victimId, bool? verdict)
            => Line(sphereName, "damage " + Verdict(verdict) + ": " + Who(attackerName, attackerId) + " -> " + Who(victimName, victimId));

        public static string DescribeEjection(string sphereName, string playerName, ulong playerId)
            => Line(sphereName, "ejected " + Who(playerName, playerId));

        private static string Line(string sphereName, string message) => LogPrefix + " [" + sphereName + "] " + message;

        private static string Who(string playerName, ulong playerId)
            => playerId == 0UL ? "none" : playerName + " (" + playerId + ")";

        private static string Explain(AccessReason reason)
        {
            switch (reason)
            {
                case AccessReason.AdminBypass: return "admin bypass";
                case AccessReason.NoOwner: return "sphere has no owner";
                case AccessReason.IsOwner: return "is the owner";
                case AccessReason.IsTeammate: return "is on the owner's team";
                default: return "not the owner, a teammate, or a bypassing admin";
            }
        }

        private static string Verdict(bool? verdict)
            => verdict == null ? "no opinion" : verdict.Value ? "allowed" : "blocked";

        private static string N(float value) => value.ToString("0.#", CultureInfo.InvariantCulture);

        #endregion
    }
}

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