forked from MagicBane/Server
PowerParser loads Powers.cfg into PowerTemplate._powers. The 22 header fields are named after the client loader's error messages (0x56E410); ACTION= lines keep trains, duration, curve and stack fields; prereqs, curves, flags, messages and CONDITION blocks are kept. Keys compare case-insensitively, as in the client. Four keys the client ignores (APPLYDAMAGESELF=, APPLYEFFECTCASTER=, APPLYEFFECTTARGET= and a POWEREND= typo) are skipped; any other unknown key fails the load. Numbers are read like the client's atoi/atof with a warning when a token isn't clean: PRL-033 init time "2.9.0" -> 2.9 and SGE-001 precision "10.0" -> 10, which matches the DB. Ten EQPREREQ lines end without a required flag, which the client reads as 0. Needs the fixed Powers.cfg (SRDX-DB header had "SPELL 15 Unknown" pasted twice; fixed in MBDATA, findings section 6). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
159 lines
4.9 KiB
Java
159 lines
4.9 KiB
Java
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
|
// Magicbane Emulator Project © 2013 - 2026
|
|
// https://www.magicbane.com
|
|
|
|
package engine.parsers.wpak;
|
|
|
|
import engine.parsers.cache.CacheParserUtil;
|
|
import org.pmw.tinylog.Logger;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
// One non-comment line of a wpak cfg file, split into tokens.
|
|
// Every accessor that fails names the file and line.
|
|
|
|
public class WpakLine {
|
|
|
|
// The client reads numbers like C atoi/atof: the leading number counts, the rest is dropped
|
|
// (Powers.cfg has "2.9.0" for a float and "10.0" for an int)
|
|
private static final Pattern intPrefix = Pattern.compile("[-+]?\\d+");
|
|
private static final Pattern floatPrefix = Pattern.compile("[-+]?(\\d+\\.?\\d*|\\.\\d+)");
|
|
|
|
public final String fileName;
|
|
public final int lineNumber;
|
|
public final String text;
|
|
public final ArrayList<String> tokens;
|
|
|
|
public WpakLine(String fileName, int lineNumber, String text, ArrayList<String> tokens) {
|
|
this.fileName = fileName;
|
|
this.lineNumber = lineNumber;
|
|
this.text = text;
|
|
this.tokens = tokens;
|
|
}
|
|
|
|
public String location() {
|
|
return fileName + ":" + lineNumber;
|
|
}
|
|
|
|
public WpakFormatException error(String message) {
|
|
return new WpakFormatException(location() + ": " + message);
|
|
}
|
|
|
|
public int size() {
|
|
return tokens.size();
|
|
}
|
|
|
|
public boolean has(int index) {
|
|
return index < tokens.size();
|
|
}
|
|
|
|
public String token(int index) {
|
|
|
|
if (!has(index))
|
|
throw error("missing token " + index + " in '" + text.trim() + "'");
|
|
|
|
return tokens.get(index);
|
|
}
|
|
|
|
public int intToken(int index) {
|
|
|
|
String value = token(index);
|
|
Matcher prefix;
|
|
|
|
try {
|
|
return Integer.parseInt(value);
|
|
} catch (NumberFormatException e) {
|
|
prefix = intPrefix.matcher(value);
|
|
}
|
|
|
|
if (!prefix.lookingAt())
|
|
throw error("token " + index + " '" + value + "' is not an integer");
|
|
|
|
Logger.warn(location() + ": token " + index + " '" + value + "' read as " + prefix.group() + ", as the client does");
|
|
return Integer.parseInt(prefix.group());
|
|
}
|
|
|
|
public float floatToken(int index) {
|
|
|
|
String value = token(index);
|
|
Matcher prefix;
|
|
|
|
try {
|
|
return Float.parseFloat(value);
|
|
} catch (NumberFormatException e) {
|
|
prefix = floatPrefix.matcher(value);
|
|
}
|
|
|
|
if (!prefix.lookingAt())
|
|
throw error("token " + index + " '" + value + "' is not a number");
|
|
|
|
Logger.warn(location() + ": token " + index + " '" + value + "' read as " + prefix.group() + ", as the client does");
|
|
return Float.parseFloat(prefix.group());
|
|
}
|
|
|
|
// The client reads a missing trailing number as 0 (String::ToInt on an empty token)
|
|
|
|
public int optionalIntToken(int index) {
|
|
|
|
if (!has(index))
|
|
return 0;
|
|
|
|
return intToken(index);
|
|
}
|
|
|
|
// TRUE/FALSE, or a number where non-zero is true
|
|
|
|
public boolean boolToken(int index) {
|
|
|
|
String value = token(index);
|
|
|
|
if (value.equalsIgnoreCase("TRUE"))
|
|
return true;
|
|
|
|
if (value.equalsIgnoreCase("FALSE"))
|
|
return false;
|
|
|
|
try {
|
|
return Integer.parseInt(value) != 0;
|
|
} catch (NumberFormatException e) {
|
|
throw error("token " + index + " '" + value + "' is not a boolean");
|
|
}
|
|
}
|
|
|
|
public boolean optionalBoolToken(int index) {
|
|
|
|
if (!has(index))
|
|
return false;
|
|
|
|
return boolToken(index);
|
|
}
|
|
|
|
public ArrayList<Integer> intTokensFrom(int index) {
|
|
|
|
ArrayList<Integer> values = new ArrayList<>();
|
|
|
|
for (int i = index; i < tokens.size(); i++)
|
|
values.add(intToken(i));
|
|
|
|
return values;
|
|
}
|
|
|
|
public <E extends Enum<E>> E enumToken(Class<E> enumClass, int index) {
|
|
|
|
String value = token(index);
|
|
String name = CacheParserUtil.enumName(value);
|
|
|
|
for (E constant : enumClass.getEnumConstants())
|
|
if (constant.name().equalsIgnoreCase(name))
|
|
return constant;
|
|
|
|
throw error("token " + index + " '" + value + "' has no " + enumClass.getSimpleName());
|
|
}
|
|
}
|