Farmstead Mods
EN

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

Симптом, причина и что с этим делать — по всем системам.

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

Как пользоваться этой страницей#

Ниже собраны известные проблемы со всех систем: симптом, каким его видит администратор, затем причина и что с этим делать. Каждая запись выверена по исходникам, Issues_Discovered.md или записям «Fixed» в чейнджлоге — сюда не попадают предположения.

Записи сгруппированы по системам; на странице самой системы они же продублированы в конце, рядом с её командами.

Сначала проверьте это#

Три причины покрывают большую часть обращений «команда не работает»:

  1. Подсистема выключена. Секции modules и commands в config.json читаются один раз при старте сервера. Если команда там выключена, её просто нет в реестре — /help её не покажет, а сервер ответит как на неизвестную команду. /neoessentials reload тут не поможет, нужен перезапуск.

  2. Нет права, а не поломка. В поставке permissions.requireExplicitGrant: true: узел из LuckPerms (или внутреннего менеджера) нужно выдать явно. defaultValue=true в реестре сам доступ не даёт. Без узла команда не видна в /help и ведёт себя как несуществующая. OP всё ещё проходит при opsBypassPermissions: true. Проверьте реальный узел в колонке «Право» на странице Все команды — он может не совпадать с шаблоном neoessentials.<имя команды>. Игроку при отказе показывается только сообщение об отсутствии прав: требуемый узел и его описание намеренно не выводятся в чат.

  3. Правку конфига не перечитали. /neoessentials reload обновляет значения, но не состав команд и не уже открытые GUI. После правок в modules, commands и вообще после смены структуры конфига нужен перезапуск.

Если нужно больше подробностей#

Включите подробное логирование для конкретной подсистемы — не глобально, а точечно:

"logging": {
  "categories": {
    "TELEPORTATION": { "normal": true, "debug": true }
  }
}

Отладочный вывод пишется в logs/debug.log. Категории и их назначение — на странице Логирование.

/payconfirmtoggle ничего не делает с /pay#

Экономика

Комментарий в MiscItemCommands обещает поведение Essentials Commandpayconfirmtoggle: «toggle whether /pay asks for confirmation». Команда пилит UUID в in-memory Set, геттер isPayConfirmDisabled больше нигде не вызывается. В PayCommand нет ни подтверждения, ни обращения к этому Set — перевод уходит сразу после проверок баланса, налога и paytoggle. Флаг ещё и не пишется на диск, рестарт его обнуляет.

Что делать: Не обещайте игрокам «сначала подтверди перевод». Если нужны подтверждения — это не эта команда; сейчас она только меняет строку в чате о своём внутреннем флаге.

/pwarp не телепортирует на player warp#

Телепортация

Issues_Discovered.md: «Player Warps having issues». В PwarpCommands.executePwarp после успешного getPlayerWarp вызывается warpManager.teleportToWarp(player, warpName) — поиск в карте серверных варпов. Комментарий в коде: «Reuse teleport logic (may need adjustment)». Метод teleportToPlayerWarp существует и помечен admin-only (OP 4), но ни /pwarp, ни /warp его не вызывают. Симптом: /setpwarp shop + /pwarps показывают shop, /pwarp shop отвечает not_found либо уносит на серверный /warp shop, если имя совпало.

Что делать: Не используйте pwarp как публичные точки игроков. Нужен рабочий телепорт — /home или /warp. Починить: в executePwarp вызывать телепорт по TeleportLocation из getPlayerWarp, не teleportToWarp.

Два разных /tpo в одном литерале#

Телепортация

DirectTeleportCommands.registerTpoCommand (если modules телепортации и commands.tpo) вешает /tpo <player> → teleportToOfflinePlayer (PlayerData.lastLocation), право neoessentials.teleport.admin.tpo. Позже ServerAdminCommands.registerTpo вешает /tpo <target> → онлайн teleportTo с обходом tptoggle, право neoessentials.teleport.tpo. Имена аргументов разные (player vs target), оба word: у корня tpo два ребёнка. Какой execute получит /tpo Steve на конкретной версии Brigadier, из исходника не следует однозначно.

Что делать: Онлайн в обход toggle — рассчитывайте на /tpo только если commands.tpo выключен (тогда останется узел ServerAdmin). Офлайн — всегда /tpoffline. Не документируйте /tpo как «офлайн-телепорт».

/tpaall отправляет заявку только первому игроку#

Телепортация

WorldInteractionCommands.executeTpaAll в цикле зовёт sendTeleportRequest(hub, online, TPAHERE). Менеджер держит одну исходящую заявку на UUID (sentRequests): вторая итерация отвечает already_sent и откатывается. Итог: «sent N» почти всегда 1 (или 0, если единственный кандидат с выключенным tptoggle). Это не очередь maxPendingRequests.

Что делать: Собрать всех — /tpall (принудительно) или разослать /tpahere руками. /tpaall в текущем виде массовым приглашением не является.

IP-мут из панели не закрывает чат#

Модерация

MuteManager.muteIP / коллекция ip_mutes / маршруты /api/moderation/ipmute существуют, isIPMuted объявлен. ChatMuteGuard и ChatHandler проверяют только isMuted по имени игрока; вызовов isIPMuted в моде больше нет. Игровой команды IP-мута тоже нет. Запись в хранилище появляется, чат, /msg и почта идут как обычно.

Что делать: Глушите имя через /mute или POST /api/moderation/mute. IP-бан (/banip) по-прежнему не пускает на сервер — это другой механизм.

Штабной пинг /report не локализуется#

Модерация

/modhistory использует локализованные строки и спокойный формат с причиной, выдавшим и датой. После /report онлайн с neoessentials.moderation.reports всё ещё получают сырую строку [Report] <nick> reported <nick>: <reason>, минуя lang.

Что делать: Историю наказаний смотрите через /modhistory <player>. Для пинга новых жалоб используйте панель, если нужен русскоязычный интерфейс.

/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.

Поставочный command team ломает ванильную /team#

Каналы чата

chat.channels.team.command = team. dispatcher.register(Commands.literal("team")) перехватывает имя ванильного scoreboard /team. Это не баг парсера — конфликт заложен в дефолтном конфиге. Алиасы t и teamchat от ванили свободны.

Что делать: Переименуйте command канала (оставьте teamchat) либо выставьте team.enabled: false, если FTB Teams не используете, и перезапустите.

Комментарий в ChatHandler про общий try/catch устарел#

Каналы чата

Javadoc getChannelDisplayName всё ещё утверждает, что один сломанный литерал абортит регистрацию всех каналов после него. В ChannelCommands уже per-channel try/catch. На поведение это не влияет, на чтение исходников — да.

Что делать: Ориентируйтесь на ChannelCommands.java, не на этот комментарий.

`permissionSync.permissionMappings` не выдаёт узлы прав#

Discord-интеграция

DiscordPermissionSync.mapDiscordRoleToPermissionGroup берёт permissions.get(0) и передаёт в user.setGroup. Существование группы не проверяется. Поставляемый discord_auth.json начинает списки с neoessentials.*, neoessentials.dashboard.temppass и т. д. — это не имена групп. Плюс reloadConfig() ниоткуда не вызывается: после правки файла синглтон продолжает старый маппинг до рестарта, хотя /neoe reload сбрасывает JSON-кеш.

Что делать: Выключите permissionSync.enabled и пользуйтесь discordrolesync.json. Если оставляете этот путь — первым элементом списка должно быть имя группы из permissions.json, и после правки нужен рестарт, не только /neoe reload.

Включили `discordRoleSync.enabled` на уже запущенном сервере — периодический обход молчит#

Discord-интеграция

DiscordRoleSyncTask.start() вызывается из ServerStartedEvent и сразу выходит, если enabled тогда был false. Join-обработчик читает флаг каждый раз и после /neoe reload уже работает. ScheduledExecutorService при этом никто не поднимает — смена роли без перезахода подтянется только после рестарта (или не подтянется, пока игрок онлайн).

Что делать: Первое включение role-sync — рестартом. Дальше достаточно /neoe reload для карты ролей; для смены intervalSeconds снова рестарт.

Детектор native relay не обновляется с `/neoe reload`#

Discord-интеграция

Разбор simple-discord-link.toml / mc2discord.toml делается в initialize() адаптера на старте. Правка родного конфига моста или переключение его native-ключей без рестарта оставит старый набор подавлений: либо снова пойдут дубли, либо NeoEssentials продолжит молчать, хотя native уже выключен.

Что делать: После правок конфига моста перезапускайте сервер. Смотрите WARN на старте и nativeRelayWarnings в статусе панели.

Родной релей моста по-прежнему может удвоить префикс#

Discord-интеграция

Сообщения, которые шлёт сам SDLink из chat.playerMessages, читают Player.getDisplayName() (там уже префикс scoreboard-команды таблиста) и отдельно клеят ранг из LuckPerms/FTB Ranks. Это закрытый код моста. Сообщения адаптеров NeoEssentials с build 68 берут префикс из PermissionAPI и не удваиваются. Детектор как раз глушит default-route NeoEssentials, когда native включён — тогда в Discord остаётся именно удваивающий native.

Что делать: Выключите native-релей моста для этого события и перезапустите, чтобы в канал шёл только адаптер NeoEssentials. tablist.json → nametagSettings.enabled: false убирает префикс с таблиста/над головой целиком — это не точечный фикс Discord.

kits.pastebinCreatekit отдаёт поддельный URL и не создаёт кит#

Киты

uploadToPastebin в CreateKitCommand — заглушка: всегда возвращает https://pastebin.com/mock/…, HTTP-вызова нет. При включённом флаге ветка сохранения в DataStore пропускается целиком, поэтому /kit этот кит не видит.

Что делать: Оставьте kits.pastebinCreatekit: false. Нужен JSON — заберите его из коллекции kits в хранилище.

Раньше /invsee копировал предметы в инвентарь смотрящего (исправлено)#

Предметы

До build.214 /invsee//inv//ec//enderchest открывали ChestMenu над SimpleContainer с .copy() стака. Клиент мог вытащить копии, оригиналы оставались у цели. Сейчас read-only идёт через buildReadOnlyMenu (mayPickup/mayPlace = false). Правка шла через меню без слотов хотбара смотрящего — рассинхрон; слоты зрителя добавлены. Алиасы больше не redirect() без requires() (обход прав на /inv//ec закрыт).

Что делать: На текущей сборке дублирование через read-only окно не воспроизводится. Если игрок «вынес» вещи из /invsee — это либо старый билд, либо у него есть neoessentials.invsee.edit.

/ciconfirmtoggle ни на что не влияет#

Предметы

MiscItemCommands.isCiConfirmDisabled ни один вызов /clearinventory//ci не читает: подтверждения нет, тоггл только пишет «enabled/disabled» в чат. Состояние в памяти, без DataStore.

Что делать: Не обещайте игрокам превью перед очисткой. Нужна страховка — не выдавайте neoessentials.item.clearinventory.

/pong и /fw есть в /help, в Brigadier их нет#

Служебные команды

NeoEssentials.java вызывает registerCommand("pong") и registerCommand("fw"), поэтому строки попадают в каталог HelpCommand. Ни PingCommand, ни FunCommands не делают Commands.literal("pong") / literal("fw"). Автодополнение и разбор команды идут по дереву: /pong и /fw — неизвестные команды (или ваниль/другой мод, если кто-то ещё их занял).

Что делать: Пинг — /ping. Фейерверк — /firework clear|power|fire|color. Не обещайте игрокам алиасы из /help, пока литералы не появятся в исходнике.

/info в каталоге подписан как алиас whois, а выполняет другое#

Служебные команды

registerCommand("info", "Get player information (alias)") описывает whois, но WhoisCommand литерал info не регистрирует. FunCommands вешает /info на печать MOTD + footer про /rules. Право neoessentials.info, аргументов нет — /info Steve не сработает как whois.

Что делать: Данные игрока — только /whois . /info трактуйте как краткий MOTD.

/compass печатает карточку дважды#

Служебные команды

В ветке без цели CompassCommand подряд дважды вызывает showCompassInfo(...). Игрок видит дублирующийся блок «facing / coordinates / biome». Ветка с [player] вызывается один раз.

Что делать: Пока не исправят — смотрите первую карточку. Чужой /compass дубля не даёт.

/whois не показывает офлайн-игроков#

Служебные команды

Ветка word вызывает showOfflinePlayerInfo → getOfflinePlayerSeenInfo, который всегда возвращает Optional.empty() (заглушка «integrate with SeenCommand»). Даже с neoessentials.whois.detailed офлайн-ник заканчивается player_not_found. Онлайн — EntityArgument.player() — работает.

Что делать: Офлайн-статус смотрите /seen (seen_data.json). /whois только для тех, кто сейчас в мире.

Голый /fireball требует несуществующий узел fireball.fireball#

Служебные команды

Корневой requires проверяет neoessentials.fireball. executeFireball для типа по умолчанию "fireball" дополнительно требует neoessentials.fireball.fireball или neoessentials.fireball.*. Узла fireball.fireball в PermissionRegistry нет. Игрок с одним только fireball (и без OP-обхода) получит no_perm_type. Подсказка trident есть в PROJECTILE_TYPES, в switch его нет — спавнится large fireball.

Что делать: Выдайте neoessentials.fireball.* (или OP при opsBypassPermissions). Для трезубца не рассчитывайте на отдельную сущность.

/recipe, /give, /kill, /xp, /effect перекрывают ваниль#

Служебные команды

Мод регистрирует те же литералы позже ванили. Пропадают: /recipe give, /give с селекторами и компонентами, /kill @e, полный /xp add/query, ванильный /effect. /help мод снимает рефлексией и ставит свой. Это не сбой разбора — последнее дерево побеждает.

Что делать: Ванильный разбор — с префиксом пространства имён: /minecraft:give, /minecraft:kill, /minecraft:recipe, /minecraft:xp, /minecraft:effect, /minecraft:help.

/rest не пропускает ночь#

Служебные команды

Апстрим-wiki пишет «Skip the night (vote)». Реализация — resetStat(TIME_SINCE_REST): обнуляет счётчик бессонницы, из-за которого появляются фантомы. Игровой день, погода и sleep-vote не меняются.

Что делать: Ночь — /time night / /day или ванильный сон. /rest — только антифантомы.

/away не снимает АФК, пока trackCommands включён#

АФК

CommandEvent срабатывает до executes. Для /away (его нет в дефолтном excludedCommands) AfkCommandHandler вызывает updateActivity: если игрок уже АФК, статус снимается. Затем toggleAfk ставит АФК снова — в чат могут уйти и return, и «is now AFK», а игрок остаётся АФК. /afk в списке исключений, поэтому туда-обратно работает.

Что делать: Возвращайтесь командой /afk или движением. Либо добавьте away в afk.excludedCommands.

Ключ `titleFrames` из оригинальной вики не читается#

Скорборд

ScoreboardManager.loadConfig() берёт кадры заголовка только из title (строка или массив). Поля titleFrames в JSON нет в загрузчике. Анимация заголовка задаётся массивом в title. Комментарий в поставке и страница ScoreboardSystem.md расходятся с кодом.

Что делать: Пишите кадры так: "title": ["кадр1", "кадр2"]. Не заводите отдельный titleFrames — он будет проигнорирован.

`toggleCommandEnabled` ни на что не влияет#

Скорборд

Оригинальная вики описывает ключ toggleCommandEnabled. Загрузчик его не читает, ветка /scoreboard toggle регистрируется вместе с остальной командой, пока включены модуль и commands.scoreboard.

Что делать: Чтобы отнять toggle у игроков, заберите узел neoessentials.scoreboard.toggle. Чтобы убрать всю команду — commands.scoreboard: false и рестарт.

Запись доски через веб-панель выбрасывает `refreshMultiplier`#

Скорборд

toConfigJson() / boardToJson() сериализуют name, priority, conditions, title, lines — и не пишут refreshMultiplier. Следующий save с панели перезапишет scoreboard.json без этого поля; после reload множитель снова станет 1.

Что делать: Задавайте refreshMultiplier вручную в файле и не сохраняйте эту доску кнопкой панели, либо верните поле в файл после save.

`{displayname}` на сайдбаре — это IGN, не ник#

Скорборд

resolveText подставляет и {player}, и {displayname} через player.getName(). /nick на эту строку не действует. Имя плейсхолдера обещает display name, код отдаёт реестровое имя.

Что делать: Не ждите ника в {displayname} на сайдбаре. Для строк, где нужен ник, используйте тот плейсхолдер ника, который реально резолвит PlaceholderAPI, и проверьте его /scoreboard preview.

GUI не использует `entryFormat`, хотя комментарий в leaderboard.json обещает это#

Лидерборды

Комментарий поставки и LeaderboardSystem.md пишут, что entryFormat задаёт и чат, и имена предметов в GUI. LeaderboardMenu.buildEntryStack всегда делает Component.literal("#" + rank + " " + name) и не читает definition.entryFormat(). На доске shop_sales в чате медали и цвета есть, в сундуке — нет.

Что делать: Ориентируйтесь на чат /leaderboard <board> как на единственное место, где шаблон работает. GUI — только ранг, имя и значение в lore.

Инвалидация экономики привязана к id `money`#

Лидерборды

BaltopCommand.invalidateCache() после /pay и /eco инвалидирует LeaderboardManager.getBoard("money"). Другое id экономической доски этот вызов не найдёт. Сам /baltop при этом обновится.

Что делать: Оставляйте id money или смиритесь с задержкой до refreshInterval / ручным /leaderboard reload.

`{network_online}`, `{server_online:X}` и `{current_server}` всегда показывают локальные данные, `/tablist proxy status` вечно `detected=false`#

Таблист

Интеграция с прокси не работает ни в одну сторону. Исходящие сообщения: метод sendBungeeMessage — заглушка, в его javadoc прямо написано, что кодирование через StreamCodec на NeoForge 1.21.1 не сделано и отложено; он только пишет debug-строку в лог и ничего не отправляет. Входящие: обработчик onPluginMessage, который разбирает ответы GetServer/GetServers/PlayerCount, не вызывается ниоткуда — канал плагин-сообщений нигде не подписан (обработчик onServerStarted в TablistEventHandler пустой, там только комментарий). Поэтому serverPlayerCounts остаётся пустым, {network_online} откатывается на локальный счётчик, {server_online:ЛЮБОЕ} даёт 0, а {current_server} — значение proxy.serverLabel.

