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.
This commit is contained in:
Athar42
2025-07-20 21:05:29 +02:00
parent 0e605d872a
commit 3dd9c47147
25 changed files with 1325 additions and 247 deletions

View File

@@ -1,6 +1,6 @@
plugins {
id 'java'
id "com.gradleup.shadow" version "8.3.6"
id "com.gradleup.shadow" version "8.3.8"
}
java {

View File

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

View File

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

120
readme.md
View File

@@ -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
# 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.'
```

View File

@@ -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();
@@ -55,6 +74,28 @@ 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,17 +104,26 @@ 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");
}
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;
}
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")));
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
@@ -83,6 +133,7 @@ public final class CustomDiscs extends JavaPlugin {
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; }
}

View File

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

View File

@@ -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<UUID, PlayerReference> 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,10 +68,12 @@ public class PlayerManager {
Collection<ServerPlayer> playersInRange = api.getPlayersInRange(api.fromServerLevel(block.getWorld()), audioChannel.getLocation(), range);
if (musicDiscPlayingEnableResult) {
for (ServerPlayer serverPlayer : playersInRange) {
Player bukkitPlayer = (Player) serverPlayer.getPlayer();
bukkitPlayer.sendActionBar(actionbarComponent);
}
}
AtomicBoolean stopped = new AtomicBoolean();
AtomicReference<de.maxhenkel.voicechat.api.audiochannel.AudioPlayer> player = new AtomicReference<>();
@@ -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<ServerPlayer> 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<de.maxhenkel.voicechat.api.audiochannel.AudioPlayer> 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<ServerPlayer> 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<de.maxhenkel.voicechat.api.audiochannel.AudioPlayer> 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<ServerPlayer> 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<ServerPlayer> 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<ServerPlayer> 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() {

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,6 +48,7 @@ public class CreateSubCommand extends CommandAPICommand {
this.withFullDescription(NamedTextColor.GRAY + "Creates a custom music disc.");
this.withUsage("/customdisc create <filename> \"Custom Lore\"");
this.withPermission("customdiscs.create");
this.withArguments(new StringArgument("filename").replaceSuggestions(ArgumentSuggestions.stringCollection((sender) -> {
File musicDataFolder = new File(this.plugin.getDataFolder(), "musicdata");
@@ -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,6 +104,8 @@ public class CreateSubCommand extends CommandAPICommand {
String song_name = Objects.requireNonNull(arguments.getByClass("song_name", String.class));
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();
@@ -105,6 +117,47 @@ public class CreateSubCommand extends CommandAPICommand {
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<Component> 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;
}
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");
}
}

View File

@@ -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,6 +39,7 @@ public class DownloadSubCommand extends CommandAPICommand {
this.withFullDescription(NamedTextColor.GRAY + "Downloads a file from a given URL.");
this.withUsage("/customdisc download <url> <filename.extension>");
this.withPermission("customdiscs.download");
this.withArguments(new TextArgument("url"));
this.withArguments(new StringArgument("filename"));
@@ -41,10 +49,7 @@ public class DownloadSubCommand extends CommandAPICommand {
}
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 {
@@ -52,14 +57,17 @@ public class DownloadSubCommand extends CommandAPICommand {
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 "";
}
}
}

View File

@@ -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 <range>");
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;
}
}

View File

@@ -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,6 +30,7 @@ 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 <range>");
this.withPermission("customdiscs.range");
this.withArguments(new FloatArgument("range"));
@@ -41,21 +41,32 @@ 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;
}
@@ -63,6 +74,7 @@ public class SetRangeSubCommand extends CommandAPICommand {
PersistentDataContainer data = meta.getPersistentDataContainer();
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"));
}
}

View File

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

View File

@@ -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<Material> 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!");
}
}
}
}

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

View File

@@ -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%\"."
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.'

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

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