public static int GetResolveValue(this AbstractActor actor) { WeightClass weightClass = actor.GetWeightClass(); MoraleConstantsDef activeMoraleDef = actor.Combat.Constants.GetActiveMoraleDef(actor.Combat); // Special case for turrets as in related code (AttackDirector.ResolveSequenceMorale) if (actor.UnitType == UnitType.Turret) { return(activeMoraleDef.ChangeEnemyDestroyedLight); } switch (weightClass) { case WeightClass.LIGHT: return(activeMoraleDef.ChangeEnemyDestroyedLight); case WeightClass.MEDIUM: return(activeMoraleDef.ChangeEnemyDestroyedMedium); case WeightClass.HEAVY: return(activeMoraleDef.ChangeEnemyDestroyedHeavy); case WeightClass.ASSAULT: return(activeMoraleDef.ChangeEnemyDestroyedAssault); default: return(0); } }
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 ShouldPanic(Mech mech, AttackDirector.AttackSequence attackSequence) { if (mech == null || mech.IsDead || (mech.IsFlaggedForDeath && mech.HasHandledDeath)) { return(false); } if (attackSequence == 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 (unclear what lowArmorStruck is) { Logger.Logline($"attackDamagedStructure {attackSequence.attackDamagedStructure}, lowArmorStruck {attackSequence.lowArmorStruck}"); float totalArmor = 0, maxArmor = 0; maxArmor = GetTotalMechArmour(mech, maxArmor); totalArmor = GetCurrentMechArmour(mech, totalArmor); float currentArmorPercent = totalArmor / maxArmor * 100; Logger.Logline($"maxArmor {maxArmor}, totalArmor {totalArmor}, currentArmorPercent { currentArmorPercent}"); var settings = BasicPanic.Settings; var percentOfCurrentArmorDamaged = attackSequence.attackArmorDamage / currentArmorPercent; float mininumDamagePerecentRequired = settings.MinimumArmourDamagePercentageRequired; if (percentOfCurrentArmorDamaged <= 10) // (deprecated) 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); }