// 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.");
     }
 }
    public override void Enter()
    {
        if (!attack)
        {
            // count the number of times the melee mob attacks
            chargeCounter = charge;
            enemyProp = melee.GetComponent<EnemyProperties>();
            int nAmmo = enemyProp.getAmmo();

            if (chargeCounter < 20)
            {

                // transform.position something something
                // make the enemy dash forward towards the player in a straight line

                if (!hit)
                {
                    nextState = new MeleeStateReturn(m_MeleeFSM);
                    attack = true;
                }
                else
                {
                    nextState = new MeleeStateExplode(m_MeleeFSM);

                    attack = true;
                }
            }
            else
            {
                nextState = new MeleeStateRetreat(m_MeleeFSM);
            }
        }

        Execute();
    }
Example #3
0
        // This assumes you're calling from a place that has already determined that we can reach the target.
        public static MeleeState GetMeleeStates(AbstractActor attacker, Vector3 attackPos, ICombatant target)
        {
            if (attacker == null || target == null)
            {
                Mod.MeleeLog.Warn?.Write("Null attacker or target - cannot melee!");
                return(new MeleeState());
            }

            Mech attackerMech = attacker as Mech;

            if (attackerMech == null)
            {
                Mod.MeleeLog.Warn?.Write("Vehicles and buildings cannot melee!");
                return(new MeleeState());
            }

            AbstractActor targetActor = target as AbstractActor;

            if (targetActor == null)
            {
                Mod.MeleeLog.Error?.Write("Target is not an abstractactor - must be building. Cannot melee!");
                return(new MeleeState());
            }

            Mod.MeleeLog.Info?.Write($"Building melee state for attacker: {CombatantUtils.Label(attacker)} against target: {CombatantUtils.Label(target)}");

            MeleeState states = new MeleeState(attackerMech, attackPos, targetActor);

            Mod.MeleeLog.Info?.Write($" - valid attacks => charge: {states.Charge.IsValid}  dfa: {states.DFA.IsValid}  kick: {states.Kick.IsValid}  " +
                                     $"weapon: {states.PhysicalWeapon.IsValid}  punch: {states.Punch.IsValid}");

            return(states);
        }
    public override void Enter()
    {
        if (!attack)
        {
            // count the number of times the melee mob attacks
            chargeCounter = charge;
            enemyProp     = melee.GetComponent <EnemyProperties>();
            int nAmmo = enemyProp.getAmmo();

            if (chargeCounter < 20)
            {
                // transform.position something something
                // make the enemy dash forward towards the player in a straight line

                if (!hit)
                {
                    nextState = new MeleeStateReturn(m_MeleeFSM);
                    attack    = true;
                }
                else
                {
                    nextState = new MeleeStateExplode(m_MeleeFSM);

                    attack = true;
                }
            }
            else
            {
                nextState = new MeleeStateRetreat(m_MeleeFSM);
            }
        }

        Execute();
    }
    public override void Enter()
    {
        if(!retreat)
        {
            // make the enemy retreat. like move out of the map from its initial position and move up
            nextState = new MeleeStateDeleteEntity(m_MeleeFSM);
        }

        Execute();
    }
Example #6
0
    public override void Enter()
    {
        if (!retreat)
        {
            // make the enemy retreat. like move out of the map from its initial position and move up
            nextState = new MeleeStateDeleteEntity(m_MeleeFSM);
        }

        Execute();
    }
    public override void Enter()
    {
        if (!spawn)
        {
            nextState = new MeleeStateIdle(m_MeleeFSM);

            spawn = true;
        }

        Execute();
    }
    public override void Enter()
    {
        if (!spawn)
        {
            nextState = new MeleeStateIdle(m_MeleeFSM);

            spawn = true;
        }

        Execute();
    }
Example #9
0
    public override void Enter()
    {
        if (!idle)
        {
            nextState = new MeleeStateAttack(m_MeleeFSM);
            time      = timer;

            idle = true;
        }

        Execute();
    }
    public override void Enter()
    {
        if (!idle)
        {
            nextState = new MeleeStateAttack(m_MeleeFSM);
            time = timer;

            idle = true;
        }

        Execute();
    }
