Add TextDisplay system for floating gradient names above players (1.19.4+)
This commit is contained in:
parent
64c672c02e
commit
dbb6a61351
7 changed files with 313 additions and 1 deletions
|
|
@ -12,6 +12,7 @@ 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 com.loki.lochat.gradient.util.TextDisplayManager;
|
||||
import org.black_ixx.playerpoints.PlayerPoints;
|
||||
import org.black_ixx.playerpoints.PlayerPointsAPI;
|
||||
import org.bukkit.Bukkit;
|
||||
|
|
@ -29,6 +30,7 @@ public class GradientModule {
|
|||
private GradientDataManager dataManager;
|
||||
private GradientLuckPermsHook luckPermsHook;
|
||||
private PlayerPointsAPI playerPointsAPI;
|
||||
private TextDisplayManager textDisplayManager;
|
||||
private boolean enabled;
|
||||
|
||||
public GradientModule(JavaPlugin plugin) {
|
||||
|
|
@ -64,6 +66,9 @@ public class GradientModule {
|
|||
// Подключение LuckPerms
|
||||
luckPermsHook = new GradientLuckPermsHook(plugin);
|
||||
|
||||
// Инициализация TextDisplayManager
|
||||
textDisplayManager = new TextDisplayManager(this);
|
||||
|
||||
// Регистрация команд
|
||||
registerCommands();
|
||||
|
||||
|
|
@ -100,6 +105,9 @@ public class GradientModule {
|
|||
* Выключение модуля
|
||||
*/
|
||||
public void shutdown() {
|
||||
if (textDisplayManager != null) {
|
||||
textDisplayManager.removeAllDisplays();
|
||||
}
|
||||
if (dataManager != null) {
|
||||
dataManager.saveAll();
|
||||
}
|
||||
|
|
@ -260,6 +268,7 @@ public class GradientModule {
|
|||
public GradientDataManager getDataManager() { return dataManager; }
|
||||
public GradientLuckPermsHook getLuckPermsHook() { return luckPermsHook; }
|
||||
public PlayerPointsAPI getPlayerPointsAPI() { return playerPointsAPI; }
|
||||
public TextDisplayManager getTextDisplayManager() { return textDisplayManager; }
|
||||
public boolean hasPlayerPoints() { return playerPointsAPI != null; }
|
||||
public boolean isEnabled() { return enabled; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,6 +87,9 @@ public class GradientAdminCommand implements CommandExecutor, TabCompleter {
|
|||
case "updateall" -> {
|
||||
handleUpdateAll(sender, msg);
|
||||
}
|
||||
case "toggledisplay" -> {
|
||||
handleToggleDisplay(sender, msg);
|
||||
}
|
||||
default -> sendHelp(sender);
|
||||
}
|
||||
|
||||
|
|
@ -104,6 +107,7 @@ public class GradientAdminCommand implements CommandExecutor, TabCompleter {
|
|||
sender.sendMessage("§e/aprefix resetcolor <игрок> §7— сбросить цвет");
|
||||
sender.sendMessage("§e/aprefix resetprefix <игрок> §7— сбросить префикс");
|
||||
sender.sendMessage("§e/aprefix updateall §7— обновить display names всех игроков");
|
||||
sender.sendMessage("§e/aprefix toggledisplay §7— переключить TextDisplay/обычный режим");
|
||||
}
|
||||
|
||||
private void handleInfo(CommandSender sender, String playerName, GradientMessages msg) {
|
||||
|
|
@ -330,6 +334,37 @@ public class GradientAdminCommand implements CommandExecutor, TabCompleter {
|
|||
sender.sendMessage("§aОбновлены display names для §f" + count + " §aигроков");
|
||||
}
|
||||
|
||||
private void handleToggleDisplay(CommandSender sender, GradientMessages msg) {
|
||||
// Переключаем режим TextDisplay в конфиге
|
||||
boolean currentMode = module.getConfig().isUseTextDisplay();
|
||||
boolean newMode = !currentMode;
|
||||
|
||||
// Обновляем конфиг (временно в памяти)
|
||||
module.getConfig().setUseTextDisplay(newMode);
|
||||
|
||||
if (newMode) {
|
||||
// Переключаемся на TextDisplay
|
||||
sender.sendMessage("§aВключен режим TextDisplay");
|
||||
// Удаляем все обычные display names и создаем TextDisplay
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
player.displayName(null); // Сбрасываем обычный display name
|
||||
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
|
||||
FoliaUtil.runEntityTask(module.getPlugin(), player,
|
||||
() -> module.getTextDisplayManager().updatePlayerDisplay(player));
|
||||
}
|
||||
} else {
|
||||
// Переключаемся на обычный display name
|
||||
sender.sendMessage("§cВыключен режим TextDisplay");
|
||||
// Удаляем все TextDisplay и обновляем обычные display names
|
||||
module.getTextDisplayManager().removeAllDisplays();
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
|
||||
FoliaUtil.runEntityTask(module.getPlugin(), player,
|
||||
() -> DisplayNameUtil.updateDisplayName(module, player, data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
|
||||
@NotNull String alias, @NotNull String[] args) {
|
||||
|
|
@ -337,7 +372,7 @@ public class GradientAdminCommand implements CommandExecutor, TabCompleter {
|
|||
|
||||
if (args.length == 1) {
|
||||
return Arrays.asList("reload", "info", "setcolor", "setprefix", "coloron", "coloroff",
|
||||
"prefixon", "prefixoff", "resetcolor", "resetprefix", "updateall").stream()
|
||||
"prefixon", "prefixoff", "resetcolor", "resetprefix", "updateall", "toggledisplay").stream()
|
||||
.filter(s -> s.startsWith(args[0].toLowerCase()))
|
||||
.toList();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,4 +83,12 @@ public class GradientConfig {
|
|||
// Display settings
|
||||
public boolean isUpdateDisplayName() { return config.getBoolean("display.update-display-name", true); }
|
||||
public boolean isUpdateTabList() { return config.getBoolean("display.update-tab-list", true); }
|
||||
public boolean isUseTextDisplay() { return config.getBoolean("display.use-text-display", true); }
|
||||
public double getTextDisplayHeight() { return config.getDouble("display.text-display-height", 2.3); }
|
||||
public float getTextDisplayScale() { return (float) config.getDouble("display.text-display-scale", 0.8); }
|
||||
|
||||
// Метод для временного изменения настройки TextDisplay
|
||||
public void setUseTextDisplay(boolean useTextDisplay) {
|
||||
config.set("display.use-text-display", useTextDisplay);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,12 @@ public class GradientPlayerListener implements Listener {
|
|||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
|
||||
// Удаляем TextDisplay если есть
|
||||
if (module.getTextDisplayManager() != null) {
|
||||
module.getTextDisplayManager().removePlayerDisplay(player.getUniqueId());
|
||||
}
|
||||
|
||||
FoliaUtil.runAsync(module.getPlugin(),
|
||||
() -> module.getDataManager().savePlayerData(player.getUniqueId()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,12 @@ public final class DisplayNameUtil {
|
|||
public static void updateDisplayName(GradientModule module, Player player, GradientPlayerData data) {
|
||||
GradientConfig cfg = module.getConfig();
|
||||
|
||||
// Если включен TextDisplay, используем его вместо обычного display name
|
||||
if (cfg.isUseTextDisplay()) {
|
||||
module.getTextDisplayManager().updatePlayerDisplay(player);
|
||||
return;
|
||||
}
|
||||
|
||||
String prefix = null;
|
||||
String prefixFormat = cfg.getPrefixFormat();
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,242 @@
|
|||
package com.loki.lochat.gradient.util;
|
||||
|
||||
import com.loki.lochat.gradient.GradientModule;
|
||||
import com.loki.lochat.gradient.data.GradientPlayerData;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.TextDisplay;
|
||||
import org.bukkit.util.Transformation;
|
||||
import org.joml.AxisAngle4f;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Менеджер для управления TextDisplay над игроками
|
||||
*/
|
||||
public class TextDisplayManager {
|
||||
|
||||
private final GradientModule module;
|
||||
private final Map<UUID, TextDisplay> playerDisplays = new ConcurrentHashMap<>();
|
||||
private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();
|
||||
|
||||
public TextDisplayManager(GradientModule module) {
|
||||
this.module = module;
|
||||
}
|
||||
|
||||
/**
|
||||
* Создает или обновляет TextDisplay над игроком
|
||||
*/
|
||||
public void updatePlayerDisplay(Player player) {
|
||||
if (!module.getConfig().isUpdateDisplayName() || !module.getConfig().isUseTextDisplay()) {
|
||||
return;
|
||||
}
|
||||
|
||||
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
|
||||
|
||||
// Получаем текст для отображения
|
||||
String displayText = getDisplayText(player, data);
|
||||
Component textComponent = MINI_MESSAGE.deserialize(displayText);
|
||||
|
||||
TextDisplay display = playerDisplays.get(player.getUniqueId());
|
||||
|
||||
if (display == null || !display.isValid()) {
|
||||
// Создаем новый TextDisplay
|
||||
createTextDisplay(player, textComponent);
|
||||
} else {
|
||||
// Обновляем существующий
|
||||
updateTextDisplay(display, player, textComponent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Создает новый TextDisplay над игроком
|
||||
*/
|
||||
private void createTextDisplay(Player player, Component textComponent) {
|
||||
// Удаляем старый display если есть
|
||||
removePlayerDisplay(player.getUniqueId());
|
||||
|
||||
double height = module.getConfig().getTextDisplayHeight();
|
||||
float scale = module.getConfig().getTextDisplayScale();
|
||||
|
||||
// Создаем новый TextDisplay
|
||||
TextDisplay display = player.getWorld().spawn(
|
||||
player.getLocation().add(0, height, 0), // Над головой игрока
|
||||
TextDisplay.class,
|
||||
entity -> {
|
||||
entity.text(textComponent);
|
||||
entity.setBillboard(org.bukkit.entity.Display.Billboard.CENTER);
|
||||
entity.setViewRange(64.0f); // Видимость на 64 блока
|
||||
entity.setSeeThrough(true); // Видно через стены
|
||||
entity.setShadowRadius(0.0f); // Без тени
|
||||
entity.setShadowStrength(0.0f); // Без тени
|
||||
|
||||
// Настройка трансформации (размер текста)
|
||||
entity.setTransformation(new Transformation(
|
||||
new Vector3f(0, 0, 0), // Без смещения
|
||||
new AxisAngle4f(0, 0, 0, 1), // Без поворота
|
||||
new Vector3f(scale, scale, scale), // Размер текста
|
||||
new AxisAngle4f(0, 0, 0, 1) // Без поворота после масштабирования
|
||||
));
|
||||
}
|
||||
);
|
||||
|
||||
// Привязываем TextDisplay к игроку как пассажира
|
||||
player.addPassenger(display);
|
||||
|
||||
// Сохраняем в карте
|
||||
playerDisplays.put(player.getUniqueId(), display);
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновляет существующий TextDisplay
|
||||
*/
|
||||
private void updateTextDisplay(TextDisplay display, Player player, Component textComponent) {
|
||||
display.text(textComponent);
|
||||
|
||||
// Проверяем, что display все еще привязан к игроку
|
||||
if (!player.getPassengers().contains(display)) {
|
||||
player.addPassenger(display);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает текст для отображения над игроком
|
||||
*/
|
||||
private String getDisplayText(Player player, GradientPlayerData data) {
|
||||
String prefix = null;
|
||||
String prefixFormat = module.getConfig().getPrefixFormat();
|
||||
|
||||
// Определяем префикс: сначала кастомный, потом LuckPerms
|
||||
if (data.isPrefixEnabled() && data.hasPrefix()) {
|
||||
prefix = data.getPrefix();
|
||||
} else if (module.getLuckPermsHook().isEnabled()) {
|
||||
String lpPrefix = module.getLuckPermsHook().getActivePrefix(player);
|
||||
if (lpPrefix != null && !lpPrefix.isEmpty()) {
|
||||
// Для LuckPerms префикса используем специальную логику
|
||||
return buildWithLuckPermsPrefix(
|
||||
lpPrefix,
|
||||
player.getName(),
|
||||
data.isColorEnabled() ? data.getColors() : null,
|
||||
module.getConfig().isUseLegacyRgbFormat(),
|
||||
module.getConfig().isContinuousGradient()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Строим display text с кастомным префиксом или без префикса
|
||||
String displayText = GradientUtil.buildDisplayName(
|
||||
prefix,
|
||||
player.getName(),
|
||||
data.isColorEnabled() ? data.getColors() : null,
|
||||
module.getConfig().isGradientOnPrefix(),
|
||||
module.getConfig().isContinuousGradient(),
|
||||
prefixFormat,
|
||||
false // Используем MiniMessage формат для TextDisplay
|
||||
);
|
||||
|
||||
// Конвертируем &#RRGGBB в <#RRGGBB> для MiniMessage
|
||||
return convertToMiniMessageFormat(displayText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Строит текст с LuckPerms префиксом
|
||||
*/
|
||||
private String buildWithLuckPermsPrefix(String lpPrefix, String nick,
|
||||
java.util.List<String> colors,
|
||||
boolean useLegacyFormat,
|
||||
boolean continuousGradient) {
|
||||
if (colors == null || colors.isEmpty()) {
|
||||
return convertToMiniMessageFormat(lpPrefix + nick);
|
||||
}
|
||||
|
||||
if (continuousGradient) {
|
||||
String cleanPrefix = stripColors(lpPrefix);
|
||||
String fullText = cleanPrefix + nick;
|
||||
String result = GradientUtil.applyGradient(fullText, colors, false); // MiniMessage формат
|
||||
return convertToMiniMessageFormat(result);
|
||||
} else {
|
||||
String result = convertToMiniMessageFormat(lpPrefix) +
|
||||
convertToMiniMessageFormat(GradientUtil.applyGradient(nick, colors, false));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Убирает цветовые коды из строки
|
||||
*/
|
||||
private String stripColors(String text) {
|
||||
if (text == null) return "";
|
||||
return text.replaceAll("(?i)(§x(§[0-9a-f]){6}|§[0-9a-fk-or]|&[0-9a-fk-or]|&#[0-9a-f]{6}|<[^>]+>)", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Конвертирует &#RRGGBB в <#RRGGBB> для MiniMessage
|
||||
*/
|
||||
private String convertToMiniMessageFormat(String text) {
|
||||
if (text == null) return "";
|
||||
// Конвертируем &#RRGGBB в <#RRGGBB>
|
||||
text = text.replaceAll("&#([0-9a-fA-F]{6})", "<#$1>");
|
||||
// Конвертируем §x§R§R§G§G§B§B в <#RRGGBB> (используем Pattern и Matcher)
|
||||
java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("§x(§[0-9a-fA-F]){6}");
|
||||
java.util.regex.Matcher matcher = pattern.matcher(text);
|
||||
StringBuffer sb = new StringBuffer();
|
||||
while (matcher.find()) {
|
||||
String hex = matcher.group().replaceAll("§", "").substring(1); // Убираем все § и x
|
||||
matcher.appendReplacement(sb, "<#" + hex + ">");
|
||||
}
|
||||
matcher.appendTail(sb);
|
||||
text = sb.toString();
|
||||
|
||||
// Конвертируем обычные цветовые коды §a в <green> и т.д.
|
||||
text = text.replaceAll("§([0-9a-fk-or])", "<$1>");
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаляет TextDisplay игрока
|
||||
*/
|
||||
public void removePlayerDisplay(UUID playerId) {
|
||||
TextDisplay display = playerDisplays.remove(playerId);
|
||||
if (display != null && display.isValid()) {
|
||||
display.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаляет все TextDisplay
|
||||
*/
|
||||
public void removeAllDisplays() {
|
||||
for (TextDisplay display : playerDisplays.values()) {
|
||||
if (display != null && display.isValid()) {
|
||||
display.remove();
|
||||
}
|
||||
}
|
||||
playerDisplays.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновляет позицию TextDisplay (вызывается при движении игрока)
|
||||
*/
|
||||
public void updateDisplayPosition(Player player) {
|
||||
TextDisplay display = playerDisplays.get(player.getUniqueId());
|
||||
if (display != null && display.isValid()) {
|
||||
// TextDisplay автоматически следует за игроком как пассажир
|
||||
// Но можем обновить высоту если нужно
|
||||
if (!player.getPassengers().contains(display)) {
|
||||
player.addPassenger(display);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет, есть ли у игрока TextDisplay
|
||||
*/
|
||||
public boolean hasDisplay(UUID playerId) {
|
||||
TextDisplay display = playerDisplays.get(playerId);
|
||||
return display != null && display.isValid();
|
||||
}
|
||||
}
|
||||
|
|
@ -49,6 +49,12 @@ display:
|
|||
update-display-name: true
|
||||
# Обновлять tab list
|
||||
update-tab-list: true
|
||||
# Использовать TextDisplay вместо обычного display name (1.19.4+)
|
||||
use-text-display: true
|
||||
# Высота TextDisplay над игроком (в блоках)
|
||||
text-display-height: 2.3
|
||||
# Размер TextDisplay (от 0.1 до 2.0)
|
||||
text-display-scale: 0.8
|
||||
|
||||
# Тип хранилища: YAML или SQLITE
|
||||
storage-type: YAML
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue