Compute XP thresholds from the client formula instead of the LevelToExp table
Client ArcCharacter_ExpForLevelUp (0x4715D0): XP to reach level L is (L-1)^3 * max(1, 1 + (L-40) * 0.0512f) * class * race rune_exp_req_to_level, rounded half up. getLevel steps levels with it up to LEVELCAP. Both methods now take the PlayerCharacter so the factors come from its runes; the CSR promotions 2901+ have no rune and use the base class factor. Same values: every player race is 10 and every class rune 15, and the formula equals the removed table for all 76 levels. Levels above the table no longer use the old approximate fallback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
db67e56302
commit
0c7b6f6c46
2 files changed
+55
-135
No files matched your search
@@ -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<Integer, Integer> 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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user