using System; using HG; using UnityEngine; namespace RoR2.Navigation; public class NodeGraphNavigationSystem : BroadNavigationSystem { public struct AgentData { public NodeGraph nodeGraph; public PathFollower pathFollower; public NodeGraph.PathRequest pathRequest; public HullClassification hullClassification; public PathTask pathTask; public float updateInterval; public float nextUpdate; public bool targetReachable; public float lastTargetReachableUpdate; public NodeGraph.NodeIndex? lastKnownNode; public float timeLastKnownNodeEncountered; public float estimatedArrivalTimeAtLastKnownNode; private PathFollower.Drawer pathDrawer; public bool drawPath { get { return pathDrawer != null; } set { if (drawPath != value) { if (!value) { pathDrawer.Dispose(); pathDrawer = null; } else { pathDrawer = new PathFollower.Drawer(pathFollower); } } } } public void Initialize() { nextUpdate = float.NegativeInfinity; nodeGraph = null; updateInterval = 2f; hullClassification = HullClassification.Human; pathFollower = pathFollower ?? new PathFollower(); pathRequest = pathRequest ?? new NodeGraph.PathRequest(); targetReachable = false; lastTargetReachableUpdate = float.NegativeInfinity; lastKnownNode = null; timeLastKnownNodeEncountered = float.NegativeInfinity; estimatedArrivalTimeAtLastKnownNode = float.NegativeInfinity; drawPath = false; } public void Dispose() { drawPath = false; nodeGraph = null; nextUpdate = float.NegativeInfinity; pathFollower.Reset(); pathRequest.Reset(); pathTask = null; } public void InvalidatePath() { nextUpdate = float.NegativeInfinity; pathTask = null; } } public new struct Agent { private readonly BroadNavigationSystem.Agent inner; private NodeGraphNavigationSystem system => (NodeGraphNavigationSystem)inner.system; private ref AgentData agentData => ref system.allAgentData[(int)inner.handle]; public NodeGraph nodeGraph { get { return agentData.nodeGraph; } set { agentData.nodeGraph = value; } } public HullClassification hullClassification { get { return agentData.hullClassification; } set { agentData.hullClassification = value; } } public bool drawPath { get { return agentData.drawPath; } set { agentData.drawPath = value; } } public PathFollower pathFollower { get { return agentData.pathFollower; } set { agentData.pathFollower = value; } } public Agent(BroadNavigationSystem.Agent inner) { this.inner = inner; } public void InvalidatePath() { agentData.InvalidatePath(); } public void DebugDrawPath(Color color, float duration) { agentData.pathFollower.DebugDrawPath(color, duration); } public static explicit operator Agent(BroadNavigationSystem.Agent other) { return new Agent(other); } } private AgentData[] allAgentData = Array.Empty(); private int maxAgentUpdatesPerFrame = 1; private int remainingAgentUpdateBudget; public new Agent GetAgent(AgentHandle agentHandle) { return new Agent(base.GetAgent(agentHandle)); } protected override void CreateAgent(in AgentHandle index) { ArrayUtils.EnsureCapacity(ref allAgentData, base.agentCount); allAgentData[(int)index].Initialize(); } protected override void DestroyAgent(in AgentHandle index) { allAgentData[(int)index].Dispose(); } protected override void UpdateAgents(float deltaTime) { remainingAgentUpdateBudget = maxAgentUpdatesPerFrame; for (AgentHandle agentHandle = (AgentHandle)0; (int)agentHandle < allAgentData.Length; agentHandle++) { UpdateAgent(in agentHandle); } } protected override void ConfigureAgentFromBody(in AgentHandle index, CharacterBody body) { ref AgentData reference = ref allAgentData[(int)index]; if ((bool)body) { reference.hullClassification = body.hullClassification; reference.nodeGraph = (SceneInfo.instance ? SceneInfo.instance.GetNodeGraph(body.isFlying ? MapNodeGroup.GraphType.Air : MapNodeGroup.GraphType.Ground) : null); } else { reference.hullClassification = HullClassification.Human; reference.nodeGraph = null; reference.InvalidatePath(); } } protected override void InvalidateAgentPath(in AgentHandle index) { allAgentData[(int)index].InvalidatePath(); } private void UpdateAgent(in AgentHandle agentHandle) { bool flag = false; ref readonly BaseAgentData agentData = ref GetAgentData(in agentHandle); if (!agentData.enabled) { return; } ref AgentData reference = ref allAgentData[(int)agentHandle]; if (reference.pathTask == null && reference.nextUpdate <= localTime && remainingAgentUpdateBudget != 0) { flag = true; remainingAgentUpdateBudget--; reference.nextUpdate = localTime + reference.updateInterval; if (reference.lastKnownNode != reference.pathFollower.nextNode) { reference.lastKnownNode = reference.pathFollower.nextNode; reference.timeLastKnownNodeEncountered = localTime; Vector3? nextPosition = reference.pathFollower.GetNextPosition(); if (nextPosition.HasValue && agentData.currentPosition.HasValue && agentData.maxWalkSpeed > 0f) { reference.estimatedArrivalTimeAtLastKnownNode = localTime + (nextPosition.Value - agentData.currentPosition.Value).magnitude / agentData.maxWalkSpeed; } else { reference.estimatedArrivalTimeAtLastKnownNode = float.PositiveInfinity; } } if (agentData.currentPosition.HasValue && agentData.goalPosition.HasValue && (bool)reference.nodeGraph && reference.lastKnownNode.HasValue) { reference.pathRequest.startPos = agentData.currentPosition.Value; if (localTime <= reference.estimatedArrivalTimeAtLastKnownNode + 1f && reference.lastKnownNode.Value.nodeIndex != -1) { reference.nodeGraph.GetNodePosition(reference.lastKnownNode.Value, out var position); float num = (agentData.maxWalkSpeed + reference.updateInterval) * 1.5f; float num2 = num * num; if ((position - agentData.currentPosition.Value).sqrMagnitude < num2 && !Physics.Linecast(agentData.currentPosition.Value, position + new Vector3(0f, 0.2f, 0f), out var _, LayerIndex.world.intVal, QueryTriggerInteraction.Ignore)) { reference.pathRequest.startPos = reference.lastKnownNode.Value; } } reference.pathRequest.endPos = agentData.goalPosition.Value; reference.pathRequest.hullClassification = reference.hullClassification; reference.pathRequest.maxJumpHeight = agentData.maxJumpHeight; reference.pathRequest.maxSlope = agentData.maxSlopeAngle; reference.pathRequest.maxSpeed = agentData.maxWalkSpeed; reference.pathRequest.path = new Path(reference.nodeGraph); reference.pathTask = reference.nodeGraph.ComputePath(reference.pathRequest); } } if (reference.pathTask != null && (flag || remainingAgentUpdateBudget != 0)) { if (!flag) { remainingAgentUpdateBudget--; flag = true; } if (reference.pathTask.status == PathTask.TaskStatus.Complete) { reference.pathFollower.SetPath(reference.pathTask.path); reference.lastTargetReachableUpdate = localTime; reference.targetReachable = reference.pathTask.wasReachable; reference.pathTask = null; } } if (agentData.currentPosition.HasValue) { reference.pathFollower.UpdatePosition(agentData.currentPosition.Value); } Vector3? nextPosition2 = reference.pathFollower.GetNextPosition(); if (reference.targetReachable && !nextPosition2.HasValue) { nextPosition2 = agentData.goalPosition; } AgentOutput agentOutput = default(AgentOutput); agentOutput.nextPosition = nextPosition2; agentOutput.desiredJumpVelocity = (reference.pathFollower.nextWaypointNeedsJump ? reference.pathFollower.CalculateJumpVelocityNeededToReachNextWaypoint(agentData.maxWalkSpeed) : 0f); agentOutput.targetReachable = reference.targetReachable; agentOutput.lastPathUpdate = reference.lastTargetReachableUpdate; AgentOutput output = agentOutput; SetAgentOutput(in agentHandle, in output); } }