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

FREE CustomWoundedNPC : 1.0.5 1.0.5

Нет прав для скачивания
C#:
using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using Rust.Ai.Gen2;
using UnityEngine;
using UnityEngine.AI;

namespace Oxide.Plugins
{
    [Info("Custom Wounded NPC", "Razor", "1.0.5")]
    [Description("Makes NPCs enter a configurable wounded state before recovering or dying.")]
    public class CustomWoundedNPC : RustPlugin
    {
        #region Things

        private const string WoundedScreamEffect = "assets/bundled/prefabs/fx/player/beartrap_scream.prefab";
        private ConfigData configData;

        #endregion

        #region Config

        private class ConfigData
        {
            [JsonProperty(PropertyName = "Settings")]
            public Settings settings { get; set; }

            [JsonProperty(PropertyName = "NPC settings by ShortPrefabName")]
            public Dictionary<string, NpcSettings> Npcs { get; set; }

            public class Settings
            {
                [JsonProperty(PropertyName = "Excluded NPC prefab paths (exact match)")]
                public List<string> ExcludedPrefabPaths { get; set; }

                [JsonProperty(PropertyName = "Excluded NPC partial names (case-insensitive)")]
                public List<string> ExcludedPartialNames { get; set; }
            }

            public class NpcSettings
            {
                [JsonProperty(PropertyName = "Can become wounded")]
                public bool CanBecomeWounded { get; set; }

                [JsonProperty(PropertyName = "Chance to become wounded (0-100)")]
                public float WoundedChance { get; set; }

                [JsonProperty(PropertyName = "Allow becoming wounded from headshots")]
                public bool AllowHeadshotWounding { get; set; }

                [JsonProperty(PropertyName = "Wounded position downward offset")]
                public float DownwardOffset { get; set; }

                [JsonProperty(PropertyName = "Chance to get back up (0-100)")]
                public float RecoveryChance { get; set; }

                [JsonProperty(PropertyName = "Wounded duration in seconds")]
                public float WoundedDuration { get; set; }

                [JsonProperty(PropertyName = "Health after getting back up")]
                public float RecoveryHealth { get; set; }

                [JsonProperty(PropertyName = "Play Rust's built-in wounded scream effect")]
                public bool PlayScreamEffect { get; set; }

                [JsonProperty(PropertyName = "Number of times to play the scream effect")]
                public int ScreamPlayCount { get; set; }

                [JsonProperty(PropertyName = "Delay between scream effects in seconds")]
                public float ScreamRepeatDelay { get; set; }
            }
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            configData = Config.ReadObject<ConfigData>();
            ValidateConfig();
            Config.WriteObject(configData, true);
        }

        protected override void LoadDefaultConfig()
        {
            configData = GetBaseConfig();
        }

        private ConfigData GetBaseConfig()
        {
            return new ConfigData
            {
                settings = new ConfigData.Settings
                {
                    ExcludedPrefabPaths = new List<string>(),
                    ExcludedPartialNames = new List<string> { "zombie" }
                },
                Npcs = new Dictionary<string, ConfigData.NpcSettings>()
            };
        }

        private ConfigData.NpcSettings GetDefaultNpcSettings()
        {
            return new ConfigData.NpcSettings
            {
                CanBecomeWounded = true,
                WoundedChance = 25f,
                AllowHeadshotWounding = false,
                DownwardOffset = 0f,
                RecoveryChance = 50f,
                WoundedDuration = 30f,
                RecoveryHealth = 25f,
                PlayScreamEffect = true,
                ScreamPlayCount = 3,
                ScreamRepeatDelay = 10f
            };
        }

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

        private void ValidateConfig()
        {
            if (configData == null)
                configData = GetBaseConfig();
            if (configData.settings == null)
                configData.settings = GetBaseConfig().settings;
            if (configData.settings.ExcludedPrefabPaths == null)
                configData.settings.ExcludedPrefabPaths = new List<string>();
            if (configData.settings.ExcludedPartialNames == null)
                configData.settings.ExcludedPartialNames = new List<string>();
            if (configData.Npcs == null)
                configData.Npcs = new Dictionary<string, ConfigData.NpcSettings>();
            foreach (var entry in configData.Npcs)
            {
                if (entry.Value == null)
                    continue;
                entry.Value.WoundedChance = Mathf.Clamp(entry.Value.WoundedChance, 0f, 100f);
                entry.Value.DownwardOffset = Mathf.Clamp(entry.Value.DownwardOffset, 0f, 2f);
                entry.Value.RecoveryChance = Mathf.Clamp(entry.Value.RecoveryChance, 0f, 100f);
                entry.Value.WoundedDuration = Mathf.Max(0.1f, entry.Value.WoundedDuration);
                entry.Value.RecoveryHealth = Mathf.Max(1f, entry.Value.RecoveryHealth);
                entry.Value.ScreamPlayCount = Mathf.Max(0, entry.Value.ScreamPlayCount);
                entry.Value.ScreamRepeatDelay = Mathf.Max(0.1f, entry.Value.ScreamRepeatDelay);
            }
        }

