- Поддерживаемые версии
- 1.16
В чём, собственно, боль
Пишу я, значит, модуль кастомизации для своего плагина — цветной глов админам. Команда
/myplugin color <цвет> даёт игроку эффект GLOWING, а я через scoreboard-команду (Team.setColor()) крашу его в нужный цвет. В игре всё работает — красный, синий, радужный.Но захожу на сервер с TAB — и вижу, что глов стал белым. Игрок светится, но дефолтным цветом. Радуга не переливается, красный не красный. Просто белое свечение.
И тут начинается классика: «а давай полезу в API TAB», «а почему TAB перезаписывает мои команды», «а можно как-то заставить его не трогать мои scoreboard-команды»…
Спойлер: лезть в API TAB не надо. Официальная документация TAB сама говорит, что делать. Ниже — как я это сделал у себя.
Почему TAB ломает глов (коротко)
Глов в Minecraft — это не «цвет эффекта». Это цвет scoreboard-команды, в которой состоит игрок. Задаёшь команде
RED — глов красный. Задаёшь BLUE — синий. Это единственный способ красить свечение в Bukkit/Paper.Проблема в том, что TAB тоже использует scoreboard-команды — для сортировки игроков в табе, для префиксов, суффиксов и т.д. А Minecraft устроен так, что игрок может быть только в одной команде одновременно.
Что происходит:
- Твой плагин: добавляет игрока в команду
myplugin_red→ глов красный. - TAB: видит, что игрок не в его команде → выкидывает его из твоей и добавляет в свою → глов становится белым.
- И так каждый тик. Ты добавляешь — он выкидывает. В итоге побеждает TAB.
Именно поэтому в официальном вики TAB написано: «Only one plugin can handle teams at a time» — только один плагин может управлять командами.
Решение — отдать цвет через PlaceholderAPI
TAB не даёт тебе красить глов напрямую через команды. Но он сам красит глов, если ты скажешь ему цвет — через последний цветовой код в
tagprefix.Как это работает: TAB смотрит на
tagprefix игрока, берёт последний цветовой код (§c, §6 и т.д.) и ставит его в свою команду. Этот цвет применяется и к нику, и к глову.Значит, задача сводится к простому: дать TAB’у плейсхолдер, который возвращает текущий цвет глова.
Шаг 1. Регистрируем expansion для PlaceholderAPI
Java:
package me.jonycape.dev.myplugin.placeholder;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.jonycape.dev.myplugin.Main;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
public final class MyExpansion extends PlaceholderExpansion {
@Override
public String getIdentifier() {
return "myplugin";
}
@Override
public String getAuthor() {
return "jonycape";
}
@Override
public String getVersion() {
return "1.0.0";
}
@Override
public boolean persist() {
// без этого expansion слетит при /papi reload
return true;
}
@Override
public String onPlaceholderRequest(Player player, String params) {
if (player == null) {
return "";
}
if (!params.equalsIgnoreCase("glowcolor")) {
return "";
}
Main main = Main.getInstance();
if (main == null || main.getCustomizeService() == null) {
return "";
}
ChatColor color = main.getCustomizeService().currentColor(player);
// возвращаем legacy-формат §c, §6 и т.д.
return color == null ? "" : color.toString();
}
}
Регистрация в
onEnable() — с проверкой, что PlaceholderAPI вообще есть:
Java:
private void initPlaceholders() {
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") == null) {
return;
}
try {
new MyExpansion().register();
getLogger().info("Зарегистрировано расширение PlaceholderAPI 'myplugin'.");
} catch (Throwable t) {
getLogger().log(Level.WARNING, "Не удалось зарегистрировать расширение PlaceholderAPI", t);
}
}
Шаг 2. Вклеиваем плейсхолдер в tab.yml
Важно: плейсхолдер должен быть в самом конце
tagprefix. TAB берёт именно последний цветовой код[citation:1][citation:8].У меня в
tab.yml секция admin выглядит так:
YAML:
admin:
tabprefix: "%player_world% &8❘ &7[%hsubscribe_vip_check%&7]%luckperms-prefix%"
tagprefix: "%luckperms-prefix%%myplugin_glowcolor%"
tabsuffix: "%luckperms-suffix%"
tagsuffix: "%luckperms-suffix%"
Ключевая строка —
tagprefix. Плейсхолдер идёт после %luckperms-prefix%, в самом конце. TAB прочитает цвет из %myplugin_glowcolor% и применит его к глову.Что было бы без плейсхолдера: TAB брал бы последний цвет из
%luckperms-prefix% (если он вообще есть) или ставил бы белый дефолт. Твой глов был бы белым, сколько бы ты ни красил команды в своём плагине.Шаг 3. Радуга обновляется сама
У меня в
CustomizeService есть таск, который тикает раз в N тиков и меняет currentColor для радужного режима:
Java:
private static final ChatColor[] RAINBOW = {
ChatColor.RED, ChatColor.GOLD, ChatColor.YELLOW,
ChatColor.GREEN, ChatColor.AQUA, ChatColor.BLUE,
ChatColor.LIGHT_PURPLE
};
private void tick() {
// ...
if (state.isRainbow()) {
if (rainbowTick) {
applyRainbow(player, nextRainbow());
}
}
// ...
}
Плейсхолдер
%myplugin_glowcolor% при следующем запросе от TAB вернёт новый цвет. TAB обновит tagprefix → обновит team color → глов перекрасится.Нюанс: TAB опрашивает плейсхолдер не каждый тик, а по своим интервалам. Радуга в глове будет чуть медленнее, чем в твоём таске. У меня выглядит нормально.
Подводные камни
1. Плейсхолдер ТОЛЬКО в конце tagprefix
Официальное требование TAB[citation:1][citation:8]. Если поставить его в середину — TAB возьмёт не тот цвет. Если после него есть текст с цветом — всё сломается.
2. MiniMessage не поймёт §c
Если TAB в MiniMessage-режиме,
ChatColor.toString() с §c может не сработать. Либо оставляй legacy-режим в TAB, либо возвращай теги:
Java:
// вместо color.toString()
return "<" + color.name().toLowerCase().replace("_", "") + ">";
Но с legacy надёжнее — TAB его до сих пор поддерживает стабильно[citation:5].
3. PlaceholderAPI обязателен
В
plugin.yml:
YAML:
softdepend:
- PlaceholderAPI
- TAB
4. Твои scoreboard-команды всё ещё нужны
Да, TAB управляет командами. Но твой
applyColorTeam всё равно нужен — для тех случаев, когда TAB не установлен или отключён на сервере. Плейсхолдер — это мост для TAB, а не замена scoreboard-механизма.Итог
- Глов в мире — цветной, как задумано, даже с TAB.
- Радуга работает.
- Префиксы и сортировка TAB не сломаны.
- Весь фикс — один класс на 40 строк и одна строка в tab.yml.
Официальная документация TAB сама описывает это решение — я лишь адаптировал его под свой плагин. Если у кого-то похожая задача — не лезьте в API TAB, посмотрите в сторону PlaceholderAPI. В 90% случаев хватает его.
Вопросы по коду — пишите в тему.