Add wpak cfg reader and PowerActions.cfg loader
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>
This commit is contained in:
1 parent
76dcdc0f72
commit
6c820940d3
7 files changed
+711
No files matched your search
@@ -0,0 +1,86 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.objects.wpak;
|
||||
|
||||
import engine.mbEnums;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
// One POWERACTIONBEGIN block of PowerActions.cfg.
|
||||
//
|
||||
// Header: <action_id> <action_type> <type-specific args>. The per-type layouts
|
||||
// come from the client's ArcPowerAction*_ParseHeader functions. header_args keeps
|
||||
// every arg as written, including ones the client reads but nothing names yet.
|
||||
// KEY= lines map to the lower-case fields named after them.
|
||||
//
|
||||
// Read only once loaded.
|
||||
|
||||
public class PowerActionTemplate {
|
||||
|
||||
public static HashMap<String, PowerActionTemplate> _powerActions = new HashMap<>();
|
||||
|
||||
public String action_id;
|
||||
public mbEnums.PowerActionType action_type;
|
||||
public final ArrayList<String> header_args = new ArrayList<>();
|
||||
|
||||
// Header
|
||||
|
||||
// ApplyEffect, ApplyEffects, Block, Charm, DamageOverTime, DeferredPower,
|
||||
// DirectDamage, Invis, SpireDisable, Track, Transform, TransferStatOT
|
||||
public final ArrayList<String> effect_ids = new ArrayList<>();
|
||||
|
||||
public String deferred_power_id;
|
||||
public int num_iterations;
|
||||
public int mob_id;
|
||||
public int mob_level;
|
||||
public String effect_source_to_remove;
|
||||
public mbEnums.ItemFlags item_flag;
|
||||
public boolean item_flag_value;
|
||||
public String track_filter;
|
||||
public int max_track;
|
||||
|
||||
public mbEnums.CostType transfer_from_type;
|
||||
public mbEnums.CostType transfer_to_type;
|
||||
|
||||
// The client reads both with String::ToInt, so 31.5 acts as 31 (0x56ADF0)
|
||||
public float transfer_amount;
|
||||
public String transfer_amount_curve;
|
||||
public float transfer_efficiency;
|
||||
public String transfer_efficiency_curve;
|
||||
|
||||
// Keys, plus the header's trailing aggressive flag (both set the same client field)
|
||||
|
||||
public boolean isaggressive;
|
||||
public boolean bladetrails;
|
||||
public boolean isresistable;
|
||||
public String applyeffectself;
|
||||
public String applyeffectother;
|
||||
public String wearoffeffectself;
|
||||
public String wearoffeffectother;
|
||||
public boolean applyeffectblank;
|
||||
public boolean wearoffeffectblank;
|
||||
public boolean is_available_for_formulae;
|
||||
public boolean removeall;
|
||||
public final ArrayList<Integer> bodyparts = new ArrayList<>();
|
||||
public final ArrayList<Integer> femalebodyparts = new ArrayList<>();
|
||||
public boolean shouldshowweapons;
|
||||
public boolean shouldshowarmor;
|
||||
public final ArrayList<Integer> attackanims = new ArrayList<>();
|
||||
public float[] scalefactor;
|
||||
public String effectid;
|
||||
public int levelcap;
|
||||
public String levelcap_curve;
|
||||
public boolean clearaggro;
|
||||
public boolean targetbecomespet;
|
||||
public boolean destroyoldpet;
|
||||
public String rootfsmid;
|
||||
public mbEnums.DamageType damagetype;
|
||||
public final ArrayList<Integer> splashdamage = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.wpak;
|
||||
|
||||
import engine.mbEnums;
|
||||
import engine.parsers.objects.wpak.PowerActionTemplate;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
// Loads PowerActions.cfg (client loader PowerActions_LoadCfg 0x578EC0).
|
||||
|
||||
public class PowerActionParser {
|
||||
|
||||
private static final String fileName = "PowerActions.cfg";
|
||||
|
||||
public static HashMap<String, PowerActionTemplate> parsePowerActionsFile() {
|
||||
|
||||
HashMap<String, PowerActionTemplate> powerActions = new HashMap<>();
|
||||
WpakBlock root;
|
||||
PowerActionTemplate powerAction;
|
||||
int blockCount = 0;
|
||||
int failedCount = 0;
|
||||
|
||||
try {
|
||||
root = WpakFileReader.readBlocks(fileName);
|
||||
} catch (Exception e) {
|
||||
Logger.error(fileName + ": " + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
for (WpakLine line : root.lines()) {
|
||||
Logger.error(line.location() + ": line outside POWERACTIONBEGIN/END");
|
||||
failedCount++;
|
||||
}
|
||||
|
||||
for (WpakBlock block : root.blocks()) {
|
||||
try {
|
||||
if (!block.name.equals("POWERACTION"))
|
||||
throw block.beginLine.error("unexpected " + block.name + "BEGIN");
|
||||
|
||||
blockCount++;
|
||||
powerAction = parsePowerActionBlock(block);
|
||||
|
||||
// The client's map assigns on insert, so a repeated id keeps the later entry
|
||||
if (powerActions.containsKey(powerAction.action_id))
|
||||
Logger.warn(block.location() + ": duplicate power action " + powerAction.action_id + "; the later entry wins");
|
||||
|
||||
powerActions.put(powerAction.action_id, powerAction);
|
||||
} catch (WpakFormatException e) {
|
||||
Logger.error(e.getMessage());
|
||||
failedCount++;
|
||||
} catch (Exception e) {
|
||||
Logger.error(block.location() + ": " + e);
|
||||
failedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.info("Loaded " + powerActions.size() + " of " + blockCount + " power actions");
|
||||
|
||||
if (failedCount > 0)
|
||||
return null;
|
||||
|
||||
return powerActions;
|
||||
}
|
||||
|
||||
private static PowerActionTemplate parsePowerActionBlock(WpakBlock block) {
|
||||
|
||||
PowerActionTemplate powerAction = new PowerActionTemplate();
|
||||
ArrayList<WpakLine> lines = block.lines();
|
||||
WpakLine header;
|
||||
|
||||
if (!block.blocks().isEmpty())
|
||||
throw block.blocks().get(0).beginLine.error("nested block in a power action");
|
||||
|
||||
if (lines.isEmpty())
|
||||
throw block.beginLine.error("empty power action");
|
||||
|
||||
header = lines.get(0);
|
||||
powerAction.action_id = header.token(0);
|
||||
powerAction.action_type = header.enumToken(mbEnums.PowerActionType.class, 1);
|
||||
powerAction.header_args.addAll(header.tokens.subList(2, header.size()));
|
||||
|
||||
parseHeader(powerAction, header);
|
||||
|
||||
for (int i = 1; i < lines.size(); i++)
|
||||
parseKey(powerAction, lines.get(i));
|
||||
|
||||
return powerAction;
|
||||
}
|
||||
|
||||
// Layouts from the ArcPowerAction*_ParseHeader functions; args start at token 2
|
||||
|
||||
private static void parseHeader(PowerActionTemplate powerAction, WpakLine header) {
|
||||
|
||||
switch (powerAction.action_type) {
|
||||
case ApplyEffect:
|
||||
case Block:
|
||||
case Charm:
|
||||
case DirectDamage:
|
||||
case Invis:
|
||||
case SpireDisable:
|
||||
powerAction.effect_ids.add(header.token(2));
|
||||
powerAction.isaggressive = header.optionalIntToken(3) != 0;
|
||||
break;
|
||||
case ApplyEffects:
|
||||
powerAction.isaggressive = header.intToken(2) != 0;
|
||||
for (int i = 3; i < header.size(); i++)
|
||||
powerAction.effect_ids.add(header.token(i));
|
||||
break;
|
||||
case DeferredPower:
|
||||
powerAction.effect_ids.add(header.token(2));
|
||||
powerAction.isaggressive = header.intToken(3) != 0;
|
||||
powerAction.deferred_power_id = header.token(4);
|
||||
break;
|
||||
case DamageOverTime:
|
||||
powerAction.effect_ids.add(header.token(2));
|
||||
powerAction.num_iterations = header.intToken(3);
|
||||
powerAction.isaggressive = header.optionalIntToken(4) != 0;
|
||||
break;
|
||||
case CreateMob:
|
||||
powerAction.mob_id = header.intToken(2);
|
||||
powerAction.mob_level = header.intToken(3);
|
||||
break;
|
||||
case RemoveEffect:
|
||||
powerAction.effect_source_to_remove = header.token(2);
|
||||
break;
|
||||
case SetItemFlag:
|
||||
powerAction.item_flag = header.enumToken(mbEnums.ItemFlags.class, 2);
|
||||
powerAction.item_flag_value = header.optionalBoolToken(3);
|
||||
break;
|
||||
case Track:
|
||||
powerAction.effect_ids.add(header.token(2));
|
||||
powerAction.track_filter = header.token(5);
|
||||
powerAction.max_track = header.intToken(7);
|
||||
break;
|
||||
case Transform:
|
||||
powerAction.effect_ids.add(header.token(2));
|
||||
break;
|
||||
case TransferStat:
|
||||
parseTransferStat(powerAction, header);
|
||||
break;
|
||||
case TransferStatOT:
|
||||
parseTransferStat(powerAction, header);
|
||||
powerAction.effect_ids.add(header.token(10));
|
||||
powerAction.num_iterations = header.intToken(11);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseTransferStat(PowerActionTemplate powerAction, WpakLine header) {
|
||||
|
||||
powerAction.transfer_from_type = header.enumToken(mbEnums.CostType.class, 2);
|
||||
powerAction.transfer_to_type = header.enumToken(mbEnums.CostType.class, 3);
|
||||
powerAction.transfer_amount = header.floatToken(4);
|
||||
powerAction.transfer_amount_curve = header.token(5);
|
||||
powerAction.transfer_efficiency = header.floatToken(6);
|
||||
powerAction.transfer_efficiency_curve = header.token(7);
|
||||
powerAction.isaggressive = header.optionalIntToken(9) != 0;
|
||||
}
|
||||
|
||||
// Shared keys from ArcPowerAction_ParseKey 0x579820; the rest are per action type.
|
||||
// The client logs an unknown key and goes on; here it fails the load so no data is dropped silently.
|
||||
|
||||
private static void parseKey(PowerActionTemplate powerAction, WpakLine line) {
|
||||
|
||||
switch (line.token(0)) {
|
||||
case "ISAGGRESSIVE=":
|
||||
powerAction.isaggressive = line.optionalBoolToken(1);
|
||||
break;
|
||||
case "BLADETRAILS=":
|
||||
powerAction.bladetrails = line.optionalBoolToken(1);
|
||||
break;
|
||||
case "ISRESISTABLE=":
|
||||
powerAction.isresistable = line.optionalBoolToken(1);
|
||||
break;
|
||||
case "APPLYEFFECTSELF=":
|
||||
powerAction.applyeffectself = line.token(1);
|
||||
break;
|
||||
case "APPLYEFFECTOTHER=":
|
||||
powerAction.applyeffectother = line.token(1);
|
||||
break;
|
||||
case "WEAROFFEFFECTSELF=":
|
||||
powerAction.wearoffeffectself = line.token(1);
|
||||
break;
|
||||
case "WEAROFFEFFECTOTHER=":
|
||||
powerAction.wearoffeffectother = line.token(1);
|
||||
break;
|
||||
case "APPLYEFFECTBLANK=":
|
||||
powerAction.applyeffectblank = line.optionalBoolToken(1);
|
||||
break;
|
||||
case "WEAROFFEFFECTBLANK=":
|
||||
powerAction.wearoffeffectblank = line.optionalBoolToken(1);
|
||||
break;
|
||||
case "IS_AVAILABLE_FOR_FORMULAE=":
|
||||
powerAction.is_available_for_formulae = line.optionalBoolToken(1);
|
||||
break;
|
||||
case "REMOVEALL=":
|
||||
powerAction.removeall = line.optionalBoolToken(1);
|
||||
break;
|
||||
case "BODYPARTS=":
|
||||
powerAction.bodyparts.addAll(line.intTokensFrom(1));
|
||||
break;
|
||||
case "FEMALEBODYPARTS=":
|
||||
powerAction.femalebodyparts.addAll(line.intTokensFrom(1));
|
||||
break;
|
||||
case "SHOULDSHOWWEAPONS=":
|
||||
powerAction.shouldshowweapons = line.optionalBoolToken(1);
|
||||
break;
|
||||
case "SHOULDSHOWARMOR=":
|
||||
powerAction.shouldshowarmor = line.optionalBoolToken(1);
|
||||
break;
|
||||
case "ATTACKANIMS=":
|
||||
powerAction.attackanims.addAll(line.intTokensFrom(1));
|
||||
break;
|
||||
case "SCALEFACTOR=":
|
||||
powerAction.scalefactor = new float[]{line.floatToken(1), line.floatToken(2), line.floatToken(3)};
|
||||
break;
|
||||
case "EFFECTID=":
|
||||
powerAction.effectid = line.token(1);
|
||||
break;
|
||||
case "LEVELCAP=":
|
||||
powerAction.levelcap = line.intToken(1);
|
||||
powerAction.levelcap_curve = line.has(2) ? line.token(2) : null;
|
||||
break;
|
||||
case "CLEARAGGRO=":
|
||||
powerAction.clearaggro = line.optionalBoolToken(1);
|
||||
break;
|
||||
case "TARGETBECOMESPET=":
|
||||
powerAction.targetbecomespet = line.optionalBoolToken(1);
|
||||
break;
|
||||
case "DESTROYOLDPET=":
|
||||
powerAction.destroyoldpet = line.optionalBoolToken(1);
|
||||
break;
|
||||
case "ROOTFSMID=":
|
||||
powerAction.rootfsmid = line.token(1);
|
||||
break;
|
||||
case "DAMAGETYPE=":
|
||||
powerAction.damagetype = line.enumToken(mbEnums.DamageType.class, 1);
|
||||
break;
|
||||
case "SPLASHDAMAGE=":
|
||||
powerAction.splashdamage.addAll(line.intTokensFrom(1));
|
||||
break;
|
||||
default:
|
||||
throw line.error("unknown power action key " + line.token(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.wpak;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
// A XxxBEGIN/XxxEND block (name "Xxx"), or the whole file for the root block.
|
||||
// Lines and nested blocks keep their file order in children.
|
||||
|
||||
public class WpakBlock {
|
||||
|
||||
public final String name;
|
||||
public final WpakLine beginLine;
|
||||
public final ArrayList<Object> children = new ArrayList<>();
|
||||
|
||||
public WpakBlock(String name, WpakLine beginLine) {
|
||||
this.name = name;
|
||||
this.beginLine = beginLine;
|
||||
}
|
||||
|
||||
public String location() {
|
||||
return beginLine == null ? name : beginLine.location();
|
||||
}
|
||||
|
||||
public ArrayList<WpakLine> lines() {
|
||||
|
||||
ArrayList<WpakLine> lines = new ArrayList<>();
|
||||
|
||||
for (Object child : children)
|
||||
if (child instanceof WpakLine)
|
||||
lines.add((WpakLine) child);
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
public ArrayList<WpakBlock> blocks() {
|
||||
|
||||
ArrayList<WpakBlock> blocks = new ArrayList<>();
|
||||
|
||||
for (Object child : children)
|
||||
if (child instanceof WpakBlock)
|
||||
blocks.add((WpakBlock) child);
|
||||
|
||||
return blocks;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.wpak;
|
||||
|
||||
// A malformed wpak cfg entry. The message starts with file:line.
|
||||
|
||||
public class WpakFormatException extends RuntimeException {
|
||||
|
||||
public WpakFormatException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.wpak;
|
||||
|
||||
import engine.parsers.cache.CacheParserUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
// One non-comment line of a wpak cfg file, split into tokens.
|
||||
// Every accessor that fails names the file and line.
|
||||
|
||||
public class WpakLine {
|
||||
|
||||
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);
|
||||
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) {
|
||||
throw error("token " + index + " '" + value + "' is not an integer");
|
||||
}
|
||||
}
|
||||
|
||||
public float floatToken(int index) {
|
||||
|
||||
String value = token(index);
|
||||
|
||||
try {
|
||||
return Float.parseFloat(value);
|
||||
} catch (NumberFormatException e) {
|
||||
throw error("token " + index + " '" + value + "' is not a number");
|
||||
}
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,8 @@ import engine.parsers.cache.RuneParser;
|
||||
import engine.parsers.objects.DeedTemplate;
|
||||
import engine.parsers.objects.ItemTemplate;
|
||||
import engine.parsers.objects.RuneTemplate;
|
||||
import engine.parsers.objects.wpak.PowerActionTemplate;
|
||||
import engine.parsers.wpak.PowerActionParser;
|
||||
import engine.powers.RunePowerEntry;
|
||||
import engine.powers.RuneSkillAdjustEntry;
|
||||
import engine.server.MBServerStatics;
|
||||
@@ -334,6 +336,14 @@ public class WorldServer {
|
||||
Logger.info("Indexing Cache Templates");
|
||||
CObjectIndex.build();
|
||||
|
||||
Logger.info("Loading Power Actions");
|
||||
PowerActionTemplate._powerActions = PowerActionParser.parsePowerActionsFile();
|
||||
|
||||
if (PowerActionTemplate._powerActions == null || PowerActionTemplate._powerActions.isEmpty()) {
|
||||
Logger.error("ABORT! Power actions failed to load!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
Logger.info("Loading PromotionClasses");
|
||||
PromotionClass.LoadAllPromotions();
|
||||
|
||||
|
||||
Reference in new issue
Block a user