Thread Pools and Hopper Comaptibility

2.0 release of Custom Discs
This commit is contained in:
Navoei
2022-08-07 23:44:24 -05:00
parent aac5cc4112
commit 0d41e54e6b
8 changed files with 667 additions and 220 deletions

View File

@@ -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()
}

View File

@@ -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

View File

@@ -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```

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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<UUID, Stoppable> 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<de.maxhenkel.voicechat.api.audiochannel.AudioPlayer> 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();
}
}

View File

@@ -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<UUID, AudioPlayer> playerMap = new ConcurrentHashMap<>();
private final Map<Location, BukkitRunnable> 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<UUID, AudioPlayer> playerMap, UUID id) {
AudioPlayer audioPlayer = playerMap.get(id);
if (audioPlayer == null) return false;
return audioPlayer.isPlaying();
}
private void stopAudioPlayer(Map<UUID, AudioPlayer> playerMap, UUID id) {
AudioPlayer audioPlayer = playerMap.get(id);
if (audioPlayer != null && audioPlayer.isPlaying()) {
audioPlayer.stopPlaying();
}
playerMap.remove(id);
}
public boolean isAsyncTaskRunning(Map<Location, BukkitRunnable> 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();
}
}

View File

@@ -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: