Skip to content

Instantly share code, notes, and snippets.

@Jikoo
Last active April 30, 2024 17:14
Show Gist options
  • Star 19 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save Jikoo/30ec040443a4701b8980 to your computer and use it in GitHub Desktop.
Save Jikoo/30ec040443a4701b8980 to your computer and use it in GitHub Desktop.
A utility for managing experience with Bukkit.
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.gamepedia.com/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.gamepedia.com/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.gamepedia.com/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
Copy link

oddlama commented Oct 30, 2021

Hey @Jikoo, thanks for your work! It looks like you made a slight mistake while simplifying the formula here.

// Simplified formula. Internal: 112 + (level - 30) * 9
return level * 9 + 158;

I believe this should read level * 9 - 158, and as far as I can see this also affects your PlanarWrappers utility library.

@Jikoo
Copy link
Author

Jikoo commented Oct 30, 2021

@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.

@anjoismysign
Copy link

License?

@Jikoo
Copy link
Author

Jikoo commented Apr 30, 2024

WTFPL/Unlicense, do what you like.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment