diff --git a/src/main/java/in/arcadelabs/labaide/experience/ExperienceManager.java b/src/main/java/in/arcadelabs/labaide/experience/ExperienceManager.java new file mode 100644 index 0000000..b381bdc --- /dev/null +++ b/src/main/java/in/arcadelabs/labaide/experience/ExperienceManager.java @@ -0,0 +1,140 @@ +/* + * LabAide - Common utility library for our products. + * Copyright (C) 2022 ArcadeLabs Production. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package in.arcadelabs.labaide.experience; + +import org.bukkit.entity.Player; + +/** + * Good job Mojang with messed up XP maths that I don't understand. + * Thanks to Jikoo for this utility class that made my life easier. + */ +public class ExperienceManager { + /** + * 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 Experience#Leveling_up + */ + public static int getExp(final 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 Experience#Leveling_up + */ + public static int getExpFromLevel(final 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(final 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(final 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 Experience#Leveling_up + */ + private static int getExpToNext(final 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. + * + *

This method is preferred over {@link Player#giveExp(int)}. + *
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. + *
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(final 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)); + } +} \ No newline at end of file