From 02015a6886512b5cdcc421e9f49923fc0ddc1df0 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 6 Apr 2025 18:28:34 -0500 Subject: [PATCH] Added ATOW Toughness Rule And Updated Fatigue And Injury Logic - Introduced the ATOW Toughness option for personnel, providing reduced fatigue gain and injury severity. - Updated fatigue calculation logic to account for the new Toughness option when combined with the Glass Jaw flaw. - Ensured Glass Jaw and Toughness cannot coexist due to their opposing effects. - Applied modified fatigue and injury calculations across multiple classes, including `StratconRulesManager`, `ResolveScenarioTracker`, and `EventEffectsManager`. - Refactored redundant logic and improved readability of fatigue and injury handling. - Updated `defaultspa.xml` to define ATOW Toughness attributes and prevent coexistence with Glass Jaw. --- MekHQ/data/universe/defaultspa.xml | 20 ++++++-- .../campaign/ResolveScenarioTracker.java | 35 ++++++++++---- .../campaign/personnel/PersonnelOptions.java | 2 + .../education/TrainingCombatTeams.java | 12 ++++- .../prisoners/EventEffectsManager.java | 46 ++++++++++++++++--- .../stratcon/StratconRulesManager.java | 15 +++++- 6 files changed, 107 insertions(+), 23 deletions(-) diff --git a/MekHQ/data/universe/defaultspa.xml b/MekHQ/data/universe/defaultspa.xml index 21dd7fc6fa..524931f726 100644 --- a/MekHQ/data/universe/defaultspa.xml +++ b/MekHQ/data/universe/defaultspa.xml @@ -464,10 +464,20 @@ flaw_glass_jaw Glass Jaw (ATOW) - All injuries suffered and fatigue gained is doubled + All injuries suffered and fatigue gained is doubled. Fatigue gain from sustaining injuries is not doubled. -300 1 + atow_toughness + + + atow_toughness + Toughness (ATOW) + All injuries suffered are reduced by 75% and Fatigue gain is halved. Fatigue gain from sustaining injuries is not halved. + 300 + 1 + + flaw_glass_jaw eagle_eyes @@ -541,9 +551,9 @@ 1 Unit Reputation is decreased by 1 if this character is the campaign commander. -A pilot who has Combat Sense rolls three dice per initiative check, keeping the top two rolls. + A pilot who has Combat Sense rolls three dice per initiative check, keeping the top two rolls. -If individual initiative is enabled, this penalty applies only to the pilot's unit. Otherwise, the penalty is applied only if the pilot's unit is the force commander. + If individual initiative is enabled, this penalty applies only to the pilot's unit. Otherwise, the penalty is applied only if the pilot's unit is the force commander. atow_combat_sense @@ -554,9 +564,9 @@ If individual initiative is enabled, this penalty applies only to the pilot's un 1 Unit Reputation is increased by 1 if this character is the campaign commander. -A pilot who has Combat Sense rolls three dice per initiative check, keeping the top two rolls. + A pilot who has Combat Sense rolls three dice per initiative check, keeping the top two rolls. -If individual initiative is enabled, this bonus applies only to the pilot's unit. Otherwise, the bonus is applied only if the pilot's unit is the force commander. + If individual initiative is enabled, this bonus applies only to the pilot's unit. Otherwise, the bonus is applied only if the pilot's unit is the force commander. Gunnery/Mek::Veteran diff --git a/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java b/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java index 27f50547a2..c58e4f2787 100644 --- a/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java +++ b/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java @@ -28,8 +28,10 @@ */ package mekhq.campaign; +import static java.lang.Math.ceil; import static mekhq.campaign.mission.Scenario.T_SPACE; import static mekhq.campaign.parts.enums.PartQuality.QUALITY_D; +import static mekhq.campaign.personnel.PersonnelOptions.ATOW_TOUGHNESS; import static mekhq.campaign.personnel.PersonnelOptions.FLAW_GLASS_JAW; import java.io.File; @@ -841,7 +843,7 @@ private void processLargeCraft(Unit ship, Entity en, List personnel, Uni currentHits = 6; } int newHits = Math.max(0, currentHits - existingHits); - casualties = (int) Math.ceil(Compute.getFullCrewSize(en) * (newHits / 6.0)); + casualties = (int) ceil(Compute.getFullCrewSize(en) * (newHits / 6.0)); // Now reduce the casualties if some "hits" were caused by ejection casualties = Math.max(0, casualties - rescuedCrew); @@ -1079,7 +1081,7 @@ private void processPrisonerCapture(List unitsToProcess) { currentHits = entity.getCrew().getHits(); } int newHits = Math.max(0, currentHits - existingHits); - casualties = (int) Math.ceil(Compute.getFullCrewSize(entity) * (newHits / 6.0)); + casualties = (int) ceil(Compute.getFullCrewSize(entity) * (newHits / 6.0)); } for (Person person : crew) { @@ -1547,17 +1549,25 @@ public void resolveScenario(ScenarioStatus resolution, String report) { int statusHits = status.getHits(); int priorHits = person.getHits(); int newHits = statusHits - priorHits; - int extraHits = 0; + int adjustedHits = 0; boolean hasGlassJaw = person.getOptions().booleanOption(FLAW_GLASS_JAW); + boolean hasToughness = person.getOptions().booleanOption(ATOW_TOUGHNESS); + boolean hasGlassJawAndToughness = hasGlassJaw && hasToughness; - if (hasGlassJaw) { - extraHits = newHits; + if (hasGlassJaw && !hasGlassJawAndToughness) { + adjustedHits = newHits * 2; + } else if (hasToughness && !hasGlassJawAndToughness) { + adjustedHits = (int) ceil(newHits * 0.75); } if (campaign.getCampaignOptions().isUseInjuryFatigue()) { int fatigueRate = campaign.getCampaignOptions().getFatigueRate(); - int fatigueIncrease = (hasGlassJaw ? fatigueRate * 2 : fatigueRate) * (newHits + extraHits); + int fatigueIncrease = newHits * fatigueRate; + + if ((hasGlassJaw || hasToughness) && !hasGlassJawAndToughness) { + fatigueIncrease = adjustedHits; + } person.changeFatigue(fatigueIncrease); @@ -1565,7 +1575,7 @@ public void resolveScenario(ScenarioStatus resolution, String report) { } person.setHitsPrior(priorHits); - person.setHits(statusHits + extraHits); + person.setHits(statusHits + adjustedHits); } if (status.wasDeployed()) { @@ -1595,9 +1605,18 @@ public void resolveScenario(ScenarioStatus resolution, String report) { if (!status.isDead()) { int fatigueChangeRate = campaign.getCampaignOptions().getFatigueRate(); + boolean hasGlassJaw = person.getOptions().booleanOption(FLAW_GLASS_JAW); + boolean hasToughness = person.getOptions().booleanOption(ATOW_TOUGHNESS); + boolean hasGlassJawAndToughness = hasGlassJaw && hasToughness; + + if (hasGlassJaw && !hasGlassJawAndToughness) { + fatigueChangeRate = fatigueChangeRate * 2; + } else if (hasToughness && !hasGlassJawAndToughness) { + fatigueChangeRate = (int) ceil(fatigueChangeRate * 0.75); + } - person.changeFatigue(hasGlassJaw ? fatigueChangeRate * 2 : fatigueChangeRate); + person.changeFatigue(fatigueChangeRate); if (campaign.getCampaignOptions().isUseFatigue()) { Fatigue.processFatigueActions(campaign, person); diff --git a/MekHQ/src/mekhq/campaign/personnel/PersonnelOptions.java b/MekHQ/src/mekhq/campaign/personnel/PersonnelOptions.java index 706898bc44..f07cca120b 100644 --- a/MekHQ/src/mekhq/campaign/personnel/PersonnelOptions.java +++ b/MekHQ/src/mekhq/campaign/personnel/PersonnelOptions.java @@ -63,6 +63,7 @@ public class PersonnelOptions extends PilotOptions { public static final String TECH_FIXER = "tech_fixer"; public static final String TECH_MAINTAINER = "tech_maintainer"; public static final String FLAW_GLASS_JAW = "flaw_glass_jaw"; + public static final String ATOW_TOUGHNESS = "atow_toughness"; @Override public void initialize() { @@ -107,6 +108,7 @@ public void initialize() { addOption(l3a, TECH_FIXER, false); addOption(l3a, TECH_MAINTAINER, false); addOption(l3a, FLAW_GLASS_JAW, false); + addOption(l3a, ATOW_TOUGHNESS, false); addOption(edge, EDGE_MEDICAL, true); addOption(edge, EDGE_REPAIR_BREAK_PART, true); diff --git a/MekHQ/src/mekhq/campaign/personnel/education/TrainingCombatTeams.java b/MekHQ/src/mekhq/campaign/personnel/education/TrainingCombatTeams.java index f524f70d64..3da0e9bfbd 100644 --- a/MekHQ/src/mekhq/campaign/personnel/education/TrainingCombatTeams.java +++ b/MekHQ/src/mekhq/campaign/personnel/education/TrainingCombatTeams.java @@ -28,6 +28,7 @@ package mekhq.campaign.personnel.education; import static java.lang.Math.round; +import static mekhq.campaign.personnel.PersonnelOptions.ATOW_TOUGHNESS; import static mekhq.campaign.personnel.PersonnelOptions.FLAW_GLASS_JAW; import static mekhq.campaign.personnel.skills.SkillType.EXP_GREEN; import static mekhq.utilities.ReportingUtilities.CLOSING_SPAN_TAG; @@ -170,8 +171,17 @@ private static void performTraining(Campaign campaign, Force force, Person comma for (Person trainee : unit.getActiveCrew()) { if (campaign.getCampaignOptions().isUseFatigue()) { - int fatigueChangeRate = campaign.getCampaignOptions().getFatigueRate(); + int fatigueChangeRate = campaign.getCampaignOptions().getFatigueRate() * 2; + boolean hasGlassJaw = trainee.getOptions().booleanOption(FLAW_GLASS_JAW); + boolean hasToughness = trainee.getOptions().booleanOption(ATOW_TOUGHNESS); + boolean hasGlassJawAndToughness = hasGlassJaw && hasToughness; + + if (hasGlassJaw && !hasGlassJawAndToughness) { + fatigueChangeRate = fatigueChangeRate * 2; + } else if (hasToughness && !hasGlassJawAndToughness) { + fatigueChangeRate = (int) round(fatigueChangeRate * 0.5); + } trainee.changeFatigue(fatigueChangeRate * (hasGlassJaw ? 4 : 2)); } diff --git a/MekHQ/src/mekhq/campaign/randomEvents/prisoners/EventEffectsManager.java b/MekHQ/src/mekhq/campaign/randomEvents/prisoners/EventEffectsManager.java index 6e77348176..8c2bd51f65 100644 --- a/MekHQ/src/mekhq/campaign/randomEvents/prisoners/EventEffectsManager.java +++ b/MekHQ/src/mekhq/campaign/randomEvents/prisoners/EventEffectsManager.java @@ -30,10 +30,12 @@ import static java.lang.Math.ceil; import static java.lang.Math.max; import static java.lang.Math.min; +import static java.lang.Math.round; import static megamek.codeUtilities.MathUtility.clamp; import static megamek.codeUtilities.ObjectUtility.getRandomItem; import static megamek.common.Compute.d6; import static mekhq.campaign.force.ForceType.SECURITY; +import static mekhq.campaign.personnel.PersonnelOptions.ATOW_TOUGHNESS; import static mekhq.campaign.personnel.PersonnelOptions.FLAW_GLASS_JAW; import static mekhq.campaign.personnel.enums.PersonnelRole.DEPENDENT; import static mekhq.campaign.personnel.enums.PersonnelRole.NONE; @@ -60,10 +62,10 @@ import mekhq.campaign.mission.AtBContract; import mekhq.campaign.mission.enums.AtBMoraleLevel; import mekhq.campaign.personnel.Person; -import mekhq.campaign.personnel.skills.Skill; -import mekhq.campaign.personnel.skills.SkillType; import mekhq.campaign.personnel.enums.PersonnelRole; import mekhq.campaign.personnel.enums.PersonnelStatus; +import mekhq.campaign.personnel.skills.Skill; +import mekhq.campaign.personnel.skills.SkillType; import mekhq.campaign.personnel.turnoverAndRetention.Fatigue; import mekhq.campaign.randomEvents.prisoners.enums.EventResultEffect; import mekhq.campaign.randomEvents.prisoners.enums.PrisonerEvent; @@ -786,7 +788,7 @@ private String eventEffectFatigueOne(EventResult result) { } final boolean isGuard = result.isGuard(); - final int magnitude = result.magnitude(); + int magnitude = result.magnitude(); Person target = getRandomTarget(isGuard); @@ -795,7 +797,16 @@ private String eventEffectFatigueOne(EventResult result) { } boolean hasGlassJaw = target.getOptions().booleanOption(FLAW_GLASS_JAW); - target.changeFatigue(hasGlassJaw ? magnitude * 2 : magnitude); + boolean hasToughness = target.getOptions().booleanOption(ATOW_TOUGHNESS); + boolean hasGlassJawAndToughness = hasGlassJaw && hasToughness; + + if (hasGlassJaw && !hasGlassJawAndToughness) { + magnitude = magnitude * 2; + } else if (hasToughness && !hasGlassJawAndToughness) { + magnitude = (int) round(magnitude * 0.5); + } + + target.changeFatigue(magnitude); if (campaign.getCampaignOptions().isUseFatigue()) { Fatigue.processFatigueActions(campaign, target); @@ -847,7 +858,18 @@ private String eventEffectFatigueAll(EventResult result) { for (Person target : targets) { boolean hasGlassJaw = target.getOptions().booleanOption(FLAW_GLASS_JAW); - target.changeFatigue(hasGlassJaw ? magnitude * 2 : magnitude); + boolean hasToughness = target.getOptions().booleanOption(ATOW_TOUGHNESS); + boolean hasGlassJawAndToughness = hasGlassJaw && hasToughness; + + int fatigueGain = magnitude; + + if (hasGlassJaw && !hasGlassJawAndToughness) { + fatigueGain = magnitude * 2; + } else if (hasToughness && !hasGlassJawAndToughness) { + fatigueGain = (int) round(magnitude * 0.5); + } + + target.changeFatigue(fatigueGain); if (campaign.getCampaignOptions().isUseFatigue()) { Fatigue.processFatigueActions(campaign, target); @@ -1074,9 +1096,19 @@ private String eventEffectUniquePoison(EventResult result) { for (int i = 0; i < targetCount; i++) { Person target = getRandomItem(potentialTargets); - int fatigueChange = d6(magnitude); + int fatigueGain = d6(magnitude); + boolean hasGlassJaw = target.getOptions().booleanOption(FLAW_GLASS_JAW); - target.changeFatigue(hasGlassJaw ? fatigueChange * 2 : fatigueChange); + boolean hasToughness = target.getOptions().booleanOption(ATOW_TOUGHNESS); + boolean hasGlassJawAndToughness = hasGlassJaw && hasToughness; + + if (hasGlassJaw && !hasGlassJawAndToughness) { + fatigueGain = magnitude * 2; + } else if (hasToughness && !hasGlassJawAndToughness) { + fatigueGain = (int) round(magnitude * 0.5); + } + + target.changeFatigue(fatigueGain); if (campaign.getCampaignOptions().isUseFatigue()) { Fatigue.processFatigueActions(campaign, target); diff --git a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java index edcf36a3b0..74e034437b 100644 --- a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java +++ b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java @@ -48,6 +48,7 @@ import static mekhq.campaign.mission.ScenarioMapParameters.MapLocation.LowAtmosphere; import static mekhq.campaign.mission.ScenarioMapParameters.MapLocation.Space; import static mekhq.campaign.mission.ScenarioMapParameters.MapLocation.SpecificGroundTerrain; +import static mekhq.campaign.personnel.PersonnelOptions.ATOW_TOUGHNESS; import static mekhq.campaign.personnel.PersonnelOptions.FLAW_GLASS_JAW; import static mekhq.campaign.personnel.skills.SkillType.S_ADMIN; import static mekhq.campaign.personnel.skills.SkillType.S_TACTICS; @@ -1564,11 +1565,21 @@ private static void scanNeighboringCoords(StratconCoords coords, int forceID, Ca */ private static void increaseFatigue(int forceID, Campaign campaign) { for (UUID unit : campaign.getForce(forceID).getAllUnits(false)) { + int fatigueChangeRate = campaign.getCampaignOptions().getFatigueRate(); for (Person person : campaign.getUnit(unit).getCrew()) { - int fatigueChangeRate = campaign.getCampaignOptions().getFatigueRate(); + int fatigueChange = fatigueChangeRate; + boolean hasGlassJaw = person.getOptions().booleanOption(FLAW_GLASS_JAW); + boolean hasToughness = person.getOptions().booleanOption(ATOW_TOUGHNESS); + boolean hasGlassJawAndToughness = hasGlassJaw && hasToughness; + + if (hasGlassJaw && !hasGlassJawAndToughness) { + fatigueChange = fatigueChangeRate * 2; + } else if (hasToughness && !hasGlassJawAndToughness) { + fatigueChange = (int) round(fatigueChangeRate * 0.5); + } - person.changeFatigue(hasGlassJaw ? fatigueChangeRate * 2 : fatigueChangeRate); + person.changeFatigue(fatigueChange); if (campaign.getCampaignOptions().isUseFatigue()) { Fatigue.processFatigueActions(campaign, person);