diff --git a/src/main/java/net/Indyuce/mmocore/MMOCore.java b/src/main/java/net/Indyuce/mmocore/MMOCore.java
index 8d4b3b8c..d173f3d8 100644
--- a/src/main/java/net/Indyuce/mmocore/MMOCore.java
+++ b/src/main/java/net/Indyuce/mmocore/MMOCore.java
@@ -1,6 +1,7 @@
 package net.Indyuce.mmocore;
 
 import io.lumine.mythic.lib.MythicLib;
+import io.lumine.mythic.lib.UtilityMethods;
 import io.lumine.mythic.lib.comp.Metrics;
 import io.lumine.mythic.lib.version.SpigotPlugin;
 import io.lumine.mythic.utils.plugin.LuminePlugin;
@@ -40,7 +41,7 @@ import net.Indyuce.mmocore.manager.profession.*;
 import net.Indyuce.mmocore.manager.social.BoosterManager;
 import net.Indyuce.mmocore.manager.social.PartyManager;
 import net.Indyuce.mmocore.manager.social.RequestManager;
-import net.Indyuce.mmocore.skill.cast.listener.SkillBar;
+import net.Indyuce.mmocore.skill.cast.SkillCastingMode;
 import net.Indyuce.mmocore.skill.list.Ambers;
 import net.Indyuce.mmocore.skill.list.Neptune_Gift;
 import net.Indyuce.mmocore.skill.list.Sneaky_Picky;
@@ -221,6 +222,14 @@ public class MMOCore extends LuminePlugin {
 			DebugMode.enableActionBar();
 		}
 
+		// Skill casting
+		try {
+			SkillCastingMode mode = SkillCastingMode.valueOf(UtilityMethods.enumName(getConfig().getString("skill-casting.mode")));
+			Bukkit.getPluginManager().registerEvents(mode.loadFromConfig(getConfig().getConfigurationSection("skill-casting")), this);
+		} catch (RuntimeException exception) {
+			getLogger().log(Level.WARNING, "Could not load skill casting: " + exception.getMessage());
+		}
+
 		if (configManager.overrideVanillaExp = getConfig().getBoolean("override-vanilla-exp"))
 			Bukkit.getPluginManager().registerEvents(new VanillaExperienceOverride(), this);
 
diff --git a/src/main/java/net/Indyuce/mmocore/api/SoundEvent.java b/src/main/java/net/Indyuce/mmocore/api/SoundEvent.java
new file mode 100644
index 00000000..47993004
--- /dev/null
+++ b/src/main/java/net/Indyuce/mmocore/api/SoundEvent.java
@@ -0,0 +1,20 @@
+package net.Indyuce.mmocore.api;
+
+public enum SoundEvent {
+    LEVEL_UP,
+    WARP_TELEPORT,
+    WARP_CANCELLED,
+    WARP_CHARGE,
+    WARP_UNLOCK,
+    HOTBAR_SWAP,
+    SPELL_CAST_BEGIN,
+    SPELL_CAST_END,
+    CANT_SELECT_CLASS,
+    SELECT_CLASS,
+    LEVEL_ATTRIBUTE,
+    RESET_ATTRIBUTES,
+    NOT_ENOUGH_POINTS,
+    CANCEL_QUEST,
+    START_QUEST,
+    CLOSE_LOOT_CHEST;
+}
\ No newline at end of file
diff --git a/src/main/java/net/Indyuce/mmocore/api/SoundObject.java b/src/main/java/net/Indyuce/mmocore/api/SoundObject.java
index 0d1a0ec3..763cc7b6 100644
--- a/src/main/java/net/Indyuce/mmocore/api/SoundObject.java
+++ b/src/main/java/net/Indyuce/mmocore/api/SoundObject.java
@@ -1,53 +1,106 @@
 package net.Indyuce.mmocore.api;
 
+import io.lumine.mythic.lib.UtilityMethods;
+import io.lumine.mythic.lib.util.configobject.ConfigObject;
+import org.bukkit.Location;
 import org.bukkit.Sound;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.entity.Player;
+
+import javax.annotation.Nullable;
 
 public class SoundObject {
-	private final Sound sound;
-	private final String key;
-	private final float volume;
-	private final float pitch;
-	
-	public SoundObject(String input) {
-		String[] split = input.split(",");
-		if(split.length > 2) {
-			input = split[0];
-			volume = Float.parseFloat(split[1]);
-			pitch = Float.parseFloat(split[2]);
-		} else {
-			volume = 1;
-			pitch = 1;
-		}
 
-		Sound sound = null;
-		String key = "";
-		try {
-			sound = Sound.valueOf(input.toUpperCase().replace("-", "_"));
-		} catch(Exception ignored) {
-			key = input;
-		}
+    @Nullable
+    private final Sound sound;
 
-		this.sound = sound;
-		this.key = key;
-	}
-	
-	public boolean hasSound() {
-		return key.isEmpty();
-	}
-	
-	public Sound getSound() {
-		return sound;
-	}
+    @Nullable
+    private final String key;
 
-	public String getKey() {
-		return key;
-	}
+    private final float volume;
+    private final float pitch;
 
-	public float getVolume() {
-		return volume;
-	}
+    public SoundObject(String input) {
+        String[] split = input.split(",");
 
-	public float getPitch() {
-		return pitch;
-	}
+        Sound sound = null;
+        String key = null;
+        try {
+            sound = Sound.valueOf(UtilityMethods.enumName(split[0]));
+        } catch (Exception ignored) {
+            key = split[0];
+        }
+
+        this.sound = sound;
+        this.key = key;
+
+        volume = split.length > 1 ? Float.parseFloat(split[1]) : 1;
+        pitch = split.length > 2 ? Float.parseFloat(split[2]) : 1;
+    }
+
+    public SoundObject(ConfigurationSection config) {
+        String input = config.getString("sound");
+
+        Sound sound = null;
+        String key = null;
+        try {
+            sound = Sound.valueOf(UtilityMethods.enumName(input));
+        } catch (Exception ignored) {
+            key = input;
+        }
+
+        this.sound = sound;
+        this.key = key;
+
+        volume = (float) config.getDouble("volume", 1);
+        pitch = (float) config.getDouble("pitch", 1);
+    }
+
+    /**
+     * @return If this object is custom a custom sound, potentially
+     *         from a resource pack
+     */
+    public boolean isCustom() {
+        return sound == null;
+    }
+
+    @Nullable
+    public Sound getSound() {
+        return sound;
+    }
+
+    @Nullable
+    public String getKey() {
+        return key;
+    }
+
+    public float getVolume() {
+        return volume;
+    }
+
+    public float getPitch() {
+        return pitch;
+    }
+
+    public void playTo(Player player) {
+        playTo(player, volume, pitch);
+    }
+
+    public void playTo(Player player, float volume, float pitch) {
+        if (isCustom())
+            player.playSound(player.getLocation(), key, volume, pitch);
+        else
+            player.playSound(player.getLocation(), sound, volume, pitch);
+    }
+
+    public void playAt(Location loc) {
+        playAt(loc, volume, pitch);
+    }
+
+    public void playAt(Location loc, float volume, float pitch) {
+        if (isCustom())
+            loc.getWorld().playSound(loc, key, volume, pitch);
+        else
+            loc.getWorld().playSound(loc, sound, volume, pitch);
+    }
 }
diff --git a/src/main/java/net/Indyuce/mmocore/api/player/PlayerData.java b/src/main/java/net/Indyuce/mmocore/api/player/PlayerData.java
index 828f0d69..babf946e 100644
--- a/src/main/java/net/Indyuce/mmocore/api/player/PlayerData.java
+++ b/src/main/java/net/Indyuce/mmocore/api/player/PlayerData.java
@@ -5,6 +5,7 @@ import io.lumine.mythic.lib.player.TemporaryPlayerData;
 import io.lumine.mythic.lib.player.cooldown.CooldownMap;
 import net.Indyuce.mmocore.MMOCore;
 import net.Indyuce.mmocore.api.ConfigMessage;
+import net.Indyuce.mmocore.api.SoundEvent;
 import net.Indyuce.mmocore.api.Waypoint;
 import net.Indyuce.mmocore.api.event.PlayerExperienceGainEvent;
 import net.Indyuce.mmocore.api.event.PlayerLevelUpEvent;