        #endregion

        #region Hooks

        private void OnServerInitialized()
        {
            AddKnownNpcSettings();
        }

        private object OnEntityTakeDamage(BasePlayer player, HitInfo info)
        {
            if (!IsEligibleNpc(player) || info == null || IsExcluded(player))
                return null;
            var npcSettings = GetNpcSettings(player);
            if (npcSettings == null)
                return null;
            if (!npcSettings.CanBecomeWounded)
                return null;
            if (info.isHeadshot && !npcSettings.AllowHeadshotWounding)
                return null;
            var controller = player.GetComponent<WoundedNpcController>();
            if (controller != null)
            {
                if (info.damageTypes.Total() >= player.health)
                    controller.PrepareDeath();
                return null;
            }
            if (info.damageTypes.Total() < player.health)
                return null;
            if (UnityEngine.Random.Range(0f, 100f) >= npcSettings.WoundedChance)
                return null;
            info.damageTypes.Clear();
            player.health = 1f;
            player.metabolism.bleeding.value = 0f;
            player.healingWhileCrawling = 0f;
            player.SetPlayerFlag(BasePlayer.PlayerFlags.Wounded, true);
            player.SetFlagLocal(BaseEntity.Flags.Busy, true);
            player.SetServerFall(true);
            player.lastWoundedStartTime = Time.realtimeSinceStartup;
            player.CancelInvoke(player.WoundingTick);
            player.SendNetworkUpdateImmediate();
            var willRecover = UnityEngine.Random.Range(0f, 100f) < npcSettings.RecoveryChance;
            controller = player.gameObject.AddComponent<WoundedNpcController>();
            controller.Initialize(player, npcSettings.WoundedDuration, willRecover, npcSettings.RecoveryHealth, npcSettings.DownwardOffset, npcSettings.PlayScreamEffect, npcSettings.ScreamPlayCount, npcSettings.ScreamRepeatDelay);
            return true;
        }

        private object OnBotReSpawnNPCTarget(ScientistNPC npc, BasePlayer player)
        {
            if (npc == null || npc.GetComponent<WoundedNpcController>() == null)
                return null;
            return true;
        }

        #endregion

        #region NPC Handling

        private bool IsEligibleNpc(BasePlayer player)
        {
            if (player == null || player.IsDestroyed || player.IsDead())
                return false;
            if (player.isMounted)
                return false;
            if (IsSafeZoneNpc(player))
                return false;
            return player.IsNpc || !player.userID.IsSteamId();
        }

        private bool IsSafeZoneNpc(BasePlayer player)
        {
            return player is NPCTalking || player is NPCShopKeeper || player is BanditGuard;
        }

        private bool IsExcluded(BasePlayer player)
        {
            var prefabPath = player.PrefabName ?? string.Empty;
            foreach (var excludedPrefab in configData.settings.ExcludedPrefabPaths)
            {
                if (string.Equals(prefabPath, excludedPrefab, StringComparison.OrdinalIgnoreCase))
                    return true;
            }
            var displayName = player.displayName ?? string.Empty;
            var shortPrefabName = player.ShortPrefabName ?? string.Empty;
            var typeName = player.GetType().Name;
            foreach (var partialName in configData.settings.ExcludedPartialNames)
            {
                if (displayName.IndexOf(partialName, StringComparison.OrdinalIgnoreCase) >= 0 || shortPrefabName.IndexOf(partialName, StringComparison.OrdinalIgnoreCase) >= 0 || typeName.IndexOf(partialName, StringComparison.OrdinalIgnoreCase) >= 0)
                    return true;
            }
            return false;
        }

        private void AddKnownNpcSettings()
        {
            var changed = false;
            foreach (var prefabPath in GameManifest.Current.entities)
            {
                var prefab = GameManager.server.FindPrefab(prefabPath);
                if (prefab == null)
                    continue;
                var player = prefab.GetComponent<BasePlayer>();
                if (player == null || player.GetType() == typeof(BasePlayer) && !player.IsNpc)
                    continue;
                if (IsSafeZoneNpc(player))
                    continue;
                var shortPrefabName = player.ShortPrefabName;
                if (string.IsNullOrWhiteSpace(shortPrefabName))
                    shortPrefabName = Path.GetFileNameWithoutExtension(prefabPath);
                if (configData.Npcs.ContainsKey(shortPrefabName))
                    continue;
                configData.Npcs[shortPrefabName] = GetDefaultNpcSettings();
                changed = true;
            }
            if (changed)
                SaveConfig();
        }

