diff --git a/MekHQ/data/scenariotemplates/Low-Atmosphere DropShip Escort.xml b/MekHQ/data/scenariotemplates/Low-Atmosphere DropShip Escort.xml
index fd561de952..5e311109a4 100644
--- a/MekHQ/data/scenariotemplates/Low-Atmosphere DropShip Escort.xml
+++ b/MekHQ/data/scenariotemplates/Low-Atmosphere DropShip Escort.xml
@@ -156,7 +156,7 @@
5
0
2
- 1.0
+ 2.0
OpFor
1
5
@@ -178,9 +178,9 @@
11
0
false
- true
+ false
true
- true
+ false
false
6
diff --git a/MekHQ/data/scenariotemplates/Space Blockade Run.xml b/MekHQ/data/scenariotemplates/Space Blockade Run.xml
index 58c9816f07..df46580fda 100644
--- a/MekHQ/data/scenariotemplates/Space Blockade Run.xml
+++ b/MekHQ/data/scenariotemplates/Space Blockade Run.xml
@@ -195,7 +195,7 @@
5
1
2
- 1.0
+ 2.0
OpFor
1
5
diff --git a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java
index e3466b8573..677bb23b50 100644
--- a/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java
+++ b/MekHQ/src/mekhq/campaign/mission/AtBDynamicScenarioFactory.java
@@ -68,8 +68,11 @@
import static java.lang.Math.round;
import static megamek.client.ratgenerator.MissionRole.CIVILIAN;
+import static megamek.common.Compute.randomInt;
+import static megamek.common.UnitType.*;
import static megamek.common.planetaryconditions.Wind.TORNADO_F4;
import static mekhq.campaign.mission.Scenario.T_GROUND;
+import static mekhq.campaign.mission.ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_AERO_MIX;
import static mekhq.campaign.mission.ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_CIVILIANS;
import static mekhq.campaign.mission.ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_MIX;
@@ -165,7 +168,7 @@ public static AtBDynamicScenario initializeScenarioFromTemplate(ScenarioTemplate
if (template.mapParameters.getMapLocation() == MapLocation.LowAtmosphere) {
defaultReinforcements.setAllowedUnitType(ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_AERO_MIX);
} else if (template.mapParameters.getMapLocation() == MapLocation.Space) {
- defaultReinforcements.setAllowedUnitType(UnitType.AEROSPACEFIGHTER);
+ defaultReinforcements.setAllowedUnitType(AEROSPACEFIGHTER);
}
template.getScenarioForces().put(defaultReinforcements.getForceName(), defaultReinforcements);
@@ -270,7 +273,7 @@ private static int generateForces(AtBDynamicScenario scenario, AtBContract contr
// recalculate effective BV and unit count each time we change levels
// how close to the allowances do we want to get?
- int targetPercentage = 100 + ((Compute.randomInt(8) - 3) * 5);
+ int targetPercentage = 100 + ((randomInt(8) - 3) * 5);
logger.info(String.format("Target Percentage: %s", targetPercentage));
logger.info(String.format("Difficulty Multiplier: %s", getDifficultyMultiplier(campaign)));
@@ -410,7 +413,7 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac
factionCode = "PIR";
}
- int randomInt = Compute.randomInt(6);
+ int randomInt = randomInt(6);
skill = switch (randomInt) {
case 1, 2, 3 -> SkillLevel.REGULAR;
@@ -442,7 +445,7 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac
int forceBV = 0;
double forceMultiplier = forceTemplate.getForceMultiplier();
- if (forceTemplate.getForceMultiplier() != 1) {
+ if (forceMultiplier != 1) {
logger.info(String.format("Force BV Multiplier: %s (from scenario template)", forceMultiplier));
}
@@ -522,14 +525,14 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac
if (!baseRoles.isEmpty()) {
if (forceTemplate.getAllowedUnitType() == SPECIAL_UNIT_TYPE_ATB_MIX) {
- requiredRoles.put(UnitType.MEK, new ArrayList<>(baseRoles));
- requiredRoles.put(UnitType.TANK, new ArrayList<>(baseRoles));
+ requiredRoles.put(MEK, new ArrayList<>(baseRoles));
+ requiredRoles.put(TANK, new ArrayList<>(baseRoles));
} else if (forceTemplate.getAllowedUnitType() == ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_AERO_MIX) {
- requiredRoles.put(UnitType.CONV_FIGHTER, new ArrayList<>(baseRoles));
- requiredRoles.put(UnitType.AEROSPACEFIGHTER, new ArrayList<>(baseRoles));
+ requiredRoles.put(CONV_FIGHTER, new ArrayList<>(baseRoles));
+ requiredRoles.put(AEROSPACEFIGHTER, new ArrayList<>(baseRoles));
} else if (forceTemplate.getAllowedUnitType() == SPECIAL_UNIT_TYPE_ATB_CIVILIANS) {
// TODO: this will need to be adjusted to cover SUPPORT and CIVILIAN separately
- for (int i = 0; i <= UnitType.AERO; i++) {
+ for (int i = 0; i <= AERO; i++) {
if (CIVILIAN.fitsUnitType(i)) {
requiredRoles.put(i, new ArrayList<>(baseRoles));
}
@@ -547,10 +550,10 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac
} else {
infantryRoles.add(MissionRole.XCT);
}
- if (requiredRoles.containsKey(UnitType.INFANTRY)) {
- requiredRoles.get(UnitType.INFANTRY).addAll(infantryRoles);
+ if (requiredRoles.containsKey(INFANTRY)) {
+ requiredRoles.get(INFANTRY).addAll(infantryRoles);
} else {
- requiredRoles.put(UnitType.INFANTRY, infantryRoles);
+ requiredRoles.put(INFANTRY, infantryRoles);
}
}
@@ -559,23 +562,23 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac
if (forceTemplate.getUseArtillery()) {
int artilleryCarriers = forceTemplate.getAllowedUnitType();
- if (artilleryCarriers == SPECIAL_UNIT_TYPE_ATB_MIX || artilleryCarriers == UnitType.MEK) {
- if (!requiredRoles.containsKey(UnitType.MEK)) {
- requiredRoles.put(UnitType.MEK, new HashSet<>());
+ if (artilleryCarriers == SPECIAL_UNIT_TYPE_ATB_MIX || artilleryCarriers == MEK) {
+ if (!requiredRoles.containsKey(MEK)) {
+ requiredRoles.put(MEK, new HashSet<>());
}
- requiredRoles.get(UnitType.MEK).add((MissionRole.ARTILLERY));
+ requiredRoles.get(MEK).add((MissionRole.ARTILLERY));
}
- if (artilleryCarriers == SPECIAL_UNIT_TYPE_ATB_MIX || artilleryCarriers == UnitType.TANK) {
- if (!requiredRoles.containsKey(UnitType.TANK)) {
- requiredRoles.put(UnitType.TANK, new HashSet<>());
+ if (artilleryCarriers == SPECIAL_UNIT_TYPE_ATB_MIX || artilleryCarriers == TANK) {
+ if (!requiredRoles.containsKey(TANK)) {
+ requiredRoles.put(TANK, new HashSet<>());
}
- requiredRoles.get(UnitType.TANK).add((MissionRole.ARTILLERY));
+ requiredRoles.get(TANK).add((MissionRole.ARTILLERY));
}
- if (artilleryCarriers == UnitType.INFANTRY) {
- if (!requiredRoles.containsKey(UnitType.INFANTRY)) {
- requiredRoles.put(UnitType.INFANTRY, new HashSet<>());
+ if (artilleryCarriers == INFANTRY) {
+ if (!requiredRoles.containsKey(INFANTRY)) {
+ requiredRoles.put(INFANTRY, new HashSet<>());
}
- requiredRoles.get(UnitType.INFANTRY).add((MissionRole.ARTILLERY));
+ requiredRoles.get(INFANTRY).add((MissionRole.ARTILLERY));
}
}
@@ -595,50 +598,46 @@ 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)) {
- actualUnitType = Compute.d6() > 3 ? UnitType.AEROSPACEFIGHTER : UnitType.CONV_FIGHTER;
- lanceSize = getAeroLanceSize(actualUnitType, isPlanetOwner, factionCode);
- } else if (actualUnitType == ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_AERO_MIX) {
- actualUnitType = UnitType.AEROSPACEFIGHTER;
- lanceSize = getAeroLanceSize(actualUnitType, isPlanetOwner, factionCode);
- }
-
// If there are no weight classes available, something went wrong so don't
// bother trying to generate units
if (currentLanceWeightString == null) {
generatedLance = new ArrayList<>();
} else {
// Hazardous conditions may prohibit deploying infantry or vehicles
- if ((actualUnitType == UnitType.INFANTRY && !allowsConvInfantry)
- || (actualUnitType == UnitType.BATTLE_ARMOR && !allowsBattleArmor)) {
+ if ((actualUnitType == INFANTRY && !allowsConvInfantry)
+ || (actualUnitType == BATTLE_ARMOR && !allowsBattleArmor)) {
logger.warn("Unable to generate Infantry due to hostile conditions." +
" Switching to Tank.");
- actualUnitType = UnitType.TANK;
+ actualUnitType = TANK;
}
- if (actualUnitType == UnitType.TANK && !allowsTanks) {
+ if (actualUnitType == TANK && !allowsTanks) {
logger.warn("Unable to generate Tank due to hostile conditions." +
" Switching to Mek.");
- actualUnitType = UnitType.MEK;
+ actualUnitType = MEK;
}
// Gun emplacements use fixed tables instead of the force generator system
- if (actualUnitType == UnitType.GUN_EMPLACEMENT) {
+ if (actualUnitType == GUN_EMPLACEMENT) {
generatedLance = generateTurrets(4, skill, quality, campaign, faction);
// All other unit types use the force generator system to randomly select units
} else {
+ boolean allowConventionalAircraft = isPlanetOwner
+ && actualUnitType == ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_AERO_MIX
+ && scenario.getTemplate().mapParameters.getMapLocation() != MapLocation.Space
+ && scenario.getAtmosphere().isDenserThan(Atmosphere.THIN);
+
+ if (actualUnitType == ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_AERO_MIX) {
+ lanceSize = getAeroLanceSize(faction);
+ }
+
// 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.
+ // a Mek/vehicle mixed formation. Similarly, SPECIAL_UNIT_TYPE_ATB_AERO_MIX may
+ // generate all Aerospace Fighters, Conventional Fighters, or a mixed formation.
List unitTypes = generateUnitTypes(actualUnitType, lanceSize, quality,
- factionCode, allowsTanks, campaign);
+ factionCode, allowsTanks, allowConventionalAircraft, campaign);
// Formations composed entirely of Meks, aerospace fighters (but not conventional),
// and ground vehicles use weight categories as do SPECIAL_UNIT_TYPE_ATB_MIX.
@@ -672,7 +671,7 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac
// 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) {
+ if (actualUnitType == INFANTRY) {
for (Entity curPlatoon : generatedLance) {
changeInfantryKit((Infantry) curPlatoon, isLowPressure, isTainted, scenario.getTemperature());
}
@@ -707,7 +706,7 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac
case Third:
// Slight hack, assume "Unidentified Hostiles" are pirates with variable
// quality
- ownerBaseQuality = Compute.randomInt(3);
+ ownerBaseQuality = randomInt(3);
isPirate = forceTemplate.getForceName().toLowerCase().contains("unidentified");
break;
default:
@@ -754,13 +753,17 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac
// Add the formation member BVs to the running total, and the entities to the tracking
// list
- for (Entity ent : generatedLance) {
+ for (Entity entity : generatedLance) {
+ int individualBV;
+
if (campaign.getCampaignOptions().isUseGenericBattleValue()) {
- forceBV += ent.getGenericBattleValue();
+ individualBV = entity.getGenericBattleValue();
} else {
- forceBV += ent.calculateBattleValue();
+ individualBV = entity.calculateBattleValue();
}
- generatedEntities.add(ent);
+
+ forceBV += individualBV;
+ generatedEntities.add(entity);
}
// Terminate force generation if we've gone over the unit count or BV budget.
@@ -782,39 +785,68 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac
// 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());
+ int targetUnit = randomInt(generatedEntities.size());
generatedEntities.remove(targetUnit);
}
if (forceTemplate.getGenerationMethod() == ForceGenerationMethod.BVScaled.ordinal()) {
String balancingType = "";
+
if (campaign.getCampaignOptions().isUseGenericBattleValue()) {
balancingType = " Generic";
}
+
logger.info(String.format("%s generated a force with %s / %s %s BV",
forceTemplate.getForceName(), forceBV, forceBVBudget, balancingType));
- int adjustedBvBudget = (int) (forceBVBudget * 1.25);
+ if ((forceBV > forceBVBudget) && generatedEntities.size() != 1) {
+ List culledEntities = new ArrayList<>();
+ Collections.shuffle(generatedEntities);
- while ((forceBV > adjustedBvBudget) && (generatedEntities.size() > 1)) {
- int targetUnit = Compute.randomInt(generatedEntities.size());
+ forceBV = 0;
- int battleValue;
- if (campaign.getCampaignOptions().isUseGenericBattleValue()) {
- battleValue = generatedEntities.get(targetUnit).getGenericBattleValue();
- } else {
- battleValue = generatedEntities.get(targetUnit).calculateBattleValue();
+ for (Entity entity : generatedEntities) {
+ int battleValue;
+
+ if (campaign.getCampaignOptions().isUseGenericBattleValue()) {
+ battleValue = entity.getGenericBattleValue();
+ } else {
+ battleValue = entity.calculateBattleValue();
+ }
+
+ if ((forceBV + battleValue) > forceBVBudget) {
+ culledEntities.add(entity);
+
+ logger.info(String.format("Culled %s (%s %s BV)",
+ entity.getDisplayName(), battleValue, balancingType));
+
+ continue;
+ }
+
+ forceBV += battleValue;
}
- forceBV -= battleValue;
+ if (generatedEntities.isEmpty()) {
+ Entity entity = culledEntities.get(0);
- logger.info(String.format("Culled %s (%s %s BV)",
- generatedEntities.get(targetUnit).getDisplayName(), battleValue, balancingType));
+ int battleValue;
+ if (campaign.getCampaignOptions().isUseGenericBattleValue()) {
+ battleValue = entity.getGenericBattleValue();
+ } else {
+ battleValue = entity.calculateBattleValue();
+ }
+
+ logger.info(String.format("Ended up with an empty force, restoring %s (%s %s BV)",
+ entity.getDisplayName(), battleValue, balancingType));
- generatedEntities.remove(targetUnit);
+ culledEntities.remove(0);
+ }
+
+ generatedEntities.removeAll(culledEntities);
}
- logger.info(String.format("Final force %s / %s %s BV (may exceed by *1.25)",
+
+ logger.info(String.format("Final force %s / %s %s BV",
forceBV, forceBVBudget, balancingType));
}
@@ -825,7 +857,7 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac
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()) {
+ for (Entity curPlatoon : transportedEntities.stream().filter(i -> i.getUnitType() == INFANTRY).toList()) {
changeInfantryKit((Infantry) curPlatoon, isLowPressure, isTainted, scenario.getTemperature());
}
}
@@ -844,7 +876,7 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac
&& BatchallFactions.usesBatchalls(factionCode)
&& contract.isBatchallAccepted()) {
// Simulate bidding away of forces
- List bidAwayForces = new ArrayList<>();
+ List bidAwayForces = new ArrayList<>();
int supplementedForces = 0;
if (generatedForce.getTeam() != 1
@@ -852,74 +884,82 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac
&& BatchallFactions.usesBatchalls(factionCode)
&& contract.isBatchallAccepted()) {
- // Add dialog
- bidAwayForces = new ArrayList<>();
-
// Player force values
int playerBattleValue = calculateEffectiveBV(scenario, campaign, true);
int playerUnitValue = calculateEffectiveUnitCount(scenario, campaign, true);
- // Bot force values
- int botBattleValue = 0;
- for (Entity entity : generatedForce.getFullEntityList(campaign)) {
- botBattleValue += entity.calculateBattleValue();
+ // First bid away units that exceed the player's estimated Battle Value
+ int targetBattleValue = (int) round(playerBattleValue * getHonorRating(campaign, factionCode));
+ int currentBattleValue = 0;
+
+ List entities = generatedForce.getFullEntityList(campaign);
+ Collections.shuffle(entities);
+
+ for (Entity entity : entities) {
+ int battleValue = entity.calculateBattleValue();
+ if ((currentBattleValue + battleValue) > targetBattleValue) {
+ bidAwayForces.add(entity);
+ continue;
+ }
+
+ currentBattleValue += battleValue;
}
- // First bid away units that exceed the player's estimated Battle Value
- 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();
- generatedForce.removeEntity(targetUnit);
- }
-
- // There is no point in adding extra Battle Armor to non-ground scenarios
- // Similarly, there is no point adding Battle Armor to scenarios they cannot survive in.
- if (scenario.getBoardType() == T_GROUND && scenario.getWind() != TORNADO_F4) {
- // 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;
- }
+ for (Entity entity : bidAwayForces) {
+ int entityIndex = entities.indexOf(entity);
+ generatedForce.removeEntity(entityIndex);
+ }
+
+ // We don't want to sub in Battle Armor for forces that are meant to only have a
+ // certain number of units.
+ if (forceTemplate.getGenerationMethod() != ForceGenerationMethod.FixedUnitCount.ordinal()) {
+ // There is no point in adding extra Battle Armor to non-ground scenarios
+ // Similarly, there is no point adding Battle Armor to scenarios they cannot survive in.
+ if (scenario.getBoardType() == T_GROUND && scenario.getWind() != TORNADO_F4) {
+ // 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),
+ List generatedBA = generateBAForNova(scenario, List.of(entity),
factionCode, skill, quality, campaign, true);
- if (!generatedBA.isEmpty()) {
- for (Entity battleArmor : generatedBA) {
- generatedForce.addEntity(battleArmor);
+ if (!generatedBA.isEmpty()) {
+ for (Entity battleArmor : generatedBA) {
+ generatedForce.addEntity(battleArmor);
+ }
+ supplementedForces += generatedBA.size();
+ sizeDisparity -= generatedBA.size();
}
- 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++;
+ // 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,
+ BATTLE_ARMOR, UNIT_WEIGHT_UNSPECIFIED, campaign);
+ if (newEntity != null) {
+ generatedForce.addEntity(newEntity);
+ supplementedForces++;
+ }
}
}
}
@@ -932,8 +972,8 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac
&& campaign.getCampaignOptions().isUseGenericBattleValue()
&& BatchallFactions.usesBatchalls(factionCode)
&& contract.isBatchallAccepted()) {
- reportResultsOfBidding(scenario, campaign, bidAwayForces, generatedForce,
- supplementedForces, factionCode);
+ reportResultsOfBidding(campaign, bidAwayForces, generatedForce, supplementedForces,
+ factionCode);
}
}
@@ -949,7 +989,7 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac
*/
private static double getHonorRating(Campaign campaign, String factionCode) {
// Our research showed the post-Invasion shift in Clan doctrine to occur between 3053 and 3055
- boolean isPostInvasion = campaign.getLocalDate().getYear() >= 3053 + Compute.randomInt(2);
+ boolean isPostInvasion = campaign.getLocalDate().getYear() >= 3053 + randomInt(2);
// 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
@@ -984,15 +1024,14 @@ private static double getHonorRating(Campaign campaign, String factionCode) {
/**
* 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, String factionCode) {
+ private static void reportResultsOfBidding(Campaign campaign, List bidAwayForces,
+ BotForce generatedForce, int supplementedForces,
+ String factionCode) {
double honor = getHonorRating(campaign, factionCode);
String honorLevel;
@@ -1012,12 +1051,12 @@ private static void reportResultsOfBidding(AtBDynamicScenario scenario, Campaign
StringBuilder report = new StringBuilder();
if (useVerboseBidding) {
- for (String unitName : bidAwayForces) {
+ for (Entity entity : bidAwayForces) {
if (report.isEmpty()) {
report.append(String.format(resources.getString("bidAwayForcesVerbose.text"),
- generatedForce.getName(), unitName));
+ generatedForce.getName(), entity.getFullChassis()));
} else {
- report.append(unitName).append("
");
+ report.append(entity.getFullChassis()).append("
");
}
}
} else {
@@ -1026,13 +1065,13 @@ private static void reportResultsOfBidding(AtBDynamicScenario scenario, Campaign
generatedForce.getName(), bidAwayForces.size(), bidAwayForces.size() > 1 ? "s" : ""));
boolean isUseLoggerHeader = true;
- for (String unitName : bidAwayForces) {
+ for (Entity entity : bidAwayForces) {
if (isUseLoggerHeader) {
logger.info(String.format(resources.getString("bidAwayForcesLogger.text"),
generatedForce.getName()));
isUseLoggerHeader = false;
}
- logger.info(unitName);
+ logger.info(String.format("%s, %s", entity.getFullChassis(), entity.getGenericBattleValue()));
}
}
}
@@ -1299,7 +1338,7 @@ public static void setTerrain(AtBDynamicScenario scenario) {
List allowedTerrain = biomeManifest.getTempMap(StratconBiomeManifest.TERRAN_BIOME)
.floorEntry(kelvinTemp).getValue().allowedTerrainTypes;
- int terrainIndex = Compute.randomInt(allowedTerrain.size());
+ int terrainIndex = randomInt(allowedTerrain.size());
scenario.setTerrainType(allowedTerrain.get(terrainIndex));
scenario.setMapFile();
} else if (scenario.getTemplate().mapParameters.getMapLocation() == MapLocation.Space) {
@@ -1325,7 +1364,7 @@ public static void setTerrain(AtBDynamicScenario scenario) {
allowedTemplate = !allowedTemplate.isEmpty() ? allowedTemplate
: scenario.getTemplate().mapParameters.allowedTerrainTypes;
- int terrainIndex = Compute.randomInt(allowedTemplate.size());
+ int terrainIndex = randomInt(allowedTemplate.size());
scenario.setTerrainType(scenario.getTemplate().mapParameters.allowedTerrainTypes.get(terrainIndex));
scenario.setMapFile();
}
@@ -1374,7 +1413,7 @@ public static void setScenarioMapSize(AtBDynamicScenario scenario) {
// if the template says to use standard AtB sizing, determine it randomly here
if (template.mapParameters.isUseStandardAtBSizing()) {
- int roll = Compute.randomInt(20) + 1;
+ int roll = randomInt(20) + 1;
if (roll < 6) {
mapSizeX = 20;
mapSizeY = 10;
@@ -1409,7 +1448,7 @@ public static void setScenarioMapSize(AtBDynamicScenario scenario) {
// 50/50 odds to rotate the map 90 degrees if specified.
if (template.mapParameters.isAllowRotation()) {
- int roll = Compute.randomInt(20) + 1;
+ int roll = randomInt(20) + 1;
if (roll <= 10) {
int swap = mapSizeX;
mapSizeX = mapSizeY;
@@ -1437,7 +1476,7 @@ public static void setScenarioModifiers(CampaignOptions campaignOptions, AtBDyna
if (modMax != 0) {
while (addMods) {
- if (Compute.randomInt(100) < modChance) {
+ if (randomInt(100) < modChance) {
numMods++;
if (numMods >= modMax) {
@@ -1545,9 +1584,9 @@ public static Entity getEntity(String faction,
}
// Vehicles and infantry require some additional processing
- if (unitType == UnitType.TANK) {
+ if (unitType == TANK) {
return getTankEntity(params, skill, campaign);
- } else if (unitType == UnitType.INFANTRY) {
+ } else if (unitType == INFANTRY) {
return getInfantryEntity(params, skill, true, campaign);
} else {
unitData = campaign.getUnitGenerator().generate(params);
@@ -1562,7 +1601,7 @@ public static Entity getEntity(String faction,
logger.warn(String.format("Unable to randomly generate %s %s with roles: %s." +
" Second chance generation also failed.",
EntityWeightClass.getClassName(params.getWeightClass()),
- UnitType.getTypeName(unitType),
+ getTypeName(unitType),
params.getMissionRoles().stream().map(Enum::name).collect(Collectors.joining(","))));
} else {
return secondChanceEntity;
@@ -1611,7 +1650,7 @@ public static Entity getTankEntity(UnitGeneratorParameters params,
if (!params.getMissionRoles().isEmpty()) {
logger.warn(String.format("Unable to randomly generate %s %s with roles: %s",
EntityWeightClass.getClassName(params.getWeightClass()),
- UnitType.getTypeName(UnitType.TANK),
+ getTypeName(TANK),
params.getMissionRoles().stream().map(Enum::name).collect(Collectors.joining(","))));
}
return null;
@@ -1662,7 +1701,7 @@ public static Entity getInfantryEntity(UnitGeneratorParameters params,
if (unitData == null) {
if (!params.getMissionRoles().isEmpty()) {
logger.warn(String.format("Unable to randomly generate %s with roles: %s",
- UnitType.getTypeName(UnitType.INFANTRY),
+ getTypeName(INFANTRY),
params.getMissionRoles().stream().map(Enum::name).collect(Collectors.joining(","))));
}
return null;
@@ -1749,10 +1788,10 @@ public static List fillTransports(AtBScenario scenario,
// Don't bother processing if various non-useful conditions are present
if (transports == null ||
transports.isEmpty() ||
- transports.stream().map(Entity::getUnitType).allMatch(curType -> curType != UnitType.TANK &&
- curType != UnitType.VTOL &&
- curType != UnitType.NAVAL &&
- curType != UnitType.CONV_FIGHTER)) {
+ transports.stream().map(Entity::getUnitType).allMatch(curType -> curType != TANK &&
+ curType != VTOL &&
+ curType != NAVAL &&
+ curType != CONV_FIGHTER)) {
return new ArrayList<>();
}
@@ -1762,16 +1801,16 @@ public static List fillTransports(AtBScenario scenario,
if (requiredRoles != null) {
- transportedRoles.put(UnitType.INFANTRY,
- requiredRoles.containsKey(UnitType.INFANTRY) ? new ArrayList<>(requiredRoles.get(UnitType.INFANTRY))
+ transportedRoles.put(INFANTRY,
+ requiredRoles.containsKey(INFANTRY) ? new ArrayList<>(requiredRoles.get(INFANTRY))
: new ArrayList<>());
- transportedRoles.get(UnitType.INFANTRY).remove((MissionRole.ARTILLERY));
+ transportedRoles.get(INFANTRY).remove((MissionRole.ARTILLERY));
- transportedRoles.put(UnitType.BATTLE_ARMOR,
- requiredRoles.containsKey(UnitType.BATTLE_ARMOR)
- ? new ArrayList<>(requiredRoles.get(UnitType.BATTLE_ARMOR))
+ transportedRoles.put(BATTLE_ARMOR,
+ requiredRoles.containsKey(BATTLE_ARMOR)
+ ? new ArrayList<>(requiredRoles.get(BATTLE_ARMOR))
: new ArrayList<>());
- transportedRoles.get(UnitType.BATTLE_ARMOR).remove((MissionRole.ARTILLERY));
+ transportedRoles.get(BATTLE_ARMOR).remove((MissionRole.ARTILLERY));
}
List transportedUnits = new ArrayList<>();
@@ -1784,10 +1823,10 @@ public static List fillTransports(AtBScenario scenario,
// Only check unit types that can have an infantry bay
for (Entity transport : transports) {
- if (IntStream.of(UnitType.TANK,
- UnitType.VTOL,
- UnitType.NAVAL,
- UnitType.CONV_FIGHTER).anyMatch(i -> transport.getUnitType() == i)) {
+ if (IntStream.of(TANK,
+ VTOL,
+ NAVAL,
+ CONV_FIGHTER).anyMatch(i -> transport.getUnitType() == i)) {
transportedUnits.addAll(fillTransport(scenario,
transport,
params,
@@ -1852,7 +1891,7 @@ private static List fillTransport(AtBScenario scenario,
// If a roll against the battle armor target number succeeds, try to generate a
// battle armor unit first
if (Compute.d6(2) >= infantryToBAUpgradeTNs[params.getQuality()]) {
- newParams.setMissionRoles(requiredRoles.getOrDefault(UnitType.BATTLE_ARMOR, new HashSet<>()));
+ newParams.setMissionRoles(requiredRoles.getOrDefault(BATTLE_ARMOR, new HashSet<>()));
transportedUnit = generateTransportedBAUnit(newParams, bayCapacity, skill, false, campaign);
// If the transporter has both bay space and is an omni unit, try to add a
@@ -1866,8 +1905,8 @@ private static List fillTransport(AtBScenario scenario,
// If a battle armor unit wasn't generated and conditions permit, try generating
// conventional infantry. Generate air assault infantry for VTOL transports.
if (transportedUnit == null && allowInfantry) {
- newParams.setMissionRoles(requiredRoles.getOrDefault(UnitType.INFANTRY, new HashSet<>()));
- if (transport.getUnitType() == UnitType.VTOL
+ newParams.setMissionRoles(requiredRoles.getOrDefault(INFANTRY, new HashSet<>()));
+ if (transport.getUnitType() == VTOL
&& !newParams.getMissionRoles().contains(MissionRole.XCT)) {
UnitGeneratorParameters paratrooperParams = newParams.clone();
paratrooperParams.addMissionRole(MissionRole.PARATROOPER);
@@ -1937,7 +1976,7 @@ private static Entity generateTransportedInfantryUnit(UnitGeneratorParameters pa
Campaign campaign) {
UnitGeneratorParameters newParams = params.clone();
- newParams.setUnitType(UnitType.INFANTRY);
+ newParams.setUnitType(INFANTRY);
MekSummary unitData;
boolean temporaryXCT = false;
UnitGeneratorParameters noXCTParams;
@@ -2043,7 +2082,7 @@ private static Entity generateTransportedBAUnit(UnitGeneratorParameters params,
}
UnitGeneratorParameters newParams = params.clone();
- newParams.setUnitType(UnitType.BATTLE_ARMOR);
+ newParams.setUnitType(BATTLE_ARMOR);
newParams.getMovementModes().addAll(IUnitGenerator.ALL_BATTLE_ARMOR_MODES);
@@ -2226,7 +2265,7 @@ private static Entity getEntityByName(String name, String factionCode, SkillLeve
Gender gender;
int nonBinaryDiceSize = campaign.getCampaignOptions().getNonBinaryDiceSize();
- if ((nonBinaryDiceSize > 0) && (Compute.randomInt(nonBinaryDiceSize) == 0)) {
+ if ((nonBinaryDiceSize > 0) && (randomInt(nonBinaryDiceSize) == 0)) {
gender = RandomGenderGenerator.generateOther();
} else {
gender = RandomGenderGenerator.generate();
@@ -2260,30 +2299,30 @@ private static Entity getEntityByName(String name, String factionCode, SkillLeve
if (faction.isClan() && (Compute.d6(2) > (6 - skill.ordinal() + skills[0] + skills[1]))) {
Phenotype phenotype = Phenotype.NONE;
switch (en.getUnitType()) {
- case UnitType.MEK:
+ case MEK:
phenotype = Phenotype.MEKWARRIOR;
break;
- case UnitType.TANK:
- case UnitType.VTOL:
+ case TANK:
+ case VTOL:
// The Vehicle Phenotype is unique to Clan Hell's Horses
if (faction.getShortName().equals("CHH")) {
phenotype = Phenotype.VEHICLE;
}
break;
- case UnitType.BATTLE_ARMOR:
+ case BATTLE_ARMOR:
phenotype = Phenotype.ELEMENTAL;
break;
- case UnitType.AEROSPACEFIGHTER:
- case UnitType.CONV_FIGHTER:
+ case AEROSPACEFIGHTER:
+ case CONV_FIGHTER:
phenotype = Phenotype.AEROSPACE;
break;
- case UnitType.PROTOMEK:
+ case PROTOMEK:
phenotype = Phenotype.PROTOMEK;
break;
- case UnitType.SMALL_CRAFT:
- case UnitType.DROPSHIP:
- case UnitType.JUMPSHIP:
- case UnitType.WARSHIP:
+ case SMALL_CRAFT:
+ case DROPSHIP:
+ case JUMPSHIP:
+ case WARSHIP:
// The Naval Phenotype is unique to Clan Snow Raven and the Raven Alliance
if (faction.getShortName().equals("CSR") || faction.getShortName().equals("RA")) {
phenotype = Phenotype.NAVAL;
@@ -2397,9 +2436,7 @@ private static String adjustWeightsForFaction(String weights, String faction) {
* II, or similar
* tactical formation.
* TODO: generate ProtoMek points when Clan mixed stars are called for
- * TODO: generate Clan mixed nova stars e.g. two points of Meks, two of
- * vehicles, one ProtoMek
- * point
+ * TODO: generate Clan mixed nova stars e.g. two points of Meks, two of vehicles, one ProtoMek point
*
* @param unitTypeCode The type of units to generate, also accepts
* SPECIAL_UNIT_TYPE_ATB_MIX for
@@ -2418,6 +2455,7 @@ private static List generateUnitTypes(int unitTypeCode,
int forceQuality,
String factionCode,
boolean allowTanks,
+ boolean allowConventionalAircraft,
Campaign campaign) {
List unitTypes = new ArrayList<>(unitCount);
int actualUnitType = unitTypeCode;
@@ -2429,8 +2467,7 @@ private static List generateUnitTypes(int unitTypeCode,
Faction faction = Factions.getInstance().getFaction(factionCode);
// If ground vehicles are permitted in general and by environmental conditions,
- // and
- // for Clans if this is a Clan faction, then use them. Otherwise, only use Meks.
+ // and for Clans if this is a Clan faction, then use them. Otherwise, only use Meks.
if (campaign.getCampaignOptions().isUseVehicles() &&
allowTanks &&
(!faction.isClan() ||
@@ -2471,28 +2508,40 @@ private static List generateUnitTypes(int unitTypeCode,
// Roll for unit types
if (totalWeight <= 0) {
- actualUnitType = UnitType.MEK;
+ for (int x = 0; x < unitCount; x++) {
+ unitTypes.addAll(checkForProtoMek(faction, campaign));
+ }
+
+ return unitTypes;
} else {
- int roll = Compute.randomInt(totalWeight);
+ int roll = randomInt(totalWeight);
if (roll < vehicleLanceWeight) {
- actualUnitType = UnitType.TANK;
- // Mixed units randomly select between Mek or ground vehicle
+ actualUnitType = TANK;
+ // Mixed units randomly select between Mek, ProtoMek, or ground vehicle
} else if (roll < vehicleLanceWeight + mixedLanceWeight) {
for (int x = 0; x < unitCount; x++) {
- boolean addTank = Compute.randomInt(2) == 0;
+ boolean addTank = randomInt(2) == 0;
if (addTank) {
- unitTypes.add(UnitType.TANK);
+ unitTypes.add(TANK);
} else {
- unitTypes.add(UnitType.MEK);
+ unitTypes.addAll(checkForProtoMek(faction, campaign));
}
}
return unitTypes;
} else {
- actualUnitType = UnitType.MEK;
+ for (int x = 0; x < unitCount; x++) {
+ unitTypes.addAll(checkForProtoMek(faction, campaign));
+ }
+
+ return unitTypes;
}
}
} else {
- actualUnitType = UnitType.MEK;
+ for (int x = 0; x < unitCount; x++) {
+ unitTypes.addAll(checkForProtoMek(faction, campaign));
+ }
+
+ return unitTypes;
}
} else if (unitTypeCode == SPECIAL_UNIT_TYPE_ATB_CIVILIANS) {
// Use the Vehicle/Mixed ratios from campaign options as weighted values for
@@ -2504,24 +2553,75 @@ private static List generateUnitTypes(int unitTypeCode,
// Roll for unit types
if (totalWeight <= 0) {
- actualUnitType = UnitType.TANK;
+ actualUnitType = TANK;
} else {
- int roll = Compute.randomInt(totalWeight);
+ int roll = randomInt(totalWeight);
if (roll < vehicleLanceWeight) {
- actualUnitType = UnitType.TANK;
+ actualUnitType = TANK;
// Mixed units randomly select between Mek or ground vehicle
} else {
for (int x = 0; x < unitCount; x++) {
- boolean addTank = Compute.randomInt(2) == 0;
+ boolean addTank = randomInt(2) == 0;
if (addTank) {
- unitTypes.add(UnitType.TANK);
+ unitTypes.add(TANK);
} else {
- unitTypes.add(UnitType.MEK);
+ unitTypes.add(MEK);
}
}
return unitTypes;
}
}
+ } else if (unitTypeCode == SPECIAL_UNIT_TYPE_ATB_AERO_MIX) {
+ Faction faction = Factions.getInstance().getFaction(factionCode);
+
+ if (campaign.getCampaignOptions().isUseVehicles() && allowConventionalAircraft
+ && (!faction.isClan() || (faction.isClan() && campaign.getCampaignOptions().isClanVehicles()))) {
+
+ // Use the Mek/Vehicle/Mixed ratios from campaign options as weighted values for
+ // random unit types.
+ // Then modify based on faction.
+ int aeroFlightWeight = campaign.getCampaignOptions().getOpForLanceTypeVehicles();
+ int mixedFlightWeight = campaign.getCampaignOptions().getOpForLanceTypeMixed();
+ int conventionalLanceWeight = campaign.getCampaignOptions().getOpForLanceTypeMeks();
+
+ if (faction.isClan()) {
+ aeroFlightWeight += 2;
+ mixedFlightWeight = 0;
+ conventionalLanceWeight = Math.max(0, conventionalLanceWeight - 2);
+ } else if (faction.isMinorPower() || faction.isPirate()) {
+ aeroFlightWeight = Math.max(0, aeroFlightWeight - 1);
+ mixedFlightWeight++;
+ conventionalLanceWeight++;
+ }
+
+ int totalWeight = aeroFlightWeight + mixedFlightWeight + conventionalLanceWeight;
+
+ // Roll for unit types
+ if (totalWeight <= 0) {
+ actualUnitType = AEROSPACEFIGHTER;
+ } else {
+ int roll = randomInt(totalWeight);
+
+ if (roll < conventionalLanceWeight) {
+ actualUnitType = CONV_FIGHTER;
+ // Mixed units randomly select between Aerospace or Conventional Fighter
+ } else if (roll < conventionalLanceWeight + mixedFlightWeight) {
+ for (int x = 0; x < unitCount; x++) {
+ boolean addConventional = randomInt(2) == 0;
+ if (addConventional) {
+ unitTypes.add(CONV_FIGHTER);
+ } else {
+ unitTypes.add(AEROSPACEFIGHTER);
+ }
+ }
+ return unitTypes;
+ } else {
+ actualUnitType = AEROSPACEFIGHTER;
+ }
+ }
+ } else {
+ actualUnitType = AEROSPACEFIGHTER;
+ }
}
// Add unit types to the list of actual unity types
@@ -2532,6 +2632,33 @@ private static List generateUnitTypes(int unitTypeCode,
return unitTypes;
}
+ /**
+ * Checks if the given faction is a Clan faction, if the current game year is 3057 or greater,
+ * and if a random integer between 0 and 99 inclusive is less than 6. If all these conditions
+ * are met, the method returns a list containing five instances of the PROTOMEK constant. If not,
+ * it creates a new list with a single instance of the MEK constant.
+ *
+ * @param faction the Faction to check for Clan-ness
+ * @param campaign the current Campaign, used to get the current game year
+ *
+ * @return List of PROTOMEK constants if all conditions are met, otherwise a list containing MEK
+ */
+ private static List checkForProtoMek(Faction faction, Campaign campaign) {
+ List unitTypes = new ArrayList<>();
+ if (faction.isClan() && (campaign.getGameYear() >= 3057) && (randomInt(100) < 6)) {
+ // There are five ProtoMeks to a Point
+ for (int i = 0; i < 5; i++) {
+ unitTypes.add(PROTOMEK);
+ }
+ }
+
+ if (unitTypes.isEmpty()) {
+ unitTypes.add(MEK);
+ }
+
+ return unitTypes;
+ }
+
/**
* Generates a selection of unit types, typically for a Clan star of five
* points. May generate
@@ -2563,8 +2690,13 @@ private static List generateClanUnitTypes(int unitCount,
// Random determination of Mek or ground vehicle
int roll = Compute.d6(2);
- int unitType = campaign.getCampaignOptions().isClanVehicles() && (roll <= vehicleTarget) ? UnitType.TANK
- : UnitType.MEK;
+ int unitType = campaign.getCampaignOptions().isClanVehicles() && (roll <= vehicleTarget) ? TANK
+ : MEK;
+
+ if ((campaign.getGameYear() >= 3057) && (randomInt(100) < 6)) {
+ unitType = PROTOMEK;
+ }
+
List unitTypes = new ArrayList<>();
for (int x = 0; x < unitCount; x++) {
unitTypes.add(unitType);
@@ -2638,7 +2770,7 @@ private static List generateClanUnitTypes(int unitCount,
weights = adjustForMinWeight(weights, minWeight);
// Aerospace fighter weight cap
- if (unitTypes.contains(UnitType.AEROSPACEFIGHTER)) {
+ if (unitTypes.contains(AEROSPACEFIGHTER)) {
weights = adjustForMaxWeight(weights, EntityWeightClass.WEIGHT_HEAVY);
}
@@ -2648,27 +2780,27 @@ private static List generateClanUnitTypes(int unitCount,
for (int curType : requiredRoles.keySet()) {
if (requiredRoles.get(curType).contains(MissionRole.RECON)) {
- if (curType == UnitType.MEK || curType == UnitType.PROTOMEK) {
+ if (curType == MEK || curType == PROTOMEK) {
weights = adjustForMaxWeight(weights, EntityWeightClass.WEIGHT_MEDIUM);
}
}
if (requiredRoles.get(curType).contains(MissionRole.APC)) {
- if (curType == UnitType.TANK || curType == UnitType.VTOL) {
+ if (curType == TANK || curType == VTOL) {
weights = adjustForMaxWeight(weights, EntityWeightClass.WEIGHT_MEDIUM);
}
}
if (requiredRoles.get(curType).contains(MissionRole.CAVALRY)) {
- if (curType == UnitType.MEK) {
+ if (curType == MEK) {
weights = adjustForMaxWeight(weights, EntityWeightClass.WEIGHT_HEAVY);
- } else if (curType == UnitType.TANK || curType == UnitType.PROTOMEK) {
+ } else if (curType == TANK || curType == PROTOMEK) {
weights = adjustForMaxWeight(weights, EntityWeightClass.WEIGHT_MEDIUM);
}
}
if (requiredRoles.get(curType).contains(MissionRole.RAIDER)) {
- if (curType == UnitType.MEK || curType == UnitType.PROTOMEK) {
+ if (curType == MEK || curType == PROTOMEK) {
weights = adjustForMaxWeight(weights, EntityWeightClass.WEIGHT_HEAVY);
}
}
@@ -2816,7 +2948,7 @@ private static double getDifficultyMultiplier(Campaign campaign) {
* @return the randomly generated {@link EntityWeightClass}
*/
public static int randomForceWeight() {
- int roll = Compute.randomInt(89);
+ int roll = randomInt(89);
// These values are based the random force weight table found on page 265 of Total Warfare
if (roll < 19) { // 19%
@@ -2906,7 +3038,7 @@ private static List generateLance(String faction, SkillLevel skill, int
if (newEntity == null) {
logger.info(String.format("Failed to generate unit of type %s, weight %s. Beginning substitution.",
- UnitType.getTypeName(unitTypes.get(i)),
+ getTypeName(unitTypes.get(i)),
EntityWeightClass.getClassName(AtBConfiguration.decodeWeightStr(weights, i))));
// If we've failed to get an entity, we start adjusting weight categories to see
@@ -2933,9 +3065,9 @@ private static List generateLance(String faction, SkillLevel skill, int
if (newEntity == null) {
logger.info("Substitution unsuccessful. Using hardcoded fallbacks");
- if (unitTypes.get(0) == UnitType.DROPSHIP) {
- newEntity = getNewEntity(faction, skill, quality, List.of(UnitType.DROPSHIP),
- weights, Map.of(UnitType.DROPSHIP, List.of(CIVILIAN)),
+ if (unitTypes.get(0) == DROPSHIP) {
+ newEntity = getNewEntity(faction, skill, quality, List.of(DROPSHIP),
+ weights, Map.of(DROPSHIP, List.of(CIVILIAN)),
campaign, 0);
if (newEntity != null) {
@@ -2943,12 +3075,19 @@ private static List generateLance(String faction, SkillLevel skill, int
}
} else {
if (scenario.getBoardType() == T_GROUND && allowsTanks) {
- newEntity = getNewEntity(faction, skill, quality, List.of(UnitType.TANK),
+ newEntity = getNewEntity(faction, skill, quality, List.of(TANK),
weights, null, campaign, 0);
if (newEntity != null) {
logger.info("Substitution successful. Substituted with Tank.");
}
+ } else {
+ newEntity = getNewEntity(faction, skill, quality, List.of(AEROSPACEFIGHTER),
+ weights, null, campaign, 0);
+
+ if (newEntity != null) {
+ logger.info("Substitution successful. Substituted with Aerospace Fighter.");
+ }
}
}
@@ -3069,7 +3208,7 @@ public static int calculateDeploymentZone(ScenarioForceTemplate forceTemplate, A
// if we got in here without a force template somehow, just return a random
// start zone
if (forceTemplate == null) {
- return Compute.randomInt(Board.START_CENTER);
+ return randomInt(Board.START_CENTER);
// if we have a specific calculated deployment zone already
} else if (forceTemplate.getActualDeploymentZone() != Board.START_NONE) {
return forceTemplate.getActualDeploymentZone();
@@ -3078,7 +3217,7 @@ public static int calculateDeploymentZone(ScenarioForceTemplate forceTemplate, A
} else if (forceTemplate.getSyncDeploymentType() == SynchronizedDeploymentType.None ||
Objects.equals(forceTemplate.getSyncedForceName(), originalForceTemplateID)) {
calculatedEdge = forceTemplate.getDeploymentZones()
- .get(Compute.randomInt(forceTemplate.getDeploymentZones().size()));
+ .get(randomInt(forceTemplate.getDeploymentZones().size()));
} else if (forceTemplate.getSyncDeploymentType() == SynchronizedDeploymentType.SameEdge) {
calculatedEdge = calculateDeploymentZone(
scenario.getTemplate().getScenarioForces().get(forceTemplate.getSyncedForceName()), scenario,
@@ -3093,13 +3232,13 @@ public static int calculateDeploymentZone(ScenarioForceTemplate forceTemplate, A
scenario.getTemplate().getScenarioForces().get(forceTemplate.getSyncedForceName()), scenario,
originalForceTemplateID);
List arc = getArc(syncDeploymentZone, true);
- calculatedEdge = arc.get(Compute.randomInt(arc.size()));
+ calculatedEdge = arc.get(randomInt(arc.size()));
} else if (forceTemplate.getSyncDeploymentType() == SynchronizedDeploymentType.OppositeArc) {
int syncDeploymentZone = calculateDeploymentZone(
scenario.getTemplate().getScenarioForces().get(forceTemplate.getSyncedForceName()), scenario,
originalForceTemplateID);
List arc = getArc(syncDeploymentZone, false);
- calculatedEdge = arc.get(Compute.randomInt(arc.size()));
+ calculatedEdge = arc.get(randomInt(arc.size()));
}
if (calculatedEdge == ScenarioForceTemplate.DEPLOYMENT_ZONE_NARROW_EDGE) {
@@ -3113,7 +3252,7 @@ public static int calculateDeploymentZone(ScenarioForceTemplate forceTemplate, A
edges.add(Board.START_S);
}
- calculatedEdge = edges.get(Compute.randomInt(2));
+ calculatedEdge = edges.get(randomInt(2));
}
forceTemplate.setActualDeploymentZone(calculatedEdge);
@@ -3137,7 +3276,7 @@ public static void setDestinationZone(BotForce force, ScenarioForceTemplate forc
if (forceTemplate.getDestinationZone() == ScenarioForceTemplate.DESTINATION_EDGE_RANDOM) {
// compute a random cardinal edge between 0 and 3 to avoid None
- actualDestinationEdge = Compute.randomInt(CardinalEdge.values().length - 1);
+ actualDestinationEdge = randomInt(CardinalEdge.values().length - 1);
} else if (forceTemplate.getDestinationZone() == ScenarioForceTemplate.DESTINATION_EDGE_OPPOSITE_DEPLOYMENT) {
actualDestinationEdge = getOppositeEdge(force.getStartingPos());
} else {
@@ -3564,58 +3703,18 @@ public static int getLanceSize(String factionCode) {
/**
* Worker function to determine the formation size of fixed wing aircraft.
- * Directly calling for
- * aerospace fighters will return a single flight/point size, normally 2 except
- * for CC which
- * uses 3 per flight. Conventional fighters return 1-3 flights/2-6 total. The
- * SPECIAL_UNIT_TYPE_ATB_AERO_MIX unit type randomly returns an aerospace flight
- * or conventional
- * squadron.
- *
- * @param unitTypeCode type of unit may be aerospace, conventional, or ATB
- * 'special'
- * @param isPlanetOwner true if the generating faction controls the system,
- * which is required
- * to generate conventional fighters
- * @param factionCode Short name of faction
+ * @param faction The faction spawning the force
* @return Number of fighters to use as a formation size
*/
- public static int getAeroLanceSize(int unitTypeCode, boolean isPlanetOwner, String factionCode) {
- int numFightersPerFlight = factionCode.equals("CC") ? 3 : 2;
-
- // If this is the planet owner, it may generate a full squadron of conventional
- // fighters
- int useASFRoll = isPlanetOwner ? Compute.d6() : 6;
- int weightCountRoll = (Compute.randomInt(3) + 1) * numFightersPerFlight;
- return getAeroLanceSize(unitTypeCode, numFightersPerFlight, weightCountRoll, useASFRoll);
- }
-
- /**
- * Unwrapped inner logic of above function to be deterministic, for testing
- * purposes.
- *
- * @param unitTypeCode {@link UnitType} value, should be
- * AEROSPACEFIGHTER,
- * CONV_FIGHTER, or SPECIAL_UNIT_TYPE_ATB_AERO_MIX.
- * @param numFightersPerFlight Number of fighters per flight/point, typically 2
- * @param weightCountRoll Number of fighters per squadron/star, typically 6
- * or 10
- * @param useASFRoll test value for dynamic generation of aerospace or
- * conventional
- * @return flight size for aerospace, squadron size for conventional
- */
- public static int getAeroLanceSize(int unitTypeCode, int numFightersPerFlight, int weightCountRoll,
- int useASFRoll) {
- if (unitTypeCode == UnitType.AEROSPACEFIGHTER) {
- return numFightersPerFlight;
- } else if (unitTypeCode == UnitType.CONV_FIGHTER) {
- return weightCountRoll;
+ public static int getAeroLanceSize(Faction faction) {
+ if (faction.isClan()) {
+ return 10;
+ } else if (faction.isComStarOrWoB()) {
+ return 6;
+ } else if (faction.getShortName().equals("CC")) {
+ return randomInt(2) == 0 ? 3 : 2;
} else {
- // if we are the planet owner, we may use ASF or conventional fighters
- boolean useASF = useASFRoll >= 4;
- // if we are using ASF, we "always" use 2 at a time, otherwise, use the # of
- // conventional fighters
- return useASF ? numFightersPerFlight : weightCountRoll;
+ return 2;
}
}
@@ -3626,8 +3725,8 @@ public static int getAeroLanceSize(int unitTypeCode, int numFightersPerFlight, i
* @param entityList
*/
private static void deployArtilleryOffBoard(List entityList) {
- OffBoardDirection direction = OffBoardDirection.getDirection(Compute.randomInt(4));
- int distance = (Compute.randomInt(2) + 1) * 17;
+ OffBoardDirection direction = OffBoardDirection.getDirection(randomInt(4));
+ int distance = (randomInt(2) + 1) * 17;
for (Entity entity : entityList) {
entity.setOffBoard(distance, direction);
diff --git a/MekHQ/unittests/mekhq/campaign/mission/DynamicScenarioFactoryTest.java b/MekHQ/unittests/mekhq/campaign/mission/DynamicScenarioFactoryTest.java
index d2a8097a03..749cf4326d 100644
--- a/MekHQ/unittests/mekhq/campaign/mission/DynamicScenarioFactoryTest.java
+++ b/MekHQ/unittests/mekhq/campaign/mission/DynamicScenarioFactoryTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020-2022 - The MegaMek Team. All Rights Reserved.
+ * Copyright (c) 2020-2024 - The MegaMek Team. All Rights Reserved.
*
* This file is part of MekHQ.
*
@@ -19,15 +19,13 @@
package mekhq.campaign.mission;
import megamek.common.Board;
-import megamek.common.Compute;
-import megamek.common.UnitType;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* Tests relevant to the AtBDynamicScenarioFactory
- *
+ *
* @author NickAragua
*/
public class DynamicScenarioFactoryTest {
@@ -57,61 +55,4 @@ public void testGetOppositeEdge() {
startingEdge = Board.START_NW;
assertEquals(Board.START_SE, AtBDynamicScenarioFactory.getOppositeEdge(startingEdge));
}
-
- private void testAeroLanceSizeInner(int unitTypeCode, int numFightersPerFlight, boolean isPlanetOwner) {
- int weightCountRoll = (Compute.randomInt(3) + 1) * numFightersPerFlight;
- int useASFRoll = isPlanetOwner ? Compute.d6() : 6;
- int expected;
- switch (unitTypeCode) {
- case UnitType.AEROSPACEFIGHTER:
- expected = numFightersPerFlight;
- break;
- case UnitType.CONV_FIGHTER:
- expected = weightCountRoll;
- break;
- default:
- expected = (useASFRoll >= 4) ? numFightersPerFlight : weightCountRoll;
- }
-
- assertEquals(expected, AtBDynamicScenarioFactory.getAeroLanceSize(unitTypeCode, numFightersPerFlight,
- weightCountRoll, useASFRoll));
- }
-
- @Test
- public void testAeroLanceSize() {
- assertEquals(2, AtBDynamicScenarioFactory.getAeroLanceSize(UnitType.AEROSPACEFIGHTER, true, "FC"));
- assertEquals(3, AtBDynamicScenarioFactory.getAeroLanceSize(UnitType.AEROSPACEFIGHTER, true, "CC"));
- assertEquals(2,
- AtBDynamicScenarioFactory.getAeroLanceSize(ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_AERO_MIX, false,
- "FC"));
- assertEquals(3,
- AtBDynamicScenarioFactory.getAeroLanceSize(ScenarioForceTemplate.SPECIAL_UNIT_TYPE_ATB_AERO_MIX, false,
- "CC"));
-
- // Roll some "random" values and check inner function return values
- int unitTypeCode = UnitType.AEROSPACEFIGHTER;
- int numFightersPerFlight = 2;
- boolean isPlanetOwner = false;
- testAeroLanceSizeInner(unitTypeCode, numFightersPerFlight, isPlanetOwner);
- isPlanetOwner = true;
- testAeroLanceSizeInner(unitTypeCode, numFightersPerFlight, isPlanetOwner);
- numFightersPerFlight = 3;
- isPlanetOwner = false;
- testAeroLanceSizeInner(unitTypeCode, numFightersPerFlight, isPlanetOwner);
- isPlanetOwner = true;
- testAeroLanceSizeInner(unitTypeCode, numFightersPerFlight, isPlanetOwner);
-
- unitTypeCode = UnitType.CONV_FIGHTER;
- numFightersPerFlight = 2;
- isPlanetOwner = false;
- testAeroLanceSizeInner(unitTypeCode, numFightersPerFlight, isPlanetOwner);
- testAeroLanceSizeInner(unitTypeCode, numFightersPerFlight, isPlanetOwner);
- isPlanetOwner = true;
- testAeroLanceSizeInner(unitTypeCode, numFightersPerFlight, isPlanetOwner);
- numFightersPerFlight = 3;
- isPlanetOwner = false;
- testAeroLanceSizeInner(unitTypeCode, numFightersPerFlight, isPlanetOwner);
- isPlanetOwner = true;
- testAeroLanceSizeInner(unitTypeCode, numFightersPerFlight, isPlanetOwner);
- }
}