Example #11
0
    public override void Enter()
    {
        if (!explode)
        {
            explosionTimer = explosionDuration;
            nextState      = new MeleeStateDeleteEntity(m_MeleeFSM);

            explosion = MonoBehaviour.Instantiate(explosionn, m_MeleeFSM.transform.position, Quaternion.identity) as GameObject;

            explode = true;
        }

        Execute();
    }
        private static void ToggleStateButtons(MeleeState meleeState)
        {
            if (ModState.ChargeFB != null)
            {
                if (meleeState == null || meleeState.Charge == null || !meleeState.Charge.IsValid)
                {
                    ModState.ChargeFB.CurrentFireMode = CombatHUDFireButton.FireMode.None;
                }
                else
                {
                    ModState.ChargeFB.CurrentFireMode = CombatHUDFireButton.FireMode.Engage;
                }
            }

            if (ModState.KickFB != null)
            {
                if (meleeState == null || meleeState.Kick == null || !meleeState.Kick.IsValid)
                {
                    ModState.KickFB.CurrentFireMode = CombatHUDFireButton.FireMode.None;
                }
                else
                {
                    ModState.KickFB.CurrentFireMode = CombatHUDFireButton.FireMode.Engage;
                }
            }

            if (ModState.PhysicalWeaponFB != null)
            {
                if (meleeState == null || meleeState.PhysicalWeapon == null || !meleeState.PhysicalWeapon.IsValid)
                {
                    ModState.PhysicalWeaponFB.CurrentFireMode = CombatHUDFireButton.FireMode.None;
                }
                else
                {
                    ModState.PhysicalWeaponFB.CurrentFireMode = CombatHUDFireButton.FireMode.Engage;
                }
            }

            if (ModState.PunchFB != null)
            {
                if (meleeState == null || meleeState.Punch == null || !meleeState.Punch.IsValid)
                {
                    ModState.PunchFB.CurrentFireMode = CombatHUDFireButton.FireMode.None;
                }
                else
                {
                    ModState.PunchFB.CurrentFireMode = CombatHUDFireButton.FireMode.Engage;
                }
            }
        }
    public override void Enter()
    {
        if (!explode)
        {
            explosionTimer = explosionDuration;
            nextState = new MeleeStateDeleteEntity(m_MeleeFSM);

            explosion = MonoBehaviour.Instantiate(explosionn, m_MeleeFSM.transform.position, Quaternion.identity) as GameObject;

            explode = true;
        }

        Execute();
    }
Example #14
0
    public override void Enter()
    {
        if (!goBack)
        {
            if (contact)
            {
                // make the entity go back to its original position as it reaches the end of the screen (bottom)
                nextState = new MeleeStateIdle(m_MeleeFSM);
            }

            goBack = true;
        }

        Execute();
    }
Example #15
0
        static void Postfix(MechMeleeSequence __instance, Mech mech, ICombatant meleeTarget,
                            List <Weapon> requestedWeapons, Vector3 desiredMeleePosition)
        {
            try
            {
                // Find the selectedAttack we should use for this sequence
                MeleeAttack selectedAttack = ModState.GetSelectedAttack(mech);
                if (selectedAttack == null)
                {
                    Mod.MeleeLog.Warn?.Write($"Melee sequence {__instance.SequenceGUID} has no pre-selected attack state, will have to autoselected. Let Frost know as this should not happen!");
                    MeleeState meleeState = ModState.AddorUpdateMeleeState(mech, desiredMeleePosition, meleeTarget as AbstractActor);
                    if (meleeState == null)
                    {
                        Mod.Log.Error?.Write($"Could not build melee state for selected melee attack - this should NEVER happen!");
                        return;
                    }
                    selectedAttack = meleeState.GetHighestDamageAttackForUI();
                }

                if (selectedAttack == null || !selectedAttack.IsValid)
                {
                    Mod.Log.Error?.Write($"Could not select a valid attack for the selected sequence - this should NEVER happen!");
                    return;
                }

                // Check to see if we have an imaginary weapon to use; if not create it
                (Weapon meleeWeapon, Weapon dfaWeapon)weapons = ModState.GetFakedWeapons(mech);

                // Create the weapon + representation
                ModState.AddOrUpdateMeleeSequenceState(__instance.SequenceGUID, selectedAttack, weapons.meleeWeapon);

                StringBuilder sb = new StringBuilder();
                foreach (Weapon weapon in requestedWeapons)
                {
                    sb.Append(weapon.UIName);
                    sb.Append(",");
                }
                Mod.MeleeLog.Info?.Write($"  -- Initial requested weapons: {sb}");

                isValid = true;
            }
            catch (Exception e)
            {
                Mod.Log.Error?.Write(e, $"Failed to initialize Melee sequence {__instance.SequenceGUID}!");
            }
        }
 void Start()
 {
     currentState = new MeleeStateSpawn(this);
 }
Example #17
0
 // Events called by weapon animations
 #region Melee Events
 public void SetIdle()
 {
     stateMelee = MeleeState.Idle;
 }
