protected override BehaviorTreeResults Tick()
        {
            if (this.unit.Pathing == null)
            {
                return(BehaviorTreeResults.BehaviorTreeResultsFromBoolean(false));
            }
            this.tickCount++;
            if (this.unit.Pathing.ArePathGridsComplete)
            {
                Main.LogDebug($"[AI] [BlockUntilPathfindingReadyNode] Block until pathfinding completing with grids complete");
                base.LogAI("Block until pathfinding completing with grids complete", "AI.BehaviorNodes");
                return(new BehaviorTreeResults(BehaviorNodeState.Success));
            }
            float num = Time.realtimeSinceStartup - this.startTime;

            if (num > 60f && this.tickCount > 20)
            {
                Main.LogDebug($"[AI] [BlockUntilPathfindingReadyNode] Block until pathfinding failing, having timed out with too long a time '{num}' '{this.tickCount}'");
                base.LogAI(string.Format("Block until pathfinding failing, having timed out with too long a time {0} {1}", num, this.tickCount), "AI.BehaviorNodes");
                return(new BehaviorTreeResults(BehaviorNodeState.Failure));
            }
            Main.LogDebug($"[AI] [BlockUntilPathfindingReadyNode] Block until pathfinding waiting for pathing");
            base.LogAI("Block until pathfinding waiting for pathing", "AI.BehaviorNodes");
            return(new BehaviorTreeResults(BehaviorNodeState.Running));
        }
 public static void Postfix(AbstractActor unit, bool isStationary, BehaviorTreeResults __result)
 {
     CustomAmmoCategoriesLog.Log.LogWrite("Choose result for " + unit.DisplayName + "\n");
     try
     {
         if (__result.nodeState == BehaviorNodeState.Failure)
         {
             CustomAmmoCategoriesLog.Log.LogWrite("  AI choosed not attack\n");
         }
         else
         if (__result.orderInfo is AttackOrderInfo)
         {
             CustomAmmoCategoriesLog.Log.LogWrite("  AI choosed to attack " + (__result.orderInfo as AttackOrderInfo).TargetUnit.DisplayName + "\n");
             CustomAmmoCategories.ChooseBestWeaponForTarget(unit, (__result.orderInfo as AttackOrderInfo).TargetUnit, isStationary);
         }
         else
         {
             CustomAmmoCategoriesLog.Log.LogWrite("  AI choosed something else beside attaking\n");
         }
         return;
     }
     catch (Exception e)
     {
         CustomAmmoCategoriesLog.Log.LogWrite("Exception " + e.ToString() + "\nFallback to default\n");
         return;
     }
 }
 // This patch needs to run to correctly fix the melee attack to the target selected by CleverGirl/AI.
 //   During AI eval multiple targets are evaluated, and this ensures we use the attack for the one that was picked.
 static void Postfix(AbstractActor unit, ref BehaviorTreeResults __result)
 {
     if (__result != null && __result.nodeState == BehaviorNodeState.Success && __result.orderInfo is AttackOrderInfo attackOrderInfo)
     {
         if (attackOrderInfo.IsMelee)
         {
             Mod.Log.Debug?.Write($"Setting melee weapon for attack from attacker: {unit?.DistinctId()} versus target: {attackOrderInfo.TargetUnit?.DistinctId()}");
             // Create melee options
             MeleeState meleeState = ModState.AddorUpdateMeleeState(unit, attackOrderInfo.AttackFromLocation, attackOrderInfo.TargetUnit);
             if (meleeState != null)
             {
                 MeleeAttack meleeAttack = meleeState.GetHighestDamageAttackForUI();
                 ModState.AddOrUpdateSelectedAttack(unit, meleeAttack);
             }
         }
         else if (attackOrderInfo.IsDeathFromAbove)
         {
             // Create melee options
             MeleeState meleeState = ModState.AddorUpdateMeleeState(unit, attackOrderInfo.AttackFromLocation, attackOrderInfo.TargetUnit);
             if (meleeState != null)
             {
                 MeleeAttack meleeAttack = meleeState.DFA;
                 ModState.AddOrUpdateSelectedAttack(unit, meleeAttack);
             }
         }
     }
     else
     {
         Mod.Log.Trace?.Write($"BehaviorTree result is not failed: {__result?.nodeState} or is not an attackOrderInfo, skipping.");
     }
 }
Ejemplo n.º 4
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);
        }
Ejemplo n.º 5
0
        protected override BehaviorTreeResults Tick()
        {
            // don't have lance
            if (unit.lance == null)
            {
                return(new BehaviorTreeResults(BehaviorNodeState.Success));
            }

            var lanceUnits = unit.lance.unitGuids
                             .Select(guid => unit.Combat.FindActorByGUID(guid))
                             .Where(u => !u.IsDead)
                             .ToArray();

            // solo in lance
            if (lanceUnits.Length <= 1)
            {
                return(new BehaviorTreeResults(BehaviorNodeState.Success));
            }

            var minSpeed = lanceUnits.Min(u => u.MovementCaps.MaxWalkDistance);
            // ReSharper disable once CompareOfFloatsByEqualityOperator
            var slowestUnit = lanceUnits.First(u => minSpeed == u.MovementCaps.MaxWalkDistance);

            return(BehaviorTreeResults.BehaviorTreeResultsFromBoolean(unit == slowestUnit));
        }
Ejemplo n.º 6
0
 public static void Postfix(BehaviorNode __instance, ref BehaviorTreeResults __result)
 {
     if (__instance is LeafBehaviorNode && __result.orderInfo != null)
     {
         AIPause.PausePopup.AppendText(__instance.GetName());
     }
 }
        public static void Postfix(object __instance, ref BehaviorTreeResults __result, AbstractActor ___unit)
        {
            if (__result.nodeState != BehaviorNodeState.Success || !Main.Settings.ShouldPauseAI)
            {
                return;
            }

            AIPause.InfluenceMapVisual.OnInfluenceMapSort(___unit);
        }
