Files
prestonbane/src/engine/parsers/wpak/PowerActionParser.java
T
MagicBotandClaude Opus 5.5 3fc5e7253a Build power actions from PowerActions.cfg
PowersManager.loadPowerActions builds every AbstractPowerAction from
PowerActionTemplate (template ctor on all subclasses), in id order with IDs
1..3046; OPENGATE keeps 5000. The ResultSet loader stays until removal.
The parser now reads the SimpleDamage (min, max), Teleport (bool), Track
(two bools) and TransferStat (bool at token 8) header fields.

Parity (JSON-Refactor-Plan/scripts/p47.py, every column): ids 3046/3046 plus
the DB-only AELFBORN row, which nothing references (the cfg has
BOON-AELFBORN). Effect ids, deferred powers, RemoveEffect source/REMOVEALL,
track filter/max, transfer types/ramps/ramp-add, mob level identical. The old
flags match the new header fields: Track 1024/2048 = tokens 3/4,
TransferStat 16384 = token 8, Teleport 32768 = SAB-001A's TRUE.

Value changes (PowerActions.cfg wins):
- DamageOverTime/TransferStatOT: the cfg value is the tick count; tick length
  = action duration / ticks. The DB stored cfg+1 as tick seconds, which only
  worked for 30s DoTs (5 x 6s, unchanged). HOLY-DOT-17 (60s) goes 10 -> 5
  ticks, BLEED-DOT (4s) 0 -> 4, TransferStatOT 30s 6 -> 5 ticks.
- TransferStat amounts are read with ToInt as the client does: 16 actions
  drop by 0.5 (BEL-001A 31.5 -> 31, CON-001A 13.5 -> 13, ...).
- PRE-334..337 become ApplyEffects with one effect; ApplyEffectsPowerAction
  now allows a missing second effect on baked-in items.
- Charm level cap ramp from the LEVELCAP= curve (same values as the DB).
- Charm actions with effect FEAR/CONFUSION (12, DB type Confusion) stay
  ConfusionPowerAction no-ops; CHARM ones use CharmPowerAction (decided).
- CreateMob: the cfg client mob id (49..74, 3500, 3501) maps to the mobbase
  through a map in CreateMobPowerAction, from the old DB values.
- The 50 Charm/Block/SpireDisable rows with no DB effect id get the cfg id
  (unread by those classes); isAggressive differs on 330 rows (unread).
- SimpleDamage keeps min/max instead of the always-0 simpleDamage (unread).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 05:37:36 -04:00

269 lines
11 KiB
Java

// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// 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).
// CompoundCurves.cfg must be loaded first.
public class PowerActionParser {
private static final String fileName = "Config/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_player = header.boolToken(3);
powerAction.track_corpse = header.boolToken(4);
powerAction.track_filter = header.token(5);
powerAction.max_track = header.intToken(7);
break;
case Transform:
powerAction.effect_ids.add(header.token(2));
break;
case SimpleDamage:
powerAction.simple_damage_min = header.floatToken(2);
powerAction.simple_damage_max = header.floatToken(3);
break;
case Teleport:
powerAction.ignore_no_tele_spire = header.optionalBoolToken(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 = WpakParserUtil.curveToken(header, 5);
powerAction.transfer_efficiency = header.floatToken(6);
powerAction.transfer_efficiency_curve = WpakParserUtil.curveToken(header, 7);
powerAction.transfer_target_to_caster = header.boolToken(8);
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) ? WpakParserUtil.curveToken(line, 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));
}
}
}