public static void AppendDangerousTerrainText(MoveStatusPreview __instance, AbstractActor actor, Vector3 worldPos) { try { MapTerrainDataCell cell = Combat.EncounterLayerData.GetCellAt(worldPos).relatedTerrainCell; bool isLandingZone = SplatMapInfo.IsDropshipLandingZone(cell.terrainMask), isDangerous = SplatMapInfo.IsDangerousLocation(cell.terrainMask); if (!isLandingZone && !isDangerous) { return; } DesignMaskDef mask = Combat.MapMetaData.GetPriorityDesignMask(cell); if (mask == null) { return; } string title = mask.Description.Name, text = mask.Description.Details; CombatUIConstantsDef desc = Combat.Constants.CombatUIConstants; if (isDangerous) { title += " <#FF0000>(" + desc.DangerousLocationDesc.Name + ")"; text += " <#FF0000>" + desc.DangerousLocationDesc.Details; if (isLandingZone) { text += " " + desc.DrophipLocationDesc.Details; } } else { title += " <#FF0000>(" + desc.DrophipLocationDesc.Name + ")"; text += " <#FF0000>" + desc.DrophipLocationDesc.Details; } CombatHUDInfoSidePanel sidePanel = (CombatHUDInfoSidePanel)SidePanelProp?.GetValue(__instance, null); sidePanel?.ForceShowSingleFrame(new Text(title), new Text(text), null, false); } catch (Exception ex) { Error(ex); } }
// WARNING: DUPLICATE OF HBS CODE. THIS IS LIKELY TO BREAK IF HBS CHANGES THE SOURCE FUNCTIONS public static float GetTargetSignature(ICombatant target, EWState sourceState) { if (target == null || (target as AbstractActor) == null) { return(1f); } AbstractActor targetActor = target as AbstractActor; float allTargetSignatureModifiers = GetAllTargetSignatureModifiers(targetActor, sourceState); float staticSignature = 1f * allTargetSignatureModifiers; // Add in any design mask boosts DesignMaskDef occupiedDesignMask = targetActor.occupiedDesignMask; if (occupiedDesignMask != null) { staticSignature *= occupiedDesignMask.signatureMultiplier; } if (staticSignature < Mod.Config.Sensors.MinSignature) { staticSignature = Mod.Config.Sensors.MinSignature; } return(staticSignature); }
public AttackDetails(AbstractActor attacker, ICombatant target, Vector3 attackPos, Vector3 targetPos, bool useRevengeBonus) { this.Attacker = attacker; this.Target = target; this.AttackPosition = attackPos; this.TargetPosition = targetPos; this.UseRevengeBonus = useRevengeBonus; // Precalculate some values heavily used by the prediction engine // Impact quality for any melee attack is always solid this.BaseRangedImpactQuality = SharedState.Combat.ToHit.GetBlowQuality(attacker, attackPos, null, target, MeleeAttackType.Punch, false); this.AttackerDesignMask = SharedState.Combat.MapMetaData.GetPriorityDesignMaskAtPos(attackPos); if (this.AttackerDesignMask == null) { AttackerDesignMask = new DesignMaskDef(); } this.TargetDesignMask = SharedState.Combat.MapMetaData.GetPriorityDesignMaskAtPos(targetPos); if (this.TargetDesignMask == null) { TargetDesignMask = new DesignMaskDef(); } // Calculate the total damage multiplier for attacks by weaponType this.DamageMultipliers.Add(DamageMultiType.Ballistic, CalculateDamageMulti(DamageMultiType.Ballistic, target)); this.DamageMultipliers.Add(DamageMultiType.Energy, CalculateDamageMulti(DamageMultiType.Energy, target)); this.DamageMultipliers.Add(DamageMultiType.Missile, CalculateDamageMulti(DamageMultiType.Missile, target)); this.DamageMultipliers.Add(DamageMultiType.Support, CalculateDamageMulti(DamageMultiType.Support, target)); this.DamageMultipliers.Add(DamageMultiType.Generic, CalculateDamageMulti(DamageMultiType.Generic, target)); }
// This method is used by the IL generators in IntermediateLanguageFuckery public static float VariantDamage(WeaponEffect weaponEffect, DesignMaskDef designMask) { var weapon = weaponEffect.weapon; var key = ShotMemoKey(weaponEffect); if (DamageWasAlreadyCalculated(key, out var variantDamage)) { return(variantDamage); } if (IsNonVariantWeapon(key, weapon, out var damageVariance, out var normalDamage)) { return(normalDamage); } // the following damage calcs should match with Weapon.DamagePerShotAdjusted(DesignMaskDef), with // the addition of the variance computations var damagePerShot = weapon.DamagePerShotAdjusted(); Logger.Debug( $"some damage numbers:\n" + $"weapon damage: {weapon.DamagePerShot}\n" + $"weapon damage adjusted: {weapon.DamagePerShotAdjusted()}\n" + $"stats based: {weapon.StatCollection.GetValue<float>("DamagePerShot")}" ); var bounds = new VarianceBounds( min: damagePerShot - damageVariance, max: damagePerShot + damageVariance, standardDeviation: ModSettings.StandardDeviationVarianceMultiplier * damageVariance ); var damage = Utility.NormalDistibutionRandom(bounds); var combat = Traverse.Create(weapon).Field("combat").GetValue <CombatGameState>(); var damageWDesign = damage * weapon.GetMaskDamageMultiplier(weapon.parent.occupiedDesignMask); var result = damageWDesign * weapon.GetMaskDamageMultiplier(combat.MapMetaData.biomeDesignMask); Logger.Debug( $"effect id: {key.weaponEffectId}\n" + $"hit index: {key.hitIndex}\n" + $"damage and variance: {damagePerShot}+-{damageVariance}\n" + $"damage range: {bounds.min}-{bounds.max} (std. dev. {bounds.standardDeviation}\n" + $"computed damage: {damage}\n" + $"damage w/ design mask: {damageWDesign}\n" + $"damage w/ env: {result}" ); WeaponDamageMemo[key] = result; return(WeaponDamageMemo[key]); }
// WARNING: DUPLICATE OF HBS CODE. THIS IS LIKELY TO BREAK IF HBS CHANGES THE SOURCE FUNCTIONS public static float GetAllSensorRangeMultipliers(AbstractActor source) { if (source == null) { return(1f); } float sensorMulti = source.SensorDistanceMultiplier; DesignMaskDef occupiedDesignMask = source.occupiedDesignMask; if (occupiedDesignMask != null) { sensorMulti *= occupiedDesignMask.sensorRangeMultiplier; } return(sensorMulti); }
// Replicates logic from Mech::AdjustedHeatSinkCapacity to allow displaying multiplier public static float DesignMaskHeatMulti(this Mech mech, bool isProjectedHeat) { float capacityMulti = 1f; try { // Check for currently occupied, or future if (isProjectedHeat) { Mod.HeatLog.Trace?.Write("Calculating projected position heat."); if (mech.Pathing != null && mech.Pathing.CurrentPath != null && mech.Pathing.CurrentPath.Count > 0) { // Determine the destination designMask Mod.HeatLog.Trace?.Write($"CurrentPath has: {mech.Pathing.CurrentPath.Count} nodes, using destination path: {mech.Pathing.ResultDestination}"); DesignMaskDef destinationDesignMaskDef = mech?.Combat?.MapMetaData?.GetPriorityDesignMaskAtPos(mech.Pathing.ResultDestination); if (destinationDesignMaskDef != null && !Mathf.Approximately(destinationDesignMaskDef.heatSinkMultiplier, 1f)) { Mod.HeatLog.Trace?.Write($"Destination design mask: {destinationDesignMaskDef?.Description?.Name} has heatSinkMulti: x{destinationDesignMaskDef?.heatSinkMultiplier} "); capacityMulti *= destinationDesignMaskDef.heatSinkMultiplier; } // Check for any cells along the way that will apply the burning sticky effect. // See CustomAmmoCategories\designmask\DesignMaskBurningForest List <WayPoint> waypointsFromPath = ActorMovementSequence.ExtractWaypointsFromPath( mech, mech.Pathing.CurrentPath, mech.Pathing.ResultDestination, (ICombatant)mech.Pathing.CurrentMeleeTarget, mech.Pathing.MoveType ); List <MapTerrainCellWaypoint> terrainWaypoints = DynamicMapHelper.getVisitedWaypoints(mech.Combat, waypointsFromPath); Mod.HeatLog.Trace?.Write($" Count of waypointsFromPath: {waypointsFromPath?.Count} terrainWaypoints: {terrainWaypoints?.Count}"); // This assumes 1) only KMission is using stickyEffects that modify HeatSinkCapacity and 2) it has a stackLimit of 1. Anything else will break this. float stickyModifier = 1f; foreach (MapTerrainCellWaypoint cell in terrainWaypoints) { if (cell != null && cell?.cell?.BurningStrength > 0 && cell?.cell?.mapMetaData?.designMaskDefs != null) { Mod.HeatLog.Trace?.Write($" checking burningCell for designMask."); foreach (DesignMaskDef cellDesignMaskDef in cell?.cell?.mapMetaData?.designMaskDefs?.Values) { Mod.HeatLog.Trace?.Write($" checking designMask for stickyEffects."); if (cellDesignMaskDef.stickyEffect != null && cellDesignMaskDef.stickyEffect?.statisticData != null && cellDesignMaskDef.stickyEffect.statisticData.statName == ModStats.HBS_HeatSinkCapacity) { Mod.HeatLog.Trace?.Write($" found stickyEffects."); stickyModifier = Single.Parse(cellDesignMaskDef.stickyEffect.statisticData.modValue); } } } } if (!Mathf.Approximately(stickyModifier, 1f)) { capacityMulti *= stickyModifier; Mod.HeatLog.Trace?.Write($" capacityMulti: {capacityMulti} after stickyModifier: {stickyModifier}"); } } else { Mod.HeatLog.Trace?.Write($"Current path is null or has 0 count, skipping."); } } else { Mod.HeatLog.Trace?.Write("Calculating current position heat."); if (mech.occupiedDesignMask != null && !Mathf.Approximately(mech.occupiedDesignMask.heatSinkMultiplier, 1f)) { Mod.HeatLog.Trace?.Write($"Multi for currentPos is: {mech?.occupiedDesignMask?.heatSinkMultiplier}"); capacityMulti *= mech.occupiedDesignMask.heatSinkMultiplier; } } if (mech?.Combat?.MapMetaData?.biomeDesignMask != null && !Mathf.Approximately(mech.Combat.MapMetaData.biomeDesignMask.heatSinkMultiplier, 1f)) { Mod.HeatLog.Trace?.Write($"Biome: {mech.Combat.MapMetaData.biomeDesignMask.Id} has heatSinkMulti: x{mech.Combat.MapMetaData.biomeDesignMask.heatSinkMultiplier} "); capacityMulti *= mech.Combat.MapMetaData.biomeDesignMask.heatSinkMultiplier; } } catch (Exception e) { Mod.HeatLog.Error?.Write(e, $"Failed to calculate designMaskHeatMulti due to error: {e}"); } Mod.HeatLog.Trace?.Write($"Calculated capacityMulti: {capacityMulti} x globalHeatSinkMulti: {mech.Combat.Constants.Heat.GlobalHeatSinkMultiplier} "); capacityMulti *= mech.Combat.Constants.Heat.GlobalHeatSinkMultiplier; return(capacityMulti); }