Ejemplo n.º 8
0
        static void Postfix(BehaviorNode __instance, ref BehaviorTreeResults __result)
        {
            Traverse      unitT = Traverse.Create(__instance).Field("unit");
            AbstractActor unit  = unitT.GetValue <AbstractActor>();

            if (unit is Mech mech)
            {
                float heatCheck  = mech.HeatCheckMod(Mod.Config.SkillChecks.ModPerPointOfGuts);
                int   futureHeat = mech.CurrentHeat - mech.AdjustedHeatsinkCapacity;

                // Check to see if we will shutdown
                bool passedStartupCheck = CheckHelper.DidCheckPassThreshold(Mod.Config.Heat.Shutdown, futureHeat, mech, heatCheck, ModText.FT_Check_Startup);
                Mod.Log.Info?.Write($"AI unit {CombatantUtils.Label(mech)} heatCheck: {heatCheck} vs. futureHeat: {futureHeat} " +
                                    $"(from currentHeat: {mech.CurrentHeat} - sinking: {mech.AdjustedHeatsinkCapacity}) => passed: {passedStartupCheck}");

                if (!passedStartupCheck)
                {
                    Mod.Log.Info?.Write($" -- shutdown check failed, forcing it to remain shutdown.");
                    BehaviorTreeResults newResult = new BehaviorTreeResults(BehaviorNodeState.Failure);
                    newResult.orderInfo = new OrderInfo(OrderType.Stand);
                    __result            = newResult;

                    bool failedInjuryCheck = CheckHelper.ResolvePilotInjuryCheck(mech, futureHeat, -1, -1, heatCheck);
                    if (failedInjuryCheck)
                    {
                        Mod.Log.Info?.Write("  -- unit did not pass injury check!");
                    }

                    bool failedSystemFailureCheck = CheckHelper.ResolveSystemFailureCheck(mech, futureHeat, -1, heatCheck);
                    if (failedSystemFailureCheck)
                    {
                        Mod.Log.Info?.Write("  -- unit did not pass system failure check!");
                    }

                    bool failedAmmoCheck = CheckHelper.ResolveRegularAmmoCheck(mech, futureHeat, -1, heatCheck);
                    if (failedAmmoCheck)
                    {
                        Mod.Log.Info?.Write("  -- unit did not pass ammo explosion check!");
                    }

                    bool failedVolatileAmmoCheck = CheckHelper.ResolveVolatileAmmoCheck(mech, futureHeat, -1, heatCheck);
                    if (failedVolatileAmmoCheck)
                    {
                        Mod.Log.Info?.Write("  -- unit did not pass volatile ammo explosion check!");
                    }

                    QuipHelper.PublishQuip(mech, Mod.LocalizedText.Quips.Startup);
                }
                else
                {
                    Mod.Log.Info?.Write($" -- shutdown check passed, starting up normally.");
                }
            }
        }
 public static bool Prefix(BehaviorNode __instance, ref BehaviorTreeResults __result, ref BehaviorTree ___tree, ref AbstractActor ___unit)
 {
     try {
         List <AbstractActor> allAlliesOf = ___unit.Combat.GetAllAlliesOf(___unit);
         AuraBubble           sensors     = ___unit.sensorAura();
         for (int index1 = 0; index1 < ___tree.enemyUnits.Count; ++index1)
         {
             ICombatant    enemyUnit     = ___tree.enemyUnits[index1];
             AbstractActor abstractActor = enemyUnit as AbstractActor;
             float         magnitude     = (enemyUnit.CurrentPosition - ___unit.CurrentPosition).magnitude;
             if (AIUtil.UnitHasVisibilityToTargetFromPosition(___unit, enemyUnit, ___unit.CurrentPosition, allAlliesOf))
             {
                 if (___unit.CanEngageTarget(enemyUnit) || ___unit.CanDFATargetFromPosition(enemyUnit, ___unit.CurrentPosition))
                 {
                     __result = new BehaviorTreeResults(BehaviorNodeState.Success);
                     return(false);
                 }
                 if ((double)magnitude <= (double)___unit.MaxWalkDistance)
                 {
                     __result = new BehaviorTreeResults(BehaviorNodeState.Success);
                     return(false);
                 }
                 if (abstractActor != null && abstractActor.IsGhosted)
                 {
                     float num   = Mathf.Lerp(___unit.MaxWalkDistance, ___unit.MaxSprintDistance, ___unit.BehaviorTree.GetBehaviorVariableValue(BehaviorVariableName.Float_SignalInWeapRngWhenEnemyGhostedWithinMoveDistance).FloatVal);
                     float range = sensors.collider.radius;
                     if ((double)Vector3.Distance(___unit.CurrentPosition, abstractActor.CurrentPosition) - (double)range >= (double)num)
                     {
                         continue;
                     }
                 }
                 for (int index2 = 0; index2 < ___unit.Weapons.Count; ++index2)
                 {
                     Weapon weapon = ___unit.Weapons[index2];
                     if (weapon.CanFire && (double)weapon.MaxRange >= (double)magnitude)
                     {
                         __result = new BehaviorTreeResults(BehaviorNodeState.Success);
                         return(false);
                     }
                 }
             }
         }
         __result = new BehaviorTreeResults(BehaviorNodeState.Failure);
         return(false);
     } catch (Exception e) {
         Log.Debug?.Write(e.ToString() + "\n");
         __result = new BehaviorTreeResults(BehaviorNodeState.Failure);
         return(false);
     }
 }
Ejemplo n.º 10
0
        override protected BehaviorTreeResults Tick()
        {
            unit.BehaviorTree.enemyUnits = new List <ICombatant>();

            string[]           targetTags   = { "tutorial_sprint_target" };
            TagSet             targetTagSet = new TagSet(targetTags);
            List <ITaggedItem> items        = unit.Combat.ItemRegistry.GetObjectsOfTypeWithTagSet(TaggedObjectType.Unit, targetTagSet);

            for (int i = 0; i < items.Count; ++i)
            {
                ICombatant targetUnit = items[i] as ICombatant;
                if ((targetUnit != null) && (targetUnit.IsOperational))
                {
                    unit.BehaviorTree.enemyUnits.Add(targetUnit);
                }
            }

            return(BehaviorTreeResults.BehaviorTreeResultsFromBoolean(unit.BehaviorTree.enemyUnits.Count > 0));
        }
Ejemplo n.º 11
0
        override protected BehaviorTreeResults Tick()
        {
            unit.BehaviorTree.enemyUnits = new List <ICombatant>();

            List <ITaggedItem> items = unit.Combat.ItemRegistry.GetObjectsOfType(TaggedObjectType.Unit);

            for (int i = 0; i < items.Count; ++i)
            {
                ICombatant targetUnit = items[i] as ICombatant;
                if ((targetUnit != null) &&
                    (targetUnit.team.PlayerControlsTeam) &&
                    (targetUnit.IsOperational))
                {
                    unit.BehaviorTree.enemyUnits.Add(targetUnit);
                }
            }

            return(BehaviorTreeResults.BehaviorTreeResultsFromBoolean(unit.BehaviorTree.enemyUnits.Count > 0));
        }
Ejemplo n.º 12
0
        override protected BehaviorTreeResults Tick()
        {
            const int laserCount = 2;

            List <Weapon> lasers = new List <Weapon>();

            for (int wi = 0; wi < unit.Weapons.Count; ++wi)
            {
                if (lasers.Count >= laserCount)
                {
                    break;
                }

                Weapon w = unit.Weapons[wi];
                // TODO? (dlecompte) make this more specific
                if (w.WeaponCategoryValue.IsEnergy)
                {
                    lasers.Add(w);
                }
            }

            if ((lasers.Count == 0) || (unit.BehaviorTree.enemyUnits.Count == 0))
            {
                return(BehaviorTreeResults.BehaviorTreeResultsFromBoolean(false));
            }

            AttackOrderInfo attackOrder = new AttackOrderInfo(unit.BehaviorTree.enemyUnits[0]);

            attackOrder.Weapons    = lasers;
            attackOrder.TargetUnit = unit.BehaviorTree.enemyUnits[0];

            BehaviorTreeResults results = new BehaviorTreeResults(BehaviorNodeState.Success);

            results.orderInfo = attackOrder;
            return(results);
        }
Ejemplo n.º 13
0
    override protected BehaviorTreeResults Tick()
    {
        BattleTech.Designed.EncounterBoundaryChunkGameLogic boundaryChunk = unit.Combat.EncounterLayerData.encounterBoundaryChunk;

        if (boundaryChunk.IsInEncounterBounds(unit.CurrentPosition))
        {
            return(new BehaviorTreeResults(BehaviorNodeState.Success));
        }

        // find closest center
        float   bestDist    = float.MaxValue;
        Vector3 destination = Vector3.zero;

        if (boundaryChunk.encounterBoundaryRectList.Count == 0)
        {
            return(new BehaviorTreeResults(BehaviorNodeState.Failure));
        }

        for (int i = 0; i < boundaryChunk.encounterBoundaryRectList.Count; ++i)
        {
            RectHolder rh   = boundaryChunk.encounterBoundaryRectList[i];
            Vector3    c    = rh.rect.center;
            float      dist = (unit.CurrentPosition - c).magnitude;

            if (dist < bestDist)
            {
                bestDist    = dist;
                destination = c;
            }
        }

        if ((destination - unit.CurrentPosition).magnitude < 1)
        {
            // already close (should probably have been caught, above)
            return(new BehaviorTreeResults(BehaviorNodeState.Success));
        }

        unit.Pathing.UpdateAIPath(destination, destination, MoveType.Sprinting);
        Vector3 destinationThisTurn = unit.Pathing.ResultDestination;

        float        movementBudget = unit.Pathing.MaxCost;
        PathNodeGrid grid           = unit.Pathing.CurrentGrid;
        Vector3      successorPoint = destination;

        if ((grid.GetValidPathNodeAt(destinationThisTurn, movementBudget) == null) ||
            ((destinationThisTurn - destination).magnitude > 1.0f))
        {
            // can't get all the way to the destination.
            if (unit.Combat.EncounterLayerData.inclineMeshData != null)
            {
                List <AbstractActor> lanceUnits = AIUtil.GetLanceUnits(unit.Combat, unit.LanceId);
                List <Vector3>       path       = DynamicLongRangePathfinder.GetDynamicPathToDestination(destinationThisTurn, movementBudget, unit, true, lanceUnits, unit.Pathing.CurrentGrid, 100.0f);

                if ((path != null) && (path.Count > 0))
                {
                    destinationThisTurn = path[path.Count - 1];
                }
            }
        }

        Vector3 cur = unit.CurrentPosition;

        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,
                                   destinationThisTurn.x, destinationThisTurn.y, destinationThisTurn.z,
                                   successorPoint.x, successorPoint.y, successorPoint.z
                                   ));

        BehaviorTreeResults results      = new BehaviorTreeResults(BehaviorNodeState.Success);
        MovementOrderInfo   mvtOrderInfo = new MovementOrderInfo(destinationThisTurn, successorPoint);

        mvtOrderInfo.IsSprinting = true;
        results.orderInfo        = mvtOrderInfo;
        results.debugOrderString = string.Format("{0}: dest:{1} sprint:{2}", this.name, destination, mvtOrderInfo.IsSprinting);
        return(results);
    }
Ejemplo n.º 14
0
 override protected BehaviorTreeResults Tick()
 {
     return(BehaviorTreeResults.BehaviorTreeResultsFromBoolean(unit.Combat.EncounterLayerData.IsInEncounterBounds(unit.CurrentPosition)));
 }