Example #18
0
        // Determine the best possible melee attack for a given attacker, target, and position
        //  - Usable weapons will include a MeleeWeapon/DFAWeapon with the damage set to the expected virtual damage BEFORE toHit is applied
        //     this is necessary to allow the EV calculations to proccess in CG
        //  - attackPos has to be a valid attackPosition for the target. This can be the 'safest' position as evaluated by
        //     FindBestPositionToMeleeFrom
        public static void OptimizeMelee(Mech attacker, AbstractActor target, Vector3 attackPos,
                                         List <Weapon> canFireInMeleeWeapons,
                                         out List <Weapon> usableWeapons, out MeleeAttack selectedState,
                                         out float virtualMeleeDamage, out float totalStateDamage)
        {
            usableWeapons      = new List <Weapon>();
            selectedState      = null;
            virtualMeleeDamage = 0f;
            totalStateDamage   = 0f;

            Mech targetMech = target as Mech;

            try
            {
                Mod.AILog.Info?.Write($"=== Optimizing melee attack for attacker: {attacker.DistinctId()} vs. " +
                                      $"target: {target.DistinctId()} at attackPos: {attackPos} with {canFireInMeleeWeapons.Count} melee ranged weapons.");

                Mod.AILog.Info?.Write($"Generating melee state - see melee log.");
                MeleeState meleeStates = MeleeHelper.GetMeleeStates(attacker, attackPos, target);

                // Iterate each state, add physical and weapon damage and evaluate virtual benefit from the sum
                Mod.AILog.Info?.Write($"Iterating non-DFA melee states.");
                float highestStateDamage     = 0f;
                List <MeleeAttack> allStates = new List <MeleeAttack> {
                    meleeStates.Charge, meleeStates.Kick, meleeStates.PhysicalWeapon, meleeStates.Punch
                };
                foreach (MeleeAttack meleeState in allStates)
                {
                    Mod.AILog.Info?.Write($"Evaluating damage for state: {meleeState.Label}");
                    if (!meleeState.IsValid)
                    {
                        Mod.AILog.Info?.Write($" -- melee state is invalid, skipping.");
                        continue;
                    }

                    Mod.AILog.Info?.Write($"  -- Checking ranged weapons");
                    float         rangedDamage = 0;
                    float         rangedStab   = 0;
                    float         rangedHeat   = 0;
                    List <Weapon> stateWeapons = new List <Weapon>();
                    foreach (Weapon weapon in canFireInMeleeWeapons)
                    {
                        if (meleeState.IsRangedWeaponAllowed(weapon) && !weapon.AOECapable)
                        {
                            stateWeapons.Add(weapon);
                            rangedDamage += weapon.DamagePerShot * weapon.ShotsWhenFired;
                            rangedDamage += weapon.StructureDamagePerShot * weapon.ShotsWhenFired;
                            rangedStab   += weapon.Instability() * weapon.ShotsWhenFired;
                            rangedHeat   += weapon.HeatDamagePerShot * weapon.ShotsWhenFired;
                            Mod.AILog.Info?.Write($"  weapon: {weapon.UIName} adds damage: {weapon.DamagePerShot} " +
                                                  $"structDam: {weapon.StructureDamagePerShot} instab: {weapon.Instability()}  heat: {weapon.HeatDamagePerShot} " +
                                                  $"x shots: {weapon.ShotsWhenFired}");
                        }
                    }

                    float meleeDamage      = meleeState.TargetDamageClusters.Sum();
                    float stateTotalDamage = meleeDamage + rangedDamage;
                    if (targetMech != null)
                    {
                        float totalTargetStab = meleeState.TargetInstability + rangedStab;
                        Mod.AILog.Info?.Write($"  - Calculating utility based upon total projected instab of: {totalTargetStab}");

                        // Apply evasion break and knockdown utility to the melee weapon
                        float evasionBreakUtility = 0f;
                        if (targetMech != null && targetMech.EvasivePipsCurrent > 0 &&
                            (meleeState.OnTargetMechHitForceUnsteady || AttackHelper.WillUnsteadyTarget(totalTargetStab, targetMech))
                            )
                        {
                            // Target will lose their evasion pips
                            evasionBreakUtility = targetMech.EvasivePipsCurrent * Mod.Config.Melee.AI.EvasionPipRemovedUtility;
                            Mod.AILog.Info?.Write($"  Adding {evasionBreakUtility} virtual damage to EV from " +
                                                  $"evasivePips: {targetMech.EvasivePipsCurrent} x bonusDamagePerPip: {Mod.Config.Melee.AI.EvasionPipRemovedUtility}");
                        }

                        float knockdownUtility = 0f;
                        if (targetMech != null && targetMech.pilot != null &&
                            AttackHelper.WillKnockdownTarget(totalTargetStab, targetMech, meleeState.OnTargetMechHitForceUnsteady))
                        {
                            float centerTorsoArmorAndStructure = targetMech.GetMaxArmor(ArmorLocation.CenterTorso) + targetMech.GetMaxStructure(ChassisLocations.CenterTorso);
                            if (AttackHelper.WillInjuriesKillTarget(targetMech, 1))
                            {
                                knockdownUtility = centerTorsoArmorAndStructure * Mod.Config.Melee.AI.PilotInjuryMultiUtility;
                                Mod.AILog.Info?.Write($"  Adding {knockdownUtility} virtual damage to EV from " +
                                                      $"centerTorsoArmorAndStructure: {centerTorsoArmorAndStructure} x injuryMultiUtility: {Mod.Config.Melee.AI.PilotInjuryMultiUtility}");
                            }
                            else
                            {
                                // Attack won't kill, so only apply a fraction equal to the totalHeath
                                float injuryFraction = (targetMech.pilot.TotalHealth - 1) - (targetMech.pilot.Injuries + 1);
                                knockdownUtility = (centerTorsoArmorAndStructure * Mod.Config.Melee.AI.PilotInjuryMultiUtility) / injuryFraction;
                                Mod.AILog.Info?.Write($"  Adding {knockdownUtility} virtual damage to EV from " +
                                                      $"(centerTorsoArmorAndStructure: {centerTorsoArmorAndStructure} x injuryMultiUtility: {Mod.Config.Melee.AI.PilotInjuryMultiUtility}) " +
                                                      $"/ injuryFraction: {injuryFraction}");
                            }
                        }

                        // Check to see how much evasion loss the attacker will have
                        //  use current pips + any pips gained from movement (charge)
                        float distance      = (attackPos + attacker.CurrentPosition).magnitude;
                        int   newPips       = attacker.GetEvasivePipsResult(distance, false, false, true);
                        int   normedNewPips = (attacker.EvasivePipsCurrent + newPips) > attacker.StatCollection.GetValue <int>("MaxEvasivePips") ?
                                              attacker.StatCollection.GetValue <int>("MaxEvasivePips") : (attacker.EvasivePipsCurrent + newPips);
                        float selfEvasionDamage = 0f;
                        if (meleeState.UnsteadyAttackerOnHit || meleeState.UnsteadyAttackerOnMiss)
                        {
                            // TODO: Should evaluate chance to hit, and apply these partial damage based upon success chances
                            selfEvasionDamage = normedNewPips * Mod.Config.Melee.AI.EvasionPipLostUtility;
                            Mod.AILog.Info?.Write($"  Reducing virtual damage by {selfEvasionDamage} due to potential loss of {normedNewPips} pips.");
                        }

                        // Check to see how much damage the attacker will take
                        float selfDamage = 0f;
                        if (meleeState.AttackerDamageClusters.Length > 0)
                        {
                            selfDamage = meleeState.AttackerDamageClusters.Sum();
                            Mod.AILog.Info?.Write($"  Reducing virtual damage by {selfDamage} due to attacker damage on attack.");
                        }

                        float virtualDamage = evasionBreakUtility + knockdownUtility - selfEvasionDamage - selfDamage;
                        Mod.AILog.Info?.Write($"  Virtual damage calculated as {virtualDamage} = " +
                                              $"evasionBreakUtility: {evasionBreakUtility} + knockdownUtility: {knockdownUtility}" +
                                              $" - selfDamage: {selfDamage} - selfEvasionDamage: {selfEvasionDamage}");

                        stateTotalDamage += virtualDamage;
                        // Add to melee damage as well, to so it can be set on the melee weapon
                        meleeDamage += virtualDamage;
                    }

                    if (stateTotalDamage > highestStateDamage)
                    {
                        Mod.AILog.Debug?.Write($"  State {meleeState.Label} exceeds previous state damages, adding it as highest damage state");
                        highestStateDamage = stateTotalDamage;

                        totalStateDamage   = stateTotalDamage;
                        virtualMeleeDamage = meleeDamage;
                        selectedState      = meleeState;

                        usableWeapons.Clear();
                        usableWeapons.AddRange(stateWeapons);
                    }
                }
                Mod.AILog.Info?.Write($"Iteration complete.");

                Mod.AILog.Info?.Write($"=== Best state for attacker: {attacker.DistinctId()} vs. " +
                                      $"target: {target.DistinctId()} at attackPos: {attackPos} is state: {selectedState?.Label} with " +
                                      $"virtualMeleeDamage: {virtualMeleeDamage} and totalStateDamage: {totalStateDamage}");
            } catch (Exception e)
            {
                Mod.AILog.Warn?.Write(e, $"Failed to optimize melee attack! ");
                Mod.AILog.Warn?.Write($"  Attacker: {(attacker == null ? "IS NULL" : attacker.DistinctId())}");
                Mod.AILog.Warn?.Write($"  Target: {(target == null ? "IS NULL" : target.DistinctId())}");
            }
            return;
        }
 public void ChangeState(MeleeState state)
 {
     currentState = state;
 }
