// 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(); }
// 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(); }
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 (!idle) { nextState = new MeleeStateAttack(m_MeleeFSM); time = timer; idle = true; } Execute(); }
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 (!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(); }
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); }
// Events called by weapon animations #region Melee Events public void SetIdle() { stateMelee = MeleeState.Idle; }
// 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; }
public void SetAttack2_Transition() { stateMelee = MeleeState.Attack2_Transition; }
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 }
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!"); } }
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!"); } }
public void SetMoving() { stateMelee = MeleeState.Moving; }
public void PlayerDead() { stateMelee = MeleeState.Ranged; stateRanged = RangedState.RestrictMovement; }
public void SetDodging() { stateMelee = MeleeState.Dodging; playerMovement.Dodge(); }