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

FREE HauntedDoors : 1.8.2 1.8.2

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

namespace Oxide.Plugins
{
    [Info("HauntedDoors", "RustFlash", "1.8.2")]
    [Description("Spawns scary ghosts when opening double doors")]
    public class HauntedDoors : RustPlugin
    {
        #region Configuration

        private Configuration config;

        public class Configuration
        {
            [JsonProperty("Ghost spawn chance (0.0 - 1.0)")]
            public float SpawnChance { get; set; } = 0.15f;

            [JsonProperty("Ghost visibility duration (seconds)")]
            public float GhostDuration { get; set; } = 4f;

            [JsonProperty("Play sound effects")]
            public bool EnableSounds { get; set; } = true;

            [JsonProperty("Show visual effects (smoke/fog)")]
            public bool EnableVisualEffects { get; set; } = true;

            [JsonProperty("Allow single doors to trigger ghosts")]
            public bool SingleDoors { get; set; } = true;

            [JsonProperty("Allow double doors to trigger ghosts")]
            public bool DoubleDoors { get; set; } = true;
        }

        #endregion

        #region Fields

        private readonly Dictionary<BaseEntity, string> ghostDespawnSounds = new Dictionary<BaseEntity, string>();
        private readonly HashSet<BaseEntity> ghostEntities = new HashSet<BaseEntity>();
        private readonly System.Random random = new System.Random();

        private readonly LayerMask groundRaycastMask = LayerMask.GetMask("Terrain", "World", "Construction");

        private static readonly HashSet<string> singleDoorShortNames = new HashSet<string>
        {
            "door.hinged.wood",
            "door.hinged.metal",
            "door.hinged.toptier"
        };

        private static readonly HashSet<string> doubleDoorShortNames = new HashSet<string>
        {
            "door.double.hinged.wood",
            "door.double.hinged.metal",
            "door.double.hinged.toptier"
        };

        private readonly List<GhostType> ghostTypes = new List<GhostType>
        {
            new GhostType
            {
                Prefab = "assets/prefabs/misc/halloween/scarecrow/scarecrow.deployed.prefab",
                SpawnSound = "assets/prefabs/npc/patrol helicopter/effects/rocket_fire.prefab",
                DespawnSound = "assets/bundled/prefabs/fx/missing.prefab"
            },
            new GhostType
            {
                Prefab = "assets/rust.ai/agents/zombie/zombie.prefab",
                SpawnSound = "assets/bundled/prefabs/fx/missing.prefab",
                DespawnSound = "assets/prefabs/locks/keypad/effects/lock.code.denied.prefab"
            }
        };

        private readonly List<string> ghostEffects = new List<string>
        {
            "assets/bundled/prefabs/fx/explosions/explosion_03.prefab",
            "assets/prefabs/npc/patrol helicopter/effects/heli_explosion.prefab",
            "assets/bundled/prefabs/fx/explosions/explosion_02.prefab"
        };

        private class GhostType
        {
            public string Prefab { get; set; }
            public string SpawnSound { get; set; }
            public string DespawnSound { get; set; }
        }

        #endregion

        #region Hooks

        private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info)
        {
            if (!(entity is BasePlayer))
                return null;

            if (!ghostEntities.Contains(entity))
                return null;

            return true;
        }

        #endregion

        #region Initialization

        private void Init()
        {
            Unsubscribe(nameof(OnEntityTakeDamage));
        }

