diff --git a/src/engine/gameManager/PowersManager.java b/src/engine/gameManager/PowersManager.java index 54b17321..6ca102f8 100644 --- a/src/engine/gameManager/PowersManager.java +++ b/src/engine/gameManager/PowersManager.java @@ -10,7 +10,6 @@ package engine.gameManager; import engine.InterestManagement.Terrain; import engine.InterestManagement.WorldGrid; -import engine.db.handlers.dbSkillReqHandler; import engine.job.AbstractJob; import engine.job.AbstractScheduleJob; import engine.job.JobContainer; @@ -36,11 +35,9 @@ import engine.server.MBServerStatics; import org.pmw.tinylog.Logger; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.Set; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadLocalRandom; @@ -61,8 +58,6 @@ public enum PowersManager { public static HashMap> _allRunePowers; public static HashMap> _allRuneSkillAdjusts; public static HashMap> _effect_costMaps = new HashMap<>(); - // Succor, Phaedra's Fury and Fleet of Foot keep their DB target type until the Phase 4 power task decides them - private static final Set DB_TARGET_TYPE_POWERS = new HashSet<>(Arrays.asList("HLR-025", "HNT-023", "RNG-033")); private static JobScheduler js; public static void initPowersManager(boolean fullPowersLoad) { @@ -243,41 +238,24 @@ public enum PowersManager { PowersManager.effectsBaseByIDString.put(effectsBase.getIDString(), effectsBase); } - // Powers.cfg target type wins over static_power_powerbase - private static void applyClientTargetTypes() { + // Powers.cfg in file order, ids 1..n (the old static_power_powerbase IDs) + private static void loadPowers() { - PowerTemplate powerTemplate; - int applied = 0; + int powerID = 0; + PowersBase powersBase; - for (PowersBase powersBase : PowersManager.powersBaseByIDString.values()) { - - if (DB_TARGET_TYPE_POWERS.contains(powersBase.getIDString())) - continue; - - powerTemplate = PowerTemplate._powers.get(powersBase.getIDString()); - - if (powerTemplate == null || powerTemplate.target_type == null) { - Logger.warn("Power " + powersBase.getIDString() + " has no Powers.cfg target type; keeping the DB value"); - continue; - } - - powersBase.setTargetType(powerTemplate.target_type.name()); - applied++; + for (PowerTemplate powerTemplate : PowerTemplate._powers.values()) { + powersBase = new PowersBase(powerTemplate, ++powerID); + PowersManager.powersBaseByIDString.put(powersBase.getIDString(), powersBase); + PowersManager.powersBaseByToken.put(powersBase.getToken(), powersBase); } - Logger.info("Applied Powers.cfg target types to " + applied + " powers"); + Logger.info("Loaded " + PowersManager.powersBaseByIDString.size() + " powers from Powers.cfg"); } - // This pre-loads only PowersBase for login + // The login server needs only the token map public static void InitializeLoginPowers() { - - // get all PowersBase - ArrayList pbList = dbSkillReqHandler.getAllPowersBase(); - - for (PowersBase pb : pbList) { - if (pb.getToken() != 0) - PowersManager.powersBaseByToken.put(pb.getToken(), pb); - } + loadPowers(); } public static ArrayList getPowersForRune(int rune_id) { @@ -297,25 +275,11 @@ public enum PowersManager { loadPowerActions(); - // get all PowersBase - ArrayList pbList = dbSkillReqHandler.getAllPowersBase(); - for (PowersBase pb : pbList) { - if (pb.getToken() != 0) { - PowersManager.powersBaseByIDString.put(pb.getIDString(), pb); - PowersManager.powersBaseByToken.put(pb.getToken(), pb); - } - } + loadPowers(); - // Add Power Prereqs - PowerPrereq.getAllPowerPrereqs(PowersManager.powersBaseByIDString); - // Add Fail Conditions - dbSkillReqHandler.getFailConditions(PowersManager.powersBaseByIDString); // Add Actions Base ActionsBase.getActionsBase(PowersManager.powersBaseByIDString, PowersManager.powerActionsByIDString); - - applyClientTargetTypes(); - } public static EffectsBase setEffectToken(int ID, int token) { diff --git a/src/engine/parsers/objects/wpak/CurveRamp.java b/src/engine/parsers/objects/wpak/CurveRamp.java index 8d97954c..17a6d135 100644 --- a/src/engine/parsers/objects/wpak/CurveRamp.java +++ b/src/engine/parsers/objects/wpak/CurveRamp.java @@ -17,6 +17,9 @@ public class CurveRamp { private static final int RAMP_RANGE = 100; + // No curve: no ramp, add formula (the DB default) + public static final CurveRamp NONE = new CurveRamp(0, true); + public final float ramp; public final boolean useAdd; diff --git a/src/engine/powers/PowerPrereq.java b/src/engine/powers/PowerPrereq.java index 4411766c..0049d7b1 100644 --- a/src/engine/powers/PowerPrereq.java +++ b/src/engine/powers/PowerPrereq.java @@ -10,6 +10,8 @@ package engine.powers; import engine.gameManager.DbManager; +import engine.mbEnums.EquipSlotType; +import engine.parsers.objects.wpak.PowerTemplate; import org.pmw.tinylog.Logger; import java.sql.Connection; @@ -61,6 +63,33 @@ public class PowerPrereq { } } + // EFFECTPREREQ= on the caster + public PowerPrereq(PowerTemplate.EffectPrereq effectPrereq) { + + this.effect = effectPrereq.effect_id; + this.message = effectPrereq.message; + this.mainHand = false; + this.required = false; + } + + // TARGETEFFECTPREREQS_ANDED= / _ORED= on the target + public PowerPrereq(PowerTemplate.EffectPrereq targetPrereq, boolean anded) { + + this.effect = targetPrereq.effect_id; + this.message = ""; + this.mainHand = false; + this.required = anded; + } + + // EQPREREQ=: the message holds the skill name + public PowerPrereq(PowerTemplate.EquipPrereq equipPrereq) { + + this.effect = ""; + this.message = equipPrereq.skill; + this.mainHand = equipPrereq.slot == EquipSlotType.RHELD; + this.required = equipPrereq.required; + } + public static void getAllPowerPrereqs(HashMap powers) { try (Connection connection = DbManager.getConnection(); diff --git a/src/engine/powers/PowersBase.java b/src/engine/powers/PowersBase.java index 43f5f5c5..3f51e930 100644 --- a/src/engine/powers/PowersBase.java +++ b/src/engine/powers/PowersBase.java @@ -9,10 +9,20 @@ package engine.powers; +import engine.mbEnums.AreaType; +import engine.mbEnums.CastingModeType; +import engine.mbEnums.CostType; +import engine.mbEnums.ExcludeType; import engine.mbEnums.MonsterType; import engine.mbEnums.PowerCategoryType; import engine.mbEnums.PowerTargetType; +import engine.mbEnums.PowerType; +import engine.mbEnums.TargetSelectType; import engine.parsers.cache.CacheParserUtil; +import engine.parsers.objects.wpak.CurveRamp; +import engine.parsers.objects.wpak.EffectTemplate; +import engine.parsers.objects.wpak.PowerTemplate; +import engine.util.Hasher; import org.pmw.tinylog.Logger; import java.sql.ResultSet; @@ -22,6 +32,9 @@ import java.util.EnumSet; public class PowersBase { + // Patch notes: "The Fury's persistent AEs now hit 10 targets instead of 7"; the client ctor leaves these unset + private static final int DEFAULT_MAX_TARGETS = 7; + public final EnumSet monsterTypeRestrictions = EnumSet.noneOf(MonsterType.class); public final ArrayList actions = new ArrayList<>(); public final ArrayList effectPrereqs = new ArrayList<>(); @@ -285,6 +298,189 @@ public class PowersBase { this.monsterTypeRestrictions.add(CacheParserUtil.readEnum(MonsterType.class, ct, this.UUID, "monsterTypeRestrict3")); } + public PowersBase(PowerTemplate powerTemplate, int id) { + + CurveRamp hateRamp; + CurveRamp costRamp; + CurveRamp recycleRamp; + CurveRamp initRamp; + + this.UUID = id; + this.IDString = powerTemplate.power_id; + + hateRamp = rampOf(powerTemplate.hatevalue_curve, "HATEVALUE"); + costRamp = rampOf(powerTemplate.curves.get("COSTAMT"), "COSTAMT"); + recycleRamp = rampOf(powerTemplate.curves.get("RECYCLETIME"), "RECYCLETIME"); + initRamp = rampOf(powerTemplate.curves.get("INITTIME"), "INITTIME"); + + this.name = powerTemplate.power_name; + this.token = Hasher.SBStringHash(this.IDString); + this.isSpell = powerTemplate.power_type == PowerType.SPELL; + this.skillID = powerTemplate.icon_number; // the DB skillID column held the icon number; unread + this.skillName = powerTemplate.focus; + this.range = powerTemplate.range; + this.description = String.join(" ", powerTemplate.descriptions); + this.useHealth = powerTemplate.cost_type == CostType.HEALTH; + this.useMana = powerTemplate.cost_type == CostType.MANA; + this.useStamina = powerTemplate.cost_type == CostType.STAMINA; + this.setTargetType(powerTemplate.target_type.name()); + this.setCategory(powerTemplate.category); + + this.cost = powerTemplate.cost_amt; + this.costRamp = costRamp.ramp; + this.costRampAdd = costRamp.useAdd; + this.castTime = powerTemplate.init_time; + this.castTimeRamp = initRamp.ramp; + this.initRampAdd = initRamp.useAdd; + this.cooldown = powerTemplate.release_time; + this.recycleTime = powerTemplate.recycle_time; + this.recycleRamp = recycleRamp.ramp; + this.recycleRampAdd = recycleRamp.useAdd; + this.maxTrains = powerTemplate.maxlevel; + this.hateValue = powerTemplate.hatevalue; + this.hateRamp = hateRamp.ramp; + this.hateRampAdd = hateRamp.useAdd; + + // Only CLICK sets a source: the old loader's NAME and NEARBYMOBS branches were unreachable + this.targetFromLastTarget = !this.targetSelf && !this.targetGroup + && powerTemplate.target_sel == TargetSelectType.CLICK; + + this.monsterTypePrereq = powerTemplate.monstertypeprereqs.isEmpty() ? "" : powerTemplate.monstertypeprereqs.get(0).name(); + this.monsterTypeRestrictions.addAll(powerTemplate.monstertyperestricts); + + this.isAOE = powerTemplate.area_type == AreaType.CONE || powerTemplate.area_type == AreaType.POINTBLANK + || powerTemplate.area_type == AreaType.SPHERE; + this.useCone = powerTemplate.area_type == AreaType.CONE; + this.usePointBlank = powerTemplate.area_type == AreaType.POINTBLANK; + this.useSphere = powerTemplate.area_type == AreaType.SPHERE; + this.radius = powerTemplate.area_range; + this.setExcludeType(powerTemplate.exclude_type); + this.maxNumMobTargets = (powerTemplate.maxnummobtargets == 0) ? DEFAULT_MAX_TARGETS : powerTemplate.maxnummobtargets; + this.maxNumPlayerTargets = (powerTemplate.maxnumplayertargets == 0) ? DEFAULT_MAX_TARGETS : powerTemplate.maxnumplayertargets; + + this.chantDuration = powerTemplate.pulseinfo_duration; + this.chantIterations = powerTemplate.pulseinfo_iterations; + this.isChant = powerTemplate.pulseinfo_iterations > 0; + + this.allowedInCombat = powerTemplate.casting_mode == CastingModeType.COMBAT || powerTemplate.casting_mode == CastingModeType.BOTH; + this.allowedOutOfCombat = powerTemplate.casting_mode == CastingModeType.NONCOMBAT || powerTemplate.casting_mode == CastingModeType.BOTH; + this.regularPlayerCanCast = "PGOV_PLAYER".equals(powerTemplate.grantoverridevar); + + this.canCastWhileMoving = powerTemplate.cancastwhilemoving; + this.canCastWhileFlying = powerTemplate.cancastwhileflying; + this.losCheck = powerTemplate.shouldcheckpath; + this.sticky = powerTemplate.sticky; + this.isAdminPower = powerTemplate.isadminpower; + this.requiresHitRoll = powerTemplate.hit_roll_yn; + + for (PowerTemplate.EffectPrereq effectPrereq : powerTemplate.effectprereqs) + this.effectPrereqs.add(new PowerPrereq(effectPrereq)); + + for (PowerTemplate.EquipPrereq equipPrereq : powerTemplate.eqprereqs) + this.equipPrereqs.add(new PowerPrereq(equipPrereq)); + + for (PowerTemplate.EffectPrereq targetPrereq : powerTemplate.targeteffectprereqs_anded) + this.targetEffectPrereqs.add(new PowerPrereq(targetPrereq, true)); + + for (PowerTemplate.EffectPrereq targetPrereq : powerTemplate.targeteffectprereqs_ored) + this.targetEffectPrereqs.add(new PowerPrereq(targetPrereq, false)); + + for (EffectTemplate.ConditionEntry condition : powerTemplate.conditions) + switch (condition.condition) { + case CastSpell: + this.cancelOnCastSpell = true; + break; + case TakeDamage: + this.cancelOnTakeDamage = true; + break; + } + } + + private CurveRamp rampOf(String curveName, String field) { + + CurveRamp curveRamp; + + if (curveName == null) + return CurveRamp.NONE; + + curveRamp = CurveRamp.fromCurve(curveName); + + if (curveRamp == null) { + Logger.warn("Power " + this.IDString + " " + field + " curve " + curveName + " has no ramp form"); + return CurveRamp.NONE; + } + + return curveRamp; + } + + // CHANT and uncategorised powers are never harmful: the old loader tested the cost type against target type names + private void setCategory(PowerCategoryType categoryType) { + + this.powerCategory = (categoryType == null) ? PowerCategoryType.NONE : categoryType; + this.category = (categoryType == null) ? "" : categoryType.name(); + + switch (this.powerCategory) { + case WEAPON: + this.isWeaponPower = true; + this.isHarmful = false; + if (this.skillName.equals("Bow") || this.skillName.equals("Crossbow") || this.skillName.equals("Archery")) + this.weaponRange = 1000f; + else if (this.skillName.equals("Throwing")) + this.weaponRange = 60f; + else + this.weaponRange = 15f; + break; + case HEAL: + case GROUPHEAL: + this.isHeal = true; + this.isHarmful = false; + break; + case TRACK: + this.isTrack = true; + this.isHarmful = false; + break; + case AE: + case AEDAMAGE: + case BREAKFLY: + case DAMAGE: + case DEBUFF: + case MOVE: + case SPECIAL: + case SPIREDISABLE: + this.isHarmful = true; + break; + case VAMPDRAIN: + this.vampDrain = true; + this.isHarmful = true; + break; + default: + this.isHarmful = false; + } + } + + private void setExcludeType(ExcludeType excludeType) { + + switch (excludeType) { + case CASTER: + this.isCasterFriendly = true; + break; + case GROUP: + this.isGroupFriendly = true; + this.isCasterFriendly = true; + break; + case ALLBUTGROUP: + this.isGroupOnly = true; + break; + case ALLBUTPETS: + this.mustHitPets = true; + break; + case NATION: + this.isNationFriendly = true; + this.isCasterFriendly = true; + break; + } + } + public void setTargetType(String targetType) { this.targetPlayer = false; @@ -362,8 +558,6 @@ public class PowersBase { } public int getToken() { - if (this.IDString.equals("BLEED-DOT-10.5-RANGE")) - return -369682965; return this.token; }