import megamek.common.battleValue.BVCalculator; import megamek.common.loaders.MekSummaryCache; import megamek.common.units.Entity; import megamek.common.loaders.MULParser; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; /** * Dump MegaMek's battle value for the units in a `.mul`. * *

The other dumps score a design as it leaves the factory, or a design this * program shot at in a way it chose. Neither can answer for a unit somebody * else varied: a `.mul` records what is in every critical slot, and MegaMek * reads those types back - a magazine whose `type` names inferno rounds is * loaded with inferno rounds and scored as such. That is how a player changes * a unit without designing one, and it is the case helm has to agree about. * *

Usage: DumpMul out.jsonl file.mul [file.mul ...] * *

Must run with MegaMek's install dir as the working directory. The summary * cache has to be up before a `.mul` can name a design. */ public final class DumpMul { public static void main(String[] args) throws Exception { if (args.length < 2) { System.err.println("usage: DumpMul [file.mul ...]"); System.exit(2); } MekSummaryCache cache = MekSummaryCache.getInstance(); long deadline = System.currentTimeMillis() + 600_000L; while (!cache.isInitialized()) { if (System.currentTimeMillis() > deadline) { System.err.println("units.cache did not initialise within 600s"); System.exit(1); } Thread.sleep(250L); } int written = 0; try (PrintWriter out = new PrintWriter(new BufferedWriter( new OutputStreamWriter(new FileOutputStream(args[0]), StandardCharsets.UTF_8)))) { for (int i = 1; i < args.length; i++) { File file = new File(args[i]); MULParser parser = new MULParser(file, null); for (Entity entity : parser.getEntities()) { BVCalculator calc = BVCalculator.getBVCalculator(entity); // Crew and C3 left out: the crew is arithmetic helm does // itself from a table, and C3 needs a game to see the // other units. What is being asked here is what the unit // itself is worth once somebody has varied it. int bv = calc.calculateBV(true, true); StringBuilder sb = new StringBuilder(256); sb.append("{\"file\":"); quote(sb, file.getName()); sb.append(",\"name\":"); quote(sb, entity.getShortNameRaw()); sb.append(",\"bv\":").append(bv); // Which half is wrong is the first question every // disagreement asks, and the fields are not public. for (String field : new String[] { "defensiveValue", "offensiveValue" }) { try { java.lang.reflect.Field f = findField(calc.getClass(), field); if (f != null) { f.setAccessible(true); sb.append(",\"").append(field).append("\":").append(f.get(calc)); } } catch (Throwable ignored) { // A MegaMek that renames these should lose the // columns rather than fail. } } sb.append('}'); out.println(sb); written++; } String warning = parser.getWarningMessage(); if (warning != null && !warning.isBlank()) { System.err.println(file.getName() + ": " + warning.trim()); } } } System.err.println("wrote " + written + " rows"); } /** A field on this class or any above it. */ private static java.lang.reflect.Field findField(Class type, String name) { for (Class at = type; at != null; at = at.getSuperclass()) { try { return at.getDeclaredField(name); } catch (NoSuchFieldException ignored) { // Keep walking up. } } return null; } private static void quote(StringBuilder sb, String value) { sb.append('"'); for (int i = 0; i < value.length(); i++) { char c = value.charAt(i); switch (c) { case '"' -> sb.append("\\\""); case '\\' -> sb.append("\\\\"); case '\n' -> sb.append("\\n"); case '\r' -> sb.append("\\r"); case '\t' -> sb.append("\\t"); default -> { if (c < 0x20) { sb.append(String.format("\\u%04x", (int) c)); } else { sb.append(c); } } } } sb.append('"'); } }