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

FREE CustomNPC : 2.0.5 2.0.5

Нет прав для скачивания
C#:
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Oxide.Core.Plugins;
using Rust;
using Rust.Ai;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.AI;
using Random = UnityEngine.Random;
namespace Oxide.Plugins {
    using CustomNPCEx;
    [Info("CustomNPC", "k1lly0u", "2.0.5")]
    public class CustomNPC : RustPlugin, ICustomNPCPlugin {
#region Fields
        [PluginReference]
        private Plugin Kits;
        private static Hash<Plugin, List<CustomScientistNPC>> _npcLookup;
        private static CustomNPC Instance;
#endregion
#region Oxide Hooks
        private void Loaded() {
            Instance = this;
            _npcLookup = new Hash<Plugin, List<CustomScientistNPC>>();
        }
        private void OnEntityKill(CustomScientistNPC customNpc) {
            if (customNpc == null || customNpc.Plugin == null) return;
            _npcLookup[customNpc.Plugin].Remove(customNpc);
        }
        private void OnEntityTakeDamage(CustomScientistNPC customNpc, HitInfo hitInfo) {
            if (customNpc == null || hitInfo == null || hitInfo.InitiatorPlayer == null) return;
            CustomScientistNPC initiator = hitInfo.InitiatorPlayer as CustomScientistNPC;
            if (initiator != null && customNpc.Settings.PreventFriendlyFire) {
                if (customNpc.Plugin == initiator.Plugin) {
                    hitInfo.damageTypes.Clear();
                    hitInfo.HitEntity = null;
                    hitInfo.HitMaterial = 0;
                    hitInfo.PointStart = Vector3.zero;
                }
            }
        }
        private object OnNpcTarget(CustomScientistNPC customNpc, BasePlayer target) {
            if (customNpc == null) return null;
            if (target.IsSleeping() || target.IsFlying) return true;
            return null;
        }
        private object CanLootPlayer(CustomScientistNPC customNpc, BasePlayer player) => customNpc != null && customNpc.IsWounded() ? (object)false : null;
        private object OnPlayerAssist(CustomScientistNPC customNpc, BasePlayer player) => false;
        private void OnPluginUnloaded(Plugin plugin) {
            List<CustomScientistNPC> list;
            if (_npcLookup.TryGetValue(plugin, out list)) {
                for (int i = list.Count - 1; i >= 0; i--) {
                    CustomScientistNPC customNpc = list[i];
                    if (customNpc != null) customNpc.Kill(BaseNetworkable.DestroyMode.None);
                }
                _npcLookup.Remove(plugin);
            }
        }
        private void Unload() {
            foreach (KeyValuePair<Plugin, List<CustomScientistNPC>> kvp in _npcLookup) {
                for (int i = kvp.Value.Count - 1; i >= 0; i--) {
                    CustomScientistNPC customNpc = kvp.Value[i];
                    if (customNpc != null) customNpc.Kill(BaseNetworkable.DestroyMode.None);
                }
            }
            _npcLookup.Clear();
            _npcLookup = null;
            Instance = null;
        }
#endregion
#region Functions
        public static void CopySerializeableFields<T>(T src, T dst) {
            FieldInfo[] srcFields = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
            foreach (FieldInfo field in srcFields) {
                object value = field.GetValue(src);
                field.SetValue(dst, value);
            }
        }
        private static bool IsInSafeZone(Vector3 position) {
            int count = Physics.OverlapSphereNonAlloc(position, 1f, Vis.colBuffer, 1 << 18, QueryTriggerInteraction.Collide);
            for (int i = 0; i < count; i++) {
                Collider collider = Vis.colBuffer[i];
                if (collider.GetComponent<TriggerSafeZone>()) return true;
            }
            return false;
        }
#endregion
#region API
        public static CustomScientistNPC SpawnNPC(Plugin plugin, Vector3 position, NPCSettings settings) {
            if (Rust.Ai.AiManager.nav_disable) {
                Debug.LogWarning($"[CustomNPC] - NPC's can not be spawned when the Navmesh is disabled!\nYou can turn it on with the 'aimanager.nav_disable false' convar");
                return null;
            }
            if (!(plugin is ICustomNPCPlugin)) {
                Debug.LogError($"Plugin {plugin.Name} is attempting to create a CustomNPC but is not setup correctly");
                return null;
            }
            CustomScientistNPC customNpc = CreateCustomNPC(position, settings, plugin);
            if (customNpc == null) return null;
            RegisterNPC(plugin, customNpc);
            return customNpc;
        }
        public static void RegisterNPC(Plugin plugin, CustomScientistNPC customNpc) {
            List<CustomScientistNPC> list;
            if (!_npcLookup.TryGetValue(plugin, out list)) list = _npcLookup[plugin] = new List<CustomScientistNPC>();
            list.Add(customNpc);
        }
        private static CustomScientistNPC CreateCustomNPC(Vector3 position, NPCSettings settings, Plugin plugin) {
            if (settings.EnableNavMesh && NavmeshSpawnPoint.Find(position, 60, out position)) {
                if (settings.EnableNavMesh && (position.y < -0.25f || (settings.KillInSafeZone && IsInSafeZone(position)))) return null;
            }
            const string SCIENTIST_PREFAB = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_heavy.prefab";
            ScientistNPC scientistNPC = GameManager.server.CreateEntity(SCIENTIST_PREFAB, position, Quaternion.identity, false) as ScientistNPC;
            ScientistBrain scientistBrain = scientistNPC.GetComponent<ScientistBrain>();
            NPCPlayerNavigator scientistNavigator = scientistNPC.GetComponent<NPCPlayerNavigator>();
            CustomScientistNPC customScientist = scientistNPC.gameObject.AddComponent<CustomScientistNPC>();
            CustomScientistBrain customScientistBrain = scientistNPC.gameObject.AddComponent<CustomScientistBrain>();
            CustomScientistNavigator customScientistNavigator = scientistNPC.gameObject.AddComponent<CustomScientistNavigator>();
            CopySerializeableFields<ScientistNPC>(scientistNPC, customScientist);
            CopySerializeableFields<NPCPlayerNavigator>(scientistNavigator, customScientistNavigator);
            customScientist.enableSaving = false;
            customScientist.Plugin = plugin;
            customScientist.Settings = settings;
            customScientistBrain.UseQueuedMovementUpdates = scientistBrain.UseQueuedMovementUpdates;
            customScientistBrain.AllowedToSleep = false;
            customScientistBrain.DefaultDesignSO = scientistBrain.DefaultDesignSO;
            customScientistBrain.Designs = new List<AIDesignSO>(scientistBrain.Designs);
            customScientistBrain.InstanceSpecificDesign = scientistBrain.InstanceSpecificDesign;
            customScientistBrain.CheckLOS = scientistBrain.CheckLOS;
            customScientistBrain.UseAIDesign = true;
            customScientistBrain.Pet = false;
            scientistBrain._baseEntity = scientistNPC;
            UnityEngine.Object.DestroyImmediate(scientistNavigator, true);
            UnityEngine.Object.DestroyImmediate(scientistBrain, true);
            UnityEngine.Object.DestroyImmediate(scientistNPC, true);
            customScientist.gameObject.AwakeFromInstantiate();
            customScientist.Spawn();
            return customScientist;
        }
        public static void SetRoamHomePosition(CustomScientistNPC customNpc, Vector3 position) {
            if (customNpc == null) return;
            customNpc.HomePosition = position;
            if (customNpc.CurrentState == AIState.Roam) customNpc.Brain.SwitchToState(AIState.Idle, 0);
        }
        public static void SetDestination(CustomScientistNPC customNpc, Vector3 destination, Action onDestinationReached = null) {
            if (customNpc == null) return;
            if (NavmeshSpawnPoint.Find(destination, 20, out destination)) customNpc.SetDestination(destination, onDestinationReached);
        }
#endregion
#region Settings
        [Serializable]
        public class NPCSettings {
            [JsonProperty(PropertyName = "NPC types (HeavyScientist, Scientist, Scarecrow, BanditGuard, TunnelDweller)")]
            public NPCType[] Types {
                get;
                set;
            }
            = new NPCType[] {
                NPCType.HeavyScientist
            }
            ;
            [JsonProperty(PropertyName = "Display names (Chosen at random)")]
            public string[] DisplayNames {
                get;
                set;
            }
            = new string[0];
            [JsonProperty(PropertyName = "Kits (Chosen at random)")]
            public string[] Kits {
                get;
                set;
            }
            = new string[0];
            [JsonProperty(PropertyName = "Don't drop loot with corpse")]
            public bool StripCorpseLoot {
                get;
                set;
            }
            [JsonProperty(PropertyName = "Drop inventory as loot")]
            public bool DropInventoryOnDeath {
                get;
                set;
            }
            [JsonProperty(PropertyName = "Max roam range")]
            public float RoamRange {
                get;
                set;
            }
            = -1f;
            [JsonProperty(PropertyName = "Max chase range")]
            public float ChaseRange {
                get;
                set;
            }
            = -1f;
            [JsonProperty(PropertyName = "Aim cone scale")]
            public float AimConeScale {
                get;
                set;
            }
            = 2f;
            [JsonProperty(PropertyName = "Kill in safe zone")]
            public bool KillInSafeZone {
                get;
                set;
            }
            = true;
            [JsonProperty(PropertyName = "Despawn time (seconds)")]
            public float DespawnTime {
                get;
                set;
            }
            = 0f;
            [JsonIgnore]
            public bool StartDead {
                get;
                set;
            }
            [JsonProperty(PropertyName = "Wounded chance (x out of 100)")]
            public float WoundedChance {
                get;
                set;
            }
            = 0f;
            [JsonProperty(PropertyName = "Wounded duration min (seconds)")]
            public float WoundedDurationMin {
                get;
                set;
            }
            = 0f;
            [JsonProperty(PropertyName = "Wounded duration max (seconds)")]
            public float WoundedDurationMax {
                get;
                set;
            }
            = 0f;
            [JsonProperty(PropertyName = "Wounded recovery chance (x out of 100)")]
            public float WoundedRecoveryChance {
                get;
                set;
            }
            = 100f;
            [JsonProperty(PropertyName = "Prevent friendly fire")]
            public bool PreventFriendlyFire {
                get;
                set;
            }
            = true;
            [JsonIgnore]
            public bool EnableNavMesh {
                get;
                set;
            }
            = true;
            [JsonIgnore]
            public bool EquipWeapon {
                get;
                set;
            }
            = true;
            [JsonIgnore]
            public bool CanUseWeaponMounted {
                get;
                set;
            }
            = false;
            [JsonProperty(PropertyName = "Kill if under water")]
            public bool KillUnderWater {
                get;
                set;
            }
            = true;
            public VitalStats Vitals {
                get;
                set;
            }
            = new VitalStats();
            public MovementStats Movement {
                get;
                set;
            }
            = new MovementStats();
            public SensoryStats Sensory {
                get;
                set;
            }
            = new SensoryStats();
            public class VitalStats {
                public float Health {
                    get;
                    set;
                }
                = 200f;
            }
            public class MovementStats {
                public float Speed {
                    get;
                    set;
                }
                = 6.2f;
                public float Acceleration {
                    get;
                    set;
                }
                = 12f;
                [JsonProperty(PropertyName = "Turn speed")]
                public float TurnSpeed {
                    get;
                    set;
                }
                = 120f;
                [JsonProperty(PropertyName = "Speed multiplier - Slowest")]
                public float SlowestSpeedFraction {
                    get;
                    set;
                }
                = 0.1f;
                [JsonProperty(PropertyName = "Speed multiplier - Slow")]
                public float SlowSpeedFraction {
                    get;
                    set;
                }
                = 0.3f;
                [JsonProperty(PropertyName = "Speed multiplier - Normal")]
                public float NormalSpeedFraction {
                    get;
                    set;
                }
                = 0.5f;
                [JsonProperty(PropertyName = "Speed multiplier - Fast")]
                public float FastSpeedFraction {
                    get;
                    set;
                }
                = 1f;
                [JsonProperty(PropertyName = "Speed multiplier - Low health")]
                public float LowHealthMaxSpeedFraction {
                    get;
                    set;
                }
                = 0.5f;
                public void ApplySettingsToNavigator(BaseNavigator baseNavigator) {
                    baseNavigator.Acceleration = Acceleration;
                    baseNavigator.FastSpeedFraction = FastSpeedFraction;
                    baseNavigator.LowHealthMaxSpeedFraction = LowHealthMaxSpeedFraction;
                    baseNavigator.NormalSpeedFraction = NormalSpeedFraction;
                    baseNavigator.SlowestSpeedFraction = SlowestSpeedFraction;
                    baseNavigator.SlowSpeedFraction = SlowSpeedFraction;
                    baseNavigator.Speed = Speed;
                    baseNavigator.TurnSpeed = TurnSpeed;
                    baseNavigator.topologyPreference = (TerrainTopology.Enum)1673010749;
                }
            }
            public class SensoryStats {
                [JsonProperty(PropertyName = "Attack range multiplier")]
                public float AttackRangeMultiplier {
                    get;
                    set;
                }
                = 1.5f;
                [JsonProperty(PropertyName = "Sense range")]
                public float SenseRange {
                    get;
                    set;
                }
                = 30f;
                [JsonProperty(PropertyName = "Listen range")]
                public float ListenRange {
                    get;
                    set;
                }
                = 20f;
                [JsonProperty(PropertyName = "Target lost range")]
                public float TargetLostRange {
                    get;
                    set;
                }
                = 90f;
                [JsonProperty(PropertyName = "Target lost range time (seconds)")]
                public float TargetLostRangeTime {
                    get;
                    set;
                }
                = 5f;
                [JsonProperty(PropertyName = "Target lost LOS time (seconds)")]
                public float TargetLostLOSTime {
                    get;
                    set;
                }
                = 5f;
                [JsonProperty(PropertyName = "Ignore sneaking outside of vision range")]
                public bool IgnoreNonVisionSneakers {
                    get;
                    set;
                }
                = true;
                [JsonProperty(PropertyName = "Vision cone (0 - 180 degrees)")]
                public float VisionCone {
                    get;
                    set;
                }
                = 135f;
                [JsonProperty(PropertyName = "Ignore players in safe zone")]
                public bool IgnoreSafeZonePlayers {
                    get;
                    set;
                }
                = true;
                public void ApplySettingsToBrain(BaseAIBrain<global : : HumanNPC> brain) {
                    brain.MaxGroupSize = int.MaxValue;
                    brain.AttackRangeMultiplier = 1f;
                    brain.SenseRange = SenseRange;
                    brain.ListenRange = ListenRange;
                    brain.TargetLostRange = TargetLostRange;
                    brain.CheckVisionCone = IgnoreNonVisionSneakers;
                    brain.IgnoreNonVisionSneakers = IgnoreNonVisionSneakers;
                    brain.IgnoreSafeZonePlayers = IgnoreSafeZonePlayers;
                    brain.VisionCone = Vector3.Dot(Vector3.forward, Quaternion.Euler(0f, VisionCone, 0f) * Vector3.forward);
                }
            }
        }
        [JsonConverter(typeof(StringEnumConverter))]
        public enum NPCType {
            HeavyScientist, Scientist, Scarecrow, BanditGuard, TunnelDweller
        }
#endregion
#region NavMesh
        public static class NavmeshSpawnPoint {
            private static NavMeshHit navmeshHit;
            private static RaycastHit raycastHit;
            private static readonly Collider[] _buffer = new Collider[256];
            private const int WORLD_LAYER = 65536;
            public static bool Find(Vector3 targetPosition, float maxDistance, out Vector3 position) {
                for (int i = 0; i < 10; i++) {
                    position = i == 0 ? targetPosition : targetPosition + (Random.onUnitSphere.XZ3D() * maxDistance);
                    if (NavMesh.SamplePosition(position, out navmeshHit, maxDistance, 1)) {
                        if (IsInRockPrefab(navmeshHit.position)) continue;
                        if (IsNearWorldCollider(navmeshHit.position)) continue;
                        if (navmeshHit.position.y < TerrainMeta.WaterMap.GetHeight(navmeshHit.position)) continue;
                        position = navmeshHit.position;
                        return true;
                    }
                }
                position = default(Vector3);
                return false;
            }
            private static bool IsInRockPrefab(Vector3 position) {
                Physics.queriesHitBackfaces = true;
                bool isInRock = Physics.Raycast(position, Vector3.up, out raycastHit, 20f, WORLD_LAYER, QueryTriggerInteraction.Ignore) && BLOCKED_COLLIDERS.Any(s => raycastHit.collider?.gameObject?.name.Contains(s, CompareOptions.OrdinalIgnoreCase) ?? false);
                Physics.queriesHitBackfaces = false;
                return isInRock;
            }
            private static bool IsNearWorldCollider(Vector3 position) {
                Physics.queriesHitBackfaces = true;
                int count = Physics.OverlapSphereNonAlloc(position, 2f, _buffer, WORLD_LAYER, QueryTriggerInteraction.Ignore);
                Physics.queriesHitBackfaces = false;
                int removed = 0;
                for (int i = 0; i < count; i++) {
                    if (ACCEPTED_COLLIDERS.Any(s => _buffer[i].gameObject.name.Contains(s, CompareOptions.OrdinalIgnoreCase))) removed++;
                }
                return count - removed > 0;
            }
            private static readonly string[] ACCEPTED_COLLIDERS = new string[] {
                "road", "carpark", "rocket_factory", "range", "train_track", "runway", "_grounds", "concrete_slabs", "lighthouse", "cave", "office", "walkways", "sphere", "tunnel", "industrial", "junkyard", "iceberg", "temperate"
            }
            ;
            private static readonly string[] BLOCKED_COLLIDERS = new string[] {
                "rock", "cliff", "junk", "range", "invisible"
            }
            ;
        }
#endregion
#region Loadouts
        private static Definitions _scientistDefinition;
        private static Definitions _scarecrowDefinition;
        private static Definitions _tunnelDwellerDefinition;
        private static Definitions _banditGuardDefinition;
        private static Definitions ScientistDefinition {
            get {
                if (_scientistDefinition == null) {
                    _scientistDefinition = new Definitions();
                    ScientistNPC scientistNPC = GameManager.server.FindPrefab("assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_roam.prefab").GetComponent<ScientistNPC>();
                    _scientistDefinition.LootSpawns = scientistNPC.LootSpawnSlots;
                    _scientistDefinition.Loadouts = scientistNPC.loadouts;
                }
                return _scientistDefinition;
            }
        }
        private static Definitions ScarecrowDefinition {
            get {
                if (_scarecrowDefinition == null) {
                    _scarecrowDefinition = new Definitions();
                    ScarecrowNPC scarecrowNPC = GameManager.server.FindPrefab("assets/prefabs/npc/scarecrow/scarecrow.prefab").GetComponent<ScarecrowNPC>();
                    _scarecrowDefinition.LootSpawns = scarecrowNPC.LootSpawnSlots;
                    _scarecrowDefinition.Loadouts = scarecrowNPC.loadouts;
                }
                return _scarecrowDefinition;
            }
        }
        private static Definitions TunnelDwellerDefinition {
            get {
                if (_tunnelDwellerDefinition == null) {
                    _tunnelDwellerDefinition = new Definitions();
                    TunnelDweller tunnelDweller = GameManager.server.FindPrefab("assets/rust.ai/agents/npcplayer/humannpc/tunneldweller/npc_tunneldweller.prefab").GetComponent<TunnelDweller>();
                    _tunnelDwellerDefinition.LootSpawns = tunnelDweller.LootSpawnSlots;
                    _tunnelDwellerDefinition.Loadouts = tunnelDweller.loadouts;
                }
                return _tunnelDwellerDefinition;
            }
        }
        private static Definitions BanditGuardDefinition {
            get {
                if (_banditGuardDefinition == null) {
                    _banditGuardDefinition = new Definitions();
                    BanditGuard scarecrowNPC = GameManager.server.FindPrefab("assets/rust.ai/agents/npcplayer/humannpc/banditguard/npc_bandit_guard.prefab").GetComponent<BanditGuard>();
                    _banditGuardDefinition.LootSpawns = scarecrowNPC.LootSpawnSlots;
                    _banditGuardDefinition.Loadouts = scarecrowNPC.loadouts;
                }
                return _banditGuardDefinition;
            }
        }
        private class Definitions {
            public LootContainer.LootSpawnSlot[] LootSpawns;
            public PlayerInventoryProperties[] Loadouts;
        }
#endregion
#region NPC Component
        public class CustomScientistNPC : ScientistNPC, IAIAttack {
            internal static readonly Spatial.Grid<CustomScientistNPC> NPCGrid = new Spatial.Grid<CustomScientistNPC>(32, 8096f);
            private static readonly CustomScientistNPC[] QueryResults = new CustomScientistNPC[4];
            public Plugin Plugin {
                get;
                set;
            }
            public NPCSettings Settings {
                get;
                set;
            }
            public Transform Transform {
                get;
                private set;
            }
            public BaseEntity CurrentTarget {
                get;
                protected set;
            }
            public AttackEntity CurrentWeapon {
                get;
                private set;
            }
            public AIState CurrentState {
                get;
                internal set;
            }
            = AIState.Idle;
            public Vector3 HomePosition {
                get;
                internal set;
            }
            public bool IsPaused {
                get;
                internal set;
            }
            public bool IsAlert {
                get;
                internal set;
            }
            private float nextAlertTime;
            public Vector3 DestinationOverride {
                get;
                internal set;
            }
            internal Action onDestinationReached;
            private bool isEquippingWeapon;
            private NPCType npcType;
            private const int AREA_MASK = 1;
            private const int AGENT_TYPE_ID = -1372625422;
            private static readonly LootContainer.LootSpawnSlot[] EMPTY_SLOTS = new LootContainer.LootSpawnSlot[0];
            public override void ServerInit() {
                Transform = transform;
                HomePosition = transform.position;
                npcType = Settings.Types.GetRandom();
                loadouts = Settings.Kits?.Length > 0 ? null : GetLoadoutForType(npcType);
                if (Settings.StripCorpseLoot) LootSpawnSlots = EMPTY_SLOTS;
                else LootSpawnSlots = GetLootSpawnSlotsForType(npcType);
                if (NavAgent == null) NavAgent = GetComponent<NavMeshAgent>();
                NavAgent.areaMask = AREA_MASK;
                NavAgent.agentTypeID = AGENT_TYPE_ID;
                GetComponent<BaseNavigator>().DefaultArea = "Walkable";
                startHealth = Settings.Vitals.Health;
                base.ServerInit();
                if (npcType != NPCType.HeavyScientist && npcType != NPCType.Scientist) {
                    CancelInvoke(PlayRadioChatter);
                    CancelInvoke(IdleCheck);
                }
                Invoke(SetDisplayName, 1f);
                if (Settings.Kits?.Length > 0) Instance.Kits?.Call("GiveKit", this, Settings.Kits.GetRandom());
                AdjustWeaponRanges();
                bool startWounded = Settings.WoundedChance >= 100 || (Settings.WoundedChance > 0 && Random.Range(0f, 100f) <= Settings.WoundedChance);
                if (!Settings.EquipWeapon || Settings.StartDead || startWounded) CancelInvoke(EquipTest);
                if (Settings.StartDead) {
                    Invoke(KillSelf, 1.5f);
                    return;
                }
                if (startWounded) {
                    SetPlayerFlag(PlayerFlags.Wounded, true);
                    Invoke(() => Brain.SwitchToState(AIState.Orbit, 4), 1f);
                }
                if (Settings.DespawnTime > 0f) Invoke(Despawn, Settings.DespawnTime);
                InvokeRepeating(LightCheck, 1f, 30f);
                NPCGrid.Add(this, Transform.position.x, Transform.position.z);
            }
            public override void DestroyShared() {
                NPCGrid.Remove(this);
                base.DestroyShared();
            }
            private void SetDisplayName() => displayName = Settings.DisplayNames.Length > 0 ? Settings.DisplayNames.GetRandom() : npcType.ToString();
            private void KillSelf() => Die(new HitInfo(this, this, DamageType.Explosion, 1000f));
            private void Despawn() => Kill(DestroyMode.None);
#region Loadouts
            private LootContainer.LootSpawnSlot[] GetLootSpawnSlotsForType(NPCType type) {
                switch (type) {
                    case NPCType.HeavyScientist : return LootSpawnSlots;
                    case NPCType.Scientist : return ScientistDefinition.LootSpawns;
                    case NPCType.Scarecrow : return ScarecrowDefinition.LootSpawns;
                    case NPCType.BanditGuard : return BanditGuardDefinition.LootSpawns;
                    case NPCType.TunnelDweller : return TunnelDwellerDefinition.LootSpawns;
                    default : return null;
                }
            }
            private PlayerInventoryProperties[] GetLoadoutForType(NPCType type) {
                switch (type) {
                    case NPCType.HeavyScientist : return loadouts;
                    case NPCType.Scientist : return ScientistDefinition.Loadouts;
                    case NPCType.Scarecrow : return ScarecrowDefinition.Loadouts;
                    case NPCType.BanditGuard : return BanditGuardDefinition.Loadouts;
                    case NPCType.TunnelDweller : return TunnelDwellerDefinition.Loadouts;
                    default : return null;
                }
            }
#endregion
#region Targeting
            public override void OnSensation(Sensation sensation) {
                if (sensation.Initiator != null && !(sensation.Initiator is CustomScientistNPC) && CurrentState < AIState.Chase) MoveTowardsTarget(sensation.Initiator, false);
            }
            public new BaseEntity GetBestTarget() {
                BaseEntity target = null;
                float delta = -1f;
                foreach (BaseEntity baseEntity in Brain.Senses.Memory.Targets) {
                    if (baseEntity == null || baseEntity.Health() <= 0f) continue;
                    if (!CanTargetEntity(baseEntity)) continue;
                    if (baseEntity is BasePlayer && !CanTargetBasePlayer(baseEntity as BasePlayer)) continue;
                    float distanceToTarget = Vector3.Distance(baseEntity.transform.position, Transform.position);
                    if (lastAttacker != baseEntity && distanceToTarget > Brain.Senses.TargetLostRange) continue;
                    float rangeDelta = 1f - Mathf.InverseLerp(1f, Brain.SenseRange, distanceToTarget);
                    float dot = Vector3.Dot((baseEntity.transform.position - eyes.position).normalized, eyes.BodyForward());
                    if (lastAttacker != baseEntity && Settings.Sensory.IgnoreNonVisionSneakers && dot < Brain.VisionCone) continue;
                    rangeDelta += Mathf.InverseLerp(Brain.VisionCone, 1f, dot) / 2f;
                    rangeDelta += (Brain.Senses.Memory.IsLOS(baseEntity) ? 2f : 0f);
                    if (lastAttacker != baseEntity && rangeDelta <= delta) continue;
                    target = baseEntity;
                    delta = rangeDelta;
                }
                CurrentTarget = target;
                return target;
            }
            public virtual bool CanTargetBasePlayer(BasePlayer player) {
                if (player.IsFlying) return false;
                if (player.IsSleeping()) return false;
                if (!player.IsNpc && !player.userID.IsSteamId() && player.faction != Faction.Horror) return false;
                if (Settings.Sensory.IgnoreSafeZonePlayers && player.InSafeZone()) return false;
                return true;
            }
            public bool CanTargetEntity(BaseEntity baseEntity) => baseEntity is BasePlayer || baseEntity is BaseNpc;
            public bool IsValidDistance(float distanceToTarget) {
                if (Settings.ChaseRange > 0f && distanceToTarget > Settings.ChaseRange) return false;
                if (distanceToTarget > Brain.TargetLostRange) return false;
                return true;
            }
            public void NotifyNearby() {
                if (CurrentTarget == null) return;
                int hits = NPCGrid.Query(Transform.position.x, Transform.position.z, 80f, QueryResults, (CustomScientistNPC found) => IsFellowNPC(this, found));
                for (int i = 0; i < hits; i++) {
                    CustomScientistNPC customNpc = QueryResults[i];
                    if (customNpc != this && customNpc.CurrentState < AIState.Chase && customNpc.CurrentTarget == null) customNpc.MoveTowardsTarget(CurrentTarget, true);
                }
            }
            public void MoveTowardsTarget(BaseEntity target, bool force) {
                if (Time.time > nextAlertTime) {
                    Brain.Senses.Memory.SetKnown(target, this, null);
                    DestinationOverride = target.transform.position;
                    IsAlert = true;
                    nextAlertTime = Time.time + 3f;
                    Brain.SwitchToState(AIState.Idle, 0);
                }
            }
            private static bool IsFellowNPC(CustomScientistNPC notifier, CustomScientistNPC found) => notifier.Plugin == found.Plugin && notifier != found;
#endregion
#region Equip Weapons
            public override void EquipWeapon() {
                if (!isEquippingWeapon) StartCoroutine(EquipItem());
            }
            public void HolsterWeapon() {
                svActiveItemID = 0;
                Item activeItem = GetActiveItem();
                if (activeItem != null) {
                    HeldEntity heldEntity = activeItem.GetHeldEntity() as HeldEntity;
                    if (heldEntity != null) {
                        heldEntity.SetHeld(false);
                    }
                }
                SendNetworkUpdate(NetworkQueue.Update);
                inventory.UpdatedVisibleHolsteredItems();
                CurrentWeapon = null;
            }
            private IEnumerator EquipItem(Item slot = null) {
                if (inventory != null && inventory.containerBelt != null) {
                    isEquippingWeapon = true;
                    if (slot == null) {
                        for (int i = 0; i < inventory.containerBelt.itemList.Count; i++) {
                            Item item = inventory.containerBelt.GetSlot(i);
                            if (item != null && item.GetHeldEntity() is AttackEntity) {
                                slot = item;
                                break;
                            }
                        }
                    }
                    if (slot != null) {
                        if (CurrentWeapon != null) {
                            CurrentWeapon.SetHeld(false);
                            CurrentWeapon = null;
                            SendNetworkUpdate(NetworkQueue.Update);
                            inventory.UpdatedVisibleHolsteredItems();
                        }
                        yield return CoroutineEx.waitForSeconds(0.5f);
                        UpdateActiveItem(slot.uid);
                        HeldEntity heldEntity = slot.GetHeldEntity() as HeldEntity;
                        if (heldEntity != null) {
                            if (heldEntity is AttackEntity) (heldEntity as AttackEntity).TopUpAmmo();
                            if (heldEntity is Chainsaw) (heldEntity as Chainsaw).ServerNPCStart();
                        }
                        CurrentWeapon = heldEntity as AttackEntity;
                    }
                    isEquippingWeapon = false;
                }
            }
#endregion
#region Corpses
            protected override string OverrideCorpseName() => displayName;
            public override BaseCorpse CreateCorpse() {
                ResetModifiedWeaponRange();
                NPCPlayerCorpse npcplayerCorpse = DropCorpse("assets/prefabs/npc/murderer/murderer_corpse.prefab") as NPCPlayerCorpse;
                if (npcplayerCorpse) {
                    npcplayerCorpse.transform.position = npcplayerCorpse.transform.position + Vector3.down * NavAgent.baseOffset;
                    npcplayerCorpse.SetLootableIn(2f);
                    npcplayerCorpse.SetFlag(Flags.Reserved5, HasPlayerFlag(PlayerFlags.DisplaySash), false, true);
                    npcplayerCorpse.SetFlag(Flags.Reserved2, true, false, true);
                    npcplayerCorpse.TakeFrom(new ItemContainer[] {
                        inventory.containerMain, inventory.containerWear, inventory.containerBelt
                    }
                    );
                    npcplayerCorpse.playerName = displayName;
                    npcplayerCorpse.playerSteamID = userID;
                    npcplayerCorpse.Spawn();
                    npcplayerCorpse.TakeChildren(this);
                    if (Settings.DropInventoryOnDeath) {
                        npcplayerCorpse.containers[1].Clear();
                    }
                    else {
                        ItemContainer[] containers = npcplayerCorpse.containers;
                        for (int i = 0; i < containers.Length; i++) containers[i].Clear();
                        if ((Plugin as ICustomNPCPlugin).WantsToPopulateLoot(this, npcplayerCorpse)) return npcplayerCorpse;
                        if (LootSpawnSlots.Length != 0) {
                            object obj = Core.Interface.CallHook("OnCorpsePopulate", this, npcplayerCorpse);
                            if (obj is BaseCorpse) {
                                return (BaseCorpse)obj;
                            }
                            LootContainer.LootSpawnSlot[] lootSpawnSlots = this.LootSpawnSlots;
                            for (int i = 0; i < (int)lootSpawnSlots.Length;
                            i++) {
                                LootContainer.LootSpawnSlot lootSpawnSlot = lootSpawnSlots[i];
                                for (int j = 0; j < lootSpawnSlot.numberToSpawn; j++) {
                                    if (Random.Range(0f, 1f) <= lootSpawnSlot.probability) {
                                        lootSpawnSlot.definition.SpawnIntoContainer(npcplayerCorpse.containers[0]);
                                    }
                                }
                            }
                        }
                    }
                }
                return npcplayerCorpse;
            }
#endregion
#region Weapon Range
            private void AdjustWeaponRanges() {
                float effectiveRange;
                for (int i = 0; i < inventory.containerBelt.itemList.Count; i++) {
                    Item item = inventory.containerBelt.itemList[i];
                    if (item != null) {
                        HeldEntity heldEntity = item.GetHeldEntity() as HeldEntity;
                        if (heldEntity != null) {
                            if (heldEntity is BaseProjectile) {
                                if (ProjectileEffectiveRange.TryGetValue(item.info.shortname, out effectiveRange)) {
                                    if (!_effectiveRangeDefaults.ContainsKey(item.info.shortname)) _effectiveRangeDefaults[item.info.shortname] = (heldEntity as BaseProjectile).effectiveRange;
                                    (heldEntity as BaseProjectile).effectiveRange = effectiveRange;
                                }
                            }
                        }
                    }
                }
            }
            private void ResetModifiedWeaponRange() {
                float effectiveRange;
                for (int i = 0; i < inventory.containerBelt.itemList.Count; i++) {
                    Item item = inventory.containerBelt.itemList[i];
                    if (item != null) {
                        HeldEntity heldEntity = item.GetHeldEntity() as HeldEntity;
                        if (heldEntity != null) {
                            if (heldEntity is BaseProjectile) {
                                if (GetDefaultEffectiveRange(item.info.shortname, out effectiveRange)) (heldEntity as BaseProjectile).effectiveRange = effectiveRange;
                            }
                        }
                    }
                }
            }
            private static readonly Hash<string, float> ProjectileEffectiveRange = new Hash<string, float> {
                ["bow.compound"]
                = 40, ["bow.hunting"] = 30, ["crossbow"] = 40, ["flamethrower"] = 8, ["gun.water"] = 10, ["lmg.m249"] = 150, ["multiplegrenadelauncher"] = 40, ["pistol.eoka"] = 5, ["pistol.m92"] = 45, ["pistol.nailgun"] = 20, ["pistol.python"] = 45, ["pistol.revolver"] = 45, ["pistol.semiauto"] = 45, ["pistol.water"] = 10, ["rifle.ak"] = 80, ["rifle.bolt"] = 150, ["rifle.l96"] = 200, ["rifle.lr300"] = 100, ["rifle.m39"] = 120, ["rifle.semiauto"] = 120, ["rocket.launcher"] = 50, ["shotgun.double"] = 25, ["shotgun.pump"] = 25, ["shotgun.spas12"] = 25, ["shotgun.waterpipe"] = 15, ["smg.2"] = 40, ["smg.mp5"] = 60, ["smg.thompson"] = 50, ["snowballgun"] = 20, ["speargun"] = 20,
            }
            ;
            private static Hash<string, float> _effectiveRangeDefaults = new Hash<string, float>();
            private static bool GetDefaultEffectiveRange(string shortname, out float value) => _effectiveRangeDefaults.TryGetValue(shortname, out value);
#endregion
            public override string Categorize() => Plugin.Name;
            public override float GetAimConeScale() => Settings.AimConeScale;
            internal void SetDestination(Vector3 destination, Action onDestinationReached) {
                Brain.Events.Memory.Position.Set(destination, 5);
                this.onDestinationReached = onDestinationReached;
                Brain.SwitchToState(AIState.MoveToPoint, 3);
            }
            public void SetPaused(bool paused) => IsPaused = paused;
        }
        public class CustomScientistNavigator : NPCPlayerNavigator {
            public override void Init(BaseCombatEntity entity, NavMeshAgent agent) {
                TriggerStuckEvent = true;
                base.Init(entity, agent);
            }
            public override void OnStuck() {
                CustomScientistNPC customNpc = BaseEntity as CustomScientistNPC;
                if (customNpc.Brain != null && customNpc.Brain.Navigator != null && customNpc.Brain.Events != null) customNpc.Brain.SwitchToState(AIState.Idle, 0);
            }
            public override void ApplyFacingDirectionOverride() {
                base.ApplyFacingDirectionOverride();
                if (overrideFacingDirectionMode == OverrideFacingDirectionMode.None) return;
                if (overrideFacingDirectionMode == OverrideFacingDirectionMode.Direction) {
                    NPCPlayerEntity.SetAimDirection(facingDirectionOverride);
                    return;
                }
                if (facingDirectionEntity != null) {
                    Vector3 aimDirection = GetAimDirection(NPCPlayerEntity, facingDirectionEntity);
                    facingDirectionOverride = aimDirection;
                    NPCPlayerEntity.SetAimDirection(facingDirectionOverride);
                }
            }
            protected override bool CanUpdateMovement() {
                if (BaseEntity == null || !BaseEntity.IsAlive()) return false;
                if (CurrentNavigationType == NavigationType.NavMesh && (NPCPlayerEntity.IsDormant || !NPCPlayerEntity.syncPosition) && Agent.enabled) {
                    SetDestination(NPCPlayerEntity.ServerPosition, 1f, 0f, 0f);
                    return false;
                }
                return true;
            }
            private static Vector3 GetAimDirection(BasePlayer aimingPlayer, BaseEntity target) {
                if (target == null) return Vector3Ex.Direction2D(aimingPlayer.transform.position + aimingPlayer.eyes.BodyForward() * 1000f, aimingPlayer.transform.position);
                if (Vector3Ex.Distance2D(aimingPlayer.transform.position, target.transform.position) <= 0.75f) return Vector3Ex.Direction2D(target.transform.position, EyesPosition(aimingPlayer));
                return (TargetAimPositionOffset(target) - EyesPosition(aimingPlayer)).normalized;
            }
            private static Vector3 EyesPosition(BasePlayer aimingPlayer) => aimingPlayer.eyes.position - Vector3.up * 0.15f;
            private static Vector3 TargetAimPositionOffset(BaseEntity target) {
                BasePlayer basePlayer = target as BasePlayer;
                if (basePlayer == null) return target.CenterPoint();
                if (basePlayer.IsSleeping() || basePlayer.IsWounded()) return basePlayer.transform.position + Vector3.up * 0.1f;
                return basePlayer.eyes.position - Vector3.up * 0.15f;
            }
        }
        public class CustomScientistBrain : BaseAIBrain<global : : HumanNPC> {
            public override void InitializeAI() {
                SenseTypes = EntityType.Player | EntityType.BasePlayerNPC | EntityType.NPC;
                CustomScientistNPC customNpc = GetEntity() as CustomScientistNPC;
                customNpc.Settings.Sensory.ApplySettingsToBrain(this);
                base.InitializeAI();
                ThinkMode = AIThinkMode.Interval;
                thinkRate = 0.25f;
                PathFinder = new HumanPathFinder();
                ((HumanPathFinder)PathFinder).Init(customNpc);
                customNpc.Settings.Movement.ApplySettingsToNavigator(Navigator);
                Navigator.MaxRoamDistanceFromHome = customNpc.Settings.RoamRange;
                byte[] design = (customNpc.Plugin as ICustomNPCPlugin).GetCustomDesign() ?? DefaultDesign;
                LoadAIDesign(ProtoBuf.AIDesign.Deserialize(design), null, 0);
            }
            public override void AddStates() {
                states = new Dictionary<AIState, BasicAIState>();
                CustomScientistNPC customNpc = GetEntity() as CustomScientistNPC;
                if (!(customNpc.Plugin as ICustomNPCPlugin).InitializeStates(this)) {
                    AddState(new BaseIdleState());
                    if (customNpc.IsDead()) return;
                    AddState(new RoamState());
                    AddState(new ChaseState());
                    AddState(new MoveToDestinationState());
                    AddState(new BaseMountedState());
                    AddState(new BaseDismountedState());
                    AddState(new WoundedState());
                    AddState(new FallingState());
                }
            }
            public override void Think(float delta) {
                CustomScientistNPC customNpc = GetEntity() as CustomScientistNPC;
                if (customNpc.IsPaused) {
                    Navigator?.Pause();
                    return;
                }
                base.Think(delta);
                if (sleeping) return;
                if (customNpc.Settings.KillUnderWater) {
                    if (customNpc != null && customNpc.WaterFactor() > 0.85f && !customNpc.IsDestroyed) {
                        const float DROWN_TIMER = 5f;
                        customNpc.Hurt(delta * (customNpc.MaxHealth() / DROWN_TIMER), DamageType.Drowned, null, true);
                    }
                }
                CustomScientistNPC.NPCGrid.Move(customNpc, customNpc.Transform.position.x, customNpc.Transform.position.z);
            }
            protected override void OnStateChanged() {
                base.OnStateChanged();
                CustomScientistNPC customNpc = GetEntity() as CustomScientistNPC;
                customNpc.CurrentState = CurrentState.StateType;
                if (customNpc.CurrentState
// ... [Code preview truncated due to length. Full source in attached file] ...

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

Другие ресурсы пользователя 15FPS

Сверху