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