feat: pizdec

This commit is contained in:
2026-08-02 22:21:03 +03:00
parent ef128cc08d
commit de96bd314f
80 changed files with 4121 additions and 1431 deletions

4
.gitignore vendored Normal file → Executable file
View File

@@ -1,2 +1,4 @@
/.DS_Store /.DS_Store
/build /build
.idea
bin

View File

@@ -1,2 +1,2 @@
#Fri Jan 16 17:25:37 NOVT 2026 #Fri Jan 09 22:24:47 NOVT 2026
gradle.version=9.0.0 gradle.version=9.0.0

Binary file not shown.

Binary file not shown.

View File

@@ -1 +0,0 @@
A0EDDFD9DCFA42B28E98A596AFD8237AB506F0D6CE2E10D64BEA80C21CB3949A

0
.idea/.gitignore generated vendored Normal file → Executable file
View File

0
.idea/compiler.xml generated Normal file → Executable file
View File

0
.idea/easycode.ignore generated Normal file → Executable file
View File

0
.idea/gradle.xml generated Normal file → Executable file
View File

0
.idea/misc.xml generated Normal file → Executable file
View File

0
.idea/modules.xml generated Normal file → Executable file
View File

1
.idea/modules/AdminMode.main.iml generated Normal file → Executable file
View File

@@ -4,7 +4,6 @@
<facet type="minecraft" name="Minecraft"> <facet type="minecraft" name="Minecraft">
<configuration> <configuration>
<autoDetectTypes> <autoDetectTypes>
<platformType>PAPER</platformType>
<platformType>ADVENTURE</platformType> <platformType>ADVENTURE</platformType>
</autoDetectTypes> </autoDetectTypes>
<projectReimportVersion>1</projectReimportVersion> <projectReimportVersion>1</projectReimportVersion>

6
.idea/vcs.xml generated Executable file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

234
LUCKPERMS_SETUP.md Normal file
View File

