feat: chat channel system (/join /leave /channel)

- config-driven channels in config/channels.yml (global, local, staff, trade) with
  emoji, colors, prefix, radius, cooldowns, permissions and aliases
- Channel record + ChannelConfig loader wired into ConfigManager (reload supported)
- ChannelService (api) + ConcurrentHashMap-backed ChannelServiceImpl, joined in
  ServiceRegistry; default channel join treated as 'not in channel'
- JoinChannelCommand / LeaveChannelCommand / ChannelCommand (+ tab completion),
  registered in CommandManager; plugin.yml commands + permissions
- ChatEventListener routes AsyncChatEvent to the active channel: per-channel
  cooldown, local-radius viewer filtering, channel-aware renderer
- EnhancedChatRenderer supports Channel overload (emojiFor/prefixText/channelColor)
- ChannelServiceImplTest covering join/leave/permissions/aliases
This commit is contained in:
loki5512344 2026-08-09 21:31:11 +02:00
parent 689ab1a9ae
commit a822c91637
Signed by: boba
GPG key ID: 253067914055423B
17 changed files with 821 additions and 12 deletions

View file

@ -0,0 +1,120 @@
package com.loki.lochat.commands.chat;
import com.loki.lochat.LoChat;
import com.loki.lochat.api.service.ChannelService;
import com.loki.lochat.channel.Channel;
import com.loki.lochat.commands.base.PlayerCommand;
import com.loki.lochat.utils.format.ChatFormatter;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Locale;
/**
* /channel — инфо о текущем канале со списком доступных.
* /channel <канал> — переключиться. /channel off — выйти.
* /channel list — список каналов.
*/
public class ChannelCommand extends PlayerCommand {
public ChannelCommand(LoChat plugin) {
super(plugin);
}
@Override
protected boolean executePlayerCommand(@NotNull Player player, @NotNull Command command,
@NotNull String label, @NotNull String[] args) {
ChannelService channels = plugin.getServiceRegistry().get(ChannelService.class);
if (!channels.isEnabled()) {
player.sendMessage(ChatFormatter.parse("&#CF6679Система каналов отключена"));
return true;
}
if (args.length == 0) {
showCurrent(player, channels);
return true;
}
switch (args[0].toLowerCase(Locale.ROOT)) {
case "off", "leave" -> {
if (channels.isInChannel(player)) {
channels.leave(player);
player.sendMessage(ChatFormatter.parse("&#9878C9Вы вышли из канала"));
} else {
player.sendMessage(ChatFormatter.parse("&#B798A8Вы не находитесь в канале"));
}
}
case "list" -> listChannels(player, channels);
default -> joinChannel(player, channels, args[0]);
}
return true;
}
private void showCurrent(Player player, ChannelService channels) {
Channel active = channels.getActive(player).orElse(null);
if (active == null) {
Channel def = channels.getDefault();
player.sendMessage(ChatFormatter.parse("&#B798A8Вы в дефолтном канале [&#7858E9"
+ (def == null ? "?" : def.display() + "&8 " + def.prefix()) + "&#B798A8]"));
} else {
player.sendMessage(ChatFormatter.parse("&#9878C9Вы в канале [&#7858E9"
+ active.display() + "&8 " + active.prefix() + "&#9878C9]"));
}
player.sendMessage(ChatFormatter.parse("&#B798A8/join <канал> &#9878C9— войти, "
+ "&#B798A8/channel list &#9878C9— все каналы"));
}
private void listChannels(Player player, ChannelService channels) {
player.sendMessage(ChatFormatter.parse("&#B798A8Доступные каналы:"));
for (Channel c : channels.getChannels()) {
boolean canUse = channels.canUse(player, c);
boolean active = c.equals(channels.getActive(player).orElse(null));
player.sendMessage(ChatFormatter.parse((canUse ? "&#9878C9" : "&#CF6679")
+ (active ? "\u25B8 " : "\u25AA ") + c.display() + "&8 " + c.prefix()
+ (canUse ? "" : " &#CF6679(нет прав)")
+ " &#B798A8— " + (c.isGlobalScope() ? "глобальный" : "локальный, " + c.radius() + "б")));
}
}
private void joinChannel(Player player, ChannelService channels, String name) {
Channel channel = channels.find(name);
if (channel == null) {
player.sendMessage(ChatFormatter.parse("&#CF6679Канал &#7858E9" + name + " &#CF6679не найден"));
return;
}
if (!channels.canUse(player, channel)) {
player.sendMessage(ChatFormatter.parse(
plugin.getConfigManager().getMessagesConfig().getNoPermission()));
return;
}
Channel active = channels.getActive(player).orElse(null);
channels.join(player, channel);
if (channel.equals(active)) {
player.sendMessage(ChatFormatter.parse("&#B798A8Вы уже в канале [&#7858E9"
+ channel.display() + "&8 " + channel.prefix() + "&#B798A8]"));
} else {
player.sendMessage(ChatFormatter.parse("&#9878C9Вы вошли в канал [&#7858E9"
+ channel.display() + "&8 " + channel.prefix() + "&#9878C9]"));
}
}
@Override
protected @Nullable List<String> getTabCompletions(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String alias, @NotNull String[] args) {
if (args.length != 1) {
return null;
}
ChannelService channels = plugin.getServiceRegistry().get(ChannelService.class);
String input = args[0].toLowerCase(Locale.ROOT);
return java.util.stream.Stream.concat(
java.util.stream.Stream.of("list", "off"),
channels.getChannels().stream().map(Channel::id))
.filter(s -> s.startsWith(input))
.toList();
}
}