From 3dd9c47147a1c64455c9a3471f5d8daf460b8582 Mon Sep 17 00:00:00 2001 From: Athar42 Date: Sun, 20 Jul 2025 21:05:29 +0200 Subject: [PATCH] New upcoming release 5.0 - Reintroducing the custom Goat horns, but also add the custom player heads ! Too many changes to be listed, but require a PaperMC server 1.21.7-9 at minimum. --- build.gradle | 2 +- gradle.properties | 8 +- gradle/wrapper/gradle-wrapper.properties | 2 +- readme.md | 122 +++++++--- .../Navoei/customdiscsplugin/CustomDiscs.java | 162 ++++++++++--- .../customdiscsplugin/HopperManager.java | 73 +++--- .../JukeboxStateManager.java | 2 +- .../customdiscsplugin/PlayerManager.java | 228 ++++++++++++++++-- .../ServerVersionChecker.java | 101 ++++++++ .../Navoei/customdiscsplugin/TypeChecker.java | 73 ++++++ .../Navoei/customdiscsplugin/VoicePlugin.java | 86 ++++++- .../command/CustomDiscCommand.java | 16 +- .../command/SubCommands/CreateSubCommand.java | 103 +++++--- .../SubCommands/DownloadSubCommand.java | 45 ++-- .../SetHornCooldownSubCommand.java | 75 ++++++ .../SubCommands/SetRangeSubCommand.java | 57 +++-- .../customdiscsplugin/event/HeadPlay.java | 127 ++++++++++ .../customdiscsplugin/event/HornPlay.java | 145 +++++++++++ .../customdiscsplugin/event/JukeBox.java | 18 +- .../customdiscsplugin/language/Lang.java | 18 +- src/main/resources/config.yml | 66 ++++- src/main/resources/goat_horn_category.png | Bin 0 -> 197 bytes src/main/resources/lang.yml | 41 ++-- src/main/resources/player_head_category.png | Bin 0 -> 3543 bytes src/main/resources/plugin.yml | 2 +- 25 files changed, 1325 insertions(+), 247 deletions(-) create mode 100644 src/main/java/me/Navoei/customdiscsplugin/ServerVersionChecker.java create mode 100644 src/main/java/me/Navoei/customdiscsplugin/TypeChecker.java create mode 100644 src/main/java/me/Navoei/customdiscsplugin/command/SubCommands/SetHornCooldownSubCommand.java create mode 100644 src/main/java/me/Navoei/customdiscsplugin/event/HeadPlay.java create mode 100644 src/main/java/me/Navoei/customdiscsplugin/event/HornPlay.java create mode 100644 src/main/resources/goat_horn_category.png create mode 100644 src/main/resources/player_head_category.png diff --git a/build.gradle b/build.gradle index da40844..c8a968c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id "com.gradleup.shadow" version "8.3.6" + id "com.gradleup.shadow" version "8.3.8" } java { diff --git a/gradle.properties b/gradle.properties index 12d6712..b3fac17 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,17 +1,15 @@ org.gradle.jvmargs=-Xmx2G -java_version=21 - mp3spi_version=1.9.5.4 bukkit_api_version=1.21 -bukkit_version=1.21.7-R0.1-SNAPSHOT +bukkit_version=1.21.8-R0.1-SNAPSHOT mod_id=customdiscsplugin # Target an older API to make it compatible with older versions of Simple Voice Chat -voicechat_api_version=2.3.3 +voicechat_api_version=2.5.31 command_api_version=10.1.1 -plugin_version=4.5 +plugin_version=5.0 maven_group=me.Navoei.customdiscsplugin archives_base_name=custom-discs \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1dd35f4..80a0dfe 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Sun Aug 11 17:52:06 EDT 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/readme.md b/readme.md index a27664e..6a189dc 100644 --- a/readme.md +++ b/readme.md @@ -1,7 +1,17 @@ -# Custom Discs v4.5 for Paper 1.21.6 and 1.21.7 +# Custom Discs v5.0 for Paper 1.21.7 / 1.21.8 + +> ### ⚠️⚠️⚠️ READ THIS SECTION CAREFULLY! ⚠️⚠️⚠️ +> ### This version introduce some breaking changes and *require at least the build 9 of PaperMC 1.21.7*. +> ### Any earlier version of PaperMC is not guaranteed to function and new features may not function at all! +> ### ⚠️⚠️⚠️ Please also note that this new release requires you to delete the ```config.yml``` and ```lang.yml``` files (or update them with the new options). +> ### ⚠️⚠️⚠️ Without this being done, you could experience issues with this plugin. + + +## 🆘 NEW : You can now ask for support on this Discord server ➡️ https://discord.gg/YJpqruvZ97 (but you can still use the "[Issues](https://github.com/Navoei/CustomDiscs/issues)" report section of GitHub) + A Paper fork of henkelmax's Audio Player. Special thanks to Athar42 for maintaining this plugin. -- Play custom music discs using the Simple Voice Chat API. (The voice chat mod is required on the client and server.) +- Play custom music discs, goat horns and player's head using the Simple Voice Chat API. (The voice chat mod is required on the client and server.) - Use ```/customdisc``` or ```/cd``` to view available commands. - Music files should go into ```plugins/CustomDiscs/musicdata/``` - Music files must be in the ```.wav```, ```.flac```, or ```.mp3``` format. @@ -19,16 +29,34 @@ Permission Nodes (Required to run the commands. Playing discs does not require a - ```customdiscs.create``` to create a disc - ```customdiscs.download``` to download a file - ```customdiscs.range``` to set the range of the disc +- ```customdiscs.horncooldown``` to set the cooldown (in ticks) for using the modified goat horn Dependencies: -- This plugin depends on the latest version of ProtocolLib available for your Paper version and SimpleVoiceChatBukkit (2.5.33 is recommended). +- This plugin depends on the latest version of ProtocolLib available for your Paper version and SimpleVoiceChatBukkit (latest is recommended - at least version 2.5.31 required). https://user-images.githubusercontent.com/64107368/178426026-c454ac66-5133-4f3a-9af9-7f674e022423.mp4 Default Config.yml: ``` -# [Music Disc Config] +# [General CustomDiscs Config] + +# The maximum download size in megabytes. +max-download-size: 50 + +# The master volume of music discs from 0-1. (You can set values like 0.5 for 50% volume). +music-disc-volume: 1 + +# Debug Mode - To display some more logging information and Stack Trace informations +debugMode: false + +# [Music Discs Config] + +# Enable custom music discs. +music-disc-enable: true + +# Enable "Now playing" message for custom music discs. +music-disc-playing-enable: true # The distance from which music discs can be heard in blocks. music-disc-distance: 16 @@ -36,41 +64,75 @@ music-disc-distance: 16 # The max distance from which music discs can be heard in blocks. music-disc-max-distance: 256 -# The master volume of music discs from 0-1. (You can set values like 0.5 for 50% volume). -music-disc-volume: 1 +# [Goat Horns Config] -# The maximum download size in megabytes. -max-download-size: 50 +# Enable custom goat horns. +custom-horn-enable: true -#Custom Discs Help Page +# Enable "Now playing" message for custom horns. +custom-horn-playing-enable: true + +# The distance from which custom horns can be heard in blocks. +custom-horn-distance: 16 + +# The max distance from which custom horns can be heard in blocks. +custom-horn-max-distance: 256 + +# The default cooldown time for horns in ticks from 1 to the max value of horn-max-cooldown (1 second is 20 ticks). +horn-cooldown: 140 + +# The default max cooldown time for horns in ticks (1 second is 20 ticks). +horn-max-cooldown: 6000 + +# [Player Heads Config] + +# Enable custom player heads. +custom-head-enable: true + +# Enable "Now playing" message for player heads. +custom-head-playing-enable: true + +# The distance from which music discs can be heard in blocks. +custom-head-distance: 16 + +# The max distance from which music discs can be heard in blocks. +custom-head-max-distance: 256 + + +# [DO NOT EDIT BELOW THIS LINE - Help configuration] +# Custom Discs Help Page help: - - "&8-[&6CustomDiscs Help Page&8]-" + - "&8-[&6CustomDiscs v5.0 - Help Page&8]-" - "&aAuthor&7: &6Navoei" - - "&aContributors&7: &6alfw / &6Athar42" + - "&aContributors&7: &6Athar42 / &6alfw" - "&fGit&0Hub&7: &9&ohttps://github.com/Navoei/CustomDiscs" + - "&aDiscord&7: &9&ohttps://discord.gg/YJpqruvZ97" ``` Default Lang.yml: ``` -prefix: "&8[&6CustomDiscs&8]&r" -no-permission: "&cYou do not have permission to execute this command." -invalid-filename: "&cThis is an invalid filename!" -no-disc-name-provided: "&cYou must provide a name for your disc." -invalid-format: "&cFile must be in wav, flac, or mp3 format!" -file-not-found: "&cFile not found!" -invalid-arguments: "&cInvalid arguments. &7(&a%command_syntax%&7)" -not-holding-disc: "&cYou must hold a disc in your main hand." -create-filename: "&7Your filename is: &a\"%filename%\"." -create-custom-name: "&7Your custom name is: &a\"%custom_name%\"." -downloading-file: "&7Downloading file..." -file-too-large: "&cThe file is larger than %max_download_size%MB." -successful-download: "&aFile successfully downloaded to &7%file_path%&a." -create-disc: "&aCreate a disc by doing &7/cd create %filename% \"Custom Lore\"&a." -download-error: "&cAn error has occurred while downloading." -now-playing: "&6Now playing: %song_name%" -disc-converted: "&aConverted disc to new format! &fThis is due to changes in newer Minecraft versions which introduced &7JukeboxPlayableComponent&f." -invalid-range: "&cYou need to chose a range between 1 and %range_value%" -create-custom-range: "&7Your range is set to: &a\"%custom_range%\"." +prefix: '&8[&6CustomDiscs&8]&r' +invalid-filename: '&cThis is an invalid filename!' +invalid-format: '&cFile must be in wav, flac, or mp3 format!' +file-not-found: '&cFile not found!' +not-holding-correct-item: '&cYou must either hold a disc, goat horn or player head in your main hand.' +create-filename: '&7Your filename is: &a"%filename%".' +create-custom-name: '&7Your custom name is: &a"%custom_name%".' +downloading-file: '&7Downloading file...' +file-too-large: '&cThe file is larger than %max_download_size%MB.' +successful-download: '&aFile successfully downloaded to &7%file_path%&a.' +create-disc: '&aCreate a disc by doing &7/cd create %filename% "Custom Lore"&a.' +download-error: '&cAn error has occurred while downloading.' +now-playing: '&6Now playing: %song_name%' +disc-converted: '&aConverted disc to new format! &fThis is due to changes in newer Minecraft versions which introduced &7ToolTipDisplay&f.' +invalid-range: '&cYou need to chose a range between 1 and %range_value%' +create-custom-range: '&7Your range is set to: &a"%custom_range%".' +not-holding-modified-goathorn: '&cYou must hold a modified goat horn in your main hand.' +invalid-cooldown: '&cYou need to chose a cooldown between 1 and %cooldown_value% (in ticks)' +create-custom-goat-cooldown: '&7Your goat horn cooldown is set to: &a"%custom_goat_cooldown%".' +custom-music-disabled: '&7Custom music discs are disabled in the configuration.' +custom-head-disabled: '&7Custom player heads are disabled in the configuration.' +custom-horn-disabled: '&7Custom goat horns are disabled in the configuration.' ``` diff --git a/src/main/java/me/Navoei/customdiscsplugin/CustomDiscs.java b/src/main/java/me/Navoei/customdiscsplugin/CustomDiscs.java index 56e631c..84e82b0 100644 --- a/src/main/java/me/Navoei/customdiscsplugin/CustomDiscs.java +++ b/src/main/java/me/Navoei/customdiscsplugin/CustomDiscs.java @@ -1,5 +1,11 @@ package me.Navoei.customdiscsplugin; +import me.Navoei.customdiscsplugin.command.CustomDiscCommand; +import me.Navoei.customdiscsplugin.event.JukeBox; +import me.Navoei.customdiscsplugin.event.HeadPlay; +import me.Navoei.customdiscsplugin.event.HornPlay; +import me.Navoei.customdiscsplugin.language.Lang; + import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.ProtocolManager; @@ -7,12 +13,12 @@ import com.comphenix.protocol.events.ListenerPriority; import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; + import de.maxhenkel.voicechat.api.BukkitVoicechatService; + import dev.jorel.commandapi.CommandAPI; import dev.jorel.commandapi.CommandAPIBukkitConfig; -import me.Navoei.customdiscsplugin.command.CustomDiscCommand; -import me.Navoei.customdiscsplugin.event.JukeBox; -import me.Navoei.customdiscsplugin.language.Lang; + import org.bukkit.NamespacedKey; import org.bukkit.block.Jukebox; import org.bukkit.configuration.file.YamlConfiguration; @@ -30,23 +36,36 @@ public final class CustomDiscs extends JavaPlugin { @Nullable private VoicePlugin voicechatPlugin; - private Logger log; + private Logger pluginLogger; + private static boolean debugMode = false; public static YamlConfiguration LANG; public static File LANG_FILE; + public static boolean musicDiscEnable = true; + public static boolean musicDiscPlayingEnable = true; public float musicDiscDistance; public float musicDiscMaxDistance; public float musicDiscVolume; + public static boolean customHornEnable = true; + public static boolean customHornPlayingEnable = true; + public float customHornDistance; + public float customHornMaxDistance; + public int hornCooldown; + public int hornMaxCooldown; + public static boolean customHeadEnable = true; + public static boolean customHeadPlayingEnable = true; + public float customHeadDistance; + public float customHeadMaxDistance; @Override public void onLoad() { CustomDiscs.instance = this; - CommandAPI.onLoad(new CommandAPIBukkitConfig(this).verboseOutput(true)); + CommandAPI.onLoad(new CommandAPIBukkitConfig(this).verboseOutput(true).beLenientForMinorVersions(true)); new CustomDiscCommand(this).register("customdiscs"); } @Override public void onEnable() { - log = getLogger(); + pluginLogger = getLogger(); CommandAPI.onEnable(); @@ -54,7 +73,29 @@ public final class CustomDiscs extends JavaPlugin { this.saveDefaultConfig(); loadLang(); - + + // Config initializer section + debugMode = getConfig().getBoolean("debugMode", false); + musicDiscEnable = getConfig().getBoolean("music-disc-enable"); + musicDiscPlayingEnable = getConfig().getBoolean("music-disc-playing-enable"); + musicDiscDistance = getConfig().getInt("music-disc-distance"); + musicDiscMaxDistance = getConfig().getInt("music-disc-max-distance"); + musicDiscVolume = Float.parseFloat(Objects.requireNonNull(getConfig().getString("music-disc-volume"))); + customHornEnable = getConfig().getBoolean("custom-horn-enable"); + customHornPlayingEnable = getConfig().getBoolean("custom-horn-playing-enable"); + customHornDistance = getConfig().getInt("custom-horn-distance"); + customHornMaxDistance = getConfig().getInt("custom-horn-max-distance"); + hornCooldown = getConfig().getInt("horn-cooldown"); + hornMaxCooldown = getConfig().getInt("horn-max-cooldown"); + customHeadEnable = getConfig().getBoolean("custom-head-enable"); + customHeadPlayingEnable = getConfig().getBoolean("custom-head-playing-enable"); + customHeadDistance = getConfig().getInt("custom-head-distance"); + customHeadMaxDistance = getConfig().getInt("custom-head-max-distance"); + + // Checking server version and display console message in case the server is not supported + ServerVersionChecker serverVersionChecker = new ServerVersionChecker(this); + serverVersionChecker.checkVersion(); + File musicData = new File(this.getDataFolder(), "musicdata"); if (!(musicData.exists())) { musicData.mkdirs(); @@ -63,26 +104,36 @@ public final class CustomDiscs extends JavaPlugin { if (service != null) { voicechatPlugin = new VoicePlugin(); service.registerPlugin(voicechatPlugin); - log.info("Successfully registered CustomDiscs plugin"); + pluginLogger.info("Successfully registered CustomDiscs plugin"); } else { - log.info("Failed to register CustomDiscs plugin"); + pluginLogger.info("Failed to register CustomDiscs plugin"); } - - getServer().getPluginManager().registerEvents(new JukeBox(), this); - getServer().getPluginManager().registerEvents(new HopperManager(), this); - - musicDiscDistance = Objects.requireNonNull(getConfig().getInt("music-disc-distance")); - musicDiscMaxDistance = Objects.requireNonNull(getConfig().getInt("music-disc-max-distance")); - musicDiscVolume = Float.parseFloat(Objects.requireNonNull(getConfig().getString("music-disc-volume"))); - + + if (isMusicDiscEnable()) { + getServer().getPluginManager().registerEvents(new JukeBox(), this); + getServer().getPluginManager().registerEvents(new HopperManager(), this); + } + if (isCustomHeadEnable()) { getServer().getPluginManager().registerEvents(new HeadPlay(), this); } + if (isCustomHornEnable()) { getServer().getPluginManager().registerEvents(new HornPlay(), this); } + + // To avoid any "0" values, set it to 1. + if (hornCooldown <= 0) { + hornCooldown = 1; + } + if (hornMaxCooldown <= 0) { + hornMaxCooldown = 1; + } + + ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager(); protocolManager.addPacketListener(new PacketAdapter(this, ListenerPriority.NORMAL, PacketType.Play.Server.WORLD_EVENT) { @Override public void onPacketSending(PacketEvent event) { PacketContainer packet = event.getPacket(); - + if (packet.getIntegers().read(0).toString().equals("1010")) { + if (!isMusicDiscEnable()) { return; } Jukebox jukebox = (Jukebox) packet.getBlockPositionModifier().read(0).toLocation(event.getPlayer().getWorld()).getBlock().getState(); if (!jukebox.getRecord().hasItemMeta()) return; @@ -106,7 +157,7 @@ public final class CustomDiscs extends JavaPlugin { CommandAPI.onDisable(); if (voicechatPlugin != null) { getServer().getServicesManager().unregister(voicechatPlugin); - log.info("Successfully unregistered CustomDiscs plugin"); + pluginLogger.info("Successfully unregistered CustomDiscs plugin"); } } @@ -116,8 +167,6 @@ public final class CustomDiscs extends JavaPlugin { /** * Load the lang.yml file. - * - * @return The lang.yml config. */ public void loadLang() { File lang = new File(getDataFolder(), "lang.yml"); @@ -133,10 +182,12 @@ public final class CustomDiscs extends JavaPlugin { Lang.setFile(defConfig); } } catch (IOException e) { - e.printStackTrace(); // So they notice - log.severe("Failed to create lang.yml for CustomDiscs."); - log.severe("Now disabling..."); + pluginLogger.severe("Failed to create lang.yml for CustomDiscs."); + pluginLogger.severe("Now disabling..."); this.setEnabled(false); // Without it loaded, we can't send them messages + if (isDebugMode()) { + pluginLogger.log(Level.SEVERE, "Exception output: ", e); + } } } YamlConfiguration conf = YamlConfiguration.loadConfiguration(lang); @@ -151,9 +202,11 @@ public final class CustomDiscs extends JavaPlugin { try { conf.save(getLangFile()); } catch (IOException e) { - log.log(Level.WARNING, "Failed to save lang.yml for CustomDiscs"); - log.log(Level.WARNING, "Now disabling..."); - e.printStackTrace(); + pluginLogger.warning("Failed to save lang.yml for CustomDiscs"); + pluginLogger.warning("Now disabling..."); + if (isDebugMode()) { + pluginLogger.log(Level.SEVERE, "Exception output: ", e); + } } } @@ -180,8 +233,59 @@ public final class CustomDiscs extends JavaPlugin { try (OutputStream output = new FileOutputStream(file)) { input.transferTo(output); } catch (IOException ioException) { - ioException.printStackTrace(); + if (isDebugMode()) { + CustomDiscs.getInstance().getLogger().log(Level.SEVERE, "Exception output: ", ioException); + } } } -} + + /** + * Get the debugMode configuration. + * + * @return The boolean value of debugMode. + */ + public static boolean isDebugMode() { return debugMode; } + + /** + * Get the musicDiscPlayingEnable configuration. + * + * @return The boolean value of musicDiscPlayingEnable. + */ + public static boolean isMusicDiscEnable() { return musicDiscEnable; } + + /** + * Get the customHornPlayingEnable configuration. + * + * @return The boolean value of customHornPlayingEnable. + */ + public static boolean isCustomHornEnable() { return customHornEnable; } + + /** + * Get the customHeadPlayingEnable configuration. + * + * @return The boolean value of customHeadPlayingEnable. + */ + public static boolean isCustomHeadEnable() { return customHeadEnable; } + + /** + * Get the musicDiscPlayingEnable configuration. + * + * @return The boolean value of musicDiscPlayingEnable. + */ + public static boolean isMusicDiscPlayingEnable() { return musicDiscPlayingEnable; } + + /** + * Get the customHornPlayingEnable configuration. + * + * @return The boolean value of customHornPlayingEnable. + */ + public static boolean isCustomHornPlayingEnable() { return customHornPlayingEnable; } + + /** + * Get the customHeadPlayingEnable configuration. + * + * @return The boolean value of customHeadPlayingEnable. + */ + public static boolean isCustomHeadPlayingEnable() { return customHeadPlayingEnable; } +} \ No newline at end of file diff --git a/src/main/java/me/Navoei/customdiscsplugin/HopperManager.java b/src/main/java/me/Navoei/customdiscsplugin/HopperManager.java index 90819f7..bd8a518 100644 --- a/src/main/java/me/Navoei/customdiscsplugin/HopperManager.java +++ b/src/main/java/me/Navoei/customdiscsplugin/HopperManager.java @@ -1,54 +1,49 @@ package me.Navoei.customdiscsplugin; +import me.Navoei.customdiscsplugin.language.Lang; + import io.papermc.paper.datacomponent.DataComponentTypes; import io.papermc.paper.datacomponent.item.TooltipDisplay; -import me.Navoei.customdiscsplugin.language.Lang; + import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.block.Block; + import org.bukkit.block.BlockState; -//import org.bukkit.block.Container; import org.bukkit.block.Jukebox; +import org.bukkit.entity.minecart.HopperMinecart; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryMoveItemEvent; import org.bukkit.event.inventory.InventoryType; import org.bukkit.event.world.ChunkLoadEvent; -import org.bukkit.inventory.ItemStack; import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.components.JukeboxPlayableComponent; +import org.bukkit.NamespacedKey; import org.bukkit.persistence.PersistentDataType; import java.nio.file.Path; import java.util.Objects; import java.util.Optional; - -import org.bukkit.entity.minecart.HopperMinecart; -import org.bukkit.inventory.InventoryHolder; - -// Used only if logger is needed -//import java.util.logging.Logger; -//import org.bukkit.Bukkit; +import java.util.logging.Logger; public class HopperManager implements Listener { CustomDiscs customDiscs = CustomDiscs.getInstance(); - PlayerManager playerManager = PlayerManager.instance(); - - //private static final Logger logger = Bukkit.getLogger(); // or Logger.getLogger("Minecraft"); + private final Logger pluginLogger = customDiscs.getLogger(); + private final boolean debugModeResult = CustomDiscs.isDebugMode(); @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onJukeboxInsertFromHopper(InventoryMoveItemEvent event) { - //logger.warning("Enter : onJukeboxInsertFromHopper"); + if (debugModeResult) { + pluginLogger.info("DEBUG - HopperManager -> Enter : onJukeboxInsertFromHopper"); + } if (event.getDestination().getLocation() == null) return; if (!event.getDestination().getType().equals(InventoryType.JUKEBOX)) return; - if (!isCustomMusicDisc(event.getItem())) return; + if (!TypeChecker.isCustomMusicDisc(event.getItem())) return; Component songNameComponent = Objects.requireNonNull(event.getItem().getItemMeta().lore()).get(0).asComponent(); String songName = PlainTextComponentSerializer.plainText().serialize(songNameComponent); @@ -78,7 +73,9 @@ public class HopperManager implements Listener { @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onJukeboxEjectToHopperMinecart(InventoryMoveItemEvent event) { - //logger.warning("Enter : onJukeboxEjectToHopper"); + if (debugModeResult) { + pluginLogger.info("DEBUG - HopperManager -> Enter : onJukeboxEjectToHopper"); + } InventoryHolder holderSource = event.getSource().getHolder(); InventoryHolder holderDestination = event.getDestination().getHolder(); @@ -86,55 +83,39 @@ public class HopperManager implements Listener { if (event.getSource().getLocation() == null) return; if (!event.getSource().getType().equals(InventoryType.JUKEBOX)) return; if (event.getItem().getItemMeta() == null) return; - if (!isCustomMusicDisc(event.getItem())) return; + if (!TypeChecker.isCustomMusicDisc(event.getItem())) return; if (holderDestination instanceof HopperMinecart) { - stopDisc(((BlockState) holderSource).getBlock()); + playerManager.stopDisc(((BlockState) holderSource).getBlock()); } } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onChunkLoad(ChunkLoadEvent event) { - //logger.warning("Enter : onChunkLoad"); + if (debugModeResult) { + pluginLogger.info("DEBUG - HopperManager -> Enter : onChunkLoad"); + } for (BlockState blockState : event.getChunk().getTileEntities()) { if (blockState instanceof Jukebox jukebox) { if (!jukebox.hasRecord()) return; - if (!PlayerManager.instance().isAudioPlayerPlaying(blockState.getLocation()) && isCustomMusicDisc(jukebox.getRecord())) { - //Set the block type to force an update. + if (!PlayerManager.instance().isAudioPlayerPlaying(blockState.getLocation()) && TypeChecker.isCustomMusicDisc(jukebox.getRecord())) { jukebox.stopPlaying(); } } } } - public void discToHopper(Block block) { - if (block == null) return; - if (!block.getLocation().getChunk().isLoaded()) return; - if (!block.getType().equals(Material.JUKEBOX)) return; - - Jukebox jukebox = (Jukebox) block.getState(); - jukebox.stopPlaying(); - } - - private boolean isCustomMusicDisc(ItemStack item) { - //logger.warning("Enter : isCustomMusicDisc"); - return item.getItemMeta().getPersistentDataContainer().has(new NamespacedKey(customDiscs, "customdisc"), PersistentDataType.STRING); - } - - private void stopDisc(Block block) { - playerManager.stopLocationalAudio(block.getLocation()); - } - private static HopperManager instance; public static HopperManager instance() { - //logger.warning("Enter : HopperManager Instance"); + if (CustomDiscs.isDebugMode()) { + CustomDiscs.getInstance().getLogger().info("DEBUG - HopperManager -> Enter : HopperManager Instance"); + } if (instance == null) { instance = new HopperManager(); } return instance; } - -} +} \ No newline at end of file diff --git a/src/main/java/me/Navoei/customdiscsplugin/JukeboxStateManager.java b/src/main/java/me/Navoei/customdiscsplugin/JukeboxStateManager.java index 294a6a9..7439a61 100644 --- a/src/main/java/me/Navoei/customdiscsplugin/JukeboxStateManager.java +++ b/src/main/java/me/Navoei/customdiscsplugin/JukeboxStateManager.java @@ -28,4 +28,4 @@ public class JukeboxStateManager { }, 1, 1); } -} +} \ No newline at end of file diff --git a/src/main/java/me/Navoei/customdiscsplugin/PlayerManager.java b/src/main/java/me/Navoei/customdiscsplugin/PlayerManager.java index 79acebf..7b271d0 100644 --- a/src/main/java/me/Navoei/customdiscsplugin/PlayerManager.java +++ b/src/main/java/me/Navoei/customdiscsplugin/PlayerManager.java @@ -1,28 +1,29 @@ package me.Navoei.customdiscsplugin; import de.maxhenkel.voicechat.api.ServerPlayer; -import de.maxhenkel.voicechat.api.VoicechatConnection; import de.maxhenkel.voicechat.api.VoicechatServerApi; import de.maxhenkel.voicechat.api.audiochannel.AudioChannel; import de.maxhenkel.voicechat.api.audiochannel.AudioPlayer; import de.maxhenkel.voicechat.api.audiochannel.LocationalAudioChannel; + import javazoom.spi.mpeg.sampled.convert.MpegFormatConversionProvider; import javazoom.spi.mpeg.sampled.file.MpegAudioFileReader; + import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.format.NamedTextColor; + import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.entity.Player; + import org.jflac.sound.spi.Flac2PcmAudioInputStream; import org.jflac.sound.spi.FlacAudioFileReader; import javax.annotation.Nullable; import javax.sound.sampled.*; -import java.io.File; import java.io.IOException; import java.nio.file.Path; -import java.util.Arrays; import java.util.Collection; import java.util.Map; import java.util.UUID; @@ -31,6 +32,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import java.util.logging.Logger; public class PlayerManager { @@ -38,6 +41,11 @@ public class PlayerManager { private final Map playerMap; private final ExecutorService executorService; private static final AudioFormat FORMAT = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 48000F, 16, 1, 2, 48000F, false); + private final Logger pluginLogger = plugin.getLogger(); + private final boolean debugModeResult = CustomDiscs.isDebugMode(); + private final boolean musicDiscPlayingEnableResult = CustomDiscs.isMusicDiscPlayingEnable(); + private final boolean customHornPlayingEnableResult = CustomDiscs.isCustomHornPlayingEnable(); + private final boolean customHeadPlayingEnableResult = CustomDiscs.isCustomHeadPlayingEnable(); public PlayerManager() { this.playerMap = new ConcurrentHashMap<>(); @@ -60,9 +68,11 @@ public class PlayerManager { Collection playersInRange = api.getPlayersInRange(api.fromServerLevel(block.getWorld()), audioChannel.getLocation(), range); - for (ServerPlayer serverPlayer : playersInRange) { - Player bukkitPlayer = (Player) serverPlayer.getPlayer(); - bukkitPlayer.sendActionBar(actionbarComponent); + if (musicDiscPlayingEnableResult) { + for (ServerPlayer serverPlayer : playersInRange) { + Player bukkitPlayer = (Player) serverPlayer.getPlayer(); + bukkitPlayer.sendActionBar(actionbarComponent); + } } AtomicBoolean stopped = new AtomicBoolean(); @@ -99,7 +109,142 @@ public class PlayerManager { } catch (IOException e) { throw new RuntimeException(e); } - //plugin.getServer().getRegionScheduler().run(plugin, block.getLocation(), scheduledTask -> HopperManager.instance().discToHopper(block)); + if (playerMap.containsValue(playerReference)) { + playerMap.remove(id); + } + }); + synchronized (stopped) { + if (!stopped.get()) { + player.set(audioPlayer); + } else { + audioPlayer.stopPlaying(); + } + } + }); + + } + + public void playAudioHorn(VoicechatServerApi api, Path soundFilePath, Player block, Component actionbarComponent, float range) { + UUID id = UUID.nameUUIDFromBytes(block.getLocation().toString().getBytes()); + + LocationalAudioChannel audioChannel = api.createLocationalAudioChannel(id, api.fromServerLevel(block.getWorld()), api.createPosition(block.getLocation().getX() + 0.5d, block.getLocation().getY() + 0.5d, block.getLocation().getZ() + 0.5d)); + + if (audioChannel == null) return; + + audioChannel.setCategory(VoicePlugin.GOAT_HORN_CATEGORY); + audioChannel.setDistance(range); + + Collection playersInRange = api.getPlayersInRange(api.fromServerLevel(block.getWorld()), audioChannel.getLocation(), range); + + if (customHornPlayingEnableResult) { + for (ServerPlayer serverPlayer : playersInRange) { + Player bukkitPlayer = (Player) serverPlayer.getPlayer(); + bukkitPlayer.sendActionBar(actionbarComponent); + } + } + + AtomicBoolean stopped = new AtomicBoolean(); + AtomicReference player = new AtomicReference<>(); + PlayerReference playerReference = new PlayerReference(() -> { + synchronized (stopped) { + stopped.set(true); + de.maxhenkel.voicechat.api.audiochannel.AudioPlayer audioPlayer = player.get(); + if (audioPlayer != null) { + audioPlayer.stopPlaying(); + } + } + }, player, soundFilePath); + + playerMap.put(id, playerReference); + + executorService.execute(() -> { + AudioPlayer audioPlayer = null; + AudioInputStream inputStream = null; + try { + inputStream = getAudioInputStream(soundFilePath, FORMAT); + audioPlayer = playChannelHorn(api, audioChannel, block, inputStream, playersInRange); + } catch (UnsupportedAudioFileException | IOException e) { + throw new RuntimeException(e); + } + if (audioPlayer == null) { + playerMap.remove(id); + return; + } + AudioInputStream finalInputStream = inputStream; + audioPlayer.setOnStopped(() -> { + try { + finalInputStream.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + if (playerMap.containsValue(playerReference)) { + playerMap.remove(id); + } + }); + synchronized (stopped) { + if (!stopped.get()) { + player.set(audioPlayer); + } else { + audioPlayer.stopPlaying(); + } + } + }); + + } + + public void playAudioHead(VoicechatServerApi api, Path soundFilePath, Block block, Component actionbarComponent, float range) { + UUID id = UUID.nameUUIDFromBytes(block.getLocation().toString().getBytes()); + + LocationalAudioChannel audioChannel = api.createLocationalAudioChannel(id, api.fromServerLevel(block.getWorld()), api.createPosition(block.getLocation().getX() + 0.5d, block.getLocation().getY() + 0.5d, block.getLocation().getZ() + 0.5d)); + + if (audioChannel == null) return; + + audioChannel.setCategory(VoicePlugin.PLAYER_HEAD_CATEGORY); + audioChannel.setDistance(range); + + Collection playersInRange = api.getPlayersInRange(api.fromServerLevel(block.getWorld()), audioChannel.getLocation(), range); + + if (customHeadPlayingEnableResult) { + for (ServerPlayer serverPlayer : playersInRange) { + Player bukkitPlayer = (Player) serverPlayer.getPlayer(); + bukkitPlayer.sendActionBar(actionbarComponent); + } + } + + AtomicBoolean stopped = new AtomicBoolean(); + AtomicReference player = new AtomicReference<>(); + PlayerReference playerReference = new PlayerReference(() -> { + synchronized (stopped) { + stopped.set(true); + de.maxhenkel.voicechat.api.audiochannel.AudioPlayer audioPlayer = player.get(); + if (audioPlayer != null) { + audioPlayer.stopPlaying(); + } + } + }, player, soundFilePath); + + playerMap.put(id, playerReference); + + executorService.execute(() -> { + AudioPlayer audioPlayer = null; + AudioInputStream inputStream = null; + try { + inputStream = getAudioInputStream(soundFilePath, FORMAT); + audioPlayer = playChannelHead(api, audioChannel, block, inputStream, playersInRange); + } catch (UnsupportedAudioFileException | IOException e) { + throw new RuntimeException(e); + } + if (audioPlayer == null) { + playerMap.remove(id); + return; + } + AudioInputStream finalInputStream = inputStream; + audioPlayer.setOnStopped(() -> { + try { + finalInputStream.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } if (playerMap.containsValue(playerReference)) { playerMap.remove(id); } @@ -117,18 +262,66 @@ public class PlayerManager { @Nullable private de.maxhenkel.voicechat.api.audiochannel.AudioPlayer playChannel(VoicechatServerApi api, AudioChannel audioChannel, Block block, AudioInputStream inputStream, Collection playersInRange) throws UnsupportedAudioFileException, IOException { - //short[] audio = readSoundFile(soundFilePath); AudioPlayer audioPlayer = api.createAudioPlayer(audioChannel, api.createEncoder(), () -> { try { return readSoundFile(inputStream); } catch (Exception e) { - plugin.getLogger().info("Error Occurred At: " + block.getLocation()); + pluginLogger.severe("An error did occur while trying to play a music disc!"); + pluginLogger.info("Error Occurred At: " + block.getLocation()); for (ServerPlayer serverPlayer : playersInRange) { Player bukkitPlayer = (Player) serverPlayer.getPlayer(); TextComponent textComponent = Component.text("An error has occurred while trying to play this disc.").color(NamedTextColor.RED); bukkitPlayer.sendMessage(textComponent); } - e.printStackTrace(); + if(debugModeResult) { + pluginLogger.log(Level.SEVERE, "Exception output: ", e); + } + return null; + } + }); + audioPlayer.startPlaying(); + return audioPlayer; + } + + @Nullable + private de.maxhenkel.voicechat.api.audiochannel.AudioPlayer playChannelHorn(VoicechatServerApi api, AudioChannel audioChannel, Player block, AudioInputStream inputStream, Collection playersInRange) throws UnsupportedAudioFileException, IOException { + AudioPlayer audioPlayer = api.createAudioPlayer(audioChannel, api.createEncoder(), () -> { + try { + return readSoundFile(inputStream); + } catch (Exception e) { + pluginLogger.severe("An error did occur while trying to play a goat horn!"); + pluginLogger.info("Error Occurred At: " + block.getLocation()); + for (ServerPlayer serverPlayer : playersInRange) { + Player bukkitPlayer = (Player) serverPlayer.getPlayer(); + TextComponent textComponent = Component.text("An error has occurred while trying to play this goat horn.").color(NamedTextColor.RED); + bukkitPlayer.sendMessage(textComponent); + } + if(debugModeResult) { + pluginLogger.log(Level.SEVERE, "Exception output: ", e); + } + return null; + } + }); + audioPlayer.startPlaying(); + return audioPlayer; + } + + @Nullable + private de.maxhenkel.voicechat.api.audiochannel.AudioPlayer playChannelHead(VoicechatServerApi api, AudioChannel audioChannel, Block block, AudioInputStream inputStream, Collection playersInRange) throws UnsupportedAudioFileException, IOException { + AudioPlayer audioPlayer = api.createAudioPlayer(audioChannel, api.createEncoder(), () -> { + try { + return readSoundFile(inputStream); + } catch (Exception e) { + pluginLogger.severe("An error did occur while trying to play a player head!"); + pluginLogger.info("Error Occurred At: " + block.getLocation()); + for (ServerPlayer serverPlayer : playersInRange) { + Player bukkitPlayer = (Player) serverPlayer.getPlayer(); + TextComponent textComponent = Component.text("An error has occurred while trying to play this player head.").color(NamedTextColor.RED); + bukkitPlayer.sendMessage(textComponent); + } + if(debugModeResult) { + pluginLogger.log(Level.SEVERE, "Exception output: ", e); + } return null; } }); @@ -222,10 +415,11 @@ public class PlayerManager { } } - //public static float getLengthSeconds(Path file) throws UnsupportedAudioFileException, IOException { - // short[] audio = readSoundFile(file); - // return (float) audio.length / FORMAT.getSampleRate(); - //} + // DISABLED FOR NOW, MAY BE REUSED LATER IF WE DECIDE TO IMPLEMENT IT + /*public static float getLengthSeconds(Path file) throws UnsupportedAudioFileException, IOException { + short[] audio = readSoundFile(file); + return (float) audio.length / FORMAT.getSampleRate(); + }*/ public boolean isAudioPlayerPlaying(Location blockLocation) { UUID id = UUID.nameUUIDFromBytes(blockLocation.toString().getBytes()); @@ -241,6 +435,10 @@ public class PlayerManager { } } + public void stopDisc(Block block) { + this.stopLocationalAudio(block.getLocation()); + } + private static PlayerManager instance; public static PlayerManager instance() { @@ -259,4 +457,4 @@ public class PlayerManager { Path soundFilePath) { } -} +} \ No newline at end of file diff --git a/src/main/java/me/Navoei/customdiscsplugin/ServerVersionChecker.java b/src/main/java/me/Navoei/customdiscsplugin/ServerVersionChecker.java new file mode 100644 index 0000000..dc8fd82 --- /dev/null +++ b/src/main/java/me/Navoei/customdiscsplugin/ServerVersionChecker.java @@ -0,0 +1,101 @@ +package me.Navoei.customdiscsplugin; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ServerVersionChecker { + private static final String REQUIRED_VERSION = "1.21.7-9"; // Set the PaperMC required version + private final Logger pluginLogger; + private final boolean debugModeResult = CustomDiscs.isDebugMode(); + + public ServerVersionChecker(JavaPlugin plugin) { + this.pluginLogger = plugin.getLogger(); + } + + public void checkVersion() { + // Get the full server version message output + String versionMessage = Bukkit.getVersionMessage(); + + if (versionMessage == null) { + pluginLogger.severe("Unable to detect the running server version. Is this a supported PaperMC release?"); + return; + } + + // Extract server type and version + Matcher serverInfoExtracted = Pattern.compile("This server is running (\\S+) version (\\S+)").matcher(versionMessage); + + if (serverInfoExtracted.find()) { + String serverType = serverInfoExtracted.group(1); // Extract the server type (Should be "Paper", but can be forks like "Purpur", "Spigot", ...) + String buildVersion = serverInfoExtracted.group(2); // Extract the full version info (For example : 1.21.7-9-main@5661fbb) + + if(debugModeResult) { + pluginLogger.info("DEBUG - Detected Server Type: " + serverType); + pluginLogger.info("DEBUG - Server Full Version: " + versionMessage); + } + + // As we only officially support Paper, we look up for it specifically + if ("paper".equalsIgnoreCase(serverType)) { + String cleanVersion = cleanBuildVersion(buildVersion); + if(debugModeResult) { + pluginLogger.info("DEBUG - Extracted Version: " + cleanVersion); + } + + // We then perform a version comparison + if (compareVersions(cleanVersion) < 0) { + pluginLogger.severe("This Paper server version is unsupported. Please update to at least Paper " + REQUIRED_VERSION); + } else { + pluginLogger.info("Paper server version is supported."); + } + } else { + // For Paper forks servers (mostly), log a severe message about non-support + pluginLogger.severe(serverType + " server detected. No support will be made in case of issues!"); + } + } else { + pluginLogger.severe("Unable to read the server version. Is this a supported PaperMC release?"); + } + } + + /** + * Cleans up the version string to remove the non usefull part after the build number (like '-main@5661fbb'). + * The result is only the main version part (like '1.21.7-9'). + */ + private static String cleanBuildVersion(String version) { + String[] versionParts = version.split("-"); + return versionParts.length >= 2 ? versionParts[0] + "-" + versionParts[1] : version; + } + + private static int compareVersions(String runningVersion) { + // We first start by separating the main version number from the build number + String[] currentVersion = runningVersion.split("-"); + String[] requiredVersion = REQUIRED_VERSION.split("-"); + + // Then we compare the base version (sub-function to handle it) + // If we are in the same main version, we pass to the next check, else we exit (-1 = older release ; 1 = newer release) + int result = compareVersionParts(currentVersion[0], requiredVersion[0]); + if (result != 0) return result; + + // And finally, we compare the build number (only if we are at the same main base version, to ensure we get the minimal build) + return Integer.compare(Integer.parseInt(currentVersion[1]), Integer.parseInt(requiredVersion[1])); + } + + private static int compareVersionParts(String currentVersionPart, String requiredVersionPart) { + // We split each numbers into individual components (major (1), minor (21), and patch versions (7), so we can compare it one by one) + String[] currentVersionArray = currentVersionPart.split("\\."); + String[] requiredVersionArray = requiredVersionPart.split("\\."); + + for (int i = 0; i < 3; i++) { + int currentVersion = Integer.parseInt(currentVersionArray[i]); + int requiredVersion = Integer.parseInt(requiredVersionArray[i]); + + if (currentVersion < requiredVersion) return -1; + if (currentVersion > requiredVersion) return 1; + } + + return 0; + } + +} \ No newline at end of file diff --git a/src/main/java/me/Navoei/customdiscsplugin/TypeChecker.java b/src/main/java/me/Navoei/customdiscsplugin/TypeChecker.java new file mode 100644 index 0000000..eb1b650 --- /dev/null +++ b/src/main/java/me/Navoei/customdiscsplugin/TypeChecker.java @@ -0,0 +1,73 @@ +package me.Navoei.customdiscsplugin; + +import org.bukkit.NamespacedKey; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.persistence.PersistentDataType; + +public class TypeChecker { + static CustomDiscs customDiscs = CustomDiscs.getInstance(); + + // Commented methods are kept for possible future checks usage. + + // MUSIC DISCS + + public static boolean isMusicDisc(ItemStack item) { + return item.getType().toString().contains("MUSIC_DISC"); + } + + /*public static boolean isMusicDiscPlayer(Player p) { + return p.getInventory().getItemInMainHand().getType().toString().contains("MUSIC_DISC"); + }*/ + + public static boolean isCustomMusicDisc(ItemStack itemStack) { + if (itemStack == null) return false; + if (itemStack.getItemMeta() == null) return false; + return itemStack.getType().toString().contains("MUSIC_DISC") && itemStack.getItemMeta().getPersistentDataContainer().has(new NamespacedKey(customDiscs, "customdisc"), PersistentDataType.STRING); + } + + /*public static boolean isCustomMusicDiscPlayer(Player p) { + return p.getInventory().getItemInMainHand().getType().toString().contains("MUSIC_DISC") && p.getInventory().getItemInMainHand().getItemMeta().getPersistentDataContainer().has(new NamespacedKey(customDiscs, "customdisc"), PersistentDataType.STRING); + }*/ + + // GOAT HORNS + + /*public static boolean isGoatHorn(ItemStack item) { + return item.getType().toString().contains("GOAT_HORN"); + }*/ + + public static boolean isGoatHornPlayer(Player p) { + return p.getInventory().getItemInMainHand().getType().toString().contains("GOAT_HORN"); + } + + public static boolean isCustomGoatHorn(PlayerInteractEvent e) { + if (e.getItem()==null) return false; + return e.getItem().getType().toString().contains("GOAT_HORN") && e.getItem().getItemMeta().getPersistentDataContainer().has(new NamespacedKey(customDiscs, "customhorn"), PersistentDataType.STRING); + } + + public static boolean isCustomGoatHornPlayer(Player p) { + return p.getInventory().getItemInMainHand().getType().toString().contains("GOAT_HORN") && p.getInventory().getItemInMainHand().getItemMeta().getPersistentDataContainer().has(new NamespacedKey(customDiscs, "customhorn"), PersistentDataType.STRING); + } + + // PLAYER HEADS + + /*public static boolean isHead(ItemStack item) { + return item.getType().toString().contains("PLAYER_HEAD"); + }*/ + + public static boolean isHeadPlayer(Player p) { + return p.getInventory().getItemInMainHand().getType().toString().contains("PLAYER_HEAD"); + } + + /*public static boolean isCustomHead(ItemStack itemStack) { + if (itemStack == null) return false; + if (itemStack.getItemMeta() == null) return false; + return itemStack.getType().toString().contains("PLAYER_HEAD") && itemStack.getItemMeta().getPersistentDataContainer().has(new NamespacedKey(customDiscs, "customhead"), PersistentDataType.STRING); + }*/ + + public static boolean isCustomHeadPlayer(Player p) { + return p.getInventory().getItemInMainHand().getType().toString().contains("PLAYER_HEAD") && p.getInventory().getItemInMainHand().getItemMeta().getPersistentDataContainer().has(new NamespacedKey(customDiscs, "customhead"), PersistentDataType.STRING); + } + +} \ No newline at end of file diff --git a/src/main/java/me/Navoei/customdiscsplugin/VoicePlugin.java b/src/main/java/me/Navoei/customdiscsplugin/VoicePlugin.java index 1d79927..a99599a 100644 --- a/src/main/java/me/Navoei/customdiscsplugin/VoicePlugin.java +++ b/src/main/java/me/Navoei/customdiscsplugin/VoicePlugin.java @@ -12,6 +12,8 @@ import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.net.URL; import java.util.Enumeration; +import java.util.logging.Level; +import java.util.logging.Logger; public class VoicePlugin implements VoicechatPlugin { @@ -31,6 +33,9 @@ public class VoicePlugin implements VoicechatPlugin { @Nullable public static VolumeCategory playerHeads; + private final Logger pluginLogger = CustomDiscs.getInstance().getLogger(); + private final boolean debugModeResult = CustomDiscs.isDebugMode(); + /** * @return the unique ID for this voice chat plugin */ @@ -70,6 +75,22 @@ public class VoicePlugin implements VoicechatPlugin { .build(); voicechatServerApi.registerVolumeCategory(musicDiscs); + goatHorns = voicechatServerApi.volumeCategoryBuilder() + .setId(GOAT_HORN_CATEGORY) + .setName("Goat Horns") + .setDescription("The volume of goat horns") + .setIcon(getGoatHornsIcon()) + .build(); + voicechatServerApi.registerVolumeCategory(goatHorns); + + playerHeads = voicechatServerApi.volumeCategoryBuilder() + .setId(PLAYER_HEAD_CATEGORY) + .setName("Player Heads") + .setDescription("The volume of player heads (on noteblock)") + .setIcon(getPlayerHeadIcon()) + .build(); + voicechatServerApi.registerVolumeCategory(playerHeads); + } private int[][] getMusicDiscIcon() { @@ -94,7 +115,70 @@ public class VoicePlugin implements VoicechatPlugin { } } catch (Exception e) { - e.printStackTrace(); + pluginLogger.severe("An error occurred while trying to set the Music Disc icon category."); + if(debugModeResult) { + pluginLogger.log(Level.SEVERE, "Exception output: ", e); + } + } + return null; + } + + private int[][] getGoatHornsIcon() { + try { + Enumeration resources = CustomDiscs.getInstance().getClass().getClassLoader().getResources("goat_horn_category.png"); + + while (resources.hasMoreElements()) { + BufferedImage bufferedImage = ImageIO.read(resources.nextElement().openStream()); + if (bufferedImage.getWidth() != 16) { + continue; + } + if (bufferedImage.getHeight() != 16) { + continue; + } + int[][] image = new int[16][16]; + for (int x = 0; x < bufferedImage.getWidth(); x++) { + for (int y = 0; y < bufferedImage.getHeight(); y++) { + image[x][y] = bufferedImage.getRGB(x, y); + } + } + return image; + } + + } catch (Exception e) { + pluginLogger.severe("An error occurred while trying to set the Goat Horn icon category."); + if(debugModeResult) { + pluginLogger.log(Level.SEVERE, "Exception output: ", e); + } + } + return null; + } + + private int[][] getPlayerHeadIcon() { + try { + Enumeration resources = CustomDiscs.getInstance().getClass().getClassLoader().getResources("player_head_category.png"); + + while (resources.hasMoreElements()) { + BufferedImage bufferedImage = ImageIO.read(resources.nextElement().openStream()); + if (bufferedImage.getWidth() != 16) { + continue; + } + if (bufferedImage.getHeight() != 16) { + continue; + } + int[][] image = new int[16][16]; + for (int x = 0; x < bufferedImage.getWidth(); x++) { + for (int y = 0; y < bufferedImage.getHeight(); y++) { + image[x][y] = bufferedImage.getRGB(x, y); + } + } + return image; + } + + } catch (Exception e) { + pluginLogger.severe("An error occurred while trying to set the Player Head icon category."); + if(debugModeResult) { + pluginLogger.log(Level.SEVERE, "Exception output: ", e); + } } return null; } diff --git a/src/main/java/me/Navoei/customdiscsplugin/command/CustomDiscCommand.java b/src/main/java/me/Navoei/customdiscsplugin/command/CustomDiscCommand.java index 82f3e09..4f0aeb4 100644 --- a/src/main/java/me/Navoei/customdiscsplugin/command/CustomDiscCommand.java +++ b/src/main/java/me/Navoei/customdiscsplugin/command/CustomDiscCommand.java @@ -1,13 +1,18 @@ package me.Navoei.customdiscsplugin.command; -import dev.jorel.commandapi.CommandAPICommand; -import dev.jorel.commandapi.executors.CommandArguments; import me.Navoei.customdiscsplugin.CustomDiscs; import me.Navoei.customdiscsplugin.command.SubCommands.CreateSubCommand; import me.Navoei.customdiscsplugin.command.SubCommands.DownloadSubCommand; +import me.Navoei.customdiscsplugin.command.SubCommands.SetHornCooldownSubCommand; import me.Navoei.customdiscsplugin.command.SubCommands.SetRangeSubCommand; + +import dev.jorel.commandapi.CommandAPICommand; +import dev.jorel.commandapi.CommandPermission; +import dev.jorel.commandapi.executors.CommandArguments; + import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.format.NamedTextColor; + import org.bukkit.command.ConsoleCommandSender; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.Player; @@ -21,10 +26,12 @@ public class CustomDiscCommand extends CommandAPICommand { this.withAliases("cd"); this.withFullDescription("The custom discs command."); - + this.withPermission(CommandPermission.NONE); + this.withSubcommand(new CreateSubCommand(plugin)); this.withSubcommand(new DownloadSubCommand(plugin)); this.withSubcommand(new SetRangeSubCommand(plugin)); + this.withSubcommand(new SetHornCooldownSubCommand(plugin)); this.executesPlayer(this::onCommandPlayer); this.executesConsole(this::onCommandConsole); @@ -43,4 +50,5 @@ public class CustomDiscCommand extends CommandAPICommand { executor.sendMessage(NamedTextColor.RED + "Only players can use this command : '"+arguments+"'!"); return 1; } -} + +} \ No newline at end of file diff --git a/src/main/java/me/Navoei/customdiscsplugin/command/SubCommands/CreateSubCommand.java b/src/main/java/me/Navoei/customdiscsplugin/command/SubCommands/CreateSubCommand.java index 254a79d..f1a15de 100644 --- a/src/main/java/me/Navoei/customdiscsplugin/command/SubCommands/CreateSubCommand.java +++ b/src/main/java/me/Navoei/customdiscsplugin/command/SubCommands/CreateSubCommand.java @@ -1,19 +1,29 @@ package me.Navoei.customdiscsplugin.command.SubCommands; +import me.Navoei.customdiscsplugin.CustomDiscs; +import me.Navoei.customdiscsplugin.TypeChecker; +import me.Navoei.customdiscsplugin.language.Lang; + import dev.jorel.commandapi.CommandAPICommand; import dev.jorel.commandapi.arguments.ArgumentSuggestions; import dev.jorel.commandapi.arguments.StringArgument; import dev.jorel.commandapi.arguments.TextArgument; import dev.jorel.commandapi.executors.CommandArguments; + import io.papermc.paper.datacomponent.DataComponentTypes; import io.papermc.paper.datacomponent.item.TooltipDisplay; -import me.Navoei.customdiscsplugin.CustomDiscs; -import me.Navoei.customdiscsplugin.language.Lang; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.TypedKey; +import io.papermc.paper.registry.keys.InstrumentKeys; +import io.papermc.paper.registry.keys.SoundEventKeys; + import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + import org.bukkit.*; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Player; @@ -38,7 +48,8 @@ public class CreateSubCommand extends CommandAPICommand { this.withFullDescription(NamedTextColor.GRAY + "Creates a custom music disc."); this.withUsage("/customdisc create \"Custom Lore\""); - + this.withPermission("customdiscs.create"); + this.withArguments(new StringArgument("filename").replaceSuggestions(ArgumentSuggestions.stringCollection((sender) -> { File musicDataFolder = new File(this.plugin.getDataFolder(), "musicdata"); if (!musicDataFolder.isDirectory()) { @@ -62,18 +73,17 @@ public class CreateSubCommand extends CommandAPICommand { private int onCommandPlayer(Player player, CommandArguments arguments) { ItemStack item = player.getInventory().getItemInMainHand(); + boolean resultIsMusicDisc = TypeChecker.isMusicDisc(item); + boolean resultIsHorn = TypeChecker.isGoatHornPlayer(player); + boolean resultIsHead = TypeChecker.isHeadPlayer(player); - if (!isMusicDisc(item)) { - player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.NOT_HOLDING_DISC.toString())); + if (!resultIsMusicDisc && !resultIsHorn && !resultIsHead) { + player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.NOT_HOLDING_CORRECT_ITEM.toString())); return 1; } - - if (!player.hasPermission("customdiscs.create")) { - player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.NO_PERMISSION.toString())); - return 1; - } - - // Find file, if file not there then say "file not there" + + + String filename = Objects.requireNonNull(arguments.getByClass("filename", String.class)); if (filename.contains("../")) { player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.INVALID_FILENAME.toString())); @@ -94,17 +104,60 @@ public class CreateSubCommand extends CommandAPICommand { String song_name = Objects.requireNonNull(arguments.getByClass("song_name", String.class)); - ItemStack disc = new ItemStack(player.getInventory().getItemInMainHand()); - disc.setData(DataComponentTypes.TOOLTIP_DISPLAY, TooltipDisplay.tooltipDisplay().addHiddenComponents(DataComponentTypes.JUKEBOX_PLAYABLE).build()); - ItemMeta meta = disc.getItemMeta(); - @Nullable List itemLore = new ArrayList<>(); - final TextComponent customLoreSong = Component.text().decoration(TextDecoration.ITALIC, false).content(song_name).color(NamedTextColor.GRAY).build(); - itemLore.add(customLoreSong); - meta.lore(itemLore); + if (resultIsMusicDisc) { + if (!CustomDiscs.isMusicDiscEnable()) { player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.CUSTOM_MUSIC_DISABLED.toString())); return 1; } + ItemStack disc = new ItemStack(player.getInventory().getItemInMainHand()); + disc.setData(DataComponentTypes.TOOLTIP_DISPLAY, TooltipDisplay.tooltipDisplay().addHiddenComponents(DataComponentTypes.JUKEBOX_PLAYABLE).build()); + ItemMeta meta = disc.getItemMeta(); + @Nullable List itemLore = new ArrayList<>(); + final TextComponent customLoreSong = Component.text().decoration(TextDecoration.ITALIC, false).content(song_name).color(NamedTextColor.GRAY).build(); + itemLore.add(customLoreSong); + meta.lore(itemLore); + + PersistentDataContainer data = meta.getPersistentDataContainer(); + data.set(new NamespacedKey(this.plugin, "customdisc"), PersistentDataType.STRING, filename); + player.getInventory().getItemInMainHand().setItemMeta(meta); + } else if (resultIsHorn) { + if (!CustomDiscs.isCustomHornEnable()) { player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.CUSTOM_HORN_DISABLED.toString())); return 1; } + final TextComponent customLoreSong = Component.text().decoration(TextDecoration.ITALIC, false).content(song_name).color(NamedTextColor.GRAY).build(); + MusicInstrument customInstrument = MusicInstrument.create(builder -> { + builder.copyFrom(InstrumentKeys.ADMIRE_GOAT_HORN) + .description(customLoreSong) + .range(256.0f) + .duration(1.0f) + .soundEvent(TypedKey.create( + RegistryKey.SOUND_EVENT, + SoundEventKeys.INTENTIONALLY_EMPTY + )); + }); + item.setData(DataComponentTypes.INSTRUMENT, customInstrument); + + ItemStack disc = new ItemStack(player.getInventory().getItemInMainHand()); + disc.setData(DataComponentTypes.TOOLTIP_DISPLAY, TooltipDisplay.tooltipDisplay().addHiddenComponents(DataComponentTypes.JUKEBOX_PLAYABLE).build()); + ItemMeta meta = disc.getItemMeta(); + PersistentDataContainer data = meta.getPersistentDataContainer(); + data.set(new NamespacedKey(this.plugin, "customhorn"), PersistentDataType.STRING, filename); + player.getInventory().getItemInMainHand().setItemMeta(meta); + } else if (resultIsHead) { + if (!CustomDiscs.isCustomHeadEnable()) { player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.CUSTOM_HEAD_DISABLED.toString())); return 1; } + final Component customLoreHead = Component.text().decoration(TextDecoration.ITALIC, false).content(song_name).color(NamedTextColor.GRAY).build(); + String serialized = GsonComponentSerializer.gson().serialize(customLoreHead); + + ItemStack disc = new ItemStack(player.getInventory().getItemInMainHand()); + ItemMeta meta = disc.getItemMeta(); + @Nullable List itemLore = new ArrayList<>(); + final TextComponent customLoreSong = Component.text().decoration(TextDecoration.ITALIC, false).content(song_name).color(NamedTextColor.GRAY).build(); + itemLore.add(customLoreSong); + meta.lore(itemLore); + + PersistentDataContainer data = meta.getPersistentDataContainer(); + data.set(new NamespacedKey(this.plugin, "customhead"), PersistentDataType.STRING, filename); + data.set(new NamespacedKey(this.plugin, "headlore"), PersistentDataType.STRING, serialized); + player.getInventory().getItemInMainHand().setItemMeta(meta); + } else { + return 1; + } - PersistentDataContainer data = meta.getPersistentDataContainer(); - data.set(new NamespacedKey(this.plugin, "customdisc"), PersistentDataType.STRING, filename); - player.getInventory().getItemInMainHand().setItemMeta(meta); player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.CREATE_FILENAME.toString().replace("%filename%", filename))); player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.CREATE_CUSTOM_NAME.toString().replace("%custom_name%", song_name))); return 1; @@ -124,8 +177,4 @@ public class CreateSubCommand extends CommandAPICommand { } } - public static boolean isMusicDisc(ItemStack item) { - return item.getType().toString().contains("MUSIC_DISC"); - } - -} +} \ No newline at end of file diff --git a/src/main/java/me/Navoei/customdiscsplugin/command/SubCommands/DownloadSubCommand.java b/src/main/java/me/Navoei/customdiscsplugin/command/SubCommands/DownloadSubCommand.java index 60a4f69..9f13ef5 100644 --- a/src/main/java/me/Navoei/customdiscsplugin/command/SubCommands/DownloadSubCommand.java +++ b/src/main/java/me/Navoei/customdiscsplugin/command/SubCommands/DownloadSubCommand.java @@ -1,16 +1,20 @@ package me.Navoei.customdiscsplugin.command.SubCommands; +import me.Navoei.customdiscsplugin.CustomDiscs; +import me.Navoei.customdiscsplugin.language.Lang; + import dev.jorel.commandapi.CommandAPICommand; import dev.jorel.commandapi.arguments.StringArgument; import dev.jorel.commandapi.arguments.TextArgument; import dev.jorel.commandapi.executors.CommandArguments; -import me.Navoei.customdiscsplugin.CustomDiscs; -import me.Navoei.customdiscsplugin.language.Lang; + import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.format.NamedTextColor; + import org.bukkit.Bukkit; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Player; + import org.codehaus.plexus.util.FileUtils; import java.io.File; @@ -22,9 +26,12 @@ import java.net.URLConnection; import java.net.MalformedURLException; import java.nio.file.Path; import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; public class DownloadSubCommand extends CommandAPICommand { private final CustomDiscs plugin; + private final boolean debugModeResult = CustomDiscs.isDebugMode(); public DownloadSubCommand(CustomDiscs plugin) { super("download"); @@ -32,34 +39,35 @@ public class DownloadSubCommand extends CommandAPICommand { this.withFullDescription(NamedTextColor.GRAY + "Downloads a file from a given URL."); this.withUsage("/customdisc download "); - + this.withPermission("customdiscs.download"); + this.withArguments(new TextArgument("url")); this.withArguments(new StringArgument("filename")); this.executesPlayer(this::onCommandPlayer); this.executesConsole(this::onCommandConsole); } - + private int onCommandPlayer(Player player, CommandArguments arguments) { - if (!player.hasPermission("customdiscs.download")) { - player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.NO_PERMISSION.toString())); - return 1; - } - + final Logger pluginLogger = plugin.getLogger(); + Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> { try { try { URI uri = new URI(Objects.requireNonNull(arguments.getByClass("url", String.class))); URL fileURL = uri.toURL(); String filename = Objects.requireNonNull(arguments.getByClass("filename", String.class)); + + if(debugModeResult) { + pluginLogger.info("DEBUG - Download File URL: " + fileURL); + pluginLogger.info("DEBUG - File name: " + filename); + } + if (filename.contains("../")) { player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.INVALID_FILENAME.toString())); return; } - //DEBUG - //System.out.println(filename); - String fileExtension = getFileExtension(filename); if (!fileExtension.equals("wav") && !fileExtension.equals("mp3") && !fileExtension.equals("flac")) { player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.INVALID_FORMAT.toString())); @@ -85,11 +93,17 @@ public class DownloadSubCommand extends CommandAPICommand { player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.CREATE_DISC.toString().replace("%filename%", filename))); } catch (URISyntaxException | MalformedURLException e) { player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.DOWNLOAD_ERROR.toString())); - e.printStackTrace(); + pluginLogger.warning("A download error occurred."); + if(debugModeResult) { + pluginLogger.log(Level.SEVERE, "Exception output: ", e); + } } } catch (IOException e) { player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.DOWNLOAD_ERROR.toString())); - e.printStackTrace(); + pluginLogger.warning("A download error occurred."); + if(debugModeResult) { + pluginLogger.log(Level.SEVERE, "Exception output: ", e); + } } }); @@ -109,4 +123,5 @@ public class DownloadSubCommand extends CommandAPICommand { return ""; } } -} + +} \ No newline at end of file diff --git a/src/main/java/me/Navoei/customdiscsplugin/command/SubCommands/SetHornCooldownSubCommand.java b/src/main/java/me/Navoei/customdiscsplugin/command/SubCommands/SetHornCooldownSubCommand.java new file mode 100644 index 0000000..535189c --- /dev/null +++ b/src/main/java/me/Navoei/customdiscsplugin/command/SubCommands/SetHornCooldownSubCommand.java @@ -0,0 +1,75 @@ +package me.Navoei.customdiscsplugin.command.SubCommands; + +import me.Navoei.customdiscsplugin.CustomDiscs; +import me.Navoei.customdiscsplugin.TypeChecker; +import me.Navoei.customdiscsplugin.language.Lang; + +import dev.jorel.commandapi.CommandAPICommand; +import dev.jorel.commandapi.arguments.IntegerArgument; +import dev.jorel.commandapi.executors.CommandArguments; + +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +import org.bukkit.NamespacedKey; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; + +import java.util.Objects; + +public class SetHornCooldownSubCommand extends CommandAPICommand { + private final CustomDiscs plugin; + + public SetHornCooldownSubCommand(CustomDiscs plugin) { + super("goatcooldown"); + this.plugin = plugin; + + this.withFullDescription(NamedTextColor.GRAY + "Set the cooldown for a modified goat horn (range from 1 to "+ this.plugin.hornMaxCooldown +" in ticks)."); + this.withUsage("/cd goatcooldown "); + this.withPermission("customdiscs.horncooldown"); + + this.withArguments(new IntegerArgument("goatcooldown")); + + this.executesPlayer(this::onCommandPlayer); + this.executesConsole(this::onCommandConsole); + } + + private int onCommandPlayer(Player player, CommandArguments arguments) { + if (!TypeChecker.isCustomGoatHornPlayer(player)) { + player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.NOT_HOLDING_MODIFIED_GOATHORN.toString())); + return 1; + } + + if (TypeChecker.isCustomGoatHornPlayer(player)) { + if (!CustomDiscs.isMusicDiscEnable()) { player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.CUSTOM_HORN_DISABLED.toString())); return 1; } + } + + int goatcooldown = Objects.requireNonNull(arguments.getByClass("goatcooldown", Integer.class)); + + if (goatcooldown <= 0 || goatcooldown > this.plugin.hornMaxCooldown) { + player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.INVALID_COOLDOWN.toString().replace("%cooldown_value%", Integer.toString(this.plugin.hornMaxCooldown)))); + return 1; + } + + ItemStack disc = new ItemStack(player.getInventory().getItemInMainHand()); + ItemMeta meta = disc.getItemMeta(); + PersistentDataContainer data = meta.getPersistentDataContainer(); + + data.set(new NamespacedKey(this.plugin, "customhorncoolodwn"), PersistentDataType.INTEGER, Math.min(goatcooldown, CustomDiscs.getInstance().hornMaxCooldown)); + player.getInventory().getItemInMainHand().setItemMeta(meta); + + player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.CREATE_CUSTOM_GOAT_COOLDOWN.toString().replace("%custom_goat_cooldown%", Integer.toString(goatcooldown)))); + + return 1; + } + + private int onCommandConsole(ConsoleCommandSender executor, CommandArguments arguments) { + executor.sendMessage(NamedTextColor.RED + "Only players can use this command : '"+arguments+"'!"); + return 1; + } + +} \ No newline at end of file diff --git a/src/main/java/me/Navoei/customdiscsplugin/command/SubCommands/SetRangeSubCommand.java b/src/main/java/me/Navoei/customdiscsplugin/command/SubCommands/SetRangeSubCommand.java index 32bb834..8e72900 100644 --- a/src/main/java/me/Navoei/customdiscsplugin/command/SubCommands/SetRangeSubCommand.java +++ b/src/main/java/me/Navoei/customdiscsplugin/command/SubCommands/SetRangeSubCommand.java @@ -1,26 +1,25 @@ package me.Navoei.customdiscsplugin.command.SubCommands; +import me.Navoei.customdiscsplugin.CustomDiscs; +import me.Navoei.customdiscsplugin.TypeChecker; +import me.Navoei.customdiscsplugin.language.Lang; + import dev.jorel.commandapi.CommandAPICommand; import dev.jorel.commandapi.arguments.FloatArgument; import dev.jorel.commandapi.executors.CommandArguments; -import me.Navoei.customdiscsplugin.CustomDiscs; -import me.Navoei.customdiscsplugin.language.Lang; + import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.format.NamedTextColor; -//import org.bukkit.Bukkit; + import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Player; -//import org.bukkit.Material; import org.bukkit.NamespacedKey; -//import org.bukkit.inventory.ItemFlag; -import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; import java.util.Objects; -import org.bukkit.Bukkit; public class SetRangeSubCommand extends CommandAPICommand { private final CustomDiscs plugin; @@ -31,7 +30,8 @@ public class SetRangeSubCommand extends CommandAPICommand { this.withFullDescription(NamedTextColor.GRAY + "Set the range of a disc to the defined value (range from 1 to "+ this.plugin.musicDiscMaxDistance +")."); this.withUsage("/cd range "); - + this.withPermission("customdiscs.range"); + this.withArguments(new FloatArgument("range")); this.executesPlayer(this::onCommandPlayer); @@ -41,28 +41,40 @@ public class SetRangeSubCommand extends CommandAPICommand { private int onCommandPlayer(Player player, CommandArguments arguments) { ItemStack item = player.getInventory().getItemInMainHand(); + boolean resultIsCustomDisc = TypeChecker.isCustomMusicDisc(item); + boolean resultIsCustomHorn = TypeChecker.isCustomGoatHornPlayer(player); + boolean resultIsCustomHead = TypeChecker.isCustomHeadPlayer(player); - if (!isCustomDisc(item)) { - player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.NOT_HOLDING_DISC.toString())); - return 1; - } - - if (!player.hasPermission("customdiscs.range")) { - player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.NO_PERMISSION.toString())); + if (!resultIsCustomDisc && !resultIsCustomHorn && !resultIsCustomHead) { + player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.NOT_HOLDING_CORRECT_ITEM.toString())); return 1; } Float range = Objects.requireNonNull(arguments.getByClass("range", Float.class)); - if ( range < 1 || range > this.plugin.musicDiscMaxDistance) { - player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.INVALID_RANGE.toString().replace("%range_value%", Float.toString(this.plugin.musicDiscMaxDistance)))); + Float configMusicDiscMaxDistance; + if (resultIsCustomHorn) { + if (!CustomDiscs.isCustomHornEnable()) { player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.CUSTOM_HORN_DISABLED.toString())); return 1; } + configMusicDiscMaxDistance = this.plugin.customHornMaxDistance; + } else if (resultIsCustomHead) { + if (!CustomDiscs.isCustomHeadEnable()) { player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.CUSTOM_HEAD_DISABLED.toString())); return 1; } + configMusicDiscMaxDistance = this.plugin.customHeadMaxDistance; + } else { + if (!CustomDiscs.isMusicDiscEnable()) { player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.CUSTOM_MUSIC_DISABLED.toString())); return 1; } + configMusicDiscMaxDistance = this.plugin.musicDiscMaxDistance; + } + + + if ( range < 1 || range > configMusicDiscMaxDistance) { + player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.INVALID_RANGE.toString().replace("%range_value%", Float.toString(configMusicDiscMaxDistance)))); return 1; } ItemMeta meta = item.getItemMeta(); PersistentDataContainer data = meta.getPersistentDataContainer(); - data.set(new NamespacedKey(this.plugin, "range"), PersistentDataType.FLOAT, range); - player.getInventory().getItemInMainHand().setItemMeta(meta); + data.set(new NamespacedKey(this.plugin, "range"), PersistentDataType.FLOAT, range); + player.getInventory().getItemInMainHand().setItemMeta(meta); + player.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.PREFIX + Lang.CREATE_CUSTOM_RANGE.toString().replace("%custom_range%", Float.toString(range)))); return 1; @@ -73,9 +85,4 @@ public class SetRangeSubCommand extends CommandAPICommand { return 1; } - public boolean isCustomDisc(ItemStack item) { - if (item==null) return false; - return item.getType().toString().contains("MUSIC_DISC") && item.getItemMeta().getPersistentDataContainer().has(new NamespacedKey(plugin, "customdisc")); - } - -} +} \ No newline at end of file diff --git a/src/main/java/me/Navoei/customdiscsplugin/event/HeadPlay.java b/src/main/java/me/Navoei/customdiscsplugin/event/HeadPlay.java new file mode 100644 index 0000000..5f3cc02 --- /dev/null +++ b/src/main/java/me/Navoei/customdiscsplugin/event/HeadPlay.java @@ -0,0 +1,127 @@ +package me.Navoei.customdiscsplugin.event; + +import me.Navoei.customdiscsplugin.CustomDiscs; +import me.Navoei.customdiscsplugin.PlayerManager; +import me.Navoei.customdiscsplugin.VoicePlugin; +import me.Navoei.customdiscsplugin.language.Lang; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; + +import org.bukkit.Bukkit; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.Skull; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.block.NotePlayEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.SkullMeta; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +public class HeadPlay implements Listener{ + + CustomDiscs customDiscs = CustomDiscs.getInstance(); + PlayerManager playerManager = PlayerManager.instance(); + + // Triggered on every noteblock interaction. + // Most of the function will only be executed if a custom player head is found on top of the noteblock. + @EventHandler(priority = EventPriority.HIGHEST) + public void onNotePlay(NotePlayEvent event) throws IOException { + Block noteBlock = event.getBlock(); + + if (noteBlock.getType() != Material.NOTE_BLOCK) return; + + if (PlayerManager.instance().isAudioPlayerPlaying(noteBlock.getLocation())) return; + + Block headBlock = noteBlock.getRelative(BlockFace.UP); + if (headBlock.getType() != Material.PLAYER_HEAD) return; + + Skull skull = (Skull) headBlock.getState(); + PersistentDataContainer persistentDataContainer = skull.getPersistentDataContainer(); + + if (persistentDataContainer.has(new NamespacedKey(customDiscs, "customhead"), PersistentDataType.STRING)) { + + String soundFileName = persistentDataContainer.get(new NamespacedKey(customDiscs, "customhead"), PersistentDataType.STRING); + + float range = CustomDiscs.getInstance().customHeadDistance; + NamespacedKey customSoundRangeKey = new NamespacedKey(customDiscs, "range"); + NamespacedKey customLore = new NamespacedKey(customDiscs, "headlore"); + + if(persistentDataContainer.has(customSoundRangeKey, PersistentDataType.FLOAT)) { + float soundRange = Optional.ofNullable(persistentDataContainer.get(customSoundRangeKey, PersistentDataType.FLOAT)).orElse(0f); + range = Math.min(soundRange, CustomDiscs.getInstance().customHeadMaxDistance); + } + + Path soundFilePath = Path.of(customDiscs.getDataFolder().getPath(), "musicdata", soundFileName); + + if (soundFilePath.toFile().exists()) { + Component songNameComponent = Optional.ofNullable(persistentDataContainer.get(customLore, PersistentDataType.STRING)).map(GsonComponentSerializer.gson()::deserialize).orElse(Component.text("Unknown Song", NamedTextColor.GRAY)); + String songName = PlainTextComponentSerializer.plainText().serialize(songNameComponent); + Component customActionBarSongPlaying = LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.NOW_PLAYING.toString().replace("%song_name%", songName)); + + assert VoicePlugin.voicechatServerApi != null; + playerManager.playAudioHead(VoicePlugin.voicechatServerApi, soundFilePath, noteBlock, customActionBarSongPlaying, range); + } else { + event.setCancelled(true); + throw new FileNotFoundException("Sound file is missing!"); + } + } + } + + // Event to delay by 1 tick after a player head had been placed + // Delay will only be triggered on custom player head created with this plugin + @EventHandler(priority = EventPriority.HIGHEST) + public void onHeadPlace(BlockPlaceEvent event) { + ItemStack item = event.getItemInHand(); + + if (item.getType() != Material.PLAYER_HEAD) return; + if (!(item.getItemMeta() instanceof SkullMeta meta)) return; + + PersistentDataContainer itemPDC = meta.getPersistentDataContainer(); + if (!itemPDC.has(new NamespacedKey(customDiscs, "customhead"), PersistentDataType.STRING)) return; + + Bukkit.getScheduler().runTaskLater(customDiscs, () -> { + Block block = event.getBlockPlaced(); + if (block.getType() != Material.PLAYER_HEAD && block.getType() != Material.PLAYER_WALL_HEAD) return; + + Skull skull = (Skull) block.getState(); + PersistentDataContainer blockPDC = skull.getPersistentDataContainer(); + + NamespacedKey headKey = new NamespacedKey(customDiscs, "customhead"); + NamespacedKey loreKey = new NamespacedKey(customDiscs, "headlore"); + NamespacedKey rangeKey = new NamespacedKey(customDiscs, "range"); + + if (itemPDC.has(headKey, PersistentDataType.STRING)) { + String customheadValue = itemPDC.get(headKey, PersistentDataType.STRING); + blockPDC.set(headKey, PersistentDataType.STRING, customheadValue); + } + + if (itemPDC.has(loreKey, PersistentDataType.STRING)) { + String headloreValue = itemPDC.get(loreKey, PersistentDataType.STRING); + blockPDC.set(loreKey, PersistentDataType.STRING, headloreValue); + } + + if (itemPDC.has(rangeKey, PersistentDataType.FLOAT)) { + Float rangeValue = itemPDC.get(rangeKey, PersistentDataType.FLOAT); + blockPDC.set(rangeKey, PersistentDataType.FLOAT, rangeValue); + } + + skull.update(true, true); + }, 1L); + } + +} \ No newline at end of file diff --git a/src/main/java/me/Navoei/customdiscsplugin/event/HornPlay.java b/src/main/java/me/Navoei/customdiscsplugin/event/HornPlay.java new file mode 100644 index 0000000..2d9e501 --- /dev/null +++ b/src/main/java/me/Navoei/customdiscsplugin/event/HornPlay.java @@ -0,0 +1,145 @@ +package me.Navoei.customdiscsplugin.event; + +import me.Navoei.customdiscsplugin.CustomDiscs; +import me.Navoei.customdiscsplugin.PlayerManager; +import me.Navoei.customdiscsplugin.TypeChecker; +import me.Navoei.customdiscsplugin.VoicePlugin; + +import io.papermc.paper.datacomponent.DataComponentTypes; + +import me.Navoei.customdiscsplugin.language.Lang; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; + +import org.bukkit.Material; +import org.bukkit.MusicInstrument; +import org.bukkit.NamespacedKey; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; + +import javax.annotation.Nonnull; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +public class HornPlay implements Listener{ + + CustomDiscs customDiscs = CustomDiscs.getInstance(); + PlayerManager playerManager = PlayerManager.instance(); + + private final List goatHornNotInteractable = Arrays.asList( + Material.ANVIL, + Material.ARMOR_STAND, + Material.BARREL, + Material.BEACON, + Material.BLAST_FURNACE, + Material.BREWING_STAND, + Material.CARTOGRAPHY_TABLE, + Material.CHIPPED_ANVIL, + Material.CHISELED_BOOKSHELF, + Material.COMMAND_BLOCK, + Material.COMPARATOR, + Material.CRAFTER, + Material.CRAFTING_TABLE, + Material.DAMAGED_ANVIL, + Material.DAYLIGHT_DETECTOR, + Material.DECORATED_POT, + Material.DISPENSER, + Material.DROPPER, + Material.ENCHANTING_TABLE, + Material.FLOWER_POT, + Material.FURNACE, + Material.GRINDSTONE, + Material.HOPPER, + Material.ITEM_FRAME, + Material.LECTERN, + Material.LEVER, + Material.LOOM, + Material.NOTE_BLOCK, + Material.REPEATER, + Material.SMITHING_TABLE, + Material.SMOKER, + Material.STONECUTTER, + Material.STRUCTURE_BLOCK + ); + + @EventHandler(priority = EventPriority.HIGHEST) + public void onPlayerInteract(PlayerInteractEvent event) throws IOException { + ItemStack item = event.getItem(); + Player player = event.getPlayer(); + + if (item == null) return; + if (item.getType() != Material.GOAT_HORN) return; + + Block block = event.getClickedBlock(); + if (player.hasCooldown(Material.GOAT_HORN)) return; + + if (TypeChecker.isCustomGoatHorn(event) && ((event.getAction() == Action.RIGHT_CLICK_BLOCK) || (event.getAction() == Action.RIGHT_CLICK_AIR))) { + + if (!player.isSneaking() && block != null) { + Material targetBlockType = block.getType(); + if (goatHornNotInteractable.contains(targetBlockType)) return; + if (targetBlockType.name().contains("_BED") || targetBlockType.name().contains("_BOAT") || targetBlockType.name().contains("_BUTTON") || targetBlockType.name().contains("CHEST") || targetBlockType.name().contains("_DOOR") /*|| targetBlockType.name().contains("_FENCE_GATE") */|| targetBlockType.name().contains("_GATE") || targetBlockType.name().contains("MINECART") || targetBlockType.name().contains("POTTED_") || targetBlockType.name().contains("_SIGN") || targetBlockType.name().contains("_TRAPDOOR")) return; + } + + String soundFileName = event.getItem().getItemMeta().getPersistentDataContainer().get(new NamespacedKey(customDiscs, "customhorn"), PersistentDataType.STRING); + + @Nonnull PersistentDataContainer persistentDataContainer = event.getItem().getItemMeta().getPersistentDataContainer(); + + float range = CustomDiscs.getInstance().customHornDistance; + NamespacedKey customSoundRangeKey = new NamespacedKey(customDiscs, "range"); + + if(persistentDataContainer.has(customSoundRangeKey, PersistentDataType.FLOAT)) { + float soundRange = Optional.ofNullable(persistentDataContainer.get(customSoundRangeKey, PersistentDataType.FLOAT)).orElse(0f); + range = Math.min(soundRange, CustomDiscs.getInstance().customHornMaxDistance); + } + + int hornCooldown; + NamespacedKey hornCooldownKey = new NamespacedKey(customDiscs, "customhorncoolodwn"); + if(persistentDataContainer.has(hornCooldownKey, PersistentDataType.INTEGER)) { + hornCooldown = Math.min(persistentDataContainer.get(hornCooldownKey, PersistentDataType.INTEGER), CustomDiscs.getInstance().hornMaxCooldown); + } else { + hornCooldown = Math.min(CustomDiscs.getInstance().hornCooldown, CustomDiscs.getInstance().hornMaxCooldown); + } + + Path soundFilePath = Path.of(customDiscs.getDataFolder().getPath(), "musicdata", soundFileName); + + if (soundFilePath.toFile().exists()) { + + String songName = "Unknown sound"; + + MusicInstrument instrument = item.getDataOrDefault(DataComponentTypes.INSTRUMENT, null); + if (instrument != null) { + Component songNameComponent = instrument.description(); // This is the one you're asking for + songName = PlainTextComponentSerializer.plainText().serialize(songNameComponent); + } else { + songName = "Unknown sound"; + } + + Component customActionBarSongPlaying = LegacyComponentSerializer.legacyAmpersand().deserialize(Lang.NOW_PLAYING.toString().replace("%song_name%", songName)); + + assert VoicePlugin.voicechatServerApi != null; + playerManager.playAudioHorn(VoicePlugin.voicechatServerApi, soundFilePath, player, customActionBarSongPlaying.asComponent(), range); + player.setCooldown(Material.GOAT_HORN, hornCooldown); + } else { + player.sendMessage(NamedTextColor.RED + "Sound file not found."); + event.setCancelled(true); + throw new FileNotFoundException("Sound file is missing!"); + } + } + } + +} \ No newline at end of file diff --git a/src/main/java/me/Navoei/customdiscsplugin/event/JukeBox.java b/src/main/java/me/Navoei/customdiscsplugin/event/JukeBox.java index 2cbf8cf..7f23194 100644 --- a/src/main/java/me/Navoei/customdiscsplugin/event/JukeBox.java +++ b/src/main/java/me/Navoei/customdiscsplugin/event/JukeBox.java @@ -1,15 +1,18 @@ package me.Navoei.customdiscsplugin.event; -import io.papermc.paper.datacomponent.DataComponentTypes; -import io.papermc.paper.datacomponent.item.TooltipDisplay; import me.Navoei.customdiscsplugin.CustomDiscs; import me.Navoei.customdiscsplugin.PlayerManager; import me.Navoei.customdiscsplugin.VoicePlugin; import me.Navoei.customdiscsplugin.language.Lang; + +import io.papermc.paper.datacomponent.DataComponentTypes; +import io.papermc.paper.datacomponent.item.TooltipDisplay; + import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; + import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.block.Block; @@ -24,7 +27,6 @@ import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.components.JukeboxPlayableComponent; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; @@ -112,7 +114,7 @@ public class JukeBox implements Listener{ } if (player.isSneaking() && !itemInvolvedInEvent.getType().equals(Material.AIR)) return; - stopDisc(block); + playerManager.stopDisc(block); } } @@ -125,7 +127,7 @@ public class JukeBox implements Listener{ Jukebox jukebox = (Jukebox) block.getState(); if (!isCustomMusicDisc(jukebox.getRecord())) return; - stopDisc(block); + playerManager.stopDisc(block); } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) @@ -135,7 +137,7 @@ public class JukeBox implements Listener{ if (explodedBlock.getType() == Material.JUKEBOX) { Jukebox jukebox = (Jukebox) explodedBlock.getState(); if (!isCustomMusicDisc(jukebox.getRecord())) return; - stopDisc(explodedBlock); + playerManager.stopDisc(explodedBlock); } } @@ -152,8 +154,4 @@ public class JukeBox implements Listener{ return itemStack.getItemMeta().getPersistentDataContainer().has(new NamespacedKey(customDiscs, "customdisc")); } - private void stopDisc(Block block) { - playerManager.stopLocationalAudio(block.getLocation()); - } - } \ No newline at end of file diff --git a/src/main/java/me/Navoei/customdiscsplugin/language/Lang.java b/src/main/java/me/Navoei/customdiscsplugin/language/Lang.java index 5c8e967..dc2d1c5 100644 --- a/src/main/java/me/Navoei/customdiscsplugin/language/Lang.java +++ b/src/main/java/me/Navoei/customdiscsplugin/language/Lang.java @@ -4,12 +4,10 @@ import org.bukkit.configuration.file.YamlConfiguration; public enum Lang { PREFIX("prefix", "&8[&6CustomDiscs&8]&r"), - NO_PERMISSION("no-permission", "&rYou do not have permission to execute this command."), INVALID_FILENAME("invalid-filename", "&rThis is an invalid filename!"), INVALID_FORMAT("invalid-format", "&rFile must be in wav, flac, or mp3 format!"), FILE_NOT_FOUND("file-not-found", "&rFile not found!"), - INVALID_ARGUMENTS("invalid-arguments", "&rInsufficient arguments. &7(&a%command_syntax%&7)"), - NOT_HOLDING_DISC("not-holding-disc", "&rYou must hold a disc in your main hand."), + NOT_HOLDING_CORRECT_ITEM("not-holding-correct-item", "&rYou must either hold a disc, goat horn or player head in your main hand."), CREATE_FILENAME("create-filename", "&7Your filename is: &a\"%filename%\"."), CREATE_CUSTOM_NAME("create-custom-name", "&7Your custom name is: &a\"%custom_name%\"."), DOWNLOADING_FILE("downloading-file", "&7Downloading file..."), @@ -18,9 +16,15 @@ public enum Lang { CREATE_DISC("create-disc", "&aCreate a disc by doing &7/cd create filename.extension \"Custom Lore\"&a."), DOWNLOAD_ERROR("download-error", "&rAn error has occurred while downloading."), NOW_PLAYING("now-playing","&6Now playing: %song_name%"), - DISC_CONVERTED("disc-converted", "&aConverted disc to new format! &fThis is due to changes in newer Minecraft versions which introduced &7JukeboxPlayableComponent&f."), + DISC_CONVERTED("disc-converted", "&aConverted disc to new format! &fThis is due to changes in newer Minecraft versions which introduced &ToolTipDisplay&f."), INVALID_RANGE("invalid-range","&rYou need to chose a range between 1 and %range_value%"), - CREATE_CUSTOM_RANGE("create-custom-range", "&7Your range is set to: &a\"%custom_range%\"."); + CREATE_CUSTOM_RANGE("create-custom-range", "&7Your range is set to: &a\"%custom_range%\"."), + NOT_HOLDING_MODIFIED_GOATHORN("not-holding-modified-goathorn", "&cYou must hold a modified goat horn in your main hand."), + INVALID_COOLDOWN("invalid-cooldown","&cYou need to chose a cooldown between 1 and %cooldown_value% (in ticks)"), + CREATE_CUSTOM_GOAT_COOLDOWN("create-custom-goat-cooldown", "&7Your goat horn cooldown is set to: &a\"%custom_goat_cooldown%\"."), + CUSTOM_MUSIC_DISABLED("custom-music-disabled", "&7Custom music discs are disabled in the configuration."), + CUSTOM_HEAD_DISABLED("custom-head-disabled", "&7Custom player heads are disabled in the configuration."), + CUSTOM_HORN_DISABLED("custom-horn-disabled", "&7Custom goat horns are disabled in the configuration."); private final String path; private final String def; @@ -67,6 +71,4 @@ public enum Lang { return this.path; } - //Component textComponent = LegacyComponentSerializer.legacyAmpersand().deserialize(PlaceholderAPI.setPlaceholders(player, Lang.PREFIX + Lang.COMBAT.toString())); - //player.sendMessage(textComponent); -} +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 7813db5..b8d2454 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,4 +1,21 @@ -# [Music Disc Config] +# [General CustomDiscs Config] + +# The maximum download size in megabytes. +max-download-size: 50 + +# The master volume of music discs from 0-1. (You can set values like 0.5 for 50% volume). +music-disc-volume: 1 + +# Debug Mode - To display some more logging information and Stack Trace informations +debugMode: false + +# [Music Discs Config] + +# Enable custom music discs. +music-disc-enable: true + +# Enable "Now playing" message for custom music discs. +music-disc-playing-enable: true # The distance from which music discs can be heard in blocks. music-disc-distance: 16 @@ -6,15 +23,46 @@ music-disc-distance: 16 # The max distance from which music discs can be heard in blocks. music-disc-max-distance: 256 -# The master volume of music discs from 0-1. (You can set values like 0.5 for 50% volume). -music-disc-volume: 1 +# [Goat Horns Config] -# The maximum download size in megabytes. -max-download-size: 50 +# Enable custom goat horns. +custom-horn-enable: true -#Custom Discs Help Page +# Enable "Now playing" message for custom horns. +custom-horn-playing-enable: true + +# The distance from which custom horns can be heard in blocks. +custom-horn-distance: 16 + +# The max distance from which custom horns can be heard in blocks. +custom-horn-max-distance: 256 + +# The default cooldown time for horns in ticks from 1 to the max value of horn-max-cooldown (1 second is 20 ticks). +horn-cooldown: 140 + +# The default max cooldown time for horns in ticks (1 second is 20 ticks). +horn-max-cooldown: 6000 + +# [Player Heads Config] + +# Enable custom player heads. +custom-head-enable: true + +# Enable "Now playing" message for player heads. +custom-head-playing-enable: true + +# The distance from which music discs can be heard in blocks. +custom-head-distance: 16 + +# The max distance from which music discs can be heard in blocks. +custom-head-max-distance: 256 + + +# [DO NOT EDIT BELOW THIS LINE - Help configuration] +# Custom Discs Help Page help: - - "&8-[&6CustomDiscs Help Page&8]-" + - "&8-[&6CustomDiscs v5.0 - Help Page&8]-" - "&aAuthor&7: &6Navoei" - - "&aContributors&7: &6alfw / &6Athar42" - - "&fGit&0Hub&7: &9&ohttps://github.com/Navoei/CustomDiscs" \ No newline at end of file + - "&aContributors&7: &6Athar42 / &6alfw" + - "&fGit&0Hub&7: &9&ohttps://github.com/Navoei/CustomDiscs" + - "&aDiscord&7: &9&ohttps://discord.gg/YJpqruvZ97" \ No newline at end of file diff --git a/src/main/resources/goat_horn_category.png b/src/main/resources/goat_horn_category.png new file mode 100644 index 0000000000000000000000000000000000000000..2905e57b28abc7dce95bfa6cd3cfb1738992d2fc GIT binary patch literal 197 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPGa3-AeXWn^IRaCfP%Ej82A(@F!$p_e3SXmF4EZ)?u z)FQY{gd|+Xcyr)@!mN;c8W}t(2GhzzOw5@jc_w>X qclkP;ikuVe+rXqSje9~HGlR)-e$gazi!DG47(8A5T-G@yGywn~tvP@I literal 0 HcmV?d00001 diff --git a/src/main/resources/lang.yml b/src/main/resources/lang.yml index aa1f0ee..f13b8bc 100644 --- a/src/main/resources/lang.yml +++ b/src/main/resources/lang.yml @@ -1,19 +1,22 @@ -prefix: "&8[&6CustomDiscs&8]&r" -no-permission: "&cYou do not have permission to execute this command." -invalid-filename: "&cThis is an invalid filename!" -no-disc-name-provided: "&cYou must provide a name for your disc." -invalid-format: "&cFile must be in wav, flac, or mp3 format!" -file-not-found: "&cFile not found!" -invalid-arguments: "&cInvalid arguments. &7(&a%command_syntax%&7)" -not-holding-disc: "&cYou must hold a disc in your main hand." -create-filename: "&7Your filename is: &a\"%filename%\"." -create-custom-name: "&7Your custom name is: &a\"%custom_name%\"." -downloading-file: "&7Downloading file..." -file-too-large: "&cThe file is larger than %max_download_size%MB." -successful-download: "&aFile successfully downloaded to &7%file_path%&a." -create-disc: "&aCreate a disc by doing &7/cd create %filename% \"Custom Lore\"&a." -download-error: "&cAn error has occurred while downloading." -now-playing: "&6Now playing: %song_name%" -disc-converted: "&aConverted disc to new format! &fThis is due to changes in newer Minecraft versions which introduced &7ToolTipDisplay&f." -invalid-range: "&cYou need to chose a range between 1 and %range_value%" -create-custom-range: "&7Your range is set to: &a\"%custom_range%\"." \ No newline at end of file +prefix: '&8[&6CustomDiscs&8]&r' +invalid-filename: '&cThis is an invalid filename!' +invalid-format: '&cFile must be in wav, flac, or mp3 format!' +file-not-found: '&cFile not found!' +not-holding-correct-item: '&cYou must either hold a disc, goat horn or player head in your main hand.' +create-filename: '&7Your filename is: &a"%filename%".' +create-custom-name: '&7Your custom name is: &a"%custom_name%".' +downloading-file: '&7Downloading file...' +file-too-large: '&cThe file is larger than %max_download_size%MB.' +successful-download: '&aFile successfully downloaded to &7%file_path%&a.' +create-disc: '&aCreate a disc by doing &7/cd create %filename% "Custom Lore"&a.' +download-error: '&cAn error has occurred while downloading.' +now-playing: '&6Now playing: %song_name%' +disc-converted: '&aConverted disc to new format! &fThis is due to changes in newer Minecraft versions which introduced &7ToolTipDisplay&f.' +invalid-range: '&cYou need to chose a range between 1 and %range_value%' +create-custom-range: '&7Your range is set to: &a"%custom_range%".' +not-holding-modified-goathorn: '&cYou must hold a modified goat horn in your main hand.' +invalid-cooldown: '&cYou need to chose a cooldown between 1 and %cooldown_value% (in ticks)' +create-custom-goat-cooldown: '&7Your goat horn cooldown is set to: &a"%custom_goat_cooldown%".' +custom-music-disabled: '&7Custom music discs are disabled in the configuration.' +custom-head-disabled: '&7Custom player heads are disabled in the configuration.' +custom-horn-disabled: '&7Custom goat horns are disabled in the configuration.' \ No newline at end of file diff --git a/src/main/resources/player_head_category.png b/src/main/resources/player_head_category.png new file mode 100644 index 0000000000000000000000000000000000000000..e80b86ea702aea323afa9da61aad39844489f358 GIT binary patch literal 3543 zcmd5;`8U)L7ydAo8HO1%%$OMrGseDe*|UV9v5mb_mZB_C))69UlPz1;ghGuaDU)oK zLSu;{p)6Sv8cWFj`u-R1J?FXi+~=Ni&vVc7%e`%9W5LIR02n?E$w{WX__mT+wG7?t zxRhsVqIz+2j=0~Ecy%vDwtq_F*(n8~J1efY8FmEg4%#bf z`Z0;FtSQ9&Rkam-_8uf7f)AkIux+z>$^C*E&=&iLjXjCqbs!utid|E@i{(uR;JzAa zv`qlWXx&ORl4k>Ya>{-JK+`YbQx%zL$_Fq294n5Ot2O3^z7eFi@fW-iZE8cadvYB! zf=agO!%euR<3xBK3TGQ*9NTn!c8|I z9LAksq^E@LEdj5TVL~q$otwfz@FZ*8iz^eKza?l-c;9)sjJ?vtFWsF?Tvfa*sFQrk zXgh7RQ(W!N?wuwJ)y()hd(ap5Fr!x$c=OTeY`vI4+{=>k{^P&G zLV=h&Dc*7Ys{B58zj>azd(+J0wkuYu1LZyAs_C4n=`HGdBwPsBR^-V7OR^`$As4}? zufyL4TuV!{aHY7KR*)8n^@v8W3nbyX>RV|-MulxXDGIUG9_gj~EX~!NF7-!3OrnT% zTACkuCW9==-*s==hDU;wre?`&=}#UdE0#Hw84kcN6i}oV(gM4FOoQv<=;Ar)4Qa46 z+j~1*@1`3Bb>H8bqCiR{sb!f6E6sC9XE+``P&3Ibeq!HKS@Z?`ge6@0h26&`k;1h#noi!vNLEw!T-Ls*U7rAMM^m54lZ2z3dO-RGS`FgJ?f)tM`J{$6@@~MigIx-S9(lX+;ba@H2v@pU7 z3obM&R48264b6~^ep^sdDeoxuYCBF~m@O{2TX ziI2<*-diPUC3R$vWJfbOv#ql?>Gz!tooUYN&R^-4L#GxL76OM{0_<=u`OtjbeCErF zs{E?<@ch7W!70I1*#X&ghl?Ysi@S?Fi+PJ&OFA2#8>EfEjXu$C5rpWFUZ`I5<78aV z+vf#RMMElqouln1Cz_d8Jai2W&TaS3gA6|yvOkD=aI@uc3t`-#g%~>&n;Xl$TePFM z&D=}<*E&gCMXlIyda%7=n_+8?$HqSg`GfErxg3wUH93Ytdcky_V|J)_^~_kwEA;GqFS95@-%ZmkEm}MA zJ|d^?=uCpRiJh{r?M(SaYf@|g z`=Ac7rOMONG51~(*Q}k74gH|^eY^BcQ75w?v!Im9cOko8{;NuZOoOeDynF`VG;KEU zBQE3S$IWTtR><0@P(UK3zqFoOZO4zIC|Afwo)kVA$sjkAPkd1y_u2owMdZDPif&G; zwPVyiA7(_ltxOPF&0eF3Ne<`;B~^E7c1ymc?)@FopoaQ!|^JEY6FYd;Jrz5&B>sJbDPX17yatc zzt#(S{jASdxBc1%L%#m|s^tEF*39~_h-Q-Uk_5lB2vnH> z(~uI7$MegG@~IHviV`q>2s=iA3ya_;2=m~OU=o&BOb9~6@G9W>Wkvb$C>{|(h>`?M zOB$gl4ikcLYs(#D6c&o)PusF zr7%Y%g|$f-b9HG9F9^rSDJ=}MQI*gmVchj))x=TGN2J{K<)0QD}aFn=jS@H4l4|hHvyxXBquqW9( zt5eZGh`DQ-eMl#IeneZRmp2?}H@~7Sfo)MBWI~!gYb~hNtmdj9au#*EtL^P_(Ig_F zEOWYsd;99?U^%r~mCjY(R>8BoU3-)F%=%klPz9)nZA+ W-N19JOyk4*0ZTKRW7Q@e*Zv34%Y%Rb literal 0 HcmV?d00001 diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 39b7482..0d4ee83 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -4,7 +4,7 @@ main: me.Navoei.customdiscsplugin.CustomDiscs api-version: '${bukkit_api_version}' prefix: CustomDiscs authors: [ "Navoei", "Athar42", "alfw" ] -description: A plugin which uses the Simple Voice Chat API to add custom music discs. +description: A plugin which uses the Simple Voice Chat API to add custom music discs, goat horns and player's head. depend: [ "voicechat", "ProtocolLib" ] dependencies: server: