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

FREE NpcSpawn : 1.0.0 1.0.0

Нет прав для скачивания
C#:
using Facepunch;
using Newtonsoft.Json.Linq;
using Rust;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.AI;
namespace Oxide.Plugins {
    [Info("NpcSpawn", "TopPlugin.ru", "1.0.0")]
    class NpcSpawn : RustPlugin {
#region Config
        internal class NpcItem {
            public string ShortName;
            public int Amount;
            public ulong SkinID;
        }
        internal class NpcConfig {
            public string Name {
                get;
                set;
            }
            public List<NpcItem> WearItems {
                get;
                set;
            }
            public List<NpcItem> BeltItems {
                get;
                set;
            }
            public float Health {
                get;
                set;
            }
            public float RoamRange {
                get;
                set;
            }
            public float ChaseRange {
                get;
                set;
            }
            public float DamageScale {
                get;
                set;
            }
            public float AimConeScale {
                get;
                set;
            }
            public bool DisableRadio {
                get;
                set;
            }
            public bool EnableNavMesh {
                get;
                set;
            }
            public bool Stationary {
                get;
                set;
            }
            public bool KillInSafeZone {
                get;
                set;
            }
            public bool CanUseWeaponMounted {
                get;
                set;
            }
            public float Speed {
                get;
                set;
            }
            public SensoryStats Sensory {
                get;
                set;
            }
        }
        public class SensoryStats {
            public float AttackRangeMultiplier {
                get;
                set;
            }
            public float SenseRange {
                get;
                set;
            }
            public bool CheckVisionCone {
                get;
                set;
            }
            public float VisionCone {
                get;
                set;
            }
            public bool IgnoreSafeZonePlayers {
                get;
                set;
            }
            public void ApplySettingsToBrain(BaseAIBrain<global : : HumanNPC> brain) {
                brain.MaxGroupSize = int.MaxValue;
                brain.AttackRangeMultiplier = AttackRangeMultiplier;
                brain.SenseRange = SenseRange;
                brain.TargetLostRange = SenseRange * 2f;
                brain.CheckVisionCone = CheckVisionCone;
                brain.VisionCone = Vector3.Dot(Vector3.forward, Quaternion.Euler(0f, VisionCone, 0f) * Vector3.forward);
                brain.IgnoreSafeZonePlayers = IgnoreSafeZonePlayers;
            }
        }
#endregion Config
#region Methods
        private ScientistNPC SpawnNpc(Vector3 position, JObject configJson) {
            if (Rust.Ai.AiManager.nav_disable) return null;
            CustomScientistNPC npc = CreateCustomNPC(position, configJson.ToObject<NpcConfig>());
            if (npc != null) Npcs.Add(npc);
            return npc;
        }
        private CustomScientistNPC CreateCustomNPC(Vector3 position, NpcConfig config) {
            if (config.EnableNavMesh) if (!NavmeshSpawnPoint.Find(position, config.RoamRange, out position)) return null;
            if (position.y < -0.25f || (config.KillInSafeZone && IsInSafeZone(position))) return null;
            ScientistNPC scientistNPC = GameManager.server.CreateEntity("assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_heavy.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.Config = config;
            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;
            UnityEngine.Object.DestroyImmediate(scientistNavigator);
            UnityEngine.Object.DestroyImmediate(scientistBrain, true);
            UnityEngine.Object.DestroyImmediate(scientistNPC, true);
            customScientist.gameObject.AwakeFromInstantiate();
            customScientist.Spawn();
            return customScientist;
        }
        private 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 Methods
#region Controller
        internal class DefaultSettings {
            public float effectiveRange;
            public float attackLengthMin;
            public float attackLengthMax;
        }
        private Dictionary<string, DefaultSettings> Weapons = new Dictionary<string, DefaultSettings> {
            ["rifle.bolt"]
            = new DefaultSettings {
                effectiveRange = 150f, attackLengthMin = -1f, attackLengthMax = -1f
            }
            , ["bow.compound"] = new DefaultSettings {
                effectiveRange = 15f, attackLengthMin = -1f, attackLengthMax = -1f
            }
            , ["smg.2"] = new DefaultSettings {
                effectiveRange = 20f, attackLengthMin = 0.4f, attackLengthMax = 0.4f
            }
            , ["shotgun.double"] = new DefaultSettings {
                effectiveRange = 10f, attackLengthMin = 0.3f, attackLengthMax = 1f
            }
            , ["pistol.eoka"] = new DefaultSettings {
                effectiveRange = 10f, attackLengthMin = -1f, attackLengthMax = -1f
            }
            , ["rifle.l96"] = new DefaultSettings {
                effectiveRange = 150f, attackLengthMin = -1f, attackLengthMax = -1f
            }
            , ["pistol.nailgun"] = new DefaultSettings {
                effectiveRange = 15f, attackLengthMin = 0f, attackLengthMax = 0.46f
            }
            , ["pistol.python"] = new DefaultSettings {
                effectiveRange = 15f, attackLengthMin = 0.175f, attackLengthMax = 0.525f
            }
            , ["pistol.semiauto"] = new DefaultSettings {
                effectiveRange = 15f, attackLengthMin = 0f, attackLengthMax = 0.46f
            }
            , ["smg.thompson"] = new DefaultSettings {
                effectiveRange = 20f, attackLengthMin = 0.4f, attackLengthMax = 0.4f
            }
            , ["shotgun.waterpipe"] = new DefaultSettings {
                effectiveRange = 10f, attackLengthMin = -1f, attackLengthMax = -1f
            }
            ,
        }
        ;
        public class CustomScientistNPC : ScientistNPC, IAIAttack {
            public NpcConfig Config {
                get;
                set;
            }
            public Transform Transform {
                get;
                private set;
            }
            public BaseEntity CurrentTarget {
                get;
                set;
            }
            public AttackEntity CurrentWeapon {
                get;
                private set;
            }
            public Vector3 HomePosition {
                get;
                internal set;
            }
            public float DistanceFromBase => Vector3.Distance(Transform.position, HomePosition);
            public float DistanceToTarget => Vector3.Distance(Transform.position, CurrentTarget.transform.position);
            private bool isEquippingWeapon;
            public override void ServerInit() {
                Transform = transform;
                HomePosition = Transform.position;
                if (NavAgent == null) NavAgent = GetComponent<NavMeshAgent>();
                NavAgent.areaMask = 1;
                NavAgent.agentTypeID = -1372625422;
                startHealth = Config.Health;
                damageScale = Config.DamageScale;
                base.ServerInit();
                if (Config.DisableRadio) {
                    CancelInvoke(PlayRadioChatter);
                    RadioChatterEffects = new GameObjectRef[0];
                    DeathEffects = new GameObjectRef[0];
                }
                UpdateInventory();
                Invoke(SetDisplayName, 1f);
                InvokeRepeating(LightCheck, 1f, 30f);
                InvokeRepeating(UpdateTick, 1f, 2f);
            }
            private void UpdateInventory() {
                if (Config.WearItems.Count > 0) AddItems(inventory.containerWear, Config.WearItems);
                if (Config.BeltItems.Count > 0) AddItems(inventory.containerBelt, Config.BeltItems);
            }
            private void AddItems(ItemContainer container, List<NpcItem> items) {
                List<Item> allItems = container.itemList;
                for (int i = allItems.Count - 1; i >= 0; i--) {
                    Item item = allItems[i];
                    item.RemoveFromContainer();
                    item.Remove();
                }
                foreach (NpcItem npcItem in items) {
                    Item item = ItemManager.CreateByName(npcItem.ShortName, npcItem.Amount, npcItem.SkinID);
                    if (!item.MoveToContainer(container)) item.Remove();
                }
            }
            private void SetDisplayName() => displayName = Config.Name;
            private void OnDestroy() {
                if (HealCoroutine != null) ServerMgr.Instance.StopCoroutine(HealCoroutine);
                CancelInvoke(UpdateTick);
                CancelInvoke(FinishReloadGrenade);
                CancelInvoke(FinishReloadSmoke);
                CancelInvoke(FinishRunAwayWater);
            }
            private void UpdateTick() {
                if (CurrentWeapon is FlameThrower && (CurrentWeapon as FlameThrower).ammo <= 0) CurrentWeapon.TopUpAmmo();
                if (CanRunAwayWater()) RunAwayWater();
                if (CanThrownGrenade()) ThrownGrenade(CurrentTarget.transform.position);
                if (CanHeal()) HealCoroutine = ServerMgr.Instance.StartCoroutine(Heal());
            }
#region Targeting
            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;
                    BasePlayer basePlayer = baseEntity as BasePlayer;
                    if (!CanTargetBasePlayer(basePlayer)) continue;
                    float rangeDelta = 1f - Mathf.InverseLerp(1f, Brain.SenseRange, Vector3.Distance(basePlayer.transform.position, Transform.position));
                    if (Config.Sensory.CheckVisionCone) {
                        float dot = Vector3.Dot((basePlayer.transform.position - eyes.position).normalized, eyes.BodyForward());
                        if (dot < Brain.VisionCone) continue;
                        rangeDelta += Mathf.InverseLerp(Brain.VisionCone, 1f, dot) / 2f;
                    }
                    rangeDelta += (Brain.Senses.Memory.IsLOS(basePlayer) ? 2f : 0f);
                    if (rangeDelta <= delta) continue;
                    target = basePlayer;
                    delta = rangeDelta;
                }
                return target;
            }
            public bool CanTargetBasePlayer(BasePlayer player) {
                if (player == null || IsRunAwayWater) return false;
                if (player.IsFlying || player.IsSleeping() || player.IsWounded() || player.IsDead()) return false;
                if (!player.userID.IsSteamId()) return false;
                if (Config.Sensory.IgnoreSafeZonePlayers && player.InSafeZone()) return false;
                return true;
            }
#endregion Targeting
#region Equip Weapons
            public override void EquipWeapon() {
                if (!isEquippingWeapon) {
                    Item slot = inventory.containerBelt.itemList.Where(x => x.info.shortname != "syringe.medical" && x.info.shortname != "grenade.f1" && x.info.shortname != "grenade.beancan" && x.info.shortname != "grenade.smoke" && x.info.shortname != "explosive.timed" && x.info.shortname != "rocket.launcher").FirstOrDefault();
                    if (slot != null) StartCoroutine(EquipItem(slot));
                }
            }
            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();
                            if (heldEntity is BaseProjectile && ins.Weapons.ContainsKey(slot.info.shortname)) {
                                AttackEntity attackEntity = heldEntity as AttackEntity;
                                attackEntity.effectiveRange = ins.Weapons[slot.info.shortname].effectiveRange;
                                attackEntity.attackLengthMin = ins.Weapons[slot.info.shortname].attackLengthMin;
                                attackEntity.attackLengthMax = ins.Weapons[slot.info.shortname].attackLengthMax;
                            }
                        }
                        CurrentWeapon = heldEntity as AttackEntity;
                    }
                    isEquippingWeapon = false;
                }
            }
            internal void HolsterWeapon() {
                svActiveItemID = 0;
                Item activeItem = GetActiveItem();
                if (activeItem != null) {
                    HeldEntity heldEntity = activeItem.GetHeldEntity() as HeldEntity;
                    if (heldEntity != null) heldEntity.SetHeld(false);
                }
                SendNetworkUpdate(BasePlayer.NetworkQueue.Update);
                inventory.UpdatedVisibleHolsteredItems();
            }
