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

FREE CustomCrateSpawns : 1.1.0 1.1.0

Нет прав для скачивания
C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
/*
 * Copyright (C) 2024 Game4Freak.io
 * This mod is provided under the Game4Freak EULA.* Full legal terms can be found at https://game4freak.io/eula/
 */

using Facepunch;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Plugins;
using Rust;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using UnityEngine;
using Random = UnityEngine.Random;

namespace Oxide.Plugins
{
    [Info("Custom Crate Spawns", "VisEntities", "1.1.0")]
    [Description("Lets you add additional spawn points for loot containers.")]
    public class CustomCrateSpawns : RustPlugin
    {
        #region Fields

        private static CustomCrateSpawns _plugin;
        private System.Random _randomGenerator = new System.Random();

        private SpawnGroupData _spawnGroupBeingEdited;
        private List<LootSpawnerComponent> _lootSpawners = new List<LootSpawnerComponent>();

        public const int LAYER_LOOT_CRATES = Layers.Mask.Default;
        public const int LAYER_PLAYERS = Layers.Mask.Player_Server;
        public const int LAYER_BUILDINGS = Layers.Mask.Construction | Layers.Mask.Deployed;
        public const int LAYER_GROUND = Layers.Mask.Terrain | Layers.Mask.World | Layers.Mask.Default;
        
        private static readonly Dictionary<string, string> _lootContainerPrefabs = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
        {
            {"junk", "assets/bundled/prefabs/radtown/crate_normal_2.prefab"},
            {"military", "assets/bundled/prefabs/radtown/crate_normal.prefab"},
            {"elite", "assets/bundled/prefabs/radtown/crate_elite.prefab"},
            {"medical", "assets/bundled/prefabs/radtown/crate_normal_2_medical.prefab"},
            {"food", "assets/bundled/prefabs/radtown/crate_normal_2_food.prefab"},
            {"ammo", "assets/bundled/prefabs/radtown/dmloot/dm ammo.prefab"},
            {"explosives", "assets/bundled/prefabs/radtown/dmloot/dm c4.prefab"},
            {"foodbox", "assets/bundled/prefabs/radtown/foodbox.prefab"},
            {"tools", "assets/bundled/prefabs/radtown/crate_tools.prefab"},
            {"vehicleparts", "assets/bundled/prefabs/radtown/vehicle_parts.prefab"},

            {"waterammo", "assets/bundled/prefabs/radtown/underwater_labs/crate_ammunition.prefab"},
            {"watermedical", "assets/bundled/prefabs/radtown/underwater_labs/crate_medical.prefab"},
            {"waterfood", "assets/bundled/prefabs/radtown/underwater_labs/crate_food_1.prefab"},
            {"waterfoodbox", "assets/bundled/prefabs/radtown/underwater_labs/crate_food_2.prefab"},
            {"waterfuel", "assets/bundled/prefabs/radtown/underwater_labs/crate_fuel.prefab"},
            {"watertechparts", "assets/bundled/prefabs/radtown/underwater_labs/tech_parts_2.prefab"},

            {"hackable", "assets/prefabs/deployable/chinooklockedcrate/codelockedhackablecrate.prefab"},
            {"hackableoilrig", "assets/prefabs/deployable/chinooklockedcrate/codelockedhackablecrate_oilrig.prefab"},
            {"bradley", "assets/prefabs/npc/m2bradley/bradley_crate.prefab"},
            {"patrolheli", "assets/prefabs/npc/patrol helicopter/heli_crate.prefab"},
            {"supplydrop", "assets/prefabs/misc/supply drop/supply_drop.prefab"},

            {"barrel", "assets/bundled/prefabs/radtown/loot_barrel_2.prefab"},
            {"barrelblue", "assets/bundled/prefabs/radtown/loot_barrel_1.prefab"},
            {"barreloil", "assets/bundled/prefabs/radtown/oil_barrel.prefab"},
            {"barreldiesel", "assets/prefabs/resource/diesel barrel/diesel_barrel_world.prefab"},
        };

        #endregion Fields

        #region Stored Data

        public class SpawnGroupData
        {
            [JsonProperty("Alias")]
            public string Alias { get; set; }

            [JsonProperty("Id")]
            public Guid Id { get; set; }

            [JsonProperty("Active")]
            public bool Active { get; set; }

            [JsonProperty("Maximum Population")]
            public int MaximumPopulation { get; set; }

            [JsonProperty("Minimum Number To Spawn Per Tick")]
            public int MinimumNumberToSpawnPerTick { get; set; }

            [JsonProperty("Maximum Number To Spawn Per Tick")]
            public int MaximumNumberToSpawnPerTick { get; set; }

            [JsonProperty("Initial Spawn")]
            public bool InitialSpawn { get; set; }

            [JsonProperty("Minimum Respawn Delay Seconds")]
            public float MinimumRespawnDelaySeconds { get; set; }

            [JsonProperty("Maximum Respawn Delay Seconds")]
            public float MaximumRespawnDelaySeconds { get; set; }

