// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ // ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ // 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.power_actions.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 = 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; } }