#endregion Equip Weapons
            protected override string OverrideCorpseName() => displayName;
            public override float GetAimConeScale() => Config.AimConeScale;
#region Heal
            Coroutine HealCoroutine = null;
            private bool IsReloadHeal = false;
            private bool CanHeal() {
                if (IsReloadHeal || health >= Config.Health) return false;
                if (!inventory.containerBelt.itemList.Any(x => x.info.shortname == "syringe.medical")) return false;
                if (CurrentTarget != null) return false;
                return true;
            }
            private IEnumerator Heal() {
                IsReloadHeal = true;
                Item syringe = inventory.containerBelt.itemList.Where(x => x.info.shortname == "syringe.medical").First();
                UpdateActiveItem(syringe.uid);
                yield return CoroutineEx.waitForSeconds(1.5f);
                EquipWeapon();
                InitializeHealth(health + 15f > Config.Health ? Config.Health : health + 15f, Config.Health);
                if (syringe != null) {
                    if (syringe.amount == 1) syringe.Remove();
                    else {
                        syringe.amount--;
                        syringe.MarkDirty();
                    }
                }
                IsReloadHeal = false;
            }
#endregion Heal
#region Grenades
            private HashSet<string> Barricades = new HashSet<string> {
                "barricade.cover.wood", "barricade.sandbags", "barricade.concrete", "barricade.stone"
            }
            ;
            private bool IsReloadGrenade = false;
            private bool IsReloadSmoke = false;
            private void FinishReloadGrenade() => IsReloadGrenade = false;
            private void FinishReloadSmoke() => IsReloadSmoke = false;
            private bool CanThrownGrenade() {
                if (IsReloadGrenade || CurrentTarget == null) return false;
                if (isMounted && !Config.CanUseWeaponMounted) return false;
                if (Vector3.Distance(Transform.position, CurrentTarget.transform.position) < 15f && inventory.containerBelt.itemList.Any(x => x.info.shortname == "grenade.f1" || x.info.shortname == "grenade.beancan") && (!CanSeeTarget(CurrentTarget) || IsBehindBarricade())) return true;
                return false;
            }
            internal bool IsBehindBarricade() => CanSeeTarget(CurrentTarget) && IsBarricade();
            private bool IsBarricade() {
                SetAimDirection((CurrentTarget.transform.position - Transform.position).normalized);
                RaycastHit[] hits = Physics.RaycastAll(eyes.HeadRay());
                GamePhysics.Sort(hits);
                foreach (RaycastHit hit in hits) {
                    Barricade ent = hit.GetEntity() as Barricade;
                    if (ent != null && Barricades.Contains(ent.ShortPrefabName) && Vector3.Distance(Transform.position, ent.transform.position) < Vector3.Distance(Transform.position, CurrentTarget.transform.position)) return true;
                }
                return false;
            }
            private void ThrownGrenade(Vector3 target) {
                Item item = inventory.containerBelt.itemList.Where(x => x.info.shortname == "grenade.f1" || x.info.shortname == "grenade.beancan").FirstOrDefault();
                if (item != null) {
                    GrenadeWeapon weapon = item.GetHeldEntity() as GrenadeWeapon;
                    if (weapon != null) {
                        Brain.Navigator.Stop();
                        SetAimDirection((target - Transform.position).normalized);
                        weapon.ServerThrow(target);
                        IsReloadGrenade = true;
                        Invoke(FinishReloadGrenade, 10f);
                    }
                }
            }
            internal void ThrownSmoke() {
                if (!IsReloadSmoke) {
                    Item item = inventory.containerBelt.itemList.Where(x => x.info.shortname == "grenade.smoke").FirstOrDefault();
                    if (item != null) {
                        GrenadeWeapon weapon = item.GetHeldEntity() as GrenadeWeapon;
                        if (weapon != null) {
                            weapon.ServerThrow(Transform.position);
                            IsReloadSmoke = true;
                            Invoke(FinishReloadSmoke, 30f);
                        }
                    }
                }
            }