@@ -0,0 +1,234 @@
# Настройка LuckPerms для AdminMode
Эта инструкция рассчитана на LuckPerms 5.5.x и стандартный `config.yml`, который поставляется с AdminMode.
## Как AdminMode использует LuckPerms
LuckPerms является обязательной зависимостью: без него Paper не загрузит AdminMode.
Плагин использует две разновидности групп:
- `moder` — постоянная группа обычного модератора. Она разрешает включать AdminMode.
- `admin_moder` — временная группа. AdminMode сам добавляет её при входе в режим и удаляет при выходе.
- `chief_moder` — рекомендуемая постоянная группа главного модератора. Главный модератор определяется правом `adminmode.chief`, а не названием группы.
Не назначайте `admin_moder` игрокам вручную как постоянную группу. Также не добавляйте постоянные группы в `admin_mode.luckperms.groups`: все перечисленные там группы плагин удаляет у игрока при выходе из AdminMode.
Проверяйте настройку на игроке без OP. Права `adminmode.use`, `adminmode.admin`, `adminmode.chief` и `adminmode.reload` имеют значение `default: op`, поэтому оператор сервера получит их даже без настроенного LuckPerms.
Права самого AdminMode:
- `adminmode.use` — вход в режим и открытие панели.
- `adminmode.admin` — сильные действия, принудительное отключение режима и `/admfix`.
- `adminmode.chief` — управление доступом других модераторов и Creative в режиме, если он разрешён конфигом.
- `adminmode.reload` — перезагрузка конфига AdminMode.
- `adminmode.special.belzebool` — специальная способность, которая дополнительно привязана в коде к нику `BelzeBool`; обычным группам её выдавать не нужно.
## 1. Установка LuckPerms
1. Скачайте Bukkit-версию LuckPerms 5.5.x.
2. Поместите JAR в папку `plugins/` рядом с AdminMode.
3. Полностью перезапустите сервер.
4. Проверьте из консоли:
```text
lp info
```
Команды ниже можно выполнять в консоли без `/` или в игре от имени OP с `/`.
Если удобнее работать через браузер, команда `lp editor` открывает официальный веб-редактор LuckPerms. После сохранения изменений редактор выдаст команду, которую нужно выполнить на сервере для применения данных. Команды из следующих разделов дают тот же результат без веб-редактора.
## 2. Создание групп
Выполните:
```text
lp creategroup moder
lp creategroup admin_moder
lp creategroup chief_moder
```
Если LuckPerms сообщает, что группа уже существует, это не ошибка — переходите дальше.
## 3. Права обычного модератора
Выдайте постоянной группе `moder` право входить в режим:
```text
lp group moder permission set adminmode.use true
```
Обычный модератор сможет использовать `/adminmode`, `/adm` и `/am`, но не сможет ограничивать других модераторов или выполнять административные команды повышенного риска.
## 4. Права, доступные только внутри AdminMode
Группа `admin_moder` автоматически появляется у игрока после включения режима. Рекомендуемый минимальный набор:
```text
lp group admin_moder permission set adminmode.admin true
```
`adminmode.admin` разрешает сильные действия AdminMode, включая принудительное отключение режима другого игрока и `/admfix`.
Если сильные действия обычным модераторам не нужны, не выдавайте этот узел. Вход и выход из режима продолжат работать по `adminmode.use`.
Права на кнопки наказаний выдаются отдельно. Пример полного набора для временной группы:
```text
lp group admin_moder permission set adminmode.punishment.warn true
lp group admin_moder permission set adminmode.punishment.unwarn true
lp group admin_moder permission set adminmode.punishment.mute true
lp group admin_moder permission set adminmode.punishment.unmute true
lp group admin_moder permission set adminmode.punishment.kick true
lp group admin_moder permission set adminmode.punishment.ban true
lp group admin_moder permission set adminmode.punishment.unban true
```
Выдавайте только нужные узлы. `adminmode.punishment.ban` и `adminmode.punishment.unban` позволяют менять доступ игроков к серверу и рекомендуются только доверенным ролям. AdminMode проверяет эти права сам, в том числе когда внешняя команда выполняется от консоли.
### CoreProtect
Если установлен CoreProtect и модераторы должны пользоваться его инструментами из панели AdminMode, добавьте необходимые права в временную группу:
```text
lp group admin_moder permission set coreprotect.inspect true
lp group admin_moder permission set coreprotect.lookup true
```
Rollback и restore меняют мир, поэтому выдавайте их только доверенным сотрудникам:
```text
lp group admin_moder permission set coreprotect.rollback true
lp group admin_moder permission set coreprotect.restore true
```
Не используйте `coreprotect.*`, если модератору не нужен полный доступ к CoreProtect.
### InvSee и команды наказаний
AdminMode вызывает команду `invsee` от имени модератора, поэтому право на неё зависит от установленного у вас плагина инвентарей. Добавьте соответствующий permission этого плагина в `admin_moder`.
По умолчанию команды warn, mute, ban и kick выполняются от консоли:
```yaml
moderation_actions:
dispatch_as_console: true
```
При такой настройке отдельные права на команды наказаний модератору не нужны. Если установить `dispatch_as_console: false`, эти команды начнут выполняться от имени игрока, и их permissions также потребуется выдать группе `admin_moder`.
Чтобы узнать неизвестное право конкретной команды, используйте LuckPerms verbose:
```text
lp verbose command <ник_модератора> "invsee <ник_цели>"
```
## 5. Главный модератор
Пусть `chief_moder` наследует обычную группу и получает специальные права:
```text
lp group chief_moder parent add moder
lp group chief_moder permission set adminmode.chief true
lp group chief_moder permission set adminmode.reload true
```
- `adminmode.chief` разрешает ограничивать и восстанавливать доступ других модераторов, смотреть статус ограничений и использовать Creative внутри AdminMode, если это разрешено конфигом.
- `adminmode.reload` разрешает `/adminmode reload`.
Не добавляйте `admin_moder` как родителя `chief_moder`, иначе временные права будут действовать у главного модератора постоянно.
## 6. Назначение игроков
Обычный модератор:
```text
lp user <ник> parent add moder
```
Главный модератор:
```text
lp user <ник> parent add chief_moder
```
Используется `parent add`, а не `parent set`, чтобы случайно не удалить другие существующие группы игрока.
Примеры:
```text
lp user Steve parent add moder
lp user Alex parent add chief_moder
```
## 7. Конфигурация AdminMode
Проверьте файл `plugins/AdminMode/config.yml`:
```yaml
admin_mode:
luckperms:
enabled: true
groups:
- "admin_moder"
remove_on_disable: true
groups:
base: "moder"
admin: "admin_moder"
chief: "chief_moder"
```
Рекомендации:
- Оставьте `enabled: true`.
- Оставьте `remove_on_disable: true`, чтобы временные группы снимались при выходе игрока из режима.
- Значение `groups.admin` должно совпадать с основной временной группой.
- В `admin_mode.luckperms.groups` перечисляйте только группы, которые должны выдаваться временно.
- `groups.base` не назначает игроку группу автоматически.
- Название `groups.chief` само по себе не даёт полномочий: обязательно право `adminmode.chief`.
После изменения конфига выполните:
```text
adminmode reload
```
или перезапустите сервер.
## 8. Проверка настройки
Проверьте постоянную группу и право игрока:
```text
lp user <ник> info
lp user <ник> permission check adminmode.use
```
Затем зайдите на сервер этим игроком:
1. Выполните `/adminmode on`.
2. Проверьте группы командой `lp user <ник> parent info` — должна появиться `admin_moder`.
3. Выполните `/adminmode off`.
4. Снова проверьте группы — `admin_moder` должна исчезнуть, а `moder` или `chief_moder` остаться.
## 9. Если временная группа не снялась
Удалите её вручную:
```text
lp user <ник> parent remove admin_moder
```
После аварийной остановки сервера AdminMode сохраняет активную сессию и продолжает её после следующего входа игрока. Обычно достаточно войти на сервер и корректно выполнить `/adminmode off`.
Если команда недоступна, проверьте:
```text
lp user <ник> permission check adminmode.use
lp user <ник> permission check adminmode.admin
lp user <ник> permission check adminmode.chief
```
Официальная документация: [LuckPerms Usage](https://luckperms.net/wiki/Usage), [Command Usage](https://luckperms.net/wiki/Command-Usage), [Verbose](https://luckperms.net/wiki/Verbose).

View File

@@ -0,0 +1,149 @@
# Контракт moderation-плагина для AdminMode
Этот документ описывает минимальные команды и поведение отдельного moderation-плагина, необходимого для работы кнопок Mute, Unmute, Kick и Ban в AdminMode 1.2 на Paper 26.1.2.
AdminMode выполняет команды от консоли и передаёт реального модератора отдельным аргументом `actor:<ник>`. Активные баны должны храниться только в стандартном Paper ProfileBanList, иначе BanList и кнопка разбана в AdminMode не увидят их.
## Формат сроков
Плагин обязан принимать как минимум:
- `1h` — один час;
- `1d` — один день;
- `7d` — семь дней;
- `30d` — тридцать дней;
- `permanent` — бессрочно.
Рекомендуется дополнительно принимать любое положительное значение в формате `<число><s|m|h|d|w|y>`. Нулевые, отрицательные, неизвестные и переполненные значения должны отклоняться без применения наказания.
## Автор наказания
При вызове из AdminMode команды выглядят так:
```text
mute Steve 1h actor:Moderator флуд
ban Steve 7d actor:Moderator читы
kick Steve actor:Moderator нарушение правил
```
Правила обработки `actor:`:
- учитывать аргумент только когда `CommandSender` является консолью;
- для команды игрока всегда записывать автором самого игрока;
- не разрешать игроку подменять автора через `actor:`;
- убрать `actor:<ник>` из сохраняемой причины.
## Команды
### Mute
```text
/mute <player> <duration> [actor:<name>] <reason...>
/unmute <player> [actor:<name>] [reason...]
```
Из AdminMode команда вызывается по полному Bukkit namespace `moderationcommands:unmute`, чтобы её не перехватывали FlectonePulse и другие плагины с собственной командой `/unmute`. Namespace предполагает, что имя плагина в его `plugin.yml``ModerationCommands`.
Обязательное поведение:
- разрешать мутить онлайн- и офлайн-игрока по UUID;
- хранить UUID, последнее имя, автора, причину, время выдачи и `expiresAt` между перезапусками;
- автоматически считать временный мут истёкшим;
- отменять сообщения через Paper `AsyncChatEvent`, пока мут активен;
- блокировать настраиваемый список чат-команд, минимум `/msg`, `/tell`, `/w`, `/whisper`, `/reply`, `/r` и `/me`;
- при попытке писать показывать причину и оставшийся срок;
- `/unmute` должен немедленно снимать активный мут;
- повторный mute обновляет существующее активное наказание, а не создаёт конфликтующие записи.
Permissions:
```text
moderation.command.mute
moderation.command.unmute
```
AdminMode вызывает `/mute` и `/unmute` из карточки игрока. Кнопка Unmute доступна по отдельному праву и передаёт причину, если модератор её указал.
### Ban
```text
/ban <player> <duration> [actor:<name>] <reason...>
/unban <player> [actor:<name>] [reason...]
```
Обязательное поведение:
- разрешать банить онлайн- и офлайн-профили;
- разрешать профиль до UUID, не создавать отдельные name-only баны;
- получать `ProfileBanList` через `Bukkit.getBanList(BanListType.PROFILE)`;
- добавлять бан через typed ProfileBanList с причиной, `Instant` окончания или `null` для `permanent`, а также фактическим автором;
- после добавления бана отключать игрока онлайн с сообщением о причине и сроке;
- `/unban` удаляет тот же профиль из Paper ProfileBanList;
- не использовать собственный кэш или базу как источник активного бана: AdminMode снимает бан напрямую через `BanEntry.remove()`.
Permissions:
```text
moderation.command.ban
moderation.command.unban
```
Если нужна история, её можно вести отдельным append-only журналом, но актуальный статус всегда нужно проверять в Paper ProfileBanList.
### Kick
```text
/kick <player> [actor:<name>] <reason...>
```
Обязательное поведение:
- команда работает только для игрока онлайн;
- отключает игрока Adventure-компонентом с причиной и именем модератора;
- не создаёт постоянное наказание;
- возвращает понятную ошибку, если игрок уже вышел.
Permission:
```text
moderation.command.kick
```
## Существующая команда Warn
Warn остаётся в ядре сервера и должен принимать:
```text
/warn <player> <duration> <reason...>
```
Минимально поддерживаемые сроки совпадают со списком выше, включая `permanent`. Источник истории, срока и статуса варнов — AdminMode (`plugins/AdminMode/warn-history.json`), поэтому отдельная команда `/unwarn` для интеграции не требуется.
## Возвращаемый результат команд
Все обработчики Bukkit-команд должны:
- возвращать `true`, только если команда распознана и наказание принято;
- возвращать `false` при неверном синтаксисе, неизвестном сроке или невозможности применить действие;
- не изменять данные при ошибке валидации;
- корректно работать от консоли без OP/LuckPerms-проверки;
- проверять соответствующий permission при вызове игроком.
AdminMode создаёт локальную запись warn только после успешного `dispatchCommand`. Для Mute/Ban/Kick состояние и сообщения об ошибках полностью отвечает moderation-плагин.
## Настройка AdminMode
Стандартные шаблоны `plugins/AdminMode/config.yml`:
```yaml
moderation_actions:
dispatch_as_console: true
commands:
warn: "warn {target} {duration} {reason}"
mute: "mute {target} {duration} actor:{moderator} {reason}"
unmute: "moderationcommands:unmute {target} actor:{moderator} {reason}"
ban: "ban {target} {duration} actor:{moderator} {reason}"
kick: "kick {target} actor:{moderator} {reason}"
```
После установки moderation-плагина выполните `/adminmode reload` или перезапустите сервер, затем проверьте каждую команду сначала вручную из консоли и только потом через Dialog.

68
README.md Normal file
View File

@@ -0,0 +1,68 @@
# AdminMode
Плагин режима адмистратора для Paper 26.1.2.
## Описание
AdminMode - это плагин, который позволяет модераторам и администраторам входить в специальный режим с сохранением их текущего состояния. При активации режима администратора:
- Сохраняется инвентарь, броня, предметы
- Сохраняется позиция игрока
- Сохраняется здоровье, голод, опыт
- Сохраняются эффекты зелий
- Выдаются временные права через LuckPerms (или OP)
При выходе из режима все восстанавливается обратно.
## Возможности
- **Сохранение состояния в JSON** - все данные сохраняются на диск
- **Интеграция с LuckPerms** - автоматическая выдача/отзыв групп
- **Гибкие ограничения** - настройка что можно/нельзя делать в режиме
- **Защита от читерства** - предотвращение переноса предметов
## Команды
- `/adminmode` (алиасы: `/adm`, `/am`) - включить/выключить режим администратора
## Права
- `adminmode.use` - разрешает использовать режим администратора (по умолчанию: OP)
- `adminmode.punishment.warn` / `unwarn` - выдача и снятие предупреждений
- `adminmode.punishment.mute` / `unmute` - выдача и снятие мута
- `adminmode.punishment.kick` - отключение игрока
- `adminmode.punishment.ban` / `unban` - выдача и снятие бана
## Установка
1. Установите Paper 26.1.2 build 74 или новее и Java 25.
2. Установите LuckPerms 5.5.x.
3. Скопируйте `AdminMode-1.2+26.1.2.jar` в папку `plugins/`.
4. Перезапустите сервер и настройте `config.yml`.
## Зависимости
- **Обязательные**: Paper 26.1.2, Java 25, LuckPerms 5.5.x
- **Опциональные**: CoreProtect
Подробная пошаговая настройка групп и прав: [LUCKPERMS_SETUP.md](LUCKPERMS_SETUP.md).
Контракт команд для отдельного плагина наказаний: [MODERATION_PLUGIN_CONTRACT.md](MODERATION_PLUGIN_CONTRACT.md).
Команда снятия мута вызывается как `moderationcommands:unmute`, чтобы исключить конфликт с `/unmute` из FlectonePulse.
## Сборка
```bash
./gradlew clean build
```
Готовый JAR: `build/libs/AdminMode-1.2+26.1.2.jar`.
## Автор
BelzeBool
## Лицензия
Эксклюзивно для сервера Шлакоблок

56
bin/main/config.yml Normal file → Executable file
View File

@@ -13,15 +13,23 @@ admin_mode:
# Группы которые выдаются при активации режима # Группы которые выдаются при активации режима
# Можно указать несколько групп через запятую # Можно указать несколько групп через запятую
groups: groups:
- "moder_adm" - "soft_moder_adm"
profile_groups:
soft_moder:
- "soft_moder_adm"
hard_moder:
- "hard_moder_adm"
# Удалять ли группы при деактивации (если false - группы останутся) # Удалять ли группы при деактивации (если false - группы останутся)
remove_on_disable: true remove_on_disable: true
dialogs:
enabled: true
# Настройки игрового режима # Настройки игрового режима
gamemode: gamemode:
# Принудительно ставить спектатор (если false - креатив) # Принудительно ставить спектатор (если false - survival)
force_spectator: true force_spectator: false
# Мобы игнорируют игрока в админ моде # Мобы игнорируют игрока в админ моде
mobs_ignore_player: true mobs_ignore_player: true
@@ -41,14 +49,15 @@ admin_mode:
# Разрешенные режимы игры для переключения в админ моде # Разрешенные режимы игры для переключения в админ моде
# Если список пустой - можно переключаться на любые режимы # Если список пустой - можно переключаться на любые режимы
# Доступные: SURVIVAL, CREATIVE, ADVENTURE, SPECTATOR # Доступные: SURVIVAL, CREATIVE, ADVENTURE, SPECTATOR
# ВАЖНО: CREATIVE убран - модеры не должны иметь доступ к креативу!
allowed_gamemodes: allowed_gamemodes:
- "CREATIVE" - "SURVIVAL"
- "SPECTATOR" - "SPECTATOR"
# Ограничения в режиме администратора # Ограничения в режиме администратора
restrictions: restrictions:
# Запретить выбрасывание предметов # Запретить выбрасывание предметов
prevent_item_drop: true prevent_item_drop: false
# Запретить поднятие предметов # Запретить поднятие предметов
prevent_item_pickup: false prevent_item_pickup: false
@@ -57,7 +66,7 @@ admin_mode:
prevent_container_transfer: true prevent_container_transfer: true
# Запретить использование предметов (кроме открытия контейнеров) # Запретить использование предметов (кроме открытия контейнеров)
prevent_item_use: true prevent_item_use: false
# Разрешить управление своим инвентарем # Разрешить управление своим инвентарем
allow_own_inventory: true allow_own_inventory: true
@@ -89,13 +98,9 @@ admin_mode:
# Если список пустой - можно ставить любые блоки # Если список пустой - можно ставить любые блоки
# Если список заполнен - можно ставить только эти блоки # Если список заполнен - можно ставить только эти блоки
allowed_place_blocks: allowed_place_blocks:
- "BARRIER"
- "LIGHT"
- "STRUCTURE_VOID"
- "STRUCTURE_BLOCK"
# Выдавать пресет блоков при входе в админ мод # Выдавать пресет блоков при входе в админ мод
give_preset_blocks: true give_preset_blocks: false
# Пресет блоков (формат: МАТЕРИАЛ:КОЛИЧЕСТВО) # Пресет блоков (формат: МАТЕРИАЛ:КОЛИЧЕСТВО)
preset_blocks: preset_blocks:
@@ -117,3 +122,30 @@ admin_mode:
item_use_denied: "✖ В режиме администратора нельзя использовать предметы!" item_use_denied: "✖ В режиме администратора нельзя использовать предметы!"
block_place_denied: "✖ Вы можете ставить только разрешенные блоки!" block_place_denied: "✖ Вы можете ставить только разрешенные блоки!"
reloaded: "✓ Конфигурация AdminMode перезагружена!" reloaded: "✓ Конфигурация AdminMode перезагружена!"
groups:
base: "moder"
soft: "soft_moder_adm"
hard: "hard_moder_adm"
items:
soft_panel_material: COMPASS
hard_panel_material: NETHER_STAR
cp_inspector_material: STICK
coreprotect:
enabled: true
default_radius: 10
default_time: "24h"
messages:
prefix: "§8[§bAdminMode§8] "
enter_soft: "§aВошёл в режим §bSOFT MODER"
enter_hard: "§aВошёл в режим §cHARD MODER"
exit: "§7Режим деактивирован. Состояние восстановлено."
cp_unavailable: "§cCoreProtect не найден. Hard режим работает без CP."
session_recovered: "§eВаша активная сессия модерации была восстановлена и корректно завершена."
logging:
enabled: true
log_file: "logs/sessions.log"

17
bin/main/plugin.yml Normal file → Executable file
View File

@@ -1,21 +1,34 @@
name: AdminMode name: AdminMode
version: '1.1.0' version: '1.21.11'
main: org.blz.adminmode.AdminMode main: org.blz.adminmode.AdminMode
api-version: '1.21' api-version: '1.21'
author: BelzeBool, Kilka_v_HJIebe author: BelzeBool, Kilka_v_HJIebe
description: Плагин режима администратора с сохранением состояния игрока description: Плагин режима администратора с сохранением состояния игрока
softdepend: [LuckPerms] depend: [LuckPerms]
softdepend: [CoreProtect]
commands: commands:
adminmode: adminmode:
description: Режим администратора с сохранением состояния description: Режим администратора с сохранением состояния
usage: /<command> usage: /<command>
aliases: [adm, am] aliases: [adm, am]
admfix:
description: Отключить все способности у игрока (полет, бессмертие)
usage: /<command> <игрок>
permissions: permissions:
adminmode.use: adminmode.use:
description: Разрешает использовать режим администратора description: Разрешает использовать режим администратора
default: op default: op
adminmode.profile.soft:
description: Разрешает использовать профиль soft_moder
default: op
adminmode.profile.hard:
description: Разрешает использовать профиль hard_moder
default: op
adminmode.reload: adminmode.reload:
description: Разрешает перезагружать конфигурацию AdminMode description: Разрешает перезагружать конфигурацию AdminMode
default: op default: op
adminmode.admin:
description: Разрешает использовать сильные действия и админ команды (disable, admfix)
default: op

13
build.gradle Normal file → Executable file
View File

@@ -3,7 +3,7 @@ plugins {
} }
group = 'org.blz' group = 'org.blz'
version = '1.21.11' version = '1.3+26.1.2'
repositories { repositories {
mavenCentral() mavenCentral()
@@ -14,23 +14,24 @@ repositories {
} }
dependencies { dependencies {
compileOnly 'io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT' compileOnly 'io.papermc.paper:paper-api:26.1.2.build.74-stable'
compileOnly 'net.luckperms:api:5.4' compileOnly 'net.luckperms:api:5.5'
compileOnly 'com.google.code.gson:gson:2.10.1' compileOnly 'com.google.code.gson:gson:2.13.2'
} }
java { java {
toolchain.languageVersion.set(JavaLanguageVersion.of(21)) toolchain.languageVersion.set(JavaLanguageVersion.of(25))
} }
tasks.withType(JavaCompile) { tasks.withType(JavaCompile) {
options.encoding = 'UTF-8' options.encoding = 'UTF-8'
options.compilerArgs.addAll(['-Xlint:deprecation', '-Xlint:unchecked'])
} }
processResources { processResources {
def props = [version: version] def props = [version: version]
inputs.properties props inputs.properties props
filteringCharset 'UTF-8' filteringCharset = 'UTF-8'
filesMatching('plugin.yml') { filesMatching('plugin.yml') {
expand props expand props
} }

0
gradle/wrapper/gradle-wrapper.jar vendored Normal file → Executable file
View File

0
gradle/wrapper/gradle-wrapper.properties vendored Normal file → Executable file
View File

188
gradlew.bat vendored Normal file → Executable file
View File

@@ -1,94 +1,94 @@
@rem @rem
@rem Copyright 2015 the original author or authors. @rem Copyright 2015 the original author or authors.
@rem @rem
@rem Licensed under the Apache License, Version 2.0 (the "License"); @rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License. @rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at @rem You may obtain a copy of the License at
@rem @rem
@rem https://www.apache.org/licenses/LICENSE-2.0 @rem https://www.apache.org/licenses/LICENSE-2.0
@rem @rem
@rem Unless required by applicable law or agreed to in writing, software @rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS, @rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0 @rem SPDX-License-Identifier: Apache-2.0
@rem @rem
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@rem @rem
@rem ########################################################################## @rem ##########################################################################
@rem Set local scope for the variables with windows NT shell @rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused @rem This is normally unused
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter. @rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe @rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2 echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2 echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2 echo location of your Java installation. 1>&2
goto fail goto fail
:findJavaFromJavaHome :findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. 1>&2 echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2 echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2 echo location of your Java installation. 1>&2
goto fail goto fail
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH= set CLASSPATH=
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd if %ERRORLEVEL% equ 0 goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL% set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1 if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE% exit /b %EXIT_CODE%
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal
:omega :omega

BIN
src/.DS_Store vendored Normal file

Binary file not shown.

BIN
src/main/.DS_Store vendored Normal file

Binary file not shown.

BIN
src/main/java/.DS_Store vendored Normal file

Binary file not shown.

BIN
src/main/java/org/.DS_Store vendored Normal file

Binary file not shown.

BIN
src/main/java/org/blz/.DS_Store vendored Normal file

Binary file not shown.

34
src/main/java/org/blz/adminmode/AdminMode.java Normal file → Executable file
View File

@@ -3,6 +3,8 @@ package org.blz.adminmode;
import org.blz.adminmode.commands.AdminModeCommand; import org.blz.adminmode.commands.AdminModeCommand;
import org.blz.adminmode.config.ConfigManager; import org.blz.adminmode.config.ConfigManager;
import org.blz.adminmode.dialog.AdminDialogService; import org.blz.adminmode.dialog.AdminDialogService;
import org.blz.adminmode.dialog.DialogPreferenceStore;
import org.blz.adminmode.dialog.MenuEmojiPreferenceStore;
import org.blz.adminmode.integrations.CoreProtectIntegration; import org.blz.adminmode.integrations.CoreProtectIntegration;
import org.blz.adminmode.integrations.LuckPermsIntegration; import org.blz.adminmode.integrations.LuckPermsIntegration;
import org.blz.adminmode.listeners.AdminModeListener; import org.blz.adminmode.listeners.AdminModeListener;
@@ -10,8 +12,12 @@ import org.blz.adminmode.listeners.GameModeChangeListener;
import org.blz.adminmode.listeners.MobTargetListener; import org.blz.adminmode.listeners.MobTargetListener;
import org.blz.adminmode.listeners.PlayerDeathListener; import org.blz.adminmode.listeners.PlayerDeathListener;
import org.blz.adminmode.listeners.PlayerSessionListener; import org.blz.adminmode.listeners.PlayerSessionListener;
import org.blz.adminmode.moderation.ModeratorAccessManager;
import org.blz.adminmode.moderation.ModeManager; import org.blz.adminmode.moderation.ModeManager;
import org.blz.adminmode.moderation.ModeSessionLogger; import org.blz.adminmode.moderation.ModeSessionLogger;
import org.blz.adminmode.moderation.ModeratorPreferencesStore;
import org.blz.adminmode.moderation.ModeratorWarningStore;
import org.blz.adminmode.moderation.PlayerFreezeManager;
import org.blz.adminmode.session.SessionStorage; import org.blz.adminmode.session.SessionStorage;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
@@ -22,6 +28,9 @@ public class AdminMode extends JavaPlugin {
private ModeManager modeManager; private ModeManager modeManager;
private AdminModeCommand adminModeCommand; private AdminModeCommand adminModeCommand;
private AdminDialogService dialogService; private AdminDialogService dialogService;
private org.blz.adminmode.managers.BelzeBoolManager belzeBoolManager;
private ModeratorAccessManager moderatorAccessManager;
private PlayerFreezeManager freezeManager;
@Override @Override
public void onEnable() { public void onEnable() {
@@ -30,15 +39,24 @@ public class AdminMode extends JavaPlugin {
configManager = new ConfigManager(this); configManager = new ConfigManager(this);
SessionStorage sessionStorage = new SessionStorage(this); SessionStorage sessionStorage = new SessionStorage(this);
moderatorAccessManager = new ModeratorAccessManager(this);
ModeratorPreferencesStore preferencesStore = new ModeratorPreferencesStore(this);
ModeratorWarningStore warningStore = new ModeratorWarningStore(this);
LuckPermsIntegration luckPermsIntegration = new LuckPermsIntegration(this, configManager); LuckPermsIntegration luckPermsIntegration = new LuckPermsIntegration(this, configManager);
CoreProtectIntegration coreProtectIntegration = new CoreProtectIntegration(this, configManager); CoreProtectIntegration coreProtectIntegration = new CoreProtectIntegration(this, configManager);
ModeSessionLogger sessionLogger = new ModeSessionLogger(this, configManager); ModeSessionLogger sessionLogger = new ModeSessionLogger(this, configManager);
modeManager = new ModeManager(this, configManager, luckPermsIntegration, sessionStorage, sessionLogger); modeManager = new ModeManager(this, configManager, luckPermsIntegration, sessionStorage, sessionLogger, preferencesStore);
adminModeCommand = new AdminModeCommand(configManager, modeManager, coreProtectIntegration); adminModeCommand = new AdminModeCommand(configManager, modeManager, coreProtectIntegration, moderatorAccessManager);
dialogService = new AdminDialogService(this, configManager, adminModeCommand); DialogPreferenceStore dialogPreferenceStore = new DialogPreferenceStore(this);
MenuEmojiPreferenceStore emojiPreferenceStore = new MenuEmojiPreferenceStore(this);
dialogService = new AdminDialogService(this, configManager, adminModeCommand, dialogPreferenceStore, emojiPreferenceStore, preferencesStore, warningStore);
adminModeCommand.setDialogService(dialogService); adminModeCommand.setDialogService(dialogService);
adminModeCommand.setDialogPreferenceStore(dialogPreferenceStore);
getCommand("adminmode").setExecutor(adminModeCommand); getCommand("adminmode").setExecutor(adminModeCommand);
getCommand("admfix").setExecutor(new org.blz.adminmode.commands.AdminFixCommand(adminModeCommand)); getCommand("adminmode").setTabCompleter(adminModeCommand);
org.blz.adminmode.commands.AdminFixCommand adminFixCommand = new org.blz.adminmode.commands.AdminFixCommand(adminModeCommand);
getCommand("admfix").setExecutor(adminFixCommand);
getCommand("admfix").setTabCompleter(adminFixCommand);
getServer().getPluginManager().registerEvents(new AdminModeListener(adminModeCommand, configManager, modeManager, dialogService), getServer().getPluginManager().registerEvents(new AdminModeListener(adminModeCommand, configManager, modeManager, dialogService),
this); this);
@@ -47,10 +65,13 @@ public class AdminMode extends JavaPlugin {
this); this);
getServer().getPluginManager().registerEvents(new PlayerSessionListener(adminModeCommand, modeManager), this); getServer().getPluginManager().registerEvents(new PlayerSessionListener(adminModeCommand, modeManager), this);
getServer().getPluginManager().registerEvents(new PlayerDeathListener(modeManager), this); getServer().getPluginManager().registerEvents(new PlayerDeathListener(modeManager), this);
freezeManager = new PlayerFreezeManager(this);
adminModeCommand.setFreezeManager(freezeManager);
getServer().getPluginManager().registerEvents(freezeManager, this);
for (org.bukkit.entity.Player onlinePlayer : getServer().getOnlinePlayers()) { for (org.bukkit.entity.Player onlinePlayer : getServer().getOnlinePlayers()) {
if (modeManager.hasSession(onlinePlayer.getUniqueId())) { if (modeManager.hasSession(onlinePlayer.getUniqueId())) {
getServer().getScheduler().runTaskLater(this, () -> adminModeCommand.recoverSession(onlinePlayer), 1L); getServer().getScheduler().runTaskLater(this, () -> adminModeCommand.resumeMode(onlinePlayer), 1L);
} }
} }
@@ -58,7 +79,8 @@ public class AdminMode extends JavaPlugin {
new org.blz.adminmode.utils.WorldLoader(this, "pocket_dimension").loadWorld(); new org.blz.adminmode.utils.WorldLoader(this, "pocket_dimension").loadWorld();
// Register Abilities // Register Abilities
new org.blz.adminmode.managers.BelzeBoolManager(this); belzeBoolManager = new org.blz.adminmode.managers.BelzeBoolManager(this);
adminModeCommand.setBelzeBoolManager(belzeBoolManager);
getLogger().info("AdminMode успешно включен!"); getLogger().info("AdminMode успешно включен!");
} }

View File

@@ -1,13 +1,19 @@
package org.blz.adminmode.commands; package org.blz.adminmode.commands;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
public class AdminFixCommand implements CommandExecutor { import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class AdminFixCommand implements CommandExecutor, TabCompleter {
private final AdminModeCommand adminModeCommand; private final AdminModeCommand adminModeCommand;
@@ -18,26 +24,45 @@ public class AdminFixCommand implements CommandExecutor {
@Override @Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!sender.hasPermission("adminmode.admin")) { if (!sender.hasPermission("adminmode.admin")) {
sender.sendMessage(ChatColor.RED + "У вас нет прав для использования этой команды!"); sender.sendMessage(Component.text("У вас нет прав для использования этой команды!", NamedTextColor.RED));
return true; return true;
} }
if (args.length < 1) { if (args.length < 1) {
sender.sendMessage(ChatColor.RED + "Использование: /admfix <игрок>"); sender.sendMessage(Component.text("Использование: /admfix <игрок>", NamedTextColor.RED));
return true; return true;
} }
Player target = Bukkit.getPlayer(args[0]); Player target = Bukkit.getPlayer(args[0]);
if (target == null) { if (target == null) {
sender.sendMessage(ChatColor.RED + "Игрок не найден!"); sender.sendMessage(Component.text("Игрок не найден!", NamedTextColor.RED));
return true; return true;
} }
adminModeCommand.forceDisableAbilities(target); if (!adminModeCommand.forceDisableAbilities(target)) {
sender.sendMessage(Component.text("У игрока нет активных состояний AdminMode для сброса.", NamedTextColor.YELLOW));
return true;
}
sender.sendMessage(ChatColor.GREEN + "Все способности отключены для игрока " + target.getName()); sender.sendMessage(Component.text("Все способности отключены для игрока " + target.getName(), NamedTextColor.GREEN));
target.sendMessage(ChatColor.YELLOW + "⚠ Ваши способности были сброшены администратором"); target.sendMessage(Component.text("⚠ Ваши способности были сброшены администратором", NamedTextColor.YELLOW));
return true; return true;
} }
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
if (args.length != 1) {
return Collections.emptyList();
}
String prefix = args[0].toLowerCase();
List<String> names = new ArrayList<>();
for (Player player : Bukkit.getOnlinePlayers()) {
if (prefix.isEmpty() || player.getName().toLowerCase().startsWith(prefix)) {
names.add(player.getName());
}
}
names.sort(String.CASE_INSENSITIVE_ORDER);
return names;
}
} }

View File

@@ -5,38 +5,64 @@ import java.util.UUID;
import org.blz.adminmode.config.ConfigManager; import org.blz.adminmode.config.ConfigManager;
import org.blz.adminmode.dialog.AdminDialogService; import org.blz.adminmode.dialog.AdminDialogService;
import org.blz.adminmode.dialog.DialogPreferenceStore;
import org.blz.adminmode.integrations.CoreProtectIntegration; import org.blz.adminmode.integrations.CoreProtectIntegration;
import org.blz.adminmode.managers.BelzeBoolManager;
import org.blz.adminmode.moderation.ModeratorAccessManager;
import org.blz.adminmode.moderation.ModeManager; import org.blz.adminmode.moderation.ModeManager;
import org.blz.adminmode.moderation.ModeratorProfile; import org.blz.adminmode.moderation.PlayerFreezeManager;
import org.blz.adminmode.moderation.PlayerListAction; import org.blz.adminmode.moderation.PlayerListAction;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
public class AdminModeCommand implements CommandExecutor { import java.util.ArrayList;
import java.util.Collections;
public class AdminModeCommand implements CommandExecutor, TabCompleter {
private final ConfigManager configManager; private final ConfigManager configManager;
private final ModeManager modeManager; private final ModeManager modeManager;
private final CoreProtectIntegration coreProtectIntegration; private final CoreProtectIntegration coreProtectIntegration;
private final ModeratorAccessManager moderatorAccessManager;
private BelzeBoolManager belzeBoolManager;
private PlayerFreezeManager freezeManager;
private AdminDialogService dialogService; private AdminDialogService dialogService;
private DialogPreferenceStore dialogPreferenceStore;
public AdminModeCommand( public AdminModeCommand(
ConfigManager configManager, ConfigManager configManager,
ModeManager modeManager, ModeManager modeManager,
CoreProtectIntegration coreProtectIntegration) { CoreProtectIntegration coreProtectIntegration,
ModeratorAccessManager moderatorAccessManager) {
this.configManager = configManager; this.configManager = configManager;
this.modeManager = modeManager; this.modeManager = modeManager;
this.coreProtectIntegration = coreProtectIntegration; this.coreProtectIntegration = coreProtectIntegration;
this.moderatorAccessManager = moderatorAccessManager;
} }
public void setDialogService(AdminDialogService dialogService) { public void setDialogService(AdminDialogService dialogService) {
this.dialogService = dialogService; this.dialogService = dialogService;
} }
public void setDialogPreferenceStore(DialogPreferenceStore store) {
this.dialogPreferenceStore = store;
}
public void setBelzeBoolManager(BelzeBoolManager belzeBoolManager) {
this.belzeBoolManager = belzeBoolManager;
}
public void setFreezeManager(PlayerFreezeManager freezeManager) {
this.freezeManager = freezeManager;
}
@Override @Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (args.length > 0 && args[0].equalsIgnoreCase("reload")) { if (args.length > 0 && args[0].equalsIgnoreCase("reload")) {
@@ -54,16 +80,29 @@ public class AdminModeCommand implements CommandExecutor {
sender.sendMessage(configManager.getPrefixedMessage(configManager.getMsgNoPermission())); sender.sendMessage(configManager.getPrefixedMessage(configManager.getMsgNoPermission()));
return true; return true;
} }
if (args.length < 2) { if (args.length < 2) {
sender.sendMessage(configManager.getPrefixedMessage("§cИспользование: /adminmode disable <игрок>")); sender.sendMessage(configManager.getPrefixedMessage("§cИспользование: /adminmode disable <игрок>"));
return true; return true;
} }
forceDisableTarget(sender, args[1]); forceDisableTarget(sender, args[1]);
return true; return true;
} }
if (args.length > 0 && args[0].equalsIgnoreCase("restrict")) {
handleRestrictCommand(sender, args);
return true;
}
if (args.length > 0 && args[0].equalsIgnoreCase("unrestrict")) {
handleUnrestrictCommand(sender, args);
return true;
}
if (args.length > 0 && args[0].equalsIgnoreCase("status")) {
handleStatusCommand(sender, args);
return true;
}
if (!(sender instanceof Player player)) { if (!(sender instanceof Player player)) {
sender.sendMessage("Эта команда доступна только игрокам!"); sender.sendMessage("Эта команда доступна только игрокам!");
return true; return true;
@@ -75,59 +114,49 @@ public class AdminModeCommand implements CommandExecutor {
} }
if (args.length > 0 && args[0].equalsIgnoreCase("dialog")) { if (args.length > 0 && args[0].equalsIgnoreCase("dialog")) {
if (!openRelevantDialog(player)) { if (!configManager.isDialogsEnabled()) {
player.sendMessage(configManager.getPrefixedMessage("§eDialog-панель сейчас недоступна.")); player.sendMessage(configManager.getPrefixedMessage("§cDialog-меню отключено администратором сервера."));
return true;
}
if (!player.hasPermission("adminmode.use")) {
player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgNoPermission()));
return true;
}
boolean nowEnabled = dialogPreferenceStore.toggle(player.getUniqueId());
if (nowEnabled) {
player.sendMessage(configManager.getPrefixedMessage("§aDialog-меню §2включено§a. Используйте §f/adminmode §aдля открытия панели."));
} else {
player.sendMessage(configManager.getPrefixedMessage("§7Dialog-меню §8отключено§7."));
} }
return true; return true;
} }
if (args.length > 0 && (args[0].equalsIgnoreCase("enable") || args[0].equalsIgnoreCase("profile"))) { if (!player.hasPermission("adminmode.use")) {
if (args.length < 2) {
openRelevantDialog(player);
return true;
}
ModeratorProfile profile = ModeratorProfile.fromInput(args[1]).orElse(null);
if (profile == null) {
player.sendMessage(configManager.getPrefixedMessage("§cНеизвестный профиль. Доступно: soft, hard."));
return true;
}
activateProfile(player, profile);
return true;
}
if (!isInAdminMode(player.getUniqueId()) && getAvailableProfiles(player).isEmpty()) {
player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgNoPermission())); player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgNoPermission()));
return true; return true;
} }
openRelevantDialog(player); if (args.length > 0 && args[0].equalsIgnoreCase("on")) {
return true; return handleEnableMode(player);
}
public boolean activateProfile(Player player, ModeratorProfile profile) {
if (profile == null) {
return openRelevantDialog(player);
} }
if (!profile.isAvailableFor(player)) { if (args.length > 0 && args[0].equalsIgnoreCase("off")) {
player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgNoPermission())); return handleDisableMode(player);
return false;
} }
if (modeManager.hasSession(player.getUniqueId())) { if (args.length > 0 && args[0].equalsIgnoreCase("toggle")) {
ModeratorProfile activeProfile = getActiveProfile(player.getUniqueId()); return handleHardToggle(player);
if (activeProfile == profile) { }
return openRelevantDialog(player);
}
if (!isInAdminMode(player.getUniqueId())) {
return handleEnableMode(player);
}
if (!openRelevantDialog(player)) {
player.sendMessage(configManager.getPrefixedMessage( player.sendMessage(configManager.getPrefixedMessage(
"§eУ вас уже активен профиль §f" + activeProfile.getDisplayName() + "§e. Сначала выйдите из режима.")); "§aВы в режиме администратора. Используйте §f/adminmode dialog §aдля включения GUI-панели."));
return false;
} }
return true;
return modeManager.enterMode(player, profile);
} }
public boolean exitMode(Player player) { public boolean exitMode(Player player) {
@@ -138,12 +167,8 @@ public class AdminModeCommand implements CommandExecutor {
return modeManager.recoverSession(player); return modeManager.recoverSession(player);
} }
public List<ModeratorProfile> getAvailableProfiles(Player player) { public boolean resumeMode(Player player) {
return modeManager.getAvailableProfiles(player); return modeManager.resumeMode(player);
}
public ModeratorProfile getActiveProfile(UUID playerId) {
return modeManager.getProfile(playerId);
} }
public boolean isInAdminMode(UUID playerId) { public boolean isInAdminMode(UUID playerId) {
@@ -155,37 +180,54 @@ public class AdminModeCommand implements CommandExecutor {
} }
public void applyAdminSettings(Player player, float flySpeed, float walkSpeed, boolean godMode) { public void applyAdminSettings(Player player, float flySpeed, float walkSpeed, boolean godMode) {
if (!modeManager.hasSession(player.getUniqueId())) { if (!modeManager.hasSession(player.getUniqueId())) return;
return;
}
modeManager.applyAdminSettings(player, flySpeed, walkSpeed, godMode); modeManager.applyAdminSettings(player, flySpeed, walkSpeed, godMode);
player.sendMessage(configManager.getPrefixedMessage("§aНастройки режима обновлены.")); player.sendMessage(configManager.getPrefixedMessage("§aНастройки режима обновлены."));
} }
public boolean performPlayerListAction(Player moderator, PlayerListAction action, String targetName) { public boolean toggleVanish(Player player) {
Player target = validateActiveTarget(moderator, targetName); return modeManager.toggleVanish(player);
if (target == null) { }
public boolean isVanished(UUID playerId) {
return modeManager.isVanished(playerId);
}
public boolean openBanList(Player player) {
return dialogService != null && dialogService.openBanListDialog(player);
}
public boolean toggleFreeze(Player player) {
if (freezeManager == null) {
return false; return false;
} }
return freezeManager.toggleFreeze(player);
}
public boolean isFrozen(UUID playerId) {
return freezeManager != null && freezeManager.isFrozen(playerId);
}
public boolean performPlayerListAction(Player moderator, PlayerListAction action, String targetName) {
Player target = validateActiveTarget(moderator, targetName);
if (target == null) return false;
return switch (action) { return switch (action) {
case SPECTATE -> modeManager.spectatePlayer(moderator, target); case SPECTATE -> modeManager.spectatePlayer(moderator, target);
case TELEPORT -> modeManager.teleportToPlayer(moderator, target); case TELEPORT -> modeManager.teleportToPlayer(moderator, target);
case INVSEE -> modeManager.openInvsee(moderator, target); case INVSEE -> modeManager.openInvsee(moderator, target);
case MANAGE -> {
dialogService.openManagePlayerDialog(moderator, target);
yield true;
}
}; };
} }
public boolean teleportToPlayer(Player moderator, String targetName) {
Player target = validateActiveTarget(moderator, targetName);
return target != null && modeManager.teleportToPlayer(moderator, target);
}
public boolean teleportPlayerHere(Player moderator, String targetName) { public boolean teleportPlayerHere(Player moderator, String targetName) {
if (!validateHardActionPlayer(moderator)) { if (!modeManager.hasSession(moderator.getUniqueId())) {
moderator.sendMessage(configManager.getPrefixedMessage("§cСначала войдите в режим модерации."));
return false; return false;
} }
Player target = resolveOnlinePlayer(targetName, moderator); Player target = resolveOnlinePlayer(targetName, moderator);
return target != null && modeManager.teleportPlayerHere(moderator, target); return target != null && modeManager.teleportPlayerHere(moderator, target);
} }
@@ -195,77 +237,242 @@ public class AdminModeCommand implements CommandExecutor {
sender.sendMessage(configManager.getPrefixedMessage(configManager.getMsgNoPermission())); sender.sendMessage(configManager.getPrefixedMessage(configManager.getMsgNoPermission()));
return false; return false;
} }
Player target = resolveOnlinePlayer(targetName, sender); Player target = resolveOnlinePlayer(targetName, sender);
if (target == null) { if (target == null) return false;
if (!forceDisableAbilities(target)) {
sender.sendMessage(configManager.getPrefixedMessage("§eУ игрока нет активных состояний AdminMode для сброса."));
return false; return false;
} }
forceDisableAbilities(target);
sender.sendMessage(configManager.getPrefixedMessage("§aРежим и способности игрока §f" + target.getName() + "§a сброшены.")); sender.sendMessage(configManager.getPrefixedMessage("§aРежим и способности игрока §f" + target.getName() + "§a сброшены."));
target.sendMessage(configManager.getPrefixedMessage("§eВаш режим модерации был принудительно отключен.")); target.sendMessage(configManager.getPrefixedMessage("§eВаш режим модерации был принудительно отключен."));
return true; return true;
} }
public boolean executeCPLookup(Player player, String targetPlayer, int radius, String time) { public boolean executeCPLookup(Player player, String targetPlayer, int radius, String time) {
if (!validateHardActionPlayer(player)) { if (!modeManager.hasSession(player.getUniqueId())) return false;
return false;
}
return coreProtectIntegration.lookup(player, targetPlayer, radius, time); return coreProtectIntegration.lookup(player, targetPlayer, radius, time);
} }
public boolean executeCPRollback(Player player, int radius, String time, String targetPlayer) { public boolean executeCPLookup(Player player, String targetPlayer, String action, String block, String exclude,
if (!validateHardActionPlayer(player)) { String world, int radius, String time) {
return false; if (!modeManager.hasSession(player.getUniqueId())) return false;
} return coreProtectIntegration.lookup(player, targetPlayer, action, block, exclude, world, radius, time);
return coreProtectIntegration.rollback(player, radius, time, targetPlayer); }
public boolean executeCPRollback(Player player, int radius, String time, String targetPlayer, String action, String block, String exclude) {
if (!modeManager.hasSession(player.getUniqueId())) return false;
return coreProtectIntegration.rollback(player, radius, time, targetPlayer, action, block, exclude);
}
public boolean executeCPRollback(Player player, int radius, String time, String targetPlayer, String action, String block,
String exclude, String world, String extraFilters) {
if (!modeManager.hasSession(player.getUniqueId())) return false;
return coreProtectIntegration.rollback(player, radius, time, targetPlayer, action, block, exclude, world, extraFilters);
}
public boolean executeCPRestore(Player player, int radius, String time, String targetPlayer, String action, String block,
String exclude, String world, String extraFilters) {
if (!modeManager.hasSession(player.getUniqueId())) return false;
return coreProtectIntegration.restore(player, radius, time, targetPlayer, action, block, exclude, world, extraFilters);
} }
public boolean toggleInspector(Player player) { public boolean toggleInspector(Player player) {
if (!validateHardActionPlayer(player)) { if (!modeManager.hasSession(player.getUniqueId())) return false;
return false;
}
return coreProtectIntegration.toggleInspector(player); return coreProtectIntegration.toggleInspector(player);
} }
public void forceDisableAbilities(Player player) { public boolean forceDisableAbilities(Player player) {
boolean changed = false;
if (modeManager.hasSession(player.getUniqueId())) { if (modeManager.hasSession(player.getUniqueId())) {
modeManager.exitMode(player); changed = modeManager.exitMode(player);
return;
} }
player.setInvulnerable(false); if (belzeBoolManager != null) {
player.setFlying(false); changed = belzeBoolManager.forceDisable(player) || changed;
player.setAllowFlight(false); }
player.setFlySpeed(0.1f);
player.setWalkSpeed(0.2f); return changed;
} }
public boolean canUseHardActions(CommandSender sender) { public boolean canUseHardActions(CommandSender sender) {
return modeManager.canUseHardActions(sender); return modeManager.canUseHardActions(sender);
} }
private boolean openRelevantDialog(Player player) { public boolean canManageModeratorAccess(Player actor, Player target) {
if (dialogService == null || !configManager.isDialogsEnabled()) { return actor != null
return false; && target != null
} && actor.hasPermission("adminmode.chief")
&& !actor.getUniqueId().equals(target.getUniqueId())
if (modeManager.hasSession(player.getUniqueId())) { && target.hasPermission("adminmode.use");
return dialogService.openCurrentPanel(player);
}
return dialogService.openModeSelectDialog(player);
} }
private boolean validateHardActionPlayer(Player moderator) { public boolean isModeratorRestricted(UUID playerId) {
if (!canUseHardActions(moderator)) { return moderatorAccessManager.isRestricted(playerId);
moderator.sendMessage(configManager.getPrefixedMessage("§cДля этого действия нужен активный профиль Hard Moder.")); }
public boolean restrictModerator(Player actor, OfflinePlayer target, String durationInput, String reason) {
if (actor == null || target == null || !actor.hasPermission("adminmode.chief")) {
return false;
}
return applyRestriction(actor, target, durationInput, reason);
}
public boolean unrestrictModerator(Player actor, OfflinePlayer target) {
if (actor == null || target == null || !actor.hasPermission("adminmode.chief")) {
return false;
}
return clearRestriction(actor, target);
}
private boolean handleEnableMode(Player player) {
if (moderatorAccessManager.isRestricted(player.getUniqueId())) {
player.sendMessage(configManager.getPrefixedMessage(moderatorAccessManager.describeRestriction(player.getUniqueId())));
return true;
}
if (isInAdminMode(player.getUniqueId())) {
if (!openRelevantDialog(player)) {
player.sendMessage(configManager.getPrefixedMessage("§eAdmin mode уже включён."));
}
return true;
}
if (!modeManager.enterMode(player)) {
player.sendMessage(configManager.getPrefixedMessage("§cНе удалось включить admin mode. Проверьте права и состояние игрока."));
}
return true;
}
private boolean handleDisableMode(Player player) {
if (!isInAdminMode(player.getUniqueId())) {
player.sendMessage(configManager.getPrefixedMessage("§eAdmin mode уже выключен."));
return true;
}
modeManager.exitMode(player);
return true;
}
private boolean handleHardToggle(Player player) {
if (isInAdminMode(player.getUniqueId())) {
return handleDisableMode(player);
}
return handleEnableMode(player);
}
private boolean applyRestriction(CommandSender sender, OfflinePlayer target, String durationInput, String reason) {
Long expiresAt = ModeratorAccessManager.parseDurationToExpiry(durationInput);
if (expiresAt == Long.MIN_VALUE) {
sender.sendMessage(configManager.getPrefixedMessage("§cНеверный срок. Примеры: 30s, 30m, 2h, 7d, 1y, perm"));
return false; return false;
} }
String targetName = target.getName() != null ? target.getName() : target.getUniqueId().toString();
ModeratorAccessManager.RestrictionEntry entry = moderatorAccessManager.restrict(
target,
targetName,
sender.getName(),
expiresAt,
reason);
Player onlineTarget = Bukkit.getPlayer(target.getUniqueId());
if (onlineTarget != null) {
forceDisableAbilities(onlineTarget);
onlineTarget.sendMessage(configManager.getPrefixedMessage(moderatorAccessManager.describeRestriction(onlineTarget.getUniqueId())));
}
sender.sendMessage(configManager.getPrefixedMessage(
"§aДоступ к admin mode для §f" + entry.getPlayerName() + "§a " +
(entry.isPermanent() ? "отключён навсегда." : "ограничен по времени.")));
return true; return true;
} }
private boolean clearRestriction(CommandSender sender, OfflinePlayer target) {
if (!moderatorAccessManager.clearRestriction(target.getUniqueId())) {
sender.sendMessage(configManager.getPrefixedMessage("§eДля этого игрока нет активного ограничения."));
return false;
}
String targetName = target.getName() != null ? target.getName() : target.getUniqueId().toString();
sender.sendMessage(configManager.getPrefixedMessage("§aОграничение для §f" + targetName + "§a снято."));
Player onlineTarget = Bukkit.getPlayer(target.getUniqueId());
if (onlineTarget != null) {
onlineTarget.sendMessage(configManager.getPrefixedMessage("§aДоступ к admin mode снова разрешён."));
}
return true;
}
private void handleRestrictCommand(CommandSender sender, String[] args) {
if (!sender.hasPermission("adminmode.chief")) {
sender.sendMessage(configManager.getPrefixedMessage(configManager.getMsgNoPermission()));
return;
}
if (args.length < 3) {
sender.sendMessage(configManager.getPrefixedMessage("§cИспользование: /adminmode restrict <игрок> <1s..1y|perm> [причина]"));
return;
}
OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]);
if (target.getUniqueId() == null) {
sender.sendMessage(configManager.getPrefixedMessage("§cНе удалось определить игрока."));
return;
}
applyRestriction(sender, target, args[2], joinArgs(args, 3));
}
private void handleUnrestrictCommand(CommandSender sender, String[] args) {
if (!sender.hasPermission("adminmode.chief")) {
sender.sendMessage(configManager.getPrefixedMessage(configManager.getMsgNoPermission()));
return;
}
if (args.length < 2) {
sender.sendMessage(configManager.getPrefixedMessage("§cИспользование: /adminmode unrestrict <игрок>"));
return;
}
OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]);
clearRestriction(sender, target);
}
private void handleStatusCommand(CommandSender sender, String[] args) {
if (!sender.hasPermission("adminmode.chief")) {
sender.sendMessage(configManager.getPrefixedMessage(configManager.getMsgNoPermission()));
return;
}
if (args.length < 2) {
sender.sendMessage(configManager.getPrefixedMessage("§cИспользование: /adminmode status <игрок>"));
return;
}
OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]);
if (!moderatorAccessManager.isRestricted(target.getUniqueId())) {
sender.sendMessage(configManager.getPrefixedMessage("§aУ игрока нет ограничений на admin mode."));
return;
}
sender.sendMessage(configManager.getPrefixedMessage("§eСтатус для §f" + (target.getName() != null ? target.getName() : args[1]) + "§e: " +
moderatorAccessManager.describeRestriction(target.getUniqueId())));
}
private String joinArgs(String[] args, int startIndex) {
if (startIndex >= args.length) {
return "";
}
StringBuilder builder = new StringBuilder();
for (int i = startIndex; i < args.length; i++) {
if (i > startIndex) {
builder.append(' ');
}
builder.append(args[i]);
}
return builder.toString();
}
private boolean openRelevantDialog(Player player) {
if (dialogService == null || !configManager.isDialogsEnabled()) return false;
if (dialogPreferenceStore == null || !dialogPreferenceStore.isEnabled(player.getUniqueId())) return false;
return dialogService.openAdminPanel(player);
}
private Player validateActiveTarget(Player moderator, String targetName) { private Player validateActiveTarget(Player moderator, String targetName) {
if (!modeManager.hasSession(moderator.getUniqueId())) { if (!modeManager.hasSession(moderator.getUniqueId())) {
moderator.sendMessage(configManager.getPrefixedMessage("§cСначала войдите в режим модерации.")); moderator.sendMessage(configManager.getPrefixedMessage("§cСначала войдите в режим модерации."));
@@ -279,17 +486,83 @@ public class AdminModeCommand implements CommandExecutor {
sender.sendMessage(configManager.getPrefixedMessage("§cУкажите ник игрока.")); sender.sendMessage(configManager.getPrefixedMessage("§cУкажите ник игрока."));
return null; return null;
} }
Player target = Bukkit.getPlayerExact(targetName.trim()); Player target = Bukkit.getPlayerExact(targetName.trim());
if (target == null) { if (target == null) target = Bukkit.getPlayer(targetName.trim());
target = Bukkit.getPlayer(targetName.trim());
}
if (target == null) { if (target == null) {
sender.sendMessage(configManager.getPrefixedMessage("§cИгрок не найден!")); sender.sendMessage(configManager.getPrefixedMessage("§cИгрок не найден!"));
return null; return null;
} }
return target; return target;
} }
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
if (command.getName().equalsIgnoreCase("adminmode")) {
return completeAdminMode(sender, args);
}
return Collections.emptyList();
}
private List<String> completeAdminMode(CommandSender sender, String[] args) {
if (args.length == 1) {
List<String> suggestions = new ArrayList<>();
suggestions.add("on");
suggestions.add("off");
suggestions.add("toggle");
suggestions.add("dialog");
suggestions.add("disable");
suggestions.add("restrict");
suggestions.add("unrestrict");
suggestions.add("status");
suggestions.add("reload");
return filterPrefix(suggestions, args[0]);
}
String subcommand = args[0].toLowerCase();
if (args.length == 2) {
return switch (subcommand) {
case "disable" -> filterPrefix(getOnlinePlayerNames(), args[1]);
case "restrict", "status" -> filterPrefix(getKnownPlayerNames(), args[1]);
case "unrestrict" -> filterPrefix(moderatorAccessManager.getRestrictedPlayerNames(), args[1]);
default -> Collections.emptyList();
};
}
if (args.length == 3 && subcommand.equals("restrict")) {
return filterPrefix(List.of("30s", "1m", "30m", "2h", "1d", "7d", "1y", "perm"), args[2]);
}
return Collections.emptyList();
}
private List<String> getOnlinePlayerNames() {
List<String> names = new ArrayList<>();
for (Player player : Bukkit.getOnlinePlayers()) {
names.add(player.getName());
}
names.sort(String.CASE_INSENSITIVE_ORDER);
return names;
}
private List<String> getKnownPlayerNames() {
List<String> names = new ArrayList<>(getOnlinePlayerNames());
for (OfflinePlayer offlinePlayer : Bukkit.getOfflinePlayers()) {
if (offlinePlayer.getName() != null && !offlinePlayer.getName().isBlank() && !names.contains(offlinePlayer.getName())) {
names.add(offlinePlayer.getName());
}
}
names.sort(String.CASE_INSENSITIVE_ORDER);
return names;
}
private List<String> filterPrefix(List<String> source, String prefix) {
String normalized = prefix == null ? "" : prefix.toLowerCase();
List<String> result = new ArrayList<>();
for (String value : source) {
if (normalized.isEmpty() || value.toLowerCase().startsWith(normalized)) {
result.add(value);
}
}
return result;
}
} }

436
src/main/java/org/blz/adminmode/config/ConfigManager.java Normal file → Executable file
View File

@@ -1,11 +1,10 @@
package org.blz.adminmode.config; package org.blz.adminmode.config;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumMap; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import org.blz.adminmode.moderation.ModeratorProfile; import org.blz.adminmode.moderation.PunishmentDuration;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
@@ -17,20 +16,28 @@ public class ConfigManager {
private boolean enabled; private boolean enabled;
private boolean useLuckPerms; private boolean useLuckPerms;
private Map<ModeratorProfile, List<String>> luckPermsGroups; private List<String> adminLuckPermsGroups;
private boolean removeGroupsOnDisable; private boolean removeGroupsOnDisable;
private boolean dialogsEnabled; private boolean dialogsEnabled;
private String baseGroup; private String baseGroup;
private String softGroup; private String adminGroup;
private String hardGroup; private Material panelMaterial;
private Material softPanelMaterial;
private Material hardPanelMaterial;
private Material cpInspectorMaterial; private Material cpInspectorMaterial;
private boolean coreProtectEnabled; private boolean coreProtectEnabled;
private int coreProtectDefaultRadius; private int coreProtectDefaultRadius;
private String coreProtectDefaultTime; private String coreProtectDefaultTime;
private boolean sessionLoggingEnabled; private boolean sessionLoggingEnabled;
private String sessionLogFile; private String sessionLogFile;
private boolean moderationActionsAsConsole;
private String warnCommandTemplate;
private String muteCommandTemplate;
private String unmuteCommandTemplate;
private String banCommandTemplate;
private String kickCommandTemplate;
private List<String> punishmentDurationOptions;
private String defaultWarnDuration;
private String defaultMuteDuration;
private String defaultBanDuration;
private boolean preventItemDrop; private boolean preventItemDrop;
private boolean preventItemPickup; private boolean preventItemPickup;
@@ -46,6 +53,7 @@ public class ConfigManager {
private boolean mobsIgnorePlayer; private boolean mobsIgnorePlayer;
private boolean allowFlightInSurvival; private boolean allowFlightInSurvival;
private boolean godMode; private boolean godMode;
private boolean chiefCreativeMode;
private float customFlySpeed; private float customFlySpeed;
private float customWalkSpeed; private float customWalkSpeed;
private List<String> allowedGameModes; private List<String> allowedGameModes;
@@ -63,8 +71,7 @@ public class ConfigManager {
private String msgBlockPlaceDenied; private String msgBlockPlaceDenied;
private String msgReloaded; private String msgReloaded;
private String msgPrefix; private String msgPrefix;
private String msgEnterSoft; private String msgEnter;
private String msgEnterHard;
private String msgExitMode; private String msgExitMode;
private String msgCoreProtectUnavailable; private String msgCoreProtectUnavailable;
private String msgSessionRecovered; private String msgSessionRecovered;
@@ -79,17 +86,20 @@ public class ConfigManager {
config = plugin.getConfig(); config = plugin.getConfig();
enabled = config.getBoolean("admin_mode.enabled", true); enabled = config.getBoolean("admin_mode.enabled", true);
useLuckPerms = config.getBoolean("admin_mode.luckperms.enabled", true); useLuckPerms = config.getBoolean("admin_mode.luckperms.enabled", true);
removeGroupsOnDisable = config.getBoolean("admin_mode.luckperms.remove_on_disable", true); removeGroupsOnDisable = config.getBoolean("admin_mode.luckperms.remove_on_disable", true);
dialogsEnabled = config.getBoolean("admin_mode.dialogs.enabled", true); dialogsEnabled = config.getBoolean("admin_mode.dialogs.enabled", true);
baseGroup = config.getString("groups.base", "moder"); baseGroup = config.getString("groups.base", "moder");
softGroup = config.getString("groups.soft", "soft_moder_adm"); adminGroup = config.getString("groups.admin", "admin_moder");
hardGroup = config.getString("groups.hard", "hard_moder_adm");
softPanelMaterial = readMaterial("items.soft_panel_material", Material.COMPASS); List<String> groups = config.getStringList("admin_mode.luckperms.groups");
hardPanelMaterial = readMaterial("items.hard_panel_material", Material.NETHER_STAR); if (groups.isEmpty()) {
groups = List.of(adminGroup);
}
adminLuckPermsGroups = new ArrayList<>(groups);
panelMaterial = readMaterial("items.panel_material", Material.NETHER_STAR);
cpInspectorMaterial = readMaterial("items.cp_inspector_material", Material.STICK); cpInspectorMaterial = readMaterial("items.cp_inspector_material", Material.STICK);
coreProtectEnabled = config.getBoolean("coreprotect.enabled", true); coreProtectEnabled = config.getBoolean("coreprotect.enabled", true);
@@ -98,42 +108,38 @@ public class ConfigManager {
sessionLoggingEnabled = config.getBoolean("logging.enabled", true); sessionLoggingEnabled = config.getBoolean("logging.enabled", true);
sessionLogFile = config.getString("logging.log_file", "logs/sessions.log"); sessionLogFile = config.getString("logging.log_file", "logs/sessions.log");
moderationActionsAsConsole = config.getBoolean("moderation_actions.dispatch_as_console", true);
punishmentDurationOptions = readPunishmentDurations();
defaultWarnDuration = readDefaultPunishmentDuration("warn", "7d");
defaultMuteDuration = readDefaultPunishmentDuration("mute", "1h");
defaultBanDuration = readDefaultPunishmentDuration("ban", "7d");
warnCommandTemplate = readModerationCommand("warn",
"warn {target} {reason}", "warn {target} {duration} {reason}", true);
muteCommandTemplate = readModerationCommand("mute",
"mute {target} {reason}", "mute {target} {duration} actor:{moderator} {reason}", true);
unmuteCommandTemplate = readModerationCommand("unmute",
"unmute {target} actor:{moderator} {reason}",
"moderationcommands:unmute {target} actor:{moderator} {reason}", false);
banCommandTemplate = readModerationCommand("ban",
"ban {target} {reason}", "ban {target} {duration} actor:{moderator} {reason}", true);
kickCommandTemplate = readModerationCommand("kick",
"kick {target} {reason}", "kick {target} actor:{moderator} {reason}", false);
List<String> legacyGroups = config.getStringList("admin_mode.luckperms.groups"); preventItemDrop = config.getBoolean("admin_mode.restrictions.prevent_item_drop", false);
List<String> softGroups = config.getStringList("admin_mode.luckperms.profile_groups.soft_moder");
List<String> hardGroups = config.getStringList("admin_mode.luckperms.profile_groups.hard_moder");
if (softGroups.isEmpty()) {
if (!legacyGroups.isEmpty()) {
softGroups = new ArrayList<>(legacyGroups);
} else {
softGroups = new ArrayList<>(List.of(softGroup));
}
}
if (hardGroups.isEmpty()) {
if (!legacyGroups.isEmpty()) {
hardGroups = new ArrayList<>(legacyGroups);
} else {
hardGroups = new ArrayList<>(List.of(hardGroup));
}
}
luckPermsGroups = new EnumMap<>(ModeratorProfile.class);
luckPermsGroups.put(ModeratorProfile.SOFT_MODER, new ArrayList<>(softGroups));
luckPermsGroups.put(ModeratorProfile.HARD_MODER, new ArrayList<>(hardGroups));
preventItemDrop = config.getBoolean("admin_mode.restrictions.prevent_item_drop", true);
preventItemPickup = config.getBoolean("admin_mode.restrictions.prevent_item_pickup", false); preventItemPickup = config.getBoolean("admin_mode.restrictions.prevent_item_pickup", false);
preventContainerTransfer = config.getBoolean("admin_mode.restrictions.prevent_container_transfer", true); preventContainerTransfer = config.getBoolean("admin_mode.restrictions.prevent_container_transfer", true);
preventItemUse = config.getBoolean("admin_mode.restrictions.prevent_item_use", true); preventItemUse = config.getBoolean("admin_mode.restrictions.prevent_item_use", false);
allowOwnInventory = config.getBoolean("admin_mode.restrictions.allow_own_inventory", true); allowOwnInventory = config.getBoolean("admin_mode.restrictions.allow_own_inventory", true);
allowedBlocks = config.getStringList("admin_mode.restrictions.allowed_blocks"); allowedBlocks = config.getStringList("admin_mode.restrictions.allowed_blocks");
allowedPlaceBlocks = config.getStringList("admin_mode.restrictions.allowed_place_blocks"); allowedPlaceBlocks = config.getStringList("admin_mode.restrictions.allowed_place_blocks");
givePresetBlocks = config.getBoolean("admin_mode.restrictions.give_preset_blocks", true); givePresetBlocks = config.getBoolean("admin_mode.restrictions.give_preset_blocks", false);
presetBlocks = config.getStringList("admin_mode.restrictions.preset_blocks"); presetBlocks = config.getStringList("admin_mode.restrictions.preset_blocks");
forceSpectator = config.getBoolean("admin_mode.gamemode.force_spectator", false); forceSpectator = config.getBoolean("admin_mode.gamemode.force_spectator", false);
mobsIgnorePlayer = config.getBoolean("admin_mode.gamemode.mobs_ignore_player", true); mobsIgnorePlayer = config.getBoolean("admin_mode.gamemode.mobs_ignore_player", true);
allowFlightInSurvival = config.getBoolean("admin_mode.gamemode.allow_flight_in_survival", true); allowFlightInSurvival = config.getBoolean("admin_mode.gamemode.allow_flight_in_survival", true);
godMode = config.getBoolean("admin_mode.gamemode.god_mode", true); godMode = config.getBoolean("admin_mode.gamemode.god_mode", true);
chiefCreativeMode = config.getBoolean("admin_mode.gamemode.chief_creative", true);
customFlySpeed = (float) config.getDouble("admin_mode.gamemode.custom_fly_speed", 0.2); customFlySpeed = (float) config.getDouble("admin_mode.gamemode.custom_fly_speed", 0.2);
customWalkSpeed = (float) config.getDouble("admin_mode.gamemode.custom_walk_speed", 0.2); customWalkSpeed = (float) config.getDouble("admin_mode.gamemode.custom_walk_speed", 0.2);
allowedGameModes = config.getStringList("admin_mode.gamemode.allowed_gamemodes"); allowedGameModes = config.getStringList("admin_mode.gamemode.allowed_gamemodes");
@@ -143,8 +149,7 @@ public class ConfigManager {
msgPermissionsGranted = config.getString("admin_mode.messages.permissions_granted", msgPermissionsGranted = config.getString("admin_mode.messages.permissions_granted",
"⚠ Вам выданы временные права администратора!"); "⚠ Вам выданы временные права администратора!");
msgDisabled = config.getString("admin_mode.messages.disabled", "✓ Режим администратора отключен!"); msgDisabled = config.getString("admin_mode.messages.disabled", "✓ Режим администратора отключен!");
msgDisabledSubtitle = config.getString("admin_mode.messages.disabled_subtitle", msgDisabledSubtitle = config.getString("admin_mode.messages.disabled_subtitle", "Ваше состояние восстановлено.");
"Ваше состояние восстановлено.");
msgPermissionsRemoved = config.getString("admin_mode.messages.permissions_removed", msgPermissionsRemoved = config.getString("admin_mode.messages.permissions_removed",
"✓ Права администратора отозваны!"); "✓ Права администратора отозваны!");
msgNoPermission = config.getString("admin_mode.messages.no_permission", msgNoPermission = config.getString("admin_mode.messages.no_permission",
@@ -160,241 +165,158 @@ public class ConfigManager {
msgReloaded = config.getString("admin_mode.messages.reloaded", "✓ Конфигурация AdminMode перезагружена!"); msgReloaded = config.getString("admin_mode.messages.reloaded", "✓ Конфигурация AdminMode перезагружена!");
msgPrefix = config.getString("messages.prefix", "§8[§bAdminMode§8] "); msgPrefix = config.getString("messages.prefix", "§8[§bAdminMode§8] ");
msgEnterSoft = config.getString("messages.enter_soft", "§aВошёл в режим §bSOFT MODER"); msgEnter = config.getString("messages.enter", "§aВошёл в режим §cADMIN MODE");
msgEnterHard = config.getString("messages.enter_hard", "§aВошёл в режим §cHARD MODER");
msgExitMode = config.getString("messages.exit", "§7Режим деактивирован. Состояние восстановлено."); msgExitMode = config.getString("messages.exit", "§7Режим деактивирован. Состояние восстановлено.");
msgCoreProtectUnavailable = config.getString("messages.cp_unavailable", msgCoreProtectUnavailable = config.getString("messages.cp_unavailable",
"§cCoreProtect не найден. Hard режим работает без CP."); "§cCoreProtect не найден или отключён.");
msgSessionRecovered = config.getString("messages.session_recovered", msgSessionRecovered = config.getString("messages.session_recovered",
"§eВаша активная сессия модерации была восстановлена и корректно завершена."); "§eВаша активная сессия модерации была восстановлена и корректно завершена.");
validatePresetBlocks();
} }
public boolean isEnabled() { // --- Getters ---
return enabled;
}
public boolean isUseLuckPerms() { public boolean isEnabled() { return enabled; }
return useLuckPerms; public boolean isUseLuckPerms() { return useLuckPerms; }
} public List<String> getLuckPermsGroups() { return new ArrayList<>(adminLuckPermsGroups); }
public boolean isRemoveGroupsOnDisable() { return removeGroupsOnDisable; }
public List<String> getLuckPermsGroups() { public boolean isDialogsEnabled() { return dialogsEnabled; }
return getLuckPermsGroups(ModeratorProfile.SOFT_MODER); public String getBaseGroup() { return baseGroup; }
} public String getAdminGroup() { return adminGroup; }
public Material getPanelMaterial() { return panelMaterial; }
public List<String> getLuckPermsGroups(ModeratorProfile profile) { public Material getCpInspectorMaterial() { return cpInspectorMaterial; }
return new ArrayList<>(luckPermsGroups.getOrDefault(profile, List.of())); public boolean isCoreProtectEnabled() { return coreProtectEnabled; }
} public int getCoreProtectDefaultRadius() { return coreProtectDefaultRadius; }
public String getCoreProtectDefaultTime() { return coreProtectDefaultTime; }
public boolean isRemoveGroupsOnDisable() { public boolean isSessionLoggingEnabled() { return sessionLoggingEnabled; }
return removeGroupsOnDisable; public String getSessionLogFile() { return sessionLogFile; }
} public boolean isModerationActionsAsConsole() { return moderationActionsAsConsole; }
public String getWarnCommandTemplate() { return warnCommandTemplate; }
public boolean isDialogsEnabled() { public String getMuteCommandTemplate() { return muteCommandTemplate; }
return dialogsEnabled; public String getUnmuteCommandTemplate() { return unmuteCommandTemplate; }
} public String getBanCommandTemplate() { return banCommandTemplate; }
public String getKickCommandTemplate() { return kickCommandTemplate; }
public String getBaseGroup() { public List<String> getPunishmentDurationOptions() { return new ArrayList<>(punishmentDurationOptions); }
return baseGroup; public String getDefaultWarnDuration() { return defaultWarnDuration; }
} public String getDefaultMuteDuration() { return defaultMuteDuration; }
public String getDefaultBanDuration() { return defaultBanDuration; }
public String getSoftGroup() { public boolean isPunishmentDurationAllowed(String value) {
return softGroup; String normalized = PunishmentDuration.normalizeToken(value);
} return !normalized.isEmpty() && punishmentDurationOptions.contains(normalized);
public String getHardGroup() {
return hardGroup;
}
public String getActiveGroup(ModeratorProfile profile) {
return profile != null && profile.isHardProfile() ? hardGroup : softGroup;
}
public Material getSoftPanelMaterial() {
return softPanelMaterial;
}
public Material getHardPanelMaterial() {
return hardPanelMaterial;
}
public Material getCpInspectorMaterial() {
return cpInspectorMaterial;
}
public boolean isCoreProtectEnabled() {
return coreProtectEnabled;
}
public int getCoreProtectDefaultRadius() {
return coreProtectDefaultRadius;
}
public String getCoreProtectDefaultTime() {
return coreProtectDefaultTime;
}
public boolean isSessionLoggingEnabled() {
return sessionLoggingEnabled;
}
public String getSessionLogFile() {
return sessionLogFile;
}
public boolean isPreventItemDrop() {
return preventItemDrop;
}
public boolean isPreventItemPickup() {
return preventItemPickup;
}
public boolean isPreventContainerTransfer() {
return preventContainerTransfer;
}
public boolean isPreventItemUse() {
return preventItemUse;
}
public boolean isAllowOwnInventory() {
return allowOwnInventory;
}
public List<String> getAllowedBlocks() {
return allowedBlocks;
}
public List<String> getAllowedPlaceBlocks() {
return allowedPlaceBlocks;
}
public boolean isGivePresetBlocks() {
return givePresetBlocks;
}
public List<String> getPresetBlocks() {
return presetBlocks;
}
public boolean isForceSpectator() {
return forceSpectator;
}
public boolean isMobsIgnorePlayer() {
return mobsIgnorePlayer;
}
public boolean isAllowFlightInSurvival() {
return allowFlightInSurvival;
}
public boolean isGodMode() {
return godMode;
}
public float getCustomFlySpeed() {
return customFlySpeed;
}
public float getCustomWalkSpeed() {
return customWalkSpeed;
}
public List<String> getAllowedGameModes() {
return allowedGameModes;
}
public String getMsgEnabled() {
return msgEnabled;
}
public String getMsgEnabledSubtitle() {
return msgEnabledSubtitle;
}
public String getMsgPermissionsGranted() {
return msgPermissionsGranted;
}
public String getMsgDisabled() {
return msgDisabled;
}
public String getMsgDisabledSubtitle() {
return msgDisabledSubtitle;
}
public String getMsgPermissionsRemoved() {
return msgPermissionsRemoved;
}
public String getMsgNoPermission() {
return msgNoPermission;
}
public String getMsgItemDropDenied() {
return msgItemDropDenied;
}
public String getMsgContainerTransferDenied() {
return msgContainerTransferDenied;
}
public String getMsgItemUseDenied() {
return msgItemUseDenied;
}
public String getMsgBlockPlaceDenied() {
return msgBlockPlaceDenied;
}
public String getMsgReloaded() {
return msgReloaded;
}
public String getMsgPrefix() {
return msgPrefix;
}
public String getMsgEnterSoft() {
return msgEnterSoft;
}
public String getMsgEnterHard() {
return msgEnterHard;
}
public String getMsgExitMode() {
return msgExitMode;
}
public String getMsgCoreProtectUnavailable() {
return msgCoreProtectUnavailable;
}
public String getMsgSessionRecovered() {
return msgSessionRecovered;
} }
public boolean isPreventItemDrop() { return preventItemDrop; }
public boolean isPreventItemPickup() { return preventItemPickup; }
public boolean isPreventContainerTransfer() { return preventContainerTransfer; }
public boolean isPreventItemUse() { return preventItemUse; }
public boolean isAllowOwnInventory() { return allowOwnInventory; }
public List<String> getAllowedBlocks() { return allowedBlocks; }
public List<String> getAllowedPlaceBlocks() { return allowedPlaceBlocks; }
public boolean isGivePresetBlocks() { return givePresetBlocks; }
public List<String> getPresetBlocks() { return presetBlocks; }
public boolean isForceSpectator() { return forceSpectator; }
public boolean isMobsIgnorePlayer() { return mobsIgnorePlayer; }
public boolean isAllowFlightInSurvival() { return allowFlightInSurvival; }
public boolean isGodMode() { return godMode; }
public boolean isChiefCreativeMode() { return chiefCreativeMode; }
public float getCustomFlySpeed() { return customFlySpeed; }
public float getCustomWalkSpeed() { return customWalkSpeed; }
public List<String> getAllowedGameModes() { return allowedGameModes; }
public String getMsgEnabled() { return msgEnabled; }
public String getMsgEnabledSubtitle() { return msgEnabledSubtitle; }
public String getMsgPermissionsGranted() { return msgPermissionsGranted; }
public String getMsgDisabled() { return msgDisabled; }
public String getMsgDisabledSubtitle() { return msgDisabledSubtitle; }
public String getMsgPermissionsRemoved() { return msgPermissionsRemoved; }
public String getMsgNoPermission() { return msgNoPermission; }
public String getMsgItemDropDenied() { return msgItemDropDenied; }
public String getMsgContainerTransferDenied() { return msgContainerTransferDenied; }
public String getMsgItemUseDenied() { return msgItemUseDenied; }
public String getMsgBlockPlaceDenied() { return msgBlockPlaceDenied; }
public String getMsgReloaded() { return msgReloaded; }
public String getMsgPrefix() { return msgPrefix; }
public String getMsgEnter() { return msgEnter; }
public String getMsgExitMode() { return msgExitMode; }
public String getMsgCoreProtectUnavailable() { return msgCoreProtectUnavailable; }
public String getMsgSessionRecovered() { return msgSessionRecovered; }
public String getPrefixedMessage(String message) { public String getPrefixedMessage(String message) {
if (message == null || message.isEmpty()) { if (message == null || message.isEmpty()) return msgPrefix;
return msgPrefix;
}
return msgPrefix + message; return msgPrefix + message;
} }
private Material readMaterial(String path, Material fallback) { private Material readMaterial(String path, Material fallback) {
String value = config.getString(path); String value = config.getString(path);
if (value == null || value.isBlank()) { if (value == null || value.isBlank()) return fallback;
return fallback;
}
try { try {
return Material.valueOf(value.trim().toUpperCase()); return Material.valueOf(value.trim().toUpperCase());
} catch (IllegalArgumentException exception) { } catch (IllegalArgumentException e) {
plugin.getLogger().warning("Неизвестный material в конфиге " + path + ": " + value); plugin.getLogger().warning("Неизвестный material в конфиге " + path + ": " + value);
return fallback; return fallback;
} }
} }
private void validatePresetBlocks() {
for (String blockString : presetBlocks) {
try {
String[] parts = blockString.split(":");
Material.valueOf(parts[0].trim().toUpperCase());
} catch (Exception e) {
plugin.getLogger().warning("Неверный блок в preset_blocks: " + blockString);
}
}
}
private List<String> readPunishmentDurations() {
List<String> configured = config.getStringList("moderation_actions.durations.options");
if (configured.isEmpty()) {
configured = List.of("1h", "1d", "7d", "30d", "permanent");
}
LinkedHashSet<String> normalized = new LinkedHashSet<>();
for (String value : configured) {
String token = PunishmentDuration.normalizeToken(value);
if (token.isEmpty()) {
plugin.getLogger().warning("Неизвестный срок наказания в config.yml: " + value);
} else {
normalized.add(token);
}
}
if (normalized.isEmpty()) {
normalized.addAll(List.of("1h", "1d", "7d", "30d", "permanent"));
}
return new ArrayList<>(normalized);
}
private String readDefaultPunishmentDuration(String action, String fallback) {
String configured = config.getString("moderation_actions.durations.defaults." + action, fallback);
String normalized = PunishmentDuration.normalizeToken(configured);
if (!normalized.isEmpty() && punishmentDurationOptions.contains(normalized)) {
return normalized;
}
plugin.getLogger().warning("Срок по умолчанию для " + action + " отсутствует в списке options: " + configured);
return punishmentDurationOptions.contains(fallback) ? fallback : punishmentDurationOptions.getFirst();
}
private String readModerationCommand(String action, String legacyTemplate, String newTemplate, boolean requiresDuration) {
String path = "moderation_actions.commands." + action;
String configured = config.getString(path, newTemplate);
if (configured == null || configured.isBlank()) {
return "";
}
configured = configured.trim();
if (configured.equals(legacyTemplate)) {
plugin.getLogger().info("Шаблон команды " + action + " обновлён в памяти до формата наказаний со сроком/автором.");
configured = newTemplate;
}
if (requiresDuration && !configured.contains("{duration}")) {
plugin.getLogger().warning("Команда " + action + " отключена: шаблон " + path + " должен содержать {duration}.");
return "";
}
if (!configured.contains("{target}")) {
plugin.getLogger().warning("Команда " + action + " отключена: шаблон " + path + " должен содержать {target}.");
return "";
}
return configured;
}
} }

View File

@@ -1,29 +1,23 @@
package org.blz.adminmode.data; package org.blz.adminmode.data;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import org.blz.adminmode.utils.ItemStackCodec;
import org.blz.adminmode.utils.PotionEffectCodec;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.io.BukkitObjectInputStream;
import org.bukkit.util.io.BukkitObjectOutputStream;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
@@ -187,16 +181,7 @@ public class AdminModeDataManager {
private String serializeItemArray(ItemStack[] items) { private String serializeItemArray(ItemStack[] items) {
try { try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); return ItemStackCodec.serializeItems(items);
BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream);
dataOutput.writeInt(items.length);
for (ItemStack item : items) {
dataOutput.writeObject(item);
}
dataOutput.close();
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return ""; return "";
@@ -205,17 +190,7 @@ public class AdminModeDataManager {
private ItemStack[] deserializeItemArray(String data) { private ItemStack[] deserializeItemArray(String data) {
try { try {
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(data)); return ItemStackCodec.deserializeItems(data);
BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream);
int length = dataInput.readInt();
ItemStack[] items = new ItemStack[length];
for (int i = 0; i < length; i++) {
items[i] = (ItemStack) dataInput.readObject();
}
dataInput.close();
return items;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return new ItemStack[0]; return new ItemStack[0];
@@ -224,11 +199,7 @@ public class AdminModeDataManager {
private String serializeItem(ItemStack item) { private String serializeItem(ItemStack item) {
try { try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); return ItemStackCodec.serializeItem(item);
BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream);
dataOutput.writeObject(item);
dataOutput.close();
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return ""; return "";
@@ -238,56 +209,23 @@ public class AdminModeDataManager {
private ItemStack deserializeItem(String data) { private ItemStack deserializeItem(String data) {
try { try {
if (data.isEmpty()) { if (data.isEmpty()) {
return new ItemStack(Material.AIR); return ItemStack.empty();
} }
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(data)); return ItemStackCodec.deserializeItem(data);
BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream);
ItemStack item = (ItemStack) dataInput.readObject();
dataInput.close();
return item;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return new ItemStack(Material.AIR); return ItemStack.empty();
} }
} }
private String serializePotionEffects(Collection<PotionEffect> effects) { private String serializePotionEffects(Collection<PotionEffect> effects) {
StringBuilder sb = new StringBuilder(); return PotionEffectCodec.serialize(effects);
for (PotionEffect effect : effects) {
sb.append(effect.getType().getName()).append(":")
.append(effect.getDuration()).append(":")
.append(effect.getAmplifier()).append(":")
.append(effect.isAmbient()).append(":")
.append(effect.hasParticles()).append(":")
.append(effect.hasIcon()).append(";");
}
return sb.toString();
} }
private Collection<PotionEffect> deserializePotionEffects(String data) { private Collection<PotionEffect> deserializePotionEffects(String data) {
Collection<PotionEffect> effects = new ArrayList<>(); return PotionEffectCodec.deserialize(data,
if (data.isEmpty()) return effects; encodedEffect -> plugin.getLogger().warning(
"Ошибка десериализации эффекта: " + encodedEffect));
String[] effectStrings = data.split(";");
for (String effectStr : effectStrings) {
if (effectStr.isEmpty()) continue;
try {
String[] parts = effectStr.split(":");
PotionEffectType type = PotionEffectType.getByName(parts[0]);
int duration = Integer.parseInt(parts[1]);
int amplifier = Integer.parseInt(parts[2]);
boolean ambient = Boolean.parseBoolean(parts[3]);
boolean particles = Boolean.parseBoolean(parts[4]);
boolean icon = Boolean.parseBoolean(parts[5]);
effects.add(new PotionEffect(type, duration, amplifier, ambient, particles, icon));
} catch (Exception e) {
plugin.getLogger().warning("Ошибка десериализации эффекта: " + effectStr);
}
}
return effects;
} }
public static class PlayerStateData { public static class PlayerStateData {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,63 @@
package org.blz.adminmode.dialog;
import org.bukkit.plugin.Plugin;
import java.io.*;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
* Persists which players have personally enabled the Dialog-based admin panel.
* Stored in <data-folder>/dialog_prefs.dat (one UUID per line).
* Default: disabled for every player until they explicitly toggle on.
*/
public class DialogPreferenceStore {
private final File file;
private final Set<UUID> enabledPlayers = new HashSet<>();
public DialogPreferenceStore(Plugin plugin) {
file = new File(plugin.getDataFolder(), "dialog_prefs.dat");
load();
}
public boolean isEnabled(UUID playerId) {
return enabledPlayers.contains(playerId);
}
/** Toggles the preference and persists. Returns the new state (true = enabled). */
public boolean toggle(UUID playerId) {
if (enabledPlayers.remove(playerId)) {
save();
return false;
}
enabledPlayers.add(playerId);
save();
return true;
}
private void load() {
if (!file.exists()) return;
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) continue;
try {
enabledPlayers.add(UUID.fromString(line));
} catch (IllegalArgumentException ignored) {}
}
} catch (IOException ignored) {}
}
private void save() {
file.getParentFile().mkdirs();
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
for (UUID uuid : enabledPlayers) {
writer.write(uuid.toString());
writer.newLine();
}
} catch (IOException ignored) {}
}
}

View File

@@ -0,0 +1,72 @@
package org.blz.adminmode.dialog;
import org.bukkit.plugin.Plugin;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
* Stores players who disabled emojis in the moderation dialogs.
* Default: emojis are enabled.
*/
public class MenuEmojiPreferenceStore {
private final File file;
private final Set<UUID> disabledPlayers = new HashSet<>();
public MenuEmojiPreferenceStore(Plugin plugin) {
this.file = new File(plugin.getDataFolder(), "menu_emoji_prefs.dat");
load();
}
public boolean isEnabled(UUID playerId) {
return !disabledPlayers.contains(playerId);
}
public boolean toggle(UUID playerId) {
if (disabledPlayers.remove(playerId)) {
save();
return true;
}
disabledPlayers.add(playerId);
save();
return false;
}
private void load() {
if (!file.exists()) return;
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) continue;
try {
disabledPlayers.add(UUID.fromString(line));
} catch (IllegalArgumentException ignored) {
}
}
} catch (IOException ignored) {
}
}
private void save() {
File parent = file.getParentFile();
if (parent != null) {
parent.mkdirs();
}
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
for (UUID uuid : disabledPlayers) {
writer.write(uuid.toString());
writer.newLine();
}
} catch (IOException ignored) {
}
}
}

View File

@@ -29,31 +29,67 @@ public class CoreProtectIntegration {
} }
public boolean lookup(Player player, String targetPlayer, int radius, String time) { public boolean lookup(Player player, String targetPlayer, int radius, String time) {
return lookup(player, targetPlayer, "", "", "", "", radius, time);
}
public boolean lookup(Player player, String targetPlayer, String action, String block, String exclude,
String world, int radius, String time) {
if (!isAvailable()) { if (!isAvailable()) {
player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgCoreProtectUnavailable())); player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgCoreProtectUnavailable()));
return false; return false;
} }
StringBuilder command = new StringBuilder("co lookup"); StringBuilder command = new StringBuilder("co lookup");
appendPlayerFilter(command, targetPlayer); appendCommonFilters(command, targetPlayer, action, block, exclude, world, radius, time);
command.append(" r:").append(normalizeRadius(radius));
command.append(" t:").append(normalizeTime(time));
return Bukkit.dispatchCommand(player, command.toString()); return Bukkit.dispatchCommand(player, command.toString());
} }
public boolean rollback(Player player, int radius, String time, String targetPlayer) { public boolean rollback(Player player, int radius, String time, String targetPlayer, String action, String block, String exclude) {
return rollback(player, radius, time, targetPlayer, action, block, exclude, "", "");
}
public boolean rollback(Player player, int radius, String time, String targetPlayer, String action, String block,
String exclude, String world, String extraFilters) {
if (!isAvailable()) { if (!isAvailable()) {
player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgCoreProtectUnavailable())); player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgCoreProtectUnavailable()));
return false; return false;
} }
StringBuilder command = new StringBuilder("co rollback"); StringBuilder command = new StringBuilder("co rollback");
appendPlayerFilter(command, targetPlayer); appendCommonFilters(command, targetPlayer, action, block, exclude, world, radius, time);
command.append(" r:").append(normalizeRadius(radius)); appendExtraFilters(command, extraFilters);
command.append(" t:").append(normalizeTime(time));
return Bukkit.dispatchCommand(player, command.toString()); return Bukkit.dispatchCommand(player, command.toString());
} }
public boolean restore(Player player, int radius, String time, String targetPlayer, String action, String block,
String exclude, String world, String extraFilters) {
if (!isAvailable()) {
player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgCoreProtectUnavailable()));
return false;
}
StringBuilder command = new StringBuilder("co restore");
appendCommonFilters(command, targetPlayer, action, block, exclude, world, radius, time);
appendExtraFilters(command, extraFilters);
return Bukkit.dispatchCommand(player, command.toString());
}
private String sanitizeAction(String action) {
String normalized = action.trim();
if (normalized.matches("[a-zA-Z0-9_,:+\\-]+")) {
return normalized;
}
return "";
}
private String sanitizeBlock(String block) {
String normalized = block.trim();
if (normalized.matches("[a-zA-Z0-9_,:]+")) {
return normalized;
}
return "";
}
private void appendPlayerFilter(StringBuilder command, String targetPlayer) { private void appendPlayerFilter(StringBuilder command, String targetPlayer) {
String sanitized = sanitizePlayerName(targetPlayer); String sanitized = sanitizePlayerName(targetPlayer);
if (!sanitized.isEmpty()) { if (!sanitized.isEmpty()) {
@@ -61,6 +97,58 @@ public class CoreProtectIntegration {
} }
} }
private void appendActionFilter(StringBuilder command, String action) {
String sanitized = sanitizeAction(action);
if (!sanitized.isEmpty()) {
command.append(" a:").append(sanitized);
}
}
private void appendBlockFilter(StringBuilder command, String block) {
String sanitized = sanitizeBlock(block);
if (!sanitized.isEmpty()) {
command.append(" b:").append(sanitized);
}
}
private void appendExcludeFilter(StringBuilder command, String exclude) {
String sanitized = sanitizeBlock(exclude);
if (!sanitized.isEmpty()) {
command.append(" e:").append(sanitized);
}
}
private void appendWorldFilter(StringBuilder command, String world) {
String sanitized = sanitizeWorld(world);
if (!sanitized.isEmpty()) {
command.append(" w:").append(sanitized);
}
}
private void appendCommonFilters(StringBuilder command, String targetPlayer, String action, String block,
String exclude, String world, int radius, String time) {
appendPlayerFilter(command, targetPlayer);
appendActionFilter(command, action);
appendBlockFilter(command, block);
appendExcludeFilter(command, exclude);
appendWorldFilter(command, world);
command.append(" r:").append(normalizeRadius(radius));
command.append(" t:").append(normalizeTime(time));
}
private void appendExtraFilters(StringBuilder command, String extraFilters) {
if (extraFilters == null || extraFilters.isBlank()) {
return;
}
String[] tokens = extraFilters.split(";");
for (String token : tokens) {
String normalized = token.trim();
if (normalized.matches("[a-zA-Z]+:[A-Za-z0-9_.,:+\\-]+")) {
command.append(' ').append(normalized);
}
}
}
private int normalizeRadius(int radius) { private int normalizeRadius(int radius) {
return Math.max(1, Math.min(100, radius)); return Math.max(1, Math.min(100, radius));
} }
@@ -89,4 +177,15 @@ public class CoreProtectIntegration {
} }
return ""; return "";
} }
private String sanitizeWorld(String world) {
if (world == null || world.isBlank()) {
return "";
}
String normalized = world.trim();
if (normalized.matches("[A-Za-z0-9_:\\-]+")) {
return normalized;
}
return "";
}
} }

View File

@@ -6,7 +6,6 @@ import net.luckperms.api.model.user.User;
import net.luckperms.api.node.Node; import net.luckperms.api.node.Node;
import org.blz.adminmode.config.ConfigManager; import org.blz.adminmode.config.ConfigManager;
import org.blz.adminmode.moderation.ModeSession; import org.blz.adminmode.moderation.ModeSession;
import org.blz.adminmode.moderation.ModeratorProfile;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
@@ -21,64 +20,54 @@ public class LuckPermsIntegration {
} }
public String resolveCurrentGroup(Player player) { public String resolveCurrentGroup(Player player) {
if (!configManager.isUseLuckPerms()) { if (!configManager.isUseLuckPerms()) return configManager.getBaseGroup();
return configManager.getBaseGroup();
}
try { try {
User user = resolveUser(player); User user = resolveUser(player);
if (user == null) { return user != null ? user.getPrimaryGroup() : configManager.getBaseGroup();
return configManager.getBaseGroup(); } catch (RuntimeException e) {
} plugin.getLogger().warning("Не удалось определить текущую группу LuckPerms для " + player.getName() + ": " + e.getMessage());
return user.getPrimaryGroup();
} catch (IllegalStateException exception) {
return configManager.getBaseGroup(); return configManager.getBaseGroup();
} }
} }
public void enterMode(Player player, ModeratorProfile profile) { public void enterMode(Player player) {
if (!configManager.isUseLuckPerms()) { if (!configManager.isUseLuckPerms()) {
player.setOp(true); plugin.getLogger().warning("LuckPerms-интеграция отключена в конфиге. Временные группы для " + player.getName() + " не применены.");
return; return;
} }
try { try {
User user = resolveUser(player); User user = resolveUser(player);
if (user == null) { if (user == null) return;
return; for (String group : configManager.getLuckPermsGroups()) {
user.data().add(groupNode(group));
} }
removeKnownGroups(user);
user.data().add(groupNode(configManager.getActiveGroup(profile)));
saveUser(user); saveUser(user);
} catch (IllegalStateException exception) { } catch (RuntimeException e) {
player.setOp(true); plugin.getLogger().warning("Не удалось выдать временные группы LuckPerms для " + player.getName() + ": " + e.getMessage());
plugin.getLogger().warning("LuckPerms не найден, используется OP для " + player.getName());
} }
} }
public void exitMode(Player player, ModeSession session) { public void exitMode(Player player, ModeSession session) {
if (!configManager.isUseLuckPerms()) { if (!configManager.isUseLuckPerms()) {
player.setOp(false); plugin.getLogger().warning("LuckPerms-интеграция отключена в конфиге. Группы для " + player.getName() + " не менялись.");
return; return;
} }
try { try {
User user = resolveUser(player); User user = resolveUser(player);
if (user == null) { if (user == null) return;
return; if (configManager.isRemoveGroupsOnDisable()) {
removeTemporaryGroups(user);
} }
removeKnownGroups(user);
String restoreGroup = session.getPreviousGroup();
if (restoreGroup == null || restoreGroup.isBlank() || "op".equalsIgnoreCase(restoreGroup)) {
restoreGroup = configManager.getBaseGroup();
}
user.data().add(groupNode(restoreGroup));
saveUser(user); saveUser(user);
} catch (IllegalStateException exception) { } catch (RuntimeException e) {
player.setOp(false); plugin.getLogger().warning("Не удалось восстановить группы LuckPerms для " + player.getName() + ": " + e.getMessage());
plugin.getLogger().warning("LuckPerms не найден, снят OP у " + player.getName()); }
}
private void removeTemporaryGroups(User user) {
user.data().remove(groupNode(configManager.getAdminGroup()));
for (String group : configManager.getLuckPermsGroups()) {
user.data().remove(groupNode(group));
} }
} }
@@ -91,12 +80,6 @@ public class LuckPermsIntegration {
return user; return user;
} }
private void removeKnownGroups(User user) {
user.data().remove(groupNode(configManager.getBaseGroup()));
user.data().remove(groupNode(configManager.getSoftGroup()));
user.data().remove(groupNode(configManager.getHardGroup()));
}
private Node groupNode(String group) { private Node groupNode(String group) {
return Node.builder("group." + group).build(); return Node.builder("group." + group).build();
} }

View File

@@ -97,21 +97,14 @@ public class AdminModeListener implements Listener {
if (!adminModeCommand.isInAdminMode(player.getUniqueId())) return; if (!adminModeCommand.isInAdminMode(player.getUniqueId())) return;
if ((e.getAction() == Action.RIGHT_CLICK_AIR || e.getAction() == Action.RIGHT_CLICK_BLOCK) if (e.getAction() == Action.RIGHT_CLICK_BLOCK && e.getClickedBlock() != null
&& e.getItem() != null && e.getClickedBlock().getType() == Material.ENDER_CHEST) {
&& modeManager.isPanelItem(e.getItem())) {
e.setCancelled(true); e.setCancelled(true);
player.sendMessage(configManager.getPrefixedMessage("§cВ admin mode нельзя взаимодействовать с эндер-сундуком."));
String panelType = modeManager.getPanelType(e.getItem());
if (ModeManager.PANEL_CP_INSPECTOR.equals(panelType)) {
adminModeCommand.toggleInspector(player);
return;
}
dialogService.openCurrentPanel(player);
return; return;
} }
if (!configManager.isPreventItemUse()) return; if (!configManager.isPreventItemUse()) return;
if (e.getAction() == Action.RIGHT_CLICK_BLOCK) { if (e.getAction() == Action.RIGHT_CLICK_BLOCK) {

View File

@@ -4,8 +4,6 @@ import java.util.List;
import org.blz.adminmode.commands.AdminModeCommand; import org.blz.adminmode.commands.AdminModeCommand;
import org.blz.adminmode.config.ConfigManager; import org.blz.adminmode.config.ConfigManager;
import org.blz.adminmode.moderation.ModeratorProfile;
import org.bukkit.GameMode;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
@@ -26,29 +24,11 @@ public class GameModeChangeListener implements Listener {
@EventHandler(priority = EventPriority.HIGHEST) @EventHandler(priority = EventPriority.HIGHEST)
public void onGameModeChange(PlayerGameModeChangeEvent event) { public void onGameModeChange(PlayerGameModeChangeEvent event) {
Player player = event.getPlayer(); Player player = event.getPlayer();
if (!adminModeCommand.isInAdminMode(player.getUniqueId())) return;
if (!adminModeCommand.isInAdminMode(player.getUniqueId())) {
return;
}
ModeratorProfile activeProfile = adminModeCommand.getActiveProfile(player.getUniqueId());
boolean hardProfile = activeProfile != null && activeProfile.isHardProfile();
if (event.getNewGameMode() == GameMode.CREATIVE && !hardProfile) {
event.setCancelled(true);
player.sendMessage(configManager.getPrefixedMessage("§c✖ В режиме Soft Moder креатив запрещен!"));
return;
}
if (event.getNewGameMode() == GameMode.CREATIVE && hardProfile) {
return;
}
List<String> allowedModes = configManager.getAllowedGameModes(); List<String> allowedModes = configManager.getAllowedGameModes();
if (!allowedModes.isEmpty()) { if (!allowedModes.isEmpty()) {
String newMode = event.getNewGameMode().name(); String newMode = event.getNewGameMode().name();
if (!allowedModes.contains(newMode)) { if (!allowedModes.contains(newMode)) {
event.setCancelled(true); event.setCancelled(true);
player.sendMessage(configManager.getPrefixedMessage("§c✖ В админ моде можно переключаться только на разрешенные режимы!")); player.sendMessage(configManager.getPrefixedMessage("§c✖ В админ моде можно переключаться только на разрешенные режимы!"));
@@ -57,15 +37,18 @@ public class GameModeChangeListener implements Listener {
} }
} }
// Включаем полет после смены режима, если это разрешено if (event.getNewGameMode() == org.bukkit.GameMode.CREATIVE && !player.hasPermission("adminmode.chief")) {
event.setCancelled(true);
player.sendMessage(configManager.getPrefixedMessage("§cКреатив в admin mode доступен только главным модераторам."));
return;
}
if (configManager.isAllowFlightInSurvival()) { if (configManager.isAllowFlightInSurvival()) {
player.getServer().getScheduler().runTaskLater( player.getServer().getScheduler().runTaskLater(
player.getServer().getPluginManager().getPlugin("AdminMode"), player.getServer().getPluginManager().getPlugin("AdminMode"),
() -> { () -> {
player.setAllowFlight(true); player.setAllowFlight(true);
if (!player.isFlying()) { if (!player.isFlying()) player.setFlying(true);
player.setFlying(true);
}
}, },
1L); 1L);
} }
@@ -74,18 +57,8 @@ public class GameModeChangeListener implements Listener {
@EventHandler(priority = EventPriority.HIGHEST) @EventHandler(priority = EventPriority.HIGHEST)
public void onToggleFlight(PlayerToggleFlightEvent event) { public void onToggleFlight(PlayerToggleFlightEvent event) {
Player player = event.getPlayer(); Player player = event.getPlayer();
if (!adminModeCommand.isInAdminMode(player.getUniqueId())) return;
if (!adminModeCommand.isInAdminMode(player.getUniqueId())) { if (!configManager.isAllowFlightInSurvival()) return;
return; if (!player.getAllowFlight()) player.setAllowFlight(true);
}
if (!configManager.isAllowFlightInSurvival()) {
return;
}
// Всегда разрешаем полет в админ моде
if (!player.getAllowFlight()) {
player.setAllowFlight(true);
}
} }
} }

View File

View File

@@ -15,6 +15,6 @@ public class PlayerDeathListener implements Listener {
@EventHandler @EventHandler
public void onPlayerDeath(PlayerDeathEvent event) { public void onPlayerDeath(PlayerDeathEvent event) {
modeManager.handleHardModeDeath(event); modeManager.handleAdminDeath(event);
} }
} }

View File

@@ -26,13 +26,14 @@ public class PlayerSessionListener implements Listener {
@EventHandler @EventHandler
public void onJoin(PlayerJoinEvent event) { public void onJoin(PlayerJoinEvent event) {
Player player = event.getPlayer(); Player player = event.getPlayer();
modeManager.refreshVanishFor(player);
if (!modeManager.hasSession(player.getUniqueId())) { if (!modeManager.hasSession(player.getUniqueId())) {
return; return;
} }
player.getServer().getScheduler().runTaskLater( player.getServer().getScheduler().runTaskLater(
org.blz.adminmode.AdminMode.getInstance(), org.blz.adminmode.AdminMode.getInstance(),
() -> adminModeCommand.recoverSession(player), () -> adminModeCommand.resumeMode(player),
1L); 1L);
} }
} }

View File

@@ -10,6 +10,7 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.block.Action; import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent; import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.EquipmentSlot;
@@ -23,6 +24,7 @@ import java.util.*;
public class BelzeBoolManager implements Listener { public class BelzeBoolManager implements Listener {
private static final String SPECIAL_PERMISSION = "adminmode.special.belzebool";
private final Plugin plugin; private final Plugin plugin;
private final String BELZEBOOL_NAME = "BelzeBool"; private final String BELZEBOOL_NAME = "BelzeBool";
private final String POCKET_DIMENSION_WORLD = "pocket_dimension"; private final String POCKET_DIMENSION_WORLD = "pocket_dimension";
@@ -39,6 +41,9 @@ public class BelzeBoolManager implements Listener {
// Vanish State: UUID -> isVanished // Vanish State: UUID -> isVanished
private final Set<UUID> vanishedPlayers = new HashSet<>(); private final Set<UUID> vanishedPlayers = new HashSet<>();
// Pocket return state: player UUID -> original location before abduction
private final Map<UUID, Location> pocketReturnLocations = new HashMap<>();
// Input Tracking // Input Tracking
private final Map<UUID, List<Long>> rightClickTimestamps = new HashMap<>(); private final Map<UUID, List<Long>> rightClickTimestamps = new HashMap<>();
@@ -49,7 +54,7 @@ public class BelzeBoolManager implements Listener {
} }
public boolean isBelzeBool(Player player) { public boolean isBelzeBool(Player player) {
return player.getName().equalsIgnoreCase(BELZEBOOL_NAME); return player.hasPermission(SPECIAL_PERMISSION) && player.getName().equalsIgnoreCase(BELZEBOOL_NAME);
} }
// --- Casting Mode Logic --- // --- Casting Mode Logic ---
@@ -278,6 +283,9 @@ public class BelzeBoolManager implements Listener {
target.getWorld().spawnParticle(Particle.SCULK_SOUL, target.getLocation(), 50, 0.5, 1, 0.5, 0.1); target.getWorld().spawnParticle(Particle.SCULK_SOUL, target.getLocation(), 50, 0.5, 1, 0.5, 0.1);
Location originalLoc = target.getLocation(); Location originalLoc = target.getLocation();
if (target instanceof Player targetPlayer) {
pocketReturnLocations.put(targetPlayer.getUniqueId(), originalLoc.clone());
}
// 2. Teleport after 5 seconds // 2. Teleport after 5 seconds
new BukkitRunnable() { new BukkitRunnable() {
@@ -320,6 +328,8 @@ public class BelzeBoolManager implements Listener {
if (target instanceof Player targetP) { if (target instanceof Player targetP) {
targetP.removePotionEffect(PotionEffectType.LEVITATION); targetP.removePotionEffect(PotionEffectType.LEVITATION);
targetP.removePotionEffect(PotionEffectType.DARKNESS); targetP.removePotionEffect(PotionEffectType.DARKNESS);
targetP.removePotionEffect(PotionEffectType.SLOWNESS);
pocketReturnLocations.remove(targetP.getUniqueId());
} }
} }
} }
@@ -410,4 +420,52 @@ public class BelzeBoolManager implements Listener {
player.sendMessage(Component.text("You have vanished.", NamedTextColor.AQUA)); player.sendMessage(Component.text("You have vanished.", NamedTextColor.AQUA));
} }
} }
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
Location returnLocation = pocketReturnLocations.remove(player.getUniqueId());
if (returnLocation == null) {
return;
}
Bukkit.getScheduler().runTask(plugin, () -> {
player.teleport(returnLocation);
player.removePotionEffect(PotionEffectType.LEVITATION);
player.removePotionEffect(PotionEffectType.DARKNESS);
player.removePotionEffect(PotionEffectType.SLOWNESS);
});
}
public boolean forceDisable(Player player) {
boolean changed = false;
UUID uuid = player.getUniqueId();
if (castingModePlayers.remove(uuid)) {
changed = true;
}
rightClickTimestamps.remove(uuid);
selectedSpellIndex.remove(uuid);
cooldowns.remove(uuid);
if (vanishedPlayers.remove(uuid)) {
for (Player online : Bukkit.getOnlinePlayers()) {
online.showPlayer(plugin, player);
}
changed = true;
}
Location returnLocation = pocketReturnLocations.remove(uuid);
if (returnLocation != null) {
changed = true;
if (player.isOnline()) {
player.teleport(returnLocation);
}
player.removePotionEffect(PotionEffectType.LEVITATION);
player.removePotionEffect(PotionEffectType.DARKNESS);
player.removePotionEffect(PotionEffectType.SLOWNESS);
}
return changed;
}
} }

View File