            [JsonProperty("Randomize Y Rotation")]
            public bool RandomizeYRotation { get; set; }
            
            [JsonProperty("Replace Looted Containers")]
            public bool ReplaceLootedContainers { get; set; }

            [JsonProperty("Prefabs")]
            public List<PrefabData> Prefabs { get; set; } = new List<PrefabData>();

            [JsonProperty("Spawn Points")]
            public List<SpawnPointData> SpawnPoints { get; set; } = new List<SpawnPointData>();
        }

        public class PrefabData
        {
            [JsonProperty("Prefab")]
            public string Prefab { get; set; }

            [JsonProperty("Weight")]
            public int Weight { get; set; }
        }

        public class SpawnPointData
        {
            [JsonProperty("Id")]
            public string Id { get; set; }

            [JsonProperty("Position")]
            public Vector3 Position { get; set; }

            [JsonProperty("Rotation")]
            public Vector3 Rotation { get; set; }

            [JsonProperty("Radius")]
            public float Radius { get; set; }
        }

        #endregion Stored Data

        #region Oxide Hooks

        private void Init()
        {
            _plugin = this;
            DataFileUtil.EnsureFolderCreated();
            PermissionUtil.RegisterPermissions();
        }

        private void Unload()
        {
            if (_lootSpawners != null)
            {
                for (int i = _lootSpawners.Count - 1; i >= 0; i--)
                {
                    LootSpawnerComponent lootSpawner = _lootSpawners[i];
                    if (lootSpawner != null)
                        lootSpawner.Destroy();
                }

                _lootSpawners.Clear();
            }

            _plugin = null;
        }

        private void OnServerInitialized(bool isStartup)
        {
            string[] spawnGroupFiles = DataFileUtil.GetAllFilePaths();
            foreach (string filePath in spawnGroupFiles)
            {
                SpawnGroupData spawnGroup = DataFileUtil.LoadIfExists<SpawnGroupData>(filePath);
                if (spawnGroup != null)
                {
                    LootSpawnerComponent.Create(spawnGroup);
                }
            }
        }

        private void OnLootEntityEnd(BasePlayer player, LootContainer lootContainer)
        {
            if (player == null || lootContainer == null)
                return;

            if (lootContainer.inventory == null || lootContainer.inventory.itemList == null || lootContainer.inventory.itemList.Count == 0)
            {
                LootSpawnerComponent lootSpawner = GetSpawnerForLootContainer(lootContainer);
                if (lootSpawner != null)
                {
                    lootSpawner.OnLootContainerLooted(lootContainer);
                }
            }
        }

        #endregion Oxide Hooks

        #region Loot Spawner Component

        public class LootSpawnerComponent : FacepunchBehaviour
        {
            #region Fields

            private bool _isDestroying;
            public SpawnGroupData Data { get; set; }
            public List<SpawnPointComponent> SpawnPoints { get; set; } = new List<SpawnPointComponent>();
            public List<LootContainer> SpawnedEntities { get; set; } = new List<LootContainer>();
            public int CurrentPopulation
            {
                get
                {
                    return SpawnedEntities.Count(lootContainer => lootContainer != null);
                }
            }

            #endregion Fields

            #region Initialization

            public static LootSpawnerComponent Create(SpawnGroupData data)
            {
                LootSpawnerComponent spawner = new GameObject().AddComponent<LootSpawnerComponent>();
                spawner.Initialize(data);

                return spawner;
            }

            public void Initialize(SpawnGroupData data)
            {
                Data = data;
                foreach (SpawnPointData spawnPointData in Data.SpawnPoints)
                {
                    SpawnPointComponent.Create(spawnPointData, this);
                }

                _plugin._lootSpawners.Add(this);
            }

            public void Destroy()
            {
                _isDestroying = true;
                DestroyImmediate(this);
            }

            #endregion Initialization

            #region Component Lifecycle

            private void Start()
            {
                if (!Data.Active)
                    return;

                float initialSpawnTime = GetNextSpawnTime();
                if (Data.InitialSpawn)
                    initialSpawnTime = 0f;

                InvokeRepeating(nameof(TimedSpawn), initialSpawnTime, GetNextSpawnTime());
            }

            private void OnDestroy()
            {
                CancelInvoke(nameof(TimedSpawn));
                Clear();

                for (int i = SpawnPoints.Count - 1; i >= 0; i--)
                {
                    if (SpawnPoints[i] != null)
                        SpawnPoints[i].Destroy();
                }

                _plugin._lootSpawners.Remove(this);
            }

            #endregion Component Lifecycle

            #region Spawn Lifecycle

            private void TimedSpawn()
            {
                if (!_isDestroying && CurrentPopulation < Data.MaximumPopulation)
                {
                    int numberToSpawn = Random.Range(Data.MinimumNumberToSpawnPerTick, Data.MaximumNumberToSpawnPerTick + 1);
                    Spawn(numberToSpawn);
                }
            }

            private float GetNextSpawnTime()
            {
                return Random.Range(Data.MinimumRespawnDelaySeconds, Data.MaximumRespawnDelaySeconds);
            }

