Skip to content

Commit

Permalink
feat: ExperienceManager
Browse files Browse the repository at this point in the history
  • Loading branch information
RealRONiN committed Aug 24, 2022
1 parent c5d0f68 commit cc788d8
Showing 1 changed file with 140 additions and 0 deletions.
140 changes: 140 additions & 0 deletions src/main/java/in/arcadelabs/labaide/experience/ExperienceManager.java
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*/

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 <a href="https://gist.github.com/Jikoo/30ec040443a4701b8980">Jikoo</a> 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 <a href=http://minecraft.gamepedia.com/Experience#Leveling_up>Experience#Leveling_up</a>
*/
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 <a href=http://minecraft.gamepedia.com/Experience#Leveling_up>Experience#Leveling_up</a>
*/
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 <a href=http://minecraft.gamepedia.com/Experience#Leveling_up>Experience#Leveling_up</a>
*/
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.
*
* <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(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));
}
}

0 comments on commit cc788d8

Please sign in to comment.