Ejemplo n.º 15
0
    override protected BehaviorTreeResults Tick()
    {
        string regionGUID = RegionUtil.GetStayInsideRegionGUID(unit);

        if (regionGUID == null)
        {
            return(new BehaviorTreeResults(BehaviorNodeState.Failure));
        }

        if (unit.IsInRegion(regionGUID))
        {
            return(new BehaviorTreeResults(BehaviorNodeState.Success));
        }

        ITaggedItem item = unit.Combat.ItemRegistry.GetItemByGUID(regionGUID);

        if (item == null)
        {
            Debug.Log("no item with GUID: " + regionGUID);
            return(new BehaviorTreeResults(BehaviorNodeState.Failure));
        }
        RegionGameLogic region = item as RegionGameLogic;

        if (region == null)
        {
            Debug.Log("item is not region: " + regionGUID);
            return(new BehaviorTreeResults(BehaviorNodeState.Failure));
        }

        // TODO: find a point inside the region, for now using the average of all vertices.
        int numPoints = region.regionPointList.Length;

        Vector3 destination = new Vector3();

        for (int pointIndex = 0; pointIndex < numPoints; ++pointIndex)
        {
            destination += region.regionPointList[pointIndex].Position;
        }
        if (numPoints == 0)
        {
            Debug.Log("no points in region: " + regionGUID);
            return(new BehaviorTreeResults(BehaviorNodeState.Failure));
        }

        destination = RoutingUtil.Decrowd(destination * 1.0f / numPoints, unit);
        destination = RegionUtil.MaybeClipMovementDestinationToStayInsideRegion(unit, destination);

        var cell = unit.Combat.MapMetaData.GetCellAt(destination);

        destination.y = cell.cachedHeight;

        if ((destination - unit.CurrentPosition).magnitude < 1)
        {
            // already close (should probably have been caught, above)
            return(new BehaviorTreeResults(BehaviorNodeState.Success));
        }

        bool shouldSprint = unit.CanSprint;

        //float sprintRange = Mathf.Max(unit.MaxSprintDistance, unit.MaxWalkDistance);
        float moveRange = unit.MaxWalkDistance;

        if ((destination - unit.CurrentPosition).magnitude < moveRange)
        {
            shouldSprint = false;
        }

        if (shouldSprint)
        {
            unit.Pathing.SetSprinting();
        }
        else
        {
            unit.Pathing.SetWalking();
        }

        unit.Pathing.UpdateAIPath(destination, destination, shouldSprint ? MoveType.Sprinting : MoveType.Walking);
        Vector3 destinationThisTurn = unit.Pathing.ResultDestination;

        float        movementBudget = unit.Pathing.MaxCost;
        PathNodeGrid grid           = unit.Pathing.CurrentGrid;
        Vector3      successorPoint = destination;

        var longRangeToShorRangeDistanceThreshold = unit.BehaviorTree.GetBehaviorVariableValue(BehaviorVariableName.Float_LongRangeToShortRangeDistanceThreshold).FloatVal;

        if (grid.GetValidPathNodeAt(destinationThisTurn, movementBudget) == null || (destinationThisTurn - destination).magnitude > longRangeToShorRangeDistanceThreshold)
        {
            List <AbstractActor> lanceUnits = AIUtil.GetLanceUnits(unit.Combat, unit.LanceId);
            List <Vector3>       path       = DynamicLongRangePathfinder.GetDynamicPathToDestination(destination, movementBudget, unit, shouldSprint, lanceUnits, grid, 0);
            if (path == null || path.Count == 0)
            {
                return(new BehaviorTreeResults(BehaviorNodeState.Failure));
            }

            destinationThisTurn = path[path.Count - 1];

            Vector2 flatDestination = new Vector2(destination.x, destination.z);

            float   currentClosestPointInRegionDistance = float.MaxValue;
            Vector3?closestPoint = null;

            for (int i = 0; i < path.Count; ++i)
            {
                Vector3 pointOnPath = path[i];

                if (RegionUtil.PointInRegion(unit.Combat, pointOnPath, regionGUID))
                {
                    var distance = (flatDestination - new Vector2(pointOnPath.x, pointOnPath.z)).sqrMagnitude;

                    if (distance < currentClosestPointInRegionDistance)
                    {
                        currentClosestPointInRegionDistance = distance;
                        closestPoint = pointOnPath;
                    }
                }
            }

            if (closestPoint != null)
            {
                destinationThisTurn = closestPoint.Value;
            }
        }

        Vector3 cur = unit.CurrentPosition;

        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,
                                   destinationThisTurn.x, destinationThisTurn.y, destinationThisTurn.z,
                                   successorPoint.x, successorPoint.y, successorPoint.z
                                   ));

        BehaviorTreeResults results      = new BehaviorTreeResults(BehaviorNodeState.Success);
        MovementOrderInfo   mvtOrderInfo = new MovementOrderInfo(destinationThisTurn, successorPoint);

        mvtOrderInfo.IsSprinting = shouldSprint;
        results.orderInfo        = mvtOrderInfo;
        results.debugOrderString = string.Format("{0}: dest:{1} sprint:{2}", this.name, destination, mvtOrderInfo.IsSprinting);
        return(results);
    }
Ejemplo n.º 16
0
        // WARNING: Replaces the existing logic
        // isStationary here represents the attacker, not the target
        public static bool Prefix(AbstractActor unit, bool isStationary, ref BehaviorTreeResults __result)
        {
            // If there is no unit, exit immediately
            if (unit == null)
            {
                __result = new BehaviorTreeResults(BehaviorNodeState.Failure);
                return(false);
            }

            // If there are no enemies, exit immediately
            if (unit.BehaviorTree.enemyUnits.Count == 0)
            {
                Mod.Log.Info?.Write("No important enemy units, skipping decision making.");
                __result = new BehaviorTreeResults(BehaviorNodeState.Failure);
                return(false);
            }

            // Initialize decision data caches
            AEHelper.InitializeAttackOrderDecisionData(unit);

            Mod.Log.Debug?.Write($" == Evaluating attack from unit: {CombatantUtils.Label(unit)} at pos: {unit.CurrentPosition} against {unit.BehaviorTree.enemyUnits.Count} enemies.");
            BehaviorTreeResults behaviorTreeResults = null;
            AbstractActor       designatedTarget    = AEHelper.FilterEnemyUnitsToDesignatedTarget(unit.team as AITeam, unit.lance, unit.BehaviorTree.enemyUnits);

            float desTargDamage             = 0f;
            float desTargFirepowerReduction = 0f;

            if (designatedTarget != null)
            {
                desTargDamage             = AOHelper.MakeAttackOrderForTarget(unit, designatedTarget, isStationary, out behaviorTreeResults);
                desTargFirepowerReduction = AIAttackEvaluator.EvaluateFirepowerReductionFromAttack(unit, unit.CurrentPosition, designatedTarget, designatedTarget.CurrentPosition, designatedTarget.CurrentRotation, unit.Weapons, MeleeAttackType.NotSet);
                Mod.Log.Debug?.Write($"  DesignatedTarget: {CombatantUtils.Label(designatedTarget)} will suffer: {desTargDamage} damage and lose: {desTargFirepowerReduction} firepower from attack.");
            }
            else
            {
                Mod.Log.Debug?.Write("  No designated target identified.");
            }

            float behavior1 = AIHelper.GetBehaviorVariableValue(unit.BehaviorTree, BehaviorVariableName.Float_OpportunityFireExceedsDesignatedTargetByPercentage).FloatVal;
            float opportunityFireThreshold = 1f + (behavior1 / 100f);

            float behavior2 = AIHelper.GetBehaviorVariableValue(unit.BehaviorTree, BehaviorVariableName.Float_OpportunityFireExceedsDesignatedTargetFirepowerTakeawayByPercentage).FloatVal;
            float opportunityFireTakeawayThreshold = 1f + (behavior2 / 100f);

            Mod.Log.Info?.Write($"  Opportunity Fire damageThreshold: {opportunityFireThreshold}  takeawayThreshold: {opportunityFireTakeawayThreshold}");

            // Walk through every alive enemy, and see if a better shot presents itself.
            for (int j = 0; j < unit.BehaviorTree.enemyUnits.Count; j++)
            {
                ICombatant combatant = unit.BehaviorTree.enemyUnits[j];
                if (combatant == designatedTarget || combatant.IsDead)
                {
                    continue;
                }

                Mod.Log.Debug?.Write($"  Checking opportunity fire against target: {CombatantUtils.Label(combatant)}");

                AbstractActor       opportunityFireTarget = combatant as AbstractActor;
                BehaviorTreeResults oppTargAttackOrder;
                // Should MAOFT take a param for opportunity attacks to simplify?
                float oppTargDamage             = AOHelper.MakeAttackOrderForTarget(unit, combatant, isStationary, out oppTargAttackOrder);
                float oppTargFirepowerReduction = AIAttackEvaluator.EvaluateFirepowerReductionFromAttack(unit, unit.CurrentPosition, combatant, combatant.CurrentPosition, combatant.CurrentRotation, unit.Weapons, MeleeAttackType.NotSet);
                Mod.Log.Debug?.Write($"  Target will suffer: {oppTargDamage} with firepower reduction: {oppTargFirepowerReduction}");

                // TODO: Was where opportunity cost from evasion strip was added to target damage.
                //  Reintroduce utility damage to this calculation

                bool exceedsOpportunityFireThreshold = oppTargDamage > desTargDamage * opportunityFireThreshold;
                Mod.Log.Debug?.Write($"  Comparing damage - opportunity: {oppTargDamage} > designated: {designatedTarget} * threshold: {opportunityFireThreshold}");
                bool exceedsFirepowerReductionThreshold = oppTargFirepowerReduction > desTargFirepowerReduction * opportunityFireTakeawayThreshold;
                Mod.Log.Debug?.Write($"  Comparing firepower reduction - opportunity: {oppTargFirepowerReduction} vs. designated: {desTargFirepowerReduction} * threshold: {1f + opportunityFireTakeawayThreshold}");

                // TODO: Short circuit here - takes the first result, instead of the best result. Should we fix this?
                if (oppTargAttackOrder != null && oppTargAttackOrder.orderInfo != null &&
                    (exceedsOpportunityFireThreshold || exceedsFirepowerReductionThreshold))
                {
                    Mod.Log.Debug?.Write(" Taking opportunity fire attack, instead of attacking designated target.");
                    __result = oppTargAttackOrder;
                    return(false);
                }
            }

            if (behaviorTreeResults != null && behaviorTreeResults.orderInfo != null)
            {
                Mod.Log.Debug?.Write("Successfuly calculated attack order");
                unit.BehaviorTree.AddMessageToDebugContext(AIDebugContext.Shoot, "attacking designated target. Success");
                __result = behaviorTreeResults;
                return(false);
            }

            Mod.Log.Debug?.Write("Could not calculate reasonable attacks. Skipping node.");
            __result = new BehaviorTreeResults(BehaviorNodeState.Failure);

            return(false);
        }
