From 01b5437924dde5becc7b996310ea79d1ba34853f Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Thu, 19 Sep 2024 19:49:11 -0500 Subject: [PATCH 01/21] Refactor force weight calculation Replaced `calculatePlayerForceWeightClass` with `randomForceWeight` for generating force weight class. --- .../mission/AtBDynamicScenarioFactory.java | 116 ++++++++---------- 1 file changed, 53 insertions(+), 63 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java index 88347aec21..0b92f20eb5 100644 --- a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java +++ b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java @@ -18,22 +18,13 @@ */ package mekhq.campaign.mission; -import java.io.File; -import java.time.LocalDate; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - import megamek.client.bot.princess.CardinalEdge; -import megamek.client.generator.RandomGenderGenerator; -import megamek.client.generator.RandomNameGenerator; -import megamek.client.generator.RandomUnitGenerator; -import megamek.client.generator.ReconfigurationParameters; -import megamek.client.generator.TeamLoadOutGenerator; +import megamek.client.generator.*; import megamek.client.generator.enums.SkillGeneratorType; import megamek.client.generator.skillGenerators.AbstractSkillGenerator; import megamek.client.generator.skillGenerators.StratConSkillGenerator; import megamek.client.ratgenerator.MissionRole; +import megamek.codeUtilities.MathUtility; import megamek.codeUtilities.ObjectUtility; import megamek.codeUtilities.StringUtility; import megamek.common.*; @@ -51,7 +42,6 @@ import mekhq.campaign.CampaignOptions; import mekhq.campaign.againstTheBot.AtBConfiguration; import mekhq.campaign.force.Force; -import mekhq.campaign.force.Lance; import mekhq.campaign.mission.AtBDynamicScenario.BenchedEntityData; import mekhq.campaign.mission.ScenarioForceTemplate.ForceAlignment; import mekhq.campaign.mission.ScenarioForceTemplate.ForceGenerationMethod; @@ -69,16 +59,16 @@ import mekhq.campaign.stratcon.StratconBiomeManifest; import mekhq.campaign.stratcon.StratconContractInitializer; import mekhq.campaign.unit.Unit; -import mekhq.campaign.universe.Faction; +import mekhq.campaign.universe.*; import mekhq.campaign.universe.Faction.Tag; -import mekhq.campaign.universe.Factions; -import mekhq.campaign.universe.IUnitGenerator; -import mekhq.campaign.universe.Planet; -import mekhq.campaign.universe.PlanetarySystem; -import mekhq.campaign.universe.Systems; -import mekhq.campaign.universe.UnitGeneratorParameters; import mekhq.campaign.universe.enums.EraFlag; +import java.io.File; +import java.time.LocalDate; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + /** * This class handles the creation and substantive manipulation of * AtBDynamicScenarios @@ -200,13 +190,12 @@ public static void finalizeScenario(AtBDynamicScenario scenario, AtBContract con scenario.getExternalIDLookup().clear(); scenario.getBotUnitTemplates().clear(); - // fix the player force weight class and unit count at the current time. - int playerForceWeightClass = calculatePlayerForceWeightClass(scenario, campaign); + // fix the force weight class and unit count at the current time. + int randomForceWeight = randomForceWeight(scenario, campaign); int playerForceUnitCount = calculateEffectiveUnitCount(scenario, campaign); - // at this point, only the player forces are present and contributing to BV/unit - // count - int generatedLanceCount = generateForces(scenario, contract, campaign, playerForceWeightClass); + // at this point, only the player forces are present and contributing to BV/unit count + int generatedLanceCount = generateForces(scenario, contract, campaign, randomForceWeight); // approximate estimate, anyway. scenario.setLanceCount(generatedLanceCount + (playerForceUnitCount / 4)); @@ -262,7 +251,7 @@ private static int generateForces(AtBDynamicScenario scenario, AtBContract contr // sort it by bucket in ascending order just in case Collections.sort(generationOrders); - int effectiveBV; + int playerEffectiveBv = calculateEffectiveBV(scenario, campaign); int effectiveUnitCount; // loop through all the generation orders we have, in ascending order @@ -271,15 +260,20 @@ private static int generateForces(AtBDynamicScenario scenario, AtBContract contr // recalculate effective BV and unit count each time we change levels for (int generationOrder : generationOrders) { List currentForceTemplates = orderedForceTemplates.get(generationOrder); - effectiveBV = calculateEffectiveBV(scenario, campaign); + int totalEffectiveBv = playerEffectiveBv + getBotForcesBvBudgetAdjustment(scenario, campaign); + effectiveUnitCount = calculateEffectiveUnitCount(scenario, campaign); + // This helps account for high BV player forces, helping reduce low weight unit spam + int weightModifier = playerEffectiveBv / 10000; + weightClass = Math.min(EntityWeightClass.WEIGHT_ASSAULT, weightClass + weightModifier); + for (ScenarioForceTemplate forceTemplate : currentForceTemplates) { if (forceTemplate.getGenerationMethod() == ForceGenerationMethod.FixedMUL.ordinal()) { generatedLanceCount += generateFixedForce(scenario, contract, campaign, forceTemplate); } else { generatedLanceCount += generateForce(scenario, contract, campaign, - effectiveBV, effectiveUnitCount, weightClass, forceTemplate, false); + totalEffectiveBv, effectiveUnitCount, weightClass, forceTemplate, false); } } } @@ -426,9 +420,6 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac // determine generation parameters int forceBV = 0; - double forceMultiplier = getDifficultyMultiplier(campaign); - forceTemplate.setForceMultiplier(forceMultiplier); - int forceBVBudget = (int) (effectiveBV * forceTemplate.getForceMultiplier()); if (isScenarioModifier) { @@ -2459,12 +2450,25 @@ public static int calculateEffectiveBV(AtBDynamicScenario scenario, Campaign cam bvBudget += (int) Math.round(bvBudget * scenario.getEffectivePlayerBVMultiplier()); + return bvBudget; + } + + /** + * This method calculates the budget adjustment for the Bot forces. + * + * @param scenario The game scenario. + * @param campaign The ongoing campaign. + * @return The BV budget adjustment from friendly Bot forces. + */ + private static int getBotForcesBvBudgetAdjustment(AtBDynamicScenario scenario, Campaign campaign) { + int bvBudget = 0; + // allied bot forces that contribute to BV do not get multiplied by the - // difficulty - // even if the player is super good, the AI doesn't get any better + // difficulty even if the player is perfect, the AI doesn't get any better for (int index = 0; index < scenario.getNumBots(); index++) { BotForce botForce = scenario.getBotForce(index); ScenarioForceTemplate forceTemplate = scenario.getBotForceTemplates().get(botForce); + if (forceTemplate != null && forceTemplate.getContributesToBV()) { bvBudget += botForce.getTotalBV(campaign); } @@ -2540,42 +2544,28 @@ private static double getDifficultyMultiplier(Campaign campaign) { } /** - * Helper function that calculates the "weight class" of the player force. - * Putting any kind of dropship or other unit that doesn't fit into the - * "light-medium-heavy-assault" pattern - * will probably cause it to return "ASSAULT". + * Generates a random force weight for a given scenario. * - * @param scenario - * @param campaign - * @return + * @param scenario the scenario the weight is being generated for + * @param campaign the current campaign + * @return the randomly generated {@link EntityWeightClass} */ - private static int calculatePlayerForceWeightClass(AtBDynamicScenario scenario, Campaign campaign) { - double weight = 0.0; - int unitCount = 0; + private static int randomForceWeight(AtBDynamicScenario scenario, Campaign campaign) { + int roll = Compute.d6(2); - for (int forceID : scenario.getForceIDs()) { - weight += Lance.calculateTotalWeight(campaign, forceID); - unitCount += campaign.getForce(forceID).getUnits().size(); - } + roll += scenario.getContract(campaign).getEnemyQuality() - IUnitRating.DRAGOON_C; - int normalizedWeight = (int) (weight / unitCount); + roll = MathUtility.clamp(roll, 2, 12); - if (normalizedWeight < 20) { - return EntityWeightClass.WEIGHT_ULTRA_LIGHT; - } - if (normalizedWeight < 40) { - return EntityWeightClass.WEIGHT_LIGHT; - } - if (normalizedWeight < 60) { - return EntityWeightClass.WEIGHT_MEDIUM; - } - if (normalizedWeight < 80) { - return EntityWeightClass.WEIGHT_HEAVY; - } - if (normalizedWeight < 100) { - return EntityWeightClass.WEIGHT_ASSAULT; - } - return EntityWeightClass.WEIGHT_SUPER_HEAVY; + // this is based on the random force weight table found in Total Warfare + return switch (roll) { + case 2, 3 -> EntityWeightClass.WEIGHT_ULTRA_LIGHT; + case 4, 5 -> EntityWeightClass.WEIGHT_LIGHT; + case 8, 9 -> EntityWeightClass.WEIGHT_HEAVY; + case 10, 11 -> EntityWeightClass.WEIGHT_ASSAULT; + case 12 -> EntityWeightClass.WEIGHT_SUPER_HEAVY; + default -> EntityWeightClass.WEIGHT_MEDIUM; // 6, 7 + }; } /** From 64ea5f149e4854830ed3dfd43a5ff4a98c89df4d Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Thu, 19 Sep 2024 21:07:13 -0500 Subject: [PATCH 02/21] Refactor AtBDynamicScenarioFactory and AtBConfiguration Simplified weight class handling, force generation logic, and bot selection processes. Improved readability and maintainability by consolidating imports and removing redundant code. Adjusted weighted tables in `atbconfig.xml` to streamline scenario configurations. --- MekHQ/data/universe/atbconfig.xml | 85 +++++++----------- .../againstTheBot/AtBConfiguration.java | 90 ++++++++++--------- .../mission/AtBDynamicScenarioFactory.java | 62 ++++--------- 3 files changed, 101 insertions(+), 136 deletions(-) diff --git a/MekHQ/data/universe/atbconfig.xml b/MekHQ/data/universe/atbconfig.xml index e278de10ec..6ce1e38f55 100644 --- a/MekHQ/data/universe/atbconfig.xml +++ b/MekHQ/data/universe/atbconfig.xml @@ -29,31 +29,16 @@ of 4. An entry of the form option has a weight of 1. format. --> - M - LL - H + L - LL - H - ML + M - LLL - MM - A - HL - MLL - HM + H - MML - HLL - HH - AL - MMM - HML - AM + A @@ -64,83 +49,81 @@ of 4. An entry of the form option has a weight of 1. - LLLL + LLLL LLLM - LLMM + LLMM + LLMH - LLMM - LMMM + LMMH MMMM - MMMH + MMMH + MMHH - MMHH - MHHH + MHHH HHHH + MHHA HHHA + MHAA HHAA - HAAA + HAAA AAAA - LLLLL - LLLLM - LLLMM + LLLLL + LLLLM + LLMMM + LLMMH - LLMMM LMMMM MMMMM - MMMMH + MMMMH MMMHH MMHHH MHHHH HHHHH - HHHHA + MHHHA MHHAA + HHHHA HHHAA - HHAAA - AAAAA + HHAAA LLLLLL - LLLLLM LLLLMM - LLLMMM + LLLMMM + LLLMHH - LLLMMM - LLMMMM - LMMMMM - MMMMMM - MMMMMH - MMMMHH + LLMMHH + MMMMMM + MMMMHH + MMMHHH - MMMHHH - MMHHHH - MHHHHH - HHHHHH - HHHHHA - HHHHAA + MMHHHH + HHHHHH + MMHHAA + HHHHAA + MMHAAA HHHAAA - HHAAAA - HAAAAA + HHAAAA AAAAAA diff --git a/MekHQ/src/mekhq/campaign/againstTheBot/AtBConfiguration.java b/MekHQ/src/mekhq/campaign/againstTheBot/AtBConfiguration.java index 83dd62539a..258e284f37 100644 --- a/MekHQ/src/mekhq/campaign/againstTheBot/AtBConfiguration.java +++ b/MekHQ/src/mekhq/campaign/againstTheBot/AtBConfiguration.java @@ -21,34 +21,10 @@ */ package mekhq.campaign.againstTheBot; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; -import java.util.ResourceBundle; -import java.util.function.Function; - -import javax.xml.parsers.DocumentBuilder; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -import megamek.common.Compute; -import megamek.common.EntityWeightClass; -import megamek.common.MekSummary; -import megamek.common.MekSummaryCache; -import megamek.common.TargetRoll; -import megamek.common.UnitType; +import megamek.common.*; import megamek.common.annotations.Nullable; import megamek.logging.MMLogger; import mekhq.MekHQ; -import mekhq.utilities.MHQXMLUtility; import mekhq.campaign.Campaign; import mekhq.campaign.finances.Money; import mekhq.campaign.personnel.Person; @@ -57,6 +33,18 @@ import mekhq.campaign.rating.IUnitRating; import mekhq.campaign.universe.Faction; import mekhq.utilities.MHQXMLUtility; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.xml.parsers.DocumentBuilder; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.time.LocalDate; +import java.util.*; +import java.util.function.Function; /** * @author Neoancient @@ -235,26 +223,46 @@ public int weightClassIndex(String wc) { return selectBotLances(org, weightClass, 0f); } + /** + * Selects a bot lance based on the organization, weight class, and roll modifier. + * + * @param org The organization of the bot force tables. + * @param weightClass The weight class of the bot. + * @param rollMod A modifier to the die roll, expressed as a fraction of the total weight. + * @return The selected bot lance, or null if the organization's bot force tables are not found or invalid. + */ public @Nullable String selectBotLances(String org, int weightClass, float rollMod) { - if (botForceTables.containsKey(org)) { - final List> botForceTable = botForceTables.get(org); - int weightClassIndex = weightClassIndex(weightClass); - WeightedTable table; - if ((weightClassIndex < 0) || (weightClassIndex >= botForceTable.size())) { - logger.error(String.format( - "Bot force tables for organization \"%s\" don't have an entry for weight class %d, limiting to valid values", - org, weightClass)); - weightClassIndex = Math.max(0, Math.min(weightClassIndex, botForceTable.size() - 1)); - } - table = botForceTable.get(weightClassIndex); - if (null == table) { - table = getDefaultForceTable("botForce." + org, weightClassIndex); - } - return table.select(rollMod); - } else { + // Check if the bot force tables contain the required organization + if (!botForceTables.containsKey(org)) { logger.error(String.format("Bot force tables for organization \"%s\" not found, ignoring", org)); return null; } + + // Retrieve botForceTable for the organization + final List> botForceTable = botForceTables.get(org); + + // Weight Class Index + int weightClassIndex = weightClassIndex(weightClass); + + // Check if the weightClassIndex is within valid range + if (weightClassIndex < 0 || weightClassIndex >= botForceTable.size()) { + logger.error(String.format("Bot force tables for organization \"%s\" don't have an entry for weight class %d, limiting to valid values", org, weightClass)); + + // Limit the weightClassIndex within valid range + weightClassIndex = Math.max(0, Math.min(weightClassIndex, botForceTable.size() - 1)); + } + + // Fetch table for the weight class + WeightedTable table = botForceTable.get(weightClassIndex); + + // If there isn't relevant table, provide a default one + if (table == null) { + table = getDefaultForceTable("botForce." + org, weightClassIndex); + } + + // Return the selected table + logger.info("TABLE: " + table.select(rollMod)); + return table.select(rollMod); } public @Nullable String selectBotUnitWeights(String org, int weightClass) { diff --git a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java index 0b92f20eb5..079e6e866b 100644 --- a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java +++ b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java @@ -24,7 +24,6 @@ import megamek.client.generator.skillGenerators.AbstractSkillGenerator; import megamek.client.generator.skillGenerators.StratConSkillGenerator; import megamek.client.ratgenerator.MissionRole; -import megamek.codeUtilities.MathUtility; import megamek.codeUtilities.ObjectUtility; import megamek.codeUtilities.StringUtility; import megamek.common.*; @@ -190,12 +189,11 @@ public static void finalizeScenario(AtBDynamicScenario scenario, AtBContract con scenario.getExternalIDLookup().clear(); scenario.getBotUnitTemplates().clear(); - // fix the force weight class and unit count at the current time. - int randomForceWeight = randomForceWeight(scenario, campaign); + // fix the force unit count at the current time. int playerForceUnitCount = calculateEffectiveUnitCount(scenario, campaign); // at this point, only the player forces are present and contributing to BV/unit count - int generatedLanceCount = generateForces(scenario, contract, campaign, randomForceWeight); + int generatedLanceCount = generateForces(scenario, contract, campaign); // approximate estimate, anyway. scenario.setLanceCount(generatedLanceCount + (playerForceUnitCount / 4)); @@ -228,11 +226,9 @@ public static void finalizeScenario(AtBDynamicScenario scenario, AtBContract con * @param contract The contract on which we're currently working. Used for * skill/quality/planetary info parameters * @param campaign The current campaign - * @param weightClass The average weight class across all forces * @return How many "lances" or other individual units were generated? */ - private static int generateForces(AtBDynamicScenario scenario, AtBContract contract, Campaign campaign, - int weightClass) { + private static int generateForces(AtBDynamicScenario scenario, AtBContract contract, Campaign campaign) { int generatedLanceCount = 0; List forceTemplates = scenario.getTemplate().getAllScenarioForces(); @@ -251,7 +247,7 @@ private static int generateForces(AtBDynamicScenario scenario, AtBContract contr // sort it by bucket in ascending order just in case Collections.sort(generationOrders); - int playerEffectiveBv = calculateEffectiveBV(scenario, campaign); + int effectiveBV; int effectiveUnitCount; // loop through all the generation orders we have, in ascending order @@ -260,20 +256,21 @@ private static int generateForces(AtBDynamicScenario scenario, AtBContract contr // recalculate effective BV and unit count each time we change levels for (int generationOrder : generationOrders) { List currentForceTemplates = orderedForceTemplates.get(generationOrder); - int totalEffectiveBv = playerEffectiveBv + getBotForcesBvBudgetAdjustment(scenario, campaign); - + effectiveBV = calculateEffectiveBV(scenario, campaign); effectiveUnitCount = calculateEffectiveUnitCount(scenario, campaign); - // This helps account for high BV player forces, helping reduce low weight unit spam - int weightModifier = playerEffectiveBv / 10000; - weightClass = Math.min(EntityWeightClass.WEIGHT_ASSAULT, weightClass + weightModifier); + // This helps account for high BV forces, helping reduce low weight unit spam + int weightModifier = effectiveBV / 10000; for (ScenarioForceTemplate forceTemplate : currentForceTemplates) { if (forceTemplate.getGenerationMethod() == ForceGenerationMethod.FixedMUL.ordinal()) { generatedLanceCount += generateFixedForce(scenario, contract, campaign, forceTemplate); } else { + int weightClass = randomForceWeight(); + logger.info("WEIGHT: " + weightClass); + generatedLanceCount += generateForce(scenario, contract, campaign, - totalEffectiveBv, effectiveUnitCount, weightClass, forceTemplate, false); + effectiveBV, effectiveUnitCount, weightClass, forceTemplate, false); } } } @@ -608,7 +605,6 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac // All other unit types use the force generator system to randomly select units } else { - // Determine unit types for each unit of the formation. Normally this is all one // type, but SPECIAL_UNIT_TYPE_ATB_MIX may generate all Meks, all vehicles, or // a Mek/vehicle mixed formation. @@ -2450,25 +2446,12 @@ public static int calculateEffectiveBV(AtBDynamicScenario scenario, Campaign cam bvBudget += (int) Math.round(bvBudget * scenario.getEffectivePlayerBVMultiplier()); - return bvBudget; - } - - /** - * This method calculates the budget adjustment for the Bot forces. - * - * @param scenario The game scenario. - * @param campaign The ongoing campaign. - * @return The BV budget adjustment from friendly Bot forces. - */ - private static int getBotForcesBvBudgetAdjustment(AtBDynamicScenario scenario, Campaign campaign) { - int bvBudget = 0; - // allied bot forces that contribute to BV do not get multiplied by the - // difficulty even if the player is perfect, the AI doesn't get any better + // difficulty + // even if the player is super good, the AI doesn't get any better for (int index = 0; index < scenario.getNumBots(); index++) { BotForce botForce = scenario.getBotForce(index); ScenarioForceTemplate forceTemplate = scenario.getBotForceTemplates().get(botForce); - if (forceTemplate != null && forceTemplate.getContributesToBV()) { bvBudget += botForce.getTotalBV(campaign); } @@ -2546,25 +2529,16 @@ private static double getDifficultyMultiplier(Campaign campaign) { /** * Generates a random force weight for a given scenario. * - * @param scenario the scenario the weight is being generated for - * @param campaign the current campaign * @return the randomly generated {@link EntityWeightClass} */ - private static int randomForceWeight(AtBDynamicScenario scenario, Campaign campaign) { + private static int randomForceWeight() { int roll = Compute.d6(2); - roll += scenario.getContract(campaign).getEnemyQuality() - IUnitRating.DRAGOON_C; - - roll = MathUtility.clamp(roll, 2, 12); - - // this is based on the random force weight table found in Total Warfare return switch (roll) { - case 2, 3 -> EntityWeightClass.WEIGHT_ULTRA_LIGHT; - case 4, 5 -> EntityWeightClass.WEIGHT_LIGHT; - case 8, 9 -> EntityWeightClass.WEIGHT_HEAVY; - case 10, 11 -> EntityWeightClass.WEIGHT_ASSAULT; - case 12 -> EntityWeightClass.WEIGHT_SUPER_HEAVY; - default -> EntityWeightClass.WEIGHT_MEDIUM; // 6, 7 + case 2, 3, 4 -> EntityWeightClass.WEIGHT_LIGHT; + case 8, 9, 10 -> EntityWeightClass.WEIGHT_HEAVY; + case 11, 12 -> EntityWeightClass.WEIGHT_ASSAULT; + default -> EntityWeightClass.WEIGHT_MEDIUM; // 5, 6, 7 }; } From 82eebef4b3d442ae7770285c9f45d9a54e0ba091 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Thu, 19 Sep 2024 23:55:02 -0500 Subject: [PATCH 03/21] Refactor StratConSkillGenerator skill logic Refactored the skill generation logic in StratConSkillGenerator to streamline skill level calculations and removed redundant bonus handling. Also cleaned up logging in AtBDynamicScenarioFactory and removed an unnecessary skill generator type setting. --- .../mission/AtBDynamicScenarioFactory.java | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java index 079e6e866b..e2e448cfeb 100644 --- a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java +++ b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java @@ -20,7 +20,6 @@ import megamek.client.bot.princess.CardinalEdge; import megamek.client.generator.*; -import megamek.client.generator.enums.SkillGeneratorType; import megamek.client.generator.skillGenerators.AbstractSkillGenerator; import megamek.client.generator.skillGenerators.StratConSkillGenerator; import megamek.client.ratgenerator.MissionRole; @@ -54,7 +53,6 @@ import mekhq.campaign.personnel.SkillType; import mekhq.campaign.personnel.enums.Phenotype; import mekhq.campaign.rating.IUnitRating; -import mekhq.campaign.rating.UnitRatingMethod; import mekhq.campaign.stratcon.StratconBiomeManifest; import mekhq.campaign.stratcon.StratconContractInitializer; import mekhq.campaign.unit.Unit; @@ -259,15 +257,11 @@ private static int generateForces(AtBDynamicScenario scenario, AtBContract contr effectiveBV = calculateEffectiveBV(scenario, campaign); effectiveUnitCount = calculateEffectiveUnitCount(scenario, campaign); - // This helps account for high BV forces, helping reduce low weight unit spam - int weightModifier = effectiveBV / 10000; - for (ScenarioForceTemplate forceTemplate : currentForceTemplates) { if (forceTemplate.getGenerationMethod() == ForceGenerationMethod.FixedMUL.ordinal()) { generatedLanceCount += generateFixedForce(scenario, contract, campaign, forceTemplate); } else { int weightClass = randomForceWeight(); - logger.info("WEIGHT: " + weightClass); generatedLanceCount += generateForce(scenario, contract, campaign, effectiveBV, effectiveUnitCount, weightClass, forceTemplate, false); @@ -817,25 +811,6 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac return generatedLanceCount; } - /** - * Retrieves the unit rating from the given campaign. - * - * @param campaign the campaign from which the unit rating is to be retrieved - * @return the unit rating value as an integer - */ - private static int getUnitRating(Campaign campaign) { - final CampaignOptions campaignOptions = campaign.getCampaignOptions(); - final UnitRatingMethod unitRatingMethod = campaignOptions.getUnitRatingMethod(); - - int unitRating = IUnitRating.DRAGOON_C; - if (unitRatingMethod.isFMMR()) { - unitRating = campaign.getUnitRating().getUnitRatingAsInteger(); - } else if (unitRatingMethod.isCampaignOperations()) { - unitRating = campaign.getReputation().getAtbModifier(); - } - return unitRating; - } - /** * Generates the indicated number of civilian entities. * @@ -2030,9 +2005,6 @@ private static Entity getEntityByName(String name, String factionCode, SkillLeve final AbstractSkillGenerator skillGenerator = new StratConSkillGenerator(); skillGenerator.setLevel(skill); - if (faction.isClan()) { - skillGenerator.setType(SkillGeneratorType.CLAN); - } int[] skills = skillGenerator.generateRandomSkills(en); if (faction.isClan() && (Compute.d6(2) > (6 - skill.ordinal() + skills[0] + skills[1]))) { From 579483279a725eb3b7c6582f61c7b61faf76f3ec Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Fri, 20 Sep 2024 11:20:18 -0500 Subject: [PATCH 04/21] Log and adjust BV culling in AtB mission factory Added logging for force generation and BV culling in AtB Dynamic Scenario Factory. Adjusted BV budget handling with a new multiplier for more accurate force balancing. Simplified BV budget calculations by removing redundant multiplications. --- .../mission/AtBDynamicScenarioFactory.java | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java index e2e448cfeb..96688da984 100644 --- a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java +++ b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java @@ -767,17 +767,31 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac currentLanceWeightString = currentLanceWeightString.substring(1); } + if (forceTemplate.getGenerationMethod() == ForceGenerationMethod.BVScaled.ordinal()) { + logger.info("Generated a force with " + forceBV + '/' + forceBVBudget + " BV"); + } + // If over budget for both BV and unit count, pull units until it works while (forceUnitBudget > 0 && generatedEntities.size() > forceUnitBudget) { - generatedEntities.remove(Compute.randomInt(generatedEntities.size())); + int targetUnit = Compute.randomInt(generatedEntities.size()); + generatedEntities.remove(targetUnit); } if (forceTemplate.getGenerationMethod() == ForceGenerationMethod.BVScaled.ordinal()) { - while (((forceBV / forceBVBudget * 100) > targetPercentage) && (generatedEntities.size() > 1)) { - int targetEntity = Compute.randomInt(generatedEntities.size()); - forceBV -= generatedEntities.get(targetEntity).calculateBattleValue(); - generatedEntities.remove(targetEntity); + int adjustedBvBudget = (int) (forceBVBudget * 1.25); + + while ((forceBV > adjustedBvBudget) && (generatedEntities.size() > 1)) { + int targetUnit = Compute.randomInt(generatedEntities.size()); + int battleValue = generatedEntities.get(targetUnit).calculateBattleValue(); + forceBV -= battleValue; + + logger.info("Culled " + generatedEntities.get(targetUnit).getDisplayName() + + " (" + battleValue + " BV)"); + + generatedEntities.remove(targetUnit); } + + logger.info("Final BV (approximately) " + forceBV); } // Units with infantry bays get conventional infantry or battle armor added @@ -2401,8 +2415,7 @@ public static int calculateEffectiveBV(AtBDynamicScenario scenario, Campaign cam for (int forceID : scenario.getForceIDs()) { ScenarioForceTemplate forceTemplate = scenario.getPlayerForceTemplates().get(forceID); if (forceTemplate != null && forceTemplate.getContributesToBV()) { - int forceBVBudget = (int) (campaign.getForce(forceID).getTotalBV(campaign) * difficultyMultiplier); - bvBudget += forceBVBudget; + bvBudget += campaign.getForce(forceID).getTotalBV(campaign); } } @@ -2410,13 +2423,17 @@ public static int calculateEffectiveBV(AtBDynamicScenario scenario, Campaign cam for (UUID unitID : scenario.getIndividualUnitIDs()) { ScenarioForceTemplate forceTemplate = scenario.getPlayerUnitTemplates().get(unitID); if ((forceTemplate != null) && forceTemplate.getContributesToBV()) { - int unitBVBudget = (int) (campaign.getUnit(unitID).getEntity().calculateBattleValue() - * difficultyMultiplier); - bvBudget += unitBVBudget; + bvBudget += campaign.getUnit(unitID).getEntity().calculateBattleValue(); } } - bvBudget += (int) Math.round(bvBudget * scenario.getEffectivePlayerBVMultiplier()); + double bvMultiplier = scenario.getEffectivePlayerBVMultiplier(); + + if (bvMultiplier > 0) { + bvBudget = (int) Math.round(bvBudget * scenario.getEffectivePlayerBVMultiplier() * difficultyMultiplier); + } else { + bvBudget = (int) Math.round(bvBudget * difficultyMultiplier); + } // allied bot forces that contribute to BV do not get multiplied by the // difficulty From 58e4e8740ba2f995a014aea2397e212a031ccdaf Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Fri, 20 Sep 2024 11:56:05 -0500 Subject: [PATCH 05/21] Refactored AtB scenario modifier applicator Removed a debug logging statement from AtBConfiguration and refactored AtBScenarioModifierApplicator to simplify method calls by using static imports and dynamic force weight generation. --- .../againstTheBot/AtBConfiguration.java | 1 - .../atb/AtBScenarioModifierApplicator.java | 27 ++++++++----------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/againstTheBot/AtBConfiguration.java b/MekHQ/src/mekhq/campaign/againstTheBot/AtBConfiguration.java index 258e284f37..225119ece7 100644 --- a/MekHQ/src/mekhq/campaign/againstTheBot/AtBConfiguration.java +++ b/MekHQ/src/mekhq/campaign/againstTheBot/AtBConfiguration.java @@ -261,7 +261,6 @@ public int weightClassIndex(String wc) { } // Return the selected table - logger.info("TABLE: " + table.select(rollMod)); return table.select(rollMod); } diff --git a/MekHQ/src/mekhq/campaign/mission/atb/AtBScenarioModifierApplicator.java b/MekHQ/src/mekhq/campaign/mission/atb/AtBScenarioModifierApplicator.java index 67433744ea..40b804ea51 100644 --- a/MekHQ/src/mekhq/campaign/mission/atb/AtBScenarioModifierApplicator.java +++ b/MekHQ/src/mekhq/campaign/mission/atb/AtBScenarioModifierApplicator.java @@ -18,30 +18,18 @@ */ package mekhq.campaign.mission.atb; -import java.util.UUID; - import megamek.client.generator.enums.SkillGeneratorType; import megamek.client.generator.skillGenerators.AbstractSkillGenerator; import megamek.client.generator.skillGenerators.TaharqaSkillGenerator; import megamek.codeUtilities.MathUtility; -import megamek.common.Board; -import megamek.common.Compute; -import megamek.common.Entity; -import megamek.common.EntityWeightClass; -import megamek.common.HitData; -import megamek.common.Mounted; -import megamek.common.ToHitData; +import megamek.common.*; import megamek.common.enums.SkillLevel; import megamek.common.options.OptionsConstants; import megamek.logging.MMLogger; import mekhq.campaign.Campaign; import mekhq.campaign.force.Force; -import mekhq.campaign.mission.AtBDynamicScenario; -import mekhq.campaign.mission.AtBDynamicScenarioFactory; -import mekhq.campaign.mission.BotForce; -import mekhq.campaign.mission.ScenarioForceTemplate; +import mekhq.campaign.mission.*; import mekhq.campaign.mission.ScenarioForceTemplate.ForceAlignment; -import mekhq.campaign.mission.ScenarioObjective; import mekhq.campaign.mission.atb.AtBScenarioModifier.EventTiming; import mekhq.campaign.personnel.SkillType; import mekhq.campaign.personnel.Skills; @@ -49,6 +37,11 @@ import mekhq.campaign.unit.Unit; import mekhq.campaign.universe.Factions; +import java.util.UUID; + +import static mekhq.campaign.mission.AtBDynamicScenarioFactory.generateForce; +import static mekhq.campaign.mission.AtBDynamicScenarioFactory.randomForceWeight; + /** * Class that handles the application of scenario modifier actions to * AtBDynamicScenarios @@ -81,8 +74,10 @@ private static void postAddForce(Campaign campaign, AtBDynamicScenario scenario, int deploymentZone = AtBDynamicScenarioFactory.calculateDeploymentZone(templateToApply, scenario, templateToApply.getForceName()); - AtBDynamicScenarioFactory.generateForce(scenario, scenario.getContract(campaign), campaign, - effectiveBV, effectiveUnitCount, EntityWeightClass.WEIGHT_ASSAULT, templateToApply, true); + int weightClass = randomForceWeight(); + + generateForce(scenario, scenario.getContract(campaign), campaign, effectiveBV, + effectiveUnitCount, weightClass, templateToApply, true); // the most recently added bot force is the one we just generated BotForce generatedBotForce = scenario.getBotForce(scenario.getNumBots() - 1); From bc00cc98103c7bb7201db59b1eff0b61ca2d6d0c Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Fri, 20 Sep 2024 12:07:06 -0500 Subject: [PATCH 06/21] Add option for using Generic Battle Value in scenarios Introduced a new campaign option to use Generic Battle Value for balancing bot forces. This change ensures scenarios can utilize an average battle value independent of pilot skill, offering varied difficulty depending on opponent type. Updated relevant methods and UI components to support this new option. --- .../CampaignOptionsDialog.properties | 4 + MekHQ/src/mekhq/campaign/CampaignOptions.java | 30 +++++--- MekHQ/src/mekhq/campaign/force/Force.java | 38 +++++----- .../mission/AtBDynamicScenarioFactory.java | 69 +++++++++++------ .../mekhq/gui/panes/CampaignOptionsPane.java | 74 +++++++++---------- 5 files changed, 122 insertions(+), 93 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 2cd8013647..7963447de3 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -898,6 +898,10 @@ chkAdjustPaymentForStrategy.text=Adjust contract payment for deployment limits chkAdjustPaymentForStrategy.toolTipText=If the number of lances required to be deployed exceeds the current commander's strategy skill,
reduce the number when calculating new contracts and reduce the payment proportionally. lblAdditionalStrategyDeployment.text=Per rank of strategy: spnAdditionalStrategyDeployment.toolTipText=The number of additional lances that can be deployed for each increase in strategy skill. +chkUseGenericBattleValue.text=Use Generic Battle Value +chkUseGenericBattleValue.toolTipText=Bot forces are balanced used Generic Battle Value, an estimation of the average battle value for a unit of that type and weight.\ + \ This ignores pilot skill, meaning contracts against Green and Elite OpFors should feel fundamentally different.\ + \ Similarly, OpFors with higher or lower than average equipment (such as Clans or Pirates) will present higher or lower difficulty scenarios. chkUseVehicles.text=Use vehicles chkUseVehicles.toolTipText=Enemy forces can include vehicles. chkClanVehicles.text=Clan OpFors use vehicles diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 7dcb4f182a..892ea7a679 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -19,17 +19,6 @@ */ package mekhq.campaign; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - import megamek.Version; import megamek.codeUtilities.MathUtility; import megamek.common.EquipmentType; @@ -49,6 +38,12 @@ import mekhq.campaign.rating.UnitRatingMethod; import mekhq.service.mrms.MRMSOption; import mekhq.utilities.MHQXMLUtility; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.io.PrintWriter; +import java.util.*; +import java.util.Map.Entry; /** * @author natit @@ -571,6 +566,7 @@ public static String getTransitUnitName(final int unit) { private boolean generateChases; // Scenarios + private boolean useGenericBattleValue; private boolean doubleVehicles; private int opForLanceTypeMeks; private int opForLanceTypeMixed; @@ -1205,6 +1201,7 @@ public CampaignOptions() { generateChases = true; // Scenarios + useGenericBattleValue = true; doubleVehicles = false; setOpForLanceTypeMeks(1); setOpForLanceTypeMixed(2); @@ -4259,6 +4256,14 @@ public void setClanVehicles(final boolean clanVehicles) { this.clanVehicles = clanVehicles; } + public boolean isUseGenericBattleValue() { + return useGenericBattleValue; + } + + public void setUseGenericBattleValue(final boolean useGenericBattleValue) { + this.useGenericBattleValue = useGenericBattleValue; + } + public boolean isDoubleVehicles() { return doubleVehicles; } @@ -5091,6 +5096,7 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useAero", useAero); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useVehicles", useVehicles); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "clanVehicles", clanVehicles); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useGenericBattleValue", useGenericBattleValue); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "doubleVehicles", doubleVehicles); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "adjustPlayerVehicles", adjustPlayerVehicles); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "opForLanceTypeMeks", getOpForLanceTypeMeks()); @@ -6069,6 +6075,8 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.useVehicles = Boolean.parseBoolean(wn2.getTextContent().trim()); } else if (wn2.getNodeName().equalsIgnoreCase("clanVehicles")) { retVal.clanVehicles = Boolean.parseBoolean(wn2.getTextContent().trim()); + } else if (wn2.getNodeName().equalsIgnoreCase("useGenericBattleValue")) { + retVal.useGenericBattleValue = Boolean.parseBoolean(wn2.getTextContent().trim()); } else if (wn2.getNodeName().equalsIgnoreCase("doubleVehicles")) { retVal.doubleVehicles = Boolean.parseBoolean(wn2.getTextContent().trim()); } else if (wn2.getNodeName().equalsIgnoreCase("adjustPlayerVehicles")) { diff --git a/MekHQ/src/mekhq/campaign/force/Force.java b/MekHQ/src/mekhq/campaign/force/Force.java index aee597802b..d334ee3ae4 100644 --- a/MekHQ/src/mekhq/campaign/force/Force.java +++ b/MekHQ/src/mekhq/campaign/force/Force.java @@ -21,22 +21,6 @@ */ package mekhq.campaign.force; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.TreeMap; -import java.util.UUID; -import java.util.Vector; -import java.util.stream.Collectors; - -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - import megamek.Version; import megamek.common.annotations.Nullable; import megamek.common.icons.Camouflage; @@ -54,6 +38,13 @@ import mekhq.campaign.personnel.Person; import mekhq.campaign.unit.Unit; import mekhq.utilities.MHQXMLUtility; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.io.PrintWriter; +import java.util.*; +import java.util.stream.Collectors; /** * This is a hierarchical object to define forces for TO&E. Each Force @@ -748,24 +739,29 @@ public int hashCode() { /** * Calculates the force's total BV, including sub forces. * - * @param c The working campaign. + * @param campaign The working campaign. * @return Total BV */ - public int getTotalBV(Campaign c) { + public int getTotalBV(Campaign campaign) { int bvTotal = 0; for (Force sforce : getSubForces()) { - bvTotal += sforce.getTotalBV(c); + bvTotal += sforce.getTotalBV(campaign); } for (UUID id : getUnits()) { // no idea how this would happen, but sometimes a unit in a forces unit ID list // has an invalid ID? - if (c.getUnit(id) == null) { + if (campaign.getUnit(id) == null) { continue; } - bvTotal += c.getUnit(id).getEntity().calculateBattleValue(); + + if (campaign.getCampaignOptions().isUseGenericBattleValue()) { + bvTotal += campaign.getUnit(id).getEntity().getGenericBattleValue(); + } else { + bvTotal += campaign.getUnit(id).getEntity().calculateBattleValue(); + } } return bvTotal; diff --git a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java index 96688da984..3e0617c9b6 100644 --- a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java +++ b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java @@ -744,7 +744,11 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac // tracking // list for (Entity ent : generatedLance) { - forceBV += ent.calculateBattleValue(); + if (campaign.getCampaignOptions().isUseGenericBattleValue()) { + forceBV += ent.getGenericBattleValue(); + } else { + forceBV += ent.calculateBattleValue(); + } generatedEntities.add(ent); } @@ -767,33 +771,12 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac currentLanceWeightString = currentLanceWeightString.substring(1); } - if (forceTemplate.getGenerationMethod() == ForceGenerationMethod.BVScaled.ordinal()) { - logger.info("Generated a force with " + forceBV + '/' + forceBVBudget + " BV"); - } - // If over budget for both BV and unit count, pull units until it works while (forceUnitBudget > 0 && generatedEntities.size() > forceUnitBudget) { int targetUnit = Compute.randomInt(generatedEntities.size()); generatedEntities.remove(targetUnit); } - if (forceTemplate.getGenerationMethod() == ForceGenerationMethod.BVScaled.ordinal()) { - int adjustedBvBudget = (int) (forceBVBudget * 1.25); - - while ((forceBV > adjustedBvBudget) && (generatedEntities.size() > 1)) { - int targetUnit = Compute.randomInt(generatedEntities.size()); - int battleValue = generatedEntities.get(targetUnit).calculateBattleValue(); - forceBV -= battleValue; - - logger.info("Culled " + generatedEntities.get(targetUnit).getDisplayName() - + " (" + battleValue + " BV)"); - - generatedEntities.remove(targetUnit); - } - - logger.info("Final BV (approximately) " + forceBV); - } - // Units with infantry bays get conventional infantry or battle armor added List transportedEntities = fillTransports(scenario, generatedEntities, @@ -817,6 +800,43 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac } } + // re-calculate forceBV to account for extra units just added + for (Entity entity : generatedEntities) { + if (campaign.getCampaignOptions().isUseGenericBattleValue()) { + forceBV += entity.getGenericBattleValue(); + } else { + forceBV += entity.calculateBattleValue(); + } + } + + if (forceTemplate.getGenerationMethod() == ForceGenerationMethod.BVScaled.ordinal()) { + logger.info("Generated a force with " + forceBV + '/' + forceBVBudget + " Generic BV"); + + int adjustedBvBudget = (int) (forceBVBudget * 1.25); + + String balancingType = ""; + while ((forceBV > adjustedBvBudget) && (generatedEntities.size() > 1)) { + int targetUnit = Compute.randomInt(generatedEntities.size()); + + int battleValue; + if (campaign.getCampaignOptions().isUseGenericBattleValue()) { + battleValue = generatedEntities.get(targetUnit).getGenericBattleValue(); + balancingType = " Generic"; + } else { + battleValue = generatedEntities.get(targetUnit).calculateBattleValue(); + } + + forceBV -= battleValue; + + logger.info("Culled " + generatedEntities.get(targetUnit).getDisplayName() + + " (" + battleValue + balancingType + " BV)"); + + generatedEntities.remove(targetUnit); + } + + logger.info("Final force " + forceBV + '/' + forceBVBudget + balancingType + " BV)"); + } + BotForce generatedForce = new BotForce(); generatedForce.setFixedEntityList(generatedEntities); setBotForceParameters(generatedForce, forceTemplate, forceAlignment, contract); @@ -2423,7 +2443,8 @@ public static int calculateEffectiveBV(AtBDynamicScenario scenario, Campaign cam for (UUID unitID : scenario.getIndividualUnitIDs()) { ScenarioForceTemplate forceTemplate = scenario.getPlayerUnitTemplates().get(unitID); if ((forceTemplate != null) && forceTemplate.getContributesToBV()) { - bvBudget += campaign.getUnit(unitID).getEntity().calculateBattleValue(); + // bvBudget += campaign.getUnit(unitID).getEntity().calculateBattleValue(); + bvBudget += campaign.getUnit(unitID).getEntity().getGenericBattleValue(); } } @@ -2520,7 +2541,7 @@ private static double getDifficultyMultiplier(Campaign campaign) { * * @return the randomly generated {@link EntityWeightClass} */ - private static int randomForceWeight() { + public static int randomForceWeight() { int roll = Compute.d6(2); return switch (roll) { diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index f9877dff18..bc069ef0d8 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -18,43 +18,6 @@ */ package mekhq.gui.panes; -import static megamek.client.ui.WrapLayout.wordWrap; - -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.GridLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.time.LocalDate; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.ResourceBundle; -import java.util.Set; -import java.util.Vector; -import java.util.stream.IntStream; - -import javax.swing.*; -import javax.swing.GroupLayout.Alignment; -import javax.swing.JSpinner.DefaultEditor; -import javax.swing.JSpinner.NumberEditor; -import javax.swing.LayoutStyle.ComponentPlacement; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import javax.swing.table.DefaultTableCellRenderer; -import javax.swing.table.DefaultTableModel; -import javax.swing.table.JTableHeader; -import javax.swing.table.TableColumn; - import megamek.client.generator.RandomGenderGenerator; import megamek.client.generator.RandomNameGenerator; import megamek.client.ui.baseComponents.JDisableablePanel; @@ -112,6 +75,29 @@ import mekhq.module.PersonnelMarketServiceManager; import mekhq.module.api.PersonnelMarketMethod; +import javax.swing.*; +import javax.swing.GroupLayout.Alignment; +import javax.swing.JSpinner.DefaultEditor; +import javax.swing.JSpinner.NumberEditor; +import javax.swing.LayoutStyle.ComponentPlacement; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.JTableHeader; +import javax.swing.table.TableColumn; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.time.LocalDate; +import java.util.List; +import java.util.*; +import java.util.Map.Entry; +import java.util.stream.IntStream; + +import static megamek.client.ui.WrapLayout.wordWrap; + /** * @author Justin 'Windchild' Bowen */ @@ -680,6 +666,7 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkGenerateChases; // scenarios + private JCheckBox chkUseGenericBattleValue; private JCheckBox chkDoubleVehicles; private JSpinner spnOpForLanceTypeMeks; private JSpinner spnOpForLanceTypeMixed; @@ -3226,6 +3213,17 @@ private JScrollPane createAgainstTheBotTab() { panSubAtBContract.add(chkGenerateChases, gridBagConstraints); int yTablePosition = 0; + chkUseGenericBattleValue = new JCheckBox(resources.getString("chkUseGenericBattleValue.text")); + chkUseGenericBattleValue.setToolTipText(wordWrap(resources.getString("chkUseGenericBattleValue.toolTipText"))); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = yTablePosition++; + gridBagConstraints.gridwidth = 2; + gridBagConstraints.fill = GridBagConstraints.NONE; + gridBagConstraints.insets = new Insets(5, 5, 5, 5); + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + panSubAtBScenario.add(chkUseGenericBattleValue, gridBagConstraints); + chkDoubleVehicles = new JCheckBox(resources.getString("chkDoubleVehicles.text")); chkDoubleVehicles.setToolTipText(resources.getString("chkDoubleVehicles.toolTipText")); gridBagConstraints = new GridBagConstraints(); @@ -9046,6 +9044,7 @@ public void setOptions(@Nullable CampaignOptions options, btnIntensityUpdate.doClick(); chkGenerateChases.setSelected(options.isGenerateChases()); + chkUseGenericBattleValue.setSelected(options.isUseGenericBattleValue()); chkDoubleVehicles.setSelected(options.isDoubleVehicles()); spnOpForLanceTypeMeks.setValue(options.getOpForLanceTypeMeks()); spnOpForLanceTypeMixed.setValue(options.getOpForLanceTypeMixed()); @@ -9634,6 +9633,7 @@ public void updateOptions() { options.setUseVehicles(chkUseVehicles.isSelected()); options.setClanVehicles(chkClanVehicles.isSelected()); options.setAutoConfigMunitions(chkAutoConfigMunitions.isSelected()); + options.setUseGenericBattleValue(chkUseGenericBattleValue.isSelected()); options.setDoubleVehicles(chkDoubleVehicles.isSelected()); options.setAdjustPlayerVehicles(chkAdjustPlayerVehicles.isSelected()); options.setOpForLanceTypeMeks((Integer) spnOpForLanceTypeMeks.getValue()); From 83920ca680c116c2b42ece252441031e0a7026e4 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Fri, 20 Sep 2024 12:07:06 -0500 Subject: [PATCH 07/21] Add option for using Generic Battle Value in scenarios Introduced a new campaign option to use Generic Battle Value for balancing bot forces. This change ensures scenarios can utilize an average battle value independent of pilot skill, offering varied difficulty depending on opponent type. Updated relevant methods and UI components to support this new option. --- .../CampaignOptionsDialog.properties | 4 + MekHQ/src/mekhq/campaign/CampaignOptions.java | 30 +++++--- MekHQ/src/mekhq/campaign/force/Force.java | 41 +++++----- .../mission/AtBDynamicScenarioFactory.java | 42 ++++++++--- .../mekhq/gui/panes/CampaignOptionsPane.java | 74 +++++++++---------- 5 files changed, 109 insertions(+), 82 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 2cd8013647..7963447de3 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -898,6 +898,10 @@ chkAdjustPaymentForStrategy.text=Adjust contract payment for deployment limits chkAdjustPaymentForStrategy.toolTipText=If the number of lances required to be deployed exceeds the current commander's strategy skill,
reduce the number when calculating new contracts and reduce the payment proportionally. lblAdditionalStrategyDeployment.text=Per rank of strategy: spnAdditionalStrategyDeployment.toolTipText=The number of additional lances that can be deployed for each increase in strategy skill. +chkUseGenericBattleValue.text=Use Generic Battle Value +chkUseGenericBattleValue.toolTipText=Bot forces are balanced used Generic Battle Value, an estimation of the average battle value for a unit of that type and weight.\ + \ This ignores pilot skill, meaning contracts against Green and Elite OpFors should feel fundamentally different.\ + \ Similarly, OpFors with higher or lower than average equipment (such as Clans or Pirates) will present higher or lower difficulty scenarios. chkUseVehicles.text=Use vehicles chkUseVehicles.toolTipText=Enemy forces can include vehicles. chkClanVehicles.text=Clan OpFors use vehicles diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 7dcb4f182a..892ea7a679 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -19,17 +19,6 @@ */ package mekhq.campaign; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - import megamek.Version; import megamek.codeUtilities.MathUtility; import megamek.common.EquipmentType; @@ -49,6 +38,12 @@ import mekhq.campaign.rating.UnitRatingMethod; import mekhq.service.mrms.MRMSOption; import mekhq.utilities.MHQXMLUtility; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.io.PrintWriter; +import java.util.*; +import java.util.Map.Entry; /** * @author natit @@ -571,6 +566,7 @@ public static String getTransitUnitName(final int unit) { private boolean generateChases; // Scenarios + private boolean useGenericBattleValue; private boolean doubleVehicles; private int opForLanceTypeMeks; private int opForLanceTypeMixed; @@ -1205,6 +1201,7 @@ public CampaignOptions() { generateChases = true; // Scenarios + useGenericBattleValue = true; doubleVehicles = false; setOpForLanceTypeMeks(1); setOpForLanceTypeMixed(2); @@ -4259,6 +4256,14 @@ public void setClanVehicles(final boolean clanVehicles) { this.clanVehicles = clanVehicles; } + public boolean isUseGenericBattleValue() { + return useGenericBattleValue; + } + + public void setUseGenericBattleValue(final boolean useGenericBattleValue) { + this.useGenericBattleValue = useGenericBattleValue; + } + public boolean isDoubleVehicles() { return doubleVehicles; } @@ -5091,6 +5096,7 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useAero", useAero); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useVehicles", useVehicles); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "clanVehicles", clanVehicles); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useGenericBattleValue", useGenericBattleValue); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "doubleVehicles", doubleVehicles); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "adjustPlayerVehicles", adjustPlayerVehicles); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "opForLanceTypeMeks", getOpForLanceTypeMeks()); @@ -6069,6 +6075,8 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.useVehicles = Boolean.parseBoolean(wn2.getTextContent().trim()); } else if (wn2.getNodeName().equalsIgnoreCase("clanVehicles")) { retVal.clanVehicles = Boolean.parseBoolean(wn2.getTextContent().trim()); + } else if (wn2.getNodeName().equalsIgnoreCase("useGenericBattleValue")) { + retVal.useGenericBattleValue = Boolean.parseBoolean(wn2.getTextContent().trim()); } else if (wn2.getNodeName().equalsIgnoreCase("doubleVehicles")) { retVal.doubleVehicles = Boolean.parseBoolean(wn2.getTextContent().trim()); } else if (wn2.getNodeName().equalsIgnoreCase("adjustPlayerVehicles")) { diff --git a/MekHQ/src/mekhq/campaign/force/Force.java b/MekHQ/src/mekhq/campaign/force/Force.java index aee597802b..cd63024ee6 100644 --- a/MekHQ/src/mekhq/campaign/force/Force.java +++ b/MekHQ/src/mekhq/campaign/force/Force.java @@ -21,22 +21,6 @@ */ package mekhq.campaign.force; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.TreeMap; -import java.util.UUID; -import java.util.Vector; -import java.util.stream.Collectors; - -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - import megamek.Version; import megamek.common.annotations.Nullable; import megamek.common.icons.Camouflage; @@ -54,6 +38,13 @@ import mekhq.campaign.personnel.Person; import mekhq.campaign.unit.Unit; import mekhq.utilities.MHQXMLUtility; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.io.PrintWriter; +import java.util.*; +import java.util.stream.Collectors; /** * This is a hierarchical object to define forces for TO&E. Each Force @@ -748,24 +739,28 @@ public int hashCode() { /** * Calculates the force's total BV, including sub forces. * - * @param c The working campaign. + * @param campaign The working campaign. * @return Total BV */ - public int getTotalBV(Campaign c) { + public int getTotalBV(Campaign campaign) { int bvTotal = 0; - for (Force sforce : getSubForces()) { - bvTotal += sforce.getTotalBV(c); + for (Force subforce : getSubForces()) { + bvTotal += subforce.getTotalBV(campaign); } - for (UUID id : getUnits()) { + for (UUID unitId : getUnits()) { // no idea how this would happen, but sometimes a unit in a forces unit ID list // has an invalid ID? - if (c.getUnit(id) == null) { + if (campaign.getUnit(unitId) == null) { continue; } - bvTotal += c.getUnit(id).getEntity().calculateBattleValue(); + if (campaign.getCampaignOptions().isUseGenericBattleValue()) { + bvTotal += campaign.getUnit(unitId).getEntity().getGenericBattleValue(); + } else { + bvTotal += campaign.getUnit(unitId).getEntity().calculateBattleValue(); + } } return bvTotal; diff --git a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java index 96688da984..2b1170c888 100644 --- a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java +++ b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java @@ -59,6 +59,8 @@ import mekhq.campaign.universe.*; import mekhq.campaign.universe.Faction.Tag; import mekhq.campaign.universe.enums.EraFlag; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.io.File; import java.time.LocalDate; @@ -89,6 +91,7 @@ public class AtBDynamicScenarioFactory { private static final int COMSTAR_LANCE_SIZE = 6; private static final int REINFORCEMENT_ARRIVAL_SCALE = 30; + private static final Logger log = LogManager.getLogger(AtBDynamicScenarioFactory.class); /** * Method that sets some initial scenario parameters from the given template, @@ -744,7 +747,11 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac // tracking // list for (Entity ent : generatedLance) { - forceBV += ent.calculateBattleValue(); + if (campaign.getCampaignOptions().isUseGenericBattleValue()) { + forceBV += ent.getGenericBattleValue(); + } else { + forceBV += ent.calculateBattleValue(); + } generatedEntities.add(ent); } @@ -767,31 +774,40 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac currentLanceWeightString = currentLanceWeightString.substring(1); } - if (forceTemplate.getGenerationMethod() == ForceGenerationMethod.BVScaled.ordinal()) { - logger.info("Generated a force with " + forceBV + '/' + forceBVBudget + " BV"); - } - - // If over budget for both BV and unit count, pull units until it works + // If over budget for BV or unit count, pull units until it works while (forceUnitBudget > 0 && generatedEntities.size() > forceUnitBudget) { int targetUnit = Compute.randomInt(generatedEntities.size()); generatedEntities.remove(targetUnit); } if (forceTemplate.getGenerationMethod() == ForceGenerationMethod.BVScaled.ordinal()) { + String balancingType = ""; + if (campaign.getCampaignOptions().isUseGenericBattleValue()) { + balancingType = " Generic"; + } + logger.info("Generated a force with " + forceBV + '/' + forceBVBudget + ' ' + balancingType + " BV"); + int adjustedBvBudget = (int) (forceBVBudget * 1.25); while ((forceBV > adjustedBvBudget) && (generatedEntities.size() > 1)) { int targetUnit = Compute.randomInt(generatedEntities.size()); - int battleValue = generatedEntities.get(targetUnit).calculateBattleValue(); + + int battleValue; + if (campaign.getCampaignOptions().isUseGenericBattleValue()) { + battleValue = generatedEntities.get(targetUnit).getGenericBattleValue(); + } else { + battleValue = generatedEntities.get(targetUnit).calculateBattleValue(); + } + forceBV -= battleValue; logger.info("Culled " + generatedEntities.get(targetUnit).getDisplayName() - + " (" + battleValue + " BV)"); + + " (" + battleValue + balancingType + " BV)"); generatedEntities.remove(targetUnit); } - logger.info("Final BV (approximately) " + forceBV); + logger.info("Final force " + forceBV + '/' + adjustedBvBudget + balancingType + " BV)"); } // Units with infantry bays get conventional infantry or battle armor added @@ -2423,7 +2439,11 @@ public static int calculateEffectiveBV(AtBDynamicScenario scenario, Campaign cam for (UUID unitID : scenario.getIndividualUnitIDs()) { ScenarioForceTemplate forceTemplate = scenario.getPlayerUnitTemplates().get(unitID); if ((forceTemplate != null) && forceTemplate.getContributesToBV()) { - bvBudget += campaign.getUnit(unitID).getEntity().calculateBattleValue(); + if (campaign.getCampaignOptions().isUseGenericBattleValue()) { + bvBudget += campaign.getUnit(unitID).getEntity().getGenericBattleValue(); + } else { + bvBudget += campaign.getUnit(unitID).getEntity().calculateBattleValue(); + } } } @@ -2520,7 +2540,7 @@ private static double getDifficultyMultiplier(Campaign campaign) { * * @return the randomly generated {@link EntityWeightClass} */ - private static int randomForceWeight() { + public static int randomForceWeight() { int roll = Compute.d6(2); return switch (roll) { diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index f9877dff18..bc069ef0d8 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -18,43 +18,6 @@ */ package mekhq.gui.panes; -import static megamek.client.ui.WrapLayout.wordWrap; - -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.GridLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.time.LocalDate; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.ResourceBundle; -import java.util.Set; -import java.util.Vector; -import java.util.stream.IntStream; - -import javax.swing.*; -import javax.swing.GroupLayout.Alignment; -import javax.swing.JSpinner.DefaultEditor; -import javax.swing.JSpinner.NumberEditor; -import javax.swing.LayoutStyle.ComponentPlacement; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import javax.swing.table.DefaultTableCellRenderer; -import javax.swing.table.DefaultTableModel; -import javax.swing.table.JTableHeader; -import javax.swing.table.TableColumn; - import megamek.client.generator.RandomGenderGenerator; import megamek.client.generator.RandomNameGenerator; import megamek.client.ui.baseComponents.JDisableablePanel; @@ -112,6 +75,29 @@ import mekhq.module.PersonnelMarketServiceManager; import mekhq.module.api.PersonnelMarketMethod; +import javax.swing.*; +import javax.swing.GroupLayout.Alignment; +import javax.swing.JSpinner.DefaultEditor; +import javax.swing.JSpinner.NumberEditor; +import javax.swing.LayoutStyle.ComponentPlacement; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.JTableHeader; +import javax.swing.table.TableColumn; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.time.LocalDate; +import java.util.List; +import java.util.*; +import java.util.Map.Entry; +import java.util.stream.IntStream; + +import static megamek.client.ui.WrapLayout.wordWrap; + /** * @author Justin 'Windchild' Bowen */ @@ -680,6 +666,7 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkGenerateChases; // scenarios + private JCheckBox chkUseGenericBattleValue; private JCheckBox chkDoubleVehicles; private JSpinner spnOpForLanceTypeMeks; private JSpinner spnOpForLanceTypeMixed; @@ -3226,6 +3213,17 @@ private JScrollPane createAgainstTheBotTab() { panSubAtBContract.add(chkGenerateChases, gridBagConstraints); int yTablePosition = 0; + chkUseGenericBattleValue = new JCheckBox(resources.getString("chkUseGenericBattleValue.text")); + chkUseGenericBattleValue.setToolTipText(wordWrap(resources.getString("chkUseGenericBattleValue.toolTipText"))); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = yTablePosition++; + gridBagConstraints.gridwidth = 2; + gridBagConstraints.fill = GridBagConstraints.NONE; + gridBagConstraints.insets = new Insets(5, 5, 5, 5); + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + panSubAtBScenario.add(chkUseGenericBattleValue, gridBagConstraints); + chkDoubleVehicles = new JCheckBox(resources.getString("chkDoubleVehicles.text")); chkDoubleVehicles.setToolTipText(resources.getString("chkDoubleVehicles.toolTipText")); gridBagConstraints = new GridBagConstraints(); @@ -9046,6 +9044,7 @@ public void setOptions(@Nullable CampaignOptions options, btnIntensityUpdate.doClick(); chkGenerateChases.setSelected(options.isGenerateChases()); + chkUseGenericBattleValue.setSelected(options.isUseGenericBattleValue()); chkDoubleVehicles.setSelected(options.isDoubleVehicles()); spnOpForLanceTypeMeks.setValue(options.getOpForLanceTypeMeks()); spnOpForLanceTypeMixed.setValue(options.getOpForLanceTypeMixed()); @@ -9634,6 +9633,7 @@ public void updateOptions() { options.setUseVehicles(chkUseVehicles.isSelected()); options.setClanVehicles(chkClanVehicles.isSelected()); options.setAutoConfigMunitions(chkAutoConfigMunitions.isSelected()); + options.setUseGenericBattleValue(chkUseGenericBattleValue.isSelected()); options.setDoubleVehicles(chkDoubleVehicles.isSelected()); options.setAdjustPlayerVehicles(chkAdjustPlayerVehicles.isSelected()); options.setOpForLanceTypeMeks((Integer) spnOpForLanceTypeMeks.getValue()); From f0a73603d68afde4f632aa776350c580a73f8eac Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Fri, 20 Sep 2024 17:45:09 -0500 Subject: [PATCH 08/21] Refactor forceBV calculation in scenario generation Removed redundant forceBV recalculation before logging and moved balancingType determination for concise code. This change improves efficiency by eliminating unnecessary loops and ensures that forceBV and balancingType are determined only when needed. --- .../mission/AtBDynamicScenarioFactory.java | 37 ------------------- 1 file changed, 37 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java index 818c68aeb4..2b1170c888 100644 --- a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java +++ b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java @@ -833,43 +833,6 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac } } - // re-calculate forceBV to account for extra units just added - for (Entity entity : generatedEntities) { - if (campaign.getCampaignOptions().isUseGenericBattleValue()) { - forceBV += entity.getGenericBattleValue(); - } else { - forceBV += entity.calculateBattleValue(); - } - } - - if (forceTemplate.getGenerationMethod() == ForceGenerationMethod.BVScaled.ordinal()) { - logger.info("Generated a force with " + forceBV + '/' + forceBVBudget + " Generic BV"); - - int adjustedBvBudget = (int) (forceBVBudget * 1.25); - - String balancingType = ""; - while ((forceBV > adjustedBvBudget) && (generatedEntities.size() > 1)) { - int targetUnit = Compute.randomInt(generatedEntities.size()); - - int battleValue; - if (campaign.getCampaignOptions().isUseGenericBattleValue()) { - battleValue = generatedEntities.get(targetUnit).getGenericBattleValue(); - balancingType = " Generic"; - } else { - battleValue = generatedEntities.get(targetUnit).calculateBattleValue(); - } - - forceBV -= battleValue; - - logger.info("Culled " + generatedEntities.get(targetUnit).getDisplayName() - + " (" + battleValue + balancingType + " BV)"); - - generatedEntities.remove(targetUnit); - } - - logger.info("Final force " + forceBV + '/' + forceBVBudget + balancingType + " BV)"); - } - BotForce generatedForce = new BotForce(); generatedForce.setFixedEntityList(generatedEntities); setBotForceParameters(generatedForce, forceTemplate, forceAlignment, contract); From fdbe731f46e6baf4b30d313d46f12cf99ded78d7 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Fri, 20 Sep 2024 19:20:46 -0500 Subject: [PATCH 09/21] Add optional standard BV calculation and Clan force bid logic Added a forceStandardBattleValue parameter in multiple BV calculation methods to allow optional usage of standard BV. Implemented bidding away of forces for Clan factions to create more balanced engagements. Adjusted various components to handle this new logic appropriately. --- MekHQ/src/mekhq/campaign/force/Force.java | 12 ++++---- .../mission/AtBDynamicScenarioFactory.java | 30 +++++++++++++++---- .../atb/AtBScenarioModifierApplicator.java | 9 ++++-- .../stratcon/ScenarioWizardLanceRenderer.java | 11 ++++--- 4 files changed, 43 insertions(+), 19 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/force/Force.java b/MekHQ/src/mekhq/campaign/force/Force.java index cd63024ee6..41629c55bc 100644 --- a/MekHQ/src/mekhq/campaign/force/Force.java +++ b/MekHQ/src/mekhq/campaign/force/Force.java @@ -739,14 +739,16 @@ public int hashCode() { /** * Calculates the force's total BV, including sub forces. * - * @param campaign The working campaign. - * @return Total BV + * @param campaign The working campaign. This is the campaign object that the force belongs to. + * @param forceStandardBattleValue Flag indicating whether to override campaign settings that + * call for the use of Generic BV + * @return The total battle value (BV) of the force. */ - public int getTotalBV(Campaign campaign) { + public int getTotalBV(Campaign campaign, boolean forceStandardBattleValue) { int bvTotal = 0; for (Force subforce : getSubForces()) { - bvTotal += subforce.getTotalBV(campaign); + bvTotal += subforce.getTotalBV(campaign, forceStandardBattleValue); } for (UUID unitId : getUnits()) { @@ -756,7 +758,7 @@ public int getTotalBV(Campaign campaign) { continue; } - if (campaign.getCampaignOptions().isUseGenericBattleValue()) { + if (campaign.getCampaignOptions().isUseGenericBattleValue() && !forceStandardBattleValue) { bvTotal += campaign.getUnit(unitId).getEntity().getGenericBattleValue(); } else { bvTotal += campaign.getUnit(unitId).getEntity().calculateBattleValue(); diff --git a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java index 2b1170c888..85f6c12817 100644 --- a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java +++ b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java @@ -257,7 +257,7 @@ private static int generateForces(AtBDynamicScenario scenario, AtBContract contr // recalculate effective BV and unit count each time we change levels for (int generationOrder : generationOrders) { List currentForceTemplates = orderedForceTemplates.get(generationOrder); - effectiveBV = calculateEffectiveBV(scenario, campaign); + effectiveBV = calculateEffectiveBV(scenario, campaign, false); effectiveUnitCount = calculateEffectiveUnitCount(scenario, campaign); for (ScenarioForceTemplate forceTemplate : currentForceTemplates) { @@ -806,7 +806,6 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac generatedEntities.remove(targetUnit); } - logger.info("Final force " + forceBV + '/' + adjustedBvBudget + balancingType + " BV)"); } @@ -833,6 +832,26 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac } } + // simulate bidding away of forces + if (faction.isClan() && campaign.getCampaignOptions().isUseGenericBattleValue()) { + logger.info("Beginning to bid away forces"); + + int playerBattleValue = calculateEffectiveBV(scenario, campaign, true); + int enemyBattleValue = 0; + + for (Entity entity : generatedEntities) { + enemyBattleValue += entity.calculateBattleValue(); + } + + while ((enemyBattleValue > (playerBattleValue * 1.1)) && (generatedEntities.size() > 1)) { + int targetUnit = Compute.randomInt(generatedEntities.size()); + enemyBattleValue -= generatedEntities.get(targetUnit).calculateBattleValue(); + logger.info("Bid away " + generatedEntities.get(targetUnit).getDisplayName()); + + generatedEntities.remove(targetUnit); + } + } + BotForce generatedForce = new BotForce(); generatedForce.setFixedEntityList(generatedEntities); setBotForceParameters(generatedForce, forceTemplate, forceAlignment, contract); @@ -2421,7 +2440,8 @@ private static List generateClanUnitTypes(int unitCount, * @param campaign The campaign in which the scenario resides. * @return Effective BV. */ - public static int calculateEffectiveBV(AtBDynamicScenario scenario, Campaign campaign) { + public static int calculateEffectiveBV(AtBDynamicScenario scenario, Campaign campaign, + boolean forceStandardBattleValue) { // for each deployed player and bot force that's marked as contributing to the // BV budget int bvBudget = 0; @@ -2431,7 +2451,7 @@ public static int calculateEffectiveBV(AtBDynamicScenario scenario, Campaign cam for (int forceID : scenario.getForceIDs()) { ScenarioForceTemplate forceTemplate = scenario.getPlayerForceTemplates().get(forceID); if (forceTemplate != null && forceTemplate.getContributesToBV()) { - bvBudget += campaign.getForce(forceID).getTotalBV(campaign); + bvBudget += campaign.getForce(forceID).getTotalBV(campaign, forceStandardBattleValue); } } @@ -2439,7 +2459,7 @@ public static int calculateEffectiveBV(AtBDynamicScenario scenario, Campaign cam for (UUID unitID : scenario.getIndividualUnitIDs()) { ScenarioForceTemplate forceTemplate = scenario.getPlayerUnitTemplates().get(unitID); if ((forceTemplate != null) && forceTemplate.getContributesToBV()) { - if (campaign.getCampaignOptions().isUseGenericBattleValue()) { + if (campaign.getCampaignOptions().isUseGenericBattleValue() && !forceStandardBattleValue) { bvBudget += campaign.getUnit(unitID).getEntity().getGenericBattleValue(); } else { bvBudget += campaign.getUnit(unitID).getEntity().calculateBattleValue(); diff --git a/MekHQ/src/mekhq/campaign/mission/atb/AtBScenarioModifierApplicator.java b/MekHQ/src/mekhq/campaign/mission/atb/AtBScenarioModifierApplicator.java index 40b804ea51..58d2eda3ee 100644 --- a/MekHQ/src/mekhq/campaign/mission/atb/AtBScenarioModifierApplicator.java +++ b/MekHQ/src/mekhq/campaign/mission/atb/AtBScenarioModifierApplicator.java @@ -64,12 +64,15 @@ public static void addForce(Campaign campaign, AtBDynamicScenario scenario, Scen } /** - * Adds the given force to the scenario after primary forces have been - * generated. + * Adds the given force to the scenario after primary forces have been generated. + * + * @param campaign the current campaign + * @param scenario the current scenario + * @param templateToApply the force template to apply to the scenario */ private static void postAddForce(Campaign campaign, AtBDynamicScenario scenario, ScenarioForceTemplate templateToApply) { - int effectiveBV = AtBDynamicScenarioFactory.calculateEffectiveBV(scenario, campaign); + int effectiveBV = AtBDynamicScenarioFactory.calculateEffectiveBV(scenario, campaign, false); int effectiveUnitCount = AtBDynamicScenarioFactory.calculateEffectiveUnitCount(scenario, campaign); int deploymentZone = AtBDynamicScenarioFactory.calculateDeploymentZone(templateToApply, scenario, templateToApply.getForceName()); diff --git a/MekHQ/src/mekhq/gui/stratcon/ScenarioWizardLanceRenderer.java b/MekHQ/src/mekhq/gui/stratcon/ScenarioWizardLanceRenderer.java index 4b7190ac63..43bc129ae4 100644 --- a/MekHQ/src/mekhq/gui/stratcon/ScenarioWizardLanceRenderer.java +++ b/MekHQ/src/mekhq/gui/stratcon/ScenarioWizardLanceRenderer.java @@ -18,14 +18,13 @@ */ package mekhq.gui.stratcon; -import java.awt.*; - -import javax.swing.*; - import mekhq.campaign.Campaign; import mekhq.campaign.force.Force; import mekhq.campaign.force.Lance; +import javax.swing.*; +import java.awt.*; + /** * Handles rendering of individual lances in the StratCon scenario wizard. * @author NickAragua @@ -56,8 +55,8 @@ public Component getListCellRendererComponent(final JList list, if (lance != null) { roleString = lance.getRole().toString() + ", "; } - - setText(String.format("%s (%sBV: %d)", value.getName(), roleString, value.getTotalBV(campaign))); + + setText(String.format("%s (%sBV: %d)", value.getName(), roleString, value.getTotalBV(campaign, false))); return this; } From d042c3838404e14ac54690543913e4975285f0bc Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 22 Sep 2024 10:59:16 -0500 Subject: [PATCH 10/21] Add verbose bidding mode for Clan forces Implemented a new option "useVerboseBidding" for detailed Clan force bid reporting. Added logic to dynamically adjust and supplement bot forces during scenario generation to maintain balance. Enhanced methods for Clan and mixed unit generation accordingly. --- .../CampaignOptionsDialog.properties | 3 + MekHQ/src/mekhq/campaign/CampaignOptions.java | 34 ++ MekHQ/src/mekhq/campaign/force/Force.java | 44 +- .../mission/AtBDynamicScenarioFactory.java | 403 ++++++++++-------- .../atb/AtBScenarioModifierApplicator.java | 2 +- .../mekhq/gui/panes/CampaignOptionsPane.java | 14 + 6 files changed, 329 insertions(+), 171 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 7963447de3..81fd11ae92 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -902,6 +902,9 @@ chkUseGenericBattleValue.text=Use Generic Battle Value chkUseGenericBattleValue.toolTipText=Bot forces are balanced used Generic Battle Value, an estimation of the average battle value for a unit of that type and weight.\ \ This ignores pilot skill, meaning contracts against Green and Elite OpFors should feel fundamentally different.\ \ Similarly, OpFors with higher or lower than average equipment (such as Clans or Pirates) will present higher or lower difficulty scenarios. +chkUseVerboseBidding.text=Use Verbose Bidding +chkUseVerboseBidding.toolTipText=When Generic BV is in use, Clan OpFors will engage in bidding prior to the scenario.\ + \ If this option is enabled, a list of all units bid away will be provided. chkUseVehicles.text=Use vehicles chkUseVehicles.toolTipText=Enemy forces can include vehicles. chkClanVehicles.text=Clan OpFors use vehicles diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 892ea7a679..021c160ec4 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -567,6 +567,7 @@ public static String getTransitUnitName(final int unit) { // Scenarios private boolean useGenericBattleValue; + private boolean useVerboseBidding; private boolean doubleVehicles; private int opForLanceTypeMeks; private int opForLanceTypeMixed; @@ -1202,6 +1203,7 @@ public CampaignOptions() { // Scenarios useGenericBattleValue = true; + useVerboseBidding = false; doubleVehicles = false; setOpForLanceTypeMeks(1); setOpForLanceTypeMixed(2); @@ -4256,14 +4258,43 @@ public void setClanVehicles(final boolean clanVehicles) { this.clanVehicles = clanVehicles; } + /** + * Returns whether Generic BV is being used. + * + * @return {@code true} if Generic BV is enabled, {@code false} otherwise. + */ public boolean isUseGenericBattleValue() { return useGenericBattleValue; } + + /** + * Sets the flag indicating whether BV Balanced bot forces should use Generic BV. + * + * @param useGenericBattleValue flag indicating whether to use Generic BV + */ public void setUseGenericBattleValue(final boolean useGenericBattleValue) { this.useGenericBattleValue = useGenericBattleValue; } + /** + * Returns whether the verbose bidding mode is enabled. + * + * @return {@code true} if verbose bidding is enabled, {@code false} otherwise. + */ + public boolean isUseVerboseBidding() { + return useVerboseBidding; + } + + /** + * Sets the flag indicating whether verbose bidding should be used. + * + * @param useVerboseBidding flag indicating whether to use verbose bidding + */ + public void setUseVerboseBidding(final boolean useVerboseBidding) { + this.useVerboseBidding = useVerboseBidding; + } + public boolean isDoubleVehicles() { return doubleVehicles; } @@ -5097,6 +5128,7 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useVehicles", useVehicles); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "clanVehicles", clanVehicles); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useGenericBattleValue", useGenericBattleValue); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useVerboseBidding", useVerboseBidding); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "doubleVehicles", doubleVehicles); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "adjustPlayerVehicles", adjustPlayerVehicles); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "opForLanceTypeMeks", getOpForLanceTypeMeks()); @@ -6077,6 +6109,8 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.clanVehicles = Boolean.parseBoolean(wn2.getTextContent().trim()); } else if (wn2.getNodeName().equalsIgnoreCase("useGenericBattleValue")) { retVal.useGenericBattleValue = Boolean.parseBoolean(wn2.getTextContent().trim()); + } else if (wn2.getNodeName().equalsIgnoreCase("useVerboseBidding")) { + retVal.useVerboseBidding = Boolean.parseBoolean(wn2.getTextContent().trim()); } else if (wn2.getNodeName().equalsIgnoreCase("doubleVehicles")) { retVal.doubleVehicles = Boolean.parseBoolean(wn2.getTextContent().trim()); } else if (wn2.getNodeName().equalsIgnoreCase("adjustPlayerVehicles")) { diff --git a/MekHQ/src/mekhq/campaign/force/Force.java b/MekHQ/src/mekhq/campaign/force/Force.java index 41629c55bc..e0b213a5b1 100644 --- a/MekHQ/src/mekhq/campaign/force/Force.java +++ b/MekHQ/src/mekhq/campaign/force/Force.java @@ -22,6 +22,7 @@ package mekhq.campaign.force; import megamek.Version; +import megamek.common.Entity; import megamek.common.annotations.Nullable; import megamek.common.icons.Camouflage; import megamek.logging.MMLogger; @@ -46,6 +47,8 @@ import java.util.*; import java.util.stream.Collectors; +import static java.lang.Math.round; + /** * This is a hierarchical object to define forces for TO&E. Each Force * object can have a parent force object and a vector of child force objects. @@ -567,8 +570,7 @@ public List updateForceIconOperationalStatus( // to get // the ordinal of the force's status. Then assign the operational status to // this. - final int index = (int) Math - .round(statuses.stream().mapToInt(Enum::ordinal).sum() / (statuses.size() * 1.0)); + final int index = (int) round(statuses.stream().mapToInt(Enum::ordinal).sum() / (statuses.size() * 1.0)); final LayeredForceIconOperationalStatus status = LayeredForceIconOperationalStatus.values()[index]; ((LayeredForceIcon) getForceIcon()).getPieces().put(LayeredForceIconLayer.SPECIAL_MODIFIER, new ArrayList<>()); @@ -768,6 +770,44 @@ public int getTotalBV(Campaign campaign, boolean forceStandardBattleValue) { return bvTotal; } + /** + * Calculates the total count of units in the given force, including all sub forces. + * + * @param campaign the current campaign + * @param isClanBidding flag to indicate whether clan bidding is being performed + * @return the total count of units in the force (including sub forces) + */ + public int getTotalUnitCount(Campaign campaign, boolean isClanBidding) { + int unitTotal = 0; + + for (Force subforce : getSubForces()) { + unitTotal += subforce.getTotalUnitCount(campaign, isClanBidding); + } + + // If we're getting the unit count specifically for Clan Bidding, we don't want to count + // Conventional Infantry, and we only count Battle Armor as half a unit. + // If we're not performing Clan Bidding, we just need the total count of units. + if (isClanBidding) { + double rollingCount = 0; + + for (UUID unitId : getUnits()) { + Entity unit = campaign.getUnit(unitId).getEntity(); + + if (unit.isBattleArmor()) { + rollingCount += 0.5; + } else if (!unit.isConventionalInfantry()) { + rollingCount++; + } + } + + unitTotal += (int) round(rollingCount); + } else { + unitTotal += getUnits().size(); + } + + return unitTotal; + } + /** * Calculates the unit type most represented in this force * and all subforces. diff --git a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java index 85f6c12817..8c1c715e02 100644 --- a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java +++ b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java @@ -68,6 +68,8 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; +import static java.lang.Math.round; + /** * This class handles the creation and substantive manipulation of * AtBDynamicScenarios @@ -191,7 +193,7 @@ public static void finalizeScenario(AtBDynamicScenario scenario, AtBContract con scenario.getBotUnitTemplates().clear(); // fix the force unit count at the current time. - int playerForceUnitCount = calculateEffectiveUnitCount(scenario, campaign); + int playerForceUnitCount = calculateEffectiveUnitCount(scenario, campaign, false); // at this point, only the player forces are present and contributing to BV/unit count int generatedLanceCount = generateForces(scenario, contract, campaign); @@ -258,7 +260,7 @@ private static int generateForces(AtBDynamicScenario scenario, AtBContract contr for (int generationOrder : generationOrders) { List currentForceTemplates = orderedForceTemplates.get(generationOrder); effectiveBV = calculateEffectiveBV(scenario, campaign, false); - effectiveUnitCount = calculateEffectiveUnitCount(scenario, campaign); + effectiveUnitCount = calculateEffectiveUnitCount(scenario, campaign, false); for (ScenarioForceTemplate forceTemplate : currentForceTemplates) { if (forceTemplate.getGenerationMethod() == ForceGenerationMethod.FixedMUL.ordinal()) { @@ -398,8 +400,7 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac } break; default: - logger.warn( - String.format("Invalid force alignment %d", forceTemplate.getForceAlignment())); + logger.warn(String.format("Invalid force alignment %d", forceTemplate.getForceAlignment())); } final Faction faction = Factions.getInstance().getFaction(factionCode); @@ -417,19 +418,15 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac int forceBVBudget = (int) (effectiveBV * forceTemplate.getForceMultiplier()); if (isScenarioModifier) { - forceBVBudget = (int) (forceBVBudget * ((double) campaign.getCampaignOptions().getScenarioModBV() / 100) - * forceTemplate.getForceMultiplier()); + forceBVBudget = (int) (forceBVBudget * ((double) campaign.getCampaignOptions().getScenarioModBV() / 100) * forceTemplate.getForceMultiplier()); } int forceUnitBudget = 0; if (forceTemplate.getGenerationMethod() == ForceGenerationMethod.UnitCountScaled.ordinal()) { forceUnitBudget = (int) (effectiveUnitCount * forceTemplate.getForceMultiplier()); - } else if ((forceTemplate.getGenerationMethod() == ForceGenerationMethod.FixedUnitCount.ordinal()) || - (forceTemplate.getGenerationMethod() == ForceGenerationMethod.PlayerOrFixedUnitCount.ordinal())) { - forceUnitBudget = forceTemplate.getFixedUnitCount() == ScenarioForceTemplate.FIXED_UNIT_SIZE_LANCE - ? lanceSize - : forceTemplate.getFixedUnitCount(); + } else if ((forceTemplate.getGenerationMethod() == ForceGenerationMethod.FixedUnitCount.ordinal()) || (forceTemplate.getGenerationMethod() == ForceGenerationMethod.PlayerOrFixedUnitCount.ordinal())) { + forceUnitBudget = forceTemplate.getFixedUnitCount() == ScenarioForceTemplate.FIXED_UNIT_SIZE_LANCE ? lanceSize : forceTemplate.getFixedUnitCount(); } // Conditions parameters - atmospheric pressure, toxic atmosphere, and gravity @@ -442,8 +439,7 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac isLowPressure = true; allowsTanks = false; } else { - mekhq.campaign.universe.Atmosphere specific_atmosphere = contract.getSystem().getPrimaryPlanet() - .getAtmosphere(currentDate); + mekhq.campaign.universe.Atmosphere specific_atmosphere = contract.getSystem().getPrimaryPlanet().getAtmosphere(currentDate); switch (specific_atmosphere) { case TOXICPOISON: case TOXICCAUSTIC: @@ -511,20 +507,17 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac } // If the force template is set up for artillery, add the role to all applicable - // unit - // types including the dynamic Mek/vehicle mixed type + // unit types including the dynamic Mek/vehicle mixed type if (forceTemplate.getUseArtillery()) { int artilleryCarriers = forceTemplate.getAllowedUnitType(); - if (artilleryCarriers == ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_MIX - || artilleryCarriers == UnitType.MEK) { + if (artilleryCarriers == ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_MIX || artilleryCarriers == UnitType.MEK) { if (!requiredRoles.containsKey(UnitType.MEK)) { requiredRoles.put(UnitType.MEK, new HashSet<>()); } requiredRoles.get(UnitType.MEK).add((MissionRole.ARTILLERY)); } - if (artilleryCarriers == ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_MIX - || artilleryCarriers == UnitType.TANK) { + if (artilleryCarriers == ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_MIX || artilleryCarriers == UnitType.TANK) { if (!requiredRoles.containsKey(UnitType.TANK)) { requiredRoles.put(UnitType.TANK, new HashSet<>()); } @@ -568,10 +561,7 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac // Aerospace fighters are added in single flights/points, while conventional // fighters // are added in full squadrons (1-3 flights, 2-6 total). - if (isPlanetOwner && - actualUnitType == ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_AERO_MIX && - scenario.getTemplate().mapParameters.getMapLocation() != MapLocation.Space && - scenario.getAtmosphere().isDenserThan(Atmosphere.THIN)) { + if (isPlanetOwner && actualUnitType == ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_AERO_MIX && scenario.getTemplate().mapParameters.getMapLocation() != MapLocation.Space && scenario.getAtmosphere().isDenserThan(Atmosphere.THIN)) { actualUnitType = Compute.d6() > 3 ? UnitType.AEROSPACEFIGHTER : UnitType.CONV_FIGHTER; lanceSize = getAeroLanceSize(actualUnitType, isPlanetOwner, factionCode); } else if (actualUnitType == ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_AERO_MIX) { @@ -585,12 +575,9 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac if (currentLanceWeightString == null) { generatedLance = new ArrayList<>(); // Hazardous conditions may prohibit deploying infantry or vehicles - } else if ((actualUnitType == UnitType.INFANTRY && !allowsConvInfantry) || - (actualUnitType == UnitType.TANK && !allowsTanks)) { + } else if ((actualUnitType == UnitType.INFANTRY && !allowsConvInfantry) || (actualUnitType == UnitType.TANK && !allowsTanks)) { generatedLance = new ArrayList<>(); - logger - .warn(String.format("Skipping generation of unit type %s due to hostile conditions.", - UnitType.getTypeName(actualUnitType))); + logger.warn(String.format("Skipping generation of unit type %s due to hostile conditions.", UnitType.getTypeName(actualUnitType))); // Gun emplacements use fixed tables instead of the force generator system } else if (actualUnitType == UnitType.GUN_EMPLACEMENT) { @@ -605,67 +592,40 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac // Determine unit types for each unit of the formation. Normally this is all one // type, but SPECIAL_UNIT_TYPE_ATB_MIX may generate all Meks, all vehicles, or // a Mek/vehicle mixed formation. - List unitTypes = generateUnitTypes(actualUnitType, lanceSize, quality, factionCode, - allowsTanks, campaign); + List unitTypes = generateUnitTypes(actualUnitType, lanceSize, quality, factionCode, allowsTanks, campaign); - // Formations composed entirely of Meks, aerospace fighters (but not - // conventional), + // Formations composed entirely of Meks, aerospace fighters (but not conventional), // and ground vehicles use weight categories as do SPECIAL_UNIT_TYPE_ATB_MIX. - // Formations of other types, plus artillery formations, do not use weight - // classes. - if ((actualUnitType == ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_MIX || - IUnitGenerator.unitTypeSupportsWeightClass(actualUnitType)) && - !forceTemplate.getUseArtillery()) { + // Formations of other types, plus artillery formations do not use weight classes. + if ((actualUnitType == ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_MIX || IUnitGenerator.unitTypeSupportsWeightClass(actualUnitType)) && !forceTemplate.getUseArtillery()) { // Generate a specific weight class for each unit based on the formation weight // class and lower/upper bounds - final String unitWeights = generateUnitWeights(unitTypes, factionCode, - AtBConfiguration.decodeWeightStr(currentLanceWeightString, 0), - forceTemplate.getMaxWeightClass(), - forceTemplate.getMinWeightClass(), - requiredRoles, - campaign); + final String unitWeights = generateUnitWeights(unitTypes, factionCode, AtBConfiguration.decodeWeightStr(currentLanceWeightString, 0), forceTemplate.getMaxWeightClass(), forceTemplate.getMinWeightClass(), requiredRoles, campaign); if (unitWeights != null) { - generatedLance = generateLance(factionCode, - skill, - quality, - unitTypes, - unitWeights, - requiredRoles, - campaign); + generatedLance = generateLance(factionCode, skill, quality, unitTypes, unitWeights, requiredRoles, campaign); } else { generatedLance = new ArrayList<>(); } } else { - generatedLance = generateLance(factionCode, - skill, - quality, - unitTypes, - requiredRoles, - campaign); + generatedLance = generateLance(factionCode, skill, quality, unitTypes, requiredRoles, campaign); // If extreme temperatures are present and XCT infantry is not being generated, // swap out standard armor for snowsuits or heat suits as appropriate if (actualUnitType == UnitType.INFANTRY) { for (Entity curPlatoon : generatedLance) { - changeInfantryKit((Infantry) curPlatoon, - isLowPressure, - isTainted, - scenario.getTemperature()); + changeInfantryKit((Infantry) curPlatoon, isLowPressure, isTainted, scenario.getTemperature()); } } } } // If something went wrong with unit generation, stop generating formations and - // work - // with what is already generated + // work with what is already generated if (generatedLance.isEmpty()) { stopGenerating = true; - logger.warn( - String.format("Unable to generate units from RAT: %s, type %d, max weight %d", - factionCode, forceTemplate.getAllowedUnitType(), weightClass)); + logger.warn(String.format("Unable to generate units from RAT: %s, type %d, max weight %d", factionCode, forceTemplate.getAllowedUnitType(), weightClass)); continue; } @@ -701,15 +661,7 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac ArrayList arrayGeneratedLance = new ArrayList<>(generatedLance); // bin fill ratio will be adjusted by the load out generator based on piracy and // quality - ReconfigurationParameters rp = TeamLoadOutGenerator.generateParameters( - cGame, - cGame.getOptions(), - arrayGeneratedLance, - factionCode, - new ArrayList<>(), - new ArrayList<>(), - ownerBaseQuality, - ((isPirate) ? TeamLoadOutGenerator.UNSET_FILL_RATIO : 1.0f)); + ReconfigurationParameters rp = TeamLoadOutGenerator.generateParameters(cGame, cGame.getOptions(), arrayGeneratedLance, factionCode, new ArrayList<>(), new ArrayList<>(), ownerBaseQuality, ((isPirate) ? TeamLoadOutGenerator.UNSET_FILL_RATIO : 1.0f)); rp.isPirate = isPirate; rp.groundMap = onGround; rp.spaceEnvironment = (mapLocation == MapLocation.Space); @@ -717,11 +669,7 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac tlg.reconfigureEntities(arrayGeneratedLance, factionCode, mt, rp); } else { // Load the fighters with bombs - TeamLoadOutGenerator.populateAeroBombs(generatedLance, - campaign.getGameYear(), - onGround, - ownerBaseQuality, - isPirate); + TeamLoadOutGenerator.populateAeroBombs(generatedLance, campaign.getGameYear(), onGround, ownerBaseQuality, isPirate); } } @@ -732,19 +680,16 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac setStartingAltitude(generatedLance, forceTemplate.getStartingAltitude()); correctNonAeroFlyerBehavior(generatedLance, scenario.getBoardType()); - // If force contributes to map size, increment the generated count of formations - // added + // If force contributes to map size, increment the generated count of formations added if (forceTemplate.getContributesToMapSize()) { generatedLanceCount++; } - // Check for mekanized battle armor added to Clan star formations (must be - // exactly - // 5 OmniMeks, no more, no less) - generatedLance.addAll(generateBAForNova(scenario, generatedLance, factionCode, skill, quality, campaign)); + // Check for mekanized battle armor added to Clan star formations (must be exactly 5 + // OmniMeks, no more, no less) + generatedLance.addAll(generateBAForNova(scenario, generatedLance, factionCode, skill, quality, campaign, false)); - // Add the formation member BVs to the running total, and the entities to the - // tracking + // Add the formation member BVs to the running total, and the entities to the tracking // list for (Entity ent : generatedLance) { if (campaign.getCampaignOptions().isUseGenericBattleValue()) { @@ -756,13 +701,11 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac } // Terminate force generation if we've gone over the unit count or BV budget. - // For BV-scaled forces, check whether to stop generating after each formation - // is + // For BV-scaled forces, check whether to stop generating after each formation is // generated. if (forceTemplate.getGenerationMethod() == ForceGenerationMethod.BVScaled.ordinal()) { // Check random number vs. percentage of the BV budget already generated, with - // the - // percentage chosen based on unit rating + // the percentage chosen based on unit rating double currentPercentage = ((double) forceBV / forceBVBudget) * 100; stopGenerating = currentPercentage > targetPercentage; @@ -801,8 +744,7 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac forceBV -= battleValue; - logger.info("Culled " + generatedEntities.get(targetUnit).getDisplayName() - + " (" + battleValue + balancingType + " BV)"); + logger.info("Culled " + generatedEntities.get(targetUnit).getDisplayName() + " (" + battleValue + balancingType + " BV)"); generatedEntities.remove(targetUnit); } @@ -810,56 +752,146 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac } // Units with infantry bays get conventional infantry or battle armor added - List transportedEntities = fillTransports(scenario, - generatedEntities, - factionCode, - skill, - quality, - requiredRoles, - allowsConvInfantry, - campaign); + List transportedEntities = fillTransports(scenario, generatedEntities, factionCode, skill, quality, requiredRoles, allowsConvInfantry, campaign); generatedEntities.addAll(transportedEntities); if (!transportedEntities.isEmpty()) { - // Transported units need to filter out battle armor before applying armor - // changes - for (Entity curPlatoon : transportedEntities.stream().filter(i -> i.getUnitType() == UnitType.INFANTRY) - .toList()) { - changeInfantryKit((Infantry) curPlatoon, - isLowPressure, - isTainted, - scenario.getTemperature()); + // Transported units need to filter out battle armor before applying armor changes + for (Entity curPlatoon : transportedEntities.stream().filter(i -> i.getUnitType() == UnitType.INFANTRY).toList()) { + changeInfantryKit((Infantry) curPlatoon, isLowPressure, isTainted, scenario.getTemperature()); } } - // simulate bidding away of forces + // Simulate bidding away of forces + List bidAwayForces = new ArrayList<>(); + int supplementedForces = 0; + if (faction.isClan() && campaign.getCampaignOptions().isUseGenericBattleValue()) { - logger.info("Beginning to bid away forces"); + bidAwayForces = new ArrayList<>(); + // Player force values int playerBattleValue = calculateEffectiveBV(scenario, campaign, true); - int enemyBattleValue = 0; + int playerUnitValue = calculateEffectiveUnitCount(scenario, campaign, true); + // Bot force values + int botBattleValue = 0; for (Entity entity : generatedEntities) { - enemyBattleValue += entity.calculateBattleValue(); + botBattleValue += entity.calculateBattleValue(); } - while ((enemyBattleValue > (playerBattleValue * 1.1)) && (generatedEntities.size() > 1)) { + // First bid away units that exceed the player's estimated Battle Value + while ((botBattleValue > (playerBattleValue * 1.5)) && (generatedEntities.size() > 1)) { int targetUnit = Compute.randomInt(generatedEntities.size()); - enemyBattleValue -= generatedEntities.get(targetUnit).calculateBattleValue(); - logger.info("Bid away " + generatedEntities.get(targetUnit).getDisplayName()); + bidAwayForces.add(generatedEntities.get(targetUnit).getShortNameRaw()); + botBattleValue -= generatedEntities.get(targetUnit).calculateBattleValue(); generatedEntities.remove(targetUnit); } + + // Next, if the size of the forces results in a Player:Bot unit count ratio of >= 2:1, + // add additional units of Battle Armor to compensate. + int sizeDisparity = playerUnitValue - generatedEntities.size(); + sizeDisparity = (int) round(sizeDisparity * 0.5); + + List allRemainingUnits = new ArrayList<>(generatedEntities); + Collections.shuffle(allRemainingUnits); + + // First, attempt to add Mechanized Battle Armor + Iterator entityIterator = allRemainingUnits.iterator(); + while (entityIterator.hasNext() && sizeDisparity > 0) { + Entity entity = entityIterator.next(); + if (!entity.isOmni()) { + continue; + } + + List generatedBA = generateBAForNova(scenario, List.of(entity), factionCode, skill, quality, campaign, true); + + if (!generatedBA.isEmpty()) { + generatedEntities.addAll(generatedBA); + supplementedForces += generatedBA.size(); + sizeDisparity -= generatedBA.size(); + } + } + + // If there is still a disproportionate size disparity, add loose Battle Armor + for (int i = 0; i < sizeDisparity; i++) { + Entity newEntity = getEntity(factionCode, skill, quality, UnitType.BATTLE_ARMOR, UNIT_WEIGHT_UNSPECIFIED, campaign); + if (newEntity != null) { + generatedEntities.add(newEntity); + supplementedForces++; + } + } } + // Generate the force BotForce generatedForce = new BotForce(); generatedForce.setFixedEntityList(generatedEntities); setBotForceParameters(generatedForce, forceTemplate, forceAlignment, contract); scenario.addBotForce(generatedForce, forceTemplate, campaign); + // Report the bidding results (if any) to the player + if (!bidAwayForces.isEmpty() || supplementedForces > 0) { + reportResultsOfBidding(scenario, campaign, bidAwayForces, generatedForce, supplementedForces); + } + return generatedLanceCount; } + /** + * Reports the results of Clan bidding for a scenario. + * + * @param scenario the scenario for which bidding was done + * @param campaign the campaign in which the bidding took place + * @param bidAwayForces the list of forces bid away by the generated force + * @param generatedForce the force that generated the bid + * @param supplementedForces the number of additional Battle Armor units supplemented to the force + */ + private static void reportResultsOfBidding(AtBDynamicScenario scenario, Campaign campaign, + List bidAwayForces, BotForce generatedForce, + int supplementedForces) { + boolean useVerboseBidding = campaign.getCampaignOptions().isUseVerboseBidding(); + StringBuilder report = new StringBuilder(); + + if (useVerboseBidding) { + for (String unitName : bidAwayForces) { + if (report.isEmpty()) { + report.append(generatedForce.getName()).append(" (") + .append(scenario.getName()).append('/') + .append(scenario.getContract(campaign)).append("") + .append(") has bid away the following forces:

") + .append(unitName).append("
"); + } else { + report.append(unitName).append("
"); + } + } + } else { + report.append(generatedForce.getName()).append(" (") + .append(scenario.getName()).append('/') + .append(scenario.getContract(campaign)).append("") + .append(") has bid away ").append(bidAwayForces.size()) + .append(" units."); + } + + if (supplementedForces > 0) { + if (report.isEmpty()) { + report.append(generatedForce.getName()).append(" (").append(scenario.getName()) + .append('/').append(scenario.getContract(campaign)).append("") + .append(") has "); + } else { + report.append(("They also")); + } + + report.append(" supplemented their force with ") + .append(supplementedForces).append(" additional units of Battle Armor."); + } + + if (!report.isEmpty()) { + report.append("
"); + } + + campaign.addReport(report.toString()); + } + /** * Generates the indicated number of civilian entities. * @@ -1876,8 +1908,7 @@ private static Entity generateTransportedBAUnit(UnitGeneratorParameters params, newParams.getMovementModes().addAll(IUnitGenerator.ALL_BATTLE_ARMOR_MODES); // Set the parameters to filter out types that are too heavy for the provided - // bay space, - // or those that cannot use mechanized BA travel + // bay space, or those that cannot use mechanized BA travel if (bayCapacity != IUnitGenerator.NO_WEIGHT_LIMIT) { newParams.setFilter(inf -> inf.getTons() <= bayCapacity); } else { @@ -1886,8 +1917,7 @@ private static Entity generateTransportedBAUnit(UnitGeneratorParameters params, MekSummary unitData = campaign.getUnitGenerator().generate(newParams); - // If generating for an internal bay fails, try again as mechanized if the flag - // is set + // If generating for an internal bay fails, try again as mechanized if the flag is set if (unitData == null) { if (bayCapacity != IUnitGenerator.NO_WEIGHT_LIMIT && retryAsMechanized) { newParams.setFilter(null); @@ -1904,12 +1934,21 @@ private static Entity generateTransportedBAUnit(UnitGeneratorParameters params, } /** - * Worker function that generates a battle armor unit to attach to a unit of - * clan meks + * Generates and associates Battle Armor units with a designated transport. + * + * @param scenario The current scenario. + * @param starUnits List of {@link Entity} objects representing the star units. + * @param factionCode The code of the faction. + * @param skill The skill level. + * @param quality The quality level. + * @param campaign The current campaign. + * @param forceBASpawn Flag to determine whether to bypass the Star size check and BA + * spawn dice roll + * @return List of {@link Entity} objects representing the transported Battle Armor. */ public static List generateBAForNova(AtBScenario scenario, List starUnits, - String factionCode, SkillLevel skill, int quality, - Campaign campaign) { + String factionCode, SkillLevel skill, int quality, Campaign campaign, + boolean forceBASpawn) { List transportedUnits = new ArrayList<>(); // determine if this should be a nova @@ -1919,25 +1958,26 @@ public static List generateBAForNova(AtBScenario scenario, List // non-clan forces and units that aren't stars don't become novas // TODO: allow for non-Clan integrated mechanized formations, like WOB choirs, // as well as stars that are short one or more omnis - if (!Factions.getInstance().getFaction(factionCode).isClan() && (starUnits.size() != 5)) { + if (!Factions.getInstance().getFaction(factionCode).isClan() + && ((starUnits.size() != 5) || forceBASpawn)) { return transportedUnits; } - // logic copied from AtBScenario.addStar() to randomly determine if the given - // unit is actually going to be a nova - // adjusted from 11/8 to 8/6 (distribution of novas in newest AtB doc is a lot - // higher) so that players actually encounter novas - // whatever CBS is still gets no novas, so there - int roll = Compute.d6(2); - int novaTarget = 8; - if (factionCode.equals("CHH") || factionCode.equals("CSL")) { - novaTarget = 6; - } else if (factionCode.equals("CBS")) { - novaTarget = 13; - } + if (!forceBASpawn) { + // logic copied from AtBScenario.addStar() to randomly determine if the given + // unit is actually going to be a nova adjusted from 11/8 to 8/6 so that players + // actually encounter novas. + int roll = Compute.d6(2); + int novaTarget = 8; + if (factionCode.equals("CHH") || factionCode.equals("CSL")) { + novaTarget = 6; + } else if (factionCode.equals("CBS")) { + novaTarget = 13; + } - if (roll < novaTarget) { - return transportedUnits; + if (roll < novaTarget) { + return transportedUnits; + } } Entity actualTransport = null; @@ -2244,20 +2284,39 @@ private static List generateUnitTypes(int unitTypeCode, return generateClanUnitTypes(unitCount, forceQuality, factionCode, campaign); } - // Use the Mek/vehicle/mixed ratios from campaign options as weighted values for - // random unit type - int totalWeight = campaign.getCampaignOptions().getOpForLanceTypeMeks() + - campaign.getCampaignOptions().getOpForLanceTypeMixed() + - campaign.getCampaignOptions().getOpForLanceTypeVehicles(); + // Use the Mek/Vehicle/Mixed ratios from campaign options as weighted values for + // random unit types. + // Then modify based on faction. + int mekLanceWeight = campaign.getCampaignOptions().getOpForLanceTypeMeks(); + int mixedLanceWeight = campaign.getCampaignOptions().getOpForLanceTypeMixed(); + int vehicleLanceWeight = campaign.getCampaignOptions().getOpForLanceTypeVehicles(); + + if (faction.isClan()) { + if (Objects.equals(factionCode, "CHH")) { + mixedLanceWeight++; + vehicleLanceWeight++; + } else { + mekLanceWeight++; + mixedLanceWeight = Math.max(0, mixedLanceWeight - 1); + vehicleLanceWeight = Math.max(0, vehicleLanceWeight - 1); + } + } else if (faction.isMinorPower() || faction.isPirate()) { + mekLanceWeight = Math.max(0, mekLanceWeight - 1); + mixedLanceWeight++; + vehicleLanceWeight++; + } + + int totalWeight = mekLanceWeight + mixedLanceWeight + vehicleLanceWeight; + + // Roll for unit types if (totalWeight <= 0) { actualUnitType = UnitType.MEK; } else { int roll = Compute.randomInt(totalWeight); - if (roll < campaign.getCampaignOptions().getOpForLanceTypeVehicles()) { + if (roll < vehicleLanceWeight) { actualUnitType = UnitType.TANK; - // Mixed units randomly select between Mek or ground vehicle - } else if (roll < campaign.getCampaignOptions().getOpForLanceTypeVehicles() + - campaign.getCampaignOptions().getOpForLanceTypeMixed()) { + // Mixed units randomly select between Mek or ground vehicle + } else if (roll < vehicleLanceWeight + mixedLanceWeight) { for (int x = 0; x < unitCount; x++) { boolean addTank = Compute.randomInt(2) == 0; if (addTank) { @@ -2275,9 +2334,12 @@ private static List generateUnitTypes(int unitTypeCode, actualUnitType = UnitType.MEK; } } + + // Add unit types to the list of actual unity types for (int x = 0; x < unitCount; x++) { unitTypes.add(actualUnitType); } + return unitTypes; } @@ -2470,9 +2532,9 @@ public static int calculateEffectiveBV(AtBDynamicScenario scenario, Campaign cam double bvMultiplier = scenario.getEffectivePlayerBVMultiplier(); if (bvMultiplier > 0) { - bvBudget = (int) Math.round(bvBudget * scenario.getEffectivePlayerBVMultiplier() * difficultyMultiplier); + bvBudget = (int) round(bvBudget * scenario.getEffectivePlayerBVMultiplier() * difficultyMultiplier); } else { - bvBudget = (int) Math.round(bvBudget * difficultyMultiplier); + bvBudget = (int) round(bvBudget * difficultyMultiplier); } // allied bot forces that contribute to BV do not get multiplied by the @@ -2490,32 +2552,34 @@ public static int calculateEffectiveBV(AtBDynamicScenario scenario, Campaign cam } /** - * Calculates from scratch the current effective player and allied unit count - * present in the given scenario. + * Calculates the current effective player and allied unit count present in the given scenario. * * @param scenario The scenario to process. * @param campaign The campaign in which the scenario resides. - * @return Effective BV. + * @param isClanBidding {@link Boolean} flag indicating if Clan bidding is enabled. + * @return The effective unit count for the scenario. */ - public static int calculateEffectiveUnitCount(AtBDynamicScenario scenario, Campaign campaign) { + public static int calculateEffectiveUnitCount(AtBDynamicScenario scenario, Campaign campaign, + boolean isClanBidding) { // for each deployed player and bot force that's marked as contributing to the - // BV budget + // unit count budget int unitCount = 0; double difficultyMultiplier = getDifficultyMultiplier(campaign); // deployed player forces: for (int forceID : scenario.getForceIDs()) { ScenarioForceTemplate forceTemplate = scenario.getPlayerForceTemplates().get(forceID); + Force force = campaign.getForce(forceID); + if (forceTemplate != null && forceTemplate.getContributesToUnitCount()) { - int forceUnitCount = campaign.getForce(forceID).getUnits().size(); - unitCount += forceUnitCount; + unitCount += force.getTotalUnitCount(campaign, isClanBidding); } } // deployed individual player units for (UUID unitID : scenario.getIndividualUnitIDs()) { ScenarioForceTemplate forceTemplate = scenario.getPlayerUnitTemplates().get(unitID); - if ((forceTemplate != null) && forceTemplate.getContributesToBV()) { + if ((forceTemplate != null) && forceTemplate.getContributesToUnitCount()) { unitCount++; } } @@ -2524,8 +2588,7 @@ public static int calculateEffectiveUnitCount(AtBDynamicScenario scenario, Campa unitCount = (int) Math.floor((double) unitCount * difficultyMultiplier); // allied bot forces that contribute to BV do not get multiplied by the - // difficulty - // even if the player is super good, the AI doesn't get any better + // difficulty even if the player is good, the AI doesn't get any better for (int index = 0; index < scenario.getNumBots(); index++) { BotForce botForce = scenario.getBotForce(index); ScenarioForceTemplate forceTemplate = scenario.getBotForceTemplates().get(botForce); @@ -2561,14 +2624,18 @@ private static double getDifficultyMultiplier(Campaign campaign) { * @return the randomly generated {@link EntityWeightClass} */ public static int randomForceWeight() { - int roll = Compute.d6(2); - - return switch (roll) { - case 2, 3, 4 -> EntityWeightClass.WEIGHT_LIGHT; - case 8, 9, 10 -> EntityWeightClass.WEIGHT_HEAVY; - case 11, 12 -> EntityWeightClass.WEIGHT_ASSAULT; - default -> EntityWeightClass.WEIGHT_MEDIUM; // 5, 6, 7 - }; + int roll = Compute.randomInt(89); + + // These values are based the random force weight table found on page 265 of Total Warfare + if (roll < 19) { // 19% + return EntityWeightClass.WEIGHT_LIGHT; + } else if (roll < 50) { // 31% + return EntityWeightClass.WEIGHT_MEDIUM; + } else if (roll < 75) { // 25% + return EntityWeightClass.WEIGHT_HEAVY; + } else { // 14% + return EntityWeightClass.WEIGHT_ASSAULT; + } } /** diff --git a/MekHQ/src/mekhq/campaign/mission/atb/AtBScenarioModifierApplicator.java b/MekHQ/src/mekhq/campaign/mission/atb/AtBScenarioModifierApplicator.java index 58d2eda3ee..a558b6d06c 100644 --- a/MekHQ/src/mekhq/campaign/mission/atb/AtBScenarioModifierApplicator.java +++ b/MekHQ/src/mekhq/campaign/mission/atb/AtBScenarioModifierApplicator.java @@ -73,7 +73,7 @@ public static void addForce(Campaign campaign, AtBDynamicScenario scenario, Scen private static void postAddForce(Campaign campaign, AtBDynamicScenario scenario, ScenarioForceTemplate templateToApply) { int effectiveBV = AtBDynamicScenarioFactory.calculateEffectiveBV(scenario, campaign, false); - int effectiveUnitCount = AtBDynamicScenarioFactory.calculateEffectiveUnitCount(scenario, campaign); + int effectiveUnitCount = AtBDynamicScenarioFactory.calculateEffectiveUnitCount(scenario, campaign, false); int deploymentZone = AtBDynamicScenarioFactory.calculateDeploymentZone(templateToApply, scenario, templateToApply.getForceName()); diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index bc069ef0d8..4897a0eea2 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -667,6 +667,7 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { // scenarios private JCheckBox chkUseGenericBattleValue; + private JCheckBox chkUseVerboseBidding; private JCheckBox chkDoubleVehicles; private JSpinner spnOpForLanceTypeMeks; private JSpinner spnOpForLanceTypeMixed; @@ -3224,6 +3225,17 @@ private JScrollPane createAgainstTheBotTab() { gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; panSubAtBScenario.add(chkUseGenericBattleValue, gridBagConstraints); + chkUseVerboseBidding = new JCheckBox(resources.getString("chkUseVerboseBidding.text")); + chkUseVerboseBidding.setToolTipText(wordWrap(resources.getString("chkUseVerboseBidding.toolTipText"))); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = yTablePosition++; + gridBagConstraints.gridwidth = 2; + gridBagConstraints.fill = GridBagConstraints.NONE; + gridBagConstraints.insets = new Insets(5, 5, 5, 5); + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + panSubAtBScenario.add(chkUseVerboseBidding, gridBagConstraints); + chkDoubleVehicles = new JCheckBox(resources.getString("chkDoubleVehicles.text")); chkDoubleVehicles.setToolTipText(resources.getString("chkDoubleVehicles.toolTipText")); gridBagConstraints = new GridBagConstraints(); @@ -9045,6 +9057,7 @@ public void setOptions(@Nullable CampaignOptions options, chkGenerateChases.setSelected(options.isGenerateChases()); chkUseGenericBattleValue.setSelected(options.isUseGenericBattleValue()); + chkUseVerboseBidding.setSelected(options.isUseVerboseBidding()); chkDoubleVehicles.setSelected(options.isDoubleVehicles()); spnOpForLanceTypeMeks.setValue(options.getOpForLanceTypeMeks()); spnOpForLanceTypeMixed.setValue(options.getOpForLanceTypeMixed()); @@ -9634,6 +9647,7 @@ public void updateOptions() { options.setClanVehicles(chkClanVehicles.isSelected()); options.setAutoConfigMunitions(chkAutoConfigMunitions.isSelected()); options.setUseGenericBattleValue(chkUseGenericBattleValue.isSelected()); + options.setUseVerboseBidding(chkUseVerboseBidding.isSelected()); options.setDoubleVehicles(chkDoubleVehicles.isSelected()); options.setAdjustPlayerVehicles(chkAdjustPlayerVehicles.isSelected()); options.setOpForLanceTypeMeks((Integer) spnOpForLanceTypeMeks.getValue()); From 5970df3ac0cf9b209d5182e9d6b00a7ea34155c9 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 22 Sep 2024 15:01:50 -0500 Subject: [PATCH 11/21] Update AtBDynamicScenarioFactory: Add Batchall initiation Incorporated new logic to initiate Batchall with detailed prompts and enhanced user interactions. Added functionality to adjust bot forces based on player responses and introduced localized resource strings for varied Batchall statements. --- .../AtBDynamicScenarioFactory.properties | 44 +++ .../mission/AtBDynamicScenarioFactory.java | 323 +++++++++++++----- 2 files changed, 291 insertions(+), 76 deletions(-) create mode 100644 MekHQ/resources/mekhq/resources/AtBDynamicScenarioFactory.properties diff --git a/MekHQ/resources/mekhq/resources/AtBDynamicScenarioFactory.properties b/MekHQ/resources/mekhq/resources/AtBDynamicScenarioFactory.properties new file mode 100644 index 0000000000..0946634095 --- /dev/null +++ b/MekHQ/resources/mekhq/resources/AtBDynamicScenarioFactory.properties @@ -0,0 +1,44 @@ +# initiateBatchall +incomingTransmission.title=++INCOMING TRANSMISSION++ + +nameRedacted.text=[REDACTED] +starCommander.text=Star Commander +starCaptain.text=Star Captain +starColonel.text=Star Colonel +batchallOpener.text=
%s, %s

"I am %s %s commanding the %s forces on %s."
+ +batchallStatementCBS.text="Blood calls for blood. What forces do you commit to the challenge?" +batchallStatementCB.text="We come with the strength of the Burrock. What do you offer in return?" +batchallStatementCCC.text="The storm is coming. Will you weather it, or shall we take all that you hold dear?" +batchallStatementCCO.text="The Coyote hunts. What do you commit to the chase?" +batchallStatementCFM.text="We are the fire that consumes all. What will you offer us to avoid the blaze?" +batchallStatementCGB.text="We are Clan Ghost Bear. This world is ours. Those who dispute our claim must identify the size and location of their forces for immediate disposal." +batchallStatementCGS.text="We are the scorpion, relentless in pursuit. What do you bid for your survival?" +batchallStatementCHH.text="The stampede is upon you. Do you have the strength to stand?" +batchallStatementCIH.text="The Hellions are unleashed. Will you surrender before the cold death claims you?" +batchallStatementCJF.text="What forces dare to defend this world from the steel talons of the Jade Falcon?" +batchallStatementCMG.text="The mongoose hunts. What resists our claim?" +batchallStatementCNC.text="We are the Nova Cat, and we see your end. Will you face your fate or flee?" +batchallStatementCDS.text="We take what we desire. What will you offer in defense?" +batchallStatementCSJ.text="The Smoke Jaguar claims this world. Identify the forces that defend it so that we from the mists of space may know on whom we pounce." +batchallStatementCSR.text="The Raven flies. Will you submit to our talons?" +batchallStatementCSA.text="The Star Adder rises. What will you give us in battle?" +batchallStatementCSV.text="The Viper strikes swiftly. How will you resist our venom?" +batchallStatementCSL.text="The Lion is upon you. Will you fight for your claim, or will you fall?" +batchallStatementCWI.text="The Widowmaker has come for its prize. What will you wager in return?" +batchallStatementCW.text="The Wolves of Kerensky have claimed this world for their own. What tame dogs defend it?" +batchallStatementCWIE.text="The Wolf returns to claim what was lost. How will you answer?" +batchallStatementCWOV.text="The Wolverine fights to the last. Will you rise to the challenge, or be swept aside?" +batchallStatementGeneric.text="We come to claim what is rightfully ours. What forces will you commit to defend it?" + +batchallCloser.text=

Do you accept the Batchall? +responseAccept.text=Accept +responseRefuse.text=Refuse + +refusalReport.text=
YOU DARE REFUSE MY BATCHALL!?!
+ +# reportResultsOfBidding +bidAwayForcesVerbose.text=%s (%s/%s) has bid away the following forces:

%s
+bidAwayForces.text=%s (%s/%s) has bid away %s units. +addedBattleArmorNewReport.text=%s (%s/%s) has supplemented their force with %s additional unit/s of Battle Armor. +addedBattleArmorContinueReport.text=

They also supplemented their force with %s additional unit/s of Battle Armor. diff --git a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java index 8c1c715e02..1e38f13a31 100644 --- a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java +++ b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java @@ -36,6 +36,7 @@ import megamek.logging.MMLogger; import megamek.utilities.BoardClassifier; import mekhq.MHQConstants; +import mekhq.MekHQ; import mekhq.campaign.Campaign; import mekhq.campaign.CampaignOptions; import mekhq.campaign.againstTheBot.AtBConfiguration; @@ -50,7 +51,9 @@ import mekhq.campaign.mission.atb.AtBScenarioModifier; import mekhq.campaign.mission.atb.AtBScenarioModifier.EventTiming; import mekhq.campaign.personnel.Bloodname; +import mekhq.campaign.personnel.Person; import mekhq.campaign.personnel.SkillType; +import mekhq.campaign.personnel.enums.PersonnelRole; import mekhq.campaign.personnel.enums.Phenotype; import mekhq.campaign.rating.IUnitRating; import mekhq.campaign.stratcon.StratconBiomeManifest; @@ -59,11 +62,12 @@ import mekhq.campaign.universe.*; import mekhq.campaign.universe.Faction.Tag; import mekhq.campaign.universe.enums.EraFlag; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import javax.swing.*; +import java.awt.*; import java.io.File; import java.time.LocalDate; +import java.util.List; import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -93,7 +97,10 @@ public class AtBDynamicScenarioFactory { private static final int COMSTAR_LANCE_SIZE = 6; private static final int REINFORCEMENT_ARRIVAL_SCALE = 30; - private static final Logger log = LogManager.getLogger(AtBDynamicScenarioFactory.class); + + private static final ResourceBundle resources = ResourceBundle.getBundle( + "mekhq.resources.AtBDynamicScenarioFactory", + MekHQ.getMHQOptions().getLocale()); /** * Method that sets some initial scenario parameters from the given template, @@ -744,7 +751,8 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac forceBV -= battleValue; - logger.info("Culled " + generatedEntities.get(targetUnit).getDisplayName() + " (" + battleValue + balancingType + " BV)"); + logger.info("Culled " + generatedEntities.get(targetUnit).getDisplayName() + + " (" + battleValue + balancingType + " BV)"); generatedEntities.remove(targetUnit); } @@ -752,7 +760,8 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac } // Units with infantry bays get conventional infantry or battle armor added - List transportedEntities = fillTransports(scenario, generatedEntities, factionCode, skill, quality, requiredRoles, allowsConvInfantry, campaign); + List transportedEntities = fillTransports(scenario, generatedEntities, factionCode, + skill, quality, requiredRoles, allowsConvInfantry, campaign); generatedEntities.addAll(transportedEntities); if (!transportedEntities.isEmpty()) { @@ -762,79 +771,246 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac } } - // Simulate bidding away of forces - List bidAwayForces = new ArrayList<>(); - int supplementedForces = 0; + // Generate the force + BotForce generatedForce = new BotForce(); + generatedForce.setFixedEntityList(generatedEntities); + setBotForceParameters(generatedForce, forceTemplate, forceAlignment, contract); + scenario.addBotForce(generatedForce, forceTemplate, campaign); - if (faction.isClan() && campaign.getCampaignOptions().isUseGenericBattleValue()) { - bidAwayForces = new ArrayList<>(); + boolean batchallAccepted = initiateBatchall(campaign, factionCode, generatedForce, contract.getName(), scenario.getName()); - // Player force values - int playerBattleValue = calculateEffectiveBV(scenario, campaign, true); - int playerUnitValue = calculateEffectiveUnitCount(scenario, campaign, true); + if (batchallAccepted) { + // Simulate bidding away of forces + List bidAwayForces = new ArrayList<>(); + int supplementedForces = 0; - // Bot force values - int botBattleValue = 0; - for (Entity entity : generatedEntities) { - botBattleValue += entity.calculateBattleValue(); - } + if (faction.isClan() && campaign.getCampaignOptions().isUseGenericBattleValue()) { + // Add dialog + bidAwayForces = new ArrayList<>(); - // First bid away units that exceed the player's estimated Battle Value - while ((botBattleValue > (playerBattleValue * 1.5)) && (generatedEntities.size() > 1)) { - int targetUnit = Compute.randomInt(generatedEntities.size()); - bidAwayForces.add(generatedEntities.get(targetUnit).getShortNameRaw()); + // Player force values + int playerBattleValue = calculateEffectiveBV(scenario, campaign, true); + int playerUnitValue = calculateEffectiveUnitCount(scenario, campaign, true); - botBattleValue -= generatedEntities.get(targetUnit).calculateBattleValue(); - generatedEntities.remove(targetUnit); - } + // Bot force values + int botBattleValue = 0; + for (Entity entity : generatedForce.getFullEntityList(campaign)) { + botBattleValue += entity.calculateBattleValue(); + } - // Next, if the size of the forces results in a Player:Bot unit count ratio of >= 2:1, - // add additional units of Battle Armor to compensate. - int sizeDisparity = playerUnitValue - generatedEntities.size(); - sizeDisparity = (int) round(sizeDisparity * 0.5); + // First bid away units that exceed the player's estimated Battle Value + while ((botBattleValue > (playerBattleValue * 1.5)) && (generatedForce.getFullEntityList(campaign).size() > 1)) { + int targetUnit = Compute.randomInt(generatedForce.getFullEntityList(campaign).size()); + bidAwayForces.add(generatedForce.getFullEntityList(campaign).get(targetUnit).getShortNameRaw()); + botBattleValue -= generatedForce.getFullEntityList(campaign).get(targetUnit).calculateBattleValue(); + generatedForce.removeEntity(targetUnit); + } - List allRemainingUnits = new ArrayList<>(generatedEntities); - Collections.shuffle(allRemainingUnits); + // Next, if the size of the forces results in a Player:Bot unit count ratio of >= 2:1, + // add additional units of Battle Armor to compensate. + int sizeDisparity = playerUnitValue - generatedForce.getFullEntityList(campaign).size(); + sizeDisparity = (int) round(sizeDisparity * 0.5); - // First, attempt to add Mechanized Battle Armor - Iterator entityIterator = allRemainingUnits.iterator(); - while (entityIterator.hasNext() && sizeDisparity > 0) { - Entity entity = entityIterator.next(); - if (!entity.isOmni()) { - continue; - } + List allRemainingUnits = new ArrayList<>(generatedForce.getFullEntityList(campaign)); + Collections.shuffle(allRemainingUnits); - List generatedBA = generateBAForNova(scenario, List.of(entity), factionCode, skill, quality, campaign, true); + // First, attempt to add Mechanized Battle Armor + Iterator entityIterator = allRemainingUnits.iterator(); + while (entityIterator.hasNext() && sizeDisparity > 0) { + Entity entity = entityIterator.next(); + if (!entity.isOmni()) { + continue; + } + + List generatedBA = generateBAForNova(scenario, List.of(entity), factionCode, + skill, quality, campaign, true); - if (!generatedBA.isEmpty()) { - generatedEntities.addAll(generatedBA); - supplementedForces += generatedBA.size(); - sizeDisparity -= generatedBA.size(); + if (!generatedBA.isEmpty()) { + for (Entity battleArmor : generatedBA) { + generatedForce.addEntity(battleArmor); + } + supplementedForces += generatedBA.size(); + sizeDisparity -= generatedBA.size(); + } } - } - // If there is still a disproportionate size disparity, add loose Battle Armor - for (int i = 0; i < sizeDisparity; i++) { - Entity newEntity = getEntity(factionCode, skill, quality, UnitType.BATTLE_ARMOR, UNIT_WEIGHT_UNSPECIFIED, campaign); - if (newEntity != null) { - generatedEntities.add(newEntity); - supplementedForces++; + // If there is still a disproportionate size disparity, add loose Battle Armor + for (int i = 0; i < sizeDisparity; i++) { + Entity newEntity = getEntity(factionCode, skill, quality, UnitType.BATTLE_ARMOR, + UNIT_WEIGHT_UNSPECIFIED, campaign); + if (newEntity != null) { + generatedForce.addEntity(newEntity); + supplementedForces++; + } } } + + // Report the bidding results (if any) to the player + if (!bidAwayForces.isEmpty() || supplementedForces > 0) { + reportResultsOfBidding(scenario, campaign, bidAwayForces, generatedForce, supplementedForces); + } } - // Generate the force - BotForce generatedForce = new BotForce(); - generatedForce.setFixedEntityList(generatedEntities); - setBotForceParameters(generatedForce, forceTemplate, forceAlignment, contract); - scenario.addBotForce(generatedForce, forceTemplate, campaign); + return generatedLanceCount; + } + + /** + * Initiates a batchall. Prompts the player with a message and options to accept or refuse the + * batchall. + * + * @param campaign The campaign object. + * @param factionCode The faction code. + * @param generatedForce The generated force object. + * @param contractName The contract name. + * @param scenarioName The scenario name. + * @return {@code true} if the batchall is accepted, {@code false} otherwise. + */ + private static boolean initiateBatchall(Campaign campaign, String factionCode, BotForce generatedForce, + String contractName, String scenarioName) { + // Set the title of the dialog + String title = resources.getString("incomingTransmission.title"); + + // Generate the list of entities involved in the campaign + List entityList = generatedForce.getFullEntityList(campaign); + int unitCount = entityList.size(); + + int highestBattleValue = 0; + Entity commandEntity = null; + + // Find the entity with the highest battle value + for (Entity entity : generatedForce.getFullEntityList(campaign)) { + int battleValue = entity.calculateBattleValue(); + + if (battleValue > highestBattleValue) { + highestBattleValue = battleValue; + commandEntity = entity; + } + } + + // Hold the portrait of the commander + ImageIcon portrait = null; + + // Assign an appropriate portrait to the commander + if (commandEntity != null) { + Person dummyPerson = new Person(campaign); + dummyPerson.setClanPersonnel(true); + dummyPerson.setPrimaryRole(campaign, deriveRoleFromUnitType(commandEntity)); + campaign.assignRandomPortraitFor(dummyPerson); + commandEntity.getCrew().setPortrait(dummyPerson.getPortrait(), 0); + portrait = commandEntity.getCrew().getPortrait(0).getImageIcon(128); + } + + // Determine the rank of the commander based on the unit count + String rank; + if (unitCount <= 5) { + rank = resources.getString("starCommander.text"); + } else if (unitCount <= 15) { + rank = resources.getString("starCaptain.text"); + } else { + rank = resources.getString("starColonel.text"); + } + + // Generate the batchall statement according to the faction code + String batchallStatement = switch (factionCode) { + case "CBS" -> resources.getString("batchallStatementCBS.text"); + case "CB" -> resources.getString("batchallStatementCB.text"); + case "CCC" -> resources.getString("batchallStatementCCC.text"); + case "CCO" -> resources.getString("batchallStatementCCO.text"); + case "CFM" -> resources.getString("batchallStatementCFM.text"); + case "CGB" -> resources.getString("batchallStatementCGB.text"); + case "CGS" -> resources.getString("batchallStatementCGS.text"); + case "CHH" -> resources.getString("batchallStatementCHH.text"); + case "CIH" -> resources.getString("batchallStatementCIH.text"); + case "CJF" -> resources.getString("batchallStatementCJF.text"); + case "CMG" -> resources.getString("batchallStatementCMG.text"); + case "CNC" -> resources.getString("batchallStatementCNC.text"); + case "CDS" -> resources.getString("batchallStatementCDS.text"); + case "CSJ" -> resources.getString("batchallStatementCSJ.text"); + case "CSR" -> resources.getString("batchallStatementCSR.text"); + case "CSA" -> resources.getString("batchallStatementCSA.text"); + case "CSV" -> resources.getString("batchallStatementCSV.text"); + case "CSL" -> resources.getString("batchallStatementCSL.text"); + case "CWI" -> resources.getString("batchallStatementCWI.text"); + case "CW" -> resources.getString("batchallStatementCW.text"); + case "CWIE" -> resources.getString("batchallStatementCWIE.text"); + case "CWOV" -> resources.getString("batchallStatementCWOV.text"); + default -> resources.getString("batchallStatementGeneric.text"); + }; - // Report the bidding results (if any) to the player - if (!bidAwayForces.isEmpty() || supplementedForces > 0) { - reportResultsOfBidding(scenario, campaign, bidAwayForces, generatedForce, supplementedForces); + // Prepare the name of the commander + String commander = resources.getString("nameRedacted.text"); + if (commandEntity != null) { + commander = commandEntity.getCrew().getName(0); } - return generatedLanceCount; + // Prepare the display message for the dialog + String message = String.format(resources.getString("batchallOpener.text"), + contractName, scenarioName, rank, commander, generatedForce.getName(), + campaign.getLocation().getPlanet().getName(campaign.getLocalDate())); + message = message + batchallStatement; + message = message + resources.getString("batchallCloser.text"); + + // Create a pane to display both the message and the commander's portrait + JTextPane textPane = new JTextPane(); + textPane.setContentType("text/html"); + textPane.setText(message); + textPane.setEditable(false); + + JPanel panel = new JPanel(new BorderLayout()); + if (portrait != null) { + JLabel imageLabel = new JLabel(portrait); + panel.add(imageLabel, BorderLayout.CENTER); + } + panel.add(textPane, BorderLayout.SOUTH); + + // Prepare the options for the dialog + Object[] options = { + resources.getString("responseAccept.text"), + resources.getString("responseRefuse.text") + }; + + // Display the dialog and capture the response + int optionDialog = JOptionPane.showOptionDialog(null, panel, title, + JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); + + // Handle the response + if (optionDialog == JOptionPane.NO_OPTION) { + // Report refusal of the batchall + campaign.addReport(resources.getString("refusalReport.text")); + return false; + } else { + return true; + } + } + + /** + * Derives the personnel role based on the unit type of the given entity. + * + * @param entity The entity whose unit type needs to be evaluated. + * @return The personnel role derived from the unit type of the entity. + */ + private static PersonnelRole deriveRoleFromUnitType(Entity entity) { + if (entity.isAerospaceFighter()) { + return PersonnelRole.AEROSPACE_PILOT; + } else if (entity.isBattleArmor()) { + return PersonnelRole.BATTLE_ARMOUR; + } else if (entity.isConventionalFighter()) { + return PersonnelRole.CONVENTIONAL_AIRCRAFT_PILOT; + } else if (entity.isNaval()) { + return PersonnelRole.NAVAL_VEHICLE_DRIVER; + } else if (entity.isProtoMek()) { + return PersonnelRole.PROTOMEK_PILOT; + } else if (entity.isConventionalInfantry()) { + return PersonnelRole.SOLDIER; + } else if (entity.isAirborneVTOLorWIGE()) { + return PersonnelRole.VTOL_PILOT; + } else if (entity.isVehicle()) { + return PersonnelRole.GROUND_VEHICLE_DRIVER; + } else if (entity.isLargeCraft() || entity.isSmallCraft()) { + return PersonnelRole.VESSEL_PILOT; + } else { + return PersonnelRole.MEKWARRIOR; + } } /** @@ -855,34 +1031,29 @@ private static void reportResultsOfBidding(AtBDynamicScenario scenario, Campaign if (useVerboseBidding) { for (String unitName : bidAwayForces) { if (report.isEmpty()) { - report.append(generatedForce.getName()).append(" (") - .append(scenario.getName()).append('/') - .append(scenario.getContract(campaign)).append("") - .append(") has bid away the following forces:

") - .append(unitName).append("
"); + report.append(String.format(resources.getString("bidAwayForcesVerbose.text"), + generatedForce.getName(), scenario.getName(), scenario.getContract(campaign), + unitName)); } else { report.append(unitName).append("
"); } } } else { - report.append(generatedForce.getName()).append(" (") - .append(scenario.getName()).append('/') - .append(scenario.getContract(campaign)).append("") - .append(") has bid away ").append(bidAwayForces.size()) - .append(" units."); + report.append(String.format(resources.getString("bidAwayForces.text"), + generatedForce.getName(), scenario.getName(), scenario.getContract(campaign), + bidAwayForces.size())); } if (supplementedForces > 0) { if (report.isEmpty()) { - report.append(generatedForce.getName()).append(" (").append(scenario.getName()) - .append('/').append(scenario.getContract(campaign)).append("") - .append(") has "); + report.append(String.format(resources.getString("addedBattleArmorNewReport.text"), + generatedForce.getName(), scenario.getName(), scenario.getContract(campaign), + supplementedForces)); } else { - report.append(("They also")); + report.append(String.format( + resources.getString("addedBattleArmorContinueReport.text"), + supplementedForces)); } - - report.append(" supplemented their force with ") - .append(supplementedForces).append(" additional units of Battle Armor."); } if (!report.isEmpty()) { From f31a9a0b936ce689372baa8a322a41b82a0c3da7 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 22 Sep 2024 15:03:37 -0500 Subject: [PATCH 12/21] Fix Batchall initiation for non-player forces Ensured Batchall initiation occurs only for non-player forces by checking the team's alignment before calling the method. Also adjusted the reporting of bidding results to apply the same condition, preventing unnecessary reporting for player forces. --- .../campaign/mission/AtBDynamicScenarioFactory.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java index 1e38f13a31..bb14439b5a 100644 --- a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java +++ b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java @@ -777,7 +777,10 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac setBotForceParameters(generatedForce, forceTemplate, forceAlignment, contract); scenario.addBotForce(generatedForce, forceTemplate, campaign); - boolean batchallAccepted = initiateBatchall(campaign, factionCode, generatedForce, contract.getName(), scenario.getName()); + boolean batchallAccepted = true; + if (generatedForce.getTeam() != 1) { + batchallAccepted = initiateBatchall(campaign, factionCode, generatedForce, contract.getName(), scenario.getName()); + } if (batchallAccepted) { // Simulate bidding away of forces @@ -846,8 +849,11 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac } // Report the bidding results (if any) to the player - if (!bidAwayForces.isEmpty() || supplementedForces > 0) { - reportResultsOfBidding(scenario, campaign, bidAwayForces, generatedForce, supplementedForces); + if (generatedForce.getTeam() != 1) { + if (!bidAwayForces.isEmpty() || supplementedForces > 0) { + reportResultsOfBidding(scenario, campaign, bidAwayForces, generatedForce, + supplementedForces); + } } } From 8e239807dd020657d7484e86574267c7ef3ca81d Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 22 Sep 2024 15:05:47 -0500 Subject: [PATCH 13/21] Update batchall conclusion text in scenario report Replaced static HTML tag with a localized string for the batchall conclusion message in AtBDynamicScenarioFactory.java. Added new localized string "batchallConcluded.text" in AtBDynamicScenarioFactory.properties to facilitate this change. --- .../mekhq/resources/AtBDynamicScenarioFactory.properties | 1 + MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/MekHQ/resources/mekhq/resources/AtBDynamicScenarioFactory.properties b/MekHQ/resources/mekhq/resources/AtBDynamicScenarioFactory.properties index 0946634095..de29993022 100644 --- a/MekHQ/resources/mekhq/resources/AtBDynamicScenarioFactory.properties +++ b/MekHQ/resources/mekhq/resources/AtBDynamicScenarioFactory.properties @@ -42,3 +42,4 @@ bidAwayForcesVerbose.text=%s (%s/%s) has bid away the following forces:%s/%s) has bid away %s units. addedBattleArmorNewReport.text=%s (%s/%s) has supplemented their force with %s additional unit/s of Battle Armor. addedBattleArmorContinueReport.text=

They also supplemented their force with %s additional unit/s of Battle Armor. +batchallConcluded.text=

"Bargained Well and Done." diff --git a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java index bb14439b5a..ee790108f2 100644 --- a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java +++ b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java @@ -1063,7 +1063,7 @@ private static void reportResultsOfBidding(AtBDynamicScenario scenario, Campaign } if (!report.isEmpty()) { - report.append("
"); + report.append(resources.getString("batchallConcluded.text")); } campaign.addReport(report.toString()); From 47229ca664f268e2135111f8e8d94d8c9493da6d Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 22 Sep 2024 15:09:08 -0500 Subject: [PATCH 14/21] Add variation to batchallConcluded report text Updated the AtBDynamicScenarioFactory to randomly select between two different conclusion texts for batchall reports, adding variability to the report generation. The two possible texts are "Bargained Well and Done" and "Well-Bargained and Done". --- .../mekhq/resources/AtBDynamicScenarioFactory.properties | 3 ++- .../mekhq/campaign/mission/AtBDynamicScenarioFactory.java | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/AtBDynamicScenarioFactory.properties b/MekHQ/resources/mekhq/resources/AtBDynamicScenarioFactory.properties index de29993022..84ba1ffd81 100644 --- a/MekHQ/resources/mekhq/resources/AtBDynamicScenarioFactory.properties +++ b/MekHQ/resources/mekhq/resources/AtBDynamicScenarioFactory.properties @@ -42,4 +42,5 @@ bidAwayForcesVerbose.text=%s (%s/%s) has bid away the following forces:%s/%s) has bid away %s units. addedBattleArmorNewReport.text=%s (%s/%s) has supplemented their force with %s additional unit/s of Battle Armor. addedBattleArmorContinueReport.text=

They also supplemented their force with %s additional unit/s of Battle Armor. -batchallConcluded.text=

"Bargained Well and Done." +batchallConcludedVersion1.text=

"Bargained Well and Done." +batchallConcludedVersion2.text=

"Well-Bargained and Done." diff --git a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java index ee790108f2..bf2bbf06bc 100644 --- a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java +++ b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java @@ -1063,7 +1063,11 @@ private static void reportResultsOfBidding(AtBDynamicScenario scenario, Campaign } if (!report.isEmpty()) { - report.append(resources.getString("batchallConcluded.text")); + if (Compute.randomInt(8) == 0) { + report.append(resources.getString("batchallConcludedVersion2.text")); + } else { + report.append(resources.getString("batchallConcludedVersion1.text")); + } } campaign.addReport(report.toString()); From bcc4df5490832cfb06e811b287a2dcc15f0def1b Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 22 Sep 2024 15:25:45 -0500 Subject: [PATCH 15/21] Implement dynamic honor rating for Clan bot bidding Replaced the fixed Battle Value multiplier with a dynamic honor rating system for different Clans based on the timeline. Added the `getHonorRating` method to calculate the honor rating and adjusted the bidding logic accordingly. This makes the bot's behavior more realistic and aligned with Clan honor practices. --- .../mission/AtBDynamicScenarioFactory.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java index bf2bbf06bc..e10351e774 100644 --- a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java +++ b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java @@ -802,7 +802,8 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac } // First bid away units that exceed the player's estimated Battle Value - while ((botBattleValue > (playerBattleValue * 1.5)) && (generatedForce.getFullEntityList(campaign).size() > 1)) { + while ((botBattleValue > (playerBattleValue * getHonorRating(campaign, factionCode))) + && (generatedForce.getFullEntityList(campaign).size() > 1)) { int targetUnit = Compute.randomInt(generatedForce.getFullEntityList(campaign).size()); bidAwayForces.add(generatedForce.getFullEntityList(campaign).get(targetUnit).getShortNameRaw()); botBattleValue -= generatedForce.getFullEntityList(campaign).get(targetUnit).calculateBattleValue(); @@ -860,6 +861,32 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac return generatedLanceCount; } + /** + * Calculates the honor rating for a given Clan. + * + * @param campaign the ongoing campaign + * @param factionCode the faction code for which to calculate honor rating + * @return the honor rating as a double value + */ + private static double getHonorRating(Campaign campaign, String factionCode) { + final double STRICT = 1.25; + final double OPPORTUNISTIC = 1.5; + final double LIBERAL = 1.75; + + boolean isPostInvasion = campaign.getLocalDate().getYear() > 3061; + + // This is based on the table found on page 274 of Total Warfare + // Any Clan not mentioned on that table is assumed to be Strict → Opportunistic + return switch (factionCode) { + case "CCC", "CHH", "CIH", "CNC", "CSR" -> OPPORTUNISTIC; + case "CCO", "CGS", "CSV" -> STRICT; + case "CGB", "CWIE" -> isPostInvasion ? STRICT : LIBERAL; + case "CDS" -> LIBERAL; + case "CW" -> isPostInvasion ? LIBERAL : OPPORTUNISTIC; + default -> isPostInvasion ? STRICT : OPPORTUNISTIC; + }; + } + /** * Initiates a batchall. Prompts the player with a message and options to accept or refuse the * batchall. From 0161edc849897c074a64269ccabe628b95e11644 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Mon, 23 Sep 2024 16:05:57 -0500 Subject: [PATCH 16/21] Refactored Batchall initiation logic Moved Batchall initiation logic to AtBContract for better encapsulation. This involved removing redundancy from AtBDynamicScenarioFactory and incorporating related resource strings into AtBContract. --- .../mekhq/resources/AtBContract.properties | 42 +++ .../AtBDynamicScenarioFactory.properties | 41 +-- MekHQ/src/mekhq/campaign/Campaign.java | 10 + .../mekhq/campaign/mission/AtBContract.java | 287 +++++++++++++++++- .../mission/AtBDynamicScenarioFactory.java | 278 ++++------------- 5 files changed, 385 insertions(+), 273 deletions(-) create mode 100644 MekHQ/resources/mekhq/resources/AtBContract.properties diff --git a/MekHQ/resources/mekhq/resources/AtBContract.properties b/MekHQ/resources/mekhq/resources/AtBContract.properties new file mode 100644 index 0000000000..9875e8c956 --- /dev/null +++ b/MekHQ/resources/mekhq/resources/AtBContract.properties @@ -0,0 +1,42 @@ +# initiateBatchall +incomingTransmission.title=++INCOMING TRANSMISSION++ + +nameRedacted.text=[REDACTED] +starColonel.text=Star Colonel +batchallOpener.text=
%s

"I am %s %s commanding the %s forces on %s."
+ +batchallStatementCBS.text="We are Clan Blood Spirit. Blood calls for blood. What forces stand to contest our rightful claim?" +batchallStatementCB.text="Clan Burrock brings strength unbroken by time. Identify your forces so that we may take what is ours." +batchallStatementCCC.text="Under the sacred laws of combat as laid out by the Way of the Clans, I challenge you. With what forces will you bid to oppose us?" +batchallStatementCCO.text="The Coyote hunts. What prey hides behind these defenses, and how shall it fall to our fangs?" +batchallStatementCFM.text="We are Clan Fire Mandrill. The flame of conquest burns bright, and we claim this world. What will you offer before we consume you?" +batchallStatementCGB.text="We are Clan Ghost Bear. This world is ours. Those who dispute our claim must identify the size and location of their forces for immediate disposal." +batchallStatementCGBDominion.text="The strength of the Dominion claims this world. Identify your defenses or face our wrath." +batchallStatementCGS.text="The relentless pursuit of Clan Goliath Scorpion begins. What forces do you offer in defense of that which we seek?" +batchallStatementCHH.text="Clan Hell's Horses charges forward. Who stands to face the stampede, and what meager defenses do you offer?" +batchallStatementCIH.text="The warriors of Clan Ice Hellion are unleashed. Declare your forces now, or freeze beneath the fury of our onslaught." +batchallStatementCJF.text="What forces dare to defend this world from the steel talons of the Jade Falcon?" +batchallStatementCMG.text="Clan Mongoose strikes swiftly and decisively. Identify your defenses, or be swept away in our hunt." +batchallStatementCNC.text="Clan Nova Cat has seen your end. Declare the strength of your defenses, and we will ensure your fate is met." +batchallStatementCDS.text="We are Clan Diamond Shark. We strike across the void ocean at our Prey. Name the price you are prepared to lose to defend this world." +batchallStatementCDSSeaFox.text="Clan Sea Fox comes to claim what is rightfully ours. Identify your forces, so we may assess your defenses." +batchallStatementCSJ.text="The Smoke Jaguar claims this world. Identify the forces that defend it so that we from the mists of space may know on whom we pounce." +batchallStatementCSR.text="The wings of Clan Snow Raven shadow this world. Declare your forces, or be torn from the skies." +batchallStatementCSA.text="The coils of Clan Star Adder tighten around this world. What forces defend it from our grasp?" +batchallStatementCSV.text="The fangs of Clan Steel Viper are bared. Declare your defenses, or fall to our swift strike." +batchallStatementCSL.text="The lions of Clan Stone Lion are at your gates. What forces will dare to stand against our pride?" +batchallStatementCWI.text="Clan Widowmaker comes to claim what is ours. Name the forces that defend this world, or be crushed beneath us." +batchallStatementCW.text="The Wolves of Kerensky have claimed this world for their own. What tame dogs defend it?" +batchallStatementCWIE.text="The Wolves in Exile return to reclaim what was lost. Declare your forces, or prepare to be removed." +batchallStatementCWOV.text="The Wolverines fight to the last. Will you rise to face us, or be swept aside as we claim this world?" +batchallStatementRD.text="The Ghost Bear and Rasalhague unite. What forces stand to oppose our honor-bound claim?" +batchallStatementRA.text="The Raven's talons extend across this world. Declare your forces, or fall beneath our wings." +batchallStatementSOC.text="We, the Society, bring the future of the Clans. What forces stand in opposition to progress?" +batchallStatementGeneric.text="We are The Clans. This world is ours by right. Identify the forces that dare oppose us, or face immediate destruction." + +batchallCloser.text=

Do you accept the Batchall? +responseAccept.text=Accept Batchall +responseRefuse.text=Refuse Batchall + +refusalConfirmation.text=Are you sure? This will increase the difficulty of the entire contract. +refusalReport.text=
YOU DARE TO REFUSE MY BATCHALL!?!
\ No newline at end of file diff --git a/MekHQ/resources/mekhq/resources/AtBDynamicScenarioFactory.properties b/MekHQ/resources/mekhq/resources/AtBDynamicScenarioFactory.properties index 84ba1ffd81..062eb7148e 100644 --- a/MekHQ/resources/mekhq/resources/AtBDynamicScenarioFactory.properties +++ b/MekHQ/resources/mekhq/resources/AtBDynamicScenarioFactory.properties @@ -1,46 +1,7 @@ -# initiateBatchall -incomingTransmission.title=++INCOMING TRANSMISSION++ - -nameRedacted.text=[REDACTED] -starCommander.text=Star Commander -starCaptain.text=Star Captain -starColonel.text=Star Colonel -batchallOpener.text=
%s, %s

"I am %s %s commanding the %s forces on %s."
- -batchallStatementCBS.text="Blood calls for blood. What forces do you commit to the challenge?" -batchallStatementCB.text="We come with the strength of the Burrock. What do you offer in return?" -batchallStatementCCC.text="The storm is coming. Will you weather it, or shall we take all that you hold dear?" -batchallStatementCCO.text="The Coyote hunts. What do you commit to the chase?" -batchallStatementCFM.text="We are the fire that consumes all. What will you offer us to avoid the blaze?" -batchallStatementCGB.text="We are Clan Ghost Bear. This world is ours. Those who dispute our claim must identify the size and location of their forces for immediate disposal." -batchallStatementCGS.text="We are the scorpion, relentless in pursuit. What do you bid for your survival?" -batchallStatementCHH.text="The stampede is upon you. Do you have the strength to stand?" -batchallStatementCIH.text="The Hellions are unleashed. Will you surrender before the cold death claims you?" -batchallStatementCJF.text="What forces dare to defend this world from the steel talons of the Jade Falcon?" -batchallStatementCMG.text="The mongoose hunts. What resists our claim?" -batchallStatementCNC.text="We are the Nova Cat, and we see your end. Will you face your fate or flee?" -batchallStatementCDS.text="We take what we desire. What will you offer in defense?" -batchallStatementCSJ.text="The Smoke Jaguar claims this world. Identify the forces that defend it so that we from the mists of space may know on whom we pounce." -batchallStatementCSR.text="The Raven flies. Will you submit to our talons?" -batchallStatementCSA.text="The Star Adder rises. What will you give us in battle?" -batchallStatementCSV.text="The Viper strikes swiftly. How will you resist our venom?" -batchallStatementCSL.text="The Lion is upon you. Will you fight for your claim, or will you fall?" -batchallStatementCWI.text="The Widowmaker has come for its prize. What will you wager in return?" -batchallStatementCW.text="The Wolves of Kerensky have claimed this world for their own. What tame dogs defend it?" -batchallStatementCWIE.text="The Wolf returns to claim what was lost. How will you answer?" -batchallStatementCWOV.text="The Wolverine fights to the last. Will you rise to the challenge, or be swept aside?" -batchallStatementGeneric.text="We come to claim what is rightfully ours. What forces will you commit to defend it?" - -batchallCloser.text=

Do you accept the Batchall? -responseAccept.text=Accept -responseRefuse.text=Refuse - -refusalReport.text=
YOU DARE REFUSE MY BATCHALL!?!
- # reportResultsOfBidding bidAwayForcesVerbose.text=%s (%s/%s) has bid away the following forces:

%s
bidAwayForces.text=%s (%s/%s) has bid away %s units. addedBattleArmorNewReport.text=%s (%s/%s) has supplemented their force with %s additional unit/s of Battle Armor. -addedBattleArmorContinueReport.text=

They also supplemented their force with %s additional unit/s of Battle Armor. +addedBattleArmorContinueReport.text=
They also supplemented their force with %s additional unit/s of Battle Armor. batchallConcludedVersion1.text=

"Bargained Well and Done." batchallConcludedVersion2.text=

"Well-Bargained and Done." diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index 12116d680e..94b533708e 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -3615,6 +3615,16 @@ && getCampaignOptions().getRandomDependentMethod().isAgainstTheBot() } } + for (AtBContract contract : getActiveAtBContracts()) { + if (campaignOptions.isUseGenericBattleValue()) { + if (contract.getStartDate().equals(getLocalDate()) && getLocation().isOnPlanet()) { + if (contract.getEnemy().isClan()) { + contract.setBatchallAccepted(contract.initiateBatchall(this)); + } + } + } + } + processNewDayATBScenarios(); } diff --git a/MekHQ/src/mekhq/campaign/mission/AtBContract.java b/MekHQ/src/mekhq/campaign/mission/AtBContract.java index 897e2beedf..4b8759d144 100644 --- a/MekHQ/src/mekhq/campaign/mission/AtBContract.java +++ b/MekHQ/src/mekhq/campaign/mission/AtBContract.java @@ -21,25 +21,12 @@ */ package mekhq.campaign.mission; -import java.io.PrintWriter; -import java.text.ParseException; -import java.time.DayOfWeek; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.Objects; -import java.util.UUID; - -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - +import megamek.client.generator.RandomNameGenerator; import megamek.client.generator.RandomUnitGenerator; import megamek.client.ui.swing.util.PlayerColour; -import megamek.common.Compute; -import megamek.common.Entity; -import megamek.common.MekFileParser; -import megamek.common.MekSummary; -import megamek.common.UnitType; +import megamek.common.*; import megamek.common.annotations.Nullable; +import megamek.common.enums.Gender; import megamek.common.enums.SkillLevel; import megamek.common.icons.Camouflage; import megamek.common.loaders.EntityLoadingException; @@ -52,9 +39,12 @@ import mekhq.campaign.mission.atb.AtBScenarioFactory; import mekhq.campaign.mission.enums.AtBContractType; import mekhq.campaign.mission.enums.AtBMoraleLevel; +import mekhq.campaign.personnel.Bloodname; import mekhq.campaign.personnel.Person; import mekhq.campaign.personnel.SkillType; import mekhq.campaign.personnel.backgrounds.BackgroundsController; +import mekhq.campaign.personnel.enums.PersonnelRole; +import mekhq.campaign.personnel.enums.Phenotype; import mekhq.campaign.rating.IUnitRating; import mekhq.campaign.stratcon.StratconCampaignState; import mekhq.campaign.stratcon.StratconContractDefinition; @@ -64,6 +54,19 @@ import mekhq.campaign.universe.Factions; import mekhq.campaign.universe.RandomFactionGenerator; import mekhq.utilities.MHQXMLUtility; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.swing.*; +import java.awt.*; +import java.io.PrintWriter; +import java.text.ParseException; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Objects; +import java.util.ResourceBundle; +import java.util.UUID; /** * Contract class for use with Against the Bot rules @@ -117,6 +120,7 @@ public class AtBContract extends Contract { protected LocalDate routEnd; protected int partsAvailabilityLevel; protected int sharesPct; + private boolean batchallAccepted; protected int playerMinorBreaches; protected int employerMinorBreaches; @@ -141,6 +145,10 @@ public class AtBContract extends Contract { private StratconCampaignState stratconCampaignState; + private static final ResourceBundle resources = ResourceBundle.getBundle( + "mekhq.resources.AtBContract", + MekHQ.getMHQOptions().getLocale()); + protected AtBContract() { this(null); } @@ -169,6 +177,7 @@ public AtBContract(String name) { extensionLength = 0; sharesPct = 0; + batchallAccepted = true; setMoraleLevel(AtBMoraleLevel.NORMAL); routEnd = null; numBonusParts = 0; @@ -844,6 +853,7 @@ protected int writeToXMLBegin(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "partsAvailabilityLevel", getPartsAvailabilityLevel()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "extensionLength", extensionLength); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "sharesPct", sharesPct); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "batchallAccepted", batchallAccepted); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "playerMinorBreaches", playerMinorBreaches); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "employerMinorBreaches", employerMinorBreaches); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "contractScoreArbitraryModifier", contractScoreArbitraryModifier); @@ -920,6 +930,8 @@ public void loadFieldsFromXmlNode(Node wn) throws ParseException { extensionLength = Integer.parseInt(wn2.getTextContent()); } else if (wn2.getNodeName().equalsIgnoreCase("sharesPct")) { sharesPct = Integer.parseInt(wn2.getTextContent()); + } else if (wn2.getNodeName().equalsIgnoreCase("batchallAccepted")) { + batchallAccepted = Boolean.parseBoolean(wn2.getTextContent()); } else if (wn2.getNodeName().equalsIgnoreCase("numBonusParts")) { numBonusParts = Integer.parseInt(wn2.getTextContent()); } else if (wn2.getNodeName().equalsIgnoreCase("playerMinorBreaches")) { @@ -1184,6 +1196,24 @@ public void setAtBSharesPercent(int pct) { sharesPct = pct; } + /** + * Checks if the Batchall has been accepted for the contract. + * + * @return {@code true} if the Batchall has been accepted, {@code false} otherwise. + */ + public boolean isBatchallAccepted() { + return batchallAccepted; + } + + /** + * Sets the {@code batchallAccepted} flag for this contract. + * + * @param batchallAccepted The value to set for the {@code batchallAccepted} flag. + */ + public void setBatchallAccepted(final boolean batchallAccepted) { + this.batchallAccepted = batchallAccepted; + } + public void addPlayerMinorBreach() { playerMinorBreaches++; } @@ -1323,4 +1353,229 @@ public AtBContractRef(int id) { setId(id); } } + + + + /** + * Initiates a batchall. + * Prompts the player with a message and options to accept or refuse the batchall. + * + * @param campaign The current campaign. + * @return {@code true} if the batchall is accepted, {@code false} otherwise. + */ + public boolean initiateBatchall(Campaign campaign) { + // Set the title of the dialog + String title = resources.getString("incomingTransmission.title"); + + // Hold the portrait of the commander + + // Generate the batchall statement and fetch the faction image + String batchallStatement; + final String PORTRAIT_DIRECTORY = "data/images/force/Pieces/Logos/Clan/"; + final String PORTRAIT_FILE_TYPE = ".png"; + + ImageIcon portrait; + + switch (enemyCode) { + case "CBS" -> { + batchallStatement = resources.getString("batchallStatementCBS.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Blood Spirit" + PORTRAIT_FILE_TYPE); + } + case "CB" -> { + batchallStatement = resources.getString("batchallStatementCB.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Burrock" + PORTRAIT_FILE_TYPE); + } + case "CCC" -> { + batchallStatement = resources.getString("batchallStatementCCC.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Cloud Cobra" + PORTRAIT_FILE_TYPE); + } + case "CCO" -> { + batchallStatement = resources.getString("batchallStatementCCO.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Coyote" + PORTRAIT_FILE_TYPE); + } + case "CDS" -> { + if (campaign.getGameYear() >= 3100) { + batchallStatement = resources.getString("batchallStatementCDSSeaFox.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Sea Fox" + PORTRAIT_FILE_TYPE); + } else { + batchallStatement = resources.getString("batchallStatementCDS.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Diamond Shark" + PORTRAIT_FILE_TYPE); + } + } + case "CFM" -> { + batchallStatement = resources.getString("batchallStatementCFM.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Fire Mandrill" + PORTRAIT_FILE_TYPE); + } + case "CGB" -> { + if (campaign.getGameYear() >= 3060) { + batchallStatement = resources.getString("batchallStatementCGBDominion.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Ghost Bear Dominion" + PORTRAIT_FILE_TYPE); + } else { + batchallStatement = resources.getString("batchallStatementCGB.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Ghost Bear" + PORTRAIT_FILE_TYPE); + } + } + case "CGS" -> { + batchallStatement = resources.getString("batchallStatementCGS.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Goliath Scorpion" + PORTRAIT_FILE_TYPE); + } + case "CHH" -> { + batchallStatement = resources.getString("batchallStatementCHH.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Hell's Horses" + PORTRAIT_FILE_TYPE); + } + case "CIH" -> { + batchallStatement = resources.getString("batchallStatementCIH.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Ice Hellion" + PORTRAIT_FILE_TYPE); + } + case "CJF" -> { + batchallStatement = resources.getString("batchallStatementCJF.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Jade Falcon" + PORTRAIT_FILE_TYPE); + } + case "CMG" -> { + batchallStatement = resources.getString("batchallStatementCMG.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Mongoose" + PORTRAIT_FILE_TYPE); + } + case "CNC" -> { + batchallStatement = resources.getString("batchallStatementCNC.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Nova Cat" + PORTRAIT_FILE_TYPE); + } + case "CSJ" -> { + batchallStatement = resources.getString("batchallStatementCSJ.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Smoke Jaguar" + PORTRAIT_FILE_TYPE); + } + case "CSR" -> { + batchallStatement = resources.getString("batchallStatementCSR.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Snow Raven" + PORTRAIT_FILE_TYPE); + } + case "CSA" -> { + batchallStatement = resources.getString("batchallStatementCSA.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Star Adder" + PORTRAIT_FILE_TYPE); + } + case "CSV" -> { + batchallStatement = resources.getString("batchallStatementCSV.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Steel Viper" + PORTRAIT_FILE_TYPE); + } + case "CSL" -> { + batchallStatement = resources.getString("batchallStatementCSL.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Stone Lion" + PORTRAIT_FILE_TYPE); + } + case "CWI" -> { + batchallStatement = resources.getString("batchallStatementCWI.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Widowmaker" + PORTRAIT_FILE_TYPE); + } + case "CW" -> { + batchallStatement = resources.getString("batchallStatementCW.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Wolf" + PORTRAIT_FILE_TYPE); + } + case "CWIE" -> { + batchallStatement = resources.getString("batchallStatementCWIE.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Wolf-in-Exile" + PORTRAIT_FILE_TYPE); + } + case "CWOV" -> { + batchallStatement = resources.getString("batchallStatementCWOV.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Wolverine" + PORTRAIT_FILE_TYPE); + } + case "RD" -> { + batchallStatement = resources.getString("batchallStatementRD.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Rasalhague Dominion" + PORTRAIT_FILE_TYPE); + } + case "RA" -> { + batchallStatement = resources.getString("batchallStatementRA.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Raven Alliance" + PORTRAIT_FILE_TYPE); + } + case "SOC" -> { + batchallStatement = resources.getString("batchallStatementSOC.text"); + portrait = new ImageIcon(PORTRAIT_DIRECTORY + "The Society" + PORTRAIT_FILE_TYPE); + } + default -> { + batchallStatement = resources.getString("batchallStatementGeneric.text"); + portrait = new ImageIcon("data/images/force/Pieces/Logos/Inner Sphere/Star League.png"); + } + } + + // Determine the name of the commander based on faction + String rank = resources.getString("starColonel.text"); + RandomNameGenerator randomNameGenerator = new RandomNameGenerator(); + String commander = randomNameGenerator.generate(Gender.RANDOMIZE, true, enemyCode); + commander += ' ' + Bloodname.randomBloodname(enemyCode, Phenotype.MEKWARRIOR, campaign.getGameYear()).getName(); + + // Prepare the display message for the dialog + String message = String.format(resources.getString("batchallOpener.text"), + this.getName(), rank, commander, getEnemy().getFullName(campaign.getGameYear()), + getSystemName(campaign.getLocalDate())); + message = message + batchallStatement; + message = message + resources.getString("batchallCloser.text"); + + // Create a pane to display both the message and the commander's portrait + JTextPane textPane = new JTextPane(); + textPane.setContentType("text/html"); + textPane.setText(message); + textPane.setEditable(false); + + JPanel panel = new JPanel(new BorderLayout()); + JLabel imageLabel = new JLabel(portrait); + panel.add(imageLabel, BorderLayout.CENTER); + panel.add(textPane, BorderLayout.SOUTH); + + // Prepare the options for the dialog + Object[] options = { + resources.getString("responseAccept.text"), + resources.getString("responseRefuse.text") + }; + + // Display the dialog and capture the response + int batchallDialog = JOptionPane.showOptionDialog(null, panel, title, + JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); + + // Handle the response + if (batchallDialog == JOptionPane.NO_OPTION) { + // Display the dialog and capture the response + int refusalConfirmation = JOptionPane.showOptionDialog(null, + resources.getString("refusalConfirmation.text"), + resources.getString("responseRefuse.text"), + JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, + options[0]); + + // Handle the response + if (refusalConfirmation == JOptionPane.NO_OPTION) { + // Report refusal of the batchall + campaign.addReport(resources.getString("refusalReport.text")); + return false; + } else { + return true; + } + } else { + return true; + } + } + + /** + * Derives the personnel role based on the unit type of the given entity. + * + * @param entity The entity whose unit type needs to be evaluated. + * @return The personnel role derived from the unit type of the entity. + */ + private static PersonnelRole deriveRoleFromUnitType(Entity entity) { + if (entity.isAerospaceFighter()) { + return PersonnelRole.AEROSPACE_PILOT; + } else if (entity.isBattleArmor()) { + return PersonnelRole.BATTLE_ARMOUR; + } else if (entity.isConventionalFighter()) { + return PersonnelRole.CONVENTIONAL_AIRCRAFT_PILOT; + } else if (entity.isNaval()) { + return PersonnelRole.NAVAL_VEHICLE_DRIVER; + } else if (entity.isProtoMek()) { + return PersonnelRole.PROTOMEK_PILOT; + } else if (entity.isConventionalInfantry()) { + return PersonnelRole.SOLDIER; + } else if (entity.isAirborneVTOLorWIGE()) { + return PersonnelRole.VTOL_PILOT; + } else if (entity.isVehicle()) { + return PersonnelRole.GROUND_VEHICLE_DRIVER; + } else if (entity.isLargeCraft() || entity.isSmallCraft()) { + return PersonnelRole.VESSEL_PILOT; + } else { + return PersonnelRole.MEKWARRIOR; + } + } } diff --git a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java index e10351e774..66d73c4d2e 100644 --- a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java +++ b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java @@ -51,9 +51,7 @@ import mekhq.campaign.mission.atb.AtBScenarioModifier; import mekhq.campaign.mission.atb.AtBScenarioModifier.EventTiming; import mekhq.campaign.personnel.Bloodname; -import mekhq.campaign.personnel.Person; import mekhq.campaign.personnel.SkillType; -import mekhq.campaign.personnel.enums.PersonnelRole; import mekhq.campaign.personnel.enums.Phenotype; import mekhq.campaign.rating.IUnitRating; import mekhq.campaign.stratcon.StratconBiomeManifest; @@ -63,16 +61,15 @@ import mekhq.campaign.universe.Faction.Tag; import mekhq.campaign.universe.enums.EraFlag; -import javax.swing.*; -import java.awt.*; import java.io.File; import java.time.LocalDate; -import java.util.List; import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; import static java.lang.Math.round; +import static mekhq.campaign.mission.Scenario.T_GROUND; +import static mekhq.campaign.mission.ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_MIX; /** * This class handles the creation and substantive manipulation of @@ -480,7 +477,7 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac Collection baseRoles = forceTemplate.getRequiredRoles(); if (!baseRoles.isEmpty()) { - if (forceTemplate.getAllowedUnitType() == ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_MIX) { + if (forceTemplate.getAllowedUnitType() == SPECIAL_UNIT_TYPE_ATB_MIX) { requiredRoles.put(UnitType.MEK, new ArrayList<>(baseRoles)); requiredRoles.put(UnitType.TANK, new ArrayList<>(baseRoles)); } else if (forceTemplate.getAllowedUnitType() == ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_AERO_MIX) { @@ -518,13 +515,13 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac if (forceTemplate.getUseArtillery()) { int artilleryCarriers = forceTemplate.getAllowedUnitType(); - if (artilleryCarriers == ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_MIX || artilleryCarriers == UnitType.MEK) { + if (artilleryCarriers == SPECIAL_UNIT_TYPE_ATB_MIX || artilleryCarriers == UnitType.MEK) { if (!requiredRoles.containsKey(UnitType.MEK)) { requiredRoles.put(UnitType.MEK, new HashSet<>()); } requiredRoles.get(UnitType.MEK).add((MissionRole.ARTILLERY)); } - if (artilleryCarriers == ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_MIX || artilleryCarriers == UnitType.TANK) { + if (artilleryCarriers == SPECIAL_UNIT_TYPE_ATB_MIX || artilleryCarriers == UnitType.TANK) { if (!requiredRoles.containsKey(UnitType.TANK)) { requiredRoles.put(UnitType.TANK, new HashSet<>()); } @@ -561,14 +558,12 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac int actualUnitType = forceTemplate.getAllowedUnitType(); // The SPECIAL_UNIT_TYPE_ATB_AERO_MIX value allows for random selection of - // aerospace or - // conventional fighters. Only allow for conventional fighters where this force - // controls - // the system, and where there is an atmosphere. - // Aerospace fighters are added in single flights/points, while conventional - // fighters - // are added in full squadrons (1-3 flights, 2-6 total). - if (isPlanetOwner && actualUnitType == ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_AERO_MIX && scenario.getTemplate().mapParameters.getMapLocation() != MapLocation.Space && scenario.getAtmosphere().isDenserThan(Atmosphere.THIN)) { + // aerospace or conventional fighters. + // Only allow for conventional fighters where this force controls the system, and where + // there is an atmosphere. Aerospace fighters are added in single flights/points, while + // conventional fighters are added in full squadrons (1-3 flights, 2-6 total). + if (isPlanetOwner && actualUnitType == ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_AERO_MIX + && scenario.getTemplate().mapParameters.getMapLocation() != MapLocation.Space && scenario.getAtmosphere().isDenserThan(Atmosphere.THIN)) { actualUnitType = Compute.d6() > 3 ? UnitType.AEROSPACEFIGHTER : UnitType.CONV_FIGHTER; lanceSize = getAeroLanceSize(actualUnitType, isPlanetOwner, factionCode); } else if (actualUnitType == ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_AERO_MIX) { @@ -604,7 +599,7 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac // Formations composed entirely of Meks, aerospace fighters (but not conventional), // and ground vehicles use weight categories as do SPECIAL_UNIT_TYPE_ATB_MIX. // Formations of other types, plus artillery formations do not use weight classes. - if ((actualUnitType == ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_MIX || IUnitGenerator.unitTypeSupportsWeightClass(actualUnitType)) && !forceTemplate.getUseArtillery()) { + if ((actualUnitType == SPECIAL_UNIT_TYPE_ATB_MIX || IUnitGenerator.unitTypeSupportsWeightClass(actualUnitType)) && !forceTemplate.getUseArtillery()) { // Generate a specific weight class for each unit based on the formation weight // class and lower/upper bounds @@ -777,12 +772,7 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac setBotForceParameters(generatedForce, forceTemplate, forceAlignment, contract); scenario.addBotForce(generatedForce, forceTemplate, campaign); - boolean batchallAccepted = true; - if (generatedForce.getTeam() != 1) { - batchallAccepted = initiateBatchall(campaign, factionCode, generatedForce, contract.getName(), scenario.getName()); - } - - if (batchallAccepted) { + if (contract.isBatchallAccepted()) { // Simulate bidding away of forces List bidAwayForces = new ArrayList<>(); int supplementedForces = 0; @@ -810,41 +800,54 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac generatedForce.removeEntity(targetUnit); } - // Next, if the size of the forces results in a Player:Bot unit count ratio of >= 2:1, - // add additional units of Battle Armor to compensate. - int sizeDisparity = playerUnitValue - generatedForce.getFullEntityList(campaign).size(); - sizeDisparity = (int) round(sizeDisparity * 0.5); - - List allRemainingUnits = new ArrayList<>(generatedForce.getFullEntityList(campaign)); - Collections.shuffle(allRemainingUnits); - - // First, attempt to add Mechanized Battle Armor - Iterator entityIterator = allRemainingUnits.iterator(); - while (entityIterator.hasNext() && sizeDisparity > 0) { - Entity entity = entityIterator.next(); - if (!entity.isOmni()) { - continue; - } - - List generatedBA = generateBAForNova(scenario, List.of(entity), factionCode, - skill, quality, campaign, true); + // There is no point in adding extra Battle Armor to non-ground scenarios + if (scenario.getBoardType() == T_GROUND) { + // We want to purposefully exclude off-board artillery, to stop them being + // assigned random units of Battle Armor. + // If we ever implement the ability to move those units on-board, or for players + // to intercept off-board units, we'll probably want to remove this exclusion, + // so they can have some bodyguards. + if (!forceTemplate.getUseArtillery() && !forceTemplate.getDeployOffboard()) { + // Similarly, there is no value in adding random Battle Armor to aircraft forces + if (forceTemplate.getAllowedUnitType() != SPECIAL_UNIT_TYPE_ATB_MIX) { + // Next, if the size of the forces results in a Player:Bot unit count ratio of >= 2:1, + // add additional units of Battle Armor to compensate. + int sizeDisparity = playerUnitValue - generatedForce.getFullEntityList(campaign).size(); + sizeDisparity = (int) round(sizeDisparity * 0.5); + + List allRemainingUnits = new ArrayList<>(generatedForce.getFullEntityList(campaign)); + Collections.shuffle(allRemainingUnits); + + // First, attempt to add Mechanized Battle Armor + Iterator entityIterator = allRemainingUnits.iterator(); + while (entityIterator.hasNext() && sizeDisparity > 0) { + Entity entity = entityIterator.next(); + if (!entity.isOmni()) { + continue; + } + + List generatedBA = generateBAForNova(scenario, List.of(entity), + factionCode, skill, quality, campaign, true); + + if (!generatedBA.isEmpty()) { + for (Entity battleArmor : generatedBA) { + generatedForce.addEntity(battleArmor); + } + supplementedForces += generatedBA.size(); + sizeDisparity -= generatedBA.size(); + } + } - if (!generatedBA.isEmpty()) { - for (Entity battleArmor : generatedBA) { - generatedForce.addEntity(battleArmor); + // If there is still a disproportionate size disparity, add loose Battle Armor + for (int i = 0; i < sizeDisparity; i++) { + Entity newEntity = getEntity(factionCode, skill, quality, + UnitType.BATTLE_ARMOR, UNIT_WEIGHT_UNSPECIFIED, campaign); + if (newEntity != null) { + generatedForce.addEntity(newEntity); + supplementedForces++; + } + } } - supplementedForces += generatedBA.size(); - sizeDisparity -= generatedBA.size(); - } - } - - // If there is still a disproportionate size disparity, add loose Battle Armor - for (int i = 0; i < sizeDisparity; i++) { - Entity newEntity = getEntity(factionCode, skill, quality, UnitType.BATTLE_ARMOR, - UNIT_WEIGHT_UNSPECIFIED, campaign); - if (newEntity != null) { - generatedForce.addEntity(newEntity); - supplementedForces++; } } } @@ -887,165 +890,6 @@ private static double getHonorRating(Campaign campaign, String factionCode) { }; } - /** - * Initiates a batchall. Prompts the player with a message and options to accept or refuse the - * batchall. - * - * @param campaign The campaign object. - * @param factionCode The faction code. - * @param generatedForce The generated force object. - * @param contractName The contract name. - * @param scenarioName The scenario name. - * @return {@code true} if the batchall is accepted, {@code false} otherwise. - */ - private static boolean initiateBatchall(Campaign campaign, String factionCode, BotForce generatedForce, - String contractName, String scenarioName) { - // Set the title of the dialog - String title = resources.getString("incomingTransmission.title"); - - // Generate the list of entities involved in the campaign - List entityList = generatedForce.getFullEntityList(campaign); - int unitCount = entityList.size(); - - int highestBattleValue = 0; - Entity commandEntity = null; - - // Find the entity with the highest battle value - for (Entity entity : generatedForce.getFullEntityList(campaign)) { - int battleValue = entity.calculateBattleValue(); - - if (battleValue > highestBattleValue) { - highestBattleValue = battleValue; - commandEntity = entity; - } - } - - // Hold the portrait of the commander - ImageIcon portrait = null; - - // Assign an appropriate portrait to the commander - if (commandEntity != null) { - Person dummyPerson = new Person(campaign); - dummyPerson.setClanPersonnel(true); - dummyPerson.setPrimaryRole(campaign, deriveRoleFromUnitType(commandEntity)); - campaign.assignRandomPortraitFor(dummyPerson); - commandEntity.getCrew().setPortrait(dummyPerson.getPortrait(), 0); - portrait = commandEntity.getCrew().getPortrait(0).getImageIcon(128); - } - - // Determine the rank of the commander based on the unit count - String rank; - if (unitCount <= 5) { - rank = resources.getString("starCommander.text"); - } else if (unitCount <= 15) { - rank = resources.getString("starCaptain.text"); - } else { - rank = resources.getString("starColonel.text"); - } - - // Generate the batchall statement according to the faction code - String batchallStatement = switch (factionCode) { - case "CBS" -> resources.getString("batchallStatementCBS.text"); - case "CB" -> resources.getString("batchallStatementCB.text"); - case "CCC" -> resources.getString("batchallStatementCCC.text"); - case "CCO" -> resources.getString("batchallStatementCCO.text"); - case "CFM" -> resources.getString("batchallStatementCFM.text"); - case "CGB" -> resources.getString("batchallStatementCGB.text"); - case "CGS" -> resources.getString("batchallStatementCGS.text"); - case "CHH" -> resources.getString("batchallStatementCHH.text"); - case "CIH" -> resources.getString("batchallStatementCIH.text"); - case "CJF" -> resources.getString("batchallStatementCJF.text"); - case "CMG" -> resources.getString("batchallStatementCMG.text"); - case "CNC" -> resources.getString("batchallStatementCNC.text"); - case "CDS" -> resources.getString("batchallStatementCDS.text"); - case "CSJ" -> resources.getString("batchallStatementCSJ.text"); - case "CSR" -> resources.getString("batchallStatementCSR.text"); - case "CSA" -> resources.getString("batchallStatementCSA.text"); - case "CSV" -> resources.getString("batchallStatementCSV.text"); - case "CSL" -> resources.getString("batchallStatementCSL.text"); - case "CWI" -> resources.getString("batchallStatementCWI.text"); - case "CW" -> resources.getString("batchallStatementCW.text"); - case "CWIE" -> resources.getString("batchallStatementCWIE.text"); - case "CWOV" -> resources.getString("batchallStatementCWOV.text"); - default -> resources.getString("batchallStatementGeneric.text"); - }; - - // Prepare the name of the commander - String commander = resources.getString("nameRedacted.text"); - if (commandEntity != null) { - commander = commandEntity.getCrew().getName(0); - } - - // Prepare the display message for the dialog - String message = String.format(resources.getString("batchallOpener.text"), - contractName, scenarioName, rank, commander, generatedForce.getName(), - campaign.getLocation().getPlanet().getName(campaign.getLocalDate())); - message = message + batchallStatement; - message = message + resources.getString("batchallCloser.text"); - - // Create a pane to display both the message and the commander's portrait - JTextPane textPane = new JTextPane(); - textPane.setContentType("text/html"); - textPane.setText(message); - textPane.setEditable(false); - - JPanel panel = new JPanel(new BorderLayout()); - if (portrait != null) { - JLabel imageLabel = new JLabel(portrait); - panel.add(imageLabel, BorderLayout.CENTER); - } - panel.add(textPane, BorderLayout.SOUTH); - - // Prepare the options for the dialog - Object[] options = { - resources.getString("responseAccept.text"), - resources.getString("responseRefuse.text") - }; - - // Display the dialog and capture the response - int optionDialog = JOptionPane.showOptionDialog(null, panel, title, - JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); - - // Handle the response - if (optionDialog == JOptionPane.NO_OPTION) { - // Report refusal of the batchall - campaign.addReport(resources.getString("refusalReport.text")); - return false; - } else { - return true; - } - } - - /** - * Derives the personnel role based on the unit type of the given entity. - * - * @param entity The entity whose unit type needs to be evaluated. - * @return The personnel role derived from the unit type of the entity. - */ - private static PersonnelRole deriveRoleFromUnitType(Entity entity) { - if (entity.isAerospaceFighter()) { - return PersonnelRole.AEROSPACE_PILOT; - } else if (entity.isBattleArmor()) { - return PersonnelRole.BATTLE_ARMOUR; - } else if (entity.isConventionalFighter()) { - return PersonnelRole.CONVENTIONAL_AIRCRAFT_PILOT; - } else if (entity.isNaval()) { - return PersonnelRole.NAVAL_VEHICLE_DRIVER; - } else if (entity.isProtoMek()) { - return PersonnelRole.PROTOMEK_PILOT; - } else if (entity.isConventionalInfantry()) { - return PersonnelRole.SOLDIER; - } else if (entity.isAirborneVTOLorWIGE()) { - return PersonnelRole.VTOL_PILOT; - } else if (entity.isVehicle()) { - return PersonnelRole.GROUND_VEHICLE_DRIVER; - } else if (entity.isLargeCraft() || entity.isSmallCraft()) { - return PersonnelRole.VESSEL_PILOT; - } else { - return PersonnelRole.MEKWARRIOR; - } - } - /** * Reports the results of Clan bidding for a scenario. * @@ -1337,7 +1181,7 @@ public static void setTerrain(AtBDynamicScenario scenario) { // if we are allowing all terrain types, then pick one from the list // otherwise, pick one from the allowed ones if (scenario.getTemplate().mapParameters.getMapLocation() == MapLocation.AllGroundTerrain) { - scenario.setBoardType(AtBScenario.T_GROUND); + scenario.setBoardType(T_GROUND); StratconBiomeManifest biomeManifest = StratconBiomeManifest.getInstance(); int kelvinTemp = scenario.getTemperature() + StratconContractInitializer.ZERO_CELSIUS_IN_KELVIN; List allowedTerrain = biomeManifest.getTempMap(StratconBiomeManifest.TERRAN_BIOME) @@ -2472,7 +2316,7 @@ private static List generateUnitTypes(int unitTypeCode, // This special unit type code randomly selects between all Mek, all vehicle, or // mixed // Mek/vehicle formations - if (unitTypeCode == ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_MIX) { + if (unitTypeCode == SPECIAL_UNIT_TYPE_ATB_MIX) { Faction faction = Factions.getInstance().getFaction(factionCode); // If ground vehicles are permitted in general and by environmental conditions, From 64bfdf862fb5175527469bab345e666c6fcaca65 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Mon, 23 Sep 2024 16:17:17 -0500 Subject: [PATCH 17/21] Improve logging format for force generation and culling Converted string concatenations to `String.format` for generating log messages in `AtBDynamicScenarioFactory`. This change enhances readability and consistency in logging output for force generation, culling, and final force statistics. --- .../mekhq/resources/CampaignOptionsDialog.properties | 2 +- .../campaign/mission/AtBDynamicScenarioFactory.java | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 81fd11ae92..bcd8e0849d 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -898,7 +898,7 @@ chkAdjustPaymentForStrategy.text=Adjust contract payment for deployment limits chkAdjustPaymentForStrategy.toolTipText=If the number of lances required to be deployed exceeds the current commander's strategy skill,
reduce the number when calculating new contracts and reduce the payment proportionally. lblAdditionalStrategyDeployment.text=Per rank of strategy: spnAdditionalStrategyDeployment.toolTipText=The number of additional lances that can be deployed for each increase in strategy skill. -chkUseGenericBattleValue.text=Use Generic Battle Value +chkUseGenericBattleValue.text=Use Force Generation 3 chkUseGenericBattleValue.toolTipText=Bot forces are balanced used Generic Battle Value, an estimation of the average battle value for a unit of that type and weight.\ \ This ignores pilot skill, meaning contracts against Green and Elite OpFors should feel fundamentally different.\ \ Similarly, OpFors with higher or lower than average equipment (such as Clans or Pirates) will present higher or lower difficulty scenarios. diff --git a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java index 66d73c4d2e..95809e00f1 100644 --- a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java +++ b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java @@ -730,7 +730,8 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac if (campaign.getCampaignOptions().isUseGenericBattleValue()) { balancingType = " Generic"; } - logger.info("Generated a force with " + forceBV + '/' + forceBVBudget + ' ' + balancingType + " BV"); + logger.info(String.format("Generated a force with %s / %s %s BV", + forceBV, forceBVBudget, balancingType)); int adjustedBvBudget = (int) (forceBVBudget * 1.25); @@ -746,12 +747,14 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac forceBV -= battleValue; - logger.info("Culled " + generatedEntities.get(targetUnit).getDisplayName() - + " (" + battleValue + balancingType + " BV)"); + logger.info(String.format("Culled %s (%s %s BV)", + generatedEntities.get(targetUnit).getDisplayName(), battleValue, balancingType)); generatedEntities.remove(targetUnit); } - logger.info("Final force " + forceBV + '/' + adjustedBvBudget + balancingType + " BV)"); + + logger.info(String.format("Final force %s / %s %s BV", + forceBV, adjustedBvBudget, balancingType)); } // Units with infantry bays get conventional infantry or battle armor added From df83fd57fdc1aab01370e2c0066a1167f0c25537 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Tue, 24 Sep 2024 09:39:33 -0500 Subject: [PATCH 18/21] Update enemy handling in AtBContract Added Campaign parameter to updateEnemy method and enhanced batchall logic for Clan enemies. Removed redundant deriveRoleFromUnitType method. These changes streamline enemy updates and improve modularity within the mission handling framework. --- MekHQ/src/mekhq/campaign/Campaign.java | 77 ++++++------------- .../mekhq/campaign/mission/AtBContract.java | 58 ++++++-------- 2 files changed, 46 insertions(+), 89 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index 94b533708e..d78e055cd1 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -21,23 +21,6 @@ */ package mekhq.campaign; -import static mekhq.campaign.personnel.backgrounds.BackgroundsController.randomMercenaryCompanyNameGenerator; -import static mekhq.campaign.personnel.education.EducationController.getAcademy; -import static mekhq.campaign.personnel.turnoverAndRetention.RetirementDefectionTracker.Payout.isBreakingContract; -import static mekhq.campaign.unit.Unit.SITE_FACILITY_MAINTENANCE; - -import java.io.PrintWriter; -import java.text.MessageFormat; -import java.time.DayOfWeek; -import java.time.LocalDate; -import java.time.Month; -import java.time.temporal.ChronoUnit; -import java.util.*; -import java.util.Map.Entry; -import java.util.stream.Collectors; - -import javax.swing.JOptionPane; - import megamek.client.generator.RandomGenderGenerator; import megamek.client.generator.RandomNameGenerator; import megamek.client.generator.RandomUnitGenerator; @@ -54,11 +37,7 @@ import megamek.common.loaders.BLKFile; import megamek.common.loaders.EntityLoadingException; import megamek.common.loaders.EntitySavingException; -import megamek.common.options.GameOptions; -import megamek.common.options.IBasicOption; -import megamek.common.options.IOption; -import megamek.common.options.IOptionGroup; -import megamek.common.options.OptionsConstants; +import megamek.common.options.*; import megamek.common.util.BuildingBlock; import megamek.common.weapons.autocannons.ACWeapon; import megamek.common.weapons.flamers.FlamerWeapon; @@ -71,11 +50,7 @@ import mekhq.campaign.Quartermaster.PartAcquisitionResult; import mekhq.campaign.againstTheBot.AtBConfiguration; import mekhq.campaign.event.*; -import mekhq.campaign.finances.Accountant; -import mekhq.campaign.finances.CurrencyManager; -import mekhq.campaign.finances.Finances; -import mekhq.campaign.finances.Loan; -import mekhq.campaign.finances.Money; +import mekhq.campaign.finances.*; import mekhq.campaign.finances.enums.TransactionType; import mekhq.campaign.force.Force; import mekhq.campaign.force.Lance; @@ -90,12 +65,7 @@ import mekhq.campaign.market.ShoppingList; import mekhq.campaign.market.unitMarket.AbstractUnitMarket; import mekhq.campaign.market.unitMarket.DisabledUnitMarket; -import mekhq.campaign.mission.AtBContract; -import mekhq.campaign.mission.AtBDynamicScenario; -import mekhq.campaign.mission.AtBScenario; -import mekhq.campaign.mission.Contract; -import mekhq.campaign.mission.Mission; -import mekhq.campaign.mission.Scenario; +import mekhq.campaign.mission.*; import mekhq.campaign.mission.atb.AtBScenarioFactory; import mekhq.campaign.mission.enums.AtBLanceRole; import mekhq.campaign.mission.enums.MissionStatus; @@ -105,12 +75,7 @@ import mekhq.campaign.parts.equipment.AmmoBin; import mekhq.campaign.parts.equipment.EquipmentPart; import mekhq.campaign.parts.equipment.MissingEquipmentPart; -import mekhq.campaign.personnel.Bloodname; -import mekhq.campaign.personnel.Person; -import mekhq.campaign.personnel.PersonnelOptions; -import mekhq.campaign.personnel.Skill; -import mekhq.campaign.personnel.SkillType; -import mekhq.campaign.personnel.SpecialAbility; +import mekhq.campaign.personnel.*; import mekhq.campaign.personnel.autoAwards.AutoAwardsController; import mekhq.campaign.personnel.death.AbstractDeath; import mekhq.campaign.personnel.death.DisabledRandomDeath; @@ -118,12 +83,7 @@ import mekhq.campaign.personnel.divorce.DisabledRandomDivorce; import mekhq.campaign.personnel.education.Academy; import mekhq.campaign.personnel.education.EducationController; -import mekhq.campaign.personnel.enums.FamilialRelationshipType; -import mekhq.campaign.personnel.enums.PersonnelRole; -import mekhq.campaign.personnel.enums.PersonnelStatus; -import mekhq.campaign.personnel.enums.Phenotype; -import mekhq.campaign.personnel.enums.PrisonerStatus; -import mekhq.campaign.personnel.enums.SplittingSurnameStyle; +import mekhq.campaign.personnel.enums.*; import mekhq.campaign.personnel.generator.AbstractPersonnelGenerator; import mekhq.campaign.personnel.generator.DefaultPersonnelGenerator; import mekhq.campaign.personnel.generator.RandomPortraitGenerator; @@ -136,21 +96,16 @@ import mekhq.campaign.personnel.ranks.Ranks; import mekhq.campaign.personnel.turnoverAndRetention.Fatigue; import mekhq.campaign.personnel.turnoverAndRetention.RetirementDefectionTracker; +import mekhq.campaign.rating.CamOpsReputation.ReputationController; import mekhq.campaign.rating.FieldManualMercRevDragoonsRating; import mekhq.campaign.rating.IUnitRating; import mekhq.campaign.rating.UnitRatingMethod; -import mekhq.campaign.rating.CamOpsReputation.ReputationController; import mekhq.campaign.storyarc.StoryArc; import mekhq.campaign.stratcon.StratconContractInitializer; import mekhq.campaign.stratcon.StratconRulesManager; import mekhq.campaign.stratcon.StratconTrackState; -import mekhq.campaign.unit.CargoStatistics; import mekhq.campaign.unit.CrewType; -import mekhq.campaign.unit.HangarStatistics; -import mekhq.campaign.unit.TestUnit; -import mekhq.campaign.unit.Unit; -import mekhq.campaign.unit.UnitOrder; -import mekhq.campaign.unit.UnitTechProgression; +import mekhq.campaign.unit.*; import mekhq.campaign.universe.*; import mekhq.campaign.universe.Planet.PlanetaryEvent; import mekhq.campaign.universe.PlanetarySystem.PlanetarySystemEvent; @@ -171,6 +126,22 @@ import mekhq.service.mrms.MRMSService; import mekhq.utilities.MHQXMLUtility; +import javax.swing.*; +import java.io.PrintWriter; +import java.text.MessageFormat; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.Month; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +import static mekhq.campaign.personnel.backgrounds.BackgroundsController.randomMercenaryCompanyNameGenerator; +import static mekhq.campaign.personnel.education.EducationController.getAcademy; +import static mekhq.campaign.personnel.turnoverAndRetention.RetirementDefectionTracker.Payout.isBreakingContract; +import static mekhq.campaign.unit.Unit.SITE_FACILITY_MAINTENANCE; + /** * The main campaign class, keeps track of teams and units * @@ -3609,7 +3580,7 @@ && getCampaignOptions().getRandomDependentMethod().isAgainstTheBot() } for (AtBContract contract : getActiveAtBContracts()) { - contract.checkMorale(getLocalDate(), getAtBUnitRatingMod()); + contract.checkMorale(this, getLocalDate(), getAtBUnitRatingMod()); addReport("Enemy Morale is now " + contract.getMoraleLevel() + " on contract " + contract.getName()); } diff --git a/MekHQ/src/mekhq/campaign/mission/AtBContract.java b/MekHQ/src/mekhq/campaign/mission/AtBContract.java index 4b8759d144..30403d0e2d 100644 --- a/MekHQ/src/mekhq/campaign/mission/AtBContract.java +++ b/MekHQ/src/mekhq/campaign/mission/AtBContract.java @@ -43,7 +43,6 @@ import mekhq.campaign.personnel.Person; import mekhq.campaign.personnel.SkillType; import mekhq.campaign.personnel.backgrounds.BackgroundsController; -import mekhq.campaign.personnel.enums.PersonnelRole; import mekhq.campaign.personnel.enums.Phenotype; import mekhq.campaign.rating.IUnitRating; import mekhq.campaign.stratcon.StratconCampaignState; @@ -299,12 +298,19 @@ public void calculatePaymentMultiplier(Campaign campaign) { setMultiplier(multiplier); } - public void checkMorale(LocalDate today, int dragoonRating) { + /** + * Checks the morale level of the campaign based on various factors. + * + * @param campaign The ongoing campaign. + * @param today The current date. + * @param dragoonRating The player's dragoon rating + */ + public void checkMorale(Campaign campaign, LocalDate today, int dragoonRating) { if (null != routEnd) { if (today.isAfter(routEnd)) { setMoraleLevel(AtBMoraleLevel.NORMAL); routEnd = null; - updateEnemy(today); // mix it up a little + updateEnemy(campaign, today); // mix it up a little } else { setMoraleLevel(AtBMoraleLevel.BROKEN); } @@ -402,10 +408,12 @@ public void checkMorale(LocalDate today, int dragoonRating) { } /** - * Changes the enemy to a randomly selected faction that's an enemy of - * the current employer + * Updates the enemy faction and enemy bot name for this contract. + * + * @param campaign The current campaign. + * @param today The current LocalDate object. */ - private void updateEnemy(LocalDate today) { + private void updateEnemy(Campaign campaign, LocalDate today) { String enemyCode = RandomFactionGenerator.getInstance().getEnemy( Factions.getInstance().getFaction(employerCode), false, true); setEnemyCode(enemyCode); @@ -414,6 +422,14 @@ private void updateEnemy(LocalDate today) { setEnemyBotName(enemyFaction.getFullName(today.getYear())); enemyName = ""; // wipe the old enemy name getEnemyName(today.getYear()); // we use this to update enemyName + + // Update the Batchall information + batchallAccepted = true; + if (campaign.getCampaignOptions().isUseGenericBattleValue()) { + if (getEnemy().isClan()) { + setBatchallAccepted(initiateBatchall(campaign)); + } + } } /** @@ -1548,34 +1564,4 @@ public boolean initiateBatchall(Campaign campaign) { return true; } } - - /** - * Derives the personnel role based on the unit type of the given entity. - * - * @param entity The entity whose unit type needs to be evaluated. - * @return The personnel role derived from the unit type of the entity. - */ - private static PersonnelRole deriveRoleFromUnitType(Entity entity) { - if (entity.isAerospaceFighter()) { - return PersonnelRole.AEROSPACE_PILOT; - } else if (entity.isBattleArmor()) { - return PersonnelRole.BATTLE_ARMOUR; - } else if (entity.isConventionalFighter()) { - return PersonnelRole.CONVENTIONAL_AIRCRAFT_PILOT; - } else if (entity.isNaval()) { - return PersonnelRole.NAVAL_VEHICLE_DRIVER; - } else if (entity.isProtoMek()) { - return PersonnelRole.PROTOMEK_PILOT; - } else if (entity.isConventionalInfantry()) { - return PersonnelRole.SOLDIER; - } else if (entity.isAirborneVTOLorWIGE()) { - return PersonnelRole.VTOL_PILOT; - } else if (entity.isVehicle()) { - return PersonnelRole.GROUND_VEHICLE_DRIVER; - } else if (entity.isLargeCraft() || entity.isSmallCraft()) { - return PersonnelRole.VESSEL_PILOT; - } else { - return PersonnelRole.MEKWARRIOR; - } - } } From 66bc4aa4df029fcd7dc7eede529c51eecf4fe301 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Tue, 24 Sep 2024 15:17:16 -0500 Subject: [PATCH 19/21] Introduced the beginnings of the Fame and Infamy module with batchall functionality Implemented the Fame and Infamy module to track faction fame levels and batchall statements. Introduced the `BatchallFactions` and `FameAndInfamyController` classes for managing batchall usage and fame data. Updated relevant methods in `Campaign` and `AtBContract` to integrate this new feature, including XML writing and greeting adaptations. --- .../mekhq/resources/AtBContract.properties | 3 +- .../mekhq/resources/FameAndInfamy.properties | 482 ++++++++++++++++++ MekHQ/src/mekhq/campaign/Campaign.java | 15 +- .../mekhq/campaign/io/CampaignXmlParser.java | 73 +-- .../mekhq/campaign/mission/AtBContract.java | 172 +++---- .../fameAndInfamy/BatchallFactions.java | 107 ++++ .../FameAndInfamyController.java | 181 +++++++ 7 files changed, 871 insertions(+), 162 deletions(-) create mode 100644 MekHQ/resources/mekhq/resources/FameAndInfamy.properties create mode 100644 MekHQ/src/mekhq/campaign/universe/fameAndInfamy/BatchallFactions.java create mode 100644 MekHQ/src/mekhq/campaign/universe/fameAndInfamy/FameAndInfamyController.java diff --git a/MekHQ/resources/mekhq/resources/AtBContract.properties b/MekHQ/resources/mekhq/resources/AtBContract.properties index 9875e8c956..79eb4c16ca 100644 --- a/MekHQ/resources/mekhq/resources/AtBContract.properties +++ b/MekHQ/resources/mekhq/resources/AtBContract.properties @@ -37,6 +37,7 @@ batchallStatementGeneric.text="We are The Clans. This world is ours by right. Id batchallCloser.text=

Do you accept the Batchall? responseAccept.text=Accept Batchall responseRefuse.text=Refuse Batchall +responseBringItOn.text=Bring It On -refusalConfirmation.text=Are you sure? This will increase the difficulty of the entire contract. +refusalConfirmation.text=Are you sure? %s will not forget this betrayal. refusalReport.text=
YOU DARE TO REFUSE MY BATCHALL!?!
\ No newline at end of file diff --git a/MekHQ/resources/mekhq/resources/FameAndInfamy.properties b/MekHQ/resources/mekhq/resources/FameAndInfamy.properties new file mode 100644 index 0000000000..a27697b081 --- /dev/null +++ b/MekHQ/resources/mekhq/resources/FameAndInfamy.properties @@ -0,0 +1,482 @@ +# updateFameForFaction +fameChangeReportInfamy.text=You are now at Infamy %s with %s. + +# Batchall Statements +greetingFormatBatchall.text=greeting%s%sLevel%sType%s.text + +greetingCBSLevel0Type0.text=Let us craft a battle worthy of our ancestors. +greetingCBSLevel0Type1.text=Stand proud, and let us clash with all our might. +greetingCBSLevel0Type2.text=Together, let us write a new chapter in history. +greetingCBSLevel1Type0.text=Stand ready, and let us test each other in battle. +greetingCBSLevel1Type1.text=Show us your strength, and may the best prevail. +greetingCBSLevel1Type2.text=Let us meet on the field with honor. +greetingCBSLevel2Type0.text=Defend if you must; it changes nothing. +greetingCBSLevel2Type1.text=We claim what is ours, but your presence hardly matters to us. +greetingCBSLevel2Type2.text=You may stand against us, but it will make no difference in the end. +greetingCBSLevel3Type0.text=Your defense will be as mist before us: fleeting. +greetingCBSLevel3Type1.text=Blood calls for blood, but your efforts barely deserve acknowledgment. +greetingCBSLevel3Type2.text=You oppose Clan Blood Spirit? We shall erase this insult swiftly. +greetingCBSLevel4Type0.text=We have no time for the weak. What pitiful forces dare stand against us? +greetingCBSLevel4Type1.text=Blood calls for blood, yet yours is unworthy of the challenge. Face us if you dare. +greetingCBSLevel4Type2.text=We demand what is ours. Your resistance is nothing but a stain to be washed away. + + +greetingCBLevel0Type0.text=We admire your tenacity. Reveal your defenders, so we may face each other as equals. +greetingCBLevel0Type1.text=yet you stand strong. May this Trial be remembered for your bravery and strength. +greetingCBLevel0Type2.text=We honor your resolve. Let us create a battle that will echo through the ages. +greetingCBLevel1Type0.text=We recognize your strength. Identify your forces, and let us engage in honorable combat. +greetingCBLevel1Type1.text=You have chosen to stand before us. May our Trial be one worthy of remembrance. +greetingCBLevel1Type2.text=We respect your courage. Stand firm and let us see who endures. +greetingCBLevel2Type0.text=We advance. Stand in our way, or do not; it changes nothing. +greetingCBLevel2Type1.text=Your resistance is noted but irrelevant. We take what we desire. +greetingCBLevel2Type2.text=We claim this prize, but your defense holds no significance. +greetingCBLevel3Type0.text=We see little challenge here. Do you truly think you can oppose us? +greetingCBLevel3Type1.text=Your efforts are but a whisper against the strength of stone. Prepare to be ignored. +greetingCBLevel3Type2.text=You are nothing but an inconvenience. We shall cast you aside with ease. +greetingCBLevel4Type0.text=We stand unbroken, unlike your futile defense. +greetingCBLevel4Type1.text=We have endured ages, and you think to stop us? Prepare to be shattered. +greetingCBLevel4Type2.text=Your resistance is as soft as sand. We will grind you to dust. + + +greetingCCCLevel0Type0.text=We marvel at your courage. Declare your defenses, and let us meet in an honorable storm. +greetingCCCLevel0Type1.text=You have shown spirit. Let us see who can stand against the might of the Cobra. +greetingCCCLevel0Type2.text=Your bravery is commendable. Let us create a Trial that will be remembered in history. +greetingCCCLevel1Type0.text=We respect your resolve. Show us your defenses, and face the storm with pride. +greetingCCCLevel1Type1.text=You face us with courage. May this Trial be one of honor. +greetingCCCLevel1Type2.text=You have shown strength. Let us clash and see who stands victorious. +greetingCCCLevel2Type0.text=Stand if you wish; it makes no difference. +greetingCCCLevel2Type1.text=This target is ours. +greetingCCCLevel2Type2.text=You may show yourself, but it will not change the outcome. +greetingCCCLevel3Type0.text=You think to stand before us? We will barely feel your resistance. +greetingCCCLevel3Type1.text=Your presence is an annoyance. We will brush you aside with ease. +greetingCCCLevel3Type2.text=You are unworthy of our attention, but we shall remove you nonetheless. +greetingCCCLevel4Type0.text=What insignificant force dares oppose us? +greetingCCCLevel4Type1.text=You are but insects before the storm. Prepare to be swept away. +greetingCCCLevel4Type2.text=Your defense is a mere breeze against our power. + + +greetingCCOLevel0Type0.text=We admire your strength. Reveal yourselves, and let us engage in a battle worthy of legend. +greetingCCOLevel0Type1.text=You face us with the courage of a warrior. Let this Trial honor both our strengths. +greetingCCOLevel0Type2.text=We honor your tenacity. May this be a hunt that will be sung of for years to come. +greetingCCOLevel1Type0.text=We honor worthy prey. Reveal yourselves, and let us see if you can match our cunning. +greetingCCOLevel1Type1.text=You have shown courage by standing before us. Let us see if you are a worthy challenge. +greetingCCOLevel1Type2.text=We respect your spirit. Meet us in battle, and may the best hunter prevail. +greetingCCOLevel2Type0.text=Whether you stand or flee, it changes nothing. +greetingCCOLevel2Type1.text=You are but another obstacle in our hunt. We shall take what we desire. +greetingCCOLevel2Type2.text=Your defense is noted but irrelevant. We move forward regardless. +greetingCCOLevel3Type0.text=You cannot hope to outwit us. Your resistance is futile. +greetingCCOLevel3Type1.text=Your attempts at defense are laughable. We will tear through them with ease. +greetingCCOLevel3Type2.text=You dare to stand before us? We will enjoy this brief challenge. +greetingCCOLevel4Type0.text=We hunger, and you will be our prey. Resist if you dare. +greetingCCOLevel4Type1.text=You are nothing but a shadow in our path. Prepare to be devoured. +greetingCCOLevel4Type2.text=We hunt without mercy. Your defenses will crumble before us. + + +greetingCDSVersion1Level0Type0.text=Your resolve impresses us. Show your defenses, and let us challenge each other. +greetingCDSVersion1Level0Type1.text=You swim in dangerous waters with courage. Let us see who emerges victorious. +greetingCDSVersion1Level0Type2.text=Your bravery shines like a beacon. Let us create a Trial to be remembered. +greetingCDSVersion1Level1Type0.text=We acknowledge your resolve. Reveal your defenses, and let us cross the void together. +greetingCDSVersion1Level1Type1.text=You have shown courage against the tide. Let us see who truly commands the waters. +greetingCDSVersion1Level1Type2.text=We respect your tenacity. Let this Trial be as deep as the ocean we traverse. +greetingCDSVersion1Level2Type0.text=We continue our hunt. Defend if you must. +greetingCDSVersion1Level2Type1.text=You are but another ripple in the ocean. We will not be deterred. +greetingCDSVersion1Level2Type2.text=We take what we desire. Your defense is unimportant. +greetingCDSVersion1Level3Type0.text=We hardly notice you. What little can you muster? +greetingCDSVersion1Level3Type1.text=You are but a minnow in our ocean. Prepare to be devoured. +greetingCDSVersion1Level3Type2.text=Your defenses will mean nothing. We will consume all. +greetingCDSVersion1Level4Type0.text=We bare our teeth. What worthless forces will you offer? +greetingCDSVersion1Level4Type1.text=Your defense is a minor inconvenience. We will take what is ours. +greetingCDSVersion1Level4Type2.text=You are but fish in the sea. Prepare to be devoured. + +greetingCDSVersion2Level0Type0.text=PLACEHOLDER +greetingCDSVersion2Level0Type1.text=PLACEHOLDER +greetingCDSVersion2Level0Type2.text=PLACEHOLDER +greetingCDSVersion2Level1Type0.text=PLACEHOLDER +greetingCDSVersion2Level1Type1.text=PLACEHOLDER +greetingCDSVersion2Level1Type2.text=PLACEHOLDER +greetingCDSVersion2Level2Type0.text=PLACEHOLDER +greetingCDSVersion2Level2Type1.text=PLACEHOLDER +greetingCDSVersion2Level2Type2.text=PLACEHOLDER +greetingCDSVersion2Level3Type0.text=PLACEHOLDER +greetingCDSVersion2Level3Type1.text=PLACEHOLDER +greetingCDSVersion2Level3Type2.text=PLACEHOLDER +greetingCDSVersion2Level4Type0.text=PLACEHOLDER +greetingCDSVersion2Level4Type1.text=PLACEHOLDER +greetingCDSVersion2Level4Type2.text=PLACEHOLDER + + +greetingCFMLevel0Type0.text=We honor your valor. Show us your defenses, and let us see who burns the brightest. +greetingCFMLevel0Type1.text=You stand before us with courage. Let us create a blaze that will light the path for others. +greetingCFMLevel0Type2.text=Your spirit is strong. May this Trial be as fierce as the flames that guide us. +greetingCFMLevel1Type0.text=We acknowledge your courage. Stand firm, and be tested in the fires of battle. +greetingCFMLevel1Type1.text=You face us with bravery. Let us burn brightly together in this Trial. +greetingCFMLevel1Type2.text=We honor your strength. May our flames clash and reveal who is strongest. +greetingCFMLevel2Type0.text=We burn bright. Defend if you dare, or step aside. +greetingCFMLevel2Type1.text=Your resistance will be consumed all the same. Stand, or do not. +greetingCFMLevel2Type2.text=You matter little to us. We take what is ours, regardless. +greetingCFMLevel3Type0.text=Your efforts barely deserve our notice. +greetingCFMLevel3Type1.text=You are but kindling to our flame. Your resistance will be short-lived. +greetingCFMLevel3Type2.text=We burn brighter than you will ever understand. Stand aside or be consumed. +greetingCFMLevel4Type0.text=You are unworthy of our flame. Prepare to be consumed. +greetingCFMLevel4Type1.text=Your defenses will fuel our fire, leaving nothing but ash. +greetingCFMLevel4Type2.text=We burn through all. What pathetic resistance will you offer? + + +greetingCGBVersion1Level0Type0.text=We are impressed by your resolve. Identify your forces, so that we may share this Trial. +greetingCGBVersion1Level0Type1.text=You have shown the heart of a warrior. Let us test one another in this honored battle. +greetingCGBVersion1Level0Type2.text=Your courage is an inspiration. Together, let us make this a battle to be remembered. +greetingCGBVersion1Level1Type0.text=We respect your strength. Declare your forces, and we will meet you in battle. +greetingCGBVersion1Level1Type1.text=You have shown bravery. Let this be a Trial worthy of our ancestors. +greetingCGBVersion1Level1Type2.text=We acknowledge your courage. Stand before us, and let us see who is truly strong. +greetingCGBVersion1Level2Type0.text=Identify your forces or be forgotten. +greetingCGBVersion1Level2Type1.text=We claim what we desire. Your stand is of little concern. +greetingCGBVersion1Level2Type2.text=We care not for your defense. We will proceed. +greetingCGBVersion1Level3Type0.text=We see through your weakness. Declare your forces, or be removed. +greetingCGBVersion1Level3Type1.text=You amount to little more than a fleeting challenge. Prepare to fall. +greetingCGBVersion1Level3Type2.text=Your efforts are inconsequential. We shall take what is ours. +greetingCGBVersion1Level4Type0.text=We dismiss your efforts. Reveal your forces, or be swept aside. +greetingCGBVersion1Level4Type1.text=You are nothing but prey, and we are the hunters. Identify yourselves. +greetingCGBVersion1Level4Type2.text=Your resistance is beneath notice. Prepare to be obliterated. + +greetingCGBVersion2Level0Type0.text=PLACEHOLDER +greetingCGBVersion2Level0Type1.text=PLACEHOLDER +greetingCGBVersion2Level0Type2.text=PLACEHOLDER +greetingCGBVersion2Level1Type0.text=PLACEHOLDER +greetingCGBVersion2Level1Type1.text=PLACEHOLDER +greetingCGBVersion2Level1Type2.text=PLACEHOLDER +greetingCGBVersion2Level2Type0.text=PLACEHOLDER +greetingCGBVersion2Level2Type1.text=PLACEHOLDER +greetingCGBVersion2Level2Type2.text=PLACEHOLDER +greetingCGBVersion2Level3Type0.text=PLACEHOLDER +greetingCGBVersion2Level3Type1.text=PLACEHOLDER +greetingCGBVersion2Level3Type2.text=PLACEHOLDER +greetingCGBVersion2Level4Type0.text=PLACEHOLDER +greetingCGBVersion2Level4Type1.text=PLACEHOLDER +greetingCGBVersion2Level4Type2.text=PLACEHOLDER + + +greetingCGSLevel0Type0.text=We admire your tenacity. Present your defenses, and let us face each other with honor. +greetingCGSLevel0Type1.text=You have shown great strength. May this Trial be one that is recounted in tales. +greetingCGSLevel0Type2.text=Your resolve shines brightly. Let us make this encounter a legendary one. +greetingCGSLevel1Type0.text=We recognize your bravery. Present your defenses, and face us with honor. +greetingCGSLevel1Type1.text=You have chosen to stand. Let us see if you are worthy of our sting. +greetingCGSLevel1Type2.text=We respect your resolve. Let us engage in a Trial worthy of song. +greetingCGSLevel2Type0.text=We move to strike. Stand or fall; it makes no difference. +greetingCGSLevel2Type1.text=You are but another hindrance. We will claim what we seek. +greetingCGSLevel2Type2.text=Your defenses matter little. We advance regardless. +greetingCGSLevel3Type0.text=We see no challenge. Present your futile defenses. +greetingCGSLevel3Type1.text=You are beneath us, unworthy of our sting. Stand if you must. +greetingCGSLevel3Type2.text=We expected more. Prepare to be crushed. +greetingCGSLevel4Type0.text=We strike. Your defenses will be crushed underfoot. +greetingCGSLevel4Type1.text=You are but insects to us. We claim what is ours. +greetingCGSLevel4Type2.text=There is no place for the weak in our path. Prepare to be stung. + + +greetingCHHLevel0Type0.text=We respect your strength. Stand before us proudly, and let us charge into battle together. +greetingCHHLevel0Type1.text=You have proven yourself worthy. Let us create a Trial that will be told to future generations. +greetingCHHLevel0Type2.text=Your courage matches our own. Let this be a contest of champions. +greetingCHHLevel1Type0.text=We admire your courage. Stand before us, and show your strength against our charge. +greetingCHHLevel1Type1.text=You have proven worthy to face us. Let us clash and see who emerges victorious. +greetingCHHLevel1Type2.text=We honor your bravery. Let our hooves and your defenses meet in glorious battle. +greetingCHHLevel2Type0.text=We charge onward. Whether you stand or not is irrelevant. +greetingCHHLevel2Type1.text=Your efforts do not concern us. We ride forward unimpeded. +greetingCHHLevel2Type2.text=You are but dust beneath our hooves. We take what is ours. +greetingCHHLevel3Type0.text=We find your stand laughable. Who dares face our charge? +greetingCHHLevel3Type1.text=Your defenses are nothing but a hindrance. We will trample you. +greetingCHHLevel3Type2.text=You offer so little. It will be over before you know it. +greetingCHHLevel4Type0.text=We trample over the weak. Who dares claim they can stop us? +greetingCHHLevel4Type1.text=You are but dust beneath our hooves. We will ride through you. +greetingCHHLevel4Type2.text=You are no obstacle, just a fleeting moment before the stampede. + + +greetingCIHLevel0Type0.text=We are impressed by your courage. Reveal your forces, and let us test our mettle. +greetingCIHLevel0Type1.text=You face us with bravery. Let this Trial be as fierce and unyielding as ice. +greetingCIHLevel0Type2.text=Your spirit is admirable. Together, let us create a battle worth remembering. +greetingCIHLevel1Type0.text=We acknowledge your readiness. Reveal your forces, and face the fury of our onslaught. +greetingCIHLevel1Type1.text=You have shown resolve. Let us see if you can withstand our speed and precision. +greetingCIHLevel1Type2.text=We respect your stand. May this Trial be as fierce as the Ice Hellion's bite. +greetingCIHLevel2Type0.text=We descend. Your resistance is of no consequence. +greetingCIHLevel2Type1.text=We strike regardless of your presence. Stand or move aside. +greetingCIHLevel2Type2.text=You mean little to us. We take this prize without delay. +greetingCIHLevel3Type0.text=We regard you with indifference. Stand if you wish, but you will fall. +greetingCIHLevel3Type1.text=You are but a whisper in the wind. We will sweep you away. +greetingCIHLevel3Type2.text=Your resistance is but ice beneath our claws. It will shatter. +greetingCIHLevel4Type0.text=We care not for your resistance. Prepare to be frozen in fear. +greetingCIHLevel4Type1.text=Your defenses will shatter like ice before us. Stand if you wish. +greetingCIHLevel4Type2.text=Our fury is upon you. You will be swept away. + + +greetingCJFLevel0Type0.text=We admire worthy adversaries. Show us your defenses, and face our talons with pride. +greetingCJFLevel0Type1.text=You face us with the heart of a true warrior. Let us make this a battle of legends. +greetingCJFLevel0Type2.text=Your strength inspires us. May this Trial be one that honors both our names. +greetingCJFLevel1Type0.text=We honor worthy opponents. Show us your defenses, and let our talons test your mettle. +greetingCJFLevel1Type1.text=You have chosen to face the Jade Falcon. May your stand be worthy of song. +greetingCJFLevel1Type2.text=We respect your courage. Let us see who earns the sky today. +greetingCJFLevel2Type0.text=We claim this prize. Stand if you choose; it matters not. +greetingCJFLevel2Type1.text=You may defend yourself, but it changes nothing. We will proceed. +greetingCJFLevel2Type2.text=The talons of the Jade Falcon reach forth. Your resistance is trivial. +greetingCJFLevel3Type0.text=We disregard you. Who believes they can stand against us? +greetingCJFLevel3Type1.text=Your defenses are nothing more than a fleeting distraction. +greetingCJFLevel3Type2.text=You are but prey beneath our talons. Prepare to fall. +greetingCJFLevel4Type0.text=We scoff at your pitiful defenses. Prepare to be torn apart. +greetingCJFLevel4Type1.text=You are but prey, awaiting our talons. Reveal yourselves. +greetingCJFLevel4Type2.text=We do not ask; we take. Stand, or be crushed. + + +greetingCMGLevel0Type0.text=We respect your agility. Identify yourselves, and let us test our swiftness together. +greetingCMGLevel0Type1.text=You have shown incredible speed. Let us see who truly commands the hunt. +greetingCMGLevel0Type2.text=We admire your tenacity. May this Trial be one for the annals of history. +greetingCMGLevel1Type0.text=We respect your defenses. Identify yourselves, and let us see who is truly swift. +greetingCMGLevel1Type1.text=You have proven to be quick-witted. Let us match our speed against yours. +greetingCMGLevel1Type2.text=We honor your agility. Stand before us and may the swiftest emerge victorious. +greetingCMGLevel2Type0.text=We move swiftly. Show yourselves, or be ignored. +greetingCMGLevel2Type1.text=Your defenses mean nothing to us. We will take what we seek. +greetingCMGLevel2Type2.text=We move forward, whether you stand or not. It matters little. +greetingCMGLevel3Type0.text=We find you insignificant. Show yourselves, or be hunted down. +greetingCMGLevel3Type1.text=Your presence is barely worth acknowledging. Stand if you must. +greetingCMGLevel3Type2.text=You are nothing but prey in our path. We will strike you down. +greetingCMGLevel4Type0.text=We strike without mercy. You will not stand in our way. +greetingCMGLevel4Type1.text=Your defenses mean nothing. Prepare to be hunted. +greetingCMGLevel4Type2.text=You are but prey in our path. Identify yourselves or be swept aside. + + +greetingCNCLevel0Type0.text=We honor your spirit. Declare your forces, and let us meet our fate together. +greetingCNCLevel0Type1.text=You have faced us without fear. Let this battle be guided by the stars themselves. +greetingCNCLevel0Type2.text=We respect your strength. Together, let us fulfill the vision of this Trial. +greetingCNCLevel1Type0.text=We see strength in you. Declare your forces, and let destiny guide our battle. +greetingCNCLevel1Type1.text=You face us with courage. May this Trial reveal the strength of both our spirits. +greetingCNCLevel1Type2.text=We respect your resolve. Let us fight, as the stars have foreseen. +greetingCNCLevel2Type0.text=We have seen this moment. Your stand is inconsequential. +greetingCNCLevel2Type1.text=We advance as foreseen. Whether you resist changes nothing. +greetingCNCLevel2Type2.text=Your efforts are insignificant. We take what is destined. +greetingCNCLevel3Type0.text=We see your end is near. What futile defense do you offer? +greetingCNCLevel3Type1.text=You stand against destiny. We will put you in your place. +greetingCNCLevel3Type2.text=You will be swept aside as the vision decreed. +greetingCNCLevel4Type0.text=We see only your defeat. Prepare to be extinguished. +greetingCNCLevel4Type1.text=Your resistance is but an illusion. You cannot escape your fate. +greetingCNCLevel4Type2.text=We have foreseen your failure. Stand if you must, but it changes nothing. + + +greetingCSJLevel0Type0.text=We commend your courage. Identify your forces, and let us battle with all our might. +greetingCSJLevel0Type1.text=You have proven yourself worthy of our claws. Let this Trial be one of legends. +greetingCSJLevel0Type2.text=We honor your spirit. May this be a fierce and worthy battle. +greetingCSJLevel1Type0.text=We respect your courage. Identify your forces, and face our fury with pride. +greetingCSJLevel1Type1.text=You stand against us with bravery. Let us see if you can match our ferocity. +greetingCSJLevel1Type2.text=We honor your strength. May this Trial be as fierce as our claws. +greetingCSJLevel2Type0.text=We prowl onward. Your resistance is irrelevant. +greetingCSJLevel2Type1.text=We strike whether you stand or flee. It matters little to us. +greetingCSJLevel2Type2.text=You are but prey, and we will proceed regardless. +greetingCSJLevel3Type0.text=We disregard you. Identify your forces, or be swept away. +greetingCSJLevel3Type1.text=Your defenses are a mere annoyance. We will tear through you. +greetingCSJLevel3Type2.text=You are not worthy of our attention. Prepare to be destroyed. +greetingCSJLevel4Type0.text=Our fury is unmatched. You will not survive. +greetingCSJLevel4Type1.text=Your defenses will crumble before us. Prepare to be annihilated. +greetingCSJLevel4Type2.text=We do not tolerate weakness. Stand and be destroyed. + + +greetingCSRLevel0Type0.text=We admire your bravery. Declare your defenses, and let us soar together in this Trial. +greetingCSRLevel0Type1.text=You face us with courage. May this battle be worthy of the highest peaks. +greetingCSRLevel0Type2.text=Your strength is commendable. Let us create a story that will be told for ages. +greetingCSRLevel1Type0.text=We honor your stand. Declare your forces, and let us test our wings against one another. +greetingCSRLevel1Type1.text=You have shown courage beneath our shadow. May this Trial lift you to the skies. +greetingCSRLevel1Type2.text=We respect your resolve. Let us meet in battle, where the wind favors the worthy. +greetingCSRLevel2Type0.text=Your presence is of no concern to us. +greetingCSRLevel2Type1.text=We glide past your defenses. Stand if you wish; it changes nothing. +greetingCSRLevel2Type2.text=You are but a shadow below. We take what we came for. +greetingCSRLevel3Type0.text=We have no patience for you. Show yourselves, or be scattered. +greetingCSRLevel3Type1.text=You are but a shadow on the snow. We will brush you aside. +greetingCSRLevel3Type2.text=You matter little to us. We will take what we came for. +greetingCSRLevel4Type0.text=We look down upon you. You are unworthy of our notice. +greetingCSRLevel4Type1.text=Your resistance is but a flurry in the storm. Prepare to be blown away. +greetingCSRLevel4Type2.text=We do not wait. Stand or be cast aside. + + +greetingCSALevel0Type0.text=We respect your tenacity. Reveal your defenses, and let us face each other with honor. +greetingCSALevel0Type1.text=You have proven yourself worthy of our strike. Let us make this a Trial to remember. +greetingCSALevel0Type2.text=We admire your strength. Let this encounter be one of the greatest Trials. +greetingCSALevel1Type0.text=We respect your strength. Present your defenses, and prepare for a true challenge. +greetingCSALevel1Type1.text=You face us with bravery. May this Trial be as fierce as our venom. +greetingCSALevel1Type2.text=We acknowledge your strength. Let us see who will earn the right to this prize. +greetingCSALevel2Type0.text=Defend if you wish, but it changes nothing. +greetingCSALevel2Type1.text=You are insignificant to our advance. We strike regardless. +greetingCSALevel2Type2.text=Your resistance is noted but unimportant. We will claim what is ours. +greetingCSALevel3Type0.text=We find you unworthy. Present yourselves, or be crushed. +greetingCSALevel3Type1.text=You are but prey before us. Your resistance will be for nothing. +greetingCSALevel3Type2.text=You stand against us? Prepare to be destroyed. +greetingCSALevel4Type0.text=We strike with venom. You will not survive our bite. +greetingCSALevel4Type1.text=Your defenses are laughable. Prepare to be crushed. +greetingCSALevel4Type2.text=You are prey caught in our coils. Identify yourselves. + + +greetingCSVLevel0Type0.text=We admire your strength. Show yourselves, and let us clash in battle. +greetingCSVLevel0Type1.text=You have faced us with courage. May this Trial prove the strength of both our wills. +greetingCSVLevel0Type2.text=Your resolve is commendable. Let us create a battle that will echo through time. +greetingCSVLevel1Type0.text=We acknowledge your resolve. Stand ready, and let us test our fangs. +greetingCSVLevel1Type1.text=You have shown strength. May this Trial prove who is truly worthy. +greetingCSVLevel1Type2.text=We respect your stand. Let us see who survives this battle of steel. +greetingCSVLevel2Type0.text=We slither forward. Your stand is meaningless. +greetingCSVLevel2Type1.text=You will be crushed whether you resist or not. We will take this prize. +greetingCSVLevel2Type2.text=You are but prey. We advance regardless of your defense. +greetingCSVLevel3Type0.text=We pay you no mind. Reveal your forces, or fall swiftly. +greetingCSVLevel3Type1.text=You will be crushed under our coils. You are but a hindrance. +greetingCSVLevel3Type2.text=You are nothing but prey to be constricted and eliminated. +greetingCSVLevel4Type0.text=We will strike you down. You have no chance to survive. +greetingCSVLevel4Type1.text=Your efforts are futile. We will constrict you until nothing remains. +greetingCSVLevel4Type2.text=You are unworthy of our notice. Prepare to be eradicated. + + +greetingCSLLevel0Type0.text=We honor your courage. Present your defenses, and let us fight with pride. +greetingCSLLevel0Type1.text=You have shown the spirit of a lion. Let us meet in battle, as true warriors. +greetingCSLLevel0Type2.text=We respect your strength. May this be a Trial worthy of our ancestors. +greetingCSLLevel1Type0.text=We respect your courage. Reveal your defenses, and face us with pride. +greetingCSLLevel1Type1.text=You stand before us with bravery. Let us test each other's strength. +greetingCSLLevel1Type2.text=We honor your spirit. Let this Trial determine who is truly worthy. +greetingCSLLevel2Type0.text=Whether you stand or yield means nothing. +greetingCSLLevel2Type1.text=You are but dust in our path. We claim this goal regardless. +greetingCSLLevel2Type2.text=Your defense is but a minor inconvenience. We move forward. +greetingCSLLevel3Type0.text=We find your resistance laughable. Prepare for defeat. +greetingCSLLevel3Type1.text=You will be a mere notch on our claws. Stand if you dare. +greetingCSLLevel3Type2.text=Your defenses are pebbles before us. We will crush you. +greetingCSLLevel4Type0.text=We pounce. You will not survive our fury. +greetingCSLLevel4Type1.text=Your defenses are mere pebbles. We will crush you. +greetingCSLLevel4Type2.text=We show no mercy. Stand and be torn apart. + + +greetingCWILevel0Type0.text=We respect your valor. Declare your forces, and let us engage in worthy combat. +greetingCWILevel0Type1.text=You have shown courage. Let us create a Trial that honors both our legacies. +greetingCWILevel0Type2.text=We admire your strength. Together, let us create a battle that will be sung of. +greetingCWILevel1Type0.text=We respect your valor. Declare your forces, and meet us with dignity. +greetingCWILevel1Type1.text=You have shown courage. May this battle be worthy of our ancestors. +greetingCWILevel1Type2.text=We acknowledge your strength. Let us engage in combat befitting warriors. +greetingCWILevel2Type0.text=Whether you resist or not, you will fall. +greetingCWILevel2Type1.text=Your presence is irrelevant. We take what belongs to us. +greetingCWILevel2Type2.text=You are but another thread in our web. We will take what we want. +greetingCWILevel3Type0.text=We care little for your stand. Declare your forces, or be destroyed. +greetingCWILevel3Type1.text=You will be trapped in our web. Your resistance is futile. +greetingCWILevel3Type2.text=You are but a distraction. We will eradicate you. +greetingCWILevel4Type0.text=You will not survive this encounter. +greetingCWILevel4Type1.text=Your defense is meaningless. Prepare to be swept aside. +greetingCWILevel4Type2.text=You are but prey in our web. Prepare for destruction. + + +greetingCWLevel0Type0.text=We admire your strength. Identify yourselves, and let us meet as true warriors. +greetingCWLevel0Type1.text=You have shown the heart of a warrior. Let this be a Trial that echoes through time. +greetingCWLevel0Type2.text=We respect your spirit. May this be a battle worthy of our ancestors. +greetingCWLevel1Type0.text=We respect the strength of those who stand. Show us your forces, and face us with pride. +greetingCWLevel1Type1.text=You have chosen to face the Wolves. Let us see who earns the right to this land. +greetingCWLevel1Type2.text=We honor your resolve. Let us meet in a battle worthy of legends. +greetingCWLevel2Type0.text=Your resistance is trivial at best. +greetingCWLevel2Type1.text=Stand if you wish; we take this land regardless. +greetingCWLevel2Type2.text=Your defense means nothing. We claim what is ours. +greetingCWLevel3Type0.text=We see only tame dogs before us. You dare to challenge us? +greetingCWLevel3Type1.text=Your defenses are barely worth our time. Stand aside or be destroyed. +greetingCWLevel3Type2.text=You amount to nothing before us. Prepare to be devoured. +greetingCWLevel4Type0.text=We have no patience for the weak. Reveal yourselves. +greetingCWLevel4Type1.text=You are mere sheep before us. Prepare to be devoured. +greetingCWLevel4Type2.text=Your resistance is pitiful. We will tear through you. + + +greetingCWELevel1Type0.text=PLACEHOLDER +greetingCWELevel1Type1.text=PLACEHOLDER +greetingCWELevel1Type2.text=PLACEHOLDER +greetingCWELevel2Type0.text=PLACEHOLDER +greetingCWELevel2Type1.text=PLACEHOLDER +greetingCWELevel2Type2.text=PLACEHOLDER +greetingCWELevel3Type0.text=PLACEHOLDER +greetingCWELevel3Type1.text=PLACEHOLDER +greetingCWELevel3Type2.text=PLACEHOLDER +greetingCWELevel4Type0.text=PLACEHOLDER +greetingCWELevel4Type1.text=PLACEHOLDER +greetingCWELevel4Type2.text=PLACEHOLDER + + +greetingCWIELevel0Type0.text=We are impressed by your resolve. Reveal your defenses, and let us reclaim our honor together. +greetingCWIELevel0Type1.text=You stand before us with courage. Let this Trial be one to remember. +greetingCWIELevel0Type2.text=We admire your bravery. Let us engage in battle as warriors of old. +greetingCWIELevel1Type0.text=We honor worthy defenders. Reveal yourselves, and let us reclaim what is rightfully ours. +greetingCWIELevel1Type1.text=You stand before us with courage. May this Trial be one to remember. +greetingCWIELevel1Type2.text=We respect your strength. Let us engage and determine who truly deserves this place. +greetingCWIELevel2Type0.text=Your efforts are meaningless. +greetingCWIELevel2Type1.text=We reclaim what belongs to us, regardless of your resistance. +greetingCWIELevel2Type2.text=Your presence is of little concern. We will take what is ours. +greetingCWIELevel3Type0.text=We find you irrelevant. Reveal yourselves, or be cast out. +greetingCWIELevel3Type1.text=You have no place before us. We will reclaim what is ours. +greetingCWIELevel3Type2.text=Your defenses are insignificant. We will take back our ground. +greetingCWIELevel4Type0.text=We will reclaim what is ours. +greetingCWIELevel4Type1.text=Your defense means nothing. Stand or be swept away. +greetingCWIELevel4Type2.text=You cannot stop us. Prepare to be eradicated. + + +greetingCEIVersion1Level0Type0.text=PLACEHOLDER +greetingCEIVersion1Level0Type1.text=PLACEHOLDER +greetingCEIVersion1Level0Type2.text=PLACEHOLDER +greetingCEIVersion1Level1Type0.text=PLACEHOLDER +greetingCEIVersion1Level1Type1.text=PLACEHOLDER +greetingCEIVersion1Level1Type2.text=PLACEHOLDER +greetingCEIVersion1Level2Type0.text=PLACEHOLDER +greetingCEIVersion1Level2Type1.text=PLACEHOLDER +greetingCEIVersion1Level2Type2.text=PLACEHOLDER +greetingCEIVersion1Level3Type0.text=PLACEHOLDER +greetingCEIVersion1Level3Type1.text=PLACEHOLDER +greetingCEIVersion1Level3Type2.text=PLACEHOLDER +greetingCEIVersion1Level4Type0.text=PLACEHOLDER +greetingCEIVersion1Level4Type1.text=PLACEHOLDER +greetingCEIVersion1Level4Type2.text=PLACEHOLDER + +greetingCEIVersion2Level0Type0.text=PLACEHOLDER +greetingCEIVersion2Level0Type1.text=PLACEHOLDER +greetingCEIVersion2Level0Type2.text=PLACEHOLDER +greetingCEIVersion2Level1Type0.text=PLACEHOLDER +greetingCEIVersion2Level1Type1.text=PLACEHOLDER +greetingCEIVersion2Level1Type2.text=PLACEHOLDER +greetingCEIVersion2Level2Type0.text=PLACEHOLDER +greetingCEIVersion2Level2Type1.text=PLACEHOLDER +greetingCEIVersion2Level2Type2.text=PLACEHOLDER +greetingCEIVersion2Level3Type0.text=PLACEHOLDER +greetingCEIVersion2Level3Type1.text=PLACEHOLDER +greetingCEIVersion2Level3Type2.text=PLACEHOLDER +greetingCEIVersion2Level4Type0.text=PLACEHOLDER +greetingCEIVersion2Level4Type1.text=PLACEHOLDER +greetingCEIVersion2Level4Type2.text=PLACEHOLDER + + +greetingRDLevel0Type0.text=PLACEHOLDER +greetingRDLevel0Type1.text=PLACEHOLDER +greetingRDLevel0Type2.text=PLACEHOLDER +greetingRDLevel1Type0.text=PLACEHOLDER +greetingRDLevel1Type1.text=PLACEHOLDER +greetingRDLevel1Type2.text=PLACEHOLDER +greetingRDLevel2Type0.text=PLACEHOLDER +greetingRDLevel2Type1.text=PLACEHOLDER +greetingRDLevel2Type2.text=PLACEHOLDER +greetingRDLevel3Type0.text=PLACEHOLDER +greetingRDLevel3Type1.text=PLACEHOLDER +greetingRDLevel3Type2.text=PLACEHOLDER +greetingRDLevel4Type0.text=PLACEHOLDER +greetingRDLevel4Type1.text=PLACEHOLDER +greetingRDLevel4Type2.text=PLACEHOLDER + + +greetingRALevel0Type0.text=PLACEHOLDER +greetingRALevel0Type1.text=PLACEHOLDER +greetingRALevel0Type2.text=PLACEHOLDER +greetingRALevel1Type0.text=PLACEHOLDER +greetingRALevel1Type1.text=PLACEHOLDER +greetingRALevel1Type2.text=PLACEHOLDER +greetingRALevel2Type0.text=PLACEHOLDER +greetingRALevel2Type1.text=PLACEHOLDER +greetingRALevel2Type2.text=PLACEHOLDER +greetingRALevel3Type0.text=PLACEHOLDER +greetingRALevel3Type1.text=PLACEHOLDER +greetingRALevel3Type2.text=PLACEHOLDER +greetingRALevel4Type0.text=PLACEHOLDER +greetingRALevel4Type1.text=PLACEHOLDER +greetingRALevel4Type2.text=PLACEHOLDER + + +greetingCLANLevel0Type0.text=Identify yourselves, and let us share this honorable battle. +greetingCLANLevel1Type0.text=Identify yourselves, and let us meet in honorable battle. +greetingCLANLevel2Type0.text=Stand in our way or not; it changes nothing. +greetingCLANLevel3Type0.text=Identify yourselves, or be erased. +greetingCLANLevel4Type0.text=Identify the forces that dare oppose us, or face immediate destruction. +greetingCLANLevel5Type0.text=Since you have proven yourself unworthy of honor, we will dispense with a formal Batchall. \ No newline at end of file diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index d78e055cd1..b5e7489a69 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -111,6 +111,8 @@ import mekhq.campaign.universe.PlanetarySystem.PlanetarySystemEvent; import mekhq.campaign.universe.eras.Era; import mekhq.campaign.universe.eras.Eras; +import mekhq.campaign.universe.fameAndInfamy.BatchallFactions; +import mekhq.campaign.universe.fameAndInfamy.FameAndInfamyController; import mekhq.campaign.universe.selectors.factionSelectors.AbstractFactionSelector; import mekhq.campaign.universe.selectors.factionSelectors.DefaultFactionSelector; import mekhq.campaign.universe.selectors.factionSelectors.RangedFactionSelector; @@ -264,6 +266,7 @@ public class Campaign implements ITechManager { private final CampaignSummary campaignSummary; private final Quartermaster quartermaster; private StoryArc storyArc; + private FameAndInfamyController fameAndInfamy; private final transient ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Campaign", MekHQ.getMHQOptions().getLocale()); @@ -330,6 +333,7 @@ public Campaign() { campaignSummary = new CampaignSummary(this); quartermaster = new Quartermaster(this); fieldKitchenWithinCapacity = false; + fameAndInfamy = new FameAndInfamyController(); } /** @@ -3589,7 +3593,7 @@ && getCampaignOptions().getRandomDependentMethod().isAgainstTheBot() for (AtBContract contract : getActiveAtBContracts()) { if (campaignOptions.isUseGenericBattleValue()) { if (contract.getStartDate().equals(getLocalDate()) && getLocation().isOnPlanet()) { - if (contract.getEnemy().isClan()) { + if (BatchallFactions.usesBatchalls(contract.getEnemyCode())) { contract.setBatchallAccepted(contract.initiateBatchall(this)); } } @@ -4810,6 +4814,10 @@ public List getCurrentObjectives() { return new ArrayList<>(); } + public FameAndInfamyController getFameAndInfamy() { + return fameAndInfamy; + } + public void writeToXML(final PrintWriter pw) { int indent = 0; @@ -4947,6 +4955,11 @@ public void writeToXML(final PrintWriter pw) { storyArc.writeToXml(pw, indent); } + // Fame and Infamy + if (fameAndInfamy != null) { + fameAndInfamy.writeToXml(pw, indent); + } + // Markets getPersonnelMarket().writeToXML(pw, indent, this); diff --git a/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java b/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java index b0333ec883..0bbc8daf25 100644 --- a/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java +++ b/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java @@ -18,44 +18,12 @@ */ package mekhq.campaign.io; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintStream; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.UUID; - -import javax.xml.parsers.DocumentBuilder; - -import org.apache.commons.lang3.StringUtils; -import org.w3c.dom.DOMException; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - import megamek.Version; import megamek.client.generator.RandomGenderGenerator; import megamek.client.generator.RandomNameGenerator; import megamek.client.ui.swing.util.PlayerColour; import megamek.common.Entity; -import megamek.common.EntityMovementMode; -import megamek.common.Jumpship; -import megamek.common.Mek; -import megamek.common.MekSummaryCache; -import megamek.common.MiscType; -import megamek.common.Mounted; -import megamek.common.SmallCraft; -import megamek.common.Tank; +import megamek.common.*; import megamek.common.annotations.Nullable; import megamek.common.icons.Camouflage; import megamek.common.weapons.bayweapons.BayWeapon; @@ -63,12 +31,7 @@ import mekhq.MekHQ; import mekhq.NullEntityException; import mekhq.Utilities; -import mekhq.campaign.Campaign; -import mekhq.campaign.CampaignOptions; -import mekhq.campaign.CurrentLocation; -import mekhq.campaign.Kill; -import mekhq.campaign.RandomSkillPreferences; -import mekhq.campaign.Warehouse; +import mekhq.campaign.*; import mekhq.campaign.againstTheBot.AtBConfiguration; import mekhq.campaign.finances.Finances; import mekhq.campaign.force.Force; @@ -81,20 +44,8 @@ import mekhq.campaign.mission.Mission; import mekhq.campaign.mission.Scenario; import mekhq.campaign.mod.am.InjuryTypes; -import mekhq.campaign.parts.EnginePart; -import mekhq.campaign.parts.MekActuator; -import mekhq.campaign.parts.MekLocation; -import mekhq.campaign.parts.MissingEnginePart; -import mekhq.campaign.parts.MissingMekActuator; -import mekhq.campaign.parts.MissingPart; -import mekhq.campaign.parts.Part; -import mekhq.campaign.parts.equipment.AmmoBin; -import mekhq.campaign.parts.equipment.EquipmentPart; -import mekhq.campaign.parts.equipment.HeatSink; -import mekhq.campaign.parts.equipment.MASC; -import mekhq.campaign.parts.equipment.MissingAmmoBin; -import mekhq.campaign.parts.equipment.MissingEquipmentPart; -import mekhq.campaign.parts.equipment.MissingMASC; +import mekhq.campaign.parts.*; +import mekhq.campaign.parts.equipment.*; import mekhq.campaign.personnel.Person; import mekhq.campaign.personnel.PersonnelOptions; import mekhq.campaign.personnel.SkillType; @@ -112,9 +63,18 @@ import mekhq.campaign.universe.Planet.PlanetaryEvent; import mekhq.campaign.universe.PlanetarySystem.PlanetarySystemEvent; import mekhq.campaign.universe.Systems; +import mekhq.campaign.universe.fameAndInfamy.FameAndInfamyController; import mekhq.io.idReferenceClasses.PersonIdReference; import mekhq.module.atb.AtBEventProcessor; import mekhq.utilities.MHQXMLUtility; +import org.apache.commons.lang3.StringUtils; +import org.w3c.dom.*; + +import javax.xml.parsers.DocumentBuilder; +import java.io.*; +import java.time.LocalDate; +import java.util.*; +import java.util.Map.Entry; public class CampaignXmlParser { private static final MMLogger logger = MMLogger.create(CampaignXmlParser.class); @@ -298,6 +258,8 @@ public Campaign parse() throws CampaignXmlParseException, NullEntityException { processSpecialAbilityNodes(retVal, wn, version); } else if (xn.equalsIgnoreCase("storyArc")) { processStoryArcNodes(retVal, wn, version); + } else if (xn.equalsIgnoreCase("fameAndInfamy")) { + processFameAndInfamyNodes(retVal, wn); } else if (xn.equalsIgnoreCase("gameOptions")) { retVal.getGameOptions().fillFromXML(wn.getChildNodes()); } else if (xn.equalsIgnoreCase("kills")) { @@ -957,6 +919,11 @@ private static void processStoryArcNodes(Campaign retVal, Node wn, Version versi retVal.useStoryArc(storyArc, false); } + private static void processFameAndInfamyNodes(Campaign relativeValue, Node workingNode) { + logger.info("Loading Fame and Infamy Nodes from XML..."); + FameAndInfamyController.parseFromXML(workingNode.getChildNodes(), relativeValue); + } + private static void processSpecialAbilityNodes(Campaign retVal, Node wn, Version version) { logger.info("Loading Special Ability Nodes from XML..."); diff --git a/MekHQ/src/mekhq/campaign/mission/AtBContract.java b/MekHQ/src/mekhq/campaign/mission/AtBContract.java index 30403d0e2d..48c59c04ab 100644 --- a/MekHQ/src/mekhq/campaign/mission/AtBContract.java +++ b/MekHQ/src/mekhq/campaign/mission/AtBContract.java @@ -52,6 +52,7 @@ import mekhq.campaign.universe.Faction; import mekhq.campaign.universe.Factions; import mekhq.campaign.universe.RandomFactionGenerator; +import mekhq.campaign.universe.fameAndInfamy.BatchallFactions; import mekhq.utilities.MHQXMLUtility; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -1370,8 +1371,6 @@ public AtBContractRef(int id) { } } - - /** * Initiates a batchall. * Prompts the player with a message and options to accept or refuse the batchall. @@ -1383,130 +1382,51 @@ public boolean initiateBatchall(Campaign campaign) { // Set the title of the dialog String title = resources.getString("incomingTransmission.title"); - // Hold the portrait of the commander - // Generate the batchall statement and fetch the faction image - String batchallStatement; + String batchallStatement = BatchallFactions.getGreeting(campaign, enemyCode); final String PORTRAIT_DIRECTORY = "data/images/force/Pieces/Logos/Clan/"; final String PORTRAIT_FILE_TYPE = ".png"; ImageIcon portrait; switch (enemyCode) { - case "CBS" -> { - batchallStatement = resources.getString("batchallStatementCBS.text"); - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Blood Spirit" + PORTRAIT_FILE_TYPE); - } - case "CB" -> { - batchallStatement = resources.getString("batchallStatementCB.text"); - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Burrock" + PORTRAIT_FILE_TYPE); - } - case "CCC" -> { - batchallStatement = resources.getString("batchallStatementCCC.text"); - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Cloud Cobra" + PORTRAIT_FILE_TYPE); - } - case "CCO" -> { - batchallStatement = resources.getString("batchallStatementCCO.text"); - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Coyote" + PORTRAIT_FILE_TYPE); - } + case "CBS" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Blood Spirit" + PORTRAIT_FILE_TYPE); + case "CB" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Burrock" + PORTRAIT_FILE_TYPE); + case "CCC" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Cloud Cobra" + PORTRAIT_FILE_TYPE); + case "CCO" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Coyote" + PORTRAIT_FILE_TYPE); case "CDS" -> { if (campaign.getGameYear() >= 3100) { - batchallStatement = resources.getString("batchallStatementCDSSeaFox.text"); portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Sea Fox" + PORTRAIT_FILE_TYPE); } else { - batchallStatement = resources.getString("batchallStatementCDS.text"); portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Diamond Shark" + PORTRAIT_FILE_TYPE); } } - case "CFM" -> { - batchallStatement = resources.getString("batchallStatementCFM.text"); - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Fire Mandrill" + PORTRAIT_FILE_TYPE); - } + case "CFM" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Fire Mandrill" + PORTRAIT_FILE_TYPE); case "CGB" -> { if (campaign.getGameYear() >= 3060) { - batchallStatement = resources.getString("batchallStatementCGBDominion.text"); portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Ghost Bear Dominion" + PORTRAIT_FILE_TYPE); } else { - batchallStatement = resources.getString("batchallStatementCGB.text"); portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Ghost Bear" + PORTRAIT_FILE_TYPE); } } - case "CGS" -> { - batchallStatement = resources.getString("batchallStatementCGS.text"); - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Goliath Scorpion" + PORTRAIT_FILE_TYPE); - } - case "CHH" -> { - batchallStatement = resources.getString("batchallStatementCHH.text"); - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Hell's Horses" + PORTRAIT_FILE_TYPE); - } - case "CIH" -> { - batchallStatement = resources.getString("batchallStatementCIH.text"); - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Ice Hellion" + PORTRAIT_FILE_TYPE); - } - case "CJF" -> { - batchallStatement = resources.getString("batchallStatementCJF.text"); - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Jade Falcon" + PORTRAIT_FILE_TYPE); - } - case "CMG" -> { - batchallStatement = resources.getString("batchallStatementCMG.text"); - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Mongoose" + PORTRAIT_FILE_TYPE); - } - case "CNC" -> { - batchallStatement = resources.getString("batchallStatementCNC.text"); - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Nova Cat" + PORTRAIT_FILE_TYPE); - } - case "CSJ" -> { - batchallStatement = resources.getString("batchallStatementCSJ.text"); - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Smoke Jaguar" + PORTRAIT_FILE_TYPE); - } - case "CSR" -> { - batchallStatement = resources.getString("batchallStatementCSR.text"); - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Snow Raven" + PORTRAIT_FILE_TYPE); - } - case "CSA" -> { - batchallStatement = resources.getString("batchallStatementCSA.text"); - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Star Adder" + PORTRAIT_FILE_TYPE); - } - case "CSV" -> { - batchallStatement = resources.getString("batchallStatementCSV.text"); - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Steel Viper" + PORTRAIT_FILE_TYPE); - } - case "CSL" -> { - batchallStatement = resources.getString("batchallStatementCSL.text"); - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Stone Lion" + PORTRAIT_FILE_TYPE); - } - case "CWI" -> { - batchallStatement = resources.getString("batchallStatementCWI.text"); - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Widowmaker" + PORTRAIT_FILE_TYPE); - } - case "CW" -> { - batchallStatement = resources.getString("batchallStatementCW.text"); - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Wolf" + PORTRAIT_FILE_TYPE); - } - case "CWIE" -> { - batchallStatement = resources.getString("batchallStatementCWIE.text"); - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Wolf-in-Exile" + PORTRAIT_FILE_TYPE); - } - case "CWOV" -> { - batchallStatement = resources.getString("batchallStatementCWOV.text"); - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Wolverine" + PORTRAIT_FILE_TYPE); - } - case "RD" -> { - batchallStatement = resources.getString("batchallStatementRD.text"); - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Rasalhague Dominion" + PORTRAIT_FILE_TYPE); - } - case "RA" -> { - batchallStatement = resources.getString("batchallStatementRA.text"); - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Raven Alliance" + PORTRAIT_FILE_TYPE); - } - case "SOC" -> { - batchallStatement = resources.getString("batchallStatementSOC.text"); - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "The Society" + PORTRAIT_FILE_TYPE); - } - default -> { - batchallStatement = resources.getString("batchallStatementGeneric.text"); - portrait = new ImageIcon("data/images/force/Pieces/Logos/Inner Sphere/Star League.png"); - } + case "CGS" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Goliath Scorpion" + PORTRAIT_FILE_TYPE); + case "CHH" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Hell's Horses" + PORTRAIT_FILE_TYPE); + case "CIH" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Ice Hellion" + PORTRAIT_FILE_TYPE); + case "CJF" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Jade Falcon" + PORTRAIT_FILE_TYPE); + case "CMG" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Mongoose" + PORTRAIT_FILE_TYPE); + case "CNC" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Nova Cat" + PORTRAIT_FILE_TYPE); + case "CSJ" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Smoke Jaguar" + PORTRAIT_FILE_TYPE); + case "CSR" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Snow Raven" + PORTRAIT_FILE_TYPE); + case "CSA" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Star Adder" + PORTRAIT_FILE_TYPE); + case "CSV" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Steel Viper" + PORTRAIT_FILE_TYPE); + case "CSL" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Stone Lion" + PORTRAIT_FILE_TYPE); + case "CWI" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Widowmaker" + PORTRAIT_FILE_TYPE); + case "CW", "CWE" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Wolf" + PORTRAIT_FILE_TYPE); + case "CWIE" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Wolf-in-Exile" + PORTRAIT_FILE_TYPE); + case "CEI" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Scorpion Empire" + PORTRAIT_FILE_TYPE); + case "RD" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Rasalhague Dominion" + PORTRAIT_FILE_TYPE); + case "RA" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Raven Alliance" + PORTRAIT_FILE_TYPE); + default -> portrait = new ImageIcon("data/images/force/Pieces/Logos/Inner Sphere/Star League.png"); } // Determine the name of the commander based on faction @@ -1520,7 +1440,10 @@ public boolean initiateBatchall(Campaign campaign) { this.getName(), rank, commander, getEnemy().getFullName(campaign.getGameYear()), getSystemName(campaign.getLocalDate())); message = message + batchallStatement; - message = message + resources.getString("batchallCloser.text"); + + if (campaign.getFameAndInfamy().getFameForFaction(enemyCode) < 5) { + message = message + resources.getString("batchallCloser.text"); + } // Create a pane to display both the message and the commander's portrait JTextPane textPane = new JTextPane(); @@ -1533,6 +1456,23 @@ public boolean initiateBatchall(Campaign campaign) { panel.add(imageLabel, BorderLayout.CENTER); panel.add(textPane, BorderLayout.SOUTH); + if (campaign.getFameAndInfamy().getFameForFaction(enemyCode) > 4) { + noBatchallOfferedDialog(panel, title); + return false; + } else { + return batchallDialog(campaign, panel, title); + } + } + + /** + * Display a batchall dialog. + * + * @param campaign the current campaign + * @param panel the panel to display in the dialog + * @param title the title of the dialog + * @return {@code true} if the batchall is accepted, {@code false} otherwise + */ + private boolean batchallDialog(Campaign campaign, JPanel panel, String title) { // Prepare the options for the dialog Object[] options = { resources.getString("responseAccept.text"), @@ -1547,7 +1487,8 @@ public boolean initiateBatchall(Campaign campaign) { if (batchallDialog == JOptionPane.NO_OPTION) { // Display the dialog and capture the response int refusalConfirmation = JOptionPane.showOptionDialog(null, - resources.getString("refusalConfirmation.text"), + String.format(resources.getString("refusalConfirmation.text"), + getEnemy().getFullName(campaign.getGameYear())), resources.getString("responseRefuse.text"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); @@ -1556,6 +1497,7 @@ public boolean initiateBatchall(Campaign campaign) { if (refusalConfirmation == JOptionPane.NO_OPTION) { // Report refusal of the batchall campaign.addReport(resources.getString("refusalReport.text")); + campaign.getFameAndInfamy().updateFameForFaction(campaign, enemyCode, -1); return false; } else { return true; @@ -1564,4 +1506,20 @@ public boolean initiateBatchall(Campaign campaign) { return true; } } + + /** + * Displays a dialog with a message for when the faction has refused to offer a Batchall due to + * past player refusals. + * + * @param panel The panel to display in the dialog. + * @param title The title of the dialog. + */ + private void noBatchallOfferedDialog(JPanel panel, String title) { + Object[] options = { + resources.getString("responseBringItOn.text") + }; + + JOptionPane.showOptionDialog(null, panel, title, JOptionPane.DEFAULT_OPTION, + JOptionPane.QUESTION_MESSAGE, null, options, options[0]); + } } diff --git a/MekHQ/src/mekhq/campaign/universe/fameAndInfamy/BatchallFactions.java b/MekHQ/src/mekhq/campaign/universe/fameAndInfamy/BatchallFactions.java new file mode 100644 index 0000000000..aecb6c1c1d --- /dev/null +++ b/MekHQ/src/mekhq/campaign/universe/fameAndInfamy/BatchallFactions.java @@ -0,0 +1,107 @@ +package mekhq.campaign.universe.fameAndInfamy; + +import megamek.codeUtilities.MathUtility; +import megamek.common.Compute; +import mekhq.MekHQ; +import mekhq.campaign.Campaign; + +import java.util.List; +import java.util.ResourceBundle; + +/** + * Provides utility methods for working with clan factions within the Fame and Infamy module: + * determining whether they engage in batchalling, retrieving greetings and version strings for + * factions based on various conditions, such as the faction code, infamy level, and current year. + *

+ * The class is stateless and all methods are static, so it doesn't need to be instantiated. + * Therefore, all of its methods can be called directly on the class. + */ +public class BatchallFactions { + private static final ResourceBundle resources = ResourceBundle.getBundle( + "mekhq.resources.FameAndInfamy", + MekHQ.getMHQOptions().getLocale()); + + /** + * Determines whether a given faction engages in batchalling. + * + * @param factionCode The faction code to check eligibility for. Must be a non-null {@link String}. + * @return {@code true} if the faction code engages in batchalling, {@code false} otherwise. + */ + public static boolean usesBatchalls(String factionCode) { + if (factionCode == null) { + return false; + } + + List factionCodes = List.of("CBS", "CB", "CCC", "CCO", "CDS", "CFM", "CGB", + "CGS", "CHH", "CIH", "CJF", "CMG", "CNC", "CSJ", "CSR", "CSA", "CSV", "CSL", "CWI", "CW", + "CWE", "CWIE", "CEI", "RD", "RA", "CP", "AML", "CLAN"); + + return factionCodes.contains(factionCode); + } + + /** + * Retrieves the greeting for faction based on infamy. + * + * @param campaign The campaign for which to retrieve the greeting. + * @param factionCode The faction code for which to retrieve the greeting. + * @return The greeting message as a {@link String}. + */ + public static String getGreeting(Campaign campaign, String factionCode) { + final int infamy = MathUtility.clamp(campaign.getFameAndInfamy().getFameLevelForFaction(factionCode), + 0, 5); + + String version = getVersionString(campaign.getGameYear(), factionCode); + + String greeting; + int type = 0; + + if (infamy == 5) { + greeting = resources.getString("greetingCLANLevel5Type0.text"); + } else { + if (!factionCode.equals("CLAN")) { + type = Compute.randomInt(3); + } + String greetingReference = String.format(resources.getString("greetingFormatBatchall.text"), + factionCode, version, infamy, type); + greeting = resources.getString(greetingReference); + } + + return '"' + greeting + '"'; + } + + /** + * Retrieves the version string based on the given faction code and current year. + * + * @param currentYear The current year as an {@link Integer}. + * @param factionCode The faction code as a {@link String}. + * @return The version {@link String} for the given faction code and current year. + */ + private static String getVersionString(int currentYear, String factionCode) { + switch (factionCode) { + case "CDS" -> { + if (currentYear < 3100) { + return "Version 1"; + } else { + return "Version 2"; + } + } + case "CGB" -> { + if (currentYear < 3060) { + return "Version 1"; + } else { + return "Version 2"; + } + } + case "CEI" -> { + if (currentYear < 3141) { + return "Version 1"; + } else { + return "Version 2"; + } + } + default -> { + return ""; + } + } + } +} diff --git a/MekHQ/src/mekhq/campaign/universe/fameAndInfamy/FameAndInfamyController.java b/MekHQ/src/mekhq/campaign/universe/fameAndInfamy/FameAndInfamyController.java new file mode 100644 index 0000000000..c24aca0ab7 --- /dev/null +++ b/MekHQ/src/mekhq/campaign/universe/fameAndInfamy/FameAndInfamyController.java @@ -0,0 +1,181 @@ +package mekhq.campaign.universe.fameAndInfamy; + +import megamek.codeUtilities.MathUtility; +import megamek.logging.MMLogger; +import mekhq.MekHQ; +import mekhq.campaign.Campaign; +import mekhq.campaign.universe.Factions; +import mekhq.utilities.MHQXMLUtility; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.File; +import java.io.PrintWriter; +import java.util.*; + +import static java.lang.Math.round; + +public class FameAndInfamyController { + private Map trackingFactions; + + private static final ResourceBundle resources = ResourceBundle.getBundle( + "mekhq.resources.FameAndInfamy", + MekHQ.getMHQOptions().getLocale()); + + private final static MMLogger logger = MMLogger.create(FameAndInfamyController.class); + + /** + * Constructor for the {@link FameAndInfamyController} class. + * Initializes the {@code trackingFactions} map with the provided map of factions. + * If any factions are missing from the provided map, they will be added with a default fame + * value of 3.0 (or 0.0 if the provided faction uses Batchalls). + */ + public FameAndInfamyController() { + this.trackingFactions = new HashMap<>(); + for (String shortName : getAllFactionShortnames()) { + if (BatchallFactions.usesBatchalls(shortName)) { + this.trackingFactions.put(shortName, 0.0); + } else { + this.trackingFactions.put(shortName, 2.0); + } + } + } + + /** + * Retrieves the shortnames of all factions from the XML file. + * + * @return A list of faction shortnames. + */ + public List getAllFactionShortnames() { + List shortnames = new ArrayList<>(); + + try { + File inputFile = new File("data/universe/factions.xml"); + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + Document doc = dBuilder.parse(inputFile); + doc.getDocumentElement().normalize(); + + NodeList nList = doc.getElementsByTagName("shortname"); + + for (int temp = 0; temp < nList.getLength(); temp++) { + Node nNode = nList.item(temp); + + if (nNode.getNodeType() == Node.ELEMENT_NODE) { + Element element = (Element) nNode; + shortnames.add(element.getTextContent()); + } + } + } catch (Exception e) { + logger.error(String.format("FameAndInfamyController failed to parse contents of 'shortname'" + + " in 'data/universe/factions.xml'. Last successfully parsed Faction shortname: %s", + shortnames.get(shortnames.size() - 1))); + return shortnames; + } + + return shortnames; + } + + /** + * Retrieves the precise fame value for a given faction. + * Normally we don't care what the exact value is, + * so {@code getFameLevelForFaction} should be used, instead. + * + * @param factionCode the code of the faction + * @return the fame value for the faction + */ + public double getFameForFaction(String factionCode) { + return trackingFactions.get(factionCode); + } + + /** + * Retrieves the fame level for a faction. This is determined by normally rounding raw fame to + * the nearest {@link Integer} + * + * @param factionCode The code of the faction. + * @return The fame level of the faction. + */ + public int getFameLevelForFaction(String factionCode) { + return (int) round(trackingFactions.get(factionCode)); + } + + /** + * Sets the fame value for a specific faction. + * + * @param factionCode The code representing the faction. + * @param fame The fame value to be set for the faction. + */ + public void setFameForFaction(String factionCode, double fame) { + fame = MathUtility.clamp(fame, 0.0, 5.0); + + trackingFactions.put(factionCode, fame); + } + + /** + * Updates the fame of a faction by a specified adjustment. + * + * @param factionCode The code representing the faction. + * @param campaign The current campaign. + * @param adjustment The adjustment to be made to the faction's fame. + */ + public void updateFameForFaction(Campaign campaign, String factionCode, double adjustment) { + int originalFame = getFameLevelForFaction(factionCode); + + double currentFame = trackingFactions.get(factionCode); + adjustment = currentFame + adjustment; + adjustment = MathUtility.clamp(adjustment, 0.0, 5.0); + + trackingFactions.put(factionCode, adjustment); + + int newFame = getFameLevelForFaction(factionCode); + + if (originalFame != newFame) { + campaign.addReport(String.format(resources.getString("fameChangeReportInfamy.text"), + newFame, Factions.getInstance().getFaction(factionCode).getFullName(campaign.getGameYear()))); + } + } + + /** + * Writes the fame and infamy data to an XML file using the provided {@link PrintWriter} and indent level. + * + * @param printWriter The {@link PrintWriter} used to write to the XML file. + * @param indent The indent level for formatting the XML file. + */ + public void writeToXml(PrintWriter printWriter, int indent) { + MHQXMLUtility.writeSimpleXMLOpenTag(printWriter, indent++, "fameAndInfamy"); + for (String trackedFaction : trackingFactions.keySet()) { + double factionFame = trackingFactions.get(trackedFaction); + boolean shouldWrite = factionFame != (BatchallFactions.usesBatchalls(trackedFaction) ? 0 : 2); + + if (shouldWrite) { + MHQXMLUtility.writeSimpleXMLTag(printWriter, indent, trackedFaction, getFameForFaction(trackedFaction)); + } + } + MHQXMLUtility.writeSimpleXMLCloseTag(printWriter, --indent, "fameAndInfamy"); + } + + /** + * Parses the XML {@link NodeList} and updates the fame values for factions in a {@link Campaign}. + * + * @param nodeList The XML {@link NodeList} containing the faction fame values. + * @param campaign The {@link Campaign} object to update with the parsed fame values. + */ + public static void parseFromXML(final NodeList nodeList, Campaign campaign) { + FameAndInfamyController fameAndInfamy = campaign.getFameAndInfamy(); + try { + for (int x = 0; x < nodeList.getLength(); x++) { + final Node workingNode = nodeList.item(x); + if (workingNode.getNodeType() == Node.ELEMENT_NODE) { + double value = Double.parseDouble(workingNode.getTextContent()); + fameAndInfamy.setFameForFaction(workingNode.getNodeName(), value); + } + } + } catch (Exception e) { + logger.error(e); + } + } +} From 33eaf307e6b80fea8e35990ef6048aae79522eb7 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Tue, 24 Sep 2024 19:00:57 -0500 Subject: [PATCH 20/21] Refactored Batchall handling with detailed faction-specific logic Replaced the version string retrieval method with detailed faction-specific handling using a switch statement. Improved the batchall dialog by adding tooltips and refactoring the refuse confirmation dialog for better user interaction. Switch statement also added for better management of faction icons. --- .../mekhq/resources/AtBContract.properties | 2 + .../mekhq/resources/FameAndInfamy.properties | 375 +++++++++--------- .../mekhq/campaign/mission/AtBContract.java | 224 +++++++---- .../fameAndInfamy/BatchallFactions.java | 65 ++- .../FameAndInfamyController.java | 37 ++ 5 files changed, 397 insertions(+), 306 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/AtBContract.properties b/MekHQ/resources/mekhq/resources/AtBContract.properties index 79eb4c16ca..db292f06ba 100644 --- a/MekHQ/resources/mekhq/resources/AtBContract.properties +++ b/MekHQ/resources/mekhq/resources/AtBContract.properties @@ -36,7 +36,9 @@ batchallStatementGeneric.text="We are The Clans. This world is ours by right. Id batchallCloser.text=

Do you accept the Batchall? responseAccept.text=Accept Batchall +responseAccept.tooltip=The scenarios for this contract will be balanced to roughly match your forces. responseRefuse.text=Refuse Batchall +responseRefuse.tooltip=You will face the full strength of the Clans during this contract, and they will regard you less favorably in future dealings. responseBringItOn.text=Bring It On refusalConfirmation.text=Are you sure? %s will not forget this betrayal. diff --git a/MekHQ/resources/mekhq/resources/FameAndInfamy.properties b/MekHQ/resources/mekhq/resources/FameAndInfamy.properties index a27697b081..2257a94cd2 100644 --- a/MekHQ/resources/mekhq/resources/FameAndInfamy.properties +++ b/MekHQ/resources/mekhq/resources/FameAndInfamy.properties @@ -72,37 +72,37 @@ greetingCCOLevel4Type1.text=You are nothing but a shadow in our path. Prepare to greetingCCOLevel4Type2.text=We hunt without mercy. Your defenses will crumble before us. -greetingCDSVersion1Level0Type0.text=Your resolve impresses us. Show your defenses, and let us challenge each other. -greetingCDSVersion1Level0Type1.text=You swim in dangerous waters with courage. Let us see who emerges victorious. -greetingCDSVersion1Level0Type2.text=Your bravery shines like a beacon. Let us create a Trial to be remembered. -greetingCDSVersion1Level1Type0.text=We acknowledge your resolve. Reveal your defenses, and let us cross the void together. -greetingCDSVersion1Level1Type1.text=You have shown courage against the tide. Let us see who truly commands the waters. -greetingCDSVersion1Level1Type2.text=We respect your tenacity. Let this Trial be as deep as the ocean we traverse. -greetingCDSVersion1Level2Type0.text=We continue our hunt. Defend if you must. -greetingCDSVersion1Level2Type1.text=You are but another ripple in the ocean. We will not be deterred. -greetingCDSVersion1Level2Type2.text=We take what we desire. Your defense is unimportant. -greetingCDSVersion1Level3Type0.text=We hardly notice you. What little can you muster? -greetingCDSVersion1Level3Type1.text=You are but a minnow in our ocean. Prepare to be devoured. -greetingCDSVersion1Level3Type2.text=Your defenses will mean nothing. We will consume all. -greetingCDSVersion1Level4Type0.text=We bare our teeth. What worthless forces will you offer? -greetingCDSVersion1Level4Type1.text=Your defense is a minor inconvenience. We will take what is ours. -greetingCDSVersion1Level4Type2.text=You are but fish in the sea. Prepare to be devoured. - -greetingCDSVersion2Level0Type0.text=PLACEHOLDER -greetingCDSVersion2Level0Type1.text=PLACEHOLDER -greetingCDSVersion2Level0Type2.text=PLACEHOLDER -greetingCDSVersion2Level1Type0.text=PLACEHOLDER -greetingCDSVersion2Level1Type1.text=PLACEHOLDER -greetingCDSVersion2Level1Type2.text=PLACEHOLDER -greetingCDSVersion2Level2Type0.text=PLACEHOLDER -greetingCDSVersion2Level2Type1.text=PLACEHOLDER -greetingCDSVersion2Level2Type2.text=PLACEHOLDER -greetingCDSVersion2Level3Type0.text=PLACEHOLDER -greetingCDSVersion2Level3Type1.text=PLACEHOLDER -greetingCDSVersion2Level3Type2.text=PLACEHOLDER -greetingCDSVersion2Level4Type0.text=PLACEHOLDER -greetingCDSVersion2Level4Type1.text=PLACEHOLDER -greetingCDSVersion2Level4Type2.text=PLACEHOLDER +greetingCDSVersion1Level0Type0.text=You face us with the spirit of a predator. Let this battle be one that echoes through the tides. +greetingCDSVersion1Level0Type1.text=Your strength shines like the hard ridges of our own form. Together, let us carve a path worthy of remembrance. +greetingCDSVersion1Level0Type2.text=You swim fearlessly in dangerous waters. Let us fight as equals, and create a Trial worthy of the deep. +greetingCDSVersion1Level1Type0.text=You stand with courage. Show us your strength, and we will test it against our own. +greetingCDSVersion1Level1Type1.text=You move with the flow, yet stand firm. Let us see if you can withstand the weight of our jaws. +greetingCDSVersion1Level1Type2.text=We acknowledge a worthy challenge. May this Trial prove who truly commands these waters. +greetingCDSVersion1Level2Type0.text=Resist if you wish, but it changes nothing; we will have what we desire. +greetingCDSVersion1Level2Type1.text=You are but another creature in our waters. Stand, flee, or fall - it matters little to us. +greetingCDSVersion1Level2Type2.text=You are already in our grasp. Move, struggle, or yield; we will take what belongs to us. +greetingCDSVersion1Level3Type0.text=You thrash in the waters, unaware that we have already begun our feast. Your end is inevitable. +greetingCDSVersion1Level3Type1.text=We strike fast, and you will be left in pieces. +greetingCDSVersion1Level3Type2.text=You think you stand a chance? We do not wait; we seize what we want and leave nothing behind. +greetingCDSVersion1Level4Type0.text=You are but prey. We will wait until you bleed out before claiming what is ours. +greetingCDSVersion1Level4Type1.text=Your resistance is feeble. Like flesh against diamond, you will break, and we shall take what is ours. +greetingCDSVersion1Level4Type2.text=You cannot escape the inevitable. The waters belong to us now; resist, and we will cut you down. + +greetingCDSVersion2Level0Type0.text=You rise with the power of a thousand waves. We bow, then challenge you - let this be a battle worthy of our ancestors. +greetingCDSVersion2Level0Type1.text=Your strength flows like the ocean's heartbeat. Together, we will create a story that dances across the waves. +greetingCDSVersion2Level0Type2.text=The Sea Fox bows deeply, in awe of your spirit. Let this Trial be a dance that lives on in the waters forever. +greetingCDSVersion2Level1Type0.text=You face us with the strength of warriors. We bow, then raise our voices - let our Trial be one to remember. +greetingCDSVersion2Level1Type1.text=You stand firm as the tide rises. We honor you with this challenge; may you match the power of the waves. +greetingCDSVersion2Level1Type2.text=The Sea Fox respects your courage. Let our cries and steps echo together as we test our might. +greetingCDSVersion2Level2Type0.text=The Sea Fox moves as the ocean commands. Your resistance is but a ripple; we will flow over you. +greetingCDSVersion2Level2Type1.text=We chant and call, but it is not for you - it is for the journey. Stand or fall, the waves endure. +greetingCDSVersion2Level2Type2.text=The Sea Fox does not stop. You are but a shadow on the water, soon to be forgotten. +greetingCDSVersion2Level3Type0.text=You shiver like a leaf in the wind. The Sea Fox steps forward, and soon, your fear will be realized. +greetingCDSVersion2Level3Type1.text=Your efforts are weak; you tremble before us. We shall break you, as the ocean smashes rock. +greetingCDSVersion2Level3Type2.text=We dance on the water's edge, and you falter. Prepare to meet the force of the storm. +greetingCDSVersion2Level4Type0.text=The Sea Fox bows but knows that this is no greeting - it is a warning. Your fate is sealed, and the sea will consume you. +greetingCDSVersion2Level4Type1.text=You are but driftwood, tossed by the waves. We stomp and call out, preparing to tear you apart. +greetingCDSVersion2Level4Type2.text=The tide rises, the Sea Fox snarls. There is no escape - you will be swallowed whole. greetingCFMLevel0Type0.text=We honor your valor. Show us your defenses, and let us see who burns the brightest. @@ -138,55 +138,39 @@ greetingCGBVersion1Level4Type0.text=We dismiss your efforts. Reveal your forces, greetingCGBVersion1Level4Type1.text=You are nothing but prey, and we are the hunters. Identify yourselves. greetingCGBVersion1Level4Type2.text=Your resistance is beneath notice. Prepare to be obliterated. -greetingCGBVersion2Level0Type0.text=PLACEHOLDER -greetingCGBVersion2Level0Type1.text=PLACEHOLDER -greetingCGBVersion2Level0Type2.text=PLACEHOLDER -greetingCGBVersion2Level1Type0.text=PLACEHOLDER -greetingCGBVersion2Level1Type1.text=PLACEHOLDER -greetingCGBVersion2Level1Type2.text=PLACEHOLDER -greetingCGBVersion2Level2Type0.text=PLACEHOLDER -greetingCGBVersion2Level2Type1.text=PLACEHOLDER -greetingCGBVersion2Level2Type2.text=PLACEHOLDER -greetingCGBVersion2Level3Type0.text=PLACEHOLDER -greetingCGBVersion2Level3Type1.text=PLACEHOLDER -greetingCGBVersion2Level3Type2.text=PLACEHOLDER -greetingCGBVersion2Level4Type0.text=PLACEHOLDER -greetingCGBVersion2Level4Type1.text=PLACEHOLDER -greetingCGBVersion2Level4Type2.text=PLACEHOLDER - - -greetingCGSLevel0Type0.text=We admire your tenacity. Present your defenses, and let us face each other with honor. -greetingCGSLevel0Type1.text=You have shown great strength. May this Trial be one that is recounted in tales. -greetingCGSLevel0Type2.text=Your resolve shines brightly. Let us make this encounter a legendary one. -greetingCGSLevel1Type0.text=We recognize your bravery. Present your defenses, and face us with honor. -greetingCGSLevel1Type1.text=You have chosen to stand. Let us see if you are worthy of our sting. -greetingCGSLevel1Type2.text=We respect your resolve. Let us engage in a Trial worthy of song. -greetingCGSLevel2Type0.text=We move to strike. Stand or fall; it makes no difference. -greetingCGSLevel2Type1.text=You are but another hindrance. We will claim what we seek. -greetingCGSLevel2Type2.text=Your defenses matter little. We advance regardless. -greetingCGSLevel3Type0.text=We see no challenge. Present your futile defenses. -greetingCGSLevel3Type1.text=You are beneath us, unworthy of our sting. Stand if you must. -greetingCGSLevel3Type2.text=We expected more. Prepare to be crushed. -greetingCGSLevel4Type0.text=We strike. Your defenses will be crushed underfoot. -greetingCGSLevel4Type1.text=You are but insects to us. We claim what is ours. -greetingCGSLevel4Type2.text=There is no place for the weak in our path. Prepare to be stung. - - -greetingCHHLevel0Type0.text=We respect your strength. Stand before us proudly, and let us charge into battle together. -greetingCHHLevel0Type1.text=You have proven yourself worthy. Let us create a Trial that will be told to future generations. -greetingCHHLevel0Type2.text=Your courage matches our own. Let this be a contest of champions. -greetingCHHLevel1Type0.text=We admire your courage. Stand before us, and show your strength against our charge. -greetingCHHLevel1Type1.text=You have proven worthy to face us. Let us clash and see who emerges victorious. -greetingCHHLevel1Type2.text=We honor your bravery. Let our hooves and your defenses meet in glorious battle. -greetingCHHLevel2Type0.text=We charge onward. Whether you stand or not is irrelevant. -greetingCHHLevel2Type1.text=Your efforts do not concern us. We ride forward unimpeded. -greetingCHHLevel2Type2.text=You are but dust beneath our hooves. We take what is ours. -greetingCHHLevel3Type0.text=We find your stand laughable. Who dares face our charge? -greetingCHHLevel3Type1.text=Your defenses are nothing but a hindrance. We will trample you. -greetingCHHLevel3Type2.text=You offer so little. It will be over before you know it. -greetingCHHLevel4Type0.text=We trample over the weak. Who dares claim they can stop us? -greetingCHHLevel4Type1.text=You are but dust beneath our hooves. We will ride through you. -greetingCHHLevel4Type2.text=You are no obstacle, just a fleeting moment before the stampede. + +greetingCGSLevel0Type0.text=You seek it as if it were your own, and we admire that strength. Together, let us see who is worthy of this prize. +greetingCGSLevel0Type1.text=You stand as a true warrior before what we seek. Let this battle be worthy of the artifact's legacy. +greetingCGSLevel0Type2.text=Your defense is noble, but the Goliath Scorpion's desire is relentless. Let us determine who the rightful keeper is. +greetingCGSLevel1Type0.text=You have admirable strength. Prove your worth, and let this Trial determine who holds the greater claim. +greetingCGSLevel1Type1.text=You stand between us and our heritage. We respect your courage but know this: we shall not be denied. +greetingCGSLevel1Type2.text=This artifact is ours to reclaim. Face us with honor, and let this battle determine its true guardian. +greetingCGSLevel2Type0.text=The artifact you seek will be ours. Your presence is merely a delay, not a deterrent. +greetingCGSLevel2Type1.text=We desire something of value, but your life does not concern us. Stand aside, or be removed. +greetingCGSLevel2Type2.text=The prize we seek lies beyond you. Move, or be swept away. +greetingCGSLevel3Type0.text=You think you can protect what the Goliath Scorpion desires? We will carve you from its side. +greetingCGSLevel3Type1.text=Your grip is weak, and your resolve weaker. Prepare to be cast aside as we claim what belongs to us. +greetingCGSLevel3Type2.text=You attempt to shield the past from us, but you will soon learn that nothing escapes our reach. +greetingCGSLevel4Type0.text=You desire what belongs to us. Step aside, or feel the venom of the Goliath Scorpion as we take back our legacy. +greetingCGSLevel4Type1.text=Your hands are unworthy to hold such relics. We will strike you down and reclaim what is ours by right. +greetingCGSLevel4Type2.text=You stand before what we seek. Know that we will stop at nothing to possess it. + + +greetingCHHLevel0Type0.text=We see in you the fire that drives warriors forward. May this Trial be one that the stars themselves shall remember. +greetingCHHLevel0Type1.text=Your strength matches our own. Let us clash and create a story that will be remembered by the wind and the earth. +greetingCHHLevel0Type2.text=You stand as one who understands the spirit of the wild. Together, let us create a battle that echoes across the ages. +greetingCHHLevel1Type0.text=We honor your courage. The Hell's Horses will meet you in battle, and let the strongest prevail. +greetingCHHLevel1Type1.text=You stand firm, as few have before. Let our clash be a testament to the strength of man and beast. +greetingCHHLevel1Type2.text=You face us with the heart of a warrior. Let this battle be worthy of the blood and fury that drives us. +greetingCHHLevel2Type0.text=Our path is clear, and you are but an echo of defiance. We shall trample it into the dust. +greetingCHHLevel2Type1.text=You are but a shadow in the wind. We ride to victory, and you will be left behind. +greetingCHHLevel2Type2.text=We charge forward, and your resistance means nothing. Stand or fall; it matters not to the Hell's Horses." +greetingCHHLevel3Type0.text=Your forces are weak, like grass before the hooves of the horse. We will ride over you without pause. +greetingCHHLevel3Type1.text=You attempt to tame what cannot be tamed. We will show you the wrath of the untamed herd. +greetingCHHLevel3Type2.text=You are but a stone in our path. The Hell's Horses will shatter you as we have countless others. +greetingCHHLevel4Type0.text=You think yourself strong, but you will be broken like all who stand before our stampede. +greetingCHHLevel4Type1.text=You cannot match our fury. We will trample you underfoot, as the horse crushes bones in its charge. +greetingCHHLevel4Type2.text=You are merely prey. We shall ride you down and leave nothing but dust. greetingCIHLevel0Type0.text=We are impressed by your courage. Reveal your forces, and let us test our mettle. @@ -197,7 +181,7 @@ greetingCIHLevel1Type1.text=You have shown resolve. Let us see if you can withst greetingCIHLevel1Type2.text=We respect your stand. May this Trial be as fierce as the Ice Hellion's bite. greetingCIHLevel2Type0.text=We descend. Your resistance is of no consequence. greetingCIHLevel2Type1.text=We strike regardless of your presence. Stand or move aside. -greetingCIHLevel2Type2.text=You mean little to us. We take this prize without delay. +greetingCIHLevel2Type2.text=You mean little to us. We will take this prize without delay. greetingCIHLevel3Type0.text=We regard you with indifference. Stand if you wish, but you will fall. greetingCIHLevel3Type1.text=You are but a whisper in the wind. We will sweep you away. greetingCIHLevel3Type2.text=Your resistance is but ice beneath our claws. It will shatter. @@ -240,21 +224,21 @@ greetingCMGLevel4Type1.text=Your defenses mean nothing. Prepare to be hunted. greetingCMGLevel4Type2.text=You are but prey in our path. Identify yourselves or be swept aside. -greetingCNCLevel0Type0.text=We honor your spirit. Declare your forces, and let us meet our fate together. -greetingCNCLevel0Type1.text=You have faced us without fear. Let this battle be guided by the stars themselves. -greetingCNCLevel0Type2.text=We respect your strength. Together, let us fulfill the vision of this Trial. -greetingCNCLevel1Type0.text=We see strength in you. Declare your forces, and let destiny guide our battle. -greetingCNCLevel1Type1.text=You face us with courage. May this Trial reveal the strength of both our spirits. -greetingCNCLevel1Type2.text=We respect your resolve. Let us fight, as the stars have foreseen. -greetingCNCLevel2Type0.text=We have seen this moment. Your stand is inconsequential. -greetingCNCLevel2Type1.text=We advance as foreseen. Whether you resist changes nothing. -greetingCNCLevel2Type2.text=Your efforts are insignificant. We take what is destined. -greetingCNCLevel3Type0.text=We see your end is near. What futile defense do you offer? -greetingCNCLevel3Type1.text=You stand against destiny. We will put you in your place. -greetingCNCLevel3Type2.text=You will be swept aside as the vision decreed. -greetingCNCLevel4Type0.text=We see only your defeat. Prepare to be extinguished. -greetingCNCLevel4Type1.text=Your resistance is but an illusion. You cannot escape your fate. -greetingCNCLevel4Type2.text=We have foreseen your failure. Stand if you must, but it changes nothing. +greetingCNCLevel0Type0.text=You burn with the light of a thousand suns. Together, let us create a battle that shines across the ages. +greetingCNCLevel0Type1.text=Your spirit dances like fire in the darkness. Let us meet and write our story in the stars. +greetingCNCLevel0Type2.text=We honor your brilliance. May our Trial be a blaze that the universe itself will remember. +greetingCNCLevel1Type0.text=You face us with the spirit of one who understands the flame. Let our Trial be one that lights the heavens. +greetingCNCLevel1Type1.text=You have chosen to stand against the Nova Cat. We honor your courage, and may the fire of battle burn brightly. +greetingCNCLevel1Type2.text=We see your strength, and it mirrors our own. Let this clash be worthy of the stars. +greetingCNCLevel2Type0.text=Our path is clear, and you are but an ember in the night. We shall step over you. +greetingCNCLevel2Type1.text=The Nova Cat walks through the darkness, unbothered by your flickering resistance. +greetingCNCLevel2Type2.text=You are but a momentary shadow. We shall pass by, leaving only scorched earth in our wake. +greetingCNCLevel3Type0.text=You hide from our light, but your darkness is shallow. We will burn through and reveal your weakness. +greetingCNCLevel3Type1.text=The Nova Cat wastes no time with prey that cannot see. You are already lost in the flames. +greetingCNCLevel3Type2.text=You stand before us, blind to your fate. We shall sweep you aside like ash in the wind. +greetingCNCLevel4Type0.text=The Nova Cat's eyes burn bright. You are but a shadow, soon to be scorched from existence. +greetingCNCLevel4Type1.text=We see through your defenses, and they mean nothing. The flames of our vision will consume you. +greetingCNCLevel4Type2.text=In the darkness, you cower, but the Nova Cat's gaze pierces all. Prepare to be undone. greetingCSJLevel0Type0.text=We commend your courage. Identify your forces, and let us battle with all our might. @@ -274,21 +258,21 @@ greetingCSJLevel4Type1.text=Your defenses will crumble before us. Prepare to be greetingCSJLevel4Type2.text=We do not tolerate weakness. Stand and be destroyed. -greetingCSRLevel0Type0.text=We admire your bravery. Declare your defenses, and let us soar together in this Trial. -greetingCSRLevel0Type1.text=You face us with courage. May this battle be worthy of the highest peaks. -greetingCSRLevel0Type2.text=Your strength is commendable. Let us create a story that will be told for ages. -greetingCSRLevel1Type0.text=We honor your stand. Declare your forces, and let us test our wings against one another. -greetingCSRLevel1Type1.text=You have shown courage beneath our shadow. May this Trial lift you to the skies. -greetingCSRLevel1Type2.text=We respect your resolve. Let us meet in battle, where the wind favors the worthy. -greetingCSRLevel2Type0.text=Your presence is of no concern to us. -greetingCSRLevel2Type1.text=We glide past your defenses. Stand if you wish; it changes nothing. -greetingCSRLevel2Type2.text=You are but a shadow below. We take what we came for. -greetingCSRLevel3Type0.text=We have no patience for you. Show yourselves, or be scattered. -greetingCSRLevel3Type1.text=You are but a shadow on the snow. We will brush you aside. -greetingCSRLevel3Type2.text=You matter little to us. We will take what we came for. -greetingCSRLevel4Type0.text=We look down upon you. You are unworthy of our notice. -greetingCSRLevel4Type1.text=Your resistance is but a flurry in the storm. Prepare to be blown away. -greetingCSRLevel4Type2.text=We do not wait. Stand or be cast aside. +greetingCSRLevel0Type0.text=You face us with the spirit of a true warrior. Let this Trial be remembered in the songs of our people. +greetingCSRLevel0Type1.text=You stand proud, and the Snow Raven bows in admiration. Together, we shall create a story for the ages. +greetingCSRLevel0Type2.text=Your strength echoes across the sky. Let this be a Trial worthy of the ancestors who watch over us. +greetingCSRLevel1Type0.text=You stand with purpose, and the Snow Raven acknowledges this. Let us see if you are worthy of the feast. +greetingCSRLevel1Type1.text=You have proven to be more than mere scraps. We shall meet you as equals in this Trial. +greetingCSRLevel1Type2.text=The Snow Raven respects your strength. May our clash be one of honor and purpose. +greetingCSRLevel2Type0.text=The Snow Raven does not concern itself with your resistance. We will take what you leave behind. +greetingCSRLevel2Type1.text=You are but a moment in the wind. We shall continue our path, unbothered by your presence. +greetingCSRLevel2Type2.text=Our gaze has turned to you, but it matters little. The outcome is already decided. +greetingCSRLevel3Type0.text=You offer little but bones. We will pick them clean as we always do. +greetingCSRLevel3Type1.text=You stand before the Snow Raven, yet you have nothing to offer but your remains. +greetingCSRLevel3Type2.text=You are but a small morsel, and we will make use of even that. Prepare to be taken. +greetingCSRLevel4Type0.text=The Snow Raven circles, seeing your weakness. You are but scraps left behind, soon to be taken. +greetingCSRLevel4Type1.text=You waste what you have, but we waste nothing. We shall claim what you leave behind. +greetingCSRLevel4Type2.text=Your presence is like carrion; the Snow Raven will strip you to the bone. greetingCSALevel0Type0.text=We respect your tenacity. Reveal your defenses, and let us face each other with honor. @@ -376,20 +360,6 @@ greetingCWLevel4Type1.text=You are mere sheep before us. Prepare to be devoured. greetingCWLevel4Type2.text=Your resistance is pitiful. We will tear through you. -greetingCWELevel1Type0.text=PLACEHOLDER -greetingCWELevel1Type1.text=PLACEHOLDER -greetingCWELevel1Type2.text=PLACEHOLDER -greetingCWELevel2Type0.text=PLACEHOLDER -greetingCWELevel2Type1.text=PLACEHOLDER -greetingCWELevel2Type2.text=PLACEHOLDER -greetingCWELevel3Type0.text=PLACEHOLDER -greetingCWELevel3Type1.text=PLACEHOLDER -greetingCWELevel3Type2.text=PLACEHOLDER -greetingCWELevel4Type0.text=PLACEHOLDER -greetingCWELevel4Type1.text=PLACEHOLDER -greetingCWELevel4Type2.text=PLACEHOLDER - - greetingCWIELevel0Type0.text=We are impressed by your resolve. Reveal your defenses, and let us reclaim our honor together. greetingCWIELevel0Type1.text=You stand before us with courage. Let this Trial be one to remember. greetingCWIELevel0Type2.text=We admire your bravery. Let us engage in battle as warriors of old. @@ -407,76 +377,87 @@ greetingCWIELevel4Type1.text=Your defense means nothing. Stand or be swept away. greetingCWIELevel4Type2.text=You cannot stop us. Prepare to be eradicated. -greetingCEIVersion1Level0Type0.text=PLACEHOLDER -greetingCEIVersion1Level0Type1.text=PLACEHOLDER -greetingCEIVersion1Level0Type2.text=PLACEHOLDER -greetingCEIVersion1Level1Type0.text=PLACEHOLDER -greetingCEIVersion1Level1Type1.text=PLACEHOLDER -greetingCEIVersion1Level1Type2.text=PLACEHOLDER -greetingCEIVersion1Level2Type0.text=PLACEHOLDER -greetingCEIVersion1Level2Type1.text=PLACEHOLDER -greetingCEIVersion1Level2Type2.text=PLACEHOLDER -greetingCEIVersion1Level3Type0.text=PLACEHOLDER -greetingCEIVersion1Level3Type1.text=PLACEHOLDER -greetingCEIVersion1Level3Type2.text=PLACEHOLDER -greetingCEIVersion1Level4Type0.text=PLACEHOLDER -greetingCEIVersion1Level4Type1.text=PLACEHOLDER -greetingCEIVersion1Level4Type2.text=PLACEHOLDER - -greetingCEIVersion2Level0Type0.text=PLACEHOLDER -greetingCEIVersion2Level0Type1.text=PLACEHOLDER -greetingCEIVersion2Level0Type2.text=PLACEHOLDER -greetingCEIVersion2Level1Type0.text=PLACEHOLDER -greetingCEIVersion2Level1Type1.text=PLACEHOLDER -greetingCEIVersion2Level1Type2.text=PLACEHOLDER -greetingCEIVersion2Level2Type0.text=PLACEHOLDER -greetingCEIVersion2Level2Type1.text=PLACEHOLDER -greetingCEIVersion2Level2Type2.text=PLACEHOLDER -greetingCEIVersion2Level3Type0.text=PLACEHOLDER -greetingCEIVersion2Level3Type1.text=PLACEHOLDER -greetingCEIVersion2Level3Type2.text=PLACEHOLDER -greetingCEIVersion2Level4Type0.text=PLACEHOLDER -greetingCEIVersion2Level4Type1.text=PLACEHOLDER -greetingCEIVersion2Level4Type2.text=PLACEHOLDER - - -greetingRDLevel0Type0.text=PLACEHOLDER -greetingRDLevel0Type1.text=PLACEHOLDER -greetingRDLevel0Type2.text=PLACEHOLDER -greetingRDLevel1Type0.text=PLACEHOLDER -greetingRDLevel1Type1.text=PLACEHOLDER -greetingRDLevel1Type2.text=PLACEHOLDER -greetingRDLevel2Type0.text=PLACEHOLDER -greetingRDLevel2Type1.text=PLACEHOLDER -greetingRDLevel2Type2.text=PLACEHOLDER -greetingRDLevel3Type0.text=PLACEHOLDER -greetingRDLevel3Type1.text=PLACEHOLDER -greetingRDLevel3Type2.text=PLACEHOLDER -greetingRDLevel4Type0.text=PLACEHOLDER -greetingRDLevel4Type1.text=PLACEHOLDER -greetingRDLevel4Type2.text=PLACEHOLDER - - -greetingRALevel0Type0.text=PLACEHOLDER -greetingRALevel0Type1.text=PLACEHOLDER -greetingRALevel0Type2.text=PLACEHOLDER -greetingRALevel1Type0.text=PLACEHOLDER -greetingRALevel1Type1.text=PLACEHOLDER -greetingRALevel1Type2.text=PLACEHOLDER -greetingRALevel2Type0.text=PLACEHOLDER -greetingRALevel2Type1.text=PLACEHOLDER -greetingRALevel2Type2.text=PLACEHOLDER -greetingRALevel3Type0.text=PLACEHOLDER -greetingRALevel3Type1.text=PLACEHOLDER -greetingRALevel3Type2.text=PLACEHOLDER -greetingRALevel4Type0.text=PLACEHOLDER -greetingRALevel4Type1.text=PLACEHOLDER -greetingRALevel4Type2.text=PLACEHOLDER - - -greetingCLANLevel0Type0.text=Identify yourselves, and let us share this honorable battle. -greetingCLANLevel1Type0.text=Identify yourselves, and let us meet in honorable battle. -greetingCLANLevel2Type0.text=Stand in our way or not; it changes nothing. -greetingCLANLevel3Type0.text=Identify yourselves, or be erased. -greetingCLANLevel4Type0.text=Identify the forces that dare oppose us, or face immediate destruction. +greetingCEIVersion1Level0Type0.text=You protect the past with honor, and we admire your spirit. Let us meet in a Trial worthy of the ancestors who watch over us. +greetingCEIVersion1Level0Type1.text=Your resolve shines brightly, like the stars above. Together, let us create a battle that will be remembered for generations. +greetingCEIVersion1Level0Type2.text=We honor your strength and dedication. May this Trial be one that the universe itself will speak of. +greetingCEIVersion1Level1Type0.text=You face us with the strength of a true warrior. The Scorpion Empire honors your courage; let this Trial determine who is worthy. +greetingCEIVersion1Level1Type1.text=You stand against us, and we recognize your resolve. Let this battle be one that echoes in the annals of history. +greetingCEIVersion1Level1Type2.text=We see in you a defender of what is sacred. Let this Trial decide who shall bear the legacy of the past. +greetingCEIVersion1Level2Type0.text=Your presence is but a moment in our journey. We shall take what is needed, and you will be left in the shadows. +greetingCEIVersion1Level2Type1.text=The Scorpion Empire does not stop for those who stand in our way. We will advance, and you will be swept aside. +greetingCEIVersion1Level2Type2.text=We have seen countless obstacles, and you are just another. We will take what we seek, and you will be forgotten. +greetingCEIVersion1Level3Type0.text=You hold onto relics you cannot comprehend. We, the Scorpion Empire, will take what you have squandered. +greetingCEIVersion1Level3Type1.text=You are but a fragment of the past, unworthy of what you protect. We will strip you of your illusions and take what is ours. +greetingCEIVersion1Level3Type2.text=You attempt to shield what belongs to us, but we have outlasted greater threats than you. The Empire will reclaim what you have failed to claim. +greetingCEIVersion1Level4Type0.text=You stand against an Empire that was forged in unity and strength. We will take what you desire, as we have taken all that we desire. +greetingCEIVersion1Level4Type1.text=You face the Scorpion Empire, and yet you fail to see your insignificance. We shall claim what belongs to us, as we have always done. +greetingCEIVersion1Level4Type2.text=Your defiance means nothing to us. We shall break you as we have broken all who stood before us. + + +greetingRDLevel0Type0.text=You stand with the strength of the bear, as we stand with the line of our people. May this battle be remembered by the brave who live forever. +greetingRDLevel0Type1.text=Your courage shines bright, like the Northern lights. Let us fight, knowing our fathers and mothers watch us from the halls of Valhalla. +greetingRDLevel0Type2.text=You are a warrior worthy of the Dominion's respect. Together, let us write a tale that will echo back to the beginning, where the brave may live forever. +greetingRDLevel1Type0.text=Stand firm, as we do, with the strength of our fathers and mothers who watch us from beyond. +greetingRDLevel1Type1.text=You have chosen to face us. May this battle be worthy of our ancestors, who call us from the halls of heroes. +greetingRDLevel1Type2.text=We recognize a warrior's heart in you. Reveal your strength, and let us join our ancestors in the tales of valor, where the brave live forever. +greetingRDLevel2Type0.text=We claim this prize, whether you resist or not. The bear moves, and your presence is but a passing shadow. +greetingRDLevel2Type1.text=Stand if you wish; it makes no difference. The Dominion advances, unyielding as the mountains. +greetingRDLevel2Type2.text=Your choice is yours alone. Fight, flee, or submit - it matters little to us. +greetingRDLevel3Type0.text=Your defenses are little more than leaves in the wind. +greetingRDLevel3Type1.text=We find no worth in your resistance. Like the bear swatting a fly, we will brush you aside. +greetingRDLevel3Type2.text=You think yourself strong? We shall show you the true meaning of power. +greetingRDLevel4Type0.text=Like a bear crushing twigs, we will break you. +greetingRDLevel4Type1.text=You are but prey to our strength. Defy us, and feel the weight of the bear's fury. +greetingRDLevel4Type2.text=We do not seek a challenge here. Step aside, or be trampled by the might of our claws. + + +greetingRALevel0Type0.text=You stand with the strength of those who carved their place from the stars. Together, let us create a story worth remembering. +greetingRALevel0Type1.text=Your courage shines bright, like the morning sun on an endless plain. Let us clash, and may our Trial be one of legends. +greetingRALevel0Type2.text=We honor the spirit that rises to meet us. May our battle be as vast as the open sky and as deep as the raven's cry. +greetingRALevel1Type0.text=You face us with courage, and the Raven bows its head. Let this battle be worthy of the endless horizon. +greetingRALevel1Type1.text=You have chosen to meet us, and we respect that choice. May your spirit match the open sky. +greetingRALevel1Type2.text=We honor your resolve. As pioneers of this land, let us test who has the greater claim. +greetingRALevel2Type0.text=Stand if you must; it matters not, for we always reach our destination. +greetingRALevel2Type1.text=You are but another obstacle on this long journey. Resist if you wish, but our path is already set. +greetingRALevel2Type2.text=Our course is true, and your presence is but a stone in the river. We flow forward regardless. +greetingRALevel3Type0.text=You think your defenses are strong, but they are as brittle as old wood. We will break through without pausing. +greetingRALevel3Type1.text=You stand before the Raven, yet your strength is that of a dried riverbed - cracked and weak. +greetingRALevel3Type2.text=You may try to defend this land, but like a tumbleweed, you will be carried away. +greetingRALevel4Type0.text=You are but dust on the trail. We will take what is needed and leave nothing behind. +greetingRALevel4Type1.text=You stand in our path, but like windblown sand, you will be scattered before us. +greetingRALevel4Type2.text=We waste no time with those who cannot endure. Move, or be swept aside by the coming storm. + + +greetingCPLevel0Type0.text=You burn with a light that defies the darkness. Let this battle be one that brings life to our stories once more. +greetingCPLevel0Type1.text=Your spirit shines with the brilliance of a nova. Together, we shall create a Trial that the universe itself will remember. +greetingCPLevel0Type2.text=We see in you the strength of those who fight for more than themselves. Let this battle be one that honors all who came before us. +greetingCPLevel1Type0.text=You stand with courage, as we have done many times before. Let us see if your spirit can match the strength of those who endure. +greetingCPLevel1Type1.text=You face us with eyes unclouded. We honor that bravery, and may this Trial be one that echoes through the stars. +greetingCPLevel1Type2.text=We recognize your strength, a strength that mirrors our own. Let us meet in this Trial as warriors who respect the path we walk. +greetingCPLevel2Type0.text=We have walked through fire, and your presence is but a whisper against the storm. We have no time for you. +greetingCPLevel2Type1.text=The path is clear, and you are but a fleeting obstacle. Stand or flee - it matters little to us. +greetingCPLevel2Type2.text=You are neither our enemy nor our savior. We walk a journey far greater than this moment. +greetingCPLevel3Type0.text=You see only the surface, unaware of the depths that guide us. We will strike, and you shall see the truth too late. +greetingCPLevel3Type1.text=You treat us as prey, but you forget - survivors know the path to victory. Prepare to face what you cannot understand. +greetingCPLevel3Type2.text=You mock the scars we bear, but it is through pain we have found strength. We will show you what true resilience is. +greetingCPLevel4Type0.text=The Protectorate stands as the last of our kind. You are but an echo of what was - we will silence you. +greetingCPLevel4Type1.text=You threaten what remains of us. We will show you why even the shadows fear the light of our flames. +greetingCPLevel4Type2.text=You step into a realm where survivors become hunters. We will fight with the fury of those who have lost everything. + + +greetingCLANLevel0Type0.text=We honor your resolve. May this battle be one that shines among the stars. +greetingCLANLevel0Type1.text=Your strength is worthy of respect. Together, let us create a Trial that will be remembered. +greetingCLANLevel0Type2.text=You fight with the spirit of a true warrior. Let this battle be one for the ages. +greetingCLANLevel1Type0.text=You stand with honor. May our clash be one that echoes in history. +greetingCLANLevel1Type1.text=We acknowledge your bravery. Let this Trial determine who is truly worthy. +greetingCLANLevel1Type2.text=You face us with courage. Let this battle be a true test of strength. +greetingCLANLevel2Type0.text=Your presence is insignificant. We shall take what we desire and move on. +greetingCLANLevel2Type1.text=You are but an obstacle on our path. We will advance regardless. +greetingCLANLevel2Type2.text=You may stand or fall; it matters not to us. We will take what we seek. +greetingCLANLevel3Type0.text=You are nothing before our might. Prepare to be cast aside. +greetingCLANLevel3Type1.text=Your defenses are weak and unworthy. We will break through and take what is ours. +greetingCLANLevel3Type2.text=You cannot hope to stand against us. We shall sweep you aside with little effort. +greetingCLANLevel4Type0.text=Your defiance is futile. We shall crush you and take what belongs to us. +greetingCLANLevel4Type1.text=You are but a shadow before us. We will strike you down and claim what we desire. +greetingCLANLevel4Type2.text=You stand in our path, but your resistance is meaningless. We shall take what is ours. greetingCLANLevel5Type0.text=Since you have proven yourself unworthy of honor, we will dispense with a formal Batchall. \ No newline at end of file diff --git a/MekHQ/src/mekhq/campaign/mission/AtBContract.java b/MekHQ/src/mekhq/campaign/mission/AtBContract.java index 48c59c04ab..103308cc13 100644 --- a/MekHQ/src/mekhq/campaign/mission/AtBContract.java +++ b/MekHQ/src/mekhq/campaign/mission/AtBContract.java @@ -1372,90 +1372,126 @@ public AtBContractRef(int id) { } /** - * Initiates a batchall. + * This method initiates a batchall, a challenge/dialog to decide on the conduct of a campaign. * Prompts the player with a message and options to accept or refuse the batchall. * * @param campaign The current campaign. * @return {@code true} if the batchall is accepted, {@code false} otherwise. */ + // public boolean initiateBatchall(Campaign campaign) { - // Set the title of the dialog + // Retrieves the title from the resources String title = resources.getString("incomingTransmission.title"); - // Generate the batchall statement and fetch the faction image + // Retrieves the batchall statement based on infamy and enemy code String batchallStatement = BatchallFactions.getGreeting(campaign, enemyCode); + + // Constants for the directory of the portraits and the file type final String PORTRAIT_DIRECTORY = "data/images/force/Pieces/Logos/Clan/"; final String PORTRAIT_FILE_TYPE = ".png"; - ImageIcon portrait; + // An ImageIcon to hold the clan's faction icon + ImageIcon icon; + // A switch statement that selects the icon based on the enemy code switch (enemyCode) { - case "CBS" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Blood Spirit" + PORTRAIT_FILE_TYPE); - case "CB" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Burrock" + PORTRAIT_FILE_TYPE); - case "CCC" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Cloud Cobra" + PORTRAIT_FILE_TYPE); - case "CCO" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Coyote" + PORTRAIT_FILE_TYPE); + // Each case sets the icon to the corresponding image + case "CBS" -> icon = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Blood Spirit" + + PORTRAIT_FILE_TYPE); + case "CB" -> icon = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Burrock" + + PORTRAIT_FILE_TYPE); + case "CCC" -> icon = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Cloud Cobra" + + PORTRAIT_FILE_TYPE); + case "CCO" -> icon = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Coyote" + + PORTRAIT_FILE_TYPE); case "CDS" -> { if (campaign.getGameYear() >= 3100) { - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Sea Fox" + PORTRAIT_FILE_TYPE); + icon = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Sea Fox" + + PORTRAIT_FILE_TYPE); } else { - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Diamond Shark" + PORTRAIT_FILE_TYPE); + icon = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Diamond Shark" + + PORTRAIT_FILE_TYPE); } } - case "CFM" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Fire Mandrill" + PORTRAIT_FILE_TYPE); + case "CFM" -> icon = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Fire Mandrill" + + PORTRAIT_FILE_TYPE); case "CGB" -> { if (campaign.getGameYear() >= 3060) { - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Ghost Bear Dominion" + PORTRAIT_FILE_TYPE); + icon = new ImageIcon(PORTRAIT_DIRECTORY + "Ghost Bear Dominion" + + PORTRAIT_FILE_TYPE); } else { - portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Ghost Bear" + PORTRAIT_FILE_TYPE); + icon = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Ghost Bear" + + PORTRAIT_FILE_TYPE); } } - case "CGS" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Goliath Scorpion" + PORTRAIT_FILE_TYPE); - case "CHH" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Hell's Horses" + PORTRAIT_FILE_TYPE); - case "CIH" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Ice Hellion" + PORTRAIT_FILE_TYPE); - case "CJF" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Jade Falcon" + PORTRAIT_FILE_TYPE); - case "CMG" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Mongoose" + PORTRAIT_FILE_TYPE); - case "CNC" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Nova Cat" + PORTRAIT_FILE_TYPE); - case "CSJ" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Smoke Jaguar" + PORTRAIT_FILE_TYPE); - case "CSR" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Snow Raven" + PORTRAIT_FILE_TYPE); - case "CSA" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Star Adder" + PORTRAIT_FILE_TYPE); - case "CSV" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Steel Viper" + PORTRAIT_FILE_TYPE); - case "CSL" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Stone Lion" + PORTRAIT_FILE_TYPE); - case "CWI" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Widowmaker" + PORTRAIT_FILE_TYPE); - case "CW", "CWE" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Wolf" + PORTRAIT_FILE_TYPE); - case "CWIE" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Wolf-in-Exile" + PORTRAIT_FILE_TYPE); - case "CEI" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Scorpion Empire" + PORTRAIT_FILE_TYPE); - case "RD" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Rasalhague Dominion" + PORTRAIT_FILE_TYPE); - case "RA" -> portrait = new ImageIcon(PORTRAIT_DIRECTORY + "Raven Alliance" + PORTRAIT_FILE_TYPE); - default -> portrait = new ImageIcon("data/images/force/Pieces/Logos/Inner Sphere/Star League.png"); + case "CGS" -> icon = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Goliath Scorpion" + + PORTRAIT_FILE_TYPE); + case "CHH" -> icon = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Hell's Horses" + + PORTRAIT_FILE_TYPE); + case "CIH" -> icon = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Ice Hellion" + + PORTRAIT_FILE_TYPE); + case "CJF" -> icon = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Jade Falcon" + + PORTRAIT_FILE_TYPE); + case "CMG" -> icon = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Mongoose" + + PORTRAIT_FILE_TYPE); + case "CNC" -> icon = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Nova Cat" + + PORTRAIT_FILE_TYPE); + case "CSJ" -> icon = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Smoke Jaguar" + + PORTRAIT_FILE_TYPE); + case "CSR" -> icon = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Snow Raven" + + PORTRAIT_FILE_TYPE); + case "CSA" -> icon = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Star Adder" + + PORTRAIT_FILE_TYPE); + case "CSV" -> icon = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Steel Viper" + + PORTRAIT_FILE_TYPE); + case "CSL" -> icon = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Stone Lion" + + PORTRAIT_FILE_TYPE); + case "CWI" -> icon = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Widowmaker" + + PORTRAIT_FILE_TYPE); + case "CW", "CWE" -> icon = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Wolf" + + PORTRAIT_FILE_TYPE); + case "CWIE" -> icon = new ImageIcon(PORTRAIT_DIRECTORY + "Clan Wolf-in-Exile" + + PORTRAIT_FILE_TYPE); + case "CEI" -> icon = new ImageIcon(PORTRAIT_DIRECTORY + "Scorpion Empire" + + PORTRAIT_FILE_TYPE); + case "RD" -> icon = new ImageIcon(PORTRAIT_DIRECTORY + "Rasalhague Dominion" + + PORTRAIT_FILE_TYPE); + case "RA" -> icon = new ImageIcon(PORTRAIT_DIRECTORY + "Raven Alliance" + + PORTRAIT_FILE_TYPE); + default -> icon = new ImageIcon("data/images/force/Pieces/Logos/Inner Sphere/Star League.png"); } - // Determine the name of the commander based on faction + // Set the commander's rank and use a name generator to generate the commander's name String rank = resources.getString("starColonel.text"); RandomNameGenerator randomNameGenerator = new RandomNameGenerator(); String commander = randomNameGenerator.generate(Gender.RANDOMIZE, true, enemyCode); - commander += ' ' + Bloodname.randomBloodname(enemyCode, Phenotype.MEKWARRIOR, campaign.getGameYear()).getName(); + commander += ' ' + Bloodname.randomBloodname(enemyCode, Phenotype.MEKWARRIOR, + campaign.getGameYear()).getName(); - // Prepare the display message for the dialog + // Construct the batchall message String message = String.format(resources.getString("batchallOpener.text"), - this.getName(), rank, commander, getEnemy().getFullName(campaign.getGameYear()), - getSystemName(campaign.getLocalDate())); + this.getName(), rank, commander, getEnemy().getFullName(campaign.getGameYear()), + getSystemName(campaign.getLocalDate())); message = message + batchallStatement; + // Append additional message text if the fame is less than 5 if (campaign.getFameAndInfamy().getFameForFaction(enemyCode) < 5) { message = message + resources.getString("batchallCloser.text"); } - // Create a pane to display both the message and the commander's portrait + // Create a text pane to display the message JTextPane textPane = new JTextPane(); textPane.setContentType("text/html"); textPane.setText(message); textPane.setEditable(false); + // Create a panel to display the icon and the batchall message JPanel panel = new JPanel(new BorderLayout()); - JLabel imageLabel = new JLabel(portrait); + JLabel imageLabel = new JLabel(icon); panel.add(imageLabel, BorderLayout.CENTER); panel.add(textPane, BorderLayout.SOUTH); + // Choose dialog to display based on the fame if (campaign.getFameAndInfamy().getFameForFaction(enemyCode) > 4) { noBatchallOfferedDialog(panel, title); return false; @@ -1465,7 +1501,7 @@ public boolean initiateBatchall(Campaign campaign) { } /** - * Display a batchall dialog. + * This function creates a dialog with accept and refuse buttons. * * @param campaign the current campaign * @param panel the panel to display in the dialog @@ -1473,38 +1509,88 @@ public boolean initiateBatchall(Campaign campaign) { * @return {@code true} if the batchall is accepted, {@code false} otherwise */ private boolean batchallDialog(Campaign campaign, JPanel panel, String title) { - // Prepare the options for the dialog - Object[] options = { - resources.getString("responseAccept.text"), - resources.getString("responseRefuse.text") - }; + // We use a single-element array to store the result, because we need to modify it inside + // the action listeners, which requires the variable to be effectively final + final boolean[] result = {false}; + + // Create a custom dialog + JDialog dialog = new JDialog(); + dialog.setTitle(title); // Set the title of the dialog + dialog.setLayout(new BorderLayout()); // Set a border layout manager + + // Create an accept button and add its action listener. When clicked, it will set the result + // to true and close the dialog + JButton acceptButton = new JButton(resources.getString("responseAccept.text")); + acceptButton.setToolTipText(resources.getString("responseAccept.tooltip")); + acceptButton.addActionListener(e -> { + result[0] = true; + dialog.dispose(); + }); + + // Create a refuse button and add its action listener. + // When clicked, it will trigger a refusal confirmation dialog + JButton refuseButton = new JButton(resources.getString("responseRefuse.text")); + refuseButton.setToolTipText(resources.getString("responseRefuse.tooltip")); + refuseButton.addActionListener(e -> { + dialog.dispose(); // Close the current dialog + // Use another method to show a refusal confirmation dialog and store the result + result[0] = refusalConfirmationDialog(campaign); + }); + + // Create a panel for buttons and add buttons to it + JPanel buttonPanel = new JPanel(); + buttonPanel.add(acceptButton); + buttonPanel.add(refuseButton); + + // Add the original panel and button panel to the dialog + dialog.add(panel, BorderLayout.CENTER); + dialog.add(buttonPanel, BorderLayout.SOUTH); + + dialog.pack(); // Size the dialog to fit the preferred size and layouts of its components + dialog.setLocationRelativeTo(null); // Center the dialog on the screen + dialog.setModal(true); // Make the dialog block user input to other top-level windows + dialog.setVisible(true); // Show the dialog + + return result[0]; // Return the result when the dialog is disposed + } - // Display the dialog and capture the response - int batchallDialog = JOptionPane.showOptionDialog(null, panel, title, - JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); - - // Handle the response - if (batchallDialog == JOptionPane.NO_OPTION) { - // Display the dialog and capture the response - int refusalConfirmation = JOptionPane.showOptionDialog(null, - String.format(resources.getString("refusalConfirmation.text"), - getEnemy().getFullName(campaign.getGameYear())), - resources.getString("responseRefuse.text"), - JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, - options[0]); - - // Handle the response - if (refusalConfirmation == JOptionPane.NO_OPTION) { - // Report refusal of the batchall - campaign.addReport(resources.getString("refusalReport.text")); - campaign.getFameAndInfamy().updateFameForFaction(campaign, enemyCode, -1); - return false; - } else { - return true; - } - } else { - return true; + /** + * This function displays a dialog asking for final confirmation to refuse a batchall, + * and performs related actions if the refusal is confirmed. + * + * @param campaign the current campaign + * @return {@code true} if the user accepts the refusal, {@code false} if the user cancels the refusal + */ + private boolean refusalConfirmationDialog(Campaign campaign) { + // Display a dialog with options for accepting or refusing a batchall. + // The dialog message is retrieved from resources, including the full name of the enemy for + // the year of the campaign. + // OptionDialog will return an int relating to the option chosen by the user. + int refusalConfirmation = JOptionPane.showOptionDialog( + null, + String.format(resources.getString("refusalConfirmation.text"), + getEnemy().getFullName(campaign.getGameYear())), + resources.getString("responseRefuse.text"), + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + new Object[]{resources.getString("responseAccept.text"), + resources.getString("responseRefuse.text")}, + resources.getString("responseAccept.text")); + + // If the refusal is confirmed (NO_OPTION selected), perform needed actions. + if (refusalConfirmation == JOptionPane.NO_OPTION) { + // Add a report to the campaign about the refusal + campaign.addReport(resources.getString("refusalReport.text")); + // Update the fame factor for the enemy faction in this campaign + campaign.getFameAndInfamy().updateFameForFaction(campaign, enemyCode, -1); + // Return false indicating that batchall is refused + return false; } + + // If the response is anything else than refusal (no option selected), it implies that the + // action is accepted. + return true; } /** diff --git a/MekHQ/src/mekhq/campaign/universe/fameAndInfamy/BatchallFactions.java b/MekHQ/src/mekhq/campaign/universe/fameAndInfamy/BatchallFactions.java index aecb6c1c1d..508d5f66e7 100644 --- a/MekHQ/src/mekhq/campaign/universe/fameAndInfamy/BatchallFactions.java +++ b/MekHQ/src/mekhq/campaign/universe/fameAndInfamy/BatchallFactions.java @@ -50,17 +50,38 @@ public static String getGreeting(Campaign campaign, String factionCode) { final int infamy = MathUtility.clamp(campaign.getFameAndInfamy().getFameLevelForFaction(factionCode), 0, 5); - String version = getVersionString(campaign.getGameYear(), factionCode); + // Faction special handlers + String version = ""; + switch (factionCode) { + case "CWE" -> factionCode = "CW"; // Wolf Empire uses the lines for Clan Wolf + case "CGB" -> { // The Ghost Bear Dominion uses the lines for the Rasalhague Dominion + if (campaign.getGameYear() < 3060) { + factionCode = "CGB"; + } else { + factionCode = "RD"; + } + } + // Alyina Mercantile League isn't big enough to warrant their own lines, so they use the + // generic fallback + case "AML" -> factionCode = "CLAN"; + case "CDS" -> { // This handles the switch from Clan Diamond Shark to Clan Sea Fox + if (campaign.getGameYear() < 3100) { + version = "Version 1"; + } else { + version = "Version 2"; + } + } + default -> {} + } + // The rest of the method String greeting; int type = 0; if (infamy == 5) { greeting = resources.getString("greetingCLANLevel5Type0.text"); } else { - if (!factionCode.equals("CLAN")) { - type = Compute.randomInt(3); - } + type = Compute.randomInt(3); String greetingReference = String.format(resources.getString("greetingFormatBatchall.text"), factionCode, version, infamy, type); greeting = resources.getString(greetingReference); @@ -68,40 +89,4 @@ public static String getGreeting(Campaign campaign, String factionCode) { return '"' + greeting + '"'; } - - /** - * Retrieves the version string based on the given faction code and current year. - * - * @param currentYear The current year as an {@link Integer}. - * @param factionCode The faction code as a {@link String}. - * @return The version {@link String} for the given faction code and current year. - */ - private static String getVersionString(int currentYear, String factionCode) { - switch (factionCode) { - case "CDS" -> { - if (currentYear < 3100) { - return "Version 1"; - } else { - return "Version 2"; - } - } - case "CGB" -> { - if (currentYear < 3060) { - return "Version 1"; - } else { - return "Version 2"; - } - } - case "CEI" -> { - if (currentYear < 3141) { - return "Version 1"; - } else { - return "Version 2"; - } - } - default -> { - return ""; - } - } - } } diff --git a/MekHQ/src/mekhq/campaign/universe/fameAndInfamy/FameAndInfamyController.java b/MekHQ/src/mekhq/campaign/universe/fameAndInfamy/FameAndInfamyController.java index c24aca0ab7..9a83c61cd4 100644 --- a/MekHQ/src/mekhq/campaign/universe/fameAndInfamy/FameAndInfamyController.java +++ b/MekHQ/src/mekhq/campaign/universe/fameAndInfamy/FameAndInfamyController.java @@ -43,6 +43,8 @@ public FameAndInfamyController() { this.trackingFactions.put(shortName, 2.0); } } + + // TODO add modifiers for inter-faction relationships } /** @@ -103,6 +105,41 @@ public int getFameLevelForFaction(String factionCode) { return (int) round(trackingFactions.get(factionCode)); } + /** + * Retrieves the name of the fame level for a faction. + * + * @param factionCode The code of the faction. + * @param isInfamy Specifies whether to retrieve the fame name for infamy or fame. + * @return The name of the fame level for the faction. + */ + public String getFameName(String factionCode, boolean isInfamy) { + final int level = getFameLevelForFaction(factionCode); + + if (isInfamy) { + return switch (level) { + case 0 -> "Reviled"; + case 1 -> "Disgraced"; + case 2 -> "Insignificant"; + case 3 -> "Venerated"; + case 4 -> "Exalted"; + case 5 -> "Legendary"; + default -> throw new IllegalStateException("Unexpected value in getFameName, infamy: " + + level); + }; + } else { + return switch (level) { + case 0 -> "Insignificant"; + case 1 -> "Obscure"; + case 2 -> "Noted"; + case 3 -> "Notorious"; + case 4 -> "Infamous"; + case 5 -> "Reviled"; + default -> throw new IllegalStateException("Unexpected value in getFameName, fame: " + + level); + }; + } + } + /** * Sets the fame value for a specific faction. * From 84304bf3caf54ee92d271094d901ba061bf8d8df Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Tue, 24 Sep 2024 19:23:52 -0500 Subject: [PATCH 21/21] Refactor dialog handling in Batchall classes (again) Refactored dialog creation and handling for refusal and noBatchall scenarios to use JDialog and improve user experience with more interactive components. Made minor adjustments to variable initialization and fixed a string concatenation issue in BatchallFactions.java. Updated the ranks.xml version to reflect these changes. --- .../mekhq/resources/AtBContract.properties | 36 +------ .../mekhq/campaign/mission/AtBContract.java | 93 ++++++++++++------- .../fameAndInfamy/BatchallFactions.java | 4 +- .../FameAndInfamyController.java | 2 - MekHQ/userdata/data/universe/ranks.xml | 2 +- 5 files changed, 68 insertions(+), 69 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/AtBContract.properties b/MekHQ/resources/mekhq/resources/AtBContract.properties index db292f06ba..cf3567b0e9 100644 --- a/MekHQ/resources/mekhq/resources/AtBContract.properties +++ b/MekHQ/resources/mekhq/resources/AtBContract.properties @@ -1,45 +1,15 @@ # initiateBatchall incomingTransmission.title=++INCOMING TRANSMISSION++ -nameRedacted.text=[REDACTED] starColonel.text=Star Colonel -batchallOpener.text=
%s

"I am %s %s commanding the %s forces on %s."
- -batchallStatementCBS.text="We are Clan Blood Spirit. Blood calls for blood. What forces stand to contest our rightful claim?" -batchallStatementCB.text="Clan Burrock brings strength unbroken by time. Identify your forces so that we may take what is ours." -batchallStatementCCC.text="Under the sacred laws of combat as laid out by the Way of the Clans, I challenge you. With what forces will you bid to oppose us?" -batchallStatementCCO.text="The Coyote hunts. What prey hides behind these defenses, and how shall it fall to our fangs?" -batchallStatementCFM.text="We are Clan Fire Mandrill. The flame of conquest burns bright, and we claim this world. What will you offer before we consume you?" -batchallStatementCGB.text="We are Clan Ghost Bear. This world is ours. Those who dispute our claim must identify the size and location of their forces for immediate disposal." -batchallStatementCGBDominion.text="The strength of the Dominion claims this world. Identify your defenses or face our wrath." -batchallStatementCGS.text="The relentless pursuit of Clan Goliath Scorpion begins. What forces do you offer in defense of that which we seek?" -batchallStatementCHH.text="Clan Hell's Horses charges forward. Who stands to face the stampede, and what meager defenses do you offer?" -batchallStatementCIH.text="The warriors of Clan Ice Hellion are unleashed. Declare your forces now, or freeze beneath the fury of our onslaught." -batchallStatementCJF.text="What forces dare to defend this world from the steel talons of the Jade Falcon?" -batchallStatementCMG.text="Clan Mongoose strikes swiftly and decisively. Identify your defenses, or be swept away in our hunt." -batchallStatementCNC.text="Clan Nova Cat has seen your end. Declare the strength of your defenses, and we will ensure your fate is met." -batchallStatementCDS.text="We are Clan Diamond Shark. We strike across the void ocean at our Prey. Name the price you are prepared to lose to defend this world." -batchallStatementCDSSeaFox.text="Clan Sea Fox comes to claim what is rightfully ours. Identify your forces, so we may assess your defenses." -batchallStatementCSJ.text="The Smoke Jaguar claims this world. Identify the forces that defend it so that we from the mists of space may know on whom we pounce." -batchallStatementCSR.text="The wings of Clan Snow Raven shadow this world. Declare your forces, or be torn from the skies." -batchallStatementCSA.text="The coils of Clan Star Adder tighten around this world. What forces defend it from our grasp?" -batchallStatementCSV.text="The fangs of Clan Steel Viper are bared. Declare your defenses, or fall to our swift strike." -batchallStatementCSL.text="The lions of Clan Stone Lion are at your gates. What forces will dare to stand against our pride?" -batchallStatementCWI.text="Clan Widowmaker comes to claim what is ours. Name the forces that defend this world, or be crushed beneath us." -batchallStatementCW.text="The Wolves of Kerensky have claimed this world for their own. What tame dogs defend it?" -batchallStatementCWIE.text="The Wolves in Exile return to reclaim what was lost. Declare your forces, or prepare to be removed." -batchallStatementCWOV.text="The Wolverines fight to the last. Will you rise to face us, or be swept aside as we claim this world?" -batchallStatementRD.text="The Ghost Bear and Rasalhague unite. What forces stand to oppose our honor-bound claim?" -batchallStatementRA.text="The Raven's talons extend across this world. Declare your forces, or fall beneath our wings." -batchallStatementSOC.text="We, the Society, bring the future of the Clans. What forces stand in opposition to progress?" -batchallStatementGeneric.text="We are The Clans. This world is ours by right. Identify the forces that dare oppose us, or face immediate destruction." - -batchallCloser.text=

Do you accept the Batchall? +batchallOpener.text=

%s
"I am %s %s commanding the %s forces on %s.
+batchallCloser.text=

Do you accept the Batchall?
responseAccept.text=Accept Batchall responseAccept.tooltip=The scenarios for this contract will be balanced to roughly match your forces. responseRefuse.text=Refuse Batchall responseRefuse.tooltip=You will face the full strength of the Clans during this contract, and they will regard you less favorably in future dealings. responseBringItOn.text=Bring It On +responseBringItOn.tooltip=You will face the full strength of the Clans during this contract. refusalConfirmation.text=Are you sure? %s will not forget this betrayal. refusalReport.text=
YOU DARE TO REFUSE MY BATCHALL!?!
\ No newline at end of file diff --git a/MekHQ/src/mekhq/campaign/mission/AtBContract.java b/MekHQ/src/mekhq/campaign/mission/AtBContract.java index 103308cc13..c9d8aa5810 100644 --- a/MekHQ/src/mekhq/campaign/mission/AtBContract.java +++ b/MekHQ/src/mekhq/campaign/mission/AtBContract.java @@ -1562,35 +1562,53 @@ private boolean batchallDialog(Campaign campaign, JPanel panel, String title) { * @return {@code true} if the user accepts the refusal, {@code false} if the user cancels the refusal */ private boolean refusalConfirmationDialog(Campaign campaign) { - // Display a dialog with options for accepting or refusing a batchall. - // The dialog message is retrieved from resources, including the full name of the enemy for - // the year of the campaign. - // OptionDialog will return an int relating to the option chosen by the user. - int refusalConfirmation = JOptionPane.showOptionDialog( - null, - String.format(resources.getString("refusalConfirmation.text"), - getEnemy().getFullName(campaign.getGameYear())), - resources.getString("responseRefuse.text"), - JOptionPane.YES_NO_OPTION, - JOptionPane.QUESTION_MESSAGE, - null, - new Object[]{resources.getString("responseAccept.text"), - resources.getString("responseRefuse.text")}, - resources.getString("responseAccept.text")); - - // If the refusal is confirmed (NO_OPTION selected), perform needed actions. - if (refusalConfirmation == JOptionPane.NO_OPTION) { - // Add a report to the campaign about the refusal + // Create modal JDialog + JDialog dialog = new JDialog(); + dialog.setLayout(new BorderLayout()); + + // Buffer for storing user response (acceptance/refusal) + final boolean[] response = {false}; + + // "Accept" Button + JButton acceptButton = new JButton(resources.getString("responseAccept.text")); + acceptButton.setToolTipText(resources.getString("responseAccept.tooltip")); + acceptButton.addActionListener(e -> { + response[0] = true; // User has accepted + dialog.dispose(); // Close dialog + }); + + // "Refuse" Button + JButton refuseButton = new JButton(resources.getString("responseRefuse.text")); + refuseButton.setToolTipText(resources.getString("responseRefuse.tooltip")); + refuseButton.addActionListener(e -> { + // Update the campaign state on refusal campaign.addReport(resources.getString("refusalReport.text")); - // Update the fame factor for the enemy faction in this campaign campaign.getFameAndInfamy().updateFameForFaction(campaign, enemyCode, -1); - // Return false indicating that batchall is refused - return false; - } + response[0] = false; // User has refused + dialog.dispose(); // Close dialog + }); - // If the response is anything else than refusal (no option selected), it implies that the - // action is accepted. - return true; + // Panel for hosting buttons + JPanel buttonPanel = new JPanel(); + buttonPanel.add(acceptButton); + buttonPanel.add(refuseButton); + + // Message Label + JLabel messageLabel = new JLabel(String.format(resources.getString("refusalConfirmation.text"), + getEnemy().getFullName(campaign.getGameYear()))); + + // Add Message and Buttons to the dialog + dialog.add(messageLabel, BorderLayout.CENTER); + dialog.add(buttonPanel, BorderLayout.SOUTH); + + // Configure and display dialog + dialog.pack(); // Fit dialog to its contents + dialog.setLocationRelativeTo(null); // Center dialog + dialog.setModal(true); // Block access to other windows + dialog.setVisible(true); // Display dialog + + // Return user response + return response[0]; } /** @@ -1601,11 +1619,24 @@ private boolean refusalConfirmationDialog(Campaign campaign) { * @param title The title of the dialog. */ private void noBatchallOfferedDialog(JPanel panel, String title) { - Object[] options = { - resources.getString("responseBringItOn.text") - }; + // Create a new JDialog + JDialog dialog = new JDialog(); + dialog.setTitle(title); + dialog.setLayout(new BorderLayout()); + + JButton responseButton = new JButton(resources.getString("responseBringItOn.text")); + responseButton.setToolTipText(resources.getString("responseBringItOn.tooltip")); + responseButton.addActionListener(e -> dialog.dispose()); // Dispose the dialog when the button is clicked + + JPanel buttonPanel = new JPanel(); + buttonPanel.add(responseButton); // Add the button to the panel + + dialog.add(panel, BorderLayout.CENTER); + dialog.add(buttonPanel, BorderLayout.SOUTH); - JOptionPane.showOptionDialog(null, panel, title, JOptionPane.DEFAULT_OPTION, - JOptionPane.QUESTION_MESSAGE, null, options, options[0]); + dialog.pack(); // Size the dialog to fit the preferred size and layouts of its components + dialog.setLocationRelativeTo(null); // Center the dialog on the screen + dialog.setModal(true); // Set the dialog to be modal + dialog.setVisible(true); // Show the dialog } } diff --git a/MekHQ/src/mekhq/campaign/universe/fameAndInfamy/BatchallFactions.java b/MekHQ/src/mekhq/campaign/universe/fameAndInfamy/BatchallFactions.java index 508d5f66e7..06c7659c29 100644 --- a/MekHQ/src/mekhq/campaign/universe/fameAndInfamy/BatchallFactions.java +++ b/MekHQ/src/mekhq/campaign/universe/fameAndInfamy/BatchallFactions.java @@ -76,7 +76,7 @@ public static String getGreeting(Campaign campaign, String factionCode) { // The rest of the method String greeting; - int type = 0; + int type; if (infamy == 5) { greeting = resources.getString("greetingCLANLevel5Type0.text"); @@ -87,6 +87,6 @@ public static String getGreeting(Campaign campaign, String factionCode) { greeting = resources.getString(greetingReference); } - return '"' + greeting + '"'; + return greeting + '"'; } } diff --git a/MekHQ/src/mekhq/campaign/universe/fameAndInfamy/FameAndInfamyController.java b/MekHQ/src/mekhq/campaign/universe/fameAndInfamy/FameAndInfamyController.java index 9a83c61cd4..0b6b5acb3c 100644 --- a/MekHQ/src/mekhq/campaign/universe/fameAndInfamy/FameAndInfamyController.java +++ b/MekHQ/src/mekhq/campaign/universe/fameAndInfamy/FameAndInfamyController.java @@ -43,8 +43,6 @@ public FameAndInfamyController() { this.trackingFactions.put(shortName, 2.0); } } - - // TODO add modifiers for inter-faction relationships } /** diff --git a/MekHQ/userdata/data/universe/ranks.xml b/MekHQ/userdata/data/universe/ranks.xml index a84279a626..9222a5a93e 100644 --- a/MekHQ/userdata/data/universe/ranks.xml +++ b/MekHQ/userdata/data/universe/ranks.xml @@ -1,3 +1,3 @@ - +