Что делать: Не полагайтесь на сетевые плейсхолдеры. Если число нужно показать прямо сейчас — задавайте его вручную: /tablist proxy setserver Lobby 12 (это единственный работающий источник данных для {server_online:Lobby} и суммы {network_online}). proxy.enabled: false в поставке стоит по умолчанию не случайно.

`excludeServers`, `hiddenServers`, `playersByServer` и `showNetworkPlayers` не влияют ни на что#

Таблист

Значения читаются из tablist.json и печатаются в /tablist layout info, но методы, которые должны их применять (isHiddenByServer, isExcludedServer, groupedByServer, isShowNetworkPlayers), не вызываются ни из одного места мода. Раскладка по серверам прокси не собрана в рендер, а поскольку данных от прокси всё равно нет (см. выше), фильтры по именам серверов бессмысленны вдвойне.

Что делать: Считайте эти четыре ключа задекларированными на будущее. Группировку игроков делайте через layout.groupSections по группам прав — эта ветка реализована полностью.

Удалённая или переименованная строка `fakePlayers` продолжает висеть в табе после `/tablist reload`#

Таблист

При перезагрузке список записей в памяти очищается и перечитывается из конфига, после чего игрокам рассылаются только новые записи. Пакет удаления (ClientboundPlayerInfoRemovePacket) при этом не отправляется, а множество уже вставленных UUID для каждого зрителя не чистится, поэтому клиент продолжает рисовать запись, которой в конфиге больше нет. Смена id у существующей записи даёт тот же эффект: UUID выводится из id, старый остаётся висеть, рядом появляется новый. Команда /tablist fakeplayer remove <id> этой проблемы не имеет — она честно рассылает пакет удаления всем онлайн-игрокам.

Что делать: После любой правки секции fakePlayers выполняйте /tablist fakeplayer refresh — он снимает все записи у всех и вставляет заново. Перезаход игрока на сервер тоже очищает его таб.

`/tablist layout sort` ничего не переключает#

Таблист

В дереве команды у ветки layout sort нет аргумента on|off — узел просто выполняется и печатает информационное сообщение. Заголовочный javadoc самого класса и страница оригинальной вики заявляют /tablist layout sort [on|off], но такой ветки в регистрации нет, и ввод on/off будет отвергнут как неизвестный аргумент.

Что делать: Меняйте layout.sortByGroupWeight в tablist.json и делайте /tablist reload. Проверить текущее состояние можно через /tablist layout info или /tablist info.

Ручные счётчики `/tablist proxy setserver` переживают `/tablist reload`#

Таблист

У ProxyIntegration есть метод reset() с комментарием «вызывается при перезагрузке», но вызовов у него нет ни одного. loadConfig() перечитывает только настройки секции proxy, а накопленные serverPlayerCounts, сумма networkOnline и карта «игрок → сервер» остаются в памяти.

Что делать: Перезатрите значение тем же /tablist proxy setserver <server> 0 или перезапустите сервер — другого способа сбросить их нет.

Префикс/суффикс пропал у всех игроков после перезагрузки или после входа нового игрока#

Таблист

Исторически это давали два независимых бага, оба уже исправлены и подробно описаны комментариями в коде. Первый: перед основным обновлением команд вызывался отдельный проход сортировки, который переставлял игроков на команду neL_... без префикса; кэш «что уже отправлено» этого не замечал и не переотправлял префикс, так что после первого же цикла обновления все оставались с пустым префиксом до /tablist reload. Второй: команда называлась просто по группе, то есть все игроки группы делили один объект команды с одним общим префиксом/суффиксом — чей апдейт отработал последним, тот текст и видели все остальные (новые подключения обрабатываются последними, поэтому это выглядело как «зашёл игрок — и у всех сбросился суффикс»). Сейчас имя команды включает хэш фактически собранного префикса+суффикса, и лишний проход сортировки убран.

Что делать: Если симптом воспроизводится — у вас сборка до исправления, обновитесь. Если и на свежей сборке префиксы слетают, проверьте, не управляет ли scoreboard-командами другой мод: игрок может состоять только в одной команде, и любой сторонний /team join перетирает то, что поставил таблист.

`/tablist disable` выключает не всё и не навсегда#

Таблист

Ветка disable ставит флаг в памяти и рассылает пустой пакет шапки/подвала. Она не снимает фейковые строки и строки-заполнители сетки, не разбирает scoreboard-команды и не трогает tablist.json. Дальше updatePlayer выходит на первой же строке, поэтому висящие записи уже никто не уберёт. Любой /tablist reload, /neoe reload или перезапуск вернут значение enabled из конфига.

