// Duplication of HBS code, avoiding prefix=true for now. public static void Postfix(ref BehaviorTreeResults __result, string ___name, BehaviorTree ___tree, AbstractActor ___unit) { Mod.Log.Info?.Write("CJMCN:T - entered"); Mech mech = ___unit as Mech; if (mech != null && mech.WorkingJumpjets > 0) { string stayInsideRegionGUID = RegionUtil.GetStayInsideRegionGUID(___unit); float acceptableHeat = AIUtil.GetAcceptableHeatLevelForMech(mech); float currentHeat = (float)mech.CurrentHeat; Mod.Log.Info?.Write($"CJMCN:T - === actor:{CombatantUtils.Label(mech)} has currentHeat:{currentHeat} and acceptableHeat:{acceptableHeat}"); List <PathNode> sampledPathNodes = ___unit.JumpPathing.GetSampledPathNodes(); Mod.Log.Info?.Write($"CJMCN:T - calculating {sampledPathNodes.Count} nodes"); for (int i = 0; i < sampledPathNodes.Count; i++) { Vector3 candidatePos = sampledPathNodes[i].Position; float distanceBetween2D = AIUtil.Get2DDistanceBetweenVector3s(candidatePos, ___unit.CurrentPosition); float distanceBetween3D = Vector3.Distance(candidatePos, ___unit.CurrentPosition); Mod.Log.Info?.Write($"CJMCN:T - calculated distances 2D:'{distanceBetween2D}' 3D:'{distanceBetween3D} "); if (distanceBetween2D >= 1f) { float magnitude = (candidatePos - ___unit.CurrentPosition).magnitude; float jumpHeat = (float)mech.CalcJumpHeat(magnitude); Mod.Log.Info?.Write($"CJMCN:T - calculated jumpHeat:'{jumpHeat}' from magnitude:'{magnitude}. "); Mod.Log.Info?.Write($"CJMCN:T - comparing heat: [jumpHeat:'{jumpHeat}' + currentHeat:'{currentHeat}'] <= acceptableHeat:'{acceptableHeat}. "); if (jumpHeat + (float)mech.CurrentHeat <= acceptableHeat) { if (stayInsideRegionGUID != null) { MapTerrainDataCell cellAt = ___unit.Combat.MapMetaData.GetCellAt(candidatePos); if (cellAt != null) { MapEncounterLayerDataCell mapEncounterLayerDataCell = cellAt.MapEncounterLayerDataCell; if (mapEncounterLayerDataCell != null && mapEncounterLayerDataCell.regionGuidList != null && !mapEncounterLayerDataCell.regionGuidList.Contains(stayInsideRegionGUID)) { // Skip this loop iteration if Mod.Log.Info?.Write($"CJMCN:T - candidate outside of constraint region, ignoring."); goto CANDIDATE_OUTSIDE_REGION; } } } Mod.Log.Info?.Write($"CJMCN:T - adding candidate position:{candidatePos}"); ___tree.movementCandidateLocations.Add(new MoveDestination(sampledPathNodes[i], MoveType.Jumping)); } } CANDIDATE_OUTSIDE_REGION :; } } // Should already be set by prefix method //__result = BehaviorTreeResults(BehaviorNodeState.Success); }
bool findClosestWalkablePointToDestination(HexGrid grid, Vector3 destination, Vector3 startLoc, List <PathNode> pathNodes, float destinationRadius, out Vector3 foundPoint, out PathNode foundPathNode) { bool foundAny = false; foundPoint = Vector3.zero; foundPathNode = null; float closestDistanceToDestination = float.MaxValue; float closestDistanceToStart = float.MaxValue; // used for tie-breaking between nodes of equal distance to destination for (int nodeIndex = 0; nodeIndex < pathNodes.Count; ++nodeIndex) { PathNode node = pathNodes[nodeIndex]; float distToDest = AIUtil.Get2DDistanceBetweenVector3s(node.Position, destination); if (distToDest <= destinationRadius) { float distToStart = AIUtil.Get2DDistanceBetweenVector3s(node.Position, startLoc); if ((distToDest < closestDistanceToDestination) || ((distToDest == closestDistanceToDestination) && (distToStart < closestDistanceToStart))) { foundAny = true; foundPathNode = node; foundPoint = node.Position; closestDistanceToDestination = distToDest; closestDistanceToStart = distToStart; } } } return(foundAny); }
float findDistanceFromGuardLance(Vector3 position, Lance guardLance) { float bestDistance = float.MaxValue; for (int i = 0; i < guardLance.unitGuids.Count; ++i) { string unitGUID = guardLance.unitGuids[i]; AbstractActor guardUnitActor = unit.Combat.ItemRegistry.GetItemByGUID <AbstractActor>(unitGUID); if ((guardUnitActor != null) && (!guardUnitActor.IsDead)) { bestDistance = Mathf.Min(bestDistance, AIUtil.Get2DDistanceBetweenVector3s(guardUnitActor.CurrentPosition, position)); } } return(bestDistance); }
int FindIndexOfPointInNodeList(HexGrid grid, Vector3 point, List <PathNode> pathNodes, bool findClosest = false) { HBS.Math.HexPoint3 hexPoint = unit.Combat.HexGrid.GetClosestHexPoint3OnGrid(point); float closestDistance = float.MaxValue; int closestIndex = -1; for (int i = 0; i < pathNodes.Count; ++i) { PathNode pn = pathNodes[i]; HBS.Math.HexPoint3 testHexPoint = unit.Combat.HexGrid.GetClosestHexPoint3OnGrid(pn.Position); if (hexPoint == testHexPoint) { return(i); } if (findClosest) { float distance = AIUtil.Get2DDistanceBetweenVector3s(point, pn.Position); if (distance < closestDistance) { closestDistance = distance; closestIndex = i; } } } if (findClosest) { return(closestIndex); } else { return(-1); } }
/// <summary> /// Finds a path from any of start to goal, such that the path has no links that are steeper than the unit's maxGrade. /// </summary> /// <returns>The path.</returns> /// <param name="startPointList">List of PointWithDistances for points to start from</param> /// <param name="snappedGoalPoint">HexPoint3 to go to</param> /// <param name="unit">moving unit</param> /// <param name="moveType">move type - walk, sprint</param> /// <param name="targetRadius">how close to get to the target</param> /// <param name="actorAware">Discard nodes where other actors reside</param> public static List <PointWithCost> FindPath(List <PointWithCost> startPointList, HexPoint3 snappedGoalPoint, AbstractActor unit, MoveType moveType, float targetRadius, bool actorAware) { MapMetaData mapMetaData = unit.Combat.MapMetaData; HexGrid hexGrid = unit.Combat.HexGrid; unit.Pathing.MoveType = moveType; List <AbstractActor> actors = null; bool startedInEncounterBounds = false; BattleTech.Designed.EncounterBoundaryChunkGameLogic boundaryChunk = unit.Combat.EncounterLayerData.encounterBoundaryChunk; for (int spi = 0; spi < startPointList.Count; ++spi) { PointWithCost sp = startPointList[spi]; //Vector3 wp = HexPoint3ToWorldPoint(sp.point, hexGrid); if (boundaryChunk.IsInEncounterBounds(unit.CurrentPosition)) { startedInEncounterBounds = true; break; } } actorAware = unit.BehaviorTree.GetBehaviorVariableValue(BehaviorVariableName.Bool_EnableLongRangePathfindingBeActorAware).BoolVal ? true : actorAware; if (actorAware) { actors = unit.Combat.AllActors; actors.Remove(unit); } List <PointWithCost> path = new List <PointWithCost>(); HeapQueue <PointWithCost> openHeap = new HeapQueue <PointWithCost>(); Dictionary <HexPoint3, float> bestCostDict = new Dictionary <HexPoint3, float>(); Dictionary <HexPoint3, PointWithCost> bestPrevPoint = new Dictionary <HexPoint3, PointWithCost>(); Vector3 worldGoalPoint = HexPoint3ToWorldPoint(snappedGoalPoint, hexGrid); float bestPathCost = float.MaxValue; bool anyPathFound = false; PointWithCost bestGoalPoint = new PointWithCost(new HexPoint3(-4000, -4000), float.MaxValue, float.MaxValue); for (int startIndex = 0; startIndex < startPointList.Count; ++startIndex) { PointWithCost pwd = startPointList[startIndex]; openHeap.Push(pwd); bestCostDict[pwd.point] = pwd.cost; bestPrevPoint[pwd.point] = null; Vector3 wp = HexPoint3ToWorldPoint(pwd.point, hexGrid); if ((pwd.point.Equals(snappedGoalPoint)) || (AIUtil.Get2DDistanceBetweenVector3s(wp, worldGoalPoint) < targetRadius)) { if (pwd.cost < bestPathCost) { anyPathFound = true; bestPathCost = pwd.cost; bestGoalPoint = pwd; } } } while (!openHeap.IsEmpty()) { PointWithCost ptWithCost = openHeap.PopMinimum(); if (ptWithCost.estimatedTotalCost > bestPathCost) { continue; } Vector3 worldPoint = HexPoint3ToWorldPoint(ptWithCost.point, hexGrid); if (actorAware && CheckForOccupiedPoint(actors, worldPoint)) { continue; } if (startedInEncounterBounds && (!boundaryChunk.IsInEncounterBounds(worldPoint))) { continue; } for (int direction = 0; direction < 6; ++direction) { HexPoint3 neighborHexPoint = ptWithCost.point.Step(direction, 1); Vector3 neighborWorldPoint = HexPoint3ToWorldPoint(neighborHexPoint, hexGrid); if ((!mapMetaData.IsWithinBounds(neighborWorldPoint)) || (unit.Pathing.CurrentGrid.FindBlockerReciprocal(worldPoint, neighborWorldPoint))) { continue; } Debug.DrawLine(worldPoint, neighborWorldPoint, Color.yellow, 15.0f); float linkCost = unit.Pathing.CurrentGrid.GetTerrainModifiedCost(worldPoint, neighborWorldPoint); float newCost = ptWithCost.cost + linkCost; if (newCost >= bestPathCost) { continue; } if ((!bestCostDict.ContainsKey(neighborHexPoint)) || (newCost < bestCostDict[neighborHexPoint])) { bestCostDict[neighborHexPoint] = newCost; bestPrevPoint[neighborHexPoint] = ptWithCost; if ((neighborHexPoint.Equals(snappedGoalPoint)) || ((neighborWorldPoint - worldGoalPoint).magnitude < targetRadius)) { if (newCost < bestPathCost) { anyPathFound = true; bestPathCost = newCost; bestGoalPoint = new PointWithCost(neighborHexPoint, newCost, 0.0f); } } else { Vector3 remainingDistance = (worldGoalPoint - neighborWorldPoint); float estRemainingCost = remainingDistance.magnitude; openHeap.Push(new PointWithCost(neighborHexPoint, newCost, newCost + estRemainingCost)); } } } } if (anyPathFound) { PointWithCost p = bestGoalPoint; path.Add(p); while (bestPrevPoint.ContainsKey(p.point)) { PointWithCost prevPoint = bestPrevPoint[p.point]; if ((prevPoint == null) || (path.Contains(prevPoint))) { break; } path.Insert(0, prevPoint); p = prevPoint; } } else { // draw the failed path data const int SIDES = 3; const float RADIUS = 12; foreach (PointWithCost startPoint in startPointList) { Vector3 worldStartPoint = HexPoint3ToWorldPoint(startPoint.point, hexGrid); for (int i = 0; i < SIDES; ++i) { float dx0 = RADIUS * Mathf.Cos(i * Mathf.PI * 2 / SIDES); float dz0 = RADIUS * Mathf.Sin(i * Mathf.PI * 2 / SIDES); float dx1 = RADIUS * Mathf.Cos((i + 1) * Mathf.PI * 2 / SIDES); float dz1 = RADIUS * Mathf.Sin((i + 1) * Mathf.PI * 2 / SIDES); Vector3 wp0 = new Vector3(worldStartPoint.x + dx0, 0, worldStartPoint.z + dz0); Vector3 wp1 = new Vector3(worldStartPoint.x + dx1, 0, worldStartPoint.z + dz1); Debug.DrawLine(wp0, wp1, Color.magenta, 15.0f); } } Vector3 worldEndPoint = HexPoint3ToWorldPoint(snappedGoalPoint, hexGrid); Color orangeColor = new Color(1.0f, 0.5f, 0.0f); for (int i = 0; i < SIDES; ++i) { float dx0 = RADIUS * Mathf.Cos(i * Mathf.PI * 2 / SIDES); float dz0 = RADIUS * Mathf.Sin(i * Mathf.PI * 2 / SIDES); float dx1 = RADIUS * Mathf.Cos((i + 1) * Mathf.PI * 2 / SIDES); float dz1 = RADIUS * Mathf.Sin((i + 1) * Mathf.PI * 2 / SIDES); Vector3 wp0 = new Vector3(worldEndPoint.x + dx0, 0, worldEndPoint.z + dz0); Vector3 wp1 = new Vector3(worldEndPoint.x + dx1, 0, worldEndPoint.z + dz1); Debug.DrawLine(wp0, wp1, orangeColor, 15.0f); } } int removedCount = 0; // Now, check to see if the end of the path is in "danger". If it is, prune until it's not, which might lead to an empty path. while (path.Count > 0) { PointWithCost lastHexPoint = path[path.Count - 1]; Vector3 lastWorldPoint = HexPoint3ToWorldPoint(lastHexPoint.point, hexGrid); MapTerrainDataCell dataCell = unit.Combat.MapMetaData.GetCellAt(lastWorldPoint); if (SplatMapInfo.IsDropshipLandingZone(dataCell.terrainMask) || SplatMapInfo.IsDangerousLocation(dataCell.terrainMask) || SplatMapInfo.IsDropPodLandingZone(dataCell.terrainMask)) { path.RemoveAt(path.Count - 1); ++removedCount; } else { break; } } if (removedCount > 0) { if (path.Count == 0) { BehaviorNode.LogAI(unit, string.Format("DANGER TRIM: removed all {0} points, bracing", removedCount)); } else { BehaviorNode.LogAI(unit, string.Format("DANGER TRIM: removed {0} points, moving to {1}", removedCount, path[path.Count - 1])); } } return(path); }
override protected BehaviorTreeResults Tick() { BehaviorTreeResults results; if (unit.BehaviorTree.GetBehaviorVariableValue(BehaviorVariableName.Bool_RouteCompleted).BoolVal) { results = new BehaviorTreeResults(BehaviorNodeState.Success); results.orderInfo = new OrderInfo(OrderType.Brace); results.debugOrderString = string.Format("{0}: bracing for end of patrol route", this.name); return(results); } bool isSprinting = unit.BehaviorTree.GetBehaviorVariableValue(BehaviorVariableName.Bool_RouteShouldSprint).BoolVal; if (isSprinting && unit.CanSprint) { unit.Pathing.SetSprinting(); } else { unit.Pathing.SetWalking(); } PathNodeGrid grid = unit.Pathing.CurrentGrid; if (grid.UpdateBuild(25) > 0) { // have to wait for the grid to build. results = new BehaviorTreeResults(BehaviorNodeState.Running); return(results); } if (!unit.Pathing.ArePathGridsComplete) { // have to wait for the grid to build. results = new BehaviorTreeResults(BehaviorNodeState.Running); return(results); } float destinationRadius = unit.BehaviorTree.GetBehaviorVariableValue(BehaviorVariableName.Float_RouteWaypointRadius).FloatVal; RouteGameLogic myPatrolRoute = getRoute(); if (myPatrolRoute == null) { AIUtil.LogAI("Move Along Route failing because no route found", unit); return(new BehaviorTreeResults(BehaviorNodeState.Failure)); } BehaviorVariableValue nrpiVal = unit.BehaviorTree.GetBehaviorVariableValue(BehaviorVariableName.Int_RouteTargetPoint); int nextRoutePointIndex = (nrpiVal != null) ? nrpiVal.IntVal : 0; BehaviorVariableValue pfVal = unit.BehaviorTree.GetBehaviorVariableValue(BehaviorVariableName.Bool_RouteFollowingForward); bool patrollingForward = (pfVal != null) ? pfVal.BoolVal : true; PatrolRouteWaypoints routeWaypointIterator = null; switch (myPatrolRoute.routeTransitType) { case RouteTransitType.Circuit: routeWaypointIterator = new CircuitRouteWaypoints(nextRoutePointIndex, patrollingForward, myPatrolRoute.routePointList.Length); break; case RouteTransitType.OneWay: routeWaypointIterator = new OneWayRouteWaypoints(nextRoutePointIndex, patrollingForward, myPatrolRoute.routePointList.Length); break; case RouteTransitType.PingPong: routeWaypointIterator = new PingPongRouteWaypoints(nextRoutePointIndex, patrollingForward, myPatrolRoute.routePointList.Length); break; default: Debug.LogError("Invalid route transit type: " + myPatrolRoute.routeTransitType); AIUtil.LogAI("Move Along Route failing because patrol route was set to an invalid transit type: " + myPatrolRoute.routeTransitType, unit); return(new BehaviorTreeResults(BehaviorNodeState.Failure)); } float movementAvailable = unit.Pathing.MaxCost * unit.BehaviorTree.GetBehaviorVariableValue(BehaviorVariableName.Float_PatrolRouteThrottlePercentage).FloatVal / 100.0f; bool isComplete = false; int nextWaypoint = -1; bool nextPointGoesForward = false; Vector3 successorPoint; List <PathNode> availablePathNodes = unit.Pathing.CurrentGrid.GetSampledPathNodes(); // prune for region string regionGUID = RegionUtil.StayInsideRegionGUID(unit); if (!string.IsNullOrEmpty(regionGUID)) { availablePathNodes = availablePathNodes.FindAll(node => RegionUtil.PointInRegion(unit.Combat, node.Position, regionGUID)); } string guardGUID = unit.BehaviorTree.GetBehaviorVariableValue(BehaviorVariableName.String_GuardLanceGUID).StringVal; Lance guardLance = guardGUID != null?unit.Combat.ItemRegistry.GetItemByGUID <Lance>(guardGUID) : null; // if guarding units, adjust movement available to account for their speed if (guardLance != null) { movementAvailable = adjustMovementAvailableForGuardLance(unit, movementAvailable, guardLance); } // prune for distance from start point availablePathNodes = availablePathNodes.FindAll(node => node.CostToThisNode <= movementAvailable); // if there is a guarding lance, make sure that we're not moving out of the lance tether if (guardLance != null) { availablePathNodes = filterAvailablePathNodesForGuardTether(unit, availablePathNodes, guardLance); } Vector3 patrolPoint = getReachablePointOnRoute(unit.CurrentPosition, myPatrolRoute, routeWaypointIterator, availablePathNodes, out isComplete, out nextWaypoint, out nextPointGoesForward, out successorPoint); unit.BehaviorTree.unitBehaviorVariables.SetVariable(BehaviorVariableName.Bool_RouteFollowingForward, new BehaviorVariableValue(nextPointGoesForward)); unit.BehaviorTree.unitBehaviorVariables.SetVariable(BehaviorVariableName.Int_RouteTargetPoint, new BehaviorVariableValue(nextWaypoint)); unit.BehaviorTree.unitBehaviorVariables.SetVariable(BehaviorVariableName.Bool_RouteCompleted, new BehaviorVariableValue(isComplete)); //Vector3 destination = RegionUtil.MaybeClipMovementDestinationToStayInsideRegion(unit, patrolPoint); Vector3 destination = patrolPoint; if (!isComplete) { List <PathNode> path = constructPath(unit.Combat.HexGrid, destination, availablePathNodes); if ((path.Count == 0) || ((path.Count == 1) && (AIUtil.Get2DDistanceBetweenVector3s(path[0].Position, unit.CurrentPosition) < 1))) { // can't actually make progress - fail here, and presumably pass later on. AIUtil.LogAI("Move Along Route failing because no nodes in path.", unit); DialogueGameLogic proximityDialogue = unit.Combat.ItemRegistry.GetItemByGUID <DialogueGameLogic>(unit.Combat.Constants.CaptureEscortProximityDialogID); if (proximityDialogue != null) { TriggerDialog triggerDialogueMessage = new TriggerDialog(unit.GUID, unit.Combat.Constants.CaptureEscortProximityDialogID, async: false); unit.Combat.MessageCenter.PublishMessage(triggerDialogueMessage); } else { Debug.LogError("Could not find CaptureEscortProximityDialog. This is only a real error message if this is a Capture Escort (Normal Escort) mission. For other missions (Story, Ambush Convoy, etc) you can safely ignore this error message."); } return(new BehaviorTreeResults(BehaviorNodeState.Failure)); } destination = path[path.Count - 1].Position; } Vector3 cur = unit.CurrentPosition; if ((destination - cur).magnitude < 1) { // can't actually make progress - fail here, and presumably pass later on. AIUtil.LogAI("Move Along Route failing because destination too close to unit start.", unit); return(new BehaviorTreeResults(BehaviorNodeState.Failure)); } AIUtil.LogAI(string.Format("issuing order from [{0} {1} {2}] to [{3} {4} {5}] looking at [{6} {7} {8}]", cur.x, cur.y, cur.z, destination.x, destination.y, destination.z, successorPoint.x, successorPoint.y, successorPoint.z ), unit); results = new BehaviorTreeResults(BehaviorNodeState.Success); MovementOrderInfo mvtOrderInfo = new MovementOrderInfo(destination, successorPoint); mvtOrderInfo.IsSprinting = isSprinting; results.orderInfo = mvtOrderInfo; results.debugOrderString = string.Format("{0}: dest:{1} sprint:{2}", this.name, destination, mvtOrderInfo.IsSprinting); return(results); }