Initial commit: LoChat v1.0.0 with Gradient module

This commit is contained in:
loki5512344 2025-12-29 14:57:49 +01:00
commit 252b0a60db
86 changed files with 8922 additions and 0 deletions

View file

@ -0,0 +1,179 @@
package com.loki.lochat;
import com.loki.lochat.commands.*;
import com.loki.lochat.config.ConfigManager;
import com.loki.lochat.config.MessageConfig;
import com.loki.lochat.gradient.GradientModule;
import com.loki.lochat.integrations.PlaceholderAPIHook;
import com.loki.lochat.listeners.ChatListener;
import com.loki.lochat.managers.*;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
public final class LoChat extends JavaPlugin {
private static LoChat instance;
private ConfigManager configManager;
private MessageConfig messageConfig;
private ChatManager chatManager;
private PMManager pmManager;
private IgnoreManager ignoreManager;
private AutoMessageManager autoMessageManager;
private FilterManager filterManager;
private AntiSpamManager antiSpamManager;
private CooldownManager cooldownManager;
private MuteManager muteManager;
private MentionManager mentionManager;
private SpyManager spyManager;
private EmojiManager emojiManager;
private GradientModule gradientModule;
@Override
public void onEnable() {
instance = this;
// Инициализация конфигов
configManager = new ConfigManager(this);
messageConfig = new MessageConfig(this);
// Инициализация менеджеров
emojiManager = new EmojiManager(this);
ignoreManager = new IgnoreManager(this);
pmManager = new PMManager();
filterManager = new FilterManager(this);
antiSpamManager = new AntiSpamManager(this);
cooldownManager = new CooldownManager();
muteManager = new MuteManager(this);
mentionManager = new MentionManager(this);
spyManager = new SpyManager(this);
chatManager = new ChatManager(this);
autoMessageManager = new AutoMessageManager(this);
// Инициализация модуля градиентов (интегрированный LoPreff)
gradientModule = new GradientModule(this);
if (gradientModule.init()) {
getLogger().info("Gradient модуль (LoPreff) загружен!");
}
// Регистрация команд
registerCommands();
// Регистрация листенеров
getServer().getPluginManager().registerEvents(new ChatListener(this), this);
// Запуск автосообщений
autoMessageManager.start();
// PlaceholderAPI
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
new PlaceholderAPIHook(this).register();
getLogger().info("PlaceholderAPI подключен!");
}
getLogger().info("LoChat успешно запущен!");
}
@Override
public void onDisable() {
if (autoMessageManager != null) {
autoMessageManager.stop();
}
if (ignoreManager != null) {
ignoreManager.save();
}
if (muteManager != null) {
muteManager.save();
}
if (gradientModule != null) {
gradientModule.shutdown();
}
getLogger().info("LoChat отключен!");
}
private void registerCommands() {
getCommand("g").setExecutor(new GlobalChatCommand(this));
getCommand("globalchat").setExecutor(new GlobalChatCommand(this));
getCommand("l").setExecutor(new LocalChatCommand(this));
getCommand("msg").setExecutor(new MsgCommand(this));
getCommand("reply").setExecutor(new ReplyCommand(this));
getCommand("ignore").setExecutor(new IgnoreCommand(this));
getCommand("unignore").setExecutor(new UnignoreCommand(this));
getCommand("announce").setExecutor(new AnnounceCommand(this));
getCommand("chatspy").setExecutor(new ChatSpyCommand(this));
getCommand("clearchat").setExecutor(new ClearChatCommand(this));
getCommand("mute").setExecutor(new MuteCommand(this));
getCommand("unmute").setExecutor(new UnmuteCommand(this));
}
public void reload() {
configManager.reload();
messageConfig.reload();
emojiManager.reload();
autoMessageManager.stop();
autoMessageManager.start();
if (gradientModule != null) {
gradientModule.reload();
}
}
public static LoChat getInstance() {
return instance;
}
public ConfigManager getConfigManager() {
return configManager;
}
public MessageConfig getMessageConfig() {
return messageConfig;
}
public ChatManager getChatManager() {
return chatManager;
}
public PMManager getPmManager() {
return pmManager;
}
public IgnoreManager getIgnoreManager() {
return ignoreManager;
}
public AutoMessageManager getAutoMessageManager() {
return autoMessageManager;
}
public FilterManager getFilterManager() {
return filterManager;
}
public AntiSpamManager getAntiSpamManager() {
return antiSpamManager;
}
public CooldownManager getCooldownManager() {
return cooldownManager;
}
public MuteManager getMuteManager() {
return muteManager;
}
public MentionManager getMentionManager() {
return mentionManager;
}
public SpyManager getSpyManager() {
return spyManager;
}
public EmojiManager getEmojiManager() {
return emojiManager;
}
public GradientModule getGradientModule() {
return gradientModule;
}
}

View file

@ -0,0 +1,61 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.utils.ChatFormatter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.title.Title;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.time.Duration;
public class AnnounceCommand implements CommandExecutor {
private final LoChat plugin;
public AnnounceCommand(LoChat plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length == 0) {
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getInvalidUsage("/announce <сообщение>")));
return true;
}
// /announce reload
if (args[0].equalsIgnoreCase("reload") && sender.hasPermission("chat.announce.reload")) {
plugin.reload();
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("announce.reloaded")));
return true;
}
String message = String.join(" ", args);
String format = plugin.getConfigManager().getAnnouncementFormat();
Component formatted = ChatFormatter.formatAnnouncement(format, message);
// Отправка в чат
for (Player player : Bukkit.getOnlinePlayers()) {
player.sendMessage(formatted);
// ActionBar
player.sendActionBar(formatted);
// Title
Title title = Title.title(
ChatFormatter.parse("<gold>ОБЪЯВЛЕНИЕ</gold>"),
ChatFormatter.parse(message),
Title.Times.times(Duration.ofMillis(500), Duration.ofSeconds(3), Duration.ofMillis(500))
);
player.showTitle(title);
}
plugin.getLogger().info("[ANNOUNCE] " + sender.getName() + ": " + ChatFormatter.stripTags(message));
return true;
}
}

View file

@ -0,0 +1,32 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class ChatSpyCommand implements CommandExecutor {
private final LoChat plugin;
public ChatSpyCommand(LoChat plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage("Только для игроков!");
return true;
}
boolean enabled = plugin.getSpyManager().toggleSpy(player.getUniqueId());
String msgKey = enabled ? "spy.enabled" : "spy.disabled";
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get(msgKey)));
return true;
}
}

View file

@ -0,0 +1,36 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class ClearChatCommand implements CommandExecutor {
private final LoChat plugin;
public ClearChatCommand(LoChat plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
// Отправляем 100 пустых строк всем игрокам
for (Player player : Bukkit.getOnlinePlayers()) {
for (int i = 0; i < 100; i++) {
player.sendMessage("");
}
// Уведомление
player.sendMessage(ChatFormatter.parse(
plugin.getMessageConfig().get("clearchat.cleared", "{player}", sender.getName())
));
}
return true;
}
}

View file

@ -0,0 +1,48 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class GlobalChatCommand implements CommandExecutor {
private final LoChat plugin;
public GlobalChatCommand(LoChat plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage("Только для игроков!");
return true;
}
// /globalchat toggle
if (command.getName().equalsIgnoreCase("globalchat")) {
if (args.length > 0 && args[0].equalsIgnoreCase("toggle")) {
boolean enabled = plugin.getChatManager().toggleGlobalChat(player.getUniqueId());
String msgKey = enabled ? "global.toggled-on" : "global.toggled-off";
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get(msgKey)));
} else {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getInvalidUsage("/globalchat toggle")));
}
return true;
}
// /g <message>
if (args.length == 0) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getInvalidUsage("/g <сообщение>")));
return true;
}
String message = String.join(" ", args);
plugin.getChatManager().sendGlobalMessage(player, message);
return true;
}
}

View file

@ -0,0 +1,55 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class IgnoreCommand implements CommandExecutor {
private final LoChat plugin;
public IgnoreCommand(LoChat plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage("Только для игроков!");
return true;
}
if (args.length == 0) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getInvalidUsage("/ignore <ник>")));
return true;
}
Player target = Bukkit.getPlayer(args[0]);
if (target == null) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getPlayerNotFound()));
return true;
}
if (target.equals(player)) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("ignore.self")));
return true;
}
if (plugin.getIgnoreManager().isIgnoring(player.getUniqueId(), target.getUniqueId())) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("ignore.already-ignored")));
return true;
}
plugin.getIgnoreManager().addIgnore(player.getUniqueId(), target.getUniqueId());
player.sendMessage(ChatFormatter.parse(
plugin.getMessageConfig().get("ignore.added", "{player}", target.getName())
));
return true;
}
}

View file

@ -0,0 +1,35 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class LocalChatCommand implements CommandExecutor {
private final LoChat plugin;
public LocalChatCommand(LoChat plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage("Только для игроков!");
return true;
}
if (args.length == 0) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getInvalidUsage("/l <сообщение>")));
return true;
}
String message = String.join(" ", args);
plugin.getChatManager().sendLocalMessage(player, message);
return true;
}
}

View file

@ -0,0 +1,97 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class MsgCommand implements CommandExecutor {
private final LoChat plugin;
public MsgCommand(LoChat plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage("Только для игроков!");
return true;
}
if (!plugin.getConfigManager().isPmEnabled()) {
return true;
}
if (args.length < 2) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getInvalidUsage("/msg <ник> <сообщение>")));
return true;
}
Player target = Bukkit.getPlayer(args[0]);
if (target == null) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.player-offline")));
return true;
}
if (target.equals(player)) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.self")));
return true;
}
// Проверка игнора
if (plugin.getIgnoreManager().isIgnoring(target.getUniqueId(), player.getUniqueId())) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.ignored")));
return true;
}
// Собираем сообщение
StringBuilder messageBuilder = new StringBuilder();
for (int i = 1; i < args.length; i++) {
if (i > 1) messageBuilder.append(" ");
messageBuilder.append(args[i]);
}
String message = messageBuilder.toString();
// Отправляем сообщения
player.sendMessage(ChatFormatter.formatPmSent(
plugin.getMessageConfig().get("pm.sent"),
target.getName(),
message
));
target.sendMessage(ChatFormatter.formatPmReceived(
plugin.getMessageConfig().get("pm.received"),
player.getName(),
message
));
// Звук для получателя
if (plugin.getConfigManager().isPmSoundEnabled()) {
try {
Sound sound = Sound.valueOf(plugin.getConfigManager().getPmSoundType());
target.playSound(target.getLocation(), sound, 1.0f, 1.0f);
} catch (IllegalArgumentException ignored) {}
}
// Шпион
plugin.getSpyManager().broadcastPM(player, target, message);
// Сохраняем последнего собеседника
plugin.getPmManager().setLastConversation(player.getUniqueId(), target.getUniqueId());
plugin.getPmManager().setLastConversation(target.getUniqueId(), player.getUniqueId());
// Логирование
if (plugin.getConfigManager().isPmLogEnabled()) {
plugin.getLogger().info("[PM] " + player.getName() + " -> " + target.getName() + ": " + message);
}
return true;
}
}

View file

@ -0,0 +1,56 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.managers.MuteManager;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class MuteCommand implements CommandExecutor {
private final LoChat plugin;
public MuteCommand(LoChat plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length < 2) {
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getInvalidUsage("/mute <ник> <время> [причина]")));
return true;
}
Player target = Bukkit.getPlayer(args[0]);
if (target == null) {
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getPlayerNotFound()));
return true;
}
if (plugin.getMuteManager().isAlreadyMuted(target.getUniqueId())) {
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("mute.already-muted")));
return true;
}
long duration = MuteManager.parseTime(args[1]);
String reason = args.length > 2 ? String.join(" ", java.util.Arrays.copyOfRange(args, 2, args.length)) : null;
plugin.getMuteManager().mute(target.getUniqueId(), duration, reason);
String timeStr = duration <= 0 ? "навсегда" : args[1];
sender.sendMessage(ChatFormatter.parse(
plugin.getMessageConfig().get("mute.muted", "{player}", target.getName(), "{time}", timeStr)
));
// Уведомляем игрока
target.sendMessage(ChatFormatter.parse(
plugin.getMessageConfig().get("mute.you-muted", "{time}", timeStr)
));
return true;
}
}

View file

@ -0,0 +1,93 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public class ReplyCommand implements CommandExecutor {
private final LoChat plugin;
public ReplyCommand(LoChat plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage("Только для игроков!");
return true;
}
if (!plugin.getConfigManager().isPmEnabled()) {
return true;
}
if (args.length == 0) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getInvalidUsage("/reply <сообщение>")));
return true;
}
UUID lastUuid = plugin.getPmManager().getLastConversation(player.getUniqueId());
if (lastUuid == null) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.no-reply")));
return true;
}
Player target = Bukkit.getPlayer(lastUuid);
if (target == null) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.player-offline")));
return true;
}
// Проверка игнора
if (plugin.getIgnoreManager().isIgnoring(target.getUniqueId(), player.getUniqueId())) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.ignored")));
return true;
}
String message = String.join(" ", args);
// Отправляем сообщения
player.sendMessage(ChatFormatter.formatPmSent(
plugin.getMessageConfig().get("pm.sent"),
target.getName(),
message
));
target.sendMessage(ChatFormatter.formatPmReceived(
plugin.getMessageConfig().get("pm.received"),
player.getName(),
message
));
// Звук для получателя
if (plugin.getConfigManager().isPmSoundEnabled()) {
try {
Sound sound = Sound.valueOf(plugin.getConfigManager().getPmSoundType());
target.playSound(target.getLocation(), sound, 1.0f, 1.0f);
} catch (IllegalArgumentException ignored) {}
}
// Шпион
plugin.getSpyManager().broadcastPM(player, target, message);
// Обновляем последнего собеседника
plugin.getPmManager().setLastConversation(target.getUniqueId(), player.getUniqueId());
// Логирование
if (plugin.getConfigManager().isPmLogEnabled()) {
plugin.getLogger().info("[PM] " + player.getName() + " -> " + target.getName() + ": " + message);
}
return true;
}
}

View file

@ -0,0 +1,50 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class UnignoreCommand implements CommandExecutor {
private final LoChat plugin;
public UnignoreCommand(LoChat plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage("Только для игроков!");
return true;
}
if (args.length == 0) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getInvalidUsage("/unignore <ник>")));
return true;
}
Player target = Bukkit.getPlayer(args[0]);
if (target == null) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getPlayerNotFound()));
return true;
}
if (!plugin.getIgnoreManager().isIgnoring(player.getUniqueId(), target.getUniqueId())) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("ignore.not-ignored")));
return true;
}
plugin.getIgnoreManager().removeIgnore(player.getUniqueId(), target.getUniqueId());
player.sendMessage(ChatFormatter.parse(
plugin.getMessageConfig().get("ignore.removed", "{player}", target.getName())
));
return true;
}
}

View file

@ -0,0 +1,45 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class UnmuteCommand implements CommandExecutor {
private final LoChat plugin;
public UnmuteCommand(LoChat plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length < 1) {
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getInvalidUsage("/unmute <ник>")));
return true;
}
Player target = Bukkit.getPlayer(args[0]);
if (target == null) {
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getPlayerNotFound()));
return true;
}
if (!plugin.getMuteManager().isMuted(target.getUniqueId())) {
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("mute.not-muted")));
return true;
}
plugin.getMuteManager().unmute(target.getUniqueId());
sender.sendMessage(ChatFormatter.parse(
plugin.getMessageConfig().get("mute.unmuted", "{player}", target.getName())
));
return true;
}
}

View file

@ -0,0 +1,168 @@
package com.loki.lochat.config;
import com.loki.lochat.LoChat;
import org.bukkit.configuration.file.FileConfiguration;
import java.util.List;
public class ConfigManager {
private final LoChat plugin;
private FileConfiguration config;
public ConfigManager(LoChat plugin) {
this.plugin = plugin;
plugin.saveDefaultConfig();
this.config = plugin.getConfig();
}
public void reload() {
plugin.reloadConfig();
this.config = plugin.getConfig();
}
// Global chat
public boolean isGlobalEnabled() {
return config.getBoolean("chat.global.enabled", true);
}
public String getGlobalPrefix() {
return config.getString("chat.global.prefix", "[G]");
}
public String getGlobalFormat() {
return config.getString("chat.global.format", "<prefix><player>: <message>");
}
public String getGlobalSymbol() {
return config.getString("chat.global.symbol", "!");
}
public int getGlobalCooldown() {
return config.getInt("chat.global.cooldown", 3);
}
// Local chat
public boolean isLocalEnabled() {
return config.getBoolean("chat.local.enabled", true);
}
public int getLocalRadius() {
return config.getInt("chat.local.radius", 100);
}
public String getLocalPrefix() {
return config.getString("chat.local.prefix", "[L]");
}
public String getLocalFormat() {
return config.getString("chat.local.format", "<player>: <message>");
}
public int getLocalCooldown() {
return config.getInt("chat.local.cooldown", 1);
}
// PM
public boolean isPmEnabled() {
return config.getBoolean("chat.pm.enabled", true);
}
public boolean isPmLogEnabled() {
return config.getBoolean("chat.pm.log", false);
}
public boolean isPmSoundEnabled() {
return config.getBoolean("chat.pm.sound", true);
}
public String getPmSoundType() {
return config.getString("chat.pm.sound-type", "ENTITY_EXPERIENCE_ORB_PICKUP");
}
// Mentions
public boolean isMentionsEnabled() {
return config.getBoolean("mentions.enabled", true);
}
public boolean isMentionSoundEnabled() {
return config.getBoolean("mentions.sound", true);
}
public String getMentionSoundType() {
return config.getString("mentions.sound-type", "BLOCK_NOTE_BLOCK_PLING");
}
public String getMentionHighlight() {
return config.getString("mentions.highlight", "<yellow>@{player}</yellow>");
}
public String getSelfMentionHighlight() {
return config.getString("mentions.self-highlight", "<gold><bold>{player}</bold></gold>");
}
// Filter
public boolean isFilterEnabled() {
return config.getBoolean("filter.enabled", true);
}
public List<String> getFilterWords() {
return config.getStringList("filter.words");
}
public String getFilterAction() {
return config.getString("filter.action", "censor");
}
public String getFilterReplacement() {
return config.getString("filter.replacement", "***");
}
// Anti-spam
public boolean isAntiSpamEnabled() {
return config.getBoolean("antispam.enabled", true);
}
public int getMaxCapsPercent() {
return config.getInt("antispam.max-caps-percent", 70);
}
public int getMaxRepeatChars() {
return config.getInt("antispam.max-repeat-chars", 5);
}
public int getSimilarMessageDelay() {
return config.getInt("antispam.similar-message-delay", 30);
}
// Auto messages
public boolean isAutoMessagesEnabled() {
return config.getBoolean("automessages.enabled", true);
}
public int getAutoMessagesInterval() {
return config.getInt("automessages.interval", 300);
}
public boolean isAutoMessagesRandom() {
return config.getBoolean("automessages.random", false);
}
// Formats
public String getAnnouncementFormat() {
return config.getString("formats.announcement", "<gold>[ОБЪЯВЛЕНИЕ]</gold> <message>");
}
// LoPreff integration
public boolean isLopreffEnabled() {
return config.getBoolean("lopreff.enabled", true);
}
public boolean useGradientName() {
return config.getBoolean("lopreff.use-gradient-name", true);
}
public boolean useCustomPrefix() {
return config.getBoolean("lopreff.use-custom-prefix", true);
}
}

View file

@ -0,0 +1,60 @@
package com.loki.lochat.config;
import com.loki.lochat.LoChat;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
public class MessageConfig {
private final LoChat plugin;
private FileConfiguration messages;
private File messagesFile;
public MessageConfig(LoChat plugin) {
this.plugin = plugin;
loadMessages();
}
private void loadMessages() {
messagesFile = new File(plugin.getDataFolder(), "messages.yml");
if (!messagesFile.exists()) {
plugin.saveResource("messages.yml", false);
}
messages = YamlConfiguration.loadConfiguration(messagesFile);
}
public void reload() {
loadMessages();
}
public String get(String path) {
return messages.getString(path, "<red>Message not found: " + path + "</red>");
}
public String get(String path, String... replacements) {
String message = get(path);
for (int i = 0; i < replacements.length - 1; i += 2) {
message = message.replace(replacements[i], replacements[i + 1]);
}
return message;
}
// Shortcuts
public String getPrefix() {
return get("prefix");
}
public String getNoPermission() {
return get("errors.no-permission");
}
public String getPlayerNotFound() {
return get("errors.player-not-found");
}
public String getInvalidUsage(String usage) {
return get("errors.invalid-usage", "{usage}", usage);
}
}

View file

@ -0,0 +1,165 @@
package com.loki.lochat.gradient;
import com.loki.lochat.gradient.commands.GradientAdminCommand;
import com.loki.lochat.gradient.commands.GradientColorCommand;
import com.loki.lochat.gradient.commands.GradientPrefixCommand;
import com.loki.lochat.gradient.config.GradientConfig;
import com.loki.lochat.gradient.config.GradientMessages;
import com.loki.lochat.gradient.data.GradientDataManager;
import com.loki.lochat.gradient.data.GradientPlayerData;
import com.loki.lochat.gradient.hooks.GradientLuckPermsHook;
import com.loki.lochat.gradient.listeners.GradientGUIListener;
import com.loki.lochat.gradient.listeners.GradientPlayerListener;
import com.loki.lochat.gradient.util.DisplayNameUtil;
import com.loki.lochat.gradient.util.GradientUtil;
import org.black_ixx.playerpoints.PlayerPoints;
import org.black_ixx.playerpoints.PlayerPointsAPI;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
/**
* Модуль градиентных ников (интегрированный LoPreff)
*/
public class GradientModule {
private final JavaPlugin plugin;
private GradientConfig config;
private GradientMessages messages;
private GradientDataManager dataManager;
private GradientLuckPermsHook luckPermsHook;
private PlayerPointsAPI playerPointsAPI;
private boolean enabled;
public GradientModule(JavaPlugin plugin) {
this.plugin = plugin;
this.enabled = false;
}
/**
* Инициализация модуля
* @return true если модуль успешно инициализирован
*/
public boolean init() {
// Загружаем конфиги
config = new GradientConfig(plugin);
messages = new GradientMessages(plugin);
if (!config.isEnabled()) {
plugin.getLogger().info("Gradient модуль отключен в конфиге.");
return false;
}
// Проверяем PlayerPoints (опционально теперь)
if (Bukkit.getPluginManager().isPluginEnabled("PlayerPoints")) {
playerPointsAPI = PlayerPoints.getInstance().getAPI();
plugin.getLogger().info("Gradient: PlayerPoints подключен!");
} else {
plugin.getLogger().warning("Gradient: PlayerPoints не найден. Покупки будут бесплатными.");
}
// Инициализация хранилища
dataManager = new GradientDataManager(plugin, config);
// Подключение LuckPerms
luckPermsHook = new GradientLuckPermsHook(plugin);
// Регистрация команд
registerCommands();
// Регистрация слушателей
Bukkit.getPluginManager().registerEvents(new GradientPlayerListener(this), plugin);
Bukkit.getPluginManager().registerEvents(new GradientGUIListener(this), plugin);
enabled = true;
plugin.getLogger().info("Gradient модуль успешно загружен!");
return true;
}
private void registerCommands() {
GradientColorCommand colorCommand = new GradientColorCommand(this);
if (plugin.getCommand("color") != null) {
plugin.getCommand("color").setExecutor(colorCommand);
plugin.getCommand("color").setTabCompleter(colorCommand);
}
GradientPrefixCommand prefixCommand = new GradientPrefixCommand(this);
if (plugin.getCommand("prefix") != null) {
plugin.getCommand("prefix").setExecutor(prefixCommand);
plugin.getCommand("prefix").setTabCompleter(prefixCommand);
}
GradientAdminCommand adminCommand = new GradientAdminCommand(this);
if (plugin.getCommand("aprefix") != null) {
plugin.getCommand("aprefix").setExecutor(adminCommand);
plugin.getCommand("aprefix").setTabCompleter(adminCommand);
}
}
/**
* Выключение модуля
*/
public void shutdown() {
if (dataManager != null) {
dataManager.saveAll();
}
}
/**
* Перезагрузка модуля
*/
public void reload() {
config.reload();
messages.reload();
}
/**
* Получает отформатированное имя игрока с градиентом
*/
public String getFormattedName(Player player) {
if (!enabled) return player.getName();
return DisplayNameUtil.getFullDisplayName(this, player);
}
/**
* Получает только градиентный ник (без префикса)
*/
public String getGradientNick(Player player) {
if (!enabled) return player.getName();
GradientPlayerData data = dataManager.getPlayerData(player.getUniqueId());
if (!data.hasColors() || !data.isColorEnabled()) {
return player.getName();
}
return GradientUtil.applyGradient(player.getName(), data.getColors(), config.isUseLegacyRgbFormat());
}
/**
* Получает только префикс игрока
*/
public String getPrefix(Player player) {
if (!enabled) return "";
GradientPlayerData data = dataManager.getPlayerData(player.getUniqueId());
if (!data.hasPrefix() || !data.isPrefixEnabled()) {
return "";
}
String prefix = config.getPrefixFormat().replace("{prefix}", data.getPrefix());
if (data.hasColors() && data.isColorEnabled() && config.isGradientOnPrefix()) {
return GradientUtil.applyGradient(prefix, data.getColors(), config.isUseLegacyRgbFormat());
}
return prefix;
}
// Геттеры
public JavaPlugin getPlugin() { return plugin; }
public GradientConfig getConfig() { return config; }
public GradientMessages getMessages() { return messages; }
public GradientDataManager getDataManager() { return dataManager; }
public GradientLuckPermsHook getLuckPermsHook() { return luckPermsHook; }
public PlayerPointsAPI getPlayerPointsAPI() { return playerPointsAPI; }
public boolean hasPlayerPoints() { return playerPointsAPI != null; }
public boolean isEnabled() { return enabled; }
}

View file

@ -0,0 +1,349 @@
package com.loki.lochat.gradient.commands;
import com.loki.lochat.gradient.GradientModule;
import com.loki.lochat.gradient.config.GradientConfig;
import com.loki.lochat.gradient.config.GradientMessages;
import com.loki.lochat.gradient.data.GradientPlayerData;
import com.loki.lochat.gradient.util.DisplayNameUtil;
import com.loki.lochat.gradient.util.FoliaUtil;
import com.loki.lochat.gradient.util.GradientUtil;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Админ команда /aprefix для управления градиентами
*/
public class GradientAdminCommand implements CommandExecutor, TabCompleter {
private final GradientModule module;
private static final List<String> PRESET_COLORS = Arrays.asList(
"#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#4B0082", "#9400D3"
);
public GradientAdminCommand(GradientModule module) {
this.module = module;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String label, @NotNull String[] args) {
GradientMessages msg = module.getMessages();
if (!sender.hasPermission("gradient.admin")) {
sender.sendMessage(msg.get("no-permission"));
return true;
}
if (args.length == 0) {
sendHelp(sender);
return true;
}
String subCommand = args[0].toLowerCase();
switch (subCommand) {
case "reload" -> {
module.reload();
sender.sendMessage(msg.get("reload-success"));
}
case "info" -> {
if (args.length < 2) {
sender.sendMessage("§cИспользование: /aprefix info <игрок>");
return true;
}
handleInfo(sender, args[1], msg);
}
case "setcolor" -> {
if (args.length < 3) {
sender.sendMessage("§cИспользование: /aprefix setcolor <игрок> <цвет1> [цвет2] ...");
return true;
}
handleSetColor(sender, args, msg);
}
case "setprefix" -> {
if (args.length < 3) {
sender.sendMessage("§cИспользование: /aprefix setprefix <игрок> <префикс>");
return true;
}
handleSetPrefix(sender, args, msg);
}
case "coloron" -> handleColorToggle(sender, args, true, msg);
case "coloroff" -> handleColorToggle(sender, args, false, msg);
case "prefixon" -> handlePrefixToggle(sender, args, true, msg);
case "prefixoff" -> handlePrefixToggle(sender, args, false, msg);
case "resetcolor" -> handleResetColor(sender, args, msg);
case "resetprefix" -> handleResetPrefix(sender, args, msg);
default -> sendHelp(sender);
}
return true;
}
private void sendHelp(CommandSender sender) {
sender.sendMessage("§e=== LoChat Gradient Admin ===");
sender.sendMessage("§e/aprefix reload §7— перезагрузить конфиг");
sender.sendMessage("§e/aprefix info <игрок> §7— информация об игроке");
sender.sendMessage("§e/aprefix setcolor <игрок> <цвета> §7— установить цвет");
sender.sendMessage("§e/aprefix setprefix <игрок> <префикс> §7— установить префикс");
sender.sendMessage("§e/aprefix coloron/coloroff <игрок> §7— вкл/выкл цвет");
sender.sendMessage("§e/aprefix prefixon/prefixoff <игрок> §7— вкл/выкл префикс");
sender.sendMessage("§e/aprefix resetcolor <игрок> §7— сбросить цвет");
sender.sendMessage("§e/aprefix resetprefix <игрок> §7— сбросить префикс");
}
private void handleInfo(CommandSender sender, String playerName, GradientMessages msg) {
OfflinePlayer target = Bukkit.getOfflinePlayer(playerName);
if (!target.hasPlayedBefore() && !target.isOnline()) {
sender.sendMessage(msg.get("player-not-found"));
return;
}
GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId());
sender.sendMessage(msg.get("info-header", "player", playerName));
sender.sendMessage(msg.get("info-prefix", "prefix", data.hasPrefix() ? data.getPrefix() : "нет"));
sender.sendMessage(msg.get("info-prefix-enabled", "enabled", data.isPrefixEnabled() ? "да" : "нет"));
sender.sendMessage(msg.get("info-colors", "colors", data.hasColors() ? String.join(", ", data.getColors()) : "нет"));
sender.sendMessage(msg.get("info-color-enabled", "enabled", data.isColorEnabled() ? "да" : "нет"));
sender.sendMessage(msg.get("info-prefix-purchased", "purchased", data.isPrefixPurchased() ? "да" : "нет"));
}
private void handleSetColor(CommandSender sender, String[] args, GradientMessages msg) {
OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]);
if (!target.hasPlayedBefore() && !target.isOnline()) {
sender.sendMessage(msg.get("player-not-found"));
return;
}
List<String> colors = new ArrayList<>();
for (int i = 2; i < args.length; i++) {
String color = args[i].startsWith("#") ? args[i] : "#" + args[i];
if (!GradientUtil.isValidHex(color)) {
sender.sendMessage(msg.get("invalid-color", "color", args[i]));
return;
}
colors.add(color.toUpperCase());
}
GradientConfig cfg = module.getConfig();
if (colors.size() > cfg.getMaxColors()) {
sender.sendMessage(msg.get("too-many-colors", "max", String.valueOf(cfg.getMaxColors())));
return;
}
GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId());
data.setColors(colors);
data.setColorEnabled(true);
Player onlineTarget = target.getPlayer();
if (onlineTarget != null) {
FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget,
() -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data));
}
FoliaUtil.runAsync(module.getPlugin(),
() -> module.getDataManager().savePlayerData(target.getUniqueId()));
sender.sendMessage("§aЦвет для игрока §f" + args[1] + " §aустановлен: §f" + String.join(" ", colors));
}
private void handleSetPrefix(CommandSender sender, String[] args, GradientMessages msg) {
OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]);
if (!target.hasPlayedBefore() && !target.isOnline()) {
sender.sendMessage(msg.get("player-not-found"));
return;
}
StringBuilder prefixBuilder = new StringBuilder();
for (int i = 2; i < args.length; i++) {
if (i > 2) prefixBuilder.append(" ");
prefixBuilder.append(args[i]);
}
String prefix = prefixBuilder.toString();
GradientConfig cfg = module.getConfig();
if (prefix.length() > cfg.getMaxPrefixLength()) {
sender.sendMessage(msg.get("prefix-too-long", "max", String.valueOf(cfg.getMaxPrefixLength())));
return;
}
GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId());
data.setPrefix(prefix);
data.setPrefixEnabled(true);
data.setPrefixPurchased(true);
module.getLuckPermsHook().setPrefix(target.getUniqueId(), DisplayNameUtil.buildColoredPrefix(module, data));
Player onlineTarget = target.getPlayer();
if (onlineTarget != null) {
FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget,
() -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data));
}
FoliaUtil.runAsync(module.getPlugin(),
() -> module.getDataManager().savePlayerData(target.getUniqueId()));
sender.sendMessage("§aПрефикс для игрока §f" + args[1] + " §aустановлен: §f" + prefix);
}
private void handleColorToggle(CommandSender sender, String[] args, boolean enable, GradientMessages msg) {
if (args.length < 2) {
sender.sendMessage("§cИспользование: /aprefix " + (enable ? "coloron" : "coloroff") + " <игрок>");
return;
}
OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]);
if (!target.hasPlayedBefore() && !target.isOnline()) {
sender.sendMessage(msg.get("player-not-found"));
return;
}
GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId());
data.setColorEnabled(enable);
Player onlineTarget = target.getPlayer();
if (onlineTarget != null) {
FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget,
() -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data));
}
FoliaUtil.runAsync(module.getPlugin(),
() -> module.getDataManager().savePlayerData(target.getUniqueId()));
sender.sendMessage("§aЦвет для игрока §f" + args[1] + (enable ? " §aвключён" : " §cвыключен"));
}
private void handlePrefixToggle(CommandSender sender, String[] args, boolean enable, GradientMessages msg) {
if (args.length < 2) {
sender.sendMessage("§cИспользование: /aprefix " + (enable ? "prefixon" : "prefixoff") + " <игрок>");
return;
}
OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]);
if (!target.hasPlayedBefore() && !target.isOnline()) {
sender.sendMessage(msg.get("player-not-found"));
return;
}
GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId());
data.setPrefixEnabled(enable);
if (enable && data.hasPrefix()) {
module.getLuckPermsHook().setPrefix(target.getUniqueId(), DisplayNameUtil.buildColoredPrefix(module, data));
} else {
module.getLuckPermsHook().removePrefix(target.getUniqueId());
}
Player onlineTarget = target.getPlayer();
if (onlineTarget != null) {
FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget,
() -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data));
}
FoliaUtil.runAsync(module.getPlugin(),
() -> module.getDataManager().savePlayerData(target.getUniqueId()));
sender.sendMessage("§aПрефикс для игрока §f" + args[1] + (enable ? " §aвключён" : " §cвыключен"));
}
private void handleResetColor(CommandSender sender, String[] args, GradientMessages msg) {
if (args.length < 2) {
sender.sendMessage("§cИспользование: /aprefix resetcolor <игрок>");
return;
}
OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]);
if (!target.hasPlayedBefore() && !target.isOnline()) {
sender.sendMessage(msg.get("player-not-found"));
return;
}
GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId());
data.setColors(new ArrayList<>());
data.setColorEnabled(false);
Player onlineTarget = target.getPlayer();
if (onlineTarget != null) {
FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget,
() -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data));
}
FoliaUtil.runAsync(module.getPlugin(),
() -> module.getDataManager().savePlayerData(target.getUniqueId()));
sender.sendMessage("§aЦвет для игрока §f" + args[1] + " §aсброшен");
}
private void handleResetPrefix(CommandSender sender, String[] args, GradientMessages msg) {
if (args.length < 2) {
sender.sendMessage("§cИспользование: /aprefix resetprefix <игрок>");
return;
}
OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]);
if (!target.hasPlayedBefore() && !target.isOnline()) {
sender.sendMessage(msg.get("player-not-found"));
return;
}
GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId());
data.setPrefix(null);
data.setPrefixEnabled(false);
module.getLuckPermsHook().removePrefix(target.getUniqueId());
Player onlineTarget = target.getPlayer();
if (onlineTarget != null) {
FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget,
() -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data));
}
FoliaUtil.runAsync(module.getPlugin(),
() -> module.getDataManager().savePlayerData(target.getUniqueId()));
sender.sendMessage("§aПрефикс для игрока §f" + args[1] + " §aсброшен");
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String alias, @NotNull String[] args) {
if (!sender.hasPermission("gradient.admin")) return new ArrayList<>();
if (args.length == 1) {
return Arrays.asList("reload", "info", "setcolor", "setprefix", "coloron", "coloroff",
"prefixon", "prefixoff", "resetcolor", "resetprefix").stream()
.filter(s -> s.startsWith(args[0].toLowerCase()))
.toList();
}
if (args.length == 2) {
String sub = args[0].toLowerCase();
if (Arrays.asList("info", "setcolor", "setprefix", "coloron", "coloroff",
"prefixon", "prefixoff", "resetcolor", "resetprefix").contains(sub)) {
return Bukkit.getOnlinePlayers().stream()
.map(Player::getName)
.filter(s -> s.toLowerCase().startsWith(args[1].toLowerCase()))
.toList();
}
}
if (args.length >= 3 && args[0].equalsIgnoreCase("setcolor")) {
return PRESET_COLORS.stream()
.filter(s -> s.toLowerCase().startsWith(args[args.length - 1].toLowerCase()))
.toList();
}
return new ArrayList<>();
}
}

View file

@ -0,0 +1,256 @@
package com.loki.lochat.gradient.commands;
import com.loki.lochat.gradient.GradientModule;
import com.loki.lochat.gradient.config.GradientConfig;
import com.loki.lochat.gradient.config.GradientMessages;
import com.loki.lochat.gradient.data.GradientPlayerData;
import com.loki.lochat.gradient.gui.GradientConfirmGUI;
import com.loki.lochat.gradient.util.DisplayNameUtil;
import com.loki.lochat.gradient.util.FoliaUtil;
import com.loki.lochat.gradient.util.GradientConstants;
import com.loki.lochat.gradient.util.GradientUtil;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.BiFunction;
/**
* Команда /color для установки градиента ника
*/
public class GradientColorCommand implements CommandExecutor, TabCompleter {
private final GradientModule module;
private final Map<String, BiFunction<Player, String[], Boolean>> subCommands = new HashMap<>();
private static final List<String> PRESET_COLORS = List.of(
"#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#4B0082", "#9400D3",
"#FF69B4", "#00FFFF", "#FFD700", "#FFFFFF", "#000000"
);
private static final Set<String> SUB_COMMANDS = Set.of("on", "off", "reset", "copy");
public GradientColorCommand(GradientModule module) {
this.module = module;
registerSubCommands();
}
private void registerSubCommands() {
subCommands.put("on", this::handleOn);
subCommands.put("off", this::handleOff);
subCommands.put("reset", this::handleReset);
subCommands.put("copy", this::handleCopy);
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage(module.getMessages().get("only-player"));
return true;
}
if (!player.hasPermission("gradient.color")) {
player.sendMessage(module.getMessages().get("no-permission"));
return true;
}
if (args.length == 0) {
player.sendMessage(module.getMessages().get("color-help"));
return true;
}
String subCommand = args[0].toLowerCase();
var handler = subCommands.get(subCommand);
if (handler != null) {
return handler.apply(player, args);
}
return handleSetColors(player, args);
}
private boolean handleOn(Player player, String[] args) {
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
GradientMessages msg = module.getMessages();
if (!data.hasColors()) {
player.sendMessage(msg.get("color-no-colors"));
return true;
}
data.setColorEnabled(true);
saveAndUpdate(player, data);
player.sendMessage(msg.get("color-enabled"));
return true;
}
private boolean handleOff(Player player, String[] args) {
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
data.setColorEnabled(false);
saveAndUpdate(player, data);
player.sendMessage(module.getMessages().get("color-disabled"));
return true;
}
private boolean handleReset(Player player, String[] args) {
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
GradientMessages msg = module.getMessages();
if (!data.hasColors()) {
player.sendMessage(msg.get("color-no-colors"));
return true;
}
data.setColors(new ArrayList<>());
data.setColorEnabled(false);
saveAndUpdate(player, data);
player.sendMessage(msg.get("color-reset"));
return true;
}
private boolean handleCopy(Player player, String[] args) {
GradientMessages msg = module.getMessages();
GradientConfig cfg = module.getConfig();
if (args.length < 2) {
player.sendMessage(msg.get("color-copy-usage"));
return true;
}
Player target = Bukkit.getPlayer(args[1]);
if (target == null) {
player.sendMessage(msg.get("player-not-found"));
return true;
}
GradientPlayerData targetData = module.getDataManager().getPlayerData(target.getUniqueId());
if (!targetData.hasColors()) {
player.sendMessage(msg.get("color-copy-no-colors", "player", target.getName()));
return true;
}
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
if (!player.hasPermission("gradient.bypass.cooldown") &&
!checkCooldown(player, cfg.getColorCooldown(), data.getLastColorChange())) {
return true;
}
List<String> colors = new ArrayList<>(targetData.getColors());
int price = player.hasPermission("gradient.bypass.cost") ? 0 : colors.size() * cfg.getPricePerColor();
if (price > 0 && !checkBalance(player, price)) {
return true;
}
GradientConfirmGUI gui = new GradientConfirmGUI(module, player,
GradientConfirmGUI.ConfirmType.COLOR, colors, data.getPrefix(), price);
FoliaUtil.runEntityTask(module.getPlugin(), player, gui::open);
return true;
}
private boolean handleSetColors(Player player, String[] args) {
GradientMessages msg = module.getMessages();
GradientConfig cfg = module.getConfig();
if (args.length > cfg.getMaxColors()) {
player.sendMessage(msg.get("too-many-colors", "max", String.valueOf(cfg.getMaxColors())));
return true;
}
if (args.length < cfg.getMinColors()) {
player.sendMessage(msg.get("not-enough-colors", "min", String.valueOf(cfg.getMinColors())));
return true;
}
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
if (!player.hasPermission("gradient.bypass.cooldown") &&
!checkCooldown(player, cfg.getColorCooldown(), data.getLastColorChange())) {
return true;
}
List<String> colors = new ArrayList<>();
for (String arg : args) {
String color = arg.startsWith("#") ? arg : "#" + arg;
if (!GradientUtil.isValidHex(color)) {
player.sendMessage(msg.get("invalid-color", "color", arg));
return true;
}
colors.add(color.toUpperCase());
}
int price = player.hasPermission("gradient.bypass.cost") ? 0 : colors.size() * cfg.getPricePerColor();
if (price > 0 && !checkBalance(player, price)) {
return true;
}
GradientConfirmGUI gui = new GradientConfirmGUI(module, player,
GradientConfirmGUI.ConfirmType.COLOR, colors, data.getPrefix(), price);
FoliaUtil.runEntityTask(module.getPlugin(), player, gui::open);
return true;
}
private boolean checkCooldown(Player player, int cooldownSec, long lastChange) {
long cooldownMs = cooldownSec * GradientConstants.MILLIS_PER_SECOND;
long timePassed = System.currentTimeMillis() - lastChange;
if (timePassed < cooldownMs) {
long remaining = (cooldownMs - timePassed) / GradientConstants.MILLIS_PER_SECOND;
player.sendMessage(module.getMessages().get("cooldown", "time", String.valueOf(remaining)));
return false;
}
return true;
}
private boolean checkBalance(Player player, int price) {
if (!module.hasPlayerPoints()) {
player.sendMessage("§cPlayerPoints не установлен!");
return false;
}
int balance = module.getPlayerPointsAPI().look(player.getUniqueId());
if (balance < price) {
player.sendMessage(module.getMessages().get("not-enough-points-color", "price", String.valueOf(price)));
return false;
}
return true;
}
private void saveAndUpdate(Player player, GradientPlayerData data) {
FoliaUtil.runEntityTask(module.getPlugin(), player,
() -> DisplayNameUtil.updateDisplayName(module, player, data));
FoliaUtil.runAsync(module.getPlugin(),
() -> module.getDataManager().savePlayerData(player.getUniqueId()));
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String alias, @NotNull String[] args) {
if (args.length == 1) {
String input = args[0].toLowerCase();
List<String> completions = new ArrayList<>(SUB_COMMANDS);
completions.addAll(PRESET_COLORS);
return completions.stream().filter(s -> s.toLowerCase().startsWith(input)).toList();
}
String firstArg = args[0].toLowerCase();
if (args.length == 2 && firstArg.equals("copy")) {
return Bukkit.getOnlinePlayers().stream().map(Player::getName)
.filter(s -> s.toLowerCase().startsWith(args[1].toLowerCase())).toList();
}
if (!SUB_COMMANDS.contains(firstArg) && args.length <= module.getConfig().getMaxColors()) {
return PRESET_COLORS.stream()
.filter(s -> s.toLowerCase().startsWith(args[args.length - 1].toLowerCase()))
.toList();
}
return List.of();
}
}

View file

@ -0,0 +1,211 @@
package com.loki.lochat.gradient.commands;
import com.loki.lochat.gradient.GradientModule;
import com.loki.lochat.gradient.config.GradientConfig;
import com.loki.lochat.gradient.config.GradientMessages;
import com.loki.lochat.gradient.data.GradientPlayerData;
import com.loki.lochat.gradient.gui.GradientConfirmGUI;
import com.loki.lochat.gradient.util.DisplayNameUtil;
import com.loki.lochat.gradient.util.FoliaUtil;
import com.loki.lochat.gradient.util.GradientConstants;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.BiFunction;
import java.util.regex.Pattern;
/**
* Команда /prefix для установки кастомного префикса
*/
public class GradientPrefixCommand implements CommandExecutor, TabCompleter {
private final GradientModule module;
private final Map<String, BiFunction<Player, String[], Boolean>> subCommands = new HashMap<>();
private static final Pattern COLOR_CODE_PATTERN = Pattern.compile(
"(?i)(&[0-9a-fk-or]|§[0-9a-fk-or]|#[0-9a-f]{6}|&#[0-9a-f]{6}|<#[0-9a-f]{6}>)");
private static final Pattern ALLOWED_PREFIX_PATTERN = Pattern.compile("^[a-zA-Zа-яА-ЯёЁ0-9 _\\-]+$");
private static final Set<String> SUB_COMMANDS = Set.of("on", "off", "reset");
public GradientPrefixCommand(GradientModule module) {
this.module = module;
registerSubCommands();
}
private void registerSubCommands() {
subCommands.put("on", this::handleOn);
subCommands.put("off", this::handleOff);
subCommands.put("reset", this::handleReset);
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage(module.getMessages().get("only-player"));
return true;
}
if (!player.hasPermission("gradient.prefix")) {
player.sendMessage(module.getMessages().get("no-permission"));
return true;
}
if (args.length == 0) {
player.sendMessage(module.getMessages().get("prefix-help"));
return true;
}
String subCommand = args[0].toLowerCase();
var handler = subCommands.get(subCommand);
if (handler != null) {
return handler.apply(player, args);
}
return handleSetPrefix(player, args);
}
private boolean handleOn(Player player, String[] args) {
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
GradientMessages msg = module.getMessages();
if (!data.hasPrefix()) {
player.sendMessage(msg.get("prefix-no-prefix"));
return true;
}
data.setPrefixEnabled(true);
module.getLuckPermsHook().setPrefix(player, DisplayNameUtil.buildColoredPrefix(module, data));
saveAndUpdate(player, data);
player.sendMessage(msg.get("prefix-enabled"));
return true;
}
private boolean handleOff(Player player, String[] args) {
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
data.setPrefixEnabled(false);
module.getLuckPermsHook().removePrefix(player);
saveAndUpdate(player, data);
player.sendMessage(module.getMessages().get("prefix-disabled"));
return true;
}
private boolean handleReset(Player player, String[] args) {
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
GradientMessages msg = module.getMessages();
if (!data.hasPrefix()) {
player.sendMessage(msg.get("prefix-no-prefix"));
return true;
}
data.setPrefix(null);
data.setPrefixEnabled(false);
module.getLuckPermsHook().removePrefix(player);
saveAndUpdate(player, data);
player.sendMessage(msg.get("prefix-reset-success"));
return true;
}
private boolean handleSetPrefix(Player player, String[] args) {
GradientMessages msg = module.getMessages();
GradientConfig cfg = module.getConfig();
String prefix = String.join(" ", args);
if (prefix.trim().isEmpty()) {
player.sendMessage(msg.get("prefix-help"));
return true;
}
if (COLOR_CODE_PATTERN.matcher(prefix).find()) {
player.sendMessage(msg.get("prefix-no-colors"));
return true;
}
if (!ALLOWED_PREFIX_PATTERN.matcher(prefix).matches()) {
player.sendMessage(msg.get("prefix-invalid-chars"));
return true;
}
if (cfg.isPrefixBlacklisted(prefix)) {
player.sendMessage(msg.get("prefix-blacklisted"));
return true;
}
if (prefix.length() > cfg.getMaxPrefixLength()) {
player.sendMessage(msg.get("prefix-too-long", "max", String.valueOf(cfg.getMaxPrefixLength())));
return true;
}
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
if (!player.hasPermission("gradient.bypass.cooldown") &&
!checkCooldown(player, cfg.getPrefixCooldown(), data.getLastPrefixChange())) {
return true;
}
int price = calculatePrice(player, cfg, data);
if (price > 0 && !checkBalance(player, price)) {
return true;
}
GradientConfirmGUI gui = new GradientConfirmGUI(module, player,
GradientConfirmGUI.ConfirmType.PREFIX, data.getColors(), prefix, price);
FoliaUtil.runEntityTask(module.getPlugin(), player, gui::open);
return true;
}
private int calculatePrice(Player player, GradientConfig cfg, GradientPlayerData data) {
if (player.hasPermission("gradient.bypass.cost")) return 0;
return (cfg.isPrefixOneTimePurchase() && data.isPrefixPurchased()) ? 0 : cfg.getPrefixPrice();
}
private boolean checkCooldown(Player player, int cooldownSec, long lastChange) {
long cooldownMs = cooldownSec * GradientConstants.MILLIS_PER_SECOND;
long timePassed = System.currentTimeMillis() - lastChange;
if (timePassed < cooldownMs) {
long remaining = (cooldownMs - timePassed) / GradientConstants.MILLIS_PER_SECOND;
player.sendMessage(module.getMessages().get("cooldown", "time", String.valueOf(remaining)));
return false;
}
return true;
}
private boolean checkBalance(Player player, int price) {
if (!module.hasPlayerPoints()) {
player.sendMessage("§cPlayerPoints не установлен!");
return false;
}
int balance = module.getPlayerPointsAPI().look(player.getUniqueId());
if (balance < price) {
player.sendMessage(module.getMessages().get("not-enough-points-prefix", "price", String.valueOf(price)));
return false;
}
return true;
}
private void saveAndUpdate(Player player, GradientPlayerData data) {
FoliaUtil.runEntityTask(module.getPlugin(), player,
() -> DisplayNameUtil.updateDisplayName(module, player, data));
FoliaUtil.runAsync(module.getPlugin(),
() -> module.getDataManager().savePlayerData(player.getUniqueId()));
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String alias, @NotNull String[] args) {
if (args.length == 1) {
return SUB_COMMANDS.stream()
.filter(s -> s.startsWith(args[0].toLowerCase()))
.toList();
}
return List.of();
}
}

View file

@ -0,0 +1,81 @@
package com.loki.lochat.gradient.config;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* Конфигурация градиентного модуля
*/
public class GradientConfig {
private final JavaPlugin plugin;
private FileConfiguration config;
private File configFile;
public GradientConfig(JavaPlugin plugin) {
this.plugin = plugin;
loadConfig();
}
private void loadConfig() {
configFile = new File(plugin.getDataFolder(), "gradient-config.yml");
if (!configFile.exists()) {
plugin.saveResource("gradient-config.yml", false);
}
config = YamlConfiguration.loadConfiguration(configFile);
// Загружаем дефолты
InputStream defStream = plugin.getResource("gradient-config.yml");
if (defStream != null) {
YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(
new InputStreamReader(defStream, StandardCharsets.UTF_8));
config.setDefaults(defConfig);
}
}
public void reload() {
loadConfig();
}
public void save() {
try {
config.save(configFile);
} catch (IOException e) {
plugin.getLogger().warning("Ошибка сохранения gradient-config.yml: " + e.getMessage());
}
}
public boolean isEnabled() { return config.getBoolean("enabled", true); }
public int getMaxColors() { return config.getInt("max-colors", 7); }
public int getMinColors() { return config.getInt("min-colors", 1); }
public int getPricePerColor() { return config.getInt("price-per-color", 50); }
public int getPrefixPrice() { return config.getInt("prefix-price", 500); }
public boolean isPrefixOneTimePurchase() { return config.getBoolean("prefix-one-time-purchase", true); }
public int getColorCooldown() { return config.getInt("color-cooldown", 60); }
public int getPrefixCooldown() { return config.getInt("prefix-cooldown", 300); }
public int getMaxPrefixLength() { return config.getInt("max-prefix-length", 7); }
public String getPrefixFormat() { return config.getString("prefix-format", "[{prefix}] "); }
public boolean isGradientOnPrefix() { return config.getBoolean("gradient-on-prefix", true); }
public boolean isContinuousGradient() { return config.getBoolean("continuous-gradient", true); }
public boolean isUseLegacyRgbFormat() { return config.getBoolean("use-legacy-rgb-format", true); }
public String getStorageType() { return config.getString("storage-type", "YAML"); }
public String getSqliteFile() { return config.getString("sqlite-file", "gradient-data.db"); }
public List<String> getPrefixBlacklist() {
return config.getStringList("prefix-blacklist");
}
public boolean isPrefixBlacklisted(String prefix) {
String lowerPrefix = prefix.toLowerCase();
return getPrefixBlacklist().stream()
.anyMatch(blacklisted -> lowerPrefix.contains(blacklisted.toLowerCase()));
}
}

View file

@ -0,0 +1,58 @@
package com.loki.lochat.gradient.config;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
/**
* Сообщения градиентного модуля
*/
public class GradientMessages {
private final JavaPlugin plugin;
private FileConfiguration messages;
private File messagesFile;
public GradientMessages(JavaPlugin plugin) {
this.plugin = plugin;
loadMessages();
}
private void loadMessages() {
messagesFile = new File(plugin.getDataFolder(), "gradient-messages.yml");
if (!messagesFile.exists()) {
plugin.saveResource("gradient-messages.yml", false);
}
messages = YamlConfiguration.loadConfiguration(messagesFile);
InputStream defStream = plugin.getResource("gradient-messages.yml");
if (defStream != null) {
YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(
new InputStreamReader(defStream, StandardCharsets.UTF_8));
messages.setDefaults(defConfig);
}
}
public void reload() {
loadMessages();
}
public String get(String key) {
return messages.getString(key, "§cСообщение не найдено: " + key);
}
public String get(String key, String... replacements) {
String message = get(key);
for (int i = 0; i < replacements.length; i += 2) {
if (i + 1 < replacements.length) {
message = message.replace("{" + replacements[i] + "}", replacements[i + 1]);
}
}
return message;
}
}

View file

@ -0,0 +1,172 @@
package com.loki.lochat.gradient.data;
import com.loki.lochat.gradient.config.GradientConfig;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.io.IOException;
import java.sql.*;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* Менеджер данных градиентного модуля
*/
public class GradientDataManager {
private final JavaPlugin plugin;
private final GradientConfig config;
private final Map<UUID, GradientPlayerData> cache = new ConcurrentHashMap<>();
private Connection sqliteConnection;
private boolean useSqlite;
public GradientDataManager(JavaPlugin plugin, GradientConfig config) {
this.plugin = plugin;
this.config = config;
this.useSqlite = config.getStorageType().equalsIgnoreCase("SQLITE");
if (useSqlite) {
initSqlite();
} else {
loadYamlData();
}
}
private void initSqlite() {
try {
File dbFile = new File(plugin.getDataFolder(), config.getSqliteFile());
sqliteConnection = DriverManager.getConnection("jdbc:sqlite:" + dbFile.getAbsolutePath());
try (Statement stmt = sqliteConnection.createStatement()) {
stmt.executeUpdate("""
CREATE TABLE IF NOT EXISTS gradient_data (
uuid TEXT PRIMARY KEY,
prefix TEXT,
colors TEXT,
color_enabled INTEGER DEFAULT 1,
prefix_enabled INTEGER DEFAULT 1,
prefix_purchased INTEGER DEFAULT 0,
last_color_change INTEGER DEFAULT 0,
last_prefix_change INTEGER DEFAULT 0
)
""");
}
plugin.getLogger().info("Gradient SQLite база данных инициализирована.");
} catch (SQLException e) {
plugin.getLogger().severe("Ошибка инициализации Gradient SQLite: " + e.getMessage());
useSqlite = false;
}
}
private void loadYamlData() {
File dataFile = new File(plugin.getDataFolder(), "gradient-data.yml");
if (!dataFile.exists()) return;
FileConfiguration data = YamlConfiguration.loadConfiguration(dataFile);
for (String uuidStr : data.getKeys(false)) {
try {
UUID uuid = UUID.fromString(uuidStr);
GradientPlayerData playerData = new GradientPlayerData(uuid);
playerData.setPrefix(data.getString(uuidStr + ".prefix"));
playerData.setColors(data.getStringList(uuidStr + ".colors"));
playerData.setColorEnabled(data.getBoolean(uuidStr + ".color-enabled", true));
playerData.setPrefixEnabled(data.getBoolean(uuidStr + ".prefix-enabled", true));
playerData.setPrefixPurchased(data.getBoolean(uuidStr + ".prefix-purchased", false));
playerData.setLastColorChange(data.getLong(uuidStr + ".last-color-change", 0));
playerData.setLastPrefixChange(data.getLong(uuidStr + ".last-prefix-change", 0));
cache.put(uuid, playerData);
} catch (IllegalArgumentException ignored) {}
}
}
public GradientPlayerData getPlayerData(UUID uuid) {
return cache.computeIfAbsent(uuid, this::loadPlayerData);
}
private GradientPlayerData loadPlayerData(UUID uuid) {
GradientPlayerData data = new GradientPlayerData(uuid);
if (useSqlite && sqliteConnection != null) {
try (PreparedStatement stmt = sqliteConnection.prepareStatement(
"SELECT prefix, colors, color_enabled, prefix_enabled, prefix_purchased, last_color_change, last_prefix_change FROM gradient_data WHERE uuid = ?")) {
stmt.setString(1, uuid.toString());
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
data.setPrefix(rs.getString("prefix"));
String colorsStr = rs.getString("colors");
if (colorsStr != null && !colorsStr.isEmpty()) {
data.setColors(List.of(colorsStr.split(",")));
}
data.setColorEnabled(rs.getBoolean("color_enabled"));
data.setPrefixEnabled(rs.getBoolean("prefix_enabled"));
data.setPrefixPurchased(rs.getBoolean("prefix_purchased"));
data.setLastColorChange(rs.getLong("last_color_change"));
data.setLastPrefixChange(rs.getLong("last_prefix_change"));
}
} catch (SQLException e) {
plugin.getLogger().warning("Ошибка загрузки gradient данных: " + e.getMessage());
}
}
return data;
}
public void savePlayerData(UUID uuid) {
GradientPlayerData data = cache.get(uuid);
if (data == null) return;
if (useSqlite && sqliteConnection != null) {
try (PreparedStatement stmt = sqliteConnection.prepareStatement(
"INSERT OR REPLACE INTO gradient_data (uuid, prefix, colors, color_enabled, prefix_enabled, prefix_purchased, last_color_change, last_prefix_change) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")) {
stmt.setString(1, uuid.toString());
stmt.setString(2, data.getPrefix());
stmt.setString(3, data.hasColors() ? String.join(",", data.getColors()) : "");
stmt.setBoolean(4, data.isColorEnabled());
stmt.setBoolean(5, data.isPrefixEnabled());
stmt.setBoolean(6, data.isPrefixPurchased());
stmt.setLong(7, data.getLastColorChange());
stmt.setLong(8, data.getLastPrefixChange());
stmt.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().warning("Ошибка сохранения gradient данных: " + e.getMessage());
}
}
}
public void saveAll() {
if (useSqlite) {
for (UUID uuid : cache.keySet()) {
savePlayerData(uuid);
}
} else {
saveYamlData();
}
}
private void saveYamlData() {
File dataFile = new File(plugin.getDataFolder(), "gradient-data.yml");
FileConfiguration data = new YamlConfiguration();
for (Map.Entry<UUID, GradientPlayerData> entry : cache.entrySet()) {
String path = entry.getKey().toString();
GradientPlayerData pd = entry.getValue();
data.set(path + ".prefix", pd.getPrefix());
data.set(path + ".colors", pd.getColors());
data.set(path + ".color-enabled", pd.isColorEnabled());
data.set(path + ".prefix-enabled", pd.isPrefixEnabled());
data.set(path + ".prefix-purchased", pd.isPrefixPurchased());
data.set(path + ".last-color-change", pd.getLastColorChange());
data.set(path + ".last-prefix-change", pd.getLastPrefixChange());
}
try {
data.save(dataFile);
} catch (IOException e) {
plugin.getLogger().warning("Ошибка сохранения gradient-data.yml: " + e.getMessage());
}
}
}

View file

@ -0,0 +1,53 @@
package com.loki.lochat.gradient.data;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* Данные игрока для градиентного ника
*/
public class GradientPlayerData {
private final UUID uuid;
private String prefix;
private List<String> colors;
private boolean colorEnabled;
private boolean prefixEnabled;
private boolean prefixPurchased;
private long lastColorChange;
private long lastPrefixChange;
public GradientPlayerData(UUID uuid) {
this.uuid = uuid;
this.prefix = null;
this.colors = new ArrayList<>();
this.colorEnabled = true;
this.prefixEnabled = true;
this.prefixPurchased = false;
this.lastColorChange = 0;
this.lastPrefixChange = 0;
}
public UUID getUuid() { return uuid; }
public String getPrefix() { return prefix; }
public void setPrefix(String prefix) { this.prefix = prefix; }
public List<String> getColors() { return colors; }
public void setColors(List<String> colors) {
this.colors = colors != null ? new ArrayList<>(colors) : new ArrayList<>();
}
public boolean hasPrefix() { return prefix != null && !prefix.isEmpty(); }
public boolean hasColors() { return colors != null && !colors.isEmpty(); }
public boolean isColorEnabled() { return colorEnabled; }
public void setColorEnabled(boolean colorEnabled) { this.colorEnabled = colorEnabled; }
public boolean isPrefixEnabled() { return prefixEnabled; }
public void setPrefixEnabled(boolean prefixEnabled) { this.prefixEnabled = prefixEnabled; }
public boolean isPrefixPurchased() { return prefixPurchased; }
public void setPrefixPurchased(boolean prefixPurchased) { this.prefixPurchased = prefixPurchased; }
public long getLastColorChange() { return lastColorChange; }
public void setLastColorChange(long lastColorChange) { this.lastColorChange = lastColorChange; }
public long getLastPrefixChange() { return lastPrefixChange; }
public void setLastPrefixChange(long lastPrefixChange) { this.lastPrefixChange = lastPrefixChange; }
}

View file

@ -0,0 +1,157 @@
package com.loki.lochat.gradient.gui;
import com.loki.lochat.gradient.GradientModule;
import com.loki.lochat.gradient.util.GradientConstants;
import com.loki.lochat.gradient.util.GradientUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SkullMeta;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
* GUI подтверждения для градиентного модуля
*/
public class GradientConfirmGUI implements InventoryHolder {
public enum ConfirmType {
COLOR, PREFIX
}
private static final LegacyComponentSerializer SERIALIZER = LegacyComponentSerializer.builder()
.character('&')
.hexCharacter('#')
.hexColors()
.build();
private final GradientModule module;
private final Player player;
private final Inventory inventory;
private final ConfirmType type;
private final List<String> colors;
private final String prefix;
private final int price;
public GradientConfirmGUI(GradientModule module, Player player, ConfirmType type,
List<String> colors, String prefix, int price) {
this.module = module;
this.player = player;
this.type = type;
this.colors = colors;
this.prefix = prefix;
this.price = price;
this.inventory = Bukkit.createInventory(this, 27,
Component.text("Подтверждение", NamedTextColor.DARK_GRAY));
setupItems();
}
private void setupItems() {
ItemStack glass = createItem(Material.GRAY_STAINED_GLASS_PANE, " ");
for (int i = 0; i < 27; i++) {
inventory.setItem(i, glass);
}
ItemStack head = new ItemStack(Material.PLAYER_HEAD);
SkullMeta skullMeta = (SkullMeta) head.getItemMeta();
skullMeta.setOwningPlayer(player);
String preview = buildPreview();
Component previewComponent = SERIALIZER.deserialize(preview);
skullMeta.displayName(previewComponent.decoration(TextDecoration.ITALIC, false));
List<Component> lore = new ArrayList<>();
lore.add(Component.empty());
lore.add(Component.text("Так будет выглядеть ваш ник", NamedTextColor.GRAY)
.decoration(TextDecoration.ITALIC, false));
if (price > 0) {
lore.add(Component.text("Стоимость: ", NamedTextColor.GRAY)
.append(Component.text(price + " PP", NamedTextColor.GOLD))
.decoration(TextDecoration.ITALIC, false));
}
skullMeta.lore(lore);
head.setItemMeta(skullMeta);
inventory.setItem(GradientConstants.GUI_PREVIEW_SLOT, head);
ItemStack confirm = createItem(Material.LIME_WOOL, "§a§lПодтвердить");
ItemMeta confirmMeta = confirm.getItemMeta();
List<Component> confirmLore = new ArrayList<>();
confirmLore.add(Component.empty());
confirmLore.add(Component.text("Нажмите для подтверждения", NamedTextColor.GREEN)
.decoration(TextDecoration.ITALIC, false));
confirmMeta.lore(confirmLore);
confirm.setItemMeta(confirmMeta);
inventory.setItem(GradientConstants.GUI_CONFIRM_SLOT, confirm);
ItemStack cancel = createItem(Material.RED_WOOL, "§c§lОтменить");
ItemMeta cancelMeta = cancel.getItemMeta();
List<Component> cancelLore = new ArrayList<>();
cancelLore.add(Component.empty());
cancelLore.add(Component.text("Нажмите для отмены", NamedTextColor.RED)
.decoration(TextDecoration.ITALIC, false));
cancelMeta.lore(cancelLore);
cancel.setItemMeta(cancelMeta);
inventory.setItem(GradientConstants.GUI_CANCEL_SLOT, cancel);
}
private String buildPreview() {
String nick = player.getName();
String prefixToUse = this.prefix;
List<String> colorsToUse = this.colors;
if (type == ConfirmType.PREFIX && (colorsToUse == null || colorsToUse.isEmpty())) {
var data = module.getDataManager().getPlayerData(player.getUniqueId());
if (data.hasColors() && data.isColorEnabled()) {
colorsToUse = data.getColors();
}
}
if (type == ConfirmType.COLOR && (prefixToUse == null || prefixToUse.isEmpty())) {
var data = module.getDataManager().getPlayerData(player.getUniqueId());
if (data.hasPrefix() && data.isPrefixEnabled()) {
prefixToUse = data.getPrefix();
}
}
return GradientUtil.buildDisplayName(
prefixToUse,
nick,
colorsToUse,
module.getConfig().isGradientOnPrefix(),
module.getConfig().isContinuousGradient(),
module.getConfig().getPrefixFormat(),
module.getConfig().isUseLegacyRgbFormat()
);
}
private ItemStack createItem(Material material, String name) {
ItemStack item = new ItemStack(material);
ItemMeta meta = item.getItemMeta();
meta.displayName(LegacyComponentSerializer.legacySection().deserialize(name)
.decoration(TextDecoration.ITALIC, false));
item.setItemMeta(meta);
return item;
}
public void open() {
player.openInventory(inventory);
}
@Override
public @NotNull Inventory getInventory() { return inventory; }
public Player getPlayer() { return player; }
public ConfirmType getType() { return type; }
public List<String> getColors() { return colors; }
public String getPrefix() { return prefix; }
public int getPrice() { return price; }
}

View file

@ -0,0 +1,95 @@
package com.loki.lochat.gradient.hooks;
import com.loki.lochat.gradient.util.GradientConstants;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.cacheddata.CachedMetaData;
import net.luckperms.api.model.user.User;
import net.luckperms.api.node.types.PrefixNode;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.UUID;
/**
* Хук для LuckPerms
*/
public class GradientLuckPermsHook {
private LuckPerms luckPerms;
private boolean enabled;
public GradientLuckPermsHook(JavaPlugin plugin) {
this.enabled = false;
if (Bukkit.getPluginManager().isPluginEnabled("LuckPerms")) {
try {
this.luckPerms = LuckPermsProvider.get();
this.enabled = true;
plugin.getLogger().info("Gradient: LuckPerms подключен!");
} catch (Exception e) {
plugin.getLogger().warning("Gradient: Не удалось подключить LuckPerms: " + e.getMessage());
}
}
}
public boolean isEnabled() {
return enabled && luckPerms != null;
}
public void setPrefix(Player player, String prefix) {
if (!isEnabled()) return;
User user = luckPerms.getUserManager().getUser(player.getUniqueId());
if (user == null) return;
user.data().clear(node ->
node instanceof PrefixNode && ((PrefixNode) node).getPriority() == GradientConstants.LUCKPERMS_PREFIX_PRIORITY
);
if (prefix != null && !prefix.isEmpty()) {
PrefixNode prefixNode = PrefixNode.builder(prefix, GradientConstants.LUCKPERMS_PREFIX_PRIORITY).build();
user.data().add(prefixNode);
}
luckPerms.getUserManager().saveUser(user);
}
public void setPrefix(UUID uuid, String prefix) {
if (!isEnabled()) return;
luckPerms.getUserManager().loadUser(uuid).thenAccept(user -> {
if (user == null) return;
user.data().clear(node ->
node instanceof PrefixNode && ((PrefixNode) node).getPriority() == GradientConstants.LUCKPERMS_PREFIX_PRIORITY
);
if (prefix != null && !prefix.isEmpty()) {
PrefixNode prefixNode = PrefixNode.builder(prefix, GradientConstants.LUCKPERMS_PREFIX_PRIORITY).build();
user.data().add(prefixNode);
}
luckPerms.getUserManager().saveUser(user);
});
}
public void removePrefix(Player player) {
setPrefix(player, null);
}
public void removePrefix(UUID uuid) {
setPrefix(uuid, null);
}
public String getActivePrefix(Player player) {
if (!isEnabled()) return null;
User user = luckPerms.getUserManager().getUser(player.getUniqueId());
if (user == null) return null;
CachedMetaData metaData = user.getCachedData().getMetaData();
return metaData.getPrefix();
}
}

View file

@ -0,0 +1,96 @@
package com.loki.lochat.gradient.listeners;
import com.loki.lochat.gradient.GradientModule;
import com.loki.lochat.gradient.config.GradientMessages;
import com.loki.lochat.gradient.data.GradientPlayerData;
import com.loki.lochat.gradient.gui.GradientConfirmGUI;
import com.loki.lochat.gradient.util.DisplayNameUtil;
import com.loki.lochat.gradient.util.FoliaUtil;
import com.loki.lochat.gradient.util.GradientConstants;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import java.util.Map;
/**
* Слушатель GUI градиентного модуля
*/
public class GradientGUIListener implements Listener {
private final GradientModule module;
private static final Map<GradientConfirmGUI.ConfirmType, String> PRICE_MSG_KEYS = Map.of(
GradientConfirmGUI.ConfirmType.COLOR, "not-enough-points-color",
GradientConfirmGUI.ConfirmType.PREFIX, "not-enough-points-prefix"
);
public GradientGUIListener(GradientModule module) {
this.module = module;
}
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
if (!(event.getInventory().getHolder() instanceof GradientConfirmGUI gui)) return;
event.setCancelled(true);
if (!(event.getWhoClicked() instanceof Player player) || !player.equals(gui.getPlayer())) return;
switch (event.getRawSlot()) {
case GradientConstants.GUI_CONFIRM_SLOT -> handleConfirm(player, gui);
case GradientConstants.GUI_CANCEL_SLOT -> handleCancel(player);
}
}
private void handleConfirm(Player player, GradientConfirmGUI gui) {
player.closeInventory();
GradientMessages msg = module.getMessages();
int price = gui.getPrice();
if (price > 0 && !player.hasPermission("gradient.bypass.cost")) {
if (!module.hasPlayerPoints()) {
player.sendMessage("§cPlayerPoints не установлен!");
return;
}
int balance = module.getPlayerPointsAPI().look(player.getUniqueId());
if (balance < price) {
player.sendMessage(msg.get(PRICE_MSG_KEYS.get(gui.getType()), "price", String.valueOf(price)));
return;
}
module.getPlayerPointsAPI().take(player.getUniqueId(), price);
}
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
switch (gui.getType()) {
case COLOR -> {
data.setColors(gui.getColors());
data.setColorEnabled(true);
data.setLastColorChange(System.currentTimeMillis());
player.sendMessage(msg.get("color-success", "price", String.valueOf(price)));
}
case PREFIX -> {
data.setPrefix(gui.getPrefix());
data.setPrefixEnabled(true);
data.setPrefixPurchased(true);
data.setLastPrefixChange(System.currentTimeMillis());
module.getLuckPermsHook().setPrefix(player, DisplayNameUtil.buildColoredPrefix(module, data));
player.sendMessage(msg.get(price > 0 ? "prefix-success" : "prefix-success-free",
"price", String.valueOf(price)));
}
}
FoliaUtil.runEntityTask(module.getPlugin(), player,
() -> DisplayNameUtil.updateDisplayName(module, player, data));
FoliaUtil.runAsync(module.getPlugin(),
() -> module.getDataManager().savePlayerData(player.getUniqueId()));
}
private void handleCancel(Player player) {
player.closeInventory();
player.sendMessage("§7Действие отменено.");
}
}

View file

@ -0,0 +1,51 @@
package com.loki.lochat.gradient.listeners;
import com.loki.lochat.gradient.GradientModule;
import com.loki.lochat.gradient.data.GradientPlayerData;
import com.loki.lochat.gradient.util.DisplayNameUtil;
import com.loki.lochat.gradient.util.FoliaUtil;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
/**
* Слушатель событий игроков для градиентного модуля
*/
public class GradientPlayerListener implements Listener {
private final GradientModule module;
public GradientPlayerListener(GradientModule module) {
this.module = module;
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
FoliaUtil.runAsync(module.getPlugin(), () -> {
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
FoliaUtil.runEntityTask(module.getPlugin(), player, () -> {
if (!player.isOnline()) return;
DisplayNameUtil.updateDisplayName(module, player, data);
if (data.hasPrefix() && data.isPrefixEnabled()) {
module.getLuckPermsHook().setPrefix(player,
DisplayNameUtil.buildColoredPrefix(module, data));
}
});
});
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
FoliaUtil.runAsync(module.getPlugin(),
() -> module.getDataManager().savePlayerData(player.getUniqueId()));
}
}