        private ConfigData.NpcSettings GetNpcSettings(BasePlayer player)
        {
            var shortPrefabName = player.ShortPrefabName;
            if (string.IsNullOrWhiteSpace(shortPrefabName))
                shortPrefabName = player.GetType().Name;
            ConfigData.NpcSettings npcSettings;
            if (configData.Npcs.TryGetValue(shortPrefabName, out npcSettings))
                return npcSettings;
            return null;
        }

        #endregion

        #region Wounded Controller

        private class WoundedNpcController : FacepunchBehaviour
        {
            private BasePlayer player;
            private bool willRecover;
            private float recoveryHealth;
            private float downwardOffset;
            private BaseAIBrain brain;
            private NpcShootingComponent shootingComponent;
            private Scientist2FSM scientistFsm;
            private NavMeshAgent navMeshAgent;
            private ItemId activeItemId;
            private bool brainWasEnabled;
            private bool shootingWasEnabled;
            private bool scientistFsmWasEnabled;
            private bool navMeshAgentWasEnabled;
            private bool positionLowered;
            private bool finishing;
            private bool playScreamEffect;
            private int screamPlaysRemaining;
            private float screamRepeatDelay;

            public void Initialize(BasePlayer player, float woundedDuration, bool willRecover, float recoveryHealth, float downwardOffset, bool playScreamEffect, int screamPlayCount, float screamRepeatDelay)
            {
                this.player = player;
                this.willRecover = willRecover;
                this.recoveryHealth = recoveryHealth;
                this.downwardOffset = downwardOffset;
                this.playScreamEffect = playScreamEffect;
                screamPlaysRemaining = screamPlayCount;
                this.screamRepeatDelay = screamRepeatDelay;
                brain = player.GetComponent<BaseAIBrain>();
                shootingComponent = player.GetComponent<NpcShootingComponent>();
                scientistFsm = player.GetComponent<Scientist2FSM>();
                navMeshAgent = player.GetComponent<NavMeshAgent>();
                ClearNpcTargetAndMemory();
                var activeItem = player.GetActiveItem();
                if (activeItem != null)
                    activeItemId = activeItem.uid;
                if (brain != null)
                {
                    brainWasEnabled = brain.enabled;
                    brain.enabled = false;
                }
                if (shootingComponent != null)
                {
                    shootingWasEnabled = shootingComponent.enabled;
                    shootingComponent.enabled = false;
                }
                if (scientistFsm != null)
                {
                    scientistFsmWasEnabled = scientistFsm.enabled;
                    scientistFsm.enabled = false;
                }
                if (navMeshAgent != null)
                {
                    navMeshAgentWasEnabled = navMeshAgent.enabled;
                    navMeshAgent.enabled = false;
                }

                BaseNavigator _navigator = player.GetComponent<BaseNavigator>();
                if (_navigator != null)
                    _navigator.SetNavMeshEnabled(false);

                var heldEntity = player.GetHeldEntity();
                if (heldEntity != null)
                {
                    heldEntity.CancelInvoke();
                    heldEntity.SetHeld(false);
                }
                player.UpdateActiveItem(default(ItemId));
                InvokeRepeating(KeepNpcDisabled, 0.1f, 0.1f);
                if (downwardOffset > 0f)
                {
                    player.transform.position += Vector3.down * downwardOffset;
                    player.SendNetworkUpdate_Position();
                    positionLowered = true;
                }
                PlayScreamEffect();
                if (!willRecover)
                    Invoke(SetIncapacitated, Mathf.Max(0f, woundedDuration - 10f));
                Invoke(FinishWoundedState, woundedDuration);
            }

            private void KeepNpcDisabled()
            {
                if (player == null || player.IsDestroyed || player.IsDead() || !player.IsWounded())
                    return;
                if (brain != null && brain.enabled)
                    brain.enabled = false;
                if (shootingComponent != null && shootingComponent.enabled)
                    shootingComponent.enabled = false;
                if (scientistFsm != null && scientistFsm.enabled)
                    scientistFsm.enabled = false;
                if (navMeshAgent != null && navMeshAgent.enabled)
                    navMeshAgent.enabled = false;
                ClearNpcTargetAndMemory();
                var heldEntity = player.GetHeldEntity();
                if (heldEntity == null)
                    return;
                heldEntity.CancelInvoke();
                heldEntity.SetHeld(false);
                player.UpdateActiveItem(default(ItemId));
                player.SendNetworkUpdateImmediate();
            }

