New engine.parsers.wpak importer (replaces engine.wpak, which stays unused). WpakFileReader tokenizes cfg lines (quotes, '#'/'*' comments, trailing '#') and builds XxxBEGIN/XxxEND blocks; every error names file:line. PowerActionParser loads PowerActions.cfg into PowerActionTemplate._powerActions, keyed by action id, at world boot. Header layouts per action type come from the client's ArcPowerAction*_ParseHeader functions: the trailing 0/1 on ApplyEffect and friends is the aggressive flag, not a level. effect_ids holds the action -> effect map needed by Task 2.10b. Three ids (PRE-235..237) repeat identically; the later entry wins, as in the client. No consumers change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
153 lines
5.2 KiB
Java
153 lines
5.2 KiB
Java
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
|
// Magicbane Emulator Project © 2013 - 2026
|
|
// https://www.magicbane.com
|
|
|
|
package engine.parsers.wpak;
|
|
|
|
import engine.gameManager.ConfigManager;
|
|
|
|
import java.io.IOException;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Paths;
|
|
import java.util.ArrayDeque;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
// Reads the client's wpak cfg files (an mbEditorPro export of Config.wpak) into
|
|
// tokenized lines and XxxBEGIN/XxxEND blocks. Each parser maps the tokens itself.
|
|
|
|
public class WpakFileReader {
|
|
|
|
public static final String wpakPath = ConfigManager.DEFAULT_DATA_DIR + "wpak/";
|
|
|
|
// Lines starting with '#' or '*' are comments (PowerActions_LoadCfg 0x578EC0).
|
|
// A '#' outside quotes ends the line, as in PowActionCostInfo.cfg.
|
|
|
|
public static ArrayList<WpakLine> readLines(String fileName) throws IOException {
|
|
|
|
ArrayList<WpakLine> lines = new ArrayList<>();
|
|
List<String> fileLines = Files.readAllLines(Paths.get(wpakPath + fileName), StandardCharsets.ISO_8859_1);
|
|
String text;
|
|
String trimmed;
|
|
ArrayList<String> tokens;
|
|
|
|
for (int i = 0; i < fileLines.size(); i++) {
|
|
text = fileLines.get(i);
|
|
trimmed = text.trim();
|
|
|
|
if (trimmed.isEmpty() || trimmed.charAt(0) == '#' || trimmed.charAt(0) == '*')
|
|
continue;
|
|
|
|
tokens = tokenize(fileName, i + 1, text);
|
|
|
|
if (!tokens.isEmpty())
|
|
lines.add(new WpakLine(fileName, i + 1, text, tokens));
|
|
}
|
|
|
|
return lines;
|
|
}
|
|
|
|
// A one-token line XxxBEGIN opens block "Xxx"; XxxEND closes the innermost one.
|
|
|
|
public static WpakBlock readBlocks(String fileName) throws IOException {
|
|
|
|
WpakBlock root = new WpakBlock(fileName, null);
|
|
ArrayDeque<WpakBlock> openBlocks = new ArrayDeque<>();
|
|
WpakBlock block;
|
|
String token;
|
|
|
|
openBlocks.push(root);
|
|
|
|
for (WpakLine line : readLines(fileName)) {
|
|
token = line.token(0);
|
|
|
|
if (line.size() == 1 && token.length() > 5 && token.endsWith("BEGIN")) {
|
|
block = new WpakBlock(token.substring(0, token.length() - 5), line);
|
|
openBlocks.peek().children.add(block);
|
|
openBlocks.push(block);
|
|
continue;
|
|
}
|
|
|
|
if (line.size() == 1 && token.equals(openBlocks.peek().name + "END")) {
|
|
openBlocks.pop();
|
|
continue;
|
|
}
|
|
|
|
if (line.size() == 1 && closesOuterBlock(openBlocks, token))
|
|
throw line.error(token + " before " + openBlocks.peek().name + "END (opened at " + openBlocks.peek().location() + ")");
|
|
|
|
openBlocks.peek().children.add(line);
|
|
}
|
|
|
|
if (openBlocks.size() > 1)
|
|
throw new WpakFormatException(openBlocks.peek().location() + ": " + openBlocks.peek().name + "BEGIN is never closed");
|
|
|
|
return root;
|
|
}
|
|
|
|
private static boolean closesOuterBlock(ArrayDeque<WpakBlock> openBlocks, String token) {
|
|
|
|
for (WpakBlock block : openBlocks)
|
|
if (block.beginLine != null && token.equals(block.name + "END"))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// Whitespace separates tokens; double quotes group one token (quotes removed, no escapes).
|
|
|
|
private static ArrayList<String> tokenize(String fileName, int lineNumber, String text) {
|
|
|
|
ArrayList<String> tokens = new ArrayList<>();
|
|
StringBuilder token = new StringBuilder();
|
|
boolean inToken = false;
|
|
boolean inQuotes = false;
|
|
char c;
|
|
|
|
for (int i = 0; i < text.length(); i++) {
|
|
c = text.charAt(i);
|
|
|
|
if (inQuotes) {
|
|
if (c == '"') {
|
|
inQuotes = false;
|
|
tokens.add(token.toString());
|
|
token.setLength(0);
|
|
} else
|
|
token.append(c);
|
|
continue;
|
|
}
|
|
|
|
if (c == '"' || c == '#' || Character.isWhitespace(c)) {
|
|
if (inToken) {
|
|
tokens.add(token.toString());
|
|
token.setLength(0);
|
|
inToken = false;
|
|
}
|
|
|
|
if (c == '#')
|
|
break;
|
|
|
|
if (c == '"')
|
|
inQuotes = true;
|
|
continue;
|
|
}
|
|
|
|
token.append(c);
|
|
inToken = true;
|
|
}
|
|
|
|
if (inQuotes)
|
|
throw new WpakFormatException(fileName + ":" + lineNumber + ": unclosed quote");
|
|
|
|
if (inToken)
|
|
tokens.add(token.toString());
|
|
|
|
return tokens;
|
|
}
|
|
}
|