// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ // ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ // 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 tokens; public WpakLine(String fileName, int lineNumber, String text, ArrayList 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 intTokensFrom(int index) { ArrayList values = new ArrayList<>(); for (int i = index; i < tokens.size(); i++) values.add(intToken(i)); return values; } public > E enumToken(Class 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()); } }