aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authoryoungcoder45 <youngcoder45@gmail.com>2026-03-10 16:52:56 +0530
committerElis Eriksson <spelis.loves.rust@gmail.com>2026-03-12 07:11:01 +0100
commitda8563af76c9b83d14005dbb1cb753ad0ae290b1 (patch)
treeca00ef1e647e2f658aff89b9f2df0a22699f40fd /src
parentfce932eff7f77e3f5a9fa3f6886df8f642d66bd6 (diff)
downloadsvc_intercom-da8563af76c9b83d14005dbb1cb753ad0ae290b1.tar
svc_intercom-da8563af76c9b83d14005dbb1cb753ad0ae290b1.tar.gz
svc_intercom-da8563af76c9b83d14005dbb1cb753ad0ae290b1.tar.bz2
svc_intercom-da8563af76c9b83d14005dbb1cb753ad0ae290b1.tar.lz
svc_intercom-da8563af76c9b83d14005dbb1cb753ad0ae290b1.tar.xz
svc_intercom-da8563af76c9b83d14005dbb1cb753ad0ae290b1.tar.zst
svc_intercom-da8563af76c9b83d14005dbb1cb753ad0ae290b1.zip
optional cords and broadcast are separate now
Diffstat (limited to 'src')
-rw-r--r--src/main/java/eu/projnull/spelis/svci/commands/handlers/FileCommand.java219
-rw-r--r--src/main/java/eu/projnull/spelis/svci/commands/handlers/LiveCommand.java120
-rw-r--r--src/main/java/eu/projnull/spelis/svci/commands/handlers/SpeakerCommand.java100
-rw-r--r--src/main/java/eu/projnull/spelis/svci/voice/BroadcasterState.java15
-rw-r--r--src/main/java/eu/projnull/spelis/svci/voice/VoicePlugin.java17
5 files changed, 329 insertions, 142 deletions
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
index 5a871a3..b172423 100644
--- a/src/main/java/eu/projnull/spelis/svci/commands/handlers/FileCommand.java
+++ b/src/main/java/eu/projnull/spelis/svci/commands/handlers/FileCommand.java
@@ -2,6 +2,7 @@ package eu.projnull.spelis.svci.commands.handlers;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.ArgumentBuilder;
+import com.mojang.brigadier.context.CommandContext;
import de.maxhenkel.voicechat.api.VoicechatConnection;
import de.maxhenkel.voicechat.api.VoicechatServerApi;
import de.maxhenkel.voicechat.api.audiochannel.AudioPlayer;
@@ -11,6 +12,7 @@ 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 eu.projnull.spelis.svci.voice.SpeakerManager;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import io.papermc.paper.command.brigadier.Commands;
import org.bukkit.Bukkit;
@@ -25,110 +27,167 @@ import java.util.UUID;
public class FileCommand implements Handler {
@Override
public ArgumentBuilder<CommandSourceStack, ?> 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"));
+ 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();
+ })
+ // With optional mode parameter
+ .then(Commands.argument("mode", StringArgumentType.word())
+ .suggests((ctx, builder) -> {
+ builder.suggest("global");
+ builder.suggest("speaker");
+ return builder.buildFuture();
+ })
+ .executes(ctx -> executeFileBroadcast(ctx, true))
+ )
+ // Without mode parameter (auto-detect)
+ .executes(ctx -> executeFileBroadcast(ctx, false))
+ )
+ );
+ }
+
+ private int executeFileBroadcast(CommandContext<CommandSourceStack> ctx, boolean hasMode) {
+ 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;
+ }
- if (world == null) {
- sender.sendMessage("§cWorld not found.");
+ // Determine broadcast mode
+ BroadcasterState.Broadcaster.BroadcastMode mode;
+ if (hasMode) {
+ String modeStr = StringArgumentType.getString(ctx, "mode").toLowerCase();
+ if (modeStr.equals("global")) {
+ mode = BroadcasterState.Broadcaster.BroadcastMode.GLOBAL;
+ } else if (modeStr.equals("speaker")) {
+ mode = BroadcasterState.Broadcaster.BroadcastMode.SPEAKER;
+ } else {
+ sender.sendMessage("§cInvalid mode. Use 'global' or 'speaker'");
return 0;
}
+ } else {
+ // Auto-detect: use speaker mode if speakers exist, otherwise global
+ boolean hasSpeakers = !SpeakerManager.inst().getSpeakers(world.getUID()).isEmpty();
+ mode = hasSpeakers ? BroadcasterState.Broadcaster.BroadcastMode.SPEAKER : BroadcasterState.Broadcaster.BroadcastMode.GLOBAL;
+ }
- if (BroadcasterState.inst().isBroadcastActive(world.getUID())) {
- sender.sendMessage("§cA broadcast is already active in this world.");
- return 0;
+ // Warn if speaker mode selected but no speakers exist
+ if (mode == BroadcasterState.Broadcaster.BroadcastMode.SPEAKER) {
+ if (SpeakerManager.inst().getSpeakers(world.getUID()).isEmpty()) {
+ sender.sendMessage("§eWarning: Speaker mode selected but no speakers exist in this world. No one will hear the broadcast!");
}
+ }
- 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;
+ final BroadcasterState.Broadcaster.BroadcastMode finalMode = mode;
+
+ 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("Failed to decode file: {}", e.getMessage());
+ return;
}
- 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());
+ 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;
}
- long durationMillis = pcm.length / 48L;
+ VoicechatServerApi api = Objects.requireNonNull(Intercom.getPlugin(Intercom.class).getVoicechatPlugin()).getVoicechatServerApi();
+ if (api == null) {
+ sender.sendMessage("§cSimple Voice Chat API not available.");
+ return;
+ }
- Bukkit.getScheduler().runTask(Intercom.getPlugin(Intercom.class), () -> {
- if (BroadcasterState.inst().isBroadcastActive(world.getUID())) {
- sender.sendMessage("§cA broadcast is already active!");
- return;
- }
+ de.maxhenkel.voicechat.api.ServerLevel level = api.fromServerLevel(world);
- VoicechatServerApi api = Objects.requireNonNull(Intercom.getPlugin(Intercom.class).getVoicechatPlugin()).getVoicechatServerApi();
- if (api == null) {
- sender.sendMessage("§cSimple Voice Chat API not available.");
- return;
- }
+ BroadcasterState.Broadcaster broadcaster = new BroadcasterState.Broadcaster(
+ null,
+ world.getUID(),
+ BroadcasterState.Broadcaster.BroadcastType.FILE,
+ finalMode,
+ durationMillis,
+ filename
+ );
+ BroadcasterState.inst().startBroadcast(broadcaster);
- de.maxhenkel.voicechat.api.ServerLevel level = api.fromServerLevel(world);
+ if (finalMode == BroadcasterState.Broadcaster.BroadcastMode.GLOBAL) {
+ // Global broadcast - everyone hears equally
+ for (Player p : world.getPlayers()) {
+ VoicechatConnection connection = api.getConnectionOf(p.getUniqueId());
+ if (connection == null) continue;
- BroadcasterState.Broadcaster broadcaster = new BroadcasterState.Broadcaster(null, world.getUID(), BroadcasterState.Broadcaster.BroadcastType.FILE, durationMillis, filename);
- BroadcasterState.inst().startBroadcast(broadcaster);
+ StaticAudioChannel channel = api.createStaticAudioChannel(UUID.randomUUID(), level, connection);
+ if (channel == null) continue;
- // Get all speakers in this world
- java.util.List<eu.projnull.spelis.svci.voice.Speaker> speakers = eu.projnull.spelis.svci.voice.SpeakerManager.inst().getSpeakers(world.getUID());
+ AudioPlayer audioPlayer = api.createAudioPlayer(channel, api.createEncoder(), pcm);
+ audioPlayer.startPlaying();
+ }
+ sender.sendMessage("§aFile broadcast started: §f" + filename + " §ain §f" + world.getName() + " §7(global)");
+ } else {
+ // Speaker broadcast - positional audio from speakers
+ java.util.List<eu.projnull.spelis.svci.voice.Speaker> speakers = SpeakerManager.inst().getSpeakers(world.getUID());
- if (speakers.isEmpty()) {
- // Fallback to old behavior if no speakers are defined
- 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;
+ for (eu.projnull.spelis.svci.voice.Speaker speaker : speakers) {
+ org.bukkit.Location speakerLoc = speaker.getLocation();
+ if (speakerLoc == null) continue;
- AudioPlayer audioPlayer = api.createAudioPlayer(channel, api.createEncoder(), pcm);
- audioPlayer.startPlaying();
- }
- sender.sendMessage("§aFile broadcast started: §f" + filename + " §ain §f" + world.getName() + " §7(no speakers, using global audio)");
- } else {
- // Use positional audio from speakers
- for (eu.projnull.spelis.svci.voice.Speaker speaker : speakers) {
- org.bukkit.Location speakerLoc = speaker.getLocation();
- if (speakerLoc == null) continue;
+ de.maxhenkel.voicechat.api.Position position = api.createPosition(
+ speakerLoc.getX(),
+ speakerLoc.getY(),
+ speakerLoc.getZ()
+ );
- de.maxhenkel.voicechat.api.Position position = api.createPosition(
- speakerLoc.getX(),
- speakerLoc.getY(),
- speakerLoc.getZ()
- );
+ de.maxhenkel.voicechat.api.audiochannel.LocationalAudioChannel channel =
+ api.createLocationalAudioChannel(UUID.randomUUID(), level, position);
+
+ if (channel == null) continue;
- de.maxhenkel.voicechat.api.audiochannel.LocationalAudioChannel channel =
- api.createLocationalAudioChannel(UUID.randomUUID(), level, position);
-
- if (channel == null) continue;
+ channel.setDistance((float) speaker.getRange());
- channel.setDistance((float) speaker.getRange());
-
- AudioPlayer audioPlayer = api.createAudioPlayer(channel, api.createEncoder(), pcm);
- audioPlayer.startPlaying();
- }
- sender.sendMessage("§aFile broadcast started: §f" + filename + " §ain §f" + world.getName() + " §7(" + speakers.size() + " speakers)");
+ AudioPlayer audioPlayer = api.createAudioPlayer(channel, api.createEncoder(), pcm);
+ audioPlayer.startPlaying();
}
+ sender.sendMessage("§aFile broadcast started: §f" + filename + " §ain §f" + world.getName() + " §7(" + speakers.size() + " speakers)");
+ }
- Bukkit.getScheduler().runTaskLaterAsynchronously(Intercom.getPlugin(Intercom.class), () -> BroadcasterState.inst().stopBroadcastWithMessage(world.getUID(), "§aThe broadcast has ended."), durationMillis / 50L);
- });
+ Bukkit.getScheduler().runTaskLaterAsynchronously(
+ Intercom.getPlugin(Intercom.class),
+ () -> BroadcasterState.inst().stopBroadcastWithMessage(world.getUID(), "§aThe broadcast has ended."),
+ durationMillis / 50L
+ );
});
+ });
- return 1;
- })));
+ return 1;
}
}
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
index a220b11..aab00f9 100644
--- a/src/main/java/eu/projnull/spelis/svci/commands/handlers/LiveCommand.java
+++ b/src/main/java/eu/projnull/spelis/svci/commands/handlers/LiveCommand.java
@@ -3,10 +3,12 @@ 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 com.mojang.brigadier.context.CommandContext;
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 eu.projnull.spelis.svci.voice.SpeakerManager;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import io.papermc.paper.command.brigadier.Commands;
import io.papermc.paper.command.brigadier.argument.ArgumentTypes;
@@ -20,40 +22,106 @@ public class LiveCommand implements Handler {
@Override
public ArgumentBuilder<CommandSourceStack, ?> 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);
+ 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))
+ // With optional mode parameter
+ .then(Commands.argument("mode", StringArgumentType.word())
+ .suggests((ctx, builder) -> {
+ builder.suggest("global");
+ builder.suggest("speaker");
+ return builder.buildFuture();
+ })
+ .executes(ctx -> executeLiveBroadcast(ctx, true))
+ )
+ // Without mode parameter (defaults to speaker if speakers exist, otherwise global)
+ .executes(ctx -> executeLiveBroadcast(ctx, false))
+ )
+ )
+ );
+ }
- 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;
- }
+ private int executeLiveBroadcast(CommandContext<CommandSourceStack> ctx, boolean hasMode) {
+ CommandSourceStack source = ctx.getSource();
+ CommandSender sender = source.getSender();
+
+ Player player;
+ try {
+ player = ctx.getArgument("player", PlayerSelectorArgumentResolver.class).resolve(ctx.getSource()).getFirst();
+ } catch (Exception e) {
+ sender.sendMessage("§cCould not resolve player");
+ return 0;
+ }
+
+ 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.");
+ if (BroadcasterState.inst().isBroadcastActive(world.getUID())) {
+ sender.sendMessage("§cA broadcast is already active in this world.");
+ return 0;
+ }
+
+ // Determine broadcast mode
+ BroadcasterState.Broadcaster.BroadcastMode mode;
+ if (hasMode) {
+ String modeStr = StringArgumentType.getString(ctx, "mode").toLowerCase();
+ if (modeStr.equals("global")) {
+ mode = BroadcasterState.Broadcaster.BroadcastMode.GLOBAL;
+ } else if (modeStr.equals("speaker")) {
+ mode = BroadcasterState.Broadcaster.BroadcastMode.SPEAKER;
+ } else {
+ sender.sendMessage("§cInvalid mode. Use 'global' or 'speaker'");
return 0;
}
+ } else {
+ // Auto-detect: use speaker mode if speakers exist, otherwise global
+ boolean hasSpeakers = !SpeakerManager.inst().getSpeakers(world.getUID()).isEmpty();
+ mode = hasSpeakers ? BroadcasterState.Broadcaster.BroadcastMode.SPEAKER : BroadcasterState.Broadcaster.BroadcastMode.GLOBAL;
+ }
- BroadcasterState.Broadcaster broadcaster = new BroadcasterState.Broadcaster(player.getUniqueId(), world.getUID(), BroadcasterState.Broadcaster.BroadcastType.LIVE, seconds * 1000L, null);
+ // Warn if speaker mode selected but no speakers exist
+ if (mode == BroadcasterState.Broadcaster.BroadcastMode.SPEAKER) {
+ if (SpeakerManager.inst().getSpeakers(world.getUID()).isEmpty()) {
+ sender.sendMessage("§eWarning: Speaker mode selected but no speakers exist in this world. No one will hear the broadcast!");
+ }
+ }
- BroadcasterState.inst().startBroadcast(broadcaster);
- sender.sendMessage("§aLive broadcast started for " + player.getName() + " in world " + world.getName());
+ BroadcasterState.Broadcaster broadcaster = new BroadcasterState.Broadcaster(
+ player.getUniqueId(),
+ world.getUID(),
+ BroadcasterState.Broadcaster.BroadcastType.LIVE,
+ mode,
+ seconds * 1000L,
+ null
+ );
- // Schedule auto-stop
- Bukkit.getScheduler().runTaskLaterAsynchronously(Intercom.getPlugin(Intercom.class), () -> BroadcasterState.inst().stopBroadcastWithMessage(world.getUID(), "§aThe broadcast has expired"), seconds * 20L);
+ BroadcasterState.inst().startBroadcast(broadcaster);
+
+ String modeText = mode == BroadcasterState.Broadcaster.BroadcastMode.GLOBAL ? "§7(global)" : "§7(speaker)";
+ sender.sendMessage("§aLive broadcast started for §f" + player.getName() + " §ain world §f" + world.getName() + " " + modeText);
+ // Schedule auto-stop
+ Bukkit.getScheduler().runTaskLaterAsynchronously(
+ Intercom.getPlugin(Intercom.class),
+ () -> BroadcasterState.inst().stopBroadcastWithMessage(world.getUID(), "§aThe broadcast has expired"),
+ seconds * 20L
+ );
- return 1;
- }))));
+ return 1;
}
}
diff --git a/src/main/java/eu/projnull/spelis/svci/commands/handlers/SpeakerCommand.java b/src/main/java/eu/projnull/spelis/svci/commands/handlers/SpeakerCommand.java
index b175f4c..b17d857 100644
--- a/src/main/java/eu/projnull/spelis/svci/commands/handlers/SpeakerCommand.java
+++ b/src/main/java/eu/projnull/spelis/svci/commands/handlers/SpeakerCommand.java
@@ -33,6 +33,74 @@ public class SpeakerCommand implements Handler {
return Commands.literal("add")
.requires(cs -> cs.getSender().hasPermission("svcintercom.speaker.add"))
.then(Commands.argument("name", StringArgumentType.word())
+ // Option 1: Just name and range - uses player's current location
+ .then(Commands.argument("range", DoubleArgumentType.doubleArg(1.0, 1000.0))
+ .executes(ctx -> {
+ CommandSender sender = ctx.getSource().getSender();
+ if (!(sender instanceof Player player)) {
+ sender.sendMessage("§cYou must be a player to add a speaker at your location");
+ return 0;
+ }
+
+ String name = StringArgumentType.getString(ctx, "name");
+ double range = DoubleArgumentType.getDouble(ctx, "range");
+
+ Location loc = player.getLocation();
+ World world = loc.getWorld();
+ UUID worldId = world.getUID();
+
+ if (SpeakerManager.inst().speakerExists(worldId, name)) {
+ sender.sendMessage("§cA speaker named '§f" + name + "§c' already exists in this world");
+ return 0;
+ }
+
+ Speaker speaker = new Speaker(worldId, loc.getX(), loc.getY(), loc.getZ(), range, name);
+ SpeakerManager.inst().addSpeaker(speaker);
+
+ sender.sendMessage("§aAdded speaker at your location: §f" + speaker.toString());
+ return 1;
+ })
+ // Option 2: name, range, and world - uses player coords in specified world
+ .then(Commands.argument("world", StringArgumentType.word())
+ .suggests((ctx, builder) -> {
+ Helpers.getAllWorldsSuggestion(builder);
+ return builder.buildFuture();
+ })
+ .executes(ctx -> {
+ CommandSender sender = ctx.getSource().getSender();
+ if (!(sender instanceof Player player)) {
+ sender.sendMessage("§cYou must be a player to use coordinates from your location");
+ return 0;
+ }
+
+ String name = StringArgumentType.getString(ctx, "name");
+ double range = DoubleArgumentType.getDouble(ctx, "range");
+ String worldName = StringArgumentType.getString(ctx, "world");
+
+ World world = Bukkit.getWorld(worldName);
+ if (world == null) {
+ sender.sendMessage("§cWorld not found: §f" + worldName);
+ return 0;
+ }
+
+ Location loc = player.getLocation();
+ UUID worldId = world.getUID();
+
+ if (SpeakerManager.inst().speakerExists(worldId, name)) {
+ sender.sendMessage("§cA speaker named '§f" + name + "§c' already exists in world §f" + worldName);
+ return 0;
+ }
+
+ // Use player's X, Y, Z but in the specified world
+ Speaker speaker = new Speaker(worldId, loc.getX(), loc.getY(), loc.getZ(), range, name);
+ SpeakerManager.inst().addSpeaker(speaker);
+
+ sender.sendMessage("§aAdded speaker in world §f" + worldName + "§a: " + speaker.toString());
+ return 1;
+ })
+ )
+ )
+ // Option 3: Full specification with world and coordinates
.then(Commands.argument("world", StringArgumentType.word())
.suggests((ctx, builder) -> {
Helpers.getAllWorldsSuggestion(builder);
@@ -53,13 +121,13 @@ public class SpeakerCommand implements Handler {
World world = Bukkit.getWorld(worldName);
if (world == null) {
- sender.sendMessage("§cWorld not found: " + worldName);
+ sender.sendMessage("§cWorld not found: §f" + worldName);
return 0;
}
UUID worldId = world.getUID();
if (SpeakerManager.inst().speakerExists(worldId, name)) {
- sender.sendMessage("§cA speaker with that name already exists in this world");
+ sender.sendMessage("§cA speaker named '§f" + name + "§c' already exists in world §f" + worldName);
return 0;
}
@@ -74,34 +142,6 @@ public class SpeakerCommand implements Handler {
)
)
)
- // Add shorthand for current location (player only)
- .then(Commands.argument("range", DoubleArgumentType.doubleArg(1.0, 1000.0))
- .executes(ctx -> {
- CommandSender sender = ctx.getSource().getSender();
- if (!(sender instanceof Player player)) {
- sender.sendMessage("§cYou must be a player to use this command without coordinates");
- return 0;
- }
-
- String name = StringArgumentType.getString(ctx, "name");
- double range = DoubleArgumentType.getDouble(ctx, "range");
-
- Location loc = player.getLocation();
- World world = loc.getWorld();
- UUID worldId = world.getUID();
-
- if (SpeakerManager.inst().speakerExists(worldId, name)) {
- sender.sendMessage("§cA speaker with that name already exists in this world");
- return 0;
- }
-
- Speaker speaker = new Speaker(worldId, loc.getX(), loc.getY(), loc.getZ(), range, name);
- SpeakerManager.inst().addSpeaker(speaker);
-
- sender.sendMessage("§aAdded speaker: §f" + speaker.toString());
- return 1;
- })
- )
);
}
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 5aafe8a..556afaf 100644
--- a/src/main/java/eu/projnull/spelis/svci/voice/BroadcasterState.java
+++ b/src/main/java/eu/projnull/spelis/svci/voice/BroadcasterState.java
@@ -75,6 +75,7 @@ public final class BroadcasterState {
private final UUID playerId; // null for FILE mode
private final UUID worldId;
private final BroadcastType type;
+ private final BroadcastMode mode; // GLOBAL or SPEAKER
private final long endTimeMillis;
private final String fileName; // only for FILE mode
@@ -82,6 +83,7 @@ public final class BroadcasterState {
UUID playerId,
UUID worldId,
BroadcastType type,
+ BroadcastMode mode,
long durationMillis,
String fileName
) {
@@ -97,9 +99,13 @@ public final class BroadcasterState {
if (type == BroadcastType.FILE && fileName == null)
throw new IllegalArgumentException("FILE requires fileName");
+ if (mode == null)
+ throw new IllegalArgumentException("mode cannot be null");
+
this.playerId = playerId;
this.worldId = worldId;
this.type = type;
+ this.mode = mode;
this.endTimeMillis = System.currentTimeMillis() + durationMillis;
this.fileName = fileName;
}
@@ -128,9 +134,18 @@ public final class BroadcasterState {
return endTimeMillis;
}
+ public BroadcastMode getMode() {
+ return mode;
+ }
+
public enum BroadcastType {
LIVE,
FILE
}
+
+ public enum BroadcastMode {
+ GLOBAL, // Everyone hears equally, ignores speakers
+ SPEAKER // Only audible near speakers
+ }
}
}
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 ff6575e..88295d2 100644
--- a/src/main/java/eu/projnull/spelis/svci/voice/VoicePlugin.java
+++ b/src/main/java/eu/projnull/spelis/svci/voice/VoicePlugin.java
@@ -59,18 +59,15 @@ public class VoicePlugin implements VoicechatPlugin {
VoicechatServerApi api = event.getVoicechat();
- // Get all speakers in this world
- java.util.List<Speaker> speakers = SpeakerManager.inst().getSpeakers(worldId);
-
- if (speakers.isEmpty()) {
- // Fallback to old behavior if no speakers are defined
+ // Check broadcast mode
+ if (broadcaster.getMode() == BroadcasterState.Broadcaster.BroadcastMode.GLOBAL) {
+ // Global broadcast - everyone hears equally, no speakers
for (org.bukkit.entity.Player online : player.getWorld().getPlayers()) {
if (online.getUniqueId().equals(player.getUniqueId())) {
continue;
}
VoicechatConnection connection = api.getConnectionOf(online.getUniqueId());
-
if (connection == null) continue;
api.sendStaticSoundPacketTo(connection, event.getPacket().toStaticSoundPacket());
@@ -78,6 +75,14 @@ public class VoicePlugin implements VoicechatPlugin {
return;
}
+ // Speaker broadcast - use positional audio from speakers
+ java.util.List<Speaker> speakers = SpeakerManager.inst().getSpeakers(worldId);
+
+ if (speakers.isEmpty()) {
+ // No speakers but speaker mode - no audio will be heard
+ return;
+ }
+
// Use positional audio from speakers
for (org.bukkit.entity.Player online : player.getWorld().getPlayers()) {
if (online.getUniqueId().equals(player.getUniqueId())) {