diff --git a/src/engine/gameManager/PowersManager.java b/src/engine/gameManager/PowersManager.java index 9d4c552e..03986464 100644 --- a/src/engine/gameManager/PowersManager.java +++ b/src/engine/gameManager/PowersManager.java @@ -32,6 +32,7 @@ import engine.server.MBServerStatics; import engine.wpak.EffectsParser; import engine.wpak.PowerActionParser; import engine.wpak.PowersParser; +import engine.wpak.WpakPowerManager; import engine.wpak.data.Effect; import engine.wpak.data.PowerAction; import org.pmw.tinylog.Logger; @@ -118,7 +119,7 @@ public enum PowersManager { // Add EffectsBase ArrayList effectList = new ArrayList<>(); - for (Effect entry : EffectsParser.effect_data.values()) { + for (Effect entry : WpakPowerManager.effect_data.values()) { EffectsBase effectBase = new EffectsBase(entry); effectList.add(effectBase); PowersManager.effectsBaseByToken.put(effectBase.getToken(), effectBase); @@ -133,7 +134,7 @@ public enum PowersManager { HashMap effects = PowersManager.effectsBaseByIDString; - for (PowerAction powerAction : PowerActionParser.power_actions) { + for (PowerAction powerAction : WpakPowerManager.power_actions.values()) { AbstractPowerAction apa; String type = powerAction.action_type; String IDString = powerAction.action_id; diff --git a/src/engine/jobs/UsePowerJob.java b/src/engine/jobs/UsePowerJob.java index c9d4c18f..b6abc829 100644 --- a/src/engine/jobs/UsePowerJob.java +++ b/src/engine/jobs/UsePowerJob.java @@ -14,6 +14,7 @@ import engine.job.AbstractScheduleJob; import engine.net.client.msg.PerformActionMsg; import engine.objects.PlayerCharacter; import engine.powers.PowersBase; +import engine.wpak.data.Power; public class UsePowerJob extends AbstractScheduleJob { @@ -34,6 +35,16 @@ public class UsePowerJob extends AbstractScheduleJob { this.targetLiveCounter = targetLiveCounter; } + public UsePowerJob(PlayerCharacter pc, PerformActionMsg msg, int token, Power pb, int casterLiveCounter, int targetLiveCounter) { + super(); + this.pc = pc; + this.msg = msg; + this.token = token; + this.pb = pb; + this.casterLiveCounter = casterLiveCounter; + this.targetLiveCounter = targetLiveCounter; + } + @Override protected void doJob() { PowersManager.finishUsePower(this.msg, this.pc, casterLiveCounter, targetLiveCounter); diff --git a/src/engine/wpak/EffectsParser.java b/src/engine/wpak/EffectsParser.java index 0d7999d7..211239a5 100644 --- a/src/engine/wpak/EffectsParser.java +++ b/src/engine/wpak/EffectsParser.java @@ -25,7 +25,6 @@ import java.util.regex.Pattern; public class EffectsParser { public static String effectsPath = ConfigManager.DEFAULT_DATA_DIR + "wpak/Effects.cfg"; - public static HashMap effect_data = new HashMap<>(); private static final Pattern EFFECT_REGEX = Pattern.compile("(?<=EFFECTBEGIN)(.+?)(?=EFFECTEND)", Pattern.DOTALL); private static final Pattern SOURCE_REGEX = Pattern.compile("(?<=SOURCEBEGIN)(.+?)(?=SOURCEEND)", Pattern.DOTALL); private static final Pattern MODS_REGEX = Pattern.compile("(?<=MODSBEGIN)(.+?)(?=MODSEND)", Pattern.DOTALL); @@ -52,7 +51,7 @@ public class EffectsParser { while (matcher.find()) { Effect effect = parseEffectEntry(matcher.group()); - effect_data.put(effect.effect_id, effect); + WpakPowerManager.effect_data.put(effect.effect_id, effect); } } diff --git a/src/engine/wpak/PowerActionParser.java b/src/engine/wpak/PowerActionParser.java index e684d7cb..ce48cff4 100644 --- a/src/engine/wpak/PowerActionParser.java +++ b/src/engine/wpak/PowerActionParser.java @@ -10,6 +10,7 @@ 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; @@ -31,8 +32,6 @@ public class PowerActionParser { 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 ArrayList power_actions= new ArrayList<>(); - public static void parseWpakFile() { // Read .wpak file from disk @@ -54,7 +53,7 @@ public class PowerActionParser { while (matcher.find()) { PowerAction powerAction = parsePowerActionEntry(matcher.group().trim()); - power_actions.add(powerAction); + WpakPowerManager.power_actions.put(Hasher.SBStringHash(powerAction.action_id),powerAction); } } diff --git a/src/engine/wpak/PowersParser.java b/src/engine/wpak/PowersParser.java index b722de3b..59a206b2 100644 --- a/src/engine/wpak/PowersParser.java +++ b/src/engine/wpak/PowersParser.java @@ -10,6 +10,7 @@ package engine.wpak; import engine.gameManager.ConfigManager; import engine.mbEnums; +import engine.util.Hasher; import engine.wpak.data.*; import org.pmw.tinylog.Logger; @@ -50,7 +51,7 @@ public class PowersParser { while (matcher.find()) { Power power = parsePowerEntry(matcher.group().trim()); - + WpakPowerManager.powers.put(Hasher.SBStringHash(power.power_id),power); } } diff --git a/src/engine/wpak/WpakPowerManager.java b/src/engine/wpak/WpakPowerManager.java new file mode 100644 index 00000000..550f3391 --- /dev/null +++ b/src/engine/wpak/WpakPowerManager.java @@ -0,0 +1,332 @@ +package engine.wpak; + +import engine.gameManager.*; +import engine.job.JobContainer; +import engine.job.JobScheduler; +import engine.jobs.FinishRecycleTimeJob; +import engine.jobs.UsePowerJob; +import engine.math.Vector3fImmutable; +import engine.mbEnums; +import engine.net.Dispatch; +import engine.net.client.ClientConnection; +import engine.net.client.msg.ModifyHealthMsg; +import engine.net.client.msg.PerformActionMsg; +import engine.net.client.msg.RecyclePowerMsg; +import engine.net.client.msg.UpdateStateMsg; +import engine.objects.*; +import engine.powers.PowersBase; +import engine.server.MBServerStatics; +import engine.wpak.data.Effect; +import engine.wpak.data.EquipmentPreReq; +import engine.wpak.data.Power; +import engine.wpak.data.PowerAction; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; +import java.util.HashMap; + +import static engine.math.FastMath.sqr; + +public class WpakPowerManager { + public static HashMap effect_data = new HashMap<>(); + public static HashMap power_actions= new HashMap<>(); + public static HashMap powers= new HashMap<>(); + + private static JobScheduler js; + + public static void usePower(final PerformActionMsg msg, ClientConnection origin, + boolean sendCastToSelf) { + + if (ConfigManager.MB_RULESET.getValue().equals("LORE")) { + PowersBase pb = PowersManager.powersBaseByToken.get(msg.getPowerUsedID()); + PlayerCharacter caster = origin.getPlayerCharacter(); + PlayerCharacter target = PlayerCharacter.getFromCache(msg.getTargetID()); + if (pb != null && pb.enforceLore()) { + //if (caster.guild.equals(Guild.getErrantGuild())) + // return; + + if (target != null && caster.guild.getGuildType().equals(target.guild.getGuildType()) == false && target.getObjectType().equals(mbEnums.GameObjectType.Building) == false) { + RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(msg.getPowerUsedID()); + Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), recyclePowerMsg); + DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.PRIMARY); + + // Send Fail to cast message + PlayerCharacter pc = SessionManager + .getPlayerCharacter(origin); + + if (pc != null) { + sendPowerMsg(pc, 2, msg); + if (pc.isCasting()) { + pc.update(); + } + + pc.setIsCasting(false); + } + return; + } + } + } + + if (castPower(msg, origin, sendCastToSelf)) { + // Cast failed for some reason, reset timer + + RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(msg.getPowerUsedID()); + Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), recyclePowerMsg); + DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.PRIMARY); + + // Send Fail to cast message + PlayerCharacter pc = SessionManager + .getPlayerCharacter(origin); + + if (pc != null) { + sendPowerMsg(pc, 2, msg); + if (pc.isCasting()) { + pc.update(); + } + + pc.setIsCasting(false); + } + + } + } + + public static boolean castPower(final PerformActionMsg msg, ClientConnection origin, boolean sendCastToSelf) { + + //check to see if the caster is valid + PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter(origin); + if (playerCharacter == null) + return false; + + //make sure player is still alive + if (!playerCharacter.isAlive() && msg.getPowerUsedID() != 428589216) { //succor + RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(msg.getPowerUsedID()); + Dispatch dispatch = Dispatch.borrow(playerCharacter, recyclePowerMsg); + DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.PRIMARY); + return false; + } + + //make sure the recycle timer has actually elapsed + if (playerCharacter.getRecycleTimers().containsKey(msg.getPowerUsedID())) { + Logger.warn("usePowerA(): Cheat attempted? '" + msg.getPowerUsedID() + "' recycle timer not finished " + playerCharacter.getName()); + return false; + } + + //lookup the power that was cast + Power powerCast = powers.get(msg.getPowerUsedID()); + if (powerCast == null) { + ChatManager.chatSayInfo(playerCharacter, "This power is not implemented yet."); + return true; + } + + if (playerCharacter.getLastPower() != null) + return true; + + // get numTrains for power + int trains = msg.getNumTrains(); + + if (trains > powerCast.maxLevel) { + trains = powerCast.maxLevel; + msg.setNumTrains(trains); + } + + //sanity check for amount of trains in spell cast + if (playerCharacter.getPowers() != null && playerCharacter.getPowers().containsKey(msg.getPowerUsedID())) { + CharacterPower cp = playerCharacter.getPowers().get(msg.getPowerUsedID()); + if (cp != null) { + int tot = cp.getTotalTrains(); + if (tot == 0) + return false; + if (trains != tot) { + trains = tot; + msg.setNumTrains(trains); + } + } + } + + //get casting time + int time = powerCast.getRecycleTime(trains); + + //combat mode sanity check + if (playerCharacter.isCombat()) { + if (!powerCast.allowedInCombat()) + return true; + } else if (!powerCast.allowedOutOfCombat()) + return true; + + //stunned check + PlayerBonuses bonus = playerCharacter.getBonuses(); + mbEnums.SourceType sourceType = mbEnums.SourceType.GetSourceType(powerCast.category); + if (bonus != null && (bonus.getBool(mbEnums.ModType.Stunned, mbEnums.SourceType.None) || bonus.getBool(mbEnums.ModType.CannotCast, mbEnums.SourceType.None) || bonus.getBool(mbEnums.ModType.BlockedPowerType, sourceType))) + return true; + + //sanity check for casting while moving + Vector3fImmutable endLoc = playerCharacter.getEndLoc(); + + if (!powerCast.canCastWhileMoving) + if (playerCharacter.isMoving()) { + float distanceLeftSquared = endLoc.distanceSquared2D(playerCharacter.getLoc()); + if (distanceLeftSquared > sqr(playerCharacter.getSpeed())) + return true; + } + + //get the actual target form the message + int type = msg.getTargetType(); + int UUID = msg.getTargetID(); + + if (type == -1 || type == 0 || UUID == -1 || UUID == 0) + return true; + + AbstractWorldObject target = (AbstractWorldObject) DbManager.getObject(mbEnums.GameObjectType.values()[type], UUID); + + //check to make sure power can be cast on building if target is a building + if (target != null && target.getObjectType() == mbEnums.GameObjectType.Building && !powerCast.target_type.equals(mbEnums.PowerTargetType.BUILDING)) { + PowersManager.sendPowerMsg(playerCharacter, 9, new PerformActionMsg(msg)); + return true; + } + + //validate casting range + if (playerCharacter.getLoc().distanceSquared2D(msg.getTargetLoc()) > (powerCast.range * powerCast.range)) + return true; + + //validate prereqs for power cast + //equipment prereqs + if (!powerCast.equipmentPreReq.isEmpty()) { + for (EquipmentPreReq prereq : powerCast.equipmentPreReq) { + String requiredSkill = prereq.skill; + if (playerCharacter.charItemManager.equipped.get(prereq.slot) != null) { + Item equippedItem = playerCharacter.charItemManager.equipped.get(prereq.slot); + if (!equippedItem.template.item_skill_mastery_used.equals(requiredSkill) && !equippedItem.template.item_skill_used.equals(requiredSkill)) + return true; + } else { + return true; + } + } + } + + //effect prereqs + if (!powerCast.effectPreReqs.isEmpty()) { + for (Effect prereq : powerCast.effectPreReqs) { + if (!playerCharacter.effects.contains(prereq.effect_id) && !playerCharacter.effects.contains(prereq.effect_name)) + return true; + } + } + + float cost = powerCast.getCost(trains); + if (bonus != null) + cost *= (1 + bonus.getFloatPercentAll(mbEnums.ModType.PowerCost, mbEnums.SourceType.None)); + + if (playerCharacter.getAltitude() > 0) + cost *= 1.5f; + + if (cost > 0) + if ((playerCharacter.getObjectTypeMask() & MBServerStatics.MASK_UNDEAD) != 0) + if (playerCharacter.getHealth() <= cost) + return true; + else { + playerCharacter.modifyHealth(-cost, playerCharacter, true); + ModifyHealthMsg mhm = new ModifyHealthMsg(playerCharacter, playerCharacter, -cost, + 0f, 0f, 0, null, + 9999, 0); + mhm.setOmitFromChat(1); + DispatchManager.dispatchMsgToInterestArea(playerCharacter, mhm, mbEnums.DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + } + else if (powerCast.costType.name().equals("MANA")) + if (playerCharacter.getMana() < cost) + return true; + else + playerCharacter.modifyMana(-cost, playerCharacter, true); + else if (powerCast.costType.name().equals("STAMINA")) + if (playerCharacter.getStamina() < cost) + return true; + else + playerCharacter.modifyStamina(-cost, playerCharacter, true); + else if (playerCharacter.getHealth() <= cost) + return true; + else + playerCharacter.modifyHealth(-cost, playerCharacter, true); + + if (time > 0) { + FinishRecycleTimeJob frtj = new FinishRecycleTimeJob(playerCharacter, msg); + playerCharacter.getRecycleTimers().put(msg.getPowerUsedID(), js.scheduleJob(frtj, time)); + } else { + // else send recycle message to unlock power + RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(msg.getPowerUsedID()); + Dispatch dispatch = Dispatch.borrow(playerCharacter, recyclePowerMsg); + DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.PRIMARY); + } + int tr = msg.getNumTrains(); + DispatchManager.dispatchMsgToInterestArea(playerCharacter, msg, mbEnums.DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, sendCastToSelf, false); + + //Make new msg.. + PerformActionMsg copyMsg = new PerformActionMsg(msg); + copyMsg.setNumTrains(tr); + + // make person casting stand up if spell (unless they're casting a chant which does not make them stand up) + if (pb.isSpell() && !pb.isChant() && playerCharacter.isSit()) { + playerCharacter.update(); + playerCharacter.setSit(false); + UpdateStateMsg updateStateMsg = new UpdateStateMsg(playerCharacter); + DispatchManager.dispatchMsgToInterestArea(playerCharacter, updateStateMsg, mbEnums.DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + + } + + // update cast (use skill) fail condition + playerCharacter.cancelOnCast(); + + // update castSpell (use spell) fail condition if spell + if (pb.isSpell()) + playerCharacter.cancelOnSpell(); + + // get cast time in ms. + time = pb.getCastTime(trains); + + // set player is casting for regens + + + if (time > 100) { + playerCharacter.update(); + playerCharacter.setIsCasting(true); + } + + + playerCharacter.setLastMovementState(playerCharacter.getMovementState()); + + // run timer job to end cast + if (time < 1) // run immediately + finishUsePower(copyMsg, playerCharacter, casterLiveCounter, targetLiveCounter); + else { + UsePowerJob upj = new UsePowerJob(playerCharacter, copyMsg, copyMsg.getPowerUsedID(), powerCast, casterLiveCounter, targetLiveCounter); + JobContainer jc = js.scheduleJob(upj, time); + + // make lastPower + playerCharacter.setLastPower(jc); + } + + + return false; + } + + public static void finishUsePower(PerformActionMsg msg, int castCount, int targetCount){ + + } + + public static void sendPowerMsg(PlayerCharacter playerCharacter, int type, PerformActionMsg msg) { + + if (playerCharacter == null) + return; + + msg.setUnknown05(type); + + switch (type) { + case 3: + case 4: + msg.setUnknown04(2); + DispatchManager.dispatchMsgToInterestArea(playerCharacter, msg, mbEnums.DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); + break; + default: + msg.setUnknown04(1); + Dispatch dispatch = Dispatch.borrow(playerCharacter, msg); + DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.PRIMARY); + } + } +} diff --git a/src/engine/wpak/data/Power.java b/src/engine/wpak/data/Power.java index 90efcac5..a5c9790d 100644 --- a/src/engine/wpak/data/Power.java +++ b/src/engine/wpak/data/Power.java @@ -66,4 +66,44 @@ public class Power { public boolean isProjectile = false; public HashMap conditions = new HashMap<>(); + public int getRecycleTime(int trains) { // returns cast time in ms + if (this.curves.get("RECYCLETIME") != null) + return (int) (((this.recycle_time + (this.curves.get("RECYCLETIME").getValue() * trains)) * 1000) + getCastTime(trains)); + else + return (int) (((this.recycle_time * (1 + (this.curves.get("RECYCLETIME").getValue() * trains))) * 1000) + getCastTime(trains)); + } + public int getCastTime(int trains) { // returns cast time in ms + if (this.curves.get("INITTIME") != null) + return (int) ((this.init_time + (this.curves.get("INITTIME").getValue() * trains)) * 1000); + else + return (int) ((this.init_time * (1 + (this.curves.get("INITTIME").getValue() * trains))) * 1000); + } + + public boolean allowedInCombat() { + switch(castingMode.name()){ + case "NONE": + case "BOTH": + case "COMBAT": + return true; + } + return false; + } + + public boolean allowedOutOfCombat() { + switch(castingMode.name()){ + case "NONE": + case "BOTH": + case "NONCOMBAT": + return true; + } + return false; + } + + public float getCost(int trains) { + if (this.curves.get("COSTAMT") != null) + return this.cost + (float)(this.curves.get("COSTAMT").getValue() * trains); + else + return this.cost * (1 + (float)(this.curves.get("COSTAMT").getValue() * trains)); + + } }