import megamek.client.ui.clientGUI.calculationReport.TextCalculationReport;
import megamek.common.loaders.MekSummary;
import megamek.common.loaders.MekSummaryCache;
import megamek.common.units.Entity;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
/**
* Dump the working of MegaMek's C-bill cost calculation, one object per unit.
*
*
The same bargain as DumpBv: `DumpUnits` already carries the answer, and
* an answer alone makes a poor oracle for a reimplementation. A design's cost
* is a sum of two dozen terms - the frame, the engine, each actuator, each
* weapon, the plate - multiplied by a factor for the kind of machine it is,
* and a total that is 40,000 C-bills light says nothing about which term is
* wrong. `Entity.getCost` writes its own working into a report as it goes,
* and that report says which line.
*
* java -cp "MegaMek.jar:lib/*" DumpCost.java out.jsonl [unitType ...]
*
* Must run with MegaMek's install dir as the working directory.
*/
public final class DumpCost {
public static void main(String[] args) throws Exception {
if (args.length < 1) {
System.err.println("usage: DumpCost [unitType ...]");
System.exit(2);
}
java.util.Set only = new java.util.LinkedHashSet<>();
for (int i = 1; i < args.length; i++) {
only.add(args[i].toLowerCase(java.util.Locale.ROOT));
}
long start = System.currentTimeMillis();
MekSummaryCache cache = MekSummaryCache.getInstance();
long deadline = start + 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;
int skipped = 0;
try (PrintWriter out = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(args[0]), StandardCharsets.UTF_8)))) {
for (MekSummary ms : cache.getAllMeks()) {
String name = ms.getName();
try {
if (!only.isEmpty()
&& !only.contains(ms.getUnitType().toLowerCase(java.util.Locale.ROOT))) {
continue;
}
Entity entity = ms.loadEntity();
if (entity == null) {
skipped++;
continue;
}
TextCalculationReport report = new TextCalculationReport();
// `false` is "count the ammunition", which is what
// MekSummary.cost records and so what helm is measured
// against.
double cost = entity.getCost(report, false);
StringBuilder sb = new StringBuilder(1024);
sb.append('{');
str(sb, "name");
sb.append(':');
str(sb, name);
sb.append(',');
str(sb, "unitType");
sb.append(':');
str(sb, ms.getUnitType());
sb.append(',');
str(sb, "cost");
sb.append(':');
sb.append(Double.isFinite(cost) ? Double.toString(cost) : "null");
sb.append(',');
str(sb, "summaryCost");
sb.append(':');
double summary = ms.getCost();
sb.append(Double.isFinite(summary) ? Double.toString(summary) : "null");
sb.append(',');
// What the design's own kind multiplies the sum by, which
// the report prints but not always as a line of its own.
str(sb, "priceMultiplier");
sb.append(':');
double multiplier = entity.getPriceMultiplier();
sb.append(Double.isFinite(multiplier) ? Double.toString(multiplier) : "null");
sb.append(',');
str(sb, "report");
sb.append(':');
str(sb, report.toString());
sb.append('}');
out.println(sb);
written++;
} catch (Throwable t) {
skipped++;
System.err.println("skipped " + name + ": " + t);
}
}
}
System.out.printf("dumped %d units in %.1fs (%d skipped) -> %s%n",
written, (System.currentTimeMillis() - start) / 1000.0, skipped, args[0]);
System.exit(0);
}
private static void str(StringBuilder sb, String s) {
sb.append('"');
for (int i = 0; i < s.length(); i++) {
char c = s.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('"');
}
}