Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Munition autoconfig tweaks #5738

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions megamek/mmconf/munitionLoadoutSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
<entry key="mtGuidedAmmoFriendlyMissileBoatFractionDivisor">3.0</entry>
<entry key="mtUtilityAmmoOffboardUnitsThreshold">2.0</entry>
<entry key="mtUtilityAmmoFriendlyVsEnemyFractionDivisor">1.0</entry>
<entry key="mtEnergyBoatEnemyFractionDivisor">4.0</entry>
<entry key="mtSeekingAmmoEnemyTSMExceedThreshold">1.0</entry>
<entry key="commentTopMunitionsSubsetCount">This count determines how many munition types (out of all options) are actually selected</entry>
<entry key="mtTopMunitionsSubsetCount">4</entry>
<entry key="commentStartTLGBombArea">The following entries are used by Bombs:</entry>
Expand Down Expand Up @@ -75,6 +77,7 @@
<entry key="defaultStandardMunitionWeight">2.0</entry>
<entry key="defaultMissileStandardMunitionWeight">2.0</entry>
<entry key="defaultDeadFireMunitionWeight">3.0</entry>
<entry key="defaultArtemiscapableMunitionWeight">0.0</entry>
<entry key="defaultATMMunitionWeight">2.0</entry>
<entry key="defaultATMStandardWeight">1.0</entry>
<entry key="increaseWeightFactor">2.0</entry>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public class ReconfigurationParameters {
public long enemyFastMovers = 0;
public long enemyOffBoard = 0;
public long enemyECMCount = 0;
public long enemyTSMCount = 0;
public HashSet<String> enemyFactions = new HashSet<String>();

// Friendly stats
Expand Down
70 changes: 51 additions & 19 deletions megamek/src/megamek/client/generator/TeamLoadoutGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ public class TeamLoadoutGenerator {
try (InputStream is = new FileInputStream(LOADOUT_SETTINGS_PATH)) {
weightProperties.loadFromXML(is);
} catch (Exception e) {
LogManager.getLogger().error("Munition weight properties could not be loaded! Using defaults...", e);
LogManager.getLogger().warn("Munition weight properties could not be loaded! Using defaults...", e);
LogManager.getLogger().debug(LOADOUT_SETTINGS_PATH + " was not loaded: ", e);
}
}

Expand Down Expand Up @@ -105,17 +106,20 @@ public class TeamLoadoutGenerator {

// TODO Anti-Radiation Missiles See IO pg 62 (TO 368)
public static final ArrayList<String> SEEKING_MUNITIONS = new ArrayList<>(List.of(
"Heat-Seeking", "Listen-Kill", "Swarm", "Swarm-I"));
"Heat-Seeking", "Listen-Kill", "Swarm", "Swarm-I"
));

public static final ArrayList<String> AMMO_REDUCING_MUNITIONS = new ArrayList<>(List.of(
"Acid", "Laser Inhibiting", "Follow The Leader", "Heat-Seeking", "Tandem-Charge",
"Thunder-Active", "Thunder-Augmented", "Thunder-Vibrabomb", "Thunder-Inferno",
"AAAMissile Ammo", "ASMissile Ammo", "ASWEMissile Ammo", "ArrowIVMissile Ammo",
"AlamoMissile Ammo"));
"AlamoMissile Ammo"
));

public static final ArrayList<String> TYPE_LIST = new ArrayList<String>(List.of(
"LRM", "SRM", "AC", "ATM", "Arrow IV", "Artillery", "Artillery Cannon",
"Mek Mortar", "Narc", "Bomb"));
"Mek Mortar", "Narc", "Bomb"
));

public static final Map<String, ArrayList<String>> TYPE_MAP = Map.ofEntries(
entry("LRM", MunitionTree.LRM_MUNITION_NAMES),
Expand All @@ -127,7 +131,8 @@ public class TeamLoadoutGenerator {
entry("Artillery Cannon", MunitionTree.MEK_MORTAR_MUNITION_NAMES),
entry("Mek Mortar", MunitionTree.MEK_MORTAR_MUNITION_NAMES),
entry("Narc", MunitionTree.NARC_MUNITION_NAMES),
entry("Bomb", MunitionTree.BOMB_MUNITION_NAMES));
entry("Bomb", MunitionTree.BOMB_MUNITION_NAMES)
);

// subregion Bombs
// bomb types assignable to aerospace units on ground maps
Expand Down Expand Up @@ -311,23 +316,31 @@ public void updateOptionValues(GameOptions gameOpts) {
showExtinct = gameOptions.booleanOption((OptionsConstants.ALLOWED_SHOW_EXTINCT));
}

// See if selected ammoType is legal under current game rules, availability, TL,
// tech base, etc.
/**
* Calculates legality of ammo types given a faction, tech base (IS/CL), mixed tech, and the instance's
* already-set year, tech level, and option for showing extinct equipment.
* @param aType the AmmoType of the munition under consideration. q.v.
* @param faction MM-style faction code, per factions.xml and FactionRecord keys
* @param techBase either 'IS' or 'CL', used for clan boolean check.
* @param mixedTech makes munitions checks more lenient by allowing faction to access both IS and CL techbases.
* @return boolean true if legal for combination of inputs, false otherwise. Determins if an AmmoType is loaded.
*/
public boolean checkLegality(AmmoType aType, String faction, String techBase, boolean mixedTech) {
boolean legal = false;
boolean clan = techBase.equals("CL");

// Check if tech exists at all (or is explicitly allowed despite being extinct)
// and whether it is available at the current tech level.
legal = aType.isAvailableIn(allowedYear, showExtinct)
&& aType.isLegal(allowedYear, legalLevel, clan, mixedTech, showExtinct);

if (eraBasedTechLevel) {
// Check if tech is legal to use in this game based on year, tech level, etc.
legal = aType.isLegal(allowedYear, legalLevel, clan,
mixedTech, showExtinct);
// Check if tech is widely available, or if the specific faction has access to
// it
legal &= aType.isAvailableIn(allowedYear, showExtinct)
|| aType.isAvailableIn(allowedYear, clan, ITechnology.getCodeFromIOAbbr(faction));
} else {
// Basic year check only
legal = aType.getStaticTechLevel().ordinal() <= legalLevel.ordinal();
// Check if tech is available to this specific faction with the current year and tech base.
boolean eraBasedLegal = aType.isAvailableIn(allowedYear, clan, ITechnology.getCodeFromMMAbbr(faction));
if (mixedTech) {
eraBasedLegal |= aType.isAvailableIn(allowedYear, !clan, ITechnology.getCodeFromMMAbbr(faction));
}
legal &= eraBasedLegal;
}

// Nukes are not allowed... unless they are!
Expand Down Expand Up @@ -402,7 +415,7 @@ private static long checkForMeks(ArrayList<Entity> el) {
* @return
*/
private static long checkForEnergyBoats(ArrayList<Entity> el) {
return el.stream().filter(e -> e.getAmmo().isEmpty()).count();
return el.stream().filter(e -> e.tracksHeat() && e.getAmmo().isEmpty()).count();
}

/**
Expand Down Expand Up @@ -474,6 +487,10 @@ private static long checkForECM(ArrayList<Entity> el) {
return el.stream().filter(
Entity::hasECM).count();
}

private static long checkForTSM(ArrayList<Entity> el) {
return el.stream().filter(e -> e.isMek() && ((Mech) e).hasTSM(false)).count();
}
// endregion Check for various unit types, armor types, etc.

// region generateParameters
Expand Down Expand Up @@ -610,6 +627,7 @@ public static ReconfigurationParameters generateParameters(
rp.enemyFastMovers += checkForFastMovers(etEntities);
rp.enemyOffBoard = checkForOffboard(etEntities);
rp.enemyECMCount = checkForECM(etEntities);
rp.enemyTSMCount = checkForTSM(etEntities);
} else {
// Assume we know _nothing_ about enemies if Double Blind is on.
rp.enemiesVisible = false;
Expand Down Expand Up @@ -790,11 +808,23 @@ public static MunitionTree generateMunitionTree(ReconfigurationParameters rp, Ar
mwc.decreaseHeatMunitions();
}

// Energy boats run hot; increase heat munitions and heat-seeking specifically
if (rp.enemyEnergyBoats > rp.enemyCount / castPropertyDouble("mtEnergyBoatEnemyFractionDivisor", 4.0)) {
mwc.increaseHeatMunitions();
mwc.increaseHeatMunitions();
mwc.increaseMunitions(new ArrayList<>(List.of("Heat-Seeking")));
}

// Counter EMC by swapping Seeking in for Guided
if (rp.enemyECMCount > castPropertyDouble("mtSeekingAmmoEnemyECMExceedThreshold", 1.0)) {
mwc.decreaseGuidedMunitions();
mwc.increaseSeekingMunitions();
} else {
}
if (rp.enemyTSMCount > castPropertyDouble("mtSeekingAmmoEnemyTSMExceedThreshold", 1.0)) {
// Seeking
mwc.increaseSeekingMunitions();
}
if (rp.enemyECMCount == 0.0 && rp.enemyTSMCount == 0.0 && rp.enemyEnergyBoats == 0.0) {
// Seeking munitions are generally situational
mwc.decreaseSeekingMunitions();
}
Expand Down Expand Up @@ -1810,6 +1840,8 @@ private static HashMap<String, Double> initializeMissileWeaponWeights(ArrayList<
weights.put("Standard", getPropDouble("defaultMissileStandardMunitionWeight", 2.0));
// Dead-Fire should be even higher to start
weights.put("Dead-Fire", getPropDouble("defaultDeadFireMunitionWeight", 3.0));
// Artemis should be zeroed; Artemis-equipped launchers will be handled separately
weights.put("Artemis-capable", getPropDouble("defaultArtemiscapableMunitionWeight", 0.0));
return weights;
}

Expand Down
9 changes: 9 additions & 0 deletions megamek/src/megamek/common/ITechnology.java
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,9 @@ && getExtinctionDate() < year
}

default boolean isAvailableIn(int year, boolean clan, boolean ignoreExtinction) {
// For technology created in the IS after the Clan Invasion, Clan availability
// matches IS (TO pg 33)
clan = clan && ITechnology.getTechEra(year) < ITechnology.ERA_CLAN;
return year >= getIntroductionDate(clan) && (getIntroductionDate(clan) != DATE_NONE)
&& (ignoreExtinction || !isExtinct(year, clan));
}
Expand All @@ -294,6 +297,9 @@ default boolean isAvailableIn(int year, boolean ignoreExtinction) {
}

default boolean isAvailableIn(int year, boolean clan, int faction) {
// For technology created in the IS after the Clan Invasion, Clan availability
// matches IS (TO pg 33)
clan = clan && ITechnology.getTechEra(year) < ITechnology.ERA_CLAN;
return year >= getIntroductionDate(clan, faction)
&& getIntroductionDate(clan, faction) != DATE_NONE && !isExtinct(year, clan, faction);
}
Expand All @@ -304,6 +310,9 @@ default boolean isLegal(int year, int techLevel, boolean mixedTech) {
}

default boolean isLegal(int year, SimpleTechLevel simpleRulesLevel, boolean clanBase, boolean mixedTech, boolean ignoreExtinct) {
// For technology created in the IS after the Clan Invasion, Clan availability
// matches IS (TO pg 33)
clanBase = clanBase && ITechnology.getTechEra(year) < ITechnology.ERA_CLAN;
if (mixedTech) {
if (!isAvailableIn(year, ignoreExtinct)) {
return false;
Expand Down
8 changes: 5 additions & 3 deletions megamek/src/megamek/common/TechAdvancement.java
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,10 @@ public int getPrototypeDate(boolean clan, int faction) {
// other factions after 3d6+5 years if it hasn't gone extinct by then.
// Using the minimum value here.
int date = getDate(PROTOTYPE, clan) + 8;
if ((getDate(PRODUCTION, clan) < date)
|| (getDate(COMMON, clan) < date)
int dateProduction = getDate(PRODUCTION, clan);
int dateCommon = getDate(COMMON, clan);
if ((dateProduction != DATE_NONE && dateProduction < date)
|| (dateCommon != DATE_NONE && dateCommon < date)
|| isExtinct(date, clan)) {
return DATE_NONE;
}
Expand Down Expand Up @@ -311,7 +313,7 @@ public int getProductionDate(boolean clan, int faction) {
// Per IO p. 34, tech with no common date becomes available to
// other factions after 10 years if it hasn't gone extinct by then.
int date = getDate(PRODUCTION, clan) + 10;
if ((getDate(COMMON, clan) <= date)
if ((getDate(COMMON, clan) != DATE_NONE && getDate(COMMON, clan) <= date)
|| isExtinct(date, clan)) {
return DATE_NONE;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -385,17 +385,17 @@ void testAmmoTypeIllegalByTechLevel() {
assertFalse(tlg.checkLegality(mType, "CC", "IS", false));
assertFalse(tlg.checkLegality(mType, "FS", "IS", false));
assertFalse(tlg.checkLegality(mType, "IS", "IS", false));
assertFalse(tlg.checkLegality(mType, "CL", "CL", false));
assertFalse(tlg.checkLegality(mType, "CL", "CL", true));
assertFalse(tlg.checkLegality(mType, "CLAN", "CL", false));
assertFalse(tlg.checkLegality(mType, "CLAN", "CL", true));

// Should be available to everyone, although only as Mixed Tech for Clans
// Should be available to everyone
when(mockGameOptions.stringOption(OptionsConstants.ALLOWED_TECHLEVEL)).thenReturn("Advanced");
tlg.updateOptionValues();
assertTrue(tlg.checkLegality(mType, "CC", "IS", false));
assertTrue(tlg.checkLegality(mType, "FS", "IS", false));
assertTrue(tlg.checkLegality(mType, "IS", "IS", false));
assertTrue(tlg.checkLegality(mType, "CL", "CL", true));
assertFalse(tlg.checkLegality(mType, "CL", "CL", false));
assertTrue(tlg.checkLegality(mType, "CLAN", "CL", true));
assertTrue(tlg.checkLegality(mType, "CLAN", "CL", true));
}

@Test
Expand All @@ -408,25 +408,27 @@ void testAmmoTypeIllegalBeforeCreation() {
assertTrue(tlg.checkLegality(mType, "CC", "IS", false));
assertTrue(tlg.checkLegality(mType, "FS", "IS", false));
assertTrue(tlg.checkLegality(mType, "IS", "IS", false));
assertTrue(tlg.checkLegality(mType, "CL", "CL", true));
// Check mixed-tech and regular Clan tech, which should match IS at this point
assertTrue(tlg.checkLegality(mType, "CLAN", "CL", true));
assertTrue(tlg.checkLegality(mType, "CLAN", "CL", false));

// Set year back to 3025
when(mockGameOptions.intOption(OptionsConstants.ALLOWED_YEAR)).thenReturn(3025);
tlg.updateOptionValues();
assertFalse(tlg.checkLegality(mType, "CC", "IS", false));
assertFalse(tlg.checkLegality(mType, "FS", "IS", false));
assertFalse(tlg.checkLegality(mType, "IS", "IS", false));
assertFalse(tlg.checkLegality(mType, "CL", "CL", true));
assertFalse(tlg.checkLegality(mType, "CLAN", "CL", true));

// Move up to 3070. Because of game settings and lack of "Common" year, ADA
// becomes available
// everywhere (at least in the IS) immediately after its inception.
when(mockGameOptions.intOption(OptionsConstants.ALLOWED_YEAR)).thenReturn(3070);
tlg.updateOptionValues();
assertTrue(tlg.checkLegality(mType, "CC", "IS", false));
assertTrue(tlg.checkLegality(mType, "FS", "IS", false));
assertTrue(tlg.checkLegality(mType, "IS", "IS", false));
assertFalse(tlg.checkLegality(mType, "CL", "CL", false));
assertFalse(tlg.checkLegality(mType, "FS", "IS", false));
assertFalse(tlg.checkLegality(mType, "IS", "IS", false));
assertFalse(tlg.checkLegality(mType, "CLAN", "CL", true));
}

@Test
Expand Down