// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ // ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ // 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 parseEffectsFile() { HashMap 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 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; } }