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