diff --git a/build.gradle b/build.gradle index 5ba5112..abd312f 100644 --- a/build.gradle +++ b/build.gradle @@ -36,6 +36,8 @@ dependencies { implementation "io.papermc.paper:paper-api:${bukkit_version}" implementation "de.maxhenkel.voicechat:voicechat-api:${voicechat_api_version}" + implementation "com.comphenix.protocol:ProtocolLib:5.0.0-SNAPSHOT" + } repositories { @@ -49,6 +51,7 @@ repositories { maven { url = uri("https://papermc.io/repo/repository/maven-public/") } + maven { url "https://repo.dmulloy2.net/repository/public/" } mavenLocal() } diff --git a/gradle.properties b/gradle.properties index e9bbd4d..16a386a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,8 +9,8 @@ bukkit_version=1.19-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.2.22 +voicechat_api_version=2.2.39 -plugin_version=1.3.1 +plugin_version=2.0 maven_group=me.Navoei.customdiscsplugin archives_base_name=custom-discs \ No newline at end of file diff --git a/readme.md b/readme.md index 1bd3f93..764e9ff 100644 --- a/readme.md +++ b/readme.md @@ -5,6 +5,7 @@ A spigot/bukkit fork of henkelmax's Audio Player. - Use ```/customdisc``` or ```/cd``` to create a custom disc. - Music files should go into ```plugins/CustomDiscs/musicdata/``` - Music files must be in the ```.wav``` or ```.mp3``` format. +- Only custom discs are compatible with hoppers. Permission Node (Required to run the command. Playing discs does not require a permission.): - ```customdiscs.command``` diff --git a/src/main/java/me/Navoei/customdiscsplugin/CustomDiscs.java b/src/main/java/me/Navoei/customdiscsplugin/CustomDiscs.java index 9644fc0..86dfad3 100644 --- a/src/main/java/me/Navoei/customdiscsplugin/CustomDiscs.java +++ b/src/main/java/me/Navoei/customdiscsplugin/CustomDiscs.java @@ -1,10 +1,19 @@ package me.Navoei.customdiscsplugin; +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.ProtocolManager; +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 me.Navoei.customdiscsplugin.command.CustomDisc; import me.Navoei.customdiscsplugin.event.JukeBox; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.bukkit.block.Jukebox; +import org.bukkit.inventory.ItemFlag; import org.bukkit.plugin.java.JavaPlugin; import javax.annotation.Nullable; @@ -47,8 +56,29 @@ public final class CustomDiscs extends JavaPlugin { } getServer().getPluginManager().registerEvents(new JukeBox(), this); + getServer().getPluginManager().registerEvents(new HopperManager(), this); getCommand("customdisc").setExecutor(command); + 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")) { + Jukebox jukebox = (Jukebox) packet.getBlockPositionModifier().read(0).toLocation(event.getPlayer().getWorld()).getBlock().getState(); + + if (!jukebox.getRecord().hasItemMeta()) return; + + if (jukebox.getRecord().getItemMeta().hasItemFlag(ItemFlag.HIDE_ENCHANTS)) { + event.setCancelled(true); + } + + } + } + }); + } @Override diff --git a/src/main/java/me/Navoei/customdiscsplugin/HopperManager.java b/src/main/java/me/Navoei/customdiscsplugin/HopperManager.java new file mode 100644 index 0000000..4908eec --- /dev/null +++ b/src/main/java/me/Navoei/customdiscsplugin/HopperManager.java @@ -0,0 +1,416 @@ +package me.Navoei.customdiscsplugin; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.block.Jukebox; +import org.bukkit.block.data.type.Hopper; +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.block.BlockPlaceEvent; +import org.bukkit.event.inventory.*; +import org.bukkit.event.world.ChunkLoadEvent; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; + +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Objects; + +public class HopperManager implements Listener { + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onHopperPickupFromOtherSource(InventoryMoveItemEvent event) { + + if (!Objects.requireNonNull(event.getDestination().getLocation()).getChunk().isLoaded()) return; + if (!event.getDestination().getLocation().getBlock().getType().equals(Material.HOPPER)) return; + if (!isCustomMusicDisc(event.getItem())) return; + + Block hopperBlock = event.getDestination().getLocation().getBlock(); + Hopper hopperData = (Hopper) hopperBlock.getBlockData(); + org.bukkit.block.Hopper hopper = (org.bukkit.block.Hopper) hopperBlock.getState(); + + if (!hopperBlock.getRelative(hopperData.getFacing()).getType().equals(Material.JUKEBOX)) return; + if (hopperBlock.getRelative(BlockFace.DOWN).getType().equals(Material.HOPPER)) return; + if (!hopperData.isEnabled()) return; + + Jukebox jukebox = (Jukebox) hopperBlock.getRelative(hopperData.getFacing()).getState(); + + if (!jukebox.getRecord().getType().equals(Material.AIR)) return; + + jukebox.setRecord(event.getItem()); + jukebox.update(); + + Bukkit.getScheduler().runTaskLater(CustomDiscs.getInstance(), () -> { + hopper.getInventory().removeItem(event.getItem()); + }, 1L); + + Component soundFileNameComponent = Objects.requireNonNull(event.getItem().getItemMeta().lore()).get(1).asComponent(); + String soundFileName = PlainTextComponentSerializer.plainText().serialize(soundFileNameComponent); + + Path soundFilePath = Path.of(CustomDiscs.getInstance().getDataFolder().getPath(), "musicdata", soundFileName); + + assert VoicePlugin.voicechatServerApi != null; + PlayerManager.instance().playLocationalAudio(VoicePlugin.voicechatServerApi, soundFilePath, null, jukebox.getBlock()); + + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onHopperPickupItem(InventoryPickupItemEvent event) { + + if (!Objects.requireNonNull(event.getInventory().getLocation()).getChunk().isLoaded()) return; + if (!event.getInventory().getLocation().getBlock().getType().equals(Material.HOPPER)) return; + if (!isCustomMusicDisc(event.getItem().getItemStack())) return; + + Block hopperBlock = event.getInventory().getLocation().getBlock(); + Hopper hopperData = (Hopper) hopperBlock.getBlockData(); + org.bukkit.block.Hopper hopper = (org.bukkit.block.Hopper) hopperBlock.getState(); + + if (!hopperBlock.getRelative(hopperData.getFacing()).getType().equals(Material.JUKEBOX)) return; + if (hopperBlock.getRelative(BlockFace.DOWN).getType().equals(Material.HOPPER)) return; + if (!hopperData.isEnabled()) return; + + Jukebox jukebox = (Jukebox) hopperBlock.getRelative(hopperData.getFacing()).getState(); + + if (!jukebox.getRecord().getType().equals(Material.AIR)) return; + + jukebox.setRecord(event.getItem().getItemStack()); + jukebox.update(); + + Bukkit.getScheduler().runTaskLater(CustomDiscs.getInstance(), () -> { + hopper.getInventory().removeItem(event.getItem().getItemStack()); + }, 1L); + + Component soundFileNameComponent = Objects.requireNonNull(event.getItem().getItemStack().getItemMeta().lore()).get(1).asComponent(); + String soundFileName = PlainTextComponentSerializer.plainText().serialize(soundFileNameComponent); + + Path soundFilePath = Path.of(CustomDiscs.getInstance().getDataFolder().getPath(), "musicdata", soundFileName); + + assert VoicePlugin.voicechatServerApi != null; + PlayerManager.instance().playLocationalAudio(VoicePlugin.voicechatServerApi, soundFilePath, null, jukebox.getBlock()); + + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onItemPlayerToHopper(InventoryClickEvent event) { + + if (event.getClickedInventory() == null) return; + if (Objects.requireNonNull(event.getClickedInventory()).getLocation() == null) return; + if (event.getInventory().getHolder() instanceof HopperMinecart) return; + + if (event.getAction().equals(InventoryAction.PLACE_ALL)) { + + if (!event.getClickedInventory().getLocation().getBlock().getType().equals(Material.HOPPER)) return; + + Bukkit.getScheduler().runTaskLater(CustomDiscs.getInstance(), () -> { + + Hopper hopperData = (Hopper) event.getInventory().getLocation().getBlock().getBlockData(); + Block jukeboxBlock = event.getInventory().getLocation().getBlock().getRelative(hopperData.getFacing()); + + getNextDiscFromHopperIntoJukebox(jukeboxBlock); + + }, 1L); + return; + } + + if (event.getAction().equals(InventoryAction.MOVE_TO_OTHER_INVENTORY)) { + + if (event.getClickedInventory().getLocation().getBlock().getType().equals(Material.HOPPER)) return; + + if (!event.getInventory().getType().equals(InventoryType.HOPPER)) return; + + Bukkit.getScheduler().runTaskLater(CustomDiscs.getInstance(), () -> { + + Hopper hopperData = (Hopper) event.getInventory().getLocation().getBlock().getBlockData(); + Block jukeboxBlock = event.getInventory().getLocation().getBlock().getRelative(hopperData.getFacing()); + + getNextDiscFromHopperIntoJukebox(jukeboxBlock); + + }, 1L); + return; + } + + if (event.getAction().equals(InventoryAction.SWAP_WITH_CURSOR)) { + + if (!event.getClickedInventory().getLocation().getBlock().getType().equals(Material.HOPPER)) return; + + Bukkit.getScheduler().runTaskLater(CustomDiscs.getInstance(), () -> { + + Hopper hopperData = (Hopper) event.getInventory().getLocation().getBlock().getBlockData(); + Block jukeboxBlock = event.getInventory().getLocation().getBlock().getRelative(hopperData.getFacing()); + + getNextDiscFromHopperIntoJukebox(jukeboxBlock); + + }, 1L); + return; + } + + if (event.getClick().isRightClick()) { + + if (!event.getClickedInventory().getLocation().getBlock().getType().equals(Material.HOPPER)) return; + + Bukkit.getScheduler().runTaskLater(CustomDiscs.getInstance(), () -> { + + Hopper hopperData = (Hopper) event.getInventory().getLocation().getBlock().getBlockData(); + Block jukeboxBlock = event.getInventory().getLocation().getBlock().getRelative(hopperData.getFacing()); + + getNextDiscFromHopperIntoJukebox(jukeboxBlock); + + }, 1L); + return; + } + + if (event.getClick() == ClickType.NUMBER_KEY || event.getClick() == ClickType.SWAP_OFFHAND) { + + if (!event.getInventory().getType().equals(InventoryType.HOPPER)) return; + + Bukkit.getScheduler().runTaskLater(CustomDiscs.getInstance(), () -> { + + Hopper hopperData = (Hopper) event.getInventory().getLocation().getBlock().getBlockData(); + Block jukeboxBlock = event.getInventory().getLocation().getBlock().getRelative(hopperData.getFacing()); + + getNextDiscFromHopperIntoJukebox(jukeboxBlock); + + }, 1L); + } + + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onInventoryDrag(InventoryDragEvent event) { + + if (Objects.requireNonNull(event.getInventory()).getLocation() == null) return; + + if (!event.getInventory().getLocation().getBlock().getType().equals(Material.HOPPER)) return; + + if (!event.getInventory().getType().equals(InventoryType.HOPPER)) return; + + if (event.getInventory().getHolder() instanceof HopperMinecart) return; + + Bukkit.getScheduler().runTaskLater(CustomDiscs.getInstance(), () -> { + + Hopper hopperData = (Hopper) event.getInventory().getLocation().getBlock().getBlockData(); + Block jukeboxBlock = event.getInventory().getLocation().getBlock().getRelative(hopperData.getFacing()); + + getNextDiscFromHopperIntoJukebox(jukeboxBlock); + + }, 1L); + + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onChunkLoad(ChunkLoadEvent event) { + for (BlockState blockState : event.getChunk().getTileEntities()) { + if (blockState instanceof Jukebox) { + if (!PlayerManager.instance().isAudioPlayerPlaying(blockState.getLocation())) { + itemJukeboxToHopper(blockState.getBlock()); + } + } + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onJukeboxPlace(BlockPlaceEvent event) { + if (!event.getBlock().getType().equals(Material.JUKEBOX)) return; + + getNextDiscFromHopperIntoJukebox(event.getBlock()); + + } + + public void itemJukeboxToHopper (Block block) { + + if (!Objects.requireNonNull(block.getLocation()).getChunk().isLoaded()) return; + if (!block.getType().equals(Material.JUKEBOX)) return; + if (!block.getRelative(BlockFace.DOWN).getType().equals(Material.HOPPER)) return; + + Block hopperBlock = block.getRelative(BlockFace.DOWN); + org.bukkit.block.Hopper hopper = (org.bukkit.block.Hopper) hopperBlock.getState(); + + Jukebox jukebox = (Jukebox) block.getState(); + + if (!Arrays.toString(hopper.getInventory().getContents()).contains("null")) return; + + hopper.getInventory().setItem(hopper.getInventory().firstEmpty(), jukebox.getRecord()); + + block.setType(Material.AIR); + block.setType(Material.JUKEBOX); + + getNextDiscFromHopperIntoJukebox(block); + + } + + public void getNextDiscFromHopperIntoJukebox(Block block) { + + if (!block.getType().equals(Material.JUKEBOX)) return; + Jukebox jukebox = (Jukebox) block.getState(); + if (!jukebox.getRecord().getType().equals(Material.AIR)) return; + + if (block.getRelative(BlockFace.UP).getType().equals(Material.HOPPER)) { + org.bukkit.block.Hopper hopper = (org.bukkit.block.Hopper) block.getRelative(BlockFace.UP).getState(); + if (!hopper.getInventory().isEmpty()) { + for (int i = 0; i < hopper.getInventory().getSize(); i++) { + if (hopper.getInventory().getItem(i) != null) { + if (isCustomMusicDisc(hopper.getInventory().getItem(i))) { + + jukebox.setRecord(hopper.getInventory().getItem(i)); + jukebox.update(); + + Component soundFileNameComponent = Objects.requireNonNull(jukebox.getRecord().getItemMeta().lore()).get(1).asComponent(); + String soundFileName = PlainTextComponentSerializer.plainText().serialize(soundFileNameComponent); + + Path soundFilePath = Path.of(CustomDiscs.getInstance().getDataFolder().getPath(), "musicdata", soundFileName); + + assert VoicePlugin.voicechatServerApi != null; + PlayerManager.instance().playLocationalAudio(VoicePlugin.voicechatServerApi, soundFilePath, null, jukebox.getBlock()); + + hopper.getInventory().setItem(i, new ItemStack(Material.AIR)); + return; + } + } + } + } + } + + if (block.getRelative(BlockFace.SOUTH).getType().equals(Material.HOPPER)) { + org.bukkit.block.Hopper hopper = (org.bukkit.block.Hopper) block.getRelative(BlockFace.SOUTH).getState(); + if (!hopper.getInventory().isEmpty()) { + for (int i = 0; i < hopper.getInventory().getSize(); i++) { + if (hopper.getInventory().getItem(i) != null) { + if (isCustomMusicDisc(hopper.getInventory().getItem(i))) { + + jukebox.setRecord(hopper.getInventory().getItem(i)); + jukebox.update(); + + Component soundFileNameComponent = Objects.requireNonNull(jukebox.getRecord().getItemMeta().lore()).get(1).asComponent(); + String soundFileName = PlainTextComponentSerializer.plainText().serialize(soundFileNameComponent); + + Path soundFilePath = Path.of(CustomDiscs.getInstance().getDataFolder().getPath(), "musicdata", soundFileName); + + assert VoicePlugin.voicechatServerApi != null; + PlayerManager.instance().playLocationalAudio(VoicePlugin.voicechatServerApi, soundFilePath, null, jukebox.getBlock()); + + hopper.getInventory().setItem(i, new ItemStack(Material.AIR)); + return; + } + } + } + } + } + + if (block.getRelative(BlockFace.WEST).getType().equals(Material.HOPPER)) { + org.bukkit.block.Hopper hopper = (org.bukkit.block.Hopper) block.getRelative(BlockFace.WEST).getState(); + if (!hopper.getInventory().isEmpty()) { + for (int i = 0; i < hopper.getInventory().getSize(); i++) { + if (hopper.getInventory().getItem(i) != null) { + if (isCustomMusicDisc(hopper.getInventory().getItem(i))) { + + jukebox.setRecord(hopper.getInventory().getItem(i)); + jukebox.update(); + + Component soundFileNameComponent = Objects.requireNonNull(jukebox.getRecord().getItemMeta().lore()).get(1).asComponent(); + String soundFileName = PlainTextComponentSerializer.plainText().serialize(soundFileNameComponent); + + Path soundFilePath = Path.of(CustomDiscs.getInstance().getDataFolder().getPath(), "musicdata", soundFileName); + + assert VoicePlugin.voicechatServerApi != null; + PlayerManager.instance().playLocationalAudio(VoicePlugin.voicechatServerApi, soundFilePath, null, jukebox.getBlock()); + + hopper.getInventory().setItem(i, new ItemStack(Material.AIR)); + return; + } + } + } + } + } + + if (block.getRelative(BlockFace.NORTH).getType().equals(Material.HOPPER)) { + org.bukkit.block.Hopper hopper = (org.bukkit.block.Hopper) block.getRelative(BlockFace.NORTH).getState(); + if (!hopper.getInventory().isEmpty()) { + for (int i = 0; i < hopper.getInventory().getSize(); i++) { + if (hopper.getInventory().getItem(i) != null) { + if (isCustomMusicDisc(hopper.getInventory().getItem(i))) { + + jukebox.setRecord(hopper.getInventory().getItem(i)); + jukebox.update(); + + Component soundFileNameComponent = Objects.requireNonNull(jukebox.getRecord().getItemMeta().lore()).get(1).asComponent(); + String soundFileName = PlainTextComponentSerializer.plainText().serialize(soundFileNameComponent); + + Path soundFilePath = Path.of(CustomDiscs.getInstance().getDataFolder().getPath(), "musicdata", soundFileName); + + assert VoicePlugin.voicechatServerApi != null; + PlayerManager.instance().playLocationalAudio(VoicePlugin.voicechatServerApi, soundFilePath, null, jukebox.getBlock()); + + hopper.getInventory().setItem(i, new ItemStack(Material.AIR)); + return; + } + } + } + } + } + + if (block.getRelative(BlockFace.EAST).getType().equals(Material.HOPPER)) { + org.bukkit.block.Hopper hopper = (org.bukkit.block.Hopper) block.getRelative(BlockFace.EAST).getState(); + if (!hopper.getInventory().isEmpty()) { + for (int i = 0; i < hopper.getInventory().getSize(); i++) { + if (hopper.getInventory().getItem(i) != null) { + if (isCustomMusicDisc(hopper.getInventory().getItem(i))) { + + jukebox.setRecord(hopper.getInventory().getItem(i)); + jukebox.update(); + + Component soundFileNameComponent = Objects.requireNonNull(jukebox.getRecord().getItemMeta().lore()).get(1).asComponent(); + String soundFileName = PlainTextComponentSerializer.plainText().serialize(soundFileNameComponent); + + Path soundFilePath = Path.of(CustomDiscs.getInstance().getDataFolder().getPath(), "musicdata", soundFileName); + + assert VoicePlugin.voicechatServerApi != null; + PlayerManager.instance().playLocationalAudio(VoicePlugin.voicechatServerApi, soundFilePath, null, jukebox.getBlock()); + + hopper.getInventory().setItem(i, new ItemStack(Material.AIR)); + return; + } + } + } + } + } + + } + + private boolean isCustomMusicDisc (ItemStack item) { + + return item.hasItemFlag(ItemFlag.HIDE_ENCHANTS) && ( + item.getType().equals(Material.MUSIC_DISC_13) || + item.getType().equals(Material.MUSIC_DISC_CAT) || + item.getType().equals(Material.MUSIC_DISC_BLOCKS) || + item.getType().equals(Material.MUSIC_DISC_CHIRP) || + item.getType().equals(Material.MUSIC_DISC_FAR) || + item.getType().equals(Material.MUSIC_DISC_MALL) || + item.getType().equals(Material.MUSIC_DISC_MELLOHI) || + item.getType().equals(Material.MUSIC_DISC_STAL) || + item.getType().equals(Material.MUSIC_DISC_STRAD) || + item.getType().equals(Material.MUSIC_DISC_WARD) || + item.getType().equals(Material.MUSIC_DISC_11) || + item.getType().equals(Material.MUSIC_DISC_WAIT) || + item.getType().equals(Material.MUSIC_DISC_OTHERSIDE) || + item.getType().equals(Material.MUSIC_DISC_5) || + item.getType().equals(Material.MUSIC_DISC_PIGSTEP) + ); + } + + private static HopperManager instance; + + public static HopperManager instance() { + if (instance == null) { + instance = new HopperManager(); + } + return instance; + } + +} diff --git a/src/main/java/me/Navoei/customdiscsplugin/PlayerManager.java b/src/main/java/me/Navoei/customdiscsplugin/PlayerManager.java new file mode 100644 index 0000000..70c5b26 --- /dev/null +++ b/src/main/java/me/Navoei/customdiscsplugin/PlayerManager.java @@ -0,0 +1,171 @@ +package me.Navoei.customdiscsplugin; + +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 org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; + +import javax.annotation.Nullable; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.UnsupportedAudioFileException; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +public class PlayerManager { + + private final Map playerMap; + private final ExecutorService executorService; + private static AudioFormat FORMAT = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 48000F, 16, 1, 2, 48000F, false); + + public PlayerManager() { + this.playerMap = new ConcurrentHashMap<>(); + this.executorService = Executors.newSingleThreadExecutor(r -> { + Thread thread = new Thread(r, "AudioPlayerThread"); + thread.setDaemon(true); + return thread; + }); + } + + public void playLocationalAudio(VoicechatServerApi api, Path soundFilePath, Player bukkitPlayer, Block block) { + 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; + + AtomicBoolean stopped = new AtomicBoolean(); + AtomicReference player = new AtomicReference<>(); + + playerMap.put(id, () -> { + synchronized (stopped) { + stopped.set(true); + de.maxhenkel.voicechat.api.audiochannel.AudioPlayer audioPlayer = player.get(); + if (audioPlayer != null) { + audioPlayer.stopPlaying(); + } + } + }); + + executorService.execute(() -> { + de.maxhenkel.voicechat.api.audiochannel.AudioPlayer audioPlayer = playChannel(api, bukkitPlayer, audioChannel, block, soundFilePath); + if (audioPlayer == null) { + playerMap.remove(id); + return; + } + + audioPlayer.setOnStopped(() -> { + + Bukkit.getScheduler().runTask(CustomDiscs.getInstance(), () -> HopperManager.instance().itemJukeboxToHopper(block)); + + playerMap.remove(id); + }); + + synchronized (stopped) { + if (!stopped.get()) { + player.set(audioPlayer); + } else { + audioPlayer.stopPlaying(); + } + } + }); + } + + @Nullable + private de.maxhenkel.voicechat.api.audiochannel.AudioPlayer playChannel(VoicechatServerApi api, Player bukkitPlayer, AudioChannel audioChannel, Block block, Path soundFilePath) { + try { + short[] audio = readSoundFile(soundFilePath); + AudioPlayer audioPlayer = api.createAudioPlayer(audioChannel, api.createEncoder(), audio); + audioPlayer.startPlaying(); + return audioPlayer; + } catch (Exception e) { + e.printStackTrace(); + Bukkit.getLogger().info("Error Occurred At: " + block.getLocation()); + if (bukkitPlayer != null) { + bukkitPlayer.sendMessage(ChatColor.RED + "An error occurred while trying to play the music!"); + } + return null; + } + } + + private static short[] readSoundFile(Path file) throws UnsupportedAudioFileException, IOException { + return VoicePlugin.voicechatApi.getAudioConverter().bytesToShorts(convertFormat(file, FORMAT)); + } + + private static byte[] convertFormat(Path file, AudioFormat audioFormat) throws UnsupportedAudioFileException, IOException { + AudioInputStream finalInputStream = null; + + if (getFileExtension(file.toFile().toString()).equals("wav")) { + AudioInputStream inputStream = AudioSystem.getAudioInputStream(file.toFile()); + finalInputStream = AudioSystem.getAudioInputStream(audioFormat, inputStream); + } else if (getFileExtension(file.toFile().toString()).equals("mp3")) { + + AudioInputStream inputStream = new MpegAudioFileReader().getAudioInputStream(file.toFile()); + AudioFormat baseFormat = inputStream.getFormat(); + AudioFormat decodedFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, baseFormat.getSampleRate(), 16, baseFormat.getChannels(), baseFormat.getChannels() * 2, baseFormat.getFrameRate(), false); + AudioInputStream convertedInputStream = new MpegFormatConversionProvider().getAudioInputStream(decodedFormat, inputStream); + finalInputStream = AudioSystem.getAudioInputStream(audioFormat, convertedInputStream); + + } + + assert finalInputStream != null; + return finalInputStream.readAllBytes(); + } + + public void stopLocationalAudio(Location blockLocation) { + UUID id = UUID.nameUUIDFromBytes(blockLocation.toString().getBytes()); + Stoppable player = playerMap.get(id); + if (player != null) { + player.stop(); + } + playerMap.remove(id); + } + + 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()); + return playerMap.containsKey(id); + } + + private static String getFileExtension(String s) { + int index = s.lastIndexOf("."); + if (index > 0) { + return s.substring(index + 1); + } else { + return ""; + } + } + + private static PlayerManager instance; + + public static PlayerManager instance() { + if (instance == null) { + instance = new PlayerManager(); + } + return instance; + } + + private interface Stoppable { + void stop(); + } + +} diff --git a/src/main/java/me/Navoei/customdiscsplugin/event/JukeBox.java b/src/main/java/me/Navoei/customdiscsplugin/event/JukeBox.java index 0da9940..bd705f7 100644 --- a/src/main/java/me/Navoei/customdiscsplugin/event/JukeBox.java +++ b/src/main/java/me/Navoei/customdiscsplugin/event/JukeBox.java @@ -1,19 +1,18 @@ package me.Navoei.customdiscsplugin.event; -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 me.Navoei.customdiscsplugin.CustomDiscs; +import me.Navoei.customdiscsplugin.HopperManager; +import me.Navoei.customdiscsplugin.PlayerManager; import me.Navoei.customdiscsplugin.VoicePlugin; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; -import org.bukkit.*; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.Jukebox; -import org.bukkit.block.TileState; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -23,27 +22,15 @@ import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.ItemFlag; -import org.bukkit.inventory.ItemStack; -import org.bukkit.persistence.PersistentDataContainer; -import org.bukkit.persistence.PersistentDataType; -import org.bukkit.scheduler.BukkitRunnable; -import javax.sound.sampled.AudioFormat; -import javax.sound.sampled.AudioInputStream; -import javax.sound.sampled.AudioSystem; -import javax.sound.sampled.UnsupportedAudioFileException; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Path; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; +import java.util.Objects; +import java.util.UUID; public class JukeBox implements Listener{ - private final Map playerMap = new ConcurrentHashMap<>(); - private final Map asyncTaskMap = new ConcurrentHashMap<>(); - public static AudioFormat FORMAT = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 48000F, 16, 1, 2, 48000F, false); - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onInsert(PlayerInteractEvent event) throws IOException { @@ -53,21 +40,7 @@ public class JukeBox implements Listener{ if (event.getAction() != Action.RIGHT_CLICK_BLOCK || event.getClickedBlock() == null || event.getItem() == null || event.getItem().getItemMeta() == null || block == null) return; if (event.getClickedBlock().getType() != Material.JUKEBOX) return; - TileState tileState = (TileState) block.getState(); - PersistentDataContainer container = tileState.getPersistentDataContainer(); - NamespacedKey key = new NamespacedKey(CustomDiscs.getInstance(), - "CustomDisc"); - if (container.has(key, PersistentDataType.BYTE_ARRAY)) { - event.setCancelled(true); - ejectDisc(block, player); - return; - } - - if (isCustomMusicDisc(event) && !jukeboxContainsPersistentData(block) && !jukeboxContainsVanillaDisc(block)) { - - event.setCancelled(true); - - UUID id = UUID.nameUUIDFromBytes(block.getLocation().toString().getBytes()); + if (isCustomMusicDisc(event) && !jukeboxContainsDisc(block)) { Component soundFileNameComponent = Objects.requireNonNull(event.getItem().getItemMeta().lore()).get(1).asComponent(); String soundFileName = PlainTextComponentSerializer.plainText().serialize(soundFileNameComponent); @@ -76,57 +49,17 @@ public class JukeBox implements Listener{ if (soundFilePath.toFile().exists()) { + assert VoicePlugin.voicechatServerApi != null; + PlayerManager.instance().playLocationalAudio(VoicePlugin.voicechatServerApi, soundFilePath, player, block); + Component songNameComponent = Objects.requireNonNull(event.getItem().getItemMeta().lore()).get(0).asComponent(); String songName = PlainTextComponentSerializer.plainText().serialize(songNameComponent); - LocationalAudioChannel audioChannel = VoicePlugin.voicechatServerApi.createLocationalAudioChannel(id, VoicePlugin.voicechatApi.fromServerLevel(block.getLocation().getWorld()), VoicePlugin.voicechatApi.createPosition(block.getLocation().getX() + 0.5d, block.getLocation().getY() + 0.5d, block.getLocation().getZ() + 0.5d)); - - //Run the audio player asynchronously to prevent lag when inserting discs. - //Put the task in a hashmap to be able to access it later. - BukkitRunnable runnable = new BukkitRunnable() { - @Override - public void run() { - //Try playing the voice chat audio player. - AudioPlayer audioPlayer = null; - try { - audioPlayer = VoicePlugin.voicechatServerApi.createAudioPlayer(audioChannel, VoicePlugin.voicechatApi.createEncoder(), readSoundFile(soundFilePath)); - } catch (UnsupportedAudioFileException | IOException e) { - e.printStackTrace(); - player.sendMessage(ChatColor.RED + "An error occurred while trying to play the music!"); - return; - } - playerMap.put(id, audioPlayer); - assert audioPlayer != null; - audioPlayer.startPlaying(); - - Bukkit.getScheduler().runTask(CustomDiscs.getInstance(), () -> { - //Send Player Action Bar - TextComponent customLoreSong = Component.text() - .content("Now Playing: " + songName) - .color(NamedTextColor.GOLD) - .build(); - player.sendActionBar(customLoreSong.asComponent()); - }); - - } - }; - - asyncTaskMap.put(block.getLocation(), runnable); - asyncTaskMap.get(block.getLocation()).runTaskAsynchronously(CustomDiscs.getInstance()); - - //Send Player Action Bar - TextComponent customLoadingSong = Component.text() - .content("Loading Disc...") - .color(NamedTextColor.GRAY) + TextComponent customActionBarSongPlaying = Component.text() + .content("Now Playing: " + songName) + .color(NamedTextColor.GOLD) .build(); - player.sendActionBar(customLoadingSong.asComponent()); - - //set the persistent data container - container.set(key, PersistentDataType.BYTE_ARRAY, event.getItem().serializeAsBytes()); - tileState.update(); - - //Remove the item from the player's hand in sync to prevent players from glitching the jukebox. - player.getInventory().setItem(Objects.requireNonNull(event.getHand()), null); + player.sendActionBar(customActionBarSongPlaying.asComponent()); } else { player.sendMessage(ChatColor.RED + "Sound file not found."); @@ -134,7 +67,6 @@ public class JukeBox implements Listener{ throw new FileNotFoundException("Sound file is missing!"); } } - } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @@ -146,16 +78,11 @@ public class JukeBox implements Listener{ if (event.getAction() != Action.RIGHT_CLICK_BLOCK || event.getClickedBlock() == null || block == null) return; if (event.getClickedBlock().getType() != Material.JUKEBOX) return; - if (isAsyncTaskRunning(asyncTaskMap, block.getLocation())) { - //Send Player Action Bar - TextComponent songLoading = Component.text() - .content("Disc is currently loading!") - .color(NamedTextColor.RED) - .build(); - player.sendActionBar(songLoading.asComponent()); - event.setCancelled(true); - } else { - ejectDisc(block, player); + if (jukeboxContainsDisc(block)) { + stopDisc(block, player); + + Bukkit.getScheduler().runTaskLater(CustomDiscs.getInstance(), () -> HopperManager.instance().getNextDiscFromHopperIntoJukebox(block), 1L); + } } @@ -167,18 +94,7 @@ public class JukeBox implements Listener{ if (block.getType() != Material.JUKEBOX) return; - if (isAsyncTaskRunning(asyncTaskMap, block.getLocation())) { - //Send Player Action Bar - TextComponent songLoading = Component.text() - .content("Disc is currently loading!") - .color(NamedTextColor.RED) - .build(); - player.sendActionBar(songLoading.asComponent()); - event.setCancelled(true); - } else { - ejectDisc(block, player); - } - + stopDisc(block, player); } @EventHandler @@ -186,134 +102,44 @@ public class JukeBox implements Listener{ for (Block explodedBlock : event.blockList()) { if (explodedBlock.getType() == Material.JUKEBOX) { - - if (isAsyncTaskRunning(asyncTaskMap, explodedBlock.getLocation())) { - event.setCancelled(true); - } else { - ejectDisc(explodedBlock, null); - } + stopDisc(explodedBlock, null); } } } - - private boolean jukeboxContainsPersistentData(Block b) { - TileState tileState = (TileState) b.getState(); - PersistentDataContainer container = tileState.getPersistentDataContainer(); - NamespacedKey key = new NamespacedKey(CustomDiscs.getInstance(), - "CustomDisc"); - return container.has(key, PersistentDataType.BYTE_ARRAY); - } - - private boolean jukeboxContainsVanillaDisc(Block b) { + public boolean jukeboxContainsDisc(Block b) { Jukebox jukebox = (Jukebox) b.getLocation().getBlock().getState(); return jukebox.getPlaying() != Material.AIR; } - public static short[] readSoundFile(Path file) throws UnsupportedAudioFileException, IOException { - return VoicePlugin.voicechatApi.getAudioConverter().bytesToShorts(convertFormat(file, FORMAT)); - } - - public static byte[] convertFormat(Path file, AudioFormat audioFormat) throws UnsupportedAudioFileException, IOException { - AudioInputStream finalInputStream = null; - - if (getFileExtension(file.toFile().toString()).equals("wav")) { - AudioInputStream inputStream = AudioSystem.getAudioInputStream(file.toFile()); - finalInputStream = AudioSystem.getAudioInputStream(audioFormat, inputStream); - } else if (getFileExtension(file.toFile().toString()).equals("mp3")) { - - AudioInputStream inputStream = new MpegAudioFileReader().getAudioInputStream(file.toFile()); - AudioFormat baseFormat = inputStream.getFormat(); - AudioFormat decodedFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, baseFormat.getSampleRate(), 16, baseFormat.getChannels(), baseFormat.getChannels() * 2, baseFormat.getFrameRate(), false); - AudioInputStream convertedInputStream = new MpegFormatConversionProvider().getAudioInputStream(decodedFormat, inputStream); - finalInputStream = AudioSystem.getAudioInputStream(audioFormat, convertedInputStream); - - } - - assert finalInputStream != null; - return finalInputStream.readAllBytes(); - } - public boolean isCustomMusicDisc(PlayerInteractEvent e) { - if ( - e.getItem().getItemMeta().hasItemFlag(ItemFlag.HIDE_ENCHANTS) && + return e.getItem().getItemMeta().hasItemFlag(ItemFlag.HIDE_ENCHANTS) && ( e.getItem().getType().equals(Material.MUSIC_DISC_13) || - e.getItem().getType().equals(Material.MUSIC_DISC_CAT) || - e.getItem().getType().equals(Material.MUSIC_DISC_BLOCKS) || - e.getItem().getType().equals(Material.MUSIC_DISC_CHIRP) || - e.getItem().getType().equals(Material.MUSIC_DISC_FAR) || - e.getItem().getType().equals(Material.MUSIC_DISC_MALL) || - e.getItem().getType().equals(Material.MUSIC_DISC_MELLOHI) || - e.getItem().getType().equals(Material.MUSIC_DISC_STAL) || - e.getItem().getType().equals(Material.MUSIC_DISC_STRAD) || - e.getItem().getType().equals(Material.MUSIC_DISC_WARD) || - e.getItem().getType().equals(Material.MUSIC_DISC_11) || - e.getItem().getType().equals(Material.MUSIC_DISC_WAIT) || - e.getItem().getType().equals(Material.MUSIC_DISC_OTHERSIDE) || - e.getItem().getType().equals(Material.MUSIC_DISC_5) || - e.getItem().getType().equals(Material.MUSIC_DISC_PIGSTEP) - ) - ) { - return true; - } - return false; + e.getItem().getType().equals(Material.MUSIC_DISC_CAT) || + e.getItem().getType().equals(Material.MUSIC_DISC_BLOCKS) || + e.getItem().getType().equals(Material.MUSIC_DISC_CHIRP) || + e.getItem().getType().equals(Material.MUSIC_DISC_FAR) || + e.getItem().getType().equals(Material.MUSIC_DISC_MALL) || + e.getItem().getType().equals(Material.MUSIC_DISC_MELLOHI) || + e.getItem().getType().equals(Material.MUSIC_DISC_STAL) || + e.getItem().getType().equals(Material.MUSIC_DISC_STRAD) || + e.getItem().getType().equals(Material.MUSIC_DISC_WARD) || + e.getItem().getType().equals(Material.MUSIC_DISC_11) || + e.getItem().getType().equals(Material.MUSIC_DISC_WAIT) || + e.getItem().getType().equals(Material.MUSIC_DISC_OTHERSIDE) || + e.getItem().getType().equals(Material.MUSIC_DISC_5) || + e.getItem().getType().equals(Material.MUSIC_DISC_PIGSTEP) + ); } - private void ejectDisc(Block block, Player player) { - UUID id = UUID.nameUUIDFromBytes(block.getLocation().toString().getBytes()); - - //Spawn in item at the block position - TileState tileState = (TileState) block.getState(); - PersistentDataContainer container = tileState.getPersistentDataContainer(); - NamespacedKey key = new NamespacedKey(CustomDiscs.getInstance(), - "CustomDisc"); - if (!container.has(key, PersistentDataType.BYTE_ARRAY)) return; - container.get(key, PersistentDataType.BYTE_ARRAY); - - block.getWorld().dropItemNaturally(block.getLocation().add(0.0, 0.5, 0.0), ItemStack.deserializeBytes(container.get(key, PersistentDataType.BYTE_ARRAY))); - - container.remove(key); - tileState.update(); - - if (isAudioPlayerPlaying(playerMap, id)) { - stopAudioPlayer(playerMap, id); - } - - asyncTaskMap.remove(block.getLocation()); - + private void stopDisc(Block block, Player player) { + PlayerManager.instance().stopLocationalAudio(block.getLocation()); if (player == null) return; - player.swingMainHand(); - } - - public boolean isAudioPlayerPlaying(Map playerMap, UUID id) { - AudioPlayer audioPlayer = playerMap.get(id); - if (audioPlayer == null) return false; - return audioPlayer.isPlaying(); - } - - private void stopAudioPlayer(Map playerMap, UUID id) { - AudioPlayer audioPlayer = playerMap.get(id); - if (audioPlayer != null && audioPlayer.isPlaying()) { - audioPlayer.stopPlaying(); - } - playerMap.remove(id); - } - - public boolean isAsyncTaskRunning(Map asyncTaskMap, Location blockLocation) { - if (!asyncTaskMap.containsKey(blockLocation)) return false; - int taskId = asyncTaskMap.get(blockLocation).getTaskId(); - return Bukkit.getScheduler().isCurrentlyRunning(taskId); - } - - private static String getFileExtension(String s) { - int index = s.lastIndexOf("."); - if (index > 0) { - return s.substring(index + 1); - } else { - return ""; + if (jukeboxContainsDisc(block)) { + player.swingMainHand(); } } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 6ab15e4..7c40939 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -5,7 +5,7 @@ api-version: ${bukkit_api_version} prefix: CustomDiscs authors: [ "Navoei" ] description: A plugin which uses the Simple Voice Chat API to add custom music discs. -depend: [ voicechat ] +depend: [ voicechat, ProtocolLib ] commands: customdisc: