diff --git a/src/engine/objects/Experience.java b/src/engine/objects/Experience.java index ec096a55..6028b987 100644 --- a/src/engine/objects/Experience.java +++ b/src/engine/objects/Experience.java @@ -12,114 +12,17 @@ package engine.objects; import engine.gameManager.ZoneManager; import engine.math.Vector3fImmutable; import engine.mbEnums.TargetColor; +import engine.parsers.objects.RuneTemplate; import engine.server.MBServerStatics; import java.util.ArrayList; -import java.util.TreeMap; import static engine.gameManager.LootManager.LOOTMANAGER; public class Experience { - private static final TreeMap ExpToLevel; - private static final int[] LevelToExp = {Integer.MIN_VALUE, // Pad - // everything - // over 1 - - // R0 - 0, // Level 1 - 150, // Level 2 - 1200, // Level 3 - 4050, // Level 4 - 9600, // Level 5 - 18750, // Level 6 - 32400, // Level 7 - 51450, // Level 8 - 76800, // Level 9 - - // R1 - 109350, // Level 10 - 150000, // Level 11 - 199650, // Level 12 - 259200, // Level 13 - 329550, // Level 14 - 411600, // Level 15 - 506250, // Level 16 - 614400, // Level 17 - 736950, // Level 18 - 874800, // Level 19 - - // R2 - 1028850, // Level 20 - 1200000, // Level 21 - 1389150, // Level 22 - 1597200, // Level 23 - 1825050, // Level 24 - 2073600, // Level 25 - 2343750, // Level 26 - 2636400, // Level 27 - 2952450, // Level 28 - 3292800, // Level 29 - - // R3 - 3658350, // Level 30 - 4050000, // Level 31 - 4468650, // Level 32 - 4915200, // Level 33 - 5390550, // Level 34 - 5895600, // Level 35 - 6431250, // Level 36 - 6998400, // Level 37 - 7597950, // Level 38 - 8230800, // Level 39 - - // R4 - 8897850, // Level 40 - 10091520, // Level 41 - 11396777, // Level 42 - 12820187, // Level 43 - 14368505, // Level 44 - 16048666, // Level 45 - 17867790, // Level 46 - 19833183, // Level 47 - 21952335, // Level 48 - 24232919, // Level 49 - - // R5 - 26682793, // Level 50 - 29310000, // Level 51 - 32122766, // Level 52 - 35129502, // Level 53 - 38338805, // Level 54 - 41759452, // Level 55 - 45400409, // Level 56 - 49270824, // Level 57 - 53380030, // Level 58 - 57737542, // Level 59 - - // R6 - 62353064, // Level 60 - 67236479, // Level 61 - 72397859, // Level 62 - 77847457, // Level 63 - 83595712, // Level 64 - 89653247, // Level 65 - 96030869, // Level 66 - 102739569, // Level 67 - 109790524, // Level 68 - 117195093, // Level 69 - - // R7 - 124964822, // Level 70 - 133111438, // Level 71 - 141646855, // Level 72 - 150583171, // Level 73 - 159932666, // Level 74 - 169707808, // Level 75 - 179921247, // Level 76 - - }; - + // Client ArcCharacter_ExpForLevelUp (0x4715D0) float constant at 0x154195C + private static final float EXP_LEVEL_SLOPE = 0.0512f; private static final float[] MaxExpPerLevel = {Float.MIN_VALUE, // Pad // everything // over @@ -227,39 +130,56 @@ public class Experience { // (expGranted / group.size()) * (groupBonus * (group.size()-1) +1) private static final float GROUP_BONUS = 0.5f; // 0.2 grants (20%) addtional - static { - ExpToLevel = new TreeMap<>(); - - // flip keys and values for other Map - for (int i = 1; i < LevelToExp.length; i++) { - ExpToLevel.put(LevelToExp[i], i); - } - } // end Static block // exp per group member // called to determine current level based on xp - public static int getLevel(int experience) { + public static int getLevel(PlayerCharacter player, int experience) { - int expKey = ExpToLevel.floorKey(experience); - int level = ExpToLevel.get(expKey); + int level = 1; - if (level > MBServerStatics.LEVELCAP) - level = MBServerStatics.LEVELCAP; + while (level < MBServerStatics.LEVELCAP && experience >= getBaseExperience(player, level + 1)) + level++; return level; } - // Get the base xp for a level - public static int getBaseExperience(int level) { + // The client function returns the XP to reach level + 1, so it is called with level - 1. + // Arithmetic order and the half-up rounding follow the client so every threshold matches. + public static int getBaseExperience(PlayerCharacter player, int level) { - if (level < LevelToExp.length) - return LevelToExp[level]; + int clientLevel = level - 1; + double levelScale; - int fLevel = level - 1; - int baseXP = fLevel * fLevel * fLevel; + levelScale = Math.max(1.0, (clientLevel - 39) * (double) EXP_LEVEL_SLOPE + 1.0); - return (int) ((fLevel < 40) ? (baseXP * 150) - : (baseXP * (150 + (7.6799998 * (level - 40))))); + return (int) (long) ((double) clientLevel * clientLevel * clientLevel * levelScale + * getClassExpFactor(player) * getRaceExpFactor(player) + 0.5); + } + + // rune_exp_req_to_level; the client uses 1.0 when the rune is missing + private static double getRaceExpFactor(PlayerCharacter player) { + + RuneTemplate rune = null; + + if (player.race != null) + rune = RuneTemplate._runes.get(player.race.getRaceRuneID()); + + return (rune == null) ? 1.0 : rune.rune_exp_req_to_level; + } + + // GUESS: the client's class rune (0x45EAD0) is the promotion once taken; every CLASS rune is 15 either way. + // The CSR promotions 2901+ have no rune, so they fall back to the base class. + private static double getClassExpFactor(PlayerCharacter player) { + + RuneTemplate rune = null; + + if (player.promotionClass != null) + rune = RuneTemplate._runes.get(player.promotionClass.getObjectUUID()); + + if (rune == null && player.baseClass != null) + rune = RuneTemplate._runes.get(player.baseClass.getObjectUUID()); + + return (rune == null) ? 1.0 : rune.rune_exp_req_to_level; } // Max XP granted for killing a blue, yellow or orange mob diff --git a/src/engine/objects/PlayerCharacter.java b/src/engine/objects/PlayerCharacter.java index ee21afd8..d97c1605 100644 --- a/src/engine/objects/PlayerCharacter.java +++ b/src/engine/objects/PlayerCharacter.java @@ -1267,7 +1267,7 @@ public class PlayerCharacter extends AbstractCharacter { player.endLoc = Vector3fImmutable.ZERO; //get level based on experience - player.level = (short) Experience.getLevel(player.exp); + player.level = (short) Experience.getLevel(player, player.exp); player.setHealth(999999f); player.mana.set(999999f); @@ -2828,7 +2828,7 @@ public class PlayerCharacter extends AbstractCharacter { } public short getPCLevel() { - short level = (short) Experience.getLevel(this.exp); + short level = (short) Experience.getLevel(this, this.exp); if (this.promotionClass == null && level >= 10) return (short) 10; @@ -2873,8 +2873,8 @@ public class PlayerCharacter extends AbstractCharacter { public synchronized void grantXP(int xp) { // Stop players from getting experience past the cap - if (this.exp + xp >= Experience.getBaseExperience(MBServerStatics.LEVELCAP)) - xp = Experience.getBaseExperience(MBServerStatics.LEVELCAP) - this.exp + 1; + if (this.exp + xp >= Experience.getBaseExperience(this, MBServerStatics.LEVELCAP)) + xp = Experience.getBaseExperience(this, MBServerStatics.LEVELCAP) - this.exp + 1; if (xp == 0) xp = 1; @@ -2890,7 +2890,7 @@ public class PlayerCharacter extends AbstractCharacter { //not promoted at level 10, start checking for negative EXP if (this.promotionClass == null && this.getLevel() == 10) { - if (this.getExp() == Experience.getBaseExperience(11)) { + if (this.getExp() == Experience.getBaseExperience(this, 11)) { if (this.overFlowEXP == 110000) return; @@ -2913,10 +2913,10 @@ public class PlayerCharacter extends AbstractCharacter { } else { //Player exp begins negative exp, add remaing exp after level 11 to overflow - if (this.getExp() + remainingXP >= Experience.getBaseExperience(11)) { + if (this.getExp() + remainingXP >= Experience.getBaseExperience(this, 11)) { - this.overFlowEXP = remainingXP - (Experience.getBaseExperience(11) - this.getExp()); - this.exp = Experience.getBaseExperience(11); + this.overFlowEXP = remainingXP - (Experience.getBaseExperience(this, 11) - this.getExp()); + this.exp = Experience.getBaseExperience(this, 11); GrantExperienceMsg grantExperienceMsg = new GrantExperienceMsg(this, remainingXP); Dispatch dispatch = Dispatch.borrow(this, grantExperienceMsg); @@ -2948,7 +2948,7 @@ public class PlayerCharacter extends AbstractCharacter { else nextLevel = level + 2; - int nextLevelEXP = Experience.getBaseExperience(nextLevel); + int nextLevelEXP = Experience.getBaseExperience(this, nextLevel); // if overflow > 0, u have level 11 experience + overflow, but level is still 10 due to just promoting. //Use level + 2 experience for next level. @@ -2971,8 +2971,8 @@ public class PlayerCharacter extends AbstractCharacter { checkGuildStatus(); this.addDatabaseJob("EXP", MBServerStatics.FIVE_MINUTES); // double overflow exp used up, remaining overflow will just add to level + 1. - } else if (this.getExp() + this.overFlowEXP >= Experience.getBaseExperience(level + 1)) { - int nextExperience = Experience.getBaseExperience(level + 1) + this.overFlowEXP; + } else if (this.getExp() + this.overFlowEXP >= Experience.getBaseExperience(this, level + 1)) { + int nextExperience = Experience.getBaseExperience(this, level + 1) + this.overFlowEXP; this.exp = nextExperience; this.level++; charReloadRequired = true; @@ -2988,10 +2988,10 @@ public class PlayerCharacter extends AbstractCharacter { } else { // Hand out each Level one at a time. - isNewLevel = Experience.getLevel(exp + remainingXP) > this.getLevel(); + isNewLevel = Experience.getLevel(this, exp + remainingXP) > this.getLevel(); if (isNewLevel) { - neededXP = Experience.getBaseExperience(this.getLevel() + 1) - this.exp; + neededXP = Experience.getBaseExperience(this, this.getLevel() + 1) - this.exp; charReloadRequired = true; this.exp += neededXP; @@ -4088,7 +4088,7 @@ public class PlayerCharacter extends AbstractCharacter { tmpLevel = (short) Math.min(tmpLevel, 75); while (this.level < tmpLevel) { - grantXP(Experience.getBaseExperience(tmpLevel) - this.exp); + grantXP(Experience.getBaseExperience(this, tmpLevel) - this.exp); } }