private void SetDamageLevel(WeaponHitInfo hitInfo, ComponentDamageLevel damageLevel) { SetDamageLevel(component, hitInfo, damageLevel); if (HasLinked) { var id = LinkedScopedId(); Control.Logger.Debug?.Log($"HasLinked id={id}"); foreach (var otherMechComponent in actor.allComponents) { if (otherMechComponent.DamageLevel == ComponentDamageLevel.Destroyed) { continue; } var otherCriticals = otherMechComponent.Criticals(); if (!otherCriticals.HasLinked) { continue; } var otherId = otherCriticals.LinkedScopedId(); if (id == otherId) { SetDamageLevel(otherMechComponent, hitInfo, damageLevel); } } }
public static void LogHitLocations(WeaponHitInfo hitInfo) { try { string output = "---\n"; output += $"[Utilities_LogHitLocations] Clustered hits: {hitInfo.hitLocations.Length}\n"; for (int i = 0; i < hitInfo.hitLocations.Length; i++) { int location = hitInfo.hitLocations[i]; var chassisLocationFromArmorLocation = MechStructureRules.GetChassisLocationFromArmorLocation((ArmorLocation)location); if (location == 0 || location == 65536) { output += $"[Utilities_LogHitLocations] HitLocation {i}: NONE/INVALID({location})"; } else { output += $"[Utilities_LogHitLocations] HitLocation {i}: {chassisLocationFromArmorLocation}({location})"; } output += (i < hitInfo.hitLocations.Length - 1) ? "\n" : "\n---"; } Logger.Info(output, false); } catch (Exception e) { Logger.Error(e); } }
internal static void OverrideApplyStructureStatDamage( this Mech mech, ChassisLocations location, float damage, WeaponHitInfo hitInfo ) { try { if (IsInternalExplosion) { var properties = ComponentExplosionHandler.Shared.GetCASEProperties(currentMech, (int)location); if (properties.MaximumDamage.HasValue) { var directDamage = Mathf.Min(damage, properties.MaximumDamage.Value); var backDamage = damage - directDamage; //Control.mod.Logger.LogDebug($"reducing structure damage from {damage} to {directDamage} in {Mech.GetAbbreviatedChassisLocation(location)}"); damage = directDamage; if (backDamage > 0) { currentMech.PublishFloatieMessage("EXPLOSION REDIRECTED"); if ((location & ChassisLocations.Torso) > 0) { ArmorLocation armorLocation; switch (location) { case ChassisLocations.LeftTorso: armorLocation = ArmorLocation.LeftTorsoRear; break; case ChassisLocations.RightTorso: armorLocation = ArmorLocation.RightTorsoRear; break; default: armorLocation = ArmorLocation.CenterTorsoRear; break; } var armor = mech.GetCurrentArmor(armorLocation); if (armor > 0) { var armorDamage = Mathf.Min(backDamage, armor); //Control.mod.Logger.LogDebug($"added blowout armor damage {armorDamage} to {Mech.GetLongArmorLocation(armorLocation)}"); mech.ApplyArmorStatDamage(armorLocation, armorDamage, hitInfo); } } } } } } catch (Exception e) { Control.mod.Logger.LogError(e); } mech.ApplyStructureStatDamage(location, damage, hitInfo); }
static void Prefix(Mech __instance, int originalHitLoc, WeaponHitInfo hitInfo, ArmorLocation aLoc, Weapon weapon, float totalArmorDamage, float directStructureDamage, int hitIndex, AttackImpactQuality impactQuality, DamageType damageType) { if (aLoc == ArmorLocation.Head) { Boolean DirectHit = false; //we do some quick calculation of damage to see if it's an armor hit or an structure hit float currentArmor = Math.Max(__instance.GetCurrentArmor(aLoc), 0f); //either it has armor remaining or it's got nothing left float remainingDamage = totalArmorDamage - currentArmor; // subtract our armour damage by totalArmorDamage: if it's more than 0, we calculate for structure damage float structureDamage = directStructureDamage; if (remainingDamage > 0f) { structureDamage += remainingDamage; } if (structureDamage >= 0f) // need to account for direct structure damage { DirectHit = true; } if (!DirectHit && totalArmorDamage < LessHeadInjuries.Settings.ArmorHitDamageMinimum) { //remainign damage less or equal to zero mean no structure penetration. Treat as an armour hit. LessHeadInjuries.IgnoreNextHeadHit.Add(__instance.pilot); } else if (DirectHit && structureDamage < LessHeadInjuries.Settings.StructureHitDamageMinimum) { LessHeadInjuries.IgnoreNextHeadHit.Add(__instance.pilot); } } }
static bool Prefix(ref WeaponHitInfo hitInfo, int groupIdx, int weaponIdx, Weapon weapon, float toHitChance, float prevDodgedDamage, AttackDirector.AttackSequence __instance) { if (!weapon.weaponDef.ComponentTags.Contains(CLUSTER_TAG)) { return(true); } Logger.Debug("had the cluster tag"); var newNumberOfShots = weapon.ProjectilesPerShot * hitInfo.numberOfShots; var originalNumberOfShots = hitInfo.numberOfShots; hitInfo.numberOfShots = newNumberOfShots; hitInfo.toHitRolls = new float[newNumberOfShots]; hitInfo.locationRolls = new float[newNumberOfShots]; hitInfo.dodgeRolls = new float[newNumberOfShots]; hitInfo.dodgeSuccesses = new bool[newNumberOfShots]; hitInfo.hitLocations = new int[newNumberOfShots]; hitInfo.hitPositions = new Vector3[newNumberOfShots]; hitInfo.hitVariance = new int[newNumberOfShots]; hitInfo.hitQualities = new AttackImpactQuality[newNumberOfShots]; AttackSequenceGetClusteredHits.Invoke( __instance, new object[] { hitInfo, groupIdx, weaponIdx, weapon, toHitChance, prevDodgedDamage } ); PrintHitLocations(hitInfo); hitInfo.numberOfShots = originalNumberOfShots; return(false); }
// crit engine reduces speed // destroyed engine destroys CT public static bool Prefix(MechComponent __instance, WeaponHitInfo hitInfo, ComponentDamageLevel damageLevel, bool applyEffects) { try { if (!EngineCrits.ProcessWeaponHit(__instance, hitInfo, damageLevel, applyEffects, MechCheckForCritPatch.MessageAdditions)) { MechCheckForCritPatch.Message = null; return(false); } if (!Structure.ProcessWeaponHit(__instance, hitInfo, damageLevel, applyEffects)) { MechCheckForCritPatch.Message = null; return(false); } if (!Armor.ProcessWeaponHit(__instance, hitInfo, damageLevel, applyEffects)) { MechCheckForCritPatch.Message = null; return(false); } } catch (Exception e) { Control.mod.Logger.LogError(e); } return(true); }
private static void PrintHitLocations(WeaponHitInfo hitInfo) { if (!Core.ModSettings.debug) { return; } try { var output = ""; output += $"clustered hits: {hitInfo.hitLocations.Length}\n"; for (int i = 0; i < hitInfo.hitLocations.Length; i++) { int location = hitInfo.hitLocations[i]; var chassisLocationFromArmorLocation = MechStructureRules.GetChassisLocationFromArmorLocation((ArmorLocation)location); if (location == 0 || location == 65536) { output += $"hitLocation {i}: NONE/INVALID\n"; } else { output += $"hitLocation {i}: {chassisLocationFromArmorLocation} ({location})\n"; } } Logger.Debug(output); } catch (Exception e) { Logger.Error(e); } }
public static bool ResolveSystemFailureCheck(Mech mech, int heatToCheck, int rootSequenceGUID, float heatCheck) { bool failedSystemFailureCheck = !CheckHelper.DidCheckPassThreshold(Mod.Config.Heat.SystemFailures, heatToCheck, mech, heatCheck, ModText.FT_Check_System_Failure); Mod.Log.Debug?.Write($" failedSystemFailureCheck: {failedSystemFailureCheck}"); if (failedSystemFailureCheck) { Mod.Log.Info?.Write($"-- System Failure check failed, forcing system damage on unit: {CombatantUtils.Label(mech)}"); List <MechComponent> functionalComponents = new List <MechComponent>(); foreach (MechComponent mc in mech.allComponents) { bool canTarget = mc.IsFunctional; if (mc.mechComponentRef.Is <Flags>(out Flags flagsCC)) { if (flagsCC.IsSet(ModStats.ME_IgnoreDamage)) { canTarget = false; Mod.Log.Trace?.Write($" Component: {mc.Name} / {mc.UIName} is marked ignores_damage."); } } if (canTarget) { functionalComponents.Add(mc); } } MechComponent componentToDamage = functionalComponents.GetRandomElement(); Mod.Log.Info?.Write($" Destroying component: {componentToDamage.UIName} from heat damage."); WeaponHitInfo fakeHit = new WeaponHitInfo(rootSequenceGUID, -1, -1, -1, string.Empty, string.Empty, -1, null, null, null, null, null, null, null, new AttackDirection[] { AttackDirection.None }, null, null, null); componentToDamage.DamageComponent(fakeHit, ComponentDamageLevel.Destroyed, true); } return(failedSystemFailureCheck); }
private static void Postfix(Mech __instance, WeaponHitInfo hitInfo, Weapon weapon, MeleeAttackType meleeAttackType) { AttackDirector.AttackSequence attackSequence = __instance.Combat.AttackDirector.GetAttackSequence(hitInfo.attackSequenceId); AbstractActor actor = __instance.Combat.FindActorByGUID(hitInfo.targetId); if (actor is Mech) { Mech target = actor as Mech; float stabilityDamage = hitInfo.ConsolidateInstability(weapon.Instability(), __instance.Combat.Constants.ResolutionConstants.GlancingBlowDamageMultiplier, __instance.Combat.Constants.ResolutionConstants.NormalBlowDamageMultiplier, __instance.Combat.Constants.ResolutionConstants.SolidBlowDamageMultiplier); stabilityDamage *= __instance.StatCollection.GetValue <float>("ReceivedInstabilityMultiplier"); stabilityDamage *= __instance.EntrenchedMultiplier; if (AttackDirector.attackLogger.IsLogEnabled) { AttackDirector.attackLogger.Log("[CBTPiloting] Checking Piloting Stability"); AttackDirector.attackLogger.Log(string.Format("[CBTPiloting] Is Mech: {0}", (actor is Mech))); AttackDirector.attackLogger.Log(string.Format("[CBTPiloting] Weapon Stab Dmg: {0}", stabilityDamage)); AttackDirector.attackLogger.Log(string.Format("[CBTPiloting] Target Dead: {0}", target.IsDead)); AttackDirector.attackLogger.Log(string.Format("[CBTPiloting] Target Unsteady: {0}", target.IsUnsteady)); AttackDirector.attackLogger.Log(string.Format("[CBTPiloting] Target IsOrWillBeProne: {0}", target.IsOrWillBeProne)); } if (stabilityDamage > 0 && !target.IsDead && target.IsUnsteady && !target.IsOrWillBeProne) { float skillBonus = (float)target.SkillPiloting / __instance.Combat.Constants.PilotingConstants.PilotingDivisor; float skillRoll = __instance.Combat.NetworkRandom.Float(); float skillTotal = skillRoll + skillBonus; if (AttackDirector.attackLogger.IsLogEnabled) { AttackDirector.attackLogger.Log(string.Format("[CBTPiloting] Skill Bonus: {0}", skillBonus)); AttackDirector.attackLogger.Log(string.Format("[CBTPiloting] Skill Roll: {0}", skillRoll)); AttackDirector.attackLogger.Log(string.Format("[CBTPiloting] Skill Roll Total: {0}", skillTotal)); AttackDirector.attackLogger.Log(string.Format("[CBTPiloting] Skill Target: {0}", CBTPiloting.Settings.PilotStabilityCheck)); } if (skillTotal < CBTPiloting.Settings.PilotStabilityCheck) { if (AttackDirector.attackLogger.IsLogEnabled) { AttackDirector.attackLogger.Log(string.Format("[CBTPiloting] Skill Check Failed! Flagging for Knockdown")); } target.FlagForKnockdown(); target.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(new ShowActorInfoSequence(target, $"Stability Check: Failed!", FloatieMessage.MessageNature.Debuff, true))); } else { if (AttackDirector.attackLogger.IsLogEnabled) { AttackDirector.attackLogger.Log(string.Format("[CBTPiloting] Skill Check Succeeded!")); } target.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(new ShowActorInfoSequence(target, $"Stability Check: Passed!", FloatieMessage.MessageNature.Buff, true))); } } } }
public static bool Prefix( Mech __instance, WeaponHitInfo hitInfo, ArmorLocation aLoc, ref float directStructureDamage, ref bool __result) { try { if (ComponentExplosionsFeature.IsInternalExplosionContained) { __result = false; Control.Logger.Warning.Log("prevented explosion pass through (you should never see this message)"); return(false); } if (ComponentExplosionsFeature.IsInternalExplosion) { var location = MechStructureRules.GetChassisLocationFromArmorLocation(aLoc); UpdateStructureDamage(__instance, location, hitInfo, ref directStructureDamage); } } catch (Exception e) { Control.Logger.Error.Log(e); } return(true); }
public static bool ResolveVolatileAmmoCheck(Mech mech, int heatToCheck, int rootSequenceGUID, float heatCheck) { bool failedVolatileAmmoCheck = false; AmmunitionBox mostDamagingVolatile = HeatHelper.FindMostDamagingAmmoBox(mech, true); if (mostDamagingVolatile != null) { failedVolatileAmmoCheck = !CheckHelper.DidCheckPassThreshold(Mod.Config.Heat.Explosion, heatToCheck, mech, heatCheck, ModText.FT_Check_Explosion); Mod.Log.Debug?.Write($" failedVolatileAmmoCheck: {failedVolatileAmmoCheck}"); if (failedVolatileAmmoCheck) { Mod.Log.Info?.Write($"-- Volatile Ammo Explosion check failed on {CombatantUtils.Label(mech)}, forcing volatile ammo explosion"); if (mostDamagingVolatile != null) { Mod.Log.Info?.Write($" Exploding inferno ammo: {mostDamagingVolatile.UIName}"); WeaponHitInfo fakeHit = new WeaponHitInfo(rootSequenceGUID, -1, -1, -1, string.Empty, string.Empty, -1, null, null, null, null, null, null, null, new AttackDirection[] { AttackDirection.None }, null, null, null); mostDamagingVolatile.DamageComponent(fakeHit, ComponentDamageLevel.Destroyed, true); } else { Mod.Log.Debug?.Write(" Unit has no Volatile ammo boxes, skipping."); } } } return(failedVolatileAmmoCheck); }
private static void AddJam(AbstractActor actor, Weapon weapon) { if (!DamagesWhenJams(weapon)) { weapon.StatCollection.Set <bool>(JammedWeaponStatisticName, true); weapon.StatCollection.Set <bool>(TemporarilyDisabledStatisticName, true); actor.Combat.MessageCenter.PublishMessage( new AddSequenceToStackMessage( new ShowActorInfoSequence(actor, $"{weapon.Name} Jammed!", FloatieMessage.MessageNature.Debuff, true))); } else { var isDestroying = weapon.DamageLevel != ComponentDamageLevel.Functional; var damageLevel = isDestroying ? ComponentDamageLevel.Destroyed : ComponentDamageLevel.Penalized; var fakeHit = new WeaponHitInfo(-1, -1, -1, -1, string.Empty, string.Empty, -1, null, null, null, null, null, null, null, AttackDirection.None, Vector2.zero, null); weapon.DamageComponent(fakeHit, damageLevel, true); var message = isDestroying ? $"{weapon.Name} misfire: Destroyed!" : $"{weapon.Name} misfire: Damaged!"; actor.Combat.MessageCenter.PublishMessage( new AddSequenceToStackMessage( new ShowActorInfoSequence(actor, message, FloatieMessage.MessageNature.Debuff, true))); } }
public static void Prefix(Mech __instance, WeaponHitInfo hitInfo, ArmorLocation aLoc, Weapon weapon, float totalArmorDamage, float directStructureDamage, int hitIndex, AttackImpactQuality impactQuality, DamageType damageType) { if (aLoc == ArmorLocation.Head) { Mod.Log.Info($"Head hit from weapon:{weapon?.UIName} for {totalArmorDamage} armor damage and {directStructureDamage} structure damage. " + $"Quality was:{impactQuality} with type:{damageType}"); float currHeadArmor = __instance.GetCurrentArmor(aLoc); int damageMod = (int)Math.Ceiling(totalArmorDamage); float damageThroughArmor = totalArmorDamage - currHeadArmor; Mod.Log.Debug($"TotalArmorDamage:{totalArmorDamage} - Head armor:{currHeadArmor} = throughArmor:{damageThroughArmor}"); if (totalArmorDamage - currHeadArmor <= 0) { damageMod = (int)Math.Floor(damageMod * Mod.Config.Combat.PainTolerance.HeadHitArmorOnlyMulti); Mod.Log.Info($"Head hit impacted armor only, reduced damage to:{damageMod}"); } if (directStructureDamage != 0) { Mod.Log.Debug($"Attack inflicted ${directStructureDamage}, adding to total resist damage."); damageMod += (int)Math.Ceiling(directStructureDamage); } ModState.InjuryResistPenalty = damageMod * Mod.Config.Combat.PainTolerance.PenaltyPerHeadDamage; Mod.Log.Info($"Setting resist penalty to:{damageMod} x {Mod.Config.Combat.PainTolerance.PenaltyPerHeadDamage} = {ModState.InjuryResistPenalty}"); } }
private void CheckWeaponDistance(NebulaObject target, WeaponHitInfo hit) { if (hit.normal && BlockedByDistance(target)) { hit.Interrupt(ShotState.blockedByDistance); } }
private void CheckWeaponBlocked(WeaponHitInfo hit) { if (hit.normal && blocked) { hit.Interrupt(ShotState.blockedBySkill); } }
private static float GetWeaponDamage(AbstractActor target, WeaponHitInfo hitInfo, Weapon weapon) { float damage = weapon.parent == null ? weapon.DamagePerShot : weapon.DamagePerShotAdjusted(weapon.parent.occupiedDesignMask); AbstractActor attacker = Combat.FindActorByGUID(hitInfo.attackerId); LineOfFireLevel lineOfFireLevel = attacker.VisibilityCache.VisibilityToTarget(target).LineOfFireLevel; return(target.GetAdjustedDamage(damage, weapon.Category, target.occupiedDesignMask, lineOfFireLevel, false)); }
public static void RecordHitChance(ref WeaponHitInfo hitInfo, float toHitChance) { if (hitChance.ContainsKey(hitInfo.attackSequenceId)) { return; } hitChance.Add(hitInfo.attackSequenceId, toHitChance); }
private bool IsCritical(ref WeaponHitInfo hit) { if (RollCritical()) { hit.MakeCritical(); } return(hit.state == ShotState.normalCritical); }
static void Prefix(Mech __instance, int originalHitLoc, WeaponHitInfo hitInfo, ArmorLocation aLoc, Weapon weapon, float totalArmorDamage, float directStructureDamage, int hitIndex, AttackImpactQuality impactQuality, DamageType damageType) { if (aLoc == ArmorLocation.None || aLoc == ArmorLocation.Invalid) { return; } float num = totalArmorDamage; float currentArmor = __instance.GetCurrentArmor(aLoc); if (currentArmor > 0f) { num = totalArmorDamage - currentArmor; } num += directStructureDamage; // account for damage split: this should get us back where we were when we both had armour spillover damage and // any damage done directly to the structure if (num <= 0f) { return; //no need to continue if the shot doesn't do anything we care about } ChassisLocations chassisLocationFromArmorLocation = MechStructureRules.GetChassisLocationFromArmorLocation(aLoc); float currentStructure = __instance.GetCurrentStructure(chassisLocationFromArmorLocation); if (currentStructure > 0f) { float num4 = Math.Min(num, currentStructure); bool WasDestroyed = (currentStructure - num) <= 0; //if currentstructure minus remaining damage is less or equal to 0, then the location is destroyed. num -= num4; if (WasDestroyed && num4 > 0.01f) //this location was destroyed, so we now check for dependents. { if (chassisLocationFromArmorLocation == ChassisLocations.LeftArm) { Holder.LeftArmSurvived = false; //invalidate if the actual arm was destroyed } else if (chassisLocationFromArmorLocation == ChassisLocations.RightArm) { Holder.RightArmSurvived = false; //invalidate if the actual arm was destroyed } ChassisLocations dependentLocation = MechStructureRules.GetDependentLocation(chassisLocationFromArmorLocation); if (dependentLocation != ChassisLocations.None && !__instance.IsLocationDestroyed(dependentLocation)) { if (dependentLocation == ChassisLocations.LeftArm) { Holder.LeftArmSurvived = true; //side torso was destroyed, no reason the arm should be totally trashed. } else if (dependentLocation == ChassisLocations.RightArm) { Holder.RightArmSurvived = true; //side torso was destroyed, no reason the arm should be totally trashed. } } } } }
public static void ApplyPilotHealthDamage(AbstractActor target, WeaponHitInfo hitInfo, int headHits, out string tooltipText) { StringBuilder pilotDamageSB = new StringBuilder(); if (target == null || !target.IsPilotable || headHits == 0) { tooltipText = null; return; } int healthDamage = headHits; if (target.GetPilot().BonusHealth > 0) { int absorbedDamage; if (target.GetPilot().BonusHealth >= healthDamage) { absorbedDamage = healthDamage; healthDamage = 0; } else { absorbedDamage = target.GetPilot().BonusHealth; healthDamage = healthDamage - target.GetPilot().BonusHealth; } Mod.Log.Debug?.Write($"Bonus health aborbs: {absorbedDamage} leaving: {healthDamage} healthDamage."); target.GetPilot().StatCollection.ModifyStat <int>(hitInfo.attackerId, hitInfo.stackItemUID, "BonusHealth", StatCollection.StatOperation.Int_Subtract, absorbedDamage, -1, true); Text localText = new Text(Mod.Config.LocalizedText[ModConfig.LT_TT_PILOT_BONUS_HEALTH], new object[] { absorbedDamage }); pilotDamageSB.Append(localText.ToString()); } if (healthDamage > (target.GetPilot().Health - 1)) { Mod.Log.Debug?.Write($"Health damage: {healthDamage} would kill pilot, reducing to maxHealth: {target.GetPilot().Health} - 1"); healthDamage = target.GetPilot().Health - 1; } if (healthDamage > 0 && !Mod.Config.EnableTBAS_Injuries) { Mod.Log.Info?.Write($"Adding {healthDamage} to {CombatantUtils.Label(target)}"); target.GetPilot().StatCollection.ModifyStat <int>(hitInfo.attackerId, hitInfo.stackItemUID, "Injuries", StatCollection.StatOperation.Int_Add, healthDamage, -1, true); Text localText = new Text(Mod.Config.LocalizedText[ModConfig.LT_TT_PILOT_HEALTH], new object[] { healthDamage }); pilotDamageSB.Append(localText.ToString()); } else if (healthDamage > 0 && Mod.Config.EnableTBAS_Injuries) { Mod.Log.Info?.Write($"Adding {healthDamage} TBAS Injuries to {CombatantUtils.Label(target)}"); target.GetPilot().InjurePilot(hitInfo.attackerId, hitInfo.stackItemUID, healthDamage, BattleTech.DamageType.NOT_SET, null, null); Text localText = new Text(Mod.Config.LocalizedText[ModConfig.LT_TT_PILOT_HEALTH], new object[] { healthDamage }); pilotDamageSB.Append(localText.ToString()); } tooltipText = pilotDamageSB.ToString(); }
internal static bool ProcessWeaponHit(MechComponent mechComponent, WeaponHitInfo hitInfo, ComponentDamageLevel damageLevel, bool applyEffects) { if (mechComponent.componentDef.IsStructure()) { return(false); } return(true); }
private static void SetDamageLevel(MechComponent mechComponent, WeaponHitInfo hitInfo, ComponentDamageLevel damageLevel) { mechComponent.StatCollection.ModifyStat( hitInfo.attackerId, hitInfo.stackItemUID, "DamageLevel", StatCollection.StatOperation.Set, damageLevel); }
private void SetHits(WeaponHitInfo hitInfo, out ComponentDamageLevel damageLevel) { if (actor == null) { throw new NullReferenceException(); } int effectsMax, effectsPrev, effectsNext; { var compCritsMax = ComponentHitMax(); var compCritsPrev = ComponentHitCount(); var locationDestroyed = actor.StructureForLocation(component.Location) <= 0f; var possibleAddedHits = locationDestroyed ? compCritsMax : 1; var compCritsNext = Mathf.Min(compCritsMax, compCritsPrev + possibleAddedHits); var compCritsAdded = Mathf.Max(compCritsNext - compCritsPrev, 0); ComponentHitCount(compCritsNext); // if max is reached, component is destroyed and no new effects can be applied // Destroyed components can still soak up crits, requires properly configured AIM from CAC effectsMax = Effects?.PenalizedEffectIDs.Length + 1 ?? DefaultEffectsMax(); // move to group/component abstraction, make sure that critsAdded is clear if (HasLinked) { var prev = GroupHitCount(); var next = Mathf.Min(effectsMax, prev + compCritsAdded); GroupHitCount(next); effectsPrev = prev; effectsNext = next; } else { effectsPrev = compCritsPrev; effectsNext = compCritsNext; } } damageLevel = effectsNext >= effectsMax ? ComponentDamageLevel.Destroyed : ComponentDamageLevel.Penalized; SetDamageLevel(hitInfo, damageLevel); if (Effects != null) { CancelEffects(effectsPrev, damageLevel); CreateEffects(effectsNext, damageLevel); } Control.Logger.Debug?.Log( $"Component hit (uid={component.uid} Id={component.Description.Id} Location={component.Location}) " + $"effectsMax={effectsMax} effectsPrev={effectsPrev} effectsNext={effectsNext} " + $"damageLevel={damageLevel} HasEffects={Effects != null} LinkedStatisticName={Effects?.LinkedStatisticName}" ); }
public static void Postfix(MechComponent __instance, WeaponHitInfo hitInfo, ComponentDamageLevel damageLevel, bool applyEffects) { try { Log.Debug?.TWL(0, "MechComponent.DamageComponent Postfix " + __instance.defId + " DamageLevel:" + __instance.DamageLevel + "/" + damageLevel); if ((__instance.DamageLevel >= ComponentDamageLevel.Destroyed) || (damageLevel >= ComponentDamageLevel.Destroyed)) { Statistic isCAEDestroyed = __instance.StatCollection.GetStatistic("CAEDestroyed"); if (isCAEDestroyed == null) { isCAEDestroyed = __instance.StatCollection.AddStatistic("CAEDestroyed", false); } if (isCAEDestroyed.Value <bool>() == false) { isCAEDestroyed.SetValue(true); Log.Debug?.WL(1, "really destroyed at first time"); ActivatableComponent activatable = __instance.componentDef.GetComponent <ActivatableComponent>(); if (activatable == null) { Log.Debug?.WL(1, "not activatable"); return; } ObjectSpawnDataSelf VFX = __instance.PresitantVFX(); if (VFX != null) { VFX.CleanupSelf(); } ; VFX = __instance.ActivateVFX(); if (VFX != null) { VFX.CleanupSelf(); } ; VFX = __instance.DestroyedVFX(); if (VFX != null) { VFX.SpawnSelf(__instance.parent.Combat); } ; if (activatable.ExplodeOnDamage) { __instance.AoEExplodeComponent(); } ; __instance.playDestroySound(); } else { Log.Debug?.WL(1, "not a really destroyed"); } __instance.UpdateAuras(); } } catch (Exception e) { Log.Debug?.TWL(0, e.ToString(), true); } }
private static void SetDamageLevel(MechComponent mechComponent, WeaponHitInfo hitInfo, ComponentDamageLevel damageLevel) { Control.mod.Logger.LogDebug($"damageLevel={damageLevel} uid={mechComponent.uid} (Id={mechComponent.Description.Id} Location={mechComponent.Location})"); mechComponent.StatCollection.ModifyStat( hitInfo.attackerId, hitInfo.stackItemUID, "DamageLevel", StatCollection.StatOperation.Set, damageLevel); }
//private bool IsHitted(NebulaObject target, ref WeaponHitInfo hit) { // if(hit.normal) { // if(BlockedByDistance(target)) { // hit.Interrupt(ShotState.blockedByDistance); // return false; // } else { // float hitProb = 1.0f - ComputeMissProb(target); // hitProb = Mathf.ClampLess(hitProb, 0.0f); // if (Rand.Float01() <= hitProb) { // hit.Interrupt(ShotState.missed); // return false; // } else { // return true; // } // } // } // return false; //} private void CheckWeaponHit(NebulaObject target, WeaponHitInfo hit) { float hitProb = 1.0f - ComputeMissProb(target); hitProb = Mathf.ClampLess(hitProb, 0.0f); if (Rand.Float01() > hitProb) { hit.Interrupt(ShotState.missed); } }
public static void Postfix(Mech __instance, WeaponHitInfo hitInfo, Weapon weapon, MeleeAttackType meleeAttackType) { Mod.Log.Trace("M:RWD entered."); AttackDirector.AttackSequence attackSequence = __instance.Combat.AttackDirector.GetAttackSequence(hitInfo.attackSequenceId); AbstractActor actor = __instance.Combat.FindActorByGUID(hitInfo.targetId); if (actor is Mech target) { CombatResolutionConstantsDef crcd = __instance.Combat.Constants.ResolutionConstants; float stabilityDamage = hitInfo.ConsolidateInstability(hitInfo.targetId, weapon.Instability(), crcd.GlancingBlowDamageMultiplier, crcd.NormalBlowDamageMultiplier, crcd.SolidBlowDamageMultiplier); stabilityDamage *= __instance.StatCollection.GetValue <float>("ReceivedInstabilityMultiplier"); stabilityDamage *= __instance.EntrenchedMultiplier; Mod.Log.Debug($" == Checking Piloting Stability"); Mod.Log.Debug($" target:{CombatantHelper.LogLabel(target)} isMech:{(actor is Mech)} IsDead:{target.IsDead} IsUnsteady:{target.IsUnsteady} IsOrWillBeProne:{target.IsOrWillBeProne}"); Mod.Log.Debug($" weapon stability damage:{stabilityDamage}"); if (stabilityDamage > 0 && !target.IsDead && target.IsUnsteady && !target.IsOrWillBeProne) { float skillBonus = (float)target.SkillPiloting / __instance.Combat.Constants.PilotingConstants.PilotingDivisor; float skillRoll = __instance.Combat.NetworkRandom.Float(); float skillTotal = skillRoll + skillBonus; Mod.Log.Debug($" Skill check -> bonus: {skillBonus} roll: {skillRoll} rollTotal: {skillTotal} target:{Mod.Config.PilotStabilityCheck}"); if (skillTotal < Mod.Config.PilotStabilityCheck) { Mod.Log.Debug(string.Format(" Skill Check Failed! Flagging for Knockdown")); bool showMessage = !target.IsFlaggedForKnockdown; target.FlagForKnockdown(); if (Mod.Config.ShowAllStabilityRolls || showMessage) { target.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(new ShowActorInfoSequence(target, $"Stability Check: Failed!", FloatieMessage.MessageNature.Debuff, true))); } } else { Mod.Log.Debug(string.Format(" Skill Check Succeeded!")); if (Mod.Config.ShowAllStabilityRolls) { target.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(new ShowActorInfoSequence(target, $"Stability Check: Passed!", FloatieMessage.MessageNature.Buff, true))); } } } else { Mod.Log.Debug($" target has no stability damage, is not unsteady, or is dead or prone - skipping"); } } }
public static void ArmorDamage(this AbstractActor target, List <int> locations, float amount, bool critComponents, HashSet <string> excludeTags, HashSet <string> onlyTags) { WeaponHitInfo fakeHit = new WeaponHitInfo(-1, -1, -1, -1, target.GUID, target.GUID, 1, null, null, null, null, null, null, null, null, null, null, null); fakeHit.toHitRolls = new float[1] { 1.0f }; fakeHit.locationRolls = new float[1] { 1.0f }; fakeHit.dodgeRolls = new float[1] { 0.0f }; fakeHit.dodgeSuccesses = new bool[1] { false }; fakeHit.hitLocations = new int[1] { 0 }; fakeHit.hitVariance = new int[1] { 0 }; fakeHit.hitQualities = new AttackImpactQuality[1] { AttackImpactQuality.Solid }; fakeHit.attackDirections = new AttackDirection[1] { AttackDirection.FromArtillery }; fakeHit.hitPositions = new Vector3[1] { target.CurrentPosition }; fakeHit.secondaryTargetIds = new string[1] { null }; fakeHit.secondaryHitLocations = new int[1] { 0 }; Log.Debug?.TWL(0, "StructureDamage:" + target.DisplayName + " amount:" + amount + " critComponents:" + critComponents); foreach (int location in locations) { Log.Debug?.WL(1, "location:" + location); fakeHit.hitLocations[0] = location; fakeHit.hitPositions[0] = target.GameRep.GetHitPosition(location); float prev_armor = target.ArmorForLocation(location); if (amount >= 1f) { target.TakeWeaponDamage(fakeHit, location, target.ImaginaryLaserWeapon, amount, 0, 0, DamageType.ComponentExplosion); } if ((critComponents) && (prev_armor < amount)) { target.CritComponentsInLocation(location, ref fakeHit, excludeTags, onlyTags); } } }
public static void SetImpact(MessageCenterMessage message) { if (!(message is AttackSequenceImpactMessage impactMessage)) { return; } WeaponHitInfo info = impactMessage.hitInfo; currentImpact = info.attackSequenceId; currentRoll = info.toHitRolls[impactMessage.hitIndex]; }
public static void Postfix(MechComponent __instance, WeaponHitInfo hitInfo, ComponentDamageLevel damageLevel, bool applyEffects) { try { ComponentExplosionsFeature.Shared.CheckForExplosion(__instance, hitInfo, damageLevel, applyEffects); } catch (Exception e) { Control.mod.Logger.LogError(e); } }