From 8c6e454c3699c5908c29280ed73611d0e5983010 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sat, 26 Oct 2024 21:27:18 -0500 Subject: [PATCH 01/17] Fixed Faction Conflict Checks for Reeducation Camps Adjusted logic to ensure reeducation camps only consider wars involving the campaign faction. This means players can continue brainwashing their prisoners without fear of a pesky little thing like 'interstellar war' getting in the way. --- .../campaign/personnel/education/Academy.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/personnel/education/Academy.java b/MekHQ/src/mekhq/campaign/personnel/education/Academy.java index dd016af758..2573891f4b 100644 --- a/MekHQ/src/mekhq/campaign/personnel/education/Academy.java +++ b/MekHQ/src/mekhq/campaign/personnel/education/Academy.java @@ -622,9 +622,16 @@ public String getFilteredFaction(Campaign campaign, Person person, List return null; } - if (!hints.isAtWarWith(originFaction, faction, campaign.getLocalDate()) + // For reeducation camps we only care about whether the campaign faction is at war + if (isReeducationCamp) { + if (!hints.isAtWarWith(campaignFaction, faction, campaign.getLocalDate())) { + return faction.getShortName(); + } + } else { + if (!hints.isAtWarWith(originFaction, faction, campaign.getLocalDate()) || !hints.isAtWarWith(campaignFaction, faction, campaign.getLocalDate())) { - return faction.getShortName(); + return faction.getShortName(); + } } } @@ -697,6 +704,12 @@ public int getEducationLevel(Person person) { * @return true if there is a faction conflict, false otherwise. */ public Boolean isFactionConflict(Campaign campaign, Person person) { + // Reeducation camps only care if they're at war with the campaign faction + if (isReeducationCamp) { + return RandomFactionGenerator.getInstance().getFactionHints().isAtWarWith(campaign.getFaction(), + Factions.getInstance().getFaction(person.getEduAcademyFaction()), campaign.getLocalDate()); + } + // is there a conflict between academy faction & person's faction? if (RandomFactionGenerator.getInstance().getFactionHints().isAtWarWith(person.getOriginFaction(), Factions.getInstance().getFaction(person.getEduAcademyFaction()), campaign.getLocalDate())) { From d153a1996c3728c32db8c29957f9691d2a1c71a6 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sat, 26 Oct 2024 21:38:35 -0500 Subject: [PATCH 02/17] Stopped Scenarios Pulling Units from the Hangar Certain scenarios are set up to substitute enemy units with player units. Typically, this is the DropShip scenarios optionally using player DropShips. Previously, this could pull units from the players' hangar. Now it is restricted to units in the TO&E. --- .../campaign/stratcon/StratconRulesManager.java | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java index 28dce4c76b..31ea70272b 100644 --- a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java +++ b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java @@ -290,17 +290,11 @@ private static void swapInPlayerUnits(StratconScenario scenario, Campaign campai // find units in player's campaign (not just forces!) // by default, all units are eligible - if (explicitForceID == Force.FORCE_NONE) { - potentialUnits = campaign.getHangar().getUnits(); - // if we're using a seed force, then units transporting this force - // are eligible - } else { - Force force = campaign.getForce(explicitForceID); - for (UUID unitID : force.getUnits()) { - Unit unit = campaign.getUnit(unitID); - if (unit.getTransportShipAssignment() != null) { - potentialUnits.add(unit.getTransportShipAssignment().getTransportShip()); - } + Force force = campaign.getForce(explicitForceID); + for (UUID unitID : force.getUnits()) { + Unit unit = campaign.getUnit(unitID); + if (unit.getTransportShipAssignment() != null) { + potentialUnits.add(unit.getTransportShipAssignment().getTransportShip()); } } From 4719ce9fa6155bf93dada594cbc31e2f9b9cf784 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 27 Oct 2024 19:33:53 -0500 Subject: [PATCH 03/17] Refactored Field Kitchen Personnel Count Logic Field Kitchens will no longer count Prisoners against their limit, unless the Prisoner has a role. Furthermore, support personnel are no longer counted in the campaign summary screen (this was a visual bug only). --- MekHQ/src/mekhq/campaign/Campaign.java | 15 +++--- MekHQ/src/mekhq/campaign/CampaignSummary.java | 51 +++++++++++-------- 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index 605de15616..c0cfdf8222 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -4598,16 +4598,19 @@ private void processFatigueNewDay() { // otherwise we would need to check it once for each active person in the // campaign if (campaignOptions.isUseFatigue()) { - long validPersonnel; + int personnelCount; if (campaignOptions.isUseFieldKitchenIgnoreNonCombatants()) { - validPersonnel = getActivePersonnel().stream() - .filter(person -> person.getPrimaryRole().isCombat() || person.getSecondaryRole().isCombat()) - .count(); + personnelCount = (int) getActivePersonnel().stream() + .filter(person -> !person.getPrisonerStatus().isFree() && person.getPrimaryRole().isNone()) + .filter(person -> person.getPrimaryRole().isCombat() || person.getSecondaryRole().isCombat()) + .count(); } else { - validPersonnel = getActivePersonnel().size(); + personnelCount = (int) getActivePersonnel().stream() + .filter(person -> !person.getPrisonerStatus().isFree() && person.getPrimaryRole().isNone()) + .count(); } - fieldKitchenWithinCapacity = validPersonnel <= Fatigue.checkFieldKitchenCapacity(this); + fieldKitchenWithinCapacity = personnelCount <= Fatigue.checkFieldKitchenCapacity(this); } else { fieldKitchenWithinCapacity = false; } diff --git a/MekHQ/src/mekhq/campaign/CampaignSummary.java b/MekHQ/src/mekhq/campaign/CampaignSummary.java index f66d9486af..1247be3619 100644 --- a/MekHQ/src/mekhq/campaign/CampaignSummary.java +++ b/MekHQ/src/mekhq/campaign/CampaignSummary.java @@ -18,16 +18,6 @@ */ package mekhq.campaign; -import static mekhq.campaign.personnel.turnoverAndRetention.RetirementDefectionTracker.getAdministrativeStrain; -import static mekhq.campaign.personnel.turnoverAndRetention.RetirementDefectionTracker.getAdministrativeStrainModifier; -import static mekhq.campaign.personnel.turnoverAndRetention.RetirementDefectionTracker.getCombinedSkillValues; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import org.apache.commons.lang3.StringUtils; - import megamek.common.Entity; import megamek.common.Infantry; import megamek.common.UnitType; @@ -40,6 +30,15 @@ import mekhq.campaign.unit.CargoStatistics; import mekhq.campaign.unit.HangarStatistics; import mekhq.campaign.unit.Unit; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static mekhq.campaign.personnel.turnoverAndRetention.RetirementDefectionTracker.getAdministrativeStrain; +import static mekhq.campaign.personnel.turnoverAndRetention.RetirementDefectionTracker.getAdministrativeStrainModifier; +import static mekhq.campaign.personnel.turnoverAndRetention.RetirementDefectionTracker.getCombinedSkillValues; /** * calculates and stores summary information on a campaign for use in reporting, @@ -220,7 +219,7 @@ public void updateInformation() { /** * A report that gives numbers of combat and support personnel as well as * injuries - * + * * @return a String of the report */ public String getPersonnelReport() { @@ -230,7 +229,7 @@ public String getPersonnelReport() { /** * A report that gives the number of units in different damage states - * + * * @return a String of the report */ public String getForceRepairReport() { @@ -243,7 +242,7 @@ public String getForceRepairReport() { /** * A report that gives the percentage composition of the force in mek, armor, * infantry, and aero units. - * + * * @return a String of the report */ public String getForceCompositionReport() { @@ -265,7 +264,7 @@ public String getForceCompositionReport() { /** * A report that gives the percentage of successful missions - * + * * @return a String of the report */ public String getMissionSuccessReport() { @@ -276,7 +275,7 @@ public String getMissionSuccessReport() { /** * A report that gives capacity and existing tonnage of all cargo - * + * * @return a String of the report */ public StringBuilder getCargoCapacityReport() { @@ -310,7 +309,7 @@ public StringBuilder getCargoCapacityReport() { /** * A report that gives information about the transportation capacity - * + * * @return a String of the report */ public String getTransportCapacity() { @@ -358,24 +357,36 @@ public String getAdministrativeCapacityReport(Campaign campaign) { * @return A summary of fatigue related facilities. */ public String getFacilityReport() { - int personnelCount = campaign.getActivePersonnel().size(); + CampaignOptions campaignOptions = campaign.getCampaignOptions(); + int personnelCount; + + if (campaignOptions.isUseFieldKitchenIgnoreNonCombatants()) { + personnelCount = (int) campaign.getActivePersonnel().stream() + .filter(person -> !person.getPrisonerStatus().isFree() && person.getPrimaryRole().isNone()) + .filter(person -> person.getPrimaryRole().isCombat() || person.getSecondaryRole().isCombat()) + .count(); + } else { + personnelCount = (int) campaign.getActivePersonnel().stream() + .filter(person -> !person.getPrisonerStatus().isFree() && person.getPrimaryRole().isNone()) + .count(); + } StringBuilder report = new StringBuilder(); - if (campaign.getCampaignOptions().isUseFatigue()) { + if (campaignOptions.isUseFatigue()) { report.append(String.format("Kitchens (%s/%s) ", personnelCount, Fatigue.checkFieldKitchenCapacity(campaign))); } - if (campaign.getCampaignOptions().isUseAdvancedMedical()) { + if (campaignOptions.isUseAdvancedMedical()) { int patients = (int) campaign.getPatients().stream() .filter(patient -> patient.getDoctorId() != null) .count(); int doctorCapacity = campaign.getActivePersonnel().stream() .filter(person -> (person.getPrimaryRole().isDoctor()) || (person.getSecondaryRole().isDoctor())) - .mapToInt(person -> campaign.getCampaignOptions().getMaximumPatients()) + .mapToInt(person -> campaignOptions.getMaximumPatients()) .sum(); report.append(String.format("Hospital Beds (%s/%s)", From 1890f9e5dbf13d5fafaf98cb8b7434c26937db7b Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 27 Oct 2024 19:39:20 -0500 Subject: [PATCH 04/17] Refactored unit eligibility logic in StratconRulesManager Revised the logic for determining eligible units in the campaign. Made all units in the TO&E eligible by default and handled units using a seed force separately for transport eligibility. Improved error handling and logging during unit retrieval. --- .../stratcon/StratconRulesManager.java | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java index 31ea70272b..24a565a793 100644 --- a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java +++ b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java @@ -288,16 +288,26 @@ private static void swapInPlayerUnits(StratconScenario scenario, Campaign campai Collection potentialUnits = new HashSet<>(); - // find units in player's campaign (not just forces!) - // by default, all units are eligible - Force force = campaign.getForce(explicitForceID); - for (UUID unitID : force.getUnits()) { - Unit unit = campaign.getUnit(unitID); - if (unit.getTransportShipAssignment() != null) { - potentialUnits.add(unit.getTransportShipAssignment().getTransportShip()); + // find units in player's campaign by default, all units in the TO&E are eligible + if (explicitForceID == Force.FORCE_NONE) { + for (UUID unitId : campaign.getForces().getUnits()) { + try { + potentialUnits.add(campaign.getUnit(unitId)); + } catch (Exception exception) { + logger.error(String.format("Error retrieving unit (%s): %s", + unitId, exception.getMessage())); + } + } + // if we're using a seed force, then units transporting this force are eligible + } else { + Force force = campaign.getForce(explicitForceID); + for (UUID unitID : force.getUnits()) { + Unit unit = campaign.getUnit(unitID); + if (unit.getTransportShipAssignment() != null) { + potentialUnits.add(unit.getTransportShipAssignment().getTransportShip()); + } } } - for (Unit unit : potentialUnits) { if ((sft.getAllowedUnitType() == 11) && (!campaign.getCampaignOptions().isUseDropShips())) { continue; From 019ce1d8514770bd3c12587f833947a748cf775b Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 27 Oct 2024 19:43:51 -0500 Subject: [PATCH 05/17] Fix personnel count filtering logic Updated the logic for filtering personnel to correctly identify those who should be included based on their prisoner status and role. This change ensures that personnel count calculations are accurate when using the field kitchen feature. --- MekHQ/src/mekhq/campaign/Campaign.java | 4 ++-- MekHQ/src/mekhq/campaign/CampaignSummary.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index c0cfdf8222..27e2f60414 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -4602,12 +4602,12 @@ private void processFatigueNewDay() { if (campaignOptions.isUseFieldKitchenIgnoreNonCombatants()) { personnelCount = (int) getActivePersonnel().stream() - .filter(person -> !person.getPrisonerStatus().isFree() && person.getPrimaryRole().isNone()) + .filter(person -> !(person.getPrisonerStatus().isFree() && person.getPrimaryRole().isNone())) .filter(person -> person.getPrimaryRole().isCombat() || person.getSecondaryRole().isCombat()) .count(); } else { personnelCount = (int) getActivePersonnel().stream() - .filter(person -> !person.getPrisonerStatus().isFree() && person.getPrimaryRole().isNone()) + .filter(person -> !(person.getPrisonerStatus().isFree() && person.getPrimaryRole().isNone())) .count(); } fieldKitchenWithinCapacity = personnelCount <= Fatigue.checkFieldKitchenCapacity(this); diff --git a/MekHQ/src/mekhq/campaign/CampaignSummary.java b/MekHQ/src/mekhq/campaign/CampaignSummary.java index 1247be3619..93face382c 100644 --- a/MekHQ/src/mekhq/campaign/CampaignSummary.java +++ b/MekHQ/src/mekhq/campaign/CampaignSummary.java @@ -362,12 +362,12 @@ public String getFacilityReport() { if (campaignOptions.isUseFieldKitchenIgnoreNonCombatants()) { personnelCount = (int) campaign.getActivePersonnel().stream() - .filter(person -> !person.getPrisonerStatus().isFree() && person.getPrimaryRole().isNone()) + .filter(person -> !(person.getPrisonerStatus().isFree() && person.getPrimaryRole().isNone())) .filter(person -> person.getPrimaryRole().isCombat() || person.getSecondaryRole().isCombat()) .count(); } else { personnelCount = (int) campaign.getActivePersonnel().stream() - .filter(person -> !person.getPrisonerStatus().isFree() && person.getPrimaryRole().isNone()) + .filter(person -> !(person.getPrisonerStatus().isFree() && person.getPrimaryRole().isNone())) .count(); } From c4b5af835fc2448e4a912de19ab30824424f78bc Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 27 Oct 2024 21:05:12 -0500 Subject: [PATCH 06/17] Handled StratCon Scenario Placement Failure when All Coordinates Occupied Added checks to handle scenarios where facilities or objectives could not be placed because all coordinates were occupied. Issued warnings and errors, and updated the `getUnoccupiedCoords` method to return null when placement is not possible. --- .../stratcon/StratconContractInitializer.java | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/stratcon/StratconContractInitializer.java b/MekHQ/src/mekhq/campaign/stratcon/StratconContractInitializer.java index 53da4fce77..64d15a232b 100644 --- a/MekHQ/src/mekhq/campaign/stratcon/StratconContractInitializer.java +++ b/MekHQ/src/mekhq/campaign/stratcon/StratconContractInitializer.java @@ -289,6 +289,13 @@ private static void initializeTrackFacilities(StratconTrackState trackState, int StratconCoords coords = getUnoccupiedCoords(trackState); + if (coords == null) { + logger.warn(String.format("Unable to place facility on track %s," + + " as all coords were occupied. Aborting.", + trackState.getDisplayableName())); + return; + } + trackState.addFacility(coords, sf); if (strategicObjective) { @@ -336,6 +343,13 @@ private static void initializeObjectiveScenarios(Campaign campaign, AtBContract StratconCoords coords = getUnoccupiedCoords(trackState); + if (coords == null) { + logger.error(String.format("Unable to place objective scenario on track %s," + + " as all coords were occupied. Aborting.", + trackState.getDisplayableName())); + return; + } + // facility if (template.isFacilityScenario()) { StratconFacility facility = template.isHostileFacility() @@ -376,18 +390,24 @@ private static void initializeObjectiveScenarios(Campaign campaign, AtBContract * Utility function that, given a track state, picks a random set of unoccupied * coordinates. */ - private static StratconCoords getUnoccupiedCoords(StratconTrackState trackState) { - // plonk + public static StratconCoords getUnoccupiedCoords(StratconTrackState trackState) { + // Maximum number of attempts + int maxAttempts = trackState.getWidth() * trackState.getHeight(); + int attempts = 0; + int x = Compute.randomInt(trackState.getWidth()); int y = Compute.randomInt(trackState.getHeight()); StratconCoords coords = new StratconCoords(x, y); - // make sure we don't put the facility down on top of anything else - while ((trackState.getFacility(coords) != null) || - (trackState.getScenario(coords) != null)) { + while ((trackState.getFacility(coords) != null || trackState.getScenario(coords) != null) && attempts < maxAttempts) { x = Compute.randomInt(trackState.getWidth()); y = Compute.randomInt(trackState.getHeight()); coords = new StratconCoords(x, y); + attempts++; + } + + if (attempts == maxAttempts) { + return null; } return coords; From f13eff2056f149db1ced267af9179ffd8ffaa164 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 27 Oct 2024 23:29:43 -0500 Subject: [PATCH 07/17] Added New StratCon Scenario Generation Utility Method Extracted repeated logic into helper methods and consolidated map location imports. Enhanced clarity and maintainability by introducing generateExternalScenario and finalizeBackingScenario methods. --- .../stratcon/StratconRulesManager.java | 268 ++++++++++++++---- 1 file changed, 220 insertions(+), 48 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java index 28dce4c76b..17fb76f8c5 100644 --- a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java +++ b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java @@ -52,6 +52,12 @@ import java.util.*; import java.util.stream.Collectors; +import static mekhq.campaign.mission.ScenarioMapParameters.MapLocation.AllGroundTerrain; +import static mekhq.campaign.mission.ScenarioMapParameters.MapLocation.LowAtmosphere; +import static mekhq.campaign.mission.ScenarioMapParameters.MapLocation.Space; +import static mekhq.campaign.mission.ScenarioMapParameters.MapLocation.SpecificGroundTerrain; +import static mekhq.campaign.stratcon.StratconContractInitializer.getUnoccupiedCoords; + /** * This class contains "rules" logic for the AtB-Stratcon state * @@ -120,10 +126,12 @@ public static void generateScenariosForTrack(Campaign campaign, AtBContract cont // generate a scenario if (!availableForceIDs.isEmpty() && (Compute.randomInt(100) < targetNum)) { // pick random coordinates and force to drive the scenario - int x = Compute.randomInt(track.getWidth()); - int y = Compute.randomInt(track.getHeight()); + StratconCoords scenarioCoords = getUnoccupiedCoords(track); - StratconCoords scenarioCoords = new StratconCoords(x, y); + if (scenarioCoords == null) { + logger.warn("Target track is full, skipping scenario generation"); + continue; + } // if forces are already assigned to these coordinates, use those instead // of randomly-selected ones @@ -143,9 +151,9 @@ public static void generateScenariosForTrack(Campaign campaign, AtBContract cont availableForceIDs.remove(randomForceIndex); // we want to remove the actual int with the value, not the value at the index - sortedAvailableForceIDs.get(MapLocation.AllGroundTerrain).remove((Integer) randomForceID); - sortedAvailableForceIDs.get(MapLocation.LowAtmosphere).remove((Integer) randomForceID); - sortedAvailableForceIDs.get(MapLocation.Space).remove((Integer) randomForceID); + sortedAvailableForceIDs.get(AllGroundTerrain).remove((Integer) randomForceID); + sortedAvailableForceIDs.get(LowAtmosphere).remove((Integer) randomForceID); + sortedAvailableForceIDs.get(Space).remove((Integer) randomForceID); // two scenarios on the same coordinates wind up increasing in size if (track.getScenarios().containsKey(scenarioCoords)) { @@ -177,24 +185,125 @@ public static void generateScenariosForTrack(Campaign campaign, AtBContract cont // and generate the opfors / events / etc // if not auto-assigning lances, we then back out the lance assignments. for (StratconScenario scenario : generatedScenarios) { - AtBDynamicScenarioFactory.finalizeScenario(scenario.getBackingScenario(), contract, campaign); - setScenarioParametersFromBiome(track, scenario); - swapInPlayerUnits(scenario, campaign, Force.FORCE_NONE); + finalizeBackingScenario(campaign, contract, track, autoAssignLances, scenario); + } + } - if (!autoAssignLances && !scenario.ignoreForceAutoAssignment()) { - for (int forceID : scenario.getPlayerTemplateForceIDs()) { - scenario.getBackingScenario().removeForce(forceID); - } + /** + * Creates a new StratCon scenario, placing it in an unoccupied location on the specified track. + * If no track is specified, a random one will be chosen. + * An optional scenario template can be applied. + * This method is based on {@code generateScenariosForTrack()}, designed to simplify the + * process by which external classes can add new StratCon scenarios. + * + * @param campaign The campaign object encapsulating the current campaign state. + * @param contract The contract associated with the current scenario. + * @param track The {@link StratconTrackState} the scenario should be assigned to, or + * {@code null} to select a random track. + * @param template A specific {@link ScenarioTemplate} to use for scenario generation, + * or {@code null} to select scenario template randomly. + */ + public static void generateExternalScenario(Campaign campaign, AtBContract contract, + @Nullable StratconTrackState track, @Nullable ScenarioTemplate template) { + // If we're not generating for a specific track, randomly pick one. + if (track == null) { + List tracks = contract.getStratconCampaignState().getTracks(); + Random rand = new Random(); + + if (tracks.size() > 0) { + track = tracks.get(rand.nextInt(tracks.size())); + } else { + logger.error("No tracks available. Aborting scenario generation."); + } + } + + // Are we automatically assigning lances? + boolean autoAssignLances = contract.getCommandRights().isIntegrated(); + + // Grab the available lances and sort them by map type + List availableForceIDs = getAvailableForceIDs(campaign); + Map> sortedAvailableForceIDs = sortForcesByMapType(availableForceIDs, campaign); + + // Select the target coords. + StratconCoords scenarioCoords = getUnoccupiedCoords(track); + + if (scenarioCoords == null) { + logger.warn("Target track is full, aborting scenario generation"); + return; + } + + // If forces are already assigned to the target coordinates, use those instead of randomly + // selected a new force + StratconScenario scenario = null; + if (track.getAssignedCoordForces().containsKey(scenarioCoords)) { + scenario = generateScenarioForExistingForces(scenarioCoords, + track.getAssignedCoordForces().get(scenarioCoords), contract, campaign, track, + template); + } + + // Otherwise, pick a random force from those available + // If a template has been specified, remove forces that aren't appropriate for the + // template. + if (template != null) { + MapLocation location = template.mapParameters.getMapLocation(); + + switch (location) { + case AllGroundTerrain, SpecificGroundTerrain -> { + sortedAvailableForceIDs.get(LowAtmosphere).clear(); + sortedAvailableForceIDs.get(Space).clear(); + } + case LowAtmosphere -> { + sortedAvailableForceIDs.get(AllGroundTerrain).clear(); + sortedAvailableForceIDs.get(Space).clear(); + } + case Space -> { + sortedAvailableForceIDs.get(AllGroundTerrain).clear(); + sortedAvailableForceIDs.get(LowAtmosphere).clear(); + } + } + } + + // If we haven't generated a scenario yet, it's because we need to pick a random force. + if (scenario == null) { + int randomForceIndex = Compute.randomInt(availableForceIDs.size()); + int randomForceID = availableForceIDs.get(randomForceIndex); + + scenario = setupScenario(scenarioCoords, randomForceID, campaign, contract, track, template); + } + + // We end by finalizing the scenario + finalizeBackingScenario(campaign, contract, track, autoAssignLances, scenario); + } - scenario.setCurrentState(ScenarioState.UNRESOLVED); - track.addScenario(scenario); - } else { - commitPrimaryForces(campaign, scenario, track); - // if we're auto-assigning lances, deploy all assigned forces to the track as - // well - for (int forceID : scenario.getPrimaryForceIDs()) { - processForceDeployment(scenario.getCoords(), forceID, campaign, track, false); - } + /** + * Finalizes the backing scenario, setting up the OpFor, scenario parameters, and other + * necessary steps. + * + * @param campaign The current campaign. + * @param contract The contract associated with the scenario. + * @param track The relevant {@link StratconTrackState}. + * @param autoAssignLances Flag indicating whether lances are to be auto-assigned. + * @param scenario The {@link StratconScenario} scenario to be finalized. + */ + private static void finalizeBackingScenario(Campaign campaign, AtBContract contract, + @Nullable StratconTrackState track, boolean autoAssignLances, + StratconScenario scenario) { + AtBDynamicScenarioFactory.finalizeScenario(scenario.getBackingScenario(), contract, campaign); + setScenarioParametersFromBiome(track, scenario); + swapInPlayerUnits(scenario, campaign, Force.FORCE_NONE); + + if (!autoAssignLances && !scenario.ignoreForceAutoAssignment()) { + for (int forceID : scenario.getPlayerTemplateForceIDs()) { + scenario.getBackingScenario().removeForce(forceID); + } + + scenario.setCurrentState(ScenarioState.UNRESOLVED); + track.addScenario(scenario); + } else { + commitPrimaryForces(campaign, scenario, track); + // if we're auto-assigning lances, deploy all assigned forces to the track as well + for (int forceID : scenario.getPrimaryForceIDs()) { + processForceDeployment(scenario.getCoords(), forceID, campaign, track, false); } } } @@ -332,18 +441,47 @@ private static void swapInPlayerUnits(StratconScenario scenario, Campaign campai } /** - * Given a set of force IDs and coordinates, generate a scenario Useful for when - * we want to generate - * scenarios for forces already deployed to a track + * Generates a StratCon scenario for forces already existing at the given coordinates on the + * provided track. + * + * @param scenarioCoords The coordinates where the scenario will be placed on the track. + * @param forceIDs The set of force IDs (ideally for the forces already at the + * specified location). + * @param contract The contract associated with the current scenario. + * @param campaign The current campaign. + * @param track The relevant StratCon track. + * @return The newly generated {@link StratconScenario}. */ public static StratconScenario generateScenarioForExistingForces(StratconCoords scenarioCoords, - Set forceIDs, AtBContract contract, Campaign campaign, StratconTrackState track) { + Set forceIDs, AtBContract contract, Campaign campaign, + StratconTrackState track) { + return generateScenarioForExistingForces(scenarioCoords, forceIDs, contract, campaign, + track, null); + } + + /** + * Generates a StratCon scenario for forces already existing at the given coordinates on the + * provided track. This method allows us to specify a specific scenario template. + * + * @param scenarioCoords The coordinates where the scenario will be placed on the track. + * @param forceIDs The set of force IDs (ideally for the forces already at the + * specified location). + * @param contract The contract associated with the current scenario. + * @param campaign The current campaign. + * @param track The relevant StratCon track. + * @param template A specific {@link ScenarioTemplate} to use, or {@code null} to + * select a random template. + * @return The newly generated {@link StratconScenario}. + */ + public static StratconScenario generateScenarioForExistingForces(StratconCoords scenarioCoords, + Set forceIDs, AtBContract contract, Campaign campaign, + StratconTrackState track, @Nullable ScenarioTemplate template) { boolean firstForce = true; StratconScenario scenario = null; for (int forceID : forceIDs) { if (firstForce) { - scenario = setupScenario(scenarioCoords, forceID, campaign, contract, track); + scenario = setupScenario(scenarioCoords, forceID, campaign, contract, track, template); firstForce = false; } else { scenario.incrementRequiredPlayerLances(); @@ -411,20 +549,54 @@ public static void deployForceToCoords(StratconCoords coords, int forceID, Campa } /** - * Logic to set up a scenario + * Sets up a StratCon scenario with the given parameters. + * + * @param coords The coordinates where the scenario is to be placed on the track. + * @param forceID The ID of the forces involved in the scenario. + * @param campaign The current campaign. + * @param contract The contract associated with the current scenario. + * @param track The relevant StratCon track. + * @return The newly set up {@link StratconScenario}. + */ + private static StratconScenario setupScenario(StratconCoords coords, int forceID, Campaign campaign, + AtBContract contract, StratconTrackState track) { + return setupScenario(coords, forceID, campaign, contract, track, null); + } + + /** + * Sets up a Stratcon scenario with the given parameters optionally allowing use a specific scenario template. + *

+ * If a facility is already present at the provided coordinates, the scenario will be setup for that facility. + * If there is no facility, a new scenario will be generated; if the ScenarioTemplate argument provided was non-null, + * it will be used, else a randomly selected scenario will be generated. + * In case the generated scenario turns out to be a facility scenario, a new facility will be added to the track at + * the provided coordinates and setup for that facility. + * + * @param coords The coordinates where the scenario is to be placed on the track. + * @param forceID The ID of the forces involved in the scenario. + * @param campaign The current campaign. + * @param contract The contract associated with the current scenario. + * @param track The relevant StratCon track. + * @param template A specific {@link ScenarioTemplate} to use for scenario setup, or + * {@code null} to select the scenario template randomly. + * @return The newly set up {@link StratconScenario}. */ private static StratconScenario setupScenario(StratconCoords coords, int forceID, Campaign campaign, - AtBContract contract, StratconTrackState track) { - StratconScenario scenario = null; + AtBContract contract, StratconTrackState track, @Nullable ScenarioTemplate template) { + StratconScenario scenario; if (track.getFacilities().containsKey(coords)) { StratconFacility facility = track.getFacility(coords); boolean alliedFacility = facility.getOwner() == ForceAlignment.Allied; - ScenarioTemplate template = StratconScenarioFactory.getFacilityScenario(alliedFacility); + template = StratconScenarioFactory.getFacilityScenario(alliedFacility); scenario = generateScenario(campaign, contract, track, forceID, coords, template); setupFacilityScenario(scenario, facility); } else { - scenario = generateScenario(campaign, contract, track, forceID, coords); + if (template != null) { + scenario = generateScenario(campaign, contract, track, forceID, coords, template); + } else { + scenario = generateScenario(campaign, contract, track, forceID, coords); + } // we may generate a facility scenario randomly - if so, do the facility-related // stuff and add a new facility to the track @@ -666,17 +838,17 @@ private static void assignAppropriateExtraForceToScenario(StratconScenario scena // and ground units/conventional fighters to space battle List mapLocations = new ArrayList<>(); - mapLocations.add(MapLocation.Space); // can always add ASFs + mapLocations.add(Space); // can always add ASFs MapLocation scenarioMapLocation = scenario.getScenarioTemplate().mapParameters.getMapLocation(); - if (scenarioMapLocation == MapLocation.LowAtmosphere) { - mapLocations.add(MapLocation.LowAtmosphere); // can add conventional fighters to ground or low atmo battles + if (scenarioMapLocation == LowAtmosphere) { + mapLocations.add(LowAtmosphere); // can add conventional fighters to ground or low atmo battles } - if ((scenarioMapLocation == MapLocation.AllGroundTerrain) - || (scenarioMapLocation == MapLocation.SpecificGroundTerrain)) { - mapLocations.add(MapLocation.AllGroundTerrain); // can only add ground units to ground battles + if ((scenarioMapLocation == AllGroundTerrain) + || (scenarioMapLocation == SpecificGroundTerrain)) { + mapLocations.add(AllGroundTerrain); // can only add ground units to ground battles } MapLocation selectedLocation = mapLocations.get(Compute.randomInt(mapLocations.size())); @@ -753,9 +925,9 @@ private static boolean commanderLanceHasDefensiveAssignment(AtBDynamicScenario s private static Map> sortForcesByMapType(List forceIDs, Campaign campaign) { Map> retVal = new HashMap<>(); - retVal.put(MapLocation.AllGroundTerrain, new ArrayList<>()); - retVal.put(MapLocation.LowAtmosphere, new ArrayList<>()); - retVal.put(MapLocation.Space, new ArrayList<>()); + retVal.put(AllGroundTerrain, new ArrayList<>()); + retVal.put(LowAtmosphere, new ArrayList<>()); + retVal.put(Space, new ArrayList<>()); for (int forceID : forceIDs) { switch (campaign.getForce(forceID).getPrimaryUnitType(campaign)) { @@ -765,13 +937,13 @@ private static Map> sortForcesByMapType(List case UnitType.TANK: case UnitType.PROTOMEK: case UnitType.VTOL: - retVal.get(MapLocation.AllGroundTerrain).add(forceID); + retVal.get(AllGroundTerrain).add(forceID); break; case UnitType.AEROSPACEFIGHTER: - retVal.get(MapLocation.Space).add(forceID); + retVal.get(Space).add(forceID); // intentional fallthrough here, ASFs can go to atmospheric maps too case UnitType.CONV_FIGHTER: - retVal.get(MapLocation.LowAtmosphere).add(forceID); + retVal.get(LowAtmosphere).add(forceID); break; } } @@ -933,8 +1105,8 @@ private static void setAlliedForceModifier(StratconScenario scenario, AtBContrac // if an allied unit is present, then we want to make sure that // it's ground units for ground battles if (Compute.randomInt(100) <= alliedUnitOdds) { - if ((backingScenario.getTemplate().mapParameters.getMapLocation() == MapLocation.LowAtmosphere) - || (backingScenario.getTemplate().mapParameters.getMapLocation() == MapLocation.Space)) { + if ((backingScenario.getTemplate().mapParameters.getMapLocation() == LowAtmosphere) + || (backingScenario.getTemplate().mapParameters.getMapLocation() == Space)) { backingScenario.addScenarioModifier( AtBScenarioModifier.getScenarioModifier(MHQConstants.SCENARIO_MODIFIER_ALLIED_AIR_UNITS)); } else { @@ -953,8 +1125,8 @@ private static void setAlliedForceModifier(StratconScenario scenario, AtBContrac */ public static void setAttachedUnitsModifier(StratconScenario scenario, AtBContract contract) { AtBDynamicScenario backingScenario = scenario.getBackingScenario(); - boolean airBattle = (backingScenario.getTemplate().mapParameters.getMapLocation() == MapLocation.LowAtmosphere) - || (backingScenario.getTemplate().mapParameters.getMapLocation() == MapLocation.Space); + boolean airBattle = (backingScenario.getTemplate().mapParameters.getMapLocation() == LowAtmosphere) + || (backingScenario.getTemplate().mapParameters.getMapLocation() == Space); // if we're on cadre duty, we're getting three trainees, period if (contract.getContractType().isCadreDuty()) { From e2296b3656209279dc50ea6187c4afe37c488cb6 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 27 Oct 2024 23:30:11 -0500 Subject: [PATCH 08/17] Added New StratCon Scenario Generation Utility Method Extracted repeated logic into helper methods and consolidated map location imports. Enhanced clarity and maintainability by introducing generateExternalScenario and finalizeBackingScenario methods. --- .../mekhq/campaign/stratcon/StratconContractInitializer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MekHQ/src/mekhq/campaign/stratcon/StratconContractInitializer.java b/MekHQ/src/mekhq/campaign/stratcon/StratconContractInitializer.java index 53da4fce77..0791042380 100644 --- a/MekHQ/src/mekhq/campaign/stratcon/StratconContractInitializer.java +++ b/MekHQ/src/mekhq/campaign/stratcon/StratconContractInitializer.java @@ -376,7 +376,7 @@ private static void initializeObjectiveScenarios(Campaign campaign, AtBContract * Utility function that, given a track state, picks a random set of unoccupied * coordinates. */ - private static StratconCoords getUnoccupiedCoords(StratconTrackState trackState) { + public static StratconCoords getUnoccupiedCoords(StratconTrackState trackState) { // plonk int x = Compute.randomInt(trackState.getWidth()); int y = Compute.randomInt(trackState.getHeight()); From 8392a3850fe5d578dfbbab68fabba9e51c7db866 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 27 Oct 2024 23:31:48 -0500 Subject: [PATCH 09/17] Fix scenario generation when no tracks are available Replaced tracks.size() > 0 with tracks.isEmpty() to improve readability. Added a return statement to abort scenario generation if no tracks are available, preventing potential errors. --- MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java index 17fb76f8c5..0d389bcce4 100644 --- a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java +++ b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java @@ -210,10 +210,11 @@ public static void generateExternalScenario(Campaign campaign, AtBContract contr List tracks = contract.getStratconCampaignState().getTracks(); Random rand = new Random(); - if (tracks.size() > 0) { + if (!tracks.isEmpty()) { track = tracks.get(rand.nextInt(tracks.size())); } else { logger.error("No tracks available. Aborting scenario generation."); + return; } } From 5221b61460ddab3d982ac0c958ae639a949f5307 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 27 Oct 2024 23:46:25 -0500 Subject: [PATCH 10/17] Fix Track Calculation in `StratconContractInitializer` Changed minimum number of tracks from 1 to 0 when initializing contracts. This adjustment ensures that the number of tracks properly reflects contracts requiring fewer lances, preventing erroneous excess tracks. --- .../mekhq/campaign/stratcon/StratconContractInitializer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MekHQ/src/mekhq/campaign/stratcon/StratconContractInitializer.java b/MekHQ/src/mekhq/campaign/stratcon/StratconContractInitializer.java index 53da4fce77..c4475400c9 100644 --- a/MekHQ/src/mekhq/campaign/stratcon/StratconContractInitializer.java +++ b/MekHQ/src/mekhq/campaign/stratcon/StratconContractInitializer.java @@ -68,7 +68,7 @@ public static void initializeCampaignState(AtBContract contract, Campaign campai // scenarios // when objective is allied/hostile facility, place those facilities - int numTracks = Math.max(1, contract.getRequiredLances() / NUM_LANCES_PER_TRACK); + int numTracks = Math.max(0, contract.getRequiredLances() / NUM_LANCES_PER_TRACK); int planetaryTemperature = campaign.getLocation().getPlanet().getTemperature(campaign.getLocalDate()); for (int x = 0; x < numTracks; x++) { From a8e0e44d7f9832d2aa1ab0f03856db0df39f7ee8 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 27 Oct 2024 23:48:10 -0500 Subject: [PATCH 11/17] Refactored loop variable names in StratconContractInitializer Renamed `numTracks` to `maximumTrackIndex` for clarity and changed the loop variable `x < numTracks` to `x < maximumTrackIndex`. This improves code readability and better reflects the purpose of the variables. --- .../mekhq/campaign/stratcon/StratconContractInitializer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/stratcon/StratconContractInitializer.java b/MekHQ/src/mekhq/campaign/stratcon/StratconContractInitializer.java index c4475400c9..b7bd3540f8 100644 --- a/MekHQ/src/mekhq/campaign/stratcon/StratconContractInitializer.java +++ b/MekHQ/src/mekhq/campaign/stratcon/StratconContractInitializer.java @@ -68,10 +68,10 @@ public static void initializeCampaignState(AtBContract contract, Campaign campai // scenarios // when objective is allied/hostile facility, place those facilities - int numTracks = Math.max(0, contract.getRequiredLances() / NUM_LANCES_PER_TRACK); + int maximumTrackIndex = Math.max(0, contract.getRequiredLances() / NUM_LANCES_PER_TRACK); int planetaryTemperature = campaign.getLocation().getPlanet().getTemperature(campaign.getLocalDate()); - for (int x = 0; x < numTracks; x++) { + for (int x = 0; x < maximumTrackIndex; x++) { int scenarioOdds = contractDefinition.getScenarioOdds() .get(Compute.randomInt(contractDefinition.getScenarioOdds().size())); int deploymentTime = contractDefinition.getDeploymentTimes() From cbff0b0dc3dec640d0658404e6dfee1bd0a4d1c6 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Mon, 28 Oct 2024 01:02:20 -0500 Subject: [PATCH 12/17] Refactored difficulty multiplier application for enemy forces Moved difficulty multiplier application exclusively to enemy forces. Removed redundant multiplier from player unit and BV budgets and added detailed logging for generated forces. --- .../mission/AtBDynamicScenarioFactory.java | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java index de15f5ff9e..ed258982d6 100644 --- a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java +++ b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java @@ -272,6 +272,7 @@ private static int generateForces(AtBDynamicScenario scenario, AtBContract contr // how close to the allowances do we want to get? int targetPercentage = 100 + ((Compute.randomInt(8) - 3) * 5); logger.info(String.format("Target Percentage: %s", targetPercentage)); + logger.info(String.format("Difficulty Multiplier: %s", getDifficultyMultiplier(campaign))); for (int generationOrder : generationOrders) { List currentForceTemplates = orderedForceTemplates.get(generationOrder); @@ -283,10 +284,8 @@ private static int generateForces(AtBDynamicScenario scenario, AtBContract contr generatedLanceCount += generateFixedForce(scenario, contract, campaign, forceTemplate); } else { int weightClass = randomForceWeight(); + logger.info(String.format("++ Generating a force for the %s template ++", forceTemplate.getForceName()).toUpperCase()); - if (forceTemplate.isPlayerForce()) { - logger.info(String.format("++ Generating a force for the %s template ++", forceTemplate.getForceName()).toUpperCase()); - } generatedLanceCount += generateForce(scenario, contract, campaign, effectiveBV, effectiveUnitCount, weightClass, forceTemplate, false); } @@ -390,11 +389,15 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac break; case Opposing: factionCode = contract.getEnemyCode(); + + // We only want the difficulty multipliers applying to enemy forces + double difficultyMultiplier = getDifficultyMultiplier(campaign); + effectiveBV = (int) round(effectiveBV * difficultyMultiplier); + effectiveUnitCount = (int) round(effectiveUnitCount * difficultyMultiplier); + // Intentional fall-through: opposing third parties are either the contracted - // enemy or - // "Unidentified Hostiles" which are considered pirates or bandit caste with - // random - // quality and skill + // enemy or "Unidentified Hostiles" which are considered pirates or bandit caste + // with random quality and skill case Third: skill = scenario.getEffectiveOpforSkill(); quality = scenario.getEffectiveOpforQuality(); @@ -2689,7 +2692,6 @@ public static int calculateEffectiveBV(AtBDynamicScenario scenario, Campaign cam // for each deployed player and bot force that's marked as contributing to the // BV budget int bvBudget = 0; - double difficultyMultiplier = getDifficultyMultiplier(campaign); String generationMethod = campaign.getCampaignOptions().isUseGenericBattleValue() ? "Generic BV" : "BV2"; @@ -2720,9 +2722,7 @@ public static int calculateEffectiveBV(AtBDynamicScenario scenario, Campaign cam double bvMultiplier = scenario.getEffectivePlayerBVMultiplier(); if (bvMultiplier > 0) { - bvBudget = (int) round(bvBudget * scenario.getEffectivePlayerBVMultiplier() * difficultyMultiplier); - } else { - bvBudget = (int) round(bvBudget * difficultyMultiplier); + bvBudget = (int) round(bvBudget * scenario.getEffectivePlayerBVMultiplier()); } // allied bot forces that contribute to BV do not get multiplied by the @@ -2756,7 +2756,6 @@ public static int calculateEffectiveUnitCount(AtBDynamicScenario scenario, Campa // for each deployed player and bot force that's marked as contributing to the // unit count budget int unitCount = 0; - double difficultyMultiplier = getDifficultyMultiplier(campaign); // deployed player forces: for (int forceID : scenario.getForceIDs()) { @@ -2776,9 +2775,6 @@ public static int calculateEffectiveUnitCount(AtBDynamicScenario scenario, Campa } } - // the player unit count is now multiplied by the difficulty multiplier - 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 good, the AI doesn't get any better for (int index = 0; index < scenario.getNumBots(); index++) { From ad68dbf4314b7d7a3acd30477d1196beb2f294e0 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Mon, 28 Oct 2024 01:03:12 -0500 Subject: [PATCH 13/17] Change generation methods in scenario templates Updated the generationMethod from 2 to 1 across several scenario template XML files. This adjustment ensures consistency in the force generation approach for Convoy and Irregular Force scenarios. --- MekHQ/data/scenariotemplates/Convoy Escort.xml | 2 +- MekHQ/data/scenariotemplates/Convoy Interdiction.xml | 2 +- MekHQ/data/scenariotemplates/Convoy Raid.xml | 2 +- MekHQ/data/scenariotemplates/Critical Convoy Escort.xml | 2 +- MekHQ/data/scenariotemplates/Irregular Force Assault.xml | 4 ++-- MekHQ/data/scenariotemplates/Irregular Force Suppression.xml | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/MekHQ/data/scenariotemplates/Convoy Escort.xml b/MekHQ/data/scenariotemplates/Convoy Escort.xml index 2eb413cf55..1fbd7b5757 100644 --- a/MekHQ/data/scenariotemplates/Convoy Escort.xml +++ b/MekHQ/data/scenariotemplates/Convoy Escort.xml @@ -99,7 +99,7 @@ 1 0.5 Convoy - 2 + 1 4 4 1 diff --git a/MekHQ/data/scenariotemplates/Convoy Interdiction.xml b/MekHQ/data/scenariotemplates/Convoy Interdiction.xml index a1d69f9ab1..7e231b2008 100644 --- a/MekHQ/data/scenariotemplates/Convoy Interdiction.xml +++ b/MekHQ/data/scenariotemplates/Convoy Interdiction.xml @@ -95,7 +95,7 @@ 2 0.5 Convoy - 2 + 1 5 4 1 diff --git a/MekHQ/data/scenariotemplates/Convoy Raid.xml b/MekHQ/data/scenariotemplates/Convoy Raid.xml index 694df4d383..8d9653acde 100644 --- a/MekHQ/data/scenariotemplates/Convoy Raid.xml +++ b/MekHQ/data/scenariotemplates/Convoy Raid.xml @@ -95,7 +95,7 @@ 2 0.5 Convoy - 2 + 1 5 4 1 diff --git a/MekHQ/data/scenariotemplates/Critical Convoy Escort.xml b/MekHQ/data/scenariotemplates/Critical Convoy Escort.xml index a27cb2be94..dc428b31ab 100644 --- a/MekHQ/data/scenariotemplates/Critical Convoy Escort.xml +++ b/MekHQ/data/scenariotemplates/Critical Convoy Escort.xml @@ -99,7 +99,7 @@ 1 0.5 Convoy - 2 + 1 4 4 1 diff --git a/MekHQ/data/scenariotemplates/Irregular Force Assault.xml b/MekHQ/data/scenariotemplates/Irregular Force Assault.xml index 32bd6d1cf3..dd580c0567 100644 --- a/MekHQ/data/scenariotemplates/Irregular Force Assault.xml +++ b/MekHQ/data/scenariotemplates/Irregular Force Assault.xml @@ -109,7 +109,7 @@ 2 1.0 Infantry - 2 + 1 5 4 0 @@ -140,7 +140,7 @@ 2 1.0 Irregulars - 2 + 1 5 4 0 diff --git a/MekHQ/data/scenariotemplates/Irregular Force Suppression.xml b/MekHQ/data/scenariotemplates/Irregular Force Suppression.xml index a5c03ea141..888e0afc3d 100644 --- a/MekHQ/data/scenariotemplates/Irregular Force Suppression.xml +++ b/MekHQ/data/scenariotemplates/Irregular Force Suppression.xml @@ -79,7 +79,7 @@ 2 1.0 Infantry - 2 + 1 5 4 0 @@ -110,7 +110,7 @@ 2 1.0 Irregulars - 2 + 1 5 4 0 From 98d953e1944cec367b19779f592cfdeabf41d1b3 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Mon, 28 Oct 2024 02:54:06 -0500 Subject: [PATCH 14/17] Set default state of loot checkboxes to selected Previously, loot checkboxes were initialized as unselected. Now, they are set to be selected by default, and loot is added to the tracker only if the scenario's overall status is marked as a victory. --- .../gui/dialog/ResolveScenarioWizardDialog.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/MekHQ/src/mekhq/gui/dialog/ResolveScenarioWizardDialog.java b/MekHQ/src/mekhq/gui/dialog/ResolveScenarioWizardDialog.java index 6df2520bd3..4d7cc8209d 100644 --- a/MekHQ/src/mekhq/gui/dialog/ResolveScenarioWizardDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/ResolveScenarioWizardDialog.java @@ -782,7 +782,7 @@ private void initComponents() { for (Loot loot : loots) { j++; JCheckBox box = new JCheckBox(loot.getShortDescription()); - box.setSelected(false); + box.setSelected(true); lootBoxes.add(box); gridBagConstraints = new GridBagConstraints(); gridBagConstraints.gridx = 0; @@ -1394,10 +1394,12 @@ private void finish() { tracker.assignKills(); //now get loot - for (int i = 0; i < lootBoxes.size(); i++) { - JCheckBox box = lootBoxes.get(i); - if (box.isSelected()) { - tracker.addLoot(loots.get(i)); + if (((ScenarioStatus) Objects.requireNonNull(choiceStatus.getSelectedItem())).isOverallVictory()) { + for (int i = 0; i < lootBoxes.size(); i++) { + JCheckBox box = lootBoxes.get(i); + if (box.isSelected()) { + tracker.addLoot(loots.get(i)); + } } } From 19f8d5dacaf3f73a4efc858227e8163f6a8b4942 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Mon, 28 Oct 2024 02:58:13 -0500 Subject: [PATCH 15/17] Refactored `JScrollPaneWithSpeed` Constructor Changed the `JScrollPaneWithSpeed` constructor to take a null argument for the viewport. This was done to ensure proper initialization of the scrolling pane. --- MekHQ/src/mekhq/gui/utilities/JScrollPaneWithSpeed.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/MekHQ/src/mekhq/gui/utilities/JScrollPaneWithSpeed.java b/MekHQ/src/mekhq/gui/utilities/JScrollPaneWithSpeed.java index ee591abc66..984a0cba91 100644 --- a/MekHQ/src/mekhq/gui/utilities/JScrollPaneWithSpeed.java +++ b/MekHQ/src/mekhq/gui/utilities/JScrollPaneWithSpeed.java @@ -18,11 +18,10 @@ */ package mekhq.gui.utilities; -import java.awt.*; +import megamek.client.ui.swing.GUIPreferences; import javax.swing.*; - -import megamek.client.ui.swing.GUIPreferences; +import java.awt.*; /** * It's a JScrollPane that manages its scrollspeed based on the UI scale @@ -34,7 +33,7 @@ public class JScrollPaneWithSpeed extends JScrollPane { * @see JPanel#JPanel() */ public JScrollPaneWithSpeed() { - super(); + super(null); setScaleIncrement(); } From 03e4457ba28f9c472861f5c55af3303d366a0e74 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Mon, 28 Oct 2024 20:46:32 -0500 Subject: [PATCH 16/17] Add MekSearchFilter import to MekHQUnitSelectorDialog Integrated the MekSearchFilter import to support advanced unit searching capabilities. This update is part of an enhancement for improved search functionality within the MekHQ unit selection dialog. --- MekHQ/src/mekhq/gui/dialog/MekHQUnitSelectorDialog.java | 1 + 1 file changed, 1 insertion(+) diff --git a/MekHQ/src/mekhq/gui/dialog/MekHQUnitSelectorDialog.java b/MekHQ/src/mekhq/gui/dialog/MekHQUnitSelectorDialog.java index d8c39d43b1..be4e1940c1 100644 --- a/MekHQ/src/mekhq/gui/dialog/MekHQUnitSelectorDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/MekHQUnitSelectorDialog.java @@ -19,6 +19,7 @@ package mekhq.gui.dialog; import megamek.client.ui.Messages; +import megamek.client.ui.advancedsearch.MekSearchFilter; import megamek.client.ui.swing.UnitLoadingDialog; import megamek.client.ui.swing.dialog.AbstractUnitSelectorDialog; import megamek.common.*; From 16ba8e23385767c6e94611e2606b73dc08fedc1f Mon Sep 17 00:00:00 2001 From: HammerGS Date: Mon, 28 Oct 2024 20:02:15 -0600 Subject: [PATCH 17/17] History update. --- MekHQ/docs/history.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/MekHQ/docs/history.txt b/MekHQ/docs/history.txt index fb83312316..80a165567d 100644 --- a/MekHQ/docs/history.txt +++ b/MekHQ/docs/history.txt @@ -161,6 +161,16 @@ MEKHQ VERSION HISTORY: + PR #5121: Label OmniUnits as Omni in the MekHQ UI + Fix #5119 && #5108 && #4297 Fixed Position of NewDayEvent Trigger in processNewDay + PR #5126: Rebalanced Random Unit Quality ++ Fix #5131: Fixed Faction Conflict Checks for Reeducation Camps ++ PR #5137: Stopped Scenarios Pulling Units from the Hangar ++ Fix #5139: Refactored Field Kitchen Personnel Count Logic ++ PR #5141: Handled StratCon Scenario Placement Failure when All Coordinates Occupied ++ PR #5142: Added New StratCon Scenario Generation Utility Method ++ PR #5143: Fix Track Calculation in StratconContractInitializer ++ PR #5144: [FG3] Adjusted Generation Method in Some Scenarios ++ PR #5145: [FG3] Refactored Difficulty Multiplier Application ++ PR #5146: Adjusted Scenario Loot Box Behavior ++ PR #5147: Refactored JScrollPaneWithSpeed Constructor 0.50.0 (2024-09-01 2000 UTC) (THIS MARKS THE START OF JAVA 17 AS THE MINIMUM REQUIRED)