#endregion Grenades
#region Run Away Water
            internal bool IsRunAwayWater = false;
            private bool CanRunAwayWater() {
                if (IsRunAwayWater || CurrentTarget == null) return false;
                if (Transform.position.y > -0.25f || TerrainMeta.HeightMap.GetHeight(CurrentTarget.transform.position) > -0.25f) return false;
                if (CurrentWeapon is BaseProjectile && DistanceToTarget < EngagementRange()) return false;
                if (CurrentWeapon is BaseMelee && DistanceToTarget < CurrentWeapon.effectiveRange) return false;
                return true;
            }
            private void RunAwayWater() {
                IsRunAwayWater = true;
                CurrentTarget = null;
                Invoke(FinishRunAwayWater, 20f);
            }
            private void FinishRunAwayWater() => IsRunAwayWater = false;
#endregion Run Away Water
        }
        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> {
            private CustomScientistNPC npc = null;
            public override void InitializeAI() {
                SenseTypes = EntityType.Player;
                npc.Config.Sensory.ApplySettingsToBrain(this);
                base.InitializeAI();
                UseAIDesign = false;
                ThinkMode = AIThinkMode.Interval;
                thinkRate = 0.25f;
                PathFinder = new HumanPathFinder();
                ((HumanPathFinder)PathFinder).Init(npc);
                Navigator.DefaultArea = "Walkable";
                Navigator.topologyPreference = (TerrainTopology.Enum)1673010749;
                Navigator.Speed = npc.Config.Speed;
            }
            public override void AddStates() {
                npc = GetEntity() as CustomScientistNPC;
                states = new Dictionary<AIState, BasicAIState>();
                if (npc.Config.Stationary) {
                    AddState(new IdleState(npc));
                    AddState(new CombatStationaryState(npc));
                }
                else {
                    AddState(new RoamState(npc));
                    AddState(new ChaseState(npc));
                    AddState(new CombatState(npc));
                    AddState(new MountedState(npc));
                }
            }
            public override void Think(float delta) {
                if (npc == null) return;
                Senses.Update();
                base.Think(delta);
                if (sleeping) return;
                if (!npc.IsRunAwayWater) npc.CurrentTarget = npc.GetBestTarget();
            }
            public override void OnDestroy() {
                if (Rust.Application.isQuitting) return;
                BaseEntity.Query.Server.RemoveBrain(npc);
                LeaveGroup();
            }
            public class RoamState : BasicAIState {
                private CustomScientistNPC npc;
                public RoamState(CustomScientistNPC npc) : base(AIState.Roam) {
                    this.npc = npc;
                }
                public override float GetWeight() {
                    if (npc.isMounted) return 0f;
                    if (npc.IsRunAwayWater || npc.CurrentTarget == null) return 25f;
                    return 0f;
                }
                public override void StateEnter() {
                    base.StateEnter();
                    npc.SetPlayerFlag(BasePlayer.PlayerFlags.Relaxed, true);
                }
                public override void StateLeave() {
                    base.StateLeave();
                    npc.ThrownSmoke();
                    npc.SetPlayerFlag(BasePlayer.PlayerFlags.Relaxed, false);
                }
                public override StateStatus StateThink(float delta) {
                    base.StateThink(delta);
                    if (npc.DistanceFromBase > npc.Config.RoamRange) brain.Navigator.SetDestination(npc.HomePosition, BaseNavigator.NavigationSpeed.Fast);
                    else if (!brain.Navigator.Moving) brain.Navigator.SetDestination(GetRoamPosition(), BaseNavigator.NavigationSpeed.Slowest);
                    return StateStatus.Running;
                }
                private Vector3 GetRoamPosition() {
                    Vector2 random = UnityEngine.Random.insideUnitCircle * (npc.Config.RoamRange - 5f);
                    Vector3 result = npc.HomePosition + new Vector3(random.x, 0f, random.y);
                    result.y = TerrainMeta.HeightMap.GetHeight(result);
                    NavMeshHit navmeshHit;
                    if (NavMesh.SamplePosition(result, out navmeshHit, 5f, npc.NavAgent.areaMask)) {
                        NavMeshPath path = new NavMeshPath();
                        if (NavMesh.CalculatePath(npc.Transform.position, navmeshHit.position, npc.NavAgent.areaMask, path)) {
                            if (path.status == NavMeshPathStatus.PathComplete) result = navmeshHit.position;
                            else result = path.corners.Last();
                        }
                        else result = npc.HomePosition;
                    }
                    else result = npc.HomePosition;
                    return result;
                }
            }
            public class ChaseState : BasicAIState {
                private CustomScientistNPC npc;
                public ChaseState(CustomScientistNPC npc) : base(AIState.Chase) {
                    this.npc = npc;
                }
                public override float GetWeight() {
                    if (npc.CurrentTarget == null) return 0f;
                    if (npc.DistanceFromBase > npc.Config.ChaseRange) return 0f;
                    if (npc.IsRunAwayWater) return 0f;
                    if (npc.isMounted) return 0f;
                    if (!(npc.CurrentTarget is BasePlayer) || !npc.CanTargetBasePlayer(npc.CurrentTarget as BasePlayer)) return 0f;
                    return 50f;
                }
                public override StateStatus StateThink(float delta) {
                    base.StateThink(delta);
                    if (npc.CurrentTarget == null) return StateStatus.Error;
                    if (npc.CurrentWeapon is BaseProjectile) brain.Navigator.SetDestination(brain.PathFinder.GetRandomPositionAround(npc.CurrentTarget.transform.position, 1f, 2f), npc.DistanceToTarget >= 10f ? BaseNavigator.NavigationSpeed.Fast : BaseNavigator.NavigationSpeed.Normal);
                    else brain.Navigator.SetDestination(npc.CurrentTarget.transform.position, BaseNavigator.NavigationSpeed.Fast);
                    return StateStatus.Running;
                }
            }
            public class CombatState : BasicAIState {
                private CustomScientistNPC npc;
                private float nextStrafeTime;
                public CombatState(CustomScientistNPC npc) : base(AIState.Combat) {
                    this.npc = npc;
                }
                public override float GetWeight() {
                    if (npc.CurrentTarget == null || npc.CurrentWeapon == null) return 0f;
                    if (npc.DistanceFromBase > npc.Config.ChaseRange) return 0f;
                    if (npc.IsRunAwayWater) return 0f;
                    if (npc.isMounted) return 0f;
                    if (!(npc.CurrentTarget is BasePlayer) || !npc.CanTargetBasePlayer(npc.CurrentTarget as BasePlayer)) return 0f;
                    if (!npc.CanSeeTarget(npc.CurrentTarget) || (npc.CanSeeTarget(npc.CurrentTarget) && npc.IsBehindBarricade())) return 0f;
                    if (npc.DistanceToTarget > npc.EngagementRange()) return 0f;
                    return 75f;
                }
                public override void StateEnter() {
                    base.StateEnter();
                    brain.mainInterestPoint = npc.Transform.position;
                    brain.Navigator.SetCurrentSpeed(BaseNavigator.NavigationSpeed.Normal);
                    brain.Navigator.SetFacingDirectionEntity(npc.CurrentTarget);
                }
                public override void StateLeave() {
                    base.StateLeave();
                    npc.SetDucked(false);
                    brain.Navigator.ClearFacingDirectionOverride();
                }
                public override StateStatus StateThink(float delta) {
                    base.StateThink(delta);
                    if (npc.CurrentTarget == null) return StateStatus.Error;
                    brain.Navigator.SetFacingDirectionEntity(npc.CurrentTarget);
                    if (npc.CurrentWeapon is BaseProjectile) {
                        if (Time.time > nextStrafeTime) {
                            if (UnityEngine.Random.Range(0, 3) == 1) {
                                nextStrafeTime = Time.time + UnityEngine.Random.Range(1f, 2f);
                                npc.SetDucked(true);
                                brain.Navigator.Stop();
                            }
                            else {
                                nextStrafeTime = Time.time + UnityEngine.Random.Range(2f, 3f);
                                npc.SetDucked(false);
                                brain.Navigator.SetDestination(brain.PathFinder.GetRandomPositionAround(brain.mainInterestPoint, 1f, 2f), BaseNavigator.NavigationSpeed.Normal);
                            }
                            npc.ShotTest(npc.DistanceToTarget);
                        }
                    }
                    else if (npc.CurrentWeapon is BaseMelee) {
                        npc.nextTriggerTime = Time.time + 30f;
                        if (npc.DistanceToTarget < npc.CurrentWeapon.effectiveRange) DoMeleeAttack();
                        else brain.Navigator.SetDestination(npc.CurrentTarget.transform.position, BaseNavigator.NavigationSpeed.Fast);
                    }
                    return StateStatus.Running;
                }
                private void DoMeleeAttack() {
                    if (npc.CurrentWeapon is BaseMelee) {
                        BaseMelee baseMelee = npc.CurrentWeapon as BaseMelee;
                        if (!baseMelee.HasAttackCooldown()) {
                            baseMelee.StartAttackCooldown(baseMelee.repeatDelay * 2f);
                            npc.SignalBroadcast(BaseEntity.Signal.Attack, string.Empty, null);
                            if (baseMelee.swingEffect.isValid) Effect.server.Run(baseMelee.swingEffect.resourcePath, baseMelee.transform.position, Vector3.forward, npc.net.connection);
                            DoMeleeDamage(baseMelee);
                        }
                    }
                }
                private void DoMeleeDamage(BaseMelee baseMelee) {
                    Vector3 position = npc.eyes.position;
                    Vector3 forward = npc.eyes.BodyForward();
                    for (int i = 0; i < 2; i++) {
                        List<RaycastHit> list = Pool.GetList<RaycastHit>();
                        GamePhysics.TraceAll(new Ray(position - (forward * (i == 0 ? 0f : 0.2f)), forward), (i == 0 ? 0f : baseMelee.attackRadius), list, baseMelee.effectiveRange + 0.2f, 1219701521, QueryTriggerInteraction.UseGlobal);
                        bool hasHit = false;
                        for (int j = 0; j < list.Count; j++) {
                            RaycastHit raycastHit = list[j];
                            BaseEntity hitEntity = raycastHit.GetEntity();
                            if (hitEntity != null && hitEntity != npc && !hitEntity.EqualNetID(npc) && !(hitEntity is ScientistNPC)) {
                                float damageAmount = 0f;
                                foreach (DamageTypeEntry damageType in baseMelee.damageTypes) damageAmount += damageType.amount;
                                hitEntity.OnAttacked(new HitInfo(npc, hitEntity, DamageType.Slash, damageAmount * baseMelee.npcDamageScale));
                                HitInfo hitInfo = Pool.Get<HitInfo>();
                                hitInfo.HitEntity = hitEntity;
                                hitInfo.HitPositionWorld = raycastHit.point;
                                hitInfo.HitNormalWorld = -forward;
                                if (hitEntity is BaseNpc || hitEntity is BasePlayer) hitInfo.HitMaterial = StringPool.Get("Flesh");
                                else hitInfo.HitMaterial = StringPool.Get((raycastHit.GetCollider().sharedMaterial != null ? raycastHit.GetCollider().sharedMaterial.GetName() : "generic"));
                                Effect.server.ImpactEffect(hitInfo);
                                Pool.Free(ref hitInfo);
                                hasHit = true;
                                if (hitEntity.ShouldBlockProjectiles()) break;
                            }
                        }
                        Pool.FreeList(ref list);
                        if (hasHit) break;
                    }
                }
            }
            public class MountedState : BasicAIState {
                private CustomScientistNPC npc;
                public MountedState(CustomScientistNPC npc) : base(AIState.Mounted) {
                    this.npc = npc;
                }
                public override float GetWeight() {
                    if (npc.isMounted) return 100f;
                    else return 0f;
                }
                public override void StateEnter() {
                    base.StateEnter();
                    if (!npc.Config.CanUseWeaponMounted) npc.HolsterWeapon();
                    DisableNavAgent();
                }
                public override void StateLeave() {
                    base.StateLeave();
                    EnableNavAgent();
                    npc.EquipWeapon();
                }
                private void EnableNavAgent() {
                    Vector3 position = npc.Transform.position;
                    if (npc.Config.EnableNavMesh) if (!NavmeshSpawnPoint.Find(npc.Transform.position, npc.Config.RoamRange, out position)) return;
                    npc.NavAgent.Warp(position);
                    npc.Transform.position = position;
                    npc.HomePosition = position;
                    npc.NavAgent.enabled = true;
                    npc.NavAgent.isStopped = false;
                    brain.Navigator.SetDestination(position, BaseNavigator.NavigationSpeed.Fast);
                }
                private void DisableNavAgent() {
                    if (npc.NavAgent.enabled) {
                        npc.NavAgent.destination = npc.Transform.position;
                        npc.NavAgent.isStopped = true;
                        npc.NavAgent.enabled = false;
                    }
                }
            }
            public class IdleState : BasicAIState {
                private CustomScientistNPC npc;
                public IdleState(CustomScientistNPC npc) : base(AIState.Idle) {
                    this.npc = npc;
                }
                public override float GetWeight() => 50f;
                public override void StateEnter() {
                    base.StateEnter();
                    npc.SetPlayerFlag(BasePlayer.PlayerFlags.Relaxed, true);
                }
                public override void StateLeave() {
                    base.StateLeave();
                    npc.ThrownSmoke();
                    npc.SetPlayerFlag(BasePlayer.PlayerFlags.Relaxed, false);
                }
            }
            public class CombatStationaryState : BasicAIState {
                private CustomScientistNPC npc;
                private float nextStrafeTime;
                public CombatStationaryState(CustomScientistNPC npc) : base(AIState.CombatStationary) {
                    this.npc = npc;
                }
                public override float GetWeight() {
                    if (npc.CurrentTarget == null || npc.CurrentWeapon == null) return 0f;
                    if (!(npc.CurrentTarget is BasePlayer) || !npc.CanTargetBasePlayer(npc.CurrentTarget as BasePlayer)) return 0f;
                    if (!npc.CanSeeTarget(npc.CurrentTarget) || (npc.CanSeeTarget(npc.CurrentTarget) && npc.IsBehindBarricade())) return 0f;
                    if (npc.DistanceToTarget > npc.EngagementRange()) return 0f;
                    return 100f;
                }
                public override void StateEnter() {
                    base.StateEnter();
                    brain.Navigator.SetFacingDirectionEntity(npc.CurrentTarget);
                }
                public override void StateLeave() {
                    base.StateLeave();
                    npc.SetDucked(false);
                    brain.Navigator.ClearFacingDirectionOverride();
                }
                public override StateStatus StateThink(float delta) {
                    base.StateThink(delta);
                    if (npc.CurrentTarget == null) return StateStatus.Error;
                    brain.Navigator.SetFacingDirectionEntity(npc.CurrentTarget);
                    if (npc.CurrentWeapon is BaseProjectile) {
                        if (Time.time > nextStrafeTime) {
                            if (UnityEngine.Random.Range(0, 3) == 1) {
                                nextStrafeTime = Time.time + UnityEngine.Random.Range(1f, 2f);
                                npc.SetDucked(true);
                            }
                            else {
                                nextStrafeTime = Time.time + UnityEngine.Random.Range(2f, 3f);
                                npc.SetDucked(false);
                            }
                            npc.ShotTest(npc.DistanceToTarget);
                        }
                    }
                    return StateStatus.Running;
                }
            }
        }