View file

@ -0,0 +1,118 @@
package com.loki.lochat.gradient.util;
import com.loki.lochat.gradient.GradientModule;
import com.loki.lochat.gradient.config.GradientConfig;
import com.loki.lochat.gradient.data.GradientPlayerData;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.entity.Player;
import java.util.List;
/**
* Утилита для обновления отображаемого имени игрока
*/
public final class DisplayNameUtil {
private static final LegacyComponentSerializer SERIALIZER = LegacyComponentSerializer.builder()
.character('&')
.hexCharacter('#')
.hexColors()
.build();
private DisplayNameUtil() {}
public static void updateDisplayName(GradientModule module, Player player, GradientPlayerData data) {
GradientConfig cfg = module.getConfig();
String prefix = null;
String prefixFormat = cfg.getPrefixFormat();
if (data.isPrefixEnabled() && data.hasPrefix()) {
prefix = data.getPrefix();
} else {
String lpPrefix = module.getLuckPermsHook().getActivePrefix(player);
if (lpPrefix != null && !lpPrefix.isEmpty()) {
String displayName = buildWithLuckPermsPrefix(
lpPrefix,
player.getName(),
data.isColorEnabled() ? data.getColors() : null,
cfg.isUseLegacyRgbFormat(),
cfg.isContinuousGradient()
);
var component = SERIALIZER.deserialize(displayName);
player.displayName(component);
player.playerListName(component);
return;
}
}
String displayName = GradientUtil.buildDisplayName(
prefix,
player.getName(),
data.isColorEnabled() ? data.getColors() : null,
cfg.isGradientOnPrefix(),
cfg.isContinuousGradient(),
prefixFormat,
cfg.isUseLegacyRgbFormat()
);
var component = SERIALIZER.deserialize(displayName);
player.displayName(component);
player.playerListName(component);
}
private static String buildWithLuckPermsPrefix(String lpPrefix, String nick,
List<String> colors,
boolean useLegacyFormat,
boolean continuousGradient) {
if (colors == null || colors.isEmpty()) {
return lpPrefix + nick;
}
if (continuousGradient) {
String cleanPrefix = stripColors(lpPrefix);
String fullText = cleanPrefix + nick;
return GradientUtil.applyGradient(fullText, colors, useLegacyFormat);
} else {
return lpPrefix + GradientUtil.applyGradient(nick, colors, useLegacyFormat);
}
}
private static String stripColors(String text) {
if (text == null) return null;
return text.replaceAll("(?i)(§x(§[0-9a-f]){6}|§[0-9a-fk-or]|&[0-9a-fk-or]|&#[0-9a-f]{6})", "");
}
public static String buildColoredPrefix(GradientModule module, GradientPlayerData data) {
if (!data.hasPrefix() || !data.isPrefixEnabled()) return null;
GradientConfig cfg = module.getConfig();
String prefix = cfg.getPrefixFormat().replace("{prefix}", data.getPrefix()).stripTrailing();
return (data.hasColors() && data.isColorEnabled() && cfg.isGradientOnPrefix())
? GradientUtil.applyGradient(prefix, data.getColors(), cfg.isUseLegacyRgbFormat())
: prefix;
}
/**
* Получает полное отображаемое имя игрока с градиентом
*/
public static String getFullDisplayName(GradientModule module, Player player) {
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
GradientConfig cfg = module.getConfig();
String prefix = null;
if (data.isPrefixEnabled() && data.hasPrefix()) {
prefix = data.getPrefix();
}
return GradientUtil.buildDisplayName(
prefix,
player.getName(),
data.isColorEnabled() ? data.getColors() : null,
cfg.isGradientOnPrefix(),
cfg.isContinuousGradient(),
cfg.getPrefixFormat(),
cfg.isUseLegacyRgbFormat()
);
}
}

View file

@ -0,0 +1,74 @@
package com.loki.lochat.gradient.util;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import org.bukkit.plugin.Plugin;
import java.util.concurrent.TimeUnit;
/**
* Утилита для работы с Folia
*/
public final class FoliaUtil {
private static final boolean IS_FOLIA;
static {
IS_FOLIA = checkFolia();
}
private FoliaUtil() {}
private static boolean checkFolia() {
try {
Class.forName("io.papermc.paper.threadedregions.RegionizedServer");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
public static boolean isFolia() {
return IS_FOLIA;
}
public static void runEntityTask(Plugin plugin, Entity entity, Runnable task) {
if (IS_FOLIA) {
entity.getScheduler().run(plugin, scheduledTask -> task.run(), null);
} else {
Bukkit.getScheduler().runTask(plugin, task);
}
}
public static void runEntityTaskLater(Plugin plugin, Entity entity, Runnable task, long delayTicks) {
if (IS_FOLIA) {
entity.getScheduler().runDelayed(plugin, scheduledTask -> task.run(), null, delayTicks);
} else {
Bukkit.getScheduler().runTaskLater(plugin, task, delayTicks);
}
}
public static void runAsync(Plugin plugin, Runnable task) {
if (IS_FOLIA) {
Bukkit.getAsyncScheduler().runNow(plugin, scheduledTask -> task.run());
} else {
Bukkit.getScheduler().runTaskAsynchronously(plugin, task);
}
}
public static void runAsyncLater(Plugin plugin, Runnable task, long delayMs) {
if (IS_FOLIA) {
Bukkit.getAsyncScheduler().runDelayed(plugin, scheduledTask -> task.run(), delayMs, TimeUnit.MILLISECONDS);
} else {
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, task, delayMs / 50);
}
}
public static void runGlobalTask(Plugin plugin, Runnable task) {
if (IS_FOLIA) {
Bukkit.getGlobalRegionScheduler().run(plugin, scheduledTask -> task.run());
} else {
Bukkit.getScheduler().runTask(plugin, task);
}
}
}

View file

@ -0,0 +1,19 @@
package com.loki.lochat.gradient.util;
/**
* Константы для градиентного модуля
*/
public final class GradientConstants {
private GradientConstants() {}
public static final long MILLIS_PER_SECOND = 1000L;
// GUI слоты
public static final int GUI_CONFIRM_SLOT = 11;
public static final int GUI_CANCEL_SLOT = 15;
public static final int GUI_PREVIEW_SLOT = 13;
// LuckPerms
public static final int LUCKPERMS_PREFIX_PRIORITY = 100;
}

View file

@ -0,0 +1,123 @@
package com.loki.lochat.gradient.util;
import java.awt.Color;
import java.util.List;
import java.util.regex.Pattern;
/**
* Утилита для работы с градиентами
*/
public final class GradientUtil {
private static final Pattern HEX_PATTERN = Pattern.compile("^#[0-9A-Fa-f]{6}$");
private GradientUtil() {}
public static boolean isValidHex(String color) {
return HEX_PATTERN.matcher(color).matches();
}
public static String applyGradient(String text, List<String> hexColors, boolean useLegacyFormat) {
if (text == null || text.isEmpty() || hexColors == null || hexColors.isEmpty()) {
return text;
}
char[] chars = text.toCharArray();
int length = chars.length;
if (length == 0) return text;
StringBuilder result = new StringBuilder();
if (hexColors.size() == 1) {
String colorCode = formatHex(hexColors.get(0), useLegacyFormat);
for (char c : chars) {
result.append(colorCode).append(c);
}
} else {
Color[] colors = hexColors.stream()
.map(GradientUtil::parseHex)
.toArray(Color[]::new);
for (int i = 0; i < length; i++) {
Color interpolated = interpolateMultiColor(colors, (double) i / Math.max(1, length - 1));
String hex = String.format("#%02x%02x%02x",
interpolated.getRed(), interpolated.getGreen(), interpolated.getBlue());
result.append(formatHex(hex, useLegacyFormat)).append(chars[i]);
}
}
return result.toString();
}
private static Color interpolateMultiColor(Color[] colors, double ratio) {
if (colors.length == 1) return colors[0];
if (ratio <= 0) return colors[0];
if (ratio >= 1) return colors[colors.length - 1];
double scaledRatio = ratio * (colors.length - 1);
int index = (int) scaledRatio;
double localRatio = scaledRatio - index;
if (index >= colors.length - 1) {
return colors[colors.length - 1];
}
return interpolate(colors[index], colors[index + 1], localRatio);
}
private static Color interpolate(Color c1, Color c2, double ratio) {
int r = (int) (c1.getRed() + (c2.getRed() - c1.getRed()) * ratio);
int g = (int) (c1.getGreen() + (c2.getGreen() - c1.getGreen()) * ratio);
int b = (int) (c1.getBlue() + (c2.getBlue() - c1.getBlue()) * ratio);
return new Color(
Math.max(0, Math.min(255, r)),
Math.max(0, Math.min(255, g)),
Math.max(0, Math.min(255, b))
);
}
private static Color parseHex(String hex) {
hex = hex.replace("#", "");
return new Color(
Integer.parseInt(hex.substring(0, 2), 16),
Integer.parseInt(hex.substring(2, 4), 16),
Integer.parseInt(hex.substring(4, 6), 16)
);
}
private static String formatHex(String hex, boolean useLegacyFormat) {
hex = hex.replace("#", "").toLowerCase();
if (useLegacyFormat) {
return "&#" + hex;
} else {
return "<#" + hex + ">";
}
}
public static String buildDisplayName(String prefix, String nick, List<String> colors,
boolean gradientOnPrefix, boolean continuousGradient,
String prefixFormat, boolean useLegacyFormat) {
if (colors == null || colors.isEmpty()) {
if (prefix != null && !prefix.isEmpty()) {
return prefixFormat.replace("{prefix}", prefix) + nick;
}
return nick;
}
if (prefix == null || prefix.isEmpty()) {
return applyGradient(nick, colors, useLegacyFormat);
}
if (continuousGradient && gradientOnPrefix) {
String fullText = prefixFormat.replace("{prefix}", prefix) + nick;
return applyGradient(fullText, colors, useLegacyFormat);
} else if (gradientOnPrefix) {
String coloredPrefix = applyGradient(prefixFormat.replace("{prefix}", prefix), colors, useLegacyFormat);
String coloredNick = applyGradient(nick, colors, useLegacyFormat);
return coloredPrefix + coloredNick;
} else {
String formattedPrefix = prefixFormat.replace("{prefix}", prefix);
return formattedPrefix + applyGradient(nick, colors, useLegacyFormat);
}
}
}

View file

@ -0,0 +1,91 @@
package com.loki.lochat.integrations;
import com.loki.lochat.LoChat;
import com.loki.lochat.gradient.GradientModule;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
public class PlaceholderAPIHook extends PlaceholderExpansion {
private final LoChat plugin;
public PlaceholderAPIHook(LoChat plugin) {
this.plugin = plugin;
}
@Override
public @NotNull String getIdentifier() {
return "lochat";
}
@Override
public @NotNull String getAuthor() {
return "loki";
}
@Override
public @NotNull String getVersion() {
return plugin.getDescription().getVersion();
}
@Override
public boolean persist() {
return true;
}
@Override
public @Nullable String onRequest(OfflinePlayer player, @NotNull String params) {
if (player == null) return "";
UUID uuid = player.getUniqueId();
GradientModule gradient = plugin.getGradientModule();
return switch (params.toLowerCase()) {
case "muted" -> String.valueOf(plugin.getMuteManager().isMuted(uuid));
case "mute_time" -> {
String time = plugin.getMuteManager().getRemainingTime(uuid);
yield time != null ? time : "0";
}
case "mute_reason" -> {
String reason = plugin.getMuteManager().getReason(uuid);
yield reason != null ? reason : "";
}
case "ignored_count" -> String.valueOf(plugin.getIgnoreManager().getIgnoredCount(uuid));
case "global_enabled" -> String.valueOf(!plugin.getChatManager().isGlobalChatDisabled(uuid));
case "last_pm" -> {
UUID last = plugin.getPmManager().getLastConversation(uuid);
if (last == null) yield "";
var lastPlayer = Bukkit.getOfflinePlayer(last);
yield lastPlayer.getName() != null ? lastPlayer.getName() : "";
}
case "spy_enabled" -> String.valueOf(plugin.getSpyManager().isSpying(uuid));
// Gradient placeholders (совместимость с LoPreff)
case "gradient_full", "full" -> {
if (gradient == null || !gradient.isEnabled()) yield player.getName();
Player onlinePlayer = player.getPlayer();
if (onlinePlayer == null) yield player.getName();
yield gradient.getFormattedName(onlinePlayer);
}
case "gradient_name", "name" -> {
if (gradient == null || !gradient.isEnabled()) yield player.getName();
Player onlinePlayer = player.getPlayer();
if (onlinePlayer == null) yield player.getName();
yield gradient.getGradientNick(onlinePlayer);
}
case "gradient_prefix", "prefix" -> {
if (gradient == null || !gradient.isEnabled()) yield "";
Player onlinePlayer = player.getPlayer();
if (onlinePlayer == null) yield "";
yield gradient.getPrefix(onlinePlayer);
}
default -> null;
};
}
}

View file

@ -0,0 +1,124 @@
package com.loki.lochat.listeners;
import com.loki.lochat.LoChat;
import com.loki.lochat.managers.AntiSpamManager;
import com.loki.lochat.managers.FilterManager;
import com.loki.lochat.utils.ChatFormatter;
import io.papermc.paper.event.player.AsyncChatEvent;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
public class ChatListener implements Listener {
private final LoChat plugin;
public ChatListener(LoChat plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onChat(AsyncChatEvent event) {
event.setCancelled(true);
Player player = event.getPlayer();
String message = net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer
.plainText().serialize(event.message());
// Обработка через RegionScheduler для Folia
player.getScheduler().run(plugin, task -> {
processChat(player, message);
}, null);
}
private void processChat(Player player, String message) {
// Проверка мута
if (plugin.getMuteManager().isMuted(player.getUniqueId())) {
String remaining = plugin.getMuteManager().getRemainingTime(player.getUniqueId());
if (remaining.equals("навсегда")) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("mute.permanent")));
} else {
player.sendMessage(ChatFormatter.parse(
plugin.getMessageConfig().get("mute.you-muted", "{time}", remaining)
));
}
return;
}
String globalSymbol = plugin.getConfigManager().getGlobalSymbol();
boolean isGlobal = message.startsWith(globalSymbol) && player.hasPermission("chat.global.use");
String chatType = isGlobal ? "global" : "local";
String processedMessage = isGlobal ? message.substring(globalSymbol.length()).trim() : message;
if (processedMessage.isEmpty()) return;
// Проверка кулдауна
int cooldown = isGlobal ? plugin.getConfigManager().getGlobalCooldown() : plugin.getConfigManager().getLocalCooldown();
if (cooldown > 0 && !player.hasPermission("chat.bypass.cooldown")) {
if (plugin.getCooldownManager().isOnCooldown(player.getUniqueId(), chatType, cooldown)) {
int remaining = plugin.getCooldownManager().getRemainingCooldown(player.getUniqueId(), chatType, cooldown);
player.sendMessage(ChatFormatter.parse(
plugin.getMessageConfig().get("cooldown.wait", "{time}", String.valueOf(remaining))
));
return;
}
}
// Проверка анти-спама
if (!player.hasPermission("chat.bypass.antispam")) {
AntiSpamManager.SpamResult spamResult = plugin.getAntiSpamManager().checkMessage(player.getUniqueId(), processedMessage);
switch (spamResult) {
case TOO_MANY_CAPS -> {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("antispam.caps")));
return;
}
case REPEAT_CHARS -> {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("antispam.repeat")));
return;
}
case SIMILAR_MESSAGE -> {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("antispam.similar")));
return;
}
}
}
// Проверка фильтра
if (!player.hasPermission("chat.bypass.filter")) {
FilterManager.FilterResult filterResult = plugin.getFilterManager().checkMessage(processedMessage);
switch (filterResult) {
case BLOCKED -> {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("filter.blocked")));
return;
}
case WARNED -> {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("filter.warned")));
// Продолжаем, но цензурим
processedMessage = plugin.getFilterManager().censorMessage(processedMessage);
}
case CENSORED -> processedMessage = plugin.getFilterManager().censorMessage(processedMessage);
}
}
// Устанавливаем кулдаун
plugin.getCooldownManager().setCooldown(player.getUniqueId(), chatType);
// Отправка сообщения
if (isGlobal) {
plugin.getChatManager().sendGlobalMessage(player, processedMessage);
} else {
plugin.getChatManager().sendLocalMessage(player, processedMessage);
}
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
plugin.getPmManager().removeConversation(player.getUniqueId());
plugin.getCooldownManager().removeCooldown(player.getUniqueId());
plugin.getAntiSpamManager().clearPlayer(player.getUniqueId());
plugin.getSpyManager().removeSpy(player.getUniqueId());
}
}

View file

@ -0,0 +1,128 @@
package com.loki.lochat.managers;
import com.loki.lochat.LoChat;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class AntiSpamManager {
private final LoChat plugin;
private final Map<UUID, String> lastMessages = new ConcurrentHashMap<>();
private final Map<UUID, Long> lastMessageTime = new ConcurrentHashMap<>();
public AntiSpamManager(LoChat plugin) {
this.plugin = plugin;
}
public enum SpamResult {
ALLOWED,
TOO_MANY_CAPS,
REPEAT_CHARS,
SIMILAR_MESSAGE
}
public SpamResult checkMessage(UUID player, String message) {
if (!plugin.getConfigManager().isAntiSpamEnabled()) {
return SpamResult.ALLOWED;
}
// Проверка CAPS
if (hasTooManyCaps(message)) {
return SpamResult.TOO_MANY_CAPS;
}
// Проверка повторяющихся символов
if (hasRepeatChars(message)) {
return SpamResult.REPEAT_CHARS;
}
// Проверка похожих сообщений
if (isSimilarMessage(player, message)) {
return SpamResult.SIMILAR_MESSAGE;
}
// Сохраняем сообщение
lastMessages.put(player, message.toLowerCase());
lastMessageTime.put(player, System.currentTimeMillis());
return SpamResult.ALLOWED;
}
private boolean hasTooManyCaps(String message) {
if (message.length() < 5) return false;
int maxPercent = plugin.getConfigManager().getMaxCapsPercent();
long upperCount = message.chars().filter(Character::isUpperCase).count();
long letterCount = message.chars().filter(Character::isLetter).count();
if (letterCount == 0) return false;
int percent = (int) ((upperCount * 100) / letterCount);
return percent > maxPercent;
}
private boolean hasRepeatChars(String message) {
int maxRepeat = plugin.getConfigManager().getMaxRepeatChars();
int count = 1;
char lastChar = 0;
for (char c : message.toCharArray()) {
if (c == lastChar) {
count++;
if (count > maxRepeat) return true;
} else {
count = 1;
lastChar = c;
}
}
return false;
}
private boolean isSimilarMessage(UUID player, String message) {
String lastMsg = lastMessages.get(player);
Long lastTime = lastMessageTime.get(player);
if (lastMsg == null || lastTime == null) return false;
int delay = plugin.getConfigManager().getSimilarMessageDelay();
long elapsed = (System.currentTimeMillis() - lastTime) / 1000;
if (elapsed > delay) return false;
// Проверка схожести (простая)
return message.toLowerCase().equals(lastMsg) ||
similarity(message.toLowerCase(), lastMsg) > 0.8;
}
private double similarity(String s1, String s2) {
int maxLen = Math.max(s1.length(), s2.length());
if (maxLen == 0) return 1.0;
int distance = levenshteinDistance(s1, s2);
return 1.0 - ((double) distance / maxLen);
}
private int levenshteinDistance(String s1, String s2) {
int[][] dp = new int[s1.length() + 1][s2.length() + 1];
for (int i = 0; i <= s1.length(); i++) dp[i][0] = i;
for (int j = 0; j <= s2.length(); j++) dp[0][j] = j;
for (int i = 1; i <= s1.length(); i++) {
for (int j = 1; j <= s2.length(); j++) {
int cost = s1.charAt(i - 1) == s2.charAt(j - 1) ? 0 : 1;
dp[i][j] = Math.min(Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1), dp[i - 1][j - 1] + cost);
}
}
return dp[s1.length()][s2.length()];
}
public void clearPlayer(UUID player) {
lastMessages.remove(player);
lastMessageTime.remove(player);
}
}

View file

@ -0,0 +1,174 @@
package com.loki.lochat.managers;
import com.loki.lochat.LoChat;
import com.loki.lochat.utils.ChatFormatter;
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
public class AutoMessageManager {
private final LoChat plugin;
private final Random random = new Random();
private final AtomicInteger currentIndex = new AtomicInteger(0);
private ScheduledTask task;
private Map<String, List<String>> messages;
private List<String> messageOrder;
private String prefix;
private String mode;
public AutoMessageManager(LoChat plugin) {
this.plugin = plugin;
loadMessages();
}
private void loadMessages() {
File file = new File(plugin.getDataFolder(), "automessages.yml");
if (!file.exists()) {
plugin.saveResource("automessages.yml", false);
}
FileConfiguration config = YamlConfiguration.loadConfiguration(file);
// Настройки
prefix = config.getString("settings.prefix", "&6[&eINFO&6]&r");
mode = config.getString("settings.mode", "sequential").toLowerCase();
// Загружаем сообщения
messages = new LinkedHashMap<>();
ConfigurationSection msgSection = config.getConfigurationSection("messages");
if (msgSection != null) {
for (String key : msgSection.getKeys(false)) {
List<String> lines = new ArrayList<>();
if (msgSection.isList(key)) {
lines.addAll(msgSection.getStringList(key));
} else {
String single = msgSection.getString(key);
if (single != null) {
lines.add(single);
}
}
if (!lines.isEmpty()) {
messages.put(key, lines);
}
}
}
// Определяем порядок
messageOrder = new ArrayList<>();
if (mode.equals("custom")) {
String orderStr = config.getString("settings.order", "");
for (String item : orderStr.split(",")) {
String trimmed = item.trim();
if (!trimmed.isEmpty()) {
try {
int index = Integer.parseInt(trimmed) - 1;
List<String> keys = new ArrayList<>(messages.keySet());
if (index >= 0 && index < keys.size()) {
messageOrder.add(keys.get(index));
}
} catch (NumberFormatException e) {
if (messages.containsKey(trimmed)) {
messageOrder.add(trimmed);
}
}
}
}
}
if (messageOrder.isEmpty()) {
messageOrder.addAll(messages.keySet());
}
plugin.getLogger().info("Загружено " + messages.size() + " автосообщений");
}
public void start() {
if (!plugin.getConfigManager().isAutoMessagesEnabled()) {
return;
}
if (messages == null || messages.isEmpty()) {
plugin.getLogger().warning("Автосообщения не настроены!");
return;
}
int intervalSeconds = plugin.getConfigManager().getAutoMessagesInterval();
long intervalTicks = intervalSeconds * 20L;
task = plugin.getServer().getGlobalRegionScheduler().runAtFixedRate(
plugin,
scheduledTask -> broadcastNextMessage(),
intervalTicks,
intervalTicks
);
plugin.getLogger().info("Автосообщения запущены (интервал: " + intervalSeconds + " сек, режим: " + mode + ")");
}
public void stop() {
if (task != null && !task.isCancelled()) {
task.cancel();
task = null;
}
}
private void broadcastNextMessage() {
if (messageOrder.isEmpty() || Bukkit.getOnlinePlayers().isEmpty()) {
return;
}
String messageKey;
if (mode.equals("random")) {
messageKey = messageOrder.get(random.nextInt(messageOrder.size()));
} else {
int index = currentIndex.getAndUpdate(i -> (i + 1) % messageOrder.size());
messageKey = messageOrder.get(index);
}
List<String> lines = messages.get(messageKey);
if (lines == null || lines.isEmpty()) return;
boolean isFirstLine = true;
for (String line : lines) {
// Заменяем смайлики через EmojiManager
String processed = ChatFormatter.replaceEmojis(line);
// Заменяем плейсхолдеры
processed = processed.replace("%online%", String.valueOf(Bukkit.getOnlinePlayers().size()));
// Добавляем префикс к первой непустой строке
Component formatted;
if (isFirstLine && !line.trim().isEmpty()) {
formatted = ChatFormatter.parse(prefix + " " + processed);
isFirstLine = false;
} else {
formatted = ChatFormatter.parse(processed);
}
for (var player : Bukkit.getOnlinePlayers()) {
player.sendMessage(formatted);
}
}
}
public void reload() {
stop();
loadMessages();
currentIndex.set(0);
start();
}
}

View file

@ -0,0 +1,115 @@
package com.loki.lochat.managers;
import com.loki.lochat.LoChat;
import com.loki.lochat.utils.ChatFormatter;
import com.loki.lochat.utils.DistanceUtil;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class ChatManager {
private final LoChat plugin;
private final Set<UUID> globalChatDisabled = ConcurrentHashMap.newKeySet();
public ChatManager(LoChat plugin) {
this.plugin = plugin;
}
public void sendGlobalMessage(Player sender, String message) {
if (!plugin.getConfigManager().isGlobalEnabled()) {
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("global.disabled")));
return;
}
// Обработка упоминаний @ник
Set<Player> mentionedPlayers = new HashSet<>();
String processedMessage = plugin.getMentionManager().processMentions(message, mentionedPlayers);
String format = plugin.getConfigManager().getGlobalFormat();
String prefix = plugin.getConfigManager().getGlobalPrefix();
boolean hasColor = sender.hasPermission("chat.global.color");
// Отправка каждому игроку персонализированное сообщение
for (Player player : Bukkit.getOnlinePlayers()) {
if (isGlobalChatDisabled(player.getUniqueId())) continue;
// Персонализируем сообщение для этого игрока (выделяем его ник)
String personalizedMsg = plugin.getMentionManager().getPersonalizedMessage(processedMessage, player);
Component formatted = ChatFormatter.formatGlobalMessage(format, prefix, sender, personalizedMsg, hasColor);
player.sendMessage(formatted);
// Проверяем упоминание ника без @ и уведомляем
plugin.getMentionManager().notifyIfMentioned(message, player, sender);
}
// Уведомление упомянутых через @
plugin.getMentionManager().notifyMentioned(mentionedPlayers);
plugin.getLogger().info("[G] " + sender.getName() + ": " + ChatFormatter.stripTags(message));
}
public void sendLocalMessage(Player sender, String message) {
if (!plugin.getConfigManager().isLocalEnabled()) {
return;
}
// Обработка упоминаний @ник
Set<Player> mentionedPlayers = new HashSet<>();
String processedMessage = plugin.getMentionManager().processMentions(message, mentionedPlayers);
int radius = plugin.getConfigManager().getLocalRadius();
String format = plugin.getConfigManager().getLocalFormat();
String prefix = plugin.getConfigManager().getLocalPrefix();
boolean hasColor = sender.hasPermission("chat.local.color");
boolean foundRecipient = false;
Set<Player> recipients = new HashSet<>();
for (Player player : Bukkit.getOnlinePlayers()) {
if (DistanceUtil.isInRange(sender, player, radius)) {
// Персонализируем сообщение для этого игрока
String personalizedMsg = plugin.getMentionManager().getPersonalizedMessage(processedMessage, player);
Component formatted = ChatFormatter.formatLocalMessage(format, prefix, sender, personalizedMsg, hasColor);
player.sendMessage(formatted);
recipients.add(player);
// Проверяем упоминание ника без @
plugin.getMentionManager().notifyIfMentioned(message, player, sender);
if (!player.equals(sender)) {
foundRecipient = true;
}
}
}
// Уведомление упомянутых через @ (только тех кто в радиусе)
mentionedPlayers.retainAll(recipients);
plugin.getMentionManager().notifyMentioned(mentionedPlayers);
if (!foundRecipient) {
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("local.no-players")));
}
}
public boolean toggleGlobalChat(UUID player) {
if (globalChatDisabled.contains(player)) {
globalChatDisabled.remove(player);
return true;
} else {
globalChatDisabled.add(player);
return false;
}
}
public boolean isGlobalChatDisabled(UUID player) {
return globalChatDisabled.contains(player);
}
}

View file

@ -0,0 +1,45 @@
package com.loki.lochat.managers;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class CooldownManager {
private final Map<UUID, Long> globalCooldowns = new ConcurrentHashMap<>();
private final Map<UUID, Long> localCooldowns = new ConcurrentHashMap<>();
public boolean isOnCooldown(UUID player, String chatType, int cooldownSeconds) {
Map<UUID, Long> cooldowns = chatType.equals("global") ? globalCooldowns : localCooldowns;
Long lastTime = cooldowns.get(player);
if (lastTime == null) {
return false;
}
long elapsed = (System.currentTimeMillis() - lastTime) / 1000;
return elapsed < cooldownSeconds;
}
public int getRemainingCooldown(UUID player, String chatType, int cooldownSeconds) {
Map<UUID, Long> cooldowns = chatType.equals("global") ? globalCooldowns : localCooldowns;
Long lastTime = cooldowns.get(player);
if (lastTime == null) {
return 0;
}
long elapsed = (System.currentTimeMillis() - lastTime) / 1000;
return Math.max(0, cooldownSeconds - (int) elapsed);
}
public void setCooldown(UUID player, String chatType) {
Map<UUID, Long> cooldowns = chatType.equals("global") ? globalCooldowns : localCooldowns;
cooldowns.put(player, System.currentTimeMillis());
}
public void removeCooldown(UUID player) {
globalCooldowns.remove(player);
localCooldowns.remove(player);
}
}

View file

@ -0,0 +1,100 @@
package com.loki.lochat.managers;
import com.loki.lochat.LoChat;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
public class EmojiManager {
private final LoChat plugin;
private final Map<String, String> emojis = new HashMap<>();
private boolean enabled;
private boolean requirePermission;
public EmojiManager(LoChat plugin) {
this.plugin = plugin;
load();
}
public void load() {
File file = new File(plugin.getDataFolder(), "emojis.yml");
if (!file.exists()) {
plugin.saveResource("emojis.yml", false);
}
FileConfiguration config = YamlConfiguration.loadConfiguration(file);
emojis.clear();
// Настройки
enabled = config.getBoolean("settings.enabled", true);
requirePermission = config.getBoolean("settings.require-permission", false);
// Загружаем все категории
for (String category : config.getKeys(false)) {
if (category.equals("settings")) continue;
ConfigurationSection section = config.getConfigurationSection(category);
if (section != null) {
for (String key : section.getKeys(false)) {
String value = section.getString(key);
if (value != null) {
emojis.put(key, value);
}
}
}
}
plugin.getLogger().info("Загружено " + emojis.size() + " смайликов");
}
public void reload() {
load();
}
/**
* Заменяет смайлики в сообщении
*/
public String process(String message, Player player) {
if (!enabled) return message;
// Проверка права
if (requirePermission && player != null && !player.hasPermission("chat.emoji.use")) {
return message;
}
String result = message;
for (Map.Entry<String, String> entry : emojis.entrySet()) {
result = result.replace(entry.getKey(), entry.getValue());
}
return result;
}
/**
* Заменяет смайлики без проверки прав (для автосообщений)
*/
public String process(String message) {
if (!enabled) return message;
String result = message;
for (Map.Entry<String, String> entry : emojis.entrySet()) {
result = result.replace(entry.getKey(), entry.getValue());
}
return result;
}
public Map<String, String> getEmojis() {
return emojis;
}
public boolean isEnabled() {
return enabled;
}
}

View file

@ -0,0 +1,61 @@
package com.loki.lochat.managers;
import com.loki.lochat.LoChat;
import java.util.List;
import java.util.regex.Pattern;
public class FilterManager {
private final LoChat plugin;
public FilterManager(LoChat plugin) {
this.plugin = plugin;
}
public enum FilterResult {
ALLOWED,
CENSORED,
BLOCKED,
WARNED
}
public FilterResult checkMessage(String message) {
if (!plugin.getConfigManager().isFilterEnabled()) {
return FilterResult.ALLOWED;
}
List<String> badWords = plugin.getConfigManager().getFilterWords();
String lowerMessage = message.toLowerCase();
for (String word : badWords) {
if (lowerMessage.contains(word.toLowerCase())) {
String action = plugin.getConfigManager().getFilterAction();
return switch (action) {
case "block" -> FilterResult.BLOCKED;
case "warn" -> FilterResult.WARNED;
default -> FilterResult.CENSORED;
};
}
}
return FilterResult.ALLOWED;
}
public String censorMessage(String message) {
if (!plugin.getConfigManager().isFilterEnabled()) {
return message;
}
List<String> badWords = plugin.getConfigManager().getFilterWords();
String replacement = plugin.getConfigManager().getFilterReplacement();
String result = message;
for (String word : badWords) {
Pattern pattern = Pattern.compile("(?i)" + Pattern.quote(word));
result = pattern.matcher(result).replaceAll(replacement);
}
return result;
}
}

View file

@ -0,0 +1,97 @@
package com.loki.lochat.managers;
import com.loki.lochat.LoChat;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class IgnoreManager {
private final LoChat plugin;
private final File ignoreFile;
private final Map<UUID, Set<UUID>> ignoreMap = new ConcurrentHashMap<>();
public IgnoreManager(LoChat plugin) {
this.plugin = plugin;
this.ignoreFile = new File(plugin.getDataFolder(), "ignores.yml");
load();
}
public void load() {
if (!ignoreFile.exists()) return;
YamlConfiguration config = YamlConfiguration.loadConfiguration(ignoreFile);
for (String key : config.getKeys(false)) {
try {
UUID uuid = UUID.fromString(key);
List<String> ignoredList = config.getStringList(key);
Set<UUID> ignored = ConcurrentHashMap.newKeySet();
for (String ignoredStr : ignoredList) {
try {
ignored.add(UUID.fromString(ignoredStr));
} catch (Exception ignored2) {}
}
if (!ignored.isEmpty()) {
ignoreMap.put(uuid, ignored);
}
} catch (Exception ignored) {}
}
}
public void save() {
YamlConfiguration config = new YamlConfiguration();
for (Map.Entry<UUID, Set<UUID>> entry : ignoreMap.entrySet()) {
List<String> ignoredList = new ArrayList<>();
for (UUID ignored : entry.getValue()) {
ignoredList.add(ignored.toString());
}
config.set(entry.getKey().toString(), ignoredList);
}
try {
config.save(ignoreFile);
} catch (IOException e) {
plugin.getLogger().severe("Не удалось сохранить игноры: " + e.getMessage());
}
}
public boolean isIgnoring(UUID player, UUID target) {
Set<UUID> ignored = ignoreMap.get(player);
return ignored != null && ignored.contains(target);
}
public boolean addIgnore(UUID player, UUID target) {
Set<UUID> ignored = ignoreMap.computeIfAbsent(player, k -> ConcurrentHashMap.newKeySet());
boolean added = ignored.add(target);
if (added) save();
return added;
}
public boolean removeIgnore(UUID player, UUID target) {
Set<UUID> ignored = ignoreMap.get(player);
if (ignored == null) return false;
boolean removed = ignored.remove(target);
if (removed) save();
return removed;
}
public Set<UUID> getIgnoredPlayers(UUID player) {
return ignoreMap.getOrDefault(player, Collections.emptySet());
}
public int getIgnoredCount(UUID player) {
Set<UUID> ignored = ignoreMap.get(player);
return ignored == null ? 0 : ignored.size();
}
public void clearIgnores(UUID player) {
ignoreMap.remove(player);
save();
}
}

View file

@ -0,0 +1,141 @@
package com.loki.lochat.managers;
import com.loki.lochat.LoChat;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MentionManager {
private final LoChat plugin;
private static final Pattern MENTION_PATTERN = Pattern.compile("@(\\w+)");
// Хранит для какого игрока какие части сообщения выделить
private final Map<Player, Set<String>> highlightedNicks = new HashMap<>();
public MentionManager(LoChat plugin) {
this.plugin = plugin;
}
/**
* Обрабатывает упоминания в сообщении
* Возвращает сообщение с подсвеченными никами
*/
public String processMentions(String message, Set<Player> mentionedPlayers) {
if (!plugin.getConfigManager().isMentionsEnabled()) {
return message;
}
highlightedNicks.clear();
String highlight = plugin.getConfigManager().getMentionHighlight();
Matcher matcher = MENTION_PATTERN.matcher(message);
StringBuilder result = new StringBuilder();
while (matcher.find()) {
String name = matcher.group(1);
Player mentioned = Bukkit.getPlayer(name);
if (mentioned != null && mentioned.isOnline()) {
mentionedPlayers.add(mentioned);
String replacement = highlight.replace("{player}", mentioned.getName());
matcher.appendReplacement(result, replacement);
// Запоминаем что этот игрок упомянут
highlightedNicks.computeIfAbsent(mentioned, k -> new HashSet<>()).add(mentioned.getName());
}
}
matcher.appendTail(result);
return result.toString();
}
/**
* Создаёт персонализированное сообщение для игрока
* Если его ник упомянут — выделяет его особым образом
*/
public String getPersonalizedMessage(String message, Player viewer) {
if (!plugin.getConfigManager().isMentionsEnabled()) {
return message;
}
String viewerName = viewer.getName();
String result = message;
// Проверяем упоминание через @ник
String highlight = plugin.getConfigManager().getMentionHighlight();
String highlightedName = highlight.replace("{player}", viewerName);
// Если в сообщении есть выделенный ник этого игрока — добавляем дополнительное выделение
if (result.contains(highlightedName)) {
// Уже выделено через processMentions, добавляем эффект
String extraHighlight = "<bold>" + highlightedName + "</bold>";
result = result.replace(highlightedName, extraHighlight);
}
// Также проверяем просто ник в сообщении (без @)
// Регистронезависимый поиск
Pattern nickPattern = Pattern.compile("(?i)\\b" + Pattern.quote(viewerName) + "\\b");
Matcher nickMatcher = nickPattern.matcher(result);
if (nickMatcher.find()) {
// Ник найден в сообщении — выделяем для этого игрока
String selfHighlight = plugin.getConfigManager().getSelfMentionHighlight();
result = nickMatcher.replaceAll(selfHighlight.replace("{player}", viewerName));
}
return result;
}
/**
* Проверяет, упомянут ли игрок в сообщении (по нику без @)
*/
public boolean isPlayerMentioned(String message, Player player) {
String name = player.getName();
Pattern pattern = Pattern.compile("(?i)\\b" + Pattern.quote(name) + "\\b");
return pattern.matcher(message).find();
}
/**
* Уведомляет упомянутых игроков звуком
*/
public void notifyMentioned(Set<Player> players) {
if (!plugin.getConfigManager().isMentionSoundEnabled()) {
return;
}
String soundName = plugin.getConfigManager().getMentionSoundType();
Sound sound;
try {
sound = Sound.valueOf(soundName);
} catch (IllegalArgumentException e) {
sound = Sound.BLOCK_NOTE_BLOCK_PLING;
}
for (Player player : players) {
player.playSound(player.getLocation(), sound, 1.0f, 1.0f);
}
}
/**
* Уведомляет игрока если его ник упомянут (без @)
*/
public void notifyIfMentioned(String message, Player viewer, Player sender) {
if (viewer.equals(sender)) return;
if (!plugin.getConfigManager().isMentionSoundEnabled()) return;
if (isPlayerMentioned(message, viewer)) {
String soundName = plugin.getConfigManager().getMentionSoundType();
try {
Sound sound = Sound.valueOf(soundName);
viewer.playSound(viewer.getLocation(), sound, 1.0f, 1.0f);
} catch (IllegalArgumentException ignored) {}
}
}
}

View file

@ -0,0 +1,136 @@
package com.loki.lochat.managers;
import com.loki.lochat.LoChat;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class MuteManager {
private final LoChat plugin;
private final File muteFile;
private final Map<UUID, Long> mutedPlayers = new ConcurrentHashMap<>();
private final Map<UUID, String> muteReasons = new ConcurrentHashMap<>();
public MuteManager(LoChat plugin) {
this.plugin = plugin;
this.muteFile = new File(plugin.getDataFolder(), "mutes.yml");
load();
}
public void load() {
if (!muteFile.exists()) return;
YamlConfiguration config = YamlConfiguration.loadConfiguration(muteFile);
for (String key : config.getKeys(false)) {
try {
UUID uuid = UUID.fromString(key);
long muteEnd = config.getLong(key + ".end");
String reason = config.getString(key + ".reason");
// Пропускаем истёкшие муты
if (muteEnd != 0 && System.currentTimeMillis() > muteEnd) continue;
mutedPlayers.put(uuid, muteEnd);
if (reason != null) muteReasons.put(uuid, reason);
} catch (Exception ignored) {}
}
}
public void save() {
YamlConfiguration config = new YamlConfiguration();
for (Map.Entry<UUID, Long> entry : mutedPlayers.entrySet()) {
String key = entry.getKey().toString();
config.set(key + ".end", entry.getValue());
String reason = muteReasons.get(entry.getKey());
if (reason != null) config.set(key + ".reason", reason);
}
try {
config.save(muteFile);
} catch (IOException e) {
plugin.getLogger().severe("Не удалось сохранить муты: " + e.getMessage());
}
}
public boolean isMuted(UUID player) {
Long muteEnd = mutedPlayers.get(player);
if (muteEnd == null) return false;
if (muteEnd == 0) return true;
if (System.currentTimeMillis() > muteEnd) {
mutedPlayers.remove(player);
muteReasons.remove(player);
save();
return false;
}
return true;
}
public void mute(UUID player, long durationSeconds, String reason) {
long muteEnd = durationSeconds <= 0 ? 0 : System.currentTimeMillis() + (durationSeconds * 1000);
mutedPlayers.put(player, muteEnd);
if (reason != null && !reason.isEmpty()) {
muteReasons.put(player, reason);
}
save();
}
public void unmute(UUID player) {
mutedPlayers.remove(player);
muteReasons.remove(player);
save();
}
public String getRemainingTime(UUID player) {
Long muteEnd = mutedPlayers.get(player);
if (muteEnd == null) return null;
if (muteEnd == 0) return "навсегда";
long remaining = (muteEnd - System.currentTimeMillis()) / 1000;
if (remaining <= 0) return null;
return formatTime(remaining);
}
public String getReason(UUID player) {
return muteReasons.get(player);
}
public boolean isAlreadyMuted(UUID player) {
return mutedPlayers.containsKey(player);
}
private String formatTime(long seconds) {
if (seconds < 60) return seconds + " сек";
if (seconds < 3600) return (seconds / 60) + " мин";
if (seconds < 86400) return (seconds / 3600) + " ч";
return (seconds / 86400) + " д";
}
public static long parseTime(String time) {
if (time == null || time.isEmpty()) return -1;
try {
char unit = time.charAt(time.length() - 1);
long value = Long.parseLong(time.substring(0, time.length() - 1));
return switch (Character.toLowerCase(unit)) {
case 's' -> value;
case 'm' -> value * 60;
case 'h' -> value * 3600;
case 'd' -> value * 86400;
default -> Long.parseLong(time);
};
} catch (NumberFormatException e) {
return -1;
}
}
}

View file

@ -0,0 +1,27 @@
package com.loki.lochat.managers;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class PMManager {
// UUID игрока -> UUID последнего собеседника
private final Map<UUID, UUID> lastConversation = new ConcurrentHashMap<>();
public void setLastConversation(UUID player, UUID target) {
lastConversation.put(player, target);
}
public UUID getLastConversation(UUID player) {
return lastConversation.get(player);
}
public void removeConversation(UUID player) {
lastConversation.remove(player);
}
public boolean hasConversation(UUID player) {
return lastConversation.containsKey(player);
}
}

View file

@ -0,0 +1,52 @@
package com.loki.lochat.managers;
import com.loki.lochat.LoChat;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class SpyManager {
private final LoChat plugin;
private final Set<UUID> spyEnabled = ConcurrentHashMap.newKeySet();
public SpyManager(LoChat plugin) {
this.plugin = plugin;
}
public boolean toggleSpy(UUID player) {
if (spyEnabled.contains(player)) {
spyEnabled.remove(player);
return false;
} else {
spyEnabled.add(player);
return true;
}
}
public boolean isSpying(UUID player) {
return spyEnabled.contains(player);
}
public void broadcastPM(Player sender, Player receiver, String message) {
String format = plugin.getMessageConfig().get("spy.format");
for (UUID spyUuid : spyEnabled) {
Player spy = Bukkit.getPlayer(spyUuid);
if (spy != null && spy.isOnline() && !spy.equals(sender) && !spy.equals(receiver)) {
spy.sendMessage(ChatFormatter.parse(format
.replace("{sender}", sender.getName())
.replace("{receiver}", receiver.getName())
.replace("{message}", message)));
}
}
}
public void removeSpy(UUID player) {
spyEnabled.remove(player);
}
}

View file

@ -0,0 +1,228 @@
package com.loki.lochat.utils;
import com.loki.lochat.LoChat;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.entity.Player;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ChatFormatter {
private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();
// Паттерны для цветов
private static final Pattern HEX_PATTERN = Pattern.compile("&#([A-Fa-f0-9]{6})");
private static final Pattern LEGACY_COLOR_PATTERN = Pattern.compile("&([0-9a-fk-orA-FK-OR])");
private ChatFormatter() {}
/**
* Конвертирует все форматы цветов в MiniMessage
* Поддерживает: &коды, &#HEX, MiniMessage теги
*/
public static String convertAllColors(String message) {
String result = message;
// 1. Конвертируем &#RRGGBB -> <#RRGGBB>
result = convertHexColors(result);
// 2. Конвертируем &коды -> MiniMessage теги
result = convertLegacyColors(result);
return result;
}
/**
* Конвертирует &#RRGGBB в MiniMessage формат <#RRGGBB>
*/
public static String convertHexColors(String message) {
Matcher matcher = HEX_PATTERN.matcher(message);
StringBuilder result = new StringBuilder();
while (matcher.find()) {
matcher.appendReplacement(result, "<#" + matcher.group(1) + ">");
}
matcher.appendTail(result);
return result.toString();
}
/**
* Конвертирует &коды в MiniMessage теги
* &a -> <green>, &b -> <aqua>, &l -> <bold>, etc.
*/
public static String convertLegacyColors(String message) {
StringBuilder result = new StringBuilder();
Matcher matcher = LEGACY_COLOR_PATTERN.matcher(message);
while (matcher.find()) {
String code = matcher.group(1).toLowerCase();
String replacement = switch (code) {
case "0" -> "<black>";
case "1" -> "<dark_blue>";
case "2" -> "<dark_green>";
case "3" -> "<dark_aqua>";
case "4" -> "<dark_red>";
case "5" -> "<dark_purple>";
case "6" -> "<gold>";
case "7" -> "<gray>";
case "8" -> "<dark_gray>";
case "9" -> "<blue>";
case "a" -> "<green>";
case "b" -> "<aqua>";
case "c" -> "<red>";
case "d" -> "<light_purple>";
case "e" -> "<yellow>";
case "f" -> "<white>";
case "k" -> "<obfuscated>";
case "l" -> "<bold>";
case "m" -> "<strikethrough>";
case "n" -> "<underlined>";
case "o" -> "<italic>";
case "r" -> "<reset>";
default -> "&" + code;
};
matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
}
matcher.appendTail(result);
return result.toString();
}
/**
* Заменяет смайлики в сообщении
*/
public static String replaceEmojis(String message, Player player) {
LoChat plugin = LoChat.getInstance();
if (plugin != null && plugin.getEmojiManager() != null) {
return plugin.getEmojiManager().process(message, player);
}
return message;
}
/**
* Заменяет смайлики без проверки прав
*/
public static String replaceEmojis(String message) {
LoChat plugin = LoChat.getInstance();
if (plugin != null && plugin.getEmojiManager() != null) {
return plugin.getEmojiManager().process(message);
}
return message;
}
/**
* @deprecated Use replaceEmojis(String) instead
*/
@Deprecated
public static String replaceEmojis(String message, Map<String, String> emojis) {
return replaceEmojis(message);
}
/**
* Полная обработка сообщения игрока
* Цвета + смайлики
*/
public static String processPlayerMessage(String message, Player player, boolean hasColorPermission) {
String result = message;
// Заменяем смайлики
result = replaceEmojis(result, player);
// Если есть право на цвета - конвертируем
if (hasColorPermission) {
result = convertAllColors(result);
} else {
// Убираем цветовые коды
result = stripLegacyColors(result);
}
return result;
}
/**
* Убирает &коды из сообщения
*/
public static String stripLegacyColors(String message) {
return LEGACY_COLOR_PATTERN.matcher(message).replaceAll("");
}
public static Component parse(String message) {
return MINI_MESSAGE.deserialize(convertAllColors(message));
}
public static Component parse(String message, String... replacements) {
String result = message;
for (int i = 0; i < replacements.length - 1; i += 2) {
result = result.replace(replacements[i], replacements[i + 1]);
}
return MINI_MESSAGE.deserialize(convertAllColors(result));
}
public static String toPlain(Component component) {
return PlainTextComponentSerializer.plainText().serialize(component);
}
public static String stripTags(String message) {
// Убираем MiniMessage теги и legacy коды
String result = MINI_MESSAGE.stripTags(message);
result = stripLegacyColors(result);
result = HEX_PATTERN.matcher(result).replaceAll("");
return result;
}
/**
* Форматирует глобальное сообщение с поддержкой LoPreff
*/
public static Component formatGlobalMessage(String format, String prefix, Player player, String message, boolean hasColorPermission) {
String playerDisplay = getPlayerDisplay(player);
String finalMessage = processPlayerMessage(message, player, hasColorPermission);
String formatted = format
.replace("<prefix>", prefix)
.replace("<player>", playerDisplay)
.replace("<message>", finalMessage);
return parse(formatted);
}
/**
* Форматирует локальное сообщение с поддержкой LoPreff
*/
public static Component formatLocalMessage(String format, String prefix, Player player, String message, boolean hasColorPermission) {
String playerDisplay = getPlayerDisplay(player);
String finalMessage = processPlayerMessage(message, player, hasColorPermission);
String formatted = prefix + " " + format
.replace("<player>", playerDisplay)
.replace("<message>", finalMessage);
return parse(formatted);
}
private static String getPlayerDisplay(Player player) {
LoChat plugin = LoChat.getInstance();
if (plugin != null && plugin.getGradientModule() != null && plugin.getGradientModule().isEnabled()) {
return plugin.getGradientModule().getFormattedName(player);
}
return player.getName();
}
public static Component formatPmSent(String format, String targetName, String message) {
return parse(format
.replace("{player}", targetName)
.replace("{message}", message));
}
public static Component formatPmReceived(String format, String senderName, String message) {
return parse(format
.replace("{player}", senderName)
.replace("{message}", message));
}
public static Component formatAnnouncement(String format, String message) {
return parse(format.replace("<message>", message));
}
}

View file

@ -0,0 +1,33 @@
package com.loki.lochat.utils;
import org.bukkit.Location;
import org.bukkit.entity.Player;
public class DistanceUtil {
private DistanceUtil() {}
public static boolean isInRange(Player player1, Player player2, int radius) {
// Проверка на один мир
if (!player1.getWorld().equals(player2.getWorld())) {
return false;
}
Location loc1 = player1.getLocation();
Location loc2 = player2.getLocation();
// Быстрая проверка по квадрату (без sqrt)
double dx = loc1.getX() - loc2.getX();
double dz = loc1.getZ() - loc2.getZ();
double radiusSquared = (double) radius * radius;
return (dx * dx + dz * dz) <= radiusSquared;
}
public static double getDistance(Player player1, Player player2) {
if (!player1.getWorld().equals(player2.getWorld())) {
return Double.MAX_VALUE;
}
return player1.getLocation().distance(player2.getLocation());
}
}

View file

@ -0,0 +1,74 @@
# LoChat - Автосообщения
# Автор: loki
#
# Смайлики настраиваются в emojis.yml
# ═══════════════════════════════════════════
# НАСТРОЙКИ
# ═══════════════════════════════════════════
settings:
enabled: true
interval: 300 # Интервал между сообщениями (секунды)
prefix: "&6[&eINFO&6]&r"
# Режим отправки:
# - "sequential" — по порядку (1, 2, 3...)
# - "random" — случайный порядок
# - "custom" — свой порядок (указать в order)
mode: "sequential"
# Свой порядок (для mode: custom)
# Можно указывать названия или номера
# Пример: "welcome, rules, 3, donate, tips"
order: "welcome, motd, rules, donate, tips, discord"
# ═══════════════════════════════════════════
# СООБЩЕНИЯ
# ═══════════════════════════════════════════
messages:
# Приветствие (однострочное)
welcome: "&aДобро пожаловать на сервер! &e:star:"
# MOTD (многострочное)
motd:
- "&6╔══════════════════════════════════╗"
- "&6║ &e:star: &fДобро пожаловать! &e:star: &6║"
- "&6║ &7Онлайн: &a%online% &7игроков &6║"
- "&6║ &7Сайт: &bexample.com &6║"
- "&6╚══════════════════════════════════╝"
# Правила
rules:
- "&c&l:warning: ПРАВИЛА СЕРВЕРА :warning:"
- "&71. &fНе гриферить"
- "&72. &fНе читерить"
- "&73. &fУважать игроков"
- "&aПолные правила: &e/rules"
# Донат
donate:
- "&#FFD700&l:gem: ДОНАТ :gem:"
- "&7Поддержи сервер и получи"
- "&7крутые привилегии!"
- "&eПодробнее: &a/donate"
# Советы
tips:
- "&b:zap: &fСовет дня:"
- "&7Используй &e/sethome &7чтобы"
- "&7сохранить свою локацию!"
# Discord
discord: "&9:game: &fНаш Discord: &bdiscord.gg/example"
# Голосование
vote: "&a:thumbsup: &fГолосуй за сервер: &e/vote &7и получи награды!"
# Градиентное сообщение
gradient:
- "<gradient:#FF6B6B:#4ECDC4>━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━</gradient>"
- ""
- " &f:fire: &eСпециальное событие!"
- " &7Двойной опыт до конца недели!"
- ""
- "<gradient:#4ECDC4:#FF6B6B>━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━</gradient>"

View file

@ -0,0 +1,69 @@
# LoChat Configuration
# Author: loki
chat:
global:
enabled: true
prefix: "[G]"
format: "<prefix><player>: <message>"
symbol: "!"
cooldown: 3
local:
enabled: true
radius: 100
prefix: "[L]"
format: "<player>: <message>"
cooldown: 1
pm:
enabled: true
log: false
sound: true
sound-type: "ENTITY_EXPERIENCE_ORB_PICKUP"
# Интеграция с GradientNick (LoPreff)
# Использует PlaceholderAPI плейсхолдеры
lopreff:
enabled: true
# Использовать градиентный ник в чате
use-gradient-name: true
# Использовать кастомный префикс
use-custom-prefix: true
# Упоминания @ник
mentions:
enabled: true
sound: true
sound-type: "BLOCK_NOTE_BLOCK_PLING"
# Выделение при @ник
highlight: "<yellow>@{player}</yellow>"
# Выделение когда твой ник написали (без @)
self-highlight: "<gold><bold>{player}</bold></gold>"
# Фильтр мата
filter:
enabled: true
words:
- "хуй"
- "пизд"
- "блять"
- "ебать"
- "сука"
action: "censor" # censor/block/warn
replacement: "***"
# Анти-спам
antispam:
enabled: true
max-caps-percent: 70
max-repeat-chars: 5
similar-message-delay: 30
automessages:
enabled: true
interval: 300
random: false
formats:
announcement: "<gold>[ОБЪЯВЛЕНИЕ]</gold> <message>"

View file

@ -0,0 +1,385 @@
# LoChat - Смайлики и стикеры
# Автор: loki
#
# Формат: "триггер": "замена"
# Игроки пишут триггер, он заменяется на символ
#
# Права:
# - chat.emoji.use — использование смайликов
# - chat.emoji.* — все смайлики
# - chat.emoji.hearts — только категория hearts
# - chat.emoji.custom — кастомные смайлики
# ═══════════════════════════════════════════
# НАСТРОЙКИ
# ═══════════════════════════════════════════
settings:
enabled: true
# Требовать право chat.emoji.use
require-permission: false
# ═══════════════════════════════════════════
# ЛИЦА И ЭМОЦИИ
# ═══════════════════════════════════════════
faces:
":)": "☺"
":D": "😃"
":(": "☹"
";)": "😉"
":P": "😛"
":O": "😮"
"XD": "😆"
":/": "😕"
":*": "😘"
"^_^": "😊"
"-_-": "😑"
">:(": "😠"
":3": "😺"
":'(": "😢"
":')": "😂"
"o_o": "😳"
"O_O": "😱"
":thinking:": "🤔"
":cool:": "😎"
":angry:": "😡"
":sad:": "😢"
":happy:": "😊"
":laugh:": "😂"
":cry:": "😭"
":sleep:": "😴"
":sick:": "🤢"
":dead:": "💀"
# ═══════════════════════════════════════════
# СЕРДЦА И ЛЮБОВЬ
# ═══════════════════════════════════════════
hearts:
"<3": "❤"
":heart:": "❤"
":red_heart:": "❤"
":blue_heart:": "💙"
":green_heart:": "💚"
":yellow_heart:": "💛"
":purple_heart:": "💜"
":black_heart:": "🖤"
":white_heart:": "🤍"
":orange_heart:": "🧡"
":broken_heart:": "💔"
":two_hearts:": "💕"
":sparkling_heart:": "💖"
":heartbeat:": "💓"
":heartpulse:": "💗"
":love:": "💕"
":kiss:": "💋"
# ═══════════════════════════════════════════
# ЖЕСТЫ И РУКИ
# ═══════════════════════════════════════════
gestures:
":thumbsup:": "👍"
":+1:": "👍"
":thumbsdown:": "👎"
":-1:": "👎"
":clap:": "👏"
":wave:": "👋"
":ok:": "👌"
":ok_hand:": "👌"
":fist:": "✊"
":punch:": "👊"
":peace:": "✌"
":v:": "✌"
":muscle:": "💪"
":strong:": "💪"
":pray:": "🙏"
":hands:": "🙌"
":point_up:": "☝"
":point_down:": "👇"
":point_left:": "👈"
":point_right:": "👉"
":middle_finger:": "🖕"
":call:": "🤙"
# ═══════════════════════════════════════════
# ПРИРОДА И ПОГОДА
# ═══════════════════════════════════════════
nature:
":fire:": "🔥"
":flame:": "🔥"
":zap:": "⚡"
":lightning:": "⚡"
":boom:": "💥"
":explosion:": "💥"
":sparkles:": "✨"
":stars:": "✨"
":snow:": "❄"
":snowflake:": "❄"
":cloud:": "☁"
":sun:": "☀"
":sunny:": "☀"
":moon:": "🌙"
":crescent_moon:": "🌙"
":full_moon:": "🌕"
":rainbow:": "🌈"
":water:": "💧"
":drop:": "💧"
":ocean:": "🌊"
":wind:": "💨"
":tornado:": "🌪"
":leaf:": "🍃"
":tree:": "🌳"
":flower:": "🌸"
":rose:": "🌹"
":cactus:": "🌵"
# ═══════════════════════════════════════════
# ЗВЁЗДЫ И СИМВОЛЫ
# ═══════════════════════════════════════════
symbols:
":star:": "⭐"
":star2:": "🌟"
":glowing_star:": "🌟"
":dizzy:": "💫"
":sparkle:": "❇"
":skull:": "💀"
":ghost:": "👻"
":alien:": "👽"
":robot:": "🤖"
":poop:": "💩"
":100:": "💯"
":gem:": "💎"
":diamond:": "💎"
":money:": "💰"
":dollar:": "💵"
":coin:": "🪙"
":gift:": "🎁"
":balloon:": "🎈"
":party:": "🎉"
":tada:": "🎉"
":confetti:": "🎊"
":trophy:": "🏆"
":medal:": "🏅"
":crown:": "👑"
":king:": "👑"
# ═══════════════════════════════════════════
# МАЙНКРАФТ ТЕМАТИКА
# ═══════════════════════════════════════════
minecraft:
":sword:": "⚔"
":crossed_swords:": "⚔"
":shield:": "🛡"
":axe:": "🪓"
":pickaxe:": "⛏"
":bow:": "🏹"
":arrow:": "➤"
":bomb:": "💣"
":tnt:": "🧨"
":potion:": "🧪"
":ender:": "🟣"
":nether:": "🔴"
":emerald:": "💚"
":gold:": "🟡"
":iron:": "⬜"
":coal:": "⬛"
":redstone:": "🔴"
":lapis:": "🔵"
":exp:": "🟢"
":xp:": "🟢"
":heart_mc:": "❤"
":hunger:": "🍖"
":armor:": "🛡"
# ═══════════════════════════════════════════
# ЖИВОТНЫЕ
# ═══════════════════════════════════════════
animals:
":cat:": "🐱"
":dog:": "🐶"
":wolf:": "🐺"
":fox:": "🦊"
":pig:": "🐷"
":cow:": "🐮"
":sheep:": "🐑"
":chicken:": "🐔"
":dragon:": "🐉"
":bee:": "🐝"
":fish:": "🐟"
":spider:": "🕷"
":bat:": "🦇"
":rabbit:": "🐰"
":bear:": "🐻"
":panda:": "🐼"
":monkey:": "🐵"
":horse:": "🐴"
":unicorn:": "🦄"
":snake:": "🐍"
":turtle:": "🐢"
":frog:": "🐸"
":bird:": "🐦"
":penguin:": "🐧"
":owl:": "🦉"
# ═══════════════════════════════════════════
# ЕДА И НАПИТКИ
# ═══════════════════════════════════════════
food:
":apple:": "🍎"
":green_apple:": "🍏"
":bread:": "🍞"
":meat:": "🍖"
":steak:": "🥩"
":cake:": "🎂"
":cookie:": "🍪"
":pizza:": "🍕"
":hamburger:": "🍔"
":fries:": "🍟"
":hotdog:": "🌭"
":taco:": "🌮"
":sushi:": "🍣"
":ramen:": "🍜"
":egg:": "🥚"
":cheese:": "🧀"
":milk:": "🥛"
":coffee:": "☕"
":tea:": "🍵"
":beer:": "🍺"
":wine:": "🍷"
":cocktail:": "🍹"
":ice_cream:": "🍦"
":donut:": "🍩"
":candy:": "🍬"
":chocolate:": "🍫"
":watermelon:": "🍉"
":banana:": "🍌"
":carrot:": "🥕"
":corn:": "🌽"
# ═══════════════════════════════════════════
# ИГРОВЫЕ И ИНТЕРФЕЙС
# ═══════════════════════════════════════════
gaming:
":game:": "🎮"
":controller:": "🎮"
":joystick:": "🕹"
":dice:": "🎲"
":chess:": "♟"
":target:": "🎯"
":flag:": "🚩"
":checkered_flag:": "🏁"
":warning:": "⚠"
":no_entry:": "⛔"
":stop:": "🛑"
":check:": "✓"
":checkmark:": "✔"
":x:": "✗"
":cross:": "✖"
":question:": "❓"
":exclamation:": "❗"
":zzz:": "💤"
":bulb:": "💡"
":idea:": "💡"
":bell:": "🔔"
":mute:": "🔇"
":speaker:": "🔊"
":mega:": "📢"
":mic:": "🎤"
# ═══════════════════════════════════════════
# СТРЕЛКИ И НАПРАВЛЕНИЯ
# ═══════════════════════════════════════════
arrows:
":arrow_up:": "↑"
":arrow_down:": "↓"
":arrow_left:": "←"
":arrow_right:": "→"
":arrow_up_right:": "↗"
":arrow_up_left:": "↖"
":arrow_down_right:": "↘"
":arrow_down_left:": "↙"
":arrows_lr:": "↔"
":arrows_ud:": "↕"
":return:": "↩"
":redo:": "↪"
":refresh:": "🔄"
":infinity:": "∞"
# ═══════════════════════════════════════════
# МУЗЫКА И ИСКУССТВО
# ═══════════════════════════════════════════
music:
":note:": "♪"
":notes:": "♫"
":music:": "🎵"
":musical_note:": "🎶"
":headphones:": "🎧"
":guitar:": "🎸"
":piano:": "🎹"
":drum:": "🥁"
":violin:": "🎻"
":art:": "🎨"
":palette:": "🎨"
":camera:": "📷"
":movie:": "🎬"
":tv:": "📺"
":radio:": "📻"
# ═══════════════════════════════════════════
# РАЗНОЕ
# ═══════════════════════════════════════════
misc:
":lock:": "🔒"
":unlock:": "🔓"
":key:": "🔑"
":hourglass:": "⏳"
":timer:": "⏱"
":clock:": "🕐"
":calendar:": "📅"
":pin:": "📌"
":paperclip:": "📎"
":scissors:": "✂"
":pencil:": "✏"
":pen:": "🖊"
":book:": "📖"
":scroll:": "📜"
":mail:": "📧"
":envelope:": "✉"
":phone:": "📱"
":computer:": "💻"
":keyboard:": "⌨"
":mouse:": "🖱"
":trash:": "🗑"
":tools:": "🛠"
":wrench:": "🔧"
":hammer:": "🔨"
":gear:": "⚙"
":link:": "🔗"
":magnet:": "🧲"
":battery:": "🔋"
":plug:": "🔌"
# ═══════════════════════════════════════════
# КАСТОМНЫЕ (добавляй свои)
# ═══════════════════════════════════════════
custom:
# Примеры кастомных смайликов
":server:": "🖥"
":admin:": "👨‍💻"
":mod:": "🛡"
":vip:": "⭐"
":donate:": "💎"
":online:": "🟢"
":offline:": "🔴"
":afk:": "💤"
":gg:": "🏆"
":ez:": "😎"
":rip:": "⚰"
":f:": "🇫"
":pog:": "😲"
":kek:": "😂"
":sus:": "🤨"
":bruh:": "😐"
":based:": "💪"
":cringe:": "😬"
":ratio:": "📉"
":w:": "🏆"
":l:": "📉"

View file

@ -0,0 +1,58 @@
# LoChat Gradient Module Configuration
# Модуль градиентных ников (интегрированный LoPreff)
# Включить/выключить модуль
enabled: true
# Максимальное количество цветов в градиенте
max-colors: 7
# Минимальное количество цветов
min-colors: 1
# Цена за каждый цвет (PlayerPoints)
price-per-color: 50
# Цена за префикс
prefix-price: 500
# Покупка префикса один раз (потом бесплатно)
prefix-one-time-purchase: true
# Кулдаун на смену цвета (секунды)
color-cooldown: 60
# Кулдаун на смену префикса (секунды)
prefix-cooldown: 300
# Максимальная длина префикса
max-prefix-length: 7
# Формат префикса ({prefix} - сам префикс)
prefix-format: "[{prefix}] "
# Применять градиент на префикс
gradient-on-prefix: true
# Единый градиент на префикс + ник
continuous-gradient: true
# Использовать &#RRGGBB формат (true) или <#RRGGBB> (false)
use-legacy-rgb-format: true
# Тип хранилища: YAML или SQLITE
storage-type: YAML
# Файл SQLite базы данных
sqlite-file: gradient-data.db
# Чёрный список префиксов (нельзя использовать)
prefix-blacklist:
- admin
- owner
- moder
- helper
- staff
- модер
- админ
- хелпер

View file

@ -0,0 +1,57 @@
# LoChat Gradient Module Messages
# Сообщения модуля градиентных ников
# Общие
only-player: "§cЭта команда только для игроков!"
no-permission: "§cУ вас нет прав на это действие!"
player-not-found: "§cИгрок не найден!"
reload-success: "§aКонфигурация градиентов перезагружена!"
cooldown: "§cПодождите ещё {time} секунд!"
# Цвета
color-help: |
§e=== Команда /color ===
§e/color <цвет1> [цвет2] ... §7— установить градиент
§e/color on §7— включить цвет
§e/color off §7— выключить цвет
§e/color reset §7— сбросить цвет
§e/color copy <игрок> §7— скопировать цвет
§7Пример: /color #FF0000 #00FF00 #0000FF
color-enabled: "§aЦвет ника включён!"
color-disabled: "§cЦвет ника выключен!"
color-reset: "§aЦвет ника сброшен!"
color-no-colors: "§cУ вас нет установленных цветов!"
color-success: "§aЦвет успешно установлен! Списано: {price} PP"
color-copy-usage: "§cИспользование: /color copy <игрок>"
color-copy-no-colors: "§cУ игрока {player} нет цветов!"
invalid-color: "§cНеверный формат цвета: {color}. Используйте #RRGGBB"
too-many-colors: "§cМаксимум {max} цветов!"
not-enough-colors: "§cМинимум {min} цветов!"
not-enough-points-color: "§cНедостаточно поинтов! Нужно: {price} PP"
# Префикс
prefix-help: |
§e=== Команда /prefix ===
§e/prefix <текст> §7— установить префикс
§e/prefix on §7— включить префикс
§e/prefix off §7— выключить префикс
§e/prefix reset §7— сбросить префикс
prefix-enabled: "§aПрефикс включён!"
prefix-disabled: "§cПрефикс выключен!"
prefix-reset-success: "§aПрефикс сброшен!"
prefix-no-prefix: "§cУ вас нет установленного префикса!"
prefix-success: "§aПрефикс успешно установлен! Списано: {price} PP"
prefix-success-free: "§aПрефикс успешно изменён!"
prefix-no-colors: "§cПрефикс не может содержать цветовые коды!"
prefix-invalid-chars: "§cПрефикс содержит недопустимые символы!"
prefix-blacklisted: "§cЭтот префикс запрещён!"
prefix-too-long: "§cПрефикс слишком длинный! Максимум: {max} символов"
not-enough-points-prefix: "§cНедостаточно поинтов! Нужно: {price} PP"
# Админ информация
info-header: "§e=== Информация об игроке {player} ==="
info-prefix: "§7Префикс: §f{prefix}"
info-prefix-enabled: "§7Префикс включён: §f{enabled}"
info-colors: "§7Цвета: §f{colors}"
info-color-enabled: "§7Цвет включён: §f{enabled}"
info-prefix-purchased: "§7Префикс куплен: §f{purchased}"

View file

@ -0,0 +1,60 @@
prefix: "<gold>[LoChat]</gold>"
global:
disabled: "<red>Глобальный чат отключен</red>"
toggled-on: "<green>Глобальный чат включен</green>"
toggled-off: "<red>Глобальный чат отключен</red>"
local:
no-players: "<red>Рядом нет игроков</red>"
pm:
sent: "<gray>[PM] Ты → {player}: {message}</gray>"
received: "<gray>[PM] {player} → Ты: {message}</gray>"
no-reply: "<red>Некому ответить</red>"
ignored: "<red>Этот игрок вас игнорирует</red>"
player-offline: "<red>Игрок не в сети</red>"
self: "<red>Нельзя писать самому себе</red>"
ignore:
added: "<green>Игрок {player} добавлен в игнор</green>"
removed: "<green>Игрок {player} убран из игнора</green>"
already-ignored: "<red>Игрок уже в игноре</red>"
not-ignored: "<red>Игрок не в игноре</red>"
self: "<red>Нельзя игнорировать себя</red>"
announce:
reloaded: "<green>Конфигурация перезагружена</green>"
mute:
muted: "<green>Игрок {player} замучен на {time}</green>"
unmuted: "<green>Игрок {player} размучен</green>"
already-muted: "<red>Игрок уже замучен</red>"
not-muted: "<red>Игрок не замучен</red>"
you-muted: "<red>Вы замучены! Осталось: {time}</red>"
permanent: "<red>Вы замучены навсегда!</red>"
spy:
enabled: "<green>Шпион режим включен</green>"
disabled: "<red>Шпион режим выключен</red>"
format: "<gray>[SPY] {sender} → {receiver}: {message}</gray>"
filter:
blocked: "<red>Сообщение заблокировано (мат)</red>"
warned: "<yellow>Предупреждение: не используйте мат!</yellow>"
cooldown:
wait: "<red>Подождите {time} сек перед следующим сообщением</red>"
antispam:
caps: "<red>Слишком много заглавных букв!</red>"
repeat: "<red>Не повторяйте символы!</red>"
similar: "<red>Не отправляйте одинаковые сообщения!</red>"
clearchat:
cleared: "<green>Чат очищен администратором {player}</green>"
errors:
no-permission: "<red>Нет прав!</red>"
player-not-found: "<red>Игрок не найден</red>"
invalid-usage: "<red>Использование: {usage}</red>"

View file

@ -0,0 +1,138 @@
name: LoChat
version: '1.0.0'
main: com.loki.lochat.LoChat
api-version: '1.21'
folia-supported: true
author: loki
description: Advanced chat plugin for Folia with gradient nicks
softdepend:
- PlaceholderAPI
- PlayerPoints
- LuckPerms
commands:
g:
description: Send global message
usage: /g <message>
permission: chat.global.use
globalchat:
description: Toggle global chat
usage: /globalchat toggle
permission: chat.global.toggle
l:
description: Send local message
usage: /l <message>
permission: chat.local.use
msg:
description: Send private message
usage: /msg <player> <message>
permission: chat.pm.use
aliases: [tell, w, pm]
reply:
description: Reply to last message
usage: /reply <message>
permission: chat.pm.use
aliases: [r]
ignore:
description: Ignore player
usage: /ignore <player>
permission: chat.pm.ignore
unignore:
description: Unignore player
usage: /unignore <player>
permission: chat.pm.ignore
announce:
description: Send announcement
usage: /announce <message>
permission: chat.announce.use
chatspy:
description: Toggle chat spy mode
usage: /chatspy
permission: chat.spy
aliases: [spy]
clearchat:
description: Clear chat for all players
usage: /clearchat
permission: chat.clear
aliases: [cc]
mute:
description: Mute a player
usage: /mute <player> <time> [reason]
permission: chat.mute
unmute:
description: Unmute a player
usage: /unmute <player>
permission: chat.mute
color:
description: Set gradient nick color
usage: /color <hex1> [hex2] ...
permission: gradient.color
aliases: [gradient]
prefix:
description: Set custom prefix
usage: /prefix <text>
permission: gradient.prefix
aprefix:
description: Admin gradient commands
usage: /aprefix <subcommand>
permission: gradient.admin
aliases: [gradientadmin]
permissions:
chat.global.use:
default: true
chat.global.toggle:
default: true
chat.global.color:
default: op
chat.local.use:
default: true
chat.local.color:
default: op
chat.pm.use:
default: true
chat.pm.ignore:
default: true
chat.announce.use:
default: op
chat.announce.reload:
default: op
chat.spy:
default: op
chat.clear:
default: op
chat.mute:
default: op
chat.bypass.cooldown:
default: op
chat.bypass.filter:
default: op
chat.bypass.antispam:
default: op
chat.mention.use:
default: true
gradient.color:
default: true
gradient.prefix:
default: true
gradient.admin:
default: op
gradient.bypass.cooldown:
default: op
gradient.bypass.cost:
default: op