예제 #1
0
        // 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);
    }