diff --git a/.gitignore b/.gitignore
index 58bc0397..87ea6ca7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -122,7 +122,6 @@ run/
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle/wrapper/gradle-wrapper.jar
-!**/libs/*.jar
CLAUDE.md
.claude
updates.md
diff --git a/API/build.gradle.kts b/API/build.gradle.kts
index 1299e914..886f75e6 100644
--- a/API/build.gradle.kts
+++ b/API/build.gradle.kts
@@ -8,7 +8,6 @@ rootProject.extra.properties["sha"]?.let { sha ->
dependencies {
compileOnly(libs.paper.api)
- compileOnly(files("../libs/zMenu-1.1.2.0.jar"))
implementation(libs.sarah)
implementation(libs.folialib)
diff --git a/API/src/main/java/fr/maxlego08/essentials/api/EssentialsPlugin.java b/API/src/main/java/fr/maxlego08/essentials/api/EssentialsPlugin.java
index c7900886..31b3abf1 100644
--- a/API/src/main/java/fr/maxlego08/essentials/api/EssentialsPlugin.java
+++ b/API/src/main/java/fr/maxlego08/essentials/api/EssentialsPlugin.java
@@ -101,9 +101,16 @@ public interface EssentialsPlugin extends Plugin {
ModuleManager getModuleManager();
/**
- * Gets the inventory manager for managing inventories.
- *
- * @return The inventory manager.
+ * Gets the inventory manager provided by the required zMenu plugin.
+ *
+ * In addition to inventories, zMenu 1.1.2.1 and later expose item serialization and
+ * sound creation through this service. Its item serializer preserves the format
+ * used by existing zEssentials mailbox, vault and item hologram data.
+ *
+ * @return The registered zMenu inventory manager.
+ * @see InventoryManager#serializeItemStack(org.bukkit.inventory.ItemStack)
+ * @see InventoryManager#deserializeItemStack(String)
+ * @see InventoryManager#createSoundOption(String, String, float, float)
*/
InventoryManager getInventoryManager();
diff --git a/API/src/main/java/fr/maxlego08/essentials/api/mailbox/MailBoxItem.java b/API/src/main/java/fr/maxlego08/essentials/api/mailbox/MailBoxItem.java
index 785b8b9c..a3951917 100644
--- a/API/src/main/java/fr/maxlego08/essentials/api/mailbox/MailBoxItem.java
+++ b/API/src/main/java/fr/maxlego08/essentials/api/mailbox/MailBoxItem.java
@@ -1,7 +1,8 @@
package fr.maxlego08.essentials.api.mailbox;
import fr.maxlego08.essentials.api.dto.MailBoxDTO;
-import fr.maxlego08.menu.common.utils.nms.ItemStackUtils;
+import fr.maxlego08.menu.api.InventoryManager;
+import org.bukkit.Bukkit;
import org.bukkit.inventory.ItemStack;
import java.util.Date;
@@ -31,14 +32,21 @@ public MailBoxItem(UUID uuid, ItemStack itemStack, Date expiredAt) {
}
/**
- * Constructs a new MailBoxItem from a MailBoxDTO.
+ * Restores a mailbox item from its stored data using zMenu's item serializer.
+ *
+ * Requires zMenu 1.1.2.1 or later to be enabled and its {@link InventoryManager}
+ * service to be registered. The stored item format remains compatible with
+ * mailbox entries saved before the API migration.
*
* @param mailBoxDTO the data transfer object containing mailbox item data
+ * @throws NullPointerException if the DTO is null or the inventory manager service is unavailable
+ * @see InventoryManager#deserializeItemStack(String)
*/
public MailBoxItem(MailBoxDTO mailBoxDTO) {
this.id = mailBoxDTO.id();
this.uuid = mailBoxDTO.unique_id();
- this.itemStack = ItemStackUtils.deserializeItemStack(mailBoxDTO.itemstack());
+ InventoryManager inventoryManager = Bukkit.getServicesManager().load(InventoryManager.class);
+ this.itemStack = inventoryManager.deserializeItemStack(mailBoxDTO.itemstack());
this.expiredAt = mailBoxDTO.expired_at();
}
@@ -72,7 +80,7 @@ public UUID getUniqueId() {
/**
* Gets the item stack stored in the mailbox.
*
- * @return the item stack
+ * @return the stored item, or null if no item was stored or deserialization failed
*/
public ItemStack getItemStack() {
return itemStack;
diff --git a/build.gradle.kts b/build.gradle.kts
index bd674ed0..4f2944c8 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -75,8 +75,6 @@ allprojects {
dependencies {
compileOnly(rootLibs.zmenu.api)
- // Still needed for item persistence and sound helpers not exposed by the zMenu API.
- compileOnly(files("libs/zMenu-1.1.2.0.jar"))
compileOnly(rootLibs.sarah)
compileOnly(rootLibs.folialib)
diff --git a/changelog.md b/changelog.md
index 3f97dc45..2132a481 100644
--- a/changelog.md
+++ b/changelog.md
@@ -34,7 +34,7 @@
- `/enchant` now uses server registry identifiers, including datapack and plugin enchantments; legacy API methods remain available and deprecated
- Unified item lookup and suggestions for give, mailbox and vault commands; feedback uses translated item names and preserves custom names
- Fixed custom item delivery to offline mail recipients and stale custom items after reloads
-- Use the published zMenu **1.1.2.0** API for skulls and kit conversion/saving; the local JAR remains required for item persistence and sound helpers
+- Require zMenu **1.1.2.1** and use its published API for items and sounds; removed the local zMenu JAR while preserving stored items and custom resource-pack sounds
- Migrated messages and configuration examples to native MiniMessage hex colors, including scoreboard animations; existing files must replace legacy `&`, `ยง` and bare hex color codes
- Fixed MiniMessage text serialization, italic formatting and bold text alignment, and preserved click events with Adventure `5.x`
- Fixed feedback placeholders, duplicate Death Message registration, version comparisons and incomplete downloads; existing `command-enchant-error-enchant` messages must use `%enchant%`
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 2986087a..cf2f4b12 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -31,7 +31,7 @@ mysql-connector = "26.10.0"
mariadb-connector = "3.5.6"
# Integration Hooks
-zmenu-api = "1.1.2.0"
+zmenu-api = "1.1.2.1"
placeholderapi = "2.12.3"
blocktracker = "1.0.6"
mythicmobs = "5.7.2"
diff --git a/libs/zMenu-1.1.2.0.jar b/libs/zMenu-1.1.2.0.jar
deleted file mode 100644
index d364e61c..00000000
Binary files a/libs/zMenu-1.1.2.0.jar and /dev/null differ
diff --git a/src/main/java/fr/maxlego08/essentials/commands/commands/vault/CommandVaultGetSlot.java b/src/main/java/fr/maxlego08/essentials/commands/commands/vault/CommandVaultGetSlot.java
index 36b66f82..6130ae81 100644
--- a/src/main/java/fr/maxlego08/essentials/commands/commands/vault/CommandVaultGetSlot.java
+++ b/src/main/java/fr/maxlego08/essentials/commands/commands/vault/CommandVaultGetSlot.java
@@ -7,7 +7,6 @@
import fr.maxlego08.essentials.api.messages.Message;
import fr.maxlego08.essentials.module.modules.vault.VaultModule;
import fr.maxlego08.essentials.zutils.utils.commands.VCommand;
-import fr.maxlego08.menu.common.utils.nms.ItemStackUtils;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
@@ -51,7 +50,7 @@ protected CommandResultType perform(EssentialsPlugin plugin) {
}
VaultItemDTO vaultItemDTO = optional.get();
- ItemStack itemStack = ItemStackUtils.deserializeItemStack(vaultItemDTO.item());
+ ItemStack itemStack = plugin.getInventoryManager().deserializeItemStack(vaultItemDTO.item());
String itemName = itemStack == null ? "unknown" : getItemName(itemStack);
message(this.sender, Message.COMMAND_VAULT_GET_SLOT_SUCCESS, "%player%", playerName, "%vault%", vaultId, "%slot%", slot, "%amount%", String.valueOf(vaultItemDTO.quantity()), "%item%", itemName);
diff --git a/src/main/java/fr/maxlego08/essentials/module/modules/TeleportationModule.java b/src/main/java/fr/maxlego08/essentials/module/modules/TeleportationModule.java
index 5392e80c..c2c189f8 100644
--- a/src/main/java/fr/maxlego08/essentials/module/modules/TeleportationModule.java
+++ b/src/main/java/fr/maxlego08/essentials/module/modules/TeleportationModule.java
@@ -8,9 +8,8 @@
import fr.maxlego08.essentials.api.teleportation.WorldOverride;
import fr.maxlego08.essentials.api.user.User;
import fr.maxlego08.essentials.module.ZModule;
+import fr.maxlego08.essentials.zutils.utils.CustomSoundOption;
import fr.maxlego08.menu.api.sound.SoundOption;
-import fr.maxlego08.menu.hooks.xseries.XSound;
-import fr.maxlego08.menu.sound.ZSoundOption;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
@@ -24,7 +23,6 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import java.util.Optional;
import java.util.Queue;
import java.util.Random;
import java.util.UUID;
@@ -96,15 +94,22 @@ private SoundOption loadSoundOption(YamlConfiguration configuration, String path
float volume = (float) section.getDouble("volume", 1.0);
float pitch = (float) section.getDouble("pitch", 1.0);
- Optional xSound = XSound.of(soundName);
-
- return new ZSoundOption(xSound.orElse(null), "MASTER", soundName, pitch, volume, xSound.isEmpty());
+ SoundOption soundOption = this.plugin.getInventoryManager().createSoundOption(soundName, "MASTER", pitch, volume);
+ return soundOption == null ? new CustomSoundOption(soundName, pitch, volume) : soundOption;
}
+ /**
+ * @return the configured teleportation countdown sound, including custom resource-pack sounds,
+ * or null when this sound is disabled or unconfigured
+ */
public SoundOption getCountdownSound() {
return countdownSound;
}
+ /**
+ * @return the configured teleportation completion sound, including custom resource-pack sounds,
+ * or null when this sound is disabled or unconfigured
+ */
public SoundOption getCompleteSound() {
return completeSound;
}
diff --git a/src/main/java/fr/maxlego08/essentials/module/modules/chat/ChatModule.java b/src/main/java/fr/maxlego08/essentials/module/modules/chat/ChatModule.java
index 5891cad2..46cf7cf8 100644
--- a/src/main/java/fr/maxlego08/essentials/module/modules/chat/ChatModule.java
+++ b/src/main/java/fr/maxlego08/essentials/module/modules/chat/ChatModule.java
@@ -19,12 +19,11 @@
import fr.maxlego08.essentials.api.utils.DynamicCooldown;
import fr.maxlego08.essentials.module.ZModule;
import fr.maxlego08.essentials.storage.ConfigStorage;
+import fr.maxlego08.essentials.zutils.utils.CustomSoundOption;
import fr.maxlego08.essentials.zutils.utils.TimerBuilder;
import fr.maxlego08.essentials.zutils.utils.paper.PaperComponent;
import fr.maxlego08.menu.api.engine.Pagination;
import fr.maxlego08.menu.api.sound.SoundOption;
-import fr.maxlego08.menu.hooks.xseries.XSound;
-import fr.maxlego08.menu.sound.ZSoundOption;
import io.papermc.paper.event.player.AsyncChatEvent;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
@@ -114,11 +113,11 @@ public void loadConfiguration() {
this.chatDisplays.clear();
if (this.enablePing) {
SoundOption pingSoundOption = null;
- // Resolve the sound cross-version via XSound. Since Paper 1.21.3+ org.bukkit.Sound is an interface
- // (no longer an enum), the reflection config loader can no longer map it, so we resolve it manually.
if (this.enablePlayerPingSound && this.playerPingSound != null && !this.playerPingSound.isEmpty()) {
- Optional xSound = XSound.of(this.playerPingSound);
- pingSoundOption = new ZSoundOption(xSound.orElse(null), "MASTER", this.playerPingSound, this.playerPingSoundPitch, this.playerPingSoundVolume, xSound.isEmpty());
+ pingSoundOption = this.plugin.getInventoryManager().createSoundOption(this.playerPingSound, "MASTER", this.playerPingSoundPitch, this.playerPingSoundVolume);
+ if (pingSoundOption == null) {
+ pingSoundOption = new CustomSoundOption(this.playerPingSound, this.playerPingSoundPitch, this.playerPingSoundVolume);
+ }
}
this.pingDisplay = new PlayerPingDisplay(this.plugin, this.playerPingColor, this.playerPingColorOther, pingSoundOption);
}
diff --git a/src/main/java/fr/maxlego08/essentials/module/modules/hologram/HologramLoader.java b/src/main/java/fr/maxlego08/essentials/module/modules/hologram/HologramLoader.java
index 5c90b289..b0c6e39f 100644
--- a/src/main/java/fr/maxlego08/essentials/module/modules/hologram/HologramLoader.java
+++ b/src/main/java/fr/maxlego08/essentials/module/modules/hologram/HologramLoader.java
@@ -13,7 +13,6 @@
import fr.maxlego08.essentials.zutils.utils.ZUtils;
import fr.maxlego08.menu.api.exceptions.InventoryException;
import fr.maxlego08.menu.api.utils.Loader;
-import fr.maxlego08.menu.common.utils.nms.ItemStackUtils;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.Material;
@@ -90,7 +89,7 @@ private void loadConfiguration(YamlConfiguration configuration, HologramConfigur
}
private void loadItemConfiguration(YamlConfiguration configuration, ItemHologramConfiguration itemHologramConfiguration) {
- itemHologramConfiguration.setItemStack(ItemStackUtils.deserializeItemStack(configuration.getString("itemstack", "null")));
+ itemHologramConfiguration.setItemStack(this.plugin.getInventoryManager().deserializeItemStack(configuration.getString("itemstack", "null")));
}
private void loadBlockConfiguration(YamlConfiguration configuration, BlockHologramConfiguration blockHologramConfiguration) {
@@ -164,7 +163,7 @@ public void save(Hologram hologram, YamlConfiguration configuration, String path
configuration.set("block-material", blockHologramConfiguration.getMaterial().name());
} else if (hologramConfiguration instanceof ItemHologramConfiguration itemHologramConfiguration) {
- configuration.set("itemstack", ItemStackUtils.serializeItemStack(itemHologramConfiguration.getItemStack()));
+ configuration.set("itemstack", this.plugin.getInventoryManager().serializeItemStack(itemHologramConfiguration.getItemStack()));
}
}
diff --git a/src/main/java/fr/maxlego08/essentials/module/modules/vault/VaultModule.java b/src/main/java/fr/maxlego08/essentials/module/modules/vault/VaultModule.java
index e158ef44..29c94a30 100644
--- a/src/main/java/fr/maxlego08/essentials/module/modules/vault/VaultModule.java
+++ b/src/main/java/fr/maxlego08/essentials/module/modules/vault/VaultModule.java
@@ -14,7 +14,6 @@
import fr.maxlego08.essentials.api.vault.VaultResult;
import fr.maxlego08.essentials.api.vault.VaultSlotType;
import fr.maxlego08.essentials.module.ZModule;
-import fr.maxlego08.menu.common.utils.nms.ItemStackUtils;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
@@ -229,7 +228,7 @@ public VaultResult addNewItemToVault(Vault vault, UUID uniqueId, ItemStack curre
VaultItem newVaultItem = new ZVaultItem(slot, currentItem, quantity);
vault.getVaultItems().put(slot, newVaultItem);
int finalSlot = slot;
- getStorage().createVaultItem(uniqueId, vault.getVaultId(), finalSlot, newVaultItem.getQuantity(), ItemStackUtils.serializeItemStack(currentItem));
+ getStorage().createVaultItem(uniqueId, vault.getVaultId(), finalSlot, newVaultItem.getQuantity(), this.plugin.getInventoryManager().serializeItemStack(currentItem));
return new VaultResult(vault, slot);
}
@@ -446,7 +445,7 @@ public boolean addItem(UUID uniqueId, ItemStack itemStack, long amount) {
}
VaultItem newVaultItem = new ZVaultItem(nextSlot, itemStack, amount);
vault.getVaultItems().put(nextSlot, newVaultItem);
- storage.createVaultItem(uniqueId, vault.getVaultId(), nextSlot, newVaultItem.getQuantity(), ItemStackUtils.serializeItemStack(itemStack));
+ storage.createVaultItem(uniqueId, vault.getVaultId(), nextSlot, newVaultItem.getQuantity(), this.plugin.getInventoryManager().serializeItemStack(itemStack));
}
return true;
}
diff --git a/src/main/java/fr/maxlego08/essentials/module/modules/vault/ZPlayerVaults.java b/src/main/java/fr/maxlego08/essentials/module/modules/vault/ZPlayerVaults.java
index 8c5543e1..057d7313 100644
--- a/src/main/java/fr/maxlego08/essentials/module/modules/vault/ZPlayerVaults.java
+++ b/src/main/java/fr/maxlego08/essentials/module/modules/vault/ZPlayerVaults.java
@@ -5,7 +5,8 @@
import fr.maxlego08.essentials.api.vault.Vault;
import fr.maxlego08.essentials.api.vault.VaultManager;
import fr.maxlego08.essentials.zutils.utils.ZUtils;
-import fr.maxlego08.menu.common.utils.nms.ItemStackUtils;
+import fr.maxlego08.menu.api.InventoryManager;
+import org.bukkit.Bukkit;
import org.bukkit.inventory.ItemStack;
import java.util.HashMap;
@@ -39,7 +40,8 @@ public Map getVaults() {
@Override
public void createVault(VaultDTO vaultDTO) {
- this.vaults.put(vaultDTO.vault_id(), new ZVault(vaultDTO.unique_id(), vaultDTO.vault_id(), vaultDTO.name(), ItemStackUtils.deserializeItemStack(vaultDTO.icon())));
+ InventoryManager inventoryManager = Bukkit.getServicesManager().load(InventoryManager.class);
+ this.vaults.put(vaultDTO.vault_id(), new ZVault(vaultDTO.unique_id(), vaultDTO.vault_id(), vaultDTO.name(), inventoryManager.deserializeItemStack(vaultDTO.icon())));
}
@Override
diff --git a/src/main/java/fr/maxlego08/essentials/module/modules/vault/ZVault.java b/src/main/java/fr/maxlego08/essentials/module/modules/vault/ZVault.java
index c7e11c6c..3752692c 100644
--- a/src/main/java/fr/maxlego08/essentials/module/modules/vault/ZVault.java
+++ b/src/main/java/fr/maxlego08/essentials/module/modules/vault/ZVault.java
@@ -4,7 +4,8 @@
import fr.maxlego08.essentials.api.vault.Vault;
import fr.maxlego08.essentials.api.vault.VaultItem;
import fr.maxlego08.essentials.zutils.utils.ZUtils;
-import fr.maxlego08.menu.common.utils.nms.ItemStackUtils;
+import fr.maxlego08.menu.api.InventoryManager;
+import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
@@ -70,7 +71,8 @@ public void setIconItemStack(ItemStack iconItemStack) {
@Override
public void createItem(VaultItemDTO vaultItemDTO) {
- this.vaultItems.put(vaultItemDTO.slot(), new ZVaultItem(vaultItemDTO.slot(), ItemStackUtils.deserializeItemStack(vaultItemDTO.item()), vaultItemDTO.quantity()));
+ InventoryManager inventoryManager = Bukkit.getServicesManager().load(InventoryManager.class);
+ this.vaultItems.put(vaultItemDTO.slot(), new ZVaultItem(vaultItemDTO.slot(), inventoryManager.deserializeItemStack(vaultItemDTO.item()), vaultItemDTO.quantity()));
}
@Override
diff --git a/src/main/java/fr/maxlego08/essentials/storage/database/repositeries/UserMailBoxRepository.java b/src/main/java/fr/maxlego08/essentials/storage/database/repositeries/UserMailBoxRepository.java
index 752de05e..3a3aaf67 100644
--- a/src/main/java/fr/maxlego08/essentials/storage/database/repositeries/UserMailBoxRepository.java
+++ b/src/main/java/fr/maxlego08/essentials/storage/database/repositeries/UserMailBoxRepository.java
@@ -4,7 +4,6 @@
import fr.maxlego08.essentials.api.dto.MailBoxDTO;
import fr.maxlego08.essentials.api.mailbox.MailBoxItem;
import fr.maxlego08.essentials.storage.database.Repository;
-import fr.maxlego08.menu.common.utils.nms.ItemStackUtils;
import fr.maxlego08.sarah.DatabaseConnection;
import java.util.Date;
@@ -23,7 +22,7 @@ public List select(UUID uuid) {
public void insert(MailBoxItem mailBoxItem) {
this.insert(table -> {
table.uuid("unique_id", mailBoxItem.getUniqueId());
- table.string("itemstack", ItemStackUtils.serializeItemStack(mailBoxItem.getItemStack()));
+ table.string("itemstack", this.plugin.getInventoryManager().serializeItemStack(mailBoxItem.getItemStack()));
table.object("expired_at", mailBoxItem.getExpiredAt());
}, mailBoxItem::setId);
}
diff --git a/src/main/java/fr/maxlego08/essentials/storage/storages/SqlStorage.java b/src/main/java/fr/maxlego08/essentials/storage/storages/SqlStorage.java
index 30c08a8b..61105914 100644
--- a/src/main/java/fr/maxlego08/essentials/storage/storages/SqlStorage.java
+++ b/src/main/java/fr/maxlego08/essentials/storage/storages/SqlStorage.java
@@ -28,7 +28,6 @@
import fr.maxlego08.essentials.user.ZUser;
import fr.maxlego08.essentials.zutils.utils.StorageHelper;
import fr.maxlego08.essentials.zutils.utils.TypeSafeCache;
-import fr.maxlego08.menu.common.utils.nms.ItemStackUtils;
import fr.maxlego08.sarah.*;
import fr.maxlego08.sarah.database.DatabaseType;
import fr.maxlego08.sarah.logger.JULogger;
@@ -821,7 +820,7 @@ public List getPlayerVaultSlots() {
@Override
public void updateVault(UUID uniqueId, Vault vault) {
- async(uniqueId, () -> with(VaultRepository.class).update(uniqueId, vault.getVaultId(), vault.getName(), vault.getIconItemStack() == null ? null : ItemStackUtils.serializeItemStack(vault.getIconItemStack())));
+ async(uniqueId, () -> with(VaultRepository.class).update(uniqueId, vault.getVaultId(), vault.getName(), vault.getIconItemStack() == null ? null : this.plugin.getInventoryManager().serializeItemStack(vault.getIconItemStack())));
}
@Override
diff --git a/src/main/java/fr/maxlego08/essentials/zutils/utils/CustomSoundOption.java b/src/main/java/fr/maxlego08/essentials/zutils/utils/CustomSoundOption.java
new file mode 100644
index 00000000..836cf62f
--- /dev/null
+++ b/src/main/java/fr/maxlego08/essentials/zutils/utils/CustomSoundOption.java
@@ -0,0 +1,63 @@
+package fr.maxlego08.essentials.zutils.utils;
+
+import fr.maxlego08.menu.api.sound.SoundOption;
+import fr.maxlego08.menu.hooks.xseries.XSound;
+import org.bukkit.Location;
+import org.bukkit.SoundCategory;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * A zMenu sound option for resource-pack keys that its sound factory cannot resolve.
+ *
+ * Playback uses the configured key directly through Paper. Player sounds use the
+ * {@code MASTER} category; sounds played at another entity are heard in its world.
+ * No vanilla {@link XSound} is associated with this option.
+ *
+ * @param soundName the resource-pack sound key, for example {@code mypack:teleport}
+ * @param pitch the playback pitch
+ * @param volume the playback volume
+ * @see fr.maxlego08.menu.api.InventoryManager#createSoundOption(String, String, float, float)
+ */
+public record CustomSoundOption(String soundName, float pitch, float volume) implements SoundOption {
+
+ /**
+ * @return null because this option plays a custom key rather than a vanilla sound
+ */
+ @Override
+ public @Nullable XSound getSound() {
+ return null;
+ }
+
+ @Override
+ public float getPitch() {
+ return this.pitch;
+ }
+
+ @Override
+ public float getVolume() {
+ return this.volume;
+ }
+
+ @Override
+ public boolean isCustom() {
+ return true;
+ }
+
+ @Override
+ public @NotNull String getSoundName() {
+ return this.soundName;
+ }
+
+ @Override
+ public void play(@NotNull Entity entity) {
+ Location location = entity.getLocation();
+ if (entity instanceof Player player) {
+ player.playSound(location, this.soundName, SoundCategory.MASTER, this.volume, this.pitch);
+ } else {
+ location.getWorld().playSound(location, this.soundName, this.volume, this.pitch);
+ }
+ }
+}