            public void Spawn(int numberToSpawn)
            {
                numberToSpawn = Mathf.Min(numberToSpawn, Data.MaximumPopulation - CurrentPopulation);

                for (int i = 0; i < numberToSpawn; i++)
                {
                    string prefabPath = GetPrefab();
                    if (string.IsNullOrEmpty(prefabPath))
                        continue;

                    if (GetSpawnPoint(prefabPath, out Vector3 position, out Quaternion rotation) == null)
                        continue;

                    if (OnCustomCrateSpawn(Data.Alias, prefabPath, position, rotation))
                        continue;

                    LootContainer lootContainer = GameManager.server.CreateEntity(prefabPath, position, rotation) as LootContainer;
                    if (lootContainer == null)
                        continue;

                    // TODO: Consider parenting the spawned entity if it's placed on another entity.lootContainer.Spawn();
                    lootContainer.EnableSaving(false);
                    SpawnedEntities.Add(lootContainer);
                }
            }

            public void Fill()
            {
                int numberToFill = Data.MaximumPopulation - CurrentPopulation;
                Spawn(numberToFill);
            }

            public void Clear()
            {
                for (int i = SpawnedEntities.Count - 1; i >= 0; i--)
                {
                    LootContainer lootContainer = SpawnedEntities[i];
                    if (lootContainer != null)
                        lootContainer.Kill();

                    SpawnedEntities.RemoveAt(i);
                }
            }

            #endregion Spawn Lifecycle

            #region Prefab Selection

            public string GetPrefab()
            {
                int totalWeight = Data.Prefabs.Sum(prefab => prefab.Weight);

                if (totalWeight <= 0)
                    return null;

                float randomValue = Random.Range(0, totalWeight);

                foreach (PrefabData prefab in Data.Prefabs)
                {
                    randomValue -= prefab.Weight;
                    if (randomValue <= 0)
                    {
                        if (_lootContainerPrefabs.TryGetValue(prefab.Prefab, out string fullPrefabPath))
                            return fullPrefabPath;
                        else
                            return null;
                    }
                }

                PrefabData lastPrefab = Data.Prefabs.LastOrDefault();
                if (lastPrefab != null && _lootContainerPrefabs.TryGetValue(lastPrefab.Prefab, out string lastPrefabPath))
                    return lastPrefabPath;

                return null;
            }

            #endregion Prefab Selection

            #region Spawn Point Selection

            public SpawnPointComponent GetSpawnPoint(string prefabPath, out Vector3 position, out Quaternion rotation)
            {
                Shuffle(SpawnPoints);

                foreach (SpawnPointComponent spawnPoint in SpawnPoints)
                {
                    if (spawnPoint.HasPlayersIntersecting())
                        continue;

                    for (int attempts = 0; attempts < 5; attempts++)
                    {
                        position = spawnPoint.GetPosition();
                        rotation = spawnPoint.GetRotation();

                        if (Data.RandomizeYRotation)
                            rotation *= Quaternion.Euler(0, Random.Range(0f, 360f), 0);

                        if (!spawnPoint.HasSpaceToSpawn(prefabPath, position, rotation))
                            continue;

                        return spawnPoint;
                    }
                }

                position = default;
                rotation = default;
                return null;
            }

            #endregion Spawn Point Selection

            #region Loot Containers Removal

            public void OnLootContainerLooted(LootContainer lootContainer)
            {
                if (!_isDestroying && Data.ReplaceLootedContainers)
                    Spawn(1);
            }

            #endregion Loot Containers Removal
        }

        #endregion Loot Spawner Component

        #region Spawn Point Component

        public class SpawnPointComponent : FacepunchBehaviour
        {
            #region Fields
            
            public LootSpawnerComponent LootSpawner { get; set; }
            public SpawnPointData Data { get; set; }

            #endregion Fields

            #region Initialization

            public static SpawnPointComponent Create(SpawnPointData data, LootSpawnerComponent lootSpawner)
            {
                SpawnPointComponent spawnPoint = new GameObject().AddComponent<SpawnPointComponent>();
                spawnPoint.Initialize(data, lootSpawner);

                return spawnPoint;
            }

            public void Initialize(SpawnPointData data, LootSpawnerComponent lootSpawner)
            {
                Data = data;
                LootSpawner = lootSpawner;
                LootSpawner.SpawnPoints.Add(this);
            }

            public void Destroy()
            {
                DestroyImmediate(this);
            }

            #endregion Initialization

            #region Component Lifecycle

            private void OnDestroy()
            {
                LootSpawner.SpawnPoints.Remove(this);
            }

            #endregion Component Lifecycle

            #region Position and Rotation Retrieval

            public Vector3 GetPosition()
            {
                Vector3 targetPosition;

                if (Data.Radius > 0)
                    targetPosition = TerrainUtil.GetRandomPositionAround(Data.Position, 0f, Data.Radius);
                else
                    targetPosition = Data.Position;

                if (TerrainUtil.GetGroundInfo(targetPosition, out RaycastHit hit, Data.Radius, LAYER_GROUND | LAYER_BUILDINGS))
                    targetPosition = hit.point;

                return targetPosition;
            }