Ejemplo n.º 17
0
        public static bool Prefix(AbstractActor unit, ICombatant target, int enemyUnitIndex, bool isStationary, out BehaviorTreeResults order, ref float __result)
        {
            try {
                Mod.Log.Info?.Write("AE:MAOFT entered.");

                //ModState.RangeToTargetsAlliesCache.Clear();
                __result = AOHelper.MakeAttackOrderForTarget(unit, target, isStationary, out BehaviorTreeResults innerBTR);
                order    = innerBTR;
            } catch (Exception e) {
                Mod.Log.Error?.Write("Failed to modify AttackOrder evaluation due to error: " + e.Message);
                Mod.Log.Error?.Write($"  Source:{e.Source}  StackTrace:{e.StackTrace}");

                order = null;
                return(true);
            }

            return(false);
        }
Ejemplo n.º 18
0
        // Evaluate all possible attacks for the attacker and target based upon their current position. Returns the total damage the target will take,
        //   which will be compared against all other targets to determine the optimal attack to make
        public static float MakeAttackOrderForTarget(AbstractActor attackerAA, ICombatant target, bool isStationary, out BehaviorTreeResults order)
        {
            Mod.Log.Debug?.Write($"Evaluating AttackOrder from ({CombatantUtils.Label(attackerAA)}) against ({CombatantUtils.Label(target)} at position: ({target.CurrentPosition})");

            // If the unit has no visibility to the target from the current position, they can't attack. Return immediately.
            if (!AIUtil.UnitHasVisibilityToTargetFromCurrentPosition(attackerAA, target))
            {
                order = BehaviorTreeResults.BehaviorTreeResultsFromBoolean(false);
                return(0f);
            }

            Mech  attackerMech   = attackerAA as Mech;
            float currentHeat    = attackerMech == null ? 0f : (float)attackerMech.CurrentHeat;
            float acceptableHeat = attackerMech == null ? float.MaxValue : AIUtil.GetAcceptableHeatLevelForMech(attackerMech);

            Mod.Log.Debug?.Write($" heat: current: {currentHeat} acceptable: {acceptableHeat}");

            //float weaponToHitThreshold = attackerAA.BehaviorTree.weaponToHitThreshold;

            // Filter weapons that cannot contribute to the battle
            CandidateWeapons candidateWeapons = new CandidateWeapons(attackerAA, target);

            Mech targetMech      = target as Mech;
            bool targetIsEvasive = targetMech != null && targetMech.IsEvasive;

            List <List <CondensedWeapon> >[] weaponSetsByAttackType =
            {
                new List <List <CondensedWeapon> >()
                {
                },
                new List <List <CondensedWeapon> >()
                {
                },
                new List <List <CondensedWeapon> >()
                {
                }
            };

            // Note: Disabled the evasion fractional checking that Vanilla uses. Should make units more free with ammunition against evasive foes
            //float evasiveToHitFraction = AIHelper.GetBehaviorVariableValue(attackerAA.BehaviorTree, BehaviorVariableName.Float_EvasiveToHitFloor).FloatVal / 100f;
            // TODO: Reappropriate BehaviorVariableName.Float_EvasiveToHitFloor as floor for all shots?

            // Build three sets of sets; ranged, melee, dfa. Each set contains a set of weapons O(n^2) here
            weaponSetsByAttackType[0] = AEHelper.MakeWeaponSets(candidateWeapons.RangedWeapons);

            // Evaluate melee attacks
            string cannotEngageInMeleeMsg = "";

            if (attackerMech == null || !attackerMech.CanEngageTarget(target, out cannotEngageInMeleeMsg))
            {
                Mod.Log.Debug?.Write($" attacker cannot melee, or cannot engage due to: '{cannotEngageInMeleeMsg}'");
            }
            else
            {
                // Check Retaliation
                // TODO: Retaliation should consider all possible attackers, not just the attacker
                // TODO: Retaliation should consider how much damage you do with melee vs. non-melee - i.e. punchbots should probably prefer punching over weak weapons fire
                // TODO: Should consider if heat would be reduced by melee attack
                if (AEHelper.MeleeDamageOutweighsRisk(attackerMech, target))
                {
                    // Generate base list
                    List <List <CondensedWeapon> > meleeWeaponSets = AEHelper.MakeWeaponSets(candidateWeapons.MeleeWeapons);

                    // Add melee weapons to each set
                    CondensedWeapon cMeleeWeapon = new CondensedWeapon(attackerMech.MeleeWeapon);
                    for (int i = 0; i < meleeWeaponSets.Count; i++)
                    {
                        meleeWeaponSets[i].Add(cMeleeWeapon);
                    }

                    weaponSetsByAttackType[1] = meleeWeaponSets;
                }
                else
                {
                    Mod.Log.Debug?.Write($" potential melee retaliation too high, skipping melee.");
                }
            }

            WeaponHelper.FilterWeapons(attackerAA, target, out List <Weapon> rangedWeps, out List <Weapon> meleeWeps, out List <Weapon> dfaWeps);

            AttackDetails attackDetails = new AttackDetails(attacker: attackerAA, target: target as AbstractActor,
                                                            attackPos: attackerAA.CurrentPosition, targetPos: target.CurrentPosition, useRevengeBonus: true);

            AttackEvaluation rangedAE = RangedCalculator.OptimizeAttack(attackDetails, rangedWeps);
            AttackEvaluation meleeAE  = MeleeCalculator.OptimizeAttack(meleeWeps, attackerAA, target);
            AttackEvaluation dfaAE    = DFACalculator.OptimizeAttack(dfaWeps, attackerAA, target);

            List <AttackEvaluation> allAttackSolutions = new List <AttackEvaluation>()
            {
                rangedAE, meleeAE, dfaAE
            };

            Mod.Log.Debug?.Write(string.Format("found {0} different attack solutions", allAttackSolutions.Count));

            // TODO: Apply mode - CleverGirlHelper.ApplyAmmoMode(wep, cWeapon.ammoAndMode);

            // Find the attack with the best damage across all attacks
            float bestRangedEDam = 0f;
            float bestMeleeEDam  = 0f;
            float bestDFAEDam    = 0f;

            for (int m = 0; m < allAttackSolutions.Count; m++)
            {
                AttackEvaluation attackEvaluation = allAttackSolutions[m];
                Mod.Log.Debug?.Write($"evaluated attack of type {attackEvaluation.AttackType} with {attackEvaluation.WeaponList.Count} weapons, " +
                                     $"damage EV of {attackEvaluation.ExpectedDamage}, heat {attackEvaluation.HeatGenerated}");
                switch (attackEvaluation.AttackType)
                {
                case AIUtil.AttackType.Shooting:
                    bestRangedEDam = Mathf.Max(bestRangedEDam, attackEvaluation.ExpectedDamage);
                    break;

                case AIUtil.AttackType.Melee:
                    bestMeleeEDam = Mathf.Max(bestMeleeEDam, attackEvaluation.ExpectedDamage);
                    break;

                case AIUtil.AttackType.DeathFromAbove:
                    bestDFAEDam = Mathf.Max(bestDFAEDam, attackEvaluation.ExpectedDamage);
                    break;

                default:
                    Debug.Log("unknown attack type: " + attackEvaluation.AttackType);
                    break;
                }
            }
            Mod.Log.Debug?.Write($"best shooting: {bestRangedEDam}  melee: {bestMeleeEDam}  dfa: {bestDFAEDam}");

            //float existingTargetDamageForOverheat = AIHelper.GetBehaviorVariableValue(attackerAA.BehaviorTree, BehaviorVariableName.Float_ExistingTargetDamageForOverheatAttack).FloatVal;

            AbstractActor   targetActor         = target as AbstractActor;
            List <PathNode> meleeDestsForTarget = attackerMech.Pathing.GetMeleeDestsForTarget(targetActor);

            // LOGIC: Now, evaluate every set of attacks in the list
            for (int n = 0; n < allAttackSolutions.Count; n++)
            {
                AttackEvaluator.AttackEvaluation attackEvaluation2 = allAttackSolutions[n];
                Mod.Log.Debug?.Write("------");
                Mod.Log.Debug?.Write($"Evaluating attack solution #{n} vs target: {CombatantUtils.Label(targetActor)}");

                // TODO: Do we really need this spam?
                StringBuilder weaponListSB = new StringBuilder();
                weaponListSB.Append(" Weapons: (");
                foreach (Weapon weapon3 in attackEvaluation2.WeaponList)
                {
                    weaponListSB.Append("'");
                    weaponListSB.Append(weapon3.Name);
                    weaponListSB.Append("', ");
                }
                weaponListSB.Append(")");
                Mod.Log.Debug?.Write(weaponListSB.ToString());

                if (attackEvaluation2.WeaponList.Count == 0)
                {
                    Mod.Log.Debug?.Write("SOLUTION REJECTED - no weapons!");
                }

                // TODO: Does heatGenerated account for jump heat?
                // TODO: Does not rollup heat!
                bool willCauseOverheat = attackEvaluation2.HeatGenerated + currentHeat > acceptableHeat;
                Mod.Log.Debug?.Write($"heat generated: {attackEvaluation2.HeatGenerated}  current: {currentHeat}  acceptable: {acceptableHeat}  willOverheat: {willCauseOverheat}");
                if (willCauseOverheat && attackerMech.OverheatWillCauseDeath())
                {
                    Mod.Log.Debug?.Write("SOLUTION REJECTED - overheat would cause own death");
                    continue;
                }
                // TODO: Check for acceptable damage from overheat - as per below
                //bool flag6 = num4 >= existingTargetDamageForOverheat;
                //Mod.Log.Debug?.Write("but enough damage for overheat attack? " + flag6);
                //bool flag7 = attackEvaluation2.lowestHitChance >= weaponToHitThreshold;
                //Mod.Log.Debug?.Write("but enough accuracy for overheat attack? " + flag7);
                //if (willCauseOverheat && (!flag6 || !flag7)) {
                //    Mod.Log.Debug?.Write("SOLUTION REJECTED - not enough damage or accuracy on an attack that will overheat");
                //    continue;
                //}

                // LOGIC: If we have some damage from an attack, can we improve upon it as a morale / called shot / multi-attack?
                if (attackEvaluation2.ExpectedDamage > 0f)
                {
                    BehaviorTreeResults behaviorTreeResults = new BehaviorTreeResults(BehaviorNodeState.Success);

                    // LOGIC: Check for a morale attack (based on available morale) - target must be shutdown or knocked down
                    //CalledShotAttackOrderInfo offensivePushAttackOrderInfo = AEHelper.MakeOffensivePushOrder(attackerAA, attackEvaluation2, target);
                    //if (offensivePushAttackOrderInfo != null) {
                    //    behaviorTreeResults.orderInfo = offensivePushAttackOrderInfo;
                    //    behaviorTreeResults.debugOrderString = attackerAA.DisplayName + " using offensive push";
                    //}

                    // LOGIC: Check for a called shot - target must be shutdown or knocked down
                    //CalledShotAttackOrderInfo calledShotAttackOrderInfo = AEHelper.MakeCalledShotOrder(attackerAA, attackEvaluation2, target, false);
                    //if (calledShotAttackOrderInfo != null) {
                    //    behaviorTreeResults.orderInfo = calledShotAttackOrderInfo;
                    //    behaviorTreeResults.debugOrderString = attackerAA.DisplayName + " using called shot";
                    //}

                    // LOGIC: Check for multi-attack that will fit within our heat boundaries
                    //MultiTargetAttackOrderInfo multiAttackOrderInfo = MultiAttack.MakeMultiAttackOrder(attackerAA, attackEvaluation2, enemyUnitIndex);
                    //if (!willCauseOverheat && multiAttackOrderInfo != null) {
                    //     Multi-attack in RT / BTA only makes sense to:
                    //      1. maximize breaching shot (which ignores cover/etc) if you a single weapon
                    //      2. spread status effects around while firing on a single target
                    //      3. maximizing total damage across N targets, while sacrificing potential damage at a specific target
                    //        3a. Especially with set sof weapons across range brackets, where you can split short-range weapons and long-range weapons
                    //    behaviorTreeResults.orderInfo = multiAttackOrderInfo;
                    //    behaviorTreeResults.debugOrderString = attackerAA.DisplayName + " using multi attack";
                    //}

                    AttackOrderInfo attackOrderInfo = new AttackOrderInfo(target)
                    {
                        Weapons    = attackEvaluation2.WeaponList,
                        TargetUnit = target
                    };
                    AIUtil.AttackType attackType = attackEvaluation2.AttackType;

                    List <PathNode> dfaDestinations = attackerAA.JumpPathing.GetDFADestsForTarget(targetActor);
                    if (attackType == AIUtil.AttackType.DeathFromAbove)
                    {
                        attackOrderInfo.IsDeathFromAbove = true;
                        attackOrderInfo.Weapons.Remove(attackerMech.MeleeWeapon);
                        attackOrderInfo.Weapons.Remove(attackerMech.DFAWeapon);
                        attackOrderInfo.AttackFromLocation = attackerMech.FindBestPositionToMeleeFrom(targetActor, dfaDestinations);
                    }
                    else if (attackType == AIUtil.AttackType.Melee)
                    {
                        attackOrderInfo.IsMelee = true;
                        attackOrderInfo.Weapons.Remove(attackerMech.MeleeWeapon);
                        attackOrderInfo.Weapons.Remove(attackerMech.DFAWeapon);

                        attackOrderInfo.AttackFromLocation = attackerMech.FindBestPositionToMeleeFrom(targetActor, meleeDestsForTarget);
                    }

                    behaviorTreeResults.orderInfo        = attackOrderInfo;
                    behaviorTreeResults.debugOrderString = $" using attack type: {attackEvaluation2.AttackType} against: {target.DisplayName}";

                    Mod.Log.Debug?.Write("attack order: " + behaviorTreeResults.debugOrderString);
                    order = behaviorTreeResults;
                    return(attackEvaluation2.ExpectedDamage);
                }
                Mod.Log.Debug?.Write("Rejecting attack for not having any expected damage");
            }

            Mod.Log.Debug?.Write("There are no targets I can shoot at without overheating.");
            order = null;
            return(0f);
        }
    override protected BehaviorTreeResults Tick()
    {
        BehaviorVariableValue variableValue = tree.GetBehaviorVariableValue(destinationBVarName);

        if (variableValue == null)
        {
            return(new BehaviorTreeResults(BehaviorNodeState.Failure));
        }

        string destinationGUID = variableValue.StringVal;

        RoutePointGameLogic destination = DestinationUtil.FindDestinationByGUID(tree, destinationGUID);

        if (destination == null)
        {
            return(new BehaviorTreeResults(BehaviorNodeState.Failure));
        }

        float sprintDistance = Mathf.Max(unit.MaxSprintDistance, unit.MaxWalkDistance);

        if (waitForLance)
        {
            for (int lanceMemberIndex = 0; lanceMemberIndex < unit.lance.unitGuids.Count; ++lanceMemberIndex)
            {
                ITaggedItem item = unit.Combat.ItemRegistry.GetItemByGUID(unit.lance.unitGuids[lanceMemberIndex]);
                if (item == null)
                {
                    continue;
                }
                AbstractActor lanceUnit = item as AbstractActor;
                if (lanceUnit == null)
                {
                    continue;
                }
                float unitMoveDistance = Mathf.Max(lanceUnit.MaxWalkDistance, lanceUnit.MaxSprintDistance);
                sprintDistance = Mathf.Min(sprintDistance, unitMoveDistance);
            }
        }

        MoveType moveType = tree.GetBehaviorVariableValue(BehaviorVariableName.Bool_RouteShouldSprint).BoolVal ?
                            MoveType.Sprinting : MoveType.Walking;

        unit.Pathing.UpdateAIPath(destination.Position, destination.Position, moveType);

        Vector3 offset = unit.Pathing.ResultDestination - unit.CurrentPosition;

        if (offset.magnitude > sprintDistance)
        {
            offset = offset.normalized * sprintDistance;
        }

        Vector3 destinationThisTurn = RoutingUtil.Decrowd(unit.CurrentPosition + offset, unit);

        destinationThisTurn = RegionUtil.MaybeClipMovementDestinationToStayInsideRegion(unit, destinationThisTurn);

        float destinationRadius = unit.BehaviorTree.GetBehaviorVariableValue(BehaviorVariableName.Float_RouteWaypointRadius).FloatVal;

        List <AbstractActor> unitsToWaitFor = new List <AbstractActor>();

        if (waitForLance)
        {
            if (unit.lance != null)
            {
                for (int lanceGUIDIndex = 0; lanceGUIDIndex < unit.lance.unitGuids.Count; ++lanceGUIDIndex)
                {
                    string      guid = unit.lance.unitGuids[lanceGUIDIndex];
                    ITaggedItem item = unit.Combat.ItemRegistry.GetItemByGUID(guid);
                    if (item != null)
                    {
                        AbstractActor lanceUnit = item as AbstractActor;
                        if (lanceUnit != null)
                        {
                            unitsToWaitFor.Add(lanceUnit);
                        }
                    }
                }
            }
            else
            {
                unitsToWaitFor.Add(unit);
            }
        }
        if (RoutingUtil.AllUnitsInsideRadiusOfPoint(unitsToWaitFor, destination.Position, destinationRadius))
        {
            tree.RemoveBehaviorVariableValue(destinationBVarName);
        }

        bool isSprinting = tree.GetBehaviorVariableValue(BehaviorVariableName.Bool_RouteShouldSprint).BoolVal;

        unit.Pathing.UpdateAIPath(destinationThisTurn, destination.Position, isSprinting ? MoveType.Sprinting : MoveType.Walking);
        destinationThisTurn = unit.Pathing.ResultDestination;

        float        movementBudget = unit.Pathing.MaxCost;
        PathNodeGrid grid           = unit.Pathing.CurrentGrid;
        Vector3      successorPoint = destination.Position;

        if ((grid.GetValidPathNodeAt(destinationThisTurn, movementBudget) == null) ||
            ((destinationThisTurn - destination.Position).magnitude > 1.0f))
        {
            // can't get all the way to the destination.
            if (unit.Combat.EncounterLayerData.inclineMeshData != null)
            {
                float maxSteepnessRatio         = Mathf.Tan(Mathf.Deg2Rad * AIUtil.GetMaxSteepnessForAllLance(unit));
                List <AbstractActor> lanceUnits = AIUtil.GetLanceUnits(unit.Combat, unit.LanceId);
                destinationThisTurn = unit.Combat.EncounterLayerData.inclineMeshData.GetDestination(
                    unit.CurrentPosition,
                    destinationThisTurn,
                    movementBudget,
                    maxSteepnessRatio,
                    unit,
                    isSprinting,
                    lanceUnits,
                    unit.Pathing.CurrentGrid,
                    out successorPoint);
            }
        }

        Vector3 cur = unit.CurrentPosition;

        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,
                                   destinationThisTurn.x, destinationThisTurn.y, destinationThisTurn.z,
                                   successorPoint.x, successorPoint.y, successorPoint.z
                                   ));

        BehaviorTreeResults results      = new BehaviorTreeResults(BehaviorNodeState.Success);
        MovementOrderInfo   mvtOrderInfo = new MovementOrderInfo(destinationThisTurn, successorPoint);

        mvtOrderInfo.IsSprinting = isSprinting;
        results.orderInfo        = mvtOrderInfo;
        results.debugOrderString = string.Format("{0} moving toward destination: {1} dest: {2}", this.name, destinationThisTurn, destination.Position);
        return(results);
    }
