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