        protected override void LoadDefaultConfig()
        {
            config = new Configuration();
            SaveConfig();
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                config = Config.ReadObject<Configuration>();
                if (config == null)
                {
                    PrintError("Configuration file is empty! Using default values for this session.");
                    config = new Configuration();
                }
            }
            catch
            {
                PrintError("Configuration file is corrupt! Using default values for this session.");
                config = new Configuration();
            }
        }

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

        private void Unload()
        {
            var ghosts = Pool.Get<List<BaseEntity>>();
            ghosts.AddRange(ghostEntities);

            foreach (var ghost in ghosts)
            {
                if (ghost != null && !ghost.IsDestroyed)
                    ghost.Kill();
            }

            Pool.FreeUnmanaged(ref ghosts);

            ghostEntities.Clear();
            ghostDespawnSounds.Clear();
        }

        #endregion

        #region Door Events

        private void OnDoorOpened(Door door, BasePlayer player)
        {
            if (door == null || player == null || player.IsNpc)
                return;

            if (!IsEligibleDoor(door))
                return;

            if (random.NextDouble() > config.SpawnChance)
                return;

            SpawnGhost(door, player);
        }

        #endregion

        #region Core Methods

        private bool IsEligibleDoor(Door door)
        {
            if (door?.ShortPrefabName == null)
                return false;

            if (config.DoubleDoors && doubleDoorShortNames.Contains(door.ShortPrefabName))
                return true;

            if (config.SingleDoors && singleDoorShortNames.Contains(door.ShortPrefabName))
                return true;

            return false;
        }

        private void SpawnGhost(Door door, BasePlayer player)
        {
            if (ghostTypes.Count == 0)
            {
                PrintError("No ghost types configured!");
                return;
            }

            GhostType selectedGhost = ghostTypes[random.Next(0, ghostTypes.Count)];
            Vector3 spawnPos = CalculateGhostSpawnPosition(player);

            ApplyGhostEffects(spawnPos, selectedGhost.SpawnSound);

            timer.Once(0.5f, () =>
            {
                if (player == null || player.IsDestroyed)
                    return;

                BaseEntity ghost = GameManager.server.CreateEntity(selectedGhost.Prefab, spawnPos);
                if (ghost == null)
                {
                    PrintError($"Failed to create ghost entity: {selectedGhost.Prefab}");
                    return;
                }

                Vector3 direction = (player.transform.position - spawnPos).normalized;
                if (direction != Vector3.zero)
                    ghost.transform.rotation = Quaternion.LookRotation(direction);

                ghost.EnableSaving(false);
                ghost.Spawn();

                if (ghostEntities.Count == 0)
                    Subscribe(nameof(OnEntityTakeDamage));

                ghostEntities.Add(ghost);
                ghostDespawnSounds[ghost] = selectedGhost.DespawnSound;

                timer.Once(0.1f, () =>
                {
                    if (ghost != null && !ghost.IsDestroyed)
                    {
                        ConfigureGhost(ghost, player);
                    }
                });

                timer.Once(config.GhostDuration, () => DespawnGhost(ghost));
            });
        }

        private void ConfigureGhost(BaseEntity ghost, BasePlayer player)
        {
            if (ghost is NPCPlayer npc)
            {
                npc.InitializeHealth(999999f, 999999f);
                npc.startHealth = 999999f;
                npc.lifestate = BaseCombatEntity.LifeState.Alive;

                var navigator = npc.GetComponent<NPCPlayerNavigator>();
                if (navigator != null)
                    navigator.enabled = false;

                var brain = npc.GetComponent<BaseAIBrain>();
                if (brain != null)
                    brain.enabled = false;

                npc.SetDestination(npc.transform.position);
            }
            else if (ghost is ScarecrowNPC scarecrow)
            {
                scarecrow.InitializeHealth(999999f, 999999f);
                scarecrow.startHealth = 999999f;
                scarecrow.lifestate = BaseCombatEntity.LifeState.Alive;

                var brain = scarecrow.GetComponent<ScarecrowBrain>();
                if (brain != null)
                    brain.enabled = false;
            }
            else
            {
                if (ghost is BaseCombatEntity combatEntity)
                {
                    combatEntity.InitializeHealth(999999f, 999999f);
                    combatEntity.startHealth = 999999f;
                    combatEntity.lifestate = BaseCombatEntity.LifeState.Alive;
                }

                if (player != null && !player.IsDestroyed)
                {
                    Vector3 direction = (player.transform.position - ghost.transform.position).normalized;
                    if (direction != Vector3.zero)
                        ghost.transform.rotation = Quaternion.LookRotation(direction);
                }
            }
        }

        private void DespawnGhost(BaseEntity ghost)
        {
            if (ghost == null || ghost.IsDestroyed)
                return;

            if (ghostDespawnSounds.Remove(ghost, out var despawnSound))
            {
                ApplyGhostEffects(ghost.transform.position, despawnSound);
            }

            ghostEntities.Remove(ghost);
            if (ghostEntities.Count == 0)
                Unsubscribe(nameof(OnEntityTakeDamage));

            ghost.Kill();
        }

        #endregion

        #region Effects

        private void ApplyGhostEffects(Vector3 position, string sound)
        {
            if (config.EnableSounds && !string.IsNullOrEmpty(sound))
            {
                Effect.server.Run(sound, position);
            }

            string randomEffect = GetRandomEffect();
            if (config.EnableVisualEffects && randomEffect != null)
            {
                Effect.server.Run(randomEffect, position);
            }
        }

        private string GetRandomEffect()
        {
            if (ghostEffects.Count == 0)
                return null;

            return ghostEffects[random.Next(0, ghostEffects.Count)];
        }

        #endregion

        #region Helper Methods

        private Vector3 CalculateGhostSpawnPosition(BasePlayer player)
        {
            Vector3 playerForward = player.eyes.HeadForward();
            Vector3 spawnPos = player.transform.position + (playerForward * 2f);

            RaycastHit hit;
            if (Physics.Raycast(new Ray(spawnPos + Vector3.up * 2f, Vector3.down), out hit, 4f, groundRaycastMask))
            {
                spawnPos.y = hit.point.y;
            }
            else
            {
                spawnPos.y = player.transform.position.y;
            }

            return spawnPos;
        }

        #endregion
    }
}

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