Пример #1
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);
        }