            public Quaternion GetRotation()
            {
                Quaternion baseRotation = Quaternion.Euler(Data.Rotation);

                if (TerrainUtil.GetGroundInfo(Data.Position, out RaycastHit hit, 2f, LAYER_GROUND | LAYER_BUILDINGS))
                {
                    Quaternion terrainAlignment = Quaternion.FromToRotation(Vector3.up, hit.normal);
                    return terrainAlignment * baseRotation;
                }

                return baseRotation;
            }

            #endregion Position and Rotation Retrieval

            #region Collision and Space Validation

            public bool HasSpaceToSpawn(string prefabPath, Vector3 position, Quaternion rotation)
            {
                GameObject prefab = GameManager.server.FindPrefab(prefabPath);
                if (prefab == null)
                    return false;

                BaseEntity prefabEntity = prefab.GetComponent<BaseEntity>();
                if (prefabEntity == null)
                    return false;

                OBB entityBounds = new OBB(position, rotation, prefabEntity.bounds);

                Collider[] colliders = Physics.OverlapBox(
                    entityBounds.position,
                    entityBounds.extents,
                    entityBounds.rotation,
                    LAYER_LOOT_CRATES,
                    QueryTriggerInteraction.Ignore
                );

                if (colliders.Length > 0)
                    return false;

                return true;
            }

            public bool HasPlayersIntersecting()
            {
                return PlayerUtil.HasPlayerNearby(Data.Position, Mathf.Max(Data.Radius, 2f));
            }

            #endregion Collision and Space Validation
        }

        #endregion Spawn Point Component

        #region API

        [HookMethod(nameof(API_SpawnCratesForGroup))]
        public void API_SpawnCratesForGroup(string spawnGroupAlias)
        {
            LootSpawnerComponent lootSpawner = GetSpawnerByAlias(spawnGroupAlias);
            if (lootSpawner != null)
            {
                lootSpawner.Fill();
            }
        }
        
        [HookMethod(nameof(API_ClearCratesForGroup))]
        public void API_ClearCratesForGroup(string spawnGroupAlias)
        {
            LootSpawnerComponent lootSpawner = GetSpawnerByAlias(spawnGroupAlias);
            if (lootSpawner != null)
            {
                lootSpawner.Clear();
            }
        }
        
        [HookMethod(nameof(API_IsCrateFromCustomSpawner))]
        public bool API_IsCrateFromCustomSpawner(LootContainer lootContainer)
        {
            return GetSpawnerForLootContainer(lootContainer) != null;
        }

        [HookMethod(nameof(API_SpawnGroupExists))]
        public bool API_SpawnGroupExists(string spawnGroupAlias)
        {
            if (string.IsNullOrEmpty(spawnGroupAlias))
                return false;

            string filePath = DataFileUtil.GetFilePath(spawnGroupAlias);

            return DataFileUtil.Exists(filePath);
        }

        [HookMethod(nameof(API_GetAllSpawnGroupAliases))]
        public List<string> API_GetAllSpawnGroupAliases()
        {
            var result = new List<string>();
            if (_lootSpawners == null)
                return result;

            var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

            foreach (var spawner in _lootSpawners)
            {
                if (spawner == null || spawner.Data == null)
                    continue;

                var alias = spawner.Data.Alias;
                if (string.IsNullOrEmpty(alias))
                    continue;

                if (seen.Add(alias))
                    result.Add(alias);
            }

            return result;
        }

        #endregion API

        #region Exposed Hooks

        private static bool OnCustomCrateSpawn(string spawnGroupAlias, string prefab, Vector3 position, Quaternion rotation)
        {
            object hookResult = Interface.CallHook("OnCustomCrateSpawn", spawnGroupAlias, prefab, position, rotation);
            return hookResult is bool && (bool)hookResult == false;
        }

        #endregion Exposed Hooks

        #region Helper Functions

        private LootSpawnerComponent GetSpawnerByAlias(string spawnGroupAlias)
        {
            if (string.IsNullOrEmpty(spawnGroupAlias))
                return null;
            
            foreach (LootSpawnerComponent lootSpawner in _lootSpawners)
            {
                if (lootSpawner.Data.Alias.Equals(spawnGroupAlias, StringComparison.OrdinalIgnoreCase))
                    return lootSpawner;
            }

            return null;
        }
        
        private LootSpawnerComponent GetSpawnerForLootContainer(LootContainer lootContainer)
        {
            foreach (LootSpawnerComponent lootSpawner in _lootSpawners)
            {
                if (lootSpawner.SpawnedEntities.Contains(lootContainer))
                    return lootSpawner;
            }
            return null;
        }

        #endregion Helper Functions

        #region Helper Functions

        private static void Shuffle<T>(List<T> list)
        {
            int remainingItems = list.Count;

            while (remainingItems > 1)
            {
                remainingItems--;
                int randomIndex = _plugin._randomGenerator.Next(remainingItems + 1);

                T itemToSwap = list[randomIndex];
                list[randomIndex] = list[remainingItems];
                list[remainingItems] = itemToSwap;
            }
        }

