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('"');
}
}