forked from MagicBane/Server
Files now load from mb.data/wpak/<archive>/<file>, matching MBDATA's data/wpak layout and the client's Config/Config.wpak naming, so more archives can sit beside Config/ later. All nine current files are in wpak/Config/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
225 lines
8.1 KiB
Java
225 lines
8.1 KiB
Java
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
|
// Magicbane Emulator Project © 2013 - 2026
|
|
// https://www.magicbane.com
|
|
|
|
package engine.parsers.wpak;
|
|
|
|
import engine.mbEnums;
|
|
import engine.parsers.objects.wpak.EffectTemplate;
|
|
import engine.parsers.objects.wpak.ModTypeTemplate;
|
|
import org.pmw.tinylog.Logger;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
|
|
// Loads Effects.cfg. ModTypes.cfg and CompoundCurves.cfg must be loaded first:
|
|
// a mod's argument layout comes from its behavior, and curve names are checked.
|
|
|
|
public class EffectParser {
|
|
|
|
private static final String fileName = "Config/Effects.cfg";
|
|
|
|
public static HashMap<String, EffectTemplate> parseEffectsFile() {
|
|
|
|
HashMap<String, EffectTemplate> effects = new HashMap<>();
|
|
WpakBlock root;
|
|
EffectTemplate effect;
|
|
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 EFFECTBEGIN/END");
|
|
failedCount++;
|
|
}
|
|
|
|
for (WpakBlock block : root.blocks()) {
|
|
try {
|
|
if (!block.name.equals("EFFECT"))
|
|
throw block.beginLine.error("unexpected " + block.name + "BEGIN");
|
|
|
|
blockCount++;
|
|
effect = parseEffectBlock(block);
|
|
|
|
if (effects.containsKey(effect.effect_id))
|
|
Logger.warn(block.location() + ": duplicate effect " + effect.effect_id + "; the later entry wins");
|
|
|
|
effects.put(effect.effect_id, effect);
|
|
} catch (WpakFormatException e) {
|
|
Logger.error(e.getMessage());
|
|
failedCount++;
|
|
} catch (Exception e) {
|
|
Logger.error(block.location() + ": " + e);
|
|
failedCount++;
|
|
}
|
|
}
|
|
|
|
Logger.info("Loaded " + effects.size() + " of " + blockCount + " effects");
|
|
|
|
if (failedCount > 0)
|
|
return null;
|
|
|
|
return effects;
|
|
}
|
|
|
|
private static EffectTemplate parseEffectBlock(WpakBlock block) {
|
|
|
|
EffectTemplate effect = new EffectTemplate();
|
|
ArrayList<WpakLine> lines = block.lines();
|
|
WpakLine header;
|
|
|
|
if (lines.isEmpty())
|
|
throw block.beginLine.error("effect has no header line");
|
|
|
|
header = lines.get(0);
|
|
effect.effect_id = header.token(0);
|
|
effect.effect_name = header.token(1);
|
|
effect.icon = header.optionalIntToken(2);
|
|
|
|
for (int i = 1; i < lines.size(); i++)
|
|
parseKey(effect, lines.get(i));
|
|
|
|
for (WpakBlock child : block.blocks()) {
|
|
switch (child.name) {
|
|
case "SOURCE":
|
|
for (WpakLine line : child.lines())
|
|
effect.sources.add(line.token(0));
|
|
break;
|
|
case "MODS":
|
|
for (WpakLine line : child.lines())
|
|
effect.mods.add(parseMod(line));
|
|
break;
|
|
case "CONDITION":
|
|
for (WpakLine line : child.lines())
|
|
effect.conditions.add(parseCondition(line));
|
|
break;
|
|
default:
|
|
throw child.beginLine.error("unexpected " + child.name + "BEGIN in an effect");
|
|
}
|
|
|
|
if (!child.blocks().isEmpty())
|
|
throw child.blocks().get(0).beginLine.error("nested block in " + child.name);
|
|
}
|
|
|
|
return effect;
|
|
}
|
|
|
|
// The client compares these case-insensitively (cfg has IsItemEffect, client ISITEMEFFECT)
|
|
|
|
private static void parseKey(EffectTemplate effect, WpakLine line) {
|
|
|
|
switch (line.token(0).toUpperCase()) {
|
|
case "ISITEMEFFECT":
|
|
effect.isitemeffect = true;
|
|
break;
|
|
case "DONTSAVE":
|
|
effect.dontsave = true;
|
|
break;
|
|
case "IGNORENOMOD":
|
|
effect.ignorenomod = true;
|
|
break;
|
|
case "ISSPIREEFFECT":
|
|
effect.isspireeffect = true;
|
|
break;
|
|
default:
|
|
throw line.error("unknown effect key " + line.token(0));
|
|
}
|
|
}
|
|
|
|
private static EffectTemplate.ModEntry parseMod(WpakLine line) {
|
|
|
|
EffectTemplate.ModEntry mod = new EffectTemplate.ModEntry();
|
|
ModTypeTemplate modType = ModTypeTemplate._modTypes.get(line.token(0));
|
|
|
|
if (modType == null)
|
|
throw line.error("mod type " + line.token(0) + " is not in ModTypes.cfg");
|
|
|
|
mod.mod_type = line.enumToken(mbEnums.ModType.class, 0);
|
|
mod.behavior = modType.behavior;
|
|
mod.args.addAll(line.tokens.subList(1, line.size()));
|
|
|
|
switch (mod.behavior) {
|
|
case "Flag":
|
|
break;
|
|
case "Standard":
|
|
parseStandard(mod, line);
|
|
break;
|
|
case "FPSubTypeAttr":
|
|
case "FPSubTypeSkill":
|
|
case "FPSubTypeDmg":
|
|
case "FPSubTypeMonster":
|
|
parseStandard(mod, line);
|
|
mod.type = line.token(4);
|
|
break;
|
|
case "DD":
|
|
mod.min_mod = line.floatToken(1);
|
|
mod.max_mod = line.floatToken(2);
|
|
mod.percent_mod = line.floatToken(3);
|
|
mod.curve = WpakParserUtil.curveToken(line, 5);
|
|
mod.type = line.token(6);
|
|
break;
|
|
case "ProcInfo":
|
|
mod.min_mod = line.floatToken(1);
|
|
mod.proc_power_id = line.token(2);
|
|
mod.percent_mod = line.floatToken(3);
|
|
break;
|
|
case "SubTypeSourceType":
|
|
case "SubTypePowerType":
|
|
mod.type = line.token(1);
|
|
mod.string1 = line.has(2) ? String.join(" ", line.tokens.subList(2, line.size())) : null;
|
|
break;
|
|
case "SubTypeSkill":
|
|
case "SubTypeDmg":
|
|
case "SubTypeMod":
|
|
case "SubTypePower":
|
|
mod.type = line.token(1);
|
|
break;
|
|
case "MapIntToInts":
|
|
mod.map_key = line.intToken(1);
|
|
mod.map_values.addAll(line.intTokensFrom(2));
|
|
break;
|
|
case "String":
|
|
mod.string1 = line.token(1);
|
|
mod.string2 = line.token(2);
|
|
break;
|
|
default:
|
|
throw line.error("unknown mod behavior " + mod.behavior + " for " + line.token(0));
|
|
}
|
|
|
|
return mod;
|
|
}
|
|
|
|
// Tokens after the curve are ignored by the client (some lines end in "Unknown")
|
|
|
|
private static void parseStandard(EffectTemplate.ModEntry mod, WpakLine line) {
|
|
|
|
mod.min_mod = line.floatToken(1);
|
|
mod.percent_mod = line.floatToken(2);
|
|
mod.curve = WpakParserUtil.curveToken(line, 3);
|
|
}
|
|
|
|
static EffectTemplate.ConditionEntry parseCondition(WpakLine line) {
|
|
|
|
EffectTemplate.ConditionEntry condition = new EffectTemplate.ConditionEntry();
|
|
|
|
condition.condition = line.enumToken(mbEnums.PowerFailCondition.class, 0);
|
|
condition.amount = line.floatToken(1);
|
|
condition.curve = line.has(2) ? WpakParserUtil.curveToken(line, 2) : null;
|
|
|
|
for (int i = 3; i < line.size(); i++)
|
|
condition.damage_types.add(line.enumToken(mbEnums.DamageType.class, i));
|
|
|
|
return condition;
|
|
}
|
|
}
|