// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ // ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ // Magicbane Emulator Project © 2013 - 2024 // www.magicbane.com package engine.wpak; import engine.gameManager.ConfigManager; import engine.mbEnums; import engine.wpak.data.EffectDescription; import engine.wpak.data.PowerActionEntry; import engine.wpak.data.StatTransfer; import engine.wpak.data.TrackEntry; 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.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()) { PowerActionEntry powerActionEntry = parsePowerActionEntry(matcher.group().trim()); } } private static PowerActionEntry parsePowerActionEntry(String powerActionData) { PowerActionEntry powerActionEntry = new PowerActionEntry(); EffectDescription effectDescription; StatTransfer statTransfer; TrackEntry trackEntry; try { // Remove all lines that contain a # and leading/trailing blank lines powerActionData = powerActionData.replaceAll("(?m)^(\\s*#.*|\\s*)\r?\n?", "").trim(); String[] lineData = powerActionData.split("\n"); // Parse effect entry header Iterator entryIterator = Arrays.stream(lineData).iterator(); String headerLine = entryIterator.next(); ArrayList headerData = new ArrayList<>(); Matcher matcher = STRSPLIT_REGEX.matcher(headerLine.trim()); while (matcher.find()) headerData.add(matcher.group().trim()); Iterator headerIterator = headerData.iterator(); powerActionEntry.action_id = headerIterator.next(); powerActionEntry.action_type = headerIterator.next(); switch (powerActionEntry.action_type) { case "RemoveEffect": effectDescription = new EffectDescription(); effectDescription.effect_id = headerIterator.next(); powerActionEntry.effects.add(effectDescription); break; case "CreateMob": powerActionEntry.petLevel = Integer.parseInt(headerIterator.next()); powerActionEntry.petRace = Integer.parseInt(headerIterator.next()); break; case "DamageOverTime": effectDescription = new EffectDescription(); effectDescription.effect_id = headerIterator.next(); effectDescription.cycleDuration = Integer.parseInt(headerIterator.next()); effectDescription.cycleDelay = Integer.parseInt(headerIterator.next()); powerActionEntry.effects.add(effectDescription); break; case "ApplyEffects": int level = Integer.parseInt(headerIterator.next()); while (headerIterator.hasNext()) { effectDescription = new EffectDescription(); effectDescription.level = level; effectDescription.effect_id = headerIterator.next(); powerActionEntry.effects.add(effectDescription); } break; case "Transform": case "Invis": case "ApplyEffect": case "DeferredPower": case "DirectDamage": while (headerIterator.hasNext()) { effectDescription = new EffectDescription(); effectDescription.effect_id = headerIterator.next(); // Some applyEffect entries are naked withot a level if (headerData.size() > 3) effectDescription.level = Integer.parseInt(headerIterator.next()); powerActionEntry.effects.add(effectDescription); } break; case "TransferStat": statTransfer = new StatTransfer(); statTransfer.fromStat = mbEnums.CostType.valueOf(headerIterator.next()); statTransfer.toStat = mbEnums.CostType.valueOf(headerIterator.next()); statTransfer.fromStatValue = Float.parseFloat(headerIterator.next()); statTransfer.fromCurve = mbEnums.CompoundCurveType.valueOf(headerIterator.next()); statTransfer.toStatValue = Float.parseFloat(headerIterator.next()); statTransfer.toCurve = mbEnums.CompoundCurveType.valueOf(headerIterator.next()); statTransfer.fromStatBool = Boolean.parseBoolean(headerIterator.next()); statTransfer.toStatBool = Boolean.parseBoolean(headerIterator.next()); powerActionEntry.statTransfer = statTransfer; break; case "TransferStatOT": statTransfer = new StatTransfer(); statTransfer.fromStat = mbEnums.CostType.valueOf(headerIterator.next()); statTransfer.toStat = mbEnums.CostType.valueOf(headerIterator.next()); statTransfer.fromStatValue = Float.parseFloat(headerIterator.next()); statTransfer.fromCurve = mbEnums.CompoundCurveType.valueOf(headerIterator.next()); statTransfer.toStatValue = Float.parseFloat(headerIterator.next()); statTransfer.toCurve = mbEnums.CompoundCurveType.valueOf(headerIterator.next()); statTransfer.fromStatBool = Boolean.parseBoolean(headerIterator.next()); statTransfer.toStatBool = Boolean.parseBoolean(headerIterator.next()); statTransfer.transfer_action = headerIterator.next(); statTransfer.transfer_ticks = Integer.parseInt(headerIterator.next()); powerActionEntry.statTransfer = statTransfer; break; case "Charm": effectDescription = new EffectDescription(); effectDescription.effect_id = headerIterator.next(); effectDescription.level = Integer.parseInt(headerIterator.next()); effectDescription.type = headerIterator.next(); powerActionEntry.effects.add(effectDescription); break; case "Block": effectDescription = new EffectDescription(); effectDescription.effect_id = headerIterator.next(); effectDescription.level = Integer.parseInt(headerIterator.next()); powerActionEntry.effects.add(effectDescription); break; case "Resurrect": powerActionEntry.levelCap = Integer.parseInt(headerIterator.next()); break; case "SetItemFlag": powerActionEntry.itemFlag = mbEnums.ItemFlags.valueOf(headerIterator.next()); break; case "Track": trackEntry = new TrackEntry(); trackEntry.action_id = headerIterator.next(); trackEntry.unknown1 = Boolean.parseBoolean(headerIterator.next()); trackEntry.unknown2 = Boolean.parseBoolean(headerIterator.next()); trackEntry.type = headerIterator.next(); trackEntry.min = Integer.parseInt(headerIterator.next()); trackEntry.max = Integer.parseInt(headerIterator.next()); powerActionEntry.trackEntry = trackEntry; break; case "Recall": // No arguments for these tags or not parsed case "Teleport": case "TreeChoke": case "SimpleDamage": case "MobRecall": // One argument always zero case "ClearAggro": case "ClearNearbyAggro": case "Peek": case "ClaimMine": case "RunegateTeleport": break; default: Logger.error("Unhandled type " + powerActionEntry.action_type + " for Pow4erAction: " + powerActionEntry.action_id); break; } // Process key value pairs after header while (entryIterator.hasNext()) { String lineValue = entryIterator.next(); String[] lineValues = lineValue.split("="); String key = lineValues[0].trim(); String[] arguments; switch (key) { case "BODYPARTS": arguments = lineValues[1].trim().split("\\s+"); for (String bodyPart : arguments) powerActionEntry.bodyparts.add(Integer.parseInt(bodyPart)); break; case "FEMALEBODYPARTS": arguments = lineValues[1].trim().split("\\s+"); for (String bodyPart : arguments) powerActionEntry.femaleBodyParts.add(Integer.parseInt(bodyPart)); break; case "SCALEFACTOR": arguments = lineValues[1].trim().split("\\s+"); for (String scaleFactor : arguments) powerActionEntry.scaleFactor.add(Float.parseFloat(scaleFactor)); break; case "ISRESISTABLE": powerActionEntry.isResistible = Boolean.parseBoolean(lineValues[1].trim()); break; case "ISAGGRESSIVE": powerActionEntry.isAggressive = Boolean.parseBoolean(lineValues[1].trim()); break; case "BLADETRAILS": powerActionEntry.bladeTrails = Boolean.parseBoolean(lineValues[1].trim()); break; case "SHOULDSHOWWEAPONS": powerActionEntry.shouldShowWeapons = Boolean.parseBoolean(lineValues[1].trim()); break; case "SHOULDSHOWARMOR": powerActionEntry.shouldShowArmor = Boolean.parseBoolean(lineValues[1].trim()); break; case "APPLYEFFECTBLANK": powerActionEntry.applyEffectBlank = Boolean.parseBoolean(lineValues[1].trim()); break; case "WEAROFFEFFECTBLANK": powerActionEntry.wearOffEffectBlank = Boolean.parseBoolean(lineValues[1].trim()); break; case "ATTACKANIMS": arguments = lineValues[1].trim().split("\\s+"); for (String animation : arguments) powerActionEntry.attackAnimations.add(Integer.parseInt(animation)); break; case "REMOVEALL": powerActionEntry.removeAll = Boolean.parseBoolean(lineValues[1].trim()); break; case "EFFECTID": effectDescription = new EffectDescription(); effectDescription.effect_id = lineValues[1].trim(); powerActionEntry.effects.add(effectDescription); break; case "LEVELCAP": arguments = lineValues[1].trim().split("\\s+"); powerActionEntry.levelCap = Integer.parseInt(arguments[0]); if (arguments.length > 1) // Not all level caps have a curve powerActionEntry.levelCurve = mbEnums.CompoundCurveType.valueOf(arguments[1]); break; case "CLEARAGGRO": powerActionEntry.clearAggro = Boolean.parseBoolean(lineValues[1].trim()); break; case "TARGETBECOMESPET": powerActionEntry.targetBecomesPet = Boolean.parseBoolean(lineValues[1].trim()); break; case "DESTROYOLDPET": powerActionEntry.destroyOldPet = Boolean.parseBoolean(lineValues[1].trim()); break; case "DAMAGETYPE": powerActionEntry.damageType = mbEnums.DamageType.valueOf(lineValues[1].trim().toUpperCase()); break; case "ROOTFSMID": powerActionEntry.rootFsmID = mbEnums.MobBehaviourType.valueOf(lineValues[1].trim()); break; case "APPLYEFFECTOTHER": case "APPLYEFFECTSELF": case "WEAROFFEFFECTOTHER": // Keys not parsed go here. case "WEAROFFEFFECTSELF": break; default: Logger.error("Unhandled variable type:" + key + " for powerAction: " + powerActionEntry.action_id); } } } catch (Exception e) { Logger.error(powerActionEntry.action_id + " " + e); } return powerActionEntry; } }