#endregion Controller
#region NavMesh
        private 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;
            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"
            }
            ;
            private static readonly string[] BLOCKED_COLLIDERS = new string[] {
                "rock", "cliff", "junk", "range", "invisible"
            }
            ;
            public static bool Find(Vector3 targetPosition, float maxDistance, out Vector3 position) {
                for (int i = 0; i < 10; i++) {
                    position = i == 0 ? targetPosition : targetPosition + (UnityEngine.Random.onUnitSphere * maxDistance);
                    if (NavMesh.SamplePosition(position, out navmeshHit, maxDistance, 1)) {
                        if (IsInRockPrefab(navmeshHit.position)) continue;
                        if (IsNearWorldCollider(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;
            }
        }
#endregion
#region Oxide Hooks
        private static NpcSpawn ins;
        private List<CustomScientistNPC> Npcs = new List<CustomScientistNPC>();
        private void Init() => ins = this;
        private void Unload() {
            foreach (CustomScientistNPC npc in Npcs) if (npc != null) npc.Kill(BaseNetworkable.DestroyMode.None);
            ins = null;
        }
        private void OnEntityKill(CustomScientistNPC npc) {
            if (npc != null && Npcs.Contains(npc)) Npcs.Remove(npc);
        }
        private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info) {
            if (entity == null || info == null) return null;
            BaseEntity attacker = info.Initiator;
            if (entity is CustomScientistNPC && Npcs.Contains(entity as CustomScientistNPC)) {
                CustomScientistNPC npc = entity as CustomScientistNPC;
                if (npc.CurrentTarget == null && npc.CanTargetBasePlayer(attacker as BasePlayer)) npc.CurrentTarget = attacker as BasePlayer;
                if (attacker is AutoTurret || attacker is GunTrap || attacker is FlameTurret) {
                    info.damageTypes.ScaleAll(0.1f);
                    return null;
                }
                if (attacker is BasePlayer && (attacker as BasePlayer).userID.IsSteamId()) return null;
                else return true;
            }
            if (attacker is CustomScientistNPC && Npcs.Contains(attacker as CustomScientistNPC)) {
                if (entity is BasePlayer) {
                    if ((entity as BasePlayer).userID.IsSteamId()) return null;
                    else return true;
                }
                else if (entity.OwnerID.IsSteamId()) return null;
                else return true;
            }
            return null;
        }
        private object CanBeTargeted(CustomScientistNPC npc, MonoBehaviour behaviour) {
            if (npc == null || behaviour == null) return null;
            if (Npcs.Contains(npc)) {
                BaseEntity attacker = behaviour as BaseEntity;
                if (attacker != null) {
                    if ((attacker is AutoTurret || attacker is GunTrap || attacker is FlameTurret) && attacker.OwnerID != 0 && attacker.OwnerID.IsSteamId()) return null;
                    else return false;
                }
                else return false;
            }
            else return null;
        }
        private object OnNpcTarget(CustomScientistNPC npc, BaseEntity entity) {
// ... [Code preview truncated due to length. Full source in attached file] ...

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