        private static string FindPrefabPath(string shortPrefabName)
        {
            foreach (string prefabPath in GameManifest.Current.entities)
            {
                string prefabFileName = prefabPath.Substring(prefabPath.LastIndexOf("/") + 1).Replace(".prefab", "");

                if (string.Equals(prefabFileName, shortPrefabName, StringComparison.OrdinalIgnoreCase))
                    return prefabPath;
            }

            return null;
        }

        private static string GetShortPrefabName(string fullPrefabPath)
        {
            if (string.IsNullOrEmpty(fullPrefabPath))
                return string.Empty;

            int lastSlashIndex = fullPrefabPath.LastIndexOf("/");
            if (lastSlashIndex == -1)
                return fullPrefabPath.Replace(".prefab", "");

            return fullPrefabPath.Substring(lastSlashIndex + 1).Replace(".prefab", "");
        }

        #endregion Helper Functions

        #region Helper Classes

        public static class TerrainUtil
        {
            public static bool OnTopology(Vector3 position, TerrainTopology.Enum topology)
            {
                return (TerrainMeta.TopologyMap.GetTopology(position) & (int)topology) != 0;
            }

            public static bool OnRoadOrRail(Vector3 position)
            {
                var combinedTopology = TerrainTopology.Enum.Road | TerrainTopology.Enum.Roadside |
                                   TerrainTopology.Enum.Rail | TerrainTopology.Enum.Railside;

                return OnTopology(position, combinedTopology);
            }

            public static bool OnTerrain(Vector3 position, float radius)
            {
                return Physics.CheckSphere(position, radius, Layers.Mask.Terrain, QueryTriggerInteraction.Ignore);
            }

            public static bool InNoBuildZone(Vector3 position, float radius)
            {
                return Physics.CheckSphere(position, radius, Layers.Mask.Prevent_Building);
            }

            public static bool InWater(Vector3 position)
            {
                return WaterLevel.Test(position, false, false);
            }

            public static bool InsideRock(Vector3 position, float radius)
            {
                List<Collider> colliders = Pool.Get<List<Collider>>();
                Vis.Colliders(position, radius, colliders, Layers.Mask.World, QueryTriggerInteraction.Ignore);

                bool result = false;

                foreach (Collider collider in colliders)
                {
                    if (collider.name.Contains("rock", CompareOptions.OrdinalIgnoreCase)
                        || collider.name.Contains("cliff", CompareOptions.OrdinalIgnoreCase)
                        || collider.name.Contains("formation", CompareOptions.OrdinalIgnoreCase))
                    {
                        result = true;
                        break;
                    }
                }

                Pool.FreeUnmanaged(ref colliders);
                return result;
            }

            public static bool InRadTown(Vector3 position, bool shouldDisplayOnMap = false)
            {
                foreach (var monumentInfo in TerrainMeta.Path.Monuments)
                {
                    bool inBounds = monumentInfo.IsInBounds(position);

                    bool hasLandMarker = true;
                    if (shouldDisplayOnMap)
                        hasLandMarker = monumentInfo.shouldDisplayOnMap;

                    if (inBounds && hasLandMarker)
                        return true;
                }

                return OnTopology(position, TerrainTopology.Enum.Monument);
            }

            public static bool HasEntityNearby(Vector3 position, float radius, LayerMask mask, string prefabName = null)
            {
                List<Collider> hitColliders = Pool.Get<List<Collider>>();
                GamePhysics.OverlapSphere(position, radius, hitColliders, mask, QueryTriggerInteraction.Ignore);

                bool hasEntityNearby = false;
                foreach (Collider collider in hitColliders)
                {
                    BaseEntity entity = collider.gameObject.ToBaseEntity();
                    if (entity != null)
                    {
                        if (prefabName == null || entity.PrefabName == prefabName)
                        {
                            hasEntityNearby = true;
                            break;
                        }
                    }
                }

                Pool.FreeUnmanaged(ref hitColliders);
                return hasEntityNearby;
            }

            public static Vector3 GetRandomPositionAround(Vector3 centerPosition, float minimumRadius, float maximumRadius)
            {
                Vector3 randomDirection = Random.onUnitSphere;
                randomDirection.y = 0;
                float randomDistance = Random.Range(minimumRadius, maximumRadius);
                Vector3 randomPosition = centerPosition + randomDirection * randomDistance;

                return randomPosition;
            }

            public static Vector3 GetPositionOnCircle(Vector3 centerPosition, float radius, float angle)
            {
                float radians = angle * Mathf.Deg2Rad;
                return new Vector3(centerPosition.x + Mathf.Cos(radians) * radius, centerPosition.y, centerPosition.z + Mathf.Sin(radians) * radius);
            }

