Farmstead Mods
EN

Чат и сообщения

Форматирование, приватные сообщения, почта, упоминания, градиенты и анимации.

На этой странице

Команды 19#

КомандаСинтаксисАлиасыПравоОписание
/ac/ac <message>—neoessentials.helpopRequest help from staff (alias)Алиас /helpop (HelpopCommand), не переключатель канала. Канал staff в поставке — /staff.
/amsg/amsg <message>—neoessentials.helpopRequest help from staff (alias)Алиас /helpop, не приватное сообщение и не канал.
/chatformat/chatformat set <player> <format> | clear <player> | check <player> | list | reload—neoessentials.chat.format.setManage per-player/per-group chat formatsКорень и все ветки требуют neoessentials.chat.format.set (узла нет в PermissionRegistry — в таблице прав сайта его не будет, LuckPerms принимает строку). <player> — только онлайн. set/clear/check/reload пишут в коллекцию DataStore chat_formats; list печатает только тех онлайн-игроков, у кого override есть, хотя getCount() считает всех. Сообщения веток зашиты английским Component.literal, не lang. Дерево не регистрируется, если выключены chatEnabled или commands.chatformat.
/helpop/helpop <message>—neoessentials.helpop по соглашениюRequest help from staffБез аргумента — usage. Право отправителя neoessentials.helpop; получают только онлайн с neoessentials.helpop.receive. Заглушённый игрок режется. Кнопки [Reply] подставляют /msg <ник> ; телепорт на координаты / к игроку — если есть neoessentials.teleport.admin.tp / tphere. Это не staff-канал: /ac и /amsg — алиасы helpop, а не ChannelCommands.
/ignore/ignore <target>—neoessentials.chat.ignoreIgnore a playerТо же дерево: /block (в реестре нет). <target> только онлайн. Нельзя игнорировать себя. Право neoessentials.chat.ignore. Цель с neoessentials.chat.ignore.exempt не игнорируется (узел в PermissionRegistry не объявлен, проверка в коде есть). Повторный ignore того же ника — ошибка. Список — коллекция DataStore ignore_lists, ключ = ник в нижнем регистре, не UUID. Публичный чат IgnoreManager не фильтрует.
/mail/mail | /mail read [page] | /mail send <player> <message> | /mail sendtemp <player> <duration> <message> | /mail sendall <message> | /mail sendtempall <duration> <message> | /mail delete <id> | /mail clear [index|player [index]] | /mail clearall—neoessentials.mail по соглашениюManage mail messagesКорневой /mail и read/delete — neoessentials.mail; send — neoessentials.mail.send; sendtemp — sendtemp; sendall/sendtempall — соответствующие admin-узлы; clear своё — neoessentials.mail.clear, чужое — clear.others; clearall — clearall. <player> в send — StringArgumentType.word() плюс profile cache, офлайн с известным UUID проходит. sendall рассылает только онлайн-списку. Ящик — config/neoessentials/mail_data.json, не DataStore. Лимит 100 писем, 1000 символов, 9 на страницу, глобальный rate limit 10/мин (mailsPerMinute в поставке нет).
/me/me <target> <message>—neoessentials.chat.msgSend a private message (alias)Алиас /msg: отправляет личное сообщение, а не действие в общий чат. Использует те же проверки права neoessentials.chat.msg, mute, ignore и /msgtoggle.
/message/message <target> <message>—neoessentials.chat.msgSend a private message (alias)Полная копия /msg: тот же registerCommand в MsgCommand.java, те же проверки neoessentials.chat.msg.
/msg/msg <target> <message>—neoessentials.chat.msgSend a private messageBrigadier регистрирует то же дерево ещё как /tell, /w, /message, /me и /pm (последнего нет в реестре команд мода). <target> — EntityArgument.player(), только онлайн. Корень закрыт neoessentials.chat.msg (консоль проходит). Себе писать нельзя. Если цель игнорирует отправителя — отказ, SocialSpy не срабатывает. Если у цели включён /msgtoggle — отказ, кроме OP 4 и neoessentials.chat.msgtoggle.bypass. Заглушённый отправитель тоже режется. LastMessageManager записывает пару только у получателя. Форматы: chat.messaging.msgFormatTo / msgFormatFrom, иначе lang.
/msgtoggle/msgtoggle—neoessentials.chat.msgtoggleToggle receiving private messagesАргументов on|off нет — только переключатель. То же дерево: /togglemsg и /mt (их нет в реестре). Регистрируется только при chatEnabled и commands.msgtoggle. Право neoessentials.chat.msgtoggle, OP 4 обходит. Состояние в памяти MsgToggleManager, на диск не пишется.
/nick/nick | /nick <nickname> | /nick reset | /nick off—neoessentials.nick по соглашениюChange your nicknameПраво neoessentials.nick. Ник 3–16 символов без цветовых кодов, шаблон ^[a-zA-Z0-9_&§#]{1,32}$ — кириллица и пробелы не проходят. Цвета требуют neoessentials.nick.color. off/reset как литералы и как greedy-строка снимают ник. Хранение: config/neoessentials/nickname_data.json, не DataStore. Чужой ник — отдельная /setnick (neoessentials.nick.others), эта страница её не забирает.
/nickname/nickname …—neoessentials.nickname по соглашениюChange your nickname (alias)redirect на /nick, только если включены и commands.nick, и commands.nickname. Право то же: neoessentials.nick (в реестре у алиаса написано neoessentials.nickname — это выведенный node, Brigadier его не проверяет).
/r/r <message>—neoessentials.chat.replyReply to last private message (alias)Алиас /reply, тот же ReplyCommand.
/reply/reply <message>—neoessentials.chat.replyReply to last private messageТо же дерево вешается на /r. Цель берётся из LastMessageManager: туда попадает только тот, кому написали, не отправитель исходящего /msg. Цель должна быть онлайн; выход обоих сторон чистит карту. Право neoessentials.chat.reply, плюс те же mute / ignore / msgtoggle, что у /msg. Форматы: chat.messaging.replyFormatTo / replyFormatFrom.
/socialspy/socialspy—neoessentials.chat.socialspySpy on private messagesБез on|off. То же дерево: /ss и /spy (в реестре нет). Право neoessentials.chat.socialspy. Состояние — ConcurrentHashMap в памяти, рестарт сбрасывает. Рассылка идёт только после успешного /msg|/reply; отправитель с neoessentials.chat.socialspy.exempt не мониторится. Формат: chat.messaging.socialspyFormat.
/tell/tell <target> <message>—neoessentials.chat.msgSend a private message (alias)Полная копия /msg (ванила перекрывается тем же деревом).
/unignore/unignore <target>—neoessentials.chat.unignoreUnignore a playerТо же дерево: /unblock. Корень закрыт neoessentials.chat.unignore; execute всё ещё проверяет neoessentials.chat.ignore — нужны оба узла. Цель должна быть онлайн. Если ника нет в списке — ошибка.
/w/w <target> <message>—neoessentials.chat.msgSend a private message (alias)Полная копия /msg (ванила перекрывается тем же деревом).
/whisper/whisper <target> <message>—neoessentials.chat.msgSend a private message (alias)В NeoEssentials.java команда объявлена в реестре, но MsgCommand.register её не вызывает — литерала Commands.literal("whisper") в исходниках нет. В автодополнении Brigadier её не будет, пока её не начнут регистрировать рядом с msg/tell/w/message/pm.

Примеры#

Переписка /msg и ответ /r#

Проверить, что личка доходит только двум игрокам и что /r отвечает именно тому, кто написал вам, а не тому, кому написали вы.

/msg Alex Привет, это проверка лички
/r Принял, пишу в ответ

У отправителя строка из chat.messaging.msgFormatTo (пустая строка в конфиге = lang commands.neoessentials.msg.format.to), у Alex — msgFormatFrom. LastMessageManager.setLastMessager вызывается только для цели: после первого /msg ответить /r может Alex, но не отправитель. Когда Alex выполняет /r, пара переворачивается — тогда /r заработает и у первого. Цель офлайн в момент /r даёт reply.target_offline и запись чистится.

Игнор лички и что видит SocialSpy#

Убедиться, что ignore режет /msg до рассылки шпионам, но не прячет публичный чат и не фильтрует чужие ЛС у шпиона.

/ignore Steve
/socialspy
/msg Steve это не должно дойти

Steve игнорирует вас — /msg отвечает target_ignoring, broadcastSocialSpy не вызывается, шпионы пустую попытку не видят. Обратный /msg от Steve к вам тоже режется (isIgnoring(target, sender)). Публичный чат Steve по-прежнему виден: ChatHandler IgnoreManager не вызывает. Если третий игрок пишет не-игнорируемому, шпион с включённым /socialspy увидит строку socialspyFormat, даже если сам кого-то из пары игнорирует — рассылка шпионам ignore-списки не читает. Отправитель с neoessentials.chat.socialspy.exempt из мониторинга выпадает целиком.

Личный формат чата с градиентом#

Выдать игроку формат выше group/world/default и понять, когда реально красится, а когда теги вырезаются.

/chatformat set Steve <gradient:FFD700-FF8C00>[VIP]</gradient> &f{neoessentials_displayname}&7: &f{MESSAGE}
/chatformat check Steve
/chatformat list

set отвечает английской строкой «Set custom chat format for Steve.» и пишет коллекцию chat_formats. Приоритет: per-player → group:мир → group → world → default. Теги /// в шаблоне проходят RichTextFormatter.preprocessTags только при chat.richText.enabled: true (по умолчанию false — теги снимаются, &-коды шаблона остаются). Кадры {animation:NAME} красятся раньше, через processAnimationFrameGradients, и переживают выключенный richText. /chatformat list печатает только онлайн-игроков с override; офлайн в count есть, в списке нет. Снять: /chatformat clear Steve.

Письмо офлайн-игроку и чтение#

Почта должна дойти человеку, которого нет на сервере, и не потеряться после рестарта — она не в DataStore.

/mail send Steve Зайди, кит обновили
/mail
/mail read
/mail delete a1b2c3d4

send резолвит UUID через онлайн-список, иначе profile cache — «player not found» только если ника никогда не было. Steve офлайн: письма нет в чате, оно лежит в config/neoessentials/mail_data.json; при входе — login_notification с числом непрочитанных. /mail без аргументов — status (всего / непрочитанных). read помечает страницу прочитанной (9 писем) и сразу сохраняет файл. Клик по строке предлагает /mail delete (короткие 8 hex). Если Steve вас игнорирует и он онлайн — send врёт «отправлено», в ящик ничего не кладётся; офлайн-игнор эту проверку пропускает.

Ник в чате и вызов стаффа через /helpop#

Ник должен подставиться в {neoessentials_displayname}, а /ac не должен уводить сообщение в канал staff.

/nick &6Steve
/helpop Не могу зайти на варп spawn
/ac То же самое другим алиасом

/nick требует neoessentials.nick; с & нужен nick.color, иначе no_color_permission. Ник пишется в nickname_data.json и уходит в tab-list пакетом. В шаблоне чата {neoessentials_displayname} — это ник без префикса группы; префикс только через {prefix}. /helpop (и /ac, /amsg) шлёт блок стаффу с neoessentials.helpop.receive: время, ник, мир+координаты, текст, кнопки ответа. Это не ChannelCommands: в staff-канал уходит только /staff или префикс @ из chat.channels.staff. Если стаффа онлайн нет — helpop.no_staff, запрос всё равно логируется в консоль только когда staffCount > 0.

Префикс ответов команд (chat.modMessagePrefix)#

Убрать или вернуть тег [NE] у success/error/warning/info мода, не трогая формат игроковского чата.

/neoe reload

Ключ chat.modMessagePrefix в config.json; при сплите — config/neoessentials/chat.json. Пустая строка в поставке — префикса нет. Вернуть старый тег: "&8[&bNE&8] &r" (нужен пробел в конце, иначе текст прилипнет). MessageUtil.success/error/warning/info, SupportLinks и несколько команд с prefixedLiteral читают ключ с диска на каждое сообщение, /neoe reload достаточно. MessageUtil.component() и шаблон chat-format этот ключ не читают.

Свои сообщения о входе и выходе без ванильных дублей#

Скрыть строки Minecraft «присоединился к игре» / «покинул игру» и показать свои цветные сообщения.

В config/neoessentials/config.json, внутри объекта chat: `suppressVanillaJoinQuitMessages: true`
Там же: `customJoinMessage: "&a[+] &e{neoessentials_displayname} &fзашёл на сервер"`
Там же: `customQuitMessage: "&c[-] &e{neoessentials_displayname} &fвышел с сервера"`
Перезапустить сервер

NeoEssentials перехватывает ванильные join/leave-рассылки и отправляет только настроенный шаблон. Доступны плейсхолдеры PlaceholderAPI, включая {neoessentials_displayname}, {neoessentials_username}, {neoessentials_prefix}, {neoessentials_suffix} и {neoessentials_world}. Цвета: обычные &0–&f, стили &k–&r, а также hex &#RRGGBB, например &#FFD700Золотой.

Тонкие места#

Сообщения входа/выхода требуют рестарта#

suppressVanillaJoinQuitMessages по умолчанию false: после обновления мод не меняет привычное поведение существующего сервера. При true выключаются обе ванильные строки. Шаблон none для конкретного события означает, что NeoEssentials ничего не отправит; это подходит, например, чтобы скрыть только выходы. Сам перехват подключается при старте Minecraft, поэтому одного /neoe reload недостаточно после установки новой версии мода.

Что делать: Добавьте ключ и шаблоны в объект chat активного config/neoessentials/config.json, проверьте JSON-запятые и перезапустите сервер. Изменение уже загруженных шаблонов можно применить обычным /neoe reload.

Вентиль chat.richText.enabled (баг «игнора конфига» закрыт)#

Раньше preprocessTags красил <red>/<bold> даже при выключенном rich text, а путь без enableChatEnhancements вызывал processRichText по сырой строке. Сейчас при chat.richText.enabled: false (дефолт) RichTextFormatter.preprocessTags вызывает stripAllRichTags: снимаются gradient/rainbow/named-color/format/<color:#>/<hover>/<click>, &-коды шаблона остаются. При true теги конвертируются; allowGradients/allowRainbow (дефолт true) действуют только внутри этой ветки. Английская вики, что hover/click «не закрыты флагом», устарела.

Исключение: {animation:NAME} прогоняется через processAnimationFrameGradients до вентиля — кадр из animations.json с <gradient> в чате красится даже при выключенном richText. Сообщение игрока дополнительно режется restrictPlayerMessageColors: без neoessentials.chat.namedcolors <tag> из текста игрока вырезаются ещё до вставки в {MESSAGE}. Узлы neoessentials.chat.richtext / gradient / rainbow нигде в пайплайне не читаются.

Что делать: Градиенты в chat-format и /chatformat set включают chat.richText.enabled: true. Для анимаций в шаблоне этот флаг не обязателен. Игрокам для тегов в своём тексте выдавайте neoessentials.chat.namedcolors, не richtext.

Ignore против SocialSpy#

/msg и /reply вызывают IgnoreManager.isIgnoring(target, sender) и выходят с ошибкой до ChatAPI.broadcastSocialSpy. Попытка написать игнорирующему в SocialSpy не попадает. Обратно: SocialSpyManager.broadcast ignore-списки шпиона не смотрит — включённый /socialspy видит чужие успешные ЛС, даже если шпион игнорирует отправителя или получателя (себя и участников пары рассылка пропускает). Публичный чат ignore не фильтрует совсем: ChatHandler.onServerChat до IgnoreManager не доходит. Почта: игнор срабатывает только если цель онлайн; офлайн-письмо кладётся в ящик. socialspy.exempt на отправителе глушит мониторинг независимо от ignore.

Что делать: Не обещайте игрокам, что /ignore скроет человека из обычного чата — только личка (и онлайн-почта). Шпионам для «не видеть игнорируемых» отдельного вентиля нет.

Почта живёт в mail_data.json, не в DataStore#

MailCommand пишет config/neoessentials/mail_data.json (atomic rename). storage.type (JSON/SQLite/MySQL) этот файл не переезжает — в отличие от ignore_lists и chat_formats. /neoe reload ящик не перечитывает: loadMailData() вызывается при регистрации команды. Глобальный счётчик mailsThisMinute общий на весь сервер, не per-player; ключа mail.mailsPerMinute в поставке нет, код смотрит несуществующий корневой объект mail и падает в дефолт 10. sendall/sendtempall итерируют только getPlayers() — офлайн-ящики не пополняются.

Что делать: Бэкапьте config/neoessentials/mail_data.json отдельно от neoessentials/store/. Общей почты на сети через MySQL нет. Рассылку всем, включая офлайн, эта команда не умеет.

/r отвечает только на входящее#

LastMessageManager.setLastMessager(recipient, sender) пишется у цели /msg, не у отправителя. Карта в памяти, по никам в нижнем регистре; выход любого из пары чистит записи. Поэтому сразу после своего /msg /r у отправителя даёт reply.no_target, пока цель не ответит.

Что делать: Нужен второй /msg, либо дождитесь ответа. Двустороннего «последний собеседник» нет.

Права, которые код проверяет, а PermissionRegistry не объявляет#

neoessentials.chat.namedcolors, neoessentials.chat.format.set, neoessentials.chat.ignore.exempt, neoessentials.chat.socialspy.exempt используются в ChatFormatter / ChatFormatCommand / IgnoreCommand / SocialSpyManager, но PermissionRegistry.register(...) для них нет — в таблице прав сайта и в /permissions search их не будет. Выдача через LuckPerms/FTB всё равно работает: PermissionAPI.hasPermission смотрит строку. Без внешней выдачи сработает OP-bypass (уровень 2), дефолтной группе узлы не раздаются. /unignore: корень требует neoessentials.chat.unignore, execute — neoessentials.chat.ignore.

Что делать: Выдавайте namedcolors/format.set/ignore.exempt/socialspy.exempt явно строкой. Для снятия игнора нужны оба: neoessentials.chat.unignore и neoessentials.chat.ignore.

/msgtoggle и /socialspy без on|off, состояние не на диске#

Английская вики обещает /msgtoggle [on|off] и /socialspy [on|off]. В дереве только .executes без аргументов — каждый вызов инвертирует флаг. MsgToggleManager и SocialSpyManager держат имена в ConcurrentHashMap; рестарт сбрасывает оба. Корни закрыты узлами neoessentials.chat.msgtoggle / neoessentials.chat.socialspy / neoessentials.chat.msg. /msgtoggle не регистрируется, если выключены модуль чата или commands.msgtoggle.

Что делать: Не документируйте аргументы on|off. После рестарта модераторы включают /socialspy снова. Обойти чужой toggle: OP 4 или neoessentials.chat.msgtoggle.bypass.

/chatformat list врёт составом, set не умеет офлайн#

<player> везде EntityArgument.player() — офлайн «not found». list после заголовка с getCount() (все UUID в store) печатает только онлайн. Сообщения веток — английский Component.literal, ключей lang нет. После ручной правки store нужен /chatformat reload, не /neoe reload (reload команды как раз вызывает PlayerChatFormatManager.load()).

Что делать: Override офлайн-игроку — правка коллекции chat_formats + /chatformat reload. Не ориентируйтесь на list, пока люди офлайн.

Ник: латиница, длина, отдельный файл#

VALID_NICK_PATTERN пускает только a-zA-Z0-9_&§#. Кириллический ник — invalid_format. Длина без кодов 3–16, с кодами строка до 32. Занятость сравнивается по нижнему регистру без цветовых кодов. Файл config/neoessentials/nickname_data.json не едет в DataStore. /nickname — redirect на nick, без собственного дерева.

Что делать: Цветной ник: сначала neoessentials.nick.color. Сменить чужой — /setnick (другая команда, commands.setnick).

Известные проблемы#

/whisper есть в реестре и нет в Brigadier#

registry.registerCommandWithPermission("whisper", …) попадает в инвентарь и в таблицу команд сайта, но MsgCommand.register вешает деревья только на msg, tell, w, message, pm. Литерала whisper нет — команда не выполняется и не автодополняется. /pm наоборот работает, но в реестре мода его нет.

Что делать: Используйте /msg / /tell / /w / /message. Не обещайте /whisper, пока его не зарегистрируют в MsgCommand.

/ignore не скрывает публичный чат#

IgnoreManager вызывается из /msg, /reply и онлайн-ветки /mail send. ChatHandler при ServerChatEvent список игнора не читает — сообщения каналов (local/global/staff/team) игрок продолжает видеть. Команды /ignorelist нет: getIgnoreList() никуда не выведен.

Что делать: Для «не видеть человека в чате» ignore не подходит. Снимать игнор — /unignore по онлайн-нику; нужны оба узла neoessentials.chat.unignore и neoessentials.chat.ignore. Забытый ник из DataStore руками.

/ac — это helpop, не admin-чат#

В комментарии HelpopCommand алиас назван «admin chat», в инвентаре — «Request help from staff». Реальная регистрация: те же helpop/ac/amsg → sendHelpRequest. Канал staff из chat.channels регистрирует ChannelCommands как /staff (алиасы mod, admin, s) и требует neoessentials.chat.staff.

Что делать: Стафф-канал: /staff или префикс @. Вызов помощи: /helpop//ac//amsg и право neoessentials.helpop.receive у модераторов.

sendall почты не пишет офлайн-ящики#

Комментарий обещает broadcast «to all players», цикл идёт по server.getPlayerList().getPlayers(). Кто не в сети в эту секунду, письма не получит и при входе его не увидит. clearall наоборот чистит весь загруженный MAIL_BOX (файл целиком).

Что делать: Массовую рассылку офлайн делайте внешним скриптом по mail_data.json на остановленном сервере или пишите каждому /mail send.

Конфигурация#

animations.json#

КлючПо умолчаниюОписание
animations
animations.name"Rainbow"Classic rainbow letter-cycling effect
animations.frames
animations.frameDuration500—
name"PulseStar"Pulsing star — good as decorative separator or bullet
frames
frameDuration400—
name"StatusDot"Blinking dot — good for status indicators
frames
frameDuration750—
name"LoadingDots"Animated loading ellipsis
frames
frameDuration400—
name"GoldBanner"Scrolling server-name banner using gradient colour shifts
frames
frameDuration600—
name"Spinner"Classic BTLP-style spinning pipe
frames
frameDuration200—
name"HeartBeat"Alternating heart colour pattern
frames
frameDuration600—

Права 40#

УзелВ реестреЧто даёт
neoessentials.chat.caps.bypassреестр: нетBypass caps filter
neoessentials.chat.channel.globalреестр: даUse global chat channel
neoessentials.chat.channel.localреестр: даUse local chat channel
neoessentials.chat.colorреестр: нетUse basic color codes in chat (&0-9, &a-f)
neoessentials.chat.color.hexреестр: нетUse hex colors in chat (&#RRGGBB)
neoessentials.chat.exemptреестр: нетExempt from muting
neoessentials.chat.formatреестр: нетUse formatting codes in chat (&k-o, &r)
neoessentials.chat.gradientреестр: нетUse gradient text effects
neoessentials.chat.ignoreреестр: даIgnore players
neoessentials.chat.itemlinkреестр: даShow held item in chat with [item]
neoessentials.chat.links.bypassреестр: нетBypass link filter
neoessentials.chat.mentionреестр: даMention other players with @name
neoessentials.chat.mention.allреестр: нетMention everyone with @everyone
neoessentials.chat.msgреестр: даSend private messages
neoessentials.chat.msgtoggleреестр: даToggle message acceptance
neoessentials.chat.msgtoggle.bypassреестр: нетMessage players who have toggled off message receipt
neoessentials.chat.muteреестр: нетMute, unmute, and view the mute list (/mute, /unmute, /mutelist)
neoessentials.chat.rainbowреестр: нетUse rainbow text effects
neoessentials.chat.repeat.bypassреестр: нетBypass repeat message filter
neoessentials.chat.replyреестр: даReply to messages
neoessentials.chat.richtextреестр: нетUse rich text effects (gradients, rainbow)
neoessentials.chat.socialspyреестр: нетUse social spy
neoessentials.chat.spam.bypassреестр: нетBypass spam rate limit
neoessentials.chat.staffреестр: нетAccess to staff chat channel
neoessentials.chat.unignoreреестр: даUnignore players
neoessentials.helpopреестр: даSend a help request to staff
neoessentials.helpop.receiveреестр: нетReceive help-op requests
neoessentials.mailреестр: даUse mail system (read, delete, status)
neoessentials.mail.clearреестр: даClear own mail
neoessentials.mail.clear.othersреестр: нетClear another player's mail (admin)
neoessentials.mail.clearallреестр: нетWipe every player's mailbox (admin)
neoessentials.mail.sendреестр: даSend mail to players
neoessentials.mail.sendallреестр: нетBroadcast mail to all players
neoessentials.mail.sendtempреестр: даSend timed/expiring mail to a player
neoessentials.mail.sendtempallреестр: нетBroadcast timed mail to all players
neoessentials.msgtoggleреестр: даToggle your incoming private messages
neoessentials.msgtoggle.othersреестр: нетToggle another player's messages
neoessentials.nickреестр: даChange own nickname
neoessentials.nick.colorреестр: нетUse colour codes in nickname
neoessentials.nick.othersреестр: нетChange other players' nicknames

Подробное руководство#

Раздел ниже — исходная документация проекта (docs/Wiki/ChatSystem.md). Ссылки на другие страницы вики переписаны на этот сайт; текст самих руководств не редактируется, чтобы синк с апстримом не давал конфликтов.

Version: 1.0.5+build.54 · Config: config.json → chat section


Overview#

Full-featured chat system with format templates, rich text (gradients/rainbow/hover/click), per-group and per-player formatting, channel routing, Discord relay, mute/ignore, social spy, and per-player time/weather. All chat is logged to the server console.


Config (config.json → chat)#

Key Default Description
modules.chatEnabled true Master switch for chat handling (top-level modules section, not under chat)
chat-format (object) Per-group/world/default format templates — see Format Priority below
enableChatEnhancements true Clickable names, mentions, URL linking, [item] display
clickablePlayerNames true Make player names clickable (hover for stats, click to message)
autoLinkUrls true Automatically linkify URLs in chat
allowItemLinks true Allow [item] placeholder to show the held item
mentions.enabled true @PlayerName mention system (highlight color, sound, permission gate — see mentions.*)
badges.enabled true Rank badges/status icons — see Rank Badges & Status Icons
richText.enabled false Gradient/rainbow rich-text tags. Off by default — enable this to use <gradient>/<rainbow> (hover/click tags aren't gated by this flag)
conditionalFormatting.enabled false <if:time=...>/<if:health<50>/<if:afk>-style conditional format tags — a separate feature from richText, not otherwise documented on this page yet
channels (object) Per-channel definitions — see Chat Channels
enable-chat-formatting true Apply the chat-format templates at all — false falls back to vanilla chat formatting
enable-color-codes true Allow &-color codes (including hex) to render in formatted chat at all — a prerequisite gate above the per-player Colour Permissions below
logChatToConsole true Print formatted messages to server console
customJoinMessage "none" Custom join broadcast (placeholders supported via PlaceholderAPI). "none" = use vanilla join message
customQuitMessage "none" Custom quit broadcast. "none" = use vanilla quit message

There is no global localChatRadius/joinMessage/quitMessage key — proximity-based chat is configured per-channel (see below), and join/quit broadcasts are controlled by customJoinMessage / customQuitMessage.


Chat Channels#

Channels are defined under config.json → chat.channels and are resolved (in order) by explicit prefix, per-player channel state (set via channel-switch commands), then the channel flagged "default": true, falling back to "global" if none matches.

"channels": {
  "enabled": true,
  "global": {
    "enabled": true,
    "default": true
  },
  "local": {
    "enabled": true,
    "prefix": "!",
    "radius": 100
  },
  "staff": {
    "enabled": true,
    "prefix": "@",
    "permission": "neoessentials.chat.staff",
    "discord": {
      "enabled": true,
      "channelId": "123456789012345678"
    }
  }
}
Key (per channel) Description
enabled Enable this channel
prefix Message prefix that switches to this channel for a single message (e.g. !hello)
default Marks the channel used when the player has no explicit channel and typed no prefix
radius If set, makes the channel proximity-based (blocks); only players within radius blocks in the same dimension receive the message
permission If set, only players holding this permission receive the message (and it gates Discord relay for the channel)
discord.enabled / discord.channelId Relay this channel's messages to a specific Discord channel (see Discord Integration)

A channel with neither radius nor permission behaves as global chat. chat.channels.enabled: false disables the whole channel system (falls back to plain global chat).


Chat Format Placeholders#

Placeholder Value
{prefix} Player's permission group prefix
{suffix} Player's permission group suffix
{name} Player's real username
{displayname} Player's nickname or real name
{message} The chat message content
{world} Current world/dimension name
{neoessentials_prefix} Alias for {prefix}
{neoessentials_suffix} Alias for {suffix}
{neoessentials_username} Alias for {name}
{neoessentials_displayname} Alias for {displayname}
{neoessentials_channel} The channel this message is being sent in — that channel's displayName if set (see Chat Channels), else the raw channel key (local, global, staff, or any custom channel key from chat.channels)
{MESSAGE} Alias for {message} (case-insensitive)

{displayname}/{neoessentials_displayname} never includes the rank prefix/suffix — it only ever resolves to the player's /nick nickname (if set) or their real username, nothing else. Use {prefix}/{suffix} explicitly in your template if you want the rank shown, as the shipped default does (<{neoessentials_prefix} {neoessentials_displayname} {neoessentials_suffix}>). Prior to build.45/build.46, {displayname} could fall back to the platform's raw getDisplayName() value, which — under a permissions plugin that formats names via vanilla scoreboard teams (LuckPerms does this on Forge/NeoForge) — already had the prefix baked in, doubling it up when combined with an explicit {prefix} token (e.g. [Owner] [Owner] Name). Fixed; no config changes needed.

A color code placed right before {neoessentials_username}/{neoessentials_displayname} now actually colors the name (fixed in build.60). With chat.clickablePlayerNames enabled (the default), the name is built as its own component for its hover/click behavior — before build.60 this component always started uncolored, so e.g. "&c{neoessentials_username}" rendered the name in default white no matter what color preceded it, even though everything else in the template colored correctly. An explicit color inside the name itself (e.g. a colored /nick) still takes priority over a preceding template color, same as normal color code precedence.

Tablist-Style Short Tokens#

These tokens have no {neoessentials_*} equivalent — they were previously tablist/hologram-only, but now also resolve in chat formats (per-player override, per-group format, or the global chat-format default). Values reflect the sending player's own context — e.g. {ping} (already available as {neoessentials_ping}) means the sender's own latency, since a broadcast chat message has one sender but many recipients, unlike the tablist where each viewer sees their own ping.

Placeholder Value
{tps} Server TPS — auto-coloured green (≥19) / yellow (≥15) / red (<15)
{online} Online player count (vanish-aware, from the sender's permission level)
{max} Server max player slots
{channel} Short alias for {neoessentials_channel} — the channel this message is being sent in
{rank_weight} Sender's numeric permission group weight/priority
{network_online} Total players on the proxy network (requires tablist.proxy.enabled; falls back to local {online} otherwise)
{current_server} Proxy server name the sender is on (falls back to tablist.proxy.serverLabel if proxy is off)
{server_label} This server's configured display label (tablist.proxy.serverLabel)
{session_minutes} Total minutes elapsed in the sender's current session (not capped to 0–59 — pair with {session_hours}, same semantics as tablist)
{session_hours} Full hours elapsed in the sender's current session
{newline} Line break \n
{bar} Decorative strikethrough separator (&8&m──────────)

Animations#

{animation:name} tokens (defined in animations.json, the same ones usable in tablist headers/footers) also resolve in chat formats — e.g. {animation:network_pulse} in a per-group chat format. Animation frames advance on a global wall-clock timer independent of the tablist system, so this works even if the tablist feature itself is disabled.


Format Priority#

When a chat message is sent, NeoEssentials selects the format using this priority order (highest to lowest):

1. Per-player override    (/chatformat set <player> <format>)
2. Per-group + per-world  (key: "group:admin:world:overworld")
3. Per-group              (key: "group:admin")
4. Per-world              (key: "world:overworld")
5. Default format         (key: "default")

Color Codes in chat-format#

Color codes go in the value (the format string), not in the key. The key must remain a plain group identifier such as group:fondateur.

Legacy & codes#

Code Color/Effect
&0–&9, &a–&f Standard Minecraft colors
&l &m &n &o &k Bold / Strikethrough / Underline / Italic / Obfuscated
&r Reset all formatting

Hex colors#

&#RRGGBB

Example: &#FF5500 for orange, &#00FFCC for mint.

Per-group and per-world format examples#

"chat-format": {
  "default":                          "&f[&7Member&f] &f{neoessentials_username}&7: &f{MESSAGE}",
  "group:vip":                        "&f[&#FFD700VIP&f] &f{neoessentials_username}&7: &f{MESSAGE}",
  "group:moderateur":                 "&f[&cModérateur&f] &f{neoessentials_username}&7: &f{MESSAGE}",
  "group:fondateur":                  "&f[&4Fondateur&f] &f{neoessentials_username}&7: &f{MESSAGE}",
  "group:fondateur:world:overworld":  "&f[&4Fondateur&f|&aOverworld&f] &f{neoessentials_username}&7: &f{MESSAGE}",
  "world:the_nether":                 "&f[&6Nether&f] &f{neoessentials_username}&7: &f{MESSAGE}"
}

Note – Before v1.0.2.7 there was a bug where & color codes in format strings were silently stripped when enableChatEnhancements was true (the default), causing all chat text to appear white. This is fixed in v1.0.2.7.

Common mistakes to avoid#

❌ Wrong ✅ Correct
Color code in the key: "group:&cFondateur" Keep the key as "group:fondateur"
Unicode escapes in the key: "\u0026cgroup:fondateur" Color codes belong in the value string
Missing reset after colored text Add &f (white) or &r (reset) after the group name

Note – Group keys are matched case-insensitively ("group:VIP" and "group:vip" both resolve the same player), so casing in the key never matters — write it however's readable.


Chat Formatting with FTB Ranks#

"group:<name>" keys work with FTB Ranks the same way they do with LuckPerms — <name> is the rank's id (the key it's defined under in FTB Ranks' own rank config, e.g. a rank block written as SeasonedExplorer: { ... } is matched by "group:SeasonedExplorer", or equally "group:seasonedexplorer" — matching is case-insensitive). If a player holds more than one rank at once (FTB Ranks allows this, unlike LuckPerms' single primary group), the highest-power rank is used.

"chat-format": {
  "default":                "&7{neoessentials_username}&7: &f{MESSAGE}",
  "group:seasonedexplorer": "✈&bSeasoned Explorer&r✈&b {neoessentials_username}&7: &f{MESSAGE}"
}

The {ftbranks_prefix} / {ftbranks_suffix} placeholders#

FTB Ranks doesn't have separate "prefix" and "suffix" fields the way LuckPerms does — instead a rank sets one ftbranks.name_format permission value, a template such as "✈&bSeasoned Explorer&r✈&b {name}" that FTB Ranks substitutes {name} into to build the player's styled name elsewhere (nameplate/tablist). NeoEssentials reads that same value and splits it on the {name} token — everything before it becomes {ftbranks_prefix}, everything after becomes {ftbranks_suffix} — so you can reuse a rank's existing name_format directly in chat without redefining it:

"default": "{ftbranks_prefix}{neoessentials_username}{ftbranks_suffix}&7: &f{MESSAGE}"

For the example rank above (name_format: "✈&bSeasoned Explorer&r✈&b {name}"), this resolves to {ftbranks_prefix} = "✈&bSeasoned Explorer&r✈&b " and {ftbranks_suffix} = "" (nothing follows {name} in that template). A rank with no ftbranks.name_format set at all falls through with empty prefix/suffix, same as a player with no LuckPerms meta configured.

{ftbranks_rank} / {ftbranks_group} (equivalent aliases) resolve to the same rank id used for "group:<name>" key matching above.


Per-Player Format Overrides#

Admins can assign a completely custom chat format to any individual player. The per-player format takes the highest priority and overrides all group and world formats for that player.

Commands#

Command Permission Description
/chatformat set <player> <format> neoessentials.chat.format.set Assign a custom format to a player
/chatformat clear <player> neoessentials.chat.format.set Remove the custom format (reverts to group/default)
/chatformat check <player> neoessentials.chat.format.set Show the active per-player override
/chatformat list neoessentials.chat.format.set List all currently active per-player overrides
/chatformat reload neoessentials.chat.format.set Reload per-player formats from disk

Examples#

/chatformat set Notch &f[&#FFD700Owner&f] &6{neoessentials_username}&7: &f{MESSAGE}
/chatformat set Steve <gradient:ff0000-0000ff>{neoessentials_username}</gradient>&7: &f{MESSAGE}
/chatformat clear Notch

Persistence#

Per-player formats are saved to config/neoessentials/player_chat_formats.json and survive server restarts.


Rich Text#

When richText.enabled is true, the format string (and messages by players with the appropriate permission) can use advanced text effects.

Gradients#

<gradient:RRGGBB-RRGGBB>text</gradient>
Example Effect
<gradient:ff0000-0000ff>text</gradient> Red → blue gradient
<gradient:FFD700-FF8C00>VIP</gradient> Gold → dark-orange gradient
<gradient:00c6ff-0072ff>text</gradient> Sky-blue gradient

Gradients work in both format templates and in player messages (if the player has the neoessentials.chat.namedcolors permission — see Player Message Colour Permissions).

Format template example with gradient prefix:

"group:vip": "<gradient:FFD700-FF8C00>[VIP]</gradient> &f{neoessentials_username}&7: &f{MESSAGE}"

Always close your <gradient:...> tags. An unclosed <gradient:...> (no matching </gradient>) is treated as "gradient the rest of the line" — everything after the tag, including any &-color codes you meant to reset back to normal color (e.g. &r, &8), gets swallowed into the gradient region. Single legacy &-codes inside that region are passed through atomically rather than being shredded character-by-character, so this degrades gracefully, but closing the tag explicitly is still the clearest way to control exactly where a gradient starts and stops: <gradient:00FFC8-0080FF>&lGradiented Text</gradient>&r &8| &enormal text again

Rainbow#

<rainbow>text</rainbow>

Applies a cycling rainbow colour to each character.

Hex colour span#

<color:#RRGGBB>text</color>

Example: <color:#00FFCC>Hello world</color>

Named colours#

<red>text</red>    <gold>text</gold>    <aqua>text</aqua>    <white>text</white>
<dark_red>text</dark_red>   <dark_blue>text</dark_blue>   <yellow>text</yellow>

Format tags#

<bold>text</bold>               <italic>text</italic>
<underline>text</underline>     <strikethrough>text</strikethrough>
<obfuscated>text</obfuscated>

Hover events#

Show a tooltip when a player hovers their cursor over part of the message:

<hover:HOVER_TEXT>VISIBLE_TEXT</hover>

Examples:

<hover:Click to join our Discord!>[Discord]</hover>
<hover:This player is a donator!><gradient:FFD700-FF8C00>[VIP]</gradient></hover>

Tip – Hover text is plain text only (no color codes inside the hover tooltip value).

Click events#

Make text clickable in chat:

<click:ACTION:VALUE>VISIBLE_TEXT</click>
Action Effect
suggest_command Populates the player's chat bar with a command (does not run it)
run_command Executes a command when clicked
open_url Opens a URL in the player's browser
copy_to_clipboard Copies the value to clipboard

Examples:

<click:open_url:https://discord.gg/myserver>[Discord]</click>
<click:suggest_command:/help>[Help]</click>
<click:run_command:/spawn>[Spawn]</click>

Combining hover + click#

<hover:Visit our website!><click:open_url:https://example.com>[Website]</click></hover>

Full format template examples#

VIP group with gradient prefix and hover tooltip:

"group:vip": "<hover:VIP Member><gradient:FFD700-FF8C00>[VIP]</gradient></hover> &f{neoessentials_username}&7: &f{MESSAGE}"

Admin group with clickable rank badge:

"group:admin": "<hover:Server Administrator><click:suggest_command:/list>[&cAdmin&r]</click></hover> &f{neoessentials_username}&7: &f{MESSAGE}"

Founder with rainbow name:

"group:fondateur": "[&4Fondateur&r] <rainbow>{neoessentials_username}</rainbow>&7: &f{MESSAGE}"

Hex per-player format (set via /chatformat set):

[&#FF5500Custom&r] &#FFD700{neoessentials_username}&7: &f{MESSAGE}

Rank Badges & Status Icons#

Config: config.json → chat.badges (split configs: chat.json → chat.badges). Independent of richText/enableChatEnhancements — badges and status icons are inserted directly into the format template text, not the <tag> rich-text system. This is the "badges" feature seen in config — the working part is emoji/text badges; see the callout below for the custom-PNG path, which isn't fully wired up yet.

Key Default Description
enabled true Master switch for both rank badges and status icons
badgePosition "before_prefix" Where the rank badge is inserted: before_prefix | after_prefix | before_name | after_name
rankBadges (per-group emoji map) Emoji/text badge shown for each permission group (looked up by the player's primary group, lowercased — this correctly checks LuckPerms/FTB Ranks first when one is active, same as every other group lookup in the mod). A group with no entry (or an empty string) gets no badge
useCustomImages false See Custom PNG Badge Images below — not fully functional yet
customImageSize 16 Pixel size baked into the generated resource pack's font entries — 16, 24, or 32
customImagePath "config/neoessentials/badges" Where the mod looks for <rank>.png files and writes the generated pack
autoSendResourcePack false See Custom PNG Badge Images — does not actually push the pack to clients despite the name
requireResourcePack false Reserved for when auto-send is implemented — currently unused
resourcePackUrl (empty) Public URL to host the generated pack at, for manual server.properties setup — see below
resourcePackPrompt (default prompt text) Reserved for when auto-send is implemented — currently unused
statusIcons.enabled true Show AFK/vanished/muted status icons in chat
statusIcons.iconPosition "after_name" Where the status icon is inserted: before_name | after_name | after_message
statusIcons.afk (empty) Icon/text shown when the sender is AFK
statusIcons.vanished (empty) Icon/text shown when the sender is vanished (/vanish)
statusIcons.muted (empty) Icon/text shown when the sender is muted
statusIcons.streaming (empty) Present in config but not currently checked by anything — there's no "streaming" player status anywhere in the mod yet, so this key is a no-op regardless of value. Left as a placeholder for a future feature rather than removed.

Set the actual icon text yourself. Every rankBadges/statusIcons entry ships empty by default (except rankBadges.admin, which defaults to ⭐) — pick your own emoji or &-coded text, e.g. "vanished": "&7[Vanished]".

badgePosition/iconPosition work by inserting text next to the {neoessentials_prefix} / {neoessentials_username}/{neoessentials_name}/{neoessentials_displayname} tokens in your chat-format template — so a position that targets a token your template doesn't actually use (e.g. after_name when your format only has {neoessentials_displayname}, never {neoessentials_username}) won't show anything. after_message always works regardless of which name token your template uses, since it just appends to the very end of the formatted line.

Full example — an emoji badge per rank, plus a vanished icon:

"badges": {
  "enabled": true,
  "badgePosition": "before_prefix",
  "rankBadges": {
    "owner": "👑",
    "admin": "⭐",
    "moderator": "🛡️",
    "helper": "🔧",
    "vip": "💎",
    "default": ""
  },
  "statusIcons": {
    "enabled": true,
    "iconPosition": "after_name",
    "afk": "&7[AFK]",
    "vanished": "&7[Vanished]",
    "muted": "&c[Muted]"
  }
}

This is the entire setup — no resource pack, no restart-sensitive asset files, just edit the JSON and /neoe reload (or restart). Any Unicode emoji or &-coded text string works.

Custom PNG Badge Images — current limitations#

useCustomImages is a genuinely bigger feature — swap the emoji for your own artwork — but as shipped today only the first half of that pipeline is finished:

  • ✅ Works: drop <rank>.png files into config/neoessentials/badges/ (auto-created with a README.txt the first time the server starts with the playerTags module enabled — a separate toggle from badges.enabled, modules.playerTagsEnabled). With useCustomImages: true and autoSendResourcePack: true, the mod generates a real resource pack — config/neoessentials/NeoEssentials-Badges.zip plus a matching .sha1 — mapping each PNG to a font glyph, on next server start.
  • ❌ Not implemented — the mod cannot push that pack to players. Despite the name, autoSendResourcePack does not send anything: on player join the mod only logs the URL/hash and a reminder to configure it yourself. To actually deliver the pack you'd have to host the generated ZIP somewhere public and set vanilla's own resource-pack/resource-pack-sha1 in server.properties — a manual, server-wide resource pack, unrelated to this mod's own delivery (which doesn't exist yet).
  • ❌ Even then, the custom image never actually appears in chat. The chat badge shown is always the rankBadges emoji/text, regardless of useCustomImages — nothing in the current chat-formatting code inserts the generated pack's glyph character into a message. The resource pack it generates is real and structurally valid, but nothing in live chat renders it yet.

In short: stick to rankBadges emoji/text for now. useCustomImages is safe to leave off; turning it on doesn't break anything, it just doesn't currently change what shows up in chat.


Player Message Colour Permissions#

Players must have the appropriate permissions to use color/formatting in their own chat messages:

Permission Effect
neoessentials.chat.color Allow &-color codes in messages
neoessentials.chat.color.hex Allow &#RRGGBB hex colors in messages
neoessentials.chat.format Allow &-format codes (bold, italic, etc.) in messages
neoessentials.chat.namedcolors Allow <tag>-style rich text (named colors, gradient, rainbow, hover, click) in messages

Format strings set by admins (in config.json or via /chatformat set) are not subject to these restrictions — they always render fully.

neoessentials.chat.richtext, neoessentials.chat.gradient, and neoessentials.chat.rainbow are registered permission nodes (visible in /permissions search) but are not currently consulted anywhere in the chat pipeline — neoessentials.chat.namedcolors is the node that actually gates <gradient>/<rainbow>/<hover>/<click>/named-color tags in a player's own chat message.


Commands#

Private Messaging#

Command Syntax Permission Description
/msg /msg <player> <message> neoessentials.chat.msg Send a private message
/message, /tell, /pm, /w aliases same Aliases (there is no /whisper or /m alias)
/reply /reply <message> neoessentials.chat.reply Reply to last private message
/r alias same Alias
/msgtoggle /msgtoggle [on\|off] neoessentials.chat.msgtoggle Toggle receiving private messages
/rtoggle /rtoggle [on\|off] neoessentials.rtoggle Toggle receiving replies
/socialspy /socialspy [on\|off] neoessentials.chat.socialspy Spy on all private messages

Ignore System#

Command Syntax Permission Description
/ignore /ignore <player> neoessentials.chat.ignore Ignore a player's messages
/block alias same Alias for /ignore
/unignore /unignore <player> neoessentials.chat.ignore Unignore a player

No /ignorelist command exists. There is no registered command to list your currently ignored players (IgnoreManager.getIgnoreList() exists internally but nothing in the command layer exposes it). Treat any reference to /ignorelist as stale/unverified until such a command is actually added.

A player holding neoessentials.chat.ignore.exempt cannot be ignored. A player holding neoessentials.chat.mute.exempt cannot be muted with /mute.


When Simple Discord Link is installed, NeoEssentials relays chat to/from Discord per channel, using the discord object nested inside that channel's entry under chat.channels (see Chat Channels):

"channels": {
  "global": {
    "enabled": true,
    "default": true,
    "discord": {
      "enabled": true,
      "channelId": "123456789012345678",
      "webhookUrl": ""
    }
  }
}
Key Description
discord.enabled Relay this channel's Minecraft chat to the given Discord channel
discord.channelId Discord channel ID to relay to — used by SDLink/Mc2Discord/DCIntegration
discord.webhookUrl Discord webhook URL to relay to — used by the built-in Generic Webhook relay (see below); no bridge mod required

You can set channelId, webhookUrl, both, or neither — each is only read by the adapter(s) that understand it, and setting both just means both fire independently for the same message.

Chat's own relay settings live under each channel, not a separate top-level section — but non-chat events (join/leave/mute/AFK/advancement/private messages) aren't tied to any one NeoEssentials channel, so they get their own top-level discordEventChannels section instead:

"discordEventChannels": {
  "join":           { "enabled": true,  "channelId": "123456789012345678", "webhookUrl": "" },
  "leave":          { "enabled": true,  "channelId": "123456789012345678", "webhookUrl": "" },
  "mute":           { "enabled": true,  "channelId": "987654321098765432", "webhookUrl": "" },
  "afk":            { "enabled": false, "channelId": "", "webhookUrl": "" },
  "advancement":    { "enabled": false, "channelId": "", "webhookUrl": "" },
  "privateMessage": { "enabled": false, "channelId": "", "webhookUrl": "" }
}

Each entry works the same way as a chat channel's discord.* fields above — enabled: false or a blank channelId/webhookUrl means "let whichever bridge mod is installed route this event to its own natively-configured default channel instead," exactly as before this section existed. Setting both lets you, for example, send joins/leaves to a #server-log channel while keeping mutes in a private #mod-log channel, independent of chat's own per-channel routing. Works the same way across SDLink/Mc2Discord for all six event types. DCIntegration supports every event through this explicit-channel-override path too (as of build 67) — join/leave/mute/AFK/advancement — but only ever additively: it never has a default-route fallback for any of them, and for advancement specifically it's suppressing a real duplicate (DCIntegration relays advancements to its own default channel natively on its own, independent of NeoEssentials, whenever a player is linked and its advancementMessage template is set).

If a chat channel has a permission requirement, players without it are excluded from the Discord relay as well as in-game delivery.

Works standalone (no relay) if none of SDLink/Mc2Discord/DCIntegration/a configured webhook is present.

discordEmbedTemplate (see above) already builds a full styled embed for chat messages routed to a specific Discord channel. As of build 65, the same treatment extends to join/leave/mute/ AFK/advancement events — each gets its own nested override with sensible built-in defaults:

"discordEmbedTemplate": {
  "enabled": true,
  "authorName": "{player}",
  "...": "... (chat's own top-level fields, unchanged) ...",
  "join":        { "enabled": true, "description": "**{player}** joined the server", "color": "#57F287", "showTimestamp": true },
  "leave":       { "enabled": true, "description": "**{player}** left the server", "color": "#ED4245", "showTimestamp": true },
  "mute":        { "enabled": true, "description": "{message}", "color": "#FEE75C" },
  "afk":         { "enabled": true, "description": "{message}" },
  "advancement": { "enabled": true, "description": "**{player}** earned the advancement **{message}**", "color": "#FAA61A", "showTimestamp": true }
}

Only applies to a channel-override send (a specific discordEventChannels.<event>.channelId configured) — same condition as chat's own embed. {message} means something different per event: unused for join/leave, the full built status line for mute/AFK, and just the advancement's name for advancement. Set an event's enabled to false to fall back to a plain text line for just that event type. Currently SDLink-only — Mc2Discord/DCIntegration still send plain text for these events.

Generic Webhook relay (no bridge mod required)#

Every channelId field above needs a real Discord bot (SDLink, Mc2Discord, or DCIntegration) actually running on the server. If you don't want to run a bot at all, set the matching webhookUrl field instead — a Discord webhook is a plain URL Discord itself generates per channel (Server Settings → Integrations → Webhooks → New Webhook → Copy Webhook URL), and NeoEssentials posts to it directly over HTTPS with no companion mod, bot token, or gateway connection needed. This works even with zero of SDLink/Mc2Discord/DCIntegration installed.

Trade-offs versus a real bot-based relay: webhooks are one-way (Minecraft → Discord only — there's no way to relay a Discord reply back into chat this way) and have no concept of linked accounts/roles. Chat messages sent via webhook still impersonate the sending player (their name and Minecraft-head avatar as the message's displayed sender), same as SDLink's own default look, using Discord's per-message username/avatar_url override — you don't need to rename the webhook itself per player.

A webhook added or changed via /neoe reload takes effect immediately — no server restart needed, unlike a newly-installed bridge mod.

SDLink has its own native chat/join/leave/advancement broadcasters, independent of NeoEssentials. SDLink's own config (config/simple-discord-link/simple-discord-link.toml, under [chat]) ships with playerMessages, playerJoin, playerLeave, and advancementMessages all enabled by default — each of these can post to Discord natively, completely separately from the equivalent event NeoEssentials also sends through the same SDLink bot. As of build.59, NeoEssentials detects this automatically at startup and skips its own send for whichever event(s) SDLink's native config already handles (as of build 64, via a real TOML parse of simple-discord-link.toml rather than a best-effort text scan) — a normal SDLink install no longer double-posts join/leave/advancement/default-route chat messages, with nothing to configure. A chat message routed to a specific per-channel Discord ID (chat.channels.<name>.discord.channelId) always still sends through NeoEssentials regardless, since that targets a channel SDLink's native relay never touches. NeoEssentials logs a startup warning naming which SDLink key(s) it detected as active/suppressed — if you'd rather NeoEssentials be the one formatting a given event instead of SDLink's native phrasing, set the corresponding key under [chat] in SDLink's own config to false (advancementMessages to "NEVER") and restart.

Mc2Discord has the same kind of native relay, and gets the same conflict detection. If any channel in config/mc2discord.toml is subscribed to "chat"/"player_connect"/ "player_disconnect"/"player_advancement", Mc2Discord relays that event to Discord on its own — as of build 62, NeoEssentials detects this at startup and skips its own default-route send for that event type, exactly like the SDLink handling above. A channel-override send (discordChannelId set) always still goes through regardless. Remove the relevant subscription from mc2discord.toml and restart if you'd rather NeoEssentials format that event instead.

Rank prefix showing twice in Discord?#

If a player's rank prefix appears doubled in Discord (e.g. [Owner] [Owner] Name) even though it's correct in-game and in the tab-list, this is caused by a bridge mod's own native relay (not anything NeoEssentials sends) — it independently reads Player.getDisplayName() (which already carries whatever prefix NeoEssentials wrote onto the vanilla scoreboard team for the nametag/tab-list, see TablistSystem) and also separately re-resolves the same rank prefix itself (SDLink does this via its own LuckPerms/FTBRanks integration) — stacking both.

As of build 68, NeoEssentials' own Discord messages resolve the prefix independently — straight from the permission system, the same source chat formatting already uses — instead of the vanilla team, so they can never double no matter what the nametag/tab-list shows. Combined with the native-relay conflict detection above (which suppresses NeoEssentials' own send once a bridge mod's native relay is confirmed active for that event), the fix is:

  1. Check the startup log (or the dashboard's Discord status panel) for a native-relay-conflict warning naming the event that's doubling.
  2. Disable that specific native relay key in the bridge mod's own config (see the two notes above for SDLink/Mc2Discord's exact config keys) and restart.
  3. NeoEssentials' own send — now with the correctly single-resolved prefix — becomes the sole source for that event, unaffected by whatever the vanilla team/nametag is doing.

The floating nametag itself can't be decoupled from the vanilla scoreboard team for a server-only mod (this was investigated directly against Minecraft's own decompiled rendering code) — nametagSettings.enabled: false is only relevant if you want a different mod/plugin to own nametags entirely, not as a Discord workaround.

Discord Role → Permission Group Sync#

Separate from the chat/event relay above — see discordrolesync.json. Maps a Discord role's snowflake ID to a NeoEssentials permission group:

"discordRoleSync": {
  "enabled": false,
  "intervalSeconds": 300,
  "roleGroupMap": {
    "123456789012345678": "vip"
  }
}

When enabled, a linked player holding one of the mapped Discord roles is set to the matching permission group (highest priority wins if they hold more than one mapped role). Re-checked periodically (intervalSeconds) and immediately on join — this only ever grants/upgrades a group, it never demotes a player back to default if they no longer hold a mapped role.

Requires NeoEssentials' own internal permission manager (not LuckPerms/FTB Ranks — this can't write into an external plugin's groups) and DCIntegration specifically — it's currently the only bridge mod this can read a linked player's Discord role list through (SDLink's role lookup is a dead end without a public API for it; Mc2Discord isn't wired up yet).


Data Files#

Ignore lists and mutes are persisted through the pluggable DataStore backend (JSON by default — see Storage Backend), not dedicated bespoke files:

Collection / File Contents
ignore_lists Per-player ignore lists
mutes / ip_mutes Active player mutes / IP mutes
config/neoessentials/player_chat_formats.json Per-player format overrides (still a bespoke file, not DataStore-backed)

Legacy files: neoessentials/ignore_data.json and neoessentials/muted_players.json are the pre-DataStore on-disk formats. They're only read once, automatically, to migrate their contents into the collections above — never written to again afterward.


Back to Wiki Home