static bool Prefix(MechMeleeSequence __instance) { // allow DFA to knock down target in single hit if (Core.ModSettings.AllowSteadyToKnockdownForMelee) { if (!__instance.MeleeTarget.IsDead) { var mech = __instance.MeleeTarget as Mech; Logger.Debug($"opponent stability: {mech.CurrentStability}"); var target = __instance.MeleeTarget as AbstractActor; if (target != null) { Logger.Debug($"found target before first check. unsteady? {mech.IsUnsteady} : prone? {mech.IsProne}"); target.NeedsInstabilityCheck = true; target.CheckForInstability(); Logger.Debug($"found target before second check. unsteady? {mech.IsUnsteady} : prone? {mech.IsProne}"); target.NeedsInstabilityCheck = true; target.CheckForInstability(); Logger.Debug($"found target after second check. downed? {mech.IsUnsteady} : prone? {mech.IsProne}"); var attacker = __instance.OwningMech; target.HandleKnockdown(__instance.RootSequenceGUID, attacker.GUID, Vector2.one, null); } } } return(true); }
static void Postfix(MechMeleeSequence __instance, bool __result) { using (var logwriter = File.AppendText(SprintAndPunch.LogPath)) { logwriter?.WriteLine("Default prefix result of ConsumesMovement is {0}.", __result); // __result = true; Pilot curPilot = __instance.OwningMech.GetPilot(); if (curPilot != null && curPilot.PassiveAbilities.Count > 0) //.StatCollection.GetValue<int>("MeleeHitPushBackPhases") > 0) { logwriter?.WriteLine("Found pilot with abilities."); bool foundJug = false; for (int i = 0; i < curPilot.PassiveAbilities.Count && !foundJug; i++) { if (curPilot.PassiveAbilities[i].Def.Description.Id == "AbilityDefGu8") { logwriter?.WriteLine("Should be returning false for ConsumesMovement."); __result = false; //ignore firing cost with juggernaut active: //__instance.OwningMech.BracedLastRound = true; /// __instance.OwningMech.ApplyInstabilityReduction(StabilityChangeSource.Bracing); foundJug = true; SprintAndPunch.justPunched = foundJug; SprintAndPunch.jugMech = __instance.OwningMech; } } if (!foundJug) //assume melee cost like normal { logwriter?.WriteLine("Should be returning true for COnsumesMovement."); __result = true; } } } }
public static void Prefix(MechMeleeSequence __instance, ref List <Weapon> ___requestedWeapons) { if (___requestedWeapons.Count < 1) { return; } AbstractActor actor = __instance.owningActor; ICombatant MeleeTarget = __instance.MeleeTarget; bool TargetIsDead = MeleeTarget.IsDead; bool TargetIsFlaggedForDeath = MeleeTarget.IsFlaggedForDeath; Logger.Info("[MechMeleeSequence_OnMeleeComplete_PREFIX] TargetIsDead: " + TargetIsDead); Logger.Info("[MechMeleeSequence_OnMeleeComplete_PREFIX] TargetIsFlaggedForDeath: " + TargetIsFlaggedForDeath); if (TargetIsDead || TargetIsFlaggedForDeath) { ___requestedWeapons.Clear(); actor.Combat.MessageCenter.PublishMessage(new FloatieMessage(actor.GUID, actor.GUID, "SUSPENDED SUPPORT WEAPONS", FloatieMessage.MessageNature.Neutral)); } else { DontShootTheDead.BuildSupportWeaponSequence = true; Traverse BuildWeaponDirectorSequence = Traverse.Create(__instance).Method("BuildWeaponDirectorSequence"); BuildWeaponDirectorSequence.GetValue(); } }
static bool Prefix(MechMeleeSequence __instance) { // attacker second instability check during melee whiff var attacker = __instance.OwningMech; attacker.CheckForInstability(); HandleFall.Say(attacker); // only one I couldn't get to trigger, but I believe is correctly placed attacker.HandleKnockdown(__instance.RootSequenceGUID, attacker.GUID, Vector2.one, null); // second target instability check during melee hit if can go to ground in one hit if (Core.ModSettings.AllowSteadyToKnockdownForMelee) { if (!__instance.MeleeTarget.IsDead) { var target = __instance.MeleeTarget as AbstractActor; if (target != null) { target.NeedsInstabilityCheck = true; target.CheckForInstability(); target.HandleKnockdown(__instance.RootSequenceGUID, attacker.GUID, Vector2.one, null); } } } Logger.Debug($"did we fall? {attacker.IsProne}"); Logger.Debug($"did they fall? {__instance.MeleeTarget.IsProne}"); return(true); }
static bool Prefix(MechMeleeSequence __instance, bool __result) { using (var logwriter = File.AppendText(SprintAndPunch.LogPath)) { logwriter?.WriteLine("Default prefix result of ConsumesMovement is {0}.", __result); // __result = true; Pilot curPilot = __instance.OwningMech.GetPilot(); if (curPilot != null && curPilot.PassiveAbilities.Count > 0) //.StatCollection.GetValue<int>("MeleeHitPushBackPhases") > 0) { logwriter?.WriteLine("Found pilot with abilities."); bool foundJug = false; for (int i = 0; i < curPilot.PassiveAbilities.Count && !foundJug; i++) { if (curPilot.PassiveAbilities[i].Def.Description.Id == "AbilityDefGu8") { logwriter?.WriteLine("Should be returning false for ConsumesMovement."); __result = false; //ignore firing cost with juggernaut active: foundJug = true; return(false); //overriding if we have this... } } if (!foundJug) //assume melee cost like normal { logwriter?.WriteLine("Should be returning true for ConsumesMovement."); __result = true; } } return(true); //otherwise skip } }
// Remove the BuildWeaponDirectorSequence, to prevent duplicate ammo consumption static bool Prefix(MechMeleeSequence __instance) { Traverse BuildMeleeDirectorSequenceT = Traverse.Create(__instance).Method("BuildMeleeDirectorSequence"); BuildMeleeDirectorSequenceT.GetValue(); if (__instance.OwningMech.GameRep != null) { __instance.OwningMech.GameRep.ReturnToNeutralFacing(isParellelSequence: true, 0.5f, __instance.RootSequenceGUID, __instance.SequenceGUID, null); } if (__instance.OwningMech.GameRep != null) { __instance.OwningMech.GameRep.FadeThrottleAudio(0f, 50f, 1f); } SharedState.Combat.MessageCenter.AddSubscriber(MessageCenterMessageType.OnAttackComplete, __instance.OnMeleeComplete); SharedState.Combat.MessageCenter.AddSubscriber(MessageCenterMessageType.OnAttackSequenceFire, __instance.OnMeleeReady); // Reading meleeSequence as a property doesn't seem to work, because it always returns null. I'm unsure if this // is a harmony bug... so read it directly, which seems to work. Traverse meleeSequenceT = Traverse.Create(__instance).Field("meleeSequence"); AttackStackSequence meleeSequence = meleeSequenceT.GetValue <AttackStackSequence>(); SharedState.Combat.MessageCenter.PublishMessage(new AddParallelSequenceToStackMessage(meleeSequence)); return(false); }
static void Postfix(ref MessageCenterMessage message, MechMeleeSequence __instance) { Logger.Debug($"checking for miss: {(message as AttackCompleteMessage).attackSequence.attackCompletelyMissed}"); if (Core.ModSettings.AttackMissInstability) { // Deal with attacker missing var attackCompleteMessage = (AttackCompleteMessage)message; var attacker = __instance.OwningMech; if (attackCompleteMessage.attackSequence.attackCompletelyMissed) { Logger.Debug($"melee pre-miss stability: {attacker.CurrentStability}"); var rawInstabilityToAdd = attacker.IsLegged ? Core.ModSettings.AttackMissInstabilityLeggedPercent : Core.ModSettings.AttackMissInstabilityPercent; var instabilityToAdd = rawInstabilityToAdd; if (Core.ModSettings.pilotingSkillInstabilityMitigation) { var mitigation = Calculator.PilotingMitigation(attacker); var mitigationPercent = Mathf.RoundToInt(mitigation * 100); instabilityToAdd = rawInstabilityToAdd - rawInstabilityToAdd * mitigation; Logger.Debug($"melee miss numbers\n" + $"pilotSkill: {attacker.SkillPiloting}\n" + $"mitigation: {mitigation}\n" + $"rawInstabilityToAdd: {rawInstabilityToAdd}\n" + $"mitigationPercent: {mitigationPercent}\n" + $"instabilityToAdd: {instabilityToAdd}"); attacker.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(new ShowActorInfoSequence(attacker, $"Pilot Check: Avoided {mitigationPercent}% Instability!", FloatieMessage.MessageNature.Neutral, true))); } attacker.AddRelativeInstability(instabilityToAdd, StabilityChangeSource.Attack, attacker.GUID); Logger.Debug($"melee post-miss stability: {attacker.CurrentStability}"); attacker.NeedsInstabilityCheck = true; if (Core.ModSettings.AllowSteadyToKnockdownForMelee) { attacker.CheckForInstability(); attacker.NeedsInstabilityCheck = true; } } } // Deal with target needing additional checks if we want to be able to knock over // mechs in a single round from one attack. if (Core.ModSettings.AllowSteadyToKnockdownForMelee) { if (!__instance.MeleeTarget.IsDead) { var target = __instance.MeleeTarget as AbstractActor; if (target != null) { target.NeedsInstabilityCheck = true; target.CheckForInstability(); var attacker = __instance.OwningMech; HandleFall.Say(attacker); target.HandleKnockdown(__instance.RootSequenceGUID, attacker.GUID, Vector2.one, null); } } } }
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}!"); } }
public static bool Prefix(MechMeleeSequence __instance, ref List <Weapon> ___requestedWeapons) { Logger.Info("[MechMeleeSequence_BuildWeaponDirectorSequence_PREFIX] DontShootTheDead.BuildSupportWeaponSequence: " + DontShootTheDead.BuildSupportWeaponSequence); // Only build a weapon sequence if melee didn't already kill the target if (DontShootTheDead.BuildSupportWeaponSequence) { // Will still only build a weapon sequence if ___requestedWeapons.Count is > 0 return(true); } else { // This always needs to be done for a melee attack ___requestedWeapons.RemoveAll((Weapon x) => x.Type == WeaponType.Melee); return(false); } }
static void Postfix(MechMeleeSequence __instance) { try { if (Fields.JuggernautCharges) { // Charge and tackle causes slight instability, check for unsteady __instance.OwningMech.CheckForInstability(); } // Just to be sure Fields.JuggernautCharges = false; } catch (Exception e) { Logger.Error(e); } }
static void Postfix(ref MessageCenterMessage message, MechMeleeSequence __instance) { if (Core.ModSettings.DfaMissInstability) { Logger.Debug($"checking for miss: {(message as AttackCompleteMessage).attackSequence.attackCompletelyMissed}"); var attackCompleteMessage = (AttackCompleteMessage)message; var attacker = __instance.OwningMech; if (attackCompleteMessage.attackSequence.attackCompletelyMissed) { Logger.Debug("We missed!"); Logger.Debug($"flagged for knockdown? {attacker.IsFlaggedForKnockdown}"); var rawInstabilityToAdd = attacker.IsLegged ? Core.ModSettings.DfaMissInstabilityLeggedPercent : Core.ModSettings.DfaMissInstabilityPercent; var instabilityToAdd = rawInstabilityToAdd; if (Core.ModSettings.PilotingSkillInstabilityMitigation) { var mitigation = Calculator.PilotingMitigation(attacker); var mitigationPercent = Mathf.RoundToInt(mitigation * 100); instabilityToAdd = rawInstabilityToAdd - rawInstabilityToAdd * mitigation; Logger.Debug($"dfa miss numbers\n" + $"pilotSkill: {attacker.SkillPiloting}\n" + $"mitigation: {mitigation}\n" + $"rawInstabilityToAdd: {rawInstabilityToAdd}\n" + $"mitigationPercent: {mitigationPercent}\n" + $"instabilityToAdd: {instabilityToAdd}"); attacker.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(new ShowActorInfoSequence(attacker, $"Pilot Check: Avoided {mitigationPercent}% Instability!", FloatieMessage.MessageNature.Neutral, true))); } attacker.AddRelativeInstability(instabilityToAdd, StabilityChangeSource.DFA, attacker.GUID); attacker.NeedsInstabilityCheck = true; attacker.CheckForInstability(); Logger.Debug($"flagged for knockdown? {attacker.IsFlaggedForKnockdown}"); if (Core.ModSettings.AllowSteadyToKnockdownForMelee) { attacker.NeedsInstabilityCheck = true; attacker.CheckForInstability(); Logger.Debug($"flagged for knockdown? {attacker.IsFlaggedForKnockdown}"); } } HandleFall.Say(attacker); } }
static void Prefix(MechMeleeSequence __instance) { try { Pilot pilot = __instance.owningActor.GetPilot(); if (pilot.IsJuggernaut() && Fields.JuggernautCharges) { Logger.Debug("[MechMeleeSequence_BuildMeleeDirectorSequence_PREFIX] Fields.JuggernautCharges: " + Fields.JuggernautCharges); MeleeAttackType selectedMeleeType = (MeleeAttackType)AccessTools.Property(typeof(MechMeleeSequence), "selectedMeleeType").GetValue(__instance, null); Logger.Info("[MechMeleeSequence_BuildMeleeDirectorSequence_PREFIX] BEFORE selectedMeleeType: " + selectedMeleeType); selectedMeleeType = MeleeAttackType.Charge; Logger.Info("[MechMeleeSequence_BuildMeleeDirectorSequence_PREFIX] AFTER selectedMeleeType: " + selectedMeleeType); } } catch (Exception e) { Logger.Error(e); } }
static void Postfix(MechMeleeSequence __instance) { if (!MechMeleeSequence_ctor.isValid) { Mod.MeleeLog.Info?.Write($" -- Invalid sequence in OnAdded, skipping!"); return; } Mod.MeleeLog.Trace?.Write("MMS:CO - entered."); // Base method checks for target knockdown. Do the same for the attacker. if (!__instance.OwningMech.IsDead) { __instance.OwningMech.CheckForInstability(); __instance.OwningMech.HandleKnockdown(__instance.RootSequenceGUID, __instance.owningActor.GUID, Vector2.one, null); } ModState.InvalidateState(__instance.OwningMech); }
static void Prefix(MechMeleeSequence __instance) { Mod.MeleeLog.Debug?.Write("Regenerating melee support weapons hit locations..."); Traverse BuildWeaponDirectorSequenceT = Traverse.Create(__instance).Method("BuildWeaponDirectorSequence"); if (BuildWeaponDirectorSequenceT == null) { Mod.Log.Error?.Write($"No method named BuildWeaponDirectorSequence found - no clue what will happen next!"); System.Diagnostics.StackTrace t = new System.Diagnostics.StackTrace(); Mod.Log.Info?.Write($" Error occured at: {t}"); } else { BuildWeaponDirectorSequenceT.GetValue(); Mod.MeleeLog.Debug?.Write(" -- Done!"); } // Reset damage table ModState.ForceDamageTable = DamageTable.NONE; }
static void Prefix(MechMeleeSequence __instance) { (MeleeAttack meleeAttack, Weapon fakeWeapon)seqState = ModState.GetMeleeSequenceState(__instance.SequenceGUID); if (seqState.meleeAttack != null && seqState.meleeAttack.AttackerInstability != 0) { Mod.MeleeLog.Info?.Write($" -- Adding {seqState.meleeAttack.AttackerInstability} absolute instability to attacker."); __instance.OwningMech.AddAbsoluteInstability(seqState.meleeAttack.AttackerInstability, StabilityChangeSource.Attack, "-1"); } // Publish a floatie warning of the swarm attack on the target if (ModState.ForceDamageTable == DamageTable.SWARM) { string swarmAttackText = new Text(Mod.LocalizedText.Floaties[ModText.FT_Swarm_Attack]).ToString(); MultiSequence showInfoSequence = new ShowActorInfoSequence(__instance.MeleeTarget, swarmAttackText, FloatieMessage.MessageNature.Debuff, false) { RootSequenceGUID = __instance.SequenceGUID }; SharedState.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(showInfoSequence)); Mod.Log.Info?.Write(" -- published fall sequence."); } }
static void Prefix(MechMeleeSequence __instance) { try { Pilot pilot = __instance.owningActor.GetPilot(); if (pilot.IsJuggernaut()) { // In some rare occasions DistMovedThisRound is smaller than MaxWalkDistance BUT unit is marked as sprinting via CostLeft from Pathing. // Relying on the mark set at [Pathing_UpdateMeleePath_POSTFIX], DistMovedThisRound is only logged as a reference here. Logger.Debug("[MechMeleeSequence_OnMoveComplete_PREFIX] maxWalkDistance: " + __instance.OwningMech.MaxWalkDistance); Logger.Debug("[MechMeleeSequence_OnMoveComplete_PREFIX] distMovedThisRound: " + __instance.OwningMech.DistMovedThisRound); Logger.Debug("[MechMeleeSequence_OnMoveComplete_PREFIX] SprintedLastRound: " + __instance.owningActor.SprintedLastRound); Logger.Debug("[MechMeleeSequence_ExecuteMove_PREFIX] Fields.JuggernautCharges: " + Fields.JuggernautCharges); // Juggernauts only gain GUARDED on regular melee attack... if (!__instance.owningActor.SprintedLastRound) { Logger.Debug("[MechMeleeSequence_OnMoveComplete_PREFIX] Juggernaut only moved. Apply braced but don't further reduce instability."); __instance.owningActor.BracedLastRound = true; // Include stability reduction only when Mech remained "stationary" if (__instance.OwningMech.DistMovedThisRound < 10f) { // @ToDo: Check if this will be applied from Core already and thus will result in a doubled reduction... Logger.Debug("[MechMeleeSequence_OnMoveComplete_PREFIX] Juggernaut did not move at all. Reduce instability."); __instance.OwningMech.ApplyInstabilityReduction(StabilityChangeSource.RemainingStationary); } } // ...but not when charging else { Logger.Debug("[MechMeleeSequence_OnMoveComplete_PREFIX] Juggernaut sprinted. Should not apply instability reduction."); } } } catch (Exception e) { Logger.Error(e); } }
public static void Postfix(MechMeleeSequence __instance) { try { Logger.Debug($"[MechMeleeSequence_OnAdded_POSTFIX] Focus on melee target..."); CombatGameState ___combatGameState = (CombatGameState)AccessTools.Property(typeof(MechMeleeSequence), "Combat").GetValue(__instance, null); if ( __instance.owningActor.TeamId == ___combatGameState.LocalPlayerTeamGuid || __instance.MeleeTarget.team.GUID == ___combatGameState.LocalPlayerTeamGuid || (___combatGameState.HostilityMatrix.IsLocalPlayerFriendly(__instance.owningActor.TeamId) || ___combatGameState.HostilityMatrix.IsLocalPlayerFriendly(__instance.MeleeTarget.team.GUID)) || ___combatGameState.LocalPlayerTeam.CanDetectPosition(__instance.MeleeTarget.CurrentPosition, __instance.owningActor) ) { CameraControl.Instance.SetMovingToGroundPos(__instance.MeleeTarget.CurrentPosition, 0.95f); } } catch (Exception e) { Logger.Error(e); } }
static void Prefix(MechMeleeSequence __instance) { if (!MechMeleeSequence_ctor.isValid) { Mod.MeleeLog.Info?.Write($" -- Invalid sequence in OnAdded, skipping!"); Traverse meleeTargetT = Traverse.Create(__instance).Property("MeleeTarget"); meleeTargetT.SetValue(null); // This apparently throws NREs in some cases... so skip //Traverse setStateT = Traverse.Create(__instance).Method("setState"); //setStateT.GetValue(new object[] { FakeMeleeSequenceState.Finished }); Traverse ordersAreCompleteT = Traverse.Create(__instance).Property("OrdersAreComplete"); ordersAreCompleteT.SetValue(true); return; } Mod.MeleeLog.Info?.Write($"MeleeSequence added for " + $"attacker: {__instance.OwningMech.DistinctId()} from pos: {__instance.DesiredMeleePosition}" + $"against target: {__instance.MeleeTarget.DistinctId()} at pos: {__instance.MeleeTarget.CurrentPosition}"); }
static void Prefix(MechMeleeSequence __instance) { if (__instance == null || __instance.OwningMech == null || __instance.OwningMech.GetPilot() == null) { return; // Nothing to do } List <Ability> passives = __instance.OwningMech.GetPilot().PassiveAbilities; if (passives.Count > 0) { foreach (Ability ability in passives) { if (ability.Def.Description.Id.Equals(Mod.Config.Abilities.JuggernautId, StringComparison.InvariantCultureIgnoreCase)) { Mod.Log.Info?.Write("Pilot has Juggernaut, bracing after melee attack."); __instance.OwningMech.BracedLastRound = true; __instance.OwningMech.ApplyInstabilityReduction(StabilityChangeSource.Bracing); } } } return; }
static void Postfix(MechMeleeSequence __instance, ref ActorMovementSequence ___moveSequence) { try { Pilot pilot = __instance.owningActor.GetPilot(); if (pilot.IsJuggernaut() && Fields.JuggernautCharges) { Logger.Debug("[MechMeleeSequence_GenerateMeleePath_POSTFIX] Fields.JuggernautCharges: " + Fields.JuggernautCharges); // Setting this prevents the footstep effects from Coils to be displayed when a Juggernauts charges new Traverse(___moveSequence).Property("isSprinting").SetValue(true); ___moveSequence.IgnoreEndSmoothing = true; ___moveSequence.meleeType = MeleeAttackType.Charge; Logger.Info("[MechMeleeSequence_GenerateMeleePath_POSTFIX] moveSequence.isSprinting: " + ___moveSequence.isSprinting); Logger.Info("[MechMeleeSequence_GenerateMeleePath_POSTFIX] moveSequence.IgnoreEndSmoothing: " + ___moveSequence.IgnoreEndSmoothing); Logger.Info("[MechMeleeSequence_GenerateMeleePath_POSTFIX] moveSequence.meleeType: " + ___moveSequence.meleeType); } } catch (Exception e) { Logger.Error(e); } }
static void Prefix(MechMeleeSequence __instance, List <Weapon> ___requestedWeapons) { // TODO: If this happens before the above... need to grab the selected melee type from state Mod.MeleeLog.Info?.Write($"Setting current melee type to: {__instance.selectedMeleeType} and weapon to: {__instance.OwningMech.MeleeWeapon.UIName}"); (MeleeAttack meleeAttack, Weapon fakeWeapon)seqState = ModState.GetMeleeSequenceState(__instance.SequenceGUID); if (seqState.meleeAttack != null) { // Modify the owning mech melee weapon to do the 'first' hit - but apply stab damage later float targetDamage = seqState.meleeAttack.TargetDamageClusters?.Length > 0 ? seqState.meleeAttack.TargetDamageClusters[0] : 0; __instance.OwningMech.MeleeWeapon.StatCollection.Set <float>(ModStats.HBS_Weapon_DamagePerShot, targetDamage); __instance.OwningMech.MeleeWeapon.StatCollection.Set <float>(ModStats.HBS_Weapon_Instability, 0); Mod.MeleeLog.Info?.Write($"For {CombatantUtils.Label(__instance.OwningMech)} set melee weapon damage: {targetDamage} and instability: {seqState.meleeAttack.TargetInstability}"); // Make sure we use the targets's damage table ModState.ForceDamageTable = seqState.meleeAttack.TargetTable; // Filter any weapons from requested weapons. This works because BuildMeleeDirectorSequence is called immediately before BuildWeaponDirectorSequence if (Mod.Config.Melee.FilterCanUseInMeleeWeaponsByAttack) { Mod.MeleeLog.Debug?.Write($"Filtering melee weapons by attack type: {seqState.meleeAttack.Label}"); List <Weapon> allowedWeapons = new List <Weapon>(); foreach (Weapon weapon in ___requestedWeapons) { if (seqState.meleeAttack.IsRangedWeaponAllowed(weapon)) { Mod.MeleeLog.Debug?.Write($" -- Weapon: {weapon.UIName} is allowed by melee type."); allowedWeapons.Add(weapon); } } ___requestedWeapons.Clear(); ___requestedWeapons.AddRange(allowedWeapons); } } }
static void Prefix(MechMeleeSequence __instance) { try { Pilot pilot = __instance.owningActor.GetPilot(); if (pilot.IsJuggernaut() && Fields.JuggernautCharges) { Logger.Debug("[MechMeleeSequence_ExecuteMove_PREFIX] Fields.JuggernautCharges: " + Fields.JuggernautCharges); // This is to handle instability reduction correctly. If a juggernaut is charging it will be handled as a sprint. // This should also sanitize (that is: disable) the damage multiplier for Coil-S __instance.OwningMech.SprintedLastRound = true; Logger.Debug("[MechMeleeSequence_ExecuteMove_PREFIX] OwningMech.SprintedLastRound: " + __instance.OwningMech.SprintedLastRound); // Push message out AbstractActor actor = __instance.owningActor; actor.Combat.MessageCenter.PublishMessage(new FloatieMessage(actor.GUID, actor.GUID, "CHARGING", FloatieMessage.MessageNature.Buff)); } } catch (Exception e) { Logger.Error(e); } }
public static void Postfix(MechMeleeSequence __instance) { TurnDamageTracker.hintAttackComplete("MechMeleeSequence:CompleteOrders"); }
static void Prefix(MechMeleeSequence __instance) { Mod.MeleeLog.Info?.Write($"DFASequence added for attacker: {__instance.OwningMech.DistinctId()} from position: {__instance.DesiredMeleePosition} " + $"against target: {__instance.MeleeTarget}"); }
static void Postfix(MechMeleeSequence __instance, MessageCenterMessage message) { try { AttackCompleteMessage attackCompleteMessage = (AttackCompleteMessage)message; if (attackCompleteMessage.attackSequence.attackCompletelyMissed) { Logger.Debug("[MechMeleeSequence_OnMeleeComplete_POSTFIX] Missed! Aborting..."); return; } Pilot pilot = __instance.owningActor.GetPilot(); if (pilot.IsJuggernaut()) { // Get melee target ICombatant MeleeTarget = (ICombatant)AccessTools.Property(typeof(MechMeleeSequence), "MeleeTarget").GetValue(__instance, null); // Reapplying "MeleeHitPushBackPhases" here as it doesn't seem to work anymore when only defined in AbilityDef (MeleeTarget as AbstractActor).ForceUnitOnePhaseDown(__instance.owningActor.GUID, __instance.SequenceGUID, false); Logger.Debug("[MechMeleeSequence_OnMeleeComplete_POSTFIX] Fields.JuggernautCharges: " + Fields.JuggernautCharges); if (Fields.JuggernautCharges) { // Charge and tackle causes slight instability //float stabilityDamageSelf = __instance.OwningMech.GetMinStability(0, -1); //__instance.OwningMech.AddAbsoluteInstability(stabilityDamageSelf, StabilityChangeSource.NotSet, __instance.owningActor.GUID); float resultingStability = __instance.OwningMech.GetMinStability(__instance.OwningMech.CurrentStability, -1); __instance.OwningMech.StatCollection.Set <float>("Stability", resultingStability); __instance.OwningMech.NeedsInstabilityCheck = true; if (MeleeTarget.IsDead || MeleeTarget.IsFlaggedForDeath) { return; } if (MeleeTarget is Mech TargetMech) { // Remove Entrenched from target when charging if (TargetMech.IsEntrenched) { Logger.Debug("[MechMeleeSequence_OnMeleeComplete_POSTFIX] Removing Entrenched from target"); TargetMech.IsEntrenched = false; TargetMech.Combat.MessageCenter.PublishMessage(new FloatieMessage(TargetMech.GUID, TargetMech.GUID, "LOST: ENTRENCHED", FloatieMessage.MessageNature.Debuff)); } /* * // IMPORTANT! At this point any stab dmg is already applied to the target, normalized by entrenched or terrain... * // Additional stability damage depending on distance? * float additionalStabilityDamage = Utilities.GetAdditionalStabilityDamageFromSprintDistance(__instance.OwningMech, TargetMech, false); * * // Using the attacker from the sequence is more reliable than __instance.OwningMech? * //Mech AttackingMech = attackCompleteMessage.attackSequence.attacker as Mech; * //float additionalStabilityDamage = Utilities.GetAdditionalStabilityDamageFromSprintDistance(AttackingMech, TargetMech, false); * * Logger.Debug("[MechMeleeSequence_OnMeleeComplete_POSTFIX] Apply additional stability damage from distance sprinted: " + additionalStabilityDamage); * * TargetMech.AddAbsoluteInstability(additionalStabilityDamage, StabilityChangeSource.NotSet, __instance.owningActor.GUID); * Logger.Debug("[MechMeleeSequence_OnMeleeComplete_POSTFIX] MeleeTarget.CurrentStability: " + TargetMech.CurrentStability); */ } } } } catch (Exception e) { Logger.Error(e); } }
public static void Prefix(MechMeleeSequence __instance) { // Reset flag for weapon sequence DontShootTheDead.BuildSupportWeaponSequence = false; }
static void Prefix(MechMeleeSequence __instance, MessageCenterMessage message, AttackStackSequence ___meleeSequence) { Mod.MeleeLog.Trace?.Write("MMS:OMC entered."); AttackCompleteMessage attackCompleteMessage = message as AttackCompleteMessage; Mod.MeleeLog.Info?.Write($"== Resolving cluster damage, instability, and unsteady on melee attacker: {CombatantUtils.Label(__instance.OwningMech)} and target: {CombatantUtils.Label(__instance.MeleeTarget)}."); (MeleeAttack meleeAttack, Weapon fakeWeapon)seqState = ModState.GetMeleeSequenceState(__instance.SequenceGUID); if (attackCompleteMessage.stackItemUID == ___meleeSequence.SequenceGUID && seqState.meleeAttack != null) { // Check to see if the target was hit bool targetWasHit = false; foreach (AttackDirector.AttackSequence attackSequence in ___meleeSequence.directorSequences) { if (!attackSequence.attackCompletelyMissed) { targetWasHit = true; Mod.MeleeLog.Info?.Write($" -- AttackSequence: {attackSequence.stackItemUID} hit the target."); } else { Mod.MeleeLog.Info?.Write($" -- AttackSequence: {attackSequence.stackItemUID} missed the target."); } } if (__instance.OwningMech.isHasStability() && !__instance.OwningMech.IsOrWillBeProne) { // Target stability and unsteady - always applies as we're always a mech if ((targetWasHit && seqState.meleeAttack.UnsteadyAttackerOnHit) || (!targetWasHit && seqState.meleeAttack.UnsteadyAttackerOnMiss)) { Mod.MeleeLog.Info?.Write(" -- Forcing attacker to become unsteady from attack!"); __instance.OwningMech.DumpEvasion(); } } // Attacker cluster damage if (targetWasHit && !__instance.OwningMech.IsDead) { // Make sure we use the attackers's damage table ModState.ForceDamageTable = seqState.meleeAttack.AttackerTable; if (seqState.meleeAttack.AttackerDamageClusters.Length > 0) { try { Mod.MeleeLog.Info?.Write($" -- Applying {seqState.meleeAttack.AttackerDamageClusters.Sum()} damage to attacker as {seqState.meleeAttack.AttackerDamageClusters.Length} clusters."); AttackHelper.CreateImaginaryAttack(__instance.OwningMech, seqState.fakeWeapon, __instance.OwningMech, __instance.SequenceGUID, seqState.meleeAttack.AttackerDamageClusters, DamageType.Melee, seqState.meleeAttack.AttackAnimation); } catch (Exception e) { Mod.Log.Error?.Write(e, "FAILED TO APPLY MELEE DAMAGE TO ATTACKER!"); } } } if (targetWasHit) { // Target mech instability and unsteady if (__instance.MeleeTarget is Mech targetMech && targetMech.isHasStability() && !targetMech.IsProne) { if (seqState.meleeAttack.TargetInstability != 0) { Mod.MeleeLog.Info?.Write($" -- Adding {seqState.meleeAttack.TargetInstability} absolute instability to target."); targetMech.AddAbsoluteInstability(seqState.meleeAttack.TargetInstability, StabilityChangeSource.Attack, "-1"); } if (seqState.meleeAttack.OnTargetMechHitForceUnsteady) { Mod.MeleeLog.Info?.Write(" -- Forcing target to become unsteady from attack!"); targetMech.DumpEvasion(); } } // Target vehicle evasion damage if (__instance.MeleeTarget is Vehicle || __instance.MeleeTarget.FakeVehicle() || __instance.MeleeTarget.NavalUnit()) { AbstractActor targetActor = __instance.MeleeTarget as AbstractActor; if (seqState.meleeAttack.OnTargetVehicleHitEvasionPipsRemoved != 0 && targetActor.EvasivePipsCurrent > 0) { Mod.MeleeLog.Info?.Write($" -- Removing {seqState.meleeAttack.OnTargetVehicleHitEvasionPipsRemoved} from target vehicle."); int modifiedPips = targetActor.EvasivePipsCurrent - seqState.meleeAttack.OnTargetVehicleHitEvasionPipsRemoved; if (modifiedPips < 0) { modifiedPips = 0; } targetActor.EvasivePipsCurrent = modifiedPips; SharedState.Combat.MessageCenter.PublishMessage(new EvasiveChangedMessage(targetActor.GUID, targetActor.EvasivePipsCurrent)); } } // Target cluster damage if (seqState.meleeAttack.TargetDamageClusters.Length > 1 && !__instance.MeleeTarget.IsDead) { try { // Make sure we use the targets's damage table ModState.ForceDamageTable = seqState.meleeAttack.TargetTable; // The target already got hit by the first cluster as the weapon damage. Only add the additional hits float[] clusterDamage = seqState.meleeAttack.TargetDamageClusters.SubArray(1, seqState.meleeAttack.TargetDamageClusters.Length); Mod.MeleeLog.Info?.Write($" -- Applying {clusterDamage.Sum()} damage to target as {clusterDamage.Length} clusters."); AttackHelper.CreateImaginaryAttack(__instance.OwningMech, seqState.fakeWeapon, __instance.MeleeTarget, __instance.SequenceGUID, clusterDamage, DamageType.Melee, seqState.meleeAttack.AttackAnimation); } catch (Exception e) { Mod.Log.Error?.Write(e, "FAILED TO APPLY MELEE DAMAGE TO TARGET!"); } } } Mod.MeleeLog.Info?.Write($"== Done."); } // Reset damage table for mechs only; troops need to persist through to the end if (!(__instance.OwningMech is TrooperSquad)) { Mod.MeleeLog.Info?.Write($" -- resetting damage table so mech weapons fire will randomize normally"); ModState.ForceDamageTable = DamageTable.NONE; } }
public static void Postfix(MechMeleeSequence __instance, MessageCenterMessage message) { AttackCompleteMessage attackCompleteMessage = message as AttackCompleteMessage; if (__instance.selectedMeleeType == MeleeAttackType.Kick || __instance.selectedMeleeType == MeleeAttackType.Stomp) { if (attackCompleteMessage.attackSequence.attackCompletelyMissed) { Mod.Log.Debug($" Kick attack by {CombatantUtils.Label(__instance.OwningMech)} vs. {CombatantUtils.Label(__instance.MeleeTarget)} failed."); // Check for source falling float sourceMulti = __instance.OwningMech.PilotCheckMod(Mod.Config.Melee.SkillMulti); bool sourcePassed = CheckHelper.DidCheckPassThreshold(Mod.Config.Melee.MissedKickFallChance, __instance.OwningMech, sourceMulti, ModConfig.FT_Melee_Kick); if (!sourcePassed) { Mod.Log.Info($"Source actor: {CombatantUtils.Label(__instance.OwningMech)} failed pilot check from missed kick, forcing fall."); MechHelper.AddFallingSequence(__instance.OwningMech, __instance, ModConfig.FT_Melee_Kick); } } else { Mod.Log.Debug($" Kick attack by {CombatantUtils.Label(__instance.OwningMech)} vs. {CombatantUtils.Label(__instance.MeleeTarget)} succeeded."); // Check for target falling if (__instance.MeleeTarget is Mech targetMech) { float targetMulti = targetMech.PilotCheckMod(Mod.Config.Melee.SkillMulti); bool targetPassed = CheckHelper.DidCheckPassThreshold(Mod.Config.Melee.HitByKickFallChance, targetMech, targetMulti, ModConfig.FT_Melee_Kick); if (!targetPassed) { Mod.Log.Info($"Target actor: {CombatantUtils.Label(targetMech)} failed pilot check from kick, forcing fall."); MechHelper.AddFallingSequence(targetMech, __instance, ModConfig.FT_Melee_Kick); } } else { Mod.Log.Debug($"Target actor: {CombatantUtils.Label(__instance.MeleeTarget)} is not a mech, cannot fall - skipping."); } } } if (__instance.selectedMeleeType == MeleeAttackType.Charge || __instance.selectedMeleeType == MeleeAttackType.Tackle) { if (!attackCompleteMessage.attackSequence.attackCompletelyMissed) { Mod.Log.Debug($" Charge attack by {CombatantUtils.Label(__instance.OwningMech)} vs. {CombatantUtils.Label(__instance.MeleeTarget)} succeeded."); // Check for source falling float sourceSkillMulti = __instance.OwningMech.PilotCheckMod(Mod.Config.Melee.SkillMulti); bool sourcePassed = CheckHelper.DidCheckPassThreshold(Mod.Config.Melee.MadeChargeFallChance, __instance.OwningMech, sourceSkillMulti, ModConfig.FT_Melee_Charge); if (!sourcePassed) { Mod.Log.Info($"Source actor: {CombatantUtils.Label(__instance.OwningMech)} failed pilot check from charge, forcing fall."); MechHelper.AddFallingSequence(__instance.OwningMech, __instance, ModConfig.FT_Melee_Charge); } // Check for target falling if (__instance.MeleeTarget is Mech targetMech) { float targetSkillMulti = targetMech.PilotCheckMod(Mod.Config.Melee.SkillMulti); bool targetPassed = CheckHelper.DidCheckPassThreshold(Mod.Config.Melee.HitByChargeFallChance, targetMech, targetSkillMulti, ModConfig.FT_Melee_Charge); if (!targetPassed) { Mod.Log.Info($"Target actor: {CombatantUtils.Label(targetMech)} failed pilot check from charge, forcing fall."); MechHelper.AddFallingSequence(targetMech, __instance, ModConfig.FT_Melee_Charge); } } else { Mod.Log.Debug($"Target actor: {CombatantUtils.Label(__instance.MeleeTarget)} is not a mech, cannot fall - skipping."); } } else { Mod.Log.Debug($" Charge attack by {CombatantUtils.Label(__instance.OwningMech)} vs. {CombatantUtils.Label(__instance.MeleeTarget)} failed."); } } }
public static void Postfix(MechMeleeSequence __instance) { meleeHasSupportWeapons = Traverse.Create(__instance).Field("requestedWeapons").GetValue <List <Weapon> >().Count > 0; }
public static void Prefix(MechMeleeSequence __instance, ref float timeout) { // Was a hardcoded 5f timeout = 3f; Logger.Debug("[MechMeleeSequence_DelayFireWeapons_PREFIX] timeout: " + timeout); }