            public static bool GetGroundInfo(Vector3 startPosition, out RaycastHit raycastHit, float range, LayerMask mask)
            {
                return Physics.Linecast(startPosition + new Vector3(0.0f, range, 0.0f), startPosition - new Vector3(0.0f, range, 0.0f), out raycastHit, mask);
            }

            public static bool GetGroundInfo(Vector3 startPosition, out RaycastHit raycastHit, float range, LayerMask mask, Transform ignoreTransform = null)
            {
                startPosition.y += 0.25f;
                range += 0.25f;
                raycastHit = default;

                RaycastHit hit;
                if (!GamePhysics.Trace(new Ray(startPosition, Vector3.down), 0f, out hit, range, mask, QueryTriggerInteraction.UseGlobal, null))
                    return false;

                if (ignoreTransform != null && hit.collider != null
                    && (hit.collider.transform == ignoreTransform || hit.collider.transform.IsChildOf(ignoreTransform)))
                {
                    return GetGroundInfo(startPosition - new Vector3(0f, 0.01f, 0f), out raycastHit, range, mask, ignoreTransform);
                }

                raycastHit = hit;
                return true;
            }
        }

        public static class DataFileUtil
        {
            private const string FOLDER = "CustomCrateSpawns";

            public static void EnsureFolderCreated()
            {
                string path = Path.Combine(Interface.Oxide.DataDirectory, FOLDER);

                if (!Directory.Exists(path))
                    Directory.CreateDirectory(path);
            }

            public static string GetFilePath(string filename = null)
            {
                if (filename == null)
                    filename = _plugin.Name;

                return Path.Combine(FOLDER, filename);
            }

            public static string[] GetAllFilePaths()
            {
                string[] filePaths = Interface.Oxide.DataFileSystem.GetFiles(FOLDER);
                for (int i = 0; i < filePaths.Length; i++)
                {
                    filePaths[i] = filePaths[i].Substring(0, filePaths[i].Length - 5);
                }

                return filePaths;
            }

            public static bool Exists(string filePath)
            {
                return Interface.Oxide.DataFileSystem.ExistsDatafile(filePath);
            }

            public static T Load<T>(string filePath) where T : class, new()
            {
                T data = Interface.Oxide.DataFileSystem.ReadObject<T>(filePath);
                if (data == null)
                    data = new T();

                return data;
            }

            public static T LoadIfExists<T>(string filePath) where T : class, new()
            {
                if (Exists(filePath))
                    return Load<T>(filePath);
                else
                    return null;
            }

            public static T LoadOrCreate<T>(string filePath) where T : class, new()
            {
                T data = LoadIfExists<T>(filePath);
                if (data == null)
                    data = new T();

                return data;
            }

            public static void Save<T>(string filePath, T data)
            {
                Interface.Oxide.DataFileSystem.WriteObject<T>(filePath, data);
            }

            public static void Delete(string filePath)
            {
                Interface.Oxide.DataFileSystem.DeleteDataFile(filePath);
            }
        }

        public static class DrawUtil
        {
            public static void Box(BasePlayer player, float durationSeconds, Color color, Vector3 position, float radius)
            {
                player.SendConsoleCommand("ddraw.box", durationSeconds, color, position, radius);
            }

            public static void Sphere(BasePlayer player, float durationSeconds, Color color, Vector3 position, float radius)
            {
                player.SendConsoleCommand("ddraw.sphere", durationSeconds, color, position, radius);
            }

            public static void Line(BasePlayer player, float durationSeconds, Color color, Vector3 fromPosition, Vector3 toPosition)
            {
                player.SendConsoleCommand("ddraw.line", durationSeconds, color, fromPosition, toPosition);
            }

            public static void Arrow(BasePlayer player, float durationSeconds, Color color, Vector3 fromPosition, Vector3 toPosition, float headSize)
            {
                player.SendConsoleCommand("ddraw.arrow", durationSeconds, color, fromPosition, toPosition, headSize);
            }

            public static void Text(BasePlayer player, float durationSeconds, Color color, Vector3 position, string text)
            {
                player.SendConsoleCommand("ddraw.text", durationSeconds, color, position, text);
            }
        }

        public static class PlayerUtil
        {
            public static BasePlayer FindById(ulong playerId)
            {
                return RelationshipManager.FindByID(playerId);
            }

            public static bool HasPlayerNearby(Vector3 position, float radius)
            {
                return BaseNetworkable.HasCloseConnections(position, radius);
            }
        }

        #endregion Helper Classes

        #region Permissions

        private static class PermissionUtil
        {
            public const string ADMIN = "customcratespawns.admin";
            private static readonly List<string> _permissions = new List<string>
            {
                ADMIN,
            };

            public static void RegisterPermissions()
            {
                foreach (var permission in _permissions)
                {
                    _plugin.permission.RegisterPermission(permission, _plugin);
                }
            }

            public static bool HasPermission(BasePlayer player, string permissionName)
            {
                return _plugin.permission.UserHasPermission(player.UserIDString, permissionName);
            }
        }

        #endregion Permissions

        #region Commands