Что делать: Перед /tablist disable выполните /tablist fakeplayer refresh… а точнее, чтобы выключить систему по-настоящему, поставьте "enabled": false в tablist.json (или modules.tablistEnabled: false в config.json) и перезапустите сервер. Оставшиеся декоративные строки уходят у игрока при перезаходе.

«У вас нет ключей» при открытии командой, хотя ключ-предмет лежит в руке#

Ящики

Вопреки описанию в английской вики и в changelog, /crate open <crate> предметы-ключи не учитывает: обработчик команды проверяет только виртуальный баланс и передаёт в выдачу null вместо предмета из руки. Физический ключ работает исключительно при правом клике по блоку, зарегистрированному через /crate admin setblock.

Что делать: Либо ставьте физический блок (тогда ключ-предмет работает как задумано), либо выдавайте виртуальные ключи /crate key give. Если ящик открывается только командой — предметы-ключи для него не выдавайте.

Закрытие окна с анимацией roulette до остановки барабана съедает ключ без награды#

Ящики

Ключ списывается до открытия GUI, а награда выдаётся в конце анимации. У roulette каждый следующий шаг планируется из предыдущего, и при закрытии окна планировщик останавливается — цепочка обрывается, финал не наступает, grantReward не вызывается (в консоли при этом возможна ошибка RejectedExecutionException). У sequential все кадры запланированы заранее, поэтому там награда выдаётся даже при досрочном закрытии, а instant выдаёт её ещё до появления окна.

Что делать: Предупреждайте игроков не закрывать окно до конца прокрутки; для нетерпеливой аудитории ставьте /crate admin setanimation <crate> sequential или instant. Пострадавшему вернуть ключ: /crate key give <player> <crate> 1.

Опечатка в id ящика в votifier.json уносит ключи в никуда#

Ящики

Комментарий в votifier.json обещает, что начисление «молча пропускается» без подходящего ящика, но кода проверки нет: addKeys создаёт запись баланса для любой строки-идентификатора. Ключи для несуществующего vote_crate реально копятся в хранилище, но не показываются ни в /crate keys, ни в плейсхолдере (оба перебирают только объявленные ящики) и никогда не тратятся.

Что делать: Сверяйте ключи секции keys с выводом /crate list — регистр не важен, написание важно. Уже «потерянные» ключи проще перевыдать вручную через /crate key give.

Файл истории открытий растёт бесконечно и никем не читается#

Ящики

Каждое открытие пишет запись в коллекцию crate_history (игрок, ящик, награда, время). Ни одной команды, страницы веб-панели или отчёта, которые бы её читали, в моде нет, а на дефолтном JSON-бэкенде каждая запись перезаписывает весь файл neoessentials/store/crate_history.json — на активном сервере это заметная дисковая нагрузка и постоянно растущий файл.

Что делать: Либо переведите хранилище на SQLite/MySQL, либо периодически удаляйте neoessentials/store/crate_history.json на остановленном сервере — на работу ящиков он не влияет.

Все сообщения системы ящиков выводятся по-английски#

Ящики