Ejemplo n.º 20
0
        static bool Prefix(ref BehaviorTreeResults __result,
                           string ___name, BehaviorTree ___tree, AbstractActor ___unit)
        {
            Mod.AILog.Info?.Write("CanMeleeHostileTargetsNode:Tick() invoked.");

            if (!(___unit is Mech))
            {
                // Not a mech, so don't allow them to melee
                __result = new BehaviorTreeResults(BehaviorNodeState.Failure);
                return(false);
            }

            //bool flag = false;
            //for (int i = 0; i < unit.Weapons.Count; i++)
            //{
            //	if (unit.Weapons[i].CanFire)
            //	{
            //		flag = true;
            //		break;
            //	}
            //}

            Mod.AILog.Info?.Write($"AI attacker: {___unit.DistinctId()} has {___unit.BehaviorTree.enemyUnits.Count} enemy units.");
            for (int j = 0; j < ___unit.BehaviorTree.enemyUnits.Count; j++)
            {
                ICombatant targetCombatant = ___unit.BehaviorTree.enemyUnits[j];

                // Skip the retaliation check; melee w/ kick is always viable, as is charge
                //Mech targetMech = targetCombatant as Mech;
                //if (targetMech != null)
                //{
                //	float num = AIUtil.ExpectedDamageForMeleeAttackUsingUnitsBVs(targetMech, unit, targetMech.CurrentPosition, mech.CurrentPosition, useRevengeBonus: false, unit);
                //	float num2 = AIUtil.ExpectedDamageForMeleeAttackUsingUnitsBVs(mech, targetMech, mech.CurrentPosition, targetMech.CurrentPosition, useRevengeBonus: false, unit);
                //	if (num2 <= 0f)
                //	{
                //		continue;
                //	}
                //	float num3 = num / num2;
                //	if (flag2 && num3 > unit.BehaviorTree.GetBehaviorVariableValue(BehaviorVariableName.Float_MeleeDamageRatioCap).FloatVal)
                //	{
                //		continue;
                //	}
                //}

                if (___unit.CanEngageTarget(targetCombatant))
                {
                    Mod.AILog.Info?.Write($"AI attacker: {___unit.DistinctId()} can engage target: {targetCombatant.DistinctId()}, returning true nodeState.");
                    __result = new BehaviorTreeResults(BehaviorNodeState.Success);
                    return(false);
                }
                else
                {
                    Mod.AILog.Info?.Write($"AI attacker: {___unit.DistinctId()} can NOT engage target: {targetCombatant.DistinctId()}");
                }
            }

            Mod.AILog.Info?.Write($"AI source: {___unit.DistinctId()} could find no targets, skipping.");
            // Fall through - couldn't find a single enemy unit we could attack, so skip
            __result = new BehaviorTreeResults(BehaviorNodeState.Failure);
            return(false);
        }
 public static bool Prefix(ref BehaviorTreeResults __result, AbstractActor ___unit, BehaviorNode __instance)
 {
     __result = AltTick(___unit, __instance);
     return(false);
 }
Ejemplo n.º 22
0
    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);
    }