Example #20
0
 public void SetAttack2_Transition()
 {
     stateMelee = MeleeState.Attack2_Transition;
 }
Example #21
0
    void Update()
    {
        #region Melee States
        switch (stateMelee)
        {
        case MeleeState.Idle:
            if (playerCombat.ranged)
            {
                stateMelee = MeleeState.Ranged;
            }

            playerMovement.restrictMovement = false;
            playerCombat.canKnockback       = false;
            stateRanged = RangedState.Melee;

            if (playerCombat.inputRecieved)
            {
                //Debug.Log("Attack Animation");
                animator.SetTrigger("AttackOne");
                playerCombat.MeleeAttack();
                playerCombat.InputManager();
                playerCombat.inputRecieved = false;
                stateMelee = MeleeState.Attack;
            }
            break;

        case MeleeState.Moving:
            stateRanged         = RangedState.Melee;
            playerCombat.ranged = false;
            if (playerCombat.inputRecieved)
            {
                //Debug.Log("Attack Animation");
                animator.SetTrigger("AttackOne");
                playerCombat.MeleeAttack();
                playerCombat.InputManager();
                playerCombat.inputRecieved = false;
            }
            break;

        case MeleeState.Dodging:
            stateRanged = RangedState.Melee;
            break;

        case MeleeState.Attack:
            stateRanged = RangedState.Melee;
            playerMovement.restrictMovement = true;
            break;

        case MeleeState.Attack1_Transition:
            stateRanged = RangedState.Melee;
            playerCombat.canRecieveInput = true;

            if (playerCombat.inputRecieved)
            {
                animator.SetTrigger("AttackTwo");
                playerCombat.MeleeAttack();
                playerCombat.InputManager();
                playerCombat.inputRecieved = false;
            }
            break;

        case MeleeState.Attack2_Transition:
            stateRanged = RangedState.Melee;
            playerCombat.canRecieveInput = true;
            playerCombat.canKnockback    = true;

            if (playerCombat.inputRecieved)
            {
                animator.SetTrigger("AttackThree");
                playerCombat.MeleeAttack();
                playerCombat.InputManager();
                playerCombat.inputRecieved = false;
            }
            break;

        case MeleeState.Ranged:
            break;
        }
        #endregion

        #region Ranged States
        switch (stateRanged)
        {
        case RangedState.NormalMovement:
            if (!playerCombat.ranged)
            {
                stateRanged = RangedState.Melee;
            }

            playerCombat.ranged             = true;
            stateMelee                      = MeleeState.Ranged;
            playerMovement.restrictMovement = false;
            break;

        case RangedState.RestrictMovement:
            stateMelee = MeleeState.Ranged;
            playerMovement.restrictMovement = true;
            playerCombat.ranged             = true;
            break;

        case RangedState.Melee:
            break;

        case RangedState.RangedDodging:
            stateMelee = MeleeState.Ranged;
            break;
        }
        #endregion
    }
