fix: read Name= from .dck header for Forge deck lookup

Forge's sim mode looks up decks by the Name= field inside the .dck
file metadata header, NOT by filename. For example:
  Filename: Counter_Intelligence.dck
  Header:   Name=Counter Intelligence
  Forge expects: 'Counter Intelligence' (with space)

BatchRunner now reads the Name= field from the .dck file and passes
that to Forge. Falls back to filename with underscores→spaces if
the Name= field can't be read.
This commit is contained in:
KoalaTrapLord committed 2026-04-05 16:54:38 +00:00
1 parent 0b01f1c467
commit d00093897a
1 file changed
+45 -14
@@ -443,25 +443,30 @@ public class BatchRunner {
cmd.add("sim"); cmd.add("sim");
// Forge's sim mode looks up decks by NAME inside its own // Forge's sim mode looks up decks by the Name= field inside the
// decks/commander/ directory (%APPDATA%/Forge/decks/commander/). // .dck file header, NOT by filename. For example:
// It does NOT accept full paths — passing a path like // Filename: Counter_Intelligence.dck
// D:\...\precon-decks\Counterpunch.dck causes "Could not load deck". // Header: Name=Counter Intelligence
// Forge expects: "Counter Intelligence" (with space)
// //
// Strip the path and .dck extension so Forge sees just the bare // We read the Name= field from the .dck file. If that fails,
// name (e.g. "Counterpunch") and finds it in its own folder. // fall back to the filename with underscores replaced by spaces.
cmd.add("-d"); cmd.add("-d");
for (DeckInfo deck : decks) { for (DeckInfo deck : decks) {
String resolvedPath = resolveFullDeckPath(deck.deckFile); String resolvedPath = resolveFullDeckPath(deck.deckFile);
// Extract just the deck name: "Counterpunch" from full path String forgeName = readDckInternalName(resolvedPath);
String deckName = resolvedPath; if (forgeName == null || forgeName.isBlank()) {
int lastSep = Math.max(deckName.lastIndexOf('/'), deckName.lastIndexOf('\\')); // Fallback: strip path/extension and replace _ with space
if (lastSep >= 0) deckName = deckName.substring(lastSep + 1); forgeName = resolvedPath;
if (deckName.toLowerCase().endsWith(".dck")) { int lastSep = Math.max(forgeName.lastIndexOf('/'), forgeName.lastIndexOf('\\'));
deckName = deckName.substring(0, deckName.length() - 4); if (lastSep >= 0) forgeName = forgeName.substring(lastSep + 1);
if (forgeName.toLowerCase().endsWith(".dck")) {
forgeName = forgeName.substring(0, forgeName.length() - 4);
} }
cmd.add(deckName); forgeName = forgeName.replace('_', ' ');
System.out.println("[DECK] Resolved: " + resolvedPath + " → Forge name: " + deckName); }
cmd.add(forgeName);
System.out.println("[DECK] Resolved: " + resolvedPath + " \u2192 Forge name: \\"" + forgeName + "\\"");
} }
cmd.add("-f"); cmd.add("-f");
@@ -931,6 +936,32 @@ public class BatchRunner {
double simsPerSec, GameResult lastResult); double simsPerSec, GameResult lastResult);
} }
/**
* Read the Name= field from a .dck file header.
* Forge uses this internal name (not the filename) for deck lookup.
* Returns null if the file can't be read or has no Name= field.
*/
private String readDckInternalName(String deckFilePath) {
try {
Path path = resolveExistingDeckPath(deckFilePath);
if (path == null) return null;
List<String> lines = Files.readAllLines(path);
for (String line : lines) {
line = line.trim();
if (line.startsWith("Name=")) {
String name = line.substring(5).trim();
if (!name.isEmpty()) return name;
}
// Stop scanning after [Main] or [Commander] — Name is always in [metadata]
if (line.startsWith("[Main]") || line.startsWith("[Commander]")) break;
}
} catch (Exception e) {
System.err.println("[DECK] Could not read Name= from " + deckFilePath + ": " + e.getMessage());
}
return null;
}
private List<String> readDeckCardNames(String deckFilePath) { private List<String> readDeckCardNames(String deckFilePath) {
List<String> names = new ArrayList<>(); List<String> names = new ArrayList<>();
try { try {