В поставляемом ru_ru.json ключей commands.neoessentials.crate.* нет вообще. При старте недостающие ключи домердживаются из en_us, поэтому на сервере с localization.language: ru_ru игроки видят английские тексты («You don't have any keys for ...»), а не непереведённый ключ.

Что делать: Переведите их прямо в своём файле neoessentials/languages/custom/ru_ru.json — после первого старта английские значения уже лежат там, слияние затрагивает только отсутствующие ключи и ваши правки не перетирает.

Мониторинг рапортует «голос отправлен», а на сервере ничего не происходит и в логе пусто#

Votifier

Все отказы приёма голоса логируются через NeoLog.debug(..., LogCategory.VOTIFIER, ...): неверная HMAC-подпись V2, несовпавший challenge, битый 256-байтовый блок V1, любое исключение при разборе соединения. В config.json logging.categories.votifier.debug по умолчанию false, а debug-уровень пишется только в logs/debug.log, не в консоль. В результате самая частая ошибка настройки — токен V2 или публичный ключ V1, скопированные не полностью, — выглядит как полная тишина.

Что делать: Поставьте logging.categories.votifier.debug: true в config.json, выполните /neoessentials reload и смотрите logs/debug.log; там будет «Invalid V2 vote signature» либо «Invalid V2 vote challenge». Успешно принятый голос, наоборот, логируется на уровне info строкой «Received V1/V2 vote from ... for player ...».

Опечатка в sites.*.keys тихо выдаёт ключ от несуществующего кейса#

Votifier

Комментарий в votifier.json обещает, что без подходящего кейса в crates.json ключ будет «silently skipped». На деле VoteRewardManager вызывает CrateKeyManager.addKeys() напрямую и существование кейса не проверяет: баланс ключа записывается в коллекцию crate_keys под указанным id. Игрок увидит ключи в /crate keys, но /crate open <id> ответит «crate not found», и вернуть эти ключи на настоящий кейс автоматически нечем.

Что делать: Сверяйте id в sites.<site>.keys с id кейсов из crates.json символ в символ. После правки конфига проверяйте связку целиком: /votifier testvote <site> <ник> → /crate keys <ник> → /crate open <id>.

/votifier genkeys ничего не генерирует#

Votifier

И название подкоманды, и её описание в реестре команд обещают генерацию ключей. Реализация состоит из одного sendSuccess с текстом-подсказкой: ключи создаются только внутри RSAKeyManager.load(), а он срабатывает при старте сервера и только если на диске нет config/neoessentials/votifier/public.pem и private.pem.

Что делать: Воспринимайте genkeys как справку. Реальная перегенерация: остановить сервер, удалить оба .pem, запустить сервер, забрать новый публичный ключ из консоли и вставить его во все панели мониторингов.

Голоса по V2 перестают приходить после каждого перезапуска сервера#

Votifier

Если v2Token в votifier.json пуст, сервер генерирует случайный UUID и пытается сохранить его обратно в конфиг. Когда запись не удаётся (права на файл, конфиг только для чтения, том смонтирован read-only), в лог уходит ERROR «Failed to save auto-generated Votifier V2 token — it will regenerate every restart until this is fixed», и при следующем старте токен будет уже другой — тот, что вы вставили в панель мониторинга, станет неверным.

Что делать: Найдите этот ERROR в логе, исправьте права на config/neoessentials/votifier.json либо впишите постоянный v2Token руками и перезапустите сервер.

/votifier testvote — не безопасная проверка, а полноценный голос#

Votifier

Подкоманда собирает объект Vote и публикует PlayerVoteEvent напрямую, минуя весь слой протокола. Все подписчики отрабатывают по-настоящему: выполняются команды награды (включая выдачу денег и предметов), начисляются ключи кейсов, статистика игрока растёт, счётчик vote party растёт и может прямо здесь перевалить порог, а всем онлайн-игрокам уходит броадкаст «X проголосовал». Подтверждения и отмены нет.

Что делать: Тестируйте на служебном нике, а не на реальном игроке. Откатывать последствия придётся руками: neoessentials/store/votifier_stats.json и votifier_voteparty.json на остановленном сервере.

Протокол V1 не защищён от повтора#

Votifier

В V2 сервер выдаёт в приветствии одноразовый challenge и отклоняет голос, если присланный challenge не совпал, — это защита от переотправки перехваченного пакета. В V1 такого поля в протоколе нет, и handleV1() ничего подобного не проверяет: один и тот же 256-байтовый RSA-блок можно отправить на порт повторно и получить награду столько раз, сколько захочется.

Что делать: Там, где мониторинг умеет NuVotifier (V2), настраивайте именно его. Порт слушателя (по умолчанию 8192) не выставляйте в интернет шире необходимого — ограничьте доступ фаерволом до адресов мониторингов.

Сообщения Votifier не переведены на русский#

Votifier

В поставляемом файле data/lang/ru_ru.json нет ни одного ключа commands.neoessentials.votifier.* — все 10 строк системы есть только в en_us.json. При localization.language: ru_ru загрузчик докладывает недостающие ключи английским фолбэком, поэтому и игроки, и админы увидят «Vote Party: 3/50», «Claimed 1 pending vote reward(s)!» и остальные сообщения по-английски.

Что делать: Эти строки автоматически попадают в neoessentials/languages/custom/ru_ru.json при слиянии на старте — переведите их прямо там и перезапустите сервер. Не включайте при этом localization.preserveCustomTranslations, пока перевод не дописан, иначе слияние новых ключей отключится.

При LuckPerms/FTB почти все мутации /permissions падают NPE#

Система прав

Защита getManager() == null стоит только в setPrefix/setSuffix. addGroupPermission, setUserGroup, createGroup, temp и context сразу зовут PermissionAPI.getManager().getGroup/getUser. В внешнем режиме менеджер намеренно null — будет unexpected_error с NullPointerException, хотя в лог пишется «internal groups loaded but NOT USED». PermissionStorage.save при этом всё равно no-op, даже если бы NPE не случился.

Что делать: Не используйте /permissions group|user|create|delete при активном адаптере. Нужный узел выдавайте в LuckPerms/FTB Ranks. Префикс тоже задаётся там, не через setprefix.

FakePermissionsEX и pex-bridge — мёртвый код#

Система прав

registerFakePexCommand должен был повесить урезанный /pex group|user add|remove с сообщением simulation_notice и требованием OP 2. injectPermissionCommands — отдельную команду neoessentials-pex-bridge с OP 4. Grep по репозиторию не находит ни одного вызова. Инвентарь генератора помечает /pex как implementedIn FakePermissionsEXCommand.java только потому, что в файле есть Commands.literal("pex").

Что делать: Игнорируйте javadoc/wiki про «fake PEX until the real plugin loads». Рабочий /pex = /permissions.

canModifyPermissions ни на что не подписан#

Система прав

В PermissionManager есть кулдаун 1000 мс на «модификации прав», но canModifyPermissions не вызывается из PermissionsCommand и ниоткуда больше. Спамить group add можно без этой паузы; аудит пишется в permissions_audit.log при permissions.auditLogging: true.

Что делать: Не рассчитывайте на серверный rate-limit команды. Если нужен — ограничивайте правом и OP, не этим методом.

Неизвестный role у /apikey create всё равно создаёт ключ ADMIN#

Веб-панель

parseRole при мусоре вроде admins шлёт sendFailure «expected ADMIN, OPERATOR, MODERATOR, or VIEWER. Defaulting to ADMIN» и возвращает User.Role.ADMIN. Brigadier после этого всё равно вызывает create — ключ появляется, токен печатается. Failure не откатывает ветку.

Что делать: Сразу /apikey list: если роль ADMIN, а вы хотели VIEWER — /apikey revoke <id> и создайте заново. Не считайте красную строку в чате признаком, что ключа нет.

/apikey нельзя выключить секцией commands#

Веб-панель

Соседние dashboard-команды смотрят isCommandEnabled. ApiKeyCommand.register этой проверки не делает: при загруженном моде /apikey в дереве есть всегда, закрыт только узлом neoessentials.dashboard.apikeys (плюс OP-bypass PermissionAPI для игроков и OP 2 для консоли).

Что делать: Уберите узел у не-админов. Выключить команду ключом commands.apikey: false в текущей сборке нельзя.

На старых сборках /dashboard start был мёртвым кодом#

Подключение панели

Класс существовал, в диспетчер не регистрировался — команда была «unknown». В этой ветке /dashboard уже подключён; если на сервере древний jar, status/start будут неизвестны независимо от конфига.

Что делать: Сверьте версию мода. На текущем форке команда должна быть в /help при включённом модуле.

Одиночная кавычка в строке с {0} печатает литерал {0}#

Локализация

Это MessageFormat: ' начинает экранированный кусок и глотает плейсхолдер. В en_us/ru_ru это уже учтено удвоением; свой override легко сломать.

Что делать: Пишите '' вместо '. После правки — /language override reload или /language reload.

{neoessentials_tps} из апстрим-вики в реестре нет#

API и плейсхолдеры

APISystem.md и таблица «Server-wide» обещают {neoessentials_tps} / {tps}. В DefaultPlaceholderExpansion.initializePlaceholders ключа tps нет, ветки switch тоже нет. TPS считает TablistManager.getTps и подставляет только короткий {tps} в шапке таблиста и в chat short-form. /placeholder test {neoessentials_tps} и REST resolve оставят токен нетронутым. То же с {player}: в expansion это name/username/displayname, а {player} режет только таблист.

Что делать: Для PlaceholderAPI/REST используйте {neoessentials_name} и смиритесь, что TPS через этот реестр не отдаётся. В tablist.json пишите {tps}.

Javaddoc /placeholder test [player] не совпадает с деревом#

API и плейсхолдеры

Заголовок класса PlaceholderCommand обещает необязательную цель. Регистрация: test → greedy text → executes. Всё после первого слова уходит в текст, отдельного аргумента player нет. Попытка /placeholder test {neoessentials_name} Steve просто резолвит строку «{neoessentials_name} Steve» от исполнителя.

Что делать: Чужой контекст — только GET /api/placeholders/resolve?player=Steve&text=….

/vault convert молча пропускает офлайн-счета#

API и плейсхолдеры

Это не недописанный TODO в вики: метод сознательно итерирует только онлайн. Админ, сверивший baltop из SQLite с двумя провайдерами, увидит convert_complete по числу онлайна (часто 0–несколько) и решит, что конвертация «сломалась», хотя офлайн просто вне скоупа.

Что делать: Считать команду сессионной сверкой онлайна, не миграцией базы. Полный перенос — экспорт/импорт самой экономики.