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

FREE RoamingNPCVendors : 0.1.6 0.1.6

Нет прав для скачивания
C#:
using Facepunch;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Oxide.Core;
using Oxide.Game.Rust.Cui;
using Oxide.Plugins;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.Networking;
using Random = UnityEngine.Random;
using Time = UnityEngine.Time;
namespace Oxide.Plugins {
    [Info("Roaming NPC Vendors", "k1lly0u", "0.1.6")]
    class RoamingNPCVendors : RustPlugin {
#region Oxide Hooks
        private readonly List<RoamingVendor> activeVendors = new List<RoamingVendor>();
        private static Action<BasePlayer, string, Vector3> Message;
        private const string ADMIN_PERMISSION = "roamingnpcvendors.admin";
        private const TerrainTopology.Enum PREFERRED_TOPOLOGY = TerrainTopology.Enum.Roadside;
        private const TerrainTopology.Enum BLOCKED_TOPOLOGY = TerrainTopology.Enum.Road;
        private void OnServerInitialized() {
            Message = ChatMessage;
            permission.RegisterPermission(ADMIN_PERMISSION, this);
            WalkablePath.GeneratePathMap(SpawnVendors);
        }
        protected override void LoadDefaultMessages() {
            lang.RegisterMessages(new Dictionary<string, string> {
                ["OnNPCAttacked"]
                = "<size=20> Don't shoot, I'm friendly!</size>"
            }
            , this);
        }
        private object CanBradleyApcTarget(BradleyAPC bradleyAPC, RoamingVendor roamingVendor) => false;
        private void OnEntityKill(RoamingVendor roamingVendor) {
            if (roamingVendor != null) activeVendors.Remove(roamingVendor);
        }
        private void Unload() {
            foreach (BasePlayer player in BasePlayer.activePlayerList) CuiHelper.DestroyUi(player, PaginatedVendingMachine.UI_PANEL);
            for (int i = 0; i < activeVendors.Count; i++) {
                RoamingVendor roamingVendor = activeVendors[i];
                if (roamingVendor == null || roamingVendor.IsDestroyed) continue;
                roamingVendor.inventory.Strip();
                roamingVendor.machine.inventory.Clear();
                roamingVendor.machine.Kill();
                roamingVendor.Kill();
            }
            activeVendors.Clear();
            PaginatedVendingMachine.ActiveVendingPlayers.Clear();
            Message = null;
            Configuration = null;
        }
#endregion
#region Functions
        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 void ChatMessage(BasePlayer player, string key, Vector3 position) => player.SendConsoleCommand("ddraw.text", 10, Color.red, position, lang.GetMessage(key, this, player.UserIDString));
        private void SpawnVendors() {
            if (WalkablePath._allPaths.Count == 0) {
                Debug.LogError($"[Roaming NPC Vendors] No paths found on map");
                return;
            }
            for (int i = 0; i < Configuration.Vendors.Count; i++) {
                if (!Configuration.Vendors[i].Enabled) continue;
                Vector3 position;
                SpawnVendor(Configuration.Vendors[i], out position);
            }
        }
        private bool SpawnVendor(ConfigData.Vendor vendor, out Vector3 position) {
            position = Vector3.zero;
            if (WalkablePath._allPaths.Count == 0) return false;
            WalkablePath walkablePath = WalkablePath.GetRandom();
            for (int y = 0; y < 10; y++) {
                int positionIndex;
                position = walkablePath.GetRandomPosition(out positionIndex);
                if (NavmeshSpawnPoint.Find(position, 10f, out position)) {
                    RoamingVendor roamingVendor = InstantiateTrader(position, vendor);
                    roamingVendor.WalkablePath = walkablePath;
                    roamingVendor.PathIndex = positionIndex;
                    roamingVendor.Spawn();
                    activeVendors.Add(roamingVendor);
                    Debug.Log($"[Roaming NPC Vendors] Spawned {vendor.VendorName} at {position}");
                    return true;
                }
            }
            return false;
        }
#endregion
#region Pathing
        public class HapisFixedPath : WalkablePath {
            private List<Vector3> nodes;
            public HapisFixedPath(Action callback) : base() {
                ServerMgr.Instance.StartCoroutine(DownloadHapisFixedPath(callback));
            }
            public override Vector3 GetNextNode(ref WalkablePath path, ref int currentIndex, ref bool isReversed) {
                currentIndex++;
                if (currentIndex >= nodes.Count) currentIndex = 0;
                return nodes[currentIndex];
            }
            public override Vector3 GetRandomPosition(out int index) {
                index = Random.Range(0, nodes.Count);
                return nodes[index];
            }
            public IEnumerator DownloadHapisFixedPath(Action callback) {
                Debug.Log("[Roaming NPC Vendors] Downloading Hapis fixed path...");
                const string GITHUB_URL = "https://github.com/k1lly0u/RoamingNPCVendors-Hapis/raw/main/hapis-fixed-path.json";
                UnityWebRequest request = UnityWebRequest.Get(GITHUB_URL);
                yield return request.SendWebRequest();
                if (request.isNetworkError || request.isHttpError) {
                    Debug.Log($"[Roaming NPC Vendors] Failed to download Hapis path");
                    request.Dispose();
                    yield break;
                }
                nodes = JsonConvert.DeserializeObject<List<Vector3>>(request.downloadHandler.text);
                request.Dispose();
                callback.Invoke();
            }
        }
        public class WalkablePath {
            public static readonly List<WalkablePath> _allPaths = new List<WalkablePath>();
            private readonly PathList pathList;
            private Hash<int, ConnectedPath> intersections;
            private bool IsTrailPath {
                get;
                set;
            }
            private float PathOffset {
                get;
                set;
            }
            public static void GeneratePathMap(Action callback) {
                if (ConVar.Server.level == "HapisIsland") {
                    if (_allPaths.Count == 0) _allPaths.Add(new HapisFixedPath(callback));
                    else callback.Invoke();
                }
                else {
                    if (_allPaths.Count == 0) {
                        for (int i = 0; i < TerrainMeta.Path.Roads.Count; i++) _allPaths.Add(new WalkablePath(TerrainMeta.Path.Roads[i]));
                        for (int i = 0; i < _allPaths.Count; i++) _allPaths[i].FindIntersections();
                        for (int i = _allPaths.Count - 1; i >= 0; i--) {
                            if (!_allPaths[i].HasIntersections()) _allPaths.RemoveAt(i);
                        }
                        Debug.Log($"[Roaming NPC Vendors] Generated path map with {_allPaths.Count} connected paths");
                    }
                    else Debug.Log($"[Roaming NPC Vendors] Using previously generated path map with {_allPaths.Count} connected paths");
                    callback.Invoke();
                }
            }
            public WalkablePath() {
            }
            public WalkablePath(PathList pathList) {
                this.pathList = pathList;
                IsTrailPath = pathList.Width < 5f;
                PathOffset = pathList.Width * 0.6f;
            }
            public void FindIntersections() {
                Vector3 startPoint = pathList.Path.GetStartPoint();
                Vector3 endPoint = pathList.Path.GetEndPoint();
                for (int y = 0; y < _allPaths.Count; y++) {
                    WalkablePath walkablePath = _allPaths[y];
                    if (walkablePath == this) continue;
                    for (int i = 0; i < walkablePath.pathList.Path.Points.Length; i++) {
                        Vector3 n = walkablePath.pathList.Path.Points[i];
                        if (Vector3.Distance(startPoint, n) < 6f) {
                            RegisterIntersection(0, walkablePath, i);
                            walkablePath.RegisterIntersection(i, this, 0);
                        }
                        if (Vector3.Distance(endPoint, n) < 6f) {
                            RegisterIntersection(pathList.Path.Points.Length - 1, walkablePath, i);
                            walkablePath.RegisterIntersection(i, this, pathList.Path.Points.Length - 1);
                        }
                    }
                }
            }
            public bool HasIntersections() => intersections != null && intersections.Count > 0;
            private void RegisterIntersection(int index, WalkablePath connectedPath, int connectedIndex) {
                if (intersections == null) intersections = new Hash<int, ConnectedPath>();
                intersections[index] = new ConnectedPath(connectedPath, connectedIndex);
            }
            private bool HasIntersection(int index, out ConnectedPath connectedPath) {
                if (intersections == null) {
                    connectedPath = default(ConnectedPath);
                    return false;
                }
                return intersections.TryGetValue(index, out connectedPath);
            }
            public virtual Vector3 GetNextNode(ref WalkablePath path, ref int currentIndex, ref bool isReversed) {
                PathList pathList = this.pathList;
                ConnectedPath connectedPath;
                if (HasIntersection(currentIndex, out connectedPath)) {
                    if ((currentIndex == 0 || currentIndex >= pathList.Path.Points.Length - 1) || (currentIndex > 0 && currentIndex < pathList.Path.Points.Length - 1 && Random.Range(0f, 1f) <= 0.5f)) {
                        path = connectedPath.walkablePath;
                        currentIndex = connectedPath.connectedIndex;
                        pathList = path.pathList;
                        isReversed = currentIndex >= pathList.Path.Points.Length - 1 ? true : Random.Range(0f, 1f) > 0.5f;
                    }
                }
                if (isReversed) {
                    if (currentIndex == 0) {
                        if (pathList.Path.Circular) {
                            currentIndex = pathList.Path.Points.Length - 1;
                        }
                        else {
                            isReversed = false;
                            currentIndex = Mathf.Clamp(currentIndex + 1, 0, pathList.Path.Points.Length - 1);
                        }
                    }
                    else currentIndex = Mathf.Clamp(currentIndex - 1, 0, pathList.Path.Points.Length - 1);
                }
                else {
                    if (currentIndex == pathList.Path.Points.Length - 1) {
                        if (pathList.Path.Circular) {
                            currentIndex = 0;
                        }
                        else {
                            isReversed = true;
                            currentIndex = Mathf.Clamp(currentIndex - 1, 0, pathList.Path.Points.Length - 1);
                        }
                    }
                    else currentIndex = Mathf.Clamp(currentIndex + 1, 0, pathList.Path.Points.Length - 1);
                }
                if (IsTrailPath) return pathList.Path.Points[currentIndex];
                else {
                    Vector3 currentNode = isReversed ? pathList.Path.Points[Mathf.Clamp(currentIndex + 1, 0, pathList.Path.Points.Length - 1)] : pathList.Path.Points[Mathf.Clamp(currentIndex - 1, 0, pathList.Path.Points.Length - 1)];
                    Vector3 nextNode = pathList.Path.Points[currentIndex];
                    Quaternion rotation = Quaternion.LookRotation((currentNode - nextNode).normalized, Vector3.up);
                    return nextNode + (rotation * (Vector3.right * PathOffset));
                }
            }
            public static WalkablePath GetRandom() => _allPaths.GetRandom();
            public virtual Vector3 GetRandomPosition(out int index) {
                index = Random.Range(0, pathList.Path.Points.Length);
                return pathList.Path.Points[index];
            }
            private struct ConnectedPath {
                public WalkablePath walkablePath;
                public int connectedIndex;
                public ConnectedPath(WalkablePath walkablePath, int connectedIndex) {
                    this.walkablePath = walkablePath;
                    this.connectedIndex = connectedIndex;
                }
            }
        }
#endregion
#region NPC Spawning
        private static RoamingVendor _vendorReference;
        private static RoamingVendor VendorReference {
            get {
                if (_vendorReference == null) {
                    const string BANDIT_SHOPKEEPER = "assets/prefabs/npc/bandit/shopkeepers/bandit_shopkeeper.prefab";
                    GameObject gameObject = Instantiate.GameObject(GameManager.server.FindPrefab(BANDIT_SHOPKEEPER), Vector3.zero, Quaternion.identity);
                    gameObject.name = BANDIT_SHOPKEEPER;
                    gameObject.SetActive(false);
                    NPCShopKeeper npcShopKeeper = gameObject.GetComponent<NPCShopKeeper>();
                    RoamingVendor roamingVendor = gameObject.AddComponent<RoamingVendor>();
                    CopySerializeableFields<NPCShopKeeper>(npcShopKeeper, roamingVendor);
                    NPCPlayerNavigator navigator = gameObject.AddComponent<NPCPlayerNavigator>();
                    navigator.Acceleration = 8f;
                    navigator.Speed = 6.2f;
                    navigator.TurnSpeed = 120;
                    navigator.DefaultArea = "Walkable";
                    navigator.topologyPreference = TerrainTopology.Enum.Roadside;
                    VendorBrain brain = gameObject.AddComponent<VendorBrain>();
                    brain.SenseTypes = EntityType.Player;
                    brain.MaxGroupSize = int.MaxValue;
                    brain.AttackRangeMultiplier = 1f;
                    brain.SenseRange = 5;
                    brain.ListenRange = 5;
                    brain.TargetLostRange = 5;
                    brain.CheckVisionCone = true;
                    brain.IgnoreNonVisionSneakers = true;
                    brain.IgnoreSafeZonePlayers = true;
                    brain.VisionCone = Vector3.Dot(Vector3.forward, Quaternion.Euler(0f, 135f, 0f) * Vector3.forward);
                    brain.UseAIDesign = true;
                    brain.Pet = false;
                    NavMeshAgent navAgent = gameObject.AddComponent<NavMeshAgent>();
                    navAgent.stoppingDistance = 0.25f;
                    navAgent.autoBraking = true;
                    navAgent.autoRepath = true;
                    navAgent.agentTypeID = 0;
                    navAgent.areaMask = 1;
                    navAgent.agentTypeID = -1372625422;
                    navAgent.speed = 3.5f;
                    navAgent.angularSpeed = 120;
                    navAgent.acceleration = 8;
                    navAgent.radius = 0.5f;
                    navAgent.height = 2;
                    navAgent.obstacleAvoidanceType = ObstacleAvoidanceType.HighQualityObstacleAvoidance;
                    navAgent.avoidancePriority = 50;
                    navAgent.updatePosition = true;
                    navAgent.updateRotation = true;
                    navAgent.updateUpAxis = true;
                    roamingVendor.enableSaving = false;
                    UnityEngine.Object.DestroyImmediate(npcShopKeeper);
                    _vendorReference = roamingVendor;
                }
                return _vendorReference;
            }
        }
        private static RoamingVendor InstantiateTrader(Vector3 position, ConfigData.Vendor config) {
            RoamingVendor roamingVendor = UnityEngine.Object.Instantiate<RoamingVendor>(VendorReference, position, Quaternion.identity);
            roamingVendor.name = VendorReference.name;
            roamingVendor.Config = config;
            UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(roamingVendor.gameObject, Rust.Server.EntityScene);
            roamingVendor.gameObject.SetActive(true);
            return roamingVendor;
        }
        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;
            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 * 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"
            }
            ;
            private static readonly string[] BLOCKED_COLLIDERS = new string[] {
                "rock", "cliff", "junk", "range", "invisible"
            }
            ;
        }