@@ -74,11 +75,11 @@ public class PlayerData extends OfflinePlayerData implements Closable {
     private final PlayerProfessions collectSkills = new PlayerProfessions(this);
     private final PlayerAttributes attributes = new PlayerAttributes(this);
     private final Map<String, SavedClassInformation> classSlots = new HashMap<>();
-private final Map<PlayerActivity, Long> lastActivity = new HashMap<>();
+    private final Map<PlayerActivity, Long> lastActivity = new HashMap<>();
 
     // NON-FINAL player data stuff made public to facilitate field change
     public int skillGuiDisplayOffset;
-    public SkillCasting skillCasting;
+    public Object skillCasting;
     public boolean noCooldown;
     public CombatRunnable combat;
 
@@ -438,7 +439,7 @@ private final Map<PlayerActivity, Long> lastActivity = new HashMap<>();
                     return;
                 if (getPlayer().getLocation().getBlockX() != x || getPlayer().getLocation().getBlockY() != y
                         || getPlayer().getLocation().getBlockZ() != z) {
-                    MMOCore.plugin.soundManager.play(getPlayer(), SoundManager.SoundEvent.WARP_CANCELLED);
+                    MMOCore.plugin.soundManager.getSound(SoundEvent.WARP_CANCELLED).playTo(getPlayer());
                     MMOCore.plugin.configManager.getSimpleMessage("warping-canceled").send(getPlayer());
                     giveStellium(waypoint.getStelliumCost(), PlayerResourceUpdateEvent.UpdateReason.SKILL_REGENERATION);
                     cancel();
@@ -449,12 +450,12 @@ private final Map<PlayerActivity, Long> lastActivity = new HashMap<>();
                 if (t++ >= 100) {
                     getPlayer().teleport(waypoint.getLocation());
                     getPlayer().addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 20, 1, false, false));
-                    MMOCore.plugin.soundManager.play(getPlayer(), SoundManager.SoundEvent.WARP_TELEPORT);
+                    MMOCore.plugin.soundManager.getSound(SoundEvent.WARP_TELEPORT).playTo(getPlayer());
                     cancel();
                     return;
                 }
 
