From 65a247a7d0900fd105d5e9d48a6571cd480fc155 Mon Sep 17 00:00:00 2001 From: Elis Eriksson Date: Sat, 7 Mar 2026 14:54:03 +0100 Subject: Reformat some files, Switch to brigadier for better argument parsing and tab complete ( this was hell) and now actually has permissions. --- gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 46175 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- .../java/eu/projnull/spelis/svci/Intercom.java | 6 +- .../spelis/svci/commands/BroadcastTypeHandler.java | 19 ---- .../eu/projnull/spelis/svci/commands/Handler.java | 8 ++ .../eu/projnull/spelis/svci/commands/Helpers.java | 37 +++++++ .../spelis/svci/commands/IntercomCommand.java | 55 ++++------ .../svci/commands/handlers/AboutCommand.java | 34 ++++++ .../commands/handlers/FileBroadcastHandler.java | 115 --------------------- .../spelis/svci/commands/handlers/FileCommand.java | 106 +++++++++++++++++++ .../commands/handlers/InfoBroadcastHandler.java | 49 --------- .../spelis/svci/commands/handlers/InfoCommand.java | 48 +++++++++ .../commands/handlers/LiveBroadcastHandler.java | 78 -------------- .../spelis/svci/commands/handlers/LiveCommand.java | 59 +++++++++++ .../commands/handlers/StopBroadcastHandler.java | 38 ------- .../spelis/svci/commands/handlers/StopCommand.java | 45 ++++++++ .../spelis/svci/misc/BroadcastHudTask.java | 5 +- .../eu/projnull/spelis/svci/misc/OggDecoder.java | 25 ++--- .../spelis/svci/voice/BroadcasterState.java | 21 +++- .../projnull/spelis/svci/voice/FileBroadcast.java | 56 ---------- .../eu/projnull/spelis/svci/voice/VoicePlugin.java | 25 ++--- src/main/resources/plugin.yml | 17 ++- 22 files changed, 412 insertions(+), 436 deletions(-) delete mode 100644 src/main/java/eu/projnull/spelis/svci/commands/BroadcastTypeHandler.java create mode 100644 src/main/java/eu/projnull/spelis/svci/commands/Handler.java create mode 100644 src/main/java/eu/projnull/spelis/svci/commands/Helpers.java create mode 100644 src/main/java/eu/projnull/spelis/svci/commands/handlers/AboutCommand.java delete mode 100644 src/main/java/eu/projnull/spelis/svci/commands/handlers/FileBroadcastHandler.java create mode 100644 src/main/java/eu/projnull/spelis/svci/commands/handlers/FileCommand.java delete mode 100644 src/main/java/eu/projnull/spelis/svci/commands/handlers/InfoBroadcastHandler.java create mode 100644 src/main/java/eu/projnull/spelis/svci/commands/handlers/InfoCommand.java delete mode 100644 src/main/java/eu/projnull/spelis/svci/commands/handlers/LiveBroadcastHandler.java create mode 100644 src/main/java/eu/projnull/spelis/svci/commands/handlers/LiveCommand.java delete mode 100644 src/main/java/eu/projnull/spelis/svci/commands/handlers/StopBroadcastHandler.java create mode 100644 src/main/java/eu/projnull/spelis/svci/commands/handlers/StopCommand.java delete mode 100644 src/main/java/eu/projnull/spelis/svci/voice/FileBroadcast.java diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e644113..61285a6 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a441313..2a84e18 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/src/main/java/eu/projnull/spelis/svci/Intercom.java b/src/main/java/eu/projnull/spelis/svci/Intercom.java index e4cf3f5..4f40019 100644 --- a/src/main/java/eu/projnull/spelis/svci/Intercom.java +++ b/src/main/java/eu/projnull/spelis/svci/Intercom.java @@ -4,10 +4,10 @@ import de.maxhenkel.voicechat.api.BukkitVoicechatService; import eu.projnull.spelis.svci.commands.IntercomCommand; import eu.projnull.spelis.svci.misc.BroadcastHudTask; import eu.projnull.spelis.svci.voice.VoicePlugin; -import org.bukkit.plugin.java.JavaPlugin; - +import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.bukkit.plugin.java.JavaPlugin; import javax.annotation.Nullable; @@ -32,7 +32,7 @@ public final class Intercom extends JavaPlugin { LOGGER.info("Failed to register intercom plugin"); } - getCommand("intercom").setExecutor(new IntercomCommand()); + this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, commands -> new IntercomCommand().register(commands)); hudTask.start(this); } diff --git a/src/main/java/eu/projnull/spelis/svci/commands/BroadcastTypeHandler.java b/src/main/java/eu/projnull/spelis/svci/commands/BroadcastTypeHandler.java deleted file mode 100644 index b394a74..0000000 --- a/src/main/java/eu/projnull/spelis/svci/commands/BroadcastTypeHandler.java +++ /dev/null @@ -1,19 +0,0 @@ -package eu.projnull.spelis.svci.commands; - -import org.bukkit.command.CommandSender; - -public interface BroadcastTypeHandler { - - /** - * Called when the command executes for this type. - * @param sender The executor - * @param args Command arguments - * @return true if handled successfully (for Bukkit), false to show usage - */ - boolean execute(CommandSender sender, String[] args); - - /** - * The name of this type (e.g., "file", "live") - */ - String getName(); -} diff --git a/src/main/java/eu/projnull/spelis/svci/commands/Handler.java b/src/main/java/eu/projnull/spelis/svci/commands/Handler.java new file mode 100644 index 0000000..62922cf --- /dev/null +++ b/src/main/java/eu/projnull/spelis/svci/commands/Handler.java @@ -0,0 +1,8 @@ +package eu.projnull.spelis.svci.commands; + +import com.mojang.brigadier.builder.ArgumentBuilder; +import io.papermc.paper.command.brigadier.CommandSourceStack; + +public interface Handler { + ArgumentBuilder buildCommand(); +} diff --git a/src/main/java/eu/projnull/spelis/svci/commands/Helpers.java b/src/main/java/eu/projnull/spelis/svci/commands/Helpers.java new file mode 100644 index 0000000..cd65279 --- /dev/null +++ b/src/main/java/eu/projnull/spelis/svci/commands/Helpers.java @@ -0,0 +1,37 @@ +package eu.projnull.spelis.svci.commands; + +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import eu.projnull.spelis.svci.Intercom; +import eu.projnull.spelis.svci.voice.BroadcasterState; +import org.bukkit.Bukkit; +import org.bukkit.World; + +import java.io.File; +import java.util.Arrays; + +public class Helpers { + public static void getActiveWorldsSuggestion(SuggestionsBuilder builder) { + BroadcasterState.inst().activeBroadcastWorldNames().forEach(builder::suggest); + } + + public static void getAllWorldsSuggestion(SuggestionsBuilder builder) { + Bukkit.getWorlds() + .stream() + .map(World::getName) + .forEach(builder::suggest); + } + + public static void getAllSoundsSuggestion(SuggestionsBuilder builder) { + File soundsDir = new File( + Intercom.getPlugin(Intercom.class).getDataFolder(), + "sounds" + ); + File[] files = soundsDir.listFiles(); + if (files == null) return; + + Arrays.stream(files) + .filter(File::isFile) + .map(File::getName) + .forEach(builder::suggest); + } +} diff --git a/src/main/java/eu/projnull/spelis/svci/commands/IntercomCommand.java b/src/main/java/eu/projnull/spelis/svci/commands/IntercomCommand.java index 1e9e5bc..418408e 100644 --- a/src/main/java/eu/projnull/spelis/svci/commands/IntercomCommand.java +++ b/src/main/java/eu/projnull/spelis/svci/commands/IntercomCommand.java @@ -1,48 +1,37 @@ package eu.projnull.spelis.svci.commands; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import eu.projnull.spelis.svci.commands.handlers.*; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent; +import org.jetbrains.annotations.NotNull; -import java.util.HashMap; -import java.util.Map; +import java.util.ArrayList; +import java.util.List; -public class IntercomCommand implements CommandExecutor { - - private final Map handlers = new HashMap<>(); +public class IntercomCommand { + private final List handlers = new ArrayList<>(); public IntercomCommand() { - registerHandler(new eu.projnull.spelis.svci.commands.handlers.LiveBroadcastHandler()); - registerHandler(new eu.projnull.spelis.svci.commands.handlers.StopBroadcastHandler()); - registerHandler(new eu.projnull.spelis.svci.commands.handlers.InfoBroadcastHandler()); - registerHandler(new eu.projnull.spelis.svci.commands.handlers.FileBroadcastHandler()); + registerHandler(new InfoCommand()); + registerHandler(new StopCommand()); + registerHandler(new LiveCommand()); + registerHandler(new FileCommand()); + registerHandler(new AboutCommand()); } - public void registerHandler(BroadcastTypeHandler handler) { - handlers.put(handler.getName().toLowerCase(), handler); + public void registerHandler(Handler handler) { + handlers.add(handler); } - @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - - if (!sender.hasPermission("svcintercom.broadcast")) { - sender.sendMessage("§cYou don't have permission to use the intercom."); - return true; - } - - if (args.length < 1) { - sender.sendMessage("§cUsage: /intercom ..."); - return true; - } - - String type = args[0].toLowerCase(); - BroadcastTypeHandler handler = handlers.get(type); + public void register(ReloadableRegistrarEvent<@NotNull Commands> commands) { + LiteralArgumentBuilder root = Commands.literal("intercom").requires(cs->cs.getSender().hasPermission("svcintercom.broadcast")); - if (handler == null) { - sender.sendMessage("§cUnknown broadcast type: " + type); - return true; + for (Handler handler : handlers) { + root.then(handler.buildCommand()); } - return handler.execute(sender, args); + commands.registrar().register(root.build()); } } diff --git a/src/main/java/eu/projnull/spelis/svci/commands/handlers/AboutCommand.java b/src/main/java/eu/projnull/spelis/svci/commands/handlers/AboutCommand.java new file mode 100644 index 0000000..452bfd3 --- /dev/null +++ b/src/main/java/eu/projnull/spelis/svci/commands/handlers/AboutCommand.java @@ -0,0 +1,34 @@ +package eu.projnull.spelis.svci.commands.handlers; + +import com.mojang.brigadier.builder.ArgumentBuilder; +import eu.projnull.spelis.svci.Intercom; +import eu.projnull.spelis.svci.commands.Handler; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.java.JavaPlugin; + +public class AboutCommand implements Handler { + @Override + public ArgumentBuilder buildCommand() { + return Commands.literal("about").executes(ctx -> { // <-- attach here + CommandSourceStack source = ctx.getSource(); + CommandSender sender = source.getSender(); + + Plugin plugin = JavaPlugin.getPlugin(Intercom.class); + PluginDescriptionFile desc = plugin.getDescription(); + + String pluginName = "§e" + desc.getName(); + String version = "§a" + desc.getVersion(); + + String authors = String.join(", ", desc.getAuthors()); + String authorText = "§7By §b" + authors; + + sender.sendMessage(pluginName + " §fVersion §f" + version + "\n" + authorText); + + return 1; + }); + } +} diff --git a/src/main/java/eu/projnull/spelis/svci/commands/handlers/FileBroadcastHandler.java b/src/main/java/eu/projnull/spelis/svci/commands/handlers/FileBroadcastHandler.java deleted file mode 100644 index 71f5f74..0000000 --- a/src/main/java/eu/projnull/spelis/svci/commands/handlers/FileBroadcastHandler.java +++ /dev/null @@ -1,115 +0,0 @@ -package eu.projnull.spelis.svci.commands.handlers; - -import de.maxhenkel.voicechat.api.ServerLevel; -import de.maxhenkel.voicechat.api.VoicechatServerApi; -import de.maxhenkel.voicechat.api.audiochannel.AudioPlayer; -import de.maxhenkel.voicechat.api.audiochannel.StaticAudioChannel; -import de.maxhenkel.voicechat.api.VoicechatConnection; -import eu.projnull.spelis.svci.Intercom; -import eu.projnull.spelis.svci.commands.BroadcastTypeHandler; -import eu.projnull.spelis.svci.voice.BroadcasterState; -import eu.projnull.spelis.svci.voice.BroadcasterState.Broadcaster; -import eu.projnull.spelis.svci.voice.BroadcasterState.Broadcaster.BroadcastType; -import eu.projnull.spelis.svci.misc.OggDecoder; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.craftbukkit.CraftWorld; - -import java.io.File; -import java.util.UUID; - -public class FileBroadcastHandler implements BroadcastTypeHandler { - - @Override - public boolean execute(CommandSender sender, String[] args) { - if (args.length < 3) { - sender.sendMessage("§cUsage: /intercom file "); - return true; - } - - String fileName = args[1]; - World world = Bukkit.getWorld(args[2]); - if (world == null) { - sender.sendMessage("§cWorld not found."); - return true; - } - - if (BroadcasterState.inst().isBroadcastActive(world.getUID())) { - sender.sendMessage("§cA broadcast is already active in this world."); - return true; - } - - File soundFile = new File(Intercom.getPlugin(Intercom.class).getDataFolder(), "sounds/" + fileName); - if (!soundFile.exists()) { - Intercom.LOGGER.warn("File not found: {}", soundFile.getAbsoluteFile()); - sender.sendMessage("§cFile not found: sounds/" + fileName); - return true; - } - - sender.sendMessage("§7Loading audio file..."); - - Bukkit.getScheduler().runTaskAsynchronously(Intercom.getPlugin(Intercom.class), () -> { - short[] pcm; - try { - pcm = OggDecoder.decode(soundFile); - } catch (Exception e) { - sender.sendMessage("§cFailed to decode audio file: " + e.getMessage()); - return; - } - - long durationMillis = pcm.length / 48L; - - Bukkit.getScheduler().runTask(Intercom.getPlugin(Intercom.class), () -> { - if (BroadcasterState.inst().isBroadcastActive(world.getUID())) { - sender.sendMessage("§cA broadcast started in this world while the file was loading."); - return; - } - - VoicechatServerApi api = Intercom.getPlugin(Intercom.class).getVoicechatPlugin().getVoicechatServerApi(); - if (api == null) { - sender.sendMessage("§cVoice chat API is not available."); - return; - } - - de.maxhenkel.voicechat.api.ServerLevel level = api.fromServerLevel(world); - - Broadcaster broadcaster = new Broadcaster( - null, - world.getUID(), - BroadcastType.FILE, - durationMillis, - fileName - ); - BroadcasterState.inst().startBroadcast(broadcaster); - - for (Player p : world.getPlayers()) { - VoicechatConnection connection = api.getConnectionOf(p.getUniqueId()); - if (connection == null) continue; - - StaticAudioChannel channel = api.createStaticAudioChannel(UUID.randomUUID(), level, connection); - if (channel == null) continue; - - AudioPlayer audioPlayer = api.createAudioPlayer(channel, api.createEncoder(), pcm); - audioPlayer.startPlaying(); - } - - sender.sendMessage("§aFile broadcast started: §f" + fileName + " §ain §f" + world.getName()); - - Bukkit.getScheduler().runTaskLaterAsynchronously( - Intercom.getPlugin(Intercom.class), - () -> BroadcasterState.inst().stopBroadcastWithMessage(world.getUID(), "§aThe broadcast has ended."), - durationMillis / 50L - ); - }); - }); - - return true; - } - - @Override - public String getName() { - return "file"; - } -} diff --git a/src/main/java/eu/projnull/spelis/svci/commands/handlers/FileCommand.java b/src/main/java/eu/projnull/spelis/svci/commands/handlers/FileCommand.java new file mode 100644 index 0000000..3d7fb4c --- /dev/null +++ b/src/main/java/eu/projnull/spelis/svci/commands/handlers/FileCommand.java @@ -0,0 +1,106 @@ +package eu.projnull.spelis.svci.commands.handlers; + +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.ArgumentBuilder; +import de.maxhenkel.voicechat.api.VoicechatConnection; +import de.maxhenkel.voicechat.api.VoicechatServerApi; +import de.maxhenkel.voicechat.api.audiochannel.AudioPlayer; +import de.maxhenkel.voicechat.api.audiochannel.StaticAudioChannel; +import eu.projnull.spelis.svci.Intercom; +import eu.projnull.spelis.svci.commands.Handler; +import eu.projnull.spelis.svci.commands.Helpers; +import eu.projnull.spelis.svci.misc.OggDecoder; +import eu.projnull.spelis.svci.voice.BroadcasterState; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.io.File; +import java.util.Objects; +import java.util.UUID; + +public class FileCommand implements Handler { + @Override + public ArgumentBuilder buildCommand() { + return Commands.literal("file").requires(cs->cs.getSender().hasPermission("svcintercom.broadcast.start")).then(Commands.argument("filename", StringArgumentType.string()).suggests((ctx, builder) -> { + Helpers.getAllSoundsSuggestion(builder); + return builder.buildFuture(); + }).then(Commands.argument("world", StringArgumentType.word()).suggests((ctx, builder) -> { + Helpers.getAllWorldsSuggestion(builder); + return builder.buildFuture(); + }).executes(ctx -> { + CommandSourceStack source = ctx.getSource(); + CommandSender sender = source.getSender(); + String filename = StringArgumentType.getString(ctx, "filename"); + World world = Bukkit.getWorld(StringArgumentType.getString(ctx, "world")); + + if (world == null) { + sender.sendMessage("§cWorld not found."); + return 0; + } + + if (BroadcasterState.inst().isBroadcastActive(world.getUID())) { + sender.sendMessage("§cA broadcast is already active in this world."); + return 0; + } + + File soundFile = new File(Intercom.getPlugin(Intercom.class).getDataFolder(), "sounds/" + filename); + if (!soundFile.exists()) { + Intercom.LOGGER.warn("File not found: {}", soundFile.getAbsoluteFile()); + sender.sendMessage("§cFile not found."); + return 0; + } + + Bukkit.getScheduler().runTaskAsynchronously(Intercom.getPlugin(Intercom.class), () -> { + short[] pcm; + try { + pcm = OggDecoder.decode(soundFile); + } catch (Exception e) { + sender.sendMessage("§cFailed to decode file."); + Intercom.LOGGER.warn("File not found: {}", e.getMessage()); + return; + } + + long durationMillis = pcm.length / 48L; + + Bukkit.getScheduler().runTask(Intercom.getPlugin(Intercom.class), () -> { + if (BroadcasterState.inst().isBroadcastActive(world.getUID())) { + sender.sendMessage("§cA broadcast is already active!"); + return; + } + + VoicechatServerApi api = Objects.requireNonNull(Intercom.getPlugin(Intercom.class).getVoicechatPlugin()).getVoicechatServerApi(); + if (api == null) { + sender.sendMessage("§cSimple Voice Chat API not available."); + return; + } + + de.maxhenkel.voicechat.api.ServerLevel level = api.fromServerLevel(world); + + BroadcasterState.Broadcaster broadcaster = new BroadcasterState.Broadcaster(null, world.getUID(), BroadcasterState.Broadcaster.BroadcastType.FILE, durationMillis, filename); + BroadcasterState.inst().startBroadcast(broadcaster); + + for (Player p : world.getPlayers()) { + VoicechatConnection connection = api.getConnectionOf(p.getUniqueId()); + if (connection == null) continue; + + StaticAudioChannel channel = api.createStaticAudioChannel(UUID.randomUUID(), level, connection); + if (channel == null) continue; + + AudioPlayer audioPlayer = api.createAudioPlayer(channel, api.createEncoder(), pcm); + audioPlayer.startPlaying(); + } + + sender.sendMessage("§aFile broadcast started: §f" + filename + " §ain §f" + world.getName()); + + Bukkit.getScheduler().runTaskLaterAsynchronously(Intercom.getPlugin(Intercom.class), () -> BroadcasterState.inst().stopBroadcastWithMessage(world.getUID(), "§aThe broadcast has ended."), durationMillis / 50L); + }); + }); + + return 1; + }))); + } +} diff --git a/src/main/java/eu/projnull/spelis/svci/commands/handlers/InfoBroadcastHandler.java b/src/main/java/eu/projnull/spelis/svci/commands/handlers/InfoBroadcastHandler.java deleted file mode 100644 index 7712c39..0000000 --- a/src/main/java/eu/projnull/spelis/svci/commands/handlers/InfoBroadcastHandler.java +++ /dev/null @@ -1,49 +0,0 @@ -package eu.projnull.spelis.svci.commands.handlers; - -import eu.projnull.spelis.svci.commands.BroadcastTypeHandler; -import eu.projnull.spelis.svci.voice.BroadcasterState; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.command.CommandSender; - -public class InfoBroadcastHandler implements BroadcastTypeHandler { - - @Override - public boolean execute(CommandSender sender, String[] args) { - - if (args.length < 2) { - sender.sendMessage("§cUsage: /intercom info "); - return true; - } - - World world = Bukkit.getWorld(args[1]); - if (world == null) { - sender.sendMessage("§cWorld not found."); - return true; - } - - BroadcasterState.Broadcaster b = BroadcasterState.inst().getBroadcast(world.getUID()); - - if (b == null) { - sender.sendMessage("§aNo active broadcast in this world."); - return true; - } - - String type = b.getType().name(); - String owner = b.getType() == BroadcasterState.Broadcaster.BroadcastType.LIVE ? - Bukkit.getOfflinePlayer(b.getPlayerId()).getName() : b.getFileName(); - long secondsLeft = (b.getEndTimeMillis() - System.currentTimeMillis()) / 1000; - - sender.sendMessage("§eBroadcast info:"); - sender.sendMessage(" §7Type: §f" + type); - sender.sendMessage(" §7Owner/File: §f" + owner); - sender.sendMessage(" §7Time remaining: §f" + secondsLeft + "s"); - - return true; - } - - @Override - public String getName() { - return "info"; - } -} diff --git a/src/main/java/eu/projnull/spelis/svci/commands/handlers/InfoCommand.java b/src/main/java/eu/projnull/spelis/svci/commands/handlers/InfoCommand.java new file mode 100644 index 0000000..a256cb8 --- /dev/null +++ b/src/main/java/eu/projnull/spelis/svci/commands/handlers/InfoCommand.java @@ -0,0 +1,48 @@ +package eu.projnull.spelis.svci.commands.handlers; + +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.ArgumentBuilder; +import eu.projnull.spelis.svci.commands.Handler; +import eu.projnull.spelis.svci.commands.Helpers; +import eu.projnull.spelis.svci.voice.BroadcasterState; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.command.CommandSender; + +public class InfoCommand implements Handler { + @Override + public ArgumentBuilder buildCommand() { + return Commands.literal("info").requires(cs->cs.getSender().hasPermission("svcintercom.broadcast.info")).then(Commands.argument("world", StringArgumentType.word()).suggests((ctx, builder) -> { + Helpers.getActiveWorldsSuggestion(builder); + return builder.buildFuture(); + }).executes(ctx -> { + CommandSourceStack source = ctx.getSource(); + CommandSender sender = source.getSender(); + String worldName = ctx.getArgument("world", String.class); + World world = Bukkit.getWorld(worldName); + if (world == null) { + sender.sendMessage("§cWorld not found."); + return 0; + } + + BroadcasterState.Broadcaster b = BroadcasterState.inst().getBroadcast(world.getUID()); + if (b == null) { + sender.sendMessage(String.format("§cNo active broadcast in %s.", worldName)); + return 0; + } + + String type = b.getType().name(); + String owner = b.getType() == BroadcasterState.Broadcaster.BroadcastType.LIVE ? Bukkit.getOfflinePlayer(b.getPlayerId()).getName() : b.getFileName(); + long secondsLeft = (b.getEndTimeMillis() - System.currentTimeMillis()) / 1000; + + sender.sendMessage("§eBroadcast info:"); + sender.sendMessage(" §7Type: §f" + type); + sender.sendMessage(" §7Owner/File: §f" + owner); + sender.sendMessage(" §7Time remaining: §f" + secondsLeft + "s"); + + return 1; + }).build()); + } +} diff --git a/src/main/java/eu/projnull/spelis/svci/commands/handlers/LiveBroadcastHandler.java b/src/main/java/eu/projnull/spelis/svci/commands/handlers/LiveBroadcastHandler.java deleted file mode 100644 index 798ac66..0000000 --- a/src/main/java/eu/projnull/spelis/svci/commands/handlers/LiveBroadcastHandler.java +++ /dev/null @@ -1,78 +0,0 @@ -package eu.projnull.spelis.svci.commands.handlers; - -import eu.projnull.spelis.svci.Intercom; -import eu.projnull.spelis.svci.commands.BroadcastTypeHandler; -import eu.projnull.spelis.svci.voice.BroadcasterState; -import eu.projnull.spelis.svci.voice.BroadcasterState.Broadcaster; -import eu.projnull.spelis.svci.voice.BroadcasterState.Broadcaster.BroadcastType; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -public class LiveBroadcastHandler implements BroadcastTypeHandler { - - @Override - public boolean execute(CommandSender sender, String[] args) { - - if (args.length < 4) { - sender.sendMessage("§cUsage: /intercom live "); - return true; - } - - Player target = Bukkit.getPlayer(args[1]); - if (target == null) { - sender.sendMessage("§cPlayer not found."); - return true; - } - - World world = Bukkit.getWorld(args[2]); - if (world == null) { - sender.sendMessage("§cWorld not found."); - return true; - } - - int seconds; - try { - seconds = Integer.parseInt(args[3]); - } catch (NumberFormatException e) { - sender.sendMessage("§cInvalid duration."); - return true; - } - - if (seconds <= 0 || seconds > 300) { // optional max - sender.sendMessage("§cDuration must be between 1 and 300 seconds."); - return true; - } - - if (BroadcasterState.inst().isBroadcastActive(world.getUID())) { - sender.sendMessage("§cA broadcast is already active in this world."); - return true; - } - - Broadcaster broadcaster = new Broadcaster( - target.getUniqueId(), - world.getUID(), - BroadcastType.LIVE, - seconds * 1000L, - null - ); - - BroadcasterState.inst().startBroadcast(broadcaster); - sender.sendMessage("§aLive broadcast started for " + target.getName() + " in world " + world.getName()); - - // Schedule auto-stop - Bukkit.getScheduler().runTaskLaterAsynchronously( - Intercom.getPlugin(Intercom.class), - () -> BroadcasterState.inst().stopBroadcastWithMessage(world.getUID(), "§aThe broadcast has expired"), - seconds * 20L - ); - - return true; - } - - @Override - public String getName() { - return "live"; - } -} diff --git a/src/main/java/eu/projnull/spelis/svci/commands/handlers/LiveCommand.java b/src/main/java/eu/projnull/spelis/svci/commands/handlers/LiveCommand.java new file mode 100644 index 0000000..a220b11 --- /dev/null +++ b/src/main/java/eu/projnull/spelis/svci/commands/handlers/LiveCommand.java @@ -0,0 +1,59 @@ +package eu.projnull.spelis.svci.commands.handlers; + +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.ArgumentBuilder; +import eu.projnull.spelis.svci.Intercom; +import eu.projnull.spelis.svci.commands.Handler; +import eu.projnull.spelis.svci.commands.Helpers; +import eu.projnull.spelis.svci.voice.BroadcasterState; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class LiveCommand implements Handler { + + @Override + public ArgumentBuilder buildCommand() { + return Commands.literal("live").requires(cs->cs.getSender().hasPermission("svcintercom.broadcast.start")).then(Commands.argument("player", ArgumentTypes.player()).then(Commands.argument("world", StringArgumentType.word()).suggests((ctx, builder) -> { + Helpers.getAllWorldsSuggestion(builder); + return builder.buildFuture(); + }).then(Commands.argument("duration", IntegerArgumentType.integer(1, 300)).executes(ctx -> { + CommandSourceStack source = ctx.getSource(); + CommandSender sender = source.getSender(); + Player player = ctx.getArgument("player", PlayerSelectorArgumentResolver.class).resolve(ctx.getSource()).getFirst(); + World world = Bukkit.getWorld(StringArgumentType.getString(ctx, "world")); + int seconds = ctx.getArgument("duration", int.class); + + if (world == null) { + sender.sendMessage("§cWorld not found."); + return 0; + } + if (seconds <= 0 || seconds > 300) { + sender.sendMessage("§cInvalid duration (must be 1-300 seconds)"); + return 0; + } + + if (BroadcasterState.inst().isBroadcastActive(world.getUID())) { + sender.sendMessage("§cA broadcast is already active in this world."); + return 0; + } + + BroadcasterState.Broadcaster broadcaster = new BroadcasterState.Broadcaster(player.getUniqueId(), world.getUID(), BroadcasterState.Broadcaster.BroadcastType.LIVE, seconds * 1000L, null); + + BroadcasterState.inst().startBroadcast(broadcaster); + sender.sendMessage("§aLive broadcast started for " + player.getName() + " in world " + world.getName()); + + // Schedule auto-stop + Bukkit.getScheduler().runTaskLaterAsynchronously(Intercom.getPlugin(Intercom.class), () -> BroadcasterState.inst().stopBroadcastWithMessage(world.getUID(), "§aThe broadcast has expired"), seconds * 20L); + + + return 1; + })))); + } +} diff --git a/src/main/java/eu/projnull/spelis/svci/commands/handlers/StopBroadcastHandler.java b/src/main/java/eu/projnull/spelis/svci/commands/handlers/StopBroadcastHandler.java deleted file mode 100644 index c806b47..0000000 --- a/src/main/java/eu/projnull/spelis/svci/commands/handlers/StopBroadcastHandler.java +++ /dev/null @@ -1,38 +0,0 @@ -package eu.projnull.spelis.svci.commands.handlers; - -import eu.projnull.spelis.svci.commands.BroadcastTypeHandler; -import eu.projnull.spelis.svci.voice.BroadcasterState; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.command.CommandSender; - -public class StopBroadcastHandler implements BroadcastTypeHandler { - - @Override - public boolean execute(CommandSender sender, String[] args) { - - if (args.length < 2) { - sender.sendMessage("§cUsage: /intercom stop "); - return true; - } - - World world = Bukkit.getWorld(args[1]); - if (world == null) { - sender.sendMessage("§cWorld not found."); - return true; - } - - if (!BroadcasterState.inst().isBroadcastActive(world.getUID())) { - sender.sendMessage("§cNo active broadcast in this world."); - return true; - } - - BroadcasterState.inst().stopBroadcastWithMessage(world.getUID(), "§aThe broadcast has been stopped."); - return true; - } - - @Override - public String getName() { - return "stop"; - } -} diff --git a/src/main/java/eu/projnull/spelis/svci/commands/handlers/StopCommand.java b/src/main/java/eu/projnull/spelis/svci/commands/handlers/StopCommand.java new file mode 100644 index 0000000..559234f --- /dev/null +++ b/src/main/java/eu/projnull/spelis/svci/commands/handlers/StopCommand.java @@ -0,0 +1,45 @@ +package eu.projnull.spelis.svci.commands.handlers; + +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.ArgumentBuilder; +import eu.projnull.spelis.svci.commands.Handler; +import eu.projnull.spelis.svci.commands.Helpers; +import eu.projnull.spelis.svci.voice.BroadcasterState; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.command.CommandSender; + +public class StopCommand implements Handler { + @Override + public ArgumentBuilder buildCommand() { + return Commands.literal("stop").requires(cs->cs.getSender().hasPermission("svcintercom.broadcast.stop")) + .then(Commands.argument("world", StringArgumentType.word()) + .suggests((ctx, builder) -> { + Helpers.getActiveWorldsSuggestion(builder); + return builder.buildFuture(); + }) + .executes(ctx -> { // <-- attach here + CommandSourceStack source = ctx.getSource(); + CommandSender sender = source.getSender(); + String worldName = StringArgumentType.getString(ctx, "world"); + + World world = Bukkit.getWorld(worldName); + if (world == null) { + sender.sendMessage("§cWorld not found."); + return 0; + } + + if (!BroadcasterState.inst().isBroadcastActive(world.getUID())) { + sender.sendMessage("§cNo active broadcast in this world."); + return 0; + } + + BroadcasterState.inst().stopBroadcastWithMessage(world.getUID(), "§aThe broadcast has been stopped."); + sender.sendMessage("§aBroadcast stopped in " + world.getName()); + return 1; + }) + ); + } +} diff --git a/src/main/java/eu/projnull/spelis/svci/misc/BroadcastHudTask.java b/src/main/java/eu/projnull/spelis/svci/misc/BroadcastHudTask.java index aecf751..017f565 100644 --- a/src/main/java/eu/projnull/spelis/svci/misc/BroadcastHudTask.java +++ b/src/main/java/eu/projnull/spelis/svci/misc/BroadcastHudTask.java @@ -11,7 +11,6 @@ import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitTask; -import java.util.UUID; import java.util.concurrent.TimeUnit; public final class BroadcastHudTask { @@ -46,8 +45,8 @@ public final class BroadcastHudTask { private Component buildMessage(Broadcaster broadcaster) { long remaining = broadcaster.getEndTimeMillis() - System.currentTimeMillis(); - long minutes = TimeUnit.MILLISECONDS.toMinutes(remaining); - long seconds = TimeUnit.MILLISECONDS.toSeconds(remaining) % 60; + long minutes = TimeUnit.MILLISECONDS.toMinutes(remaining); + long seconds = TimeUnit.MILLISECONDS.toSeconds(remaining) % 60; String timer = String.format("%d:%02d remaining", minutes, seconds); diff --git a/src/main/java/eu/projnull/spelis/svci/misc/OggDecoder.java b/src/main/java/eu/projnull/spelis/svci/misc/OggDecoder.java index 6ea5b76..07db0cb 100644 --- a/src/main/java/eu/projnull/spelis/svci/misc/OggDecoder.java +++ b/src/main/java/eu/projnull/spelis/svci/misc/OggDecoder.java @@ -1,30 +1,23 @@ package eu.projnull.spelis.svci.misc; -import javax.sound.sampled.*; -import java.io.File; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; import java.io.ByteArrayOutputStream; +import java.io.File; public final class OggDecoder { - private OggDecoder() {} + private OggDecoder() { + } public static short[] decode(File file) throws Exception { try (AudioInputStream raw = AudioSystem.getAudioInputStream(file)) { AudioFormat source = raw.getFormat(); - AudioFormat target = new AudioFormat( - AudioFormat.Encoding.PCM_SIGNED, - source.getSampleRate(), - 16, - source.getChannels(), - source.getChannels() * 2, - source.getSampleRate(), - false - ); - - try (AudioInputStream pcm = - AudioSystem.getAudioInputStream(target, raw); - ByteArrayOutputStream buffer = new ByteArrayOutputStream()) { + AudioFormat target = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, source.getSampleRate(), 16, source.getChannels(), source.getChannels() * 2, source.getSampleRate(), false); + + try (AudioInputStream pcm = AudioSystem.getAudioInputStream(target, raw); ByteArrayOutputStream buffer = new ByteArrayOutputStream()) { byte[] data = new byte[4096]; int read; diff --git a/src/main/java/eu/projnull/spelis/svci/voice/BroadcasterState.java b/src/main/java/eu/projnull/spelis/svci/voice/BroadcasterState.java index 2512af4..5aafe8a 100644 --- a/src/main/java/eu/projnull/spelis/svci/voice/BroadcasterState.java +++ b/src/main/java/eu/projnull/spelis/svci/voice/BroadcasterState.java @@ -4,9 +4,11 @@ import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.entity.Player; -import java.util.UUID; import java.util.Map; +import java.util.Objects; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; public final class BroadcasterState { @@ -15,17 +17,18 @@ public final class BroadcasterState { // Key = worldId private final Map broadcasts = new ConcurrentHashMap<>(); - private BroadcasterState() {} + private BroadcasterState() { + } public static BroadcasterState inst() { return INSTANCE; } - public boolean startBroadcast(Broadcaster broadcaster) { + public void startBroadcast(Broadcaster broadcaster) { UUID worldId = broadcaster.getWorldId(); // only allow one broadcast per world - return broadcasts.putIfAbsent(worldId, broadcaster) == null; + broadcasts.putIfAbsent(worldId, broadcaster); } public Broadcaster getBroadcast(UUID worldId) { @@ -41,6 +44,16 @@ public final class BroadcasterState { return b; } + public Stream activeBroadcastWorldNames() { + long now = System.currentTimeMillis(); + + return broadcasts.values().stream() + .filter(b -> b.getEndTimeMillis() > now) + .map(b -> Bukkit.getWorld(b.getWorldId())) + .filter(Objects::nonNull) + .map(World::getName); + } + public void stopBroadcastWithMessage(UUID worldId, String message) { Broadcaster b = broadcasts.remove(worldId); if (b == null) return; diff --git a/src/main/java/eu/projnull/spelis/svci/voice/FileBroadcast.java b/src/main/java/eu/projnull/spelis/svci/voice/FileBroadcast.java deleted file mode 100644 index 07cb5f5..0000000 --- a/src/main/java/eu/projnull/spelis/svci/voice/FileBroadcast.java +++ /dev/null @@ -1,56 +0,0 @@ -package eu.projnull.spelis.svci.voice; - -import de.maxhenkel.voicechat.api.VoicechatServerApi; -import de.maxhenkel.voicechat.api.audiochannel.AudioPlayer; -import de.maxhenkel.voicechat.api.audiochannel.StaticAudioChannel; -import de.maxhenkel.voicechat.api.VoicechatConnection; -import eu.projnull.spelis.svci.misc.OggDecoder; -import org.bukkit.World; -import org.bukkit.entity.Player; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -public class FileBroadcast { - - private final List players = new ArrayList<>(); - - /** - * Starts playing an audio file to all players currently in the world. - * New players joining mid-broadcast won't hear it (acceptable for now). - * - * @param api the SVC server API instance - * @param world the target world - * @param audioFile the .ogg file to play - * @param onDone called when all players' audio finishes - */ - public void start(VoicechatServerApi api, World world, File audioFile, Runnable onDone) throws Exception { - short[] pcm = OggDecoder.decode(audioFile); // see below - - for (Player player : world.getPlayers()) { - VoicechatConnection connection = api.getConnectionOf(player.getUniqueId()); - if (connection == null) continue; // player doesn't have SVC mod - - StaticAudioChannel channel = api.createStaticAudioChannel( - UUID.randomUUID(), - api.fromServerLevel(/* ServerLevel */ null), // explained below - connection - ); - if (channel == null) continue; - - AudioPlayer ap = api.createAudioPlayer(channel, api.createEncoder(), pcm); - ap.setOnStopped(onDone); - ap.startPlaying(); - players.add(ap); - } - } - - public void stop() { - for (AudioPlayer ap : players) { - ap.stopPlaying(); - } - players.clear(); - } -} diff --git a/src/main/java/eu/projnull/spelis/svci/voice/VoicePlugin.java b/src/main/java/eu/projnull/spelis/svci/voice/VoicePlugin.java index ba11792..55b19ca 100644 --- a/src/main/java/eu/projnull/spelis/svci/voice/VoicePlugin.java +++ b/src/main/java/eu/projnull/spelis/svci/voice/VoicePlugin.java @@ -8,15 +8,11 @@ import de.maxhenkel.voicechat.api.events.EventRegistration; import de.maxhenkel.voicechat.api.events.MicrophonePacketEvent; import de.maxhenkel.voicechat.api.events.VoicechatServerStartedEvent; import eu.projnull.spelis.svci.Intercom; -import org.bukkit.permissions.Permission; -import org.bukkit.permissions.PermissionDefault; import java.util.UUID; public class VoicePlugin implements VoicechatPlugin { - - public static Permission BROADCAST_PERMISSION = new Permission("svcintercom.broadcast", PermissionDefault.OP); private VoicechatServerApi voicechatServerApi; public VoicechatServerApi getVoicechatServerApi() { @@ -36,9 +32,7 @@ public class VoicePlugin implements VoicechatPlugin { @Override public void registerEvents(EventRegistration registration) { registration.registerEvent(MicrophonePacketEvent.class, this::onMicPacket); - registration.registerEvent(VoicechatServerStartedEvent.class, e->{ - this.voicechatServerApi = e.getVoicechat(); - }); + registration.registerEvent(VoicechatServerStartedEvent.class, e -> this.voicechatServerApi = e.getVoicechat()); } private void onMicPacket(MicrophonePacketEvent event) { @@ -53,16 +47,13 @@ public class VoicePlugin implements VoicechatPlugin { UUID worldId = player.getWorld().getUID(); - BroadcasterState.Broadcaster broadcaster = - BroadcasterState.inst().getBroadcast(worldId); + BroadcasterState.Broadcaster broadcaster = BroadcasterState.inst().getBroadcast(worldId); if (broadcaster == null) return; - if (broadcaster.getType() != BroadcasterState.Broadcaster.BroadcastType.LIVE) - return; + if (broadcaster.getType() != BroadcasterState.Broadcaster.BroadcastType.LIVE) return; - if (!player.getUniqueId().equals(broadcaster.getPlayerId())) - return; + if (!player.getUniqueId().equals(broadcaster.getPlayerId())) return; event.cancel(); @@ -73,15 +64,11 @@ public class VoicePlugin implements VoicechatPlugin { continue; } - VoicechatConnection connection = - api.getConnectionOf(online.getUniqueId()); + VoicechatConnection connection = api.getConnectionOf(online.getUniqueId()); if (connection == null) continue; - api.sendStaticSoundPacketTo( - connection, - event.getPacket().toStaticSoundPacket() - ); + api.sendStaticSoundPacketTo(connection, event.getPacket().toStaticSoundPacket()); } } } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 866188f..fc740dd 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: svc_intercom -version: '1.0' +version: '1.1' main: eu.projnull.spelis.svci.Intercom api-version: '1.21' prefix: SVCIntercom @@ -13,6 +13,19 @@ depend: [ voicechat ] commands: intercom: description: Manage intercom broadcasts - usage: /intercom ... + usage: /intercom ... permission: svcintercom.broadcast permission-message: You do not have permission to use the intercom. + +permissions: + svcintercom.broadcast: + description: Allows use of /intercom commands + default: true + svcintercom.broadcast.start: + description: Allows player to start a file or live broadcast + default: op + svcintercom.broadcast.stop: + description: Allows player to end a broadcast + default: op + svcintercom.broadcast.info: + description: Shows information about the current broadcast \ No newline at end of file -- cgit v1.3-7-ge9ab