#endregion
#region NPC Components
        public class RoamingVendor : NPCShopKeeper, IAISenses, IAIAttack, IThinker {
            private float lastThinkTime;
            private VendingMachineMapMarker mapMarker;
            public WalkablePath WalkablePath;
            public int PathIndex;
            public bool IsPanicked {
                get;
                private set;
            }
            public ConfigData.Vendor Config {
                get;
                set;
            }
            public Transform Transform {
                get;
                private set;
            }
            public VendorBrain Brain {
                get;
                private set;
            }
            private void Awake() {
                Transform = transform;
            }
            public override void ServerInit() {
                loadouts = null;
                machine = InstantiateVendingMachine(Transform.position);
                (machine as PaginatedVendingMachine).Vendor = this;
                machine.syncPosition = true;
                machine.enableSaving = false;
                machine.Spawn();
                mapMarker = typeof(VendingMachine).GetField("myMarker", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).GetValue(machine) as VendingMachineMapMarker;
                if (!Configuration.ShowMapMarker) machine.SetFlag(Flags.Reserved4, false);
                LegacyNavigation = false;
                base.ServerInit();
                Config.ApplyClothing(this);
                Brain = gameObject.GetComponent<VendorBrain>();
                AIThinkManager.Add(this);
                InvokeRandomized(UpdateVendingPositionRotation, 1f, 1f, 0.1f);
                Invoke(SetDisplayName, 1f);
            }
            private void SetDisplayName() => displayName = Config.VendorName;
            private void UpdateVendingPositionRotation() {
                machine.transform.position = Transform.position;
                machine.transform.rotation = Quaternion.Euler(0f, eyes.rotation.eulerAngles.y, 0f);
                machine.SendNetworkUpdate_Position();
                if (mapMarker != null && !mapMarker.IsDestroyed) {
                    mapMarker.transform.position = Transform.position;
                    mapMarker.SendNetworkUpdate_Position();
                }
            }
            public override void Hurt(HitInfo info) {
                BasePlayer player = info.InitiatorPlayer;
                if (player != null && (player != lastAttacker || Time.time - lastAttackedTime > 10f)) {
                    lastAttackedTime = Time.time;
                    Message(player, "OnNPCAttacked", eyes.position + (Vector3.up * 0.1f));
                    lastAttacker = player;
                    IsPanicked = true;
                    CancelInvoke(CancelPanic);
                    Invoke(CancelPanic, 30f);
                }
            }
            private void CancelPanic() => IsPanicked = false;
#region IThinker
            public override void ServerThink(float delta) {
                base.ServerThink(delta);
                if (Brain.ShouldServerThink()) Brain.DoThink();
            }
            public void TryThink() {
                ServerThink(Time.time - lastThinkTime);
                lastThinkTime = Time.time;
            }
#endregion
#region IAISenses
            public bool IsFriendly(BaseEntity entity) => !IsPanicked;
            public bool IsThreat(BaseEntity entity) => IsTarget(entity);
            public bool IsTarget(BaseEntity entity) {
                if (IsPanicked) return false;
                BasePlayer player = entity as BasePlayer;
                if (player != null && player.IsConnected && !player.IsSleeping()) return true;
                return false;
            }
#endregion
#region IAIAttack
            public void AttackTick(float delta, BaseEntity target, bool targetIsLOS) {
            }
            public BaseEntity GetBestTarget() {
                BaseEntity baseEntity = null;
                float delta = -1f;
                if (IsPanicked) return baseEntity;
                foreach (BaseEntity player in Brain.Senses.Players) {
                    if (player == null || player.Health() <= 0f || player.IsNpc) continue;
                    if (!(player is BasePlayer) || !(player as BasePlayer).IsConnected || (player as BasePlayer).IsSleeping()) continue;
                    float distanceToTarget = Vector3.Distance(player.transform.position, Transform.position);
                    if (distanceToTarget > Brain.SenseRange) continue;
                    float rangeDelta = 1f - Mathf.InverseLerp(1f, Brain.SenseRange, distanceToTarget);
                    float dot = Vector3.Dot((player.transform.position - eyes.position).normalized, eyes.BodyForward());
                    rangeDelta += Mathf.InverseLerp(Brain.VisionCone, 1f, dot) / 2f;
                    rangeDelta += (Brain.Senses.Memory.IsLOS(player) ? 2f : 0f);
                    if (rangeDelta <= delta) continue;
                    baseEntity = player;
                    delta = rangeDelta;
                }
                return baseEntity;
            }
            public bool CanAttack(BaseEntity entity) => false;
            public float EngagementRange() => 5f;
            public bool IsTargetInRange(BaseEntity entity, out float dist) {
                dist = Vector3.Distance(entity.transform.position, Transform.position);
                BasePlayer player = entity as BasePlayer;
                if (player == null || !player.IsConnected || player.IsSleeping()) return false;
                return IsPanicked ? false : dist <= EngagementRange();
            }
            public bool CanSeeTarget(BaseEntity entity) {
                if (IsPanicked) return false;
                BasePlayer player = entity as BasePlayer;
                if (player == null || !player.IsConnected || player.IsSleeping()) return false;
                if (Vector3.Distance(entity.transform.position, Transform.position) > 5f) return false;
                return IsPlayerVisibleToUs(player, 1218519041);
            }
            public float GetAmmoFraction() => 0f;
            public bool NeedsToReload() => false;
            public bool Reload() => false;
            public float CooldownDuration() => 5f;
            public bool IsOnCooldown() => false;
            public bool StartAttacking(BaseEntity entity) => false;
            public void StopAttacking() {
            }
#endregion
        }
        public class VendorBrain : BaseAIBrain {
            public override void InitializeAI() {
                RoamingVendor entity = GetBaseEntity() as RoamingVendor;
                entity.HasBrain = true;
                Navigator = GetComponent<BaseNavigator>();
                AIDesign = new AIDesign();
                AIDesign.SetAvailableStates(GetStateList());
                if (Events == null) Events = new AIEvents();
                Senses.Init(entity, this, MemoryDuration, SenseRange, TargetLostRange, VisionCone, CheckVisionCone, CheckLOS, IgnoreNonVisionSneakers, ListenRange, HostileTargetsOnly, false, IgnoreSafeZonePlayers, SenseTypes, RefreshKnownLOS);
                Events.Memory.Position.Set(transform.position, 4);
                LoadAIDesign(ProtoBuf.AIDesign.Deserialize(DefaultDesign), null, 0);
                AIInformationZone forPoint = AIInformationZone.GetForPoint(transform.position, false);
                if (forPoint != null) forPoint.RegisterSleepableEntity(this);
                BaseEntity.Query.Server.AddBrain(entity);
                CancelInvoke(TickMovement);
                InvokeRandomized(TickMovement, 1f, 0.1f, 0.0100000007f);
                ThinkMode = AIThinkMode.Interval;
                thinkRate = 0.25f;
                PathFinder = new HumanPathFinder();
                ((HumanPathFinder)PathFinder).Init(entity);
            }
            public override void AddStates() {
                states = new Dictionary<AIState, BasicAIState>();
                AddState(new BaseIdleState());
                AddState(new RoamState());
                AddState(new InteractionState());
            }
            public class RoamState : BasicAIState {
                private StateStatus status = StateStatus.Error;
                private bool isReversed;
                private static readonly Vector3[] preferedTopologySamples = new Vector3[4];
                private static readonly Vector3[] topologySamples = new Vector3[4];
                public RoamState() : base(AIState.Roam) {
                }
                public override void StateEnter(BaseAIBrain brain, BaseEntity entity) {
                    base.StateEnter(brain, entity);
                    status = StateStatus.Error;
                    if (brain.PathFinder == null) return;
                    RoamingVendor vendor = entity as RoamingVendor;
                    Vector3 bestRoamPosition = vendor.WalkablePath.GetNextNode(ref vendor.WalkablePath, ref vendor.PathIndex, ref isReversed);
                    if (brain.Navigator.GetNearestNavmeshPosition(bestRoamPosition, out bestRoamPosition, 20f) && brain.Navigator.IsAcceptableWaterDepth(bestRoamPosition)) {
                        if (brain.Navigator.SetDestination(bestRoamPosition, vendor.IsPanicked ? BaseNavigator.NavigationSpeed.Fast : vendor.Config.PreferredSpeed, 0f, 0f)) {
                            status = StateStatus.Running;
                            return;
                        }
                    }
                    status = StateStatus.Error;
                }
                public override void StateLeave(BaseAIBrain brain, BaseEntity entity) {
                    base.StateLeave(brain, entity);
                    Stop();
                }
                public override StateStatus StateThink(float delta, BaseAIBrain brain, BaseEntity entity) {
                    base.StateThink(delta, brain, entity);
                    if (Vector3.Distance(brain.Navigator.Destination, (entity as RoamingVendor).Transform.position) < 2f) StateEnter(brain, entity);
                    if (status == StateStatus.Error) return status;
                    if (brain.Navigator.Moving) return StateStatus.Running;
                    return StateStatus.Finished;
                }
                private void Stop() => brain.Navigator.Stop();
                private bool IsPositionATopologyPreference(Vector3 position) {
                    if (TerrainMeta.TopologyMap != null) {
                        int topology = TerrainMeta.TopologyMap.GetTopology(position);
                        if (((int)PREFERRED_TOPOLOGY & topology) > 0 && ((int)BLOCKED_TOPOLOGY & topology) == 0) return true;
                    }
                    return false;
                }
            }
            public class InteractionState : BasicAIState {
                public InteractionState() : base(AIState.TakeCover) {
                }
                public override void StateEnter(BaseAIBrain brain, BaseEntity entity) {
                    base.StateEnter(brain, entity);
                    RoamingVendor vendor = entity as RoamingVendor;
                    if (vendor.IsPanicked) {
                        brain.SwitchToState(AIState.Roam, 1);
                        return;
                    }
                    BasePlayer player = brain.Events.Memory.Entity.Get(brain.Events.CurrentInputMemorySlot) as BasePlayer;
                    if (player != null) {
                        vendor.SignalBroadcast(BaseEntity.Signal.Gesture, "wave", null);
                        vendor.SetAimDirection(Vector3Ex.Direction2D(player.eyes.position, vendor.eyes.position));
                        brain.Navigator.SetFacingDirectionEntity(player);
                    }
                }
                public override void StateLeave(BaseAIBrain brain, BaseEntity entity) {
                    base.StateLeave(brain, entity);
                    brain.Navigator.ClearFacingDirectionOverride();
                }
                public override StateStatus StateThink(float delta, BaseAIBrain brain, BaseEntity entity) {
                    base.StateThink(delta, brain, entity);
                    return StateStatus.Running;
                }
            }
            private static readonly byte[] DefaultDesign = new byte[] {
                8, 1, 8, 2, 8, 18, 18, 61, 8, 0, 16, 1, 26, 25, 8, 0, 16, 1, 24, 0, 32, 0, 40, 0, 48, 0, 162, 6, 10, 13, 0, 0, 0, 0, 21, 0, 0, 0, 0, 26, 12, 8, 3, 16, 2, 24, 0, 32, 0, 40, 0, 48, 0, 26, 12, 8, 14, 16, 2, 24, 0, 32, 0, 40, 0, 48, 0, 32, 0, 18, 62, 8, 1, 16, 2, 26, 12, 8, 4, 16, 0, 24, 0, 32, 0, 40, 0, 48, 0, 26, 12, 8, 2, 16, 0, 24, 0, 32, 0, 40, 0, 48, 0, 26, 12, 8, 3, 16, 2, 24, 0, 32, 0, 40, 0, 48, 0, 26, 12, 8, 14, 16, 2, 24, 0, 32, 0, 40, 0, 48, 0, 32, 0, 18, 48, 8, 2, 16, 18, 26, 12, 8, 4, 16, 0, 24, 0, 32, 0, 40, 0, 48, 0, 26, 12, 8, 2, 16, 0, 24, 0, 32, 0, 40, 0, 48, 0, 26, 12, 8, 5, 16, 1, 24, 1, 32, 0, 40, 0, 48, 0, 32, 0, 24, 0, 40, 0, 48, 0
            }
            ;
        }
#endregion
#region Vending Spawning
        private static PaginatedVendingMachine _vendingReference;
        private static PaginatedVendingMachine VendingReference {
            get {
                if (_vendingReference == null) {
                    const string INVIS_VENDING_MACHINE = "assets/prefabs/deployable/vendingmachine/npcvendingmachines/shopkeeper_vm_invis.prefab";
                    GameObject gameObject = Instantiate.GameObject(GameManager.server.FindPrefab(INVIS_VENDING_MACHINE), Vector3.zero, Quaternion.identity);
                    gameObject.name = INVIS_VENDING_MACHINE;
                    gameObject.SetActive(false);
                    InvisibleVendingMachine invisibleVendingMachine = gameObject.GetComponent<InvisibleVendingMachine>();
                    PaginatedVendingMachine paginatedVendingMachine = gameObject.AddComponent<PaginatedVendingMachine>();
                    CopySerializeableFields<InvisibleVendingMachine>(invisibleVendingMachine, paginatedVendingMachine);
                    paginatedVendingMachine.enableSaving = false;
                    UnityEngine.Object.DestroyImmediate(invisibleVendingMachine);
                    _vendingReference = paginatedVendingMachine;
                }
                return _vendingReference;
            }
        }
        private static PaginatedVendingMachine InstantiateVendingMachine(Vector3 position) {
            PaginatedVendingMachine paginatedVendingMachine = UnityEngine.Object.Instantiate<PaginatedVendingMachine>(VendingReference, position, Quaternion.identity);
            paginatedVendingMachine.name = VendorReference.name;
            UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(paginatedVendingMachine.gameObject, Rust.Server.EntityScene);
            paginatedVendingMachine.gameObject.SetActive(true);
            return paginatedVendingMachine;
        }
#endregion
#region Vending Components
        public class PaginatedVendingMachine : InvisibleVendingMachine {
            private int currentPage = 0;
            private float[] refillTimes = new float[0];
            private bool isInitialized = false;
            private readonly List<ConfigData.Vendor.VendingEntry> items = new List<ConfigData.Vendor.VendingEntry>();
            private readonly List<ConfigData.Vendor.VendingEntry> validItems = new List<ConfigData.Vendor.VendingEntry>();
            public RoamingVendor Vendor {
                get;
                set;
            }
            private int TotalPages {
                get {
                    return Mathf.CeilToInt((float)items.Count / (float)TOTAL_PER_PAGE);
                }
            }
            public static readonly Hash<ulong, PaginatedVendingMachine> ActiveVendingPlayers = new Hash<ulong, PaginatedVendingMachine>();
            private const int TOTAL_PER_PAGE = 7;
            public override void ServerInit() {
                vendingOrders = ScriptableObject.CreateInstance<NPCVendingOrder>();
                vmoManifest = ScriptableObject.CreateInstance<NPCVendingOrderManifest>();
                for (int i = 0; i < Vendor.Config.Items.Count; i++) {
                    NPCVendingOrder.Entry entry = Vendor.Config.Items[i];
                    if (entry != null) validItems.Add(Vendor.Config.Items[i]);
                }
                int maxItems = Mathf.Clamp(Vendor.Config.MaxItems, 0, validItems.Count);
                if (maxItems > 0 && maxItems < validItems.Count) {
                    RandomlySelectSellOrders();
                    Invoke(RandomlySelectSellOrders, Mathf.Max(Vendor.Config.RefreshRate, 60f));
                }
                else {
                    items.AddRange(validItems);
                    UpdateSellOrderManifest();
                }
                isInitialized = true;
                base.ServerInit();
                CancelInvoke(Refill);
                InvokeRandomized(DoRefill, 1f, 1f, 0.1f);
                shopName = Vendor.Config.VendorName;
            }
            private void RandomlySelectSellOrders() {
                if (ActiveVendingPlayers.Values.Contains(this)) {
                    Invoke(RandomlySelectSellOrders, 20f);
                    return;
                }
                items.Clear();
                List<ConfigData.Vendor.VendingEntry> itemList = Pool.GetList<ConfigData.Vendor.VendingEntry>();
                itemList.AddRange(validItems);
                int maxItems = Mathf.Clamp(Vendor.Config.MaxItems, 0, validItems.Count);
                while(items.Count < maxItems) {
                    if (itemList.Count == 0) break;
                    ConfigData.Vendor.VendingEntry entry = itemList.GetRandom();
                    itemList.Remove(entry);
                    items.Add(entry);
                }
                Pool.FreeList(ref itemList);
                UpdateSellOrderManifest();
                InstallFromVendingOrders();
                Invoke(RandomlySelectSellOrders, Mathf.Max(Vendor.Config.RefreshRate, 60f));
            }
            private void UpdateSellOrderManifest() {
                if (refillTimes.Length != items.Count) {
                    Array.Resize(ref refillTimes, items.Count);
                    for (int i = 0; i < refillTimes.Length; i++) refillTimes[i] = 0;
                }
                inventorySlots = Mathf.Max(inventorySlots, items.Count);
                if (inventory != null) inventory.capacity = inventorySlots;
                vendingOrders.orders = items.Select(x => (NPCVendingOrder.Entry)x).ToArray();
                vmoManifest.orderList = new NPCVendingOrder[] {
                    vendingOrders
                }
                ;
            }
            public override void InstallFromVendingOrders() {
                if (!isInitialized) return;
                if (items == null || items.Count == 0) {
                    Debug.LogError("[Roaming NPC Vendors] No vending orders installed!");
                    return;
                }
                ClearSellOrders();
                inventory.Clear();
                ItemManager.DoRemoves();
                for (int i = 0; i < items.Count; i++) {
                    NPCVendingOrder.Entry entry = items[i];
                    if (entry != null) AddItemForSale(entry.sellItem.itemid, entry.sellItemAmount, entry.currencyItem.itemid, entry.currencyAmount, GetBPState(entry.sellItemAsBP, entry.currencyAsBP), items[i].SellItemSkin, items[i].StockAmount, i < TOTAL_PER_PAGE);
                }
            }
            public void AddItemForSale(int itemID, int amountToSell, int currencyID, int currencyPerTransaction, byte bpState, ulong skinID, int stockAmount, bool addSellOrder) {
                if (addSellOrder) AddSellOrder(itemID, amountToSell, currencyID, currencyPerTransaction, bpState);
                transactionActive = true;
                if (bpState == 1 || bpState == 3) {
                    for (int i = 0; i < stockAmount; i++) {
                        Item item = ItemManager.CreateByItemID(blueprintBaseDef.itemid, 1, 0);
                        item.blueprintTarget = itemID;
                        inventory.Insert(item);
                    }
                }
                else {
                    inventory.AddItem(ItemManager.FindItemDefinition(itemID), amountToSell * stockAmount, skinID);
                }
                transactionActive = false;
                RefreshSellOrderStockLevel(null);
            }
            public void ChangePage(BasePlayer player, int direction) {
                currentPage = Mathf.Clamp(currentPage + direction, 0, TotalPages);
                int startIndex = currentPage * TOTAL_PER_PAGE;
                ClearSellOrders();
                for (int i = startIndex; i < Mathf.Min(startIndex + TOTAL_PER_PAGE, items.Count);
                i++) {
                    NPCVendingOrder.Entry entry = items[i];
                    AddSellOrder(entry.sellItem.itemid, entry.sellItemAmount, entry.currencyItem.itemid, entry.currencyAmount, GetBPState(entry.sellItemAsBP, entry.currencyAsBP));
                }
                ClientRPCPlayer<ProtoBuf.VendingMachine.SellOrderContainer>(null, player, "CLIENT_ReceiveSellOrders", sellOrders);
                SetFlag(BaseEntity.Flags.Reserved2, true, false, true);
                Invoke(() => SetFlag(BaseEntity.Flags.Reserved2, false, false, true), 0.5f);
                CreateOverlay(player);
            }
            public override bool PlayerOpenLoot(BasePlayer player, string panelToOpen = "", bool doPositionChecks = true) {
                if (base.PlayerOpenLoot(player, panelToOpen, doPositionChecks)) {
                    ActiveVendingPlayers[player.userID] = this;
                    if (TotalPages > 1) CreateOverlay(player);
                    return true;
                }
                return false;
            }
            public override void PlayerStoppedLooting(BasePlayer player) {
                currentPage = 0;
                ActiveVendingPlayers.Remove(player.userID);
                CuiHelper.DestroyUi(player, UI_PANEL);
                base.PlayerStoppedLooting(player);
            }
            private void DoRefill() {
                if (vendingOrders == null || vendingOrders.orders == null) return;
                if (inventory == null) return;
                for (int i = 0; i < items.Count; i++) {
                    ConfigData.Vendor.VendingEntry entry = items[i];
                    if (Time.realtimeSinceStartup > refillTimes[i]) {
                        int amountPerSaleInInventory = Mathf.FloorToInt((float)(inventory.GetAmount(entry.SellItem.itemid, false) / entry.SellItemAmount));
                        int refillStockAmount = Mathf.Min(entry.StockAmount - amountPerSaleInInventory, entry.RefillAmount) * entry.SellItemAmount;
                        //if (entry.SellItemShortname == "radiationresisttea.advanced")
                        //{
                        //    Debug.Log($"Refill stock ticked for {entry.SellItemShortname}");
                        //    Debug.Log($"Current stock amount {inventory.GetAmount(entry.SellItem.itemid, false)}");
                        //    Debug.Log($"Amount to refill (config : {entry.RefillAmount}) (calculated : {refillStockAmount})");
                        //}
                        if (refillStockAmount > 0) {
                            transactionActive = true;
                            Item item = null;
                            if (!entry.SellItemBP) {
                                item = ItemManager.Create(entry.SellItem, refillStockAmount, entry.SellItemSkin);
                            }
                            else {
                                item = ItemManager.Create(blueprintBaseDef, refillStockAmount);
                                item.blueprintTarget = entry.SellItem.itemid;
                            }
                            if (!item.MoveToContainer(inventory, -1, true, false)) item.Remove(0f);
                            transactionActive = false;
                        }
                        refillTimes[i] = Time.realtimeSinceStartup + entry.RefillDelay;
                    }
                }
            }
#region Vending UI
            public const string UI_PANEL = "RoamingNPCVendors_UI";
            private const string PAGE_COLOR = "0.65 0.65 0.65 0.06";
            private const string PAGE_TEXT_COLOR = "0.7 0.7 0.7 1.0";
            private const string BUTTON_COLOR = "0.75 0.75 0.75 0.2";
            private const string BUTTON_TEXT_COLOR = "0.77 0.68 0.68 1";
            private readonly UI4 Container = new UI4(0.9505f, 0.15f, 0.99f, 0.6f);
            private readonly UI4 BackButton = new UI4(0f, 0.7f, 1f, 1f);
            private readonly UI4 Text = new UI4(0f, 0.3f, 1f, 0.7f);
            private readonly UI4 NextButton = new UI4(0f, 0f, 1f, 0.3f);
            protected void CreateOverlay(BasePlayer player) {
                CuiElementContainer container = UI.Container(UI_PANEL, Container);
                UI.Button(container, UI_PANEL, BUTTON_COLOR, "◀", BUTTON_TEXT_COLOR, 50, BackButton, currentPage > 0 ? "roamingnpcvendors.pageprev" : "");
                UI.Panel(container, UI_PANEL, PAGE_COLOR, Text);
                UI.Label(container, UI_PANEL, $"{currentPage + 1}\nof\n{TotalPages}", PAGE_TEXT_COLOR, 20, Text);
                UI.Button(container, UI_PANEL, BUTTON_COLOR, "▶", BUTTON_TEXT_COLOR, 50, NextButton, TotalPages > 1 && currentPage + 1 < TotalPages ? "roamingnpcvendors.pagenext" : "");
                CuiHelper.DestroyUi(player, UI_PANEL);
                CuiHelper.AddUi(player, container);
            }
#endregion
        }