-                MMOCore.plugin.soundManager.play(getPlayer(), SoundManager.SoundEvent.WARP_CHARGE, 1, (float) (t / Math.PI * .015 + .5));
+                MMOCore.plugin.soundManager.getSound(SoundEvent.WARP_CHARGE).playTo(getPlayer(), 1, (float) (t / Math.PI * .015 + .5));
                 double r = Math.sin((double) t / 100 * Math.PI);
                 for (double j = 0; j < Math.PI * 2; j += Math.PI / 4)
                     getPlayer().getLocation().getWorld().spawnParticle(Particle.REDSTONE,
@@ -532,7 +533,7 @@ private final Map<PlayerActivity, Long> lastActivity = new HashMap<>();
             Bukkit.getPluginManager().callEvent(new PlayerLevelUpEvent(this, null, oldLevel, level));
             if (isOnline()) {
                 new ConfigMessage("level-up").addPlaceholders("level", "" + level).send(getPlayer());
-                MMOCore.plugin.soundManager.play(getPlayer(), SoundManager.SoundEvent.LEVEL_UP);
+                MMOCore.plugin.soundManager.getSound(SoundEvent.LEVEL_UP).playTo(getPlayer());
                 new SmallParticleEffect(getPlayer(), Particle.SPELL_INSTANT);
             }
             getStats().updateStats();
diff --git a/src/main/java/net/Indyuce/mmocore/api/player/social/Party.java b/src/main/java/net/Indyuce/mmocore/api/player/social/Party.java
index c2cc27b1..9844d0cd 100644
--- a/src/main/java/net/Indyuce/mmocore/api/player/social/Party.java
+++ b/src/main/java/net/Indyuce/mmocore/api/player/social/Party.java
@@ -149,21 +149,18 @@ public class Party {
         new ArrayList<>(members).forEach(action);
     }
 
-    private static final String PARTY_BUFF_MODIFIER_KEY = "mmocoreParty";
-
     /**
      * Applies party stat bonuses to a specific player
      */
     private void applyStatBonuses(PlayerData player) {
-        MMOCore.plugin.partyManager.getBonuses().forEach(stat -> player.getStats().getInstance(stat).addModifier(PARTY_BUFF_MODIFIER_KEY,
-                MMOCore.plugin.partyManager.getBonus(stat).multiply(members.size() - 1)));
+        MMOCore.plugin.partyManager.getBonuses().forEach(buff -> buff.multiply(members.size() - 1).register(player.getMMOPlayerData()));
     }
 
     /**
      * Clear party stat bonuses from a player
      */
     private void clearStatBonuses(PlayerData player) {
-        MMOCore.plugin.partyManager.getBonuses().forEach(stat -> player.getStats().getInstance(stat).remove(PARTY_BUFF_MODIFIER_KEY));
+        MMOCore.plugin.partyManager.getBonuses().forEach(buff -> buff.unregister(player.getMMOPlayerData()));
     }
 
     @Override
diff --git a/src/main/java/net/Indyuce/mmocore/experience/PlayerProfessions.java b/src/main/java/net/Indyuce/mmocore/experience/PlayerProfessions.java
index 8f72bf32..aeab17be 100644
--- a/src/main/java/net/Indyuce/mmocore/experience/PlayerProfessions.java
+++ b/src/main/java/net/Indyuce/mmocore/experience/PlayerProfessions.java
@@ -6,6 +6,7 @@ import com.google.gson.JsonObject;
 import io.lumine.mythic.lib.MythicLib;
 import net.Indyuce.mmocore.MMOCore;
 import net.Indyuce.mmocore.api.ConfigMessage;
+import net.Indyuce.mmocore.api.SoundEvent;
 import net.Indyuce.mmocore.api.event.PlayerExperienceGainEvent;
 import net.Indyuce.mmocore.api.event.PlayerLevelUpEvent;
 import net.Indyuce.mmocore.api.player.PlayerData;
@@ -195,7 +196,7 @@ public class PlayerProfessions {
 			new SmallParticleEffect(playerData.getPlayer(), Particle.SPELL_INSTANT);
 			new ConfigMessage("profession-level-up").addPlaceholders("level", "" + level, "profession", profession.getName())
 					.send(playerData.getPlayer());
-			MMOCore.plugin.soundManager.play(playerData.getPlayer(), SoundManager.SoundEvent.LEVEL_UP);
+			MMOCore.plugin.soundManager.getSound(SoundEvent.LEVEL_UP).playTo(playerData.getPlayer());
 			playerData.getStats().updateStats();
 
 			// Apply profession experience table
diff --git a/src/main/java/net/Indyuce/mmocore/gui/AttributeView.java b/src/main/java/net/Indyuce/mmocore/gui/AttributeView.java
index fb2348c3..49e74717 100644
--- a/src/main/java/net/Indyuce/mmocore/gui/AttributeView.java
+++ b/src/main/java/net/Indyuce/mmocore/gui/AttributeView.java
@@ -1,6 +1,7 @@
 package net.Indyuce.mmocore.gui;
 
 import net.Indyuce.mmocore.MMOCore;
+import net.Indyuce.mmocore.api.SoundEvent;
 import net.Indyuce.mmocore.api.event.PlayerAttributeUseEvent;
 import net.Indyuce.mmocore.api.player.PlayerData;
 import net.Indyuce.mmocore.api.player.attribute.PlayerAttribute;
@@ -89,13 +90,13 @@ public class AttributeView extends EditableInventory {
 				int spent = playerData.getAttributes().countSkillPoints();
 				if (spent < 1) {
 					MMOCore.plugin.configManager.getSimpleMessage("no-attribute-points-spent").send(player);
-					MMOCore.plugin.soundManager.play(getPlayer(), SoundManager.SoundEvent.NOT_ENOUGH_POINTS);
+					MMOCore.plugin.soundManager.getSound(SoundEvent.NOT_ENOUGH_POINTS).playTo(getPlayer());
 					return;
 				}
 
 				if (playerData.getAttributeReallocationPoints() < 1) {
 					MMOCore.plugin.configManager.getSimpleMessage("not-attribute-reallocation-point").send(player);
-					MMOCore.plugin.soundManager.play(getPlayer(), SoundManager.SoundEvent.NOT_ENOUGH_POINTS);
+					MMOCore.plugin.soundManager.getSound(SoundEvent.NOT_ENOUGH_POINTS).playTo(getPlayer());
 					return;
 				}
 
@@ -103,7 +104,7 @@ public class AttributeView extends EditableInventory {
 				playerData.giveAttributePoints(spent);
 				playerData.giveAttributeReallocationPoints(-1);
 				MMOCore.plugin.configManager.getSimpleMessage("attribute-points-reallocated", "points", "" + playerData.getAttributePoints()).send(player);
-				MMOCore.plugin.soundManager.play(getPlayer(), SoundManager.SoundEvent.RESET_ATTRIBUTES);
+				MMOCore.plugin.soundManager.getSound(SoundEvent.RESET_ATTRIBUTES).playTo(getPlayer());
 				open();
 			}
 
@@ -112,21 +113,21 @@ public class AttributeView extends EditableInventory {
 
 				if (playerData.getAttributePoints() < 1) {
 					MMOCore.plugin.configManager.getSimpleMessage("not-attribute-point").send(player);
-					MMOCore.plugin.soundManager.play(getPlayer(), SoundManager.SoundEvent.NOT_ENOUGH_POINTS);
+					MMOCore.plugin.soundManager.getSound(SoundEvent.NOT_ENOUGH_POINTS).playTo(getPlayer());
 					return;
 				}
 
 				AttributeInstance ins = playerData.getAttributes().getInstance(attribute);
 				if (attribute.hasMax() && ins.getBase() >= attribute.getMax()) {
 					MMOCore.plugin.configManager.getSimpleMessage("attribute-max-points-hit").send(player);
-					MMOCore.plugin.soundManager.play(getPlayer(), SoundManager.SoundEvent.NOT_ENOUGH_POINTS);
+					MMOCore.plugin.soundManager.getSound(SoundEvent.NOT_ENOUGH_POINTS).playTo(getPlayer());
 					return;
 				}
 
 				ins.addBase(1);
 				playerData.giveAttributePoints(-1);
 				MMOCore.plugin.configManager.getSimpleMessage("attribute-level-up", "attribute", attribute.getName(), "level", "" + ins.getBase()).send(player);
-				MMOCore.plugin.soundManager.play(getPlayer(), SoundManager.SoundEvent.LEVEL_ATTRIBUTE);
+				MMOCore.plugin.soundManager.getSound(SoundEvent.LEVEL_ATTRIBUTE).playTo(getPlayer());
 
 				PlayerAttributeUseEvent playerAttributeUseEvent = new PlayerAttributeUseEvent(playerData, attribute);
 				Bukkit.getServer().getPluginManager().callEvent(playerAttributeUseEvent);
diff --git a/src/main/java/net/Indyuce/mmocore/gui/ClassConfirmation.java b/src/main/java/net/Indyuce/mmocore/gui/ClassConfirmation.java
index 5db7a70b..6e6c65ad 100644
--- a/src/main/java/net/Indyuce/mmocore/gui/ClassConfirmation.java
+++ b/src/main/java/net/Indyuce/mmocore/gui/ClassConfirmation.java
@@ -1,6 +1,7 @@
 package net.Indyuce.mmocore.gui;
 
 import net.Indyuce.mmocore.MMOCore;
+import net.Indyuce.mmocore.api.SoundEvent;
 import net.Indyuce.mmocore.api.event.PlayerChangeClassEvent;
 import net.Indyuce.mmocore.api.player.PlayerData;
 import net.Indyuce.mmocore.api.player.profess.PlayerClass;
@@ -125,7 +126,7 @@ public class ClassConfirmation extends EditableInventory {
 				(playerData.hasSavedClass(profess) ? playerData.getClassInfo(profess)
 						: new SavedClassInformation(MMOCore.plugin.dataProvider.getDataManager().getDefaultData())).load(profess, playerData);
 				MMOCore.plugin.configManager.getSimpleMessage("class-select", "class", profess.getName()).send(player);
-				MMOCore.plugin.soundManager.play(player, SoundManager.SoundEvent.SELECT_CLASS);
+				MMOCore.plugin.soundManager.getSound(SoundEvent.SELECT_CLASS).playTo(player);
 				player.closeInventory();
 			}
 		}
diff --git a/src/main/java/net/Indyuce/mmocore/gui/ClassSelect.java b/src/main/java/net/Indyuce/mmocore/gui/ClassSelect.java
index a8dedce5..dc62d6db 100644
--- a/src/main/java/net/Indyuce/mmocore/gui/ClassSelect.java
+++ b/src/main/java/net/Indyuce/mmocore/gui/ClassSelect.java
@@ -5,6 +5,7 @@ import io.lumine.mythic.lib.api.item.ItemTag;
 import io.lumine.mythic.lib.api.item.NBTItem;
 import net.Indyuce.mmocore.MMOCore;
 import net.Indyuce.mmocore.api.ConfigMessage;
+import net.Indyuce.mmocore.api.SoundEvent;
 import net.Indyuce.mmocore.api.player.PlayerData;
 import net.Indyuce.mmocore.api.player.profess.ClassOption;
 import net.Indyuce.mmocore.api.player.profess.PlayerClass;
@@ -109,14 +110,14 @@ public class ClassSelect extends EditableInventory {
 					return;
 
 				if (playerData.getClassPoints() < 1) {
-					MMOCore.plugin.soundManager.play(player, SoundManager.SoundEvent.CANT_SELECT_CLASS);
+					MMOCore.plugin.soundManager.getSound(SoundEvent.CANT_SELECT_CLASS).playTo(player);
 					new ConfigMessage("cant-choose-new-class").send(player);
 					return;
 				}
 
 				PlayerClass profess = MMOCore.plugin.classManager.get(tag);
 				if (profess.equals(playerData.getProfess())) {
-					MMOCore.plugin.soundManager.play(player, SoundManager.SoundEvent.CANT_SELECT_CLASS);
+					MMOCore.plugin.soundManager.getSound(SoundEvent.CANT_SELECT_CLASS).playTo(player);
 					MMOCore.plugin.configManager.getSimpleMessage("already-on-class", "class", profess.getName()).send(player);
 					return;
 				}
diff --git a/src/main/java/net/Indyuce/mmocore/gui/QuestViewer.java b/src/main/java/net/Indyuce/mmocore/gui/QuestViewer.java
index 02eff450..66e2193d 100644
--- a/src/main/java/net/Indyuce/mmocore/gui/QuestViewer.java
+++ b/src/main/java/net/Indyuce/mmocore/gui/QuestViewer.java
@@ -3,6 +3,7 @@ package net.Indyuce.mmocore.gui;
 import io.lumine.mythic.lib.api.item.ItemTag;
 import io.lumine.mythic.lib.api.item.NBTItem;
 import net.Indyuce.mmocore.MMOCore;
+import net.Indyuce.mmocore.api.SoundEvent;
 import net.Indyuce.mmocore.experience.Profession;
 import net.Indyuce.mmocore.api.player.PlayerData;
 import net.Indyuce.mmocore.api.quest.Quest;
@@ -235,7 +236,7 @@ public class QuestViewer extends EditableInventory {
 					if (playerData.getQuestData().hasCurrent(quest)) {
 						if (event.getAction() == InventoryAction.PICKUP_HALF) {
 							playerData.getQuestData().start(null);
-							MMOCore.plugin.soundManager.play(player, SoundManager.SoundEvent.CANCEL_QUEST);
+							MMOCore.plugin.soundManager.getSound(SoundEvent.CANCEL_QUEST).playTo(player);
 							MMOCore.plugin.configManager.getSimpleMessage("cancel-quest").send(player);
 							open();
 						}
@@ -293,7 +294,7 @@ public class QuestViewer extends EditableInventory {
 				 * eventually start a new quest.
 				 */
 				MMOCore.plugin.configManager.getSimpleMessage("start-quest", "quest", quest.getName()).send(player);
-				MMOCore.plugin.soundManager.play(player, SoundManager.SoundEvent.START_QUEST);
+				MMOCore.plugin.soundManager.getSound(SoundEvent.START_QUEST).playTo(player);
 				playerData.getQuestData().start(quest);
 				open();
 			}
diff --git a/src/main/java/net/Indyuce/mmocore/gui/SubclassConfirmation.java b/src/main/java/net/Indyuce/mmocore/gui/SubclassConfirmation.java
index d973a1d5..cd499e10 100644
--- a/src/main/java/net/Indyuce/mmocore/gui/SubclassConfirmation.java
+++ b/src/main/java/net/Indyuce/mmocore/gui/SubclassConfirmation.java
@@ -1,6 +1,7 @@
 package net.Indyuce.mmocore.gui;
 
 import net.Indyuce.mmocore.MMOCore;
+import net.Indyuce.mmocore.api.SoundEvent;
 import net.Indyuce.mmocore.api.event.PlayerChangeClassEvent;
 import net.Indyuce.mmocore.api.player.PlayerData;
 import net.Indyuce.mmocore.api.player.profess.PlayerClass;
@@ -66,7 +67,7 @@ public class SubclassConfirmation extends EditableInventory {
 
 				playerData.setClass(profess);
 				MMOCore.plugin.configManager.getSimpleMessage("class-select", "class", profess.getName()).send(player);
-				MMOCore.plugin.soundManager.play(player, SoundManager.SoundEvent.SELECT_CLASS);
+				MMOCore.plugin.soundManager.getSound(SoundEvent.SELECT_CLASS).playTo(player);
 				player.closeInventory();
 			}
 		}
diff --git a/src/main/java/net/Indyuce/mmocore/gui/SubclassSelect.java b/src/main/java/net/Indyuce/mmocore/gui/SubclassSelect.java
index 57631bb8..d7080135 100644
--- a/src/main/java/net/Indyuce/mmocore/gui/SubclassSelect.java
+++ b/src/main/java/net/Indyuce/mmocore/gui/SubclassSelect.java
@@ -5,6 +5,7 @@ import io.lumine.mythic.lib.api.item.ItemTag;
 import io.lumine.mythic.lib.api.item.NBTItem;
 import net.Indyuce.mmocore.MMOCore;
 import net.Indyuce.mmocore.api.ConfigMessage;
+import net.Indyuce.mmocore.api.SoundEvent;
 import net.Indyuce.mmocore.api.player.PlayerData;
 import net.Indyuce.mmocore.api.player.profess.PlayerClass;
 import net.Indyuce.mmocore.api.player.profess.Subclass;
@@ -118,7 +119,7 @@ public class SubclassSelect extends EditableInventory {
 
 				if (playerData.getClassPoints() < 1) {
 					player.closeInventory();
-					MMOCore.plugin.soundManager.play(getPlayer(), SoundManager.SoundEvent.CANT_SELECT_CLASS);
+					MMOCore.plugin.soundManager.getSound(SoundEvent.CANT_SELECT_CLASS).playTo(getPlayer());
 					new ConfigMessage("cant-choose-new-class").send(player);
 					return;
 				}
diff --git a/src/main/java/net/Indyuce/mmocore/listener/WaypointsListener.java b/src/main/java/net/Indyuce/mmocore/listener/WaypointsListener.java
index b4f21b78..b95d7c7c 100644
--- a/src/main/java/net/Indyuce/mmocore/listener/WaypointsListener.java
+++ b/src/main/java/net/Indyuce/mmocore/listener/WaypointsListener.java
@@ -1,5 +1,6 @@
 package net.Indyuce.mmocore.listener;
 
+import net.Indyuce.mmocore.api.SoundEvent;
 import net.Indyuce.mmocore.manager.SoundManager;
 import org.bukkit.Particle;
 import org.bukkit.entity.Player;
@@ -29,7 +30,7 @@ public class WaypointsListener implements Listener {
 			data.unlockWaypoint(waypoint);
 			new SmallParticleEffect(player, Particle.SPELL_WITCH);
 			MMOCore.plugin.configManager.getSimpleMessage("new-waypoint", "waypoint", waypoint.getName()).send(player);
-			MMOCore.plugin.soundManager.play(player, SoundManager.SoundEvent.WARP_UNLOCK);
+			MMOCore.plugin.soundManager.getSound(SoundEvent.WARP_UNLOCK).playTo(player);
 			return;
 		}
 
diff --git a/src/main/java/net/Indyuce/mmocore/loot/chest/LootChest.java b/src/main/java/net/Indyuce/mmocore/loot/chest/LootChest.java
index ea52030b..e20111db 100644
--- a/src/main/java/net/Indyuce/mmocore/loot/chest/LootChest.java
+++ b/src/main/java/net/Indyuce/mmocore/loot/chest/LootChest.java
@@ -1,6 +1,7 @@
 package net.Indyuce.mmocore.loot.chest;
 
 import net.Indyuce.mmocore.MMOCore;
+import net.Indyuce.mmocore.api.SoundEvent;
 import net.Indyuce.mmocore.manager.SoundManager;
 import org.bukkit.Location;
 import org.bukkit.Material;
@@ -66,7 +67,7 @@ public class LootChest {
 
         // If a player is responsible of closing the chest, close it with sound
         if (player) {
-            MMOCore.plugin.soundManager.play(block.loc.getBlock(), SoundManager.SoundEvent.CLOSE_LOOT_CHEST);
+            MMOCore.plugin.soundManager.getSound(SoundEvent.CLOSE_LOOT_CHEST).playAt(block.loc);
             block.loc.getWorld().spawnParticle(Particle.CRIT, block.loc.clone().add(.5, .5, .5), 16, 0, 0, 0, .5);
             MMOCore.plugin.lootChests.unregister(this);
         }
diff --git a/src/main/java/net/Indyuce/mmocore/manager/SoundManager.java b/src/main/java/net/Indyuce/mmocore/manager/SoundManager.java
index 0f70949d..b64c47a5 100644
--- a/src/main/java/net/Indyuce/mmocore/manager/SoundManager.java
+++ b/src/main/java/net/Indyuce/mmocore/manager/SoundManager.java
@@ -1,72 +1,24 @@
 package net.Indyuce.mmocore.manager;
 
+import net.Indyuce.mmocore.api.SoundEvent;
 import net.Indyuce.mmocore.api.SoundObject;
-import org.bukkit.Location;
-import org.bukkit.block.Block;
 import org.bukkit.configuration.file.FileConfiguration;
-import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
 
 public class SoundManager {
-	private final Map<SoundEvent, SoundObject> sounds = new HashMap<>();
+    private final Map<SoundEvent, SoundObject> sounds = new HashMap<>();
 
-	public SoundManager(FileConfiguration config) {
-		for(SoundEvent sound : SoundEvent.values())
-			sounds.put(sound, new SoundObject(config.getString(sound.name().replace("_", "-").toLowerCase())));
-	}
+    public SoundManager(FileConfiguration config) {
+        for (SoundEvent sound : SoundEvent.values())
+            sounds.put(sound, new SoundObject(config.getString(sound.name().replace("_", "-").toLowerCase())));
+    }
 
-
-	public void play(Block block, SoundEvent event) {
-		play(block, block.getLocation(), event);
-	}
-
-	public void play(Block block, Location loc, SoundEvent event) {
-		SoundObject sound = sounds.get(event);
-		if(sound.hasSound())
-			block.getWorld().playSound(loc, sound.getSound(), sound.getVolume(), sound.getPitch());
-		else block.getWorld().playSound(loc, sound.getKey(), sound.getVolume(), sound.getPitch());
-	}
-
-	public void play(Player player, SoundEvent event) {
-		play(player, player.getLocation(), event);
-	}
-
-	public void play(Player player, Location loc, SoundEvent event) {
-		SoundObject sound = sounds.get(event);
-		if(sound.hasSound())
-			player.playSound(loc, sound.getSound(), sound.getVolume(), sound.getPitch());
-		else player.playSound(loc, sound.getKey(), sound.getVolume(), sound.getPitch());
-	}
-
-	public void play(Player player, SoundEvent event, float volume, float pitch) {
-		play(player, player.getLocation(), event, volume, pitch);
-	}
-
-	public void play(Player player, Location loc, SoundEvent event, float volume, float pitch) {
-		SoundObject sound = sounds.get(event);
-		if(sound.hasSound())
-			player.playSound(loc, sound.getSound(), volume, pitch);
-		else player.playSound(loc, sound.getKey(), volume, pitch);
-	}
-
-	public enum SoundEvent {
-		LEVEL_UP,
-		WARP_TELEPORT,
-		WARP_CANCELLED,
-		WARP_CHARGE,
-		WARP_UNLOCK,
-		HOTBAR_SWAP,
-		SPELL_CAST_BEGIN,
-		SPELL_CAST_END,
-		CANT_SELECT_CLASS,
-		SELECT_CLASS,
-		LEVEL_ATTRIBUTE,
-		RESET_ATTRIBUTES,
-		NOT_ENOUGH_POINTS,
-		CANCEL_QUEST,
-		START_QUEST,
-		CLOSE_LOOT_CHEST
-	}
+    @NotNull
+    public SoundObject getSound(SoundEvent event) {
+        return Objects.requireNonNull(sounds.get(event), "Could not find sound for " + event.name());
+    }
 }