public static bool ShouldPanic(Mech mech, AttackDirector.AttackSequence attackSequence) { if (mech == null || attackSequence == null) { return(false); } if (mech.IsDead || (mech.IsFlaggedForDeath && mech.HasHandledDeath) || mech.team == null) { return(false); } if (!attackSequence.attackDidDamage) //no point in panicking over nothing { return(false); } if (!attackSequence.attackDamagedStructure && !attackSequence.lowArmorStruck) //no structure damage and didn't strike low armour { float totalArmor = 0, maxArmor = 0; maxArmor = GetTotalMechArmour(mech, maxArmor); totalArmor = GetCurrentMechArmour(mech, totalArmor); if ((totalArmor / maxArmor * 100) + ((BasicPanic.Settings.MinimumArmourDamagePercentageRequired * maxArmor / 100) / maxArmor * 100) >= 100) //basically if this equals to 100%, mech didn't lose enough armour { return(false); } } if (mech.team == mech.Combat.LocalPlayerTeam && !BasicPanic.Settings.PlayerTeamCanPanic) { return(false); } else if (mech.team != mech.Combat.LocalPlayerTeam && !BasicPanic.Settings.EnemiesCanPanic) { return(false); } int PanicRoll = 0; Pilot pilot = mech.GetPilot(); var weapons = mech.Weapons; var guts = mech.SkillGuts; var tactics = mech.SkillTactics; var total = guts + tactics; int index = -1; index = PanicHelpers.GetTrackedPilotIndex(mech); float lowestRemaining = mech.CenterTorsoStructure + mech.CenterTorsoFrontArmor; float panicModifiers = 0; if (index < 0) { Holder.TrackedPilots.Add(new PanicTracker(mech)); //add a new tracker to tracked pilot, then we run it all over again; index = PanicHelpers.GetTrackedPilotIndex(mech); if (index < 0) { return(false); } } if (Holder.TrackedPilots[index].trackedMech != mech.GUID) { return(false); } if (Holder.TrackedPilots[index].trackedMech == mech.GUID && Holder.TrackedPilots[index].ChangedRecently && BasicPanic.Settings.AlwaysGatedChanges) { return(false); } // pilot health if (pilot != null) { float pilotHealthPercent = 1 - ((float)pilot.Injuries / pilot.Health); if (pilotHealthPercent < 1) { panicModifiers += BasicPanic.Settings.PilotHealthMaxModifier * (1 - pilotHealthPercent); } } if (mech.IsUnsteady) { panicModifiers += BasicPanic.Settings.UnsteadyModifier; } // Head var headHealthPercent = (mech.HeadArmor + mech.HeadStructure) / (mech.GetMaxArmor(ArmorLocation.Head) + mech.GetMaxStructure(ChassisLocations.Head)); if (headHealthPercent < 1) { panicModifiers += BasicPanic.Settings.HeadDamageMaxModifier * (1 - headHealthPercent); } // CT var ctPercent = (mech.CenterTorsoFrontArmor + mech.CenterTorsoStructure) / (mech.GetMaxArmor(ArmorLocation.CenterTorso) + mech.GetMaxStructure(ChassisLocations.CenterTorso)); if (ctPercent < 1) { panicModifiers += BasicPanic.Settings.CTDamageMaxModifier * (1 - ctPercent); lowestRemaining = Math.Min(mech.CenterTorsoStructure, lowestRemaining); } // side torsos var ltStructurePercent = mech.LeftTorsoStructure / mech.GetMaxStructure(ChassisLocations.LeftTorso); if (ltStructurePercent < 1) { panicModifiers += BasicPanic.Settings.SideTorsoInternalDamageMaxModifier * (1 - ltStructurePercent); } var rtStructurePercent = mech.RightTorsoStructure / mech.GetMaxStructure(ChassisLocations.RightTorso); if (rtStructurePercent < 1) { panicModifiers += BasicPanic.Settings.SideTorsoInternalDamageMaxModifier * (1 - rtStructurePercent); } // legs if (mech.RightLegDamageLevel == LocationDamageLevel.Destroyed || mech.LeftLegDamageLevel == LocationDamageLevel.Destroyed) { float legPercent; if (mech.LeftLegDamageLevel == LocationDamageLevel.Destroyed) { legPercent = (mech.RightLegStructure + mech.RightLegArmor) / (mech.GetMaxStructure(ChassisLocations.RightLeg) + mech.GetMaxArmor(ArmorLocation.RightLeg)); } else { legPercent = (mech.LeftLegStructure + mech.LeftLegArmor) / (mech.GetMaxStructure(ChassisLocations.LeftLeg) + mech.GetMaxArmor(ArmorLocation.LeftLeg)); } if (legPercent < 1) { lowestRemaining = Math.Min(legPercent * (mech.GetMaxStructure(ChassisLocations.LeftLeg) + mech.GetMaxArmor(ArmorLocation.LeftLeg)), lowestRemaining); panicModifiers += BasicPanic.Settings.LeggedMaxModifier * (1 - legPercent); } } // next shot could kill if (lowestRemaining <= attackSequence.cumulativeDamage) { panicModifiers += BasicPanic.Settings.NextShotLikeThatCouldKill; } // weaponless if (weapons.TrueForAll(w => w.DamageLevel == ComponentDamageLevel.Destroyed || w.DamageLevel == ComponentDamageLevel.NonFunctional)) { panicModifiers += BasicPanic.Settings.WeaponlessModifier; } // alone if (mech.Combat.GetAllAlliesOf(mech).TrueForAll(m => m.IsDead || m == mech as AbstractActor)) { panicModifiers += BasicPanic.Settings.AloneModifier; } //straight up add guts, tactics, and morale to this as negative values panicModifiers -= total; if (mech.team == mech.Combat.LocalPlayerTeam) { MoraleConstantsDef moraleDef = mech.Combat.Constants.GetActiveMoraleDef(mech.Combat); panicModifiers -= Math.Max(mech.Combat.LocalPlayerTeam.Morale - moraleDef.CanUseInspireLevel, 0) / (float)2; } //reduce modifiers by 5 to account change to D20 roll instead of D100 roll, then min it t0 20 or modified floor panicModifiers /= 5; PanicRoll = PanicRoll + (int)panicModifiers; if ((total >= 20 || PanicRoll <= 0) && !BasicPanic.Settings.AtLeastOneChanceToPanic) { return(false); } PanicRoll = Math.Min(PanicRoll, 20); if (PanicRoll < 0) { PanicRoll = 0; //make this have some kind of chance to happen } PanicRoll = UnityEngine.Random.Range(PanicRoll, 20); // actual roll //we get this far, we reduce total to under the max panic chance total = Math.Min(total, (int)BasicPanic.Settings.MaxPanicResistTotal); int rngRoll = UnityEngine.Random.Range(total, 20); if (rngRoll <= PanicRoll) { ApplyPanicDebuff(mech, index); return(true); } mech.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(new ShowActorInfoSequence(mech, $"Resisted Morale Check!", FloatieMessage.MessageNature.Buff, true))); return(false); }
public static bool RollForEjectionResult(Mech mech, AttackDirector.AttackSequence attackSequence, bool IsEarlyPanic) { if (mech == null || mech.IsDead || (mech.IsFlaggedForDeath && !mech.HasHandledDeath)) { return(false); } // knocked down mechs cannot eject if (mech.IsProne && Settings.KnockedDownCannotEject) { return(false); } // have to do damage if (!attackSequence.attackDidDamage) { return(false); } Pilot pilot = mech.GetPilot(); var weapons = mech.Weapons; var guts = mech.SkillGuts; var tactics = mech.SkillTactics; var total = guts + tactics; float lowestRemaining = mech.CenterTorsoStructure + mech.CenterTorsoFrontArmor; float ejectModifiers = 0; // guts 10 makes you immune, player character cannot be forced to eject if ((guts >= 10 && Settings.GutsTenAlwaysResists) || (pilot != null && pilot.IsPlayerCharacter && Settings.PlayerCharacterAlwaysResists)) { return(false); } // tactics 10 makes you immune, or combination of guts and tactics makes you immune. if ((tactics >= 10 && Settings.TacticsTenAlwaysResists) || (total >= 10 && Settings.ComboTenAlwaysResists)) { return(false); } // pilots that cannot eject or be headshot shouldn't eject if (!mech.CanBeHeadShot || (pilot != null && !pilot.CanEject)) { return(false); } // pilot health if (pilot != null) { float pilotHealthPercent = 1 - ((float)pilot.Injuries / pilot.Health); if (pilotHealthPercent < 1) { ejectModifiers += Settings.PilotHealthMaxModifier * (1 - pilotHealthPercent); } } if (mech.IsUnsteady) { ejectModifiers += Settings.UnsteadyModifier; } // Head var headHealthPercent = (mech.HeadArmor + mech.HeadStructure) / (mech.GetMaxArmor(ArmorLocation.Head) + mech.GetMaxStructure(ChassisLocations.Head)); if (headHealthPercent < 1) { ejectModifiers += Settings.HeadDamageMaxModifier * (1 - headHealthPercent); } // CT var ctPercent = (mech.CenterTorsoFrontArmor + mech.CenterTorsoStructure) / (mech.GetMaxArmor(ArmorLocation.CenterTorso) + mech.GetMaxStructure(ChassisLocations.CenterTorso)); if (ctPercent < 1) { ejectModifiers += Settings.CTDamageMaxModifier * (1 - ctPercent); lowestRemaining = Math.Min(mech.CenterTorsoStructure, lowestRemaining); } // side torsos var ltStructurePercent = mech.LeftTorsoStructure / mech.GetMaxStructure(ChassisLocations.LeftTorso); if (ltStructurePercent < 1) { ejectModifiers += Settings.SideTorsoInternalDamageMaxModifier * (1 - ltStructurePercent); } var rtStructurePercent = mech.RightTorsoStructure / mech.GetMaxStructure(ChassisLocations.RightTorso); if (rtStructurePercent < 1) { ejectModifiers += Settings.SideTorsoInternalDamageMaxModifier * (1 - rtStructurePercent); } // legs if (mech.RightLegDamageLevel == LocationDamageLevel.Destroyed || mech.LeftLegDamageLevel == LocationDamageLevel.Destroyed) { float legPercent; if (mech.LeftLegDamageLevel == LocationDamageLevel.Destroyed) { legPercent = (mech.RightLegStructure + mech.RightLegArmor) / (mech.GetMaxStructure(ChassisLocations.RightLeg) + mech.GetMaxArmor(ArmorLocation.RightLeg)); } else { legPercent = (mech.LeftLegStructure + mech.LeftLegArmor) / (mech.GetMaxStructure(ChassisLocations.LeftLeg) + mech.GetMaxArmor(ArmorLocation.LeftLeg)); } if (legPercent < 1) { lowestRemaining = Math.Min(legPercent * (mech.GetMaxStructure(ChassisLocations.LeftLeg) + mech.GetMaxArmor(ArmorLocation.LeftLeg)), lowestRemaining); ejectModifiers += Settings.LeggedMaxModifier * (1 - legPercent); } } // next shot could kill if (lowestRemaining <= attackSequence.cumulativeDamage) { ejectModifiers += Settings.NextShotLikeThatCouldKill; } // weaponless if (weapons.TrueForAll(w => w.DamageLevel == ComponentDamageLevel.Destroyed || w.DamageLevel == ComponentDamageLevel.NonFunctional)) { ejectModifiers += Settings.WeaponlessModifier; } // alone if (mech.Combat.GetAllAlliesOf(mech).TrueForAll(m => m.IsDead || m == mech as AbstractActor)) { ejectModifiers += Settings.AloneModifier; } var modifiers = (ejectModifiers - Settings.BaseEjectionResist - (Settings.GutsEjectionResistPerPoint * guts) - (Settings.TacticsEjectionResistPerPoint * tactics)) * 5; if (mech.team == mech.Combat.LocalPlayerTeam) { MoraleConstantsDef moraleDef = mech.Combat.Constants.GetActiveMoraleDef(mech.Combat); modifiers -= Math.Max(mech.Combat.LocalPlayerTeam.Morale - moraleDef.CanUseInspireLevel, 0); } if (modifiers < 0) { return(false); } var rng = (new System.Random()).Next(100); float rollToBeat; if (!IsEarlyPanic) { rollToBeat = Math.Min(modifiers, Settings.MaxEjectChance); } else { rollToBeat = Math.Min(modifiers, Settings.MaxEjectChanceWhenEarly); } mech.Combat.MessageCenter.PublishMessage(!(rng < rollToBeat) ? new AddSequenceToStackMessage(new ShowActorInfoSequence(mech, $"Guts/Tactics Check Passed {Math.Floor(rollToBeat)}%", FloatieMessage.MessageNature.Buff, true)) : new AddSequenceToStackMessage(new ShowActorInfoSequence(mech, $"Punchin' Out! {Math.Floor(rollToBeat)}%", FloatieMessage.MessageNature.Debuff, true))); return(rng < rollToBeat); }
public static bool Prefix(AttackDirector.AttackSequence __instance, ref WeaponHitInfo hitInfo, int groupIdx, int weaponIdx, Weapon weapon, ref float toHitChance, float prevDodgedDamage) { try { if (weapon.Type == WeaponType.SRM && weapon.AmmoCategoryValue.Name == "SRMStreak") { Logger.Debug($"[AttackDirector.AttackSequence_GetIndividualHits_PREFIX] ---"); Logger.Debug($"[AttackSequence_GetIndividualHits_PREFIX] ({weapon.parent.DisplayName}) PREPARE AttackSequence: {__instance.id}, WeaponGroup: {groupIdx}, Weapon: {weapon.Name}({weaponIdx})"); Logger.Info($"[AttackDirector.AttackSequence_GetIndividualHits_PREFIX] ({weapon.Name}) toHitChance: {toHitChance}"); float hitChance = __instance.Director.Combat.ToHit.GetToHitChance(__instance.attacker, weapon, __instance.chosenTarget, __instance.attackPosition, __instance.chosenTarget.CurrentPosition, __instance.numTargets, __instance.meleeAttackType, __instance.isMoraleAttack); Logger.Info($"[AttackDirector.AttackSequence_OnAttackSequenceFire_PREFIX] ({weapon.Name}) hitChance: {hitChance}"); float hitRoll = UnityEngine.Random.Range(0f, 1f); Logger.Info($"[AttackDirector.AttackSequence_OnAttackSequenceFire_PREFIX] ({weapon.Name}) hitRoll: {hitRoll}"); bool streakWillHit = hitRoll <= hitChance; Logger.Debug($"[AttackDirector.AttackSequence_OnAttackSequenceFire_PREFIX] ({weapon.Name}) streakWillHit: {streakWillHit}"); if (streakWillHit) { toHitChance = 1f; } else { toHitChance = 0f; } Logger.Info($"[AttackDirector.AttackSequence_GetIndividualHits_PREFIX] ({weapon.Name}) toHitChance: {toHitChance}"); // Redirecting to hit determination method for clustered hits (LRMs only by default) AttackSequenceGetClusteredHits.Invoke(__instance, new object[] { hitInfo, groupIdx, weaponIdx, weapon, toHitChance, prevDodgedDamage }); Logger.Info($"[AttackDirector.AttackSequence_GetIndividualHits_PREFIX] ({weapon.Name}) Fetched clustered hits..."); if (streakWillHit) { // Make absolutely sure ALL missiles hits (Needed because of roll correction in AttackDirector.AttackSequence.GetClusteredHits() which sometimes returns "corrected" rolls of > 1f) // With the added Patch to AttackDirector.AttackSequence.GetCorrectedRoll() this should NEVER be called for (int i = 0; i < hitInfo.numberOfShots; i++) { // 0 = no hit, 65536 = secondary target hit if (hitInfo.hitLocations[i] == 0 || hitInfo.hitLocations[i] == 65536) { Logger.Debug($"[AttackDirector.AttackSequence_GetIndividualHits_PREFIX] WARNING: Missile[{i}] had a hit location of 0|65536 even though the Streak should hit. Recalculating!"); hitInfo.hitLocations[i] = __instance.chosenTarget.GetHitLocation(__instance.attacker, __instance.attackPosition, hitInfo.locationRolls[i], __instance.calledShotLocation, __instance.attacker.CalledShotBonusMultiplier); hitInfo.hitPositions[i] = __instance.chosenTarget.GetImpactPosition(__instance.attacker, __instance.attackPosition, weapon, ref hitInfo.hitLocations[i], ref hitInfo.attackDirections[i], ref hitInfo.secondaryTargetIds[i], ref hitInfo.secondaryHitLocations[i]); } } } else { // Make absolutely sure NO (potential) missile would hit ANYTHING (They won't be fired anyway, but they NEED a valid hitInfo) for (int i = 0; i < hitInfo.numberOfShots; i++) { if (hitInfo.hitLocations[i] != 0) { Logger.Debug($"[AttackDirector.AttackSequence_GetIndividualHits_PREFIX] WARNING: Missile[{i}] had a hit location != 0 even though the Streak should miss. Setting (imaginary) missVector manually!"); hitInfo.hitLocations[i] = 0; Vector3 missVector = __instance.attackPosition + __instance.attacker.HighestLOSPosition; Vector3 shotVector = __instance.chosenTarget.GameRep.GetMissPosition(missVector, weapon, __instance.Director.Combat.NetworkRandom); Vector3 normalized = (shotVector - missVector).normalized; shotVector = missVector + normalized * 500f; hitInfo.hitPositions[i] = shotVector; } } } Utilities.LogHitLocations(hitInfo); // SAFEGUARD: Clean hitInfo from potentially set secondary targets, there AREN'T secondary targets for Streaks hitInfo.secondaryTargetIds = new string[hitInfo.numberOfShots]; // Hit locations are set, skipping original method... return(false); } else { return(true); } } catch (Exception e) { Logger.Error(e); return(true); } }
public static bool Prefix(AttackDirector.AttackSequence __instance, MessageCenterMessage message) { CustomAmmoCategoriesLog.Log.LogWrite("AttackDirector.AttackSequence.OnAttackSequenceResolveDamage"); try { AttackSequenceResolveDamageMessage resolveDamageMessage = (AttackSequenceResolveDamageMessage)message; WeaponHitInfo hitInfo = resolveDamageMessage.hitInfo; if (hitInfo.attackSequenceId != __instance.id) { return(true); } ; MessageCoordinator messageCoordinator = (MessageCoordinator)typeof(AttackDirector.AttackSequence).GetField("messageCoordinator", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance); if (!messageCoordinator.CanProcessMessage(resolveDamageMessage)) { messageCoordinator.StoreMessage((MessageCenterMessage)resolveDamageMessage); } else { if (AttackDirector.AttackSequence.logger.IsLogEnabled) { AttackDirector.AttackSequence.logger.Log((object)string.Format("[OnAttackSequenceResolveDamage] ID {0}, Group {1}, Weapon {2}, AttackerId [{3}], TargetId [{4}]", (object)__instance.id, (object)hitInfo.attackGroupIndex, (object)hitInfo.attackWeaponIndex, (object)hitInfo.attackerId, (object)hitInfo.targetId)); } Weapon weapon = __instance.GetWeapon(resolveDamageMessage.hitInfo.attackGroupIndex, resolveDamageMessage.hitInfo.attackWeaponIndex); if (__instance.meleeAttackType == MeleeAttackType.DFA) { float damageAmount = __instance.attacker.StatCollection.GetValue <float>("DFASelfDamage"); __instance.attacker.TakeWeaponDamage(resolveDamageMessage.hitInfo, 64, weapon, damageAmount, 0, DamageType.DFASelf); __instance.attacker.TakeWeaponDamage(resolveDamageMessage.hitInfo, 128, weapon, damageAmount, 0, DamageType.DFASelf); if (AttackDirector.damageLogger.IsLogEnabled) { AttackDirector.damageLogger.Log((object)string.Format("@@@@@@@@ {0} takes {1} damage to its legs from the DFA attack!", (object)__instance.attacker.DisplayName, (object)damageAmount)); } } __instance.target.ResolveWeaponDamage(resolveDamageMessage.hitInfo); AbstractActor target = __instance.target as AbstractActor; int attackIndex = -1; int num1 = 0; int num2 = 65536; for (int index = 0; index < resolveDamageMessage.hitInfo.hitLocations.Length; ++index) { int hitLocation = resolveDamageMessage.hitInfo.hitLocations[index]; if (hitLocation != num1 && hitLocation != num2 && attackIndex == -1) { attackIndex = index; } } if (attackIndex > -1) { //typeof(AttackDirector.AttackSequence).GetProperty("attackCompletelyMissed", BindingFlags.NonPublic).SetValue(__instance, (object)false); PropertyInfo property = typeof(AttackDirector.AttackSequence).GetProperty("attackCompletelyMissed"); property.DeclaringType.GetProperty("attackCompletelyMissed"); property.GetSetMethod(true).Invoke(__instance, new object[1] { (object)false }); //__instance.attackCompletelyMissed = false; } if (attackIndex > -1 && !__instance.target.IsDead && target != null) { foreach (EffectData statusEffect in CustomAmmoCategories.getWeaponStatusEffects(weapon)) { if (statusEffect.targetingData.effectTriggerType == EffectTriggerType.OnHit) { string effectID = string.Format("OnHitEffect_{0}_{1}", (object)__instance.attacker.GUID, (object)resolveDamageMessage.hitInfo.attackSequenceId); if (statusEffect.Description == null || statusEffect.Description.Id == null || statusEffect.Description.Name == null) { CustomAmmoCategoriesLog.Log.LogWrite($"WARNING: EffectID:{effectID} has broken effectDescId:{statusEffect?.Description.Id} effectDescName:{statusEffect?.Description.Name}! SKIPPING"); } else { CustomAmmoCategoriesLog.Log.LogWrite($"Applying effectID:{effectID} with effectDescId:{statusEffect?.Description.Id} effectDescName:{statusEffect?.Description.Name}"); __instance.Director.Combat.EffectManager.CreateEffect(statusEffect, effectID, __instance.stackItemUID, (ICombatant)__instance.attacker, __instance.target, hitInfo, attackIndex, false); if (__instance.target != null) { __instance.Director.Combat.MessageCenter.PublishMessage((MessageCenterMessage) new FloatieMessage(__instance.target.GUID, __instance.target.GUID, statusEffect.Description.Name, FloatieMessage.MessageNature.Debuff)); } } } } if (target != null) { List <EffectData> effectsForTriggerType = target.GetComponentStatusEffectsForTriggerType(EffectTriggerType.OnDamaged); for (int index = 0; index < effectsForTriggerType.Count; ++index) { __instance.Director.Combat.EffectManager.CreateEffect(effectsForTriggerType[index], string.Format("OnDamagedEffect_{0}_{1}", (object)target.GUID, (object)resolveDamageMessage.hitInfo.attackSequenceId), __instance.stackItemUID, __instance.target, (ICombatant)__instance.attacker, hitInfo, attackIndex, false); } } } __instance.attacker.HandleDeath(__instance.attacker.GUID); __instance.attacker.HandleDeath(__instance.attacker.GUID); messageCoordinator.MessageComplete((MessageCenterMessage)resolveDamageMessage); } } catch (Exception e) { CustomAmmoCategoriesLog.Log.LogWrite("Exception " + e.ToString() + "\nFallback to default"); return(true); } return(false); }
public static bool Prefix(AttackDirector.AttackSequence __instance, MessageCenterMessage message, List <List <Weapon> > ___sortedWeapons, ref int[][] ___numberOfShots, ref WeaponHitInfo?[][] ___weaponHitInfo) { try { AttackSequenceFireMessage attackSequenceFireMessage = (AttackSequenceFireMessage)message; if (attackSequenceFireMessage.sequenceId != __instance.id) { return(false); } int groupIdx = attackSequenceFireMessage.groupIdx; int weaponIdx = attackSequenceFireMessage.weaponIdx; Weapon weapon = ___sortedWeapons[groupIdx][weaponIdx]; Logger.Debug($"---"); Logger.Debug($"[AttackDirector.AttackSequence_OnAttackSequenceFire_PREFIX] ({weapon.parent.DisplayName}) STARTED AttackSequence: {__instance.id}, WeaponGroup: {groupIdx}, Weapon: {weapon.Name}({weaponIdx})"); //if(weapon.weaponDef.ComponentTags.Contains("component_type_srmstreak")) if (weapon.Type == WeaponType.SRM && weapon.AmmoCategoryValue.Name == "SRMStreak") { WeaponHitInfo weaponHitInfo = ___weaponHitInfo[groupIdx][weaponIdx].Value; bool streakWillHit = weaponHitInfo.DidShotHitChosenTarget(0); // If first missile hits/misses, all will hit/miss Logger.Info($"[AttackDirector.AttackSequence_OnAttackSequenceFire_PREFIX] ({weapon.Name}) streakWillHit: {streakWillHit}"); // Fire targeting laser Vector3 floatieVector = new Vector3(); Utilities.CreateAndFireStreakTargetingLaser(__instance, weapon, out floatieVector, streakWillHit); if (streakWillHit) { // Only floaties, everything else is prepared at this point // Big Floatie //__instance.Director.Combat.MessageCenter.PublishMessage(new FloatieMessage(__instance.chosenTarget.GUID, __instance.chosenTarget.GUID, "STREAK LOCKED-ON", FloatieMessage.MessageNature.CriticalHit)); // Small Floatie FloatieMessage hitFloatie = new FloatieMessage(__instance.attacker.GUID, __instance.chosenTarget.GUID, "STREAK LOCKED-ON", __instance.Director.Combat.Constants.CombatUIConstants.floatieSizeMedium, FloatieMessage.MessageNature.Suppression, floatieVector.x, floatieVector.y, floatieVector.z); __instance.Director.Combat.MessageCenter.PublishMessage(hitFloatie); return(true); } else { // Cancel firing, see code of original method... // Mark Streak SRMs as having fired nevertheless because a failed lock on should be handled like "fired" new Traverse(weapon).Property("HasFired").SetValue(true); weapon.CompleteFiring(); Logger.Info($"[AttackDirector.AttackSequence_OnAttackSequenceFire_PREFIX] ({weapon.Name}) HasFired: {weapon.HasFired}, RoundsSinceLastFire: {weapon.roundsSinceLastFire}"); // If weapon already prefired we would need to reincrement ammo (Note that Weapon.OffsetAmmo() is a custom extension method) if (weapon.HasPreFired) { weapon.OffsetAmmo(); } // Send out all necessary messages to keep the current AttackSequence in sync AttackSequenceWeaponPreFireCompleteMessage weaponPreFireCompleteMessage = new AttackSequenceWeaponPreFireCompleteMessage(__instance.stackItemUID, __instance.id, groupIdx, weaponIdx); __instance.Director.Combat.MessageCenter.PublishMessage(weaponPreFireCompleteMessage); int numberOfShots = ___numberOfShots[groupIdx][weaponIdx]; for (int j = 0; j < numberOfShots; j++) { float hitDamage = weapon.DamagePerShotAdjusted(weapon.parent.occupiedDesignMask); float structureDamage = weapon.StructureDamagePerShotAdjusted(weapon.parent.occupiedDesignMask); AttackSequenceImpactMessage impactMessage = new AttackSequenceImpactMessage(weaponHitInfo, j, hitDamage, structureDamage); __instance.Director.Combat.MessageCenter.PublishMessage(impactMessage); } AttackSequenceResolveDamageMessage resolveDamageMessage = new AttackSequenceResolveDamageMessage(weaponHitInfo); __instance.Director.Combat.MessageCenter.PublishMessage(resolveDamageMessage); AttackSequenceWeaponCompleteMessage weaponCompleteMessage = new AttackSequenceWeaponCompleteMessage(__instance.stackItemUID, __instance.id, groupIdx, weaponIdx); __instance.Director.Combat.MessageCenter.PublishMessage(weaponCompleteMessage); // Big Floaties //__instance.Director.Combat.MessageCenter.PublishMessage(new FloatieMessage(weapon.parent.GUID, weapon.parent.GUID, "STREAK LOCK-ON FAILED", FloatieMessage.MessageNature.Debuff)); //__instance.Director.Combat.MessageCenter.PublishMessage(new FloatieMessage(__instance.chosenTarget.GUID, __instance.chosenTarget.GUID, "STREAK LOCK-ON AVOIDED", FloatieMessage.MessageNature.Buff)); // Small Floatie FloatieMessage missFloatie = new FloatieMessage(__instance.attacker.GUID, __instance.chosenTarget.GUID, "STREAK LOCK-ON FAILED", __instance.Director.Combat.Constants.CombatUIConstants.floatieSizeMedium, FloatieMessage.MessageNature.Dodge, floatieVector.x, floatieVector.y, floatieVector.z); __instance.Director.Combat.MessageCenter.PublishMessage(missFloatie); // Skip original method! return(false); } } return(true); } catch (Exception e) { Logger.Error(e); return(true); } }
static void Postfix(AbstractActor __instance, string sourceID, int sequenceID, int stackItemID, AttackDirection attackDirection) { try { AttackDirector.AttackSequence attackSequence = __instance.Combat.AttackDirector.GetAttackSequence(sequenceID); if (attackSequence != null && attackSequence.GetAttackDidDamage(__instance.GUID)) { List <Effect> list = __instance.Combat.EffectManager.GetAllEffectsTargeting(__instance).FindAll((Effect x) => x.EffectData.targetingData.effectTriggerType == EffectTriggerType.OnDamaged); for (int i = 0; i < list.Count; i++) { list[i].OnEffectTakeDamage(attackSequence.attacker, __instance); } if (attackSequence.isMelee) { int value = attackSequence.attacker.StatCollection.GetValue <int>("MeleeHitPushBackPhases"); if (value > 0) { for (int j = 0; j < value; j++) { __instance.ForceUnitOnePhaseDown(sourceID, stackItemID, false); } } } } int evasivePipsCurrent = __instance.EvasivePipsCurrent; var settings = PermanentEvasion.Settings; float totalDamageReceived = 1; if (attackSequence.GetAttackDidDamage(__instance.GUID)) { totalDamageReceived += attackSequence.GetArmorDamageDealt(__instance.GUID) + attackSequence.GetStructureDamageDealt(__instance.GUID); if ((totalDamageReceived > settings.MinDamageForEvasionStrip) && settings.AllowHitStrip) { __instance.ConsumeEvasivePip(true); Fields.LoosePip = false; } else if (Fields.LoosePip) { __instance.ConsumeEvasivePip(true); Fields.LoosePip = false; } } else if (Fields.LoosePip) { __instance.ConsumeEvasivePip(true); Fields.LoosePip = false; } int evasivePipsCurrent2 = __instance.EvasivePipsCurrent; if (evasivePipsCurrent2 < evasivePipsCurrent && (totalDamageReceived > settings.MinDamageForEvasionStrip) && settings.AllowHitStrip && !__instance.IsDead && !__instance.IsFlaggedForDeath) { __instance.Combat.MessageCenter.PublishMessage(new FloatieMessage(__instance.GUID, __instance.GUID, "HIT: -1 EVASION", FloatieMessage.MessageNature.Debuff)); } else if (evasivePipsCurrent2 < evasivePipsCurrent && !__instance.IsDead && !__instance.IsFlaggedForDeath) { __instance.Combat.MessageCenter.PublishMessage(new FloatieMessage(__instance.GUID, __instance.GUID, "-1 EVASION", FloatieMessage.MessageNature.Debuff)); } else if (evasivePipsCurrent2 > 0 && Fields.KeptPip && !__instance.IsDead && !__instance.IsFlaggedForDeath) { __instance.Combat.MessageCenter.PublishMessage(new FloatieMessage(__instance.GUID, __instance.GUID, "EVASION KEPT", FloatieMessage.MessageNature.Debuff)); Fields.KeptPip = false; } } catch (Exception e) { Logger.Error(e); } }
private static void GetStreakHits(AttackDirector.AttackSequence instance, ref WeaponHitInfo hitInfo, int groupIdx, int weaponIdx, Weapon weapon, float toHitChance, float prevDodgedDamage) { CustomAmmoCategoriesLog.Log.LogWrite("GetStreakHits\n"); if (hitInfo.numberOfShots == 0) { return; } ; if (AttackDirector.hitLogger.IsLogEnabled) { AttackDirector.hitLogger.Log((object)string.Format("???????? RANDOM HIT ROLLS (GetStreakHits): Weapon Group: {0} // Weapon: {1}", (object)groupIdx, (object)weaponIdx)); } hitInfo.toHitRolls = instance.GetRandomNumbers(groupIdx, weaponIdx, hitInfo.numberOfShots); if (AttackDirector.hitLogger.IsLogEnabled) { AttackDirector.hitLogger.Log((object)string.Format("???????? RANDOM LOCATION ROLLS (GetStreakHits): Weapon Group: {0} // Weapon: {1}", (object)groupIdx, (object)weaponIdx)); } hitInfo.locationRolls = instance.GetRandomNumbers(groupIdx, weaponIdx, hitInfo.numberOfShots); if (AttackDirector.hitLogger.IsLogEnabled) { AttackDirector.hitLogger.Log((object)string.Format("???????? DODGE ROLLS (GetStreakHits): Weapon Group: {0} // Weapon: {1}", (object)groupIdx, (object)weaponIdx)); } hitInfo.dodgeRolls = instance.GetRandomNumbers(groupIdx, weaponIdx, hitInfo.numberOfShots); hitInfo.hitVariance = instance.GetVarianceSums(groupIdx, weaponIdx, hitInfo.numberOfShots, weapon); int previousHitLocation = 0; float originalMultiplier = 1f; float adjacentMultiplier = 1f; AbstractActor target = instance.target as AbstractActor; Team team = weapon == null || weapon.parent == null || weapon.parent.team == null ? (Team)null : weapon.parent.team; bool primeSucceeded = false; bool primeFlag = false; for (int hitIndex = 0; hitIndex < hitInfo.numberOfShots; ++hitIndex) { float corrRolls = (float)typeof(AttackDirector.AttackSequence).GetMethod("GetCorrectedRoll", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(instance, new object[2] { (object)hitInfo.toHitRolls[hitIndex], (object)team }); //bool succeeded = (double)instance.GetCorrectedRoll(hitInfo.toHitRolls[hitIndex], team) <= (double)toHitChance; bool succeeded = (double)corrRolls <= (double)toHitChance; if (team != null) { team.ProcessRandomRoll(toHitChance, succeeded); } bool flag = false; if (target != null) { flag = target.CheckDodge(instance.attacker, weapon, hitInfo, hitIndex, instance.IsBreachingShot); } if (hitIndex == 0) { primeSucceeded = succeeded; primeFlag = flag; CustomAmmoCategoriesLog.Log.LogWrite(" prime success:" + primeSucceeded + " dodge:" + primeFlag + "\n"); } if (primeSucceeded && primeFlag) { hitInfo.dodgeSuccesses[hitIndex] = true; instance.FlagAttackContainsDodge(); } else { hitInfo.dodgeSuccesses[hitIndex] = false; } if (primeSucceeded && !primeFlag) { if (previousHitLocation == 0) { previousHitLocation = instance.target.GetHitLocation(instance.attacker, instance.attackPosition, hitInfo.locationRolls[hitIndex], instance.calledShotLocation, instance.attacker.CalledShotBonusMultiplier); hitInfo.hitLocations[hitIndex] = previousHitLocation; CustomAmmoCategoriesLog.Log.LogWrite(" hitLocation:" + hitInfo.hitLocations[hitIndex] + "\n"); if (AttackDirector.attackLogger.IsLogEnabled) { AttackDirector.attackLogger.Log((object)string.Format("SEQ:{0}: WEAP:{1} SHOT:{2} Initial streak hit! Location: {3}", (object)instance.id, (object)weaponIdx, (object)hitIndex, (object)hitInfo.hitLocations[hitIndex])); } if (AttackDirector.hitminLogger.IsLogEnabled) { AttackDirector.hitminLogger.Log((object)string.Format("WEAPON: {0} - SHOT: {1} Hits! ////// INITIAL HIT - HEX VAL {2}", (object)weapon.Name, (object)hitIndex, (object)hitInfo.hitLocations[hitIndex])); } } else { hitInfo.hitLocations[hitIndex] = instance.target.GetAdjacentHitLocation(instance.attackPosition, hitInfo.locationRolls[hitIndex], previousHitLocation, originalMultiplier, adjacentMultiplier); CustomAmmoCategoriesLog.Log.LogWrite(" hitLocation:" + hitInfo.hitLocations[hitIndex] + "\n"); if (AttackDirector.attackLogger.IsLogEnabled) { AttackDirector.attackLogger.Log((object)string.Format("SEQ:{0}: WEAP:{1} SHOT:{2} streak hit! Location: {3}", (object)instance.id, (object)weaponIdx, (object)hitIndex, (object)hitInfo.hitLocations[hitIndex])); } if (AttackDirector.hitminLogger.IsLogEnabled) { AttackDirector.hitminLogger.Log((object)string.Format("WEAPON: {0} - SHOT: {1} Hits! ////// STREAK HIT - HEX VAL {2}", (object)weapon.Name, (object)hitIndex, (object)hitInfo.hitLocations[hitIndex])); } } hitInfo.hitQualities[hitIndex] = instance.Director.Combat.ToHit.GetBlowQuality(instance.attacker, instance.attackPosition, weapon, instance.target, instance.meleeAttackType, instance.IsBreachingShot); instance.FlagShotHit(); } else { hitInfo.hitLocations[hitIndex] = 0; CustomAmmoCategoriesLog.Log.LogWrite(" hitLocation:" + hitInfo.hitLocations[hitIndex] + "\n"); if (AttackDirector.attackLogger.IsLogEnabled) { AttackDirector.attackLogger.Log((object)string.Format("SEQ:{0}: WEAP:{1} SHOT:{2} Miss!", (object)instance.id, (object)weaponIdx, (object)hitIndex, (object)hitInfo.hitLocations[hitIndex])); } if (AttackDirector.hitminLogger.IsLogEnabled) { AttackDirector.hitminLogger.Log((object)string.Format("WEAPON: {0} - SHOT: {1} Misses!", (object)weapon.Name, (object)hitIndex)); } instance.FlagShotMissed(); } hitInfo.hitPositions[hitIndex] = instance.target.GetImpactPosition(instance.attacker, instance.attackPosition, weapon, ref hitInfo.hitLocations[hitIndex]); } }
public static bool Prefix(AttackDirector.AttackSequence __instance, Weapon weapon, int groupIdx, int weaponIdx, int numberOfShots, bool indirectFire, float dodgedDamage, ref WeaponHitInfo __result) { WeaponHitInfo hitInfo = new WeaponHitInfo(); hitInfo.attackerId = __instance.attacker.GUID; hitInfo.targetId = __instance.target.GUID; hitInfo.numberOfShots = numberOfShots; hitInfo.stackItemUID = __instance.stackItemUID; hitInfo.attackSequenceId = __instance.id; hitInfo.attackGroupIndex = groupIdx; hitInfo.attackWeaponIndex = weaponIdx; hitInfo.toHitRolls = new float[numberOfShots]; hitInfo.locationRolls = new float[numberOfShots]; hitInfo.dodgeRolls = new float[numberOfShots]; hitInfo.dodgeSuccesses = new bool[numberOfShots]; hitInfo.hitLocations = new int[numberOfShots]; hitInfo.hitPositions = new Vector3[numberOfShots]; hitInfo.hitVariance = new int[numberOfShots]; hitInfo.hitQualities = new AttackImpactQuality[numberOfShots]; if (AttackDirector.hitLogger.IsLogEnabled) { Vector3 collisionWorldPos; LineOfFireLevel lineOfFire = __instance.Director.Combat.LOS.GetLineOfFire(__instance.attacker, __instance.attackPosition, __instance.target, __instance.target.CurrentPosition, __instance.target.CurrentRotation, out collisionWorldPos); float allModifiers = __instance.Director.Combat.ToHit.GetAllModifiers(__instance.attacker, weapon, __instance.target, __instance.attackPosition + __instance.attacker.HighestLOSPosition, __instance.target.TargetPosition, lineOfFire, __instance.isMoraleAttack); string modifiersDescription = __instance.Director.Combat.ToHit.GetAllModifiersDescription(__instance.attacker, weapon, __instance.target, __instance.attackPosition + __instance.attacker.HighestLOSPosition, __instance.target.TargetPosition, lineOfFire, __instance.isMoraleAttack); Pilot pilot = __instance.attacker.GetPilot(); AttackDirector.hitLogger.Log((object)string.Format("======================================== Unit Firing: {0} | Weapon: {1} | Shots: {2}", (object)__instance.attacker.DisplayName, (object)weapon.Name, (object)numberOfShots)); AttackDirector.hitLogger.Log((object)string.Format("======================================== Hit Info: GROUP {0} | ID {1}", (object)groupIdx, (object)weaponIdx)); AttackDirector.hitLogger.Log((object)string.Format("======================================== MODIFIERS: {0}... FINAL: [[ {1} ]] ", (object)modifiersDescription, (object)allModifiers)); if (pilot != null) { AttackDirector.hitLogger.Log((object)__instance.Director.Combat.ToHit.GetBaseToHitChanceDesc(__instance.attacker)); } else { AttackDirector.hitLogger.Log((object)string.Format("======================================== Gunnery Check: NO PILOT")); } } float toHitChance = __instance.Director.Combat.ToHit.GetToHitChance(__instance.attacker, weapon, __instance.target, __instance.attackPosition, __instance.target.CurrentPosition, __instance.numTargets, __instance.meleeAttackType, __instance.isMoraleAttack); if (Mech.TEST_KNOCKDOWN) { toHitChance = 1f; } if (AttackDirector.hitLogger.IsLogEnabled) { AttackDirector.hitLogger.Log((object)string.Format("======================================== HIT CHANCE: [[ {0:P2} ]]", (object)toHitChance)); } hitInfo.attackDirection = __instance.Director.Combat.HitLocation.GetAttackDirection(__instance.attackPosition, __instance.target); hitInfo.attackDirectionVector = __instance.Director.Combat.HitLocation.GetAttackDirectionVector(__instance.attackPosition, __instance.target); object[] args = new object[6]; HitGeneratorType hitGenType = CustomAmmoCategories.getHitGenerator(weapon); switch (hitGenType) { case HitGeneratorType.Individual: args[0] = hitInfo; args[1] = groupIdx; args[2] = weaponIdx; args[3] = weapon; args[4] = toHitChance; args[5] = dodgedDamage; typeof(AttackDirector.AttackSequence).GetMethod("GetIndividualHits", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(__instance, args); hitInfo = (WeaponHitInfo)args[0]; break; case HitGeneratorType.Cluster: args[0] = hitInfo; args[1] = groupIdx; args[2] = weaponIdx; args[3] = weapon; args[4] = toHitChance; args[5] = dodgedDamage; typeof(AttackDirector.AttackSequence).GetMethod("GetClusteredHits", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(__instance, args); hitInfo = (WeaponHitInfo)args[0]; break; case HitGeneratorType.Streak: AttackSequence_GenerateHitInfo.GetStreakHits(__instance, ref hitInfo, groupIdx, weaponIdx, weapon, toHitChance, dodgedDamage); break; default: AttackDirector.attackLogger.LogError((object)string.Format("GenerateHitInfo found invalid weapon type: {0}, using basic hit info", (object)hitGenType)); args[0] = hitInfo; args[1] = groupIdx; args[2] = weaponIdx; args[3] = weapon; args[4] = toHitChance; args[5] = dodgedDamage; typeof(AttackDirector.AttackSequence).GetMethod("GetIndividualHits", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(__instance, args); hitInfo = (WeaponHitInfo)args[0]; break; } __result = hitInfo; return(false); //return hitInfo; }
public static void Postfix(AttackDirector __instance, MessageCenterMessage message) { Mod.Log.Debug?.Write("AD:OASE - entered."); // Nothing to do if (ModState.BreachAttackId == ModState.NO_ATTACK_SEQUENCE_ID) { return; } AttackSequenceEndMessage attackSequenceEndMessage = message as AttackSequenceEndMessage; if (__instance == null || attackSequenceEndMessage == null) { return; } int sequenceId = attackSequenceEndMessage.sequenceId; AttackDirector.AttackSequence attackSequence = __instance.GetAttackSequence(sequenceId); if (attackSequence == null) { Mod.Log.Info?.Write($"Could not find attack sequence with id: {sequenceId}. CAC may have killed it, or there's an error in processing. Skipping hull breach checks."); return; } // No chosen target, nothing to damage if (attackSequence.chosenTarget == null) { return; } if (ModState.BreachAttackId != attackSequence.id) { Mod.Log.Debug?.Write($"Attack sequence ID {attackSequence.id} does not match Hull Breach Attack Id - skipping hull breach resolution."); return; } float structureDamage = attackSequence.GetStructureDamageDealt(attackSequence.chosenTarget.GUID); Mod.Log.Debug?.Write($"Attack sequence {sequenceId} did {structureDamage} structure damage to target: {attackSequence.chosenTarget.GUID}"); if (structureDamage == 0f) { Mod.Log.Debug?.Write($"Attack did no structure damage, skipping."); return; } if (attackSequence.chosenTarget is Mech targetMech) { Mod.Log.Debug?.Write($"Checking hull breaches for targetMech: {CombatantUtils.Label(targetMech)}"); ResolveMechHullBreaches(targetMech); } if (attackSequence.chosenTarget is Turret targetTurret) { Mod.Log.Debug?.Write($"Checking hull breaches for targetTurret: {CombatantUtils.Label(targetTurret)}"); ResolveTurretHullBreaches(targetTurret); } if (attackSequence.chosenTarget is Vehicle targetVehicle) { Mod.Log.Debug?.Write($"Checking hull breaches for targetVehicle: {CombatantUtils.Label(targetVehicle)}"); ResolveVehicleHullBreaches(targetVehicle); } // Reset state ModState.BreachAttackId = ModState.NO_ATTACK_SEQUENCE_ID; ModState.BreachHitsMech.Clear(); ModState.BreachHitsTurret.Clear(); ModState.BreachHitsVehicle.Clear(); Mod.Log.Debug?.Write("AD:OASE - exiting."); }