@@ -8,7 +8,6 @@ import org.blz.adminmode.session.SessionStorage;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.attribute.Attribute; import org.bukkit.attribute.Attribute;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
@@ -16,46 +15,44 @@ import org.bukkit.entity.Player;
import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
public class ModeManager { public class ModeManager {
public static final String PANEL_SOFT = "soft";
public static final String PANEL_HARD = "hard";
public static final String PANEL_CP_INSPECTOR = "cp_inspector";
private final Plugin plugin; private final Plugin plugin;
private final ConfigManager configManager; private final ConfigManager configManager;
private final LuckPermsIntegration luckPermsIntegration; private final LuckPermsIntegration luckPermsIntegration;
private final SessionStorage sessionStorage; private final SessionStorage sessionStorage;
private final ModeSessionLogger sessionLogger; private final ModeSessionLogger sessionLogger;
private final NamespacedKey panelTypeKey; private final ModeratorPreferencesStore preferencesStore;
private final Map<UUID, ModeSession> activeSessions = new HashMap<>(); private final Map<UUID, ModeSession> activeSessions = new HashMap<>();
private final Set<UUID> vanishedModerators = new HashSet<>();
public ModeManager( public ModeManager(
Plugin plugin, Plugin plugin,
ConfigManager configManager, ConfigManager configManager,
LuckPermsIntegration luckPermsIntegration, LuckPermsIntegration luckPermsIntegration,
SessionStorage sessionStorage, SessionStorage sessionStorage,
ModeSessionLogger sessionLogger) { ModeSessionLogger sessionLogger,
ModeratorPreferencesStore preferencesStore) {
this.plugin = plugin; this.plugin = plugin;
this.configManager = configManager; this.configManager = configManager;
this.luckPermsIntegration = luckPermsIntegration; this.luckPermsIntegration = luckPermsIntegration;
this.sessionStorage = sessionStorage; this.sessionStorage = sessionStorage;
this.sessionLogger = sessionLogger; this.sessionLogger = sessionLogger;
this.panelTypeKey = new NamespacedKey(plugin, "panel_type"); this.preferencesStore = preferencesStore;
this.activeSessions.putAll(sessionStorage.loadSessions()); this.activeSessions.putAll(sessionStorage.loadSessions());
} }
@@ -71,37 +68,32 @@ public class ModeManager {
return player == null ? null : getSession(player.getUniqueId()); return player == null ? null : getSession(player.getUniqueId());
} }
public ModeratorProfile getProfile(UUID playerId) {
ModeSession session = getSession(playerId);
return session == null ? null : session.getProfile();
}
public List<ModeratorProfile> getAvailableProfiles(Player player) {
return ModeratorProfile.availableFor(player);
}
public Collection<ModeSession> getActiveSessions() { public Collection<ModeSession> getActiveSessions() {
return new ArrayList<>(activeSessions.values()); return new ArrayList<>(activeSessions.values());
} }
public boolean enterMode(Player player, ModeratorProfile profile) { public boolean enterMode(Player player) {
if (player == null || profile == null || hasSession(player.getUniqueId())) { if (player == null || hasSession(player.getUniqueId())) return false;
return false; if (!player.hasPermission(ModeratorProfile.ADMIN.getPermission())) return false;
}
String previousGroup = luckPermsIntegration.resolveCurrentGroup(player); String previousGroup = luckPermsIntegration.resolveCurrentGroup(player);
String activeGroup = configManager.getActiveGroup(profile); ModeSession session = ModeSession.capture(player, ModeratorProfile.ADMIN, previousGroup, configManager.getAdminGroup());
ModeSession session = ModeSession.capture(player, profile, previousGroup, activeGroup); try {
activeSessions.put(player.getUniqueId(), session); activeSessions.put(player.getUniqueId(), session);
persistSessions(); persistSessions();
preparePlayerForModeration(player, profile); preparePlayerForModeration(player);
luckPermsIntegration.enterMode(player, profile); luckPermsIntegration.enterMode(player);
sessionLogger.logEnter(session); sessionLogger.logEnter(session);
String enterMessage = profile.isHardProfile() ? configManager.getMsgEnterHard() : configManager.getMsgEnterSoft(); player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgEnter()));
player.sendMessage(configManager.getPrefixedMessage(enterMessage)); return true;
return true; } catch (RuntimeException exception) {
activeSessions.remove(player.getUniqueId());
persistSessions();
plugin.getLogger().warning("AdminMode: не удалось включить режим для " + player.getName() + ": " + exception.getMessage());
return false;
}
} }
public boolean exitMode(Player player) { public boolean exitMode(Player player) {
@@ -112,6 +104,19 @@ public class ModeManager {
return finishSession(player, true); return finishSession(player, true);
} }
public boolean resumeMode(Player player) {
if (player == null || !hasSession(player.getUniqueId())) {
return false;
}
applyResumedModerationState(player);
luckPermsIntegration.enterMode(player);
if (isVanished(player.getUniqueId())) {
reapplyVanish(player);
}
return true;
}
public void markQuit(Player player) { public void markQuit(Player player) {
if (player != null && hasSession(player.getUniqueId())) { if (player != null && hasSession(player.getUniqueId())) {
persistSessions(); persistSessions();
@@ -119,33 +124,48 @@ public class ModeManager {
} }
public boolean canUseHardActions(CommandSender sender) { public boolean canUseHardActions(CommandSender sender) {
if (sender.hasPermission("adminmode.admin")) { return sender.hasPermission("adminmode.admin");
return true; }
}
if (!(sender instanceof Player player)) { public boolean isVanished(UUID playerId) {
return vanishedModerators.contains(playerId);
}
public boolean toggleVanish(Player player) {
if (player == null || !hasSession(player.getUniqueId())) {
return false; return false;
} }
if (isVanished(player.getUniqueId())) {
disableVanish(player);
player.sendMessage(configManager.getPrefixedMessage("§7Vanish отключён."));
return false;
}
enableVanish(player);
player.sendMessage(configManager.getPrefixedMessage("§aVanish включён."));
return true;
}
ModeSession session = getSession(player); public void refreshVanishFor(Player viewer) {
return session != null && session.getProfile().isHardProfile(); if (viewer == null) {
return;
}
for (UUID vanishedId : vanishedModerators) {
Player vanishedPlayer = plugin.getServer().getPlayer(vanishedId);
if (vanishedPlayer != null && !viewer.getUniqueId().equals(vanishedId)) {
viewer.hidePlayer(plugin, vanishedPlayer);
}
}
} }
public boolean teleportToPlayer(Player moderator, Player target) { public boolean teleportToPlayer(Player moderator, Player target) {
if (moderator == null || target == null) { if (moderator == null || target == null) return false;
return false;
}
moderator.teleport(target.getLocation()); moderator.teleport(target.getLocation());
moderator.sendMessage(configManager.getPrefixedMessage("§aТелепортация к §f" + target.getName() + "§a выполнена.")); moderator.sendMessage(configManager.getPrefixedMessage("§aТелепортация к §f" + target.getName() + "§a выполнена."));
return true; return true;
} }
public boolean teleportPlayerHere(Player moderator, Player target) { public boolean teleportPlayerHere(Player moderator, Player target) {
if (moderator == null || target == null) { if (moderator == null || target == null) return false;
return false;
}
target.teleport(moderator.getLocation()); target.teleport(moderator.getLocation());
moderator.sendMessage(configManager.getPrefixedMessage("§aИгрок §f" + target.getName() + "§a телепортирован к вам.")); moderator.sendMessage(configManager.getPrefixedMessage("§aИгрок §f" + target.getName() + "§a телепортирован к вам."));
target.sendMessage(configManager.getPrefixedMessage("§eВас телепортировал модератор §f" + moderator.getName())); target.sendMessage(configManager.getPrefixedMessage("§eВас телепортировал модератор §f" + moderator.getName()));
@@ -153,15 +173,11 @@ public class ModeManager {
} }
public boolean spectatePlayer(Player moderator, Player target) { public boolean spectatePlayer(Player moderator, Player target) {
if (moderator == null || target == null) { if (moderator == null || target == null) return false;
return false;
}
if (moderator.equals(target)) { if (moderator.equals(target)) {
moderator.sendMessage(configManager.getPrefixedMessage("§cНельзя спектейтить самого себя.")); moderator.sendMessage(configManager.getPrefixedMessage("§cНельзя спектейтить самого себя."));
return false; return false;
} }
moderator.setGameMode(GameMode.SPECTATOR); moderator.setGameMode(GameMode.SPECTATOR);
moderator.setSpectatorTarget((Entity) target); moderator.setSpectatorTarget((Entity) target);
moderator.sendMessage(configManager.getPrefixedMessage("§bВы наблюдаете за §f" + target.getName())); moderator.sendMessage(configManager.getPrefixedMessage("§bВы наблюдаете за §f" + target.getName()));
@@ -169,20 +185,14 @@ public class ModeManager {
} }
public boolean openInvsee(Player moderator, Player target) { public boolean openInvsee(Player moderator, Player target) {
if (moderator == null || target == null) { if (moderator == null || target == null) return false;
return false; moderator.getServer().dispatchCommand(moderator, "invsee " + target.getName());
}
moderator.openInventory(target.getInventory());
moderator.sendMessage(configManager.getPrefixedMessage("§7Открыт инвентарь игрока §f" + target.getName())); moderator.sendMessage(configManager.getPrefixedMessage("§7Открыт инвентарь игрока §f" + target.getName()));
return true; return true;
} }
public void applyAdminSettings(Player player, float flySpeed, float walkSpeed, boolean invulnerable) { public void applyAdminSettings(Player player, float flySpeed, float walkSpeed, boolean invulnerable) {
if (!hasSession(player.getUniqueId())) { if (!hasSession(player.getUniqueId())) return;
return;
}
player.setAllowFlight(true); player.setAllowFlight(true);
player.setFlying(true); player.setFlying(true);
player.setFlySpeed(clampSpeed(flySpeed, 0.1f)); player.setFlySpeed(clampSpeed(flySpeed, 0.1f));
@@ -192,7 +202,7 @@ public class ModeManager {
public List<Player> listOtherOnlinePlayers(Player viewer) { public List<Player> listOtherOnlinePlayers(Player viewer) {
List<Player> players = new ArrayList<>(plugin.getServer().getOnlinePlayers()); List<Player> players = new ArrayList<>(plugin.getServer().getOnlinePlayers());
players.removeIf(player -> viewer != null && player.getUniqueId().equals(viewer.getUniqueId())); players.removeIf(p -> viewer != null && p.getUniqueId().equals(viewer.getUniqueId()));
players.sort(Comparator.comparing(Player::getName, String.CASE_INSENSITIVE_ORDER)); players.sort(Comparator.comparing(Player::getName, String.CASE_INSENSITIVE_ORDER));
return players; return players;
} }
@@ -200,10 +210,10 @@ public class ModeManager {
public List<Component> buildOnlineSummary(Player viewer) { public List<Component> buildOnlineSummary(Player viewer) {
List<Component> lines = new ArrayList<>(); List<Component> lines = new ArrayList<>();
for (Player target : listOtherOnlinePlayers(viewer)) { for (Player target : listOtherOnlinePlayers(viewer)) {
Location location = target.getLocation(); Location loc = target.getLocation();
lines.add(Component.text( lines.add(Component.text(
target.getName() + " | " + target.getWorld().getName() + " | " + target.getName() + " | " + target.getWorld().getName() + " | " +
location.getBlockX() + ", " + location.getBlockY() + ", " + location.getBlockZ(), loc.getBlockX() + ", " + loc.getBlockY() + ", " + loc.getBlockZ(),
NamedTextColor.GRAY)); NamedTextColor.GRAY));
} }
if (lines.isEmpty()) { if (lines.isEmpty()) {
@@ -212,12 +222,11 @@ public class ModeManager {
return lines; return lines;
} }
public void handleHardModeDeath(PlayerDeathEvent event) { /** Keep inventory/levels and teleport back on death while in admin mode. */
public void handleAdminDeath(PlayerDeathEvent event) {
Player player = event.getEntity(); Player player = event.getEntity();
ModeSession session = getSession(player); ModeSession session = getSession(player);
if (session == null || !session.getProfile().isHardProfile()) { if (session == null) return;
return;
}
event.setKeepInventory(true); event.setKeepInventory(true);
event.setKeepLevel(true); event.setKeepLevel(true);
@@ -228,45 +237,18 @@ public class ModeManager {
} }
} }
public boolean isPanelItem(ItemStack itemStack) {
return getPanelType(itemStack) != null;
}
public String getPanelType(ItemStack itemStack) {
if (itemStack == null || itemStack.getType() == Material.AIR || !itemStack.hasItemMeta()) {
return null;
}
ItemMeta itemMeta = itemStack.getItemMeta();
PersistentDataContainer container = itemMeta.getPersistentDataContainer();
if (!container.has(panelTypeKey, PersistentDataType.STRING)) {
return null;
}
return container.get(panelTypeKey, PersistentDataType.STRING);
}
public boolean isReadOnlyViewedInventory(Player viewer, org.bukkit.inventory.Inventory inventory) { public boolean isReadOnlyViewedInventory(Player viewer, org.bukkit.inventory.Inventory inventory) {
if (viewer == null || inventory == null || inventory.getHolder() == null) { if (viewer == null || inventory == null || inventory.getHolder() == null) return false;
return false; if (!(inventory.getHolder() instanceof Player target)) return false;
}
if (!(inventory.getHolder() instanceof Player target)) {
return false;
}
return hasSession(viewer.getUniqueId()) && !viewer.getUniqueId().equals(target.getUniqueId()); return hasSession(viewer.getUniqueId()) && !viewer.getUniqueId().equals(target.getUniqueId());
} }
private boolean finishSession(Player player, boolean recovered) { private boolean finishSession(Player player, boolean recovered) {
if (player == null) { if (player == null) return false;
return false;
}
ModeSession session = activeSessions.remove(player.getUniqueId()); ModeSession session = activeSessions.remove(player.getUniqueId());
if (session == null) { if (session == null) return false;
return false;
}
disableVanish(player);
restorePlayerState(player, session); restorePlayerState(player, session);
luckPermsIntegration.exitMode(player, session); luckPermsIntegration.exitMode(player, session);
persistSessions(); persistSessions();
@@ -281,14 +263,14 @@ public class ModeManager {
return true; return true;
} }
private void preparePlayerForModeration(Player player, ModeratorProfile profile) { private void preparePlayerForModeration(Player player) {
PlayerInventory inventory = player.getInventory(); PlayerInventory inventory = player.getInventory();
inventory.clear(); inventory.clear();
inventory.setArmorContents(new ItemStack[4]); inventory.setArmorContents(new ItemStack[4]);
inventory.setItemInOffHand(new ItemStack(Material.AIR)); inventory.setItemInOffHand(ItemStack.empty());
player.closeInventory(); player.closeInventory();
player.setSpectatorTarget(null); clearSpectatorTargetIfNeeded(player);
for (PotionEffect effect : player.getActivePotionEffects()) { for (PotionEffect effect : player.getActivePotionEffects()) {
player.removePotionEffect(effect.getType()); player.removePotionEffect(effect.getType());
} }
@@ -305,27 +287,35 @@ public class ModeManager {
player.setFlying(true); player.setFlying(true);
player.setFlySpeed(clampSpeed(configManager.getCustomFlySpeed(), 0.1f)); player.setFlySpeed(clampSpeed(configManager.getCustomFlySpeed(), 0.1f));
player.setWalkSpeed(clampSpeed(configManager.getCustomWalkSpeed(), 0.1f)); player.setWalkSpeed(clampSpeed(configManager.getCustomWalkSpeed(), 0.1f));
player.setInvulnerable(true); player.setInvulnerable(configManager.isGodMode());
player.setGameMode(profile.isHardProfile() ? GameMode.CREATIVE : GameMode.SPECTATOR); player.setGameMode(resolveAdminGameMode(player));
inventory.setItem(0, createPanelItem(profile.isHardProfile() ? configManager.getHardPanelMaterial() : configManager.getSoftPanelMaterial(),
profile.isHardProfile() ? "Hard Panel" : "Soft Panel",
profile.isHardProfile() ? NamedTextColor.RED : NamedTextColor.AQUA,
profile.isHardProfile() ? PANEL_HARD : PANEL_SOFT));
if (profile.isHardProfile()) {
inventory.setItem(1, createPanelItem(
configManager.getCpInspectorMaterial(),
"CP Inspector",
NamedTextColor.YELLOW,
PANEL_CP_INSPECTOR));
}
if (configManager.isGivePresetBlocks()) { if (configManager.isGivePresetBlocks()) {
givePresetBlocks(player); givePresetBlocks(player);
} }
} }
private void applyResumedModerationState(Player player) {
player.closeInventory();
clearSpectatorTargetIfNeeded(player);
player.setFireTicks(0);
player.setInvulnerable(configManager.isGodMode());
player.setFlySpeed(clampSpeed(player.getFlySpeed(), configManager.getCustomFlySpeed()));
player.setWalkSpeed(clampSpeed(player.getWalkSpeed(), configManager.getCustomWalkSpeed()));
GameMode resumedMode = isAdminGameModeAllowedForPlayer(player, player.getGameMode())
? player.getGameMode()
: resolveAdminGameMode(player);
player.setGameMode(resumedMode);
if (!player.getAllowFlight() && (resumedMode != GameMode.SURVIVAL || configManager.isAllowFlightInSurvival())) {
player.setAllowFlight(true);
}
if (player.getAllowFlight() && resumedMode != GameMode.SURVIVAL) {
player.setFlying(true);
}
}
private void restorePlayerState(Player player, ModeSession session) { private void restorePlayerState(Player player, ModeSession session) {
PlayerInventory inventory = player.getInventory(); PlayerInventory inventory = player.getInventory();
inventory.clear(); inventory.clear();
@@ -333,7 +323,7 @@ public class ModeManager {
inventory.setArmorContents(session.getArmorContents()); inventory.setArmorContents(session.getArmorContents());
inventory.setItemInOffHand(session.getOffHandItem()); inventory.setItemInOffHand(session.getOffHandItem());
player.setSpectatorTarget(null); clearSpectatorTargetIfNeeded(player);
player.setInvulnerable(false); player.setInvulnerable(false);
player.setAllowFlight(false); player.setAllowFlight(false);
player.setFlying(false); player.setFlying(false);
@@ -346,7 +336,11 @@ public class ModeManager {
player.setGameMode(session.getGameMode()); player.setGameMode(session.getGameMode());
if (session.getLocation() != null) { if (session.getLocation() != null) {
player.teleport(session.getLocation()); player.teleportAsync(session.getLocation()).thenAccept(success -> {
if (!success) {
plugin.getLogger().warning("AdminMode: Не удалось телепортировать " + player.getName() + " обратно.");
}
});
} }
player.setHealth(Math.min(resolveMaxHealth(player), session.getHealth())); player.setHealth(Math.min(resolveMaxHealth(player), session.getHealth()));
@@ -377,26 +371,96 @@ public class ModeManager {
String[] parts = blockString.split(":"); String[] parts = blockString.split(":");
Material material = Material.valueOf(parts[0].trim().toUpperCase()); Material material = Material.valueOf(parts[0].trim().toUpperCase());
int amount = parts.length > 1 ? Integer.parseInt(parts[1].trim()) : 64; int amount = parts.length > 1 ? Integer.parseInt(parts[1].trim()) : 64;
player.getInventory().addItem(new ItemStack(material, Math.max(1, amount))); player.getInventory().addItem(ItemStack.of(material, Math.max(1, amount)));
} catch (Exception exception) { } catch (Exception e) {
plugin.getLogger().warning("Неверный блок в preset_blocks: " + blockString); plugin.getLogger().warning("Неверный блок в preset_blocks: " + blockString);
} }
} }
} }
private ItemStack createPanelItem(Material material, String title, NamedTextColor color, String panelType) {
ItemStack itemStack = new ItemStack(material);
ItemMeta itemMeta = itemStack.getItemMeta();
itemMeta.displayName(Component.text("[" + title + "]", color));
itemMeta.getPersistentDataContainer().set(panelTypeKey, PersistentDataType.STRING, panelType);
itemStack.setItemMeta(itemMeta);
return itemStack;
}
private void persistSessions() { private void persistSessions() {
sessionStorage.saveSessions(activeSessions); sessionStorage.saveSessions(activeSessions);
} }
private void clearSpectatorTargetIfNeeded(Player player) {
if (player.getGameMode() == GameMode.SPECTATOR) {
player.setSpectatorTarget(null);
}
}
private GameMode resolveAdminGameMode(Player player) {
if (configManager.isForceSpectator()) {
return GameMode.SPECTATOR;
}
ModeratorPreferencesStore.DefaultEntryMode preferredMode = preferencesStore.get(player.getUniqueId()).defaultEntryMode();
return switch (preferredMode) {
case SURVIVAL -> GameMode.SURVIVAL;
case SPECTATOR -> GameMode.SPECTATOR;
case AUTO -> fallbackDefaultGameMode(player);
};
}
private GameMode fallbackDefaultGameMode(Player player) {
if (canUseCreativeInAdminMode(player)) {
return GameMode.CREATIVE;
}
return GameMode.SPECTATOR;
}
private boolean canUseCreativeInAdminMode(Player player) {
return player.hasPermission("adminmode.chief") && configManager.isChiefCreativeMode();
}
private boolean isAdminGameModeAllowedForPlayer(Player player, GameMode gameMode) {
if (gameMode == null) {
return false;
}
if (configManager.isForceSpectator()) {
return gameMode == GameMode.SPECTATOR;
}
return switch (gameMode) {
case SURVIVAL, SPECTATOR -> true;
case CREATIVE -> canUseCreativeInAdminMode(player);
default -> false;
};
}
private void enableVanish(Player player) {
vanishedModerators.add(player.getUniqueId());
player.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, Integer.MAX_VALUE, 0, false, false, false));
player.setSilent(true);
for (Player online : plugin.getServer().getOnlinePlayers()) {
if (!online.getUniqueId().equals(player.getUniqueId())) {
online.hidePlayer(plugin, player);
}
}
}
private void disableVanish(Player player) {
if (!vanishedModerators.remove(player.getUniqueId())) {
return;
}
player.removePotionEffect(PotionEffectType.INVISIBILITY);
player.setSilent(false);
for (Player online : plugin.getServer().getOnlinePlayers()) {
if (!online.getUniqueId().equals(player.getUniqueId())) {
online.showPlayer(plugin, player);
}
}
}
private void reapplyVanish(Player player) {
vanishedModerators.add(player.getUniqueId());
player.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, Integer.MAX_VALUE, 0, false, false, false));
player.setSilent(true);
for (Player online : plugin.getServer().getOnlinePlayers()) {
if (!online.getUniqueId().equals(player.getUniqueId())) {
online.hidePlayer(plugin, player);
}
}
}
private double resolveMaxHealth(Player player) { private double resolveMaxHealth(Player player) {
if (player.getAttribute(Attribute.MAX_HEALTH) != null) { if (player.getAttribute(Attribute.MAX_HEALTH) != null) {
return player.getAttribute(Attribute.MAX_HEALTH).getValue(); return player.getAttribute(Attribute.MAX_HEALTH).getValue();
@@ -404,7 +468,8 @@ public class ModeManager {
return 20.0D; return 20.0D;
} }
private float clampSpeed(float value, float min) { private float clampSpeed(float value, float fallback) {
return Math.max(min, Math.min(1.0f, value)); float normalized = value <= 0.0f ? fallback : value;
return Math.max(0.1f, Math.min(1.0f, normalized));
} }
} }

View File

@@ -246,7 +246,7 @@ public final class ModeSession {
private static ItemStack cloneItem(ItemStack item) { private static ItemStack cloneItem(ItemStack item) {
if (item == null || item.getType() == Material.AIR) { if (item == null || item.getType() == Material.AIR) {
return new ItemStack(Material.AIR); return ItemStack.empty();
} }
return item.clone(); return item.clone();
} }

View File

@@ -60,7 +60,7 @@ public class ModeSessionLogger {
} }
private String modeSuffix(ModeSession session) { private String modeSuffix(ModeSession session) {
return session.getProfile().isHardProfile() ? "HARD" : "SOFT"; return "ADMIN";
} }
private String formatDuration(long millis) { private String formatDuration(long millis) {

View File

@@ -0,0 +1,280 @@
package org.blz.adminmode.moderation;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.bukkit.OfflinePlayer;
import org.bukkit.plugin.Plugin;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.ArrayList;
import java.util.List;
public class ModeratorAccessManager {
private static final long PERMANENT_EXPIRY = -1L;
private final Plugin plugin;
private final File storageFile;
private final Gson gson;
private final Map<UUID, RestrictionEntry> restrictions = new HashMap<>();
public ModeratorAccessManager(Plugin plugin) {
this.plugin = plugin;
this.storageFile = new File(plugin.getDataFolder(), "moderator-access.json");
this.gson = new GsonBuilder().setPrettyPrinting().create();
load();
}
public Optional<RestrictionEntry> getActiveRestriction(UUID playerId) {
RestrictionEntry entry = restrictions.get(playerId);
if (entry == null) {
return Optional.empty();
}
if (entry.isExpired()) {
restrictions.remove(playerId);
save();
return Optional.empty();
}
return Optional.of(entry);
}
public boolean isRestricted(UUID playerId) {
return getActiveRestriction(playerId).isPresent();
}
public RestrictionEntry restrict(OfflinePlayer target, String targetName, String actorName, Long expiresAt, String reason) {
RestrictionEntry entry = new RestrictionEntry(
target.getUniqueId(),
targetName,
actorName,
reason == null ? "" : reason.trim(),
System.currentTimeMillis(),
expiresAt == null ? PERMANENT_EXPIRY : expiresAt);
restrictions.put(target.getUniqueId(), entry);
save();
return entry;
}
public boolean clearRestriction(UUID playerId) {
RestrictionEntry removed = restrictions.remove(playerId);
if (removed != null) {
save();
return true;
}
return false;
}
public List<String> getRestrictedPlayerNames() {
List<String> names = new ArrayList<>();
for (RestrictionEntry entry : restrictions.values()) {
if (!entry.isExpired() && entry.getPlayerName() != null && !entry.getPlayerName().isBlank()) {
names.add(entry.getPlayerName());
}
}
names.sort(String.CASE_INSENSITIVE_ORDER);
return names;
}
public String describeRestriction(UUID playerId) {
Optional<RestrictionEntry> optional = getActiveRestriction(playerId);
if (optional.isEmpty()) {
return "";
}
RestrictionEntry entry = optional.get();
StringBuilder message = new StringBuilder("§cВам запрещено входить в admin mode");
if (!entry.isPermanent()) {
message.append(" §7(").append(formatRemaining(entry.getExpiresAt() - System.currentTimeMillis())).append("§7)");
} else {
message.append(" §7(навсегда)");
}
if (!entry.getReason().isBlank()) {
message.append("§c. Причина: §f").append(entry.getReason());
}
return message.toString();
}
public static Long parseDurationToExpiry(String input) {
if (input == null || input.isBlank()) {
return null;
}
String normalized = input.trim().toLowerCase();
if (normalized.equals("perm") || normalized.equals("permanent") || normalized.equals("forever")
|| normalized.equals("навсегда")) {
return null;
}
if (!normalized.matches("\\d+[smhdwy]")) {
return Long.MIN_VALUE;
}
long amount = Long.parseLong(normalized.substring(0, normalized.length() - 1));
char suffix = normalized.charAt(normalized.length() - 1);
Duration duration = switch (suffix) {
case 's' -> Duration.ofSeconds(amount);
case 'm' -> Duration.ofMinutes(amount);
case 'h' -> Duration.ofHours(amount);
case 'd' -> Duration.ofDays(amount);
case 'w' -> Duration.ofDays(amount * 7);
case 'y' -> Duration.ofDays(amount * 365);
default -> null;
};
if (duration == null || duration.isNegative() || duration.isZero()) {
return Long.MIN_VALUE;
}
return System.currentTimeMillis() + duration.toMillis();
}
private void load() {
restrictions.clear();
if (!storageFile.exists()) {
return;
}
try (FileReader reader = new FileReader(storageFile)) {
JsonElement root = JsonParser.parseReader(reader);
if (!root.isJsonArray()) {
return;
}
for (JsonElement element : root.getAsJsonArray()) {
if (!element.isJsonObject()) {
continue;
}
RestrictionEntry entry = deserialize(element.getAsJsonObject());
if (entry != null && !entry.isExpired()) {
restrictions.put(entry.getPlayerId(), entry);
}
}
} catch (Exception exception) {
plugin.getLogger().warning("Не удалось загрузить moderator-access.json: " + exception.getMessage());
}
}
private void save() {
File parent = storageFile.getParentFile();
if (parent != null && !parent.exists()) {
parent.mkdirs();
}
JsonArray array = new JsonArray();
for (RestrictionEntry entry : restrictions.values()) {
if (!entry.isExpired()) {
array.add(serialize(entry));
}
}
try (FileWriter writer = new FileWriter(storageFile)) {
writer.write(gson.toJson(array));
} catch (IOException exception) {
plugin.getLogger().warning("Не удалось сохранить moderator-access.json: " + exception.getMessage());
}
}
private JsonObject serialize(RestrictionEntry entry) {
JsonObject json = new JsonObject();
json.addProperty("playerId", entry.getPlayerId().toString());
json.addProperty("playerName", entry.getPlayerName());
json.addProperty("actorName", entry.getActorName());
json.addProperty("reason", entry.getReason());
json.addProperty("createdAt", entry.getCreatedAt());
json.addProperty("expiresAt", entry.getExpiresAt());
return json;
}
private RestrictionEntry deserialize(JsonObject json) {
try {
UUID playerId = UUID.fromString(json.get("playerId").getAsString());
String playerName = json.has("playerName") ? json.get("playerName").getAsString() : playerId.toString();
String actorName = json.has("actorName") ? json.get("actorName").getAsString() : "unknown";
String reason = json.has("reason") ? json.get("reason").getAsString() : "";
long createdAt = json.has("createdAt") ? json.get("createdAt").getAsLong() : System.currentTimeMillis();
long expiresAt = json.has("expiresAt") ? json.get("expiresAt").getAsLong() : PERMANENT_EXPIRY;
return new RestrictionEntry(playerId, playerName, actorName, reason, createdAt, expiresAt);
} catch (Exception exception) {
plugin.getLogger().warning("Не удалось прочитать запись ограничения admin mode: " + exception.getMessage());
return null;
}
}
private String formatRemaining(long millis) {
Duration duration = Duration.ofMillis(Math.max(millis, 0L));
long days = duration.toDays();
long hours = duration.toHoursPart();
long minutes = duration.toMinutesPart();
if (days > 0) {
return days + "д " + hours + "ч";
}
if (duration.toHours() > 0) {
return duration.toHours() + "ч " + minutes + "м";
}
if (duration.toMinutes() > 0) {
return duration.toMinutes() + "м";
}
if (duration.toSeconds() > 0) {
return duration.toSeconds() + "с";
}
return Math.max(1L, duration.toMinutes()) + "м";
}
public static final class RestrictionEntry {
private final UUID playerId;
private final String playerName;
private final String actorName;
private final String reason;
private final long createdAt;
private final long expiresAt;
public RestrictionEntry(UUID playerId, String playerName, String actorName, String reason, long createdAt, long expiresAt) {
this.playerId = playerId;
this.playerName = playerName;
this.actorName = actorName;
this.reason = reason;
this.createdAt = createdAt;
this.expiresAt = expiresAt;
}
public UUID getPlayerId() {
return playerId;
}
public String getPlayerName() {
return playerName;
}
public String getActorName() {
return actorName;
}
public String getReason() {
return reason;
}
public long getCreatedAt() {
return createdAt;
}
public long getExpiresAt() {
return expiresAt;
}
public boolean isPermanent() {
return expiresAt == PERMANENT_EXPIRY;
}
public boolean isExpired() {
return !isPermanent() && System.currentTimeMillis() >= expiresAt;
}
}
}

View File

@@ -0,0 +1,230 @@
package org.blz.adminmode.moderation;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.plugin.Plugin;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
public class ModeratorPreferencesStore {
private final Plugin plugin;
private final File storageFile;
private final Gson gson;
private final Map<UUID, ModeratorPreferences> preferences = new LinkedHashMap<>();
public ModeratorPreferencesStore(Plugin plugin) {
this.plugin = plugin;
this.storageFile = new File(plugin.getDataFolder(), "moderator-preferences.json");
this.gson = new GsonBuilder().setPrettyPrinting().create();
load();
}
public ModeratorPreferences get(UUID playerId) {
return preferences.computeIfAbsent(playerId, ignored -> new ModeratorPreferences());
}
public DefaultEntryMode cycleDefaultEntryMode(UUID playerId) {
ModeratorPreferences preferences = get(playerId);
DefaultEntryMode[] values = new DefaultEntryMode[]{DefaultEntryMode.AUTO, DefaultEntryMode.SURVIVAL, DefaultEntryMode.SPECTATOR};
preferences.defaultEntryMode = cycle(values, preferences.defaultEntryMode);
save();
return preferences.defaultEntryMode;
}
public MenuTheme cycleTheme(UUID playerId) {
ModeratorPreferences preferences = get(playerId);
preferences.theme = cycle(MenuTheme.values(), preferences.theme);
save();
return preferences.theme;
}
public AccentColor cycleAccentColor(UUID playerId) {
ModeratorPreferences preferences = get(playerId);
preferences.accentColor = cycle(AccentColor.values(), preferences.accentColor);
save();
return preferences.accentColor;
}
private <T> T cycle(T[] values, T current) {
for (int i = 0; i < values.length; i++) {
if (values[i] == current) {
return values[(i + 1) % values.length];
}
}
return values[0];
}
private void load() {
preferences.clear();
if (!storageFile.exists()) {
return;
}
try (FileReader reader = new FileReader(storageFile)) {
JsonElement root = JsonParser.parseReader(reader);
if (!root.isJsonObject()) {
return;
}
for (Map.Entry<String, JsonElement> entry : root.getAsJsonObject().entrySet()) {
if (!entry.getValue().isJsonObject()) {
continue;
}
try {
UUID uuid = UUID.fromString(entry.getKey());
preferences.put(uuid, readPreferences(entry.getValue().getAsJsonObject()));
} catch (IllegalArgumentException ignored) {
}
}
} catch (Exception exception) {
plugin.getLogger().warning("Не удалось загрузить moderator-preferences.json: " + exception.getMessage());
}
}
private ModeratorPreferences readPreferences(JsonObject json) {
ModeratorPreferences result = new ModeratorPreferences();
result.defaultEntryMode = DefaultEntryMode.fromName(getString(json, "defaultEntryMode"));
result.theme = MenuTheme.fromName(getString(json, "theme"));
result.accentColor = AccentColor.fromName(getString(json, "accentColor"));
return result;
}
private void save() {
File parent = storageFile.getParentFile();
if (parent != null && !parent.exists()) {
parent.mkdirs();
}
JsonObject root = new JsonObject();
for (Map.Entry<UUID, ModeratorPreferences> entry : preferences.entrySet()) {
JsonObject json = new JsonObject();
json.addProperty("defaultEntryMode", entry.getValue().defaultEntryMode.name());
json.addProperty("theme", entry.getValue().theme.name());
json.addProperty("accentColor", entry.getValue().accentColor.name());
root.add(entry.getKey().toString(), json);
}
try (FileWriter writer = new FileWriter(storageFile)) {
writer.write(gson.toJson(root));
} catch (IOException exception) {
plugin.getLogger().warning("Не удалось сохранить moderator-preferences.json: " + exception.getMessage());
}
}
private String getString(JsonObject json, String key) {
return json.has(key) && !json.get(key).isJsonNull() ? json.get(key).getAsString() : "";
}
public static final class ModeratorPreferences {
private DefaultEntryMode defaultEntryMode = DefaultEntryMode.AUTO;
private MenuTheme theme = MenuTheme.RAINBOW;
private AccentColor accentColor = AccentColor.AQUA;
public DefaultEntryMode defaultEntryMode() {
return defaultEntryMode;
}
public MenuTheme theme() {
return theme;
}
public AccentColor accentColor() {
return accentColor;
}
}
public enum DefaultEntryMode {
AUTO("По серверу"),
SURVIVAL("SURVIVAL"),
SPECTATOR("SPECTATOR");
private final String displayName;
DefaultEntryMode(String displayName) {
this.displayName = displayName;
}
public String displayName() {
return displayName;
}
public static DefaultEntryMode fromName(String value) {
for (DefaultEntryMode mode : values()) {
if (mode.name().equalsIgnoreCase(value)) {
return mode;
}
}
return AUTO;
}
}
public enum MenuTheme {
RAINBOW("Радужная"),
CLEAN_ACCENT("Светлая акцентная"),
WHITE("Белая");
private final String displayName;
MenuTheme(String displayName) {
this.displayName = displayName;
}
public String displayName() {
return displayName;
}
public static MenuTheme fromName(String value) {
for (MenuTheme theme : values()) {
if (theme.name().equalsIgnoreCase(value)) {
return theme;
}
}
return RAINBOW;
}
}
public enum AccentColor {
AQUA("Аква", NamedTextColor.AQUA),
GOLD("Золото", NamedTextColor.GOLD),
GREEN("Лайм", NamedTextColor.GREEN),
YELLOW("Жёлтый", NamedTextColor.YELLOW),
RED("Красный", NamedTextColor.RED),
BLUE("Синий", NamedTextColor.BLUE);
private final String displayName;
private final NamedTextColor textColor;
AccentColor(String displayName, NamedTextColor textColor) {
this.displayName = displayName;
this.textColor = textColor;
}
public String displayName() {
return displayName;
}
public NamedTextColor textColor() {
return textColor;
}
public static AccentColor fromName(String value) {
for (AccentColor color : values()) {
if (color.name().equalsIgnoreCase(value)) {
return color;
}
}
return AQUA;
}
}
}

View File

@@ -2,14 +2,10 @@ package org.blz.adminmode.moderation;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Optional; import java.util.Optional;
public enum ModeratorProfile { public enum ModeratorProfile {
SOFT_MODER("soft_moder", "Soft Moder", "adminmode.profile.soft"), ADMIN("admin", "Admin Mode", "adminmode.use");
HARD_MODER("hard_moder", "Hard Moder", "adminmode.profile.hard");
private final String configKey; private final String configKey;
private final String displayName; private final String displayName;
@@ -33,54 +29,12 @@ public enum ModeratorProfile {
return permission; return permission;
} }
public boolean isHardProfile() {
return this == HARD_MODER;
}
public boolean isAvailableFor(Player player) { public boolean isAvailableFor(Player player) {
return switch (this) { return player.hasPermission(permission);
case SOFT_MODER -> player.hasPermission("adminmode.use")
|| player.hasPermission(permission)
|| player.hasPermission(HARD_MODER.permission);
case HARD_MODER -> player.hasPermission(permission);
};
}
public static List<ModeratorProfile> availableFor(Player player) {
List<ModeratorProfile> profiles = new ArrayList<>();
for (ModeratorProfile profile : values()) {
if (profile.isAvailableFor(player)) {
profiles.add(profile);
}
}
return profiles;
} }
/** Accepts any old key (soft_moder, hard_moder, admin) and returns ADMIN. */
public static Optional<ModeratorProfile> fromConfigKey(String key) { public static Optional<ModeratorProfile> fromConfigKey(String key) {
if (key == null || key.isBlank()) { return Optional.of(ADMIN);
return Optional.empty();
}
String normalized = key.toLowerCase(Locale.ROOT);
for (ModeratorProfile profile : values()) {
if (profile.configKey.equals(normalized)) {
return Optional.of(profile);
}
}
return Optional.empty();
}
public static Optional<ModeratorProfile> fromInput(String input) {
if (input == null || input.isBlank()) {
return Optional.empty();
}
String normalized = input.trim().toLowerCase(Locale.ROOT);
return switch (normalized) {
case "soft", "soft_moder", "soft-moder", "softmoder" -> Optional.of(SOFT_MODER);
case "hard", "hard_moder", "hard-moder", "hardmoder" -> Optional.of(HARD_MODER);
default -> Optional.empty();
};
} }
} }

View File

@@ -0,0 +1,256 @@
package org.blz.adminmode.moderation;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public class ModeratorWarningStore {
private static final long NOT_REVOKED = -1L;
private final Plugin plugin;
private final File storageFile;
private final Gson gson;
private final List<WarningEntry> entries = new ArrayList<>();
public ModeratorWarningStore(Plugin plugin) {
this.plugin = plugin;
this.storageFile = new File(plugin.getDataFolder(), "warn-history.json");
this.gson = new GsonBuilder().setPrettyPrinting().create();
load();
}
public WarningEntry recordWarn(Player target, Player moderator, String reason, String durationToken) {
if (target == null || moderator == null) {
return null;
}
long now = System.currentTimeMillis();
long expiresAt = PunishmentDuration.toExpiry(durationToken, now);
WarningEntry entry = new WarningEntry(
UUID.randomUUID(),
target.getUniqueId(),
target.getName(),
moderator.getName(),
sanitizeReason(reason),
now,
expiresAt,
NOT_REVOKED,
"",
"");
entries.add(entry);
save();
return entry;
}
public List<WarningEntry> getRecentWarnings(UUID playerId, int limit) {
return entries.stream()
.filter(entry -> entry.playerId.equals(playerId))
.sorted(Comparator.comparingLong(WarningEntry::createdAt).reversed())
.limit(Math.max(0, limit))
.toList();
}
public List<WarningEntry> getActiveWarnings(UUID playerId) {
return entries.stream()
.filter(entry -> entry.playerId.equals(playerId) && entry.status() == WarningStatus.ACTIVE)
.sorted(Comparator.comparingLong(WarningEntry::createdAt).reversed())
.toList();
}
public Optional<WarningEntry> findWarning(UUID warningId) {
if (warningId == null) {
return Optional.empty();
}
return entries.stream().filter(entry -> entry.id.equals(warningId)).findFirst();
}
public boolean revokeWarning(UUID warningId, Player moderator, String reason) {
if (warningId == null || moderator == null || reason == null || reason.isBlank()) {
return false;
}
for (int index = 0; index < entries.size(); index++) {
WarningEntry current = entries.get(index);
if (!current.id.equals(warningId) || current.status() != WarningStatus.ACTIVE) {
continue;
}
entries.set(index, current.revokedBy(moderator.getName(), sanitizeReason(reason), System.currentTimeMillis()));
save();
return true;
}
return false;
}
private String sanitizeReason(String reason) {
if (reason == null || reason.isBlank()) {
return "без причины";
}
return reason.replace('\n', ' ').replace('\r', ' ').trim();
}
private void load() {
entries.clear();
if (!storageFile.exists()) {
return;
}
try (FileReader reader = new FileReader(storageFile)) {
JsonElement root = JsonParser.parseReader(reader);
if (!root.isJsonArray()) {
return;
}
for (JsonElement element : root.getAsJsonArray()) {
if (!element.isJsonObject()) {
continue;
}
WarningEntry entry = readEntry(element.getAsJsonObject());
if (entry != null) {
entries.add(entry);
}
}
} catch (Exception exception) {
plugin.getLogger().warning("Не удалось загрузить warn-history.json: " + exception.getMessage());
}
}
private WarningEntry readEntry(JsonObject json) {
try {
long createdAt = json.get("createdAt").getAsLong();
boolean legacyActive = !json.has("active") || json.get("active").getAsBoolean();
long revokedAt = json.has("revokedAt")
? json.get("revokedAt").getAsLong()
: legacyActive ? NOT_REVOKED : 0L;
return new WarningEntry(
json.has("id") ? UUID.fromString(json.get("id").getAsString()) : UUID.randomUUID(),
UUID.fromString(json.get("playerId").getAsString()),
json.get("playerName").getAsString(),
json.get("moderatorName").getAsString(),
json.get("reason").getAsString(),
createdAt,
json.has("expiresAt") ? json.get("expiresAt").getAsLong() : PunishmentDuration.PERMANENT_EXPIRY,
revokedAt,
readString(json, "revokedBy", legacyActive ? "" : "неизвестно"),
readString(json, "revocationReason", legacyActive ? "" : "данные отсутствуют"));
} catch (Exception exception) {
plugin.getLogger().warning("Не удалось загрузить запись warn: " + exception.getMessage());
return null;
}
}
private String readString(JsonObject json, String key, String fallback) {
return json.has(key) && !json.get(key).isJsonNull() ? json.get(key).getAsString() : fallback;
}
private void save() {
File parent = storageFile.getParentFile();
if (parent != null && !parent.exists()) {
parent.mkdirs();
}
JsonArray root = new JsonArray();
for (WarningEntry entry : entries) {
JsonObject json = new JsonObject();
json.addProperty("id", entry.id.toString());
json.addProperty("playerId", entry.playerId.toString());
json.addProperty("playerName", entry.playerName);
json.addProperty("moderatorName", entry.moderatorName);
json.addProperty("reason", entry.reason);
json.addProperty("createdAt", entry.createdAt);
json.addProperty("expiresAt", entry.expiresAt);
json.addProperty("revokedAt", entry.revokedAt);
json.addProperty("revokedBy", entry.revokedBy);
json.addProperty("revocationReason", entry.revocationReason);
json.addProperty("active", entry.status() == WarningStatus.ACTIVE);
root.add(json);
}
try (FileWriter writer = new FileWriter(storageFile)) {
writer.write(gson.toJson(root));
} catch (IOException exception) {
plugin.getLogger().warning("Не удалось сохранить warn-history.json: " + exception.getMessage());
}
}
public enum WarningStatus {
ACTIVE,
EXPIRED,
REVOKED
}
public static final class WarningEntry {
private final UUID id;
private final UUID playerId;
private final String playerName;
private final String moderatorName;
private final String reason;
private final long createdAt;
private final long expiresAt;
private final long revokedAt;
private final String revokedBy;
private final String revocationReason;
private WarningEntry(UUID id, UUID playerId, String playerName, String moderatorName, String reason,
long createdAt, long expiresAt, long revokedAt, String revokedBy, String revocationReason) {
this.id = id;
this.playerId = playerId;
this.playerName = playerName;
this.moderatorName = moderatorName;
this.reason = reason;
this.createdAt = createdAt;
this.expiresAt = expiresAt;
this.revokedAt = revokedAt;
this.revokedBy = revokedBy;
this.revocationReason = revocationReason;
}
private WarningEntry revokedBy(String actor, String revocationReason, long timestamp) {
return new WarningEntry(id, playerId, playerName, moderatorName, reason, createdAt, expiresAt,
timestamp, actor, revocationReason);
}
public UUID id() { return id; }
public UUID playerId() { return playerId; }
public String playerName() { return playerName; }
public String moderatorName() { return moderatorName; }
public String reason() { return reason; }
public long createdAt() { return createdAt; }
public long expiresAt() { return expiresAt; }
public long revokedAt() { return revokedAt; }
public String revokedBy() { return revokedBy; }
public String revocationReason() { return revocationReason; }
public boolean isPermanent() {
return expiresAt == PunishmentDuration.PERMANENT_EXPIRY;
}
public WarningStatus status() {
if (revokedAt != NOT_REVOKED) {
return WarningStatus.REVOKED;
}
if (!isPermanent() && System.currentTimeMillis() >= expiresAt) {
return WarningStatus.EXPIRED;
}
return WarningStatus.ACTIVE;
}
public boolean active() {
return status() == WarningStatus.ACTIVE;
}
}
}

View File

@@ -0,0 +1,127 @@
package org.blz.adminmode.moderation;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityPickupItemEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.plugin.Plugin;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
public class PlayerFreezeManager implements Listener {
private static final long NOTIFY_COOLDOWN_MS = 1500L;
private final Plugin plugin;
private final Set<UUID> frozenPlayers = new HashSet<>();
private final Map<UUID, Long> lastNotify = new HashMap<>();
public PlayerFreezeManager(Plugin plugin) {
this.plugin = plugin;
}
public boolean toggleFreeze(Player target) {
UUID uuid = target.getUniqueId();
if (frozenPlayers.remove(uuid)) {
target.sendMessage(Component.text("Вы больше не заморожены.", NamedTextColor.GREEN));
return false;
}
frozenPlayers.add(uuid);
target.sendMessage(Component.text("Вы заморожены. Любые действия заблокированы.", NamedTextColor.RED));
return true;
}
public boolean isFrozen(UUID playerId) {
return frozenPlayers.contains(playerId);
}
private boolean shouldBlock(Player player) {
return frozenPlayers.contains(player.getUniqueId());
}
private void notifyFrozen(Player player) {
long now = System.currentTimeMillis();
long previous = lastNotify.getOrDefault(player.getUniqueId(), 0L);
if (now - previous < NOTIFY_COOLDOWN_MS) {
return;
}
lastNotify.put(player.getUniqueId(), now);
player.sendActionBar(Component.text("Вы заморожены модератором.", NamedTextColor.RED));
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onMove(PlayerMoveEvent event) {
Player player = event.getPlayer();
if (!shouldBlock(player) || event.getTo() == null) return;
if (event.getFrom().distanceSquared(event.getTo()) > 0.0D) {
event.setTo(event.getFrom());
notifyFrozen(player);
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onInteract(PlayerInteractEvent event) {
if (!shouldBlock(event.getPlayer())) return;
event.setCancelled(true);
notifyFrozen(event.getPlayer());
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onDrop(PlayerDropItemEvent event) {
if (!shouldBlock(event.getPlayer())) return;
event.setCancelled(true);
notifyFrozen(event.getPlayer());
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPickup(EntityPickupItemEvent event) {
if (!(event.getEntity() instanceof Player player)) return;
if (!shouldBlock(player)) return;
event.setCancelled(true);
notifyFrozen(player);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onInventoryClick(InventoryClickEvent event) {
if (!(event.getWhoClicked() instanceof Player player)) return;
if (!shouldBlock(player)) return;
event.setCancelled(true);
notifyFrozen(player);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onCommand(PlayerCommandPreprocessEvent event) {
Player player = event.getPlayer();
if (!shouldBlock(player)) return;
event.setCancelled(true);
notifyFrozen(player);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBlockPlace(BlockPlaceEvent event) {
if (!shouldBlock(event.getPlayer())) return;
event.setCancelled(true);
notifyFrozen(event.getPlayer());
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBlockBreak(BlockBreakEvent event) {
if (!shouldBlock(event.getPlayer())) return;
event.setCancelled(true);
notifyFrozen(event.getPlayer());
}
}

View File

@@ -3,5 +3,6 @@ package org.blz.adminmode.moderation;
public enum PlayerListAction { public enum PlayerListAction {
SPECTATE, SPECTATE,
TELEPORT, TELEPORT,
INVSEE INVSEE,
MANAGE
} }

View File

@@ -0,0 +1,57 @@
package org.blz.adminmode.moderation;
import java.time.Duration;
import java.util.Locale;
public final class PunishmentDuration {
public static final long PERMANENT_EXPIRY = -1L;
private PunishmentDuration() {
}
public static String normalizeToken(String input) {
if (input == null || input.isBlank()) {
return "";
}
String normalized = input.trim().toLowerCase(Locale.ROOT);
if (normalized.equals("perm") || normalized.equals("permanent") || normalized.equals("permanently")) {
return "permanent";
}
if (!normalized.matches("[1-9]\\d*[smhdwy]")) {
return "";
}
try {
toDuration(normalized);
return normalized;
} catch (ArithmeticException | IllegalArgumentException exception) {
return "";
}
}
public static long toExpiry(String token, long now) {
String normalized = normalizeToken(token);
if (normalized.isEmpty()) {
throw new IllegalArgumentException("Unsupported punishment duration: " + token);
}
if (normalized.equals("permanent")) {
return PERMANENT_EXPIRY;
}
return Math.addExact(now, toDuration(normalized).toMillis());
}
private static Duration toDuration(String normalized) {
long amount = Long.parseLong(normalized.substring(0, normalized.length() - 1));
return switch (normalized.charAt(normalized.length() - 1)) {
case 's' -> Duration.ofSeconds(amount);
case 'm' -> Duration.ofMinutes(amount);
case 'h' -> Duration.ofHours(amount);
case 'd' -> Duration.ofDays(amount);
case 'w' -> Duration.ofDays(Math.multiplyExact(amount, 7L));
case 'y' -> Duration.ofDays(Math.multiplyExact(amount, 365L));
default -> throw new IllegalArgumentException("Unsupported punishment duration: " + normalized);
};
}
}

View File

@@ -8,29 +8,22 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import org.blz.adminmode.moderation.ModeSession; import org.blz.adminmode.moderation.ModeSession;
import org.blz.adminmode.moderation.ModeratorProfile; import org.blz.adminmode.moderation.ModeratorProfile;
import org.blz.adminmode.utils.ItemStackCodec;
import org.blz.adminmode.utils.PotionEffectCodec;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.io.BukkitObjectInputStream;
import org.bukkit.util.io.BukkitObjectOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
@@ -128,7 +121,7 @@ public class SessionStorage {
UUID playerId = UUID.fromString(json.get("playerId").getAsString()); UUID playerId = UUID.fromString(json.get("playerId").getAsString());
String playerName = json.get("playerName").getAsString(); String playerName = json.get("playerName").getAsString();
ModeratorProfile profile = ModeratorProfile.fromConfigKey(json.get("profile").getAsString()) ModeratorProfile profile = ModeratorProfile.fromConfigKey(json.get("profile").getAsString())
.orElse(ModeratorProfile.SOFT_MODER); .orElse(ModeratorProfile.ADMIN);
long startTimestamp = json.get("startTimestamp").getAsLong(); long startTimestamp = json.get("startTimestamp").getAsLong();
String previousGroup = getString(json, "previousGroup"); String previousGroup = getString(json, "previousGroup");
String activeGroup = getString(json, "activeGroup"); String activeGroup = getString(json, "activeGroup");
@@ -224,14 +217,7 @@ public class SessionStorage {
private String serializeItemArray(ItemStack[] items) { private String serializeItemArray(ItemStack[] items) {
try { try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); return ItemStackCodec.serializeItems(items);
try (BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream)) {
dataOutput.writeInt(items.length);
for (ItemStack item : items) {
dataOutput.writeObject(item);
}
}
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
} catch (Exception exception) { } catch (Exception exception) {
plugin.getLogger().warning("Не удалось сериализовать item array: " + exception.getMessage()); plugin.getLogger().warning("Не удалось сериализовать item array: " + exception.getMessage());
return ""; return "";
@@ -244,15 +230,7 @@ public class SessionStorage {
} }
try { try {
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(data)); return ItemStackCodec.deserializeItems(data);
try (BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream)) {
int length = dataInput.readInt();
ItemStack[] items = new ItemStack[length];
for (int i = 0; i < length; i++) {
items[i] = (ItemStack) dataInput.readObject();
}
return items;
}
} catch (Exception exception) { } catch (Exception exception) {
plugin.getLogger().warning("Не удалось десериализовать item array: " + exception.getMessage()); plugin.getLogger().warning("Не удалось десериализовать item array: " + exception.getMessage());
return new ItemStack[0]; return new ItemStack[0];
@@ -261,11 +239,7 @@ public class SessionStorage {
private String serializeItem(ItemStack item) { private String serializeItem(ItemStack item) {
try { try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); return ItemStackCodec.serializeItem(item);
try (BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream)) {
dataOutput.writeObject(item);
}
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
} catch (Exception exception) { } catch (Exception exception) {
plugin.getLogger().warning("Не удалось сериализовать item: " + exception.getMessage()); plugin.getLogger().warning("Не удалось сериализовать item: " + exception.getMessage());
return ""; return "";
@@ -274,62 +248,24 @@ public class SessionStorage {
private ItemStack deserializeItem(String data) { private ItemStack deserializeItem(String data) {
if (data == null || data.isEmpty()) { if (data == null || data.isEmpty()) {
return new ItemStack(Material.AIR); return ItemStack.empty();
} }
try { try {
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(data)); return ItemStackCodec.deserializeItem(data);
try (BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream)) {
return (ItemStack) dataInput.readObject();
}
} catch (Exception exception) { } catch (Exception exception) {
plugin.getLogger().warning("Не удалось десериализовать item: " + exception.getMessage()); plugin.getLogger().warning("Не удалось десериализовать item: " + exception.getMessage());
return new ItemStack(Material.AIR); return ItemStack.empty();
} }
} }
private String serializePotionEffects(Collection<PotionEffect> effects) { private String serializePotionEffects(Collection<PotionEffect> effects) {
StringBuilder builder = new StringBuilder(); return PotionEffectCodec.serialize(effects);
for (PotionEffect effect : effects) {
builder.append(effect.getType().getName()).append(':')
.append(effect.getDuration()).append(':')
.append(effect.getAmplifier()).append(':')
.append(effect.isAmbient()).append(':')
.append(effect.hasParticles()).append(':')
.append(effect.hasIcon()).append(';');
}
return builder.toString();
} }
private Collection<PotionEffect> deserializePotionEffects(String data) { private Collection<PotionEffect> deserializePotionEffects(String data) {
List<PotionEffect> effects = new ArrayList<>(); return PotionEffectCodec.deserialize(data,
if (data == null || data.isEmpty()) { encodedEffect -> plugin.getLogger().warning(
return effects; "Не удалось десериализовать potion effect: " + encodedEffect));
}
String[] effectStrings = data.split(";");
for (String effectString : effectStrings) {
if (effectString.isEmpty()) {
continue;
}
try {
String[] parts = effectString.split(":");
PotionEffectType type = PotionEffectType.getByName(parts[0]);
if (type == null) {
continue;
}
effects.add(new PotionEffect(
type,
Integer.parseInt(parts[1]),
Integer.parseInt(parts[2]),
Boolean.parseBoolean(parts[3]),
Boolean.parseBoolean(parts[4]),
Boolean.parseBoolean(parts[5])));
} catch (Exception exception) {
plugin.getLogger().warning("Не удалось десериализовать potion effect: " + effectString);
}
}
return effects;
} }
} }

View File

@@ -0,0 +1,85 @@
package org.blz.adminmode.utils;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.io.BukkitObjectInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Base64;
/**
* Stores item stacks using Paper's data-version-aware NBT format while retaining
* read compatibility with sessions written by older AdminMode releases.
*/
public final class ItemStackCodec {
private static final String ITEM_FORMAT_PREFIX = "nbt-item-v1:";
private static final String ITEMS_FORMAT_PREFIX = "nbt-items-v1:";
private ItemStackCodec() {
}
public static String serializeItems(ItemStack[] items) {
ItemStack[] safeItems = items == null ? new ItemStack[0] : items;
return ITEMS_FORMAT_PREFIX
+ Base64.getEncoder().encodeToString(ItemStack.serializeItemsAsBytes(safeItems));
}
public static ItemStack[] deserializeItems(String data) throws IOException, ClassNotFoundException {
if (data == null || data.isEmpty()) {
return new ItemStack[0];
}
if (data.startsWith(ITEMS_FORMAT_PREFIX)) {
byte[] bytes = decode(data.substring(ITEMS_FORMAT_PREFIX.length()));
return ItemStack.deserializeItemsFromBytes(bytes);
}
return deserializeLegacyItems(data);
}
public static String serializeItem(ItemStack item) {
ItemStack safeItem = item == null ? ItemStack.empty() : item;
return ITEM_FORMAT_PREFIX
+ Base64.getEncoder().encodeToString(safeItem.serializeAsBytes());
}
public static ItemStack deserializeItem(String data) throws IOException, ClassNotFoundException {
if (data == null || data.isEmpty()) {
return ItemStack.empty();
}
if (data.startsWith(ITEM_FORMAT_PREFIX)) {
byte[] bytes = decode(data.substring(ITEM_FORMAT_PREFIX.length()));
return ItemStack.deserializeBytes(bytes);
}
return deserializeLegacyItem(data);
}
private static byte[] decode(String data) throws IOException {
try {
return Base64.getDecoder().decode(data);
} catch (IllegalArgumentException exception) {
throw new IOException("Invalid Base64 item data", exception);
}
}
@SuppressWarnings("deprecation")
private static ItemStack[] deserializeLegacyItems(String data) throws IOException, ClassNotFoundException {
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(decode(data));
BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream)) {
int length = dataInput.readInt();
ItemStack[] items = new ItemStack[length];
for (int i = 0; i < length; i++) {
items[i] = (ItemStack) dataInput.readObject();
}
return items;
}
}
@SuppressWarnings("deprecation")
private static ItemStack deserializeLegacyItem(String data) throws IOException, ClassNotFoundException {
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(decode(data));
BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream)) {
ItemStack item = (ItemStack) dataInput.readObject();
return item == null ? ItemStack.empty() : item;
}
}
}

View File

@@ -0,0 +1,86 @@
package org.blz.adminmode.utils;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.function.Consumer;
/** Encodes potion effects with registry keys and reads the legacy enum-name format. */
public final class PotionEffectCodec {
private static final String FORMAT_PREFIX = "potion-v2:";
private PotionEffectCodec() {
}
public static String serialize(Collection<PotionEffect> effects) {
StringBuilder builder = new StringBuilder(FORMAT_PREFIX);
if (effects == null) {
return builder.toString();
}
for (PotionEffect effect : effects) {
NamespacedKey key = Registry.MOB_EFFECT.getKey(effect.getType());
builder.append(key).append('|')
.append(effect.getDuration()).append('|')
.append(effect.getAmplifier()).append('|')
.append(effect.isAmbient()).append('|')
.append(effect.hasParticles()).append('|')
.append(effect.hasIcon()).append(';');
}
return builder.toString();
}
public static Collection<PotionEffect> deserialize(String data, Consumer<String> warningSink) {
List<PotionEffect> effects = new ArrayList<>();
if (data == null || data.isEmpty()) {
return effects;
}
boolean currentFormat = data.startsWith(FORMAT_PREFIX);
String payload = currentFormat ? data.substring(FORMAT_PREFIX.length()) : data;
for (String encodedEffect : payload.split(";")) {
if (encodedEffect.isEmpty()) {
continue;
}
try {
String[] parts = currentFormat
? encodedEffect.split("\\|", -1)
: encodedEffect.split(":", -1);
if (parts.length != 6) {
throw new IllegalArgumentException("Expected 6 fields");
}
PotionEffectType type = resolveType(parts[0], currentFormat);
if (type == null) {
throw new IllegalArgumentException("Unknown effect " + parts[0]);
}
effects.add(new PotionEffect(
type,
Integer.parseInt(parts[1]),
Integer.parseInt(parts[2]),
Boolean.parseBoolean(parts[3]),
Boolean.parseBoolean(parts[4]),
Boolean.parseBoolean(parts[5])));
} catch (RuntimeException exception) {
warningSink.accept(encodedEffect);
}
}
return effects;
}
private static PotionEffectType resolveType(String identifier, boolean currentFormat) {
NamespacedKey key = currentFormat
? NamespacedKey.fromString(identifier)
: NamespacedKey.minecraft(identifier.toLowerCase(Locale.ROOT));
return key == null ? null : Registry.MOB_EFFECT.get(key);
}
}

View File

59
src/main/java/org/blz/adminmode/utils/WorldLoader.java Normal file → Executable file
View File

@@ -38,6 +38,7 @@ public class WorldLoader {
return; return;
} }
} }
cleanupSkippedFiles(worldFolder);
plugin.getLogger().info("Загрузка мира " + worldName + "..."); plugin.getLogger().info("Загрузка мира " + worldName + "...");
WorldCreator creator = new WorldCreator(worldName); WorldCreator creator = new WorldCreator(worldName);
@@ -64,8 +65,18 @@ public class WorldLoader {
try (ZipInputStream zipIn = new ZipInputStream(in)) { try (ZipInputStream zipIn = new ZipInputStream(in)) {
ZipEntry entry = zipIn.getNextEntry(); ZipEntry entry = zipIn.getNextEntry();
String destinationPath = destination.getCanonicalPath() + File.separator;
while (entry != null) { while (entry != null) {
if (shouldSkipEntry(entry.getName())) {
zipIn.closeEntry();
entry = zipIn.getNextEntry();
continue;
}
File file = new File(destination, entry.getName()); File file = new File(destination, entry.getName());
String filePath = file.getCanonicalPath();
if (!filePath.startsWith(destinationPath)) {
throw new IOException("Blocked unsafe zip entry: " + entry.getName());
}
if (entry.isDirectory()) { if (entry.isDirectory()) {
file.mkdirs(); file.mkdirs();
} else { } else {
@@ -91,4 +102,52 @@ public class WorldLoader {
return false; return false;
} }
} }
private boolean shouldSkipEntry(String entryName) {
String normalized = entryName.replace('\\', '/');
return normalized.equals("level.dat")
|| normalized.equals("level.dat_old")
|| normalized.equals("session.lock")
|| normalized.equals("icon.png")
|| normalized.equals("xaeromap.txt")
|| normalized.startsWith("serverconfig/")
|| normalized.startsWith("playerdata/")
|| normalized.startsWith("stats/")
|| normalized.startsWith("advancements/");
}
private void cleanupSkippedFiles(File destination) {
deleteIfExists(new File(destination, "level.dat"));
deleteIfExists(new File(destination, "level.dat_old"));
deleteIfExists(new File(destination, "session.lock"));
deleteIfExists(new File(destination, "icon.png"));
deleteIfExists(new File(destination, "xaeromap.txt"));
deleteRecursively(new File(destination, "serverconfig"));
deleteRecursively(new File(destination, "playerdata"));
deleteRecursively(new File(destination, "stats"));
deleteRecursively(new File(destination, "advancements"));
}
private void deleteIfExists(File file) {
if (file.exists() && !file.delete()) {
plugin.getLogger().warning("Не удалось удалить файл " + file.getAbsolutePath());
}
}
private void deleteRecursively(File file) {
if (!file.exists()) {
return;
}
if (file.isDirectory()) {
File[] children = file.listFiles();
if (children != null) {
for (File child : children) {
deleteRecursively(child);
}
}
}
if (!file.delete()) {
plugin.getLogger().warning("Не удалось удалить " + file.getAbsolutePath());
}
}
} }

107
src/main/resources/config.yml Normal file → Executable file
View File

@@ -1,77 +1,43 @@
# AdminMode Configuration # AdminMode Configuration
#РЕЖИМ АДМИНИСТРАТОРА
admin_mode: admin_mode:
# Включен ли режим администратора
enabled: true enabled: true
# LuckPerms настройки
luckperms: luckperms:
# Использовать ли LuckPerms (если false - используется стандартный OP)
enabled: true enabled: true
# LuckPerms-группы, которые выдаются при входе в режим
# Группы которые выдаются при активации режима
# Можно указать несколько групп через запятую
groups: groups:
- "soft_moder_adm" - "admin_moder"
profile_groups:
soft_moder:
- "soft_moder_adm"
hard_moder:
- "hard_moder_adm"
# Удалять ли группы при деактивации (если false - группы останутся)
remove_on_disable: true remove_on_disable: true
dialogs: dialogs:
# Глобальный мастер-переключатель Dialog API (false = отключить для всего сервера).
# Каждый модератор лично включает/выключает своё GUI командой /adminmode dialog
enabled: true enabled: true
# Настройки игрового режима
gamemode: gamemode:
# Принудительно ставить спектатор (если false - survival) # false = chiefs can use CREATIVE, regular moderators use SPECTATOR
# true = everyone enters in SPECTATOR
force_spectator: false force_spectator: false
# Мобы игнорируют игрока в админ моде
mobs_ignore_player: true mobs_ignore_player: true
# Разрешить полет в режиме выживания (в админ моде)
allow_flight_in_survival: true allow_flight_in_survival: true
# Режим бога (неуязвимость)
god_mode: true god_mode: true
chief_creative: true
# Кастомная скорость полета (0.1 = медленно, 0.2 = нормально, 0.5 = быстро, 1.0 = очень быстро) # Скорость полёта: 0.1 (медленно) ... 1.0 (максимум)
custom_fly_speed: 0.2 custom_fly_speed: 0.2
# Кастомная скорость ходьбы (0.1 = медленно, 0.2 = нормально, 0.5 = быстро)
custom_walk_speed: 0.2 custom_walk_speed: 0.2
# Разрешённые режимы для переключения внутри админ-мода (пусто = любые)
# Разрешенные режимы игры для переключения в админ моде
# Если список пустой - можно переключаться на любые режимы
# Доступные: SURVIVAL, CREATIVE, ADVENTURE, SPECTATOR
# ВАЖНО: CREATIVE убран - модеры не должны иметь доступ к креативу!
allowed_gamemodes: allowed_gamemodes:
- "CREATIVE"
- "SURVIVAL" - "SURVIVAL"
- "SPECTATOR" - "SPECTATOR"
# Ограничения в режиме администратора
restrictions: restrictions:
# Запретить выбрасывание предметов
prevent_item_drop: false prevent_item_drop: false
# Запретить поднятие предметов
prevent_item_pickup: false prevent_item_pickup: false
# Запретить перемещение предметов в контейнеры (сундуки, бочки и т.д.)
prevent_container_transfer: true prevent_container_transfer: true
# Запретить использование предметов (кроме открытия контейнеров)
prevent_item_use: false prevent_item_use: false
# Разрешить управление своим инвентарем
allow_own_inventory: true allow_own_inventory: true
# Разрешенные блоки для взаимодействия (открытие, использование)
allowed_blocks: allowed_blocks:
- "CHEST" - "CHEST"
- "TRAPPED_CHEST" - "TRAPPED_CHEST"
@@ -88,27 +54,18 @@ admin_mode:
- "FENCE_GATE" - "FENCE_GATE"
- "BUTTON" - "BUTTON"
- "LEVER" - "LEVER"
- "ENDER_CHEST"
- "CRAFTING_TABLE" - "CRAFTING_TABLE"
- "ENCHANTING_TABLE" - "ENCHANTING_TABLE"
- "ANVIL" - "ANVIL"
- "BREWING_STAND" - "BREWING_STAND"
# Whitelist блоков для постановки (пусто = любые)
# Разрешенные блоки для постановки (whitelist)
# Если список пустой - можно ставить любые блоки
# Если список заполнен - можно ставить только эти блоки
allowed_place_blocks: allowed_place_blocks:
# Выдавать пресет блоков при входе в админ мод
give_preset_blocks: false give_preset_blocks: false
# Пресет блоков (формат: МАТЕРИАЛ:КОЛИЧЕСТВО)
preset_blocks: preset_blocks:
- "BARRIER:64" - "BARRIER:64"
- "LIGHT:64" - "LIGHT:64"
- "STRUCTURE_VOID:64" - "STRUCTURE_VOID:64"
# Сообщения
messages: messages:
enabled: "✓ Режим администратора активирован!" enabled: "✓ Режим администратора активирован!"
enabled_subtitle: "Ваше состояние сохранено." enabled_subtitle: "Ваше состояние сохранено."
@@ -125,12 +82,11 @@ admin_mode:
groups: groups:
base: "moder" base: "moder"
soft: "soft_moder_adm" admin: "admin_moder"
hard: "hard_moder_adm" chief: "chief_moder"
items: items:
soft_panel_material: COMPASS panel_material: NETHER_STAR
hard_panel_material: NETHER_STAR
cp_inspector_material: STICK cp_inspector_material: STICK
coreprotect: coreprotect:
@@ -140,12 +96,33 @@ coreprotect:
messages: messages:
prefix: "§8[§bAdminMode§8] " prefix: "§8[§bAdminMode§8] "
enter_soft: "§aВошёл в режим §bSOFT MODER" enter: "§aВошёл в §cADMIN MODE"
enter_hard: "§aВошёл в режим §cHARD MODER"
exit: "§7Режим деактивирован. Состояние восстановлено." exit: "§7Режим деактивирован. Состояние восстановлено."
cp_unavailable: "§cCoreProtect не найден. Hard режим работает без CP." cp_unavailable: "§cCoreProtect не найден или отключён."
session_recovered: "§eВаша активная сессия модерации была восстановлена и корректно завершена." session_recovered: "§eВаша активная сессия модерации была восстановлена и корректно завершена."
logging: logging:
enabled: true enabled: true
log_file: "logs/sessions.log" log_file: "logs/sessions.log"
moderation_actions:
dispatch_as_console: true
durations:
# Варианты, отображаемые в Dialog. permanent показывается как Permanently.
options:
- "1h"
- "1d"
- "7d"
- "30d"
- "permanent"
defaults:
warn: "7d"
mute: "1h"
ban: "7d"
commands:
warn: "warn {target} {duration} {reason}"
mute: "mute {target} {duration} actor:{moderator} {reason}"
# Namespace исключает конфликт с одноимённой командой FlectonePulse.
unmute: "moderationcommands:unmute {target} actor:{moderator} {reason}"
ban: "ban {target} {duration} actor:{moderator} {reason}"
kick: "kick {target} actor:{moderator} {reason}"

41
src/main/resources/plugin.yml Normal file → Executable file
View File

@@ -1,7 +1,7 @@
name: AdminMode name: AdminMode
version: '1.21.11' version: '${version}'
main: org.blz.adminmode.AdminMode main: org.blz.adminmode.AdminMode
api-version: '1.21' api-version: '26.1.2'
author: BelzeBool, Kilka_v_HJIebe author: BelzeBool, Kilka_v_HJIebe
description: Плагин режима администратора с сохранением состояния игрока description: Плагин режима администратора с сохранением состояния игрока
depend: [LuckPerms] depend: [LuckPerms]
@@ -10,7 +10,7 @@ softdepend: [CoreProtect]
commands: commands:
adminmode: adminmode:
description: Режим администратора с сохранением состояния description: Режим администратора с сохранением состояния
usage: /<command> usage: /<command> [on|off|toggle|dialog|disable|restrict|unrestrict|status|reload]
aliases: [adm, am] aliases: [adm, am]
admfix: admfix:
description: Отключить все способности у игрока (полет, бессмертие) description: Отключить все способности у игрока (полет, бессмертие)
@@ -20,15 +20,36 @@ permissions:
adminmode.use: adminmode.use:
description: Разрешает использовать режим администратора description: Разрешает использовать режим администратора
default: op default: op
adminmode.profile.soft:
description: Разрешает использовать профиль soft_moder
default: op
adminmode.profile.hard:
description: Разрешает использовать профиль hard_moder
default: op
adminmode.reload: adminmode.reload:
description: Разрешает перезагружать конфигурацию AdminMode description: Разрешает перезагружать конфигурацию AdminMode
default: op default: op
adminmode.admin: adminmode.admin:
description: Разрешает использовать сильные действия и админ команды (disable, admfix) description: Разрешает использовать сильные действия и команды (disable, admfix)
default: op default: op
adminmode.chief:
description: Разрешает главному модератору ограничивать доступ к admin mode
default: op
adminmode.punishment.warn:
description: Разрешает выдавать предупреждения через AdminMode
default: op
adminmode.punishment.unwarn:
description: Разрешает снимать предупреждения через AdminMode
default: op
adminmode.punishment.mute:
description: Разрешает выдавать муты через AdminMode
default: op
adminmode.punishment.unmute:
description: Разрешает снимать муты через AdminMode
default: op
adminmode.punishment.kick:
description: Разрешает кикать игроков через AdminMode
default: op
adminmode.punishment.ban:
description: Разрешает банить игроков через AdminMode
default: op
adminmode.punishment.unban:
description: Разрешает снимать баны через AdminMode
default: op
adminmode.special.belzebool:
description: Разрешает специальные способности BelzeBool
default: false

0
src/main/resources/pocket_dimension.zip Normal file → Executable file
View File