public static void CombatHUDWeaponSlot_UpdateToolTipsFiring_Postfix(CombatHUDWeaponSlot __instance, ICombatant target, CombatGameState ___Combat, CombatHUD ___HUD, int ___modifier) { if (___HUD.SelectionHandler.ActiveState.SelectionType == SelectionType.FireMorale) { Mod.Log.Trace("CHUDWS:UTTF:Post entered."); AbstractActor attacker = ___HUD.SelectedActor; // Calculate called shot modifier int pilotValue = PilotHelper.GetCalledShotModifier(___HUD.SelectedActor.GetPilot()); int unitMod = 0; if (attacker.StatCollection.ContainsStatistic(ModStats.CalledShotMod)) { unitMod = attacker.StatCollection.GetStatistic(ModStats.CalledShotMod).Value <int>(); } int calledShotMod = pilotValue + unitMod; Mod.Log.Debug($" Called Shot from pilot:{attacker.GetPilot().Name} => pilotValue:{pilotValue} + unitMod:0 = calledShotMod:{calledShotMod}"); if (calledShotMod != 0) { AddMoraleToolTip(__instance, ___Combat.Constants.CombatUIConstants.MoraleAttackDescription.Name, calledShotMod); } } }
public static void ToHit_GetAllModifiers_Postfix(ToHit __instance, ref float __result, bool isCalledShot, AbstractActor attacker, Weapon weapon, ICombatant target) { if (isCalledShot) { Mod.Log.Trace("TH:GAM entered."); // Calculate called shot modifier int pilotValue = PilotHelper.GetCalledShotModifier(attacker.GetPilot()); int unitMod = 0; if (attacker.StatCollection.ContainsStatistic(ModStats.CalledShotMod)) { unitMod = attacker.StatCollection.GetStatistic(ModStats.CalledShotMod).Value <int>(); } int calledShotMod = pilotValue + unitMod; Mod.Log.Debug($" Called Shot from pilot:{attacker.GetPilot().Name} => pilotValue:{pilotValue} + unitMod:{unitMod} = calledShotMod:{calledShotMod}"); __result = __result + calledShotMod; } }
public static void ToHit_GetAllModifiersDescription_Postfix(ToHit __instance, ref string __result, bool isCalledShot, AbstractActor attacker, Weapon weapon, ICombatant target) { if (isCalledShot) { Mod.Log.Trace("TH:GAMD entered."); // Calculate called shot modifier int pilotValue = PilotHelper.GetCalledShotModifier(attacker.GetPilot()); int unitMod = 0; if (attacker.StatCollection.ContainsStatistic(ModStats.CalledShotMod)) { unitMod = attacker.StatCollection.GetStatistic(ModStats.CalledShotMod).Value <int>(); } int calledShotMod = pilotValue + unitMod; Mod.Log.Debug($" Called Shot from pilot:{attacker.GetPilot().Name} => pilotValue:{pilotValue} + unitMod:{unitMod} = calledShotMod:{calledShotMod}"); if (calledShotMod != 0) { __result = string.Format("{0}CALLED-SHOT {1:+#;-#}; ", __result, (int)calledShotMod); } } }
static bool Prefix(PoorlyMaintainedEffect __instance, Mech targetMech) { Mod.Log.Trace?.Write("PME:AETM - entered."); // Note that OnEffectBegin will invoke *every* ApplyEffects, and expects the ApplyEfect to check that the target isn't null. if (targetMech == null) { return(false); } Mod.Log.Info?.Write($" Applying PoorlyMaintainedEffect to unit: {CombatantUtils.Label(targetMech)}"); ModState.SuppressShowActorSequences = true; WeaponHitInfo hitInfo = new WeaponHitInfo(-1, -1, -1, -1, "", "", -1, null, null, null, null, null, null, null, new AttackDirection[] { AttackDirection.FromFront }, null, null, null); // Apply any component damage first StringBuilder componentDamageSB = new StringBuilder(); MechRepairState repairState = new MechRepairState(__instance, targetMech); foreach (MechComponent mc in repairState.DamagedComponents) { if (mc.componentType == ComponentType.AmmunitionBox) { AmmunitionBox ab = (AmmunitionBox)mc; float ammoReduction = Mod.Random.Next( (int)(Mod.Config.PerHitPenalties.MinAmmoRemaining * 100f), (int)(Mod.Config.PerHitPenalties.MaxAmmoRemaining * 100f) ) / 100f; int newAmmo = (int)Math.Floor(ab.CurrentAmmo * ammoReduction); Mod.Log.Info?.Write($"Reducing ammoBox: {mc.UIName} from {ab.CurrentAmmo} x {ammoReduction} = {newAmmo}"); ab.StatCollection.Set <int>(ModStats.AmmoBoxCurrentAmmo, newAmmo); } else { Mod.Log.Info?.Write($"Damaging component: {mc.UIName}"); mc.DamageComponent(hitInfo, ComponentDamageLevel.Destroyed, false); } Text localText = new Text(" - {0}\n", new object[] { mc.UIName }); componentDamageSB.Append(localText.ToString()); } int armorOrStructHeadHits = 0; HashSet <ArmorLocation> armorHitLocs = new HashSet <ArmorLocation>(); // Then apply any armor hits for (int i = 0; i < repairState.ArmorHits; i++) { ArmorLocation location = LocationHelper.GetRandomMechArmorLocation(); armorHitLocs.Add(location); float maxArmor = targetMech.GetMaxArmor(location); float maxDamageRatio = Mod.Random.Next( (int)(Mod.Config.PerHitPenalties.MinArmorLoss * 100), (int)(Mod.Config.PerHitPenalties.MaxArmorLoss * 100) ) / 100f; float damage = (float)Math.Floor(maxArmor * maxDamageRatio); if (targetMech.GetCurrentArmor(location) - damage < 0) { damage = targetMech.GetCurrentArmor(location); } Mod.Log.Info?.Write($"Reducing armor in location {location} by {maxDamageRatio}% for {damage} points"); if (damage != 0) { targetMech.StatCollection.ModifyStat <float>(hitInfo.attackerId, hitInfo.stackItemUID, targetMech.GetStringForArmorLocation(location), StatCollection.StatOperation.Float_Subtract, damage, -1, true); } if (location == ArmorLocation.Head) { armorOrStructHeadHits++; } } // We don't limit to armor damage locations here so we can represent that armor is easily scavenged HashSet <ChassisLocations> structHitLocs = new HashSet <ChassisLocations>(); for (int i = 0; i < repairState.StructureHits; i++) { ChassisLocations location = LocationHelper.GetRandomMechStructureLocation(); structHitLocs.Add(location); float maxStructure = targetMech.GetMaxStructure(location); float maxDamageRatio = Mod.Random.Next( (int)(Mod.Config.PerHitPenalties.MinStructureLoss * 100), (int)(Mod.Config.PerHitPenalties.MaxStructureLoss * 100) ) / 100f; float damage = (float)Math.Floor(maxStructure * maxDamageRatio); if (targetMech.GetCurrentStructure(location) - damage < 1) { // Never allow a hit to completely remove a limb or location damage = targetMech.GetCurrentStructure(location) - 1; } Mod.Log.Info?.Write($"Reducing structure in location {location} by {maxDamageRatio}% for {damage} points"); if (damage != 0) { targetMech.StatCollection.ModifyStat <float>(hitInfo.attackerId, hitInfo.stackItemUID, targetMech.GetStringForStructureLocation(location), StatCollection.StatOperation.Float_Subtract, damage, -1, true); } targetMech.UpdateLocationDamageLevel(location, hitInfo.attackerId, hitInfo.stackItemUID); if (location == ChassisLocations.Head) { armorOrStructHeadHits++; } } PilotHelper.ApplyPilotHealthDamage(targetMech, hitInfo, armorOrStructHeadHits, out string pilotHealthTooltipText); PilotHelper.ApplyPilotSkillDamage(targetMech, hitInfo, repairState.PilotSkillHits, out string pilotSkillDamageTooltipText); // Build the tooltip StringBuilder descSB = new StringBuilder(); if (repairState.ArmorHits > 0) { Text localText = new Text(Mod.Config.LocalizedText[ModConfig.LT_TT_DAMAGE_ARMOR]); descSB.Append(localText.ToString()); foreach (ArmorLocation hitLoc in armorHitLocs) { Text locationText = new Text(" - {0}\n", new object[] { hitLoc }); descSB.Append(locationText.ToString()); } } if (repairState.StructureHits > 0) { Text localText = new Text(Mod.Config.LocalizedText[ModConfig.LT_TT_DAMAGE_STRUCTURE]); descSB.Append(localText.ToString()); foreach (ChassisLocations hitLoc in structHitLocs) { Text locationText = new Text(" - {0}\n", new object[] { hitLoc }); descSB.Append(locationText.ToString()); } } if (componentDamageSB.Length > 0) { Text localText = new Text(Mod.Config.LocalizedText[ModConfig.LT_TT_DAMAGE_COMP]); descSB.Append(localText.ToString()); descSB.Append(componentDamageSB.ToString()); } if (pilotHealthTooltipText != null) { Text localText = new Text(Mod.Config.LocalizedText[ModConfig.LT_TT_DAMAGE_PILOT]); descSB.Append(localText.ToString()); descSB.Append(pilotHealthTooltipText); } if (pilotSkillDamageTooltipText != null) { Text localText = new Text(Mod.Config.LocalizedText[ModConfig.LT_TT_DAMAGE_SKILL]); descSB.Append(localText.ToString()); descSB.Append(pilotSkillDamageTooltipText.ToString()); } Text titleText = new Text(ModState.CurrentTheme.Label, new object[] { repairState.effectRating }); __instance.EffectData.Description = new BaseDescriptionDef("PoorlyMaintained", titleText.ToString(), descSB.ToString(), __instance.EffectData.Description.Icon); ModState.SuppressShowActorSequences = false; return(false); }
private static string BuildTooltipText(AbstractActor actor) { ActorInitiative actorInit = ActorInitiativeHolder.GetOrCreate(actor); // -- Mech List <string> mechDetails = new List <string> { }; Mech actorMech = actor as Mech; float tonnage = actorMech.MechDef.Chassis.Tonnage; int tonnageMod = UnitHelper.GetTonnageModifier(tonnage); mechDetails.Add($"MECH => tonnage:{tonnageMod}"); int expectedInitMax = tonnageMod; // Any modifiers that come from the chassis/mech/vehicle defs int componentsMod = UnitHelper.GetNormalizedComponentModifier(actor); if (componentsMod > 0) { mechDetails.Add($"<color=#00FF00>{componentsMod:+0} components</color>"); } else if (componentsMod < 0) { mechDetails.Add($"<color=#FF0000>{componentsMod:0} components</color>"); } expectedInitMax += componentsMod; // Modifier from the engine int engineMod = UnitHelper.GetEngineModifier(actor); if (engineMod > 0) { mechDetails.Add($"<color=#00FF00>{engineMod:+0} engine</color>"); } else if (engineMod < 0) { mechDetails.Add($"<color=#FF0000>{engineMod:0} engine</color>"); } expectedInitMax += engineMod; // Check for leg / side loss Mech mech = (Mech)actor; if (mech.IsLocationDestroyed(ChassisLocations.LeftLeg) || mech.IsLocationDestroyed(ChassisLocations.RightLeg)) { int rawMod = Mod.Config.CrippledMovementModifier + actorInit.pilotingEffectMod; int penalty = Math.Min(-1, rawMod); mechDetails.Add($"<color=#FF0000>{penalty} Leg Destroyed</color>"); expectedInitMax += penalty; } // Check for prone if (actor.IsProne) { int rawMod = Mod.Config.ProneModifier + actorInit.pilotingEffectMod; int penalty = Math.Min(-1, rawMod); mechDetails.Add($"<color=#FF0000>{penalty} Prone</color>"); expectedInitMax += penalty; } // Check for shutdown if (actor.IsShutDown) { int rawMod = Mod.Config.ShutdownModifier + actorInit.pilotingEffectMod; int penalty = Math.Min(-1, rawMod); mechDetails.Add($"<color=#FF0000>{penalty} Shutdown</color>"); expectedInitMax += penalty; } // Check for melee impacts if (actorInit.lastRoundMeleeMod > 0) { expectedInitMax -= actorInit.lastRoundMeleeMod; mechDetails.Add($"<color=#FF0000>-{actorInit.lastRoundMeleeMod} Melee Impact</color>"); } // --- PILOT --- List <string> pilotDetails = new List <string> { }; Pilot selectedPilot = actor.GetPilot(); int tacticsMod = PilotHelper.GetTacticsModifier(selectedPilot); pilotDetails.Add($"PILOT => Tactics: <color=#00FF00>{tacticsMod:+0}</color>"); expectedInitMax += tacticsMod; int pilotTagsMod = PilotHelper.GetTagsModifier(selectedPilot); pilotDetails.AddRange(PilotHelper.GetTagsModifierDetails(selectedPilot)); expectedInitMax += pilotTagsMod; int[] randomnessBounds = PilotHelper.GetRandomnessBounds(selectedPilot); int expectedInitRandMin = randomnessBounds[0]; int expectedInitRandMax = randomnessBounds[1]; // Check for inspired status if (actor.IsMoraleInspired || actor.IsFuryInspired) { pilotDetails.Add($"<color=#00FF00>+1 to +3 Inspired</color>"); expectedInitRandMin += 1; expectedInitRandMin += 3; } // Check for injuries. If there injuries on the previous round, apply them in full force. Otherwise, reduce them. if (actorInit.lastRoundInjuryMod != 0) { pilotDetails.Add($"<color=#FF0000>-{actorInit.lastRoundInjuryMod} Fresh Injury</color>"); expectedInitMax -= actorInit.lastRoundInjuryMod; } else if (actor.GetPilot().Injuries != 0) { // TODO: fold this in int rawPenalty = actorInit.CalculateInjuryPenalty(0, actor.GetPilot().Injuries); int penalty = (int)Math.Ceiling(rawPenalty / 2.0); pilotDetails.Add($"<color=#FF0000>-{penalty} Painful Injury</color>"); expectedInitMax -= penalty; } // Check for an overly cautious player if (actorInit.lastRoundHesitationPenalty > 0) { expectedInitMax -= actorInit.lastRoundHesitationPenalty; pilotDetails.Add($"<color=#FF0000>-{actorInit.lastRoundHesitationPenalty} Hesitation</color>"); } // Check for called shot if (actorInit.lastRoundCalledShotMod > 0) { expectedInitMax -= actorInit.lastRoundCalledShotMod; pilotDetails.Add($"<color=#FF0000>-{actorInit.lastRoundCalledShotMod} Called Shot Target</color>"); } // Check for vigilance if (actorInit.lastRoundVigilanceMod > 0) { expectedInitMax -= actorInit.lastRoundVigilanceMod; pilotDetails.Add($"<color=#00FF00>-{actorInit.lastRoundVigilanceMod} Vigilance</color>"); } // Finally, randomness bounds pilotDetails.Add($"\nRandom (tactics): <color=#FF0000>-{actorInit.randomnessBounds[0]} to -{actorInit.randomnessBounds[1]}</color>"); int maxInit = Math.Max(expectedInitMax - expectedInitRandMin, Mod.MinPhase); int minInit = Math.Max(expectedInitMax - expectedInitRandMax, Mod.MinPhase); List <string> toolTipDetails = new List <string> { }; toolTipDetails.Add(String.Join(", ", mechDetails.ToArray())); toolTipDetails.Add(String.Join(", ", pilotDetails.ToArray())); toolTipDetails.Add($"Expected Phase: <b>{maxInit} to {minInit}</b>"); toolTipDetails.Add($"Hover over init in Mechlab/Deploy for details."); string tooltipText = String.Join("\n", toolTipDetails.ToArray()); return(tooltipText); }
public ActorInitiative(AbstractActor actor) { //SkillBasedInit.Logger.Log($"Initializing ActorInitiative for {actor.DisplayName} with GUID {actor.GUID}."); if (actor.GetType() == typeof(Mech)) { this.type = ActorType.Mech; } else if (actor.GetType() == typeof(Vehicle)) { this.type = ActorType.Vehicle; } else { this.type = ActorType.Turret; } // --- UNIT IMPACTS --- // Static initiative from tonnage float tonnage = UnitHelper.GetUnitTonnage(actor); int tonnageMod = UnitHelper.GetTonnageModifier(tonnage); // Any special modifiers by type int typeMod = UnitHelper.GetTypeModifier(actor); // Any modifiers that come from the chassis/mech/vehicle defs int componentsMod = UnitHelper.GetNormalizedComponentModifier(actor); // Modifier from the engine int engineMod = UnitHelper.GetEngineModifier(actor); // --- PILOT IMPACTS --- Pilot pilot = actor.GetPilot(); PilotHelper.LogPilotStats(pilot); // Normalize skills so that values above 10 don't screw the system this.gunneryEffectMod = PilotHelper.GetGunneryModifier(pilot); this.gutsEffectMod = PilotHelper.GetGutsModifier(pilot); this.injuryBounds = PilotHelper.GetInjuryBounds(pilot); this.pilotingEffectMod = PilotHelper.GetPilotingModifier(pilot); this.randomnessBounds = PilotHelper.GetRandomnessBounds(pilot); this.tacticsEffectMod = PilotHelper.GetTacticsModifier(pilot); int pilotTagsMod = PilotHelper.GetTagsModifier(pilot); this.calledShotMod = PilotHelper.GetCalledShotModifier(pilot); this.vigilianceMod = PilotHelper.GetVigilanceModifier(pilot); // --- COMBO IMPACTS -- // Determine the melee modifier int[] meleeMods = PilotHelper.GetMeleeModifiers(pilot, tonnage); this.meleeAttackMod = meleeMods[0]; this.meleeDefenseMod = meleeMods[1]; Mod.Log.Debug($"Actor:{actor.DisplayName}_{pilot.Name} has meleeAttackMod:{meleeAttackMod} meleeDefenseMod:{meleeDefenseMod}"); // Log the full view for testing roundInitBase = tonnageMod; roundInitBase += typeMod; roundInitBase += componentsMod; roundInitBase += engineMod; roundInitBase += tacticsEffectMod; roundInitBase += pilotTagsMod; Mod.Log.Info($"Actor:{actor.DisplayName}_{pilot.Name} has " + $"roundInitBase:{roundInitBase} = (tonnage:{tonnageMod} + typeMod:{typeMod} + components:{componentsMod} + engine:{engineMod} " + $"tactics:{tacticsEffectMod} + pilotTags:{pilotTagsMod}) " + $"randomness:({randomnessBounds[0]}-{randomnessBounds[1]}) " + $"injuryBounds:({injuryBounds[0]}-{injuryBounds[1]}) " + $"gutsMod:{gutsEffectMod} pilotingMod:{pilotingEffectMod} tacticsMod:{tacticsEffectMod}"); }
public static void Postfix(LanceLoadoutSlot __instance, GameObject ___initiativeObj, TextMeshProUGUI ___initiativeText, UIColorRefTracker ___initiativeColor, HBSTooltip ___initiativeTooltip, LanceConfiguratorPanel ___LC) { if (___initiativeObj == null || ___initiativeText == null || ___initiativeColor == null || ___initiativeTooltip == null) { return; } //SkillBasedInit.Logger.Log($"LanceLoadoutSlot::RefreshInitiativeData::post - disabling text"); bool bothSelected = __instance.SelectedMech != null && __instance.SelectedPilot != null; if (!bothSelected) { ___initiativeText.SetText("-"); ___initiativeColor.SetUIColor(UIColor.MedGray); } else { int initValue = 0; // --- MECH --- MechDef selectedMechDef = __instance.SelectedMech.MechDef; List <string> details = new List <string>(); // Static initiative from tonnage float tonnage = __instance.SelectedMech.MechDef.Chassis.Tonnage; int tonnageMod = UnitHelper.GetTonnageModifier(tonnage); initValue += tonnageMod; details.Add($"Tonnage Base: {tonnageMod}"); // Any special modifiers by type - NA, Mech is the only type // Any modifiers that come from the chassis/mech/vehicle defs int componentsMod = UnitHelper.GetNormalizedComponentModifier(selectedMechDef); initValue += componentsMod; if (componentsMod > 0) { details.Add($"<space=2em><color=#00FF00>{componentsMod:+0} components</color>"); } else if (componentsMod < 0) { details.Add($"<space=2em><color=#FF0000>{componentsMod:0} components</color>"); } // Modifier from the engine int engineMod = UnitHelper.GetEngineModifier(selectedMechDef); initValue += engineMod; if (engineMod > 0) { details.Add($"<space=2em><color=#00FF00>{engineMod:+0} engine</color>"); } else if (engineMod < 0) { details.Add($"<space=2em><color=#FF0000>{engineMod:0} engine</color>"); } // --- PILOT --- Pilot selectedPilot = __instance.SelectedPilot.Pilot; int tacticsMod = PilotHelper.GetTacticsModifier(selectedPilot); details.Add($"<space=2em>{tacticsMod:+0} tactics"); initValue += tacticsMod; int pilotTagsMod = PilotHelper.GetTagsModifier(selectedPilot); details.AddRange(PilotHelper.GetTagsModifierDetails(selectedPilot)); initValue += pilotTagsMod; int[] randomnessBounds = PilotHelper.GetRandomnessBounds(selectedPilot); // --- LANCE --- if (___LC != null) { initValue += ___LC.lanceInitiativeModifier; if (___LC.lanceInitiativeModifier > 0) { details.Add($"<space=2em><color=#00FF00>{___LC.lanceInitiativeModifier:+0} lance</color>"); } else if (___LC.lanceInitiativeModifier < 0) { details.Add($"<space=2em><color=#FF0000>{___LC.lanceInitiativeModifier:0} lance</color>"); } } // --- Badge --- ___initiativeText.SetText($"{initValue}"); ___initiativeText.color = Color.black; ___initiativeColor.SetUIColor(UIColor.White); // --- Tooltip --- int maxInit = Math.Max(initValue - randomnessBounds[0], Mod.MinPhase); int minInit = Math.Max(initValue - randomnessBounds[1], Mod.MinPhase); details.Add($"Total:{initValue}"); details.Add($"<space=2em><color=#FF0000>-{randomnessBounds[0]} to -{randomnessBounds[1]} randomness</color> (piloting)"); details.Add($"<b>Expected Phase<b>: {maxInit} to {minInit}"); string tooltipTitle = $"{selectedMechDef.Name}: {selectedPilot.Name}"; string tooltipText = String.Join("\n", details.ToArray()); BaseDescriptionDef initiativeData = new BaseDescriptionDef("LLS_MECH_TT", tooltipTitle, tooltipText, null); ___initiativeTooltip.enabled = true; ___initiativeTooltip.SetDefaultStateData(TooltipUtilities.GetStateDataFromObject(initiativeData)); } }