public static List <Vector3> GetDynamicPathToDestination(List <PointWithCost> startPointList, Vector3 goal, float movementBudget, AbstractActor unit, bool shouldSprint, List <AbstractActor> lanceUnits, PathNodeGrid pathGrid, float targetRadius, bool actorAware = false) { HexGrid hexGrid = unit.Combat.HexGrid; if (shouldSprint && unit.CanSprint) { unit.Pathing.SetSprinting(); } else { unit.Pathing.SetWalking(); } HexPoint3 goalPoint = hexGrid.GetClosestHexPoint3OnGrid(goal); List <PointWithCost> pathLatticePoints = FindPath(startPointList, goalPoint, unit, shouldSprint ? MoveType.Sprinting : MoveType.Walking, targetRadius, actorAware); if ((pathLatticePoints == null) || (pathLatticePoints.Count == 0)) { // can't find a path(!) return(null); } // Dig, if you will: // A - Current unit position // B - a point on the edge between short range and long range pathfinding // C - the goal point // FindPath returned a path from B to C // pathLatticePoints[0] is B // walking backward through parents gets us the path from A to B List <PathNode> pathNodes = new List <PathNode>(); PathNode walkNode = pathLatticePoints[0].pathNode; while (walkNode != null) { pathNodes.Insert(0, walkNode); walkNode = walkNode.Parent; } List <Vector3> longRangePathWorldPoints = pathLatticePoints.ConvertAll(x => hexGrid.HexPoint3ToCartesianWorld(x.point)); List <Vector3> pathWorldPoints = pathNodes.ConvertAll(x => x.Position); pathWorldPoints.AddRange(longRangePathWorldPoints); // we have now spliced the A->B path together with the B->C path to have a A->C path if (longRangePathWorldPoints.Count == 1) { // if the B->C path is just one node, it's just B, so just return the A->C path, // which is just the A->B path, which we can do all in one go. return(pathWorldPoints); } // Debug Draw Path float scale = 1.0f; for (int pathIndex = 0; pathIndex < pathWorldPoints.Count - 1; ++pathIndex) { int nextIndex = pathIndex + 1; Vector3 p0 = pathWorldPoints[pathIndex]; Vector3 p1 = pathWorldPoints[nextIndex]; scale = (p1 - p0).magnitude; for (int dx = -1; dx <= 1; ++dx) { for (int dz = -1; dz <= 1; ++dz) { Vector3 offset = new Vector3(dx * scale * 0.1f, 0, dz * scale * 0.1f); Debug.DrawLine(p0 + offset, p1 + offset, Color.red, 30.0f); } } } drawCircle(unit.CurrentPosition, scale * 0.4f, Color.cyan, 30.0f); drawCircle(goal, scale * 0.4f, Color.magenta, 30.0f); drawCircle(pathWorldPoints[0], scale * 0.3f, Color.red, 30.0f); drawCircle(pathWorldPoints[0], scale * 0.35f, Color.white, 30.0f); drawCircle(pathWorldPoints[0], scale * 0.4f, Color.blue, 30.0f); // TODO/dlecompte push this up to filter our selection of path nodes, above. float spread = unit.BehaviorTree.GetBehaviorVariableValue( unit.Combat.TurnDirector.IsInterleaved ? BehaviorVariableName.Float_InterleavedLanceSpreadDistance : BehaviorVariableName.Float_NonInterleavedLanceSpreadDistance).FloatVal; drawCircle(unit.CurrentPosition, spread, Color.green, 30.0f); Debug.Assert(pathWorldPoints.Count >= 2); // already tested this, above. float accumDistance = 0.0f; Vector3 clipPoint = goal; // Now we walk along the pathWorldPoints, snapping them to grid points. // We want to take the point furthest along the path that doesn't alias to an earlier point. // Also, nodes must be "safe" from artillery // Also, we want to make sure that it's the furthest point within our movement budget and within our lance spread. // MUST BE : within movement budget, not an alias to an earlier point // IF any points exist inside lance spread, pick last point inside lance spread, else last point. List <Vector3> dedupedSnappedPointsList = new List <Vector3>(); List <Vector3> snappedPointsInOrder = new List <Vector3>(); List <Vector3> nextPointsInOrder = new List <Vector3>(); List <bool> pointsInSpreadRangeList = new List <bool>(); List <bool> isNewGroundList = new List <bool>(); float ROUNDING_RADIUS = 1.0f; bool wasEverInside = false; for (int pointIndex = 0; (pointIndex < pathWorldPoints.Count) && (accumDistance <= movementBudget); ++pointIndex) { Vector3 thisPoint = pathWorldPoints[pointIndex]; Vector3 nextPoint = goal; if (pointIndex + 1 < pathWorldPoints.Count) { nextPoint = pathWorldPoints[pointIndex + 1]; } Vector3 thisSnappedPoint = unit.Combat.HexGrid.GetClosestPointOnGrid(thisPoint); if (!IsLocationSafe(unit.Combat, thisSnappedPoint)) { continue; } snappedPointsInOrder.Add(thisSnappedPoint); nextPointsInOrder.Add(nextPoint); bool pointIsInsideSpread = AIUtil.IsPositionWithinLanceSpread(unit, lanceUnits, thisSnappedPoint); wasEverInside |= pointIsInsideSpread; pointsInSpreadRangeList.Add(pointIsInsideSpread); bool alreadyVisited = isPointInList(thisSnappedPoint, dedupedSnappedPointsList, ROUNDING_RADIUS); isNewGroundList.Add(!alreadyVisited); if (!alreadyVisited) { dedupedSnappedPointsList.Add(thisSnappedPoint); } if (pointIndex + 1 < pathWorldPoints.Count) { accumDistance += (nextPoint - thisPoint).magnitude; } } if (wasEverInside) { // find the last point of our list that is "new ground" and inside for (int i = snappedPointsInOrder.Count - 1; i >= 0; --i) { if (isNewGroundList[i] && pointsInSpreadRangeList[i]) { clipPoint = snappedPointsInOrder[i]; break; } } } if ((!wasEverInside) || ((clipPoint - unit.CurrentPosition).magnitude < 1.0f)) { // find the last point of our list that is "new ground" for (int i = snappedPointsInOrder.Count - 1; i >= 0; --i) { if (isNewGroundList[i]) { clipPoint = snappedPointsInOrder[i]; break; } } } for (int i = snappedPointsInOrder.Count - 1; i >= 0; --i) { drawCircle(snappedPointsInOrder[i], scale * 0.2f, new Color(0.5f, 0.5f, 0.0f), 30.0f); if (pointsInSpreadRangeList[i]) { drawCircle(snappedPointsInOrder[i], scale * 0.25f, new Color(0.0f, 1.0f, 0.0f), 30.0f); } } //Vector3 resultPos = clipPoint; drawCircle(clipPoint, scale * 0.4f, new Color(1.0f, 0.5f, 0.0f), 30.0f); return(snappedPointsInOrder); }
public Vector3 GetDestination(List <PointWithDistance> startPointList, Vector3 goal, float movementBudget, float maxSlope, AbstractActor unit, bool shouldSprint, List <AbstractActor> lanceUnits, PathNodeGrid pathGrid, out Vector3 lookAtPoint) { if (shouldSprint && unit.CanSprint) { unit.Pathing.SetSprinting(); } else { unit.Pathing.SetWalking(); } InclineIndexPoint goalPoint = WorldPointToInclineIndices(goal); List <PointWithDistance> pathLatticePoints = FindPath(startPointList, goalPoint, maxSlope, maxSlope); if ((pathLatticePoints == null) || (pathLatticePoints.Count == 0)) { // can't find a path(!) // set the lookAtPoint (out) variable to a point further out in the direction of start->goal lookAtPoint = goal * 2.0f - unit.CurrentPosition; return(goal); } List <PathNode> pathNodes = new List <PathNode>(); PathNode walkNode = pathLatticePoints[0].pathNode; while (walkNode != null) { pathNodes.Insert(0, walkNode); walkNode = walkNode.Parent; } List <Vector3> longRangePathWorldPoints = pathLatticePoints.ConvertAll(x => InclineIndicesToWorldPoint(x.point)); List <Vector3> pathWorldPoints = pathNodes.ConvertAll(x => x.Position); pathWorldPoints.AddRange(longRangePathWorldPoints); Vector3 destination; if (longRangePathWorldPoints.Count == 1) { destination = longRangePathWorldPoints[0]; if ((goal - destination).magnitude < 1.0f) { lookAtPoint = destination * 2.0f - unit.CurrentPosition; } else { lookAtPoint = goal; } return(destination); } // Debug Draw Path float scale = 1.0f; for (int pathIndex = 0; pathIndex < pathWorldPoints.Count - 1; ++pathIndex) { int nextIndex = pathIndex + 1; Vector3 p0 = pathWorldPoints[pathIndex]; Vector3 p1 = pathWorldPoints[nextIndex]; scale = (p1 - p0).magnitude; for (int dx = -1; dx <= 1; ++dx) { for (int dz = -1; dz <= 1; ++dz) { Vector3 offset = new Vector3(dx * scale * 0.1f, 0, dz * scale * 0.1f); Debug.DrawLine(p0 + offset, p1 + offset, Color.red, 30.0f); } } } drawCircle(unit.CurrentPosition, scale * 0.4f, Color.cyan, 30.0f); drawCircle(goal, scale * 0.4f, Color.magenta, 30.0f); drawCircle(pathWorldPoints[0], scale * 0.3f, Color.red, 30.0f); drawCircle(pathWorldPoints[0], scale * 0.35f, Color.white, 30.0f); drawCircle(pathWorldPoints[0], scale * 0.4f, Color.blue, 30.0f); // TODO/dlecompte push this up to filter our selection of path nodes, above. float spread = unit.BehaviorTree.GetBehaviorVariableValue( unit.Combat.TurnDirector.IsInterleaved ? BehaviorVariableName.Float_InterleavedLanceSpreadDistance : BehaviorVariableName.Float_NonInterleavedLanceSpreadDistance).FloatVal; drawCircle(unit.CurrentPosition, spread, Color.green, 30.0f); Debug.Assert(pathWorldPoints.Count >= 2); // already tested this, above. float accumDistance = 0.0f; // if we can get to the goal, look at a point further away in the direction of start -> goal lookAtPoint = 2 * goal - unit.CurrentPosition; Vector3 clipPoint = goal; // Now we walk along the pathWorldPoints, snapping them to grid points. // We want to take the point furthest along the path that doesn't alias to an earlier point. // Also, we want to make sure that it's the furthest point within our movement budget and within our lance spread. // MUST BE : within movement budget, not an alias to an earlier point // IF any points exist inside lance spread, pick last point inside lance spread, else last point. List <Vector3> dedupedSnappedPointsList = new List <Vector3>(); List <Vector3> snappedPointsInOrder = new List <Vector3>(); List <Vector3> nextPointsInOrder = new List <Vector3>(); List <bool> pointsInSpreadRangeList = new List <bool>(); List <bool> isNewGroundList = new List <bool>(); float ROUNDING_RADIUS = 1.0f; bool wasEverInside = false; for (int pointIndex = 0; (pointIndex < pathWorldPoints.Count) && (accumDistance <= movementBudget); ++pointIndex) { Vector3 thisPoint = pathWorldPoints[pointIndex]; Vector3 nextPoint = goal; if (pointIndex + 1 < pathWorldPoints.Count) { nextPoint = pathWorldPoints[pointIndex + 1]; } Vector3 thisSnappedPoint = unit.Combat.HexGrid.GetClosestPointOnGrid(thisPoint); snappedPointsInOrder.Add(thisSnappedPoint); nextPointsInOrder.Add(nextPoint); bool pointIsInsideSpread = AIUtil.IsPositionWithinLanceSpread(unit, lanceUnits, thisSnappedPoint); wasEverInside |= pointIsInsideSpread; pointsInSpreadRangeList.Add(pointIsInsideSpread); bool alreadyVisited = isPointInList(thisSnappedPoint, dedupedSnappedPointsList, ROUNDING_RADIUS); isNewGroundList.Add(!alreadyVisited); if (!alreadyVisited) { dedupedSnappedPointsList.Add(thisSnappedPoint); } if (pointIndex + 1 < pathWorldPoints.Count) { accumDistance += (nextPoint - thisPoint).magnitude; } } if (wasEverInside) { // find the last point of our list that is "new ground" and inside for (int i = snappedPointsInOrder.Count - 1; i >= 0; --i) { if (isNewGroundList[i] && pointsInSpreadRangeList[i]) { clipPoint = snappedPointsInOrder[i]; lookAtPoint = nextPointsInOrder[i]; break; } } } if ((!wasEverInside) || ((clipPoint - unit.CurrentPosition).magnitude < 1.0f)) { // find the last point of our list that is "new ground" for (int i = snappedPointsInOrder.Count - 1; i >= 0; --i) { if (isNewGroundList[i]) { clipPoint = snappedPointsInOrder[i]; lookAtPoint = nextPointsInOrder[i]; break; } } } for (int i = snappedPointsInOrder.Count - 1; i >= 0; --i) { drawCircle(snappedPointsInOrder[i], scale * 0.2f, new Color(0.5f, 0.5f, 0.0f), 30.0f); if (pointsInSpreadRangeList[i]) { drawCircle(snappedPointsInOrder[i], scale * 0.25f, new Color(0.0f, 1.0f, 0.0f), 30.0f); } } //float lookAngle = PathingUtil.GetAngle(lookAtPoint - clipPoint); //Vector3 resultPos = clipPoint; drawCircle(clipPoint, scale * 0.4f, new Color(1.0f, 0.5f, 0.0f), 30.0f); return(clipPoint); }