            private void ClearNpcTargetAndMemory()
            {
                if (brain == null)
                    return;
                if (brain.Events != null && brain.Events.Memory != null)
                    brain.Events.Memory.Clear();
                if (brain.Senses == null || brain.Senses.Memory == null)
                    return;
                var memory = brain.Senses.Memory;
                memory.All.Clear();
                memory.Players.Clear();
                memory.LOS.Clear();
                memory.Targets.Clear();
                memory.Threats.Clear();
                memory.Friendlies.Clear();
            }

            private void PlayScreamEffect()
            {
                if (!playScreamEffect || screamPlaysRemaining <= 0 || player == null || player.IsDestroyed || player.IsDead() || !player.IsWounded())
                    return;
                Effect.server.Run(WoundedScreamEffect, player, 0, Vector3.zero, Vector3.zero, null, true);
                screamPlaysRemaining--;
                if (screamPlaysRemaining > 0)
                    Invoke(PlayScreamEffect, screamRepeatDelay);
            }

            public void PrepareDeath()
            {
                if (finishing)
                    return;
                finishing = true;
                if (positionLowered)
                    RestorePosition();
            }

            private void SetIncapacitated()
            {
                if (player == null || player.IsDestroyed || player.IsDead() || !player.IsWounded())
                    return;
                player.SetPlayerFlag(BasePlayer.PlayerFlags.Incapacitated, true);
                player.SendNetworkUpdateImmediate();
            }

            private void FinishWoundedState()
            {
                if (player == null || player.IsDestroyed || player.IsDead() || !player.IsWounded())
                {
                    Destroy(this);
                    return;
                }
                if (!willRecover)
                {
                    PrepareDeath();
                    player.DieInstantly();
                    Destroy(this);
                    return;
                }
                finishing = true;
                player.StopWounded();
                player.SetPlayerFlag(BasePlayer.PlayerFlags.Incapacitated, false);
                player.SetFlagLocal(BaseEntity.Flags.Busy, false);
                player.ResetWoundingVars();
                player.health = Mathf.Max(1f, recoveryHealth);
                if (positionLowered)
                    RestorePosition();
                player.SendNetworkUpdateImmediate();
                Invoke(FinishRecovery, 0.1f);
            }

            private void FinishRecovery()
            {
                if (player == null || player.IsDestroyed || player.IsDead())
                {
                    Destroy(this);
                    return;
                }
                RestoreNpc();
                player.SendNetworkUpdateImmediate();
                Destroy(this);
            }

            private void RestorePosition()
            {
                if (player == null || player.IsDestroyed || !positionLowered)
                    return;
                player.transform.position += Vector3.up * downwardOffset;
                player.SendNetworkUpdate_Position();
                positionLowered = false;
            }

            private void RestoreNpc()
            {
                RestoreNavMeshAgent();
                RestoreActiveItem();
                if (brain != null)
                    brain.enabled = brainWasEnabled;
                if (shootingComponent != null)
                    shootingComponent.enabled = shootingWasEnabled;
                if (scientistFsm != null)
                    scientistFsm.enabled = scientistFsmWasEnabled;
            }

            private void RestoreNavMeshAgent()
            {
                BaseNavigator _navigator = player.GetComponent<BaseNavigator>();
                if (_navigator != null)
                    _navigator.SetNavMeshEnabled(true);

                if (navMeshAgent == null || !navMeshAgentWasEnabled)
                    return;
                NavMeshHit navMeshHit;
                if (NavMesh.SamplePosition(player.transform.position, out navMeshHit, 2f, NavMesh.AllAreas))
                {
                    player.transform.position = navMeshHit.position;
                    player.SendNetworkUpdate_Position();
                }
                navMeshAgent.enabled = true;
                navMeshAgent.Warp(player.transform.position);
            }

            private void RestoreActiveItem()
            {
                if (activeItemId == default(ItemId) || player.inventory == null || player.inventory.containerBelt == null)
                    return;
                var activeItem = player.inventory.containerBelt.FindItemByUID(activeItemId);
                if (activeItem == null)
                    return;
                player.svActiveItemID = activeItem.uid;
                var heldEntity = activeItem.GetHeldEntity() as HeldEntity;
                if (heldEntity != null)
                    heldEntity.SetHeld(true);
                player.inventory.UpdatedVisibleHolsteredItems();
                player.SendNetworkUpdateImmediate();
            }

            private void OnDestroy()
            {
                CancelInvoke();
                if (player == null || player.IsDestroyed || player.IsDead())
                    return;
                if (positionLowered)
                    RestorePosition();
                if (finishing)
                    return;
                player.StopWounded();
                player.SetPlayerFlag(BasePlayer.PlayerFlags.Incapacitated, false);
                player.SetFlagLocal(BaseEntity.Flags.Busy, false);
                player.ResetWoundingVars();
                RestoreNpc();
                player.SendNetworkUpdateImmediate();
            }
        }

        #endregion  
    }
}

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