#endregion
        //private void OnBuyVendingItem(VendingMachine v, BasePlayer p, int sellOrderId, int numberOfTransactions)
        //{
        //    Debug.Log($"{p.displayName} purchased {v.sellOrders.sellOrders[sellOrderId].itemToSellAmount} x {ItemManager.FindItemDefinition(v.sellOrders.sellOrders[sellOrderId].itemToSellID).shortname}");
        //}
#region UI
        public static class UI {
            public static CuiElementContainer Container(string panel, UI4 dimensions, string parent = "Hud.Menu") {
                CuiElementContainer container = new CuiElementContainer() {
                    {
                        new CuiPanel {
                            Image = {
                                Color = "0 0 0 0"
                            }
                            , RectTransform = {
                                AnchorMin = dimensions.GetMin(), AnchorMax = dimensions.GetMax(),
                            }
                        }
                        , new CuiElement().Parent = parent, panel
                    }
                }
                ;
                return container;
            }
            public static void Panel(CuiElementContainer container, string panel, string color, UI4 dimensions) {
                container.Add(new CuiPanel {
                    Image = {
                        Color = color
                    }
                    , RectTransform = {
                        AnchorMin = dimensions.GetMin(), AnchorMax = dimensions.GetMax()
                    }
                    ,
                }
                , panel);
            }
            public static void Label(CuiElementContainer container, string panel, string text, string color, int size, UI4 dimensions, TextAnchor align = TextAnchor.MiddleCenter) {
                container.Add(new CuiLabel {
                    Text = {
      
// ... [Code preview truncated due to length. Full source in attached file] ...

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