Last active
December 11, 2024 17:09
-
-
Save Jikoo/30ec040443a4701b8980 to your computer and use it in GitHub Desktop.
A utility for managing experience with Bukkit.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.jikoo.planarwrappers.util; | |
import org.bukkit.entity.Player; | |
/** | |
* A utility for managing player experience. | |
*/ | |
public final class Experience { | |
/** | |
* Calculate a player's total experience based on level and progress to next. | |
* | |
* @param player the Player | |
* @return the amount of experience the Player has | |
* | |
* @see <a href=http://minecraft.wiki/Experience#Leveling_up>Experience#Leveling_up</a> | |
*/ | |
public static int getExp(Player player) { | |
return getExpFromLevel(player.getLevel()) | |
+ Math.round(getExpToNext(player.getLevel()) * player.getExp()); | |
} | |
/** | |
* Calculate total experience based on level. | |
* | |
* @param level the level | |
* @return the total experience calculated | |
* | |
* @see <a href=http://minecraft.wiki/Experience#Leveling_up>Experience#Leveling_up</a> | |
*/ | |
public static int getExpFromLevel(int level) { | |
if (level > 30) { | |
return (int) (4.5 * level * level - 162.5 * level + 2220); | |
} | |
if (level > 15) { | |
return (int) (2.5 * level * level - 40.5 * level + 360); | |
} | |
return level * level + 6 * level; | |
} | |
/** | |
* Calculate level (including progress to next level) based on total experience. | |
* | |
* @param exp the total experience | |
* @return the level calculated | |
*/ | |
public static double getLevelFromExp(long exp) { | |
int level = getIntLevelFromExp(exp); | |
// Get remaining exp progressing towards next level. Cast to float for next bit of math. | |
float remainder = exp - (float) getExpFromLevel(level); | |
// Get level progress with float precision. | |
float progress = remainder / getExpToNext(level); | |
// Slap both numbers together and call it a day. While it shouldn't be possible for progress | |
// to be an invalid value (value < 0 || 1 <= value) | |
return ((double) level) + progress; | |
} | |
/** | |
* Calculate level based on total experience. | |
* | |
* @param exp the total experience | |
* @return the level calculated | |
*/ | |
public static int getIntLevelFromExp(long exp) { | |
if (exp > 1395) { | |
return (int) ((Math.sqrt(72 * exp - 54215D) + 325) / 18); | |
} | |
if (exp > 315) { | |
return (int) (Math.sqrt(40 * exp - 7839D) / 10 + 8.1); | |
} | |
if (exp > 0) { | |
return (int) (Math.sqrt(exp + 9D) - 3); | |
} | |
return 0; | |
} | |
/** | |
* Get the total amount of experience required to progress to the next level. | |
* | |
* @param level the current level | |
* | |
* @see <a href=http://minecraft.wiki/Experience#Leveling_up>Experience#Leveling_up</a> | |
*/ | |
private static int getExpToNext(int level) { | |
if (level >= 30) { | |
// Simplified formula. Internal: 112 + (level - 30) * 9 | |
return level * 9 - 158; | |
} | |
if (level >= 15) { | |
// Simplified formula. Internal: 37 + (level - 15) * 5 | |
return level * 5 - 38; | |
} | |
// Internal: 7 + level * 2 | |
return level * 2 + 7; | |
} | |
/** | |
* Change a Player's experience. | |
* | |
* <p>This method is preferred over {@link Player#giveExp(int)}. | |
* <br>In older versions the method does not take differences in exp per level into account. | |
* This leads to overlevelling when granting players large amounts of experience. | |
* <br>In modern versions, while differing amounts of experience per level are accounted for, the | |
* approach used is loop-heavy and requires an excessive number of calculations, which makes it | |
* quite slow. | |
* | |
* @param player the Player affected | |
* @param exp the amount of experience to add or remove | |
*/ | |
public static void changeExp(Player player, int exp) { | |
exp += getExp(player); | |
if (exp < 0) { | |
exp = 0; | |
} | |
double levelAndExp = getLevelFromExp(exp); | |
int level = (int) levelAndExp; | |
player.setLevel(level); | |
player.setExp((float) (levelAndExp - level)); | |
} | |
private Experience() {} | |
} |
@oddlama Nice catch. Looks like it used to be correct, I wonder how I pulled that off. I'll add unit tests with standard exp <-> levels to cover that for the future.
License?
WTFPL/Unlicense, do what you like.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey @Jikoo, thanks for your work! It looks like you made a slight mistake while simplifying the formula here.
I believe this should read
level * 9 - 158
, and as far as I can see this also affects your PlanarWrappers utility library.