// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ // ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ // Magicbane Emulator Project © 2013 - 2024 // www.magicbane.com package engine.wpak; import engine.gameManager.ConfigManager; import engine.mbEnums; import engine.util.Hasher; import engine.wpak.data.Effect; import engine.wpak.data.PowerAction; import engine.wpak.data.StatTransfer; import engine.wpak.data.TrackEntry; import engine.wpakpowers.WpakPowerManager; import org.pmw.tinylog.Logger; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; public class PowerActionParser { private static final Pattern STRSPLIT_REGEX = Pattern.compile("([^\"]\\S*|\"[^\"]*\")\\s*"); private static final Pattern POWER_ACTION_REGEX = Pattern.compile("(?<=POWERACTIONBEGIN)(.+?)(?=POWERACTIONEND)", Pattern.DOTALL); private static final String powerActionPath = ConfigManager.DEFAULT_DATA_DIR + "wpak/PowerActions.cfg"; public static void parseWpakFile() { // Read .wpak file from disk byte[] fileData; try { fileData = Files.readAllBytes(Paths.get(powerActionPath)); } catch (IOException e) { throw new RuntimeException(e); } String fileContents = new String(fileData); // Iterate over power entries from .wpak data Matcher matcher = POWER_ACTION_REGEX.matcher(fileContents); while (matcher.find()) { PowerAction powerAction = parsePowerActionEntry(matcher.group().trim()); WpakPowerManager._powerActionLookup.put(Hasher.SBStringHash(powerAction.action_id), powerAction); } } private static PowerAction parsePowerActionEntry(String powerActionData) { PowerAction powerAction = new PowerAction(); Effect effect; StatTransfer statTransfer; TrackEntry trackEntry; // Remove all lines that contain a # and leading/trailing blank lines powerActionData = powerActionData.replaceAll("(?m)^(\\s*#.*|\\s*)\r?\n?", "").trim(); List lineData = Arrays.asList(powerActionData.split("\n")); // Parse effect entry header Iterator entryIterator = lineData.iterator(); String headerLine = entryIterator.next(); List headerData = new ArrayList<>(); Matcher matcher = STRSPLIT_REGEX.matcher(headerLine.trim()); while (matcher.find()) headerData.add(matcher.group().trim()); Iterator headerIterator = headerData.iterator(); powerAction.action_id = headerIterator.next(); powerAction.action_type = mbEnums.PowerActionType.valueOf(headerIterator.next()); switch (powerAction.action_type) { case RemoveEffect: effect = new Effect(); effect.effect_id = headerIterator.next(); powerAction.effects.add(effect); break; case CreateMob: powerAction.petRace = Integer.parseInt(headerIterator.next()); powerAction.petLevel = Integer.parseInt(headerIterator.next()); break; case DamageOverTime: effect = new Effect(); effect.effect_id = headerIterator.next(); effect.cycleDuration = Integer.parseInt(headerIterator.next()); effect.cycleDelay = Integer.parseInt(headerIterator.next()); powerAction.effects.add(effect); break; case ApplyEffects: int level = Integer.parseInt(headerIterator.next()); while (headerIterator.hasNext()) { effect = new Effect(); effect.level = level; effect.effect_id = headerIterator.next(); powerAction.effects.add(effect); } break; case Transform: case Invis: case ApplyEffect: case DeferredPower: case DirectDamage: case SpireDisable: while (headerIterator.hasNext()) { effect = new Effect(); effect.effect_id = headerIterator.next(); // Some applyEffect entries are naked withot a level if (headerData.size() > 3) effect.level = Integer.parseInt(headerIterator.next()); powerAction.effects.add(effect); } break; case TransferStat: statTransfer = new StatTransfer(); statTransfer.fromStat = mbEnums.CostType.valueOf(headerIterator.next()); statTransfer.toStat = mbEnums.CostType.valueOf(headerIterator.next()); statTransfer.ramp = Float.parseFloat(headerIterator.next()); statTransfer.rampCurve = mbEnums.CompoundCurveType.valueOf(headerIterator.next()); statTransfer.efficiency = Float.parseFloat(headerIterator.next()); statTransfer.efficiencyCurve = mbEnums.CompoundCurveType.valueOf(headerIterator.next()); statTransfer.fromStatBool = Boolean.parseBoolean(headerIterator.next()); statTransfer.isDrain = Boolean.parseBoolean(headerIterator.next()); powerAction.statTransfer = statTransfer; break; case TransferStatOT: statTransfer = new StatTransfer(); statTransfer.fromStat = mbEnums.CostType.valueOf(headerIterator.next()); statTransfer.toStat = mbEnums.CostType.valueOf(headerIterator.next()); statTransfer.ramp = Float.parseFloat(headerIterator.next()); statTransfer.rampCurve = mbEnums.CompoundCurveType.valueOf(headerIterator.next()); statTransfer.efficiency = Float.parseFloat(headerIterator.next()); statTransfer.efficiencyCurve = mbEnums.CompoundCurveType.valueOf(headerIterator.next()); statTransfer.fromStatBool = Boolean.parseBoolean(headerIterator.next()); statTransfer.isDrain = Boolean.parseBoolean(headerIterator.next()); statTransfer.transfer_action = headerIterator.next(); statTransfer.transfer_ticks = Integer.parseInt(headerIterator.next()); powerAction.statTransfer = statTransfer; break; case Charm: effect = new Effect(); effect.effect_id = headerIterator.next(); effect.level = Integer.parseInt(headerIterator.next()); effect.type = headerIterator.next(); powerAction.effects.add(effect); break; case Block: effect = new Effect(); effect.effect_id = headerIterator.next(); effect.level = Integer.parseInt(headerIterator.next()); powerAction.effects.add(effect); break; case Resurrect: powerAction.ramp = Integer.parseInt(headerIterator.next()); break; case SetItemFlag: powerAction.itemFlag = mbEnums.ItemFlags.valueOf(headerIterator.next()); break; case Track: trackEntry = new TrackEntry(); trackEntry.action_id = headerIterator.next(); trackEntry.trackPlayer = Boolean.parseBoolean(headerIterator.next()); trackEntry.trackCorpse = Boolean.parseBoolean(headerIterator.next()); trackEntry.filter = mbEnums.MonsterType.valueOf(headerIterator.next()); trackEntry.min = Integer.parseInt(headerIterator.next()); trackEntry.max = Integer.parseInt(headerIterator.next()); powerAction.trackEntry = trackEntry; break; case Teleport: if (headerIterator.hasNext()) powerAction.ignoreNoTeleSpire = Boolean.parseBoolean(headerIterator.next()); break; case Recall: // No arguments for these tags or not parsed case Summon: case TreeChoke: case SimpleDamage: case MobRecall: // One argument always zero case ClearAggro: case ClearNearbyAggro: case Peek: case ClaimMine: case RunegateTeleport: case Steal: break; default: Logger.error("Unhandled type " + powerAction.action_type + " for Pow4erAction: " + powerAction.action_id); break; } // Process key value pairs after header while (entryIterator.hasNext()) { String lineValue = entryIterator.next(); List lineValues = Arrays.asList(lineValue.split("=")); String key = lineValues.get(0).trim(); List arguments; switch (key) { case "BODYPARTS": arguments = Arrays.asList(lineValues.get(1).trim().split("\\s+")); for (String bodyPart : arguments) powerAction.bodyParts.add(Integer.parseInt(bodyPart)); break; case "FEMALEBODYPARTS": arguments = Arrays.asList(lineValues.get(1).trim().split("\\s+")); for (String bodyPart : arguments) powerAction.femaleBodyParts.add(Integer.parseInt(bodyPart)); break; case "SCALEFACTOR": arguments = Arrays.asList(lineValues.get(1).trim().split("\\s+")); for (String scaleFactor : arguments) powerAction.scaleFactor.add(Float.parseFloat(scaleFactor)); break; case "ISRESISTABLE": powerAction.isResistible = Boolean.parseBoolean(lineValues.get(1).trim()); break; case "ISAGGRESSIVE": powerAction.isAggressive = Boolean.parseBoolean(lineValues.get(1).trim()); break; case "BLADETRAILS": powerAction.bladeTrails = Boolean.parseBoolean(lineValues.get(1).trim()); break; case "SHOULDSHOWWEAPONS": powerAction.shouldShowWeapons = Boolean.parseBoolean(lineValues.get(1).trim()); break; case "SHOULDSHOWARMOR": powerAction.shouldShowArmor = Boolean.parseBoolean(lineValues.get(1).trim()); break; case "APPLYEFFECTBLANK": powerAction.applyEffectBlank = Boolean.parseBoolean(lineValues.get(1).trim()); break; case "WEAROFFEFFECTBLANK": powerAction.wearOffEffectBlank = Boolean.parseBoolean(lineValues.get(1).trim()); break; case "ATTACKANIMS": arguments = Arrays.asList(lineValues.get(1).trim().split("\\s+")); for (String animation : arguments) powerAction.attackAnimations.add(Integer.parseInt(animation)); break; case "REMOVEALL": powerAction.removeAll = Boolean.parseBoolean(lineValues.get(1).trim()); break; case "EFFECTID": effect = new Effect(); effect.effect_id = lineValues.get(1).trim(); powerAction.effects.add(effect); break; case "LEVELCAP": arguments = Arrays.asList(lineValues.get(1).trim().split("\\s+")); powerAction.ramp = Integer.parseInt(arguments.get(0)); if (arguments.size() > 1) // Not all level caps have a curve powerAction.rampCurve = mbEnums.CompoundCurveType.valueOf(arguments.get(1)); break; case "CLEARAGGRO": powerAction.clearAggro = Boolean.parseBoolean(lineValues.get(1).trim()); break; case "TARGETBECOMESPET": powerAction.targetBecomesPet = Boolean.parseBoolean(lineValues.get(1).trim()); break; case "DESTROYOLDPET": powerAction.destroyOldPet = Boolean.parseBoolean(lineValues.get(1).trim()); break; case "DAMAGETYPE": powerAction.damageType = mbEnums.DamageType.valueOf(lineValues.get(1).trim().toUpperCase()); break; case "ROOTFSMID": powerAction.rootFsmID = mbEnums.MobBehaviourType.valueOf(lineValues.get(1).trim()); break; case "SPLASHDAMAGE": arguments = Arrays.asList(lineValues.get(1).trim().split("\\s+")); powerAction.splashDamageMin = Integer.parseInt(arguments.get(0)); powerAction.splashDamageMax = Integer.parseInt(arguments.get(1)); break; case "APPLYEFFECTOTHER": case "APPLYEFFECTSELF": case "WEAROFFEFFECTOTHER": // Keys not parsed go here. case "WEAROFFEFFECTSELF": break; default: Logger.error("Unhandled variable type:" + key + " for powerAction: " + powerAction.action_id); } } return powerAction; } }