Example #22
0
 public void SetAttack()
 {
     stateMelee = MeleeState.Attack;
 }
        static void Prefix(AIUtil __instance, AbstractActor unit, AttackType attackType, List <Weapon> weaponList,
                           ICombatant target, Vector3 attackPosition, Vector3 targetPosition, bool useRevengeBonus,
                           AbstractActor unitForBVContext)
        {
            Mech          attackingMech = unit as Mech;
            AbstractActor targetActor   = target as AbstractActor;
            Mech          targetMech    = target as Mech;

            Mod.AILog.Info?.Write("AITUIL_EDFA - entered.");

            if (attackingMech == null || targetActor == null)
            {
                return;                                               // Nothing to do
            }
            if (attackType == AttackType.Shooting || attackType == AttackType.None || attackType == AttackType.Count)
            {
                return;                                                                                                       // nothing to do
            }
            try
            {
                Mod.AILog.Info?.Write($"=== Calculating expectedDamage for {attackingMech.DistinctId()} melee attack " +
                                      $"from position: {attackPosition} against target: {target.DistinctId()} at position: {targetPosition}");
                Mod.AILog.Info?.Write($"  useRevengeBonus: {useRevengeBonus}");
                Mod.AILog.Info?.Write($"  --- weaponList:");
                if (weaponList != null)
                {
                    foreach (Weapon weapon in weaponList)
                    {
                        Mod.AILog.Info?.Write($"      {weapon?.UIName}");
                    }
                }

                bool        modifyAttack = false;
                MeleeAttack meleeAttack  = null;
                Weapon      meleeWeapon  = null;

                bool isCharge = false;
                bool isMelee  = false;
                if (attackType == AttackType.Melee && attackingMech?.Pathing?.GetMeleeDestsForTarget(targetActor)?.Count > 0)
                {
                    Mod.AILog.Info?.Write($"Modifying {attackingMech.DistinctId()}'s melee attack damage for utility");

                    // Create melee options
                    MeleeState meleeState = ModState.AddorUpdateMeleeState(attackingMech, attackPosition, targetActor);
                    if (meleeState != null)
                    {
                        meleeAttack = meleeState.GetHighestDamageAttackForUI();
                        ModState.AddOrUpdateSelectedAttack(attackingMech, meleeAttack);
                        if (meleeAttack is ChargeAttack)
                        {
                            isCharge = true;
                        }

                        meleeWeapon  = attackingMech.MeleeWeapon;
                        modifyAttack = true;
                        isMelee      = true;
                    }
                }

                bool isDFA = false;
                if (attackType == AttackType.DeathFromAbove && attackingMech?.JumpPathing?.GetDFADestsForTarget(targetActor)?.Count > 0)
                {
                    Mod.AILog.Info?.Write($"Modifying {attackingMech.DistinctId()}'s DFA attack damage for utility");

                    // Create melee options
                    MeleeState meleeState = ModState.AddorUpdateMeleeState(attackingMech, attackPosition, targetActor);
                    if (meleeState != null)
                    {
                        meleeAttack = meleeState.DFA;
                        ModState.AddOrUpdateSelectedAttack(attackingMech, meleeAttack);

                        meleeWeapon  = attackingMech.DFAWeapon;
                        modifyAttack = true;
                        isDFA        = true;
                    }
                }

                // No pathing dests for melee or DFA - skip
                if (!isMelee && !isDFA)
                {
                    return;
                }

                if (modifyAttack && meleeAttack == null || !meleeAttack.IsValid)
                {
                    Mod.AILog.Info?.Write($"Failed to find a valid melee state, marking melee weapons as 1 damage.");
                    meleeWeapon.StatCollection.Set <float>(ModStats.HBS_Weapon_DamagePerShot, 0);
                    meleeWeapon.StatCollection.Set <float>(ModStats.HBS_Weapon_Instability, 0);
                    return;
                }

                if (modifyAttack && meleeAttack != null && meleeAttack.IsValid)
                {
                    Mod.AILog.Info?.Write($"Evaluating utility against state: {meleeAttack.Label}");
                    // Set the DFA weapon's damage to our expected damage
                    float totalDamage = meleeAttack.TargetDamageClusters.Sum();
                    Mod.AILog.Info?.Write($" - totalDamage: {totalDamage}");

                    // Check to see if the attack will unsteady a target
                    float evasionBreakUtility = 0f;
                    if (targetMech != null && targetMech.EvasivePipsCurrent > 0 &&
                        (meleeAttack.OnTargetMechHitForceUnsteady || AttackHelper.WillUnsteadyTarget(meleeAttack.TargetInstability, targetMech))
                        )
                    {
                        // Target will lose their evasion pips
                        evasionBreakUtility = targetMech.EvasivePipsCurrent * Mod.Config.Melee.AI.EvasionPipRemovedUtility;
                        Mod.AILog.Info?.Write($"  Adding {evasionBreakUtility} virtual damage to EV from " +
                                              $"evasivePips: {targetMech.EvasivePipsCurrent} x bonusDamagePerPip: {Mod.Config.Melee.AI.EvasionPipRemovedUtility}");
                    }

                    float knockdownUtility = 0f;
                    if (targetMech != null && targetMech.pilot != null &&
                        AttackHelper.WillKnockdownTarget(meleeAttack.TargetInstability, targetMech, meleeAttack.OnTargetMechHitForceUnsteady))
                    {
                        float centerTorsoArmorAndStructure = targetMech.GetMaxArmor(ArmorLocation.CenterTorso) + targetMech.GetMaxStructure(ChassisLocations.CenterTorso);
                        if (AttackHelper.WillInjuriesKillTarget(targetMech, 1))
                        {
                            knockdownUtility = centerTorsoArmorAndStructure * Mod.Config.Melee.AI.PilotInjuryMultiUtility;
                            Mod.AILog.Info?.Write($"  Adding {knockdownUtility} virtual damage to EV from " +
                                                  $"centerTorsoArmorAndStructure: {centerTorsoArmorAndStructure} x injuryMultiUtility: {Mod.Config.Melee.AI.PilotInjuryMultiUtility}");
                        }
                        else
                        {
                            // Attack won't kill, so only apply a fraction equal to the totalHeath
                            float injuryFraction = (targetMech.pilot.TotalHealth - 1) - (targetMech.pilot.Injuries + 1);
                            knockdownUtility = (centerTorsoArmorAndStructure * Mod.Config.Melee.AI.PilotInjuryMultiUtility) / injuryFraction;
                            Mod.AILog.Info?.Write($"  Adding {knockdownUtility} virtual damage to EV from " +
                                                  $"(centerTorsoArmorAndStructure: {centerTorsoArmorAndStructure} x injuryMultiUtility: {Mod.Config.Melee.AI.PilotInjuryMultiUtility}) " +
                                                  $"/ injuryFraction: {injuryFraction}");
                        }
                    }

                    // Check to see how much evasion loss the attacker will have
                    //  use current pips + any pips gained from movement (charge)
                    float distance      = (attackPosition + unit.CurrentPosition).magnitude;
                    int   newPips       = unit.GetEvasivePipsResult(distance, isDFA, isCharge, true);
                    int   normedNewPips = (unit.EvasivePipsCurrent + newPips) > unit.StatCollection.GetValue <int>("MaxEvasivePips") ?
                                          unit.StatCollection.GetValue <int>("MaxEvasivePips") : (unit.EvasivePipsCurrent + newPips);
                    float selfEvasionDamage = 0f;
                    if (meleeAttack.UnsteadyAttackerOnHit || meleeAttack.UnsteadyAttackerOnMiss)
                    {
                        // TODO: Should evaluate chance to hit, and apply these partial damage based upon success chances
                        selfEvasionDamage = normedNewPips * Mod.Config.Melee.AI.EvasionPipLostUtility;
                        Mod.AILog.Info?.Write($"  Reducing virtual damage by {selfEvasionDamage} due to potential loss of {normedNewPips} pips.");
                    }

                    // Check to see how much damage the attacker will take
                    float selfDamage = 0f;
                    if (meleeAttack.AttackerDamageClusters.Length > 0)
                    {
                        selfDamage = meleeAttack.AttackerDamageClusters.Sum();
                        Mod.AILog.Info?.Write($"  Reducing virtual damage by {selfDamage} due to attacker damage on attack.");
                    }

                    float virtualDamage = totalDamage + evasionBreakUtility + knockdownUtility - selfEvasionDamage - selfDamage;
                    Mod.AILog.Info?.Write($"  Virtual damage calculated as {virtualDamage} = " +
                                          $"totalDamage: {totalDamage} + evasionBreakUtility: {evasionBreakUtility} + knockdownUtility: {knockdownUtility}" +
                                          $" - selfDamage: {selfDamage} - selfEvasionDamage: {selfEvasionDamage}");

                    Mod.AILog.Info?.Write($"Setting weapon: {meleeWeapon.UIName} to virtual damage: {virtualDamage} for EV calculation");
                    meleeWeapon.StatCollection.Set <float>(ModStats.HBS_Weapon_DamagePerShot, virtualDamage);
                    meleeWeapon.StatCollection.Set <float>(ModStats.HBS_Weapon_Instability, 0);

                    Mod.AILog.Info?.Write($"=== Done modifying attack!");
                }
                else
                {
                    Mod.AILog.Debug?.Write($"Attack is not melee {modifyAttack}, or melee state is invalid or null. I assume the normal AI will prevent action.");
                }
            }
            catch (Exception e)
            {
                Mod.AILog.Error?.Write(e, $"Failed to calculate melee damage for {unit.DistinctId()} using attackType {attackType} due to error!");
            }
        }
 public void ChangeState(MeleeState state)
 {
     currentState = state;
 }
 void Start()
 {
     currentState = new MeleeStateSpawn(this);
 }
        static bool Prefix(CombatHUDFireButton __instance)
        {
            if (__instance == null || __instance.gameObject == null)
            {
                return(true);
            }

            Mod.UILog.Info?.Write($"CHUDFB - OnClick FIRED for FireMode: {__instance.CurrentFireMode}");

            bool shouldReturn = true;
            CombatHUDAttackModeSelector selector = SharedState.CombatHUD.AttackModeSelector;

            if (__instance.gameObject.name == ModConsts.ChargeFB_GO_ID)
            {
                MeleeState meleeState = ModState.GetMeleeState(
                    SharedState.CombatHUD.SelectionHandler.ActiveState.SelectedActor,
                    SharedState.CombatHUD.SelectionHandler.ActiveState.PreviewPos);
                ModState.AddOrUpdateSelectedAttack(
                    SharedState.CombatHUD.SelectionHandler.ActiveState.SelectedActor,
                    meleeState.Charge
                    );
                Mod.UILog.Info?.Write("User selected Charge button");
                shouldReturn = false;
            }
            else if (__instance.gameObject.name == ModConsts.KickFB_GO_ID)
            {
                MeleeState meleeState = ModState.GetMeleeState(
                    SharedState.CombatHUD.SelectionHandler.ActiveState.SelectedActor,
                    SharedState.CombatHUD.SelectionHandler.ActiveState.PreviewPos);
                ModState.AddOrUpdateSelectedAttack(
                    SharedState.CombatHUD.SelectionHandler.ActiveState.SelectedActor,
                    meleeState.Kick
                    );
                Mod.UILog.Info?.Write("User selected Kick button");
                shouldReturn = false;
            }
            else if (__instance.gameObject.name == ModConsts.PhysicalWeaponFB_GO_ID)
            {
                MeleeState meleeState = ModState.GetMeleeState(
                    SharedState.CombatHUD.SelectionHandler.ActiveState.SelectedActor,
                    SharedState.CombatHUD.SelectionHandler.ActiveState.PreviewPos);
                ModState.AddOrUpdateSelectedAttack(
                    SharedState.CombatHUD.SelectionHandler.ActiveState.SelectedActor,
                    meleeState.PhysicalWeapon
                    );
                Mod.UILog.Info?.Write("User selected PhysWeap button");
                shouldReturn = false;
            }
            else if (__instance.gameObject.name == ModConsts.PunchFB_GO_ID)
            {
                MeleeState meleeState = ModState.GetMeleeState(
                    SharedState.CombatHUD.SelectionHandler.ActiveState.SelectedActor,
                    SharedState.CombatHUD.SelectionHandler.ActiveState.PreviewPos);
                ModState.AddOrUpdateSelectedAttack(
                    SharedState.CombatHUD.SelectionHandler.ActiveState.SelectedActor,
                    meleeState.Punch
                    );
                Mod.UILog.Info?.Write("User selected Punch button");
                shouldReturn = false;
            }
            else
            {
                MeleeAttack selectedAttack2 = ModState.GetSelectedAttack(SharedState.CombatHUD?.SelectionHandler?.ActiveState?.SelectedActor);
                if (selectedAttack2 != null)
                {
                    Mod.UILog.Info?.Write("OnClick from generic CHUDFB with selected type, short-cutting to action.");

                    // Disable the buttons to prevent accidental clicks?
                    if (selectedAttack2 is ChargeAttack)
                    {
                        ModState.ChargeFB.CurrentFireMode = CombatHUDFireButton.FireMode.None;
                    }
                    if (selectedAttack2 is KickAttack)
                    {
                        ModState.KickFB.CurrentFireMode = CombatHUDFireButton.FireMode.None;
                    }
                    if (selectedAttack2 is WeaponAttack)
                    {
                        ModState.PhysicalWeaponFB.CurrentFireMode = CombatHUDFireButton.FireMode.None;
                    }
                    if (selectedAttack2 is PunchAttack)
                    {
                        ModState.PunchFB.CurrentFireMode = CombatHUDFireButton.FireMode.None;
                    }

                    return(true);
                }
            }

            MeleeAttack selectedAttack = ModState.GetSelectedAttack(SharedState.CombatHUD?.SelectionHandler?.ActiveState?.SelectedActor);

            if (selectedAttack != null)
            {
                Mod.UILog.Debug?.Write("Enabling description container for melee attack");
                selector.DescriptionContainer.SetActive(true);
                selector.DescriptionContainer.gameObject.SetActive(true);

                HashSet <string> descriptonNotes = selectedAttack.DescriptionNotes;
                string           description     = String.Join(", ", descriptonNotes);
                Mod.UILog.Debug?.Write($"Aggregate description is: {description}");

                selector.DescriptionText.SetText(description);
                selector.DescriptionText.ForceMeshUpdate(true);

                // TODO: Update weapon damage instead?

                // Update the weapon strings
                SharedState.CombatHUD.WeaponPanel.RefreshDisplayedWeapons();
            }

            return(shouldReturn);
        }
        static void Postfix(CombatHUDAttackModeSelector __instance, CombatHUDFireButton.FireMode mode,
                            ref string additionalDetails, bool showHeatWarnings)
        {
            try
            {
                // Disable the melee container if there's no active state
                if (SharedState.CombatHUD?.SelectionHandler?.ActiveState == null ||
                    SharedState.CombatHUD?.SelectionHandler?.ActiveState?.SelectedActor == null ||
                    SharedState.CombatHUD?.SelectionHandler?.ActiveState?.PreviewPos == null)
                {
                    Mod.UILog.Trace?.Write($"Disabling all CHUD_Fire_Buttons");
                    ModState.MeleeAttackContainer.SetActive(false);
                    return;
                }

                Mod.UILog.Trace?.Write($"ShowFireButton called with mode: {mode}");

                if (mode == CombatHUDFireButton.FireMode.Engage)
                {
                    Mod.UILog.Trace?.Write($"Enabling all CHUD_Fire_Buttons");
                    ModState.MeleeAttackContainer.SetActive(true);

                    MeleeState meleeState = ModState.GetMeleeState(
                        SharedState.CombatHUD.SelectionHandler.ActiveState.SelectedActor,
                        SharedState.CombatHUD.SelectionHandler.ActiveState.PreviewPos);

                    // Toggle each button by available state
                    ToggleStateButtons(meleeState);

                    // Autoselect best option
                    MeleeAttack autoselectedAttack = meleeState.GetHighestDamageAttackForUI();
                    if (autoselectedAttack != null)
                    {
                        Mod.UILog.Info?.Write($"Autoselecting state of type: '{autoselectedAttack.Label}' as most damaging.");
                    }
                    else
                    {
                        Mod.UILog.Info?.Write("No highest damaging state - no melee options!");
                    }

                    // Final check - if everything is disabled, disable the button
                    bool hasValidAttack = meleeState.Charge.IsValid || meleeState.Kick.IsValid ||
                                          meleeState.PhysicalWeapon.IsValid || meleeState.Punch.IsValid;
                    if (!hasValidAttack)
                    {
                        Mod.UILog.Info?.Write("NO VALID MELEE ATTACKS, DISABLING!");
                        __instance.FireButton.SetState(ButtonState.Disabled);
                        __instance.FireButton.CurrentFireMode = CombatHUDFireButton.FireMode.None;
                        __instance.DescriptionContainer.SetActive(false);
                        SharedState.CombatHUD.SelectionHandler.ActiveState.BackOut();
                        __instance.ForceRefreshImmediate();
                    }
                    else
                    {
                        Mod.UILog.Info?.Write($" CHECKING FOR VALID ATTACKS: hasValidAttack=>{hasValidAttack}" +
                                              $"  charge=>{meleeState.Charge.IsValid}" +
                                              $"  kick=>{meleeState.Kick.IsValid}" +
                                              $"  punch=>{meleeState.Punch.IsValid}" +
                                              $"  weapon=>{meleeState.PhysicalWeapon.IsValid}" +
                                              $"");
                    }
                }
                else
                {
                    Mod.UILog.Trace?.Write($"Disabling all CHUD_Fire_Buttons");
                    ModState.MeleeAttackContainer.SetActive(false);
                    if (ModState.ChargeFB != null)
                    {
                        ModState.ChargeFB.CurrentFireMode = CombatHUDFireButton.FireMode.None;
                    }
                    if (ModState.KickFB != null)
                    {
                        ModState.KickFB.CurrentFireMode = CombatHUDFireButton.FireMode.None;
                    }
                    if (ModState.PhysicalWeaponFB != null)
                    {
                        ModState.PhysicalWeaponFB.CurrentFireMode = CombatHUDFireButton.FireMode.None;
                    }
                    if (ModState.PunchFB != null)
                    {
                        ModState.PunchFB.CurrentFireMode = CombatHUDFireButton.FireMode.None;
                    }

                    ModState.InvalidateState(SharedState.CombatHUD.SelectionHandler.ActiveState.SelectedActor);
                }

                // Handle the DFA button here
                if (mode == CombatHUDFireButton.FireMode.DFA)
                {
                    MeleeState meleeState = ModState.GetMeleeState(
                        SharedState.CombatHUD.SelectionHandler.ActiveState.SelectedActor,
                        SharedState.CombatHUD.SelectionHandler.ActiveState.PreviewPos);

                    // Check for valid attack
                    if (!meleeState.DFA.IsValid)
                    {
                        Mod.UILog.Info?.Write($"DFA attack failed validation, disabling button.");
                        __instance.FireButton.SetState(ButtonState.Disabled);
                        __instance.FireButton.CurrentFireMode = CombatHUDFireButton.FireMode.None;
                        __instance.DescriptionContainer.SetActive(false);
                        SharedState.CombatHUD.SelectionHandler.ActiveState.BackOut();
                        __instance.ForceRefreshImmediate();
                    }

                    HashSet <string> descriptonNotes = meleeState.DFA.DescriptionNotes;
                    additionalDetails = String.Join(", ", descriptonNotes);
                    Mod.UILog.Info?.Write($"Aggregate description is: {additionalDetails}");

                    // Select state here as a click will validate
                    ModState.AddOrUpdateSelectedAttack(
                        SharedState.CombatHUD.SelectionHandler.ActiveState.SelectedActor,
                        meleeState.DFA
                        );
                }
            }
            catch (Exception e)
            {
                Mod.Log.Warn?.Write(e, "Failed to update the CombatButton states - warn Frost!");
            }
        }
Example #28
0
 public void SetMoving()
 {
     stateMelee = MeleeState.Moving;
 }
Example #29
0
 public void PlayerDead()
 {
     stateMelee  = MeleeState.Ranged;
     stateRanged = RangedState.RestrictMovement;
 }
Example #30
0
 public void SetDodging()
 {
     stateMelee = MeleeState.Dodging;
     playerMovement.Dodge();
 }