        /// <summary>
        /// ccs.spawngroup <spawnGroupAlias>
        /// ccs.spawngroup edit <spawnGroupAlias>
        /// ccs.spawngroup remove
        /// ccs.spawngroup set <property> <value>
        /// - active
        /// - initialspawn
        /// - maxpop
        /// - minspawn
        /// - maxspawn
        /// - mindelay
        /// - maxdelay
        /// - randrot
        /// - replacelooted
        /// </summary>
        [ConsoleCommand("ccs.spawngroup")]
        private void cmdConsoleSpawnGroup(ConsoleSystem.Arg conArgs)
        {
            if (conArgs == null)
                return;

            BasePlayer player = conArgs.Player();
            if (player == null)
                return;

            if (!PermissionUtil.HasPermission(player, PermissionUtil.ADMIN))
            {
                ReplyToPlayer(player, Lang.NoPermission);
                return;
            }

            string[] args = conArgs.Args;
            if (args == null || args.Length == 0)
            {
                ReplyToPlayer(player, Lang.SpawnGroupUsage);
                return;
            }

            string subCommand = args[0];
            switch (subCommand)
            {
                case "create":
                    {
                        if (args.Length < 2)
                        {
                            ReplyToPlayer(player, Lang.SpawnGroupCreateUsage);
                            return;
                        }

                        string alias = args[1];
                        string filePath = DataFileUtil.GetFilePath(alias);
                        if (DataFileUtil.Exists(filePath))
                        {
                            ReplyToPlayer(player, Lang.SpawnGroupCreateAlreadyExists, alias);
                            return;
                        }

                        SpawnGroupData newGroup = new SpawnGroupData
                        {
                            Alias = alias,
                            Id = Guid.NewGuid(),
                            Active = true,
                            MaximumPopulation = 5,
                            MinimumNumberToSpawnPerTick = 1,
                            MaximumNumberToSpawnPerTick = 2,
                            InitialSpawn = true,
                            MinimumRespawnDelaySeconds = 30f,
                            MaximumRespawnDelaySeconds = 60f,
                            RandomizeYRotation = true,
                            ReplaceLootedContainers = true
                        };

                        DataFileUtil.Save(filePath, newGroup);
                        _spawnGroupBeingEdited = newGroup;

                        ReplyToPlayer(player, Lang.SpawnGroupCreated, alias);
                        break;
                    }
                case "edit":
                    {
                        if (args.Length < 2)
                        {
                            ReplyToPlayer(player, Lang.SpawnGroupEditUsage);
                            return;
                        }

                        string alias = args[1];
                        string filePath = DataFileUtil.GetFilePath(alias);
                        SpawnGroupData group = DataFileUtil.LoadIfExists<SpawnGroupData>(filePath);
                        if (group == null)
                        {
                            ReplyToPlayer(player, Lang.SpawnGroupNotFound, alias);
                            return;
                        }

                        LootSpawnerComponent lootSpawner = GetSpawnerByAlias(alias);
                        if (lootSpawner != null)
                            lootSpawner.Destroy();

                        _spawnGroupBeingEdited = group;
                        ReplyToPlayer(player, Lang.SpawnGroupEdited, alias);
                        VisualizeSpawnGroup(player);
                        break;
                    }
                case "remove":
                    {
                        if (_spawnGroupBeingEdited == null)
                        {
                            ReplyToPlayer(player, Lang.SpawnGroupNoGroupBeingEdited);
                            return;
                        }

                        string alias = _spawnGroupBeingEdited.Alias;
                        string filePath = DataFileUtil.GetFilePath(alias);
                        DataFileUtil.Delete(filePath);
                        _spawnGroupBeingEdited = null;

                        ReplyToPlayer(player, Lang.SpawnGroupRemoved, alias);
                        break;
                    }
                case "set":
                    {
                        if (_spawnGroupBeingEdited == null)
                        {
                            ReplyToPlayer(player, Lang.SpawnGroupSetNoGroupBeingEdited);
                            return;
                        }

                        if (args.Length < 3)
                        {
                            string editableProperties = string.Join("\n", new[]
                            {
                                "- active",
                                "- initialspawn",
                                "- maxpop",
                                "- minspawn",
                                "- maxspawn",
                                "- mindelay",
                                "- maxdelay",
                                "- randrot",
                                "- replacelooted"
                            });
                            ReplyToPlayer(player, Lang.SpawnGroupSetUsage, editableProperties);
                            return;
                        }

                        string property = args[1].ToLower();
                        string value = args[2];
                        bool isValueValid = true;

                        switch (property)
                        {
                            case "active":
                                if (bool.TryParse(value, out bool active))
                                {
                                    _spawnGroupBeingEdited.Active = active;
                                }
                                else
                                {
                                    ReplyToPlayer(player, Lang.SpawnGroupSetInvalidActive);
                                    isValueValid = false;
                                }
                                break;

                            case "initialspawn":
                                if (bool.TryParse(value, out bool initialSpawn))
                                {
                                    _spawnGroupBeingEdited.InitialSpawn = initialSpawn;
                                }
                                else
                                {
                                    ReplyToPlayer(player, Lang.SpawnGroupSetInvalidInitialSpawn);
                                    isValueValid = false;
                                }
                                break;

                            case "maxpop":
                                if (int.TryParse(value, out int maxPopulation) && maxPopulation >= 0)
                                {
                                    _spawnGroupBeingEdited.MaximumPopulation = maxPopulation;
                                }
                                else
                                {
                                    ReplyToPlayer(player, Lang.SpawnGroupSetInvalidMaximumPop);
                                    isValueValid = false;
                                }
                                break;

                            case "minspawn":
                                if (int.TryParse(value, out int minSpawn) && minSpawn >= 0)
                                {
                                    _spawnGroupBeingEdited.MinimumNumberToSpawnPerTick = minSpawn;
                                }
                                else
                                {
                                    ReplyToPlayer(player, Lang.SpawnGroupSetInvalidMinimumSpawn);
                                    isValueValid = false;
                                }
                                break;

                            case "maxspawn":
                                if (int.TryParse(value, out int maxSpawn) && maxSpawn >= _spawnGroupBeingEdited.MinimumNumberToSpawnPerTick)
                                {
                                    _spawnGroupBeingEdited.MaximumNumberToSpawnPerTick = maxSpawn;
                                }
                                else
                                {
                                    ReplyToPlayer(player, Lang.SpawnGroupSetInvalidMaximumSpawn);
                                    isValueValid = false;
                                }
                                break;

                            case "mindelay":
                                if (float.TryParse(value, out float minDelay) && minDelay > 0)
                                {
                                    _spawnGroupBeingEdited.MinimumRespawnDelaySeconds = minDelay;
                                }
                                else
                                {
                                    ReplyToPlayer(player, Lang.SpawnGroupSetInvalidMinimumDelay);
                                    isValueValid = false;
                                }
                                break;

                            case "maxdelay":
                                if (float.TryParse(value, out float maxDelay) && maxDelay >= _spawnGroupBeingEdited.MinimumRespawnDelaySeconds)
                                {
                                    _spawnGroupBeingEdited.MaximumRespawnDelaySeconds = maxDelay;
                                }
                                else
                                {
                                    ReplyToPlayer(player, Lang.SpawnGroupSetInvalidMaximumDelay);
                                    isValueValid = false;
                                }
                                break;

                            case "randrot":
                                if (bool.TryParse(value, out bool randomizeYRotation))
                                {
                                    _spawnGroupBeingEdited.RandomizeYRotation = randomizeYRotation;
                                }
                                else
                                {
                                    ReplyToPlayer(player, Lang.SpawnGroupSetInvalidRandomYRotation);
                                    isValueValid = false;
                                }
                                break;

                            case "replacelooted":
                                if (bool.TryParse(value, out bool replaceLootContainers))
                                {
                                    _spawnGroupBeingEdited.ReplaceLootedContainers = replaceLootContainers;
                                }
                                else
                                {
                                    ReplyToPlayer(player, Lang.SpawnGroupSetInvalidReplaceLootedContainers);
                                    isValueValid = false;
                                }
                                break;

                            default:
                                string editableProperties = string.Join("\n", new[]
                                          {
                                            "- active",
                                            "- initialspawn",
                                            "- maxpop",
                                            "- minspawn",
                                            "- maxspawn",
                                            "- mindelay",
                                            "- maxdelay",
                                            "- randrot",
                                            "- replacelooted"
                                        });
                                ReplyToPlayer(player, Lang.SpawnGroupSetUnknownProperty, property, editableProperties);
                                return;
                        }

                        if (isValueValid)
                        {
                            DataFileUtil.Save(DataFileUtil.GetFilePath(_spawnGroupBeingEdited.Alias), _spawnGroupBeingEdited);
                            ReplyToPlayer(player, Lang.SpawnGroupSetPropertyUpdated, property, value, _spawnGroupBeingEdited.Alias);
                            VisualizeSpawnGroup(player);
                        }
                        break;
                    }
                case "done":
                    {
                        if (_spawnGroupBeingEdited == null)
                        {
                            ReplyToPlayer(player, Lang.SpawnGroupDoneNoGroupBeingEdited);
                            return;
                        }

                        string alias = _spawnGroupBeingEdited.Alias;
                        string filePath = DataFileUtil.GetFilePath(alias);

                        DataFileUtil.Save(filePath, _spawnGroupBeingEdited);

                        LootSpawnerComponent.Create(_spawnGroupBeingEdited);

                        _spawnGroupBeingEdited = null;
                        ReplyToPlayer(player, Lang.SpawnGroupDoneFinishedEditing, alias);
                        break;
                    }
                default:
                    ReplyToPlayer(player, Lang.SpawnGroupUnknownCommand, subCommand);
                    break;
            }
        }
        
        /// <summary>
        /// ccs.spawnpoint add <position> [radius]
        /// ccs.spawnpoint remove [spawnPointId]
        /// </summary>
        [ConsoleCommand("ccs.spawnpoint
// ... [Code preview truncated due to length. Full source in attached file] ...

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