private void CalculateInstability(Mech attacker, AbstractActor target) { Mod.MeleeLog.Info?.Write($"Calculating DFA instability for attacker: {CombatantUtils.Label(attacker)} " + $"vs. target: {CombatantUtils.Label(target)}"); float targetTonnage = 0; if (target is Vehicle vehicle) { targetTonnage = vehicle.tonnage; } else if (target is Mech mech) { targetTonnage = mech.tonnage; } Mod.MeleeLog.Info?.Write($" - Target tonnage is: {targetTonnage}"); float attackerInstab = attacker.DFAAttackerInstability(targetTonnage); attackerInstab = attacker.ApplyDFAInstabReduction(attackerInstab); this.AttackerInstability = attackerInstab; float targetInstab = attacker.DFATargetInstability(); targetInstab = target.ApplyDFAInstabReduction(targetInstab); this.TargetInstability = targetInstab; }
// Duplication of HBS code, avoiding prefix=true for now. public static void Postfix(ref BehaviorTreeResults __result, string ___name, BehaviorTree ___tree, AbstractActor ___unit) { Mod.Log.Info?.Write("CJMCN:T - entered"); Mech mech = ___unit as Mech; if (mech != null && mech.WorkingJumpjets > 0) { string stayInsideRegionGUID = RegionUtil.GetStayInsideRegionGUID(___unit); float acceptableHeat = AIUtil.GetAcceptableHeatLevelForMech(mech); float currentHeat = (float)mech.CurrentHeat; Mod.Log.Info?.Write($"CJMCN:T - === actor:{CombatantUtils.Label(mech)} has currentHeat:{currentHeat} and acceptableHeat:{acceptableHeat}"); List <PathNode> sampledPathNodes = ___unit.JumpPathing.GetSampledPathNodes(); Mod.Log.Info?.Write($"CJMCN:T - calculating {sampledPathNodes.Count} nodes"); for (int i = 0; i < sampledPathNodes.Count; i++) { Vector3 candidatePos = sampledPathNodes[i].Position; float distanceBetween2D = AIUtil.Get2DDistanceBetweenVector3s(candidatePos, ___unit.CurrentPosition); float distanceBetween3D = Vector3.Distance(candidatePos, ___unit.CurrentPosition); Mod.Log.Info?.Write($"CJMCN:T - calculated distances 2D:'{distanceBetween2D}' 3D:'{distanceBetween3D} "); if (distanceBetween2D >= 1f) { float magnitude = (candidatePos - ___unit.CurrentPosition).magnitude; float jumpHeat = (float)mech.CalcJumpHeat(magnitude); Mod.Log.Info?.Write($"CJMCN:T - calculated jumpHeat:'{jumpHeat}' from magnitude:'{magnitude}. "); Mod.Log.Info?.Write($"CJMCN:T - comparing heat: [jumpHeat:'{jumpHeat}' + currentHeat:'{currentHeat}'] <= acceptableHeat:'{acceptableHeat}. "); if (jumpHeat + (float)mech.CurrentHeat <= acceptableHeat) { if (stayInsideRegionGUID != null) { MapTerrainDataCell cellAt = ___unit.Combat.MapMetaData.GetCellAt(candidatePos); if (cellAt != null) { MapEncounterLayerDataCell mapEncounterLayerDataCell = cellAt.MapEncounterLayerDataCell; if (mapEncounterLayerDataCell != null && mapEncounterLayerDataCell.regionGuidList != null && !mapEncounterLayerDataCell.regionGuidList.Contains(stayInsideRegionGUID)) { // Skip this loop iteration if Mod.Log.Info?.Write($"CJMCN:T - candidate outside of constraint region, ignoring."); goto CANDIDATE_OUTSIDE_REGION; } } } Mod.Log.Info?.Write($"CJMCN:T - adding candidate position:{candidatePos}"); ___tree.movementCandidateLocations.Add(new MoveDestination(sampledPathNodes[i], MoveType.Jumping)); } } CANDIDATE_OUTSIDE_REGION :; } } // Should already be set by prefix method //__result = BehaviorTreeResults(BehaviorNodeState.Success); }
public static void Postfix(Vehicle __instance) { int initPhase = PhaseHelper.TonnageToPhase((int)Math.Ceiling(__instance.tonnage)); Mod.Log.Debug?.Write($"Setting baseInit for vehicle: {CombatantUtils.Label(__instance)} to {initPhase}"); __instance.StatCollection.Set <int>(ModStats.BaseInitiative, initPhase); }
public static void Postfix(ref CalledShotAttackOrderInfo __result, AbstractActor attackingUnit, int enemyUnitIndex) { Mod.Log.Trace?.Write("AE:CCSLTC entered"); ICombatant combatant = attackingUnit.BehaviorTree.enemyUnits[enemyUnitIndex]; if (combatant is AbstractActor targetActor) { // Prevents blips from being the targets of called shots VisibilityLevel targetVisibility = attackingUnit.VisibilityToTargetUnit(targetActor); if (targetVisibility < VisibilityLevel.LOSFull) { Mod.Log.Info?.Write($"Target {CombatantUtils.Label(combatant)} is a blip, cannot be targeted by AI called shot"); __result = null; return; } float distance = Vector3.Distance(attackingUnit.CurrentPosition, targetActor.CurrentPosition); bool hasVisualScan = VisualLockHelper.GetVisualScanRange(attackingUnit) >= distance; SensorScanType sensorScan = SensorLockHelper.CalculateSharedLock(targetActor, attackingUnit); if (sensorScan < SensorScanType.ArmorAndWeaponType && !hasVisualScan) { Mod.Log.Info?.Write($"Target {CombatantUtils.Label(targetActor)} sensor info {sensorScan} is less than SurfaceScan and outside visualID, cannot be targeted by AI called shot"); __result = null; return; } } }
// Iterates over all player-allied units, checks for highest lock level to determine unit public static SensorScanType CalculateSharedLock(ICombatant target, AbstractActor source) { SensorScanType scanType = SensorScanType.NoInfo; List <AbstractActor> sensorSources = new List <AbstractActor>(); if (source == null) { // We don't have an active source, so treat the entire allied faction as sources Mod.Log.Trace?.Write($"No primary lock source, assuming all allies."); sensorSources.AddRange(target.Combat.GetAllAlliesOf(target.Combat.LocalPlayerTeam)); } else { // We have an active source, so only use that model plus any 'shares sensors' models Mod.Log.Trace?.Write($"Actor:({CombatantUtils.Label(source)}) is primary lock source."); sensorSources.Add(source); } Mod.Log.Trace?.Write($"Checking locks from {sensorSources.Count} sources."); foreach (AbstractActor actor in sensorSources) { SensorScanType actorScanType = CalculateSensorInfoLevel(actor, target); if (actorScanType > scanType) { Mod.Log.Trace?.Write($"Increasing scanType to: ({actorScanType}) from source:({CombatantUtils.Label(actor)}) "); scanType = actorScanType; } } Mod.Log.Trace?.Write($"Shared lock to target:({CombatantUtils.Label(target)}) is type: ({scanType})"); return(scanType); }
public static void Team_AddUnit_Postfix(Team __instance, AbstractActor unit) { // The added unit is a reinforcement if round > 0 if (__instance.Combat.TurnDirector.CurrentRound > 1 && Mod.Config.Combat.SpawnProtection.ApplyToReinforcements) { HostilityMatrix hm = __instance.Combat.HostilityMatrix; Mod.Log.Info($"Checking actor:{CombatantUtils.Label(unit)} that belongs to team:{unit?.team?.Name}"); List <AbstractActor> actor = new List <AbstractActor>(); if (hm.IsLocalPlayerEnemy(unit.TeamId) && Mod.Config.Combat.SpawnProtection.ApplyToEnemies) { actor.Add(unit); } else if (hm.IsLocalPlayerNeutral(unit.TeamId) && Mod.Config.Combat.SpawnProtection.ApplyToNeutrals) { actor.Add(unit); } else if (hm.IsLocalPlayerFriendly(unit.TeamId) && Mod.Config.Combat.SpawnProtection.ApplyToAllies) { actor.Add(unit); } if (actor.Count == 1) { Mod.Log.Info($"Applying protection to reinforcement:{CombatantUtils.Label(unit)}"); ProtectionHelper.ProtectActors(actor); } } }
private static void Prefix(Team __instance, List <IStackSequence> __result) { Mod.Log.Debug?.Write($"T:DWAAA invoked"); if (!__instance.IsLocalPlayer) { return; } if (__instance.Combat.TurnDirector.IsInterleavePending) { if (__result == null) { Mod.Log.Debug?.Write("Result was null, adding a new list."); __result = new List <IStackSequence>(); } int numUnitsEndingActivation = 0; foreach (AbstractActor unit in __instance.units) { Mod.Log.Debug?.Write($"Processing unit: {unit.DisplayName}_{unit.GetPilot().Name}"); if (!unit.IsCompletingActivation && !unit.IsDead && !unit.IsFlaggedForDeath) { Mod.Log.Info?.Write($" Ending activation for unit {CombatantUtils.Label(unit)}"); IStackSequence item = unit.DoneWithActor(); numUnitsEndingActivation++; __result.Add(item); } } Traverse numUnitsEndingActivationT = Traverse.Create(__instance).Field("numUnitsEndingActivation"); int currentValue = numUnitsEndingActivationT.GetValue <int>(); numUnitsEndingActivationT.SetValue(currentValue + numUnitsEndingActivation); } }
public static void ProtectActor(AbstractActor actor) { if (actor is Turret turret) { Mod.Log.Info?.Write($" Spawn protection skipping turret:{CombatantUtils.Label(actor)}"); return; } if (!ModState.SpawnProtectedUnits.Contains(actor.GUID)) { Mod.Log.Info?.Write($" Spawn protecting actor: {actor.DistinctId()}"); if (Mod.Config.Combat.SpawnProtection.ApplyGuard) { Mod.Log.Info?.Write($" -- applying Braced state"); actor.ApplyBraced(); } if (Mod.Config.Combat.SpawnProtection.EvasionPips > 0) { Mod.Log.Info?.Write($" -- setting evasion pips to: {Mod.Config.Combat.SpawnProtection.EvasionPips}"); actor.EvasivePipsCurrent = Mod.Config.Combat.SpawnProtection.EvasionPips; Traverse.Create(actor).Property("EvasivePipsTotal").SetValue(actor.EvasivePipsCurrent); //AccessTools.Property(typeof(AbstractActor), "EvasivePipsTotal").SetValue(actor, actor.EvasivePipsCurrent, null); SharedState.Combat.MessageCenter.PublishMessage(new EvasiveChangedMessage(actor.GUID, actor.EvasivePipsCurrent)); } ModState.SpawnProtectedUnits.Add(actor.GUID); } else { Mod.Log.Info?.Write($"Actor: {actor.DistinctId()} already protected, skipping."); } }
public static List <BattleTech.Building> ClosestCandidatesToPosition(Vector3 originPos, float searchRadius) { List <BattleTech.Building> candidates = new List <BattleTech.Building>(); foreach (BattleTech.Building building in ModState.CandidateBuildings) { if (!building.IsDead && (building.CurrentPosition - originPos).magnitude < searchRadius) { Mod.Log.Debug?.Write($" -- Candidate building: {CombatantUtils.Label(building)} at position: {building.CurrentPosition} is within search range."); candidates.Add(building); } else { Mod.Log.Trace?.Write($" -- Candidate building: {CombatantUtils.Label(building)} at position: {building.CurrentPosition} is beyond search range"); } } // Remove any candidates that already have a trap in them candidates.RemoveAll(x => ModState.AmbushBuildingGUIDToTurrets.ContainsKey(x.GUID)); // Sort candidates by distance from the origin candidates.Sort((b1, b2) => (b1.CurrentPosition - originPos).magnitude.CompareTo((b2.CurrentPosition - originPos).magnitude) ); return(candidates); }
public static void Postfix(AbstractActor __instance, ref int __result, StatCollection ___statCollection) { Mod.Log.Trace?.Write("AA:BI - entered."); Mod.Log.Debug?.Write($"Actor:({CombatantUtils.Label(__instance)}) has raw result: {__result}"); if (__instance.Combat.TurnDirector.IsInterleaved) { int baseInit = ___statCollection.GetValue <int>("BaseInitiative"); int phaseMod = ___statCollection.GetValue <int>("PhaseModifier"); int modifiedInit = baseInit + phaseMod; if (modifiedInit < Mod.MinPhase) { Mod.Log.Info?.Write($"Actor:({CombatantUtils.Label(__instance)}) being set to {Mod.MinPhase} due to BaseInit:{baseInit} + PhaseMod:{phaseMod}"); __result = Mod.MinPhase; } else if (modifiedInit > Mod.MaxPhase) { Mod.Log.Info?.Write($"Actor:({CombatantUtils.Label(__instance)}) being set to {Mod.MaxPhase} due to BaseInit:{baseInit} + PhaseMod:{phaseMod}"); __result = Mod.MaxPhase; } else { Mod.Log.Info?.Write($"Actor:({CombatantUtils.Label(__instance)}) has stats BaseInit:{baseInit} + PhaseMod:{phaseMod} = modifiedInit:{modifiedInit}."); __result = modifiedInit; } } else { Mod.Log.Info?.Write($"Actor:({CombatantUtils.Label(__instance)}) is non-interleaved, returning phase: {Mod.MaxPhase}."); __result = Mod.MaxPhase; } }
public static void DevestateBuildings() { if (!Mod.Config.Devastation.Enabled) { return; } Mod.Log.Debug?.Write("Processing buildings for pre-battle devestation."); List <BattleTech.Building> shuffledBuildings = new List <BattleTech.Building>(); shuffledBuildings.AddRange(ModState.CandidateBuildings); // Randomize the buildings by shuffling them shuffledBuildings.Shuffle(); int minNum = (int)(Mod.Config.Devastation.DefaultRange.MinDevastation * 100f); int maxNum = (int)(Mod.Config.Devastation.DefaultRange.MaxDevastation * 100f); int destroyPercentile = Mod.Random.Next(minNum, maxNum); float destroyPercent = (float)destroyPercentile / 100f; int destroyedBuildings = (int)Math.Floor(shuffledBuildings.Count * destroyPercent); Mod.Log.Debug?.Write($"Destruction percentile: {destroyPercent} applied to {shuffledBuildings.Count} buildings = {destroyedBuildings} destroyed buildings."); for (int i = 0; i < destroyedBuildings; i++) { BattleTech.Building building = shuffledBuildings.ElementAt(i); Mod.Log.Debug?.Write($"Destroying building: {CombatantUtils.Label(building)}"); building.FlagForDeath("CG_PREMAP_DESTROY", DeathMethod.DespawnedNoMessage, DamageType.NOT_SET, 1, -1, "0", true); building.HandleDeath("0"); ModState.CandidateBuildings.Remove(building); } }
private static void Prefix(MechJumpSequence __instance) { Mod.ActivationLog.Debug?.Write($"MJS:OC entered for actor: {CombatantUtils.Label(__instance.OwningMech)}"); // Check for visibility to any enemies if (!__instance.owningActor.Combat.TurnDirector.IsInterleaved) { Mod.ActivationLog.Info?.Write("MJS:OC is not interleaved and no enemies - autobracing "); __instance.owningActor.AutoBrace = true; } Mod.Log.Trace?.Write($"JUMP -- ABILITY_CONSUMES_FIRING: {__instance.AbilityConsumesFiring} / CONSUMES_FIRING: {__instance.ConsumesFiring}"); if (__instance.OwningMech == null) { return; // Nothing more to do } // Movement - check for damage after a jump, and if so force a piloting check if (__instance.OwningMech.ActuatorDamageMalus() != 0 || Mod.Config.Developer.ForceFallAfterJump) { Mod.Log.Info?.Write($"Actor: {CombatantUtils.Label(__instance.OwningMech)} has actuator damage, forcing piloting check."); float sourceSkillMulti = __instance.OwningMech.PilotCheckMod(Mod.Config.SkillChecks.ModPerPointOfPiloting); float damagePenalty = __instance.OwningMech.ActuatorDamageMalus() * Mod.Config.SkillChecks.ModPerPointOfPiloting; float checkMod = sourceSkillMulti + damagePenalty; Mod.Log.Debug?.Write($" moveSkillMulti:{sourceSkillMulti} - damagePenalty: {damagePenalty} = checkMod: {checkMod}"); bool sourcePassed = Mod.Config.Developer.ForceFallAfterJump ? false : CheckHelper.DidCheckPassThreshold(Mod.Config.Move.FallAfterJumpChance, __instance.OwningMech, checkMod, ModText.FT_Fall_After_Jump); if (!sourcePassed) { Mod.Log.Info?.Write($"Source actor: {CombatantUtils.Label(__instance.OwningMech)} failed pilot check after jumping with actuator damage, forcing fall."); MechHelper.AddFallingSequence(__instance.OwningMech, __instance, ModText.FT_Fall_After_Jump); } } }
private static void Prefix(ActorMovementSequence __instance) { Mod.ActivationLog.Info?.Write($"AMS:OC:PRE entered for actor: {CombatantUtils.Label(__instance?.OwningActor)}"); // Interleaved - check for visibility to any enemies if (!__instance.owningActor.Combat.TurnDirector.IsInterleaved) { __instance.owningActor.AutoBrace = true; } // Movement - check for damage after a sprint, and if so force a piloting check if (__instance.OwningMech != null && __instance.isSprinting && __instance.OwningMech.ActuatorDamageMalus() != 0) { Mod.Log.Info?.Write($"Actor: {CombatantUtils.Label(__instance.OwningMech)} has actuator damage, forcing piloting check."); float sourceSkillMulti = __instance.OwningMech.PilotCheckMod(Mod.Config.SkillChecks.ModPerPointOfPiloting); float damagePenalty = __instance.OwningMech.ActuatorDamageMalus() * Mod.Config.SkillChecks.ModPerPointOfPiloting; float checkMod = sourceSkillMulti + damagePenalty; Mod.Log.Debug?.Write($" moveSkillMulti:{sourceSkillMulti} - damagePenalty: {damagePenalty} = checkMod: {checkMod}"); bool sourcePassed = CheckHelper.DidCheckPassThreshold(Mod.Config.Move.FallAfterRunChance, __instance.OwningMech, checkMod, ModText.FT_Fall_After_Run); if (!sourcePassed) { Mod.Log.Info?.Write($"Source actor: {CombatantUtils.Label(__instance.OwningMech)} failed pilot check after sprinting with actuator damage, forcing fall."); MechHelper.AddFallingSequence(__instance.OwningMech, __instance, ModText.FT_Fall_After_Run); } } }
// Per BT Manual pg.36, // * target takes 1 pt. each 10 tons of attacker, which is then multiplied by 3 and rounded up // * attacker takes tonnage / 5, rounded up // * Damage clustered in 5 (25) point groupings for both attacker & defender // * Target resolves on punch table // * Prone targets resolve on rear table // * Attacker resolves on kick table // * Comparative attack modifier; difference in attacker and defender is applied to attack // * +3 modifier to hit for jumping // * +2 to hit if upper or lower leg actuators are hit // * -2 modifier if target is prone // * Attacker makes PSR with +4, target with +2 and fall public DFAAttack(MeleeState state) : base(state) { Mod.MeleeLog.Info?.Write($"Building DFA state for attacker: {CombatantUtils.Label(state.attacker)} @ attackPos: {state.attackPos} vs. target: {CombatantUtils.Label(state.target)}"); this.Label = Mod.LocalizedText.Labels[ModText.LT_Label_Melee_Type_DeathFromAbove]; this.IsValid = ValidateAttack(state.attacker, state.target); if (IsValid) { this.UsePilotingDelta = Mod.Config.Melee.DFA.UsePilotingDelta; CalculateDamages(state.attacker, state.target); CalculateInstability(state.attacker, state.target); CalculateModifiers(state.attacker, state.target); CreateDescriptions(state.attacker, state.target); // Damage tables this.AttackerTable = DamageTable.KICK; this.TargetTable = state.target.IsProne ? DamageTable.REAR : DamageTable.PUNCH; // Unsteady this.UnsteadyAttackerOnHit = Mod.Config.Melee.DFA.UnsteadyAttackerOnHit; this.UnsteadyAttackerOnMiss = Mod.Config.Melee.DFA.UnsteadyAttackerOnMiss; this.OnTargetMechHitForceUnsteady = Mod.Config.Melee.DFA.UnsteadyTargetOnHit; this.OnTargetVehicleHitEvasionPipsRemoved = Mod.Config.Melee.DFA.TargetVehicleEvasionPipsRemoved; // Set the animation type this.AttackAnimation = MeleeAttackType.DFA; } }
public static bool ResolvePilotInjuryCheck(Mech mech, int heatToCheck, int rootSequenceGUID, int sequenceGUID, float heatCheck) { bool failedInjuryCheck = !CheckHelper.DidCheckPassThreshold(Mod.Config.Heat.PilotInjury, heatToCheck, mech, heatCheck, ModText.FT_Check_Injury); Mod.Log.Debug?.Write($" failedInjuryCheck: {failedInjuryCheck}"); if (failedInjuryCheck) { Mod.Log.Info?.Write($"-- Pilot Heat Injury check failed for {CombatantUtils.Label(mech)}, forcing injury from heat"); mech.pilot.InjurePilot(sequenceGUID.ToString(), rootSequenceGUID, 1, DamageType.OverheatSelf, null, mech); if (!mech.pilot.IsIncapacitated) { AudioEventManager.SetPilotVOSwitch <AudioSwitch_dialog_dark_light>(AudioSwitch_dialog_dark_light.dark, mech); AudioEventManager.PlayPilotVO(VOEvents.Pilot_TakeDamage, mech, null, null, true); if (mech.team.LocalPlayerControlsTeam) { AudioEventManager.PlayAudioEvent("audioeventdef_musictriggers_combat", "friendly_warrior_injured", null, null); } } else { mech.FlagForDeath("Pilot Killed", DeathMethod.PilotKilled, DamageType.OverheatSelf, 1, sequenceGUID, "0", false); string localText = new Text(Mod.LocalizedText.Floaties[ModText.FT_Death_By_Overheat]).ToString(); mech.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage( new ShowActorInfoSequence(mech, new Text(localText, Array.Empty <object>()), FloatieMessage.MessageNature.PilotInjury, true)) ); mech.HandleDeath("0"); } } return(failedInjuryCheck); }
// WARNING: DUPLICATE OF HBS CODE. THIS IS LIKELY TO BREAK IF HBS CHANGES THE SOURCE FUNCTIONS public static float GetAdjustedSpotterRange(AbstractActor source, ICombatant target) { float targetVisibility = 1f; AbstractActor targetActor = target as AbstractActor; if (targetActor != null) { EWState sourceState = source.GetEWState(); targetVisibility = VisualLockHelper.GetTargetVisibility(targetActor, sourceState); } float spotterRange = VisualLockHelper.GetSpotterRange(source); float modifiedRange = spotterRange * targetVisibility; if (modifiedRange < Mod.Config.Vision.MinimumVisionRange()) { modifiedRange = Mod.Config.Vision.MinimumVisionRange(); } // Round up to the nearest full hex float normalizedRange = HexUtils.CountHexes(modifiedRange, true) * 30f; Mod.Log.Trace?.Write($" -- source:{CombatantUtils.Label(source)} adjusted spotterRange:{normalizedRange}m normalized from:{modifiedRange}m"); return(normalizedRange); }
// Prevent call to checkForHeatDamage private void setState(EmergencyShutdownState newState) { Mod.Log.Info?.Write($"MESS - Setting state to: {newState} for actor: {CombatantUtils.Label(this.OwningMech)}"); if (this.state == newState) { return; } this.state = newState; this.timeInCurrentState = 0f; if (newState == EmergencyShutdownState.ShuttingDown) { if (this.OwningMech.GameRep != null) { this.OwningMech.GameRep.PlayShutdownAnim(); } this.OwningMech.CancelCreatedEffects(); return; } if (newState != EmergencyShutdownState.Finished) { return; } Mech.heatLogger.Log("Mech " + this.OwningMech.DisplayName + " shuts down from overheating"); this.OwningMech.IsShutDown = true; this.OwningMech.DumpAllEvasivePips(); }
private static void Prefix(ActorMovementSequence __instance) { Mod.Log.Trace("AMS:OC entered"); // Interleaved - check for visibility to any enemies if (!__instance.owningActor.Combat.TurnDirector.IsInterleaved) { if (__instance.owningActor.Combat.LocalPlayerTeam.GetDetectedEnemyUnits().Count > 0) { Mod.Log.Info("AMS:OC TD is not interleaved but enemies are detected - disabling autobrace. "); __instance.owningActor.AutoBrace = false; } else { Mod.Log.Info("AMS:OC TD is not interleaved and no enemies - autobracing "); __instance.owningActor.AutoBrace = true; } } // Movement - check for damage after a sprint, and if so force a piloting check if (__instance.OwningMech != null && __instance.isSprinting && __instance.OwningMech.ActuatorDamageMalus() != 0) { Mod.Log.Debug($"Actor: {CombatantUtils.Label(__instance.OwningMech)} has actuator damage, forcing piloting check."); float sourceSkillMulti = __instance.OwningMech.PilotCheckMod(Mod.Config.Move.SkillMulti); bool sourcePassed = CheckHelper.DidCheckPassThreshold(Mod.Config.Move.FallAfterRunChance, __instance.OwningMech, sourceSkillMulti, ModConfig.FT_Fall_After_Run); if (!sourcePassed) { Mod.Log.Info($"Source actor: {CombatantUtils.Label(__instance.OwningMech)} failed pilot check after sprinting with actuator damage, forcing fall."); MechHelper.AddFallingSequence(__instance.OwningMech, __instance, ModConfig.FT_Fall_After_Run); } } }
// This assumes you're calling from a place that has already determined that we can reach the target. public static MeleeState GetMeleeStates(AbstractActor attacker, Vector3 attackPos, ICombatant target) { if (attacker == null || target == null) { Mod.MeleeLog.Warn?.Write("Null attacker or target - cannot melee!"); return(new MeleeState()); } Mech attackerMech = attacker as Mech; if (attackerMech == null) { Mod.MeleeLog.Warn?.Write("Vehicles and buildings cannot melee!"); return(new MeleeState()); } AbstractActor targetActor = target as AbstractActor; if (targetActor == null) { Mod.MeleeLog.Error?.Write("Target is not an abstractactor - must be building. Cannot melee!"); return(new MeleeState()); } Mod.MeleeLog.Info?.Write($"Building melee state for attacker: {CombatantUtils.Label(attacker)} against target: {CombatantUtils.Label(target)}"); MeleeState states = new MeleeState(attackerMech, attackPos, targetActor); Mod.MeleeLog.Info?.Write($" - valid attacks => charge: {states.Charge.IsValid} dfa: {states.DFA.IsValid} kick: {states.Kick.IsValid} " + $"weapon: {states.PhysicalWeapon.IsValid} punch: {states.Punch.IsValid}"); return(states); }
public static void Postfix(CombatHUDTargetingComputer __instance, List <LocalizableText> ___weaponNames, UIManager ___uiManager) { // Skip processing if we're not initialized properly if (__instance == null || __instance?.ActivelyShownCombatant?.Combat == null || __instance.WeaponList == null) { return; } if (__instance.ActivelyShownCombatant is BattleTech.Building building && ModState.AmbushBuildingGUIDToTurrets.ContainsKey(building.GUID)) { Mod.Log.Trace?.Write($"TargetingHUD target {CombatantUtils.Label(__instance.ActivelyShownCombatant)} is trapped enemy building"); // Replicate RefreshWeaponList here as it expects an AbstractActor Turret turret = ModState.AmbushBuildingGUIDToTurrets[building.GUID]; for (int i = 0; i < ___weaponNames.Count; i++) { if (turret != null && i < turret.Weapons.Count) { Weapon weapon = turret.Weapons[i]; ___weaponNames[i].SetText(weapon.UIName); if (!weapon.HasAmmo) { ___weaponNames[i].color = ___uiManager.UIColorRefs.qualityD; } else { switch (weapon.DamageLevel) { case ComponentDamageLevel.Functional: ___weaponNames[i].color = ___uiManager.UIColorRefs.qualityA; break; case ComponentDamageLevel.Misaligned: case ComponentDamageLevel.Penalized: ___weaponNames[i].color = ___uiManager.UIColorRefs.structureUndamaged; break; case ComponentDamageLevel.NonFunctional: case ComponentDamageLevel.Destroyed: ___weaponNames[i].color = ___uiManager.UIColorRefs.qualityD; break; } } if (!___weaponNames[i].transform.parent.gameObject.activeSelf) { ___weaponNames[i].transform.parent.gameObject.SetActive(true); } } else if (___weaponNames[i].transform.parent.gameObject.activeSelf) { ___weaponNames[i].transform.parent.gameObject.SetActive(false); } } __instance.WeaponList.SetActive(true); Transform weaponListT = __instance.WeaponList?.transform?.parent?.Find("tgtWeaponsLabel"); GameObject weaponsLabel = weaponListT.gameObject; weaponsLabel.SetActive(true); } }
public float ECMSignatureMod(EWState attackerState) { if (shieldedByECMMod <= 0) { return(0f); } int strength = shieldedByECMMod - attackerState.ProbeCarrierMod(); if (this.PingedByProbeMod() > 0) { strength -= this.PingedByProbeMod(); } if (attackerState.ProbeCarrierMod() > 0) { strength -= attackerState.ProbeCarrierMod(); } // Probe can reduce you to zero, but not further. strength = Math.Max(0, strength); float sigMod = strength * 0.1f; if (sigMod != 0) { Mod.Log.Trace?.Write($"Target:({CombatantUtils.Label(actor)}) has ECMSignatureMod:{sigMod}"); } return(sigMod); }
static void Postfix(AbstractActor __instance) { // TODO: Allow spawn on ally as well if (ModState.IsUrbanBiome && __instance.team.IsLocalPlayer && !__instance.Combat.TurnDirector.IsInterleaved && !__instance.Combat.TurnDirector.IsInterleavePending) { // Check that we haven't exhausted the max traps for this mission if (ModState.Ambushes >= Mod.Config.Ambush.MaxPerMap) { return; } // Validate that we are far enough away from trap origins to spawn another foreach (Vector3 ambushOrigin in ModState.AmbushOrigins) { float magnitude = (__instance.CurrentPosition - ambushOrigin).magnitude; if (magnitude < Mod.Config.Ambush.MinDistanceBetween) { Mod.Log.Debug?.Write($" Actor {CombatantUtils.Label(__instance)} at pos: {__instance.CurrentPosition} is {magnitude}m away from " + $"previous trap origin: {ambushOrigin}. Skipping."); return; } } // If we've made it this far, record it as a potential ambush site ModState.PotentialAmbushOrigins.Add(__instance.CurrentPosition); } }
public static void Postfix(CombatHUDStatusPanel __instance, Mech mech) { Mod.Log.Trace("CHUBSP:SSDI:POST entered."); // TODO: FIXME var type = __instance.GetType(); MethodInfo methodInfo = type.GetMethod("ShowDebuff", (BindingFlags.NonPublic | BindingFlags.Instance), null, new Type[] { typeof(SVGAsset), typeof(Text), typeof(Text), typeof(Vector3), typeof(bool) }, new ParameterModifier[5]); if (mech.IsShutDown) { Mod.Log.Debug($"Mech:{CombatantUtils.Label(mech)} is shutdown."); methodInfo.Invoke(__instance, new object[] { LazySingletonBehavior <UIManager> .Instance.UILookAndColorConstants.StatusShutDownIcon, new Text("SHUT DOWN", new object[0]), new Text("This target is easier to hit, and Called Shots can be made against this target.", new object[0]), __instance.defaultIconScale, false }); } else if (mech.IsOverheated) { float shutdownChance = 0; float ammoExplosionChance = 0; // FIXME: Remove this old code Mod.Log.Debug($"Mech:{CombatantUtils.Label(mech)} is overheated, shutdownChance:{shutdownChance}% ammoExplosionChance:{ammoExplosionChance}%"); string descr = string.Format("This unit may trigger a Shutdown at the end of the turn unless heat falls below critical levels." + "\nShutdown Chance: {0:P2}\nAmmo Explosion Chance: {1:P2}", shutdownChance, ammoExplosionChance); methodInfo.Invoke(__instance, new object[] { LazySingletonBehavior <UIManager> .Instance.UILookAndColorConstants.StatusOverheatingIcon, new Text("OVERHEATING", new object[0]), new Text(descr, new object[0]), __instance.defaultIconScale, false }); } }
private void CalculateDamages(Mech attacker, AbstractActor target, int hexesMoved) { Mod.MeleeLog.Info?.Write($"Calculating CHARGE damage for attacker: {CombatantUtils.Label(attacker)} " + $"vs. target: {CombatantUtils.Label(target)} at hexesMoved: {hexesMoved}"); float targetTonnage = 0; if (target is Vehicle vehicle) { targetTonnage = vehicle.tonnage; } else if (target is Mech mech) { targetTonnage = mech.tonnage; } else if (target is Turret turret) { targetTonnage = turret.MeleeTonnage(); } Mod.MeleeLog.Info?.Write($" - Tonnage => Attacker: {attacker.tonnage} Target: {targetTonnage}"); // Calculate attacker damage float attackerDamage = attacker.ChargeAttackerDamage(targetTonnage); attackerDamage = attacker.ApplyChargeDamageReduction(attackerDamage); DamageHelper.ClusterDamage(attackerDamage, Mod.Config.Melee.Charge.DamageClusterDivisor, out this.AttackerDamageClusters); // Resolve target damage - include movement! float targetDamage = attacker.ChargeTargetDamage(hexesMoved); targetDamage = target.ApplyChargeDamageReduction(targetDamage); DamageHelper.ClusterDamage(targetDamage, Mod.Config.Melee.Charge.DamageClusterDivisor, out this.TargetDamageClusters); }
// WARNING: DUPLICATE OF HBS CODE. THIS IS LIKELY TO BREAK IF HBS CHANGES THE SOURCE FUNCTIONS public static float GetSensorsRange(AbstractActor source) { if (source.StatCollection.ContainsStatistic(ModStats.DisableSensors)) { Mod.Log.Debug?.Write($"Returning minimum sensors range for {CombatantUtils.Label(source)} due to disabled sensors."); return(Mod.Config.Sensors.MinimumSensorRange()); } // Add multipliers and absolute bonuses EWState ewState = source.GetEWState(); Mod.Log.Trace?.Write($" == Sensors Range for for actor:{CombatantUtils.Label(source)}"); float rawRangeMulti = SensorLockHelper.GetAllSensorRangeMultipliers(source); float rangeMulti = rawRangeMulti + ewState.GetSensorsRangeMulti(); Mod.Log.Trace?.Write($" rangeMulti: {rangeMulti} = rawRangeMulti: {rawRangeMulti} + sensorCheckRangeMulti: {ewState.GetSensorsRangeMulti()}"); float rawRangeMod = SensorLockHelper.GetAllSensorRangeAbsolutes(source); float rangeMod = rawRangeMod * (1 + ewState.GetSensorsRangeMulti()); Mod.Log.Trace?.Write($" rangeMod: {rangeMod} = rawRangeMod: {rawRangeMod} + sensorCheckRangeMulti: {ewState.GetSensorsRangeMulti()}"); float sensorsRange = ewState.GetSensorsBaseRange() * rangeMulti + rangeMod; Mod.Log.Trace?.Write($" sensorsRange: { sensorsRange} = baseRange: {ewState.GetSensorsBaseRange()} * rangeMult: {rangeMulti} + rangeMod: {rangeMod}"); if (sensorsRange < Mod.Config.Sensors.MinimumSensorRange()) { sensorsRange = Mod.Config.Sensors.MinimumSensorRange(); } return(sensorsRange); }
private void CalculateInstability(Mech attacker, AbstractActor target, int hexesMoved) { Mod.MeleeLog.Info?.Write($"Calculating CHARGE instability for attacker: {CombatantUtils.Label(attacker)} " + $"vs. target: {CombatantUtils.Label(target)} at hexesMoved: {hexesMoved}"); float targetTonnage = 0; if (target is Vehicle vehicle) { targetTonnage = vehicle.tonnage; } else if (target is Mech mech) { targetTonnage = mech.tonnage; } Mod.MeleeLog.Info?.Write($" - Target tonnage is: {targetTonnage}"); // Resolve attacker instability float attackerInstab = attacker.ChargeAttackerInstability(targetTonnage, hexesMoved); attackerInstab = attacker.ApplyChargeInstabReduction(attackerInstab); this.AttackerInstability = attackerInstab; // Resolve target instability float targetInstab = attacker.ChargeTargetInstability(targetTonnage, hexesMoved); targetInstab = target.ApplyChargeInstabReduction(targetInstab); this.TargetInstability = targetInstab; }
public static bool Prefix(LineOfSight __instance, ref VisibilityLevel __result, AbstractActor source, Vector3 sourcePosition, ICombatant target, Vector3 targetPosition, Quaternion targetRotation) { Mod.Log.Trace?.Write($"LOS:GVTTWPAR: source:{CombatantUtils.Label(source)} ==> target:{CombatantUtils.Label(target)}"); // Skip if we aren't ready to process // TODO: Is this necessary anymore? //if (State.TurnDirectorStarted == false || (target as AbstractActor) == null) { return true; } AbstractActor sourceActor = source as AbstractActor; // TODO: Handle buildings here bool sourceHasLineOfSight = VisualLockHelper.CanSpotTarget(sourceActor, sourcePosition, target, targetPosition, targetRotation, __instance); if (sourceHasLineOfSight) { __result = VisibilityLevel.LOSFull; } else { VisibilityLevel sensorsVisibility = VisibilityLevel.None; if (ModState.TurnDirectorStarted) { SensorScanType sensorLock = SensorLockHelper.CalculateSensorLock(sourceActor, sourcePosition, target, targetPosition); sensorsVisibility = sensorLock.Visibility(); } __result = sensorsVisibility; } //Mod.Log.Trace?.Write($"LOS:GVTTWPAR - [{__result}] visibility for source:{CombatantUtils.Label(source)} ==> target:{CombatantUtils.Label(target)}"); return(false); }
public static void Prefix(AbstractActor __instance) { Mod.Log.Debug($" OnMoveComplete for Actor: {CombatantUtils.Label(__instance)}"); foreach (KeyValuePair <string, Statistic> kvp in __instance.StatCollection) { if (kvp.Key.EndsWith("_AH_SOURCES") || kvp.Key.EndsWith("_AH_VALUES")) { Mod.Log.Debug($" -- stat: ({kvp.Key}) has value: ({kvp.Value.Value<string>()})"); } } foreach (AbstractActor unit in __instance.team.units) { if (unit.GUID != __instance.GUID) { Mod.Log.Debug($" -- friendly actor: {CombatantUtils.Label(unit)}"); foreach (KeyValuePair <string, Statistic> kvp in unit.StatCollection) { if (kvp.Key.EndsWith("_AH_SOURCES") || kvp.Key.EndsWith("_AH_VALUES")) { Mod.Log.Debug($" -- stat: ({kvp.Key}) has value: ({kvp.Value.Value<string>()})"); } } } } }
public static void Postfix(AbstractActor __instance, Effect effect) { Mod.Log.Trace?.Write("AA:CancelEffect entered"); if (effect.EffectData.effectType == EffectType.StatisticEffect) { Mod.Log.Debug?.Write($" Cancelling effectId: '{effect.EffectData.Description.Id}' effectName: '{effect.EffectData.Description.Name}' " + $"on actor: '{CombatantUtils.Label(__instance)}' from creator: {effect.creatorID}"); if (effect.EffectData.effectType == EffectType.StatisticEffect && ModStats.IsStealthStat(effect.EffectData.statisticData.statName)) { Mod.Log.Debug?.Write(" - Stealth effect found, rebuilding visibility."); List <ICombatant> allLivingCombatants = __instance.Combat.GetAllLivingCombatants(); __instance.VisibilityCache.UpdateCacheReciprocal(allLivingCombatants); // TODO: Set current stealth pips? } if (ModStats.IsStealthStat(effect.EffectData.statisticData.statName)) { Mod.Log.Debug?.Write(" - Stealth effect found, rebuilding visibility."); List <ICombatant> allLivingCombatants = __instance.Combat.GetAllLivingCombatants(); __instance.VisibilityCache.UpdateCacheReciprocal(allLivingCombatants); // TODO: Set current stealth pips? } } }
private void CalculateDamages(Mech attacker, AbstractActor target) { Mod.MeleeLog.Info?.Write($"Calculating DFA damage for attacker: {CombatantUtils.Label(attacker)} " + $"vs. target: {CombatantUtils.Label(target)}"); float targetTonnage = 0; if (target is Vehicle vehicle) { targetTonnage = vehicle.tonnage; } else if (target is Mech mech) { targetTonnage = mech.tonnage; } else if (target is Turret turret) { targetTonnage = turret.MeleeTonnage(); } Mod.MeleeLog.Info?.Write($" - Tonnage => Attacker: {attacker.tonnage} Target: {targetTonnage}"); // split attacker damage into clusters float attackerDamage = attacker.DFAAttackerDamage(targetTonnage); attackerDamage = attacker.ApplyDFADamageReduction(attackerDamage); DamageHelper.ClusterDamage(attackerDamage, Mod.Config.Melee.DFA.DamageClusterDivisor, out this.AttackerDamageClusters); // split target damage into clusters float targetDamage = attacker.DFATargetDamage(); targetDamage = target.ApplyDFADamageReduction(targetDamage); DamageHelper.ClusterDamage(targetDamage, Mod.Config.Melee.DFA.DamageClusterDivisor, out this.TargetDamageClusters); }